diff --git a/CHANGELOG.md b/CHANGELOG.md index 3c37b45f..7fd9bb72 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ Breaking changes ---------------- * `Engine::disable_doc_comments` is removed because doc-comments are now placed under the `metadata` feature flag. +* Registering a custom syntax now only requires specifying whether the `Scope` is adjusted (i.e. whether variables are added or removed). There is no need to specify the number of variables added/removed. New features ------------ @@ -16,6 +17,11 @@ New features * A new internal feature `no_smartstring` to turn off `SmartString` for those rare cases that it is needed. * `DynamicReadLock` and `DynamicWriteLoc` are exposed under `internals`. +Enhancements +------------ + +* Registering a custom syntax now only requires specifying whether the `Scope` is adjusted (i.e. whether variables are added or removed). This allows more flexibility for cases where the number of new variables declared depends on internal logic. + Version 0.20.1 ============== diff --git a/src/ast.rs b/src/ast.rs index c0f096c6..060875ca 100644 --- a/src/ast.rs +++ b/src/ast.rs @@ -1357,8 +1357,8 @@ impl Stmt { pub struct CustomExpr { /// List of keywords. pub keywords: StaticVec, - /// Delta number of variables in the scope. - pub scope_delta: isize, + /// Is the current [`Scope`][crate::Scope] modified? + pub scope_changed: bool, /// List of tokens actually parsed. pub tokens: StaticVec, } diff --git a/src/optimize.rs b/src/optimize.rs index 93878e85..25928c1a 100644 --- a/src/optimize.rs +++ b/src/optimize.rs @@ -1024,7 +1024,7 @@ fn optimize_expr(expr: &mut Expr, state: &mut State) { // Custom syntax Expr::Custom(x, _) => { - if x.scope_delta != 0 { + if x.scope_changed { state.propagate_constants = false; } x.keywords.iter_mut().for_each(|expr| optimize_expr(expr, state)); diff --git a/src/parser.rs b/src/parser.rs index aaa9a11e..5cb722bc 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -1848,22 +1848,12 @@ fn parse_custom_syntax( let mut tokens: StaticVec<_> = Default::default(); // Adjust the variables stack - match syntax.scope_delta { - delta if delta > 0 => { - // Add enough empty variable names to the stack. - // Empty variable names act as a barrier so earlier variables will not be matched. - // Variable searches stop at the first empty variable name. - let empty = state.get_identifier(""); - state.stack.resize( - state.stack.len() + delta as usize, - (empty, AccessMode::ReadWrite), - ); - } - delta if delta < 0 && state.stack.len() <= delta.abs() as usize => state.stack.clear(), - delta if delta < 0 => state - .stack - .truncate(state.stack.len() - delta.abs() as usize), - _ => (), + if syntax.scope_changed { + // Add an empty variable name to the stack. + // Empty variable names act as a barrier so earlier variables will not be matched. + // Variable searches stop at the first empty variable name. + let empty = state.get_identifier(""); + state.stack.push((empty, AccessMode::ReadWrite)); } let parse_func = &syntax.parse; @@ -1936,7 +1926,7 @@ fn parse_custom_syntax( Box::new(CustomExpr { keywords, tokens, - scope_delta: syntax.scope_delta, + scope_changed: syntax.scope_changed, }), pos, )) diff --git a/src/syntax.rs b/src/syntax.rs index 309d1c74..34632f9e 100644 --- a/src/syntax.rs +++ b/src/syntax.rs @@ -87,28 +87,33 @@ pub struct CustomSyntax { pub parse: Box, /// Custom syntax implementation function. pub func: Shared, - /// Delta number of variables in the scope. - pub scope_delta: isize, + /// Any variables added/removed in the scope? + pub scope_changed: bool, } impl Engine { /// Register a custom syntax with the [`Engine`]. /// /// * `keywords` holds a slice of strings that define the custom syntax. - /// * `new_vars` is the number of new variables declared by this custom syntax, or the number of variables removed (if negative). + /// * `scope_changed` specifies variables have been added/removed by this custom syntax. /// * `func` is the implementation function. /// - /// # Notes + /// # Caveat - Do not change beyond block scope /// - /// If `new_vars` is positive, then a number of new variables are expected to be pushed into the - /// current [`Scope`][crate::Scope]. + /// If `scope_changed` is `true`, then the current [`Scope`][crate::Scope] is assumed to be + /// modified by this custom syntax. /// - /// If `new_vars` is negative, then it is expected that only the top `new_vars` variables in the - /// current [`Scope`][crate::Scope] will be _popped_. Do not randomly remove variables. + /// Adding new variables and/or removing variables are allowed. Simply modifying the values of + /// variables does NOT count, so `false` should be passed in this case. + /// + /// However, only variables declared within the current _block scope_ should be touched, + /// since they all go away at the end of the block. + /// + /// Variables in parent blocks should be left untouched as they persist beyond the current block. pub fn register_custom_syntax + Into>( &mut self, keywords: &[S], - new_vars: isize, + scope_changed: bool, func: impl Fn(&mut EvalContext, &[Expression]) -> RhaiResult + SendSync + 'static, ) -> Result<&mut Self, ParseError> { let keywords = keywords.as_ref(); @@ -203,7 +208,7 @@ impl Engine { Ok(Some(segments[stream.len()].clone())) } }, - new_vars, + scope_changed, func, ); @@ -215,7 +220,7 @@ impl Engine { /// /// This function is very low level. /// - /// * `new_vars` is the number of new variables declared by this custom syntax, or the number of variables removed (if negative). + /// * `scope_changed` specifies variables have been added/removed by this custom syntax. /// * `parse` is the parsing function. /// * `func` is the implementation function. /// @@ -227,7 +232,7 @@ impl Engine { parse: impl Fn(&[ImmutableString], &str) -> Result, ParseError> + SendSync + 'static, - new_vars: isize, + scope_changed: bool, func: impl Fn(&mut EvalContext, &[Expression]) -> RhaiResult + SendSync + 'static, ) -> &mut Self { self.custom_syntax.insert( @@ -235,7 +240,7 @@ impl Engine { Box::new(CustomSyntax { parse: Box::new(parse), func: (Box::new(func) as Box).into(), - scope_delta: new_vars, + scope_changed, }), ); self diff --git a/tests/syntax.rs b/tests/syntax.rs index 5dd03d9a..2fa1dc0c 100644 --- a/tests/syntax.rs +++ b/tests/syntax.rs @@ -19,15 +19,15 @@ fn test_custom_syntax() -> Result<(), Box> { engine.register_custom_syntax( &[ - "exec", "|", "$ident$", "|", "->", "$block$", "while", "$expr$", + "exec", "[", "$ident$", "]", "->", "$block$", "while", "$expr$", ], - 1, + true, |context, inputs| { let var_name = inputs[0].get_variable_name().unwrap().to_string(); let stmt = inputs.get(1).unwrap(); let condition = inputs.get(2).unwrap(); - context.scope_mut().push(var_name, 0 as INT); + context.scope_mut().push(var_name.clone(), 0 as INT); let mut count: INT = 0; @@ -35,6 +35,10 @@ fn test_custom_syntax() -> Result<(), Box> { context.eval_expression_tree(stmt)?; count += 1; + context + .scope_mut() + .push(format!("{}{}", var_name, count), count); + let stop = !context .eval_expression_tree(condition)? .as_bool() @@ -59,7 +63,7 @@ fn test_custom_syntax() -> Result<(), Box> { engine.eval::( " let x = 0; - let foo = (exec |x| -> { x += 2 } while x < 42) * 10; + let foo = (exec [x] -> { x += 2 } while x < 42) * 10; foo " )?, @@ -69,7 +73,7 @@ fn test_custom_syntax() -> Result<(), Box> { engine.eval::( " let x = 0; - exec |x| -> { x += 1 } while x < 42; + exec [x] -> { x += 1 } while x < 42; x " )?, @@ -78,17 +82,27 @@ fn test_custom_syntax() -> Result<(), Box> { assert_eq!( engine.eval::( " - exec |x| -> { x += 1 } while x < 42; + exec [x] -> { x += 1 } while x < 42; x " )?, 42 ); + assert_eq!( + engine.eval::( + " + let foo = 123; + exec [x] -> { x += 1 } while x < 42; + foo + x + x1 + x2 + x3 + " + )?, + 171 + ); // The first symbol must be an identifier assert_eq!( *engine - .register_custom_syntax(&["!"], 0, |_, _| Ok(Dynamic::UNIT)) + .register_custom_syntax(&["!"], false, |_, _| Ok(Dynamic::UNIT)) .expect_err("should error") .0, ParseErrorType::BadInput(LexError::ImproperSymbol( @@ -121,7 +135,7 @@ fn test_custom_syntax_raw() -> Result<(), Box> { }, _ => unreachable!(), }, - 1, + true, |context, inputs| { context.scope_mut().push("foo", 999 as INT);