From 61638d6566df6ef6fa1394fe6c746f428bde0146 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Wed, 10 Feb 2021 12:41:27 +0800 Subject: [PATCH] Use ** for power/exponentiation. --- RELEASES.md | 6 +++- src/engine_settings.rs | 4 +-- src/fn_call.rs | 6 ++-- src/packages/arithmetic.rs | 10 +++--- src/token.rs | 64 +++++++++++++++++++++----------------- tests/power_of.rs | 30 +++++++++--------- 6 files changed, 67 insertions(+), 53 deletions(-) diff --git a/RELEASES.md b/RELEASES.md index c1121f29..4a999e76 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -13,8 +13,12 @@ Bug fixes Breaking changes ---------------- -* Trigonometry functions now take radians and return radians instead of degrees. +* 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 ------------ 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/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/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(())