From 2955a4ab6413005e2bb2e1552665e30b1586dd21 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Mon, 21 Dec 2020 17:39:37 +0800 Subject: [PATCH] Fix unary operators binding. --- RELEASES.md | 2 ++ src/ast.rs | 6 ++++ src/fn_call.rs | 22 ++++++------- src/packages/arithmetic.rs | 12 +++++++ src/parser.rs | 65 ++++++++++++++++++++++++++++++-------- tests/modules.rs | 3 ++ 6 files changed, 85 insertions(+), 25 deletions(-) diff --git a/RELEASES.md b/RELEASES.md index 9f3c3240..c82038ee 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -16,6 +16,8 @@ exports the full list of functions metadata (including those in an `AST`) as a J Bug fixes --------- +* Unary prefix operators `-`, `+` and `!` now bind correctly when applied to an expression. Previously, `-x.len` is parsed as `(-x).len` which is obviously counter-intuitive. +* Indexing of namespace-qualified variables now work properly, such as `path::to::var[x]`. * Constants are no longer propagated by the optimizer if shadowed by a non-constant variable. * Constants passed as the `this` parameter to Rhai functions now throws an error if assigned to. * Generic type parameter of `Engine::register_iterator` is `IntoIterator` instead of `Iterator`. diff --git a/src/ast.rs b/src/ast.rs index 75435825..926cf3a3 100644 --- a/src/ast.rs +++ b/src/ast.rs @@ -1191,12 +1191,16 @@ impl Expr { | Self::Map(_, _) => match token { #[cfg(not(feature = "no_index"))] Token::LeftBracket => true, + #[cfg(not(feature = "no_object"))] + Token::Period => true, _ => false, }, Self::Variable(_) => match token { #[cfg(not(feature = "no_index"))] Token::LeftBracket => true, + #[cfg(not(feature = "no_object"))] + Token::Period => true, Token::LeftParen => true, Token::Bang => true, Token::DoubleColon => true, @@ -1206,6 +1210,8 @@ impl Expr { Self::Property(_) => match token { #[cfg(not(feature = "no_index"))] Token::LeftBracket => true, + #[cfg(not(feature = "no_object"))] + Token::Period => true, Token::LeftParen => true, _ => false, }, diff --git a/src/fn_call.rs b/src/fn_call.rs index 05d20cb4..0adf5d2d 100644 --- a/src/fn_call.rs +++ b/src/fn_call.rs @@ -454,19 +454,18 @@ impl Engine { hash_script: u64, pub_only: bool, ) -> bool { - // NOTE: We skip script functions for global_namespace and packages, and native functions for lib - // First check script-defined functions (hash_script != 0 && lib.iter().any(|&m| m.contains_fn(hash_script, pub_only))) - //|| (hash_fn != 0 && lib.iter().any(|&m| m.contains_fn(hash_fn, pub_only))) + //|| lib.iter().any(|&m| m.contains_fn(hash_fn, pub_only)) // Then check registered functions - //|| self.global_namespace.contains_fn(hash_script, pub_only) + //|| (hash_script != 0 && self.global_namespace.contains_fn(hash_script, pub_only)) || self.global_namespace.contains_fn(hash_fn, false) // Then check packages - || self.packages.contains_fn(hash_script) + || (hash_script != 0 && self.packages.contains_fn(hash_script)) || self.packages.contains_fn(hash_fn) // Then check imported modules - || mods.map(|m| m.contains_fn(hash_script) || m.contains_fn(hash_fn)).unwrap_or(false) + || (hash_script != 0 && mods.map(|m| m.contains_fn(hash_script)).unwrap_or(false)) + || mods.map(|m| m.contains_fn(hash_fn)).unwrap_or(false) } /// Perform an actual function call, native Rust or scripted, taking care of special functions. @@ -678,14 +677,15 @@ impl Engine { } // Evaluate the AST - let mut new_state: State = Default::default(); - new_state.operations = state.operations; + let mut new_state = State { + operations: state.operations, + ..Default::default() + }; - let result = - self.eval_statements_raw(scope, mods, &mut new_state, ast.statements(), lib)?; + let result = self.eval_statements_raw(scope, mods, &mut new_state, ast.statements(), lib); state.operations = new_state.operations; - return Ok(result); + result } /// Call a dot method. diff --git a/src/packages/arithmetic.rs b/src/packages/arithmetic.rs index 9bc13cb8..6ed344e3 100644 --- a/src/packages/arithmetic.rs +++ b/src/packages/arithmetic.rs @@ -143,6 +143,10 @@ macro_rules! gen_signed_functions { Ok(Dynamic::from(-x)) } } + #[rhai_fn(name = "+")] + pub fn plus(x: $arg_type) -> $arg_type { + x + } #[rhai_fn(return_raw)] pub fn abs(x: $arg_type) -> Result> { if cfg!(not(feature = "unchecked")) { @@ -251,6 +255,10 @@ mod f32_functions { pub fn neg(x: f32) -> f32 { -x } + #[rhai_fn(name = "+")] + pub fn plus(x: f32) -> f32 { + -x + } pub fn abs(x: f32) -> f32 { x.abs() } @@ -310,6 +318,10 @@ mod f64_functions { pub fn neg(x: f64) -> f64 { -x } + #[rhai_fn(name = "+")] + pub fn plus(x: f64) -> f64 { + -x + } pub fn abs(x: f64) -> f64 { x.abs() } diff --git a/src/parser.rs b/src/parser.rs index 946f9653..6dfe48c3 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -1105,6 +1105,12 @@ fn parse_primary( (expr, Token::LeftBracket) => { parse_index_chain(input, state, lib, expr, settings.level_up())? } + // Method access + #[cfg(not(feature = "no_object"))] + (expr, Token::Period) => { + let rhs = parse_unary(input, state, lib, settings.level_up())?; + make_dot_expr(state, expr, rhs, token_pos)? + } // Unknown postfix operator (expr, token) => unreachable!( "unknown postfix operator '{}' for {:?}", @@ -1114,20 +1120,25 @@ fn parse_primary( } } + // Cache the hash key for namespace-qualified variables match &mut root_expr { - // Cache the hash key for namespace-qualified variables - Expr::Variable(x) if x.1.is_some() => { - let (_, modules, hash, IdentX { name, .. }) = x.as_mut(); - let namespace = modules.as_mut().unwrap(); - - // Qualifiers + variable name - *hash = calc_script_fn_hash(namespace.iter().map(|v| v.name.as_str()), name, 0); - - #[cfg(not(feature = "no_module"))] - namespace.set_index(state.find_module(&namespace[0].name)); - } - _ => (), + Expr::Variable(x) if x.1.is_some() => Some(x), + Expr::Index(x, _) | Expr::Dot(x, _) => match &mut x.lhs { + Expr::Variable(x) if x.1.is_some() => Some(x), + _ => None, + }, + _ => None, } + .map(|x| { + let (_, modules, hash, IdentX { name, .. }) = x.as_mut(); + let namespace = modules.as_mut().unwrap(); + + // Qualifiers + variable name + *hash = calc_script_fn_hash(namespace.iter().map(|v| v.name.as_str()), name, 0); + + #[cfg(not(feature = "no_module"))] + namespace.set_index(state.find_module(&namespace[0].name)); + }); // Make sure identifiers are valid Ok(root_expr) @@ -1201,8 +1212,33 @@ fn parse_unary( } // +expr Token::UnaryPlus => { - eat_token(input, Token::UnaryPlus); - parse_unary(input, state, lib, settings.level_up()) + let pos = eat_token(input, Token::UnaryPlus); + + match parse_unary(input, state, lib, settings.level_up())? { + expr @ Expr::IntegerConstant(_, _) => Ok(expr), + #[cfg(not(feature = "no_float"))] + expr @ Expr::FloatConstant(_, _) => Ok(expr), + + // Call plus function + expr => { + let op = "+"; + let hash = calc_script_fn_hash(empty(), op, 1); + let mut args = StaticVec::new(); + args.push(expr); + + Ok(Expr::FnCall( + Box::new(FnCallExpr { + name: op.into(), + native_only: true, + namespace: None, + hash, + args, + ..Default::default() + }), + pos, + )) + } + } } // !expr Token::Bang => { @@ -1770,6 +1806,7 @@ fn parse_binary_op( make_in_expr(current_lhs, rhs, pos)? } + // This is needed to parse closure followed by a dot. #[cfg(not(feature = "no_object"))] Token::Period => { let rhs = args.pop().unwrap(); diff --git a/tests/modules.rs b/tests/modules.rs index ef148bc9..656dc728 100644 --- a/tests/modules.rs +++ b/tests/modules.rs @@ -48,6 +48,7 @@ fn test_module_sub_module() -> Result<(), Box> { assert_eq!(engine.eval::("question::MYSTIC_NUMBER")?, 42); assert!(engine.eval::("MYSTIC_NUMBER").is_err()); + assert_eq!(engine.eval::("question::life::universe::answer")?, 41); assert_eq!( engine.eval::("question::life::universe::answer + 1")?, 42 @@ -60,6 +61,8 @@ fn test_module_sub_module() -> Result<(), Box> { .eval::("inc(question::life::universe::answer)") .is_err()); #[cfg(not(feature = "no_object"))] + assert_eq!(engine.eval::("question::MYSTIC_NUMBER.doubled")?, 84); + #[cfg(not(feature = "no_object"))] assert_eq!( engine.eval::("question::life::universe::answer.doubled")?, 82