Merge pull request #120 from schungx/master

Add object maps.
This commit is contained in:
Stephen Chung 2020-04-01 10:25:49 +08:00 committed by GitHub
commit d614d5da1a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
27 changed files with 1293 additions and 717 deletions

View File

@ -20,13 +20,14 @@ categories = [ "no-std", "embedded", "parser-implementations" ]
num-traits = "0.2.11"
[features]
#default = ["no_function", "no_index", "no_float", "only_i32", "no_stdlib", "unchecked", "no_optimize"]
#default = ["no_function", "no_index", "no_object", "no_float", "only_i32", "no_stdlib", "unchecked", "no_optimize"]
default = []
unchecked = [] # unchecked arithmetic
no_stdlib = [] # no standard library of utility functions
no_index = [] # no arrays and indexing
no_float = [] # no floating-point
no_function = [] # no script-defined functions
no_object = [] # no custom objects
no_optimize = [] # no script optimizer
optimize_full = [] # set optimization level to Full (default is Simple) - this is a feature used only to simplify testing
only_i32 = [] # set INT=i32 (useful for 32-bit systems)

357
README.md
View File

@ -62,6 +62,7 @@ Optional features
| `unchecked` | Exclude arithmetic checking (such as overflows and division by zero). Beware that a bad script may panic the entire system! |
| `no_function` | Disable script-defined functions if not needed. |
| `no_index` | Disable arrays and indexing features if not needed. |
| `no_object` | Disable support for custom types and objects. |
| `no_float` | Disable floating-point numbers and math if not needed. |
| `no_optimize` | Disable the script optimizer. |
| `only_i32` | Set the system integer type to `i32` and disable all other integer types. `INT` is set to `i32`. |
@ -76,6 +77,7 @@ Excluding unneeded functionalities can result in smaller, faster builds as well
[`no_index`]: #optional-features
[`no_float`]: #optional-features
[`no_function`]: #optional-features
[`no_object`]: #optional-features
[`no_optimize`]: #optional-features
[`only_i32`]: #optional-features
[`only_i64`]: #optional-features
@ -177,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 = 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:
@ -259,20 +261,23 @@ Values and types
----------------
[`type_of()`]: #values-and-types
[`to_string()`]: #values-and-types
The following primitive types are supported natively:
| Category | Equivalent Rust types | `type_of()` name |
| ----------------------------------------------------------- | ---------------------------------------------------------------------------------------------------- | ----------------- |
| **Integer number** | `u8`, `i8`, `u16`, `i16`, <br/>`u32`, `i32` (default for [`only_i32`]),<br/>`u64`, `i64` _(default)_ | _same as type_ |
| **Floating-point number** (disabled with [`no_float`]) | `f32`, `f64` _(default)_ | _same as type_ |
| **Boolean value** | `bool` | `"bool"` |
| **Unicode character** | `char` | `"char"` |
| **Unicode string** | `String` (_not_ `&str`) | `"string"` |
| **Array** (disabled with [`no_index`]) | `rhai::Array` | `"array"` |
| **Dynamic value** (i.e. can be anything) | `rhai::Dynamic` | _the actual type_ |
| **System number** (current configuration) | `rhai::INT` (`i32` or `i64`),<br/>`rhai::FLOAT` (`f32` or `f64`) | _same as type_ |
| **Nothing/void/nil/null** (or whatever you want to call it) | `()` | `"()"` |
| Category | Equivalent Rust types | `type_of()` | `to_string()` |
| ----------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------- | --------------------- | --------------------- |
| **Integer number** | `u8`, `i8`, `u16`, `i16`, <br/>`u32`, `i32` (default for [`only_i32`]),<br/>`u64`, `i64` _(default)_ | `"i32"`, `"u64"` etc. | `"42"`, `"123"` etc. |
| **Floating-point number** (disabled with [`no_float`]) | `f32`, `f64` _(default)_ | `"f32"` or `"f64"` | `"123.4567"` etc. |
| **Boolean value** | `bool` | `"bool"` | `"true"` or `"false"` |
| **Unicode character** | `char` | `"char"` | `"A"`, `"x"` etc. |
| **Unicode string** | `String` (_not_ `&str`) | `"string"` | `"hello"` etc. |
| **Array** (disabled with [`no_index`]) | `rhai::Array` | `"array"` | `"[ ? ? ? ]"` |
| **Object map** (disabled with [`no_object`]) | `rhai::Map` | `"map"` | `#{ "a": 1, "b": 2 }` |
| **Dynamic value** (i.e. can be anything) | `rhai::Dynamic` | _the actual type_ | _actual value_ |
| **System integer** (current configuration) | `rhai::INT` (`i32` or `i64`) | `"i32"` or `"i64"` | `"42"`, `"123"` etc. |
| **System floating-point** (current configuration, disabled with [`no_float`]) | `rhai::FLOAT` (`f32` or `f64`) | `"f32"` or `"f64"` | `"123.456"` etc. |
| **Nothing/void/nil/null** (or whatever you want to call it) | `()` | `"()"` | `""` _(empty string)_ |
[`Dynamic`]: #values-and-types
[`()`]: #values-and-types
@ -286,7 +291,9 @@ This is useful on some 32-bit systems where using 64-bit integers incurs a perfo
If no floating-point is needed or supported, use the [`no_float`] feature to remove it.
There is a `type_of` function to detect the actual type of a value. This is useful because all variables are `Dynamic`.
The `to_string` function converts a standard type into a string for display purposes.
The `type_of` function detects the actual type of a value. This is useful because all variables are `Dynamic`.
```rust
// Use 'type_of()' to get the actual types of values
@ -294,7 +301,7 @@ type_of('c') == "char";
type_of(42) == "i64";
let x = 123;
x.type_of(); // error - 'type_of' cannot use postfix notation
x.type_of(); // <- error: 'type_of' cannot use method-call style
type_of(x) == "i64";
x = 99.999;
@ -312,12 +319,13 @@ Value conversions
[`to_int`]: #value-conversions
[`to_float`]: #value-conversions
There is a `to_float` function to convert a supported number to an `f64`, and a `to_int` function to convert a supported number to `i64` and that's about it.
For other conversions, register custom conversion functions.
The `to_float` function converts a supported number to `FLOAT` (`f32` or `f64`),
and the `to_int` function converts a supported number to `INT` (`i32` or `i64`).
That's about it. For other conversions, register custom conversion functions.
```rust
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 z = y.to_int() + x; // works
@ -333,8 +341,8 @@ To call these functions, they need to be registered with the [`Engine`].
```rust
use rhai::{Engine, EvalAltResult};
use rhai::RegisterFn; // use `RegisterFn` trait for `register_fn`
use rhai::{Dynamic, RegisterDynamicFn}; // use `RegisterDynamicFn` trait for `register_dynamic_fn`
use rhai::RegisterFn; // use `RegisterFn` trait for `register_fn`
use rhai::{Dynamic, RegisterDynamicFn}; // use `RegisterDynamicFn` trait for `register_dynamic_fn`
// Normal function
fn add(x: i64, y: i64) -> i64 {
@ -354,14 +362,14 @@ fn main() -> Result<(), EvalAltResult>
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()
engine.register_dynamic_fn("get_an_any", get_an_any);
let result = engine.eval::<i64>("get_an_any()")?;
println!("Answer: {}", result); // prints 42
println!("Answer: {}", result); // prints 42
Ok(())
}
@ -415,13 +423,13 @@ The function must return `Result<_, EvalAltResult>`. `EvalAltResult` implements
```rust
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
fn safe_divide(x: i64, y: i64) -> Result<i64, EvalAltResult> {
if y == 0 {
// 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 {
Ok(x / y)
}
@ -435,7 +443,7 @@ fn main()
engine.register_result_fn("divide", safe_divide);
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)")
}
}
```
@ -496,6 +504,7 @@ fn main() -> Result<(), EvalAltResult>
```
All custom types must implement `Clone`. This allows the [`Engine`] to pass by value.
You can turn off support for custom types via the [`no_object`] feature.
```rust
#[derive(Clone)]
@ -522,7 +531,8 @@ let mut engine = Engine::new();
engine.register_type::<TestStruct>();
```
To use methods and functions with the [`Engine`], we need to register them. There are some convenience functions to help with this. Below I register update and new with the [`Engine`].
To use methods and functions with the [`Engine`], we need to register them. There are some convenience functions to help with this.
Below I register update and new with the [`Engine`].
*Note: [`Engine`] follows the convention that methods use a `&mut` first parameter so that invoking methods can update the value in memory.*
@ -531,7 +541,8 @@ engine.register_fn("update", TestStruct::update); // registers 'update(&mut ts
engine.register_fn("new_ts", TestStruct::new); // registers 'new'
```
Finally, we call our script. The script can see the function and method we registered earlier. We need to get the result back out from script land just as before, this time casting to our custom struct type.
Finally, we call our script. The script can see the function and method we registered earlier.
We need to get the result back out from script land just as before, this time casting to our custom struct type.
```rust
let result = engine.eval::<TestStruct>("let x = new_ts(); x.update(); x")?;
@ -539,7 +550,8 @@ let result = engine.eval::<TestStruct>("let x = new_ts(); x.update(); x")?;
println!("result: {}", result.field); // prints 42
```
In fact, any function with a first argument (either by copy or via a `&mut` reference) can be used as a method-call on that type because internally they are the same thing: methods on a type is implemented as a functions taking an first argument.
In fact, any function with a first argument (either by copy or via a `&mut` reference) can be used as a method-call on that type because internally they are the same thing:
methods on a type is implemented as a functions taking an first argument.
```rust
fn foo(ts: &mut TestStruct) -> i64 {
@ -553,7 +565,15 @@ let result = engine.eval::<i64>("let x = new_ts(); x.foo()")?;
println!("result: {}", result); // prints 1
```
[`type_of()`] works fine with custom types and returns the name of the type. If `register_type_with_name` is used to register the custom type
If the [`no_object`] feature is turned on, however, the _method_ style of function calls (i.e. calling a function as an object-method) is no longer supported.
```rust
// Below is a syntax error under 'no_object' because 'len' cannot be called in method style.
let result = engine.eval::<i64>("let x = [1, 2, 3]; x.len()")?;
```
[`type_of()`] works fine with custom types and returns the name of the type.
If `register_type_with_name` is used to register the custom type
with a special "pretty-print" name, [`type_of()`] will return that name instead.
```rust
@ -605,6 +625,9 @@ let result = engine.eval::<i64>("let a = new_ts(); a.xyz = 42; a.xyz")?;
println!("Answer: {}", result); // prints 42
```
Needless to say, `register_type`, `register_type_with_name`, `register_get`, `register_set` and `register_get_set`
are not available when the [`no_object`] feature is turned on.
Initializing and maintaining state
---------------------------------
@ -711,6 +734,8 @@ let a = { 40 + 2 }; // 'a' is set to the value of the statement block, which
Variables
---------
[variables]: #variables
Variables in Rhai follow normal C naming rules (i.e. must contain only ASCII letters, digits and underscores '`_`').
Variable names must start with an ASCII letter or an underscore '`_`', must contain at least one ASCII letter, and must start with an ASCII letter before a digit.
@ -720,41 +745,41 @@ 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.
```rust
let x = 3; // ok
let _x = 42; // ok
let x_ = 42; // also ok
let _x_ = 42; // still ok
let x = 3; // ok
let _x = 42; // ok
let x_ = 42; // also ok
let _x_ = 42; // still ok
let _ = 123; // syntax error - illegal variable name
let _9 = 9; // syntax error - illegal variable name
let _ = 123; // <- syntax error: illegal variable name
let _9 = 9; // <- syntax error: illegal variable name
let x = 42; // variable is 'x', lower case
let X = 123; // variable is 'X', upper case
let x = 42; // variable is 'x', lower case
let X = 123; // variable is 'X', upper case
x == 42;
X == 123;
{
let x = 999; // local variable 'x' shadows the 'x' in parent block
x == 999; // access to local 'x'
let x = 999; // local variable 'x' shadows the 'x' in parent block
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 can be defined using the `const` keyword and are immutable. Constants follow the same naming rules as [variables](#variables).
Constants can be defined using the `const` keyword and are immutable. Constants follow the same naming rules as [variables].
```rust
const x = 42;
print(x * 2); // prints 84
x = 123; // syntax error - cannot assign to constant
print(x * 2); // prints 84
x = 123; // <- syntax error: cannot assign to constant
```
Constants must be assigned a _value_, not an expression.
```rust
const x = 40 + 2; // syntax error - cannot assign expression to constant
const x = 40 + 2; // <- syntax error: cannot assign expression to constant
```
Numbers
@ -982,7 +1007,7 @@ let foo = [1, 2, 3][0];
foo == 1;
fn abc() {
[42, 43, 44] // a function returning an array literal
[42, 43, 44] // a function returning an array
}
let foo = abc()[0];
@ -1023,6 +1048,84 @@ print(y.len()); // prints 0
engine.register_fn("push", |list: &mut Array, item: MyType| list.push(Box::new(item)) );
```
Object maps
-----------
Object maps are dictionaries. Properties of any type (`Dynamic`) can be freely added and retrieved.
Object map literals are built within braces '`#{`' ... '`}`' (_name_ `:` _value_ syntax similar to Rust)
and separated by commas '`,`'. The property _name_ can be a simple variable name following the same
naming rules as [variables], or an arbitrary string literal.
Property values can be accessed via the dot notation (_object_ `.` _property_) or index notation (_object_ `[` _property_ `]`).
The dot notation allows only property names that follow the same naming rules as [variables].
The index notation allows setting/getting properties of arbitrary names (even the empty string).
**Important:** Trying to read a non-existent property returns `()` instead of causing an error.
The Rust type of a Rhai object map is `rhai::Map`.
[`type_of()`] an object map returns `"map"`.
Object maps are disabled via the [`no_object`] feature.
The following functions (defined in the standard library but excluded if [`no_stdlib`]) operate on object maps:
| Function | Description |
| -------- | ------------------------------------------------------------ |
| `has` | does the object map contain a property of a particular name? |
| `len` | returns the number of properties |
| `clear` | empties the object map |
Examples:
```rust
let y = #{ // object map literal with 3 properties
a: 1,
bar: "hello",
"baz!$@": 123.456, // like JS, you can use any string as property names...
"": false, // even the empty string!
a: 42 // <- syntax error: duplicated property name
};
y.a = 42; // access via dot notation
y.baz!$@ = 42; // <- syntax error: only proper variable names allowed in dot notation
y."baz!$@" = 42; // <- syntax error: strings not allowed in dot notation
print(y.a); // prints 42
print(y["baz!$@"]); // prints 123.456 - access via index notation
ts.obj = y; // object maps can be assigned completely (by value copy)
let foo = ts.list.a;
foo == 42;
let foo = #{ a:1, b:2, c:3 }["a"];
foo == 1;
fn abc() {
#{ a:1, b:2, c:3 } // a function returning an object map
}
let foo = abc().b;
foo == 2;
let foo = y["a"];
foo == 42;
y.has("a") == true;
y.has("xyz") == false;
y.xyz == (); // A non-existing property returns '()'
y["xyz"] == ();
print(y.len()); // prints 3
y.clear(); // empty the object map
print(y.len()); // prints 0
```
Comparison operators
--------------------
@ -1032,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`.
```rust
42 == 42; // true
42 > 42; // false
"hello" > "foo"; // true
"42" == 42; // false
42 == 42; // true
42 > 42; // false
"hello" > "foo"; // true
"42" == 42; // false
```
Comparing two values of _different_ data types, or of unknown data types, always results in `false`.
```rust
42 == 42.0; // false - i64 is different from f64
42 > "42"; // false - i64 is different from string
42 <= "42"; // false again
42 == 42.0; // false - i64 is different from f64
42 > "42"; // false - i64 is different from string
42 <= "42"; // false again
let ts = new_ts(); // custom type
ts == 42; // false - types are not the same
let ts = new_ts(); // custom type
ts == 42; // false - types are not the same
```
Boolean operators
@ -1066,11 +1169,11 @@ if the first one already proves the condition wrong.
Single boolean operators `&` and `|` always evaluate both operands.
```rust
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 true
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
@ -1078,13 +1181,13 @@ Compound assignment operators
```rust
let number = 5;
number += 4; // number = number + 4
number -= 3; // number = number - 3
number *= 2; // number = number * 2
number /= 1; // number = number / 1
number %= 3; // number = number % 3
number <<= 2; // number = number << 2
number >>= 1; // number = number >> 1
number += 4; // number = number + 4
number -= 3; // number = number - 3
number *= 2; // number = number * 2
number /= 1; // number = number / 1
number %= 3; // number = number % 3
number <<= 2; // number = number << 2
number >>= 1; // number = number >> 1
```
The `+=` operator can also be used to build strings:
@ -1183,9 +1286,9 @@ for x in range(0, 50) {
-------------------
```rust
return; // equivalent to return ();
return; // equivalent to return ();
return 123 + 456; // returns 579
return 123 + 456; // returns 579
```
Errors and `throw`-ing exceptions
@ -1196,10 +1299,10 @@ To deliberately return an error during an evaluation, use the `throw` keyword.
```rust
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_`))`
@ -1214,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
@ -1236,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.
```rust
fn add(x, y) {
x + y; // value of the last statement (no need for ending semicolon) is used as the return value
fn add(x, y) { // implicit return:
x + y; // value of the last statement (no need for ending semicolon)
// is used as the return value
}
fn add2(x) {
return x + 2; // explicit return
return x + 2; // explicit return
}
print(add(2, 3)); // prints 5
print(add2(42)); // prints 44
print(add(2, 3)); // prints 5
print(add2(42)); // prints 44
```
### No access to external scope
@ -1255,7 +1359,7 @@ Functions can only access their parameters. They cannot access external variabl
```rust
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
@ -1265,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.
```rust
fn change(s) { // 's' is passed by value
s = 42; // only a COPY of 's' is changed
fn change(s) { // 's' is passed by value
s = 42; // only a COPY of 's' is changed
}
let x = 500;
x.change(); // de-sugars to change(x)
x == 500; // 'x' is NOT changed!
x.change(); // de-sugars to change(x)
x == 500; // 'x' is NOT changed!
```
### Global definitions only
@ -1286,7 +1390,7 @@ fn add(x, y) {
// The following will not compile
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
}
@ -1307,10 +1411,10 @@ fn foo(x,y) { print("Two! " + x + "," + y) }
fn foo() { print("None.") }
fn foo(x) { print("HA! NEW ONE! " + x) } // overwrites previous definition
foo(1,2,3); // prints "Three!!! 1,2,3"
foo(42); // prints "HA! NEW ONE! 42"
foo(1,2); // prints "Two!! 1,2"
foo(); // prints "None."
foo(1,2,3); // prints "Three!!! 1,2,3"
foo(42); // prints "HA! NEW ONE! 42"
foo(1,2); // prints "Two!! 1,2"
foo(); // prints "None."
```
Members and methods
@ -1319,23 +1423,26 @@ Members and methods
Properties and methods in a Rust custom type registered with the [`Engine`] can be called just like in Rust.
```rust
let a = new_ts(); // constructor function
a.field = 500; // property access
a.update(); // method call
let a = new_ts(); // constructor function
a.field = 500; // property access
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.
`print` and `debug`
-------------------
The `print` and `debug` functions default to printing to `stdout`, with `debug` using standard debug formatting.
```rust
print("hello"); // prints hello to stdout
print(1 + 2 + 3); // prints 6 to stdout
print("hello" + 42); // prints hello42 to stdout
debug("world!"); // prints "world!" to stdout using debug formatting
print("hello"); // prints hello to stdout
print(1 + 2 + 3); // prints 6 to stdout
print("hello" + 42); // prints hello42 to stdout
debug("world!"); // prints "world!" to stdout using debug formatting
```
### Overriding `print` and `debug` with callback functions
@ -1376,14 +1483,13 @@ For example, in the following:
```rust
{
let x = 999; // NOT eliminated - Rhai doesn't check yet whether a variable is used later on
123; // eliminated - no effect
"hello"; // eliminated - no effect
[1, 2, x, x*2, 5]; // eliminated - no effect
foo(42); // NOT eliminated - the function 'foo' may have side effects
666 // NOT eliminated - this is the return value of the block,
// and the block is the last one
// so this is the return value of the whole script
let x = 999; // NOT eliminated: Rhai doesn't check yet whether a variable is used later on
123; // eliminated: no effect
"hello"; // eliminated: no effect
[1, 2, x, x*2, 5]; // eliminated: no effect
foo(42); // NOT eliminated: the function 'foo' may have side effects
666 // NOT eliminated: this is the return value of the block,
// and the block is the last one so this is the return value of the whole script
}
```
@ -1406,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 { print("done!"); } // <- the line above is equivalent 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
@ -1421,7 +1527,7 @@ so they are not optimized away:
const DECISION = 1;
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
:
@ -1491,14 +1597,15 @@ evaluated all function calls with constant arguments, using the result to replac
// When compiling the following with OptimizationLevel::Full...
const DECISION = 1;
// this condition is now eliminated because 'DECISION == 1'
if DECISION == 1 { // is a function call to the '==' function, and it returns 'true'
print("hello!"); // this block is promoted to the parent level
// this condition is now eliminated because 'DECISION == 1'
if DECISION == 1 { // is a function call to the '==' function, and it returns 'true'
print("hello!"); // this block is promoted to the parent level
} 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.
@ -1507,8 +1614,8 @@ This does not happen with [`OptimizationLevel::Simple`] which doesn't assume all
```rust
// When compiling the following with OptimizationLevel::Full...
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 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'
```
Function side effect considerations
@ -1539,13 +1646,13 @@ Subtle semantic changes
Some optimizations can alter subtle semantics of the script. For example:
```rust
if true { // condition always true
123.456; // eliminated
hello; // eliminated, EVEN THOUGH the variable doesn't exist!
foo(42) // promoted up-level
if true { // condition always true
123.456; // eliminated
hello; // eliminated, EVEN THOUGH the variable doesn't exist!
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,
@ -1591,22 +1698,22 @@ let x = 10;
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 += "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("y = " + y); // prints 32 - variables defined in 'eval' persist!
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
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' postfix notation 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_,
@ -1616,8 +1723,8 @@ 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
eval(script); // variable 'x' in the current scope is visible!
print(x); // prints 42
// The above is equivalent to:
let script = "x += 32";
@ -1632,7 +1739,7 @@ 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"
let x = eval("40 + 2"); // 'eval' here throws "eval is evil! I refuse to run 40 + 2"
```
Or override it from Rust:

View File

@ -15,6 +15,8 @@ impl TestStruct {
}
}
#[cfg(not(feature = "no_index"))]
#[cfg(not(feature = "no_object"))]
fn main() {
let mut engine = Engine::new();
@ -32,3 +34,6 @@ fn main() {
engine.eval::<TestStruct>("let x = [new_ts()]; x[0].update(); x[0]")
);
}
#[cfg(any(feature = "no_index", feature = "no_object"))]
fn main() {}

View File

@ -15,6 +15,7 @@ impl TestStruct {
}
}
#[cfg(not(feature = "no_object"))]
fn main() -> Result<(), EvalAltResult> {
let mut engine = Engine::new();
@ -29,3 +30,6 @@ fn main() -> Result<(), EvalAltResult> {
Ok(())
}
#[cfg(feature = "no_object")]
fn main() {}

View File

@ -2,7 +2,7 @@
use crate::any::{Any, AnyExt, Dynamic};
use crate::call::FuncArgs;
use crate::engine::{Engine, FnAny, FnSpec, FUNC_GETTER, FUNC_SETTER};
use crate::engine::{make_getter, make_setter, Engine, FnAny, FnSpec};
use crate::error::ParseError;
use crate::fn_register::RegisterFn;
use crate::parser::{lex, parse, parse_global_expr, FnDef, Position, AST};
@ -15,7 +15,6 @@ use crate::optimize::optimize_into_ast;
use crate::stdlib::{
any::{type_name, TypeId},
boxed::Box,
format,
string::{String, ToString},
sync::Arc,
vec::Vec,
@ -25,12 +24,7 @@ use crate::stdlib::{fs::File, io::prelude::*, path::PathBuf};
impl<'e> Engine<'e> {
/// Register a custom function.
pub(crate) fn register_fn_raw(
&mut self,
fn_name: &str,
args: Option<Vec<TypeId>>,
f: Box<FnAny>,
) {
pub(crate) fn register_fn_raw(&mut self, fn_name: &str, args: Vec<TypeId>, f: Box<FnAny>) {
let spec = FnSpec {
name: fn_name.to_string().into(),
args,
@ -45,7 +39,7 @@ impl<'e> Engine<'e> {
/// # Example
///
/// ```
/// #[derive(Clone)]
/// #[derive(Debug, Clone, Eq, PartialEq)]
/// struct TestStruct {
/// field: i64
/// }
@ -69,12 +63,13 @@ impl<'e> Engine<'e> {
/// engine.register_fn("update", TestStruct::update);
///
/// assert_eq!(
/// engine.eval::<TestStruct>("let x = new_ts(); x.update(41); x")?.field,
/// 42
/// engine.eval::<TestStruct>("let x = new_ts(); x.update(41); x")?,
/// TestStruct { field: 42 }
/// );
/// # Ok(())
/// # }
/// ```
#[cfg(not(feature = "no_object"))]
pub fn register_type<T: Any + Clone>(&mut self) {
self.register_type_with_name::<T>(type_name::<T>());
}
@ -86,7 +81,7 @@ impl<'e> Engine<'e> {
///
/// ```
/// #[derive(Clone)]
/// struct TestStruct {
/// struct TestStruct {
/// field: i64
/// }
///
@ -122,6 +117,7 @@ impl<'e> Engine<'e> {
/// # Ok(())
/// # }
/// ```
#[cfg(not(feature = "no_object"))]
pub fn register_type_with_name<T: Any + Clone>(&mut self, name: &str) {
// Add the pretty-print type name into the map
self.type_names
@ -145,7 +141,7 @@ impl<'e> Engine<'e> {
///
/// ```
/// #[derive(Clone)]
/// struct TestStruct {
/// struct TestStruct {
/// field: i64
/// }
///
@ -173,13 +169,13 @@ impl<'e> Engine<'e> {
/// # Ok(())
/// # }
/// ```
#[cfg(not(feature = "no_object"))]
pub fn register_get<T: Any + Clone, U: Any + Clone>(
&mut self,
name: &str,
callback: impl Fn(&mut T) -> U + 'static,
) {
let get_fn_name = format!("{}{}", FUNC_GETTER, name);
self.register_fn(&get_fn_name, callback);
self.register_fn(&make_getter(name), callback);
}
/// Register a setter function for a member of a registered type with the `Engine`.
@ -187,7 +183,7 @@ impl<'e> Engine<'e> {
/// # Example
///
/// ```
/// #[derive(Clone)]
/// #[derive(Debug, Clone, Eq, PartialEq)]
/// struct TestStruct {
/// field: i64
/// }
@ -211,17 +207,20 @@ impl<'e> Engine<'e> {
/// engine.register_set("xyz", TestStruct::set_field);
///
/// // Notice that, with a getter, there is no way to get the property value
/// engine.eval("let a = new_ts(); a.xyz = 42;")?;
/// assert_eq!(
/// engine.eval::<TestStruct>("let a = new_ts(); a.xyz = 42; a")?,
/// TestStruct { field: 42 }
/// );
/// # Ok(())
/// # }
/// ```
#[cfg(not(feature = "no_object"))]
pub fn register_set<T: Any + Clone, U: Any + Clone>(
&mut self,
name: &str,
callback: impl Fn(&mut T, U) -> () + 'static,
) {
let set_fn_name = format!("{}{}", FUNC_SETTER, name);
self.register_fn(&set_fn_name, callback);
self.register_fn(&make_setter(name), callback);
}
/// Shorthand for registering both getter and setter functions
@ -262,6 +261,7 @@ impl<'e> Engine<'e> {
/// # Ok(())
/// # }
/// ```
#[cfg(not(feature = "no_object"))]
pub fn register_get_set<T: Any + Clone, U: Any + Clone>(
&mut self,
name: &str,
@ -346,8 +346,9 @@ impl<'e> Engine<'e> {
let mut contents = String::new();
f.read_to_string(&mut contents)
.map_err(|err| EvalAltResult::ErrorReadingScriptFile(path.clone(), err))
.map(|_| contents)
.map_err(|err| EvalAltResult::ErrorReadingScriptFile(path.clone(), err))?;
Ok(contents)
}
/// Compile a script file into an `AST`, which can be used later for evaluation.
@ -552,8 +553,7 @@ impl<'e> Engine<'e> {
/// # }
/// ```
pub fn eval<T: Any + Clone>(&mut self, input: &str) -> Result<T, EvalAltResult> {
let mut scope = Scope::new();
self.eval_with_scope(&mut scope, input)
self.eval_with_scope(&mut Scope::new(), input)
}
/// Evaluate a string with own scope.
@ -602,8 +602,7 @@ impl<'e> Engine<'e> {
/// # }
/// ```
pub fn eval_expression<T: Any + Clone>(&mut self, input: &str) -> Result<T, EvalAltResult> {
let mut scope = Scope::new();
self.eval_expression_with_scope(&mut scope, input)
self.eval_expression_with_scope(&mut Scope::new(), input)
}
/// Evaluate a string containing an expression with own scope.
@ -655,8 +654,7 @@ impl<'e> Engine<'e> {
/// # }
/// ```
pub fn eval_ast<T: Any + Clone>(&mut self, ast: &AST) -> Result<T, EvalAltResult> {
let mut scope = Scope::new();
self.eval_ast_with_scope(&mut scope, ast)
self.eval_ast_with_scope(&mut Scope::new(), ast)
}
/// Evaluate an `AST` with own scope.
@ -693,14 +691,14 @@ impl<'e> Engine<'e> {
scope: &mut Scope,
ast: &AST,
) -> Result<T, EvalAltResult> {
self.eval_ast_with_scope_raw(scope, false, ast)
.and_then(|out| {
out.downcast::<T>().map(|v| *v).map_err(|a| {
EvalAltResult::ErrorMismatchOutputType(
self.map_type_name((*a).type_name()).to_string(),
Position::none(),
)
})
self.eval_ast_with_scope_raw(scope, false, ast)?
.downcast::<T>()
.map(|v| *v)
.map_err(|a| {
EvalAltResult::ErrorMismatchOutputType(
self.map_type_name((*a).type_name()).to_string(),
Position::none(),
)
})
}
@ -710,34 +708,25 @@ impl<'e> Engine<'e> {
retain_functions: bool,
ast: &AST,
) -> Result<Dynamic, EvalAltResult> {
fn eval_ast_internal(
engine: &mut Engine,
scope: &mut Scope,
retain_functions: bool,
ast: &AST,
) -> Result<Dynamic, EvalAltResult> {
if !retain_functions {
engine.clear_functions();
}
let statements = {
let AST(statements, functions) = ast;
engine.load_script_functions(functions);
statements
};
let result = statements.iter().try_fold(().into_dynamic(), |_, stmt| {
engine.eval_stmt(scope, stmt, 0)
})?;
if !retain_functions {
engine.clear_functions();
}
Ok(result)
if !retain_functions {
self.clear_functions();
}
eval_ast_internal(self, scope, retain_functions, ast).or_else(|err| match err {
let statements = {
let AST(statements, functions) = ast;
self.load_script_functions(functions);
statements
};
let result = statements
.iter()
.try_fold(().into_dynamic(), |_, stmt| self.eval_stmt(scope, stmt, 0));
if !retain_functions {
self.clear_functions();
}
result.or_else(|err| match err {
EvalAltResult::Return(out, _) => Ok(out),
_ => Err(err),
})
@ -746,7 +735,9 @@ impl<'e> Engine<'e> {
/// 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.
///
/// 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"))]
pub fn consume_file(
&mut self,
@ -759,7 +750,9 @@ impl<'e> Engine<'e> {
/// 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.
///
/// 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"))]
pub fn consume_file_with_scope(
&mut self,
@ -774,7 +767,9 @@ impl<'e> Engine<'e> {
/// 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.
///
/// 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> {
self.consume_with_scope(&mut Scope::new(), retain_functions, input)
}
@ -782,7 +777,9 @@ impl<'e> Engine<'e> {
/// 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.
///
/// 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(
&mut self,
scope: &mut Scope,
@ -800,7 +797,9 @@ impl<'e> Engine<'e> {
/// 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.
///
/// 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> {
self.consume_ast_with_scope(&mut Scope::new(), retain_functions, ast)
}
@ -808,7 +807,9 @@ impl<'e> Engine<'e> {
/// 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.
///
/// 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(
&mut self,
scope: &mut Scope,
@ -827,14 +828,13 @@ impl<'e> Engine<'e> {
let result = statements
.iter()
.try_fold(().into_dynamic(), |_, stmt| self.eval_stmt(scope, stmt, 0))
.map(|_| ());
.try_fold(().into_dynamic(), |_, stmt| self.eval_stmt(scope, stmt, 0));
if !retain_functions {
self.clear_functions();
}
result.or_else(|err| match err {
result.map(|_| ()).or_else(|err| match err {
EvalAltResult::Return(_, _) => Ok(()),
_ => Err(err),
})
@ -845,9 +845,9 @@ impl<'e> Engine<'e> {
&mut self,
functions: impl IntoIterator<Item = &'a Arc<FnDef>>,
) {
for f in functions.into_iter() {
self.fn_lib.add_or_replace_function(f.clone());
}
functions.into_iter().cloned().for_each(|f| {
self.fn_lib.add_or_replace_function(f);
});
}
/// Call a script function retained inside the Engine.
@ -864,7 +864,7 @@ impl<'e> Engine<'e> {
/// let mut engine = Engine::new();
///
/// // Set 'retain_functions' in 'consume' to keep the function definitions
/// engine.consume(true, "fn add(x, y) { x.len() + y }")?;
/// engine.consume(true, "fn add(x, y) { len(x) + y }")?;
///
/// // Call the script-defined function
/// let result: i64 = engine.call_fn("add", (String::from("abc"), 123_i64))?;
@ -883,14 +883,14 @@ impl<'e> Engine<'e> {
let mut values = args.into_vec();
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)
.and_then(|b| {
b.downcast().map(|b| *b).map_err(|a| {
EvalAltResult::ErrorMismatchOutputType(
self.map_type_name((*a).type_name()).into(),
Position::none(),
)
})
self.call_fn_raw(name, &mut arg_values, None, Position::none(), 0)?
.downcast()
.map(|b| *b)
.map_err(|a| {
EvalAltResult::ErrorMismatchOutputType(
self.map_type_name((*a).type_name()).into(),
Position::none(),
)
})
}

View File

@ -2,7 +2,7 @@
//! _standard library_ of utility functions.
use crate::any::Any;
use crate::engine::Engine;
use crate::engine::{Engine, FUNC_TO_STRING, KEYWORD_DEBUG, KEYWORD_PRINT};
use crate::fn_register::{RegisterDynamicFn, RegisterFn, RegisterResultFn};
use crate::parser::{Position, INT};
use crate::result::EvalAltResult;
@ -10,6 +10,9 @@ use crate::result::EvalAltResult;
#[cfg(not(feature = "no_index"))]
use crate::engine::Array;
#[cfg(not(feature = "no_object"))]
use crate::engine::Map;
#[cfg(not(feature = "no_float"))]
use crate::parser::FLOAT;
@ -558,10 +561,10 @@ impl Engine<'_> {
self.register_fn("==", |_: (), _: ()| true); // () == ()
// Register print and debug
fn debug<T: Debug>(x: T) -> String {
fn to_debug<T: Debug>(x: T) -> String {
format!("{:?}", x)
}
fn print<T: Display>(x: T) -> String {
fn to_string<T: Display>(x: T) -> String {
format!("{}", x)
}
@ -574,36 +577,58 @@ impl Engine<'_> {
)
}
reg_fn1!(self, "print", print, String, INT, bool, char, String);
self.register_fn("print", || "".to_string());
self.register_fn("print", |_: ()| "".to_string());
reg_fn1!(self, "debug", debug, String, INT, bool, char, String, ());
reg_fn1!(self, KEYWORD_PRINT, to_string, String, INT, bool);
reg_fn1!(self, FUNC_TO_STRING, to_string, String, INT, bool);
reg_fn1!(self, KEYWORD_PRINT, to_string, String, char, String);
reg_fn1!(self, FUNC_TO_STRING, to_string, String, char, String);
self.register_fn(KEYWORD_PRINT, || "".to_string());
self.register_fn(KEYWORD_PRINT, |_: ()| "".to_string());
self.register_fn(FUNC_TO_STRING, |_: ()| "".to_string());
reg_fn1!(self, KEYWORD_DEBUG, to_debug, String, INT, bool, ());
reg_fn1!(self, KEYWORD_DEBUG, to_debug, String, char, String);
#[cfg(not(feature = "only_i32"))]
#[cfg(not(feature = "only_i64"))]
{
reg_fn1!(self, "print", print, String, i8, u8, i16, u16);
reg_fn1!(self, "print", print, String, i32, i64, u32, u64);
reg_fn1!(self, "debug", debug, String, i8, u8, i16, u16);
reg_fn1!(self, "debug", debug, String, i32, i64, u32, u64);
reg_fn1!(self, KEYWORD_PRINT, to_string, String, i8, u8, i16, u16);
reg_fn1!(self, FUNC_TO_STRING, to_string, String, i8, u8, i16, u16);
reg_fn1!(self, KEYWORD_PRINT, to_string, String, i32, i64, u32, u64);
reg_fn1!(self, FUNC_TO_STRING, to_string, String, i32, i64, u32, u64);
reg_fn1!(self, KEYWORD_DEBUG, to_debug, String, i8, u8, i16, u16);
reg_fn1!(self, KEYWORD_DEBUG, to_debug, String, i32, i64, u32, u64);
}
#[cfg(not(feature = "no_float"))]
{
reg_fn1!(self, "print", print, String, f32, f64);
reg_fn1!(self, "debug", debug, String, f32, f64);
reg_fn1!(self, KEYWORD_PRINT, to_string, String, f32, f64);
reg_fn1!(self, FUNC_TO_STRING, to_string, String, f32, f64);
reg_fn1!(self, KEYWORD_DEBUG, to_debug, String, f32, f64);
}
#[cfg(not(feature = "no_index"))]
{
reg_fn1!(self, "print", debug, String, Array);
reg_fn1!(self, "debug", debug, String, Array);
reg_fn1!(self, KEYWORD_PRINT, to_debug, String, Array);
reg_fn1!(self, FUNC_TO_STRING, to_debug, String, Array);
reg_fn1!(self, KEYWORD_DEBUG, to_debug, String, Array);
// Register array iterator
self.register_iterator::<Array, _>(|a| {
Box::new(a.downcast_ref::<Array>().unwrap().clone().into_iter())
});
}
#[cfg(not(feature = "no_object"))]
{
self.register_fn(KEYWORD_PRINT, |x: &mut Map| -> String {
format!("#{:?}", x)
});
self.register_fn(FUNC_TO_STRING, |x: &mut Map| -> String {
format!("#{:?}", x)
});
self.register_fn(KEYWORD_DEBUG, |x: &mut Map| -> String {
format!("#{:?}", x)
});
}
}
// Register range function
@ -822,6 +847,14 @@ impl Engine<'_> {
});
}
// Register map functions
#[cfg(not(feature = "no_object"))]
{
self.register_fn("has", |map: &mut Map, prop: String| map.contains_key(&prop));
self.register_fn("len", |map: &mut Map| map.len() as INT);
self.register_fn("clear", |map: &mut Map| map.clear());
}
// Register string concatenate functions
fn prepend<T: Display>(x: T, y: String) -> String {
format!("{}{}", x, y)

View File

@ -26,33 +26,88 @@ use crate::stdlib::{
#[cfg(not(feature = "no_index"))]
pub type Array = Vec<Dynamic>;
/// An dynamic hash map of `Dynamic` values.
#[cfg(not(feature = "no_object"))]
pub type Map = HashMap<String, Dynamic>;
pub type FnCallArgs<'a> = [&'a mut Variant];
pub type FnAny = dyn Fn(&mut FnCallArgs, Position) -> Result<Dynamic, EvalAltResult>;
type IteratorFn = dyn Fn(&Dynamic) -> Box<dyn Iterator<Item = Dynamic>>;
pub(crate) const MAX_CALL_STACK_DEPTH: usize = 64;
pub(crate) const KEYWORD_PRINT: &str = "print";
pub(crate) const KEYWORD_DEBUG: &str = "debug";
pub(crate) const KEYWORD_DUMP_AST: &str = "dump_ast";
pub(crate) const KEYWORD_TYPE_OF: &str = "type_of";
pub(crate) const KEYWORD_EVAL: &str = "eval";
pub(crate) const FUNC_GETTER: &str = "get$";
pub(crate) const FUNC_SETTER: &str = "set$";
pub const MAX_CALL_STACK_DEPTH: usize = 64;
pub const KEYWORD_PRINT: &str = "print";
pub const KEYWORD_DEBUG: &str = "debug";
pub const KEYWORD_DUMP_AST: &str = "dump_ast";
pub const KEYWORD_TYPE_OF: &str = "type_of";
pub const KEYWORD_EVAL: &str = "eval";
pub const FUNC_TO_STRING: &str = "to_string";
pub const FUNC_GETTER: &str = "get$";
pub const FUNC_SETTER: &str = "set$";
#[derive(Debug, Eq, PartialEq, Hash, Clone, Copy)]
#[cfg(not(feature = "no_index"))]
enum IndexSourceType {
Array,
String,
Expression,
String,
Array,
#[cfg(not(feature = "no_object"))]
Map,
}
#[derive(Debug, Eq, PartialEq, Hash, Clone)]
enum IndexValue {
Num(usize),
Str(String),
}
impl IndexValue {
fn from_num(idx: INT) -> Self {
Self::Num(idx as usize)
}
fn from_str(name: String) -> Self {
Self::Str(name)
}
fn as_num(self) -> usize {
match self {
Self::Num(n) => n,
_ => panic!("index value is numeric"),
}
}
fn as_str(self) -> String {
match self {
Self::Str(s) => s,
_ => panic!("index value is string"),
}
}
}
#[derive(Debug)]
enum Target<'a> {
Scope(ScopeSource<'a>),
Value(&'a mut Variant),
}
impl<'a> Target<'a> {
fn from(value: &'a mut Variant) -> Self {
Self::Value(value)
}
fn from_src(src: ScopeSource<'a>) -> Self {
Self::Scope(src)
}
fn get_mut(self, scope: &'a mut Scope) -> &'a mut Variant {
match self {
Self::Value(t) => t,
Self::Scope(src) => scope.get_mut(src).as_mut(),
}
}
}
#[derive(Debug, Eq, PartialEq, Hash, Clone)]
pub struct FnSpec<'a> {
pub name: Cow<'a, str>,
pub args: Option<Vec<TypeId>>,
pub args: Vec<TypeId>,
}
/// A type that holds a library of script-defined functions.
@ -137,9 +192,9 @@ pub struct Engine<'e> {
/// A hashmap mapping type names to pretty-print names.
pub(crate) type_names: HashMap<String, String>,
/// Closure for implementing the print commands.
/// Closure for implementing the `print` command.
pub(crate) on_print: Box<dyn FnMut(&str) + 'e>,
/// Closure for implementing the debug commands.
/// Closure for implementing the `debug` command.
pub(crate) on_debug: Box<dyn FnMut(&str) + 'e>,
/// Optimize the AST after compilation.
@ -156,6 +211,8 @@ impl Default for Engine<'_> {
let type_names = [
#[cfg(not(feature = "no_index"))]
(type_name::<Array>(), "array"),
#[cfg(not(feature = "no_object"))]
(type_name::<Map>(), "map"),
(type_name::<String>(), "string"),
(type_name::<Dynamic>(), "dynamic"),
]
@ -192,6 +249,34 @@ impl Default for Engine<'_> {
}
}
/// Make getter function
pub fn make_getter(id: &str) -> String {
format!("{}{}", FUNC_GETTER, id)
}
/// Extract the property name from a getter function name.
fn extract_prop_from_getter(fn_name: &str) -> Option<&str> {
if fn_name.starts_with(FUNC_GETTER) {
Some(&fn_name[FUNC_GETTER.len()..])
} else {
None
}
}
/// Make setter function
pub fn make_setter(id: &str) -> String {
format!("{}{}", FUNC_SETTER, id)
}
/// Extract the property name from a setter function name.
fn extract_prop_from_setter(fn_name: &str) -> Option<&str> {
if fn_name.starts_with(FUNC_SETTER) {
Some(&fn_name[FUNC_SETTER.len()..])
} else {
None
}
}
impl Engine<'_> {
/// Create a new `Engine`
pub fn new() -> Self {
@ -220,7 +305,7 @@ impl Engine<'_> {
) -> Result<Option<Dynamic>, EvalAltResult> {
let spec = FnSpec {
name: fn_name.into(),
args: Some(args.iter().map(|a| Any::type_id(&**a)).collect()),
args: args.iter().map(|a| Any::type_id(&**a)).collect(),
};
// Search built-in's and external functions
@ -232,8 +317,7 @@ impl Engine<'_> {
}
}
/// Universal method for calling functions, that are either
/// registered with the `Engine` or written in Rhai
/// Universal method for calling functions either registered with the `Engine` or written in Rhai
pub(crate) fn call_fn_raw(
&mut self,
fn_name: &str,
@ -267,7 +351,7 @@ impl Engine<'_> {
let spec = FnSpec {
name: fn_name.into(),
args: Some(args.iter().map(|a| Any::type_id(&**a)).collect()),
args: args.iter().map(|a| Any::type_id(&**a)).collect(),
};
// Argument must be a string
@ -285,35 +369,46 @@ impl Engine<'_> {
// See if the function match print/debug (which requires special processing)
return Ok(match fn_name {
KEYWORD_PRINT => {
self.on_print.as_mut()(cast_to_string(result.as_ref(), pos)?);
().into_dynamic()
self.on_print.as_mut()(cast_to_string(result.as_ref(), pos)?).into_dynamic()
}
KEYWORD_DEBUG => {
self.on_debug.as_mut()(cast_to_string(result.as_ref(), pos)?);
().into_dynamic()
self.on_debug.as_mut()(cast_to_string(result.as_ref(), pos)?).into_dynamic()
}
_ => result,
});
}
if fn_name.starts_with(FUNC_GETTER) {
if let Some(prop) = extract_prop_from_getter(fn_name) {
#[cfg(not(feature = "no_object"))]
{
// Map property access
if let Some(map) = args[0].downcast_ref::<Map>() {
return Ok(map.get(prop).cloned().unwrap_or_else(|| ().into_dynamic()));
}
}
// Getter function not found
return Err(EvalAltResult::ErrorDotExpr(
format!(
"- property '{}' unknown or write-only",
&fn_name[FUNC_GETTER.len()..]
),
format!("- property '{}' unknown or write-only", prop),
pos,
));
}
if fn_name.starts_with(FUNC_SETTER) {
if let Some(prop) = extract_prop_from_setter(fn_name) {
#[cfg(not(feature = "no_object"))]
{
let value = args[1].into_dynamic();
// Map property update
if let Some(map) = args[0].downcast_mut::<Map>() {
map.insert(prop.to_string(), value);
return Ok(().into_dynamic());
}
}
// Setter function not found
return Err(EvalAltResult::ErrorDotExpr(
format!(
"- property '{}' unknown or read-only",
&fn_name[FUNC_SETTER.len()..]
),
format!("- property '{}' unknown or read-only", prop),
pos,
));
}
@ -337,39 +432,14 @@ impl Engine<'_> {
}
/// 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`.
#[cfg(not(feature = "no_object"))]
fn get_dot_val_helper(
&mut self,
scope: &mut Scope,
src: Option<ScopeSource>,
target: Option<&mut Variant>,
target: Target,
dot_rhs: &Expr,
level: usize,
) -> 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.
scope
.get_mut(src.expect("expected source in scope"))
.as_mut()
}
}
match dot_rhs {
// xxx.fn_name(args)
Expr::FunctionCall(fn_name, arg_expr_list, def_val, pos) => {
@ -378,7 +448,7 @@ impl Engine<'_> {
.map(|arg_expr| self.eval_expr(scope, arg_expr, level))
.collect::<Result<Vec<_>, _>>()?;
let this_ptr = get_this_ptr(scope, src, target);
let this_ptr = target.get_mut(scope);
let mut arg_values: Vec<_> = once(this_ptr)
.chain(values.iter_mut().map(Dynamic::as_mut))
@ -389,29 +459,23 @@ impl Engine<'_> {
// xxx.id
Expr::Property(id, pos) => {
let get_fn_name = format!("{}{}", FUNC_GETTER, id);
let this_ptr = get_this_ptr(scope, src, target);
self.call_fn_raw(&get_fn_name, &mut [this_ptr], None, *pos, 0)
let this_ptr = target.get_mut(scope);
self.call_fn_raw(&make_getter(id), &mut [this_ptr], None, *pos, 0)
}
// xxx.idx_lhs[idx_expr]
#[cfg(not(feature = "no_index"))]
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]
Expr::Property(id, pos) => {
let get_fn_name = format!("{}{}", FUNC_GETTER, id);
let this_ptr = get_this_ptr(scope, src, target);
(
self.call_fn_raw(&get_fn_name, &mut [this_ptr], None, *pos, 0)?,
*pos,
)
let this_ptr = target.get_mut(scope);
self.call_fn_raw(&make_getter(id), &mut [this_ptr], None, *pos, 0)?
}
// xxx.???[???][idx_expr]
Expr::Index(_, _, _) => (
self.get_dot_val_helper(scope, src, target, idx_lhs, level)?,
*op_pos,
),
Expr::Index(_, _, _) => {
self.get_dot_val_helper(scope, target, idx_lhs, level)?
}
// Syntax error
_ => {
return Err(EvalAltResult::ErrorDotExpr(
@ -421,41 +485,33 @@ impl Engine<'_> {
}
};
let idx = self.eval_index_value(scope, idx_expr, level)?;
self.get_indexed_value(&val, idx, idx_expr.position(), *op_pos)
.map(|(v, _)| v)
self.get_indexed_value(scope, &value, idx_expr, *op_pos, level)
.map(|(val, _, _)| val)
}
// xxx.dot_lhs.rhs
Expr::Dot(dot_lhs, rhs, _) => match dot_lhs.as_ref() {
// xxx.id.rhs
Expr::Property(id, pos) => {
let get_fn_name = format!("{}{}", FUNC_GETTER, id);
let this_ptr = get_this_ptr(scope, src, target);
self.call_fn_raw(&get_fn_name, &mut [this_ptr], None, *pos, 0)
.and_then(|mut v| {
self.get_dot_val_helper(scope, None, Some(v.as_mut()), rhs, level)
let this_ptr = target.get_mut(scope);
self.call_fn_raw(&make_getter(id), &mut [this_ptr], None, *pos, 0)
.and_then(|mut val| {
self.get_dot_val_helper(scope, Target::from(val.as_mut()), rhs, level)
})
}
// xxx.idx_lhs[idx_expr].rhs
#[cfg(not(feature = "no_index"))]
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
Expr::Property(id, pos) => {
let get_fn_name = format!("{}{}", FUNC_GETTER, id);
let this_ptr = get_this_ptr(scope, src, target);
(
self.call_fn_raw(&get_fn_name, &mut [this_ptr], None, *pos, 0)?,
*pos,
)
let this_ptr = target.get_mut(scope);
self.call_fn_raw(&make_getter(id), &mut [this_ptr], None, *pos, 0)?
}
// xxx.???[???][idx_expr].rhs
Expr::Index(_, _, _) => (
self.get_dot_val_helper(scope, src, target, idx_lhs, level)?,
*op_pos,
),
Expr::Index(_, _, _) => {
self.get_dot_val_helper(scope, target, idx_lhs, level)?
}
// Syntax error
_ => {
return Err(EvalAltResult::ErrorDotExpr(
@ -465,10 +521,9 @@ impl Engine<'_> {
}
};
let idx = self.eval_index_value(scope, idx_expr, level)?;
self.get_indexed_value(&val, idx, idx_expr.position(), *op_pos)
.and_then(|(mut v, _)| {
self.get_dot_val_helper(scope, None, Some(v.as_mut()), rhs, level)
self.get_indexed_value(scope, &val, idx_expr, *op_pos, level)
.and_then(|(mut val, _, _)| {
self.get_dot_val_helper(scope, Target::from(val.as_mut()), rhs, level)
})
}
// Syntax error
@ -487,6 +542,7 @@ impl Engine<'_> {
}
/// Evaluate a dot chain getter
#[cfg(not(feature = "no_object"))]
fn get_dot_val(
&mut self,
scope: &mut Scope,
@ -497,23 +553,23 @@ impl Engine<'_> {
match dot_lhs {
// id.???
Expr::Variable(id, pos) => {
let (entry, _) = Self::search_scope(scope, id, Ok, *pos)?;
let (entry, _) = Self::search_scope(scope, id, *pos)?;
// Avoid referencing scope which is used below as mut
let entry = ScopeSource { name: id, ..entry };
// This is a variable property access (potential function call).
// Use a direct index into `scope` to directly mutate the variable value.
self.get_dot_val_helper(scope, Some(entry), None, dot_rhs, level)
self.get_dot_val_helper(scope, Target::from_src(entry), dot_rhs, level)
}
// idx_lhs[idx_expr].???
#[cfg(not(feature = "no_index"))]
Expr::Index(idx_lhs, idx_expr, op_pos) => {
let (src_type, src, idx, mut target) =
let (idx_src_type, src, idx, mut val) =
self.eval_index_expr(scope, idx_lhs, idx_expr, *op_pos, level)?;
let this_ptr = target.as_mut();
let val = self.get_dot_val_helper(scope, None, Some(this_ptr), dot_rhs, level);
let value =
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.
if let Some(src) = src {
@ -526,84 +582,101 @@ impl Engine<'_> {
}
ScopeEntryType::Normal => {
Self::update_indexed_var_in_scope(
src_type,
idx_src_type,
scope,
src,
idx,
(target, dot_rhs.position()),
(val, dot_rhs.position()),
)?;
}
}
}
val
value
}
// {expr}.???
expr => {
let mut target = self.eval_expr(scope, expr, level)?;
let this_ptr = target.as_mut();
self.get_dot_val_helper(scope, None, Some(this_ptr), dot_rhs, level)
let mut val = self.eval_expr(scope, expr, level)?;
self.get_dot_val_helper(scope, Target::from(val.as_mut()), dot_rhs, level)
}
}
}
/// Search for a variable within the scope, returning its value and index inside the Scope
fn search_scope<'a, T>(
fn search_scope<'a>(
scope: &'a Scope,
id: &str,
convert: impl FnOnce(Dynamic) -> Result<T, EvalAltResult>,
begin: Position,
) -> Result<(ScopeSource<'a>, T), EvalAltResult> {
) -> Result<(ScopeSource<'a>, Dynamic), EvalAltResult> {
scope
.get(id)
.ok_or_else(|| EvalAltResult::ErrorVariableNotFound(id.into(), begin))
.and_then(move |(src, value)| convert(value).map(|v| (src, v)))
}
/// Evaluate the value of an index (must evaluate to INT)
#[cfg(not(feature = "no_index"))]
fn eval_index_value(
&mut self,
scope: &mut Scope,
idx_expr: &Expr,
level: usize,
) -> Result<INT, EvalAltResult> {
self.eval_expr(scope, idx_expr, level)?
.downcast::<INT>()
.map(|v| *v)
.map_err(|_| EvalAltResult::ErrorIndexExpr(idx_expr.position()))
}
/// Get the value at the indexed position of a base type
#[cfg(not(feature = "no_index"))]
fn get_indexed_value(
&self,
&mut self,
scope: &mut Scope,
val: &Dynamic,
idx: INT,
idx_pos: Position,
idx_expr: &Expr,
op_pos: Position,
) -> Result<(Dynamic, IndexSourceType), EvalAltResult> {
if val.is::<Array>() {
// val_array[idx]
let arr = val.downcast_ref::<Array>().expect("array expected");
level: usize,
) -> Result<(Dynamic, IndexSourceType, IndexValue), EvalAltResult> {
let idx_pos = idx_expr.position();
if idx >= 0 {
// val_array[idx]
if let Some(arr) = val.downcast_ref::<Array>() {
let idx = *self
.eval_expr(scope, idx_expr, level)?
.downcast::<INT>()
.map_err(|_| EvalAltResult::ErrorNumericIndexExpr(idx_expr.position()))?;
return if idx >= 0 {
arr.get(idx as usize)
.cloned()
.map(|v| (v, IndexSourceType::Array))
.map(|v| (v, IndexSourceType::Array, IndexValue::from_num(idx)))
.ok_or_else(|| EvalAltResult::ErrorArrayBounds(arr.len(), idx, idx_pos))
} else {
Err(EvalAltResult::ErrorArrayBounds(arr.len(), idx, idx_pos))
}
} else if val.is::<String>() {
// val_string[idx]
let s = val.downcast_ref::<String>().expect("string expected");
};
}
if idx >= 0 {
#[cfg(not(feature = "no_object"))]
{
// val_map[idx]
if let Some(map) = val.downcast_ref::<Map>() {
let idx = *self
.eval_expr(scope, idx_expr, level)?
.downcast::<String>()
.map_err(|_| EvalAltResult::ErrorStringIndexExpr(idx_expr.position()))?;
return Ok((
map.get(&idx).cloned().unwrap_or_else(|| ().into_dynamic()),
IndexSourceType::Map,
IndexValue::from_str(idx),
));
}
}
// val_string[idx]
if let Some(s) = val.downcast_ref::<String>() {
let idx = *self
.eval_expr(scope, idx_expr, level)?
.downcast::<INT>()
.map_err(|_| EvalAltResult::ErrorNumericIndexExpr(idx_expr.position()))?;
return if idx >= 0 {
s.chars()
.nth(idx as usize)
.map(|ch| (ch.into_dynamic(), IndexSourceType::String))
.map(|ch| {
(
ch.into_dynamic(),
IndexSourceType::String,
IndexValue::from_num(idx),
)
})
.ok_or_else(|| {
EvalAltResult::ErrorStringBounds(s.chars().count(), idx, idx_pos)
})
@ -613,14 +686,14 @@ impl Engine<'_> {
idx,
idx_pos,
))
}
} else {
// Error - cannot be indexed
Err(EvalAltResult::ErrorIndexingType(
self.map_type_name(val.type_name()).to_string(),
op_pos,
))
};
}
// Error - cannot be indexed
Err(EvalAltResult::ErrorIndexingType(
self.map_type_name(val.type_name()).to_string(),
op_pos,
))
}
/// Evaluate an index expression
@ -632,36 +705,48 @@ impl Engine<'_> {
idx_expr: &Expr,
op_pos: Position,
level: usize,
) -> Result<(IndexSourceType, Option<ScopeSource<'a>>, usize, Dynamic), EvalAltResult> {
let idx = self.eval_index_value(scope, idx_expr, level)?;
) -> Result<
(
IndexSourceType,
Option<ScopeSource<'a>>,
IndexValue,
Dynamic,
),
EvalAltResult,
> {
match lhs {
// id[idx_expr]
Expr::Variable(id, _) => Self::search_scope(
scope,
&id,
|val| self.get_indexed_value(&val, idx, idx_expr.position(), op_pos),
lhs.position(),
)
.map(|(src, (val, src_type))| {
(
src_type,
Expr::Variable(id, _) => {
let (
ScopeSource {
typ: src_type,
index: src_idx,
..
},
val,
) = Self::search_scope(scope, &id, lhs.position())?;
let (val, idx_src_type, idx) =
self.get_indexed_value(scope, &val, idx_expr, op_pos, level)?;
Ok((
idx_src_type,
Some(ScopeSource {
name: &id,
typ: src.typ,
index: src.index,
typ: src_type,
index: src_idx,
}),
idx as usize,
idx,
val,
)
}),
))
}
// (expr)[idx_expr]
expr => {
let val = self.eval_expr(scope, expr, level)?;
self.get_indexed_value(&val, idx, idx_expr.position(), op_pos)
.map(|(v, _)| (IndexSourceType::Expression, None, idx as usize, v))
self.get_indexed_value(scope, &val, idx_expr, op_pos, level)
.map(|(val, _, idx)| (IndexSourceType::Expression, None, idx, val))
}
}
}
@ -683,17 +768,25 @@ impl Engine<'_> {
/// Update the value at an index position in a variable inside the scope
#[cfg(not(feature = "no_index"))]
fn update_indexed_var_in_scope(
src_type: IndexSourceType,
idx_src_type: IndexSourceType,
scope: &mut Scope,
src: ScopeSource,
idx: usize,
idx: IndexValue,
new_val: (Dynamic, Position),
) -> Result<Dynamic, EvalAltResult> {
match src_type {
match idx_src_type {
// array_id[idx] = val
IndexSourceType::Array => {
let arr = scope.get_mut_by_type::<Array>(src);
arr[idx as usize] = new_val.0;
arr[idx.as_num()] = new_val.0;
Ok(().into_dynamic())
}
// map_id[idx] = val
#[cfg(not(feature = "no_object"))]
IndexSourceType::Map => {
let arr = scope.get_mut_by_type::<Map>(src);
arr.insert(idx.as_str(), new_val.0);
Ok(().into_dynamic())
}
@ -706,7 +799,7 @@ impl Engine<'_> {
.0
.downcast::<char>()
.map_err(|_| EvalAltResult::ErrorCharMismatch(pos))?;
Self::str_replace_char(s, idx as usize, ch);
Self::str_replace_char(s, idx.as_num(), ch);
Ok(().into_dynamic())
}
@ -718,29 +811,38 @@ impl Engine<'_> {
#[cfg(not(feature = "no_index"))]
fn update_indexed_value(
mut target: Dynamic,
idx: usize,
idx: IndexValue,
new_val: Dynamic,
pos: Position,
) -> Result<Dynamic, EvalAltResult> {
if target.is::<Array>() {
let arr = target.downcast_mut::<Array>().expect("array expected");
arr[idx as usize] = new_val;
} else if target.is::<String>() {
let s = target.downcast_mut::<String>().expect("string expected");
if let Some(arr) = target.downcast_mut::<Array>() {
arr[idx.as_num()] = new_val;
return Ok(target);
}
#[cfg(not(feature = "no_object"))]
{
if let Some(map) = target.downcast_mut::<Map>() {
map.insert(idx.as_str(), new_val);
return Ok(target);
}
}
if let Some(s) = target.downcast_mut::<String>() {
// Value must be a character
let ch = *new_val
.downcast::<char>()
.map_err(|_| EvalAltResult::ErrorCharMismatch(pos))?;
Self::str_replace_char(s, idx as usize, ch);
} else {
// All other variable types should be an error
panic!("array or string source type expected for indexing")
Self::str_replace_char(s, idx.as_num(), ch);
return Ok(target);
}
Ok(target)
// All other variable types should be an error
panic!("array, map or string source type expected for indexing")
}
/// Chain-evaluate a dot setter
#[cfg(not(feature = "no_object"))]
fn set_dot_val_helper(
&mut self,
scope: &mut Scope,
@ -752,9 +854,8 @@ impl Engine<'_> {
match dot_rhs {
// xxx.id
Expr::Property(id, pos) => {
let set_fn_name = format!("{}{}", FUNC_SETTER, id);
let mut args = [this_ptr, new_val.0.as_mut()];
self.call_fn_raw(&set_fn_name, &mut args, None, *pos, 0)
self.call_fn_raw(&make_setter(id), &mut args, None, *pos, 0)
}
// xxx.lhs[idx_expr]
@ -762,25 +863,18 @@ impl Engine<'_> {
#[cfg(not(feature = "no_index"))]
Expr::Index(lhs, idx_expr, op_pos) => match lhs.as_ref() {
// xxx.id[idx_expr]
Expr::Property(id, pos) => {
let get_fn_name = format!("{}{}", FUNC_GETTER, id);
Expr::Property(id, pos) => self
.call_fn_raw(&make_getter(id), &mut [this_ptr], None, *pos, 0)
.and_then(|val| {
let (_, _, idx) =
self.get_indexed_value(scope, &val, idx_expr, *op_pos, level)?;
self.call_fn_raw(&get_fn_name, &mut [this_ptr], None, *pos, 0)
.and_then(|v| {
let idx = self.eval_index_value(scope, idx_expr, level)?;
Self::update_indexed_value(
v,
idx as usize,
new_val.0.clone(),
new_val.1,
)
})
.and_then(|mut v| {
let set_fn_name = format!("{}{}", FUNC_SETTER, id);
let mut args = [this_ptr, v.as_mut()];
self.call_fn_raw(&set_fn_name, &mut args, None, *pos, 0)
})
}
Self::update_indexed_value(val, idx, new_val.0.clone(), new_val.1)
})
.and_then(|mut val| {
let mut args = [this_ptr, val.as_mut()];
self.call_fn_raw(&make_setter(id), &mut args, None, *pos, 0)
}),
// All others - syntax error for setters chain
_ => Err(EvalAltResult::ErrorDotExpr(
@ -793,17 +887,14 @@ impl Engine<'_> {
Expr::Dot(lhs, rhs, _) => match lhs.as_ref() {
// xxx.id.rhs
Expr::Property(id, pos) => {
let get_fn_name = format!("{}{}", FUNC_GETTER, id);
self.call_fn_raw(&get_fn_name, &mut [this_ptr], None, *pos, 0)
.and_then(|mut v| {
self.set_dot_val_helper(scope, v.as_mut(), rhs, new_val, level)
.map(|_| v) // Discard Ok return value
self.call_fn_raw(&make_getter(id), &mut [this_ptr], None, *pos, 0)
.and_then(|mut val| {
self.set_dot_val_helper(scope, val.as_mut(), rhs, new_val, level)
.map(|_| val) // Discard Ok return value
})
.and_then(|mut v| {
let set_fn_name = format!("{}{}", FUNC_SETTER, id);
let mut args = [this_ptr, v.as_mut()];
self.call_fn_raw(&set_fn_name, &mut args, None, *pos, 0)
.and_then(|mut val| {
let mut args = [this_ptr, val.as_mut()];
self.call_fn_raw(&make_setter(id), &mut args, None, *pos, 0)
})
}
@ -813,25 +904,26 @@ impl Engine<'_> {
Expr::Index(lhs, idx_expr, op_pos) => match lhs.as_ref() {
// xxx.id[idx_expr].rhs
Expr::Property(id, pos) => {
let get_fn_name = format!("{}{}", FUNC_GETTER, id);
self.call_fn_raw(&get_fn_name, &mut [this_ptr], None, *pos, 0)
self.call_fn_raw(&make_getter(id), &mut [this_ptr], None, *pos, 0)
.and_then(|v| {
let idx = self.eval_index_value(scope, idx_expr, level)?;
let (mut target, _) =
self.get_indexed_value(&v, idx, idx_expr.position(), *op_pos)?;
let (mut value, _, idx) =
self.get_indexed_value(scope, &v, idx_expr, *op_pos, level)?;
let val_pos = new_val.1;
let this_ptr = target.as_mut();
let this_ptr = value.as_mut();
self.set_dot_val_helper(scope, this_ptr, rhs, new_val, level)?;
// 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, value, val_pos)
})
.and_then(|mut v| {
let set_fn_name = format!("{}{}", FUNC_SETTER, id);
let mut args = [this_ptr, v.as_mut()];
self.call_fn_raw(&set_fn_name, &mut args, None, *pos, 0)
self.call_fn_raw(
&make_setter(id),
&mut [this_ptr, v.as_mut()],
None,
*pos,
0,
)
})
}
@ -858,6 +950,7 @@ impl Engine<'_> {
}
// Evaluate a dot chain setter
#[cfg(not(feature = "no_object"))]
fn set_dot_val(
&mut self,
scope: &mut Scope,
@ -870,7 +963,7 @@ impl Engine<'_> {
match dot_lhs {
// id.???
Expr::Variable(id, pos) => {
let (entry, mut target) = Self::search_scope(scope, id, Ok, *pos)?;
let (entry, mut target) = Self::search_scope(scope, id, *pos)?;
match entry.typ {
ScopeEntryType::Constant => Err(EvalAltResult::ErrorAssignmentToConstant(
@ -881,12 +974,13 @@ impl Engine<'_> {
// Avoid referencing scope which is used below as mut
let entry = ScopeSource { name: id, ..entry };
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.
*scope.get_mut(entry) = target;
val
value
}
}
}
@ -895,11 +989,11 @@ impl Engine<'_> {
// TODO - Allow chaining of indexing!
#[cfg(not(feature = "no_index"))]
Expr::Index(lhs, idx_expr, op_pos) => {
let (src_type, src, idx, mut target) =
let (idx_src_type, src, idx, mut target) =
self.eval_index_expr(scope, lhs, idx_expr, *op_pos, level)?;
let val_pos = new_val.1;
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.
if let Some(src) = src {
@ -912,7 +1006,7 @@ impl Engine<'_> {
}
ScopeEntryType::Normal => {
Self::update_indexed_var_in_scope(
src_type,
idx_src_type,
scope,
src,
idx,
@ -922,7 +1016,7 @@ impl Engine<'_> {
}
}
val
value
}
// Syntax error
@ -947,7 +1041,7 @@ impl Engine<'_> {
Expr::IntegerConstant(i, _) => Ok(i.into_dynamic()),
Expr::StringConstant(s, _) => Ok(s.into_dynamic()),
Expr::CharConstant(c, _) => Ok(c.into_dynamic()),
Expr::Variable(id, pos) => Self::search_scope(scope, id, Ok, *pos).map(|(_, val)| val),
Expr::Variable(id, pos) => Self::search_scope(scope, id, *pos).map(|(_, val)| val),
Expr::Property(_, _) => panic!("unexpected property."),
// lhs[idx_expr]
@ -994,7 +1088,7 @@ impl Engine<'_> {
// idx_lhs[idx_expr] = rhs
#[cfg(not(feature = "no_index"))]
Expr::Index(idx_lhs, idx_expr, op_pos) => {
let (src_type, src, idx, _) =
let (idx_src_type, src, idx, _) =
self.eval_index_expr(scope, idx_lhs, idx_expr, *op_pos, level)?;
if let Some(src) = src {
@ -1006,7 +1100,7 @@ impl Engine<'_> {
))
}
ScopeEntryType::Normal => Ok(Self::update_indexed_var_in_scope(
src_type,
idx_src_type,
scope,
src,
idx,
@ -1021,6 +1115,7 @@ impl Engine<'_> {
}
// dot_lhs.dot_rhs = rhs
#[cfg(not(feature = "no_object"))]
Expr::Dot(dot_lhs, dot_rhs, _) => self.set_dot_val(
scope,
dot_lhs,
@ -1041,11 +1136,12 @@ impl Engine<'_> {
}
}
#[cfg(not(feature = "no_object"))]
Expr::Dot(lhs, rhs, _) => self.get_dot_val(scope, lhs, rhs, level),
#[cfg(not(feature = "no_index"))]
Expr::Array(contents, _) => {
let mut arr = Vec::new();
let mut arr = Array::new();
contents.into_iter().try_for_each(|item| {
self.eval_expr(scope, item, level).map(|val| arr.push(val))
@ -1054,12 +1150,25 @@ impl Engine<'_> {
Ok(Box::new(arr))
}
#[cfg(not(feature = "no_object"))]
Expr::Map(contents, _) => {
let mut map = Map::new();
contents.into_iter().try_for_each(|item| {
self.eval_expr(scope, &item.1, level).map(|val| {
map.insert(item.0.clone(), val);
})
})?;
Ok(Box::new(map))
}
Expr::FunctionCall(fn_name, args_expr_list, def_val, pos) => {
// Has a system function an override?
fn has_override(engine: &Engine, name: &str) -> bool {
let spec = FnSpec {
name: name.into(),
args: Some(vec![TypeId::of::<String>()]),
args: vec![TypeId::of::<String>()],
};
engine.functions.contains_key(&spec) || engine.fn_lib.has_function(name, 1)
@ -1354,7 +1463,7 @@ impl Engine<'_> {
pub(crate) fn map_type_name<'a>(&'a self, name: &'a str) -> &'a str {
self.type_names
.get(name)
.map(|s| s.as_str())
.map(String::as_str)
.unwrap_or(name)
}

View File

@ -47,32 +47,24 @@ pub enum ParseErrorType {
UnexpectedEOF,
/// An unknown operator is encountered. Wrapped value is the operator.
UnknownOperator(String),
/// An open `(` is missing the corresponding closing `)`.
MissingRightParen(String),
/// Expecting `(` but not finding one.
MissingLeftBrace,
/// An open `{` is missing the corresponding closing `}`.
MissingRightBrace(String),
/// An open `[` is missing the corresponding closing `]`.
#[cfg(not(feature = "no_index"))]
MissingRightBracket(String),
/// A list of expressions is missing the separating ','.
MissingComma(String),
/// A statement is missing the ending ';'.
MissingSemicolon(String),
/// Expecting a particular token but not finding one. Wrapped values are the token and usage.
MissingToken(String, String),
/// An expression in function call arguments `()` has syntax error.
MalformedCallExpr(String),
/// An expression in indexing brackets `[]` has syntax error.
#[cfg(not(feature = "no_index"))]
MalformedIndexExpr(String),
/// A map definition has duplicated property names. Wrapped is the property name.
#[cfg(not(feature = "no_object"))]
DuplicatedProperty(String),
/// Invalid expression assigned to constant.
ForbiddenConstantExpr(String),
/// Missing a property name for custom types and maps.
PropertyExpected,
/// Missing a variable name after the `let`, `const` or `for` keywords.
VariableExpected,
/// Missing an expression.
ExprExpected(String),
/// A `for` statement is missing the `in` keyword.
MissingIn,
/// Defining a function `fn` in an appropriate place (e.g. inside another function).
#[cfg(not(feature = "no_function"))]
WrongFnDefinition,
@ -84,7 +76,7 @@ pub enum ParseErrorType {
FnMissingParams(String),
/// A function definition has duplicated parameters. Wrapped values are the function name and parameter name.
#[cfg(not(feature = "no_function"))]
FnDuplicateParam(String, String),
FnDuplicatedParam(String, String),
/// A function definition is missing the body. Wrapped value is the function name.
#[cfg(not(feature = "no_function"))]
FnMissingBody(String),
@ -130,18 +122,14 @@ impl ParseError {
ParseErrorType::BadInput(ref p) => p,
ParseErrorType::UnexpectedEOF => "Script is incomplete",
ParseErrorType::UnknownOperator(_) => "Unknown operator",
ParseErrorType::MissingRightParen(_) => "Expecting ')'",
ParseErrorType::MissingLeftBrace => "Expecting '{'",
ParseErrorType::MissingRightBrace(_) => "Expecting '}'",
#[cfg(not(feature = "no_index"))]
ParseErrorType::MissingRightBracket(_) => "Expecting ']'",
ParseErrorType::MissingComma(_) => "Expecting ','",
ParseErrorType::MissingSemicolon(_) => "Expecting ';'",
ParseErrorType::MissingToken(_, _) => "Expecting a certain token that is missing",
ParseErrorType::MalformedCallExpr(_) => "Invalid expression in function call arguments",
#[cfg(not(feature = "no_index"))]
ParseErrorType::MalformedIndexExpr(_) => "Invalid index in indexing expression",
#[cfg(not(feature = "no_object"))]
ParseErrorType::DuplicatedProperty(_) => "Duplicated property in object map literal",
ParseErrorType::ForbiddenConstantExpr(_) => "Expecting a constant",
ParseErrorType::MissingIn => "Expecting 'in'",
ParseErrorType::PropertyExpected => "Expecting name of a property",
ParseErrorType::VariableExpected => "Expecting name of a variable",
ParseErrorType::ExprExpected(_) => "Expecting an expression",
#[cfg(not(feature = "no_function"))]
@ -149,7 +137,7 @@ impl ParseError {
#[cfg(not(feature = "no_function"))]
ParseErrorType::FnMissingParams(_) => "Expecting parameters in function declaration",
#[cfg(not(feature = "no_function"))]
ParseErrorType::FnDuplicateParam(_,_) => "Duplicated parameters in function declaration",
ParseErrorType::FnDuplicatedParam(_,_) => "Duplicated parameters in function declaration",
#[cfg(not(feature = "no_function"))]
ParseErrorType::FnMissingBody(_) => "Expecting body statement block for function declaration",
#[cfg(not(feature = "no_function"))]
@ -180,6 +168,11 @@ impl fmt::Display for ParseError {
write!(f, "{}", if s.is_empty() { self.desc() } else { s })?
}
#[cfg(not(feature = "no_object"))]
ParseErrorType::DuplicatedProperty(ref s) => {
write!(f, "Duplicated property '{}' for object map literal", s)?
}
ParseErrorType::ExprExpected(ref s) => write!(f, "Expecting {} expression", s)?,
#[cfg(not(feature = "no_function"))]
@ -193,19 +186,12 @@ impl fmt::Display for ParseError {
}
#[cfg(not(feature = "no_function"))]
ParseErrorType::FnDuplicateParam(ref s, ref arg) => {
ParseErrorType::FnDuplicatedParam(ref s, ref arg) => {
write!(f, "Duplicated parameter '{}' for function '{}'", arg, s)?
}
ParseErrorType::MissingRightParen(ref s) | ParseErrorType::MissingRightBrace(ref s) => {
write!(f, "{} for {}", self.desc(), s)?
}
#[cfg(not(feature = "no_index"))]
ParseErrorType::MissingRightBracket(ref s) => write!(f, "{} for {}", self.desc(), s)?,
ParseErrorType::MissingSemicolon(ref s) | ParseErrorType::MissingComma(ref s) => {
write!(f, "{} for {}", self.desc(), s)?
ParseErrorType::MissingToken(ref token, ref s) => {
write!(f, "Expecting '{}' {}", token, s)?
}
ParseErrorType::AssignmentToConstant(ref s) if s.is_empty() => {

View File

@ -145,7 +145,7 @@ macro_rules! def_register {
fn register_fn(&mut self, name: &str, f: FN) {
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.
const NUM_ARGS: usize = count_args!($($par)*);
@ -165,7 +165,7 @@ macro_rules! def_register {
let r = f($(($clone)($par)),*);
Ok(Box::new(r) as Dynamic)
};
self.register_fn_raw(name, Some(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) {
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.
const NUM_ARGS: usize = count_args!($($par)*);
@ -196,7 +196,7 @@ macro_rules! def_register {
// potentially clone the value, otherwise pass the reference.
Ok(f($(($clone)($par)),*))
};
self.register_fn_raw(name, Some(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) {
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.
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)
.map_err(|err| err.set_position(pos))
};
self.register_fn_raw(name, Some(vec![$(TypeId::of::<$par>()),*]), Box::new(fun));
self.register_fn_raw(name, vec![$(TypeId::of::<$par>()),*], Box::new(func));
}
}

View File

@ -68,6 +68,9 @@ pub use scope::Scope;
#[cfg(not(feature = "no_index"))]
pub use engine::Array;
#[cfg(not(feature = "no_object"))]
pub use engine::Map;
#[cfg(not(feature = "no_float"))]
pub use parser::FLOAT;

View File

@ -37,7 +37,15 @@ struct State<'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.
pub fn reset(&mut self) {
self.changed = false;
@ -334,6 +342,7 @@ fn optimize_expr<'a>(expr: Expr, state: &mut State<'a>) -> Expr {
expr => Expr::Assignment(id, Box::new(optimize_expr(expr, state)), pos),
},
// lhs.rhs
#[cfg(not(feature = "no_object"))]
Expr::Dot(lhs, rhs, pos) => Expr::Dot(
Box::new(optimize_expr(*lhs, state)),
Box::new(optimize_expr(*rhs, state)),
@ -369,20 +378,16 @@ fn optimize_expr<'a>(expr: Expr, state: &mut State<'a>) -> Expr {
},
// [ items .. ]
#[cfg(not(feature = "no_index"))]
Expr::Array(items, pos) => {
let orig_len = items.len();
let items: Vec<_> = items
Expr::Array(items, pos) => Expr::Array(items
.into_iter()
.map(|expr| optimize_expr(expr, state))
.collect();
if orig_len != items.len() {
state.set_dirty();
}
Expr::Array(items, pos)
}
.collect(), pos),
// [ items .. ]
#[cfg(not(feature = "no_object"))]
Expr::Map(items, pos) => Expr::Map(items
.into_iter()
.map(|(key, expr, pos)| (key, optimize_expr(expr, state), pos))
.collect(), pos),
// lhs && rhs
Expr::And(lhs, rhs) => match (*lhs, *rhs) {
// true && rhs -> rhs
@ -434,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),
// 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.into_iter().map(|a| optimize_expr(a, state)).collect(), def_value, pos),
@ -460,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|
r.or_else(|| {
state.engine.call_ext_fn_raw(&id, &mut call_args, pos).ok().map(|result|
result.or_else(|| {
if !arg_for_type_of.is_empty() {
// Handle `type_of()`
Some(arg_for_type_of.to_string().into_dynamic())
@ -501,11 +506,7 @@ pub(crate) fn optimize<'a>(statements: Vec<Stmt>, engine: &Engine<'a>, scope: &S
}
// Set up the state
let mut state = State {
changed: false,
constants: vec![],
engine,
};
let mut state = State::new(engine);
// Add constants from the scope into the state
scope

View File

@ -11,7 +11,9 @@ use crate::optimize::optimize_into_ast;
use crate::stdlib::{
borrow::Cow,
boxed::Box,
char, fmt, format,
char,
collections::HashMap,
fmt, format,
iter::Peekable,
ops::Add,
str::Chars,
@ -177,12 +179,14 @@ impl AST {
///
/// ```
/// # fn main() -> Result<(), rhai::EvalAltResult> {
/// # #[cfg(not(feature = "no_function"))]
/// # {
/// use rhai::Engine;
///
/// let mut engine = Engine::new();
///
/// let ast1 = engine.compile(r#"fn foo(x) { 42 + x } foo(1)"#)?;
/// let ast2 = engine.compile(r#"fn foo(n) { "hello" + n } foo(2)"#)?;
/// let ast2 = engine.compile(r#"fn foo(n) { "hello" + n } foo("!")"#)?;
///
/// let ast = ast1.merge(ast2); // Merge 'ast2' into 'ast1'
///
@ -194,10 +198,11 @@ impl AST {
/// // fn foo(n) { "hello" + n } // <- definition of first 'foo' is overwritten
/// // foo(1) // <- notice this will be "hello1" instead of 43,
/// // // but it is no longer the return value
/// // foo(2) // returns "hello2"
/// // foo("!") // returns "hello!"
///
/// // Evaluate it
/// assert_eq!(engine.eval_ast::<String>(&ast)?, "hello2");
/// assert_eq!(engine.eval_ast::<String>(&ast)?, "hello!");
/// # }
/// # Ok(())
/// # }
/// ```
@ -357,6 +362,7 @@ pub enum Expr {
/// expr = expr
Assignment(Box<Expr>, Box<Expr>, Position),
/// lhs.rhs
#[cfg(not(feature = "no_object"))]
Dot(Box<Expr>, Box<Expr>, Position),
/// expr[expr]
#[cfg(not(feature = "no_index"))]
@ -364,6 +370,9 @@ pub enum Expr {
#[cfg(not(feature = "no_index"))]
/// [ expr, ... ]
Array(Vec<Expr>, Position),
#[cfg(not(feature = "no_object"))]
/// #{ name:expr, ... }
Map(Vec<(String, Expr, Position)>, Position),
/// lhs && rhs
And(Box<Expr>, Box<Expr>),
/// lhs || rhs
@ -398,6 +407,13 @@ impl Expr {
.collect::<Vec<_>>()
.into_dynamic(),
#[cfg(not(feature = "no_object"))]
Expr::Map(items, _) if items.iter().all(|(_, v, _)| v.is_constant()) => items
.iter()
.map(|(k, v, _)| (k.clone(), v.get_constant_value()))
.collect::<HashMap<_, _>>()
.into_dynamic(),
#[cfg(not(feature = "no_float"))]
Expr::FloatConstant(f, _) => f.into_dynamic(),
@ -443,10 +459,12 @@ impl Expr {
| Expr::False(pos)
| Expr::Unit(pos) => *pos,
Expr::Assignment(expr, _, _)
| Expr::Dot(expr, _, _)
| Expr::And(expr, _)
| Expr::Or(expr, _) => expr.position(),
Expr::Assignment(expr, _, _) | Expr::And(expr, _) | Expr::Or(expr, _) => {
expr.position()
}
#[cfg(not(feature = "no_object"))]
Expr::Dot(expr, _, _) => expr.position(),
#[cfg(not(feature = "no_float"))]
Expr::FloatConstant(_, pos) => *pos,
@ -454,6 +472,9 @@ impl Expr {
#[cfg(not(feature = "no_index"))]
Expr::Array(_, pos) => *pos,
#[cfg(not(feature = "no_object"))]
Expr::Map(_, pos) => *pos,
#[cfg(not(feature = "no_index"))]
Expr::Index(expr, _, _) => expr.position(),
}
@ -531,6 +552,8 @@ pub enum Token {
Colon,
Comma,
Period,
#[cfg(not(feature = "no_object"))]
MapStart,
Equals,
True,
False,
@ -606,6 +629,8 @@ impl Token {
Colon => ":",
Comma => ",",
Period => ".",
#[cfg(not(feature = "no_object"))]
MapStart => "#{",
Equals => "=",
True => "true",
False => "false",
@ -794,6 +819,11 @@ pub struct TokenIterator<'a> {
}
impl<'a> TokenIterator<'a> {
/// Consume the next character.
fn eat_next(&mut self) {
self.stream.next();
self.advance();
}
/// Move the current position one character ahead.
fn advance(&mut self) {
self.pos.advance();
@ -933,20 +963,17 @@ impl<'a> TokenIterator<'a> {
match next_char {
'0'..='9' | '_' => {
result.push(next_char);
self.stream.next();
self.advance();
self.eat_next();
}
#[cfg(not(feature = "no_float"))]
'.' => {
result.push(next_char);
self.stream.next();
self.advance();
self.eat_next();
while let Some(&next_char_in_float) = self.stream.peek() {
match next_char_in_float {
'0'..='9' | '_' => {
result.push(next_char_in_float);
self.stream.next();
self.advance();
self.eat_next();
}
_ => break,
}
@ -957,8 +984,7 @@ impl<'a> TokenIterator<'a> {
if c == '0' =>
{
result.push(next_char);
self.stream.next();
self.advance();
self.eat_next();
let valid = match ch {
'x' | 'X' => [
@ -989,8 +1015,7 @@ impl<'a> TokenIterator<'a> {
}
result.push(next_char_in_hex);
self.stream.next();
self.advance();
self.eat_next();
}
}
@ -1044,8 +1069,7 @@ impl<'a> TokenIterator<'a> {
match next_char {
x if x.is_ascii_alphanumeric() || x == '_' => {
result.push(x);
self.stream.next();
self.advance();
self.eat_next();
}
_ => break,
}
@ -1136,10 +1160,16 @@ impl<'a> TokenIterator<'a> {
#[cfg(not(feature = "no_index"))]
(']', _) => return Some((Token::RightBracket, pos)),
// Map literal
#[cfg(not(feature = "no_object"))]
('#', '{') => {
self.eat_next();
return Some((Token::MapStart, pos));
}
// Operators
('+', '=') => {
self.stream.next();
self.advance();
self.eat_next();
return Some((Token::PlusAssign, pos));
}
('+', _) if self.can_be_unary => return Some((Token::UnaryPlus, pos)),
@ -1148,24 +1178,21 @@ impl<'a> TokenIterator<'a> {
('-', '0'..='9') if self.can_be_unary => negated = true,
('-', '0'..='9') => return Some((Token::Minus, pos)),
('-', '=') => {
self.stream.next();
self.advance();
self.eat_next();
return Some((Token::MinusAssign, pos));
}
('-', _) if self.can_be_unary => return Some((Token::UnaryMinus, pos)),
('-', _) => return Some((Token::Minus, pos)),
('*', '=') => {
self.stream.next();
self.advance();
self.eat_next();
return Some((Token::MultiplyAssign, pos));
}
('*', _) => return Some((Token::Multiply, pos)),
// Comments
('/', '/') => {
self.stream.next();
self.advance();
self.eat_next();
while let Some(c) = self.stream.next() {
if c == '\n' {
@ -1179,8 +1206,7 @@ impl<'a> TokenIterator<'a> {
('/', '*') => {
let mut level = 1;
self.stream.next();
self.advance();
self.eat_next();
while let Some(c) = self.stream.next() {
self.advance();
@ -1209,8 +1235,7 @@ impl<'a> TokenIterator<'a> {
}
('/', '=') => {
self.stream.next();
self.advance();
self.eat_next();
return Some((Token::DivideAssign, pos));
}
('/', _) => return Some((Token::Divide, pos)),
@ -1221,25 +1246,21 @@ impl<'a> TokenIterator<'a> {
('.', _) => return Some((Token::Period, pos)),
('=', '=') => {
self.stream.next();
self.advance();
self.eat_next();
return Some((Token::EqualsTo, pos));
}
('=', _) => return Some((Token::Equals, pos)),
('<', '=') => {
self.stream.next();
self.advance();
self.eat_next();
return Some((Token::LessThanEqualsTo, pos));
}
('<', '<') => {
self.stream.next();
self.advance();
self.eat_next();
return Some((
if self.stream.peek() == Some(&'=') {
self.stream.next();
self.advance();
self.eat_next();
Token::LeftShiftAssign
} else {
Token::LeftShift
@ -1250,18 +1271,15 @@ impl<'a> TokenIterator<'a> {
('<', _) => return Some((Token::LessThan, pos)),
('>', '=') => {
self.stream.next();
self.advance();
self.eat_next();
return Some((Token::GreaterThanEqualsTo, pos));
}
('>', '>') => {
self.stream.next();
self.advance();
self.eat_next();
return Some((
if self.stream.peek() == Some(&'=') {
self.stream.next();
self.advance();
self.eat_next();
Token::RightShiftAssign
} else {
Token::RightShift
@ -1272,53 +1290,45 @@ impl<'a> TokenIterator<'a> {
('>', _) => return Some((Token::GreaterThan, pos)),
('!', '=') => {
self.stream.next();
self.advance();
self.eat_next();
return Some((Token::NotEqualsTo, pos));
}
('!', _) => return Some((Token::Bang, pos)),
('|', '|') => {
self.stream.next();
self.advance();
self.eat_next();
return Some((Token::Or, pos));
}
('|', '=') => {
self.stream.next();
self.advance();
self.eat_next();
return Some((Token::OrAssign, pos));
}
('|', _) => return Some((Token::Pipe, pos)),
('&', '&') => {
self.stream.next();
self.advance();
self.eat_next();
return Some((Token::And, pos));
}
('&', '=') => {
self.stream.next();
self.advance();
self.eat_next();
return Some((Token::AndAssign, pos));
}
('&', _) => return Some((Token::Ampersand, pos)),
('^', '=') => {
self.stream.next();
self.advance();
self.eat_next();
return Some((Token::XOrAssign, pos));
}
('^', _) => return Some((Token::XOr, pos)),
('%', '=') => {
self.stream.next();
self.advance();
self.eat_next();
return Some((Token::ModuloAssign, pos));
}
('%', _) => return Some((Token::Modulo, pos)),
('~', '=') => {
self.stream.next();
self.advance();
self.eat_next();
return Some((Token::PowerOfAssign, pos));
}
('~', _) => return Some((Token::PowerOf, pos)),
@ -1370,13 +1380,16 @@ fn parse_paren_expr<'a>(
// ( xxx )
Some((Token::RightParen, _)) => Ok(expr),
// ( xxx ???
Some((_, pos)) => {
Err(PERR::MissingRightParen("a matching ( in the expression".into()).into_err(pos))
}
Some((_, pos)) => Err(PERR::MissingToken(
")".into(),
"for a matching ( in this expression".into(),
)
.into_err(pos)),
// ( xxx
None => {
Err(PERR::MissingRightParen("a matching ( in the expression".into()).into_err_eof())
}
None => Err(
PERR::MissingToken(")".into(), "for a matching ( in this expression".into())
.into_err_eof(),
),
}
}
@ -1391,10 +1404,10 @@ fn parse_call_expr<'a>(
// id()
if let (Token::RightParen, _) = input.peek().ok_or_else(|| {
PERR::MissingRightParen(format!(
"closing the arguments to call of function '{}'",
id
))
PERR::MissingToken(
")".into(),
format!("to close the arguments list of this function call '{}'", id),
)
.into_err_eof()
})? {
input.next();
@ -1405,10 +1418,10 @@ fn parse_call_expr<'a>(
args_expr_list.push(parse_expr(input, allow_stmt_expr)?);
match input.peek().ok_or_else(|| {
PERR::MissingRightParen(format!(
"closing the arguments to call of function '{}'",
id
))
PERR::MissingToken(
")".into(),
format!("to close the arguments list of this function call '{}'", id),
)
.into_err_eof()
})? {
(Token::RightParen, _) => {
@ -1417,10 +1430,10 @@ fn parse_call_expr<'a>(
}
(Token::Comma, _) => (),
(_, pos) => {
return Err(PERR::MissingComma(format!(
"separating the arguments to call of function '{}'",
id
))
return Err(PERR::MissingToken(
",".into(),
format!("to separate the arguments to function call '{}'", id),
)
.into_err(*pos))
}
}
@ -1429,7 +1442,7 @@ fn parse_call_expr<'a>(
}
}
/// Parse an indexing expression.s
/// Parse an indexing expression.
#[cfg(not(feature = "no_index"))]
fn parse_index_expr<'a>(
lhs: Box<Expr>,
@ -1439,7 +1452,7 @@ fn parse_index_expr<'a>(
) -> Result<Expr, ParseError> {
let idx_expr = parse_expr(input, allow_stmt_expr)?;
// Check type of indexing - must be integer
// Check type of indexing - must be integer or string
match &idx_expr {
// lhs[int]
Expr::IntegerConstant(i, pos) if *i < 0 => {
@ -1449,6 +1462,72 @@ fn parse_index_expr<'a>(
))
.into_err(*pos))
}
Expr::IntegerConstant(_, pos) => match *lhs {
Expr::Array(_, _) | Expr::StringConstant(_, _) => (),
#[cfg(not(feature = "no_object"))]
Expr::Map(_, _) => {
return Err(PERR::MalformedIndexExpr(
"Object map access expects string index, not a number".into(),
)
.into_err(*pos))
}
Expr::FloatConstant(_, pos)
| Expr::CharConstant(_, pos)
| Expr::Assignment(_, _, pos)
| Expr::Unit(pos)
| Expr::True(pos)
| Expr::False(pos) => {
return Err(PERR::MalformedIndexExpr(
"Only arrays, object maps and strings can be indexed".into(),
)
.into_err(pos))
}
Expr::And(lhs, _) | Expr::Or(lhs, _) => {
return Err(PERR::MalformedIndexExpr(
"Only arrays, object maps and strings can be indexed".into(),
)
.into_err(lhs.position()))
}
_ => (),
},
// lhs[string]
Expr::StringConstant(_, pos) => match *lhs {
#[cfg(not(feature = "no_object"))]
Expr::Map(_, _) => (),
Expr::Array(_, _) | Expr::StringConstant(_, _) => {
return Err(PERR::MalformedIndexExpr(
"Array or string expects numeric index, not a string".into(),
)
.into_err(*pos))
}
Expr::FloatConstant(_, pos)
| Expr::CharConstant(_, pos)
| Expr::Assignment(_, _, pos)
| Expr::Unit(pos)
| Expr::True(pos)
| Expr::False(pos) => {
return Err(PERR::MalformedIndexExpr(
"Only arrays, object maps and strings can be indexed".into(),
)
.into_err(pos))
}
Expr::And(lhs, _) | Expr::Or(lhs, _) => {
return Err(PERR::MalformedIndexExpr(
"Only arrays, object maps and strings can be indexed".into(),
)
.into_err(lhs.position()))
}
_ => (),
},
// lhs[float]
#[cfg(not(feature = "no_float"))]
Expr::FloatConstant(_, pos) => {
@ -1464,13 +1543,6 @@ fn parse_index_expr<'a>(
)
.into_err(*pos))
}
// lhs[string]
Expr::StringConstant(_, pos) => {
return Err(PERR::MalformedIndexExpr(
"Array access expects integer index, not a string".into(),
)
.into_err(*pos))
}
// lhs[??? = ??? ], lhs[()]
Expr::Assignment(_, _, pos) | Expr::Unit(pos) => {
return Err(PERR::MalformedIndexExpr(
@ -1497,15 +1569,22 @@ fn parse_index_expr<'a>(
}
// Check if there is a closing bracket
match input
.peek()
.ok_or_else(|| PERR::MissingRightBracket("index expression".into()).into_err_eof())?
{
match input.peek().ok_or_else(|| {
PERR::MissingToken(
"]".into(),
"for a matching [ in this index expression".into(),
)
.into_err_eof()
})? {
(Token::RightBracket, _) => {
input.next();
Ok(Expr::Index(lhs, Box::new(idx_expr), pos))
}
(_, pos) => Err(PERR::MissingRightBracket("index expression".into()).into_err(*pos)),
(_, pos) => Err(PERR::MissingToken(
"]".into(),
"for a matching [ in this index expression".into(),
)
.into_err(*pos)),
}
}
@ -1555,35 +1634,141 @@ fn parse_array_literal<'a>(
arr.push(parse_expr(input, allow_stmt_expr)?);
match input.peek().ok_or_else(|| {
PERR::MissingRightBracket("separating items in array literal".into()).into_err_eof()
PERR::MissingToken("]".into(), "to end this array literal".into()).into_err_eof()
})? {
(Token::Comma, _) => {
input.next();
}
(Token::RightBracket, _) => break,
(_, pos) => {
return Err(
PERR::MissingComma("separating items in array literal".into())
.into_err(*pos),
return Err(PERR::MissingToken(
",".into(),
"to separate the items of this array literal".into(),
)
.into_err(*pos))
}
}
}
}
match input.peek().ok_or_else(|| {
PERR::MissingRightBracket("the end of array literal".into()).into_err_eof()
PERR::MissingToken("]".into(), "to end this array literal".into()).into_err_eof()
})? {
(Token::RightBracket, _) => {
input.next();
Ok(Expr::Array(arr, begin))
}
(_, pos) => {
Err(PERR::MissingRightBracket("the end of array literal".into()).into_err(*pos))
Err(PERR::MissingToken("]".into(), "to end this array literal".into()).into_err(*pos))
}
}
}
/// Parse a map literal.
#[cfg(not(feature = "no_object"))]
fn parse_map_literal<'a>(
input: &mut Peekable<TokenIterator<'a>>,
begin: Position,
allow_stmt_expr: bool,
) -> Result<Expr, ParseError> {
let mut map = Vec::new();
if !matches!(input.peek(), Some((Token::RightBrace, _))) {
while input.peek().is_some() {
let (name, pos) = match input.next().ok_or_else(|| {
PERR::MissingToken("}".into(), "to end this object map literal".into())
.into_err_eof()
})? {
(Token::Identifier(s), pos) => (s.clone(), pos),
(Token::StringConst(s), pos) => (s.clone(), pos),
(_, pos) if map.is_empty() => {
return Err(PERR::MissingToken(
"}".into(),
"to end this object map literal".into(),
)
.into_err(pos))
}
(_, pos) => return Err(PERR::PropertyExpected.into_err(pos)),
};
match input.next().ok_or_else(|| {
PERR::MissingToken(
":".into(),
format!(
"to follow the property '{}' in this object map literal",
name
),
)
.into_err_eof()
})? {
(Token::Colon, _) => (),
(_, pos) => {
return Err(PERR::MissingToken(
":".into(),
format!(
"to follow the property '{}' in this object map literal",
name
),
)
.into_err(pos))
}
};
let expr = parse_expr(input, allow_stmt_expr)?;
map.push((name, expr, pos));
match input.peek().ok_or_else(|| {
PERR::MissingToken("}".into(), "to end this object map literal".into())
.into_err_eof()
})? {
(Token::Comma, _) => {
input.next();
}
(Token::RightBrace, _) => break,
(Token::Identifier(_), pos) => {
return Err(PERR::MissingToken(
",".into(),
"to separate the items of this object map literal".into(),
)
.into_err(*pos))
}
(_, pos) => {
return Err(PERR::MissingToken(
"}".into(),
"to end this object map literal".into(),
)
.into_err(*pos))
}
}
}
}
// Check for duplicating properties
map.iter()
.enumerate()
.try_for_each(|(i, (k1, _, _))| {
map.iter()
.skip(i + 1)
.find(|(k2, _, _)| k2 == k1)
.map_or_else(|| Ok(()), |(k2, _, pos)| Err((k2, *pos)))
})
.map_err(|(key, pos)| PERR::DuplicatedProperty(key.to_string()).into_err(pos))?;
// Ending brace
match input.peek().ok_or_else(|| {
PERR::MissingToken("}".into(), "to end this object map literal".into()).into_err_eof()
})? {
(Token::RightBrace, _) => {
input.next();
Ok(Expr::Map(map, begin))
}
(_, pos) => Err(
PERR::MissingToken("]".into(), "to end this object map literal".into()).into_err(*pos),
),
}
}
/// Parse a primary expression.
fn parse_primary<'a>(
input: &mut Peekable<TokenIterator<'a>>,
@ -1627,6 +1812,11 @@ fn parse_primary<'a>(
can_be_indexed = true;
parse_array_literal(input, pos, allow_stmt_expr)
}
#[cfg(not(feature = "no_object"))]
(Token::MapStart, pos) => {
can_be_indexed = true;
parse_map_literal(input, pos, allow_stmt_expr)
}
(Token::True, pos) => Ok(Expr::True(pos)),
(Token::False, pos) => Ok(Expr::False(pos)),
(Token::LexError(err), pos) => Err(PERR::BadInput(err.to_string()).into_err(pos)),
@ -1756,6 +1946,7 @@ fn parse_assignment(lhs: Expr, rhs: Expr, pos: Position) -> Result<Expr, ParseEr
},
// dot_lhs.dot_rhs
#[cfg(not(feature = "no_object"))]
Expr::Dot(dot_lhs, dot_rhs, _) => match dot_lhs.as_ref() {
// var.dot_rhs
Expr::Variable(_, _) if is_top => valid_assignment_chain(dot_rhs, false),
@ -1864,28 +2055,32 @@ fn parse_binary_op<'a>(
Token::PlusAssign => parse_op_assignment("+", current_lhs, rhs, pos)?,
Token::MinusAssign => parse_op_assignment("-", current_lhs, rhs, pos)?,
#[cfg(not(feature = "no_object"))]
Token::Period => {
fn change_var_to_property(expr: Expr) -> Expr {
fn check_property(expr: Expr) -> Result<Expr, ParseError> {
match expr {
Expr::Dot(lhs, rhs, pos) => Expr::Dot(
Box::new(change_var_to_property(*lhs)),
Box::new(change_var_to_property(*rhs)),
// xxx.lhs.rhs
Expr::Dot(lhs, rhs, pos) => Ok(Expr::Dot(
Box::new(check_property(*lhs)?),
Box::new(check_property(*rhs)?),
pos,
),
)),
// xxx.lhs[idx]
#[cfg(not(feature = "no_index"))]
Expr::Index(lhs, idx, pos) => {
Expr::Index(Box::new(change_var_to_property(*lhs)), idx, pos)
Ok(Expr::Index(Box::new(check_property(*lhs)?), idx, pos))
}
Expr::Variable(s, pos) => Expr::Property(s, pos),
expr => expr,
// xxx.id
Expr::Variable(id, pos) => Ok(Expr::Property(id, pos)),
// xxx.prop
expr @ Expr::Property(_, _) => Ok(expr),
// xxx.fn()
expr @ Expr::FunctionCall(_, _, _, _) => Ok(expr),
expr => Err(PERR::PropertyExpected.into_err(expr.position())),
}
}
Expr::Dot(
Box::new(current_lhs),
Box::new(change_var_to_property(rhs)),
pos,
)
Expr::Dot(Box::new(current_lhs), Box::new(check_property(rhs)?), pos)
}
// Comparison operators default to false when passed invalid operands
@ -2070,9 +2265,16 @@ fn parse_for<'a>(
};
// for name in ...
match input.next().ok_or_else(|| PERR::MissingIn.into_err_eof())? {
match input.next().ok_or_else(|| {
PERR::MissingToken("in".into(), "after the iteration variable".into()).into_err_eof()
})? {
(Token::In, _) => (),
(_, pos) => return Err(PERR::MissingIn.into_err(pos)),
(_, pos) => {
return Err(
PERR::MissingToken("in".into(), "after the iteration variable".into())
.into_err(pos),
)
}
}
// for name in expr { body }
@ -2136,10 +2338,14 @@ fn parse_block<'a>(
// Must start with {
let pos = match input
.next()
.ok_or_else(|| PERR::MissingLeftBrace.into_err_eof())?
.ok_or_else(|| PERR::UnexpectedEOF.into_err_eof())?
{
(Token::LeftBrace, pos) => pos,
(_, pos) => return Err(PERR::MissingLeftBrace.into_err(pos)),
(_, pos) => {
return Err(
PERR::MissingToken("{".into(), "to start a statement block".into()).into_err(pos),
)
}
};
let mut statements = Vec::new();
@ -2169,20 +2375,24 @@ fn parse_block<'a>(
// { ... stmt ??? - error
Some((_, pos)) => {
// Semicolons are not optional between statements
return Err(PERR::MissingSemicolon("terminating a statement".into()).into_err(*pos));
return Err(
PERR::MissingToken(";".into(), "to terminate this statement".into())
.into_err(*pos),
);
}
}
}
match input
.peek()
.ok_or_else(|| PERR::MissingRightBrace("end of block".into()).into_err_eof())?
{
match input.peek().ok_or_else(|| {
PERR::MissingToken("}".into(), "to end this statement block".into()).into_err_eof()
})? {
(Token::RightBrace, _) => {
input.next();
Ok(Stmt::Block(statements, pos))
}
(_, pos) => Err(PERR::MissingRightBrace("end of block".into()).into_err(*pos)),
(_, pos) => {
Err(PERR::MissingToken("}".into(), "to end this statement block".into()).into_err(*pos))
}
}
}
@ -2284,28 +2494,30 @@ fn parse_fn<'a>(
if matches!(input.peek(), Some((Token::RightParen, _))) {
input.next();
} else {
let end_err = format!("closing the parameters list of function '{}'", name);
let sep_err = format!("separating the parameters of function '{}'", name);
let end_err = format!("to close the parameters list of function '{}'", name);
let sep_err = format!("to separate the parameters of function '{}'", name);
loop {
match input
.next()
.ok_or_else(|| PERR::MissingRightParen(end_err.to_string()).into_err_eof())?
.ok_or_else(|| PERR::MissingToken(")".into(), end_err.to_string()).into_err_eof())?
{
(Token::Identifier(s), pos) => {
params.push((s, pos));
}
(_, pos) => return Err(PERR::MissingRightParen(end_err).into_err(pos)),
(_, pos) => return Err(PERR::MissingToken(")".into(), end_err).into_err(pos)),
}
match input
.next()
.ok_or_else(|| PERR::MissingRightParen(end_err.to_string()).into_err_eof())?
.ok_or_else(|| PERR::MissingToken(")".into(), end_err.to_string()).into_err_eof())?
{
(Token::RightParen, _) => break,
(Token::Comma, _) => (),
(Token::Identifier(_), _) => return Err(PERR::MissingComma(sep_err).into_err(pos)),
(_, pos) => return Err(PERR::MissingRightParen(sep_err).into_err(pos)),
(Token::Identifier(_), pos) => {
return Err(PERR::MissingToken(",".into(), sep_err).into_err(pos))
}
(_, pos) => return Err(PERR::MissingToken(",".into(), sep_err).into_err(pos)),
}
}
}
@ -2322,7 +2534,7 @@ fn parse_fn<'a>(
.map_or_else(|| Ok(()), |(p2, pos)| Err((p2, *pos)))
})
.map_err(|(p, pos)| {
PERR::FnDuplicateParam(name.to_string(), p.to_string()).into_err(pos)
PERR::FnDuplicatedParam(name.to_string(), p.to_string()).into_err(pos)
})?;
// Parse function body
@ -2408,7 +2620,10 @@ fn parse_global_level<'a>(
// stmt ??? - error
Some((_, pos)) => {
// Semicolons are not optional between statements
return Err(PERR::MissingSemicolon("terminating a statement".into()).into_err(*pos));
return Err(
PERR::MissingToken(";".into(), "to terminate this statement".into())
.into_err(*pos),
);
}
}
}

View File

@ -20,6 +20,11 @@ use crate::stdlib::path::PathBuf;
pub enum EvalAltResult {
/// Syntax error.
ErrorParsing(ParseError),
/// Error reading from a script file. Wrapped value is the path of the script file.
#[cfg(not(feature = "no_std"))]
ErrorReadingScriptFile(PathBuf, std::io::Error),
/// Call to an unknown function. Wrapped value is the name of the function.
ErrorFunctionNotFound(String, Position),
/// Function call has incorrect number of arguments.
@ -36,10 +41,12 @@ pub enum EvalAltResult {
/// String indexing out-of-bounds.
/// Wrapped values are the current number of characters in the string and the index number.
ErrorStringBounds(usize, INT, Position),
/// Trying to index into a type that is not an array and not a string.
/// Trying to index into a type that is not an array, an object map, or a string.
ErrorIndexingType(String, Position),
/// Trying to index into an array or string with an index that is not `i64`.
ErrorIndexExpr(Position),
ErrorNumericIndexExpr(Position),
/// Trying to index into a map with an index that is not `String`.
ErrorStringIndexExpr(Position),
/// The guard expression in an `if` or `while` statement does not return a boolean value.
ErrorLogicGuard(Position),
/// The `for` statement encounters a type that is not an iterator.
@ -53,9 +60,6 @@ pub enum EvalAltResult {
/// Returned type is not the same as the required output type.
/// Wrapped value is the type of the actual result.
ErrorMismatchOutputType(String, Position),
/// Error reading from a script file. Wrapped value is the path of the script file.
#[cfg(not(feature = "no_std"))]
ErrorReadingScriptFile(PathBuf, std::io::Error),
/// Inappropriate member access.
ErrorDotExpr(String, Position),
/// Arithmetic error encountered. Wrapped value is the error message.
@ -64,6 +68,7 @@ pub enum EvalAltResult {
ErrorStackOverflow(Position),
/// Run-time error encountered. Wrapped value is the error message.
ErrorRuntime(String, Position),
/// Breaking out of loops - not an error if within a loop.
ErrorLoopBreak(Position),
/// Not an error: Value returned from a script via the `return` keyword.
@ -74,6 +79,9 @@ pub enum EvalAltResult {
impl EvalAltResult {
pub(crate) fn desc(&self) -> &str {
match self {
#[cfg(not(feature = "no_std"))]
Self::ErrorReadingScriptFile(_, _) => "Cannot read from script file",
Self::ErrorParsing(p) => p.desc(),
Self::ErrorFunctionNotFound(_, _) => "Function not found",
Self::ErrorFunctionArgsMismatch(_, _, _, _) => {
@ -81,9 +89,12 @@ impl EvalAltResult {
}
Self::ErrorBooleanArgMismatch(_, _) => "Boolean operator expects boolean operands",
Self::ErrorCharMismatch(_) => "Character expected",
Self::ErrorIndexExpr(_) => "Indexing into an array or string expects an integer index",
Self::ErrorNumericIndexExpr(_) => {
"Indexing into an array or string expects an integer index"
}
Self::ErrorStringIndexExpr(_) => "Indexing into an object map expects a string index",
Self::ErrorIndexingType(_, _) => {
"Indexing can only be performed on an array or a string"
"Indexing can only be performed on an array, an object map, or a string"
}
Self::ErrorArrayBounds(_, index, _) if *index < 0 => {
"Array access expects non-negative index"
@ -103,8 +114,6 @@ impl EvalAltResult {
}
Self::ErrorAssignmentToConstant(_, _) => "Assignment to a constant variable",
Self::ErrorMismatchOutputType(_, _) => "Output type is incorrect",
#[cfg(not(feature = "no_std"))]
Self::ErrorReadingScriptFile(_, _) => "Cannot read from script file",
Self::ErrorDotExpr(_, _) => "Malformed dot expression",
Self::ErrorArithmetic(_, _) => "Arithmetic error",
Self::ErrorStackOverflow(_) => "Stack overflow",
@ -122,43 +131,52 @@ impl fmt::Display for EvalAltResult {
let desc = self.desc();
match self {
Self::ErrorFunctionNotFound(s, pos) => write!(f, "{}: '{}' ({})", desc, s, pos),
Self::ErrorVariableNotFound(s, pos) => write!(f, "{}: '{}' ({})", desc, s, pos),
Self::ErrorIndexingType(_, pos) => write!(f, "{} ({})", desc, pos),
Self::ErrorIndexExpr(pos) => write!(f, "{} ({})", desc, pos),
Self::ErrorLogicGuard(pos) => write!(f, "{} ({})", desc, pos),
Self::ErrorFor(pos) => write!(f, "{} ({})", desc, pos),
Self::ErrorAssignmentToUnknownLHS(pos) => write!(f, "{} ({})", desc, pos),
Self::ErrorAssignmentToConstant(s, pos) => write!(f, "{}: '{}' ({})", desc, s, pos),
Self::ErrorMismatchOutputType(s, pos) => write!(f, "{}: {} ({})", desc, s, pos),
Self::ErrorDotExpr(s, pos) if !s.is_empty() => write!(f, "{} {} ({})", desc, s, pos),
Self::ErrorDotExpr(_, pos) => write!(f, "{} ({})", desc, pos),
Self::ErrorArithmetic(s, pos) => write!(f, "{} ({})", s, pos),
Self::ErrorStackOverflow(pos) => write!(f, "{} ({})", desc, pos),
Self::ErrorRuntime(s, pos) => {
write!(f, "{} ({})", if s.is_empty() { desc } else { s }, pos)
}
Self::ErrorLoopBreak(pos) => write!(f, "{} ({})", desc, pos),
Self::Return(_, pos) => write!(f, "{} ({})", desc, pos),
#[cfg(not(feature = "no_std"))]
Self::ErrorReadingScriptFile(path, err) => {
write!(f, "{} '{}': {}", desc, path.display(), err)
}
Self::ErrorParsing(p) => write!(f, "Syntax error: {}", p),
Self::ErrorFunctionArgsMismatch(fun, 0, n, pos) => write!(
Self::ErrorFunctionNotFound(s, pos) | Self::ErrorVariableNotFound(s, pos) => {
write!(f, "{}: '{}' ({})", desc, s, pos)
}
Self::ErrorDotExpr(s, pos) if !s.is_empty() => write!(f, "{} {} ({})", desc, s, pos),
Self::ErrorIndexingType(_, pos)
| Self::ErrorNumericIndexExpr(pos)
| Self::ErrorStringIndexExpr(pos)
| Self::ErrorLogicGuard(pos)
| Self::ErrorFor(pos)
| Self::ErrorAssignmentToUnknownLHS(pos)
| Self::ErrorDotExpr(_, pos)
| Self::ErrorStackOverflow(pos) => write!(f, "{} ({})", desc, pos),
Self::ErrorRuntime(s, pos) => {
write!(f, "{} ({})", if s.is_empty() { desc } else { s }, pos)
}
Self::ErrorAssignmentToConstant(s, pos) => write!(f, "{}: '{}' ({})", desc, s, pos),
Self::ErrorMismatchOutputType(s, pos) => write!(f, "{}: {} ({})", desc, s, pos),
Self::ErrorArithmetic(s, pos) => write!(f, "{} ({})", s, pos),
Self::ErrorLoopBreak(pos) => write!(f, "{} ({})", desc, pos),
Self::Return(_, pos) => write!(f, "{} ({})", desc, pos),
Self::ErrorFunctionArgsMismatch(fn_name, 0, n, pos) => write!(
f,
"Function '{}' expects no argument but {} found ({})",
fun, n, pos
fn_name, n, pos
),
Self::ErrorFunctionArgsMismatch(fun, 1, n, pos) => write!(
Self::ErrorFunctionArgsMismatch(fn_name, 1, n, pos) => write!(
f,
"Function '{}' expects one argument but {} found ({})",
fun, n, pos
fn_name, n, pos
),
Self::ErrorFunctionArgsMismatch(fun, need, n, pos) => write!(
Self::ErrorFunctionArgsMismatch(fn_name, need, n, pos) => write!(
f,
"Function '{}' expects {} argument(s) but {} found ({})",
fun, need, n, pos
fn_name, need, n, pos
),
Self::ErrorBooleanArgMismatch(op, pos) => {
write!(f, "{} operator expects boolean operands ({})", op, pos)
@ -225,7 +243,8 @@ impl EvalAltResult {
| Self::ErrorArrayBounds(_, _, pos)
| Self::ErrorStringBounds(_, _, pos)
| Self::ErrorIndexingType(_, pos)
| Self::ErrorIndexExpr(pos)
| Self::ErrorNumericIndexExpr(pos)
| Self::ErrorStringIndexExpr(pos)
| Self::ErrorLogicGuard(pos)
| Self::ErrorFor(pos)
| Self::ErrorVariableNotFound(_, pos)
@ -256,7 +275,8 @@ impl EvalAltResult {
| Self::ErrorArrayBounds(_, _, ref mut pos)
| Self::ErrorStringBounds(_, _, ref mut pos)
| Self::ErrorIndexingType(_, ref mut pos)
| Self::ErrorIndexExpr(ref mut pos)
| Self::ErrorNumericIndexExpr(ref mut pos)
| Self::ErrorStringIndexExpr(ref mut pos)
| Self::ErrorLogicGuard(ref mut pos)
| Self::ErrorFor(ref mut pos)
| Self::ErrorVariableNotFound(_, ref mut pos)

View File

@ -16,6 +16,7 @@ fn test_arrays() -> Result<(), EvalAltResult> {
}
#[test]
#[cfg(not(feature = "no_object"))]
fn test_array_with_structs() -> Result<(), EvalAltResult> {
#[derive(Clone)]
struct TestStruct {

View File

@ -11,7 +11,7 @@ fn test_fn() -> Result<(), EvalAltResult> {
.expect_err("should be error")
.error_type()
{
ParseErrorType::FnDuplicateParam(f, p) if f == "hello" && p == "x" => (),
ParseErrorType::FnDuplicatedParam(f, p) if f == "hello" && p == "x" => (),
_ => assert!(false, "wrong error"),
}

View File

@ -12,7 +12,7 @@ fn test_chars() -> Result<(), EvalAltResult> {
assert_eq!(engine.eval::<char>(r#"let x="hello"; x[2]"#)?, 'l');
assert_eq!(
engine.eval::<String>(r#"let y="hello"; y[2]='$'; y"#)?,
"he$lo".to_string()
"he$lo"
);
}

View File

@ -27,6 +27,7 @@ fn test_expressions() -> Result<(), EvalAltResult> {
/// This example taken from https://github.com/jonathandturner/rhai/issues/115
#[test]
#[cfg(not(feature = "no_object"))]
fn test_expressions_eval() -> Result<(), EvalAltResult> {
#[derive(Debug, Clone)]
struct AGENT {

View File

@ -21,6 +21,7 @@ fn test_float() -> Result<(), EvalAltResult> {
}
#[test]
#[cfg(not(feature = "no_object"))]
fn struct_with_float() -> Result<(), EvalAltResult> {
#[derive(Clone)]
struct TestStruct {

View File

@ -1,3 +1,5 @@
#![cfg(not(feature = "no_object"))]
use rhai::{Engine, EvalAltResult, RegisterFn, INT};
#[test]

View File

@ -7,7 +7,7 @@ fn test_increment() -> Result<(), EvalAltResult> {
assert_eq!(engine.eval::<INT>("let x = 1; x += 2; x")?, 3);
assert_eq!(
engine.eval::<String>("let s = \"test\"; s += \"ing\"; s")?,
"testing".to_string()
"testing"
);
Ok(())

66
tests/maps.rs Normal file
View File

@ -0,0 +1,66 @@
#![cfg(not(feature = "no_object"))]
use rhai::{AnyExt, Engine, EvalAltResult, Map, INT};
#[test]
fn test_map_indexing() -> Result<(), EvalAltResult> {
let mut engine = Engine::new();
#[cfg(not(feature = "no_index"))]
assert_eq!(
engine.eval::<INT>(r#"let x = #{a: 1, b: 2, c: 3}; x["b"]"#)?,
2
);
assert_eq!(
engine.eval::<INT>("let y = #{a: 1, b: 2, c: 3}; y.a = 5; y.a")?,
5
);
#[cfg(not(feature = "no_index"))]
assert_eq!(
engine.eval::<char>(
r#"
let y = #{d: 1, "e": #{a: 42, b: 88, "": "hello"}, " 123 xyz": 9};
y.e[""][4]
"#
)?,
'o'
);
engine.eval::<()>("let y = #{a: 1, b: 2, c: 3}; y.z")?;
Ok(())
}
#[test]
fn test_map_assign() -> Result<(), EvalAltResult> {
let mut engine = Engine::new();
let x = engine.eval::<Map>(r#"let x = #{a: 1, b: true, "c#": "hello"}; x"#)?;
let a = x.get("a").cloned().unwrap();
let b = x.get("b").cloned().unwrap();
let c = x.get("c#").cloned().unwrap();
assert_eq!(*a.downcast::<INT>().unwrap(), 1);
assert_eq!(*b.downcast::<bool>().unwrap(), true);
assert_eq!(*c.downcast::<String>().unwrap(), "hello");
Ok(())
}
#[test]
fn test_map_return() -> Result<(), EvalAltResult> {
let mut engine = Engine::new();
let x = engine.eval::<Map>(r#"#{a: 1, b: true, c: "hello"}"#)?;
let a = x.get("a").cloned().unwrap();
let b = x.get("b").cloned().unwrap();
let c = x.get("c").cloned().unwrap();
assert_eq!(*a.downcast::<INT>().unwrap(), 1);
assert_eq!(*b.downcast::<bool>().unwrap(), true);
assert_eq!(*c.downcast::<String>().unwrap(), "hello");
Ok(())
}

View File

@ -12,12 +12,12 @@ fn test_math() -> Result<(), EvalAltResult> {
#[cfg(not(feature = "only_i32"))]
assert_eq!(
engine.eval::<INT>("(-9223372036854775807).abs()")?,
engine.eval::<INT>("abs(-9223372036854775807)")?,
9_223_372_036_854_775_807
);
#[cfg(feature = "only_i32")]
assert_eq!(engine.eval::<INT>("(-2147483647).abs()")?, 2147483647);
assert_eq!(engine.eval::<INT>("abs(-2147483647)")?, 2147483647);
// Overflow/underflow/division-by-zero errors
#[cfg(not(feature = "unchecked"))]
@ -26,7 +26,7 @@ fn test_math() -> Result<(), EvalAltResult> {
{
assert!(matches!(
engine
.eval::<INT>("(-9223372036854775808).abs()")
.eval::<INT>("abs(-9223372036854775808)")
.expect_err("expects negation overflow"),
EvalAltResult::ErrorArithmetic(_, _)
));

View File

@ -1,8 +1,10 @@
#![cfg(not(feature = "no_object"))]
use rhai::{Engine, EvalAltResult, RegisterFn, INT};
#[test]
fn test_method_call() -> Result<(), EvalAltResult> {
#[derive(Clone)]
#[derive(Debug, Clone, Eq, PartialEq)]
struct TestStruct {
x: INT,
}
@ -24,11 +26,15 @@ fn test_method_call() -> Result<(), EvalAltResult> {
engine.register_fn("update", TestStruct::update);
engine.register_fn("new_ts", TestStruct::new);
let ts = engine.eval::<TestStruct>("let x = new_ts(); x.update(); x")?;
assert_eq!(ts.x, 1001);
assert_eq!(
engine.eval::<TestStruct>("let x = new_ts(); x.update(); x")?,
TestStruct { x: 1001 }
);
let ts = engine.eval::<TestStruct>("let x = new_ts(); update(x); x")?;
assert_eq!(ts.x, 1);
assert_eq!(
engine.eval::<TestStruct>("let x = new_ts(); update(x); x")?,
TestStruct { x: 1 }
);
Ok(())
}

View File

@ -12,6 +12,7 @@ fn test_mismatched_op() {
}
#[test]
#[cfg(not(feature = "no_object"))]
fn test_mismatched_op_custom_type() {
#[derive(Clone)]
struct TestStruct {

View File

@ -1,3 +1,5 @@
#![cfg(not(feature = "no_object"))]
///! This test simulates an external command object that is driven by a script.
use rhai::{Engine, EvalAltResult, RegisterFn, Scope, INT};
use std::cell::RefCell;

View File

@ -6,30 +6,24 @@ fn test_string() -> Result<(), EvalAltResult> {
assert_eq!(
engine.eval::<String>(r#""Test string: \u2764""#)?,
"Test string: ❤".to_string()
"Test string: ❤"
);
assert_eq!(
engine.eval::<String>(r#""Test string: \x58""#)?,
"Test string: X".to_string()
"Test string: X"
);
assert_eq!(
engine.eval::<String>(r#""foo" + "bar""#)?,
"foobar".to_string()
);
assert_eq!(engine.eval::<String>(r#""foo" + "bar""#)?, "foobar");
#[cfg(not(feature = "no_stdlib"))]
assert_eq!(
engine.eval::<String>(r#""foo" + 123"#)?,
"foo123".to_string()
);
assert_eq!(engine.eval::<String>(r#""foo" + 123"#)?, "foo123");
#[cfg(not(feature = "no_float"))]
#[cfg(not(feature = "no_stdlib"))]
assert_eq!(
engine.eval::<String>(r#""foo" + 123.4556"#)?,
"foo123.4556".to_string()
);
assert_eq!(engine.eval::<String>(r#""foo" + 123.4556"#)?, "foo123.4556");
#[cfg(not(feature = "no_stdlib"))]
assert_eq!(engine.eval::<String>("(42).to_string()")?, "42");
Ok(())
}

View File

@ -1,7 +1,12 @@
use rhai::{Engine, EvalAltResult};
use rhai::{Engine, EvalAltResult, RegisterFn, INT};
#[test]
fn test_type_of() -> Result<(), EvalAltResult> {
#[derive(Clone)]
struct TestStruct {
x: INT,
}
let mut engine = Engine::new();
#[cfg(not(feature = "only_i32"))]
@ -14,12 +19,25 @@ fn test_type_of() -> Result<(), EvalAltResult> {
assert_eq!(engine.eval::<String>("type_of(1.0 + 2.0)")?, "f64");
#[cfg(not(feature = "no_index"))]
#[cfg(not(feature = "no_float"))]
assert_eq!(
engine.eval::<String>(r#"type_of([1.0, 2, "hello"])"#)?,
engine.eval::<String>(r#"type_of([true, 2, "hello"])"#)?,
"array"
);
#[cfg(not(feature = "no_object"))]
assert_eq!(
engine.eval::<String>(r#"type_of(#{a:true, "":2, "z":"hello"})"#)?,
"map"
);
#[cfg(not(feature = "no_object"))]
{
engine.register_type_with_name::<TestStruct>("Hello");
engine.register_fn("new_ts", || TestStruct { x: 1 });
assert_eq!(engine.eval::<String>("type_of(new_ts())")?, "Hello");
}
assert_eq!(engine.eval::<String>(r#"type_of("hello")"#)?, "string");
#[cfg(not(feature = "only_i32"))]