Minor code cleanup.

This commit is contained in:
Stephen Chung 2020-04-01 09:51:33 +08:00
parent dcf5eaf64d
commit d7ac57c060
5 changed files with 223 additions and 224 deletions

212
README.md
View File

@ -179,7 +179,7 @@ let result = engine.eval::<i64>("40 + 2")?; // return type is i64, specified
let result: i64 = engine.eval("40 + 2")?; // return type is inferred to be i64 let result: i64 = engine.eval("40 + 2")?; // return type is inferred to be i64
let result = engine.eval<String>("40 + 2")?; // returns an error because the actual return type is i64, not String let result = engine.eval::<String>("40 + 2")?; // returns an error because the actual return type is i64, not String
``` ```
Evaluate a script file directly: Evaluate a script file directly:
@ -301,7 +301,7 @@ type_of('c') == "char";
type_of(42) == "i64"; type_of(42) == "i64";
let x = 123; let x = 123;
x.type_of(); // error - 'type_of' cannot use method-call style x.type_of(); // <- error: 'type_of' cannot use method-call style
type_of(x) == "i64"; type_of(x) == "i64";
x = 99.999; x = 99.999;
@ -325,7 +325,7 @@ That's about it. For other conversions, register custom conversion functions.
```rust ```rust
let x = 42; let x = 42;
let y = x * 100.0; // error: cannot multiply i64 with f64 let y = x * 100.0; // <- error: cannot multiply i64 with f64
let y = x.to_float() * 100.0; // works let y = x.to_float() * 100.0; // works
let z = y.to_int() + x; // works let z = y.to_int() + x; // works
@ -341,8 +341,8 @@ To call these functions, they need to be registered with the [`Engine`].
```rust ```rust
use rhai::{Engine, EvalAltResult}; use rhai::{Engine, EvalAltResult};
use rhai::RegisterFn; // use `RegisterFn` trait for `register_fn` use rhai::RegisterFn; // use `RegisterFn` trait for `register_fn`
use rhai::{Dynamic, RegisterDynamicFn}; // use `RegisterDynamicFn` trait for `register_dynamic_fn` use rhai::{Dynamic, RegisterDynamicFn}; // use `RegisterDynamicFn` trait for `register_dynamic_fn`
// Normal function // Normal function
fn add(x: i64, y: i64) -> i64 { fn add(x: i64, y: i64) -> i64 {
@ -362,14 +362,14 @@ fn main() -> Result<(), EvalAltResult>
let result = engine.eval::<i64>("add(40, 2)")?; let result = engine.eval::<i64>("add(40, 2)")?;
println!("Answer: {}", result); // prints 42 println!("Answer: {}", result); // prints 42
// Functions that return Dynamic values must use register_dynamic_fn() // Functions that return Dynamic values must use register_dynamic_fn()
engine.register_dynamic_fn("get_an_any", get_an_any); engine.register_dynamic_fn("get_an_any", get_an_any);
let result = engine.eval::<i64>("get_an_any()")?; let result = engine.eval::<i64>("get_an_any()")?;
println!("Answer: {}", result); // prints 42 println!("Answer: {}", result); // prints 42
Ok(()) Ok(())
} }
@ -423,13 +423,13 @@ The function must return `Result<_, EvalAltResult>`. `EvalAltResult` implements
```rust ```rust
use rhai::{Engine, EvalAltResult, Position}; use rhai::{Engine, EvalAltResult, Position};
use rhai::RegisterResultFn; // use `RegisterResultFn` trait for `register_result_fn` use rhai::RegisterResultFn; // use `RegisterResultFn` trait for `register_result_fn`
// Function that may fail // Function that may fail
fn safe_divide(x: i64, y: i64) -> Result<i64, EvalAltResult> { fn safe_divide(x: i64, y: i64) -> Result<i64, EvalAltResult> {
if y == 0 { if y == 0 {
// Return an error if y is zero // Return an error if y is zero
Err("Division by zero detected!".into()) // short-cut to create EvalAltResult Err("Division by zero detected!".into()) // short-cut to create EvalAltResult
} else { } else {
Ok(x / y) Ok(x / y)
} }
@ -443,7 +443,7 @@ fn main()
engine.register_result_fn("divide", safe_divide); engine.register_result_fn("divide", safe_divide);
if let Err(error) = engine.eval::<i64>("divide(40, 0)") { if let Err(error) = engine.eval::<i64>("divide(40, 0)") {
println!("Error: {:?}", error); // prints ErrorRuntime("Division by zero detected!", (1, 1)") println!("Error: {:?}", error); // prints ErrorRuntime("Division by zero detected!", (1, 1)")
} }
} }
``` ```
@ -745,24 +745,24 @@ Variable names are also case _sensitive_.
Variables are defined using the `let` keyword. A variable defined within a statement block is _local_ to that block. Variables are defined using the `let` keyword. A variable defined within a statement block is _local_ to that block.
```rust ```rust
let x = 3; // ok let x = 3; // ok
let _x = 42; // ok let _x = 42; // ok
let x_ = 42; // also ok let x_ = 42; // also ok
let _x_ = 42; // still ok let _x_ = 42; // still ok
let _ = 123; // syntax error - illegal variable name let _ = 123; // <- syntax error: illegal variable name
let _9 = 9; // syntax error - illegal variable name let _9 = 9; // <- syntax error: illegal variable name
let x = 42; // variable is 'x', lower case let x = 42; // variable is 'x', lower case
let X = 123; // variable is 'X', upper case let X = 123; // variable is 'X', upper case
x == 42; x == 42;
X == 123; X == 123;
{ {
let x = 999; // local variable 'x' shadows the 'x' in parent block let x = 999; // local variable 'x' shadows the 'x' in parent block
x == 999; // access to local 'x' x == 999; // access to local 'x'
} }
x == 42; // the parent block's 'x' is not changed x == 42; // the parent block's 'x' is not changed
``` ```
Constants Constants
@ -772,14 +772,14 @@ Constants can be defined using the `const` keyword and are immutable. Constants
```rust ```rust
const x = 42; const x = 42;
print(x * 2); // prints 84 print(x * 2); // prints 84
x = 123; // syntax error - cannot assign to constant x = 123; // <- syntax error: cannot assign to constant
``` ```
Constants must be assigned a _value_, not an expression. Constants must be assigned a _value_, not an expression.
```rust ```rust
const x = 40 + 2; // syntax error - cannot assign expression to constant const x = 40 + 2; // <- syntax error: cannot assign expression to constant
``` ```
Numbers Numbers
@ -1135,21 +1135,21 @@ However, if the [`no_stdlib`] feature is turned on, comparisons can only be made
types - `INT` (`i64` or `i32` depending on [`only_i32`] and [`only_i64`]), `f64` (if not [`no_float`]), string, array, `bool`, `char`. types - `INT` (`i64` or `i32` depending on [`only_i32`] and [`only_i64`]), `f64` (if not [`no_float`]), string, array, `bool`, `char`.
```rust ```rust
42 == 42; // true 42 == 42; // true
42 > 42; // false 42 > 42; // false
"hello" > "foo"; // true "hello" > "foo"; // true
"42" == 42; // false "42" == 42; // false
``` ```
Comparing two values of _different_ data types, or of unknown data types, always results in `false`. Comparing two values of _different_ data types, or of unknown data types, always results in `false`.
```rust ```rust
42 == 42.0; // false - i64 is different from f64 42 == 42.0; // false - i64 is different from f64
42 > "42"; // false - i64 is different from string 42 > "42"; // false - i64 is different from string
42 <= "42"; // false again 42 <= "42"; // false again
let ts = new_ts(); // custom type let ts = new_ts(); // custom type
ts == 42; // false - types are not the same ts == 42; // false - types are not the same
``` ```
Boolean operators Boolean operators
@ -1169,11 +1169,11 @@ if the first one already proves the condition wrong.
Single boolean operators `&` and `|` always evaluate both operands. Single boolean operators `&` and `|` always evaluate both operands.
```rust ```rust
this() || that(); // that() is not evaluated if this() is true this() || that(); // that() is not evaluated if this() is true
this() && that(); // that() is not evaluated if this() is false this() && that(); // that() is not evaluated if this() is false
this() | that(); // both this() and that() are evaluated this() | that(); // both this() and that() are evaluated
this() & that(); // both this() and that() are evaluated this() & that(); // both this() and that() are evaluated
``` ```
Compound assignment operators Compound assignment operators
@ -1181,13 +1181,13 @@ Compound assignment operators
```rust ```rust
let number = 5; let number = 5;
number += 4; // number = number + 4 number += 4; // number = number + 4
number -= 3; // number = number - 3 number -= 3; // number = number - 3
number *= 2; // number = number * 2 number *= 2; // number = number * 2
number /= 1; // number = number / 1 number /= 1; // number = number / 1
number %= 3; // number = number % 3 number %= 3; // number = number % 3
number <<= 2; // number = number << 2 number <<= 2; // number = number << 2
number >>= 1; // number = number >> 1 number >>= 1; // number = number >> 1
``` ```
The `+=` operator can also be used to build strings: The `+=` operator can also be used to build strings:
@ -1286,9 +1286,9 @@ for x in range(0, 50) {
------------------- -------------------
```rust ```rust
return; // equivalent to return (); return; // equivalent to return ();
return 123 + 456; // returns 579 return 123 + 456; // returns 579
``` ```
Errors and `throw`-ing exceptions Errors and `throw`-ing exceptions
@ -1299,10 +1299,10 @@ To deliberately return an error during an evaluation, use the `throw` keyword.
```rust ```rust
if some_bad_condition_has_happened { if some_bad_condition_has_happened {
throw error; // 'throw' takes a string to form the exception text throw error; // 'throw' takes a string as the exception text
} }
throw; // empty exception text: "" throw; // defaults to empty exception text: ""
``` ```
Exceptions thrown via `throw` in the script can be captured by matching `Err(EvalAltResult::ErrorRuntime(`_reason_`, `_position_`))` Exceptions thrown via `throw` in the script can be captured by matching `Err(EvalAltResult::ErrorRuntime(`_reason_`, `_position_`))`
@ -1317,7 +1317,7 @@ let result = engine.eval::<i64>(r#"
} }
"#); "#);
println!(result); // prints "Runtime error: 42 is too large! (line 5, position 15)" println!(result); // prints "Runtime error: 42 is too large! (line 5, position 15)"
``` ```
Functions Functions
@ -1339,16 +1339,17 @@ Just like in Rust, an implicit return can be used. In fact, the last statement o
regardless of whether it is terminated with a semicolon `';'`. This is different from Rust. regardless of whether it is terminated with a semicolon `';'`. This is different from Rust.
```rust ```rust
fn add(x, y) { fn add(x, y) { // implicit return:
x + y; // value of the last statement (no need for ending semicolon) is used as the return value x + y; // value of the last statement (no need for ending semicolon)
// is used as the return value
} }
fn add2(x) { fn add2(x) {
return x + 2; // explicit return return x + 2; // explicit return
} }
print(add(2, 3)); // prints 5 print(add(2, 3)); // prints 5
print(add2(42)); // prints 44 print(add2(42)); // prints 44
``` ```
### No access to external scope ### No access to external scope
@ -1358,7 +1359,7 @@ Functions can only access their parameters. They cannot access external variabl
```rust ```rust
let x = 42; let x = 42;
fn foo() { x } // syntax error - variable 'x' doesn't exist fn foo() { x } // <- syntax error: variable 'x' doesn't exist
``` ```
### Passing arguments by value ### Passing arguments by value
@ -1368,13 +1369,13 @@ It is important to remember that all arguments are passed by _value_, so all fun
Any update to an argument will **not** be reflected back to the caller. This can introduce subtle bugs, if not careful. Any update to an argument will **not** be reflected back to the caller. This can introduce subtle bugs, if not careful.
```rust ```rust
fn change(s) { // 's' is passed by value fn change(s) { // 's' is passed by value
s = 42; // only a COPY of 's' is changed s = 42; // only a COPY of 's' is changed
} }
let x = 500; let x = 500;
x.change(); // de-sugars to change(x) x.change(); // de-sugars to change(x)
x == 500; // 'x' is NOT changed! x == 500; // 'x' is NOT changed!
``` ```
### Global definitions only ### Global definitions only
@ -1389,7 +1390,7 @@ fn add(x, y) {
// The following will not compile // The following will not compile
fn do_addition(x) { fn do_addition(x) {
fn add_y(n) { // functions cannot be defined inside another function fn add_y(n) { // functions cannot be defined inside another function
n + y n + y
} }
@ -1410,10 +1411,10 @@ fn foo(x,y) { print("Two! " + x + "," + y) }
fn foo() { print("None.") } fn foo() { print("None.") }
fn foo(x) { print("HA! NEW ONE! " + x) } // overwrites previous definition fn foo(x) { print("HA! NEW ONE! " + x) } // overwrites previous definition
foo(1,2,3); // prints "Three!!! 1,2,3" foo(1,2,3); // prints "Three!!! 1,2,3"
foo(42); // prints "HA! NEW ONE! 42" foo(42); // prints "HA! NEW ONE! 42"
foo(1,2); // prints "Two!! 1,2" foo(1,2); // prints "Two!! 1,2"
foo(); // prints "None." foo(); // prints "None."
``` ```
Members and methods Members and methods
@ -1422,11 +1423,12 @@ Members and methods
Properties and methods in a Rust custom type registered with the [`Engine`] can be called just like in Rust. Properties and methods in a Rust custom type registered with the [`Engine`] can be called just like in Rust.
```rust ```rust
let a = new_ts(); // constructor function let a = new_ts(); // constructor function
a.field = 500; // property access a.field = 500; // property access
a.update(); // method call a.update(); // method call
update(a); // this works, but 'a' is unchanged because only a COPY of 'a' is passed to 'update' by VALUE update(a); // this works, but 'a' is unchanged because only
// a COPY of 'a' is passed to 'update' by VALUE
``` ```
Custom types, properties and methods can be disabled via the [`no_object`] feature. Custom types, properties and methods can be disabled via the [`no_object`] feature.
@ -1437,10 +1439,10 @@ Custom types, properties and methods can be disabled via the [`no_object`] featu
The `print` and `debug` functions default to printing to `stdout`, with `debug` using standard debug formatting. The `print` and `debug` functions default to printing to `stdout`, with `debug` using standard debug formatting.
```rust ```rust
print("hello"); // prints hello to stdout print("hello"); // prints hello to stdout
print(1 + 2 + 3); // prints 6 to stdout print(1 + 2 + 3); // prints 6 to stdout
print("hello" + 42); // prints hello42 to stdout print("hello" + 42); // prints hello42 to stdout
debug("world!"); // prints "world!" to stdout using debug formatting debug("world!"); // prints "world!" to stdout using debug formatting
``` ```
### Overriding `print` and `debug` with callback functions ### Overriding `print` and `debug` with callback functions
@ -1481,14 +1483,13 @@ For example, in the following:
```rust ```rust
{ {
let x = 999; // NOT eliminated - Rhai doesn't check yet whether a variable is used later on let x = 999; // NOT eliminated: Rhai doesn't check yet whether a variable is used later on
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: the function 'foo' may have side effects
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
} }
``` ```
@ -1511,7 +1512,7 @@ if ABC || some_work() { print("done!"); } // 'ABC' is constant so it is replac
if true || some_work() { print("done!"); } // since '||' short-circuits, 'some_work' is never called if true || some_work() { print("done!"); } // since '||' short-circuits, 'some_work' is never called
if true { print("done!"); } // <- the line above is equivalent to this if true { print("done!"); } // <- the line above is equivalent to this
print("done!"); // <- the line above is further simplified to this print("done!"); // <- the line above is further simplified to this
// because the condition is always true // because the condition is always true
``` ```
These are quite effective for template-based machine-generated scripts where certain constant values These are quite effective for template-based machine-generated scripts where certain constant values
@ -1526,7 +1527,7 @@ so they are not optimized away:
const DECISION = 1; const DECISION = 1;
if DECISION == 1 { // NOT optimized away because you can define if DECISION == 1 { // NOT optimized away because you can define
: // your own '==' function to override the built-in default! : // 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
: :
@ -1596,14 +1597,15 @@ evaluated all function calls with constant arguments, using the result to replac
// 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'
if DECISION == 1 { // is a function call to the '==' function, and it returns 'true' if DECISION == 1 { // is a function call to the '==' function, 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
} }
print("hello!"); // <- the above is equivalent to this ('print' and 'debug' are handled specially) print("hello!"); // <- the above is equivalent to this
// ('print' and 'debug' are handled specially)
``` ```
Because of the eager evaluation of functions, many constant expressions will be evaluated and replaced by the result. Because of the eager evaluation of functions, many constant expressions will be evaluated and replaced by the result.
@ -1612,8 +1614,8 @@ This does not happen with [`OptimizationLevel::Simple`] which doesn't assume all
```rust ```rust
// When compiling the following with OptimizationLevel::Full... // When compiling the following with OptimizationLevel::Full...
let x = (1 + 2) * 3 - 4 / 5 % 6; // <- will be replaced by 'let x = 9' 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 Function side effect considerations
@ -1644,13 +1646,13 @@ Subtle semantic changes
Some optimizations can alter subtle semantics of the script. For example: Some optimizations can alter subtle semantics of the script. For example:
```rust ```rust
if true { // condition always true if true { // condition always true
123.456; // eliminated 123.456; // eliminated
hello; // eliminated, EVEN THOUGH the variable doesn't exist! hello; // eliminated, EVEN THOUGH the variable doesn't exist!
foo(42) // promoted up-level foo(42) // promoted up-level
} }
foo(42) // <- the above optimizes to this foo(42) // <- the above optimizes to this
``` ```
Nevertheless, if the original script were evaluated instead, it would have been an error - the variable `hello` doesn't exist, Nevertheless, if the original script were evaluated instead, it would have been an error - the variable `hello` doesn't exist,
@ -1696,22 +1698,22 @@ let x = 10;
fn foo(x) { x += 12; x } fn foo(x) { x += 12; x }
let script = "let y = x;"; // build a script let script = "let y = x;"; // build a script
script += "y += foo(y);"; script += "y += foo(y);";
script += "x + y"; script += "x + y";
let result = eval(script); // <- look, JS, we can also do this! let result = eval(script); // <- look, JS, we can also do this!
print("Answer: " + result); // prints 42 print("Answer: " + result); // prints 42
print("x = " + x); // prints 10 - functions call arguments are passed by value print("x = " + x); // prints 10: functions call arguments are passed by value
print("y = " + y); // prints 32 - variables defined in 'eval' persist! print("y = " + y); // prints 32: variables defined in 'eval' persist!
eval("{ let z = y }"); // to keep a variable local, use a statement block eval("{ let z = y }"); // to keep a variable local, use a statement block
print("z = " + z); // error - variable 'z' not found print("z = " + z); // <- error: variable 'z' not found
"print(42)".eval(); // nope - just like 'type_of', method-call style doesn't work "print(42)".eval(); // <- nope... just like 'type_of', method-call style doesn't work
``` ```
Script segments passed to `eval` execute inside the current [`Scope`], so they can access and modify _everything_, Script segments passed to `eval` execute inside the current [`Scope`], so they can access and modify _everything_,
@ -1721,8 +1723,8 @@ physically pasted in at the position of the `eval` call.
```rust ```rust
let script = "x += 32"; let script = "x += 32";
let x = 10; let x = 10;
eval(script); // variable 'x' in the current scope is visible! eval(script); // variable 'x' in the current scope is visible!
print(x); // prints 42 print(x); // prints 42
// The above is equivalent to: // The above is equivalent to:
let script = "x += 32"; let script = "x += 32";
@ -1737,7 +1739,7 @@ disable `eval` by overriding it, probably with something that throws.
```rust ```rust
fn eval(script) { throw "eval is evil! I refuse to run " + script } 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" let x = eval("40 + 2"); // 'eval' here throws "eval is evil! I refuse to run 40 + 2"
``` ```
Or override it from Rust: Or override it from Rust:

View File

@ -691,14 +691,14 @@ 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) self.eval_ast_with_scope_raw(scope, false, ast)?
.and_then(|result| { .downcast::<T>()
result.downcast::<T>().map(|v| *v).map_err(|a| { .map(|v| *v)
EvalAltResult::ErrorMismatchOutputType( .map_err(|a| {
self.map_type_name((*a).type_name()).to_string(), EvalAltResult::ErrorMismatchOutputType(
Position::none(), self.map_type_name((*a).type_name()).to_string(),
) Position::none(),
}) )
}) })
} }
@ -735,7 +735,9 @@ impl<'e> Engine<'e> {
/// 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).
/// Useful for when you don't need the result, but still need to keep track of possible errors. /// Useful for when you don't need the result, but still need to keep track of possible errors.
/// ///
/// Note - if `retain_functions` is set to `true`, functions defined by previous scripts are _retained_ and not cleared from run to run. /// # Note
///
/// If `retain_functions` is set to `true`, functions defined by previous scripts are _retained_ and not cleared from run to run.
#[cfg(not(feature = "no_std"))] #[cfg(not(feature = "no_std"))]
pub fn consume_file( pub fn consume_file(
&mut self, &mut self,
@ -748,7 +750,9 @@ impl<'e> Engine<'e> {
/// Evaluate a file with own scope, but throw away the result and only return error (if any). /// Evaluate a file with own scope, but throw away the result and only return error (if any).
/// Useful for when you don't need the result, but still need to keep track of possible errors. /// Useful for when you don't need the result, but still need to keep track of possible errors.
/// ///
/// Note - if `retain_functions` is set to `true`, functions defined by previous scripts are _retained_ and not cleared from run to run. /// # Note
///
/// If `retain_functions` is set to `true`, functions defined by previous scripts are _retained_ and not cleared from run to run.
#[cfg(not(feature = "no_std"))] #[cfg(not(feature = "no_std"))]
pub fn consume_file_with_scope( pub fn consume_file_with_scope(
&mut self, &mut self,
@ -763,7 +767,9 @@ impl<'e> Engine<'e> {
/// Evaluate a string, but throw away the result and only return error (if any). /// Evaluate a string, but throw away the result and only return error (if any).
/// Useful for when you don't need the result, but still need to keep track of possible errors. /// Useful for when you don't need the result, but still need to keep track of possible errors.
/// ///
/// Note - if `retain_functions` is set to `true`, functions defined by previous scripts are _retained_and not cleared from run to run. /// # Note
///
/// If `retain_functions` is set to `true`, functions defined by previous scripts are _retained_and not cleared from run to run.
pub fn consume(&mut self, retain_functions: bool, input: &str) -> Result<(), EvalAltResult> { pub fn consume(&mut self, retain_functions: bool, input: &str) -> Result<(), EvalAltResult> {
self.consume_with_scope(&mut Scope::new(), retain_functions, input) self.consume_with_scope(&mut Scope::new(), retain_functions, input)
} }
@ -771,7 +777,9 @@ impl<'e> Engine<'e> {
/// Evaluate a string with own scope, but throw away the result and only return error (if any). /// Evaluate a string with own scope, but throw away the result and only return error (if any).
/// Useful for when you don't need the result, but still need to keep track of possible errors. /// Useful for when you don't need the result, but still need to keep track of possible errors.
/// ///
/// Note - if `retain_functions` is set to `true`, functions defined by previous scripts are _retained_and not cleared from run to run. /// # Note
///
/// If `retain_functions` is set to `true`, functions defined by previous scripts are _retained_and not cleared from run to run.
pub fn consume_with_scope( pub fn consume_with_scope(
&mut self, &mut self,
scope: &mut Scope, scope: &mut Scope,
@ -789,7 +797,9 @@ impl<'e> Engine<'e> {
/// Evaluate an AST, but throw away the result and only return error (if any). /// Evaluate an AST, but throw away the result and only return error (if any).
/// Useful for when you don't need the result, but still need to keep track of possible errors. /// Useful for when you don't need the result, but still need to keep track of possible errors.
/// ///
/// Note - if `retain_functions` is set to `true`, functions defined by previous scripts are _retained_and not cleared from run to run. /// # Note
///
/// If `retain_functions` is set to `true`, functions defined by previous scripts are _retained_and not cleared from run to run.
pub fn consume_ast(&mut self, retain_functions: bool, ast: &AST) -> Result<(), EvalAltResult> { pub fn consume_ast(&mut self, retain_functions: bool, ast: &AST) -> Result<(), EvalAltResult> {
self.consume_ast_with_scope(&mut Scope::new(), retain_functions, ast) self.consume_ast_with_scope(&mut Scope::new(), retain_functions, ast)
} }
@ -797,7 +807,9 @@ impl<'e> Engine<'e> {
/// Evaluate an `AST` with own scope, but throw away the result and only return error (if any). /// Evaluate an `AST` with own scope, but throw away the result and only return error (if any).
/// Useful for when you don't need the result, but still need to keep track of possible errors. /// Useful for when you don't need the result, but still need to keep track of possible errors.
/// ///
/// Note - if `retain_functions` is set to `true`, functions defined by previous scripts are _retained_and not cleared from run to run. /// # Note
///
/// If `retain_functions` is set to `true`, functions defined by previous scripts are _retained_and not cleared from run to run.
pub fn consume_ast_with_scope( pub fn consume_ast_with_scope(
&mut self, &mut self,
scope: &mut Scope, scope: &mut Scope,
@ -833,9 +845,9 @@ impl<'e> Engine<'e> {
&mut self, &mut self,
functions: impl IntoIterator<Item = &'a Arc<FnDef>>, functions: impl IntoIterator<Item = &'a Arc<FnDef>>,
) { ) {
for f in functions.into_iter() { functions.into_iter().cloned().for_each(|f| {
self.fn_lib.add_or_replace_function(f.clone()); self.fn_lib.add_or_replace_function(f);
} });
} }
/// Call a script function retained inside the Engine. /// Call a script function retained inside the Engine.
@ -871,14 +883,14 @@ impl<'e> Engine<'e> {
let mut values = args.into_vec(); let mut values = args.into_vec();
let mut arg_values: Vec<_> = values.iter_mut().map(Dynamic::as_mut).collect(); let mut arg_values: Vec<_> = values.iter_mut().map(Dynamic::as_mut).collect();
self.call_fn_raw(name, &mut arg_values, None, Position::none(), 0) self.call_fn_raw(name, &mut arg_values, None, Position::none(), 0)?
.and_then(|b| { .downcast()
b.downcast().map(|b| *b).map_err(|a| { .map(|b| *b)
EvalAltResult::ErrorMismatchOutputType( .map_err(|a| {
self.map_type_name((*a).type_name()).into(), EvalAltResult::ErrorMismatchOutputType(
Position::none(), self.map_type_name((*a).type_name()).into(),
) Position::none(),
}) )
}) })
} }

View File

@ -36,15 +36,15 @@ pub type FnAny = dyn Fn(&mut FnCallArgs, Position) -> Result<Dynamic, EvalAltRes
type IteratorFn = dyn Fn(&Dynamic) -> Box<dyn Iterator<Item = Dynamic>>; type IteratorFn = dyn Fn(&Dynamic) -> Box<dyn Iterator<Item = Dynamic>>;
pub(crate) const MAX_CALL_STACK_DEPTH: usize = 64; pub const MAX_CALL_STACK_DEPTH: usize = 64;
pub(crate) const KEYWORD_PRINT: &str = "print"; pub const KEYWORD_PRINT: &str = "print";
pub(crate) const KEYWORD_DEBUG: &str = "debug"; pub const KEYWORD_DEBUG: &str = "debug";
pub(crate) const KEYWORD_DUMP_AST: &str = "dump_ast"; pub const KEYWORD_DUMP_AST: &str = "dump_ast";
pub(crate) const KEYWORD_TYPE_OF: &str = "type_of"; pub const KEYWORD_TYPE_OF: &str = "type_of";
pub(crate) const KEYWORD_EVAL: &str = "eval"; pub const KEYWORD_EVAL: &str = "eval";
pub(crate) const FUNC_TO_STRING: &str = "to_string"; pub const FUNC_TO_STRING: &str = "to_string";
pub(crate) const FUNC_GETTER: &str = "get$"; pub const FUNC_GETTER: &str = "get$";
pub(crate) const FUNC_SETTER: &str = "set$"; pub const FUNC_SETTER: &str = "set$";
#[derive(Debug, Eq, PartialEq, Hash, Clone, Copy)] #[derive(Debug, Eq, PartialEq, Hash, Clone, Copy)]
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
@ -317,8 +317,7 @@ impl Engine<'_> {
} }
} }
/// Universal method for calling functions, that are either /// Universal method for calling functions either registered with the `Engine` or written in Rhai
/// registered with the `Engine` or written in Rhai
pub(crate) fn call_fn_raw( pub(crate) fn call_fn_raw(
&mut self, &mut self,
fn_name: &str, fn_name: &str,
@ -370,12 +369,10 @@ impl Engine<'_> {
// See if the function match print/debug (which requires special processing) // See if the function match print/debug (which requires special processing)
return Ok(match fn_name { return Ok(match fn_name {
KEYWORD_PRINT => { KEYWORD_PRINT => {
self.on_print.as_mut()(cast_to_string(result.as_ref(), pos)?); self.on_print.as_mut()(cast_to_string(result.as_ref(), pos)?).into_dynamic()
().into_dynamic()
} }
KEYWORD_DEBUG => { KEYWORD_DEBUG => {
self.on_debug.as_mut()(cast_to_string(result.as_ref(), pos)?); self.on_debug.as_mut()(cast_to_string(result.as_ref(), pos)?).into_dynamic()
().into_dynamic()
} }
_ => result, _ => result,
}); });
@ -400,11 +397,11 @@ impl Engine<'_> {
if let Some(prop) = extract_prop_from_setter(fn_name) { if let Some(prop) = extract_prop_from_setter(fn_name) {
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
{ {
let val = args[1].into_dynamic(); let value = args[1].into_dynamic();
// Map property update // Map property update
if let Some(map) = args[0].downcast_mut::<Map>() { if let Some(map) = args[0].downcast_mut::<Map>() {
map.insert(prop.to_string(), val); map.insert(prop.to_string(), value);
return Ok(().into_dynamic()); return Ok(().into_dynamic());
} }
} }
@ -469,7 +466,7 @@ impl Engine<'_> {
// 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, op_pos) => { Expr::Index(idx_lhs, idx_expr, op_pos) => {
let val = match idx_lhs.as_ref() { let value = match idx_lhs.as_ref() {
// xxx.id[idx_expr] // xxx.id[idx_expr]
Expr::Property(id, pos) => { Expr::Property(id, pos) => {
let this_ptr = target.get_mut(scope); let this_ptr = target.get_mut(scope);
@ -488,8 +485,8 @@ impl Engine<'_> {
} }
}; };
self.get_indexed_value(scope, &val, idx_expr, *op_pos, level) self.get_indexed_value(scope, &value, idx_expr, *op_pos, level)
.map(|(v, _, _)| v) .map(|(val, _, _)| val)
} }
// xxx.dot_lhs.rhs // xxx.dot_lhs.rhs
@ -498,8 +495,8 @@ impl Engine<'_> {
Expr::Property(id, pos) => { Expr::Property(id, pos) => {
let this_ptr = target.get_mut(scope); let this_ptr = target.get_mut(scope);
self.call_fn_raw(&make_getter(id), &mut [this_ptr], None, *pos, 0) self.call_fn_raw(&make_getter(id), &mut [this_ptr], None, *pos, 0)
.and_then(|mut v| { .and_then(|mut val| {
self.get_dot_val_helper(scope, Target::from(v.as_mut()), rhs, level) self.get_dot_val_helper(scope, Target::from(val.as_mut()), rhs, level)
}) })
} }
// xxx.idx_lhs[idx_expr].rhs // xxx.idx_lhs[idx_expr].rhs
@ -525,8 +522,8 @@ impl Engine<'_> {
}; };
self.get_indexed_value(scope, &val, idx_expr, *op_pos, level) self.get_indexed_value(scope, &val, idx_expr, *op_pos, level)
.and_then(|(mut v, _, _)| { .and_then(|(mut val, _, _)| {
self.get_dot_val_helper(scope, Target::from(v.as_mut()), rhs, level) self.get_dot_val_helper(scope, Target::from(val.as_mut()), rhs, level)
}) })
} }
// Syntax error // Syntax error
@ -569,10 +566,10 @@ impl Engine<'_> {
// idx_lhs[idx_expr].??? // idx_lhs[idx_expr].???
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
Expr::Index(idx_lhs, idx_expr, op_pos) => { Expr::Index(idx_lhs, idx_expr, op_pos) => {
let (idx_src_type, src, idx, mut value) = let (idx_src_type, src, idx, mut val) =
self.eval_index_expr(scope, idx_lhs, idx_expr, *op_pos, level)?; self.eval_index_expr(scope, idx_lhs, idx_expr, *op_pos, level)?;
let val = let value =
self.get_dot_val_helper(scope, Target::from(value.as_mut()), dot_rhs, level); self.get_dot_val_helper(scope, Target::from(val.as_mut()), dot_rhs, level);
// 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(src) = src { if let Some(src) = src {
@ -589,19 +586,19 @@ impl Engine<'_> {
scope, scope,
src, src,
idx, idx,
(value, dot_rhs.position()), (val, dot_rhs.position()),
)?; )?;
} }
} }
} }
val value
} }
// {expr}.??? // {expr}.???
expr => { expr => {
let mut value = self.eval_expr(scope, expr, level)?; let mut val = self.eval_expr(scope, expr, level)?;
self.get_dot_val_helper(scope, Target::from(value.as_mut()), dot_rhs, level) self.get_dot_val_helper(scope, Target::from(val.as_mut()), dot_rhs, level)
} }
} }
} }
@ -629,10 +626,8 @@ impl Engine<'_> {
) -> Result<(Dynamic, IndexSourceType, IndexValue), EvalAltResult> { ) -> Result<(Dynamic, IndexSourceType, IndexValue), EvalAltResult> {
let idx_pos = idx_expr.position(); let idx_pos = idx_expr.position();
if val.is::<Array>() { // val_array[idx]
// val_array[idx] if let Some(arr) = val.downcast_ref::<Array>() {
let arr = val.downcast_ref::<Array>().expect("array expected");
let idx = *self let idx = *self
.eval_expr(scope, idx_expr, level)? .eval_expr(scope, idx_expr, level)?
.downcast::<INT>() .downcast::<INT>()
@ -650,10 +645,8 @@ impl Engine<'_> {
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
{ {
if val.is::<Map>() { // val_map[idx]
// val_map[idx] if let Some(map) = val.downcast_ref::<Map>() {
let map = val.downcast_ref::<Map>().expect("array expected");
let idx = *self let idx = *self
.eval_expr(scope, idx_expr, level)? .eval_expr(scope, idx_expr, level)?
.downcast::<String>() .downcast::<String>()
@ -667,10 +660,8 @@ impl Engine<'_> {
} }
} }
if val.is::<String>() { // val_string[idx]
// val_string[idx] if let Some(s) = val.downcast_ref::<String>() {
let s = val.downcast_ref::<String>().expect("string expected");
let idx = *self let idx = *self
.eval_expr(scope, idx_expr, level)? .eval_expr(scope, idx_expr, level)?
.downcast::<INT>() .downcast::<INT>()
@ -699,10 +690,10 @@ impl Engine<'_> {
} }
// Error - cannot be indexed // Error - cannot be indexed
return Err(EvalAltResult::ErrorIndexingType( Err(EvalAltResult::ErrorIndexingType(
self.map_type_name(val.type_name()).to_string(), self.map_type_name(val.type_name()).to_string(),
op_pos, op_pos,
)); ))
} }
/// Evaluate an index expression /// Evaluate an index expression
@ -755,7 +746,7 @@ impl Engine<'_> {
let val = self.eval_expr(scope, expr, level)?; let val = self.eval_expr(scope, expr, level)?;
self.get_indexed_value(scope, &val, idx_expr, op_pos, level) self.get_indexed_value(scope, &val, idx_expr, op_pos, level)
.map(|(v, _, idx)| (IndexSourceType::Expression, None, idx, v)) .map(|(val, _, idx)| (IndexSourceType::Expression, None, idx, val))
} }
} }
} }
@ -824,23 +815,20 @@ impl Engine<'_> {
new_val: Dynamic, new_val: Dynamic,
pos: Position, pos: Position,
) -> Result<Dynamic, EvalAltResult> { ) -> Result<Dynamic, EvalAltResult> {
if target.is::<Array>() { if let Some(arr) = target.downcast_mut::<Array>() {
let arr = target.downcast_mut::<Array>().expect("array expected");
arr[idx.as_num()] = new_val; arr[idx.as_num()] = new_val;
return Ok(target); return Ok(target);
} }
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
{ {
if target.is::<Map>() { if let Some(map) = target.downcast_mut::<Map>() {
let map = target.downcast_mut::<Map>().expect("array expected");
map.insert(idx.as_str(), new_val); map.insert(idx.as_str(), new_val);
return Ok(target); return Ok(target);
} }
} }
if target.is::<String>() { if let Some(s) = target.downcast_mut::<String>() {
let s = target.downcast_mut::<String>().expect("string expected");
// Value must be a character // Value must be a character
let ch = *new_val let ch = *new_val
.downcast::<char>() .downcast::<char>()
@ -877,14 +865,14 @@ impl Engine<'_> {
// xxx.id[idx_expr] // xxx.id[idx_expr]
Expr::Property(id, pos) => self Expr::Property(id, pos) => self
.call_fn_raw(&make_getter(id), &mut [this_ptr], None, *pos, 0) .call_fn_raw(&make_getter(id), &mut [this_ptr], None, *pos, 0)
.and_then(|v| { .and_then(|val| {
let (_, _, idx) = let (_, _, idx) =
self.get_indexed_value(scope, &v, idx_expr, *op_pos, level)?; self.get_indexed_value(scope, &val, idx_expr, *op_pos, level)?;
Self::update_indexed_value(v, idx, new_val.0.clone(), new_val.1) Self::update_indexed_value(val, idx, new_val.0.clone(), new_val.1)
}) })
.and_then(|mut v| { .and_then(|mut val| {
let mut args = [this_ptr, v.as_mut()]; let mut args = [this_ptr, val.as_mut()];
self.call_fn_raw(&make_setter(id), &mut args, None, *pos, 0) self.call_fn_raw(&make_setter(id), &mut args, None, *pos, 0)
}), }),
@ -900,12 +888,12 @@ impl Engine<'_> {
// xxx.id.rhs // xxx.id.rhs
Expr::Property(id, pos) => { Expr::Property(id, pos) => {
self.call_fn_raw(&make_getter(id), &mut [this_ptr], None, *pos, 0) self.call_fn_raw(&make_getter(id), &mut [this_ptr], None, *pos, 0)
.and_then(|mut v| { .and_then(|mut val| {
self.set_dot_val_helper(scope, v.as_mut(), rhs, new_val, level) self.set_dot_val_helper(scope, val.as_mut(), rhs, new_val, level)
.map(|_| v) // Discard Ok return value .map(|_| val) // Discard Ok return value
}) })
.and_then(|mut v| { .and_then(|mut val| {
let mut args = [this_ptr, v.as_mut()]; let mut args = [this_ptr, val.as_mut()];
self.call_fn_raw(&make_setter(id), &mut args, None, *pos, 0) self.call_fn_raw(&make_setter(id), &mut args, None, *pos, 0)
}) })
} }
@ -986,12 +974,13 @@ impl Engine<'_> {
// Avoid referencing scope which is used below as mut // Avoid referencing scope which is used below as mut
let entry = ScopeSource { name: id, ..entry }; let entry = ScopeSource { name: id, ..entry };
let this_ptr = target.as_mut(); let this_ptr = target.as_mut();
let val = self.set_dot_val_helper(scope, this_ptr, dot_rhs, new_val, level); let value =
self.set_dot_val_helper(scope, this_ptr, dot_rhs, new_val, level);
// 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(entry) = target; *scope.get_mut(entry) = target;
val value
} }
} }
} }
@ -1004,7 +993,7 @@ impl Engine<'_> {
self.eval_index_expr(scope, lhs, idx_expr, *op_pos, level)?; self.eval_index_expr(scope, lhs, idx_expr, *op_pos, level)?;
let val_pos = new_val.1; let val_pos = new_val.1;
let this_ptr = target.as_mut(); let this_ptr = target.as_mut();
let val = self.set_dot_val_helper(scope, this_ptr, dot_rhs, new_val, level); let value = self.set_dot_val_helper(scope, this_ptr, dot_rhs, new_val, level);
// 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(src) = src { if let Some(src) = src {
@ -1027,7 +1016,7 @@ impl Engine<'_> {
} }
} }
val value
} }
// Syntax error // Syntax error
@ -1474,7 +1463,7 @@ impl Engine<'_> {
pub(crate) fn map_type_name<'a>(&'a self, name: &'a str) -> &'a str { pub(crate) fn map_type_name<'a>(&'a self, name: &'a str) -> &'a str {
self.type_names self.type_names
.get(name) .get(name)
.map(|s| s.as_str()) .map(String::as_str)
.unwrap_or(name) .unwrap_or(name)
} }

