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
=============
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<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
=============
@ -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

View File

@ -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.

View File

@ -113,17 +113,17 @@ 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
}
})
}
/// Call a registered function
#[inline]
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.
///
/// Constants and variables from the scope are added.
fn optimize_top_level(
statements: StaticVec<Stmt>,
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
})

View File

@ -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<EvalAltResult>> {
@ -107,3 +107,24 @@ fn test_optimizer_parse() -> Result<(), Box<EvalAltResult>> {
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(())
}