From dda7bc7b8515daec815963d6b6d727aeb646d86f Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Wed, 6 Jul 2022 12:56:15 +0800 Subject: [PATCH] Add eval_expression_tree_raw. --- CHANGELOG.md | 1 + src/api/call_fn.rs | 5 ++++ src/api/custom_syntax.rs | 22 ++++++++++++++++ src/eval/eval_context.rs | 56 +++++++++++++++++++++++++++++++++------- src/optimizer.rs | 2 +- src/tokenizer.rs | 2 +- tests/custom_syntax.rs | 28 +++++++++++++++++++- 7 files changed, 104 insertions(+), 12 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 04c06ec3..e14595d1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ Enhancements ------------ * `switch` cases can now include multiple values separated by `|`. +* `EvalContext::eval_expression_tree_raw` and `Expression::eval_with_context_raw` are added to allow for not rewinding the `Scope` at the end of a statements block. Version 1.8.0 diff --git a/src/api/call_fn.rs b/src/api/call_fn.rs index 9ad2965d..8c5a9b22 100644 --- a/src/api/call_fn.rs +++ b/src/api/call_fn.rs @@ -185,6 +185,10 @@ impl Engine { /// /// Not available under `no_function`. /// + /// # WARNING - Unstable API + /// + /// This API is volatile and may change in the future. + /// /// # WARNING - Low Level API /// /// This function is _extremely_ low level. @@ -202,6 +206,7 @@ impl Engine { /// Do not use the arguments after this call. If they are needed afterwards, clone them _before_ /// calling this function. #[cfg(feature = "internals")] + #[deprecated = "This API is NOT deprecated, but it is considered volatile and may change in the future."] #[inline(always)] pub fn call_fn_raw_raw( &self, diff --git a/src/api/custom_syntax.rs b/src/api/custom_syntax.rs index 5f365f2b..f1128604 100644 --- a/src/api/custom_syntax.rs +++ b/src/api/custom_syntax.rs @@ -74,6 +74,28 @@ impl Expression<'_> { pub fn eval_with_context(&self, context: &mut EvalContext) -> RhaiResult { context.eval_expression_tree(self) } + /// Evaluate this [expression tree][Expression] within an [evaluation context][`EvalContext`]. + /// + /// The following option is available: + /// + /// * whether to rewind the [`Scope`] after evaluation if the expression is a [`StmtBlock`][crate::ast::StmtBlock] + /// + /// # WARNING - Unstable API + /// + /// This API is volatile and may change in the future. + /// + /// # WARNING - Low Level API + /// + /// This function is _extremely_ low level. It evaluates an expression from an [`AST`][crate::AST]. + #[inline(always)] + pub fn eval_with_context_raw( + &self, + context: &mut EvalContext, + rewind_scope: bool, + ) -> RhaiResult { + #[allow(deprecated)] + context.eval_expression_tree_raw(self, rewind_scope) + } /// Get the value of this expression if it is a variable name or a string constant. /// /// Returns [`None`] also if the constant is not of the specified type. diff --git a/src/eval/eval_context.rs b/src/eval/eval_context.rs index 11245721..2b0265f1 100644 --- a/src/eval/eval_context.rs +++ b/src/eval/eval_context.rs @@ -150,6 +150,32 @@ impl<'a, 's, 'ps, 'g, 'pg, 'c, 'pc, 't, 'pt> EvalContext<'a, 's, 'ps, 'g, 'pg, ' #[cfg(not(feature = "no_custom_syntax"))] #[inline(always)] pub fn eval_expression_tree(&mut self, expr: &crate::Expression) -> crate::RhaiResult { + #[allow(deprecated)] + self.eval_expression_tree_raw(expr, true) + } + /// Evaluate an [expression tree][crate::Expression] within this [evaluation context][`EvalContext`]. + /// + /// The following option is available: + /// + /// * whether to rewind the [`Scope`] after evaluation if the expression is a [`StmtBlock`][crate::ast::StmtBlock] + /// + /// # WARNING - Unstable API + /// + /// This API is volatile and may change in the future. + /// + /// # WARNING - Low Level API + /// + /// This function is _extremely_ low level. It evaluates an expression from an [`AST`][crate::AST]. + #[cfg(not(feature = "no_custom_syntax"))] + #[deprecated = "This API is NOT deprecated, but it is considered volatile and may change in the future."] + #[inline] + pub fn eval_expression_tree_raw( + &mut self, + expr: &crate::Expression, + rewind_scope: bool, + ) -> crate::RhaiResult { + let expr: &crate::ast::Expr = expr; + let mut new_caches = Caches::new(); let caches = match self.caches.as_mut() { @@ -157,14 +183,26 @@ impl<'a, 's, 'ps, 'g, 'pg, 'c, 'pc, 't, 'pt> EvalContext<'a, 's, 'ps, 'g, 'pg, ' None => &mut new_caches, }; - self.engine.eval_expr( - self.scope, - self.global, - caches, - self.lib, - self.this_ptr, - expr, - self.level, - ) + match expr { + crate::Expr::Stmt(statements) => self.engine.eval_stmt_block( + self.scope, + self.global, + caches, + self.lib, + self.this_ptr, + &statements, + rewind_scope, + self.level, + ), + _ => self.engine.eval_expr( + self.scope, + self.global, + caches, + self.lib, + self.this_ptr, + expr, + self.level, + ), + } } } diff --git a/src/optimizer.rs b/src/optimizer.rs index c1399f10..2a807055 100644 --- a/src/optimizer.rs +++ b/src/optimizer.rs @@ -1236,7 +1236,7 @@ fn optimize_expr(expr: &mut Expr, state: &mut OptimizerState, _chaining: bool) { if x.scope_may_be_changed { state.propagate_constants = false; } - x.inputs.iter_mut().for_each(|expr| optimize_expr(expr, state, false)); + // Do not optimize custom syntax expressions as you won't know how they would be called } // All other expressions - skip diff --git a/src/tokenizer.rs b/src/tokenizer.rs index d7031e4d..fa1fcad9 100644 --- a/src/tokenizer.rs +++ b/src/tokenizer.rs @@ -2336,7 +2336,7 @@ impl<'a> Iterator for TokenIterator<'a> { } // Reserved keyword/symbol Some((Token::Reserved(s), pos)) => (match - (&*s, + (&*s, #[cfg(not(feature = "no_custom_syntax"))] (!self.engine.custom_keywords.is_empty() && self.engine.custom_keywords.contains_key(&*s)), #[cfg(feature = "no_custom_syntax")] diff --git a/tests/custom_syntax.rs b/tests/custom_syntax.rs index fbd24a93..a770acc6 100644 --- a/tests/custom_syntax.rs +++ b/tests/custom_syntax.rs @@ -52,7 +52,13 @@ fn test_custom_syntax() -> Result<(), Box> { break; } - context.eval_expression_tree(stmt)?; + // Do not rewind if the variable is upper-case + if var_name.to_uppercase() == var_name { + context.eval_expression_tree_raw(stmt, false)?; + } else { + context.eval_expression_tree(stmt)?; + } + count += 1; context @@ -125,6 +131,26 @@ fn test_custom_syntax() -> Result<(), Box> { )?, 144 ); + assert_eq!( + engine.eval::( + " + let foo = 123; + exec [x<15] -> { let foo = x; x += 1; } while x < 42; + foo + " + )?, + 123 + ); + assert_eq!( + engine.eval::( + " + let foo = 123; + exec [ABC<15] -> { let foo = ABC; ABC += 1; } while ABC < 42; + foo + " + )?, + 14 + ); // The first symbol must be an identifier assert_eq!(