#![allow(non_snake_case)] use crate::plugin::*; use crate::{def_package, Position, RhaiError, RhaiResultOf, ERR, INT}; #[cfg(feature = "no_std")] use std::prelude::v1::*; #[cfg(feature = "no_std")] #[cfg(not(feature = "no_float"))] use num_traits::Float; #[inline] pub fn make_err(msg: impl Into) -> RhaiError { ERR::ErrorArithmetic(msg.into(), Position::NONE).into() } macro_rules! gen_arithmetic_functions { ($root:ident => $($arg_type:ident),+) => { pub mod $root { $(pub mod $arg_type { use super::super::*; #[export_module] pub mod functions { #[rhai_fn(name = "+", return_raw)] pub fn add(x: $arg_type, y: $arg_type) -> RhaiResultOf<$arg_type> { if cfg!(not(feature = "unchecked")) { x.checked_add(y).ok_or_else(|| make_err(format!("Addition overflow: {} + {}", x, y))) } else { Ok(x + y) } } #[rhai_fn(name = "-", return_raw)] pub fn subtract(x: $arg_type, y: $arg_type) -> RhaiResultOf<$arg_type> { if cfg!(not(feature = "unchecked")) { x.checked_sub(y).ok_or_else(|| make_err(format!("Subtraction overflow: {} - {}", x, y))) } else { Ok(x - y) } } #[rhai_fn(name = "*", return_raw)] pub fn multiply(x: $arg_type, y: $arg_type) -> RhaiResultOf<$arg_type> { if cfg!(not(feature = "unchecked")) { x.checked_mul(y).ok_or_else(|| make_err(format!("Multiplication overflow: {} * {}", x, y))) } else { Ok(x * y) } } #[rhai_fn(name = "/", return_raw)] pub fn divide(x: $arg_type, y: $arg_type) -> RhaiResultOf<$arg_type> { if cfg!(not(feature = "unchecked")) { // Detect division by zero if y == 0 { Err(make_err(format!("Division by zero: {} / {}", x, y))) } else { x.checked_div(y).ok_or_else(|| make_err(format!("Division overflow: {} / {}", x, y))) } } else { Ok(x / y) } } #[rhai_fn(name = "%", return_raw)] pub fn modulo(x: $arg_type, y: $arg_type) -> RhaiResultOf<$arg_type> { if cfg!(not(feature = "unchecked")) { x.checked_rem(y).ok_or_else(|| make_err(format!("Modulo division by zero or overflow: {} % {}", x, y))) } else { Ok(x % y) } } #[rhai_fn(name = "**", return_raw)] pub fn power(x: $arg_type, y: INT) -> RhaiResultOf<$arg_type> { if cfg!(not(feature = "unchecked")) { if cfg!(not(feature = "only_i32")) && y > (u32::MAX as INT) { Err(make_err(format!("Integer raised to too large an index: {} ~ {}", x, y))) } else if y < 0 { Err(make_err(format!("Integer raised to a negative index: {} ~ {}", x, y))) } else { x.checked_pow(y as u32).ok_or_else(|| make_err(format!("Exponential overflow: {} ~ {}", x, y))) } } else { Ok(x.pow(y as u32)) } } #[rhai_fn(name = "<<", return_raw)] pub fn shift_left(x: $arg_type, y: INT) -> RhaiResultOf<$arg_type> { if cfg!(not(feature = "unchecked")) { if cfg!(not(feature = "only_i32")) && y > (u32::MAX as INT) { Err(make_err(format!("Left-shift by too many bits: {} << {}", x, y))) } else if y < 0 { Err(make_err(format!("Left-shift by a negative number: {} << {}", x, y))) } else { x.checked_shl(y as u32).ok_or_else(|| make_err(format!("Left-shift by too many bits: {} << {}", x, y))) } } else { Ok(x << y) } } #[rhai_fn(name = ">>", return_raw)] pub fn shift_right(x: $arg_type, y: INT) -> RhaiResultOf<$arg_type> { if cfg!(not(feature = "unchecked")) { if cfg!(not(feature = "only_i32")) && y > (u32::MAX as INT) { Err(make_err(format!("Right-shift by too many bits: {} >> {}", x, y))) } else if y < 0 { Err(make_err(format!("Right-shift by a negative number: {} >> {}", x, y))) } else { x.checked_shr(y as u32).ok_or_else(|| make_err(format!("Right-shift by too many bits: {} >> {}", x, y))) } } else { Ok(x >> y) } } #[rhai_fn(name = "&")] pub fn binary_and(x: $arg_type, y: $arg_type) -> $arg_type { x & y } #[rhai_fn(name = "|")] pub fn binary_or(x: $arg_type, y: $arg_type) -> $arg_type { x | y } #[rhai_fn(name = "^")] pub fn binary_xor(x: $arg_type, y: $arg_type) -> $arg_type { x ^ y } /// Return true if the number is zero. #[rhai_fn(get = "is_zero", name = "is_zero")] pub fn is_zero(x: $arg_type) -> bool { x == 0 } /// Return true if the number is odd. #[rhai_fn(get = "is_odd", name = "is_odd")] pub fn is_odd(x: $arg_type) -> bool { x % 2 != 0 } /// Return true if the number is even. #[rhai_fn(get = "is_even", name = "is_even")] pub fn is_even(x: $arg_type) -> bool { x % 2 == 0 } } })* } } } macro_rules! gen_signed_functions { ($root:ident => $($arg_type:ident),+) => { pub mod $root { $(pub mod $arg_type { use super::super::*; #[export_module] pub mod functions { #[rhai_fn(name = "-", return_raw)] pub fn neg(x: $arg_type) -> RhaiResultOf<$arg_type> { if cfg!(not(feature = "unchecked")) { x.checked_neg().ok_or_else(|| make_err(format!("Negation overflow: -{}", x))) } else { Ok(-x) } } #[rhai_fn(name = "+")] pub fn plus(x: $arg_type) -> $arg_type { x } /// Return the absolute value of the number. #[rhai_fn(return_raw)] pub fn abs(x: $arg_type) -> RhaiResultOf<$arg_type> { if cfg!(not(feature = "unchecked")) { x.checked_abs().ok_or_else(|| make_err(format!("Negation overflow: -{}", x))) } else { Ok(x.abs()) } } /// Return the sign (as an integer) of the number according to the following: /// /// * `0` if the number is zero /// * `1` if the number is positive /// * `-1` if the number is negative pub fn sign(x: $arg_type) -> INT { x.signum() as INT } } })* } } } macro_rules! reg_functions { ($mod_name:ident += $root:ident ; $($arg_type:ident),+ ) => { $( combine_with_exported_module!($mod_name, "arithmetic", $root::$arg_type::functions); )* } } def_package! { /// Basic arithmetic package. pub ArithmeticPackage(lib) { lib.standard = true; combine_with_exported_module!(lib, "int", int_functions); reg_functions!(lib += signed_basic; INT); #[cfg(not(feature = "only_i32"))] #[cfg(not(feature = "only_i64"))] { reg_functions!(lib += arith_numbers; i8, u8, i16, u16, i32, u32, u64); reg_functions!(lib += signed_numbers; i8, i16, i32); #[cfg(not(target_family = "wasm"))] { reg_functions!(lib += arith_num_128; i128, u128); reg_functions!(lib += signed_num_128; i128); } } // Basic arithmetic for floating-point #[cfg(not(feature = "no_float"))] { combine_with_exported_module!(lib, "f32", f32_functions); combine_with_exported_module!(lib, "f64", f64_functions); } // Decimal functions #[cfg(feature = "decimal")] combine_with_exported_module!(lib, "decimal", decimal_functions); } } #[export_module] mod int_functions { /// Return true if the number is zero. #[rhai_fn(get = "is_zero", name = "is_zero")] pub fn is_zero(x: INT) -> bool { x == 0 } /// Return true if the number is odd. #[rhai_fn(get = "is_odd", name = "is_odd")] pub fn is_odd(x: INT) -> bool { x % 2 != 0 } /// Return true if the number is even. #[rhai_fn(get = "is_even", name = "is_even")] pub fn is_even(x: INT) -> bool { x % 2 == 0 } } gen_arithmetic_functions!(arith_basic => INT); #[cfg(not(feature = "only_i32"))] #[cfg(not(feature = "only_i64"))] gen_arithmetic_functions!(arith_numbers => i8, u8, i16, u16, i32, u32, u64); #[cfg(not(feature = "only_i32"))] #[cfg(not(feature = "only_i64"))] #[cfg(not(target_family = "wasm"))] gen_arithmetic_functions!(arith_num_128 => i128, u128); gen_signed_functions!(signed_basic => INT); #[cfg(not(feature = "only_i32"))] #[cfg(not(feature = "only_i64"))] gen_signed_functions!(signed_numbers => i8, i16, i32); #[cfg(not(feature = "only_i32"))] #[cfg(not(feature = "only_i64"))] #[cfg(not(target_family = "wasm"))] gen_signed_functions!(signed_num_128 => i128); #[cfg(not(feature = "no_float"))] #[export_module] mod f32_functions { #[cfg(not(feature = "f32_float"))] pub mod basic_arithmetic { #[rhai_fn(name = "+")] pub fn add(x: f32, y: f32) -> f32 { x + y } #[rhai_fn(name = "-")] pub fn subtract(x: f32, y: f32) -> f32 { x - y } #[rhai_fn(name = "*")] pub fn multiply(x: f32, y: f32) -> f32 { x * y } #[rhai_fn(name = "/")] pub fn divide(x: f32, y: f32) -> f32 { x / y } #[rhai_fn(name = "%")] pub fn modulo(x: f32, y: f32) -> f32 { x % y } #[rhai_fn(name = "**")] pub fn pow_f_f(x: f32, y: f32) -> f32 { x.powf(y) } #[rhai_fn(name = "+")] pub fn add_if(x: INT, y: f32) -> f32 { (x as f32) + (y as f32) } #[rhai_fn(name = "+")] pub fn add_fi(x: f32, y: INT) -> f32 { (x as f32) + (y as f32) } #[rhai_fn(name = "-")] pub fn subtract_if(x: INT, y: f32) -> f32 { (x as f32) - (y as f32) } #[rhai_fn(name = "-")] pub fn subtract_fi(x: f32, y: INT) -> f32 { (x as f32) - (y as f32) } #[rhai_fn(name = "*")] pub fn multiply_if(x: INT, y: f32) -> f32 { (x as f32) * (y as f32) } #[rhai_fn(name = "*")] pub fn multiply_fi(x: f32, y: INT) -> f32 { (x as f32) * (y as f32) } #[rhai_fn(name = "/")] pub fn divide_if(x: INT, y: f32) -> f32 { (x as f32) / (y as f32) } #[rhai_fn(name = "/")] pub fn divide_fi(x: f32, y: INT) -> f32 { (x as f32) / (y as f32) } #[rhai_fn(name = "%")] pub fn modulo_if(x: INT, y: f32) -> f32 { (x as f32) % (y as f32) } #[rhai_fn(name = "%")] pub fn modulo_fi(x: f32, y: INT) -> f32 { (x as f32) % (y as f32) } } #[rhai_fn(name = "-")] pub fn neg(x: f32) -> f32 { -x } #[rhai_fn(name = "+")] pub fn plus(x: f32) -> f32 { x } /// Return the absolute value of the floating-point number. pub fn abs(x: f32) -> f32 { x.abs() } /// Return the sign (as an integer) of the floating-point number according to the following: /// /// * `0` if the number is zero /// * `1` if the number is positive /// * `-1` if the number is negative #[rhai_fn(return_raw)] pub fn sign(x: f32) -> RhaiResultOf { match x.signum() { _ if x == 0.0 => Ok(0), x if x.is_nan() => Err(make_err("Sign of NaN is undefined")), x => Ok(x as INT), } } /// Return true if the floating-point number is zero. #[rhai_fn(get = "is_zero", name = "is_zero")] pub fn is_zero(x: f32) -> bool { x == 0.0 } #[rhai_fn(name = "**", return_raw)] pub fn pow_f_i(x: f32, y: INT) -> RhaiResultOf { if cfg!(not(feature = "unchecked")) && y > (i32::MAX as INT) { Err(make_err(format!( "Number raised to too large an index: {} ~ {}", x, y ))) } else { Ok(x.powi(y as i32)) } } } #[cfg(not(feature = "no_float"))] #[export_module] mod f64_functions { #[cfg(feature = "f32_float")] pub mod basic_arithmetic { #[rhai_fn(name = "+")] pub fn add(x: f64, y: f64) -> f64 { x + y } #[rhai_fn(name = "-")] pub fn subtract(x: f64, y: f64) -> f64 { x - y } #[rhai_fn(name = "*")] pub fn multiply(x: f64, y: f64) -> f64 { x * y } #[rhai_fn(name = "/")] pub fn divide(x: f64, y: f64) -> f64 { x / y } #[rhai_fn(name = "%")] pub fn modulo(x: f64, y: f64) -> f64 { x % y } #[rhai_fn(name = "**")] pub fn pow_f_f(x: f64, y: f64) -> f64 { x.powf(y) } #[rhai_fn(name = "+")] pub fn add_if(x: INT, y: f64) -> f64 { (x as f64) + (y as f64) } #[rhai_fn(name = "+")] pub fn add_fi(x: f64, y: INT) -> f64 { (x as f64) + (y as f64) } #[rhai_fn(name = "-")] pub fn subtract_if(x: INT, y: f64) -> f64 { (x as f64) - (y as f64) } #[rhai_fn(name = "-")] pub fn subtract_fi(x: f64, y: INT) -> f64 { (x as f64) - (y as f64) } #[rhai_fn(name = "*")] pub fn multiply_if(x: INT, y: f64) -> f64 { (x as f64) * (y as f64) } #[rhai_fn(name = "*")] pub fn multiply_fi(x: f64, y: INT) -> f64 { (x as f64) * (y as f64) } #[rhai_fn(name = "/")] pub fn divide_if(x: INT, y: f64) -> f64 { (x as f64) / (y as f64) } #[rhai_fn(name = "/")] pub fn divide_fi(x: f64, y: INT) -> f64 { (x as f64) / (y as f64) } #[rhai_fn(name = "%")] pub fn modulo_if(x: INT, y: f64) -> f64 { (x as f64) % (y as f64) } #[rhai_fn(name = "%")] pub fn modulo_fi(x: f64, y: INT) -> f64 { (x as f64) % (y as f64) } } #[rhai_fn(name = "-")] pub fn neg(x: f64) -> f64 { -x } #[rhai_fn(name = "+")] pub fn plus(x: f64) -> f64 { x } /// Return the absolute value of the floating-point number. pub fn abs(x: f64) -> f64 { x.abs() } /// Return the sign (as an integer) of the floating-point number according to the following: /// /// * `0` if the number is zero /// * `1` if the number is positive /// * `-1` if the number is negative #[rhai_fn(return_raw)] pub fn sign(x: f64) -> RhaiResultOf { match x.signum() { _ if x == 0.0 => Ok(0), x if x.is_nan() => Err(make_err("Sign of NaN is undefined")), x => Ok(x as INT), } } /// Return true if the floating-point number is zero. #[rhai_fn(get = "is_zero", name = "is_zero")] pub fn is_zero(x: f64) -> bool { x == 0.0 } } #[cfg(feature = "decimal")] #[export_module] pub mod decimal_functions { use num_traits::Pow; use rust_decimal::{prelude::Zero, Decimal, MathematicalOps}; #[rhai_fn(skip, return_raw)] pub fn add(x: Decimal, y: Decimal) -> RhaiResultOf { if cfg!(not(feature = "unchecked")) { x.checked_add(y) .ok_or_else(|| make_err(format!("Addition overflow: {} + {}", x, y))) } else { Ok(x + y) } } #[rhai_fn(skip, return_raw)] pub fn subtract(x: Decimal, y: Decimal) -> RhaiResultOf { if cfg!(not(feature = "unchecked")) { x.checked_sub(y) .ok_or_else(|| make_err(format!("Subtraction overflow: {} - {}", x, y))) } else { Ok(x - y) } } #[rhai_fn(skip, return_raw)] pub fn multiply(x: Decimal, y: Decimal) -> RhaiResultOf { if cfg!(not(feature = "unchecked")) { x.checked_mul(y) .ok_or_else(|| make_err(format!("Multiplication overflow: {} * {}", x, y))) } else { Ok(x * y) } } #[rhai_fn(skip, return_raw)] pub fn divide(x: Decimal, y: Decimal) -> RhaiResultOf { if cfg!(not(feature = "unchecked")) { // Detect division by zero if y == Decimal::zero() { Err(make_err(format!("Division by zero: {} / {}", x, y))) } else { x.checked_div(y) .ok_or_else(|| make_err(format!("Division overflow: {} / {}", x, y))) } } else { Ok(x / y) } } #[rhai_fn(skip, return_raw)] pub fn modulo(x: Decimal, y: Decimal) -> RhaiResultOf { if cfg!(not(feature = "unchecked")) { x.checked_rem(y).ok_or_else(|| { make_err(format!( "Modulo division by zero or overflow: {} % {}", x, y )) }) } else { Ok(x % y) } } #[rhai_fn(skip, return_raw)] pub fn power(x: Decimal, y: Decimal) -> RhaiResultOf { if cfg!(not(feature = "unchecked")) { x.checked_powd(y) .ok_or_else(|| make_err(format!("Exponential overflow: {} + {}", x, y))) } else { Ok(x.pow(y)) } } #[rhai_fn(name = "-")] pub fn neg(x: Decimal) -> Decimal { -x } #[rhai_fn(name = "+")] pub fn plus(x: Decimal) -> Decimal { x } /// Return the absolute value of the decimal number. pub fn abs(x: Decimal) -> Decimal { x.abs() } /// Return the sign (as an integer) of the decimal number according to the following: /// /// * `0` if the number is zero /// * `1` if the number is positive /// * `-1` if the number is negative pub fn sign(x: Decimal) -> INT { if x == Decimal::zero() { 0 } else if x.is_sign_negative() { -1 } else { 1 } } /// Return true if the decimal number is zero. #[rhai_fn(get = "is_zero", name = "is_zero")] pub fn is_zero(x: Decimal) -> bool { x.is_zero() } }