View File

@ -145,7 +145,7 @@ macro_rules! def_register {
fn register_fn(&mut self, name: &str, f: FN) { fn register_fn(&mut self, name: &str, f: FN) {
let fn_name = name.to_string(); let fn_name = name.to_string();
let fun = move |args: &mut FnCallArgs, pos: Position| { let func = move |args: &mut FnCallArgs, pos: Position| {
// Check for length at the beginning to avoid per-element bound checks. // Check for length at the beginning to avoid per-element bound checks.
const NUM_ARGS: usize = count_args!($($par)*); const NUM_ARGS: usize = count_args!($($par)*);
@ -165,7 +165,7 @@ macro_rules! def_register {
let r = f($(($clone)($par)),*); let r = f($(($clone)($par)),*);
Ok(Box::new(r) as Dynamic) Ok(Box::new(r) as Dynamic)
}; };
self.register_fn_raw(name, vec![$(TypeId::of::<$par>()),*], Box::new(fun)); self.register_fn_raw(name, vec![$(TypeId::of::<$par>()),*], Box::new(func));
} }
} }
@ -177,7 +177,7 @@ macro_rules! def_register {
fn register_dynamic_fn(&mut self, name: &str, f: FN) { fn register_dynamic_fn(&mut self, name: &str, f: FN) {
let fn_name = name.to_string(); let fn_name = name.to_string();
let fun = move |args: &mut FnCallArgs, pos: Position| { let func = move |args: &mut FnCallArgs, pos: Position| {
// Check for length at the beginning to avoid per-element bound checks. // Check for length at the beginning to avoid per-element bound checks.
const NUM_ARGS: usize = count_args!($($par)*); const NUM_ARGS: usize = count_args!($($par)*);
@ -196,7 +196,7 @@ macro_rules! def_register {
// potentially clone the value, otherwise pass the reference. // potentially clone the value, otherwise pass the reference.
Ok(f($(($clone)($par)),*)) Ok(f($(($clone)($par)),*))
}; };
self.register_fn_raw(name, vec![$(TypeId::of::<$par>()),*], Box::new(fun)); self.register_fn_raw(name, vec![$(TypeId::of::<$par>()),*], Box::new(func));
} }
} }
@ -209,7 +209,7 @@ macro_rules! def_register {
fn register_result_fn(&mut self, name: &str, f: FN) { fn register_result_fn(&mut self, name: &str, f: FN) {
let fn_name = name.to_string(); let fn_name = name.to_string();
let fun = move |args: &mut FnCallArgs, pos: Position| { let func = move |args: &mut FnCallArgs, pos: Position| {
// Check for length at the beginning to avoid per-element bound checks. // Check for length at the beginning to avoid per-element bound checks.
const NUM_ARGS: usize = count_args!($($par)*); const NUM_ARGS: usize = count_args!($($par)*);
@ -229,7 +229,7 @@ macro_rules! def_register {
f($(($clone)($par)),*).map(|r| Box::new(r) as Dynamic) f($(($clone)($par)),*).map(|r| Box::new(r) as Dynamic)
.map_err(|err| err.set_position(pos)) .map_err(|err| err.set_position(pos))
}; };
self.register_fn_raw(name, vec![$(TypeId::of::<$par>()),*], Box::new(fun)); self.register_fn_raw(name, vec![$(TypeId::of::<$par>()),*], Box::new(func));
} }
} }

