diff --git a/RELEASES.md b/RELEASES.md index c647dcd0..4a999e76 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -13,7 +13,12 @@ Bug fixes Breaking changes ---------------- +* In order to be consistent with other scripting languages: + * the power/exponentiation operator is changed from `~` to `**`; `~` is now a reserved symbol + * the power/exponentiation operator now binds to the right + * trigonometry functions now take radians and return radians instead of degrees * `Dynamic::into_shared` is no longer available under `no_closure`. It used to panic. +* `Token::is_operator` is renamed to `Token::is_symbol`. Enhancements ------------ @@ -55,6 +60,7 @@ Enhancements * `ahash` is used to hash function call parameters. This should yield speed improvements. * `Dynamic` and `ImmutableString` now implement `serde::Serialize` and `serde::Deserialize`. * `NativeCallContext` has a new field containing the name of the function called, useful when the same Rust function is registered under multiple names in Rhai. +* New functions `PI()` and `E()` to return mathematical constants, and `to_radians` and `to_degrees` to convert between radians and degrees. Version 0.19.10 diff --git a/src/engine_settings.rs b/src/engine_settings.rs index 7d36b5da..84b57184 100644 --- a/src/engine_settings.rs +++ b/src/engine_settings.rs @@ -288,8 +288,8 @@ impl Engine { return Err(format!("'{}' is a reserved keyword", keyword).into()); } } - // Active standard operators cannot be made custom - Some(token) if token.is_operator() => { + // Active standard symbols cannot be made custom + Some(token) if token.is_symbol() => { if !self.disabled_symbols.contains(token.syntax().as_ref()) { return Err(format!("'{}' is a reserved operator", keyword).into()); } diff --git a/src/fn_call.rs b/src/fn_call.rs index ffe88602..3e8a1070 100644 --- a/src/fn_call.rs +++ b/src/fn_call.rs @@ -1367,7 +1367,7 @@ pub fn run_builtin_binary_op( "*" => return multiply(x, y).map(Some), "/" => return divide(x, y).map(Some), "%" => return modulo(x, y).map(Some), - "~" => return power(x, y).map(Some), + "**" => return power(x, y).map(Some), ">>" => return shift_right(x, y).map(Some), "<<" => return shift_left(x, y).map(Some), _ => (), @@ -1379,7 +1379,7 @@ pub fn run_builtin_binary_op( "*" => return Ok(Some((x * y).into())), "/" => return Ok(Some((x / y).into())), "%" => return Ok(Some((x % y).into())), - "~" => return Ok(Some(x.pow(y as u32).into())), + "**" => return Ok(Some(x.pow(y as u32).into())), ">>" => return Ok(Some((x >> y).into())), "<<" => return Ok(Some((x << y).into())), _ => (), @@ -1457,7 +1457,7 @@ pub fn run_builtin_binary_op( "*" => return Ok(Some((x * y).into())), "/" => return Ok(Some((x / y).into())), "%" => return Ok(Some((x % y).into())), - "~" => return Ok(Some(x.powf(y).into())), + "**" => return Ok(Some(x.powf(y).into())), "==" => return Ok(Some((x == y).into())), "!=" => return Ok(Some((x != y).into())), ">" => return Ok(Some((x > y).into())), diff --git a/src/packages/arithmetic.rs b/src/packages/arithmetic.rs index 6ed344e3..dbb66bb7 100644 --- a/src/packages/arithmetic.rs +++ b/src/packages/arithmetic.rs @@ -68,7 +68,7 @@ macro_rules! gen_arithmetic_functions { Ok(Dynamic::from(x % y)) } } - #[rhai_fn(name = "~", return_raw)] + #[rhai_fn(name = "**", return_raw)] pub fn power(x: INT, y: INT) -> Result> { if cfg!(not(feature = "unchecked")) { if cfg!(not(feature = "only_i32")) && y > (u32::MAX as INT) { @@ -246,7 +246,7 @@ mod f32_functions { pub fn modulo(x: f32, y: f32) -> f32 { x % y } - #[rhai_fn(name = "~", return_raw)] + #[rhai_fn(name = "**", return_raw)] pub fn pow_f_f(x: f32, y: f32) -> Result> { Ok(Dynamic::from(x.powf(y))) } @@ -271,7 +271,7 @@ mod f32_functions { 1 } } - #[rhai_fn(name = "~", return_raw)] + #[rhai_fn(name = "**", return_raw)] pub fn pow_f_i(x: f32, y: INT) -> Result> { if cfg!(not(feature = "unchecked")) && y > (i32::MAX as INT) { Err(make_err(format!( @@ -309,7 +309,7 @@ mod f64_functions { pub fn modulo(x: f64, y: f64) -> f64 { x % y } - #[rhai_fn(name = "~", return_raw)] + #[rhai_fn(name = "**", return_raw)] pub fn pow_f_f(x: f64, y: f64) -> Result> { Ok(Dynamic::from(x.powf(y))) } @@ -334,7 +334,7 @@ mod f64_functions { 1 } } - #[rhai_fn(name = "~", return_raw)] + #[rhai_fn(name = "**", return_raw)] pub fn pow_f_i(x: FLOAT, y: INT) -> Result> { if cfg!(not(feature = "unchecked")) && y > (i32::MAX as INT) { Err(make_err(format!( diff --git a/src/packages/math_basic.rs b/src/packages/math_basic.rs index 1199d4cf..bc7f87c0 100644 --- a/src/packages/math_basic.rs +++ b/src/packages/math_basic.rs @@ -112,40 +112,40 @@ mod trig_functions { use crate::FLOAT; pub fn sin(x: FLOAT) -> FLOAT { - x.to_radians().sin() + x.sin() } pub fn cos(x: FLOAT) -> FLOAT { - x.to_radians().cos() + x.cos() } pub fn tan(x: FLOAT) -> FLOAT { - x.to_radians().tan() + x.tan() } pub fn sinh(x: FLOAT) -> FLOAT { - x.to_radians().sinh() + x.sinh() } pub fn cosh(x: FLOAT) -> FLOAT { - x.to_radians().cosh() + x.cosh() } pub fn tanh(x: FLOAT) -> FLOAT { - x.to_radians().tanh() + x.tanh() } pub fn asin(x: FLOAT) -> FLOAT { - x.asin().to_degrees() + x.asin() } pub fn acos(x: FLOAT) -> FLOAT { - x.acos().to_degrees() + x.acos() } pub fn atan(x: FLOAT) -> FLOAT { - x.atan().to_degrees() + x.atan() } pub fn asinh(x: FLOAT) -> FLOAT { - x.asinh().to_degrees() + x.asinh() } pub fn acosh(x: FLOAT) -> FLOAT { - x.acosh().to_degrees() + x.acosh() } pub fn atanh(x: FLOAT) -> FLOAT { - x.atanh().to_degrees() + x.atanh() } } @@ -154,6 +154,26 @@ mod trig_functions { mod float_functions { use crate::FLOAT; + #[rhai_fn(name = "E")] + pub fn e() -> FLOAT { + #[cfg(not(feature = "f32_float"))] + return crate::stdlib::f64::consts::E; + #[cfg(feature = "f32_float")] + return crate::stdlib::f32::consts::E; + } + #[rhai_fn(name = "PI")] + pub fn pi() -> FLOAT { + #[cfg(not(feature = "f32_float"))] + return crate::stdlib::f64::consts::PI; + #[cfg(feature = "f32_float")] + return crate::stdlib::f32::consts::PI; + } + pub fn to_radians(x: FLOAT) -> FLOAT { + x.to_radians() + } + pub fn to_degrees(x: FLOAT) -> FLOAT { + x.to_degrees() + } pub fn sqrt(x: FLOAT) -> FLOAT { x.sqrt() } diff --git a/src/token.rs b/src/token.rs index 89da190c..64ed7ab2 100644 --- a/src/token.rs +++ b/src/token.rs @@ -194,7 +194,7 @@ pub enum Token { Divide, /// `%` Modulo, - /// `~` + /// `**` PowerOf, /// `<<` LeftShift, @@ -305,7 +305,7 @@ pub enum Token { XOrAssign, /// `%=` ModuloAssign, - /// `~=` + /// `**=` PowerOfAssign, /// `private` /// @@ -422,8 +422,8 @@ impl Token { XOr => "^", Modulo => "%", ModuloAssign => "%=", - PowerOf => "~", - PowerOfAssign => "~=", + PowerOf => "**", + PowerOfAssign => "**=", #[cfg(not(feature = "no_function"))] Fn => "fn", @@ -511,8 +511,8 @@ impl Token { "^" => XOr, "%" => Modulo, "%=" => ModuloAssign, - "~" => PowerOf, - "~=" => PowerOfAssign, + "**" => PowerOf, + "**=" => PowerOfAssign, #[cfg(not(feature = "no_function"))] "fn" => Fn, @@ -532,7 +532,7 @@ impl Token { #[cfg(feature = "no_module")] "import" | "export" | "as" => Reserved(syntax.into()), - "===" | "!==" | "->" | "<-" | ":=" | "**" | "::<" | "(*" | "*)" | "#" | "public" + "===" | "!==" | "->" | "<-" | ":=" | "~" | "::<" | "(*" | "*)" | "#" | "public" | "new" | "use" | "module" | "package" | "var" | "static" | "begin" | "end" | "shared" | "with" | "each" | "then" | "goto" | "unless" | "exit" | "match" | "case" | "default" | "void" | "null" | "nil" | "spawn" | "thread" | "go" | "sync" @@ -599,6 +599,8 @@ impl Token { DivideAssign | LeftShiftAssign | RightShiftAssign | + PowerOf | + PowerOfAssign | AndAssign | OrAssign | XOrAssign | @@ -609,9 +611,7 @@ impl Token { ModuloAssign | Return | Throw | - PowerOf | - In | - PowerOfAssign => true, + In => true, _ => false, } @@ -623,9 +623,9 @@ impl Token { match self { // Assignments are not considered expressions - set to zero - Equals | PlusAssign | MinusAssign | MultiplyAssign | DivideAssign | LeftShiftAssign - | RightShiftAssign | AndAssign | OrAssign | XOrAssign | ModuloAssign - | PowerOfAssign => 0, + Equals | PlusAssign | MinusAssign | MultiplyAssign | DivideAssign | PowerOfAssign + | LeftShiftAssign | RightShiftAssign | AndAssign | OrAssign | XOrAssign + | ModuloAssign => 0, Or | XOr | Pipe => 30, @@ -657,19 +657,22 @@ impl Token { match self { // Assignments bind to the right - Equals | PlusAssign | MinusAssign | MultiplyAssign | DivideAssign | LeftShiftAssign - | RightShiftAssign | AndAssign | OrAssign | XOrAssign | ModuloAssign - | PowerOfAssign => true, + Equals | PlusAssign | MinusAssign | MultiplyAssign | DivideAssign | PowerOfAssign + | LeftShiftAssign | RightShiftAssign | AndAssign | OrAssign | XOrAssign + | ModuloAssign => true, // Property access binds to the right Period => true, + // Exponentiation binds to the right + PowerOf => true, + _ => false, } } - /// Is this token an operator? - pub fn is_operator(&self) -> bool { + /// Is this token a standard symbol used in the language? + pub fn is_symbol(&self) -> bool { use Token::*; match self { @@ -1284,10 +1287,6 @@ fn get_next_token_inner( ('-', _) if !state.non_unary => return Some((Token::UnaryMinus, start_pos)), ('-', _) => return Some((Token::Minus, start_pos)), - ('*', '*') => { - eat_next(stream, pos); - return Some((Token::Reserved("**".into()), start_pos)); - } ('*', ')') => { eat_next(stream, pos); return Some((Token::Reserved("*)".into()), start_pos)); @@ -1296,6 +1295,19 @@ fn get_next_token_inner( eat_next(stream, pos); return Some((Token::MultiplyAssign, start_pos)); } + ('*', '*') => { + eat_next(stream, pos); + + return Some(( + if stream.peek_next() == Some('=') { + eat_next(stream, pos); + Token::PowerOfAssign + } else { + Token::PowerOf + }, + start_pos, + )); + } ('*', _) => return Some((Token::Multiply, start_pos)), // Comments @@ -1490,18 +1502,14 @@ fn get_next_token_inner( } ('^', _) => return Some((Token::XOr, start_pos)), + ('~', _) => return Some((Token::Reserved("~".into()), start_pos)), + ('%', '=') => { eat_next(stream, pos); return Some((Token::ModuloAssign, start_pos)); } ('%', _) => return Some((Token::Modulo, start_pos)), - ('~', '=') => { - eat_next(stream, pos); - return Some((Token::PowerOfAssign, start_pos)); - } - ('~', _) => return Some((Token::PowerOf, start_pos)), - ('@', _) => return Some((Token::Reserved("@".into()), start_pos)), ('$', _) => return Some((Token::Reserved("$".into()), start_pos)), diff --git a/tests/math.rs b/tests/math.rs index fa398c83..25bf1228 100644 --- a/tests/math.rs +++ b/tests/math.rs @@ -14,10 +14,10 @@ fn test_math() -> Result<(), Box> { assert_eq!(engine.eval::("3 % 2")?, 1); #[cfg(not(feature = "no_float"))] - assert!((engine.eval::("sin(30.0)")? - 0.5).abs() < 0.001); + assert!((engine.eval::("sin(PI()/6.0)")? - 0.5).abs() < 0.001); #[cfg(not(feature = "no_float"))] - assert!(engine.eval::("cos(90.0)")?.abs() < 0.001); + assert!(engine.eval::("cos(PI()/2.0)")?.abs() < 0.001); #[cfg(not(feature = "only_i32"))] assert_eq!( diff --git a/tests/power_of.rs b/tests/power_of.rs index 2843c37b..fb030645 100644 --- a/tests/power_of.rs +++ b/tests/power_of.rs @@ -10,18 +10,20 @@ const EPSILON: FLOAT = 0.000_001; fn test_power_of() -> Result<(), Box> { let engine = Engine::new(); - assert_eq!(engine.eval::("2 ~ 3")?, 8); - assert_eq!(engine.eval::("(-2 ~ 3)")?, -8); + assert_eq!(engine.eval::("2 ** 3")?, 8); + assert_eq!(engine.eval::("(-2 ** 3)")?, -8); + assert_eq!(engine.eval::("2 ** 3 ** 2")?, 512); #[cfg(not(feature = "no_float"))] { assert!( - (engine.eval::("2.2 ~ 3.3")? - 13.489_468_760_533_386 as FLOAT).abs() <= EPSILON + (engine.eval::("2.2 ** 3.3")? - 13.489_468_760_533_386 as FLOAT).abs() + <= EPSILON ); - assert!((engine.eval::("2.0~-2.0")? - 0.25 as FLOAT).abs() < EPSILON); - assert!((engine.eval::("(-2.0~-2.0)")? - 0.25 as FLOAT).abs() < EPSILON); - assert!((engine.eval::("(-2.0~-2)")? - 0.25 as FLOAT).abs() < EPSILON); - assert_eq!(engine.eval::("4~3")?, 64); + assert!((engine.eval::("2.0**-2.0")? - 0.25 as FLOAT).abs() < EPSILON); + assert!((engine.eval::("(-2.0**-2.0)")? - 0.25 as FLOAT).abs() < EPSILON); + assert!((engine.eval::("(-2.0**-2)")? - 0.25 as FLOAT).abs() < EPSILON); + assert_eq!(engine.eval::("4**3")?, 64); } Ok(()) @@ -31,26 +33,26 @@ fn test_power_of() -> Result<(), Box> { fn test_power_of_equals() -> Result<(), Box> { let engine = Engine::new(); - assert_eq!(engine.eval::("let x = 2; x ~= 3; x")?, 8); - assert_eq!(engine.eval::("let x = -2; x ~= 3; x")?, -8); + assert_eq!(engine.eval::("let x = 2; x **= 3; x")?, 8); + assert_eq!(engine.eval::("let x = -2; x **= 3; x")?, -8); #[cfg(not(feature = "no_float"))] { assert!( - (engine.eval::("let x = 2.2; x ~= 3.3; x")? - 13.489_468_760_533_386 as FLOAT) + (engine.eval::("let x = 2.2; x **= 3.3; x")? - 13.489_468_760_533_386 as FLOAT) .abs() <= EPSILON ); assert!( - (engine.eval::("let x = 2.0; x ~= -2.0; x")? - 0.25 as FLOAT).abs() < EPSILON + (engine.eval::("let x = 2.0; x **= -2.0; x")? - 0.25 as FLOAT).abs() < EPSILON ); assert!( - (engine.eval::("let x = -2.0; x ~= -2.0; x")? - 0.25 as FLOAT).abs() < EPSILON + (engine.eval::("let x = -2.0; x **= -2.0; x")? - 0.25 as FLOAT).abs() < EPSILON ); assert!( - (engine.eval::("let x = -2.0; x ~= -2; x")? - 0.25 as FLOAT).abs() < EPSILON + (engine.eval::("let x = -2.0; x **= -2; x")? - 0.25 as FLOAT).abs() < EPSILON ); - assert_eq!(engine.eval::("let x =4; x ~= 3; x")?, 64); + assert_eq!(engine.eval::("let x =4; x **= 3; x")?, 64); } Ok(())