Add eval function.
This commit is contained in:
parent
cc8ec12691
commit
6a6c5f30de
35
README.md
35
README.md
@ -1515,3 +1515,38 @@ let engine = rhai::Engine::new();
|
||||
// Turn off the optimizer
|
||||
engine.set_optimization_level(rhai::OptimizationLevel::None);
|
||||
```
|
||||
|
||||
`eval` - or "How to Shoot Yourself in the Foot even Easier"
|
||||
---------------------------------------------------------
|
||||
|
||||
Saving the best for last: in addition to script optimizations, there is the ever-dreaded... `eval` function!
|
||||
|
||||
```rust
|
||||
let x = 10;
|
||||
|
||||
fn foo(x) { x += 12; x }
|
||||
|
||||
let script = "let y = x;"; // build a script
|
||||
script += "y += foo(y);";
|
||||
script += "x + y";
|
||||
|
||||
let result = eval(script);
|
||||
|
||||
print("Answer: " + result); // prints 42
|
||||
|
||||
print("x = " + x); // prints 10 (functions call arguments are passed by value)
|
||||
print("y = " + y); // prints 32 (variables defined in 'eval' persist)
|
||||
|
||||
eval("{ let z = y }"); // to keep a variable local, use a statement block
|
||||
|
||||
print("z = " + z); // error - variable 'z' not found
|
||||
```
|
||||
|
||||
For those who subscribe to the (very sensible) motto of ["`eval` is **evil**"](http://linterrors.com/js/eval-is-evil),
|
||||
disable `eval` by overriding it, probably with something that throws.
|
||||
|
||||
```rust
|
||||
fn eval(script) { throw "eval is evil! I refuse to run " + script }
|
||||
|
||||
let x = eval("40 + 2"); // 'eval' here throws "eval is evil! I refuse to run 40 + 2"
|
||||
```
|
||||
|
117
src/api.rs
117
src/api.rs
@ -2,7 +2,7 @@
|
||||
|
||||
use crate::any::{Any, AnyExt, Dynamic};
|
||||
use crate::call::FuncArgs;
|
||||
use crate::engine::{Engine, FnAny, FnSpec};
|
||||
use crate::engine::{Engine, FnAny, FnSpec, FUNC_GETTER, FUNC_SETTER};
|
||||
use crate::error::ParseError;
|
||||
use crate::fn_register::RegisterFn;
|
||||
use crate::parser::{lex, parse, FnDef, Position, AST};
|
||||
@ -173,8 +173,8 @@ impl<'e> Engine<'e> {
|
||||
name: &str,
|
||||
callback: impl Fn(&mut T) -> U + 'static,
|
||||
) {
|
||||
let get_name = "get$".to_string() + name;
|
||||
self.register_fn(&get_name, callback);
|
||||
let get_fn_name = format!("{}{}", FUNC_GETTER, name);
|
||||
self.register_fn(&get_fn_name, callback);
|
||||
}
|
||||
|
||||
/// Register a setter function for a member of a registered type with the `Engine`.
|
||||
@ -215,8 +215,8 @@ impl<'e> Engine<'e> {
|
||||
name: &str,
|
||||
callback: impl Fn(&mut T, U) -> () + 'static,
|
||||
) {
|
||||
let set_name = "set$".to_string() + name;
|
||||
self.register_fn(&set_name, callback);
|
||||
let set_fn_name = format!("{}{}", FUNC_SETTER, name);
|
||||
self.register_fn(&set_fn_name, callback);
|
||||
}
|
||||
|
||||
/// Shorthand for registering both getter and setter functions
|
||||
@ -264,7 +264,7 @@ impl<'e> Engine<'e> {
|
||||
self.register_set(name, set_fn);
|
||||
}
|
||||
|
||||
/// Compile a string into an `AST`, which can be used later for evaluations.
|
||||
/// Compile a string into an `AST`, which can be used later for evaluation.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
@ -274,7 +274,7 @@ impl<'e> Engine<'e> {
|
||||
///
|
||||
/// let mut engine = Engine::new();
|
||||
///
|
||||
/// // Compile a script to an AST and store it for later evaluations
|
||||
/// // Compile a script to an AST and store it for later evaluation
|
||||
/// let ast = engine.compile("40 + 2")?;
|
||||
///
|
||||
/// for _ in 0..42 {
|
||||
@ -287,27 +287,40 @@ impl<'e> Engine<'e> {
|
||||
self.compile_with_scope(&Scope::new(), input)
|
||||
}
|
||||
|
||||
/// Compile a string into an `AST` using own scope, which can be used later for evaluations.
|
||||
/// The scope is useful for passing constants into the script for optimization.
|
||||
/// 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`.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// # fn main() -> Result<(), rhai::EvalAltResult> {
|
||||
/// use rhai::{Engine, Scope};
|
||||
/// # #[cfg(not(feature = "no_optimize"))]
|
||||
/// # {
|
||||
/// use rhai::{Engine, Scope, OptimizationLevel};
|
||||
///
|
||||
/// 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
|
||||
///
|
||||
/// // Compile a script to an AST and store it for later evaluations
|
||||
/// // Compile a script to an AST and store it for later evaluation.
|
||||
/// // Notice that `Full` optimization is on, so constants are folded
|
||||
/// // into function calls and operators.
|
||||
/// let ast = engine.compile_with_scope(&mut scope,
|
||||
/// "if x > 40 { x } else { 0 }"
|
||||
/// "if x > 40 { x } else { 0 }" // all 'x' are replaced with 42
|
||||
/// )?;
|
||||
///
|
||||
/// // Normally this would have failed because no scope is passed into the 'eval_ast'
|
||||
/// // call and so the variable 'x' does not exist. Here, it passes because the script
|
||||
/// // has been optimized and all references to 'x' are already gone.
|
||||
/// assert_eq!(engine.eval_ast::<i64>(&ast)?, 42);
|
||||
/// # }
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// ```
|
||||
@ -329,7 +342,7 @@ impl<'e> Engine<'e> {
|
||||
.map(|_| contents)
|
||||
}
|
||||
|
||||
/// Compile a script file into an `AST`, which can be used later for evaluations.
|
||||
/// Compile a script file into an `AST`, which can be used later for evaluation.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
@ -339,7 +352,7 @@ impl<'e> Engine<'e> {
|
||||
///
|
||||
/// let mut engine = Engine::new();
|
||||
///
|
||||
/// // Compile a script file to an AST and store it for later evaluations
|
||||
/// // Compile a script file to an AST and store it for later evaluation.
|
||||
/// // Notice that a PathBuf is required which can easily be constructed from a string.
|
||||
/// let ast = engine.compile_file("script.rhai".into())?;
|
||||
///
|
||||
@ -354,26 +367,33 @@ impl<'e> Engine<'e> {
|
||||
self.compile_file_with_scope(&Scope::new(), path)
|
||||
}
|
||||
|
||||
/// Compile a script file into an `AST` using own scope, which can be used later for evaluations.
|
||||
/// The scope is useful for passing constants into the script for optimization.
|
||||
/// Compile a script file 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
|
||||
///
|
||||
/// ```no_run
|
||||
/// # fn main() -> Result<(), rhai::EvalAltResult> {
|
||||
/// use rhai::{Engine, Scope};
|
||||
/// # #[cfg(not(feature = "no_optimize"))]
|
||||
/// # {
|
||||
/// use rhai::{Engine, Scope, OptimizationLevel};
|
||||
///
|
||||
/// 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
|
||||
///
|
||||
/// // Compile a script to an AST and store it for later evaluations
|
||||
/// // Compile a script to an AST and store it for later evaluation.
|
||||
/// // Notice that a PathBuf is required which can easily be constructed from a string.
|
||||
/// let ast = engine.compile_file_with_scope(&mut scope, "script.rhai".into())?;
|
||||
///
|
||||
/// let result = engine.eval_ast::<i64>(&ast)?;
|
||||
/// # }
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// ```
|
||||
@ -491,13 +511,13 @@ impl<'e> Engine<'e> {
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```no_run
|
||||
/// ```
|
||||
/// # fn main() -> Result<(), rhai::EvalAltResult> {
|
||||
/// use rhai::Engine;
|
||||
///
|
||||
/// let mut engine = Engine::new();
|
||||
///
|
||||
/// // Compile a script to an AST and store it for later evaluations
|
||||
/// // Compile a script to an AST and store it for later evaluation
|
||||
/// let ast = engine.compile("40 + 2")?;
|
||||
///
|
||||
/// // Evaluate it
|
||||
@ -514,22 +534,25 @@ impl<'e> Engine<'e> {
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```no_run
|
||||
/// ```
|
||||
/// # fn main() -> Result<(), rhai::EvalAltResult> {
|
||||
/// use rhai::{Engine, Scope};
|
||||
///
|
||||
/// let mut engine = Engine::new();
|
||||
///
|
||||
/// // Compile a script to an AST and store it for later evaluations
|
||||
/// // Compile a script to an AST and store it for later evaluation
|
||||
/// let ast = engine.compile("x + 2")?;
|
||||
///
|
||||
/// // Create initialized scope
|
||||
/// let mut scope = Scope::new();
|
||||
/// scope.push("x", 40_i64);
|
||||
///
|
||||
/// // Compile a script to an AST and store it for later evaluation
|
||||
/// let ast = engine.compile("x = x + 2; x")?;
|
||||
///
|
||||
/// // Evaluate it
|
||||
/// assert_eq!(engine.eval_with_scope::<i64>(&mut scope, "x = x + 2; x")?, 42);
|
||||
/// assert_eq!(engine.eval_with_scope::<i64>(&mut scope, "x = x + 2; x")?, 44);
|
||||
/// assert_eq!(engine.eval_ast_with_scope::<i64>(&mut scope, &ast)?, 42);
|
||||
/// assert_eq!(engine.eval_ast_with_scope::<i64>(&mut scope, &ast)?, 44);
|
||||
///
|
||||
/// // The variable in the scope is modified
|
||||
/// assert_eq!(scope.get_value::<i64>("x").expect("variable x should exist"), 44);
|
||||
@ -541,12 +564,32 @@ impl<'e> Engine<'e> {
|
||||
scope: &mut Scope,
|
||||
ast: &AST,
|
||||
) -> Result<T, EvalAltResult> {
|
||||
self.eval_ast_with_scope_raw(scope, false, ast)
|
||||
.and_then(|out| {
|
||||
out.downcast::<T>().map(|v| *v).map_err(|a| {
|
||||
EvalAltResult::ErrorMismatchOutputType(
|
||||
self.map_type_name((*a).type_name()).to_string(),
|
||||
Position::none(),
|
||||
)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn eval_ast_with_scope_raw(
|
||||
&mut self,
|
||||
scope: &mut Scope,
|
||||
retain_functions: bool,
|
||||
ast: &AST,
|
||||
) -> Result<Dynamic, EvalAltResult> {
|
||||
fn eval_ast_internal(
|
||||
engine: &mut Engine,
|
||||
scope: &mut Scope,
|
||||
retain_functions: bool,
|
||||
ast: &AST,
|
||||
) -> Result<Dynamic, EvalAltResult> {
|
||||
engine.clear_functions();
|
||||
if !retain_functions {
|
||||
engine.clear_functions();
|
||||
}
|
||||
|
||||
let statements = {
|
||||
let AST(statements, functions) = ast;
|
||||
@ -560,24 +603,17 @@ impl<'e> Engine<'e> {
|
||||
result = engine.eval_stmt(scope, stmt)?;
|
||||
}
|
||||
|
||||
engine.clear_functions();
|
||||
if !retain_functions {
|
||||
engine.clear_functions();
|
||||
}
|
||||
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
eval_ast_internal(self, scope, ast)
|
||||
.or_else(|err| match err {
|
||||
EvalAltResult::Return(out, _) => Ok(out),
|
||||
_ => Err(err),
|
||||
})
|
||||
.and_then(|out| {
|
||||
out.downcast::<T>().map(|v| *v).map_err(|a| {
|
||||
EvalAltResult::ErrorMismatchOutputType(
|
||||
self.map_type_name((*a).type_name()).to_string(),
|
||||
Position::eof(),
|
||||
)
|
||||
})
|
||||
})
|
||||
eval_ast_internal(self, scope, retain_functions, ast).or_else(|err| match err {
|
||||
EvalAltResult::Return(out, _) => Ok(out),
|
||||
_ => Err(err),
|
||||
})
|
||||
}
|
||||
|
||||
/// Evaluate a file, but throw away the result and only return error (if any).
|
||||
@ -729,9 +765,10 @@ impl<'e> Engine<'e> {
|
||||
name: &str,
|
||||
mut values: Vec<Dynamic>,
|
||||
) -> Result<Dynamic, EvalAltResult> {
|
||||
let mut scope = Scope::new();
|
||||
let values: Vec<_> = values.iter_mut().map(Dynamic::as_mut).collect();
|
||||
|
||||
let result = engine.call_fn_raw(name, values, None, Position::none());
|
||||
let result = engine.call_fn_raw(&mut scope, name, values, None, Position::none());
|
||||
|
||||
result
|
||||
}
|
||||
|
108
src/engine.rs
108
src/engine.rs
@ -38,6 +38,7 @@ pub(crate) const KEYWORD_PRINT: &'static str = "print";
|
||||
pub(crate) const KEYWORD_DEBUG: &'static str = "debug";
|
||||
pub(crate) const KEYWORD_DUMP_AST: &'static str = "dump_ast";
|
||||
pub(crate) const KEYWORD_TYPE_OF: &'static str = "type_of";
|
||||
pub(crate) const KEYWORD_EVAL: &'static str = "eval";
|
||||
pub(crate) const FUNC_GETTER: &'static str = "get$";
|
||||
pub(crate) const FUNC_SETTER: &'static str = "set$";
|
||||
|
||||
@ -160,6 +161,7 @@ impl Engine<'_> {
|
||||
/// registered with the `Engine` or written in Rhai
|
||||
pub(crate) fn call_fn_raw(
|
||||
&mut self,
|
||||
scope: &mut Scope,
|
||||
fn_name: &str,
|
||||
args: FnCallArgs,
|
||||
def_val: Option<&Dynamic>,
|
||||
@ -185,10 +187,15 @@ impl Engine<'_> {
|
||||
|
||||
// Evaluate
|
||||
// Convert return statement to return value
|
||||
return match self.eval_stmt(&mut scope, &fn_def.body) {
|
||||
Err(EvalAltResult::Return(x, _)) => Ok(x),
|
||||
other => other,
|
||||
};
|
||||
return self
|
||||
.eval_stmt(&mut scope, &fn_def.body)
|
||||
.or_else(|mut err| match err {
|
||||
EvalAltResult::Return(x, _) => Ok(x),
|
||||
_ => {
|
||||
err.set_position(pos);
|
||||
Err(err)
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
let spec = FnSpec {
|
||||
@ -196,24 +203,26 @@ impl Engine<'_> {
|
||||
args: Some(args.iter().map(|a| Any::type_id(&**a)).collect()),
|
||||
};
|
||||
|
||||
// Argument must be a string
|
||||
fn cast_to_string<'a>(r: &'a Variant, pos: Position) -> Result<&'a str, EvalAltResult> {
|
||||
r.downcast_ref::<String>()
|
||||
.map(String::as_str)
|
||||
.ok_or_else(|| EvalAltResult::ErrorMismatchOutputType(r.type_name().into(), pos))
|
||||
}
|
||||
|
||||
// Search built-in's and external functions
|
||||
if let Some(func) = self.ext_functions.get(&spec) {
|
||||
// Run external function
|
||||
let result = func(args, pos)?;
|
||||
|
||||
// See if the function match print/debug (which requires special processing)
|
||||
let callback = match fn_name {
|
||||
KEYWORD_PRINT => self.on_print.as_mut(),
|
||||
KEYWORD_DEBUG => self.on_debug.as_mut(),
|
||||
_ => return Ok(result),
|
||||
};
|
||||
match fn_name {
|
||||
KEYWORD_PRINT => self.on_print.as_mut()(cast_to_string(result.as_ref(), pos)?),
|
||||
KEYWORD_DEBUG => self.on_debug.as_mut()(cast_to_string(result.as_ref(), pos)?),
|
||||
_ => (),
|
||||
}
|
||||
|
||||
let val = &result
|
||||
.downcast::<String>()
|
||||
.map(|s| *s)
|
||||
.unwrap_or("error: not a string".into());
|
||||
|
||||
return Ok(callback(val).into_dynamic());
|
||||
return Ok(result);
|
||||
}
|
||||
|
||||
if fn_name == KEYWORD_TYPE_OF && args.len() == 1 {
|
||||
@ -224,6 +233,32 @@ impl Engine<'_> {
|
||||
.into_dynamic());
|
||||
}
|
||||
|
||||
if fn_name == KEYWORD_EVAL && args.len() == 1 {
|
||||
// Handle `eval` function
|
||||
let script = cast_to_string(args[0], pos)?;
|
||||
|
||||
#[cfg(not(feature = "no_optimize"))]
|
||||
let ast = {
|
||||
let orig_optimization_level = self.optimization_level;
|
||||
|
||||
self.set_optimization_level(OptimizationLevel::None);
|
||||
let ast = self.compile(script);
|
||||
self.set_optimization_level(orig_optimization_level);
|
||||
|
||||
ast.map_err(EvalAltResult::ErrorParsing)?
|
||||
};
|
||||
|
||||
#[cfg(feature = "no_optimize")]
|
||||
let ast = self.compile(script).map_err(EvalAltResult::ErrorParsing)?;
|
||||
|
||||
return Ok(self
|
||||
.eval_ast_with_scope_raw(scope, true, &ast)
|
||||
.map_err(|mut err| {
|
||||
err.set_position(pos);
|
||||
err
|
||||
})?);
|
||||
}
|
||||
|
||||
if fn_name.starts_with(FUNC_GETTER) {
|
||||
// Getter function not found
|
||||
return Err(EvalAltResult::ErrorDotExpr(
|
||||
@ -283,14 +318,14 @@ impl Engine<'_> {
|
||||
.chain(values.iter_mut().map(Dynamic::as_mut))
|
||||
.collect();
|
||||
|
||||
self.call_fn_raw(fn_name, args, def_val.as_ref(), *pos)
|
||||
self.call_fn_raw(scope, fn_name, args, def_val.as_ref(), *pos)
|
||||
}
|
||||
|
||||
// xxx.id
|
||||
Expr::Property(id, pos) => {
|
||||
let get_fn_name = format!("{}{}", FUNC_GETTER, id);
|
||||
|
||||
self.call_fn_raw(&get_fn_name, vec![this_ptr], None, *pos)
|
||||
self.call_fn_raw(scope, &get_fn_name, vec![this_ptr], None, *pos)
|
||||
}
|
||||
|
||||
// xxx.idx_lhs[idx_expr]
|
||||
@ -301,7 +336,7 @@ impl Engine<'_> {
|
||||
Expr::Property(id, pos) => {
|
||||
let get_fn_name = format!("{}{}", FUNC_GETTER, id);
|
||||
(
|
||||
self.call_fn_raw(&get_fn_name, vec![this_ptr], None, *pos)?,
|
||||
self.call_fn_raw(scope, &get_fn_name, vec![this_ptr], None, *pos)?,
|
||||
*pos,
|
||||
)
|
||||
}
|
||||
@ -329,7 +364,7 @@ impl Engine<'_> {
|
||||
Expr::Property(id, pos) => {
|
||||
let get_fn_name = format!("{}{}", FUNC_GETTER, id);
|
||||
|
||||
self.call_fn_raw(&get_fn_name, vec![this_ptr], None, *pos)
|
||||
self.call_fn_raw(scope, &get_fn_name, vec![this_ptr], None, *pos)
|
||||
.and_then(|mut v| self.get_dot_val_helper(scope, v.as_mut(), rhs))
|
||||
}
|
||||
// xxx.idx_lhs[idx_expr].rhs
|
||||
@ -340,7 +375,7 @@ impl Engine<'_> {
|
||||
Expr::Property(id, pos) => {
|
||||
let get_fn_name = format!("{}{}", FUNC_GETTER, id);
|
||||
(
|
||||
self.call_fn_raw(&get_fn_name, vec![this_ptr], None, *pos)?,
|
||||
self.call_fn_raw(scope, &get_fn_name, vec![this_ptr], None, *pos)?,
|
||||
*pos,
|
||||
)
|
||||
}
|
||||
@ -642,7 +677,13 @@ impl Engine<'_> {
|
||||
Expr::Property(id, pos) => {
|
||||
let set_fn_name = format!("{}{}", FUNC_SETTER, id);
|
||||
|
||||
self.call_fn_raw(&set_fn_name, vec![this_ptr, new_val.as_mut()], None, *pos)
|
||||
self.call_fn_raw(
|
||||
scope,
|
||||
&set_fn_name,
|
||||
vec![this_ptr, new_val.as_mut()],
|
||||
None,
|
||||
*pos,
|
||||
)
|
||||
}
|
||||
|
||||
// xxx.lhs[idx_expr]
|
||||
@ -653,14 +694,20 @@ impl Engine<'_> {
|
||||
Expr::Property(id, pos) => {
|
||||
let get_fn_name = format!("{}{}", FUNC_GETTER, id);
|
||||
|
||||
self.call_fn_raw(&get_fn_name, vec![this_ptr], None, *pos)
|
||||
self.call_fn_raw(scope, &get_fn_name, vec![this_ptr], None, *pos)
|
||||
.and_then(|v| {
|
||||
let idx = self.eval_index_value(scope, idx_expr)?;
|
||||
Self::update_indexed_value(v, idx as usize, new_val, val_pos)
|
||||
})
|
||||
.and_then(|mut v| {
|
||||
let set_fn_name = format!("{}{}", FUNC_SETTER, id);
|
||||
self.call_fn_raw(&set_fn_name, vec![this_ptr, v.as_mut()], None, *pos)
|
||||
self.call_fn_raw(
|
||||
scope,
|
||||
&set_fn_name,
|
||||
vec![this_ptr, v.as_mut()],
|
||||
None,
|
||||
*pos,
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
@ -677,7 +724,7 @@ impl Engine<'_> {
|
||||
Expr::Property(id, pos) => {
|
||||
let get_fn_name = format!("{}{}", FUNC_GETTER, id);
|
||||
|
||||
self.call_fn_raw(&get_fn_name, vec![this_ptr], None, *pos)
|
||||
self.call_fn_raw(scope, &get_fn_name, vec![this_ptr], None, *pos)
|
||||
.and_then(|mut v| {
|
||||
self.set_dot_val_helper(scope, v.as_mut(), rhs, new_val, val_pos)
|
||||
.map(|_| v) // Discard Ok return value
|
||||
@ -685,7 +732,13 @@ impl Engine<'_> {
|
||||
.and_then(|mut v| {
|
||||
let set_fn_name = format!("{}{}", FUNC_SETTER, id);
|
||||
|
||||
self.call_fn_raw(&set_fn_name, vec![this_ptr, v.as_mut()], None, *pos)
|
||||
self.call_fn_raw(
|
||||
scope,
|
||||
&set_fn_name,
|
||||
vec![this_ptr, v.as_mut()],
|
||||
None,
|
||||
*pos,
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
@ -697,7 +750,7 @@ impl Engine<'_> {
|
||||
Expr::Property(id, pos) => {
|
||||
let get_fn_name = format!("{}{}", FUNC_GETTER, id);
|
||||
|
||||
self.call_fn_raw(&get_fn_name, vec![this_ptr], None, *pos)
|
||||
self.call_fn_raw(scope, &get_fn_name, vec![this_ptr], None, *pos)
|
||||
.and_then(|v| {
|
||||
let idx = self.eval_index_value(scope, idx_expr)?;
|
||||
let (mut target, _) =
|
||||
@ -718,6 +771,7 @@ impl Engine<'_> {
|
||||
let set_fn_name = format!("{}{}", FUNC_SETTER, id);
|
||||
|
||||
self.call_fn_raw(
|
||||
scope,
|
||||
&set_fn_name,
|
||||
vec![this_ptr, v.as_mut()],
|
||||
None,
|
||||
@ -936,6 +990,7 @@ impl Engine<'_> {
|
||||
|
||||
// Redirect call to `print`
|
||||
self.call_fn_raw(
|
||||
scope,
|
||||
KEYWORD_PRINT,
|
||||
vec![result.into_dynamic().as_mut()],
|
||||
None,
|
||||
@ -951,6 +1006,7 @@ impl Engine<'_> {
|
||||
.collect::<Result<Vec<Dynamic>, _>>()?;
|
||||
|
||||
self.call_fn_raw(
|
||||
scope,
|
||||
fn_name,
|
||||
values.iter_mut().map(|b| b.as_mut()).collect(),
|
||||
def_val.as_ref(),
|
||||
|
@ -2,7 +2,8 @@
|
||||
|
||||
use crate::any::{Any, Dynamic};
|
||||
use crate::engine::{
|
||||
Engine, FnCallArgs, KEYWORD_DEBUG, KEYWORD_DUMP_AST, KEYWORD_PRINT, KEYWORD_TYPE_OF,
|
||||
Engine, FnCallArgs, KEYWORD_DEBUG, KEYWORD_DUMP_AST, KEYWORD_EVAL, KEYWORD_PRINT,
|
||||
KEYWORD_TYPE_OF,
|
||||
};
|
||||
use crate::parser::{map_dynamic_to_expr, Expr, FnDef, ReturnType, Stmt, AST};
|
||||
use crate::scope::{Scope, ScopeEntry, VariableType};
|
||||
@ -287,7 +288,7 @@ fn optimize_stmt<'a>(stmt: Stmt, state: &mut State<'a>, preserve_result: bool) -
|
||||
/// Optimize an expression.
|
||||
fn optimize_expr<'a>(expr: Expr, state: &mut State<'a>) -> Expr {
|
||||
// These keywords are handled specially
|
||||
const SKIP_FUNC_KEYWORDS: [&str; 3] = [KEYWORD_PRINT, KEYWORD_DEBUG, KEYWORD_DUMP_AST];
|
||||
const DONT_EVAL_KEYWORDS: [&str; 3] = [KEYWORD_PRINT, KEYWORD_DEBUG, KEYWORD_EVAL];
|
||||
|
||||
match expr {
|
||||
// ( stmt )
|
||||
@ -431,10 +432,16 @@ fn optimize_expr<'a>(expr: Expr, state: &mut State<'a>) -> Expr {
|
||||
),
|
||||
},
|
||||
|
||||
// Do not optimize anything within built-in function keywords
|
||||
Expr::FunctionCall(id, args, def_value, pos) if SKIP_FUNC_KEYWORDS.contains(&id.as_str())=>
|
||||
// Do not optimize anything within dump_ast
|
||||
Expr::FunctionCall(id, args, def_value, pos) if id == KEYWORD_DUMP_AST =>
|
||||
Expr::FunctionCall(id, args, def_value, pos),
|
||||
|
||||
// Do not optimize anything within built-in function keywords
|
||||
Expr::FunctionCall(id, args, def_value, pos) if DONT_EVAL_KEYWORDS.contains(&id.as_str())=>
|
||||
Expr::FunctionCall(id,
|
||||
args.into_iter().map(|a| optimize_expr(a, state)).collect(),
|
||||
def_value, pos),
|
||||
|
||||
// Eagerly call functions
|
||||
Expr::FunctionCall(id, args, def_value, pos)
|
||||
if state.engine.optimization_level == OptimizationLevel::Full // full optimizations
|
||||
@ -467,9 +474,13 @@ fn optimize_expr<'a>(expr: Expr, state: &mut State<'a>) -> Expr {
|
||||
})
|
||||
).flatten().unwrap_or_else(|| Expr::FunctionCall(id, args, def_value, pos))
|
||||
}
|
||||
|
||||
// id(args ..) -> optimize function call arguments
|
||||
Expr::FunctionCall(id, args, def_value, pos) =>
|
||||
Expr::FunctionCall(id, args.into_iter().map(|a| optimize_expr(a, state)).collect(), def_value, pos),
|
||||
Expr::FunctionCall(id,
|
||||
args.into_iter().map(|a| optimize_expr(a, state)).collect(),
|
||||
def_value, pos),
|
||||
|
||||
// constant-name
|
||||
Expr::Variable(ref name, _) if state.contains_constant(name) => {
|
||||
state.set_dirty();
|
||||
@ -480,6 +491,7 @@ fn optimize_expr<'a>(expr: Expr, state: &mut State<'a>) -> Expr {
|
||||
.expect("should find constant in scope!")
|
||||
.clone()
|
||||
}
|
||||
|
||||
// All other expressions - skip
|
||||
expr => expr,
|
||||
}
|
||||
|
@ -469,6 +469,10 @@ pub enum Token {
|
||||
UnaryMinus,
|
||||
Multiply,
|
||||
Divide,
|
||||
Modulo,
|
||||
PowerOf,
|
||||
LeftShift,
|
||||
RightShift,
|
||||
SemiColon,
|
||||
Colon,
|
||||
Comma,
|
||||
@ -482,15 +486,18 @@ pub enum Token {
|
||||
Else,
|
||||
While,
|
||||
Loop,
|
||||
For,
|
||||
In,
|
||||
LessThan,
|
||||
GreaterThan,
|
||||
Bang,
|
||||
LessThanEqualsTo,
|
||||
GreaterThanEqualsTo,
|
||||
EqualsTo,
|
||||
NotEqualsTo,
|
||||
Bang,
|
||||
Pipe,
|
||||
Or,
|
||||
XOr,
|
||||
Ampersand,
|
||||
And,
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
@ -507,15 +514,8 @@ pub enum Token {
|
||||
AndAssign,
|
||||
OrAssign,
|
||||
XOrAssign,
|
||||
LeftShift,
|
||||
RightShift,
|
||||
XOr,
|
||||
Modulo,
|
||||
ModuloAssign,
|
||||
PowerOf,
|
||||
PowerOfAssign,
|
||||
For,
|
||||
In,
|
||||
LexError(LexError),
|
||||
}
|
||||
|
||||
@ -2210,31 +2210,27 @@ fn parse_stmt<'a>(
|
||||
Ok(Stmt::Break(pos))
|
||||
}
|
||||
(Token::Break, pos) => return Err(ParseError::new(PERR::LoopBreak, *pos)),
|
||||
(token @ Token::Return, _) | (token @ Token::Throw, _) => {
|
||||
(token @ Token::Return, pos) | (token @ Token::Throw, pos) => {
|
||||
let return_type = match token {
|
||||
Token::Return => ReturnType::Return,
|
||||
Token::Throw => ReturnType::Exception,
|
||||
_ => panic!("token should be return or throw"),
|
||||
};
|
||||
|
||||
let pos = *pos;
|
||||
input.next();
|
||||
|
||||
match input.peek() {
|
||||
// `return`/`throw` at EOF
|
||||
None => Ok(Stmt::ReturnWithVal(None, return_type, Position::eof())),
|
||||
// `return;` or `throw;`
|
||||
Some((Token::SemiColon, pos)) => {
|
||||
let pos = *pos;
|
||||
Ok(Stmt::ReturnWithVal(None, return_type, pos))
|
||||
}
|
||||
Some((Token::SemiColon, _)) => Ok(Stmt::ReturnWithVal(None, return_type, pos)),
|
||||
// `return` or `throw` with expression
|
||||
Some((_, pos)) => {
|
||||
let pos = *pos;
|
||||
Ok(Stmt::ReturnWithVal(
|
||||
Some(Box::new(parse_expr(input)?)),
|
||||
return_type,
|
||||
pos,
|
||||
))
|
||||
Some((_, _)) => {
|
||||
let expr = parse_expr(input)?;
|
||||
let pos = expr.position();
|
||||
|
||||
Ok(Stmt::ReturnWithVal(Some(Box::new(expr)), return_type, pos))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -71,7 +71,7 @@ impl<'a> Scope<'a> {
|
||||
}
|
||||
|
||||
/// Add (push) a new variable to the Scope.
|
||||
pub fn push<K: Into<Cow<'a, str>>, T: Any>(&mut self, name: K, value: T) {
|
||||
pub fn push<K: Into<Cow<'a, str>>, T: Any + Clone>(&mut self, name: K, value: T) {
|
||||
let value = value.into_dynamic();
|
||||
|
||||
// Map into constant expressions
|
||||
@ -91,7 +91,7 @@ impl<'a> Scope<'a> {
|
||||
/// Constants propagation is a technique used to optimize an AST.
|
||||
/// However, in order to be used for optimization, constants must be in one of the recognized types:
|
||||
/// `INT` (default to `i64`, `i32` if `only_i32`), `f64`, `String`, `char` and `bool`.
|
||||
pub fn push_constant<K: Into<Cow<'a, str>>, T: Any>(&mut self, name: K, value: T) {
|
||||
pub fn push_constant<K: Into<Cow<'a, str>>, T: Any + Clone>(&mut self, name: K, value: T) {
|
||||
let value = value.into_dynamic();
|
||||
|
||||
// Map into constant expressions
|
||||
|
Loading…
Reference in New Issue
Block a user