From 5083df30960fb27fdb2e283e254b9767a51e27cb Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Mon, 8 Nov 2021 22:16:28 +0800 Subject: [PATCH] Propagate constants to functions for Engine::XXX_with_scope calls. --- CHANGELOG.md | 17 +++++------ src/engine_api.rs | 73 +++++++++++++++++++++++++++++++--------------- src/optimize.rs | 18 ++++++------ tests/optimizer.rs | 23 ++++++++++++++- 4 files changed, 87 insertions(+), 44 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a833a3b3..7cd583c4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,11 @@ Rhai Release Notes Version 1.2.0 ============= +Bug fixes +--------- + +* `Engine::XXX_with_scope` API's now properly propagate constants within the provided scope also to _functions_ in the script. + New features ------------ @@ -28,16 +33,6 @@ Deprecated API's * `From` for `Result>` is deprecated so it will no longer be possible to do `EvalAltResult::ErrorXXXXX.into()` to convert to a `Result`; instead, `Err(EvalAltResult:ErrorXXXXX.into())` must be used. Code is clearer if errors are explicitly wrapped in `Err`. -Version 1.1.3 -============= - -Bug fixes ---------- - -* Reverses a regression on string `+` operations. -* The global namespace is now searched before packages, which is the correct behavior. - - Version 1.1.2 ============= @@ -46,6 +41,8 @@ Bug fixes * `0.0` now prints correctly (used to print `0e0`). * Unary operators are now properly recognized as an expression statement. +* Reverses a regression on string `+` operations. +* The global namespace is now searched before packages, which is the correct behavior. Version 1.1.1 diff --git a/src/engine_api.rs b/src/engine_api.rs index 60a5d94b..c065b073 100644 --- a/src/engine_api.rs +++ b/src/engine_api.rs @@ -1006,8 +1006,11 @@ impl Engine { } /// Compile a string into an [`AST`] using own scope, which can be used later for evaluation. /// - /// The scope is useful for passing constants into the script for optimization - /// when using [`OptimizationLevel::Full`]. + /// ## Constants Propagation + /// + /// If not [`OptimizationLevel::None`], constants defined within the scope are propagated + /// throughout the script _including_ functions. This allows functions to be optimized based on + /// dynamic global constants. /// /// # Example /// @@ -1019,10 +1022,6 @@ impl Engine { /// /// let mut engine = Engine::new(); /// - /// // Set optimization level to 'Full' so the Engine can fold constants - /// // into function calls and operators. - /// engine.set_optimization_level(OptimizationLevel::Full); - /// /// // Create initialized scope /// let mut scope = Scope::new(); /// scope.push_constant("x", 42_i64); // 'x' is a constant @@ -1130,6 +1129,12 @@ impl Engine { /// All strings are simply parsed one after another with nothing inserted in between, not even /// a newline or space. /// + /// ## Constants Propagation + /// + /// If not [`OptimizationLevel::None`], constants defined within the scope are propagated + /// throughout the script _including_ functions. This allows functions to be optimized based on + /// dynamic global constants. + /// /// # Example /// /// ``` @@ -1140,10 +1145,6 @@ impl Engine { /// /// let mut engine = Engine::new(); /// - /// // Set optimization level to 'Full' so the Engine can fold constants - /// // into function calls and operators. - /// engine.set_optimization_level(OptimizationLevel::Full); - /// /// // Create initialized scope /// let mut scope = Scope::new(); /// scope.push_constant("x", 42_i64); // 'x' is a constant @@ -1179,6 +1180,12 @@ impl Engine { ) } /// Join a list of strings and compile into an [`AST`] using own scope at a specific optimization level. + /// + /// ## Constants Propagation + /// + /// If not [`OptimizationLevel::None`], constants defined within the scope are propagated + /// throughout the script _including_ functions. This allows functions to be optimized based on + /// dynamic global constants. #[inline] pub(crate) fn compile_with_scope_and_optimization_level( &self, @@ -1262,8 +1269,11 @@ impl Engine { /// /// Not available under `no_std` or `WASM`. /// - /// The scope is useful for passing constants into the script for optimization - /// when using [`OptimizationLevel::Full`]. + /// ## Constants Propagation + /// + /// If not [`OptimizationLevel::None`], constants defined within the scope are propagated + /// throughout the script _including_ functions. This allows functions to be optimized based on + /// dynamic global constants. /// /// # Example /// @@ -1275,9 +1285,6 @@ impl Engine { /// /// let mut engine = Engine::new(); /// - /// // Set optimization level to 'Full' so the Engine can fold constants. - /// engine.set_optimization_level(OptimizationLevel::Full); - /// /// // Create initialized scope /// let mut scope = Scope::new(); /// scope.push_constant("x", 42_i64); // 'x' is a constant @@ -1429,9 +1436,6 @@ impl Engine { /// Compile a string containing an expression into an [`AST`] using own scope, /// which can be used later for evaluation. /// - /// The scope is useful for passing constants into the script for optimization - /// when using [`OptimizationLevel::Full`]. - /// /// # Example /// /// ``` @@ -1442,10 +1446,6 @@ impl Engine { /// /// let mut engine = Engine::new(); /// - /// // Set optimization level to 'Full' so the Engine can fold constants - /// // into function calls and operators. - /// engine.set_optimization_level(OptimizationLevel::Full); - /// /// // Create initialized scope /// let mut scope = Scope::new(); /// scope.push_constant("x", 10_i64); // 'x' is a constant @@ -1515,6 +1515,12 @@ impl Engine { /// /// Not available under `no_std` or `WASM`. /// + /// ## Constants Propagation + /// + /// If not [`OptimizationLevel::None`], constants defined within the scope are propagated + /// throughout the script _including_ functions. This allows functions to be optimized based on + /// dynamic global constants. + /// /// # Example /// /// ```no_run @@ -1562,6 +1568,12 @@ impl Engine { } /// Evaluate a string with own scope. /// + /// ## Constants Propagation + /// + /// If not [`OptimizationLevel::None`], constants defined within the scope are propagated + /// throughout the script _including_ functions. This allows functions to be optimized based on + /// dynamic global constants. + /// /// # Example /// /// ``` @@ -1768,6 +1780,12 @@ impl Engine { /// Evaluate a file with own scope, returning any error (if any). /// /// Not available under `no_std` or `WASM`. + /// + /// ## Constants Propagation + /// + /// If not [`OptimizationLevel::None`], constants defined within the scope are propagated + /// throughout the script _including_ functions. This allows functions to be optimized based on + /// dynamic global constants. #[cfg(not(feature = "no_std"))] #[cfg(not(any(target_arch = "wasm32", target_arch = "wasm64")))] #[inline] @@ -1784,6 +1802,12 @@ impl Engine { self.run_with_scope(&mut Scope::new(), script) } /// Evaluate a script with own scope, returning any error (if any). + /// + /// ## Constants Propagation + /// + /// If not [`OptimizationLevel::None`], constants defined within the scope are propagated + /// throughout the script _including_ functions. This allows functions to be optimized based on + /// dynamic global constants. #[inline] pub fn run_with_scope( &self, @@ -2065,8 +2089,9 @@ impl Engine { #[cfg(feature = "no_function")] let lib = crate::StaticVec::new(); - let stmt = std::mem::take(ast.statements_mut()); - crate::optimize::optimize_into_ast(self, scope, stmt, lib, optimization_level) + let statements = std::mem::take(ast.statements_mut()); + + crate::optimize::optimize_into_ast(self, scope, statements, lib, optimization_level) } /// _(metadata)_ Generate a list of all registered functions. /// Exported under the `metadata` feature only. diff --git a/src/optimize.rs b/src/optimize.rs index 899ce282..7bed6a15 100644 --- a/src/optimize.rs +++ b/src/optimize.rs @@ -113,16 +113,16 @@ impl<'a> OptimizerState<'a> { return None; } - self.variables.iter().rev().find_map(|(n, access, value)| { + for (n, access, value) in self.variables.iter().rev() { if n == name { - match access { + return match access { AccessMode::ReadWrite => None, AccessMode::ReadOnly => value.as_ref(), - } - } else { - None + }; } - }) + } + + None } /// Call a registered function #[inline] @@ -1095,6 +1095,8 @@ fn optimize_expr(expr: &mut Expr, state: &mut OptimizerState, chaining: bool) { } /// Optimize a block of [statements][Stmt] at top level. +/// +/// Constants and variables from the scope are added. fn optimize_top_level( statements: StaticVec, engine: &Engine, @@ -1179,11 +1181,9 @@ pub fn optimize_into_ast( let mut fn_def = crate::fn_native::shared_take_or_clone(fn_def); // Optimize the function body - let state = &mut OptimizerState::new(engine, lib2, level); - let body = mem::take(fn_def.body.deref_mut()); - *fn_def.body = optimize_stmt_block(body, state, true, true, true); + *fn_def.body = optimize_top_level(body, engine, scope, lib2, level); fn_def }) diff --git a/tests/optimizer.rs b/tests/optimizer.rs index 06cbb8d5..ae071910 100644 --- a/tests/optimizer.rs +++ b/tests/optimizer.rs @@ -1,6 +1,6 @@ #![cfg(not(feature = "no_optimize"))] -use rhai::{Engine, EvalAltResult, OptimizationLevel, INT}; +use rhai::{Engine, EvalAltResult, OptimizationLevel, Scope, INT}; #[test] fn test_optimizer() -> Result<(), Box> { @@ -107,3 +107,24 @@ fn test_optimizer_parse() -> Result<(), Box> { Ok(()) } + +#[test] +fn test_optimizer_scope() -> Result<(), Box> { + let engine = Engine::new(); + let mut scope = Scope::new(); + + scope.push_constant("FOO", 42 as INT); + + let ast = engine.compile_with_scope(&scope, "fn foo() { FOO } foo()")?; + + scope.push("FOO", 123 as INT); + + assert_eq!(engine.eval_ast::(&ast)?, 42); + assert_eq!(engine.eval_ast_with_scope::(&mut scope, &ast)?, 42); + + let ast = engine.compile_with_scope(&scope, "fn foo() { FOO } foo()")?; + + assert!(engine.eval_ast_with_scope::(&mut scope, &ast).is_err()); + + Ok(()) +}