View File

@ -37,7 +37,15 @@ struct State<'a> {
engine: &'a Engine<'a>, engine: &'a Engine<'a>,
} }
impl State<'_> { impl<'a> State<'a> {
/// Create a new State.
pub fn new(engine: &'a Engine<'a>) -> Self {
Self {
changed: false,
constants: vec![],
engine,
}
}
/// Reset the state from dirty to clean. /// Reset the state from dirty to clean.
pub fn reset(&mut self) { pub fn reset(&mut self) {
self.changed = false; self.changed = false;
@ -370,24 +378,16 @@ fn optimize_expr<'a>(expr: Expr, state: &mut State<'a>) -> Expr {
}, },
// [ items .. ] // [ items .. ]
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
Expr::Array(items, pos) => { Expr::Array(items, pos) => Expr::Array(items
let items: Vec<_> = items
.into_iter() .into_iter()
.map(|expr| optimize_expr(expr, state)) .map(|expr| optimize_expr(expr, state))
.collect(); .collect(), pos),
Expr::Array(items, pos)
}
// [ items .. ] // [ items .. ]
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
Expr::Map(items, pos) => { Expr::Map(items, pos) => Expr::Map(items
let items: Vec<_> = items
.into_iter() .into_iter()
.map(|(key, expr, pos)| (key, optimize_expr(expr, state), pos)) .map(|(key, expr, pos)| (key, optimize_expr(expr, state), pos))
.collect(); .collect(), pos),
Expr::Map(items, pos)
}
// lhs && rhs // lhs && rhs
Expr::And(lhs, rhs) => match (*lhs, *rhs) { Expr::And(lhs, rhs) => match (*lhs, *rhs) {
// true && rhs -> rhs // true && rhs -> rhs
@ -439,7 +439,7 @@ fn optimize_expr<'a>(expr: Expr, state: &mut State<'a>) -> Expr {
Expr::FunctionCall(id, args, def_value, pos) if id == KEYWORD_DUMP_AST => 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 // Do not call some special keywords
Expr::FunctionCall(id, args, def_value, pos) if DONT_EVAL_KEYWORDS.contains(&id.as_str())=> 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), Expr::FunctionCall(id, args.into_iter().map(|a| optimize_expr(a, state)).collect(), def_value, pos),
@ -465,8 +465,8 @@ fn optimize_expr<'a>(expr: Expr, state: &mut State<'a>) -> Expr {
"" ""
}; };
state.engine.call_ext_fn_raw(&id, &mut call_args, pos).ok().map(|r| state.engine.call_ext_fn_raw(&id, &mut call_args, pos).ok().map(|result|
r.or_else(|| { result.or_else(|| {
if !arg_for_type_of.is_empty() { if !arg_for_type_of.is_empty() {
// Handle `type_of()` // Handle `type_of()`
Some(arg_for_type_of.to_string().into_dynamic()) Some(arg_for_type_of.to_string().into_dynamic())
@ -506,11 +506,7 @@ pub(crate) fn optimize<'a>(statements: Vec<Stmt>, engine: &Engine<'a>, scope: &S
} }
// Set up the state // Set up the state
let mut state = State { let mut state = State::new(engine);
changed: false,
constants: vec![],
engine,
};
// Add constants from the scope into the state // Add constants from the scope into the state
scope scope