Propagate constants to functions for Engine::XXX_with_scope calls.

This commit is contained in:
Stephen Chung 2021-11-08 22:16:28 +08:00
parent 31ef7e6c69
commit 5083df3096
4 changed files with 87 additions and 44 deletions

View File

@ -4,6 +4,11 @@ Rhai Release Notes
Version 1.2.0 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 New features
------------ ------------
@ -28,16 +33,6 @@ Deprecated API's
* `From<EvalAltResult>` for `Result<T, Box<EvalAltResult>>` 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`. * `From<EvalAltResult>` for `Result<T, Box<EvalAltResult>>` 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 Version 1.1.2
============= =============
@ -46,6 +41,8 @@ Bug fixes
* `0.0` now prints correctly (used to print `0e0`). * `0.0` now prints correctly (used to print `0e0`).
* Unary operators are now properly recognized as an expression statement. * 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 Version 1.1.1

View File

@ -1006,8 +1006,11 @@ impl Engine {
} }
/// Compile a string into an [`AST`] using own scope, which can be used later for evaluation. /// 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 /// ## Constants Propagation
/// when using [`OptimizationLevel::Full`]. ///
/// 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 /// # Example
/// ///
@ -1019,10 +1022,6 @@ impl Engine {
/// ///
/// let mut engine = Engine::new(); /// 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 /// // Create initialized scope
/// let mut scope = Scope::new(); /// let mut scope = Scope::new();
/// scope.push_constant("x", 42_i64); // 'x' is a constant /// 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 /// All strings are simply parsed one after another with nothing inserted in between, not even
/// a newline or space. /// 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 /// # Example
/// ///
/// ``` /// ```
@ -1140,10 +1145,6 @@ impl Engine {
/// ///
/// let mut engine = Engine::new(); /// 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 /// // Create initialized scope
/// let mut scope = Scope::new(); /// let mut scope = Scope::new();
/// scope.push_constant("x", 42_i64); // 'x' is a constant /// 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. /// 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] #[inline]
pub(crate) fn compile_with_scope_and_optimization_level( pub(crate) fn compile_with_scope_and_optimization_level(
&self, &self,
@ -1262,8 +1269,11 @@ impl Engine {
/// ///
/// Not available under `no_std` or `WASM`. /// Not available under `no_std` or `WASM`.
/// ///
/// The scope is useful for passing constants into the script for optimization /// ## Constants Propagation
/// when using [`OptimizationLevel::Full`]. ///
/// 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 /// # Example
/// ///
@ -1275,9 +1285,6 @@ impl Engine {
/// ///
/// let mut engine = Engine::new(); /// 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 /// // Create initialized scope
/// let mut scope = Scope::new(); /// let mut scope = Scope::new();
/// scope.push_constant("x", 42_i64); // 'x' is a constant /// 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, /// Compile a string containing an expression into an [`AST`] using own scope,
/// which can be used later for evaluation. /// which can be used later for evaluation.
/// ///
/// The scope is useful for passing constants into the script for optimization
/// when using [`OptimizationLevel::Full`].
///
/// # Example /// # Example
/// ///
/// ``` /// ```
@ -1442,10 +1446,6 @@ impl Engine {
/// ///
/// let mut engine = Engine::new(); /// 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 /// // Create initialized scope
/// let mut scope = Scope::new(); /// let mut scope = Scope::new();
/// scope.push_constant("x", 10_i64); // 'x' is a constant /// scope.push_constant("x", 10_i64); // 'x' is a constant
@ -1515,6 +1515,12 @@ impl Engine {
/// ///
/// Not available under `no_std` or `WASM`. /// 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 /// # Example
/// ///
/// ```no_run /// ```no_run
@ -1562,6 +1568,12 @@ impl Engine {
} }
/// Evaluate a string with own scope. /// 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 /// # Example
/// ///
/// ``` /// ```
@ -1768,6 +1780,12 @@ impl Engine {
/// Evaluate a file with own scope, returning any error (if any). /// Evaluate a file with own scope, returning any error (if any).
/// ///
/// Not available under `no_std` or `WASM`. /// 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(feature = "no_std"))]
#[cfg(not(any(target_arch = "wasm32", target_arch = "wasm64")))] #[cfg(not(any(target_arch = "wasm32", target_arch = "wasm64")))]
#[inline] #[inline]
@ -1784,6 +1802,12 @@ impl Engine {
self.run_with_scope(&mut Scope::new(), script) self.run_with_scope(&mut Scope::new(), script)
} }
/// Evaluate a script with own scope, returning any error (if any). /// 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] #[inline]
pub fn run_with_scope( pub fn run_with_scope(
&self, &self,
@ -2065,8 +2089,9 @@ impl Engine {
#[cfg(feature = "no_function")] #[cfg(feature = "no_function")]
let lib = crate::StaticVec::new(); let lib = crate::StaticVec::new();
let stmt = std::mem::take(ast.statements_mut()); let statements = std::mem::take(ast.statements_mut());
crate::optimize::optimize_into_ast(self, scope, stmt, lib, optimization_level)
crate::optimize::optimize_into_ast(self, scope, statements, lib, optimization_level)
} }
/// _(metadata)_ Generate a list of all registered functions. /// _(metadata)_ Generate a list of all registered functions.
/// Exported under the `metadata` feature only. /// Exported under the `metadata` feature only.

View File

@ -113,17 +113,17 @@ impl<'a> OptimizerState<'a> {
return None; return None;
} }
self.variables.iter().rev().find_map(|(n, access, value)| { for (n, access, value) in self.variables.iter().rev() {
if n == name { if n == name {
match access { return match access {
AccessMode::ReadWrite => None, AccessMode::ReadWrite => None,
AccessMode::ReadOnly => value.as_ref(), AccessMode::ReadOnly => value.as_ref(),
};
} }
} else { }
None None
} }
})
}
/// Call a registered function /// Call a registered function
#[inline] #[inline]
pub fn call_fn_with_constant_arguments( pub fn call_fn_with_constant_arguments(
@ -1095,6 +1095,8 @@ fn optimize_expr(expr: &mut Expr, state: &mut OptimizerState, chaining: bool) {
} }
/// Optimize a block of [statements][Stmt] at top level. /// Optimize a block of [statements][Stmt] at top level.
///
/// Constants and variables from the scope are added.
fn optimize_top_level( fn optimize_top_level(
statements: StaticVec<Stmt>, statements: StaticVec<Stmt>,
engine: &Engine, engine: &Engine,
@ -1179,11 +1181,9 @@ pub fn optimize_into_ast(
let mut fn_def = crate::fn_native::shared_take_or_clone(fn_def); let mut fn_def = crate::fn_native::shared_take_or_clone(fn_def);
// Optimize the function body // Optimize the function body
let state = &mut OptimizerState::new(engine, lib2, level);
let body = mem::take(fn_def.body.deref_mut()); 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 fn_def
}) })

View File

@ -1,6 +1,6 @@
#![cfg(not(feature = "no_optimize"))] #![cfg(not(feature = "no_optimize"))]
use rhai::{Engine, EvalAltResult, OptimizationLevel, INT}; use rhai::{Engine, EvalAltResult, OptimizationLevel, Scope, INT};
#[test] #[test]
fn test_optimizer() -> Result<(), Box<EvalAltResult>> { fn test_optimizer() -> Result<(), Box<EvalAltResult>> {
@ -107,3 +107,24 @@ fn test_optimizer_parse() -> Result<(), Box<EvalAltResult>> {
Ok(()) Ok(())
} }
#[test]
fn test_optimizer_scope() -> Result<(), Box<EvalAltResult>> {
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::<INT>(&ast)?, 42);
assert_eq!(engine.eval_ast_with_scope::<INT>(&mut scope, &ast)?, 42);
let ast = engine.compile_with_scope(&scope, "fn foo() { FOO } foo()")?;
assert!(engine.eval_ast_with_scope::<INT>(&mut scope, &ast).is_err());
Ok(())
}