Merge pull request #120 from schungx/master

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

View File

@ -20,13 +20,14 @@ categories = [ "no-std", "embedded", "parser-implementations" ]
num-traits = "0.2.11" 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)

203
README.md
View File

@ -62,6 +62,7 @@ Optional features
| `unchecked` | Exclude arithmetic checking (such as overflows and division by zero). Beware that a bad script may panic the entire system! | | `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
@ -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.
@ -725,8 +750,8 @@ 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
@ -743,18 +768,18 @@ 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
-------------------- --------------------
@ -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_`))`
@ -1236,8 +1339,9 @@ 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) {
@ -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
@ -1323,9 +1427,12 @@ 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`
------------------- -------------------
@ -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
} }
``` ```
@ -1498,7 +1604,8 @@ if DECISION == 1 { // is a function call to the '==' function, and it retur
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
@ -1599,14 +1706,14 @@ 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_,

View File

@ -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() {}

View File

@ -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() {}

View File

@ -2,7 +2,7 @@
use crate::any::{Any, AnyExt, Dynamic}; use crate::any::{Any, AnyExt, Dynamic};
use crate::call::FuncArgs; use crate::call::FuncArgs;
use crate::engine::{Engine, FnAny, FnSpec, 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>());
} }
@ -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
@ -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,15 +691,15 @@ 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)
.map_err(|a| {
EvalAltResult::ErrorMismatchOutputType( EvalAltResult::ErrorMismatchOutputType(
self.map_type_name((*a).type_name()).to_string(), self.map_type_name((*a).type_name()).to_string(),
Position::none(), Position::none(),
) )
}) })
})
} }
pub(crate) fn eval_ast_with_scope_raw( pub(crate) fn eval_ast_with_scope_raw(
@ -709,35 +707,26 @@ impl<'e> Engine<'e> {
scope: &mut Scope, scope: &mut Scope,
retain_functions: bool, retain_functions: bool,
ast: &AST, ast: &AST,
) -> Result<Dynamic, EvalAltResult> {
fn eval_ast_internal(
engine: &mut Engine,
scope: &mut Scope,
retain_functions: bool,
ast: &AST,
) -> Result<Dynamic, EvalAltResult> { ) -> Result<Dynamic, EvalAltResult> {
if !retain_functions { if !retain_functions {
engine.clear_functions(); self.clear_functions();
} }
let statements = { let statements = {
let AST(statements, functions) = ast; let AST(statements, functions) = ast;
engine.load_script_functions(functions); self.load_script_functions(functions);
statements statements
}; };
let result = statements.iter().try_fold(().into_dynamic(), |_, stmt| { let result = statements
engine.eval_stmt(scope, stmt, 0) .iter()
})?; .try_fold(().into_dynamic(), |_, stmt| self.eval_stmt(scope, stmt, 0));
if !retain_functions { if !retain_functions {
engine.clear_functions(); self.clear_functions();
} }
Ok(result) result.or_else(|err| match err {
}
eval_ast_internal(self, scope, retain_functions, ast).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,15 +883,15 @@ 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)
.map_err(|a| {
EvalAltResult::ErrorMismatchOutputType( EvalAltResult::ErrorMismatchOutputType(
self.map_type_name((*a).type_name()).into(), self.map_type_name((*a).type_name()).into(),
Position::none(), Position::none(),
) )
}) })
})
} }
/// Optimize the `AST` with constants defined in an external Scope. /// Optimize the `AST` with constants defined in an external Scope.

View File

@ -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)

View File

@ -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,15 +686,15 @@ impl Engine<'_> {
idx, idx,
idx_pos, idx_pos,
)) ))
};
} }
} else {
// Error - cannot be indexed // Error - cannot be indexed
Err(EvalAltResult::ErrorIndexingType( Err(EvalAltResult::ErrorIndexingType(
self.map_type_name(val.type_name()).to_string(), self.map_type_name(val.type_name()).to_string(),
op_pos, op_pos,
)) ))
} }
}
/// Evaluate an index expression /// Evaluate an index expression
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
@ -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)?;
Self::update_indexed_value(
v,
idx as usize,
new_val.0.clone(),
new_val.1,
)
}) })
.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) }),
})
}
// 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)
} }

View File

@ -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() => {

View File

@ -145,7 +145,7 @@ macro_rules! def_register {
fn register_fn(&mut self, name: &str, f: FN) { fn register_fn(&mut self, name: &str, f: FN) {
let fn_name = name.to_string(); let fn_name = name.to_string();
let fun = move |args: &mut FnCallArgs, pos: Position| { let func = move |args: &mut FnCallArgs, pos: Position| {
// Check for length at the beginning to avoid per-element bound checks. // Check for length at the beginning to avoid per-element bound checks.
const NUM_ARGS: usize = count_args!($($par)*); const NUM_ARGS: usize = count_args!($($par)*);
@ -165,7 +165,7 @@ macro_rules! def_register {
let r = f($(($clone)($par)),*); let r = f($(($clone)($par)),*);
Ok(Box::new(r) as Dynamic) Ok(Box::new(r) as Dynamic)
}; };
self.register_fn_raw(name, 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));
} }
} }

View File

@ -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;

View File

@ -37,7 +37,15 @@ struct State<'a> {
engine: &'a Engine<'a>, engine: &'a Engine<'a>,
} }
impl State<'_> { impl<'a> State<'a> {
/// Create a new State.
pub fn new(engine: &'a Engine<'a>) -> Self {
Self {
changed: false,
constants: vec![],
engine,
}
}
/// Reset the state from dirty to clean. /// Reset the state from dirty to clean.
pub fn reset(&mut self) { pub fn reset(&mut self) {
self.changed = false; self.changed = false;
@ -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

View File

@ -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),
);
} }
} }
} }

View File

@ -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)

View File

@ -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 {

View File

@ -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"),
} }

View File

@ -12,7 +12,7 @@ fn test_chars() -> Result<(), EvalAltResult> {
assert_eq!(engine.eval::<char>(r#"let x="hello"; x[2]"#)?, 'l'); assert_eq!(engine.eval::<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"
); );
} }

View File

@ -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 {

View File

@ -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 {

View File

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

View File

@ -7,7 +7,7 @@ fn test_increment() -> Result<(), EvalAltResult> {
assert_eq!(engine.eval::<INT>("let x = 1; x += 2; x")?, 3); assert_eq!(engine.eval::<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
View File

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

View File

@ -12,12 +12,12 @@ fn test_math() -> Result<(), EvalAltResult> {
#[cfg(not(feature = "only_i32"))] #[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(_, _)
)); ));

View File

@ -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(())
} }

View File

@ -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 {

View File

@ -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;

View File

@ -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(())
} }

View File

@ -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"))]