commit
9c7fa97171
134
README.md
134
README.md
@ -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);
|
||||||
|
```
|
||||||
|
114
src/api.rs
114
src/api.rs
@ -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> {
|
||||||
engine.clear_functions();
|
if !retain_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)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
engine.clear_functions();
|
if !retain_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).
|
||||||
|
475
src/engine.rs
475
src/engine.rs
@ -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
|
return Ok(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 {
|
|
||||||
// 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,43 +970,111 @@ impl Engine<'_> {
|
|||||||
Ok(Box::new(arr))
|
Ok(Box::new(arr))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Dump AST
|
|
||||||
Expr::FunctionCall(fn_name, args_expr_list, _, pos) if fn_name == KEYWORD_DUMP_AST => {
|
|
||||||
let pos = if args_expr_list.len() == 0 {
|
|
||||||
*pos
|
|
||||||
} else {
|
|
||||||
args_expr_list[0].position()
|
|
||||||
};
|
|
||||||
|
|
||||||
// Change the argument to a debug dump of the expressions
|
|
||||||
let result = args_expr_list
|
|
||||||
.into_iter()
|
|
||||||
.map(|expr| format!("{:#?}", expr))
|
|
||||||
.collect::<Vec<_>>()
|
|
||||||
.join("\n");
|
|
||||||
|
|
||||||
// Redirect call to `print`
|
|
||||||
self.call_fn_raw(
|
|
||||||
KEYWORD_PRINT,
|
|
||||||
vec![result.into_dynamic().as_mut()],
|
|
||||||
None,
|
|
||||||
pos,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Normal function call
|
|
||||||
Expr::FunctionCall(fn_name, args_expr_list, def_val, pos) => {
|
Expr::FunctionCall(fn_name, args_expr_list, def_val, pos) => {
|
||||||
let mut values = args_expr_list
|
// Has a system function an override?
|
||||||
.iter()
|
fn has_override(engine: &Engine, name: &str) -> bool {
|
||||||
.map(|expr| self.eval_expr(scope, expr))
|
let spec = FnSpec {
|
||||||
.collect::<Result<Vec<Dynamic>, _>>()?;
|
name: name.into(),
|
||||||
|
args: Some(vec![TypeId::of::<String>()]),
|
||||||
|
};
|
||||||
|
|
||||||
self.call_fn_raw(
|
engine.ext_functions.contains_key(&spec)
|
||||||
fn_name,
|
|| engine
|
||||||
values.iter_mut().map(|b| b.as_mut()).collect(),
|
.script_functions
|
||||||
def_val.as_ref(),
|
.binary_search_by(|f| f.compare(name, 1))
|
||||||
*pos,
|
.is_ok()
|
||||||
)
|
}
|
||||||
|
|
||||||
|
match fn_name.as_str() {
|
||||||
|
// Dump AST
|
||||||
|
KEYWORD_DUMP_AST => {
|
||||||
|
let pos = if args_expr_list.len() == 0 {
|
||||||
|
*pos
|
||||||
|
} else {
|
||||||
|
args_expr_list[0].position()
|
||||||
|
};
|
||||||
|
|
||||||
|
// Change the argument to a debug dump of the expressions
|
||||||
|
let result = args_expr_list
|
||||||
|
.into_iter()
|
||||||
|
.map(|expr| format!("{:#?}", expr))
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
.join("\n");
|
||||||
|
|
||||||
|
// Redirect call to `print`
|
||||||
|
self.call_fn_raw(
|
||||||
|
KEYWORD_PRINT,
|
||||||
|
vec![result.into_dynamic().as_mut()],
|
||||||
|
None,
|
||||||
|
pos,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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
|
||||||
|
_ => {
|
||||||
|
let mut values = args_expr_list
|
||||||
|
.iter()
|
||||||
|
.map(|expr| self.eval_expr(scope, expr))
|
||||||
|
.collect::<Result<Vec<Dynamic>, _>>()?;
|
||||||
|
|
||||||
|
self.call_fn_raw(
|
||||||
|
fn_name,
|
||||||
|
values.iter_mut().map(|b| b.as_mut()).collect(),
|
||||||
|
def_val.as_ref(),
|
||||||
|
*pos,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Expr::And(lhs, rhs) => Ok(Box::new(
|
Expr::And(lhs, rhs) => Ok(Box::new(
|
||||||
@ -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())),
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -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)?
|
||||||
|
@ -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,
|
||||||
}
|
}
|
||||||
|
100
src/parser.rs
100
src/parser.rs
@ -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> {
|
||||||
// { - block statement as expression
|
let token = match input
|
||||||
match input.peek() {
|
.peek()
|
||||||
Some((Token::LeftBrace, pos)) => {
|
.ok_or_else(|| ParseError::new(PERR::UnexpectedEOF, Position::eof()))?
|
||||||
|
{
|
||||||
|
// { - block statement as expression
|
||||||
|
(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()
|
||||||
|
@ -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)
|
||||||
|
44
src/scope.rs
44
src/scope.rs
@ -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
79
tests/eval.rs
Normal 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(())
|
||||||
|
}
|
@ -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
39
tests/side_effects.rs
Normal 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(())
|
||||||
|
}
|
@ -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(())
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
);
|
);
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user