commit
d614d5da1a
@ -20,13 +20,14 @@ categories = [ "no-std", "embedded", "parser-implementations" ]
|
|||||||
num-traits = "0.2.11"
|
num-traits = "0.2.11"
|
||||||
|
|
||||||
[features]
|
[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 = []
|
default = []
|
||||||
unchecked = [] # unchecked arithmetic
|
unchecked = [] # unchecked arithmetic
|
||||||
no_stdlib = [] # no standard library of utility functions
|
no_stdlib = [] # no standard library of utility functions
|
||||||
no_index = [] # no arrays and indexing
|
no_index = [] # no arrays and indexing
|
||||||
no_float = [] # no floating-point
|
no_float = [] # no floating-point
|
||||||
no_function = [] # no script-defined functions
|
no_function = [] # no script-defined functions
|
||||||
|
no_object = [] # no custom objects
|
||||||
no_optimize = [] # no script optimizer
|
no_optimize = [] # no script optimizer
|
||||||
optimize_full = [] # set optimization level to Full (default is Simple) - this is a feature used only to simplify testing
|
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)
|
only_i32 = [] # set INT=i32 (useful for 32-bit systems)
|
||||||
|
357
README.md
357
README.md
@ -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! |
|
| `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_function` | Disable script-defined functions if not needed. |
|
||||||
| `no_index` | Disable arrays and indexing features 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_float` | Disable floating-point numbers and math if not needed. |
|
||||||
| `no_optimize` | Disable the script optimizer. |
|
| `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`. |
|
| `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_index`]: #optional-features
|
||||||
[`no_float`]: #optional-features
|
[`no_float`]: #optional-features
|
||||||
[`no_function`]: #optional-features
|
[`no_function`]: #optional-features
|
||||||
|
[`no_object`]: #optional-features
|
||||||
[`no_optimize`]: #optional-features
|
[`no_optimize`]: #optional-features
|
||||||
[`only_i32`]: #optional-features
|
[`only_i32`]: #optional-features
|
||||||
[`only_i64`]: #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: i64 = engine.eval("40 + 2")?; // return type is inferred to be i64
|
||||||
|
|
||||||
let result = engine.eval<String>("40 + 2")?; // returns an error because the actual return type is i64, not String
|
let result = engine.eval::<String>("40 + 2")?; // returns an error because the actual return type is i64, not String
|
||||||
```
|
```
|
||||||
|
|
||||||
Evaluate a script file directly:
|
Evaluate a script file directly:
|
||||||
@ -259,20 +261,23 @@ Values and types
|
|||||||
----------------
|
----------------
|
||||||
|
|
||||||
[`type_of()`]: #values-and-types
|
[`type_of()`]: #values-and-types
|
||||||
|
[`to_string()`]: #values-and-types
|
||||||
|
|
||||||
The following primitive types are supported natively:
|
The following primitive types are supported natively:
|
||||||
|
|
||||||
| Category | Equivalent Rust types | `type_of()` name |
|
| 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)_ | _same as type_ |
|
| **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)_ | _same as type_ |
|
| **Floating-point number** (disabled with [`no_float`]) | `f32`, `f64` _(default)_ | `"f32"` or `"f64"` | `"123.4567"` etc. |
|
||||||
| **Boolean value** | `bool` | `"bool"` |
|
| **Boolean value** | `bool` | `"bool"` | `"true"` or `"false"` |
|
||||||
| **Unicode character** | `char` | `"char"` |
|
| **Unicode character** | `char` | `"char"` | `"A"`, `"x"` etc. |
|
||||||
| **Unicode string** | `String` (_not_ `&str`) | `"string"` |
|
| **Unicode string** | `String` (_not_ `&str`) | `"string"` | `"hello"` etc. |
|
||||||
| **Array** (disabled with [`no_index`]) | `rhai::Array` | `"array"` |
|
| **Array** (disabled with [`no_index`]) | `rhai::Array` | `"array"` | `"[ ? ? ? ]"` |
|
||||||
| **Dynamic value** (i.e. can be anything) | `rhai::Dynamic` | _the actual type_ |
|
| **Object map** (disabled with [`no_object`]) | `rhai::Map` | `"map"` | `#{ "a": 1, "b": 2 }` |
|
||||||
| **System number** (current configuration) | `rhai::INT` (`i32` or `i64`),<br/>`rhai::FLOAT` (`f32` or `f64`) | _same as type_ |
|
| **Dynamic value** (i.e. can be anything) | `rhai::Dynamic` | _the actual type_ | _actual value_ |
|
||||||
| **Nothing/void/nil/null** (or whatever you want to call it) | `()` | `"()"` |
|
| **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
|
[`Dynamic`]: #values-and-types
|
||||||
[`()`]: #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.
|
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
|
```rust
|
||||||
// Use 'type_of()' to get the actual types of values
|
// Use 'type_of()' to get the actual types of values
|
||||||
@ -294,7 +301,7 @@ type_of('c') == "char";
|
|||||||
type_of(42) == "i64";
|
type_of(42) == "i64";
|
||||||
|
|
||||||
let x = 123;
|
let x = 123;
|
||||||
x.type_of(); // error - 'type_of' cannot use postfix notation
|
x.type_of(); // <- error: 'type_of' cannot use method-call style
|
||||||
type_of(x) == "i64";
|
type_of(x) == "i64";
|
||||||
|
|
||||||
x = 99.999;
|
x = 99.999;
|
||||||
@ -312,12 +319,13 @@ Value conversions
|
|||||||
[`to_int`]: #value-conversions
|
[`to_int`]: #value-conversions
|
||||||
[`to_float`]: #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.
|
The `to_float` function converts a supported number to `FLOAT` (`f32` or `f64`),
|
||||||
For other conversions, register custom conversion functions.
|
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
|
```rust
|
||||||
let x = 42;
|
let x = 42;
|
||||||
let y = x * 100.0; // error: cannot multiply i64 with f64
|
let y = x * 100.0; // <- error: cannot multiply i64 with f64
|
||||||
let y = x.to_float() * 100.0; // works
|
let y = x.to_float() * 100.0; // works
|
||||||
let z = y.to_int() + x; // works
|
let z = y.to_int() + x; // works
|
||||||
|
|
||||||
@ -333,8 +341,8 @@ To call these functions, they need to be registered with the [`Engine`].
|
|||||||
|
|
||||||
```rust
|
```rust
|
||||||
use rhai::{Engine, EvalAltResult};
|
use rhai::{Engine, EvalAltResult};
|
||||||
use rhai::RegisterFn; // use `RegisterFn` trait for `register_fn`
|
use rhai::RegisterFn; // use `RegisterFn` trait for `register_fn`
|
||||||
use rhai::{Dynamic, RegisterDynamicFn}; // use `RegisterDynamicFn` trait for `register_dynamic_fn`
|
use rhai::{Dynamic, RegisterDynamicFn}; // use `RegisterDynamicFn` trait for `register_dynamic_fn`
|
||||||
|
|
||||||
// Normal function
|
// Normal function
|
||||||
fn add(x: i64, y: i64) -> i64 {
|
fn add(x: i64, y: i64) -> i64 {
|
||||||
@ -354,14 +362,14 @@ fn main() -> Result<(), EvalAltResult>
|
|||||||
|
|
||||||
let result = engine.eval::<i64>("add(40, 2)")?;
|
let result = engine.eval::<i64>("add(40, 2)")?;
|
||||||
|
|
||||||
println!("Answer: {}", result); // prints 42
|
println!("Answer: {}", result); // prints 42
|
||||||
|
|
||||||
// Functions that return Dynamic values must use register_dynamic_fn()
|
// Functions that return Dynamic values must use register_dynamic_fn()
|
||||||
engine.register_dynamic_fn("get_an_any", get_an_any);
|
engine.register_dynamic_fn("get_an_any", get_an_any);
|
||||||
|
|
||||||
let result = engine.eval::<i64>("get_an_any()")?;
|
let result = engine.eval::<i64>("get_an_any()")?;
|
||||||
|
|
||||||
println!("Answer: {}", result); // prints 42
|
println!("Answer: {}", result); // prints 42
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@ -415,13 +423,13 @@ The function must return `Result<_, EvalAltResult>`. `EvalAltResult` implements
|
|||||||
|
|
||||||
```rust
|
```rust
|
||||||
use rhai::{Engine, EvalAltResult, Position};
|
use rhai::{Engine, EvalAltResult, Position};
|
||||||
use rhai::RegisterResultFn; // use `RegisterResultFn` trait for `register_result_fn`
|
use rhai::RegisterResultFn; // use `RegisterResultFn` trait for `register_result_fn`
|
||||||
|
|
||||||
// Function that may fail
|
// Function that may fail
|
||||||
fn safe_divide(x: i64, y: i64) -> Result<i64, EvalAltResult> {
|
fn safe_divide(x: i64, y: i64) -> Result<i64, EvalAltResult> {
|
||||||
if y == 0 {
|
if y == 0 {
|
||||||
// Return an error if y is zero
|
// Return an error if y is zero
|
||||||
Err("Division by zero detected!".into()) // short-cut to create EvalAltResult
|
Err("Division by zero detected!".into()) // short-cut to create EvalAltResult
|
||||||
} else {
|
} else {
|
||||||
Ok(x / y)
|
Ok(x / y)
|
||||||
}
|
}
|
||||||
@ -435,7 +443,7 @@ fn main()
|
|||||||
engine.register_result_fn("divide", safe_divide);
|
engine.register_result_fn("divide", safe_divide);
|
||||||
|
|
||||||
if let Err(error) = engine.eval::<i64>("divide(40, 0)") {
|
if let Err(error) = engine.eval::<i64>("divide(40, 0)") {
|
||||||
println!("Error: {:?}", error); // prints ErrorRuntime("Division by zero detected!", (1, 1)")
|
println!("Error: {:?}", error); // prints ErrorRuntime("Division by zero detected!", (1, 1)")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
@ -496,6 +504,7 @@ fn main() -> Result<(), EvalAltResult>
|
|||||||
```
|
```
|
||||||
|
|
||||||
All custom types must implement `Clone`. This allows the [`Engine`] to pass by value.
|
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
|
```rust
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
@ -522,7 +531,8 @@ let mut engine = Engine::new();
|
|||||||
engine.register_type::<TestStruct>();
|
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.*
|
*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'
|
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
|
```rust
|
||||||
let result = engine.eval::<TestStruct>("let x = new_ts(); x.update(); x")?;
|
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
|
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
|
```rust
|
||||||
fn foo(ts: &mut TestStruct) -> i64 {
|
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
|
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.
|
with a special "pretty-print" name, [`type_of()`] will return that name instead.
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
@ -605,6 +625,9 @@ let result = engine.eval::<i64>("let a = new_ts(); a.xyz = 42; a.xyz")?;
|
|||||||
println!("Answer: {}", result); // prints 42
|
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
|
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
|
||||||
|
|
||||||
Variables in Rhai follow normal C naming rules (i.e. must contain only ASCII letters, digits and underscores '`_`').
|
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.
|
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.
|
Variables are defined using the `let` keyword. A variable defined within a statement block is _local_ to that block.
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
let x = 3; // ok
|
let x = 3; // ok
|
||||||
let _x = 42; // ok
|
let _x = 42; // ok
|
||||||
let x_ = 42; // also ok
|
let x_ = 42; // also ok
|
||||||
let _x_ = 42; // still ok
|
let _x_ = 42; // still ok
|
||||||
|
|
||||||
let _ = 123; // syntax error - illegal variable name
|
let _ = 123; // <- syntax error: illegal variable name
|
||||||
let _9 = 9; // syntax error - illegal variable name
|
let _9 = 9; // <- syntax error: illegal variable name
|
||||||
|
|
||||||
let x = 42; // variable is 'x', lower case
|
let x = 42; // variable is 'x', lower case
|
||||||
let X = 123; // variable is 'X', upper case
|
let X = 123; // variable is 'X', upper case
|
||||||
x == 42;
|
x == 42;
|
||||||
X == 123;
|
X == 123;
|
||||||
|
|
||||||
{
|
{
|
||||||
let x = 999; // local variable 'x' shadows the 'x' in parent block
|
let x = 999; // local variable 'x' shadows the 'x' in parent block
|
||||||
x == 999; // access to local 'x'
|
x == 999; // access to local 'x'
|
||||||
}
|
}
|
||||||
x == 42; // the parent block's 'x' is not changed
|
x == 42; // the parent block's 'x' is not changed
|
||||||
```
|
```
|
||||||
|
|
||||||
Constants
|
Constants
|
||||||
---------
|
---------
|
||||||
|
|
||||||
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
|
```rust
|
||||||
const x = 42;
|
const x = 42;
|
||||||
print(x * 2); // prints 84
|
print(x * 2); // prints 84
|
||||||
x = 123; // syntax error - cannot assign to constant
|
x = 123; // <- syntax error: cannot assign to constant
|
||||||
```
|
```
|
||||||
|
|
||||||
Constants must be assigned a _value_, not an expression.
|
Constants must be assigned a _value_, not an expression.
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
const x = 40 + 2; // syntax error - cannot assign expression to constant
|
const x = 40 + 2; // <- syntax error: cannot assign expression to constant
|
||||||
```
|
```
|
||||||
|
|
||||||
Numbers
|
Numbers
|
||||||
@ -982,7 +1007,7 @@ let foo = [1, 2, 3][0];
|
|||||||
foo == 1;
|
foo == 1;
|
||||||
|
|
||||||
fn abc() {
|
fn abc() {
|
||||||
[42, 43, 44] // a function returning an array literal
|
[42, 43, 44] // a function returning an array
|
||||||
}
|
}
|
||||||
|
|
||||||
let foo = abc()[0];
|
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)) );
|
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
|
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`.
|
types - `INT` (`i64` or `i32` depending on [`only_i32`] and [`only_i64`]), `f64` (if not [`no_float`]), string, array, `bool`, `char`.
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
42 == 42; // true
|
42 == 42; // true
|
||||||
42 > 42; // false
|
42 > 42; // false
|
||||||
"hello" > "foo"; // true
|
"hello" > "foo"; // true
|
||||||
"42" == 42; // false
|
"42" == 42; // false
|
||||||
```
|
```
|
||||||
|
|
||||||
Comparing two values of _different_ data types, or of unknown data types, always results in `false`.
|
Comparing two values of _different_ data types, or of unknown data types, always results in `false`.
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
42 == 42.0; // false - i64 is different from f64
|
42 == 42.0; // false - i64 is different from f64
|
||||||
42 > "42"; // false - i64 is different from string
|
42 > "42"; // false - i64 is different from string
|
||||||
42 <= "42"; // false again
|
42 <= "42"; // false again
|
||||||
|
|
||||||
let ts = new_ts(); // custom type
|
let ts = new_ts(); // custom type
|
||||||
ts == 42; // false - types are not the same
|
ts == 42; // false - types are not the same
|
||||||
```
|
```
|
||||||
|
|
||||||
Boolean operators
|
Boolean operators
|
||||||
@ -1066,11 +1169,11 @@ if the first one already proves the condition wrong.
|
|||||||
Single boolean operators `&` and `|` always evaluate both operands.
|
Single boolean operators `&` and `|` always evaluate both operands.
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
this() || that(); // that() is not evaluated if this() is true
|
this() || that(); // that() is not evaluated if this() is true
|
||||||
this() && that(); // that() is not evaluated if this() is false
|
this() && that(); // that() is not evaluated if this() is false
|
||||||
|
|
||||||
this() | that(); // both this() and that() are evaluated
|
this() | that(); // both this() and that() are evaluated
|
||||||
this() & that(); // both this() and that() are evaluated
|
this() & that(); // both this() and that() are evaluated
|
||||||
```
|
```
|
||||||
|
|
||||||
Compound assignment operators
|
Compound assignment operators
|
||||||
@ -1078,13 +1181,13 @@ Compound assignment operators
|
|||||||
|
|
||||||
```rust
|
```rust
|
||||||
let number = 5;
|
let number = 5;
|
||||||
number += 4; // number = number + 4
|
number += 4; // number = number + 4
|
||||||
number -= 3; // number = number - 3
|
number -= 3; // number = number - 3
|
||||||
number *= 2; // number = number * 2
|
number *= 2; // number = number * 2
|
||||||
number /= 1; // number = number / 1
|
number /= 1; // number = number / 1
|
||||||
number %= 3; // number = number % 3
|
number %= 3; // number = number % 3
|
||||||
number <<= 2; // number = number << 2
|
number <<= 2; // number = number << 2
|
||||||
number >>= 1; // number = number >> 1
|
number >>= 1; // number = number >> 1
|
||||||
```
|
```
|
||||||
|
|
||||||
The `+=` operator can also be used to build strings:
|
The `+=` operator can also be used to build strings:
|
||||||
@ -1183,9 +1286,9 @@ for x in range(0, 50) {
|
|||||||
-------------------
|
-------------------
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
return; // equivalent to return ();
|
return; // equivalent to return ();
|
||||||
|
|
||||||
return 123 + 456; // returns 579
|
return 123 + 456; // returns 579
|
||||||
```
|
```
|
||||||
|
|
||||||
Errors and `throw`-ing exceptions
|
Errors and `throw`-ing exceptions
|
||||||
@ -1196,10 +1299,10 @@ To deliberately return an error during an evaluation, use the `throw` keyword.
|
|||||||
|
|
||||||
```rust
|
```rust
|
||||||
if some_bad_condition_has_happened {
|
if some_bad_condition_has_happened {
|
||||||
throw error; // 'throw' takes a string to form the exception text
|
throw error; // 'throw' takes a string as the exception text
|
||||||
}
|
}
|
||||||
|
|
||||||
throw; // empty exception text: ""
|
throw; // defaults to empty exception text: ""
|
||||||
```
|
```
|
||||||
|
|
||||||
Exceptions thrown via `throw` in the script can be captured by matching `Err(EvalAltResult::ErrorRuntime(`_reason_`, `_position_`))`
|
Exceptions thrown via `throw` in the script can be captured by matching `Err(EvalAltResult::ErrorRuntime(`_reason_`, `_position_`))`
|
||||||
@ -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
|
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.
|
regardless of whether it is terminated with a semicolon `';'`. This is different from Rust.
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
fn add(x, y) {
|
fn add(x, y) { // implicit return:
|
||||||
x + y; // value of the last statement (no need for ending semicolon) is used as the return value
|
x + y; // value of the last statement (no need for ending semicolon)
|
||||||
|
// is used as the return value
|
||||||
}
|
}
|
||||||
|
|
||||||
fn add2(x) {
|
fn add2(x) {
|
||||||
return x + 2; // explicit return
|
return x + 2; // explicit return
|
||||||
}
|
}
|
||||||
|
|
||||||
print(add(2, 3)); // prints 5
|
print(add(2, 3)); // prints 5
|
||||||
print(add2(42)); // prints 44
|
print(add2(42)); // prints 44
|
||||||
```
|
```
|
||||||
|
|
||||||
### No access to external scope
|
### No access to external scope
|
||||||
@ -1255,7 +1359,7 @@ Functions can only access their parameters. They cannot access external variabl
|
|||||||
```rust
|
```rust
|
||||||
let x = 42;
|
let x = 42;
|
||||||
|
|
||||||
fn foo() { x } // syntax error - variable 'x' doesn't exist
|
fn foo() { x } // <- syntax error: variable 'x' doesn't exist
|
||||||
```
|
```
|
||||||
|
|
||||||
### Passing arguments by value
|
### Passing arguments by value
|
||||||
@ -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.
|
Any update to an argument will **not** be reflected back to the caller. This can introduce subtle bugs, if not careful.
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
fn change(s) { // 's' is passed by value
|
fn change(s) { // 's' is passed by value
|
||||||
s = 42; // only a COPY of 's' is changed
|
s = 42; // only a COPY of 's' is changed
|
||||||
}
|
}
|
||||||
|
|
||||||
let x = 500;
|
let x = 500;
|
||||||
x.change(); // de-sugars to change(x)
|
x.change(); // de-sugars to change(x)
|
||||||
x == 500; // 'x' is NOT changed!
|
x == 500; // 'x' is NOT changed!
|
||||||
```
|
```
|
||||||
|
|
||||||
### Global definitions only
|
### Global definitions only
|
||||||
@ -1286,7 +1390,7 @@ fn add(x, y) {
|
|||||||
|
|
||||||
// The following will not compile
|
// The following will not compile
|
||||||
fn do_addition(x) {
|
fn do_addition(x) {
|
||||||
fn add_y(n) { // functions cannot be defined inside another function
|
fn add_y(n) { // functions cannot be defined inside another function
|
||||||
n + y
|
n + y
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1307,10 +1411,10 @@ fn foo(x,y) { print("Two! " + x + "," + y) }
|
|||||||
fn foo() { print("None.") }
|
fn foo() { print("None.") }
|
||||||
fn foo(x) { print("HA! NEW ONE! " + x) } // overwrites previous definition
|
fn foo(x) { print("HA! NEW ONE! " + x) } // overwrites previous definition
|
||||||
|
|
||||||
foo(1,2,3); // prints "Three!!! 1,2,3"
|
foo(1,2,3); // prints "Three!!! 1,2,3"
|
||||||
foo(42); // prints "HA! NEW ONE! 42"
|
foo(42); // prints "HA! NEW ONE! 42"
|
||||||
foo(1,2); // prints "Two!! 1,2"
|
foo(1,2); // prints "Two!! 1,2"
|
||||||
foo(); // prints "None."
|
foo(); // prints "None."
|
||||||
```
|
```
|
||||||
|
|
||||||
Members and methods
|
Members and methods
|
||||||
@ -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.
|
Properties and methods in a Rust custom type registered with the [`Engine`] can be called just like in Rust.
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
let a = new_ts(); // constructor function
|
let a = new_ts(); // constructor function
|
||||||
a.field = 500; // property access
|
a.field = 500; // property access
|
||||||
a.update(); // method call
|
a.update(); // method call
|
||||||
|
|
||||||
update(a); // this works, but 'a' is unchanged because only a COPY of 'a' is passed to 'update' by VALUE
|
update(a); // this works, but 'a' is unchanged because only
|
||||||
|
// a COPY of 'a' is passed to 'update' by VALUE
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Custom types, properties and methods can be disabled via the [`no_object`] feature.
|
||||||
|
|
||||||
`print` and `debug`
|
`print` and `debug`
|
||||||
-------------------
|
-------------------
|
||||||
|
|
||||||
The `print` and `debug` functions default to printing to `stdout`, with `debug` using standard debug formatting.
|
The `print` and `debug` functions default to printing to `stdout`, with `debug` using standard debug formatting.
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
print("hello"); // prints hello to stdout
|
print("hello"); // prints hello to stdout
|
||||||
print(1 + 2 + 3); // prints 6 to stdout
|
print(1 + 2 + 3); // prints 6 to stdout
|
||||||
print("hello" + 42); // prints hello42 to stdout
|
print("hello" + 42); // prints hello42 to stdout
|
||||||
debug("world!"); // prints "world!" to stdout using debug formatting
|
debug("world!"); // prints "world!" to stdout using debug formatting
|
||||||
```
|
```
|
||||||
|
|
||||||
### Overriding `print` and `debug` with callback functions
|
### Overriding `print` and `debug` with callback functions
|
||||||
@ -1376,14 +1483,13 @@ For example, in the following:
|
|||||||
|
|
||||||
```rust
|
```rust
|
||||||
{
|
{
|
||||||
let x = 999; // NOT eliminated - Rhai doesn't check yet whether a variable is used later on
|
let x = 999; // NOT eliminated: Rhai doesn't check yet whether a variable is used later on
|
||||||
123; // eliminated - no effect
|
123; // eliminated: no effect
|
||||||
"hello"; // eliminated - no effect
|
"hello"; // eliminated: no effect
|
||||||
[1, 2, x, x*2, 5]; // eliminated - no effect
|
[1, 2, x, x*2, 5]; // eliminated: no effect
|
||||||
foo(42); // NOT eliminated - the function 'foo' may have side effects
|
foo(42); // NOT eliminated: the function 'foo' may have side effects
|
||||||
666 // NOT eliminated - this is the return value of the block,
|
666 // NOT eliminated: this is the return value of the block,
|
||||||
// and the block is the last one
|
// and the block is the last one so this is the return value of the whole script
|
||||||
// so this is the return value of the whole script
|
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@ -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 || some_work() { print("done!"); } // since '||' short-circuits, 'some_work' is never called
|
||||||
if true { print("done!"); } // <- the line above is equivalent to this
|
if true { print("done!"); } // <- the line above is equivalent to this
|
||||||
print("done!"); // <- the line above is further simplified to this
|
print("done!"); // <- the line above is further simplified to this
|
||||||
// because the condition is always true
|
// because the condition is always true
|
||||||
```
|
```
|
||||||
|
|
||||||
These are quite effective for template-based machine-generated scripts where certain constant values
|
These are quite effective for template-based machine-generated scripts where certain constant values
|
||||||
@ -1421,7 +1527,7 @@ so they are not optimized away:
|
|||||||
const DECISION = 1;
|
const DECISION = 1;
|
||||||
|
|
||||||
if DECISION == 1 { // NOT optimized away because you can define
|
if DECISION == 1 { // NOT optimized away because you can define
|
||||||
: // your own '==' function to override the built-in default!
|
: // your own '==' function to override the built-in default!
|
||||||
:
|
:
|
||||||
} else if DECISION == 2 { // same here, NOT optimized away
|
} else if DECISION == 2 { // same here, NOT optimized away
|
||||||
:
|
:
|
||||||
@ -1491,14 +1597,15 @@ evaluated all function calls with constant arguments, using the result to replac
|
|||||||
// When compiling the following with OptimizationLevel::Full...
|
// When compiling the following with OptimizationLevel::Full...
|
||||||
|
|
||||||
const DECISION = 1;
|
const DECISION = 1;
|
||||||
// this condition is now eliminated because 'DECISION == 1'
|
// this condition is now eliminated because 'DECISION == 1'
|
||||||
if DECISION == 1 { // is a function call to the '==' function, and it returns 'true'
|
if DECISION == 1 { // is a function call to the '==' function, and it returns 'true'
|
||||||
print("hello!"); // this block is promoted to the parent level
|
print("hello!"); // this block is promoted to the parent level
|
||||||
} else {
|
} else {
|
||||||
print("boo!"); // this block is eliminated because it is never reached
|
print("boo!"); // this block is eliminated because it is never reached
|
||||||
}
|
}
|
||||||
|
|
||||||
print("hello!"); // <- the above is equivalent to this ('print' and 'debug' are handled specially)
|
print("hello!"); // <- the above is equivalent to this
|
||||||
|
// ('print' and 'debug' are handled specially)
|
||||||
```
|
```
|
||||||
|
|
||||||
Because of the eager evaluation of functions, many constant expressions will be evaluated and replaced by the result.
|
Because of the eager evaluation of functions, many constant expressions will be evaluated and replaced by the result.
|
||||||
@ -1507,8 +1614,8 @@ This does not happen with [`OptimizationLevel::Simple`] which doesn't assume all
|
|||||||
```rust
|
```rust
|
||||||
// When compiling the following with OptimizationLevel::Full...
|
// When compiling the following with OptimizationLevel::Full...
|
||||||
|
|
||||||
let x = (1 + 2) * 3 - 4 / 5 % 6; // <- will be replaced by 'let x = 9'
|
let x = (1+2)*3-4/5%6; // <- will be replaced by 'let x = 9'
|
||||||
let y = (1 > 2) || (3 <= 4); // <- will be replaced by 'let y = true'
|
let y = (1>2) || (3<=4); // <- will be replaced by 'let y = true'
|
||||||
```
|
```
|
||||||
|
|
||||||
Function side effect considerations
|
Function side effect considerations
|
||||||
@ -1539,13 +1646,13 @@ Subtle semantic changes
|
|||||||
Some optimizations can alter subtle semantics of the script. For example:
|
Some optimizations can alter subtle semantics of the script. For example:
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
if true { // condition always true
|
if true { // condition always true
|
||||||
123.456; // eliminated
|
123.456; // eliminated
|
||||||
hello; // eliminated, EVEN THOUGH the variable doesn't exist!
|
hello; // eliminated, EVEN THOUGH the variable doesn't exist!
|
||||||
foo(42) // promoted up-level
|
foo(42) // promoted up-level
|
||||||
}
|
}
|
||||||
|
|
||||||
foo(42) // <- the above optimizes to this
|
foo(42) // <- the above optimizes to this
|
||||||
```
|
```
|
||||||
|
|
||||||
Nevertheless, if the original script were evaluated instead, it would have been an error - the variable `hello` doesn't exist,
|
Nevertheless, if the original script were evaluated instead, it would have been an error - the variable `hello` doesn't exist,
|
||||||
@ -1591,22 +1698,22 @@ let x = 10;
|
|||||||
|
|
||||||
fn foo(x) { x += 12; x }
|
fn foo(x) { x += 12; x }
|
||||||
|
|
||||||
let script = "let y = x;"; // build a script
|
let script = "let y = x;"; // build a script
|
||||||
script += "y += foo(y);";
|
script += "y += foo(y);";
|
||||||
script += "x + y";
|
script += "x + y";
|
||||||
|
|
||||||
let result = eval(script); // <- look, JS, we can also do this!
|
let result = eval(script); // <- look, JS, we can also do this!
|
||||||
|
|
||||||
print("Answer: " + result); // prints 42
|
print("Answer: " + result); // prints 42
|
||||||
|
|
||||||
print("x = " + x); // prints 10 - functions call arguments are passed by value
|
print("x = " + x); // prints 10: functions call arguments are passed by value
|
||||||
print("y = " + y); // prints 32 - variables defined in 'eval' persist!
|
print("y = " + y); // prints 32: variables defined in 'eval' persist!
|
||||||
|
|
||||||
eval("{ let z = y }"); // to keep a variable local, use a statement block
|
eval("{ let z = y }"); // to keep a variable local, use a statement block
|
||||||
|
|
||||||
print("z = " + z); // error - variable 'z' not found
|
print("z = " + z); // <- error: variable 'z' not found
|
||||||
|
|
||||||
"print(42)".eval(); // nope - just like 'type_of' 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_,
|
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
|
```rust
|
||||||
let script = "x += 32";
|
let script = "x += 32";
|
||||||
let x = 10;
|
let x = 10;
|
||||||
eval(script); // variable 'x' in the current scope is visible!
|
eval(script); // variable 'x' in the current scope is visible!
|
||||||
print(x); // prints 42
|
print(x); // prints 42
|
||||||
|
|
||||||
// The above is equivalent to:
|
// The above is equivalent to:
|
||||||
let script = "x += 32";
|
let script = "x += 32";
|
||||||
@ -1632,7 +1739,7 @@ disable `eval` by overriding it, probably with something that throws.
|
|||||||
```rust
|
```rust
|
||||||
fn eval(script) { throw "eval is evil! I refuse to run " + script }
|
fn eval(script) { throw "eval is evil! I refuse to run " + script }
|
||||||
|
|
||||||
let x = eval("40 + 2"); // 'eval' here throws "eval is evil! I refuse to run 40 + 2"
|
let x = eval("40 + 2"); // 'eval' here throws "eval is evil! I refuse to run 40 + 2"
|
||||||
```
|
```
|
||||||
|
|
||||||
Or override it from Rust:
|
Or override it from Rust:
|
||||||
|
@ -15,6 +15,8 @@ impl TestStruct {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(not(feature = "no_index"))]
|
||||||
|
#[cfg(not(feature = "no_object"))]
|
||||||
fn main() {
|
fn main() {
|
||||||
let mut engine = Engine::new();
|
let mut engine = Engine::new();
|
||||||
|
|
||||||
@ -32,3 +34,6 @@ fn main() {
|
|||||||
engine.eval::<TestStruct>("let x = [new_ts()]; x[0].update(); x[0]")
|
engine.eval::<TestStruct>("let x = [new_ts()]; x[0].update(); x[0]")
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(any(feature = "no_index", feature = "no_object"))]
|
||||||
|
fn main() {}
|
||||||
|
@ -15,6 +15,7 @@ impl TestStruct {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(not(feature = "no_object"))]
|
||||||
fn main() -> Result<(), EvalAltResult> {
|
fn main() -> Result<(), EvalAltResult> {
|
||||||
let mut engine = Engine::new();
|
let mut engine = Engine::new();
|
||||||
|
|
||||||
@ -29,3 +30,6 @@ fn main() -> Result<(), EvalAltResult> {
|
|||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "no_object")]
|
||||||
|
fn main() {}
|
||||||
|
164
src/api.rs
164
src/api.rs
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
use crate::any::{Any, AnyExt, Dynamic};
|
use crate::any::{Any, AnyExt, Dynamic};
|
||||||
use crate::call::FuncArgs;
|
use crate::call::FuncArgs;
|
||||||
use crate::engine::{Engine, FnAny, FnSpec, FUNC_GETTER, FUNC_SETTER};
|
use crate::engine::{make_getter, make_setter, Engine, FnAny, FnSpec};
|
||||||
use crate::error::ParseError;
|
use crate::error::ParseError;
|
||||||
use crate::fn_register::RegisterFn;
|
use crate::fn_register::RegisterFn;
|
||||||
use crate::parser::{lex, parse, parse_global_expr, FnDef, Position, AST};
|
use crate::parser::{lex, parse, parse_global_expr, FnDef, Position, AST};
|
||||||
@ -15,7 +15,6 @@ use crate::optimize::optimize_into_ast;
|
|||||||
use crate::stdlib::{
|
use crate::stdlib::{
|
||||||
any::{type_name, TypeId},
|
any::{type_name, TypeId},
|
||||||
boxed::Box,
|
boxed::Box,
|
||||||
format,
|
|
||||||
string::{String, ToString},
|
string::{String, ToString},
|
||||||
sync::Arc,
|
sync::Arc,
|
||||||
vec::Vec,
|
vec::Vec,
|
||||||
@ -25,12 +24,7 @@ use crate::stdlib::{fs::File, io::prelude::*, path::PathBuf};
|
|||||||
|
|
||||||
impl<'e> Engine<'e> {
|
impl<'e> Engine<'e> {
|
||||||
/// Register a custom function.
|
/// Register a custom function.
|
||||||
pub(crate) fn register_fn_raw(
|
pub(crate) fn register_fn_raw(&mut self, fn_name: &str, args: Vec<TypeId>, f: Box<FnAny>) {
|
||||||
&mut self,
|
|
||||||
fn_name: &str,
|
|
||||||
args: Option<Vec<TypeId>>,
|
|
||||||
f: Box<FnAny>,
|
|
||||||
) {
|
|
||||||
let spec = FnSpec {
|
let spec = FnSpec {
|
||||||
name: fn_name.to_string().into(),
|
name: fn_name.to_string().into(),
|
||||||
args,
|
args,
|
||||||
@ -45,7 +39,7 @@ impl<'e> Engine<'e> {
|
|||||||
/// # Example
|
/// # Example
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
/// #[derive(Clone)]
|
/// #[derive(Debug, Clone, Eq, PartialEq)]
|
||||||
/// struct TestStruct {
|
/// struct TestStruct {
|
||||||
/// field: i64
|
/// field: i64
|
||||||
/// }
|
/// }
|
||||||
@ -69,12 +63,13 @@ impl<'e> Engine<'e> {
|
|||||||
/// engine.register_fn("update", TestStruct::update);
|
/// engine.register_fn("update", TestStruct::update);
|
||||||
///
|
///
|
||||||
/// assert_eq!(
|
/// assert_eq!(
|
||||||
/// engine.eval::<TestStruct>("let x = new_ts(); x.update(41); x")?.field,
|
/// engine.eval::<TestStruct>("let x = new_ts(); x.update(41); x")?,
|
||||||
/// 42
|
/// TestStruct { field: 42 }
|
||||||
/// );
|
/// );
|
||||||
/// # Ok(())
|
/// # Ok(())
|
||||||
/// # }
|
/// # }
|
||||||
/// ```
|
/// ```
|
||||||
|
#[cfg(not(feature = "no_object"))]
|
||||||
pub fn register_type<T: Any + Clone>(&mut self) {
|
pub fn register_type<T: Any + Clone>(&mut self) {
|
||||||
self.register_type_with_name::<T>(type_name::<T>());
|
self.register_type_with_name::<T>(type_name::<T>());
|
||||||
}
|
}
|
||||||
@ -86,7 +81,7 @@ impl<'e> Engine<'e> {
|
|||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
/// #[derive(Clone)]
|
/// #[derive(Clone)]
|
||||||
/// struct TestStruct {
|
/// struct TestStruct {
|
||||||
/// field: i64
|
/// field: i64
|
||||||
/// }
|
/// }
|
||||||
///
|
///
|
||||||
@ -122,6 +117,7 @@ impl<'e> Engine<'e> {
|
|||||||
/// # Ok(())
|
/// # Ok(())
|
||||||
/// # }
|
/// # }
|
||||||
/// ```
|
/// ```
|
||||||
|
#[cfg(not(feature = "no_object"))]
|
||||||
pub fn register_type_with_name<T: Any + Clone>(&mut self, name: &str) {
|
pub fn register_type_with_name<T: Any + Clone>(&mut self, name: &str) {
|
||||||
// Add the pretty-print type name into the map
|
// Add the pretty-print type name into the map
|
||||||
self.type_names
|
self.type_names
|
||||||
@ -145,7 +141,7 @@ impl<'e> Engine<'e> {
|
|||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
/// #[derive(Clone)]
|
/// #[derive(Clone)]
|
||||||
/// struct TestStruct {
|
/// struct TestStruct {
|
||||||
/// field: i64
|
/// field: i64
|
||||||
/// }
|
/// }
|
||||||
///
|
///
|
||||||
@ -173,13 +169,13 @@ impl<'e> Engine<'e> {
|
|||||||
/// # Ok(())
|
/// # Ok(())
|
||||||
/// # }
|
/// # }
|
||||||
/// ```
|
/// ```
|
||||||
|
#[cfg(not(feature = "no_object"))]
|
||||||
pub fn register_get<T: Any + Clone, U: Any + Clone>(
|
pub fn register_get<T: Any + Clone, U: Any + Clone>(
|
||||||
&mut self,
|
&mut self,
|
||||||
name: &str,
|
name: &str,
|
||||||
callback: impl Fn(&mut T) -> U + 'static,
|
callback: impl Fn(&mut T) -> U + 'static,
|
||||||
) {
|
) {
|
||||||
let get_fn_name = format!("{}{}", FUNC_GETTER, name);
|
self.register_fn(&make_getter(name), callback);
|
||||||
self.register_fn(&get_fn_name, callback);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Register a setter function for a member of a registered type with the `Engine`.
|
/// Register a setter function for a member of a registered type with the `Engine`.
|
||||||
@ -187,7 +183,7 @@ impl<'e> Engine<'e> {
|
|||||||
/// # Example
|
/// # Example
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
/// #[derive(Clone)]
|
/// #[derive(Debug, Clone, Eq, PartialEq)]
|
||||||
/// struct TestStruct {
|
/// struct TestStruct {
|
||||||
/// field: i64
|
/// field: i64
|
||||||
/// }
|
/// }
|
||||||
@ -211,17 +207,20 @@ impl<'e> Engine<'e> {
|
|||||||
/// engine.register_set("xyz", TestStruct::set_field);
|
/// engine.register_set("xyz", TestStruct::set_field);
|
||||||
///
|
///
|
||||||
/// // Notice that, with a getter, there is no way to get the property value
|
/// // 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(())
|
/// # Ok(())
|
||||||
/// # }
|
/// # }
|
||||||
/// ```
|
/// ```
|
||||||
|
#[cfg(not(feature = "no_object"))]
|
||||||
pub fn register_set<T: Any + Clone, U: Any + Clone>(
|
pub fn register_set<T: Any + Clone, U: Any + Clone>(
|
||||||
&mut self,
|
&mut self,
|
||||||
name: &str,
|
name: &str,
|
||||||
callback: impl Fn(&mut T, U) -> () + 'static,
|
callback: impl Fn(&mut T, U) -> () + 'static,
|
||||||
) {
|
) {
|
||||||
let set_fn_name = format!("{}{}", FUNC_SETTER, name);
|
self.register_fn(&make_setter(name), callback);
|
||||||
self.register_fn(&set_fn_name, callback);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Shorthand for registering both getter and setter functions
|
/// Shorthand for registering both getter and setter functions
|
||||||
@ -262,6 +261,7 @@ impl<'e> Engine<'e> {
|
|||||||
/// # Ok(())
|
/// # Ok(())
|
||||||
/// # }
|
/// # }
|
||||||
/// ```
|
/// ```
|
||||||
|
#[cfg(not(feature = "no_object"))]
|
||||||
pub fn register_get_set<T: Any + Clone, U: Any + Clone>(
|
pub fn register_get_set<T: Any + Clone, U: Any + Clone>(
|
||||||
&mut self,
|
&mut self,
|
||||||
name: &str,
|
name: &str,
|
||||||
@ -346,8 +346,9 @@ impl<'e> Engine<'e> {
|
|||||||
let mut contents = String::new();
|
let mut contents = String::new();
|
||||||
|
|
||||||
f.read_to_string(&mut contents)
|
f.read_to_string(&mut contents)
|
||||||
.map_err(|err| EvalAltResult::ErrorReadingScriptFile(path.clone(), err))
|
.map_err(|err| EvalAltResult::ErrorReadingScriptFile(path.clone(), err))?;
|
||||||
.map(|_| contents)
|
|
||||||
|
Ok(contents)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Compile a script file into an `AST`, which can be used later for evaluation.
|
/// 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> {
|
pub fn eval<T: Any + Clone>(&mut self, input: &str) -> Result<T, EvalAltResult> {
|
||||||
let mut scope = Scope::new();
|
self.eval_with_scope(&mut Scope::new(), input)
|
||||||
self.eval_with_scope(&mut scope, input)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Evaluate a string with own scope.
|
/// 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> {
|
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::new(), input)
|
||||||
self.eval_expression_with_scope(&mut scope, input)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Evaluate a string containing an expression with own scope.
|
/// 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> {
|
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::new(), ast)
|
||||||
self.eval_ast_with_scope(&mut scope, ast)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Evaluate an `AST` with own scope.
|
/// Evaluate an `AST` with own scope.
|
||||||
@ -693,14 +691,14 @@ impl<'e> Engine<'e> {
|
|||||||
scope: &mut Scope,
|
scope: &mut Scope,
|
||||||
ast: &AST,
|
ast: &AST,
|
||||||
) -> Result<T, EvalAltResult> {
|
) -> Result<T, EvalAltResult> {
|
||||||
self.eval_ast_with_scope_raw(scope, false, ast)
|
self.eval_ast_with_scope_raw(scope, false, ast)?
|
||||||
.and_then(|out| {
|
.downcast::<T>()
|
||||||
out.downcast::<T>().map(|v| *v).map_err(|a| {
|
.map(|v| *v)
|
||||||
EvalAltResult::ErrorMismatchOutputType(
|
.map_err(|a| {
|
||||||
self.map_type_name((*a).type_name()).to_string(),
|
EvalAltResult::ErrorMismatchOutputType(
|
||||||
Position::none(),
|
self.map_type_name((*a).type_name()).to_string(),
|
||||||
)
|
Position::none(),
|
||||||
})
|
)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -710,34 +708,25 @@ impl<'e> Engine<'e> {
|
|||||||
retain_functions: bool,
|
retain_functions: bool,
|
||||||
ast: &AST,
|
ast: &AST,
|
||||||
) -> Result<Dynamic, EvalAltResult> {
|
) -> Result<Dynamic, EvalAltResult> {
|
||||||
fn eval_ast_internal(
|
if !retain_functions {
|
||||||
engine: &mut Engine,
|
self.clear_functions();
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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),
|
EvalAltResult::Return(out, _) => Ok(out),
|
||||||
_ => Err(err),
|
_ => Err(err),
|
||||||
})
|
})
|
||||||
@ -746,7 +735,9 @@ impl<'e> Engine<'e> {
|
|||||||
/// Evaluate a file, but throw away the result and only return error (if any).
|
/// Evaluate a file, but throw away the result and only return error (if any).
|
||||||
/// Useful for when you don't need the result, but still need to keep track of possible errors.
|
/// Useful for when you don't need the result, but still need to keep track of possible errors.
|
||||||
///
|
///
|
||||||
/// Note - if `retain_functions` is set to `true`, functions defined by previous scripts are _retained_ and not cleared from run to run.
|
/// # Note
|
||||||
|
///
|
||||||
|
/// If `retain_functions` is set to `true`, functions defined by previous scripts are _retained_ and not cleared from run to run.
|
||||||
#[cfg(not(feature = "no_std"))]
|
#[cfg(not(feature = "no_std"))]
|
||||||
pub fn consume_file(
|
pub fn consume_file(
|
||||||
&mut self,
|
&mut self,
|
||||||
@ -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).
|
/// Evaluate a file with own scope, but throw away the result and only return error (if any).
|
||||||
/// Useful for when you don't need the result, but still need to keep track of possible errors.
|
/// Useful for when you don't need the result, but still need to keep track of possible errors.
|
||||||
///
|
///
|
||||||
/// Note - if `retain_functions` is set to `true`, functions defined by previous scripts are _retained_ and not cleared from run to run.
|
/// # Note
|
||||||
|
///
|
||||||
|
/// If `retain_functions` is set to `true`, functions defined by previous scripts are _retained_ and not cleared from run to run.
|
||||||
#[cfg(not(feature = "no_std"))]
|
#[cfg(not(feature = "no_std"))]
|
||||||
pub fn consume_file_with_scope(
|
pub fn consume_file_with_scope(
|
||||||
&mut self,
|
&mut self,
|
||||||
@ -774,7 +767,9 @@ impl<'e> Engine<'e> {
|
|||||||
/// Evaluate a string, but throw away the result and only return error (if any).
|
/// Evaluate a string, but throw away the result and only return error (if any).
|
||||||
/// Useful for when you don't need the result, but still need to keep track of possible errors.
|
/// Useful for when you don't need the result, but still need to keep track of possible errors.
|
||||||
///
|
///
|
||||||
/// Note - if `retain_functions` is set to `true`, functions defined by previous scripts are _retained_and not cleared from run to run.
|
/// # Note
|
||||||
|
///
|
||||||
|
/// If `retain_functions` is set to `true`, functions defined by previous scripts are _retained_and not cleared from run to run.
|
||||||
pub fn consume(&mut self, retain_functions: bool, input: &str) -> Result<(), EvalAltResult> {
|
pub fn consume(&mut self, retain_functions: bool, input: &str) -> Result<(), EvalAltResult> {
|
||||||
self.consume_with_scope(&mut Scope::new(), retain_functions, input)
|
self.consume_with_scope(&mut Scope::new(), retain_functions, input)
|
||||||
}
|
}
|
||||||
@ -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).
|
/// Evaluate a string with own scope, but throw away the result and only return error (if any).
|
||||||
/// Useful for when you don't need the result, but still need to keep track of possible errors.
|
/// Useful for when you don't need the result, but still need to keep track of possible errors.
|
||||||
///
|
///
|
||||||
/// Note - if `retain_functions` is set to `true`, functions defined by previous scripts are _retained_and not cleared from run to run.
|
/// # Note
|
||||||
|
///
|
||||||
|
/// If `retain_functions` is set to `true`, functions defined by previous scripts are _retained_and not cleared from run to run.
|
||||||
pub fn consume_with_scope(
|
pub fn consume_with_scope(
|
||||||
&mut self,
|
&mut self,
|
||||||
scope: &mut Scope,
|
scope: &mut Scope,
|
||||||
@ -800,7 +797,9 @@ impl<'e> Engine<'e> {
|
|||||||
/// Evaluate an AST, but throw away the result and only return error (if any).
|
/// Evaluate an AST, but throw away the result and only return error (if any).
|
||||||
/// Useful for when you don't need the result, but still need to keep track of possible errors.
|
/// Useful for when you don't need the result, but still need to keep track of possible errors.
|
||||||
///
|
///
|
||||||
/// Note - if `retain_functions` is set to `true`, functions defined by previous scripts are _retained_and not cleared from run to run.
|
/// # Note
|
||||||
|
///
|
||||||
|
/// If `retain_functions` is set to `true`, functions defined by previous scripts are _retained_and not cleared from run to run.
|
||||||
pub fn consume_ast(&mut self, retain_functions: bool, ast: &AST) -> Result<(), EvalAltResult> {
|
pub fn consume_ast(&mut self, retain_functions: bool, ast: &AST) -> Result<(), EvalAltResult> {
|
||||||
self.consume_ast_with_scope(&mut Scope::new(), retain_functions, ast)
|
self.consume_ast_with_scope(&mut Scope::new(), retain_functions, ast)
|
||||||
}
|
}
|
||||||
@ -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).
|
/// Evaluate an `AST` with own scope, but throw away the result and only return error (if any).
|
||||||
/// Useful for when you don't need the result, but still need to keep track of possible errors.
|
/// Useful for when you don't need the result, but still need to keep track of possible errors.
|
||||||
///
|
///
|
||||||
/// Note - if `retain_functions` is set to `true`, functions defined by previous scripts are _retained_and not cleared from run to run.
|
/// # Note
|
||||||
|
///
|
||||||
|
/// If `retain_functions` is set to `true`, functions defined by previous scripts are _retained_and not cleared from run to run.
|
||||||
pub fn consume_ast_with_scope(
|
pub fn consume_ast_with_scope(
|
||||||
&mut self,
|
&mut self,
|
||||||
scope: &mut Scope,
|
scope: &mut Scope,
|
||||||
@ -827,14 +828,13 @@ impl<'e> Engine<'e> {
|
|||||||
|
|
||||||
let result = statements
|
let result = statements
|
||||||
.iter()
|
.iter()
|
||||||
.try_fold(().into_dynamic(), |_, stmt| self.eval_stmt(scope, stmt, 0))
|
.try_fold(().into_dynamic(), |_, stmt| self.eval_stmt(scope, stmt, 0));
|
||||||
.map(|_| ());
|
|
||||||
|
|
||||||
if !retain_functions {
|
if !retain_functions {
|
||||||
self.clear_functions();
|
self.clear_functions();
|
||||||
}
|
}
|
||||||
|
|
||||||
result.or_else(|err| match err {
|
result.map(|_| ()).or_else(|err| match err {
|
||||||
EvalAltResult::Return(_, _) => Ok(()),
|
EvalAltResult::Return(_, _) => Ok(()),
|
||||||
_ => Err(err),
|
_ => Err(err),
|
||||||
})
|
})
|
||||||
@ -845,9 +845,9 @@ impl<'e> Engine<'e> {
|
|||||||
&mut self,
|
&mut self,
|
||||||
functions: impl IntoIterator<Item = &'a Arc<FnDef>>,
|
functions: impl IntoIterator<Item = &'a Arc<FnDef>>,
|
||||||
) {
|
) {
|
||||||
for f in functions.into_iter() {
|
functions.into_iter().cloned().for_each(|f| {
|
||||||
self.fn_lib.add_or_replace_function(f.clone());
|
self.fn_lib.add_or_replace_function(f);
|
||||||
}
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Call a script function retained inside the Engine.
|
/// Call a script function retained inside the Engine.
|
||||||
@ -864,7 +864,7 @@ impl<'e> Engine<'e> {
|
|||||||
/// let mut engine = Engine::new();
|
/// let mut engine = Engine::new();
|
||||||
///
|
///
|
||||||
/// // Set 'retain_functions' in 'consume' to keep the function definitions
|
/// // 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
|
/// // Call the script-defined function
|
||||||
/// let result: i64 = engine.call_fn("add", (String::from("abc"), 123_i64))?;
|
/// 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 values = args.into_vec();
|
||||||
let mut arg_values: Vec<_> = values.iter_mut().map(Dynamic::as_mut).collect();
|
let mut arg_values: Vec<_> = values.iter_mut().map(Dynamic::as_mut).collect();
|
||||||
|
|
||||||
self.call_fn_raw(name, &mut arg_values, None, Position::none(), 0)
|
self.call_fn_raw(name, &mut arg_values, None, Position::none(), 0)?
|
||||||
.and_then(|b| {
|
.downcast()
|
||||||
b.downcast().map(|b| *b).map_err(|a| {
|
.map(|b| *b)
|
||||||
EvalAltResult::ErrorMismatchOutputType(
|
.map_err(|a| {
|
||||||
self.map_type_name((*a).type_name()).into(),
|
EvalAltResult::ErrorMismatchOutputType(
|
||||||
Position::none(),
|
self.map_type_name((*a).type_name()).into(),
|
||||||
)
|
Position::none(),
|
||||||
})
|
)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
//! _standard library_ of utility functions.
|
//! _standard library_ of utility functions.
|
||||||
|
|
||||||
use crate::any::Any;
|
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::fn_register::{RegisterDynamicFn, RegisterFn, RegisterResultFn};
|
||||||
use crate::parser::{Position, INT};
|
use crate::parser::{Position, INT};
|
||||||
use crate::result::EvalAltResult;
|
use crate::result::EvalAltResult;
|
||||||
@ -10,6 +10,9 @@ use crate::result::EvalAltResult;
|
|||||||
#[cfg(not(feature = "no_index"))]
|
#[cfg(not(feature = "no_index"))]
|
||||||
use crate::engine::Array;
|
use crate::engine::Array;
|
||||||
|
|
||||||
|
#[cfg(not(feature = "no_object"))]
|
||||||
|
use crate::engine::Map;
|
||||||
|
|
||||||
#[cfg(not(feature = "no_float"))]
|
#[cfg(not(feature = "no_float"))]
|
||||||
use crate::parser::FLOAT;
|
use crate::parser::FLOAT;
|
||||||
|
|
||||||
@ -558,10 +561,10 @@ impl Engine<'_> {
|
|||||||
self.register_fn("==", |_: (), _: ()| true); // () == ()
|
self.register_fn("==", |_: (), _: ()| true); // () == ()
|
||||||
|
|
||||||
// Register print and debug
|
// Register print and debug
|
||||||
fn debug<T: Debug>(x: T) -> String {
|
fn to_debug<T: Debug>(x: T) -> String {
|
||||||
format!("{:?}", x)
|
format!("{:?}", x)
|
||||||
}
|
}
|
||||||
fn print<T: Display>(x: T) -> String {
|
fn to_string<T: Display>(x: T) -> String {
|
||||||
format!("{}", x)
|
format!("{}", x)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -574,36 +577,58 @@ impl Engine<'_> {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
reg_fn1!(self, "print", print, String, INT, bool, char, String);
|
reg_fn1!(self, KEYWORD_PRINT, to_string, String, INT, bool);
|
||||||
self.register_fn("print", || "".to_string());
|
reg_fn1!(self, FUNC_TO_STRING, to_string, String, INT, bool);
|
||||||
self.register_fn("print", |_: ()| "".to_string());
|
reg_fn1!(self, KEYWORD_PRINT, to_string, String, char, String);
|
||||||
reg_fn1!(self, "debug", debug, String, INT, bool, 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_i32"))]
|
||||||
#[cfg(not(feature = "only_i64"))]
|
#[cfg(not(feature = "only_i64"))]
|
||||||
{
|
{
|
||||||
reg_fn1!(self, "print", print, String, i8, u8, i16, u16);
|
reg_fn1!(self, KEYWORD_PRINT, to_string, String, i8, u8, i16, u16);
|
||||||
reg_fn1!(self, "print", print, String, i32, i64, u32, u64);
|
reg_fn1!(self, FUNC_TO_STRING, to_string, String, i8, u8, i16, u16);
|
||||||
reg_fn1!(self, "debug", debug, String, i8, u8, i16, u16);
|
reg_fn1!(self, KEYWORD_PRINT, to_string, String, i32, i64, u32, u64);
|
||||||
reg_fn1!(self, "debug", debug, 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"))]
|
#[cfg(not(feature = "no_float"))]
|
||||||
{
|
{
|
||||||
reg_fn1!(self, "print", print, String, f32, f64);
|
reg_fn1!(self, KEYWORD_PRINT, to_string, String, f32, f64);
|
||||||
reg_fn1!(self, "debug", debug, 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"))]
|
#[cfg(not(feature = "no_index"))]
|
||||||
{
|
{
|
||||||
reg_fn1!(self, "print", debug, String, Array);
|
reg_fn1!(self, KEYWORD_PRINT, to_debug, String, Array);
|
||||||
reg_fn1!(self, "debug", 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
|
// Register array iterator
|
||||||
self.register_iterator::<Array, _>(|a| {
|
self.register_iterator::<Array, _>(|a| {
|
||||||
Box::new(a.downcast_ref::<Array>().unwrap().clone().into_iter())
|
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
|
// 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
|
// Register string concatenate functions
|
||||||
fn prepend<T: Display>(x: T, y: String) -> String {
|
fn prepend<T: Display>(x: T, y: String) -> String {
|
||||||
format!("{}{}", x, y)
|
format!("{}{}", x, y)
|
||||||
|
583
src/engine.rs
583
src/engine.rs
@ -26,33 +26,88 @@ use crate::stdlib::{
|
|||||||
#[cfg(not(feature = "no_index"))]
|
#[cfg(not(feature = "no_index"))]
|
||||||
pub type Array = Vec<Dynamic>;
|
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 FnCallArgs<'a> = [&'a mut Variant];
|
||||||
|
|
||||||
pub type FnAny = dyn Fn(&mut FnCallArgs, Position) -> Result<Dynamic, EvalAltResult>;
|
pub type FnAny = dyn Fn(&mut FnCallArgs, Position) -> Result<Dynamic, EvalAltResult>;
|
||||||
|
|
||||||
type IteratorFn = dyn Fn(&Dynamic) -> Box<dyn Iterator<Item = Dynamic>>;
|
type IteratorFn = dyn Fn(&Dynamic) -> Box<dyn Iterator<Item = Dynamic>>;
|
||||||
|
|
||||||
pub(crate) const MAX_CALL_STACK_DEPTH: usize = 64;
|
pub const MAX_CALL_STACK_DEPTH: usize = 64;
|
||||||
pub(crate) const KEYWORD_PRINT: &str = "print";
|
pub const KEYWORD_PRINT: &str = "print";
|
||||||
pub(crate) const KEYWORD_DEBUG: &str = "debug";
|
pub const KEYWORD_DEBUG: &str = "debug";
|
||||||
pub(crate) const KEYWORD_DUMP_AST: &str = "dump_ast";
|
pub const KEYWORD_DUMP_AST: &str = "dump_ast";
|
||||||
pub(crate) const KEYWORD_TYPE_OF: &str = "type_of";
|
pub const KEYWORD_TYPE_OF: &str = "type_of";
|
||||||
pub(crate) const KEYWORD_EVAL: &str = "eval";
|
pub const KEYWORD_EVAL: &str = "eval";
|
||||||
pub(crate) const FUNC_GETTER: &str = "get$";
|
pub const FUNC_TO_STRING: &str = "to_string";
|
||||||
pub(crate) const FUNC_SETTER: &str = "set$";
|
pub const FUNC_GETTER: &str = "get$";
|
||||||
|
pub const FUNC_SETTER: &str = "set$";
|
||||||
|
|
||||||
#[derive(Debug, Eq, PartialEq, Hash, Clone, Copy)]
|
#[derive(Debug, Eq, PartialEq, Hash, Clone, Copy)]
|
||||||
#[cfg(not(feature = "no_index"))]
|
#[cfg(not(feature = "no_index"))]
|
||||||
enum IndexSourceType {
|
enum IndexSourceType {
|
||||||
Array,
|
|
||||||
String,
|
|
||||||
Expression,
|
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)]
|
#[derive(Debug, Eq, PartialEq, Hash, Clone)]
|
||||||
pub struct FnSpec<'a> {
|
pub struct FnSpec<'a> {
|
||||||
pub name: Cow<'a, str>,
|
pub name: Cow<'a, str>,
|
||||||
pub args: Option<Vec<TypeId>>,
|
pub args: Vec<TypeId>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A type that holds a library of script-defined functions.
|
/// 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.
|
/// A hashmap mapping type names to pretty-print names.
|
||||||
pub(crate) type_names: HashMap<String, String>,
|
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>,
|
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>,
|
pub(crate) on_debug: Box<dyn FnMut(&str) + 'e>,
|
||||||
|
|
||||||
/// Optimize the AST after compilation.
|
/// Optimize the AST after compilation.
|
||||||
@ -156,6 +211,8 @@ impl Default for Engine<'_> {
|
|||||||
let type_names = [
|
let type_names = [
|
||||||
#[cfg(not(feature = "no_index"))]
|
#[cfg(not(feature = "no_index"))]
|
||||||
(type_name::<Array>(), "array"),
|
(type_name::<Array>(), "array"),
|
||||||
|
#[cfg(not(feature = "no_object"))]
|
||||||
|
(type_name::<Map>(), "map"),
|
||||||
(type_name::<String>(), "string"),
|
(type_name::<String>(), "string"),
|
||||||
(type_name::<Dynamic>(), "dynamic"),
|
(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<'_> {
|
impl Engine<'_> {
|
||||||
/// Create a new `Engine`
|
/// Create a new `Engine`
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
@ -220,7 +305,7 @@ impl Engine<'_> {
|
|||||||
) -> Result<Option<Dynamic>, EvalAltResult> {
|
) -> Result<Option<Dynamic>, EvalAltResult> {
|
||||||
let spec = FnSpec {
|
let spec = FnSpec {
|
||||||
name: fn_name.into(),
|
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
|
// Search built-in's and external functions
|
||||||
@ -232,8 +317,7 @@ impl Engine<'_> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Universal method for calling functions, that are either
|
/// Universal method for calling functions either registered with the `Engine` or written in Rhai
|
||||||
/// registered with the `Engine` or written in Rhai
|
|
||||||
pub(crate) fn call_fn_raw(
|
pub(crate) fn call_fn_raw(
|
||||||
&mut self,
|
&mut self,
|
||||||
fn_name: &str,
|
fn_name: &str,
|
||||||
@ -267,7 +351,7 @@ impl Engine<'_> {
|
|||||||
|
|
||||||
let spec = FnSpec {
|
let spec = FnSpec {
|
||||||
name: fn_name.into(),
|
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
|
// Argument must be a string
|
||||||
@ -285,35 +369,46 @@ impl Engine<'_> {
|
|||||||
// See if the function match print/debug (which requires special processing)
|
// See if the function match print/debug (which requires special processing)
|
||||||
return Ok(match fn_name {
|
return Ok(match fn_name {
|
||||||
KEYWORD_PRINT => {
|
KEYWORD_PRINT => {
|
||||||
self.on_print.as_mut()(cast_to_string(result.as_ref(), pos)?);
|
self.on_print.as_mut()(cast_to_string(result.as_ref(), pos)?).into_dynamic()
|
||||||
().into_dynamic()
|
|
||||||
}
|
}
|
||||||
KEYWORD_DEBUG => {
|
KEYWORD_DEBUG => {
|
||||||
self.on_debug.as_mut()(cast_to_string(result.as_ref(), pos)?);
|
self.on_debug.as_mut()(cast_to_string(result.as_ref(), pos)?).into_dynamic()
|
||||||
().into_dynamic()
|
|
||||||
}
|
}
|
||||||
_ => result,
|
_ => result,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
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
|
// Getter function not found
|
||||||
return Err(EvalAltResult::ErrorDotExpr(
|
return Err(EvalAltResult::ErrorDotExpr(
|
||||||
format!(
|
format!("- property '{}' unknown or write-only", prop),
|
||||||
"- property '{}' unknown or write-only",
|
|
||||||
&fn_name[FUNC_GETTER.len()..]
|
|
||||||
),
|
|
||||||
pos,
|
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
|
// Setter function not found
|
||||||
return Err(EvalAltResult::ErrorDotExpr(
|
return Err(EvalAltResult::ErrorDotExpr(
|
||||||
format!(
|
format!("- property '{}' unknown or read-only", prop),
|
||||||
"- property '{}' unknown or read-only",
|
|
||||||
&fn_name[FUNC_SETTER.len()..]
|
|
||||||
),
|
|
||||||
pos,
|
pos,
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
@ -337,39 +432,14 @@ impl Engine<'_> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Chain-evaluate a dot setter.
|
/// Chain-evaluate a dot setter.
|
||||||
///
|
#[cfg(not(feature = "no_object"))]
|
||||||
/// Either `src` or `target` should be `Some`.
|
|
||||||
///
|
|
||||||
/// If `target` is `Some`, then it is taken as the reference to use for `this`.
|
|
||||||
///
|
|
||||||
/// Otherwise, if `src` is `Some`, then it holds a name and index into `scope`; using `get_mut` on
|
|
||||||
/// `scope` can retrieve a mutable reference to the variable's value to use as `this`.
|
|
||||||
fn get_dot_val_helper(
|
fn get_dot_val_helper(
|
||||||
&mut self,
|
&mut self,
|
||||||
scope: &mut Scope,
|
scope: &mut Scope,
|
||||||
src: Option<ScopeSource>,
|
target: Target,
|
||||||
target: Option<&mut Variant>,
|
|
||||||
dot_rhs: &Expr,
|
dot_rhs: &Expr,
|
||||||
level: usize,
|
level: usize,
|
||||||
) -> Result<Dynamic, EvalAltResult> {
|
) -> Result<Dynamic, EvalAltResult> {
|
||||||
// Get the `this` reference. Either `src` or `target` should be `Some`.
|
|
||||||
fn get_this_ptr<'a>(
|
|
||||||
scope: &'a mut Scope,
|
|
||||||
src: Option<ScopeSource>,
|
|
||||||
target: Option<&'a mut Variant>,
|
|
||||||
) -> &'a mut Variant {
|
|
||||||
if let Some(t) = target {
|
|
||||||
// If `target` is `Some`, then it is returned.
|
|
||||||
t
|
|
||||||
} else {
|
|
||||||
// Otherwise, if `src` is `Some`, then it holds a name and index into `scope`;
|
|
||||||
// using `get_mut` on `scope` to retrieve a mutable reference for return.
|
|
||||||
scope
|
|
||||||
.get_mut(src.expect("expected source in scope"))
|
|
||||||
.as_mut()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
match dot_rhs {
|
match dot_rhs {
|
||||||
// xxx.fn_name(args)
|
// xxx.fn_name(args)
|
||||||
Expr::FunctionCall(fn_name, arg_expr_list, def_val, pos) => {
|
Expr::FunctionCall(fn_name, arg_expr_list, def_val, pos) => {
|
||||||
@ -378,7 +448,7 @@ impl Engine<'_> {
|
|||||||
.map(|arg_expr| self.eval_expr(scope, arg_expr, level))
|
.map(|arg_expr| self.eval_expr(scope, arg_expr, level))
|
||||||
.collect::<Result<Vec<_>, _>>()?;
|
.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)
|
let mut arg_values: Vec<_> = once(this_ptr)
|
||||||
.chain(values.iter_mut().map(Dynamic::as_mut))
|
.chain(values.iter_mut().map(Dynamic::as_mut))
|
||||||
@ -389,29 +459,23 @@ impl Engine<'_> {
|
|||||||
|
|
||||||
// xxx.id
|
// xxx.id
|
||||||
Expr::Property(id, pos) => {
|
Expr::Property(id, pos) => {
|
||||||
let get_fn_name = format!("{}{}", FUNC_GETTER, id);
|
let this_ptr = target.get_mut(scope);
|
||||||
let this_ptr = get_this_ptr(scope, src, target);
|
self.call_fn_raw(&make_getter(id), &mut [this_ptr], None, *pos, 0)
|
||||||
self.call_fn_raw(&get_fn_name, &mut [this_ptr], None, *pos, 0)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// xxx.idx_lhs[idx_expr]
|
// xxx.idx_lhs[idx_expr]
|
||||||
#[cfg(not(feature = "no_index"))]
|
#[cfg(not(feature = "no_index"))]
|
||||||
Expr::Index(idx_lhs, idx_expr, op_pos) => {
|
Expr::Index(idx_lhs, idx_expr, op_pos) => {
|
||||||
let (val, _) = match idx_lhs.as_ref() {
|
let value = match idx_lhs.as_ref() {
|
||||||
// xxx.id[idx_expr]
|
// xxx.id[idx_expr]
|
||||||
Expr::Property(id, pos) => {
|
Expr::Property(id, pos) => {
|
||||||
let get_fn_name = format!("{}{}", FUNC_GETTER, id);
|
let this_ptr = target.get_mut(scope);
|
||||||
let this_ptr = get_this_ptr(scope, src, target);
|
self.call_fn_raw(&make_getter(id), &mut [this_ptr], None, *pos, 0)?
|
||||||
(
|
|
||||||
self.call_fn_raw(&get_fn_name, &mut [this_ptr], None, *pos, 0)?,
|
|
||||||
*pos,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
// xxx.???[???][idx_expr]
|
// xxx.???[???][idx_expr]
|
||||||
Expr::Index(_, _, _) => (
|
Expr::Index(_, _, _) => {
|
||||||
self.get_dot_val_helper(scope, src, target, idx_lhs, level)?,
|
self.get_dot_val_helper(scope, target, idx_lhs, level)?
|
||||||
*op_pos,
|
}
|
||||||
),
|
|
||||||
// Syntax error
|
// Syntax error
|
||||||
_ => {
|
_ => {
|
||||||
return Err(EvalAltResult::ErrorDotExpr(
|
return Err(EvalAltResult::ErrorDotExpr(
|
||||||
@ -421,41 +485,33 @@ impl Engine<'_> {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let idx = self.eval_index_value(scope, idx_expr, level)?;
|
self.get_indexed_value(scope, &value, idx_expr, *op_pos, level)
|
||||||
self.get_indexed_value(&val, idx, idx_expr.position(), *op_pos)
|
.map(|(val, _, _)| val)
|
||||||
.map(|(v, _)| v)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// xxx.dot_lhs.rhs
|
// xxx.dot_lhs.rhs
|
||||||
Expr::Dot(dot_lhs, rhs, _) => match dot_lhs.as_ref() {
|
Expr::Dot(dot_lhs, rhs, _) => match dot_lhs.as_ref() {
|
||||||
// xxx.id.rhs
|
// xxx.id.rhs
|
||||||
Expr::Property(id, pos) => {
|
Expr::Property(id, pos) => {
|
||||||
let get_fn_name = format!("{}{}", FUNC_GETTER, id);
|
let this_ptr = target.get_mut(scope);
|
||||||
let this_ptr = get_this_ptr(scope, src, target);
|
self.call_fn_raw(&make_getter(id), &mut [this_ptr], None, *pos, 0)
|
||||||
|
.and_then(|mut val| {
|
||||||
self.call_fn_raw(&get_fn_name, &mut [this_ptr], None, *pos, 0)
|
self.get_dot_val_helper(scope, Target::from(val.as_mut()), rhs, level)
|
||||||
.and_then(|mut v| {
|
|
||||||
self.get_dot_val_helper(scope, None, Some(v.as_mut()), rhs, level)
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
// xxx.idx_lhs[idx_expr].rhs
|
// xxx.idx_lhs[idx_expr].rhs
|
||||||
#[cfg(not(feature = "no_index"))]
|
#[cfg(not(feature = "no_index"))]
|
||||||
Expr::Index(idx_lhs, idx_expr, op_pos) => {
|
Expr::Index(idx_lhs, idx_expr, op_pos) => {
|
||||||
let (val, _) = match idx_lhs.as_ref() {
|
let val = match idx_lhs.as_ref() {
|
||||||
// xxx.id[idx_expr].rhs
|
// xxx.id[idx_expr].rhs
|
||||||
Expr::Property(id, pos) => {
|
Expr::Property(id, pos) => {
|
||||||
let get_fn_name = format!("{}{}", FUNC_GETTER, id);
|
let this_ptr = target.get_mut(scope);
|
||||||
let this_ptr = get_this_ptr(scope, src, target);
|
self.call_fn_raw(&make_getter(id), &mut [this_ptr], None, *pos, 0)?
|
||||||
(
|
|
||||||
self.call_fn_raw(&get_fn_name, &mut [this_ptr], None, *pos, 0)?,
|
|
||||||
*pos,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
// xxx.???[???][idx_expr].rhs
|
// xxx.???[???][idx_expr].rhs
|
||||||
Expr::Index(_, _, _) => (
|
Expr::Index(_, _, _) => {
|
||||||
self.get_dot_val_helper(scope, src, target, idx_lhs, level)?,
|
self.get_dot_val_helper(scope, target, idx_lhs, level)?
|
||||||
*op_pos,
|
}
|
||||||
),
|
|
||||||
// Syntax error
|
// Syntax error
|
||||||
_ => {
|
_ => {
|
||||||
return Err(EvalAltResult::ErrorDotExpr(
|
return Err(EvalAltResult::ErrorDotExpr(
|
||||||
@ -465,10 +521,9 @@ impl Engine<'_> {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let idx = self.eval_index_value(scope, idx_expr, level)?;
|
self.get_indexed_value(scope, &val, idx_expr, *op_pos, level)
|
||||||
self.get_indexed_value(&val, idx, idx_expr.position(), *op_pos)
|
.and_then(|(mut val, _, _)| {
|
||||||
.and_then(|(mut v, _)| {
|
self.get_dot_val_helper(scope, Target::from(val.as_mut()), rhs, level)
|
||||||
self.get_dot_val_helper(scope, None, Some(v.as_mut()), rhs, level)
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
// Syntax error
|
// Syntax error
|
||||||
@ -487,6 +542,7 @@ impl Engine<'_> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Evaluate a dot chain getter
|
/// Evaluate a dot chain getter
|
||||||
|
#[cfg(not(feature = "no_object"))]
|
||||||
fn get_dot_val(
|
fn get_dot_val(
|
||||||
&mut self,
|
&mut self,
|
||||||
scope: &mut Scope,
|
scope: &mut Scope,
|
||||||
@ -497,23 +553,23 @@ impl Engine<'_> {
|
|||||||
match dot_lhs {
|
match dot_lhs {
|
||||||
// id.???
|
// id.???
|
||||||
Expr::Variable(id, pos) => {
|
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
|
// Avoid referencing scope which is used below as mut
|
||||||
let entry = ScopeSource { name: id, ..entry };
|
let entry = ScopeSource { name: id, ..entry };
|
||||||
|
|
||||||
// This is a variable property access (potential function call).
|
// This is a variable property access (potential function call).
|
||||||
// Use a direct index into `scope` to directly mutate the variable value.
|
// 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].???
|
// idx_lhs[idx_expr].???
|
||||||
#[cfg(not(feature = "no_index"))]
|
#[cfg(not(feature = "no_index"))]
|
||||||
Expr::Index(idx_lhs, idx_expr, op_pos) => {
|
Expr::Index(idx_lhs, idx_expr, op_pos) => {
|
||||||
let (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)?;
|
self.eval_index_expr(scope, idx_lhs, idx_expr, *op_pos, level)?;
|
||||||
let this_ptr = target.as_mut();
|
let value =
|
||||||
let val = self.get_dot_val_helper(scope, None, Some(this_ptr), dot_rhs, level);
|
self.get_dot_val_helper(scope, Target::from(val.as_mut()), dot_rhs, level);
|
||||||
|
|
||||||
// In case the expression mutated `target`, we need to update it back into the scope because it is cloned.
|
// In case the expression mutated `target`, we need to update it back into the scope because it is cloned.
|
||||||
if let Some(src) = src {
|
if let Some(src) = src {
|
||||||
@ -526,84 +582,101 @@ impl Engine<'_> {
|
|||||||
}
|
}
|
||||||
ScopeEntryType::Normal => {
|
ScopeEntryType::Normal => {
|
||||||
Self::update_indexed_var_in_scope(
|
Self::update_indexed_var_in_scope(
|
||||||
src_type,
|
idx_src_type,
|
||||||
scope,
|
scope,
|
||||||
src,
|
src,
|
||||||
idx,
|
idx,
|
||||||
(target, dot_rhs.position()),
|
(val, dot_rhs.position()),
|
||||||
)?;
|
)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val
|
value
|
||||||
}
|
}
|
||||||
|
|
||||||
// {expr}.???
|
// {expr}.???
|
||||||
expr => {
|
expr => {
|
||||||
let mut target = self.eval_expr(scope, expr, level)?;
|
let mut val = self.eval_expr(scope, expr, level)?;
|
||||||
let this_ptr = target.as_mut();
|
self.get_dot_val_helper(scope, Target::from(val.as_mut()), dot_rhs, level)
|
||||||
self.get_dot_val_helper(scope, None, Some(this_ptr), dot_rhs, level)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Search for a variable within the scope, returning its value and index inside the Scope
|
/// Search for a variable within the scope, returning its value and index inside the Scope
|
||||||
fn search_scope<'a, T>(
|
fn search_scope<'a>(
|
||||||
scope: &'a Scope,
|
scope: &'a Scope,
|
||||||
id: &str,
|
id: &str,
|
||||||
convert: impl FnOnce(Dynamic) -> Result<T, EvalAltResult>,
|
|
||||||
begin: Position,
|
begin: Position,
|
||||||
) -> Result<(ScopeSource<'a>, T), EvalAltResult> {
|
) -> Result<(ScopeSource<'a>, Dynamic), EvalAltResult> {
|
||||||
scope
|
scope
|
||||||
.get(id)
|
.get(id)
|
||||||
.ok_or_else(|| EvalAltResult::ErrorVariableNotFound(id.into(), begin))
|
.ok_or_else(|| EvalAltResult::ErrorVariableNotFound(id.into(), begin))
|
||||||
.and_then(move |(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
|
/// Get the value at the indexed position of a base type
|
||||||
#[cfg(not(feature = "no_index"))]
|
#[cfg(not(feature = "no_index"))]
|
||||||
fn get_indexed_value(
|
fn get_indexed_value(
|
||||||
&self,
|
&mut self,
|
||||||
|
scope: &mut Scope,
|
||||||
val: &Dynamic,
|
val: &Dynamic,
|
||||||
idx: INT,
|
idx_expr: &Expr,
|
||||||
idx_pos: Position,
|
|
||||||
op_pos: Position,
|
op_pos: Position,
|
||||||
) -> Result<(Dynamic, IndexSourceType), EvalAltResult> {
|
level: usize,
|
||||||
if val.is::<Array>() {
|
) -> Result<(Dynamic, IndexSourceType, IndexValue), EvalAltResult> {
|
||||||
// val_array[idx]
|
let idx_pos = idx_expr.position();
|
||||||
let arr = val.downcast_ref::<Array>().expect("array expected");
|
|
||||||
|
|
||||||
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)
|
arr.get(idx as usize)
|
||||||
.cloned()
|
.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))
|
.ok_or_else(|| EvalAltResult::ErrorArrayBounds(arr.len(), idx, idx_pos))
|
||||||
} else {
|
} else {
|
||||||
Err(EvalAltResult::ErrorArrayBounds(arr.len(), idx, idx_pos))
|
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()
|
s.chars()
|
||||||
.nth(idx as usize)
|
.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(|| {
|
.ok_or_else(|| {
|
||||||
EvalAltResult::ErrorStringBounds(s.chars().count(), idx, idx_pos)
|
EvalAltResult::ErrorStringBounds(s.chars().count(), idx, idx_pos)
|
||||||
})
|
})
|
||||||
@ -613,14 +686,14 @@ impl Engine<'_> {
|
|||||||
idx,
|
idx,
|
||||||
idx_pos,
|
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
|
/// Evaluate an index expression
|
||||||
@ -632,36 +705,48 @@ impl Engine<'_> {
|
|||||||
idx_expr: &Expr,
|
idx_expr: &Expr,
|
||||||
op_pos: Position,
|
op_pos: Position,
|
||||||
level: usize,
|
level: usize,
|
||||||
) -> Result<(IndexSourceType, Option<ScopeSource<'a>>, usize, Dynamic), EvalAltResult> {
|
) -> Result<
|
||||||
let idx = self.eval_index_value(scope, idx_expr, level)?;
|
(
|
||||||
|
IndexSourceType,
|
||||||
|
Option<ScopeSource<'a>>,
|
||||||
|
IndexValue,
|
||||||
|
Dynamic,
|
||||||
|
),
|
||||||
|
EvalAltResult,
|
||||||
|
> {
|
||||||
match lhs {
|
match lhs {
|
||||||
// id[idx_expr]
|
// id[idx_expr]
|
||||||
Expr::Variable(id, _) => Self::search_scope(
|
Expr::Variable(id, _) => {
|
||||||
scope,
|
let (
|
||||||
&id,
|
ScopeSource {
|
||||||
|val| self.get_indexed_value(&val, idx, idx_expr.position(), op_pos),
|
typ: src_type,
|
||||||
lhs.position(),
|
index: src_idx,
|
||||||
)
|
..
|
||||||
.map(|(src, (val, src_type))| {
|
},
|
||||||
(
|
val,
|
||||||
src_type,
|
) = 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 {
|
Some(ScopeSource {
|
||||||
name: &id,
|
name: &id,
|
||||||
typ: src.typ,
|
typ: src_type,
|
||||||
index: src.index,
|
index: src_idx,
|
||||||
}),
|
}),
|
||||||
idx as usize,
|
idx,
|
||||||
val,
|
val,
|
||||||
)
|
))
|
||||||
}),
|
}
|
||||||
|
|
||||||
// (expr)[idx_expr]
|
// (expr)[idx_expr]
|
||||||
expr => {
|
expr => {
|
||||||
let val = self.eval_expr(scope, expr, level)?;
|
let val = self.eval_expr(scope, expr, level)?;
|
||||||
|
|
||||||
self.get_indexed_value(&val, idx, idx_expr.position(), op_pos)
|
self.get_indexed_value(scope, &val, idx_expr, op_pos, level)
|
||||||
.map(|(v, _)| (IndexSourceType::Expression, None, idx as usize, v))
|
.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
|
/// Update the value at an index position in a variable inside the scope
|
||||||
#[cfg(not(feature = "no_index"))]
|
#[cfg(not(feature = "no_index"))]
|
||||||
fn update_indexed_var_in_scope(
|
fn update_indexed_var_in_scope(
|
||||||
src_type: IndexSourceType,
|
idx_src_type: IndexSourceType,
|
||||||
scope: &mut Scope,
|
scope: &mut Scope,
|
||||||
src: ScopeSource,
|
src: ScopeSource,
|
||||||
idx: usize,
|
idx: IndexValue,
|
||||||
new_val: (Dynamic, Position),
|
new_val: (Dynamic, Position),
|
||||||
) -> Result<Dynamic, EvalAltResult> {
|
) -> Result<Dynamic, EvalAltResult> {
|
||||||
match src_type {
|
match idx_src_type {
|
||||||
// array_id[idx] = val
|
// array_id[idx] = val
|
||||||
IndexSourceType::Array => {
|
IndexSourceType::Array => {
|
||||||
let arr = scope.get_mut_by_type::<Array>(src);
|
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())
|
Ok(().into_dynamic())
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -706,7 +799,7 @@ impl Engine<'_> {
|
|||||||
.0
|
.0
|
||||||
.downcast::<char>()
|
.downcast::<char>()
|
||||||
.map_err(|_| EvalAltResult::ErrorCharMismatch(pos))?;
|
.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())
|
Ok(().into_dynamic())
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -718,29 +811,38 @@ impl Engine<'_> {
|
|||||||
#[cfg(not(feature = "no_index"))]
|
#[cfg(not(feature = "no_index"))]
|
||||||
fn update_indexed_value(
|
fn update_indexed_value(
|
||||||
mut target: Dynamic,
|
mut target: Dynamic,
|
||||||
idx: usize,
|
idx: IndexValue,
|
||||||
new_val: Dynamic,
|
new_val: Dynamic,
|
||||||
pos: Position,
|
pos: Position,
|
||||||
) -> Result<Dynamic, EvalAltResult> {
|
) -> Result<Dynamic, EvalAltResult> {
|
||||||
if target.is::<Array>() {
|
if let Some(arr) = target.downcast_mut::<Array>() {
|
||||||
let arr = target.downcast_mut::<Array>().expect("array expected");
|
arr[idx.as_num()] = new_val;
|
||||||
arr[idx as usize] = new_val;
|
return Ok(target);
|
||||||
} else if target.is::<String>() {
|
}
|
||||||
let s = target.downcast_mut::<String>().expect("string expected");
|
|
||||||
|
#[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
|
// Value must be a character
|
||||||
let ch = *new_val
|
let ch = *new_val
|
||||||
.downcast::<char>()
|
.downcast::<char>()
|
||||||
.map_err(|_| EvalAltResult::ErrorCharMismatch(pos))?;
|
.map_err(|_| EvalAltResult::ErrorCharMismatch(pos))?;
|
||||||
Self::str_replace_char(s, idx as usize, ch);
|
Self::str_replace_char(s, idx.as_num(), ch);
|
||||||
} else {
|
return Ok(target);
|
||||||
// All other variable types should be an error
|
|
||||||
panic!("array or string source type expected for indexing")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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
|
/// Chain-evaluate a dot setter
|
||||||
|
#[cfg(not(feature = "no_object"))]
|
||||||
fn set_dot_val_helper(
|
fn set_dot_val_helper(
|
||||||
&mut self,
|
&mut self,
|
||||||
scope: &mut Scope,
|
scope: &mut Scope,
|
||||||
@ -752,9 +854,8 @@ impl Engine<'_> {
|
|||||||
match dot_rhs {
|
match dot_rhs {
|
||||||
// xxx.id
|
// xxx.id
|
||||||
Expr::Property(id, pos) => {
|
Expr::Property(id, pos) => {
|
||||||
let set_fn_name = format!("{}{}", FUNC_SETTER, id);
|
|
||||||
let mut args = [this_ptr, new_val.0.as_mut()];
|
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]
|
// xxx.lhs[idx_expr]
|
||||||
@ -762,25 +863,18 @@ impl Engine<'_> {
|
|||||||
#[cfg(not(feature = "no_index"))]
|
#[cfg(not(feature = "no_index"))]
|
||||||
Expr::Index(lhs, idx_expr, op_pos) => match lhs.as_ref() {
|
Expr::Index(lhs, idx_expr, op_pos) => match lhs.as_ref() {
|
||||||
// xxx.id[idx_expr]
|
// xxx.id[idx_expr]
|
||||||
Expr::Property(id, pos) => {
|
Expr::Property(id, pos) => self
|
||||||
let get_fn_name = format!("{}{}", FUNC_GETTER, id);
|
.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)
|
Self::update_indexed_value(val, idx, new_val.0.clone(), new_val.1)
|
||||||
.and_then(|v| {
|
})
|
||||||
let idx = self.eval_index_value(scope, idx_expr, level)?;
|
.and_then(|mut val| {
|
||||||
Self::update_indexed_value(
|
let mut args = [this_ptr, val.as_mut()];
|
||||||
v,
|
self.call_fn_raw(&make_setter(id), &mut args, None, *pos, 0)
|
||||||
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)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// All others - syntax error for setters chain
|
// All others - syntax error for setters chain
|
||||||
_ => Err(EvalAltResult::ErrorDotExpr(
|
_ => Err(EvalAltResult::ErrorDotExpr(
|
||||||
@ -793,17 +887,14 @@ impl Engine<'_> {
|
|||||||
Expr::Dot(lhs, rhs, _) => match lhs.as_ref() {
|
Expr::Dot(lhs, rhs, _) => match lhs.as_ref() {
|
||||||
// xxx.id.rhs
|
// xxx.id.rhs
|
||||||
Expr::Property(id, pos) => {
|
Expr::Property(id, pos) => {
|
||||||
let get_fn_name = format!("{}{}", FUNC_GETTER, id);
|
self.call_fn_raw(&make_getter(id), &mut [this_ptr], None, *pos, 0)
|
||||||
|
.and_then(|mut val| {
|
||||||
self.call_fn_raw(&get_fn_name, &mut [this_ptr], None, *pos, 0)
|
self.set_dot_val_helper(scope, val.as_mut(), rhs, new_val, level)
|
||||||
.and_then(|mut v| {
|
.map(|_| val) // Discard Ok return value
|
||||||
self.set_dot_val_helper(scope, v.as_mut(), rhs, new_val, level)
|
|
||||||
.map(|_| v) // Discard Ok return value
|
|
||||||
})
|
})
|
||||||
.and_then(|mut v| {
|
.and_then(|mut val| {
|
||||||
let set_fn_name = format!("{}{}", FUNC_SETTER, id);
|
let mut args = [this_ptr, val.as_mut()];
|
||||||
let mut args = [this_ptr, v.as_mut()];
|
self.call_fn_raw(&make_setter(id), &mut args, None, *pos, 0)
|
||||||
self.call_fn_raw(&set_fn_name, &mut args, None, *pos, 0)
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -813,25 +904,26 @@ impl Engine<'_> {
|
|||||||
Expr::Index(lhs, idx_expr, op_pos) => match lhs.as_ref() {
|
Expr::Index(lhs, idx_expr, op_pos) => match lhs.as_ref() {
|
||||||
// xxx.id[idx_expr].rhs
|
// xxx.id[idx_expr].rhs
|
||||||
Expr::Property(id, pos) => {
|
Expr::Property(id, pos) => {
|
||||||
let get_fn_name = format!("{}{}", FUNC_GETTER, id);
|
self.call_fn_raw(&make_getter(id), &mut [this_ptr], None, *pos, 0)
|
||||||
|
|
||||||
self.call_fn_raw(&get_fn_name, &mut [this_ptr], None, *pos, 0)
|
|
||||||
.and_then(|v| {
|
.and_then(|v| {
|
||||||
let idx = self.eval_index_value(scope, idx_expr, level)?;
|
let (mut value, _, idx) =
|
||||||
let (mut target, _) =
|
self.get_indexed_value(scope, &v, idx_expr, *op_pos, level)?;
|
||||||
self.get_indexed_value(&v, idx, idx_expr.position(), *op_pos)?;
|
|
||||||
|
|
||||||
let val_pos = new_val.1;
|
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)?;
|
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.
|
// 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| {
|
.and_then(|mut v| {
|
||||||
let set_fn_name = format!("{}{}", FUNC_SETTER, id);
|
self.call_fn_raw(
|
||||||
let mut args = [this_ptr, v.as_mut()];
|
&make_setter(id),
|
||||||
self.call_fn_raw(&set_fn_name, &mut args, None, *pos, 0)
|
&mut [this_ptr, v.as_mut()],
|
||||||
|
None,
|
||||||
|
*pos,
|
||||||
|
0,
|
||||||
|
)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -858,6 +950,7 @@ impl Engine<'_> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Evaluate a dot chain setter
|
// Evaluate a dot chain setter
|
||||||
|
#[cfg(not(feature = "no_object"))]
|
||||||
fn set_dot_val(
|
fn set_dot_val(
|
||||||
&mut self,
|
&mut self,
|
||||||
scope: &mut Scope,
|
scope: &mut Scope,
|
||||||
@ -870,7 +963,7 @@ impl Engine<'_> {
|
|||||||
match dot_lhs {
|
match dot_lhs {
|
||||||
// id.???
|
// id.???
|
||||||
Expr::Variable(id, pos) => {
|
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 {
|
match entry.typ {
|
||||||
ScopeEntryType::Constant => Err(EvalAltResult::ErrorAssignmentToConstant(
|
ScopeEntryType::Constant => Err(EvalAltResult::ErrorAssignmentToConstant(
|
||||||
@ -881,12 +974,13 @@ impl Engine<'_> {
|
|||||||
// Avoid referencing scope which is used below as mut
|
// Avoid referencing scope which is used below as mut
|
||||||
let entry = ScopeSource { name: id, ..entry };
|
let entry = ScopeSource { name: id, ..entry };
|
||||||
let this_ptr = target.as_mut();
|
let this_ptr = target.as_mut();
|
||||||
let val = self.set_dot_val_helper(scope, this_ptr, dot_rhs, new_val, level);
|
let value =
|
||||||
|
self.set_dot_val_helper(scope, this_ptr, dot_rhs, new_val, level);
|
||||||
|
|
||||||
// In case the expression mutated `target`, we need to update it back into the scope because it is cloned.
|
// In case the expression mutated `target`, we need to update it back into the scope because it is cloned.
|
||||||
*scope.get_mut(entry) = target;
|
*scope.get_mut(entry) = target;
|
||||||
|
|
||||||
val
|
value
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -895,11 +989,11 @@ impl Engine<'_> {
|
|||||||
// TODO - Allow chaining of indexing!
|
// TODO - Allow chaining of indexing!
|
||||||
#[cfg(not(feature = "no_index"))]
|
#[cfg(not(feature = "no_index"))]
|
||||||
Expr::Index(lhs, idx_expr, op_pos) => {
|
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)?;
|
self.eval_index_expr(scope, lhs, idx_expr, *op_pos, level)?;
|
||||||
let val_pos = new_val.1;
|
let val_pos = new_val.1;
|
||||||
let this_ptr = target.as_mut();
|
let this_ptr = target.as_mut();
|
||||||
let val = self.set_dot_val_helper(scope, this_ptr, dot_rhs, new_val, level);
|
let value = self.set_dot_val_helper(scope, this_ptr, dot_rhs, new_val, level);
|
||||||
|
|
||||||
// In case the expression mutated `target`, we need to update it back into the scope because it is cloned.
|
// In case the expression mutated `target`, we need to update it back into the scope because it is cloned.
|
||||||
if let Some(src) = src {
|
if let Some(src) = src {
|
||||||
@ -912,7 +1006,7 @@ impl Engine<'_> {
|
|||||||
}
|
}
|
||||||
ScopeEntryType::Normal => {
|
ScopeEntryType::Normal => {
|
||||||
Self::update_indexed_var_in_scope(
|
Self::update_indexed_var_in_scope(
|
||||||
src_type,
|
idx_src_type,
|
||||||
scope,
|
scope,
|
||||||
src,
|
src,
|
||||||
idx,
|
idx,
|
||||||
@ -922,7 +1016,7 @@ impl Engine<'_> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val
|
value
|
||||||
}
|
}
|
||||||
|
|
||||||
// Syntax error
|
// Syntax error
|
||||||
@ -947,7 +1041,7 @@ impl Engine<'_> {
|
|||||||
Expr::IntegerConstant(i, _) => Ok(i.into_dynamic()),
|
Expr::IntegerConstant(i, _) => Ok(i.into_dynamic()),
|
||||||
Expr::StringConstant(s, _) => Ok(s.into_dynamic()),
|
Expr::StringConstant(s, _) => Ok(s.into_dynamic()),
|
||||||
Expr::CharConstant(c, _) => Ok(c.into_dynamic()),
|
Expr::CharConstant(c, _) => Ok(c.into_dynamic()),
|
||||||
Expr::Variable(id, pos) => 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."),
|
Expr::Property(_, _) => panic!("unexpected property."),
|
||||||
|
|
||||||
// lhs[idx_expr]
|
// lhs[idx_expr]
|
||||||
@ -994,7 +1088,7 @@ impl Engine<'_> {
|
|||||||
// idx_lhs[idx_expr] = rhs
|
// idx_lhs[idx_expr] = rhs
|
||||||
#[cfg(not(feature = "no_index"))]
|
#[cfg(not(feature = "no_index"))]
|
||||||
Expr::Index(idx_lhs, idx_expr, op_pos) => {
|
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)?;
|
self.eval_index_expr(scope, idx_lhs, idx_expr, *op_pos, level)?;
|
||||||
|
|
||||||
if let Some(src) = src {
|
if let Some(src) = src {
|
||||||
@ -1006,7 +1100,7 @@ impl Engine<'_> {
|
|||||||
))
|
))
|
||||||
}
|
}
|
||||||
ScopeEntryType::Normal => Ok(Self::update_indexed_var_in_scope(
|
ScopeEntryType::Normal => Ok(Self::update_indexed_var_in_scope(
|
||||||
src_type,
|
idx_src_type,
|
||||||
scope,
|
scope,
|
||||||
src,
|
src,
|
||||||
idx,
|
idx,
|
||||||
@ -1021,6 +1115,7 @@ impl Engine<'_> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// dot_lhs.dot_rhs = rhs
|
// dot_lhs.dot_rhs = rhs
|
||||||
|
#[cfg(not(feature = "no_object"))]
|
||||||
Expr::Dot(dot_lhs, dot_rhs, _) => self.set_dot_val(
|
Expr::Dot(dot_lhs, dot_rhs, _) => self.set_dot_val(
|
||||||
scope,
|
scope,
|
||||||
dot_lhs,
|
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),
|
Expr::Dot(lhs, rhs, _) => self.get_dot_val(scope, lhs, rhs, level),
|
||||||
|
|
||||||
#[cfg(not(feature = "no_index"))]
|
#[cfg(not(feature = "no_index"))]
|
||||||
Expr::Array(contents, _) => {
|
Expr::Array(contents, _) => {
|
||||||
let mut arr = Vec::new();
|
let mut arr = Array::new();
|
||||||
|
|
||||||
contents.into_iter().try_for_each(|item| {
|
contents.into_iter().try_for_each(|item| {
|
||||||
self.eval_expr(scope, item, level).map(|val| arr.push(val))
|
self.eval_expr(scope, item, level).map(|val| arr.push(val))
|
||||||
@ -1054,12 +1150,25 @@ impl Engine<'_> {
|
|||||||
Ok(Box::new(arr))
|
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) => {
|
Expr::FunctionCall(fn_name, args_expr_list, def_val, pos) => {
|
||||||
// Has a system function an override?
|
// Has a system function an override?
|
||||||
fn has_override(engine: &Engine, name: &str) -> bool {
|
fn has_override(engine: &Engine, name: &str) -> bool {
|
||||||
let spec = FnSpec {
|
let spec = FnSpec {
|
||||||
name: name.into(),
|
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)
|
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 {
|
pub(crate) fn map_type_name<'a>(&'a self, name: &'a str) -> &'a str {
|
||||||
self.type_names
|
self.type_names
|
||||||
.get(name)
|
.get(name)
|
||||||
.map(|s| s.as_str())
|
.map(String::as_str)
|
||||||
.unwrap_or(name)
|
.unwrap_or(name)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
56
src/error.rs
56
src/error.rs
@ -47,32 +47,24 @@ pub enum ParseErrorType {
|
|||||||
UnexpectedEOF,
|
UnexpectedEOF,
|
||||||
/// An unknown operator is encountered. Wrapped value is the operator.
|
/// An unknown operator is encountered. Wrapped value is the operator.
|
||||||
UnknownOperator(String),
|
UnknownOperator(String),
|
||||||
/// An open `(` is missing the corresponding closing `)`.
|
/// Expecting a particular token but not finding one. Wrapped values are the token and usage.
|
||||||
MissingRightParen(String),
|
MissingToken(String, 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),
|
|
||||||
/// An expression in function call arguments `()` has syntax error.
|
/// An expression in function call arguments `()` has syntax error.
|
||||||
MalformedCallExpr(String),
|
MalformedCallExpr(String),
|
||||||
/// An expression in indexing brackets `[]` has syntax error.
|
/// An expression in indexing brackets `[]` has syntax error.
|
||||||
#[cfg(not(feature = "no_index"))]
|
#[cfg(not(feature = "no_index"))]
|
||||||
MalformedIndexExpr(String),
|
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.
|
/// Invalid expression assigned to constant.
|
||||||
ForbiddenConstantExpr(String),
|
ForbiddenConstantExpr(String),
|
||||||
|
/// Missing a property name for custom types and maps.
|
||||||
|
PropertyExpected,
|
||||||
/// Missing a variable name after the `let`, `const` or `for` keywords.
|
/// Missing a variable name after the `let`, `const` or `for` keywords.
|
||||||
VariableExpected,
|
VariableExpected,
|
||||||
/// Missing an expression.
|
/// Missing an expression.
|
||||||
ExprExpected(String),
|
ExprExpected(String),
|
||||||
/// A `for` statement is missing the `in` keyword.
|
|
||||||
MissingIn,
|
|
||||||
/// Defining a function `fn` in an appropriate place (e.g. inside another function).
|
/// Defining a function `fn` in an appropriate place (e.g. inside another function).
|
||||||
#[cfg(not(feature = "no_function"))]
|
#[cfg(not(feature = "no_function"))]
|
||||||
WrongFnDefinition,
|
WrongFnDefinition,
|
||||||
@ -84,7 +76,7 @@ pub enum ParseErrorType {
|
|||||||
FnMissingParams(String),
|
FnMissingParams(String),
|
||||||
/// A function definition has duplicated parameters. Wrapped values are the function name and parameter name.
|
/// A function definition has duplicated parameters. Wrapped values are the function name and parameter name.
|
||||||
#[cfg(not(feature = "no_function"))]
|
#[cfg(not(feature = "no_function"))]
|
||||||
FnDuplicateParam(String, String),
|
FnDuplicatedParam(String, String),
|
||||||
/// A function definition is missing the body. Wrapped value is the function name.
|
/// A function definition is missing the body. Wrapped value is the function name.
|
||||||
#[cfg(not(feature = "no_function"))]
|
#[cfg(not(feature = "no_function"))]
|
||||||
FnMissingBody(String),
|
FnMissingBody(String),
|
||||||
@ -130,18 +122,14 @@ impl ParseError {
|
|||||||
ParseErrorType::BadInput(ref p) => p,
|
ParseErrorType::BadInput(ref p) => p,
|
||||||
ParseErrorType::UnexpectedEOF => "Script is incomplete",
|
ParseErrorType::UnexpectedEOF => "Script is incomplete",
|
||||||
ParseErrorType::UnknownOperator(_) => "Unknown operator",
|
ParseErrorType::UnknownOperator(_) => "Unknown operator",
|
||||||
ParseErrorType::MissingRightParen(_) => "Expecting ')'",
|
ParseErrorType::MissingToken(_, _) => "Expecting a certain token that is missing",
|
||||||
ParseErrorType::MissingLeftBrace => "Expecting '{'",
|
|
||||||
ParseErrorType::MissingRightBrace(_) => "Expecting '}'",
|
|
||||||
#[cfg(not(feature = "no_index"))]
|
|
||||||
ParseErrorType::MissingRightBracket(_) => "Expecting ']'",
|
|
||||||
ParseErrorType::MissingComma(_) => "Expecting ','",
|
|
||||||
ParseErrorType::MissingSemicolon(_) => "Expecting ';'",
|
|
||||||
ParseErrorType::MalformedCallExpr(_) => "Invalid expression in function call arguments",
|
ParseErrorType::MalformedCallExpr(_) => "Invalid expression in function call arguments",
|
||||||
#[cfg(not(feature = "no_index"))]
|
#[cfg(not(feature = "no_index"))]
|
||||||
ParseErrorType::MalformedIndexExpr(_) => "Invalid index in indexing expression",
|
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::ForbiddenConstantExpr(_) => "Expecting a constant",
|
||||||
ParseErrorType::MissingIn => "Expecting 'in'",
|
ParseErrorType::PropertyExpected => "Expecting name of a property",
|
||||||
ParseErrorType::VariableExpected => "Expecting name of a variable",
|
ParseErrorType::VariableExpected => "Expecting name of a variable",
|
||||||
ParseErrorType::ExprExpected(_) => "Expecting an expression",
|
ParseErrorType::ExprExpected(_) => "Expecting an expression",
|
||||||
#[cfg(not(feature = "no_function"))]
|
#[cfg(not(feature = "no_function"))]
|
||||||
@ -149,7 +137,7 @@ impl ParseError {
|
|||||||
#[cfg(not(feature = "no_function"))]
|
#[cfg(not(feature = "no_function"))]
|
||||||
ParseErrorType::FnMissingParams(_) => "Expecting parameters in function declaration",
|
ParseErrorType::FnMissingParams(_) => "Expecting parameters in function declaration",
|
||||||
#[cfg(not(feature = "no_function"))]
|
#[cfg(not(feature = "no_function"))]
|
||||||
ParseErrorType::FnDuplicateParam(_,_) => "Duplicated parameters in function declaration",
|
ParseErrorType::FnDuplicatedParam(_,_) => "Duplicated parameters in function declaration",
|
||||||
#[cfg(not(feature = "no_function"))]
|
#[cfg(not(feature = "no_function"))]
|
||||||
ParseErrorType::FnMissingBody(_) => "Expecting body statement block for function declaration",
|
ParseErrorType::FnMissingBody(_) => "Expecting body statement block for function declaration",
|
||||||
#[cfg(not(feature = "no_function"))]
|
#[cfg(not(feature = "no_function"))]
|
||||||
@ -180,6 +168,11 @@ impl fmt::Display for ParseError {
|
|||||||
write!(f, "{}", if s.is_empty() { self.desc() } else { s })?
|
write!(f, "{}", if s.is_empty() { self.desc() } else { s })?
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[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)?,
|
ParseErrorType::ExprExpected(ref s) => write!(f, "Expecting {} expression", s)?,
|
||||||
|
|
||||||
#[cfg(not(feature = "no_function"))]
|
#[cfg(not(feature = "no_function"))]
|
||||||
@ -193,19 +186,12 @@ impl fmt::Display for ParseError {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(not(feature = "no_function"))]
|
#[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)?
|
write!(f, "Duplicated parameter '{}' for function '{}'", arg, s)?
|
||||||
}
|
}
|
||||||
|
|
||||||
ParseErrorType::MissingRightParen(ref s) | ParseErrorType::MissingRightBrace(ref s) => {
|
ParseErrorType::MissingToken(ref token, ref s) => {
|
||||||
write!(f, "{} for {}", self.desc(), s)?
|
write!(f, "Expecting '{}' {}", token, 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::AssignmentToConstant(ref s) if s.is_empty() => {
|
ParseErrorType::AssignmentToConstant(ref s) if s.is_empty() => {
|
||||||
|
@ -145,7 +145,7 @@ macro_rules! def_register {
|
|||||||
fn register_fn(&mut self, name: &str, f: FN) {
|
fn register_fn(&mut self, name: &str, f: FN) {
|
||||||
let fn_name = name.to_string();
|
let fn_name = name.to_string();
|
||||||
|
|
||||||
let fun = move |args: &mut FnCallArgs, pos: Position| {
|
let func = move |args: &mut FnCallArgs, pos: Position| {
|
||||||
// Check for length at the beginning to avoid per-element bound checks.
|
// Check for length at the beginning to avoid per-element bound checks.
|
||||||
const NUM_ARGS: usize = count_args!($($par)*);
|
const NUM_ARGS: usize = count_args!($($par)*);
|
||||||
|
|
||||||
@ -165,7 +165,7 @@ macro_rules! def_register {
|
|||||||
let r = f($(($clone)($par)),*);
|
let r = f($(($clone)($par)),*);
|
||||||
Ok(Box::new(r) as Dynamic)
|
Ok(Box::new(r) as Dynamic)
|
||||||
};
|
};
|
||||||
self.register_fn_raw(name, 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) {
|
fn register_dynamic_fn(&mut self, name: &str, f: FN) {
|
||||||
let fn_name = name.to_string();
|
let fn_name = name.to_string();
|
||||||
|
|
||||||
let fun = move |args: &mut FnCallArgs, pos: Position| {
|
let func = move |args: &mut FnCallArgs, pos: Position| {
|
||||||
// Check for length at the beginning to avoid per-element bound checks.
|
// Check for length at the beginning to avoid per-element bound checks.
|
||||||
const NUM_ARGS: usize = count_args!($($par)*);
|
const NUM_ARGS: usize = count_args!($($par)*);
|
||||||
|
|
||||||
@ -196,7 +196,7 @@ macro_rules! def_register {
|
|||||||
// potentially clone the value, otherwise pass the reference.
|
// potentially clone the value, otherwise pass the reference.
|
||||||
Ok(f($(($clone)($par)),*))
|
Ok(f($(($clone)($par)),*))
|
||||||
};
|
};
|
||||||
self.register_fn_raw(name, 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) {
|
fn register_result_fn(&mut self, name: &str, f: FN) {
|
||||||
let fn_name = name.to_string();
|
let fn_name = name.to_string();
|
||||||
|
|
||||||
let fun = move |args: &mut FnCallArgs, pos: Position| {
|
let func = move |args: &mut FnCallArgs, pos: Position| {
|
||||||
// Check for length at the beginning to avoid per-element bound checks.
|
// Check for length at the beginning to avoid per-element bound checks.
|
||||||
const NUM_ARGS: usize = count_args!($($par)*);
|
const NUM_ARGS: usize = count_args!($($par)*);
|
||||||
|
|
||||||
@ -229,7 +229,7 @@ macro_rules! def_register {
|
|||||||
f($(($clone)($par)),*).map(|r| Box::new(r) as Dynamic)
|
f($(($clone)($par)),*).map(|r| Box::new(r) as Dynamic)
|
||||||
.map_err(|err| err.set_position(pos))
|
.map_err(|err| err.set_position(pos))
|
||||||
};
|
};
|
||||||
self.register_fn_raw(name, Some(vec![$(TypeId::of::<$par>()),*]), Box::new(fun));
|
self.register_fn_raw(name, vec![$(TypeId::of::<$par>()),*], Box::new(func));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -68,6 +68,9 @@ pub use scope::Scope;
|
|||||||
#[cfg(not(feature = "no_index"))]
|
#[cfg(not(feature = "no_index"))]
|
||||||
pub use engine::Array;
|
pub use engine::Array;
|
||||||
|
|
||||||
|
#[cfg(not(feature = "no_object"))]
|
||||||
|
pub use engine::Map;
|
||||||
|
|
||||||
#[cfg(not(feature = "no_float"))]
|
#[cfg(not(feature = "no_float"))]
|
||||||
pub use parser::FLOAT;
|
pub use parser::FLOAT;
|
||||||
|
|
||||||
|
@ -37,7 +37,15 @@ struct State<'a> {
|
|||||||
engine: &'a Engine<'a>,
|
engine: &'a Engine<'a>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl State<'_> {
|
impl<'a> State<'a> {
|
||||||
|
/// Create a new State.
|
||||||
|
pub fn new(engine: &'a Engine<'a>) -> Self {
|
||||||
|
Self {
|
||||||
|
changed: false,
|
||||||
|
constants: vec![],
|
||||||
|
engine,
|
||||||
|
}
|
||||||
|
}
|
||||||
/// Reset the state from dirty to clean.
|
/// Reset the state from dirty to clean.
|
||||||
pub fn reset(&mut self) {
|
pub fn reset(&mut self) {
|
||||||
self.changed = false;
|
self.changed = false;
|
||||||
@ -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),
|
expr => Expr::Assignment(id, Box::new(optimize_expr(expr, state)), pos),
|
||||||
},
|
},
|
||||||
// lhs.rhs
|
// lhs.rhs
|
||||||
|
#[cfg(not(feature = "no_object"))]
|
||||||
Expr::Dot(lhs, rhs, pos) => Expr::Dot(
|
Expr::Dot(lhs, rhs, pos) => Expr::Dot(
|
||||||
Box::new(optimize_expr(*lhs, state)),
|
Box::new(optimize_expr(*lhs, state)),
|
||||||
Box::new(optimize_expr(*rhs, state)),
|
Box::new(optimize_expr(*rhs, state)),
|
||||||
@ -369,20 +378,16 @@ fn optimize_expr<'a>(expr: Expr, state: &mut State<'a>) -> Expr {
|
|||||||
},
|
},
|
||||||
// [ items .. ]
|
// [ items .. ]
|
||||||
#[cfg(not(feature = "no_index"))]
|
#[cfg(not(feature = "no_index"))]
|
||||||
Expr::Array(items, pos) => {
|
Expr::Array(items, pos) => Expr::Array(items
|
||||||
let orig_len = items.len();
|
|
||||||
|
|
||||||
let items: Vec<_> = items
|
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|expr| optimize_expr(expr, state))
|
.map(|expr| optimize_expr(expr, state))
|
||||||
.collect();
|
.collect(), pos),
|
||||||
|
// [ items .. ]
|
||||||
if orig_len != items.len() {
|
#[cfg(not(feature = "no_object"))]
|
||||||
state.set_dirty();
|
Expr::Map(items, pos) => Expr::Map(items
|
||||||
}
|
.into_iter()
|
||||||
|
.map(|(key, expr, pos)| (key, optimize_expr(expr, state), pos))
|
||||||
Expr::Array(items, pos)
|
.collect(), pos),
|
||||||
}
|
|
||||||
// lhs && rhs
|
// lhs && rhs
|
||||||
Expr::And(lhs, rhs) => match (*lhs, *rhs) {
|
Expr::And(lhs, rhs) => match (*lhs, *rhs) {
|
||||||
// true && rhs -> rhs
|
// true && rhs -> rhs
|
||||||
@ -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) if id == KEYWORD_DUMP_AST =>
|
||||||
Expr::FunctionCall(id, args, def_value, pos),
|
Expr::FunctionCall(id, args, def_value, pos),
|
||||||
|
|
||||||
// Do not optimize anything within built-in function keywords
|
// Do not call some special keywords
|
||||||
Expr::FunctionCall(id, args, def_value, pos) if DONT_EVAL_KEYWORDS.contains(&id.as_str())=>
|
Expr::FunctionCall(id, args, def_value, pos) if DONT_EVAL_KEYWORDS.contains(&id.as_str())=>
|
||||||
Expr::FunctionCall(id, args.into_iter().map(|a| optimize_expr(a, state)).collect(), def_value, pos),
|
Expr::FunctionCall(id, args.into_iter().map(|a| optimize_expr(a, state)).collect(), def_value, pos),
|
||||||
|
|
||||||
@ -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|
|
state.engine.call_ext_fn_raw(&id, &mut call_args, pos).ok().map(|result|
|
||||||
r.or_else(|| {
|
result.or_else(|| {
|
||||||
if !arg_for_type_of.is_empty() {
|
if !arg_for_type_of.is_empty() {
|
||||||
// Handle `type_of()`
|
// Handle `type_of()`
|
||||||
Some(arg_for_type_of.to_string().into_dynamic())
|
Some(arg_for_type_of.to_string().into_dynamic())
|
||||||
@ -501,11 +506,7 @@ pub(crate) fn optimize<'a>(statements: Vec<Stmt>, engine: &Engine<'a>, scope: &S
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Set up the state
|
// Set up the state
|
||||||
let mut state = State {
|
let mut state = State::new(engine);
|
||||||
changed: false,
|
|
||||||
constants: vec![],
|
|
||||||
engine,
|
|
||||||
};
|
|
||||||
|
|
||||||
// Add constants from the scope into the state
|
// Add constants from the scope into the state
|
||||||
scope
|
scope
|
||||||
|
479
src/parser.rs
479
src/parser.rs
@ -11,7 +11,9 @@ use crate::optimize::optimize_into_ast;
|
|||||||
use crate::stdlib::{
|
use crate::stdlib::{
|
||||||
borrow::Cow,
|
borrow::Cow,
|
||||||
boxed::Box,
|
boxed::Box,
|
||||||
char, fmt, format,
|
char,
|
||||||
|
collections::HashMap,
|
||||||
|
fmt, format,
|
||||||
iter::Peekable,
|
iter::Peekable,
|
||||||
ops::Add,
|
ops::Add,
|
||||||
str::Chars,
|
str::Chars,
|
||||||
@ -177,12 +179,14 @@ impl AST {
|
|||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
/// # fn main() -> Result<(), rhai::EvalAltResult> {
|
/// # fn main() -> Result<(), rhai::EvalAltResult> {
|
||||||
|
/// # #[cfg(not(feature = "no_function"))]
|
||||||
|
/// # {
|
||||||
/// use rhai::Engine;
|
/// use rhai::Engine;
|
||||||
///
|
///
|
||||||
/// let mut engine = Engine::new();
|
/// let mut engine = Engine::new();
|
||||||
///
|
///
|
||||||
/// let ast1 = engine.compile(r#"fn foo(x) { 42 + x } foo(1)"#)?;
|
/// 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'
|
/// 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
|
/// // fn foo(n) { "hello" + n } // <- definition of first 'foo' is overwritten
|
||||||
/// // foo(1) // <- notice this will be "hello1" instead of 43,
|
/// // foo(1) // <- notice this will be "hello1" instead of 43,
|
||||||
/// // // but it is no longer the return value
|
/// // // but it is no longer the return value
|
||||||
/// // foo(2) // returns "hello2"
|
/// // foo("!") // returns "hello!"
|
||||||
///
|
///
|
||||||
/// // Evaluate it
|
/// // Evaluate it
|
||||||
/// assert_eq!(engine.eval_ast::<String>(&ast)?, "hello2");
|
/// assert_eq!(engine.eval_ast::<String>(&ast)?, "hello!");
|
||||||
|
/// # }
|
||||||
/// # Ok(())
|
/// # Ok(())
|
||||||
/// # }
|
/// # }
|
||||||
/// ```
|
/// ```
|
||||||
@ -357,6 +362,7 @@ pub enum Expr {
|
|||||||
/// expr = expr
|
/// expr = expr
|
||||||
Assignment(Box<Expr>, Box<Expr>, Position),
|
Assignment(Box<Expr>, Box<Expr>, Position),
|
||||||
/// lhs.rhs
|
/// lhs.rhs
|
||||||
|
#[cfg(not(feature = "no_object"))]
|
||||||
Dot(Box<Expr>, Box<Expr>, Position),
|
Dot(Box<Expr>, Box<Expr>, Position),
|
||||||
/// expr[expr]
|
/// expr[expr]
|
||||||
#[cfg(not(feature = "no_index"))]
|
#[cfg(not(feature = "no_index"))]
|
||||||
@ -364,6 +370,9 @@ pub enum Expr {
|
|||||||
#[cfg(not(feature = "no_index"))]
|
#[cfg(not(feature = "no_index"))]
|
||||||
/// [ expr, ... ]
|
/// [ expr, ... ]
|
||||||
Array(Vec<Expr>, Position),
|
Array(Vec<Expr>, Position),
|
||||||
|
#[cfg(not(feature = "no_object"))]
|
||||||
|
/// #{ name:expr, ... }
|
||||||
|
Map(Vec<(String, Expr, Position)>, Position),
|
||||||
/// lhs && rhs
|
/// lhs && rhs
|
||||||
And(Box<Expr>, Box<Expr>),
|
And(Box<Expr>, Box<Expr>),
|
||||||
/// lhs || rhs
|
/// lhs || rhs
|
||||||
@ -398,6 +407,13 @@ impl Expr {
|
|||||||
.collect::<Vec<_>>()
|
.collect::<Vec<_>>()
|
||||||
.into_dynamic(),
|
.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"))]
|
#[cfg(not(feature = "no_float"))]
|
||||||
Expr::FloatConstant(f, _) => f.into_dynamic(),
|
Expr::FloatConstant(f, _) => f.into_dynamic(),
|
||||||
|
|
||||||
@ -443,10 +459,12 @@ impl Expr {
|
|||||||
| Expr::False(pos)
|
| Expr::False(pos)
|
||||||
| Expr::Unit(pos) => *pos,
|
| Expr::Unit(pos) => *pos,
|
||||||
|
|
||||||
Expr::Assignment(expr, _, _)
|
Expr::Assignment(expr, _, _) | Expr::And(expr, _) | Expr::Or(expr, _) => {
|
||||||
| Expr::Dot(expr, _, _)
|
expr.position()
|
||||||
| Expr::And(expr, _)
|
}
|
||||||
| Expr::Or(expr, _) => expr.position(),
|
|
||||||
|
#[cfg(not(feature = "no_object"))]
|
||||||
|
Expr::Dot(expr, _, _) => expr.position(),
|
||||||
|
|
||||||
#[cfg(not(feature = "no_float"))]
|
#[cfg(not(feature = "no_float"))]
|
||||||
Expr::FloatConstant(_, pos) => *pos,
|
Expr::FloatConstant(_, pos) => *pos,
|
||||||
@ -454,6 +472,9 @@ impl Expr {
|
|||||||
#[cfg(not(feature = "no_index"))]
|
#[cfg(not(feature = "no_index"))]
|
||||||
Expr::Array(_, pos) => *pos,
|
Expr::Array(_, pos) => *pos,
|
||||||
|
|
||||||
|
#[cfg(not(feature = "no_object"))]
|
||||||
|
Expr::Map(_, pos) => *pos,
|
||||||
|
|
||||||
#[cfg(not(feature = "no_index"))]
|
#[cfg(not(feature = "no_index"))]
|
||||||
Expr::Index(expr, _, _) => expr.position(),
|
Expr::Index(expr, _, _) => expr.position(),
|
||||||
}
|
}
|
||||||
@ -531,6 +552,8 @@ pub enum Token {
|
|||||||
Colon,
|
Colon,
|
||||||
Comma,
|
Comma,
|
||||||
Period,
|
Period,
|
||||||
|
#[cfg(not(feature = "no_object"))]
|
||||||
|
MapStart,
|
||||||
Equals,
|
Equals,
|
||||||
True,
|
True,
|
||||||
False,
|
False,
|
||||||
@ -606,6 +629,8 @@ impl Token {
|
|||||||
Colon => ":",
|
Colon => ":",
|
||||||
Comma => ",",
|
Comma => ",",
|
||||||
Period => ".",
|
Period => ".",
|
||||||
|
#[cfg(not(feature = "no_object"))]
|
||||||
|
MapStart => "#{",
|
||||||
Equals => "=",
|
Equals => "=",
|
||||||
True => "true",
|
True => "true",
|
||||||
False => "false",
|
False => "false",
|
||||||
@ -794,6 +819,11 @@ pub struct TokenIterator<'a> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> 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.
|
/// Move the current position one character ahead.
|
||||||
fn advance(&mut self) {
|
fn advance(&mut self) {
|
||||||
self.pos.advance();
|
self.pos.advance();
|
||||||
@ -933,20 +963,17 @@ impl<'a> TokenIterator<'a> {
|
|||||||
match next_char {
|
match next_char {
|
||||||
'0'..='9' | '_' => {
|
'0'..='9' | '_' => {
|
||||||
result.push(next_char);
|
result.push(next_char);
|
||||||
self.stream.next();
|
self.eat_next();
|
||||||
self.advance();
|
|
||||||
}
|
}
|
||||||
#[cfg(not(feature = "no_float"))]
|
#[cfg(not(feature = "no_float"))]
|
||||||
'.' => {
|
'.' => {
|
||||||
result.push(next_char);
|
result.push(next_char);
|
||||||
self.stream.next();
|
self.eat_next();
|
||||||
self.advance();
|
|
||||||
while let Some(&next_char_in_float) = self.stream.peek() {
|
while let Some(&next_char_in_float) = self.stream.peek() {
|
||||||
match next_char_in_float {
|
match next_char_in_float {
|
||||||
'0'..='9' | '_' => {
|
'0'..='9' | '_' => {
|
||||||
result.push(next_char_in_float);
|
result.push(next_char_in_float);
|
||||||
self.stream.next();
|
self.eat_next();
|
||||||
self.advance();
|
|
||||||
}
|
}
|
||||||
_ => break,
|
_ => break,
|
||||||
}
|
}
|
||||||
@ -957,8 +984,7 @@ impl<'a> TokenIterator<'a> {
|
|||||||
if c == '0' =>
|
if c == '0' =>
|
||||||
{
|
{
|
||||||
result.push(next_char);
|
result.push(next_char);
|
||||||
self.stream.next();
|
self.eat_next();
|
||||||
self.advance();
|
|
||||||
|
|
||||||
let valid = match ch {
|
let valid = match ch {
|
||||||
'x' | 'X' => [
|
'x' | 'X' => [
|
||||||
@ -989,8 +1015,7 @@ impl<'a> TokenIterator<'a> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
result.push(next_char_in_hex);
|
result.push(next_char_in_hex);
|
||||||
self.stream.next();
|
self.eat_next();
|
||||||
self.advance();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1044,8 +1069,7 @@ impl<'a> TokenIterator<'a> {
|
|||||||
match next_char {
|
match next_char {
|
||||||
x if x.is_ascii_alphanumeric() || x == '_' => {
|
x if x.is_ascii_alphanumeric() || x == '_' => {
|
||||||
result.push(x);
|
result.push(x);
|
||||||
self.stream.next();
|
self.eat_next();
|
||||||
self.advance();
|
|
||||||
}
|
}
|
||||||
_ => break,
|
_ => break,
|
||||||
}
|
}
|
||||||
@ -1136,10 +1160,16 @@ impl<'a> TokenIterator<'a> {
|
|||||||
#[cfg(not(feature = "no_index"))]
|
#[cfg(not(feature = "no_index"))]
|
||||||
(']', _) => return Some((Token::RightBracket, pos)),
|
(']', _) => return Some((Token::RightBracket, pos)),
|
||||||
|
|
||||||
|
// Map literal
|
||||||
|
#[cfg(not(feature = "no_object"))]
|
||||||
|
('#', '{') => {
|
||||||
|
self.eat_next();
|
||||||
|
return Some((Token::MapStart, pos));
|
||||||
|
}
|
||||||
|
|
||||||
// Operators
|
// Operators
|
||||||
('+', '=') => {
|
('+', '=') => {
|
||||||
self.stream.next();
|
self.eat_next();
|
||||||
self.advance();
|
|
||||||
return Some((Token::PlusAssign, pos));
|
return Some((Token::PlusAssign, pos));
|
||||||
}
|
}
|
||||||
('+', _) if self.can_be_unary => return Some((Token::UnaryPlus, 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') if self.can_be_unary => negated = true,
|
||||||
('-', '0'..='9') => return Some((Token::Minus, pos)),
|
('-', '0'..='9') => return Some((Token::Minus, pos)),
|
||||||
('-', '=') => {
|
('-', '=') => {
|
||||||
self.stream.next();
|
self.eat_next();
|
||||||
self.advance();
|
|
||||||
return Some((Token::MinusAssign, pos));
|
return Some((Token::MinusAssign, pos));
|
||||||
}
|
}
|
||||||
('-', _) if self.can_be_unary => return Some((Token::UnaryMinus, pos)),
|
('-', _) if self.can_be_unary => return Some((Token::UnaryMinus, pos)),
|
||||||
('-', _) => return Some((Token::Minus, pos)),
|
('-', _) => return Some((Token::Minus, pos)),
|
||||||
|
|
||||||
('*', '=') => {
|
('*', '=') => {
|
||||||
self.stream.next();
|
self.eat_next();
|
||||||
self.advance();
|
|
||||||
return Some((Token::MultiplyAssign, pos));
|
return Some((Token::MultiplyAssign, pos));
|
||||||
}
|
}
|
||||||
('*', _) => return Some((Token::Multiply, pos)),
|
('*', _) => return Some((Token::Multiply, pos)),
|
||||||
|
|
||||||
// Comments
|
// Comments
|
||||||
('/', '/') => {
|
('/', '/') => {
|
||||||
self.stream.next();
|
self.eat_next();
|
||||||
self.advance();
|
|
||||||
|
|
||||||
while let Some(c) = self.stream.next() {
|
while let Some(c) = self.stream.next() {
|
||||||
if c == '\n' {
|
if c == '\n' {
|
||||||
@ -1179,8 +1206,7 @@ impl<'a> TokenIterator<'a> {
|
|||||||
('/', '*') => {
|
('/', '*') => {
|
||||||
let mut level = 1;
|
let mut level = 1;
|
||||||
|
|
||||||
self.stream.next();
|
self.eat_next();
|
||||||
self.advance();
|
|
||||||
|
|
||||||
while let Some(c) = self.stream.next() {
|
while let Some(c) = self.stream.next() {
|
||||||
self.advance();
|
self.advance();
|
||||||
@ -1209,8 +1235,7 @@ impl<'a> TokenIterator<'a> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
('/', '=') => {
|
('/', '=') => {
|
||||||
self.stream.next();
|
self.eat_next();
|
||||||
self.advance();
|
|
||||||
return Some((Token::DivideAssign, pos));
|
return Some((Token::DivideAssign, pos));
|
||||||
}
|
}
|
||||||
('/', _) => return Some((Token::Divide, pos)),
|
('/', _) => return Some((Token::Divide, pos)),
|
||||||
@ -1221,25 +1246,21 @@ impl<'a> TokenIterator<'a> {
|
|||||||
('.', _) => return Some((Token::Period, pos)),
|
('.', _) => return Some((Token::Period, pos)),
|
||||||
|
|
||||||
('=', '=') => {
|
('=', '=') => {
|
||||||
self.stream.next();
|
self.eat_next();
|
||||||
self.advance();
|
|
||||||
return Some((Token::EqualsTo, pos));
|
return Some((Token::EqualsTo, pos));
|
||||||
}
|
}
|
||||||
('=', _) => return Some((Token::Equals, pos)),
|
('=', _) => return Some((Token::Equals, pos)),
|
||||||
|
|
||||||
('<', '=') => {
|
('<', '=') => {
|
||||||
self.stream.next();
|
self.eat_next();
|
||||||
self.advance();
|
|
||||||
return Some((Token::LessThanEqualsTo, pos));
|
return Some((Token::LessThanEqualsTo, pos));
|
||||||
}
|
}
|
||||||
('<', '<') => {
|
('<', '<') => {
|
||||||
self.stream.next();
|
self.eat_next();
|
||||||
self.advance();
|
|
||||||
|
|
||||||
return Some((
|
return Some((
|
||||||
if self.stream.peek() == Some(&'=') {
|
if self.stream.peek() == Some(&'=') {
|
||||||
self.stream.next();
|
self.eat_next();
|
||||||
self.advance();
|
|
||||||
Token::LeftShiftAssign
|
Token::LeftShiftAssign
|
||||||
} else {
|
} else {
|
||||||
Token::LeftShift
|
Token::LeftShift
|
||||||
@ -1250,18 +1271,15 @@ impl<'a> TokenIterator<'a> {
|
|||||||
('<', _) => return Some((Token::LessThan, pos)),
|
('<', _) => return Some((Token::LessThan, pos)),
|
||||||
|
|
||||||
('>', '=') => {
|
('>', '=') => {
|
||||||
self.stream.next();
|
self.eat_next();
|
||||||
self.advance();
|
|
||||||
return Some((Token::GreaterThanEqualsTo, pos));
|
return Some((Token::GreaterThanEqualsTo, pos));
|
||||||
}
|
}
|
||||||
('>', '>') => {
|
('>', '>') => {
|
||||||
self.stream.next();
|
self.eat_next();
|
||||||
self.advance();
|
|
||||||
|
|
||||||
return Some((
|
return Some((
|
||||||
if self.stream.peek() == Some(&'=') {
|
if self.stream.peek() == Some(&'=') {
|
||||||
self.stream.next();
|
self.eat_next();
|
||||||
self.advance();
|
|
||||||
Token::RightShiftAssign
|
Token::RightShiftAssign
|
||||||
} else {
|
} else {
|
||||||
Token::RightShift
|
Token::RightShift
|
||||||
@ -1272,53 +1290,45 @@ impl<'a> TokenIterator<'a> {
|
|||||||
('>', _) => return Some((Token::GreaterThan, pos)),
|
('>', _) => return Some((Token::GreaterThan, pos)),
|
||||||
|
|
||||||
('!', '=') => {
|
('!', '=') => {
|
||||||
self.stream.next();
|
self.eat_next();
|
||||||
self.advance();
|
|
||||||
return Some((Token::NotEqualsTo, pos));
|
return Some((Token::NotEqualsTo, pos));
|
||||||
}
|
}
|
||||||
('!', _) => return Some((Token::Bang, pos)),
|
('!', _) => return Some((Token::Bang, pos)),
|
||||||
|
|
||||||
('|', '|') => {
|
('|', '|') => {
|
||||||
self.stream.next();
|
self.eat_next();
|
||||||
self.advance();
|
|
||||||
return Some((Token::Or, pos));
|
return Some((Token::Or, pos));
|
||||||
}
|
}
|
||||||
('|', '=') => {
|
('|', '=') => {
|
||||||
self.stream.next();
|
self.eat_next();
|
||||||
self.advance();
|
|
||||||
return Some((Token::OrAssign, pos));
|
return Some((Token::OrAssign, pos));
|
||||||
}
|
}
|
||||||
('|', _) => return Some((Token::Pipe, pos)),
|
('|', _) => return Some((Token::Pipe, pos)),
|
||||||
|
|
||||||
('&', '&') => {
|
('&', '&') => {
|
||||||
self.stream.next();
|
self.eat_next();
|
||||||
self.advance();
|
|
||||||
return Some((Token::And, pos));
|
return Some((Token::And, pos));
|
||||||
}
|
}
|
||||||
('&', '=') => {
|
('&', '=') => {
|
||||||
self.stream.next();
|
self.eat_next();
|
||||||
self.advance();
|
|
||||||
return Some((Token::AndAssign, pos));
|
return Some((Token::AndAssign, pos));
|
||||||
}
|
}
|
||||||
('&', _) => return Some((Token::Ampersand, pos)),
|
('&', _) => return Some((Token::Ampersand, pos)),
|
||||||
|
|
||||||
('^', '=') => {
|
('^', '=') => {
|
||||||
self.stream.next();
|
self.eat_next();
|
||||||
self.advance();
|
|
||||||
return Some((Token::XOrAssign, pos));
|
return Some((Token::XOrAssign, pos));
|
||||||
}
|
}
|
||||||
('^', _) => return Some((Token::XOr, pos)),
|
('^', _) => return Some((Token::XOr, pos)),
|
||||||
|
|
||||||
('%', '=') => {
|
('%', '=') => {
|
||||||
self.stream.next();
|
self.eat_next();
|
||||||
self.advance();
|
|
||||||
return Some((Token::ModuloAssign, pos));
|
return Some((Token::ModuloAssign, pos));
|
||||||
}
|
}
|
||||||
('%', _) => return Some((Token::Modulo, pos)),
|
('%', _) => return Some((Token::Modulo, pos)),
|
||||||
|
|
||||||
('~', '=') => {
|
('~', '=') => {
|
||||||
self.stream.next();
|
self.eat_next();
|
||||||
self.advance();
|
|
||||||
return Some((Token::PowerOfAssign, pos));
|
return Some((Token::PowerOfAssign, pos));
|
||||||
}
|
}
|
||||||
('~', _) => return Some((Token::PowerOf, pos)),
|
('~', _) => return Some((Token::PowerOf, pos)),
|
||||||
@ -1370,13 +1380,16 @@ fn parse_paren_expr<'a>(
|
|||||||
// ( xxx )
|
// ( xxx )
|
||||||
Some((Token::RightParen, _)) => Ok(expr),
|
Some((Token::RightParen, _)) => Ok(expr),
|
||||||
// ( xxx ???
|
// ( xxx ???
|
||||||
Some((_, pos)) => {
|
Some((_, pos)) => Err(PERR::MissingToken(
|
||||||
Err(PERR::MissingRightParen("a matching ( in the expression".into()).into_err(pos))
|
")".into(),
|
||||||
}
|
"for a matching ( in this expression".into(),
|
||||||
|
)
|
||||||
|
.into_err(pos)),
|
||||||
// ( xxx
|
// ( xxx
|
||||||
None => {
|
None => Err(
|
||||||
Err(PERR::MissingRightParen("a matching ( in the expression".into()).into_err_eof())
|
PERR::MissingToken(")".into(), "for a matching ( in this expression".into())
|
||||||
}
|
.into_err_eof(),
|
||||||
|
),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1391,10 +1404,10 @@ fn parse_call_expr<'a>(
|
|||||||
|
|
||||||
// id()
|
// id()
|
||||||
if let (Token::RightParen, _) = input.peek().ok_or_else(|| {
|
if let (Token::RightParen, _) = input.peek().ok_or_else(|| {
|
||||||
PERR::MissingRightParen(format!(
|
PERR::MissingToken(
|
||||||
"closing the arguments to call of function '{}'",
|
")".into(),
|
||||||
id
|
format!("to close the arguments list of this function call '{}'", id),
|
||||||
))
|
)
|
||||||
.into_err_eof()
|
.into_err_eof()
|
||||||
})? {
|
})? {
|
||||||
input.next();
|
input.next();
|
||||||
@ -1405,10 +1418,10 @@ fn parse_call_expr<'a>(
|
|||||||
args_expr_list.push(parse_expr(input, allow_stmt_expr)?);
|
args_expr_list.push(parse_expr(input, allow_stmt_expr)?);
|
||||||
|
|
||||||
match input.peek().ok_or_else(|| {
|
match input.peek().ok_or_else(|| {
|
||||||
PERR::MissingRightParen(format!(
|
PERR::MissingToken(
|
||||||
"closing the arguments to call of function '{}'",
|
")".into(),
|
||||||
id
|
format!("to close the arguments list of this function call '{}'", id),
|
||||||
))
|
)
|
||||||
.into_err_eof()
|
.into_err_eof()
|
||||||
})? {
|
})? {
|
||||||
(Token::RightParen, _) => {
|
(Token::RightParen, _) => {
|
||||||
@ -1417,10 +1430,10 @@ fn parse_call_expr<'a>(
|
|||||||
}
|
}
|
||||||
(Token::Comma, _) => (),
|
(Token::Comma, _) => (),
|
||||||
(_, pos) => {
|
(_, pos) => {
|
||||||
return Err(PERR::MissingComma(format!(
|
return Err(PERR::MissingToken(
|
||||||
"separating the arguments to call of function '{}'",
|
",".into(),
|
||||||
id
|
format!("to separate the arguments to function call '{}'", id),
|
||||||
))
|
)
|
||||||
.into_err(*pos))
|
.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"))]
|
#[cfg(not(feature = "no_index"))]
|
||||||
fn parse_index_expr<'a>(
|
fn parse_index_expr<'a>(
|
||||||
lhs: Box<Expr>,
|
lhs: Box<Expr>,
|
||||||
@ -1439,7 +1452,7 @@ fn parse_index_expr<'a>(
|
|||||||
) -> Result<Expr, ParseError> {
|
) -> Result<Expr, ParseError> {
|
||||||
let idx_expr = parse_expr(input, allow_stmt_expr)?;
|
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 {
|
match &idx_expr {
|
||||||
// lhs[int]
|
// lhs[int]
|
||||||
Expr::IntegerConstant(i, pos) if *i < 0 => {
|
Expr::IntegerConstant(i, pos) if *i < 0 => {
|
||||||
@ -1449,6 +1462,72 @@ fn parse_index_expr<'a>(
|
|||||||
))
|
))
|
||||||
.into_err(*pos))
|
.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]
|
// lhs[float]
|
||||||
#[cfg(not(feature = "no_float"))]
|
#[cfg(not(feature = "no_float"))]
|
||||||
Expr::FloatConstant(_, pos) => {
|
Expr::FloatConstant(_, pos) => {
|
||||||
@ -1464,13 +1543,6 @@ fn parse_index_expr<'a>(
|
|||||||
)
|
)
|
||||||
.into_err(*pos))
|
.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[()]
|
// lhs[??? = ??? ], lhs[()]
|
||||||
Expr::Assignment(_, _, pos) | Expr::Unit(pos) => {
|
Expr::Assignment(_, _, pos) | Expr::Unit(pos) => {
|
||||||
return Err(PERR::MalformedIndexExpr(
|
return Err(PERR::MalformedIndexExpr(
|
||||||
@ -1497,15 +1569,22 @@ fn parse_index_expr<'a>(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Check if there is a closing bracket
|
// Check if there is a closing bracket
|
||||||
match input
|
match input.peek().ok_or_else(|| {
|
||||||
.peek()
|
PERR::MissingToken(
|
||||||
.ok_or_else(|| PERR::MissingRightBracket("index expression".into()).into_err_eof())?
|
"]".into(),
|
||||||
{
|
"for a matching [ in this index expression".into(),
|
||||||
|
)
|
||||||
|
.into_err_eof()
|
||||||
|
})? {
|
||||||
(Token::RightBracket, _) => {
|
(Token::RightBracket, _) => {
|
||||||
input.next();
|
input.next();
|
||||||
Ok(Expr::Index(lhs, Box::new(idx_expr), pos))
|
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)?);
|
arr.push(parse_expr(input, allow_stmt_expr)?);
|
||||||
|
|
||||||
match input.peek().ok_or_else(|| {
|
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, _) => {
|
(Token::Comma, _) => {
|
||||||
input.next();
|
input.next();
|
||||||
}
|
}
|
||||||
(Token::RightBracket, _) => break,
|
(Token::RightBracket, _) => break,
|
||||||
(_, pos) => {
|
(_, pos) => {
|
||||||
return Err(
|
return Err(PERR::MissingToken(
|
||||||
PERR::MissingComma("separating items in array literal".into())
|
",".into(),
|
||||||
.into_err(*pos),
|
"to separate the items of this array literal".into(),
|
||||||
)
|
)
|
||||||
|
.into_err(*pos))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
match input.peek().ok_or_else(|| {
|
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, _) => {
|
(Token::RightBracket, _) => {
|
||||||
input.next();
|
input.next();
|
||||||
Ok(Expr::Array(arr, begin))
|
Ok(Expr::Array(arr, begin))
|
||||||
}
|
}
|
||||||
(_, pos) => {
|
(_, 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.
|
/// Parse a primary expression.
|
||||||
fn parse_primary<'a>(
|
fn parse_primary<'a>(
|
||||||
input: &mut Peekable<TokenIterator<'a>>,
|
input: &mut Peekable<TokenIterator<'a>>,
|
||||||
@ -1627,6 +1812,11 @@ fn parse_primary<'a>(
|
|||||||
can_be_indexed = true;
|
can_be_indexed = true;
|
||||||
parse_array_literal(input, pos, allow_stmt_expr)
|
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::True, pos) => Ok(Expr::True(pos)),
|
||||||
(Token::False, pos) => Ok(Expr::False(pos)),
|
(Token::False, pos) => Ok(Expr::False(pos)),
|
||||||
(Token::LexError(err), pos) => Err(PERR::BadInput(err.to_string()).into_err(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
|
// dot_lhs.dot_rhs
|
||||||
|
#[cfg(not(feature = "no_object"))]
|
||||||
Expr::Dot(dot_lhs, dot_rhs, _) => match dot_lhs.as_ref() {
|
Expr::Dot(dot_lhs, dot_rhs, _) => match dot_lhs.as_ref() {
|
||||||
// var.dot_rhs
|
// var.dot_rhs
|
||||||
Expr::Variable(_, _) if is_top => valid_assignment_chain(dot_rhs, false),
|
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::PlusAssign => parse_op_assignment("+", current_lhs, rhs, pos)?,
|
||||||
Token::MinusAssign => parse_op_assignment("-", current_lhs, rhs, pos)?,
|
Token::MinusAssign => parse_op_assignment("-", current_lhs, rhs, pos)?,
|
||||||
|
|
||||||
|
#[cfg(not(feature = "no_object"))]
|
||||||
Token::Period => {
|
Token::Period => {
|
||||||
fn change_var_to_property(expr: Expr) -> Expr {
|
fn check_property(expr: Expr) -> Result<Expr, ParseError> {
|
||||||
match expr {
|
match expr {
|
||||||
Expr::Dot(lhs, rhs, pos) => Expr::Dot(
|
// xxx.lhs.rhs
|
||||||
Box::new(change_var_to_property(*lhs)),
|
Expr::Dot(lhs, rhs, pos) => Ok(Expr::Dot(
|
||||||
Box::new(change_var_to_property(*rhs)),
|
Box::new(check_property(*lhs)?),
|
||||||
|
Box::new(check_property(*rhs)?),
|
||||||
pos,
|
pos,
|
||||||
),
|
)),
|
||||||
|
// xxx.lhs[idx]
|
||||||
#[cfg(not(feature = "no_index"))]
|
#[cfg(not(feature = "no_index"))]
|
||||||
Expr::Index(lhs, idx, pos) => {
|
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),
|
// xxx.id
|
||||||
expr => expr,
|
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(
|
Expr::Dot(Box::new(current_lhs), Box::new(check_property(rhs)?), pos)
|
||||||
Box::new(current_lhs),
|
|
||||||
Box::new(change_var_to_property(rhs)),
|
|
||||||
pos,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Comparison operators default to false when passed invalid operands
|
// Comparison operators default to false when passed invalid operands
|
||||||
@ -2070,9 +2265,16 @@ fn parse_for<'a>(
|
|||||||
};
|
};
|
||||||
|
|
||||||
// for name in ...
|
// 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, _) => (),
|
(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 }
|
// for name in expr { body }
|
||||||
@ -2136,10 +2338,14 @@ fn parse_block<'a>(
|
|||||||
// Must start with {
|
// Must start with {
|
||||||
let pos = match input
|
let pos = match input
|
||||||
.next()
|
.next()
|
||||||
.ok_or_else(|| PERR::MissingLeftBrace.into_err_eof())?
|
.ok_or_else(|| PERR::UnexpectedEOF.into_err_eof())?
|
||||||
{
|
{
|
||||||
(Token::LeftBrace, pos) => pos,
|
(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();
|
let mut statements = Vec::new();
|
||||||
@ -2169,20 +2375,24 @@ fn parse_block<'a>(
|
|||||||
// { ... stmt ??? - error
|
// { ... stmt ??? - error
|
||||||
Some((_, pos)) => {
|
Some((_, pos)) => {
|
||||||
// Semicolons are not optional between statements
|
// 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
|
match input.peek().ok_or_else(|| {
|
||||||
.peek()
|
PERR::MissingToken("}".into(), "to end this statement block".into()).into_err_eof()
|
||||||
.ok_or_else(|| PERR::MissingRightBrace("end of block".into()).into_err_eof())?
|
})? {
|
||||||
{
|
|
||||||
(Token::RightBrace, _) => {
|
(Token::RightBrace, _) => {
|
||||||
input.next();
|
input.next();
|
||||||
Ok(Stmt::Block(statements, pos))
|
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, _))) {
|
if matches!(input.peek(), Some((Token::RightParen, _))) {
|
||||||
input.next();
|
input.next();
|
||||||
} else {
|
} else {
|
||||||
let end_err = format!("closing the parameters list of function '{}'", name);
|
let end_err = format!("to close the parameters list of function '{}'", name);
|
||||||
let sep_err = format!("separating the parameters of function '{}'", name);
|
let sep_err = format!("to separate the parameters of function '{}'", name);
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
match input
|
match input
|
||||||
.next()
|
.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) => {
|
(Token::Identifier(s), pos) => {
|
||||||
params.push((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
|
match input
|
||||||
.next()
|
.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::RightParen, _) => break,
|
||||||
(Token::Comma, _) => (),
|
(Token::Comma, _) => (),
|
||||||
(Token::Identifier(_), _) => return Err(PERR::MissingComma(sep_err).into_err(pos)),
|
(Token::Identifier(_), pos) => {
|
||||||
(_, pos) => return Err(PERR::MissingRightParen(sep_err).into_err(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_or_else(|| Ok(()), |(p2, pos)| Err((p2, *pos)))
|
||||||
})
|
})
|
||||||
.map_err(|(p, 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
|
// Parse function body
|
||||||
@ -2408,7 +2620,10 @@ fn parse_global_level<'a>(
|
|||||||
// stmt ??? - error
|
// stmt ??? - error
|
||||||
Some((_, pos)) => {
|
Some((_, pos)) => {
|
||||||
// Semicolons are not optional between statements
|
// 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),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -20,6 +20,11 @@ use crate::stdlib::path::PathBuf;
|
|||||||
pub enum EvalAltResult {
|
pub enum EvalAltResult {
|
||||||
/// Syntax error.
|
/// Syntax error.
|
||||||
ErrorParsing(ParseError),
|
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.
|
/// Call to an unknown function. Wrapped value is the name of the function.
|
||||||
ErrorFunctionNotFound(String, Position),
|
ErrorFunctionNotFound(String, Position),
|
||||||
/// Function call has incorrect number of arguments.
|
/// Function call has incorrect number of arguments.
|
||||||
@ -36,10 +41,12 @@ pub enum EvalAltResult {
|
|||||||
/// String indexing out-of-bounds.
|
/// String indexing out-of-bounds.
|
||||||
/// Wrapped values are the current number of characters in the string and the index number.
|
/// Wrapped values are the current number of characters in the string and the index number.
|
||||||
ErrorStringBounds(usize, INT, Position),
|
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),
|
ErrorIndexingType(String, Position),
|
||||||
/// Trying to index into an array or string with an index that is not `i64`.
|
/// Trying to index into an array or string with an index that is not `i64`.
|
||||||
ErrorIndexExpr(Position),
|
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.
|
/// The guard expression in an `if` or `while` statement does not return a boolean value.
|
||||||
ErrorLogicGuard(Position),
|
ErrorLogicGuard(Position),
|
||||||
/// The `for` statement encounters a type that is not an iterator.
|
/// The `for` statement encounters a type that is not an iterator.
|
||||||
@ -53,9 +60,6 @@ pub enum EvalAltResult {
|
|||||||
/// Returned type is not the same as the required output type.
|
/// Returned type is not the same as the required output type.
|
||||||
/// Wrapped value is the type of the actual result.
|
/// Wrapped value is the type of the actual result.
|
||||||
ErrorMismatchOutputType(String, Position),
|
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.
|
/// Inappropriate member access.
|
||||||
ErrorDotExpr(String, Position),
|
ErrorDotExpr(String, Position),
|
||||||
/// Arithmetic error encountered. Wrapped value is the error message.
|
/// Arithmetic error encountered. Wrapped value is the error message.
|
||||||
@ -64,6 +68,7 @@ pub enum EvalAltResult {
|
|||||||
ErrorStackOverflow(Position),
|
ErrorStackOverflow(Position),
|
||||||
/// Run-time error encountered. Wrapped value is the error message.
|
/// Run-time error encountered. Wrapped value is the error message.
|
||||||
ErrorRuntime(String, Position),
|
ErrorRuntime(String, Position),
|
||||||
|
|
||||||
/// Breaking out of loops - not an error if within a loop.
|
/// Breaking out of loops - not an error if within a loop.
|
||||||
ErrorLoopBreak(Position),
|
ErrorLoopBreak(Position),
|
||||||
/// Not an error: Value returned from a script via the `return` keyword.
|
/// Not an error: Value returned from a script via the `return` keyword.
|
||||||
@ -74,6 +79,9 @@ pub enum EvalAltResult {
|
|||||||
impl EvalAltResult {
|
impl EvalAltResult {
|
||||||
pub(crate) fn desc(&self) -> &str {
|
pub(crate) fn desc(&self) -> &str {
|
||||||
match self {
|
match self {
|
||||||
|
#[cfg(not(feature = "no_std"))]
|
||||||
|
Self::ErrorReadingScriptFile(_, _) => "Cannot read from script file",
|
||||||
|
|
||||||
Self::ErrorParsing(p) => p.desc(),
|
Self::ErrorParsing(p) => p.desc(),
|
||||||
Self::ErrorFunctionNotFound(_, _) => "Function not found",
|
Self::ErrorFunctionNotFound(_, _) => "Function not found",
|
||||||
Self::ErrorFunctionArgsMismatch(_, _, _, _) => {
|
Self::ErrorFunctionArgsMismatch(_, _, _, _) => {
|
||||||
@ -81,9 +89,12 @@ impl EvalAltResult {
|
|||||||
}
|
}
|
||||||
Self::ErrorBooleanArgMismatch(_, _) => "Boolean operator expects boolean operands",
|
Self::ErrorBooleanArgMismatch(_, _) => "Boolean operator expects boolean operands",
|
||||||
Self::ErrorCharMismatch(_) => "Character expected",
|
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(_, _) => {
|
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 => {
|
Self::ErrorArrayBounds(_, index, _) if *index < 0 => {
|
||||||
"Array access expects non-negative index"
|
"Array access expects non-negative index"
|
||||||
@ -103,8 +114,6 @@ impl EvalAltResult {
|
|||||||
}
|
}
|
||||||
Self::ErrorAssignmentToConstant(_, _) => "Assignment to a constant variable",
|
Self::ErrorAssignmentToConstant(_, _) => "Assignment to a constant variable",
|
||||||
Self::ErrorMismatchOutputType(_, _) => "Output type is incorrect",
|
Self::ErrorMismatchOutputType(_, _) => "Output type is incorrect",
|
||||||
#[cfg(not(feature = "no_std"))]
|
|
||||||
Self::ErrorReadingScriptFile(_, _) => "Cannot read from script file",
|
|
||||||
Self::ErrorDotExpr(_, _) => "Malformed dot expression",
|
Self::ErrorDotExpr(_, _) => "Malformed dot expression",
|
||||||
Self::ErrorArithmetic(_, _) => "Arithmetic error",
|
Self::ErrorArithmetic(_, _) => "Arithmetic error",
|
||||||
Self::ErrorStackOverflow(_) => "Stack overflow",
|
Self::ErrorStackOverflow(_) => "Stack overflow",
|
||||||
@ -122,43 +131,52 @@ impl fmt::Display for EvalAltResult {
|
|||||||
let desc = self.desc();
|
let desc = self.desc();
|
||||||
|
|
||||||
match self {
|
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"))]
|
#[cfg(not(feature = "no_std"))]
|
||||||
Self::ErrorReadingScriptFile(path, err) => {
|
Self::ErrorReadingScriptFile(path, err) => {
|
||||||
write!(f, "{} '{}': {}", desc, path.display(), err)
|
write!(f, "{} '{}': {}", desc, path.display(), err)
|
||||||
}
|
}
|
||||||
|
|
||||||
Self::ErrorParsing(p) => write!(f, "Syntax error: {}", p),
|
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,
|
f,
|
||||||
"Function '{}' expects no argument but {} found ({})",
|
"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,
|
f,
|
||||||
"Function '{}' expects one argument but {} found ({})",
|
"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,
|
f,
|
||||||
"Function '{}' expects {} argument(s) but {} found ({})",
|
"Function '{}' expects {} argument(s) but {} found ({})",
|
||||||
fun, need, n, pos
|
fn_name, need, n, pos
|
||||||
),
|
),
|
||||||
Self::ErrorBooleanArgMismatch(op, pos) => {
|
Self::ErrorBooleanArgMismatch(op, pos) => {
|
||||||
write!(f, "{} operator expects boolean operands ({})", op, pos)
|
write!(f, "{} operator expects boolean operands ({})", op, pos)
|
||||||
@ -225,7 +243,8 @@ impl EvalAltResult {
|
|||||||
| Self::ErrorArrayBounds(_, _, pos)
|
| Self::ErrorArrayBounds(_, _, pos)
|
||||||
| Self::ErrorStringBounds(_, _, pos)
|
| Self::ErrorStringBounds(_, _, pos)
|
||||||
| Self::ErrorIndexingType(_, pos)
|
| Self::ErrorIndexingType(_, pos)
|
||||||
| Self::ErrorIndexExpr(pos)
|
| Self::ErrorNumericIndexExpr(pos)
|
||||||
|
| Self::ErrorStringIndexExpr(pos)
|
||||||
| Self::ErrorLogicGuard(pos)
|
| Self::ErrorLogicGuard(pos)
|
||||||
| Self::ErrorFor(pos)
|
| Self::ErrorFor(pos)
|
||||||
| Self::ErrorVariableNotFound(_, pos)
|
| Self::ErrorVariableNotFound(_, pos)
|
||||||
@ -256,7 +275,8 @@ impl EvalAltResult {
|
|||||||
| Self::ErrorArrayBounds(_, _, ref mut pos)
|
| Self::ErrorArrayBounds(_, _, ref mut pos)
|
||||||
| Self::ErrorStringBounds(_, _, ref mut pos)
|
| Self::ErrorStringBounds(_, _, ref mut pos)
|
||||||
| Self::ErrorIndexingType(_, ref mut pos)
|
| Self::ErrorIndexingType(_, ref mut pos)
|
||||||
| Self::ErrorIndexExpr(ref mut pos)
|
| Self::ErrorNumericIndexExpr(ref mut pos)
|
||||||
|
| Self::ErrorStringIndexExpr(ref mut pos)
|
||||||
| Self::ErrorLogicGuard(ref mut pos)
|
| Self::ErrorLogicGuard(ref mut pos)
|
||||||
| Self::ErrorFor(ref mut pos)
|
| Self::ErrorFor(ref mut pos)
|
||||||
| Self::ErrorVariableNotFound(_, ref mut pos)
|
| Self::ErrorVariableNotFound(_, ref mut pos)
|
||||||
|
@ -16,6 +16,7 @@ fn test_arrays() -> Result<(), EvalAltResult> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
#[cfg(not(feature = "no_object"))]
|
||||||
fn test_array_with_structs() -> Result<(), EvalAltResult> {
|
fn test_array_with_structs() -> Result<(), EvalAltResult> {
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
struct TestStruct {
|
struct TestStruct {
|
||||||
|
@ -11,7 +11,7 @@ fn test_fn() -> Result<(), EvalAltResult> {
|
|||||||
.expect_err("should be error")
|
.expect_err("should be error")
|
||||||
.error_type()
|
.error_type()
|
||||||
{
|
{
|
||||||
ParseErrorType::FnDuplicateParam(f, p) if f == "hello" && p == "x" => (),
|
ParseErrorType::FnDuplicatedParam(f, p) if f == "hello" && p == "x" => (),
|
||||||
_ => assert!(false, "wrong error"),
|
_ => assert!(false, "wrong error"),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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::<char>(r#"let x="hello"; x[2]"#)?, 'l');
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
engine.eval::<String>(r#"let y="hello"; y[2]='$'; y"#)?,
|
engine.eval::<String>(r#"let y="hello"; y[2]='$'; y"#)?,
|
||||||
"he$lo".to_string()
|
"he$lo"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -27,6 +27,7 @@ fn test_expressions() -> Result<(), EvalAltResult> {
|
|||||||
|
|
||||||
/// This example taken from https://github.com/jonathandturner/rhai/issues/115
|
/// This example taken from https://github.com/jonathandturner/rhai/issues/115
|
||||||
#[test]
|
#[test]
|
||||||
|
#[cfg(not(feature = "no_object"))]
|
||||||
fn test_expressions_eval() -> Result<(), EvalAltResult> {
|
fn test_expressions_eval() -> Result<(), EvalAltResult> {
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
struct AGENT {
|
struct AGENT {
|
||||||
|
@ -21,6 +21,7 @@ fn test_float() -> Result<(), EvalAltResult> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
#[cfg(not(feature = "no_object"))]
|
||||||
fn struct_with_float() -> Result<(), EvalAltResult> {
|
fn struct_with_float() -> Result<(), EvalAltResult> {
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
struct TestStruct {
|
struct TestStruct {
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
#![cfg(not(feature = "no_object"))]
|
||||||
|
|
||||||
use rhai::{Engine, EvalAltResult, RegisterFn, INT};
|
use rhai::{Engine, EvalAltResult, RegisterFn, INT};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -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::<INT>("let x = 1; x += 2; x")?, 3);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
engine.eval::<String>("let s = \"test\"; s += \"ing\"; s")?,
|
engine.eval::<String>("let s = \"test\"; s += \"ing\"; s")?,
|
||||||
"testing".to_string()
|
"testing"
|
||||||
);
|
);
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
66
tests/maps.rs
Normal file
66
tests/maps.rs
Normal 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(())
|
||||||
|
}
|
@ -12,12 +12,12 @@ fn test_math() -> Result<(), EvalAltResult> {
|
|||||||
|
|
||||||
#[cfg(not(feature = "only_i32"))]
|
#[cfg(not(feature = "only_i32"))]
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
engine.eval::<INT>("(-9223372036854775807).abs()")?,
|
engine.eval::<INT>("abs(-9223372036854775807)")?,
|
||||||
9_223_372_036_854_775_807
|
9_223_372_036_854_775_807
|
||||||
);
|
);
|
||||||
|
|
||||||
#[cfg(feature = "only_i32")]
|
#[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
|
// Overflow/underflow/division-by-zero errors
|
||||||
#[cfg(not(feature = "unchecked"))]
|
#[cfg(not(feature = "unchecked"))]
|
||||||
@ -26,7 +26,7 @@ fn test_math() -> Result<(), EvalAltResult> {
|
|||||||
{
|
{
|
||||||
assert!(matches!(
|
assert!(matches!(
|
||||||
engine
|
engine
|
||||||
.eval::<INT>("(-9223372036854775808).abs()")
|
.eval::<INT>("abs(-9223372036854775808)")
|
||||||
.expect_err("expects negation overflow"),
|
.expect_err("expects negation overflow"),
|
||||||
EvalAltResult::ErrorArithmetic(_, _)
|
EvalAltResult::ErrorArithmetic(_, _)
|
||||||
));
|
));
|
||||||
|
@ -1,8 +1,10 @@
|
|||||||
|
#![cfg(not(feature = "no_object"))]
|
||||||
|
|
||||||
use rhai::{Engine, EvalAltResult, RegisterFn, INT};
|
use rhai::{Engine, EvalAltResult, RegisterFn, INT};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_method_call() -> Result<(), EvalAltResult> {
|
fn test_method_call() -> Result<(), EvalAltResult> {
|
||||||
#[derive(Clone)]
|
#[derive(Debug, Clone, Eq, PartialEq)]
|
||||||
struct TestStruct {
|
struct TestStruct {
|
||||||
x: INT,
|
x: INT,
|
||||||
}
|
}
|
||||||
@ -24,11 +26,15 @@ fn test_method_call() -> Result<(), EvalAltResult> {
|
|||||||
engine.register_fn("update", TestStruct::update);
|
engine.register_fn("update", TestStruct::update);
|
||||||
engine.register_fn("new_ts", TestStruct::new);
|
engine.register_fn("new_ts", TestStruct::new);
|
||||||
|
|
||||||
let ts = engine.eval::<TestStruct>("let x = new_ts(); x.update(); x")?;
|
assert_eq!(
|
||||||
assert_eq!(ts.x, 1001);
|
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!(
|
||||||
assert_eq!(ts.x, 1);
|
engine.eval::<TestStruct>("let x = new_ts(); update(x); x")?,
|
||||||
|
TestStruct { x: 1 }
|
||||||
|
);
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -12,6 +12,7 @@ fn test_mismatched_op() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
#[cfg(not(feature = "no_object"))]
|
||||||
fn test_mismatched_op_custom_type() {
|
fn test_mismatched_op_custom_type() {
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
struct TestStruct {
|
struct TestStruct {
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
#![cfg(not(feature = "no_object"))]
|
||||||
|
|
||||||
///! This test simulates an external command object that is driven by a script.
|
///! This test simulates an external command object that is driven by a script.
|
||||||
use rhai::{Engine, EvalAltResult, RegisterFn, Scope, INT};
|
use rhai::{Engine, EvalAltResult, RegisterFn, Scope, INT};
|
||||||
use std::cell::RefCell;
|
use std::cell::RefCell;
|
||||||
|
@ -6,30 +6,24 @@ fn test_string() -> Result<(), EvalAltResult> {
|
|||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
engine.eval::<String>(r#""Test string: \u2764""#)?,
|
engine.eval::<String>(r#""Test string: \u2764""#)?,
|
||||||
"Test string: ❤".to_string()
|
"Test string: ❤"
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
engine.eval::<String>(r#""Test string: \x58""#)?,
|
engine.eval::<String>(r#""Test string: \x58""#)?,
|
||||||
"Test string: X".to_string()
|
"Test string: X"
|
||||||
);
|
);
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(engine.eval::<String>(r#""foo" + "bar""#)?, "foobar");
|
||||||
engine.eval::<String>(r#""foo" + "bar""#)?,
|
|
||||||
"foobar".to_string()
|
|
||||||
);
|
|
||||||
|
|
||||||
#[cfg(not(feature = "no_stdlib"))]
|
#[cfg(not(feature = "no_stdlib"))]
|
||||||
assert_eq!(
|
assert_eq!(engine.eval::<String>(r#""foo" + 123"#)?, "foo123");
|
||||||
engine.eval::<String>(r#""foo" + 123"#)?,
|
|
||||||
"foo123".to_string()
|
|
||||||
);
|
|
||||||
|
|
||||||
#[cfg(not(feature = "no_float"))]
|
#[cfg(not(feature = "no_float"))]
|
||||||
#[cfg(not(feature = "no_stdlib"))]
|
#[cfg(not(feature = "no_stdlib"))]
|
||||||
assert_eq!(
|
assert_eq!(engine.eval::<String>(r#""foo" + 123.4556"#)?, "foo123.4556");
|
||||||
engine.eval::<String>(r#""foo" + 123.4556"#)?,
|
|
||||||
"foo123.4556".to_string()
|
#[cfg(not(feature = "no_stdlib"))]
|
||||||
);
|
assert_eq!(engine.eval::<String>("(42).to_string()")?, "42");
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,12 @@
|
|||||||
use rhai::{Engine, EvalAltResult};
|
use rhai::{Engine, EvalAltResult, RegisterFn, INT};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_type_of() -> Result<(), EvalAltResult> {
|
fn test_type_of() -> Result<(), EvalAltResult> {
|
||||||
|
#[derive(Clone)]
|
||||||
|
struct TestStruct {
|
||||||
|
x: INT,
|
||||||
|
}
|
||||||
|
|
||||||
let mut engine = Engine::new();
|
let mut engine = Engine::new();
|
||||||
|
|
||||||
#[cfg(not(feature = "only_i32"))]
|
#[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");
|
assert_eq!(engine.eval::<String>("type_of(1.0 + 2.0)")?, "f64");
|
||||||
|
|
||||||
#[cfg(not(feature = "no_index"))]
|
#[cfg(not(feature = "no_index"))]
|
||||||
#[cfg(not(feature = "no_float"))]
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
engine.eval::<String>(r#"type_of([1.0, 2, "hello"])"#)?,
|
engine.eval::<String>(r#"type_of([true, 2, "hello"])"#)?,
|
||||||
"array"
|
"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");
|
assert_eq!(engine.eval::<String>(r#"type_of("hello")"#)?, "string");
|
||||||
|
|
||||||
#[cfg(not(feature = "only_i32"))]
|
#[cfg(not(feature = "only_i32"))]
|
||||||
|
Loading…
Reference in New Issue
Block a user