Merge pull request #110 from schungx/master

eval
This commit is contained in:
Stephen Chung 2020-03-21 00:27:38 +08:00 committed by GitHub
commit 9c7fa97171
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 715 additions and 329 deletions

134
README.md
View File

@ -273,10 +273,11 @@ type_of('c') == "char";
type_of(42) == "i64"; type_of(42) == "i64";
let x = 123; let x = 123;
x.type_of() == "i64"; x.type_of(); // error - 'type_of' cannot use postfix notation
type_of(x) == "i64";
x = 99.999; x = 99.999;
x.type_of() == "f64"; type_of(x) == "f64";
x = "hello"; x = "hello";
if type_of(x) == "string" { if type_of(x) == "string" {
@ -538,12 +539,12 @@ with a special "pretty-print" name, [`type_of`] will return that name instead.
engine.register_type::<TestStruct>(); engine.register_type::<TestStruct>();
engine.register_fn("new_ts", TestStruct::new); engine.register_fn("new_ts", TestStruct::new);
let x = new_ts(); let x = new_ts();
print(x.type_of()); // prints "path::to::module::TestStruct" print(type_of(x)); // prints "path::to::module::TestStruct"
engine.register_type_with_name::<TestStruct>("Hello"); engine.register_type_with_name::<TestStruct>("Hello");
engine.register_fn("new_ts", TestStruct::new); engine.register_fn("new_ts", TestStruct::new);
let x = new_ts(); let x = new_ts();
print(x.type_of()); // prints "Hello" print(type_of(x)); // prints "Hello"
``` ```
Getters and setters Getters and setters
@ -1304,7 +1305,8 @@ for entry in log {
Script optimization Script optimization
=================== ===================
Rhai includes an _optimizer_ that tries to optimize a script after parsing. This can reduce resource utilization and increase execution speed. Rhai includes an _optimizer_ that tries to optimize a script after parsing.
This can reduce resource utilization and increase execution speed.
Script optimization can be turned off via the [`no_optimize`] feature. Script optimization can be turned off via the [`no_optimize`] feature.
For example, in the following: For example, in the following:
@ -1315,7 +1317,7 @@ For example, in the following:
123; // eliminated - no effect 123; // eliminated - no effect
"hello"; // eliminated - no effect "hello"; // eliminated - no effect
[1, 2, x, x*2, 5]; // eliminated - no effect [1, 2, x, x*2, 5]; // eliminated - no effect
foo(42); // NOT eliminated - the function 'foo' may have side effects foo(42); // NOT eliminated - functions calls are not touched
666 // NOT eliminated - this is the return value of the block, 666 // NOT eliminated - this is the return value of the block,
// and the block is the last one // and the block is the last one
// so this is the return value of the whole script // so this is the return value of the whole script
@ -1349,14 +1351,12 @@ are spliced into the script text in order to turn on/off certain sections.
For fixed script texts, the constant values can be provided in a user-defined [`Scope`] object For fixed script texts, the constant values can be provided in a user-defined [`Scope`] object
to the [`Engine`] for use in compilation and evaluation. to the [`Engine`] for use in compilation and evaluation.
Beware, however, that most operators are actually function calls, and those functions can be overridden, Beware, however, that most operators are actually function calls, and those are not optimized away:
so they are not optimized away:
```rust ```rust
const DECISION = 1; const DECISION = 1;
if DECISION == 1 { // NOT optimized away because you can define if DECISION == 1 { // NOT optimized away because it requires a call to the '==' function
: // your own '==' function to override the built-in default!
: :
} else if DECISION == 2 { // same here, NOT optimized away } else if DECISION == 2 { // same here, NOT optimized away
: :
@ -1367,8 +1367,8 @@ if DECISION == 1 { // NOT optimized away because you can define
} }
``` ```
because no operator functions will be run (in order not to trigger side effects) during the optimization process because no operator functions will be run during the optimization process (unless the optimization level is
(unless the optimization level is set to [`OptimizationLevel::Full`]). So, instead, do this: set to [`OptimizationLevel::Full`]). So, instead, do this:
```rust ```rust
const DECISION_1 = true; const DECISION_1 = true;
@ -1405,11 +1405,14 @@ There are actually three levels of optimizations: `None`, `Simple` and `Full`.
* `None` is obvious - no optimization on the AST is performed. * `None` is obvious - no optimization on the AST is performed.
* `Simple` (default) performs relatively _safe_ optimizations without causing side effects * `Simple` (default) relies exclusively on static analysis, performing relatively _safe_ optimizations only.
(i.e. it only relies on static analysis and will not actually perform any function calls). In particular, no function calls will be made to determine the output value. This also means that most comparison
operators and constant arithmetic expressions are untouched.
* `Full` is _much_ more aggressive, _including_ running functions on constant arguments to determine their result. * `Full` is _much_ more aggressive. Functions _will_ be run, when passed constant arguments, to determine their results.
One benefit to this is that many more optimization opportunities arise, especially with regards to comparison operators. One benefit is that many more optimization opportunities arise, especially with regards to comparison operators.
Nevertheless, the majority of scripts do not excessively rely on constants, and that is why this optimization level
is opt-in; only scripts that are machine-generated tend to have constants spliced in at generation time.
An [`Engine`]'s optimization level is set via a call to `set_optimization_level`: An [`Engine`]'s optimization level is set via a call to `set_optimization_level`:
@ -1419,15 +1422,15 @@ engine.set_optimization_level(rhai::OptimizationLevel::Full);
``` ```
When the optimization level is [`OptimizationLevel::Full`], the [`Engine`] assumes all functions to be _pure_ and will _eagerly_ When the optimization level is [`OptimizationLevel::Full`], the [`Engine`] assumes all functions to be _pure_ and will _eagerly_
evaluated all function calls with constant arguments, using the result to replace the call. This also applies to all operators call all functions when passed constant arguments, using the results to replace the actual calls. This also affects all operators
(which are implemented as functions). For instance, the same example above: because most of them are implemented as functions. For instance, the same example above:
```rust ```rust
// When compiling the following with OptimizationLevel::Full... // When compiling the following with OptimizationLevel::Full...
const DECISION = 1; const DECISION = 1;
// this condition is now eliminated because 'DECISION == 1' // this condition is now eliminated because 'DECISION == 1' is a
if DECISION == 1 { // is a function call to the '==' function, and it returns 'true' if DECISION == 1 { // function call to the '==' function with constant arguments, and it returns 'true'
print("hello!"); // this block is promoted to the parent level print("hello!"); // this block is promoted to the parent level
} else { } else {
print("boo!"); // this block is eliminated because it is never reached print("boo!"); // this block is eliminated because it is never reached
@ -1446,25 +1449,20 @@ let x = (1 + 2) * 3 - 4 / 5 % 6; // <- will be replaced by 'let x = 9'
let y = (1 > 2) || (3 <= 4); // <- will be replaced by 'let y = true' let y = (1 > 2) || (3 <= 4); // <- will be replaced by 'let y = true'
``` ```
Function side effect considerations
----------------------------------
All of Rhai's built-in functions (and operators which are implemented as functions) are _pure_ (i.e. they do not mutate state
nor cause side any effects, with the exception of `print` and `debug` which are handled specially) so using [`OptimizationLevel::Full`]
is usually quite safe _unless_ you register your own types and functions.
If custom functions are registered, they _may_ be called (or maybe not, if the calls happen to lie within a pruned code block).
If custom functions are registered to replace built-in operators, they will also be called when the operators are used (in an `if`
statement, for example) and cause side-effects.
Function volatility considerations Function volatility considerations
--------------------------------- ---------------------------------
Even if a custom function does not mutate state nor cause side effects, it may still be _volatile_, i.e. it _depends_ on the external Rhai functions never mutate state nor cause side any effects (except `print` and `debug` which are handled specially).
environment and not _pure_. A perfect example is a function that gets the current time - obviously each run will return a different value! The only functions allowed to mutate state are custom type getters, setters and methods, and functions calls involving custom types
The optimizer, when using [`OptimizationLevel::Full`], _assumes_ that all functions are _pure_, so when it finds constant arguments. are never optimized. So using [`OptimizationLevel::Full`] is usually quite safe.
This may cause the script to behave differently from the intended semantics because essentially the result of each function call will
always be the same value. However, even if a function cannot mutate state nor cause side effects, it may still be _volatile_, i.e. it may
_depend_ on the external environment and not be _pure_. A perfect example is a function that gets the current time -
obviously each run will return a different value!
The optimizer, when using [`OptimizationLevel::Full`], _assumes_ that all functions are _pure_ when it finds constant arguments.
This may cause the script to behave differently from the intended semantics because essentially the result of each function call
will always be the same value.
Therefore, **avoid using [`OptimizationLevel::Full`]** if you intend to register non-_pure_ custom types and/or functions. Therefore, **avoid using [`OptimizationLevel::Full`]** if you intend to register non-_pure_ custom types and/or functions.
@ -1515,3 +1513,67 @@ let engine = rhai::Engine::new();
// Turn off the optimizer // Turn off the optimizer
engine.set_optimization_level(rhai::OptimizationLevel::None); 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); // <- look, JS, we can also do this!
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
"print(42)".eval(); // nope - just like 'type_of' postfix notation doesn't work
```
Script segments passed to `eval` execute inside the current [`Scope`], so they can access and modify _everything_,
including all variables that are visible at that position in code! It is almost as if the script segments were
physically pasted in at the position of the `eval` call.
```rust
let script = "x += 32";
let x = 10;
eval(script); // variable 'x' in the current scope is visible!
print(x); // prints 42
// The above is equivalent to:
let script = "x += 32";
let x = 10;
x += 32;
print(x);
```
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"
```
Or override it from Rust:
```rust
fn alt_eval(script: String) -> Result<(), EvalAltResult> {
Err(format!("eval is evil! I refuse to run {}", script).into())
}
engine.register_result_fn("eval", alt_eval);
```

View File

@ -2,7 +2,7 @@
use crate::any::{Any, AnyExt, Dynamic}; use crate::any::{Any, AnyExt, Dynamic};
use crate::call::FuncArgs; 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::error::ParseError;
use crate::fn_register::RegisterFn; use crate::fn_register::RegisterFn;
use crate::parser::{lex, parse, FnDef, Position, AST}; use crate::parser::{lex, parse, FnDef, Position, AST};
@ -173,8 +173,8 @@ impl<'e> Engine<'e> {
name: &str, name: &str,
callback: impl Fn(&mut T) -> U + 'static, callback: impl Fn(&mut T) -> U + 'static,
) { ) {
let get_name = "get$".to_string() + name; let get_fn_name = format!("{}{}", FUNC_GETTER, name);
self.register_fn(&get_name, callback); self.register_fn(&get_fn_name, callback);
} }
/// Register a setter function for a member of a registered type with the `Engine`. /// Register a setter function for a member of a registered type with the `Engine`.
@ -215,8 +215,8 @@ impl<'e> Engine<'e> {
name: &str, name: &str,
callback: impl Fn(&mut T, U) -> () + 'static, callback: impl Fn(&mut T, U) -> () + 'static,
) { ) {
let set_name = "set$".to_string() + name; let set_fn_name = format!("{}{}", FUNC_SETTER, name);
self.register_fn(&set_name, callback); self.register_fn(&set_fn_name, callback);
} }
/// Shorthand for registering both getter and setter functions /// Shorthand for registering both getter and setter functions
@ -264,7 +264,7 @@ impl<'e> Engine<'e> {
self.register_set(name, set_fn); 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 /// # Example
/// ///
@ -274,7 +274,7 @@ impl<'e> Engine<'e> {
/// ///
/// let mut engine = Engine::new(); /// 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")?; /// let ast = engine.compile("40 + 2")?;
/// ///
/// for _ in 0..42 { /// for _ in 0..42 {
@ -287,27 +287,40 @@ impl<'e> Engine<'e> {
self.compile_with_scope(&Scope::new(), input) self.compile_with_scope(&Scope::new(), input)
} }
/// Compile a string into an `AST` using own scope, which can be used later for evaluations. /// 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. /// The scope is useful for passing constants into the script for optimization
/// when using `OptimizationLevel::Full`.
/// ///
/// # Example /// # Example
/// ///
/// ``` /// ```
/// # fn main() -> Result<(), rhai::EvalAltResult> { /// # fn main() -> Result<(), rhai::EvalAltResult> {
/// use rhai::{Engine, Scope}; /// # #[cfg(not(feature = "no_optimize"))]
/// # {
/// use rhai::{Engine, Scope, OptimizationLevel};
/// ///
/// 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
/// ///
/// // 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, /// 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); /// assert_eq!(engine.eval_ast::<i64>(&ast)?, 42);
/// # }
/// # Ok(()) /// # Ok(())
/// # } /// # }
/// ``` /// ```
@ -329,7 +342,7 @@ impl<'e> Engine<'e> {
.map(|_| contents) .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 /// # Example
/// ///
@ -339,7 +352,7 @@ impl<'e> Engine<'e> {
/// ///
/// let mut engine = Engine::new(); /// 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. /// // Notice that a PathBuf is required which can easily be constructed from a string.
/// let ast = engine.compile_file("script.rhai".into())?; /// let ast = engine.compile_file("script.rhai".into())?;
/// ///
@ -354,26 +367,33 @@ impl<'e> Engine<'e> {
self.compile_file_with_scope(&Scope::new(), path) 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. /// 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. /// The scope is useful for passing constants into the script for optimization
/// when using `OptimizationLevel::Full`.
/// ///
/// # Example /// # Example
/// ///
/// ```no_run /// ```no_run
/// # fn main() -> Result<(), rhai::EvalAltResult> { /// # fn main() -> Result<(), rhai::EvalAltResult> {
/// use rhai::{Engine, Scope}; /// # #[cfg(not(feature = "no_optimize"))]
/// # {
/// use rhai::{Engine, Scope, OptimizationLevel};
/// ///
/// 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
/// ///
/// // 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. /// // 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 ast = engine.compile_file_with_scope(&mut scope, "script.rhai".into())?;
/// ///
/// let result = engine.eval_ast::<i64>(&ast)?; /// let result = engine.eval_ast::<i64>(&ast)?;
/// # }
/// # Ok(()) /// # Ok(())
/// # } /// # }
/// ``` /// ```
@ -491,13 +511,13 @@ impl<'e> Engine<'e> {
/// ///
/// # Example /// # Example
/// ///
/// ```no_run /// ```
/// # fn main() -> Result<(), rhai::EvalAltResult> { /// # fn main() -> Result<(), rhai::EvalAltResult> {
/// use rhai::Engine; /// use rhai::Engine;
/// ///
/// let mut engine = Engine::new(); /// 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")?; /// let ast = engine.compile("40 + 2")?;
/// ///
/// // Evaluate it /// // Evaluate it
@ -514,22 +534,25 @@ impl<'e> Engine<'e> {
/// ///
/// # Example /// # Example
/// ///
/// ```no_run /// ```
/// # fn main() -> Result<(), rhai::EvalAltResult> { /// # fn main() -> Result<(), rhai::EvalAltResult> {
/// use rhai::{Engine, Scope}; /// use rhai::{Engine, Scope};
/// ///
/// let mut engine = Engine::new(); /// 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")?; /// let ast = engine.compile("x + 2")?;
/// ///
/// // Create initialized scope /// // Create initialized scope
/// let mut scope = Scope::new(); /// let mut scope = Scope::new();
/// scope.push("x", 40_i64); /// 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 /// // Evaluate it
/// assert_eq!(engine.eval_with_scope::<i64>(&mut scope, "x = x + 2; x")?, 42); /// assert_eq!(engine.eval_ast_with_scope::<i64>(&mut scope, &ast)?, 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)?, 44);
/// ///
/// // The variable in the scope is modified /// // The variable in the scope is modified
/// assert_eq!(scope.get_value::<i64>("x").expect("variable x should exist"), 44); /// assert_eq!(scope.get_value::<i64>("x").expect("variable x should exist"), 44);
@ -541,12 +564,32 @@ impl<'e> Engine<'e> {
scope: &mut Scope, scope: &mut Scope,
ast: &AST, ast: &AST,
) -> Result<T, EvalAltResult> { ) -> 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( fn eval_ast_internal(
engine: &mut Engine, engine: &mut Engine,
scope: &mut Scope, scope: &mut Scope,
retain_functions: bool,
ast: &AST, ast: &AST,
) -> Result<Dynamic, EvalAltResult> { ) -> Result<Dynamic, EvalAltResult> {
if !retain_functions {
engine.clear_functions(); engine.clear_functions();
}
let statements = { let statements = {
let AST(statements, functions) = ast; let AST(statements, functions) = ast;
@ -560,24 +603,17 @@ impl<'e> Engine<'e> {
result = engine.eval_stmt(scope, stmt)?; result = engine.eval_stmt(scope, stmt)?;
} }
if !retain_functions {
engine.clear_functions(); engine.clear_functions();
}
Ok(result) Ok(result)
} }
eval_ast_internal(self, scope, ast) eval_ast_internal(self, scope, retain_functions, ast).or_else(|err| match err {
.or_else(|err| match err {
EvalAltResult::Return(out, _) => Ok(out), EvalAltResult::Return(out, _) => Ok(out),
_ => Err(err), _ => 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(),
)
})
})
} }
/// Evaluate a file, but throw away the result and only return error (if any). /// Evaluate a file, but throw away the result and only return error (if any).

View File

@ -3,7 +3,7 @@
use crate::any::{Any, AnyExt, Dynamic, Variant}; use crate::any::{Any, AnyExt, Dynamic, Variant};
use crate::parser::{Expr, FnDef, Position, ReturnType, Stmt}; use crate::parser::{Expr, FnDef, Position, ReturnType, Stmt};
use crate::result::EvalAltResult; use crate::result::EvalAltResult;
use crate::scope::{Scope, VariableType}; use crate::scope::{Scope, ScopeSource, VariableType};
#[cfg(not(feature = "no_optimize"))] #[cfg(not(feature = "no_optimize"))]
use crate::optimize::OptimizationLevel; use crate::optimize::OptimizationLevel;
@ -38,6 +38,7 @@ pub(crate) const KEYWORD_PRINT: &'static str = "print";
pub(crate) const KEYWORD_DEBUG: &'static str = "debug"; pub(crate) const KEYWORD_DEBUG: &'static str = "debug";
pub(crate) const KEYWORD_DUMP_AST: &'static str = "dump_ast"; pub(crate) const KEYWORD_DUMP_AST: &'static str = "dump_ast";
pub(crate) const KEYWORD_TYPE_OF: &'static str = "type_of"; 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_GETTER: &'static str = "get$";
pub(crate) const FUNC_SETTER: &'static str = "set$"; pub(crate) const FUNC_SETTER: &'static str = "set$";
@ -185,10 +186,15 @@ impl Engine<'_> {
// Evaluate // Evaluate
// Convert return statement to return value // Convert return statement to return value
return match self.eval_stmt(&mut scope, &fn_def.body) { return self
Err(EvalAltResult::Return(x, _)) => Ok(x), .eval_stmt(&mut scope, &fn_def.body)
other => other, .or_else(|mut err| match err {
}; EvalAltResult::Return(x, _) => Ok(x),
_ => {
err.set_position(pos);
Err(err)
}
});
} }
let spec = FnSpec { let spec = FnSpec {
@ -196,32 +202,26 @@ impl Engine<'_> {
args: Some(args.iter().map(|a| Any::type_id(&**a)).collect()), 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 // Search built-in's and external functions
if let Some(func) = self.ext_functions.get(&spec) { if let Some(func) = self.ext_functions.get(&spec) {
// Run external function // Run external function
let result = func(args, pos)?; let result = func(args, pos)?;
// See if the function match print/debug (which requires special processing) // See if the function match print/debug (which requires special processing)
let callback = match fn_name { match fn_name {
KEYWORD_PRINT => self.on_print.as_mut(), KEYWORD_PRINT => self.on_print.as_mut()(cast_to_string(result.as_ref(), pos)?),
KEYWORD_DEBUG => self.on_debug.as_mut(), KEYWORD_DEBUG => self.on_debug.as_mut()(cast_to_string(result.as_ref(), pos)?),
_ => return Ok(result), _ => (),
};
let val = &result
.downcast::<String>()
.map(|s| *s)
.unwrap_or("error: not a string".into());
return Ok(callback(val).into_dynamic());
} }
if fn_name == KEYWORD_TYPE_OF && args.len() == 1 { return Ok(result);
// Handle `type_of` function
return Ok(self
.map_type_name(args[0].type_name())
.to_string()
.into_dynamic());
} }
if fn_name.starts_with(FUNC_GETTER) { if fn_name.starts_with(FUNC_GETTER) {
@ -264,13 +264,38 @@ impl Engine<'_> {
)) ))
} }
/// Chain-evaluate a dot setter /// Chain-evaluate a dot setter.
///
/// Either `src` or `target` should be `Some`.
///
/// If `target` is `Some`, then it is taken as the reference to use for `this`.
///
/// Otherwise, if `src` is `Some`, then it holds a name and index into `scope`; using `get_mut` on
/// `scope` can retrieve a mutable reference to the variable's value to use as `this`.
fn get_dot_val_helper( fn get_dot_val_helper(
&mut self, &mut self,
scope: &mut Scope, scope: &mut Scope,
this_ptr: &mut Variant, src: Option<ScopeSource>,
target: Option<&mut Variant>,
dot_rhs: &Expr, dot_rhs: &Expr,
) -> Result<Dynamic, EvalAltResult> { ) -> Result<Dynamic, EvalAltResult> {
// Get the `this` reference. Either `src` or `target` should be `Some`.
fn get_this_ptr<'a>(
scope: &'a mut Scope,
src: Option<ScopeSource>,
target: Option<&'a mut Variant>,
) -> &'a mut Variant {
if let Some(t) = target {
// If `target` is `Some`, then it is returned.
t
} else {
// Otherwise, if `src` is `Some`, then it holds a name and index into `scope`;
// using `get_mut` on `scope` to retrieve a mutable reference for return.
let ScopeSource { name, idx, .. } = src.expect("expected source in scope");
scope.get_mut(name, idx).as_mut()
}
}
match dot_rhs { match dot_rhs {
// xxx.fn_name(args) // xxx.fn_name(args)
Expr::FunctionCall(fn_name, arg_expr_list, def_val, pos) => { Expr::FunctionCall(fn_name, arg_expr_list, def_val, pos) => {
@ -279,6 +304,8 @@ impl Engine<'_> {
.map(|arg_expr| self.eval_expr(scope, arg_expr)) .map(|arg_expr| self.eval_expr(scope, arg_expr))
.collect::<Result<Vec<_>, _>>()?; .collect::<Result<Vec<_>, _>>()?;
let this_ptr = get_this_ptr(scope, src, target);
let args = once(this_ptr) let args = once(this_ptr)
.chain(values.iter_mut().map(Dynamic::as_mut)) .chain(values.iter_mut().map(Dynamic::as_mut))
.collect(); .collect();
@ -289,26 +316,28 @@ impl Engine<'_> {
// xxx.id // xxx.id
Expr::Property(id, pos) => { Expr::Property(id, pos) => {
let get_fn_name = format!("{}{}", FUNC_GETTER, id); let get_fn_name = format!("{}{}", FUNC_GETTER, id);
let this_ptr = get_this_ptr(scope, src, target);
self.call_fn_raw(&get_fn_name, vec![this_ptr], None, *pos) self.call_fn_raw(&get_fn_name, vec![this_ptr], None, *pos)
} }
// xxx.idx_lhs[idx_expr] // xxx.idx_lhs[idx_expr]
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
Expr::Index(idx_lhs, idx_expr, idx_pos) => { Expr::Index(idx_lhs, idx_expr, op_pos) => {
let (val, _) = match idx_lhs.as_ref() { let (val, _) = match idx_lhs.as_ref() {
// xxx.id[idx_expr] // xxx.id[idx_expr]
Expr::Property(id, pos) => { Expr::Property(id, pos) => {
let get_fn_name = format!("{}{}", FUNC_GETTER, id); let get_fn_name = format!("{}{}", FUNC_GETTER, id);
let this_ptr = get_this_ptr(scope, src, target);
( (
self.call_fn_raw(&get_fn_name, vec![this_ptr], None, *pos)?, self.call_fn_raw(&get_fn_name, vec![this_ptr], None, *pos)?,
*pos, *pos,
) )
} }
// xxx.???[???][idx_expr] // xxx.???[???][idx_expr]
Expr::Index(_, _, _) => { Expr::Index(_, _, _) => (
(self.get_dot_val_helper(scope, this_ptr, idx_lhs)?, *idx_pos) self.get_dot_val_helper(scope, src, target, idx_lhs)?,
} *op_pos,
),
// Syntax error // Syntax error
_ => { _ => {
return Err(EvalAltResult::ErrorDotExpr( return Err(EvalAltResult::ErrorDotExpr(
@ -319,7 +348,7 @@ impl Engine<'_> {
}; };
let idx = self.eval_index_value(scope, idx_expr)?; let idx = self.eval_index_value(scope, idx_expr)?;
self.get_indexed_value(&val, idx, idx_expr.position(), *idx_pos) self.get_indexed_value(&val, idx, idx_expr.position(), *op_pos)
.map(|(v, _)| v) .map(|(v, _)| v)
} }
@ -328,26 +357,31 @@ impl Engine<'_> {
// xxx.id.rhs // xxx.id.rhs
Expr::Property(id, pos) => { Expr::Property(id, pos) => {
let get_fn_name = format!("{}{}", FUNC_GETTER, id); let get_fn_name = format!("{}{}", FUNC_GETTER, id);
let this_ptr = get_this_ptr(scope, src, target);
self.call_fn_raw(&get_fn_name, vec![this_ptr], None, *pos) self.call_fn_raw(&get_fn_name, vec![this_ptr], None, *pos)
.and_then(|mut v| self.get_dot_val_helper(scope, v.as_mut(), rhs)) .and_then(|mut v| {
self.get_dot_val_helper(scope, None, Some(v.as_mut()), rhs)
})
} }
// xxx.idx_lhs[idx_expr].rhs // xxx.idx_lhs[idx_expr].rhs
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
Expr::Index(idx_lhs, idx_expr, idx_pos) => { Expr::Index(idx_lhs, idx_expr, op_pos) => {
let (val, _) = match idx_lhs.as_ref() { let (val, _) = match idx_lhs.as_ref() {
// xxx.id[idx_expr].rhs // xxx.id[idx_expr].rhs
Expr::Property(id, pos) => { Expr::Property(id, pos) => {
let get_fn_name = format!("{}{}", FUNC_GETTER, id); let get_fn_name = format!("{}{}", FUNC_GETTER, id);
let this_ptr = get_this_ptr(scope, src, target);
( (
self.call_fn_raw(&get_fn_name, vec![this_ptr], None, *pos)?, self.call_fn_raw(&get_fn_name, vec![this_ptr], None, *pos)?,
*pos, *pos,
) )
} }
// xxx.???[???][idx_expr].rhs // xxx.???[???][idx_expr].rhs
Expr::Index(_, _, _) => { Expr::Index(_, _, _) => (
(self.get_dot_val_helper(scope, this_ptr, idx_lhs)?, *idx_pos) self.get_dot_val_helper(scope, src, target, idx_lhs)?,
} *op_pos,
),
// Syntax error // Syntax error
_ => { _ => {
return Err(EvalAltResult::ErrorDotExpr( return Err(EvalAltResult::ErrorDotExpr(
@ -358,8 +392,10 @@ impl Engine<'_> {
}; };
let idx = self.eval_index_value(scope, idx_expr)?; let idx = self.eval_index_value(scope, idx_expr)?;
self.get_indexed_value(&val, idx, idx_expr.position(), *idx_pos) self.get_indexed_value(&val, idx, idx_expr.position(), *op_pos)
.and_then(|(mut v, _)| self.get_dot_val_helper(scope, v.as_mut(), rhs)) .and_then(|(mut v, _)| {
self.get_dot_val_helper(scope, None, Some(v.as_mut()), rhs)
})
} }
// Syntax error // Syntax error
_ => Err(EvalAltResult::ErrorDotExpr( _ => Err(EvalAltResult::ErrorDotExpr(
@ -386,28 +422,33 @@ impl Engine<'_> {
match dot_lhs { match dot_lhs {
// id.??? // id.???
Expr::Variable(id, pos) => { Expr::Variable(id, pos) => {
let (src_idx, _, mut target) = Self::search_scope(scope, id, Ok, *pos)?; let (ScopeSource { idx, var_type, .. }, _) =
let val = self.get_dot_val_helper(scope, target.as_mut(), dot_rhs); Self::search_scope(scope, id, Ok, *pos)?;
// In case the expression mutated `target`, we need to update it back into the scope because it is cloned. // This is a variable property access (potential function call).
*scope.get_mut(id, src_idx) = target; // Use a direct index into `scope` to directly mutate the variable value.
let src = ScopeSource {
name: id,
idx,
var_type,
};
val self.get_dot_val_helper(scope, Some(src), None, dot_rhs)
} }
// idx_lhs[idx_expr].??? // idx_lhs[idx_expr].???
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
Expr::Index(idx_lhs, idx_expr, idx_pos) => { Expr::Index(idx_lhs, idx_expr, op_pos) => {
let (src_type, src, idx, mut target) = let (src_type, src, idx, mut target) =
self.eval_index_expr(scope, idx_lhs, idx_expr, *idx_pos)?; self.eval_index_expr(scope, idx_lhs, idx_expr, *op_pos)?;
let val = self.get_dot_val_helper(scope, target.as_mut(), dot_rhs); let val = self.get_dot_val_helper(scope, None, Some(target.as_mut()), dot_rhs);
// In case the expression mutated `target`, we need to update it back into the scope because it is cloned. // In case the expression mutated `target`, we need to update it back into the scope because it is cloned.
if let Some((id, var_type, src_idx)) = src { if let Some(src) = src {
match var_type { match src.var_type {
VariableType::Constant => { VariableType::Constant => {
return Err(EvalAltResult::ErrorAssignmentToConstant( return Err(EvalAltResult::ErrorAssignmentToConstant(
id.to_string(), src.name.to_string(),
idx_lhs.position(), idx_lhs.position(),
)); ));
} }
@ -415,11 +456,9 @@ impl Engine<'_> {
Self::update_indexed_var_in_scope( Self::update_indexed_var_in_scope(
src_type, src_type,
scope, scope,
id, src,
src_idx,
idx, idx,
target, (target, dot_rhs.position()),
dot_rhs.position(),
)?; )?;
} }
} }
@ -431,22 +470,22 @@ impl Engine<'_> {
// {expr}.??? // {expr}.???
expr => { expr => {
let mut target = self.eval_expr(scope, expr)?; let mut target = self.eval_expr(scope, expr)?;
self.get_dot_val_helper(scope, target.as_mut(), dot_rhs) self.get_dot_val_helper(scope, None, Some(target.as_mut()), dot_rhs)
} }
} }
} }
/// Search for a variable within the scope, returning its value and index inside the Scope /// Search for a variable within the scope, returning its value and index inside the Scope
fn search_scope<T>( fn search_scope<'a, T>(
scope: &Scope, scope: &'a Scope,
id: &str, id: &str,
map: impl FnOnce(Dynamic) -> Result<T, EvalAltResult>, convert: impl FnOnce(Dynamic) -> Result<T, EvalAltResult>,
begin: Position, begin: Position,
) -> Result<(usize, VariableType, T), EvalAltResult> { ) -> Result<(ScopeSource<'a>, T), EvalAltResult> {
scope scope
.get(id) .get(id)
.ok_or_else(|| EvalAltResult::ErrorVariableNotFound(id.into(), begin)) .ok_or_else(|| EvalAltResult::ErrorVariableNotFound(id.into(), begin))
.and_then(move |(idx, _, var_type, val)| map(val).map(|v| (idx, var_type, v))) .and_then(move |(src, value)| convert(value).map(|v| (src, v)))
} }
/// Evaluate the value of an index (must evaluate to INT) /// Evaluate the value of an index (must evaluate to INT)
@ -468,8 +507,8 @@ impl Engine<'_> {
&self, &self,
val: &Dynamic, val: &Dynamic,
idx: INT, idx: INT,
val_pos: Position,
idx_pos: Position, idx_pos: Position,
op_pos: Position,
) -> Result<(Dynamic, IndexSourceType), EvalAltResult> { ) -> Result<(Dynamic, IndexSourceType), EvalAltResult> {
if val.is::<Array>() { if val.is::<Array>() {
// val_array[idx] // val_array[idx]
@ -479,9 +518,9 @@ impl Engine<'_> {
arr.get(idx as usize) arr.get(idx as usize)
.cloned() .cloned()
.map(|v| (v, IndexSourceType::Array)) .map(|v| (v, IndexSourceType::Array))
.ok_or_else(|| EvalAltResult::ErrorArrayBounds(arr.len(), idx, val_pos)) .ok_or_else(|| EvalAltResult::ErrorArrayBounds(arr.len(), idx, idx_pos))
} else { } else {
Err(EvalAltResult::ErrorArrayBounds(arr.len(), idx, val_pos)) Err(EvalAltResult::ErrorArrayBounds(arr.len(), idx, idx_pos))
} }
} else if val.is::<String>() { } else if val.is::<String>() {
// val_string[idx] // val_string[idx]
@ -492,20 +531,20 @@ impl Engine<'_> {
.nth(idx as usize) .nth(idx as usize)
.map(|ch| (ch.into_dynamic(), IndexSourceType::String)) .map(|ch| (ch.into_dynamic(), IndexSourceType::String))
.ok_or_else(|| { .ok_or_else(|| {
EvalAltResult::ErrorStringBounds(s.chars().count(), idx, val_pos) EvalAltResult::ErrorStringBounds(s.chars().count(), idx, idx_pos)
}) })
} else { } else {
Err(EvalAltResult::ErrorStringBounds( Err(EvalAltResult::ErrorStringBounds(
s.chars().count(), s.chars().count(),
idx, idx,
val_pos, idx_pos,
)) ))
} }
} else { } else {
// Error - cannot be indexed // Error - cannot be indexed
Err(EvalAltResult::ErrorIndexingType( Err(EvalAltResult::ErrorIndexingType(
self.map_type_name(val.type_name()).to_string(), self.map_type_name(val.type_name()).to_string(),
idx_pos, op_pos,
)) ))
} }
} }
@ -517,16 +556,8 @@ impl Engine<'_> {
scope: &mut Scope, scope: &mut Scope,
lhs: &'a Expr, lhs: &'a Expr,
idx_expr: &Expr, idx_expr: &Expr,
idx_pos: Position, op_pos: Position,
) -> Result< ) -> Result<(IndexSourceType, Option<ScopeSource<'a>>, usize, Dynamic), EvalAltResult> {
(
IndexSourceType,
Option<(&'a str, VariableType, usize)>,
usize,
Dynamic,
),
EvalAltResult,
> {
let idx = self.eval_index_value(scope, idx_expr)?; let idx = self.eval_index_value(scope, idx_expr)?;
match lhs { match lhs {
@ -534,13 +565,17 @@ impl Engine<'_> {
Expr::Variable(id, _) => Self::search_scope( Expr::Variable(id, _) => Self::search_scope(
scope, scope,
&id, &id,
|val| self.get_indexed_value(&val, idx, idx_expr.position(), idx_pos), |val| self.get_indexed_value(&val, idx, idx_expr.position(), op_pos),
lhs.position(), lhs.position(),
) )
.map(|(src_idx, var_type, (val, src_type))| { .map(|(src, (val, src_type))| {
( (
src_type, src_type,
Some((id.as_str(), var_type, src_idx)), Some(ScopeSource {
name: &id,
var_type: src.var_type,
idx: src.idx,
}),
idx as usize, idx as usize,
val, val,
) )
@ -550,7 +585,7 @@ impl Engine<'_> {
expr => { expr => {
let val = self.eval_expr(scope, expr)?; let val = self.eval_expr(scope, expr)?;
self.get_indexed_value(&val, idx, idx_expr.position(), idx_pos) self.get_indexed_value(&val, idx, idx_expr.position(), op_pos)
.map(|(v, _)| (IndexSourceType::Expression, None, idx as usize, v)) .map(|(v, _)| (IndexSourceType::Expression, None, idx as usize, v))
} }
} }
@ -575,26 +610,26 @@ impl Engine<'_> {
fn update_indexed_var_in_scope( fn update_indexed_var_in_scope(
src_type: IndexSourceType, src_type: IndexSourceType,
scope: &mut Scope, scope: &mut Scope,
id: &str, src: ScopeSource,
src_idx: usize,
idx: usize, idx: usize,
new_val: Dynamic, new_val: (Dynamic, Position),
val_pos: Position,
) -> Result<Dynamic, EvalAltResult> { ) -> Result<Dynamic, EvalAltResult> {
match src_type { match src_type {
// array_id[idx] = val // array_id[idx] = val
IndexSourceType::Array => { IndexSourceType::Array => {
let arr = scope.get_mut_by_type::<Array>(id, src_idx); let arr = scope.get_mut_by_type::<Array>(src.name, src.idx);
Ok((arr[idx as usize] = new_val).into_dynamic()) Ok((arr[idx as usize] = new_val.0).into_dynamic())
} }
// string_id[idx] = val // string_id[idx] = val
IndexSourceType::String => { IndexSourceType::String => {
let s = scope.get_mut_by_type::<String>(id, src_idx); let s = scope.get_mut_by_type::<String>(src.name, src.idx);
let pos = new_val.1;
// Value must be a character // Value must be a character
let ch = *new_val let ch = *new_val
.0
.downcast::<char>() .downcast::<char>()
.map_err(|_| EvalAltResult::ErrorCharMismatch(val_pos))?; .map_err(|_| EvalAltResult::ErrorCharMismatch(pos))?;
Ok(Self::str_replace_char(s, idx as usize, ch).into_dynamic()) Ok(Self::str_replace_char(s, idx as usize, ch).into_dynamic())
} }
@ -634,21 +669,20 @@ impl Engine<'_> {
scope: &mut Scope, scope: &mut Scope,
this_ptr: &mut Variant, this_ptr: &mut Variant,
dot_rhs: &Expr, dot_rhs: &Expr,
mut new_val: Dynamic, new_val: (&mut Dynamic, Position),
val_pos: Position,
) -> Result<Dynamic, EvalAltResult> { ) -> Result<Dynamic, EvalAltResult> {
match dot_rhs { match dot_rhs {
// xxx.id // xxx.id
Expr::Property(id, pos) => { Expr::Property(id, pos) => {
let set_fn_name = format!("{}{}", FUNC_SETTER, id); 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(&set_fn_name, vec![this_ptr, new_val.0.as_mut()], None, *pos)
} }
// xxx.lhs[idx_expr] // xxx.lhs[idx_expr]
// TODO - Allow chaining of indexing! // TODO - Allow chaining of indexing!
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
Expr::Index(lhs, idx_expr, idx_pos) => match lhs.as_ref() { Expr::Index(lhs, idx_expr, op_pos) => match lhs.as_ref() {
// xxx.id[idx_expr] // xxx.id[idx_expr]
Expr::Property(id, pos) => { Expr::Property(id, pos) => {
let get_fn_name = format!("{}{}", FUNC_GETTER, id); let get_fn_name = format!("{}{}", FUNC_GETTER, id);
@ -656,7 +690,12 @@ impl Engine<'_> {
self.call_fn_raw(&get_fn_name, vec![this_ptr], None, *pos) self.call_fn_raw(&get_fn_name, vec![this_ptr], None, *pos)
.and_then(|v| { .and_then(|v| {
let idx = self.eval_index_value(scope, idx_expr)?; let idx = self.eval_index_value(scope, idx_expr)?;
Self::update_indexed_value(v, idx as usize, new_val, val_pos) Self::update_indexed_value(
v,
idx as usize,
new_val.0.clone(),
new_val.1,
)
}) })
.and_then(|mut v| { .and_then(|mut v| {
let set_fn_name = format!("{}{}", FUNC_SETTER, id); let set_fn_name = format!("{}{}", FUNC_SETTER, id);
@ -667,7 +706,7 @@ impl Engine<'_> {
// All others - syntax error for setters chain // All others - syntax error for setters chain
_ => Err(EvalAltResult::ErrorDotExpr( _ => Err(EvalAltResult::ErrorDotExpr(
"for assignment".to_string(), "for assignment".to_string(),
*idx_pos, *op_pos,
)), )),
}, },
@ -679,7 +718,7 @@ impl Engine<'_> {
self.call_fn_raw(&get_fn_name, vec![this_ptr], None, *pos) self.call_fn_raw(&get_fn_name, vec![this_ptr], None, *pos)
.and_then(|mut v| { .and_then(|mut v| {
self.set_dot_val_helper(scope, v.as_mut(), rhs, new_val, val_pos) self.set_dot_val_helper(scope, v.as_mut(), rhs, new_val)
.map(|_| v) // Discard Ok return value .map(|_| v) // Discard Ok return value
}) })
.and_then(|mut v| { .and_then(|mut v| {
@ -692,7 +731,7 @@ impl Engine<'_> {
// xxx.lhs[idx_expr].rhs // xxx.lhs[idx_expr].rhs
// TODO - Allow chaining of indexing! // TODO - Allow chaining of indexing!
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
Expr::Index(lhs, idx_expr, idx_pos) => match lhs.as_ref() { Expr::Index(lhs, idx_expr, op_pos) => match lhs.as_ref() {
// xxx.id[idx_expr].rhs // xxx.id[idx_expr].rhs
Expr::Property(id, pos) => { Expr::Property(id, pos) => {
let get_fn_name = format!("{}{}", FUNC_GETTER, id); let get_fn_name = format!("{}{}", FUNC_GETTER, id);
@ -701,15 +740,10 @@ impl Engine<'_> {
.and_then(|v| { .and_then(|v| {
let idx = self.eval_index_value(scope, idx_expr)?; let idx = self.eval_index_value(scope, idx_expr)?;
let (mut target, _) = let (mut target, _) =
self.get_indexed_value(&v, idx, idx_expr.position(), *idx_pos)?; self.get_indexed_value(&v, idx, idx_expr.position(), *op_pos)?;
self.set_dot_val_helper( let val_pos = new_val.1;
scope, self.set_dot_val_helper(scope, target.as_mut(), rhs, new_val)?;
target.as_mut(),
rhs,
new_val,
val_pos,
)?;
// In case the expression mutated `target`, we need to update it back into the scope because it is cloned. // In case the expression mutated `target`, we need to update it back into the scope because it is cloned.
Self::update_indexed_value(v, idx as usize, target, val_pos) Self::update_indexed_value(v, idx as usize, target, val_pos)
@ -729,7 +763,7 @@ impl Engine<'_> {
// All others - syntax error for setters chain // All others - syntax error for setters chain
_ => Err(EvalAltResult::ErrorDotExpr( _ => Err(EvalAltResult::ErrorDotExpr(
"for assignment".to_string(), "for assignment".to_string(),
*idx_pos, *op_pos,
)), )),
}, },
@ -754,14 +788,14 @@ impl Engine<'_> {
scope: &mut Scope, scope: &mut Scope,
dot_lhs: &Expr, dot_lhs: &Expr,
dot_rhs: &Expr, dot_rhs: &Expr,
new_val: Dynamic, new_val: (&mut Dynamic, Position),
val_pos: Position,
op_pos: Position, op_pos: Position,
) -> Result<Dynamic, EvalAltResult> { ) -> Result<Dynamic, EvalAltResult> {
match dot_lhs { match dot_lhs {
// id.??? // id.???
Expr::Variable(id, pos) => { Expr::Variable(id, pos) => {
let (src_idx, var_type, mut target) = Self::search_scope(scope, id, Ok, *pos)?; let (ScopeSource { idx, var_type, .. }, mut target) =
Self::search_scope(scope, id, Ok, *pos)?;
match var_type { match var_type {
VariableType::Constant => { VariableType::Constant => {
@ -773,11 +807,10 @@ impl Engine<'_> {
_ => (), _ => (),
} }
let val = let val = self.set_dot_val_helper(scope, target.as_mut(), dot_rhs, new_val);
self.set_dot_val_helper(scope, target.as_mut(), dot_rhs, new_val, val_pos);
// In case the expression mutated `target`, we need to update it back into the scope because it is cloned. // In case the expression mutated `target`, we need to update it back into the scope because it is cloned.
*scope.get_mut(id, src_idx) = target; *scope.get_mut(id, idx) = target;
val val
} }
@ -785,24 +818,28 @@ impl Engine<'_> {
// lhs[idx_expr].??? // lhs[idx_expr].???
// TODO - Allow chaining of indexing! // TODO - Allow chaining of indexing!
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
Expr::Index(lhs, idx_expr, idx_pos) => { Expr::Index(lhs, idx_expr, op_pos) => {
let (src_type, src, idx, mut target) = let (src_type, src, idx, mut target) =
self.eval_index_expr(scope, lhs, idx_expr, *idx_pos)?; self.eval_index_expr(scope, lhs, idx_expr, *op_pos)?;
let val = let val_pos = new_val.1;
self.set_dot_val_helper(scope, target.as_mut(), dot_rhs, new_val, val_pos); let val = self.set_dot_val_helper(scope, target.as_mut(), dot_rhs, new_val);
// In case the expression mutated `target`, we need to update it back into the scope because it is cloned. // In case the expression mutated `target`, we need to update it back into the scope because it is cloned.
if let Some((id, var_type, src_idx)) = src { if let Some(src) = src {
match var_type { match src.var_type {
VariableType::Constant => { VariableType::Constant => {
return Err(EvalAltResult::ErrorAssignmentToConstant( return Err(EvalAltResult::ErrorAssignmentToConstant(
id.to_string(), src.name.to_string(),
lhs.position(), lhs.position(),
)); ));
} }
VariableType::Normal => { VariableType::Normal => {
Self::update_indexed_var_in_scope( Self::update_indexed_var_in_scope(
src_type, scope, id, src_idx, idx, target, val_pos, src_type,
scope,
src,
idx,
(target, val_pos),
)?; )?;
} }
} }
@ -828,15 +865,13 @@ impl Engine<'_> {
Expr::IntegerConstant(i, _) => Ok(i.into_dynamic()), Expr::IntegerConstant(i, _) => Ok(i.into_dynamic()),
Expr::StringConstant(s, _) => Ok(s.into_dynamic()), Expr::StringConstant(s, _) => Ok(s.into_dynamic()),
Expr::CharConstant(c, _) => Ok(c.into_dynamic()), Expr::CharConstant(c, _) => Ok(c.into_dynamic()),
Expr::Variable(id, pos) => { Expr::Variable(id, pos) => Self::search_scope(scope, id, Ok, *pos).map(|(_, val)| val),
Self::search_scope(scope, id, Ok, *pos).map(|(_, _, val)| val)
}
Expr::Property(_, _) => panic!("unexpected property."), Expr::Property(_, _) => panic!("unexpected property."),
// lhs[idx_expr] // lhs[idx_expr]
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
Expr::Index(lhs, idx_expr, idx_pos) => self Expr::Index(lhs, idx_expr, op_pos) => self
.eval_index_expr(scope, lhs, idx_expr, *idx_pos) .eval_index_expr(scope, lhs, idx_expr, *op_pos)
.map(|(_, _, _, x)| x), .map(|(_, _, _, x)| x),
// Statement block // Statement block
@ -844,43 +879,55 @@ impl Engine<'_> {
// lhs = rhs // lhs = rhs
Expr::Assignment(lhs, rhs, op_pos) => { Expr::Assignment(lhs, rhs, op_pos) => {
let rhs_val = self.eval_expr(scope, rhs)?; let mut rhs_val = self.eval_expr(scope, rhs)?;
match lhs.as_ref() { match lhs.as_ref() {
// name = rhs // name = rhs
Expr::Variable(name, pos) => match scope.get(name) { Expr::Variable(name, pos) => match scope.get(name) {
Some((idx, _, VariableType::Normal, _)) => { Some((
ScopeSource {
idx,
var_type: VariableType::Normal,
..
},
_,
)) => {
*scope.get_mut(name, idx) = rhs_val.clone(); *scope.get_mut(name, idx) = rhs_val.clone();
Ok(rhs_val) Ok(rhs_val)
} }
Some((_, _, VariableType::Constant, _)) => Err( Some((
EvalAltResult::ErrorAssignmentToConstant(name.to_string(), *op_pos), ScopeSource {
), var_type: VariableType::Constant,
..
},
_,
)) => Err(EvalAltResult::ErrorAssignmentToConstant(
name.to_string(),
*op_pos,
)),
_ => Err(EvalAltResult::ErrorVariableNotFound(name.clone(), *pos)), _ => Err(EvalAltResult::ErrorVariableNotFound(name.clone(), *pos)),
}, },
// idx_lhs[idx_expr] = rhs // idx_lhs[idx_expr] = rhs
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
Expr::Index(idx_lhs, idx_expr, idx_pos) => { Expr::Index(idx_lhs, idx_expr, op_pos) => {
let (src_type, src, idx, _) = let (src_type, src, idx, _) =
self.eval_index_expr(scope, idx_lhs, idx_expr, *idx_pos)?; self.eval_index_expr(scope, idx_lhs, idx_expr, *op_pos)?;
if let Some((id, var_type, src_idx)) = src { if let Some(src) = src {
match var_type { match src.var_type {
VariableType::Constant => { VariableType::Constant => {
return Err(EvalAltResult::ErrorAssignmentToConstant( return Err(EvalAltResult::ErrorAssignmentToConstant(
id.to_string(), src.name.to_string(),
idx_lhs.position(), idx_lhs.position(),
)); ));
} }
VariableType::Normal => Ok(Self::update_indexed_var_in_scope( VariableType::Normal => Ok(Self::update_indexed_var_in_scope(
src_type, src_type,
scope, scope,
&id, src,
src_idx,
idx, idx,
rhs_val, (rhs_val, rhs.position()),
rhs.position(),
)?), )?),
} }
} else { } else {
@ -891,9 +938,13 @@ impl Engine<'_> {
} }
// dot_lhs.dot_rhs = rhs // dot_lhs.dot_rhs = rhs
Expr::Dot(dot_lhs, dot_rhs, _) => { Expr::Dot(dot_lhs, dot_rhs, _) => self.set_dot_val(
self.set_dot_val(scope, dot_lhs, dot_rhs, rhs_val, rhs.position(), *op_pos) scope,
} dot_lhs,
dot_rhs,
(&mut rhs_val, rhs.position()),
*op_pos,
),
// Error assignment to constant // Error assignment to constant
expr if expr.is_constant() => Err(EvalAltResult::ErrorAssignmentToConstant( expr if expr.is_constant() => Err(EvalAltResult::ErrorAssignmentToConstant(
@ -919,8 +970,24 @@ impl Engine<'_> {
Ok(Box::new(arr)) Ok(Box::new(arr))
} }
Expr::FunctionCall(fn_name, args_expr_list, def_val, pos) => {
// Has a system function an override?
fn has_override(engine: &Engine, name: &str) -> bool {
let spec = FnSpec {
name: name.into(),
args: Some(vec![TypeId::of::<String>()]),
};
engine.ext_functions.contains_key(&spec)
|| engine
.script_functions
.binary_search_by(|f| f.compare(name, 1))
.is_ok()
}
match fn_name.as_str() {
// Dump AST // Dump AST
Expr::FunctionCall(fn_name, args_expr_list, _, pos) if fn_name == KEYWORD_DUMP_AST => { KEYWORD_DUMP_AST => {
let pos = if args_expr_list.len() == 0 { let pos = if args_expr_list.len() == 0 {
*pos *pos
} else { } else {
@ -943,8 +1010,58 @@ impl Engine<'_> {
) )
} }
// type_of
KEYWORD_TYPE_OF
if args_expr_list.len() == 1 && !has_override(self, KEYWORD_TYPE_OF) =>
{
let r = self.eval_expr(scope, &args_expr_list[0])?;
Ok(self
.map_type_name((*r).type_name())
.to_string()
.into_dynamic())
}
// eval
KEYWORD_EVAL
if args_expr_list.len() == 1 && !has_override(self, KEYWORD_EVAL) =>
{
let pos = args_expr_list[0].position();
let r = self.eval_expr(scope, &args_expr_list[0])?;
let script =
r.downcast_ref::<String>()
.map(String::as_str)
.ok_or_else(|| {
EvalAltResult::ErrorMismatchOutputType(
r.type_name().into(),
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
},
)?);
}
// Normal function call // Normal function call
Expr::FunctionCall(fn_name, args_expr_list, def_val, pos) => { _ => {
let mut values = args_expr_list let mut values = args_expr_list
.iter() .iter()
.map(|expr| self.eval_expr(scope, expr)) .map(|expr| self.eval_expr(scope, expr))
@ -957,6 +1074,8 @@ impl Engine<'_> {
*pos, *pos,
) )
} }
}
}
Expr::And(lhs, rhs) => Ok(Box::new( Expr::And(lhs, rhs) => Ok(Box::new(
*self *self
@ -1040,7 +1159,7 @@ impl Engine<'_> {
Stmt::IfElse(guard, if_body, else_body) => self Stmt::IfElse(guard, if_body, else_body) => self
.eval_expr(scope, guard)? .eval_expr(scope, guard)?
.downcast::<bool>() .downcast::<bool>()
.map_err(|_| EvalAltResult::ErrorIfGuard(guard.position())) .map_err(|_| EvalAltResult::ErrorLogicGuard(guard.position()))
.and_then(|guard_val| { .and_then(|guard_val| {
if *guard_val { if *guard_val {
self.eval_stmt(scope, if_body) self.eval_stmt(scope, if_body)
@ -1067,7 +1186,7 @@ impl Engine<'_> {
return Ok(().into_dynamic()); return Ok(().into_dynamic());
} }
} }
Err(_) => return Err(EvalAltResult::ErrorIfGuard(guard.position())), Err(_) => return Err(EvalAltResult::ErrorLogicGuard(guard.position())),
} }
}, },

View File

@ -47,7 +47,7 @@ pub enum ParseErrorType {
/// Error in the script text. Wrapped value is the error message. /// Error in the script text. Wrapped value is the error message.
BadInput(String), BadInput(String),
/// The script ends prematurely. /// The script ends prematurely.
InputPastEndOfFile, UnexpectedEOF,
/// An unknown operator is encountered. Wrapped value is the operator. /// An unknown operator is encountered. Wrapped value is the operator.
UnknownOperator(String), UnknownOperator(String),
/// An open `(` is missing the corresponding closing `)`. /// An open `(` is missing the corresponding closing `)`.
@ -72,6 +72,8 @@ pub enum ParseErrorType {
ForbiddenConstantExpr(String), ForbiddenConstantExpr(String),
/// Missing a variable name after the `let`, `const` or `for` keywords. /// Missing a variable name after the `let`, `const` or `for` keywords.
VariableExpected, VariableExpected,
/// Missing an expression.
ExprExpected(String),
/// A `for` statement is missing the `in` keyword. /// A `for` statement is missing the `in` keyword.
MissingIn, MissingIn,
/// Defining a function `fn` in an appropriate place (e.g. inside another function). /// Defining a function `fn` in an appropriate place (e.g. inside another function).
@ -119,7 +121,7 @@ impl ParseError {
pub(crate) fn desc(&self) -> &str { pub(crate) fn desc(&self) -> &str {
match self.0 { match self.0 {
ParseErrorType::BadInput(ref p) => p, ParseErrorType::BadInput(ref p) => p,
ParseErrorType::InputPastEndOfFile => "Script is incomplete", ParseErrorType::UnexpectedEOF => "Script is incomplete",
ParseErrorType::UnknownOperator(_) => "Unknown operator", ParseErrorType::UnknownOperator(_) => "Unknown operator",
ParseErrorType::MissingRightParen(_) => "Expecting ')'", ParseErrorType::MissingRightParen(_) => "Expecting ')'",
ParseErrorType::MissingLeftBrace => "Expecting '{'", ParseErrorType::MissingLeftBrace => "Expecting '{'",
@ -134,6 +136,7 @@ impl ParseError {
ParseErrorType::ForbiddenConstantExpr(_) => "Expecting a constant", ParseErrorType::ForbiddenConstantExpr(_) => "Expecting a constant",
ParseErrorType::MissingIn => "Expecting 'in'", ParseErrorType::MissingIn => "Expecting 'in'",
ParseErrorType::VariableExpected => "Expecting name of a variable", ParseErrorType::VariableExpected => "Expecting name of a variable",
ParseErrorType::ExprExpected(_) => "Expecting an expression",
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]
ParseErrorType::FnMissingName => "Expecting name in function declaration", ParseErrorType::FnMissingName => "Expecting name in function declaration",
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]
@ -168,6 +171,8 @@ impl fmt::Display for ParseError {
write!(f, "{}", if s.is_empty() { self.desc() } else { s })? write!(f, "{}", if s.is_empty() { self.desc() } else { s })?
} }
ParseErrorType::ExprExpected(ref s) => write!(f, "Expecting {} expression", s)?,
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]
ParseErrorType::FnMissingParams(ref s) => { ParseErrorType::FnMissingParams(ref s) => {
write!(f, "Expecting parameters for function '{}'", s)? write!(f, "Expecting parameters for function '{}'", s)?

View File

@ -2,7 +2,8 @@
use crate::any::{Any, Dynamic}; use crate::any::{Any, Dynamic};
use crate::engine::{ 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::parser::{map_dynamic_to_expr, Expr, FnDef, ReturnType, Stmt, AST};
use crate::scope::{Scope, ScopeEntry, VariableType}; 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. /// Optimize an expression.
fn optimize_expr<'a>(expr: Expr, state: &mut State<'a>) -> Expr { fn optimize_expr<'a>(expr: Expr, state: &mut State<'a>) -> Expr {
// These keywords are handled specially // 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 { match expr {
// ( stmt ) // ( stmt )
@ -431,15 +432,25 @@ fn optimize_expr<'a>(expr: Expr, state: &mut State<'a>) -> Expr {
), ),
}, },
// Do not optimize anything within built-in function keywords // Do not optimize anything within dump_ast
Expr::FunctionCall(id, args, def_value, pos) if SKIP_FUNC_KEYWORDS.contains(&id.as_str())=> Expr::FunctionCall(id, args, def_value, pos) if id == KEYWORD_DUMP_AST =>
Expr::FunctionCall(id, args, def_value, pos), 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 // Eagerly call functions
Expr::FunctionCall(id, args, def_value, pos) Expr::FunctionCall(id, args, def_value, pos)
if state.engine.optimization_level == OptimizationLevel::Full // full optimizations if state.engine.optimization_level == OptimizationLevel::Full // full optimizations
&& args.iter().all(|expr| expr.is_constant()) // all arguments are constants && args.iter().all(|expr| expr.is_constant()) // all arguments are constants
=> { => {
// First search in script-defined functions (can override built-in)
if state.engine.script_functions.binary_search_by(|f| f.compare(&id, args.len())).is_ok() {
// A script-defined function overrides the built-in function - do not make the call
return Expr::FunctionCall(id, args.into_iter().map(|a| optimize_expr(a, state)).collect(), def_value, pos);
}
let mut arg_values: Vec<_> = args.iter().map(Expr::get_constant_value).collect(); let mut arg_values: Vec<_> = args.iter().map(Expr::get_constant_value).collect();
let call_args: FnCallArgs = arg_values.iter_mut().map(Dynamic::as_mut).collect(); let call_args: FnCallArgs = arg_values.iter_mut().map(Dynamic::as_mut).collect();
@ -467,19 +478,19 @@ fn optimize_expr<'a>(expr: Expr, state: &mut State<'a>) -> Expr {
}) })
).flatten().unwrap_or_else(|| Expr::FunctionCall(id, args, def_value, pos)) ).flatten().unwrap_or_else(|| Expr::FunctionCall(id, args, def_value, pos))
} }
// id(args ..) -> optimize function call arguments // id(args ..) -> optimize function call arguments
Expr::FunctionCall(id, args, def_value, pos) => 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 // constant-name
Expr::Variable(ref name, _) if state.contains_constant(name) => { Expr::Variable(ref name, _) if state.contains_constant(name) => {
state.set_dirty(); state.set_dirty();
// Replace constant with value // Replace constant with value
state state.find_constant(name).expect("should find constant in scope!").clone()
.find_constant(name)
.expect("should find constant in scope!")
.clone()
} }
// All other expressions - skip // All other expressions - skip
expr => expr, expr => expr,
} }

View File

@ -469,6 +469,10 @@ pub enum Token {
UnaryMinus, UnaryMinus,
Multiply, Multiply,
Divide, Divide,
Modulo,
PowerOf,
LeftShift,
RightShift,
SemiColon, SemiColon,
Colon, Colon,
Comma, Comma,
@ -482,15 +486,18 @@ pub enum Token {
Else, Else,
While, While,
Loop, Loop,
For,
In,
LessThan, LessThan,
GreaterThan, GreaterThan,
Bang,
LessThanEqualsTo, LessThanEqualsTo,
GreaterThanEqualsTo, GreaterThanEqualsTo,
EqualsTo, EqualsTo,
NotEqualsTo, NotEqualsTo,
Bang,
Pipe, Pipe,
Or, Or,
XOr,
Ampersand, Ampersand,
And, And,
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]
@ -507,15 +514,8 @@ pub enum Token {
AndAssign, AndAssign,
OrAssign, OrAssign,
XOrAssign, XOrAssign,
LeftShift,
RightShift,
XOr,
Modulo,
ModuloAssign, ModuloAssign,
PowerOf,
PowerOfAssign, PowerOfAssign,
For,
In,
LexError(LexError), LexError(LexError),
} }
@ -1601,23 +1601,21 @@ fn parse_array_literal<'a>(
/// Parse a primary expression. /// Parse a primary expression.
fn parse_primary<'a>(input: &mut Peekable<TokenIterator<'a>>) -> Result<Expr, ParseError> { fn parse_primary<'a>(input: &mut Peekable<TokenIterator<'a>>) -> Result<Expr, ParseError> {
let token = match input
.peek()
.ok_or_else(|| ParseError::new(PERR::UnexpectedEOF, Position::eof()))?
{
// { - block statement as expression // { - block statement as expression
match input.peek() { (Token::LeftBrace, pos) => {
Some((Token::LeftBrace, pos)) => {
let pos = *pos; let pos = *pos;
return parse_block(input, false).map(|block| Expr::Stmt(Box::new(block), pos)); return parse_block(input, false).map(|block| Expr::Stmt(Box::new(block), pos));
} }
_ => (), _ => input.next().expect("should be a token"),
} };
let token = input.next();
let mut can_be_indexed = false; let mut can_be_indexed = false;
#[allow(unused_mut)] let mut root_expr = match token {
let mut root_expr = match token
.ok_or_else(|| ParseError::new(PERR::InputPastEndOfFile, Position::eof()))?
{
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
(Token::FloatConstant(x), pos) => Ok(Expr::FloatConstant(x, pos)), (Token::FloatConstant(x), pos) => Ok(Expr::FloatConstant(x, pos)),
@ -1666,7 +1664,7 @@ fn parse_primary<'a>(input: &mut Peekable<TokenIterator<'a>>) -> Result<Expr, Pa
fn parse_unary<'a>(input: &mut Peekable<TokenIterator<'a>>) -> Result<Expr, ParseError> { fn parse_unary<'a>(input: &mut Peekable<TokenIterator<'a>>) -> Result<Expr, ParseError> {
match input match input
.peek() .peek()
.ok_or_else(|| ParseError::new(PERR::InputPastEndOfFile, Position::eof()))? .ok_or_else(|| ParseError::new(PERR::UnexpectedEOF, Position::eof()))?
{ {
// -expr // -expr
(Token::UnaryMinus, pos) => { (Token::UnaryMinus, pos) => {
@ -1978,6 +1976,22 @@ fn parse_expr<'a>(input: &mut Peekable<TokenIterator<'a>>) -> Result<Expr, Parse
parse_binary_op(input, 1, lhs) parse_binary_op(input, 1, lhs)
} }
/// Make sure that the expression is not a statement expression (i.e. wrapped in {})
fn ensure_not_statement_expr<'a>(
input: &mut Peekable<TokenIterator<'a>>,
type_name: &str,
) -> Result<(), ParseError> {
match input
.peek()
.ok_or_else(|| ParseError(PERR::ExprExpected(type_name.to_string()), Position::eof()))?
{
// Disallow statement expressions
(Token::LeftBrace, pos) => Err(ParseError(PERR::ExprExpected(type_name.to_string()), *pos)),
// No need to check for others at this time - leave it for the expr parser
_ => Ok(()),
}
}
/// Parse an if statement. /// Parse an if statement.
fn parse_if<'a>( fn parse_if<'a>(
input: &mut Peekable<TokenIterator<'a>>, input: &mut Peekable<TokenIterator<'a>>,
@ -1986,19 +2000,20 @@ fn parse_if<'a>(
// if ... // if ...
input.next(); input.next();
// if guard { body } // if guard { if_body }
ensure_not_statement_expr(input, "a boolean")?;
let guard = parse_expr(input)?; let guard = parse_expr(input)?;
let if_body = parse_block(input, breakable)?; let if_body = parse_block(input, breakable)?;
// if guard { body } else ... // if guard { if_body } else ...
let else_body = if matches!(input.peek(), Some((Token::Else, _))) { let else_body = if matches!(input.peek(), Some((Token::Else, _))) {
input.next(); input.next();
Some(Box::new(if matches!(input.peek(), Some((Token::If, _))) { Some(Box::new(if matches!(input.peek(), Some((Token::If, _))) {
// if guard { body } else if ... // if guard { if_body } else if ...
parse_if(input, breakable)? parse_if(input, breakable)?
} else { } else {
// if guard { body } else { else-body } // if guard { if_body } else { else-body }
parse_block(input, breakable)? parse_block(input, breakable)?
})) }))
} else { } else {
@ -2014,6 +2029,7 @@ fn parse_while<'a>(input: &mut Peekable<TokenIterator<'a>>) -> Result<Stmt, Pars
input.next(); input.next();
// while guard { body } // while guard { body }
ensure_not_statement_expr(input, "a boolean")?;
let guard = parse_expr(input)?; let guard = parse_expr(input)?;
let body = parse_block(input, true)?; let body = parse_block(input, true)?;
@ -2061,6 +2077,7 @@ fn parse_for<'a>(input: &mut Peekable<TokenIterator<'a>>) -> Result<Stmt, ParseE
} }
// for name in expr { body } // for name in expr { body }
ensure_not_statement_expr(input, "a boolean")?;
let expr = parse_expr(input)?; let expr = parse_expr(input)?;
let body = parse_block(input, true)?; let body = parse_block(input, true)?;
@ -2189,10 +2206,12 @@ fn parse_stmt<'a>(
input: &mut Peekable<TokenIterator<'a>>, input: &mut Peekable<TokenIterator<'a>>,
breakable: bool, breakable: bool,
) -> Result<Stmt, ParseError> { ) -> Result<Stmt, ParseError> {
match input let token = match input.peek() {
.peek() Some(token) => token,
.ok_or_else(|| ParseError::new(PERR::InputPastEndOfFile, Position::eof()))? None => return Ok(Stmt::Noop(Position::eof())),
{ };
match token {
// Semicolon - empty statement // Semicolon - empty statement
(Token::SemiColon, pos) => Ok(Stmt::Noop(*pos)), (Token::SemiColon, pos) => Ok(Stmt::Noop(*pos)),
@ -2210,31 +2229,27 @@ fn parse_stmt<'a>(
Ok(Stmt::Break(pos)) Ok(Stmt::Break(pos))
} }
(Token::Break, pos) => return Err(ParseError::new(PERR::LoopBreak, *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 { let return_type = match token {
Token::Return => ReturnType::Return, Token::Return => ReturnType::Return,
Token::Throw => ReturnType::Exception, Token::Throw => ReturnType::Exception,
_ => panic!("token should be return or throw"), _ => panic!("token should be return or throw"),
}; };
let pos = *pos;
input.next(); input.next();
match input.peek() { match input.peek() {
// `return`/`throw` at EOF // `return`/`throw` at EOF
None => Ok(Stmt::ReturnWithVal(None, return_type, Position::eof())), None => Ok(Stmt::ReturnWithVal(None, return_type, Position::eof())),
// `return;` or `throw;` // `return;` or `throw;`
Some((Token::SemiColon, pos)) => { Some((Token::SemiColon, _)) => Ok(Stmt::ReturnWithVal(None, return_type, pos)),
let pos = *pos;
Ok(Stmt::ReturnWithVal(None, return_type, pos))
}
// `return` or `throw` with expression // `return` or `throw` with expression
Some((_, pos)) => { Some((_, _)) => {
let pos = *pos; let expr = parse_expr(input)?;
Ok(Stmt::ReturnWithVal( let pos = expr.position();
Some(Box::new(parse_expr(input)?)),
return_type, Ok(Stmt::ReturnWithVal(Some(Box::new(expr)), return_type, pos))
pos,
))
} }
} }
} }
@ -2248,10 +2263,7 @@ fn parse_stmt<'a>(
/// Parse a function definition. /// Parse a function definition.
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]
fn parse_fn<'a>(input: &mut Peekable<TokenIterator<'a>>) -> Result<FnDef, ParseError> { fn parse_fn<'a>(input: &mut Peekable<TokenIterator<'a>>) -> Result<FnDef, ParseError> {
let pos = input let pos = input.next().expect("should be fn").1;
.next()
.ok_or_else(|| ParseError::new(PERR::InputPastEndOfFile, Position::eof()))?
.1;
let name = match input let name = match input
.next() .next()

View File

@ -40,8 +40,8 @@ pub enum EvalAltResult {
ErrorIndexingType(String, Position), ErrorIndexingType(String, Position),
/// Trying to index into an array or string with an index that is not `i64`. /// Trying to index into an array or string with an index that is not `i64`.
ErrorIndexExpr(Position), ErrorIndexExpr(Position),
/// The guard expression in an `if` statement does not return a boolean value. /// The guard expression in an `if` or `while` statement does not return a boolean value.
ErrorIfGuard(Position), ErrorLogicGuard(Position),
/// The `for` statement encounters a type that is not an iterator. /// The `for` statement encounters a type that is not an iterator.
ErrorFor(Position), ErrorFor(Position),
/// Usage of an unknown variable. Wrapped value is the name of the variable. /// Usage of an unknown variable. Wrapped value is the name of the variable.
@ -93,7 +93,7 @@ impl EvalAltResult {
} }
Self::ErrorStringBounds(0, _, _) => "Indexing of empty string", Self::ErrorStringBounds(0, _, _) => "Indexing of empty string",
Self::ErrorStringBounds(_, _, _) => "String index out of bounds", Self::ErrorStringBounds(_, _, _) => "String index out of bounds",
Self::ErrorIfGuard(_) => "If guard expects boolean expression", Self::ErrorLogicGuard(_) => "Boolean expression expected",
Self::ErrorFor(_) => "For loop expects array or range", Self::ErrorFor(_) => "For loop expects array or range",
Self::ErrorVariableNotFound(_, _) => "Variable not found", Self::ErrorVariableNotFound(_, _) => "Variable not found",
Self::ErrorAssignmentToUnknownLHS(_) => { Self::ErrorAssignmentToUnknownLHS(_) => {
@ -123,7 +123,7 @@ impl fmt::Display for EvalAltResult {
Self::ErrorVariableNotFound(s, pos) => write!(f, "{}: '{}' ({})", desc, s, pos), Self::ErrorVariableNotFound(s, pos) => write!(f, "{}: '{}' ({})", desc, s, pos),
Self::ErrorIndexingType(_, pos) => write!(f, "{} ({})", desc, pos), Self::ErrorIndexingType(_, pos) => write!(f, "{} ({})", desc, pos),
Self::ErrorIndexExpr(pos) => write!(f, "{} ({})", desc, pos), Self::ErrorIndexExpr(pos) => write!(f, "{} ({})", desc, pos),
Self::ErrorIfGuard(pos) => write!(f, "{} ({})", desc, pos), Self::ErrorLogicGuard(pos) => write!(f, "{} ({})", desc, pos),
Self::ErrorFor(pos) => write!(f, "{} ({})", desc, pos), Self::ErrorFor(pos) => write!(f, "{} ({})", desc, pos),
Self::ErrorAssignmentToUnknownLHS(pos) => write!(f, "{} ({})", desc, pos), Self::ErrorAssignmentToUnknownLHS(pos) => write!(f, "{} ({})", desc, pos),
Self::ErrorAssignmentToConstant(s, pos) => write!(f, "{}: '{}' ({})", desc, s, pos), Self::ErrorAssignmentToConstant(s, pos) => write!(f, "{}: '{}' ({})", desc, s, pos),
@ -222,7 +222,7 @@ impl EvalAltResult {
| Self::ErrorStringBounds(_, _, pos) | Self::ErrorStringBounds(_, _, pos)
| Self::ErrorIndexingType(_, pos) | Self::ErrorIndexingType(_, pos)
| Self::ErrorIndexExpr(pos) | Self::ErrorIndexExpr(pos)
| Self::ErrorIfGuard(pos) | Self::ErrorLogicGuard(pos)
| Self::ErrorFor(pos) | Self::ErrorFor(pos)
| Self::ErrorVariableNotFound(_, pos) | Self::ErrorVariableNotFound(_, pos)
| Self::ErrorAssignmentToUnknownLHS(pos) | Self::ErrorAssignmentToUnknownLHS(pos)
@ -250,7 +250,7 @@ impl EvalAltResult {
| Self::ErrorStringBounds(_, _, ref mut pos) | Self::ErrorStringBounds(_, _, ref mut pos)
| Self::ErrorIndexingType(_, ref mut pos) | Self::ErrorIndexingType(_, ref mut pos)
| Self::ErrorIndexExpr(ref mut pos) | Self::ErrorIndexExpr(ref mut pos)
| Self::ErrorIfGuard(ref mut pos) | Self::ErrorLogicGuard(ref mut pos)
| Self::ErrorFor(ref mut pos) | Self::ErrorFor(ref mut pos)
| Self::ErrorVariableNotFound(_, ref mut pos) | Self::ErrorVariableNotFound(_, ref mut pos)
| Self::ErrorAssignmentToUnknownLHS(ref mut pos) | Self::ErrorAssignmentToUnknownLHS(ref mut pos)

View File

@ -31,6 +31,13 @@ pub struct ScopeEntry<'a> {
pub expr: Option<Expr>, pub expr: Option<Expr>,
} }
/// Information about a particular entry in the Scope.
pub(crate) struct ScopeSource<'a> {
pub name: &'a str,
pub idx: usize,
pub var_type: VariableType,
}
/// A type containing information about the current scope. /// A type containing information about the current scope.
/// Useful for keeping state between `Engine` evaluation runs. /// Useful for keeping state between `Engine` evaluation runs.
/// ///
@ -71,7 +78,7 @@ impl<'a> Scope<'a> {
} }
/// Add (push) a new variable to the Scope. /// 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(); let value = value.into_dynamic();
// Map into constant expressions // Map into constant expressions
@ -91,7 +98,7 @@ impl<'a> Scope<'a> {
/// Constants propagation is a technique used to optimize an AST. /// 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: /// 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`. /// `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(); let value = value.into_dynamic();
// Map into constant expressions // Map into constant expressions
@ -139,8 +146,18 @@ impl<'a> Scope<'a> {
self.0.truncate(size); self.0.truncate(size);
} }
/// Does the scope contain the variable?
pub fn contains(&self, key: &str) -> bool {
self.0
.iter()
.enumerate()
.rev() // Always search a Scope in reverse order
.find(|(_, ScopeEntry { name, .. })| name == key)
.is_some()
}
/// Find a variable in the Scope, starting from the last. /// Find a variable in the Scope, starting from the last.
pub fn get(&self, key: &str) -> Option<(usize, &str, VariableType, Dynamic)> { pub(crate) fn get(&self, key: &str) -> Option<(ScopeSource, Dynamic)> {
self.0 self.0
.iter() .iter()
.enumerate() .enumerate()
@ -155,7 +172,16 @@ impl<'a> Scope<'a> {
value, value,
.. ..
}, },
)| (i, name.as_ref(), *var_type, value.clone()), )| {
(
ScopeSource {
name: name.as_ref(),
idx: i,
var_type: *var_type,
},
value.clone(),
)
},
) )
} }
@ -174,11 +200,11 @@ impl<'a> Scope<'a> {
pub(crate) fn get_mut(&mut self, name: &str, index: usize) -> &mut Dynamic { pub(crate) fn get_mut(&mut self, name: &str, index: usize) -> &mut Dynamic {
let entry = self.0.get_mut(index).expect("invalid index in Scope"); let entry = self.0.get_mut(index).expect("invalid index in Scope");
assert_ne!( // assert_ne!(
entry.var_type, // entry.var_type,
VariableType::Constant, // VariableType::Constant,
"get mut of constant variable" // "get mut of constant variable"
); // );
assert_eq!(entry.name, name, "incorrect key at Scope entry"); assert_eq!(entry.name, name, "incorrect key at Scope entry");
&mut entry.value &mut entry.value

79
tests/eval.rs Normal file
View File

@ -0,0 +1,79 @@
use rhai::{Engine, EvalAltResult, Scope, INT};
#[test]
fn test_eval() -> Result<(), EvalAltResult> {
let mut engine = Engine::new();
assert_eq!(
engine.eval::<INT>(
r#"
eval("40 + 2")
"#
)?,
42
);
Ok(())
}
#[test]
#[cfg(not(feature = "no_function"))]
fn test_eval_function() -> Result<(), EvalAltResult> {
let mut engine = Engine::new();
let mut scope = Scope::new();
assert_eq!(
engine.eval_with_scope::<INT>(
&mut scope,
r#"
let x = 10;
fn foo(x) { x += 12; x }
let script = "let y = x;"; // build a script
script += "y += foo(y);";
script += "x + y";
eval(script)
"#
)?,
42
);
assert_eq!(
scope
.get_value::<INT>("x")
.expect("variable x should exist"),
10
);
assert_eq!(
scope
.get_value::<INT>("y")
.expect("variable y should exist"),
32
);
assert!(!scope.contains("z"));
Ok(())
}
#[test]
#[cfg(not(feature = "no_function"))]
fn test_eval_override() -> Result<(), EvalAltResult> {
let mut engine = Engine::new();
assert_eq!(
engine.eval::<String>(
r#"
fn eval(x) { x } // reflect the script back
eval("40 + 2")
"#
)?,
"40 + 2"
);
Ok(())
}

View File

@ -91,7 +91,7 @@ fn test_big_get_set() -> Result<(), EvalAltResult> {
); );
assert_eq!( assert_eq!(
engine.eval::<String>("let a = new_tp(); a.type_of()")?, engine.eval::<String>("let a = new_tp(); type_of(a)")?,
"TestParent" "TestParent"
); );

39
tests/side_effects.rs Normal file
View File

@ -0,0 +1,39 @@
use rhai::{Engine, EvalAltResult, RegisterFn, Scope};
use std::cell::Cell;
use std::rc::Rc;
#[derive(Debug, Clone)]
struct CommandWrapper {
value: Rc<Cell<i64>>,
}
impl CommandWrapper {
pub fn set_value(&mut self, x: i64) {
let val = self.value.get();
self.value.set(val + x);
}
}
#[test]
fn test_side_effects() -> Result<(), EvalAltResult> {
let mut engine = Engine::new();
let mut scope = Scope::new();
let payload = Rc::new(Cell::new(12));
assert_eq!(payload.get(), 12);
let command = CommandWrapper {
value: payload.clone(),
};
scope.push_constant("Command", command);
engine.register_type_with_name::<CommandWrapper>("CommandType");
engine.register_fn("action", CommandWrapper::set_value);
engine.eval_with_scope::<()>(&mut scope, "Command.action(30)")?;
assert_eq!(payload.get(), 42);
Ok(())
}

View File

@ -23,10 +23,10 @@ fn test_type_of() -> Result<(), EvalAltResult> {
assert_eq!(engine.eval::<String>(r#"type_of("hello")"#)?, "string"); assert_eq!(engine.eval::<String>(r#"type_of("hello")"#)?, "string");
#[cfg(not(feature = "only_i32"))] #[cfg(not(feature = "only_i32"))]
assert_eq!(engine.eval::<String>("let x = 123; x.type_of()")?, "i64"); assert_eq!(engine.eval::<String>("let x = 123; type_of(x)")?, "i64");
#[cfg(feature = "only_i32")] #[cfg(feature = "only_i32")]
assert_eq!(engine.eval::<String>("let x = 123; x.type_of()")?, "i32"); assert_eq!(engine.eval::<String>("let x = 123; type_of(x)")?, "i32");
Ok(()) Ok(())
} }

View File

@ -42,10 +42,7 @@ fn test_scope_eval() -> Result<(), EvalAltResult> {
assert_eq!( assert_eq!(
scope scope
.get_value::<INT>("y") .get_value::<INT>("y")
.ok_or(EvalAltResult::ErrorRuntime( .expect("variable y should exist"),
"variable y not found".into(),
Default::default()
))?,
1 1
); );