diff --git a/Cargo.toml b/Cargo.toml index 063abd0b..43339787 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,10 +17,10 @@ keywords = [ "scripting" ] categories = [ "no-std", "embedded", "parser-implementations" ] [dependencies] -num-traits = "0.2.11" +num-traits = "*" [features] -#default = ["no_function", "no_index", "no_object", "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", "sync"] default = [] unchecked = [] # unchecked arithmetic no_stdlib = [] # no standard library of utility functions @@ -32,6 +32,7 @@ no_optimize = [] # no script optimizer optimize_full = [] # set optimization level to Full (default is Simple) - this is a feature used only to simplify testing only_i32 = [] # set INT=i32 (useful for 32-bit systems) only_i64 = [] # set INT=i64 (default) and disable support for all other integer types +sync = [] # restrict to only types that implement Send + Sync # compiling for no-std no_std = [ "num-traits/libm", "hashbrown", "core-error", "libm" ] diff --git a/README.md b/README.md index 969e727a..51b6d637 100644 --- a/README.md +++ b/README.md @@ -8,18 +8,21 @@ Rhai - Embedded Scripting for Rust ![crates.io](https://img.shields.io/crates/d/rhai) [![API Docs](https://docs.rs/rhai/badge.svg)](https://docs.rs/rhai/) -Rhai is an embedded scripting language and evaluation engine for Rust that gives a safe and easy way to add scripting to any application. +Rhai is an embedded scripting language and evaluation engine for Rust that gives a safe and easy way +to add scripting to any application. Rhai's current features set: * `no-std` support -* Easy integration with Rust functions and data types, supporting getter/setter methods +* Easy integration with Rust native functions and data types, including getter/setter methods * Easily call a script-defined function from Rust +* Freely pass variables/constants into a script via an external [`Scope`] * Fairly efficient (1 million iterations in 0.75 sec on my 5 year old laptop) * Low compile-time overhead (~0.6 sec debug/~3 sec release for script runner app) * Easy-to-use language similar to JS+Rust * Support for overloaded functions * Compiled script is optimized for repeat evaluations +* Support for minimal builds by excluding unneeded language features * Very few additional dependencies (right now only [`num-traits`](https://crates.io/crates/num-traits/) to do checked arithmetic operations); for [`no_std`] builds, a number of additional dependencies are pulled in to provide for functionalities that used to be in `std`. @@ -68,8 +71,10 @@ Optional features | `only_i32` | Set the system integer type to `i32` and disable all other integer types. `INT` is set to `i32`. | | `only_i64` | Set the system integer type to `i64` and disable all other integer types. `INT` is set to `i64`. | | `no_std` | Build for `no-std`. Notice that additional dependencies will be pulled in to replace `std` features. | +| `sync` | Restrict all values types to those that are `Send + Sync`. Under this feature, [`Engine`], [`Scope`] and `AST` are all `Send + Sync`. | -By default, Rhai includes all the standard functionalities in a small, tight package. Most features are here to opt-**out** of certain functionalities that are not needed. +By default, Rhai includes all the standard functionalities in a small, tight package. +Most features are here to opt-**out** of certain functionalities that are not needed. Excluding unneeded functionalities can result in smaller, faster builds as well as less bugs due to a more restricted language. [`unchecked`]: #optional-features @@ -82,6 +87,7 @@ Excluding unneeded functionalities can result in smaller, faster builds as well [`only_i32`]: #optional-features [`only_i64`]: #optional-features [`no_std`]: #optional-features +[`sync`]: #optional-features Related ------- @@ -207,12 +213,12 @@ Compiling a script file is also supported: let ast = engine.compile_file("hello_world.rhai".into())?; ``` -Rhai also allows working _backwards_ from the other direction - i.e. calling a Rhai-scripted function from Rust - via `call_fn`: +Rhai also allows working _backwards_ from the other direction - i.e. calling a Rhai-scripted function from Rust - +via `call_fn` or its cousins `call_fn1` (one argument) and `call_fn0` (no argument). ```rust -// Define a function in a script and load it into the Engine. -// Pass true to 'retain_functions' otherwise these functions will be cleared at the end of consume() -engine.consume(true, +// Define functions in a script. +let ast = engine.compile(true, r" // a function with two parameters: String and i64 fn hello(x, y) { @@ -223,18 +229,30 @@ engine.consume(true, fn hello(x) { x * 2 } + + // this one takes no parameters + fn hello() { + 42 + } ")?; -// Evaluate the function in the AST, passing arguments into the script as a tuple +// A custom scope can also contain any variables/constants available to the functions +let mut scope = Scope::new(); + +// Evaluate a function defined in the script, passing arguments into the script as a tuple // if there are more than one. Beware, arguments must be of the correct types because // Rhai does not have built-in type conversions. If arguments of the wrong types are passed, // the Engine will not find the function. -let result: i64 = engine.call_fn("hello", &ast, ( String::from("abc"), 123_i64 ) )?; -// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ put arguments in a tuple +let result: i64 = engine.call_fn(&mut scope, &ast, "hello", ( String::from("abc"), 123_i64 ) )?; +// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +// put arguments in a tuple -let result: i64 = engine.call_fn("hello", 123_i64)? -// ^^^^^^^ calls 'hello' with one parameter (no need for tuple) +let result: i64 = engine.call_fn1(&mut scope, &ast, "hello", 123_i64)? +// ^^^^^^^^ use 'call_fn1' for one argument + +let result: i64 = engine.call_fn0(&mut scope, &ast, "hello")? +// ^^^^^^^^ use 'call_fn0' for no arguments ``` Evaluate expressions only @@ -279,21 +297,22 @@ The following primitive types are supported natively: | **System floating-point** (current configuration, disabled with [`no_float`]) | `rhai::FLOAT` (`f32` or `f64`) | `"f32"` or `"f64"` | `"123.456"` etc. | | **Nothing/void/nil/null** (or whatever you want to call it) | `()` | `"()"` | `""` _(empty string)_ | -[`Dynamic`]: #values-and-types [`()`]: #values-and-types -All types are treated strictly separate by Rhai, meaning that `i32` and `i64` and `u32` are completely different - they even cannot be added together. This is very similar to Rust. +All types are treated strictly separate by Rhai, meaning that `i32` and `i64` and `u32` are completely different - +they even cannot be added together. This is very similar to Rust. -The default integer type is `i64`. If other integer types are not needed, it is possible to exclude them and make a smaller build with the [`only_i64`] feature. +The default integer type is `i64`. If other integer types are not needed, it is possible to exclude them and make a +smaller build with the [`only_i64`] feature. -If only 32-bit integers are needed, enabling the [`only_i32`] feature will remove support for all integer types other than `i32`, including `i64`. -This is useful on some 32-bit systems where using 64-bit integers incurs a performance penalty. +If only 32-bit integers are needed, enabling the [`only_i32`] feature will remove support for all integer types other than `i32`, +including `i64`. This is useful on some 32-bit systems where using 64-bit integers incurs a performance penalty. If no floating-point is needed or supported, use the [`no_float`] feature to remove it. 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`. +The `type_of` function detects the actual type of a value. This is useful because all variables are [`Dynamic`] in nature. ```rust // Use 'type_of()' to get the actual types of values @@ -313,6 +332,77 @@ if type_of(x) == "string" { } ``` +`Dynamic` values +---------------- + +[`Dynamic`]: #dynamic-values + +A `Dynamic` value can be _any_ type. However, if the [`sync`] feature is used, then all types must be `Send + Sync`. + +Because [`type_of()`] a `Dynamic` value returns the type of the actual value, it is usually used to perform type-specific +actions based on the actual value's type. + +```rust +let mystery = get_some_dynamic_value(); + +if type_of(mystery) == "i64" { + print("Hey, I got an integer here!"); +} else if type_of(mystery) == "f64" { + print("Hey, I got a float here!"); +} else if type_of(mystery) == "string" { + print("Hey, I got a string here!"); +} else if type_of(mystery) == "bool" { + print("Hey, I got a boolean here!"); +} else if type_of(mystery) == "array" { + print("Hey, I got an array here!"); +} else if type_of(mystery) == "map" { + print("Hey, I got an object map here!"); +} else if type_of(mystery) == "TestStruct" { + print("Hey, I got the TestStruct custom type here!"); +} else { + print("I don't know what this is: " + type_of(mystery)); +} +``` + +In Rust, sometimes a `Dynamic` forms part of a returned value - a good example is an array with `Dynamic` elements, +or an object map with `Dynamic` property values. To get the _real_ values, the actual value types _must_ be known in advance. +There is no easy way for Rust to decide, at run-time, what type the `Dynamic` value is (short of using the `type_name` +function and match against the name). + +A `Dynamic` value's actual type can be checked via the `is` method. +The `cast` method (from the `rhai::AnyExt` trait) then converts the value into a specific, known type. +Alternatively, use the `try_cast` method which does not panic but returns an error when the cast fails. + +```rust +use rhai::AnyExt; // Pull in the trait. + +let list: Array = engine.eval("...")?; // return type is 'Array' +let item = list[0]; // an element in an 'Array' is 'Dynamic' + +item.is::() == true; // 'is' returns whether a 'Dynamic' value is of a particular type + +let value = item.cast::(); // if the element is 'i64', this succeeds; otherwise it panics +let value: i64 = item.cast(); // type can also be inferred + +let value = item.try_cast::()?; // 'try_cast' does not panic when the cast fails, but returns an error +``` + +The `type_name` method gets the name of the actual type as a static string slice, which you may match against. + +```rust +use rhai::Any; // Pull in the trait. + +let list: Array = engine.eval("...")?; // return type is 'Array' +let item = list[0]; // an element in an 'Array' is 'Dynamic' + +match item.type_name() { // 'type_name' returns the name of the actual Rust type + "i64" => ... + "std::string::String" => ... + "bool" => ... + "path::to::module::TestStruct" => ... +} +``` + Value conversions ----------------- @@ -325,11 +415,11 @@ That's about it. For other conversions, register custom conversion functions. ```rust let x = 42; -let y = x * 100.0; // <- error: cannot multiply i64 with f64 -let y = x.to_float() * 100.0; // works -let z = y.to_int() + x; // works +let y = x * 100.0; // <- error: cannot multiply i64 with f64 +let y = x.to_float() * 100.0; // works +let z = y.to_int() + x; // works -let c = 'X'; // character +let c = 'X'; // character print("c is '" + c + "' and its code is " + c.to_int()); // prints "c is 'X' and its code is 88" ``` @@ -342,7 +432,7 @@ To call these functions, they need to be registered with the [`Engine`]. ```rust use rhai::{Engine, EvalAltResult}; use rhai::RegisterFn; // use `RegisterFn` trait for `register_fn` -use rhai::{Dynamic, RegisterDynamicFn}; // use `RegisterDynamicFn` trait for `register_dynamic_fn` +use rhai::{Any, Dynamic, RegisterDynamicFn}; // use `RegisterDynamicFn` trait for `register_dynamic_fn` // Normal function fn add(x: i64, y: i64) -> i64 { @@ -351,7 +441,7 @@ fn add(x: i64, y: i64) -> i64 { // Function that returns a Dynamic value fn get_an_any() -> Dynamic { - Box::new(42_i64) + (42_i64).into_dynamic() // 'into_dynamic' is defined by the 'rhai::Any' trait } fn main() -> Result<(), EvalAltResult> @@ -375,14 +465,17 @@ fn main() -> Result<(), EvalAltResult> } ``` -To return a [`Dynamic`] value from a Rust function, simply `Box` it and return it. +To return a [`Dynamic`] value from a Rust function, use the `into_dynamic()` method +(under the `rhai::Any` trait) to convert it. ```rust +use rhai::Any; // Pull in the trait + fn decide(yes_no: bool) -> Dynamic { if yes_no { - Box::new(42_i64) + (42_i64).into_dynamic() } else { - Box::new("hello world!".to_string()) // remember &str is not supported + String::from("hello!").into_dynamic() // remember &str is not supported by Rhai } } ``` @@ -390,7 +483,8 @@ fn decide(yes_no: bool) -> Dynamic { Generic functions ----------------- -Generic functions can be used in Rhai, but separate instances for each concrete type must be registered separately: +Rust generic functions can be used in Rhai, but separate instances for each concrete type must be registered separately. +Essentially this is a form of function overloading as Rhai does not support generics. ```rust use std::fmt::Display; @@ -411,15 +505,17 @@ fn main() } ``` -This example shows how to register multiple functions (or, in this case, multiple instances of the same function) to the same name in script. -This enables function overloading based on the number and types of parameters. +This example shows how to register multiple functions (or, in this case, multiple overloaded versions of the same function) +under the same name. This enables function overloading based on the number and types of parameters. Fallible functions ------------------ -If a function is _fallible_ (i.e. it returns a `Result<_, Error>`), it can be registered with `register_result_fn` (using the `RegisterResultFn` trait). +If a function is _fallible_ (i.e. it returns a `Result<_, Error>`), it can be registered with `register_result_fn` +(using the `RegisterResultFn` trait). -The function must return `Result<_, EvalAltResult>`. `EvalAltResult` implements `From<&str>` and `From` etc. and the error text gets converted into `EvalAltResult::ErrorRuntime`. +The function must return `Result<_, EvalAltResult>`. `EvalAltResult` implements `From<&str>` and `From` etc. +and the error text gets converted into `EvalAltResult::ErrorRuntime`. ```rust use rhai::{Engine, EvalAltResult, Position}; @@ -531,14 +627,15 @@ let mut engine = Engine::new(); engine.register_type::(); ``` -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 native types, methods and functions with the [`Engine`], we need to register them. +There are some convenience functions to help with these. Below, the `update` and `new` methods are registered 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.* ```rust -engine.register_fn("update", TestStruct::update); // registers 'update(&mut ts)' -engine.register_fn("new_ts", TestStruct::new); // registers 'new' +engine.register_fn("update", TestStruct::update); // registers 'update(&mut TestStruct)' +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. @@ -550,8 +647,9 @@ let result = engine.eval::("let x = new_ts(); x.update(); x")?; println!("result: {}", result.field); // prints 42 ``` -In fact, any function with a first argument (either by copy or via a `&mut` reference) can be used as a method-call on that type because internally they are the same thing: -methods on a type is implemented as a functions taking an first argument. +In fact, any function with a first argument (either by copy or via a `&mut` reference) can be used as a method call +on that type because internally they are the same thing: +methods on a type is implemented as a functions taking a `&mut` first argument. ```rust fn foo(ts: &mut TestStruct) -> i64 { @@ -565,7 +663,8 @@ let result = engine.eval::("let x = new_ts(); x.foo()")?; println!("result: {}", result); // prints 1 ``` -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. +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. @@ -628,16 +727,21 @@ 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 ---------------------------------- +`Scope` - Initializing and maintaining state +------------------------------------------- -[`Scope`]: #initializing-and-maintaining-state +[`Scope`]: #scope---initializing-and-maintaining-state -By default, Rhai treats each [`Engine`] invocation as a fresh one, persisting only the functions that have been defined but no global state. -This gives each evaluation a clean starting slate. In order to continue using the same global state from one invocation to the next, -such a state must be manually created and passed in. +By default, Rhai treats each [`Engine`] invocation as a fresh one, persisting only the functions that have been defined +but no global state. This gives each evaluation a clean starting slate. In order to continue using the same global state +from one invocation to the next, such a state must be manually created and passed in. -In this example, a global state object (a `Scope`) is created with a few initialized variables, then the same state is threaded through multiple invocations: +All `Scope` variables are [`Dynamic`], meaning they can store values of any type. If the [`sync`] feature is used, however, +then only types that are `Send + Sync` are supported, and the entire `Scope` itself will also be `Send + Sync`. +This is extremely useful in multi-threaded applications. + +In this example, a global state object (a `Scope`) is created with a few initialized variables, then the same state is +threaded through multiple invocations: ```rust use rhai::{Engine, Scope, EvalAltResult}; @@ -649,12 +753,14 @@ fn main() -> Result<(), EvalAltResult> // First create the state let mut scope = Scope::new(); - // Then push some initialized variables into the state - // NOTE: Remember the system number types in Rhai are i64 (i32 if 'only_i32') ond f64. - // Better stick to them or it gets hard working with the script. + // Then push (i.e. add) some initialized variables into the state. + // Remember the system number types in Rhai are i64 (i32 if 'only_i32') ond f64. + // Better stick to them or it gets hard working with the script. scope.push("y", 42_i64); scope.push("z", 999_i64); - scope.push("s", "hello, world!".to_string()); // remember to use 'String', not '&str' + + // 'set_value' adds a variable when one doesn't exist + scope.set_value("s", "hello, world!".to_string()); // remember to use 'String', not '&str' // First invocation engine.eval_with_scope::<()>(&mut scope, r" @@ -665,10 +771,14 @@ fn main() -> Result<(), EvalAltResult> // Second invocation using the same state let result = engine.eval_with_scope::(&mut scope, "x")?; - println!("result: {}", result); // prints 979 + println!("result: {}", result); // prints 979 - // Variable y is changed in the script - assert_eq!(scope.get_value::("y").expect("variable x should exist"), 1); + // Variable y is changed in the script - read it with 'get_value' + assert_eq!(scope.get_value::("y").expect("variable y should exist"), 1); + + // We can modify scope variables directly with 'set_value' + scope.set_value("y", 42_i64); + assert_eq!(scope.get_value::("y").expect("variable y should exist"), 42); Ok(()) } @@ -738,7 +848,8 @@ Variables Variables in Rhai follow normal C naming rules (i.e. must contain only ASCII letters, digits and underscores '`_`'). -Variable names must start with an ASCII letter or an underscore '`_`', must contain at least one ASCII letter, and must start with an ASCII letter before a digit. +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. Therefore, names like '`_`', '`_42`', '`3a`' etc. are not legal variable names, but '`_c3po`' and '`r2d2`' are. Variable names are also case _sensitive_. @@ -789,7 +900,8 @@ Integer numbers follow C-style format with support for decimal, binary ('`0b`'), The default system integer type (also aliased to `INT`) is `i64`. It can be turned into `i32` via the [`only_i32`] feature. -Floating-point numbers are also supported if not disabled with [`no_float`]. The default system floating-point type is `i64` (also aliased to `FLOAT`). +Floating-point numbers are also supported if not disabled with [`no_float`]. The default system floating-point type is `i64` +(also aliased to `FLOAT`). '`_`' separators can be added freely and are ignored within a number. @@ -845,7 +957,8 @@ number = -5 - +5; Numeric functions ----------------- -The following standard functions (defined in the standard library but excluded if [`no_stdlib`]) operate on `i8`, `i16`, `i32`, `i64`, `f32` and `f64` only: +The following standard functions (defined in the standard library but excluded if [`no_stdlib`]) operate on +`i8`, `i16`, `i32`, `i64`, `f32` and `f64` only: | Function | Description | | ------------ | --------------------------------- | @@ -871,18 +984,21 @@ The following standard functions (defined in the standard library but excluded i Strings and Chars ----------------- -String and char literals follow C-style formatting, with support for Unicode ('`\u`_xxxx_' or '`\U`_xxxxxxxx_') and hex ('`\x`_xx_') escape sequences. +String and char literals follow C-style formatting, with support for Unicode ('`\u`_xxxx_' or '`\U`_xxxxxxxx_') and +hex ('`\x`_xx_') escape sequences. -Hex sequences map to ASCII characters, while '`\u`' maps to 16-bit common Unicode code points and '`\U`' maps the full, 32-bit extended Unicode code points. +Hex sequences map to ASCII characters, while '`\u`' maps to 16-bit common Unicode code points and '`\U`' maps the full, +32-bit extended Unicode code points. Internally Rhai strings are stored as UTF-8 just like Rust (they _are_ Rust `String`s!), but there are major differences. In Rhai a string is the same as an array of Unicode characters and can be directly indexed (unlike Rust). -This is similar to most other languages where strings are internally represented not as UTF-8 but as arrays of multi-byte Unicode characters. +This is similar to most other languages where strings are internally represented not as UTF-8 but as arrays of multi-byte +Unicode characters. Individual characters within a Rhai string can also be replaced just as if the string is an array of Unicode characters. In Rhai, there is also no separate concepts of `String` and `&str` as in Rust. -Strings can be built up from other strings and types via the `+` operator (provided by the standard library but excluded if [`no_stdlib`]). -This is particularly useful when printing output. +Strings can be built up from other strings and types via the `+` operator (provided by the standard library but excluded +if [`no_stdlib`]). This is particularly useful when printing output. [`type_of()`] a string returns `"string"`. @@ -972,24 +1088,25 @@ Arrays Arrays are first-class citizens in Rhai. Like C, arrays are accessed with zero-based, non-negative integer indices. Array literals are built within square brackets '`[`' ... '`]`' and separated by commas '`,`'. +All elements stored in an array are [`Dynamic`], and the array can freely grow or shrink with elements added or removed. -The Rust type of a Rhai array is `rhai::Array`. - -[`type_of()`] an array returns `"array"`. +The Rust type of a Rhai array is `rhai::Array`. [`type_of()`] an array returns `"array"`. Arrays are disabled via the [`no_index`] feature. The following functions (defined in the standard library but excluded if [`no_stdlib`]) operate on arrays: -| Function | Description | -| ---------- | ------------------------------------------------------------------------------------- | -| `push` | inserts an element at the end | -| `pop` | removes the last element and returns it ([`()`] if empty) | -| `shift` | removes the first element and returns it ([`()`] if empty) | -| `len` | returns the number of elements | -| `pad` | pads the array with an element until a specified length | -| `clear` | empties the array | -| `truncate` | cuts off the array at exactly a specified length (discarding all subsequent elements) | +| Function | Description | +| ------------ | ------------------------------------------------------------------------------------- | +| `push` | inserts an element at the end | +| `append` | concatenates the second array to the end of the first | +| `+` operator | concatenates the first array with the second | +| `pop` | removes the last element and returns it ([`()`] if empty) | +| `shift` | removes the first element and returns it ([`()`] if empty) | +| `len` | returns the number of elements | +| `pad` | pads the array with an element until a specified length | +| `clear` | empties the array | +| `truncate` | cuts off the array at exactly a specified length (discarding all subsequent elements) | Examples: @@ -1029,6 +1146,10 @@ last == 5; print(y.len()); // prints 3 +for item in y { // arrays can be iterated with a 'for' statement + print(item); +} + y.pad(10, "hello"); // pad the array up to 10 elements print(y.len()); // prints 10 @@ -1051,7 +1172,7 @@ engine.register_fn("push", |list: &mut Array, item: MyType| list.push(Box::new(i Object maps ----------- -Object maps are dictionaries. Properties of any type (`Dynamic`) can be freely added and retrieved. +Object maps are dictionaries. Properties are all [`Dynamic`] and 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. @@ -1062,19 +1183,21 @@ The index notation allows setting/getting properties of arbitrary names (even th **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"`. +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 | +| 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 | +| `mixin` | mixes in all the properties of the second object map to the first (values of properties with the same names replace the existing values) | +| `+` operator | merges the first object map with the second | +| `keys` | returns an array of all the property names (in random order) | +| `values` | returns an array of all the property values (in random order) | Examples: @@ -1121,6 +1244,14 @@ y["xyz"] == (); print(y.len()); // prints 3 +for name in keys(y) { // get an array of all the property names via the 'keys' function + print(name); +} + +for val in values(y) { // get an array of all the property values via the 'values' function + print(val); +} + y.clear(); // empty the object map print(y.len()); // prints 0 @@ -1131,8 +1262,8 @@ Comparison operators Comparing most values of the same data type work out-of-the-box for standard types supported by the system. -However, if the [`no_stdlib`] feature is turned on, comparisons can only be made between restricted system -types - `INT` (`i64` or `i32` depending on [`only_i32`] and [`only_i64`]), `f64` (if not [`no_float`]), string, array, `bool`, `char`. +However, if the [`no_stdlib`] feature is turned on, comparisons can only be made between restricted system types - +`INT` (`i64` or `i32` depending on [`only_i32`] and [`only_i64`]), `f64` (if not [`no_float`]), string, array, `bool`, `char`. ```rust 42 == 42; // true @@ -1242,9 +1373,10 @@ x == (); let x = 10; while x > 0 { + x = x - 1; + if x < 6 { continue; } // skip to the next iteration print(x); if x == 5 { break; } // break out of while loop - x = x - 1; } ``` @@ -1255,8 +1387,9 @@ Infinite `loop` let x = 10; loop { - print(x); x = x - 1; + if x > 5 { continue; } // skip to the next iteration + print(x); if x == 0 { break; } // break out of loop } ``` @@ -1271,14 +1404,34 @@ let array = [1, 3, 5, 7, 9, 42]; // Iterate through array for x in array { + if x > 10 { continue; } // skip to the next iteration print(x); - if x == 42 { break; } + if x == 42 { break; } // break out of for loop } // The 'range' function allows iterating from first to last-1 for x in range(0, 50) { + if x > 10 { continue; } // skip to the next iteration print(x); - if x == 42 { break; } + if x == 42 { break; } // break out of for loop +} + + +// The 'range' function also takes a step +for x in range(0, 50, 3) { // step by 3 + if x > 10 { continue; } // skip to the next iteration + print(x); + if x == 42 { break; } // break out of for loop +} + +// Iterate through the values of an object map +let map = #{a:1, b:3, c:5, d:7, e:9}; + +// Remember that keys are returned in random order +for x in keys(map) { + if x > 10 { continue; } // skip to the next iteration + print(x); + if x == 42 { break; } // break out of for loop } ``` @@ -1294,8 +1447,8 @@ return 123 + 456; // returns 579 Errors and `throw`-ing exceptions -------------------------------- -All of [`Engine`]'s evaluation/consuming methods return `Result` with `EvalAltResult` holding error information. -To deliberately return an error during an evaluation, use the `throw` keyword. +All of [`Engine`]'s evaluation/consuming methods return `Result` with `EvalAltResult` +holding error information. To deliberately return an error during an evaluation, use the `throw` keyword. ```rust if some_bad_condition_has_happened { @@ -1305,7 +1458,7 @@ if some_bad_condition_has_happened { 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_ `))` with the exception text captured by the first parameter. ```rust @@ -1354,7 +1507,8 @@ print(add2(42)); // prints 44 ### No access to external scope -Functions can only access their parameters. They cannot access external variables (even _global_ variables). +Functions are not _closures_. They do not capture the calling environment and can only access their own parameters. +They cannot access variables external to the function itself. ```rust let x = 42; @@ -1365,7 +1519,8 @@ fn foo() { x } // <- syntax error: variable 'x' doesn't exist ### Passing arguments by value Functions defined in script always take [`Dynamic`] parameters (i.e. the parameter can be of any type). -It is important to remember that all arguments are passed by _value_, so all functions are _pure_ (i.e. they never modify their arguments). +It is important to remember that all arguments are passed by _value_, so all functions are _pure_ +(i.e. they never modifytheir arguments). Any update to an argument will **not** be reflected back to the caller. This can introduce subtle bugs, if not careful. ```rust @@ -1390,7 +1545,7 @@ fn add(x, y) { // The following will not compile fn do_addition(x) { - fn add_y(n) { // functions cannot be defined inside another function + fn add_y(n) { // <- syntax error: functions cannot be defined inside another function n + y } @@ -1493,8 +1648,8 @@ For example, in the following: } ``` -Rhai attempts to eliminate _dead code_ (i.e. code that does nothing, for example an expression by itself as a statement, which is allowed in Rhai). -The above script optimizes to: +Rhai attempts to eliminate _dead code_ (i.e. code that does nothing, for example an expression by itself as a statement, +which is allowed in Rhai). The above script optimizes to: ```rust { @@ -1622,21 +1777,22 @@ Function side effect considerations ---------------------------------- All of Rhai's built-in functions (and operators which are implemented as functions) are _pure_ (i.e. they do not mutate state -nor cause side any effects, with the exception of `print` and `debug` which are handled specially) so using [`OptimizationLevel::Full`] -is usually quite safe _unless_ you register your own types and functions. +nor cause side any effects, with the exception of `print` and `debug` which are handled specially) so using +[`OptimizationLevel::Full`] is usually quite safe _unless_ you register your own types and functions. If custom functions are registered, they _may_ be called (or maybe not, if the calls happen to lie within a pruned code block). -If custom functions are registered to replace built-in operators, they will also be called when the operators are used (in an `if` -statement, for example) and cause side-effects. +If custom functions are registered to replace built-in operators, they will also be called when the operators are used +(in an `if` statement, for example) and cause side-effects. Function volatility considerations --------------------------------- -Even if a custom function does not mutate state nor cause side effects, it may still be _volatile_, i.e. it _depends_ on the external -environment and not _pure_. A perfect example is a function that gets the current time - obviously each run will return a different value! -The optimizer, when using [`OptimizationLevel::Full`], _assumes_ that all functions are _pure_, so when it finds constant arguments -it will eagerly run execute the function call. This causes the script to behave differently from the intended semantics because -essentially the result of the function call will always be the same value. +Even if a custom function does not mutate state nor cause side effects, it may still be _volatile_, i.e. it _depends_ +on the external environment and is not _pure_. A perfect example is a function that gets the current time - +obviously each run will return a different value! The optimizer, when using [`OptimizationLevel::Full`], _assumes_ that +all functions are _pure_, so when it finds constant arguments it will eagerly execute the function call. +This causes the script to behave differently from the intended semantics because essentially the result of the function call +will always be the same value. Therefore, **avoid using [`OptimizationLevel::Full`]** if you intend to register non-_pure_ custom types and/or functions. @@ -1671,15 +1827,15 @@ print("start!"); print("end!"); ``` -In the script above, if `my_decision` holds anything other than a boolean value, the script should have been terminated due to a type error. -However, after optimization, the entire `if` statement is removed (because an access to `my_decision` produces no side effects), -thus the script silently runs to completion without errors. +In the script above, if `my_decision` holds anything other than a boolean value, the script should have been terminated due to +a type error. However, after optimization, the entire `if` statement is removed (because an access to `my_decision` produces +no side effects), thus the script silently runs to completion without errors. Turning off optimizations ------------------------- -It is usually a bad idea to depend on a script failing or such kind of subtleties, but if it turns out to be necessary (why? I would never guess), -turn it off by setting the optimization level to [`OptimizationLevel::None`]. +It is usually a bad idea to depend on a script failing or such kind of subtleties, but if it turns out to be necessary +(why? I would never guess), turn it off by setting the optimization level to [`OptimizationLevel::None`]. ```rust let engine = rhai::Engine::new(); diff --git a/examples/repl.rs b/examples/repl.rs index 4ba75597..d4679842 100644 --- a/examples/repl.rs +++ b/examples/repl.rs @@ -9,10 +9,6 @@ use std::{ }; fn print_error(input: &str, err: EvalAltResult) { - fn padding(pad: &str, len: usize) -> String { - iter::repeat(pad).take(len).collect::() - } - let lines: Vec<_> = input.trim().split('\n').collect(); let line_no = if lines.len() > 1 { @@ -54,8 +50,9 @@ fn print_error(input: &str, err: EvalAltResult) { }; println!( - "{}^ {}", - padding(" ", line_no.len() + p.position().unwrap() - 1), + "{0:>1$} {2}", + "^", + line_no.len() + p.position().unwrap(), err_text.replace(&pos_text, "") ); } @@ -80,8 +77,9 @@ fn main() { let mut scope = Scope::new(); let mut input = String::new(); - let mut ast_u: Option = None; - let mut ast: Option = None; + let mut main_ast = AST::new(); + let mut ast_u = AST::new(); + let mut ast = AST::new(); println!("Rhai REPL tool"); println!("=============="); @@ -115,6 +113,10 @@ fn main() { let script = input.trim(); + if script.is_empty() { + continue; + } + // Implement standard commands match script { "help" => { @@ -123,21 +125,13 @@ fn main() { } "exit" | "quit" => break, // quit "astu" => { - if matches!(&ast_u, Some(_)) { - // print the last un-optimized AST - println!("{:#?}", ast_u.as_ref().unwrap()); - } else { - println!("()"); - } + // print the last un-optimized AST + println!("{:#?}", &ast_u); continue; } "ast" => { - if matches!(&ast, Some(_)) { - // print the last AST - println!("{:#?}", ast.as_ref().unwrap()); - } else { - println!("()"); - } + // print the last AST + println!("{:#?}", &ast); continue; } _ => (), @@ -147,26 +141,35 @@ fn main() { .compile_with_scope(&scope, &script) .map_err(EvalAltResult::ErrorParsing) .and_then(|r| { - ast_u = Some(r); + ast_u = r.clone(); #[cfg(not(feature = "no_optimize"))] { engine.set_optimization_level(OptimizationLevel::Full); - ast = Some(engine.optimize_ast(&scope, ast_u.as_ref().unwrap())); + ast = engine.optimize_ast(&scope, r); engine.set_optimization_level(OptimizationLevel::None); } #[cfg(feature = "no_optimize")] { - ast = ast_u.clone(); + ast = r; } - engine - .consume_ast_with_scope(&mut scope, true, ast.as_ref().unwrap()) + // Merge the AST into the main + main_ast = main_ast.merge(&ast); + + // Evaluate + let result = engine + .consume_ast_with_scope(&mut scope, &main_ast) .or_else(|err| match err { EvalAltResult::Return(_, _) => Ok(()), err => Err(err), - }) + }); + + // Throw away all the statements, leaving only the functions + main_ast.retain_functions(); + + result }) { println!(); diff --git a/examples/rhai_runner.rs b/examples/rhai_runner.rs index 20f6226e..9a5cfe59 100644 --- a/examples/rhai_runner.rs +++ b/examples/rhai_runner.rs @@ -5,10 +5,6 @@ use rhai::OptimizationLevel; use std::{env, fs::File, io::Read, iter, process::exit}; -fn padding(pad: &str, len: usize) -> String { - iter::repeat(pad).take(len).collect::() -} - fn eprint_error(input: &str, err: EvalAltResult) { fn eprint_line(lines: &[&str], line: usize, pos: usize, err: &str) { let line_no = format!("{}: ", line); @@ -16,8 +12,9 @@ fn eprint_error(input: &str, err: EvalAltResult) { eprintln!("{}{}", line_no, lines[line - 1]); eprintln!( - "{}^ {}", - padding(" ", line_no.len() + pos - 1), + "{:>1$} {2}", + "^", + line_no.len() + pos, err.replace(&pos_text, "") ); eprintln!(""); @@ -75,10 +72,10 @@ fn main() { exit(1); } - if let Err(err) = engine.consume(false, &contents) { - eprintln!("{}", padding("=", filename.len())); + if let Err(err) = engine.consume(&contents) { + eprintln!("{:=<1$}", "", filename.len()); eprintln!("{}", filename); - eprintln!("{}", padding("=", filename.len())); + eprintln!("{:=<1$}", "", filename.len()); eprintln!(""); eprint_error(&contents, err); diff --git a/src/any.rs b/src/any.rs index e92b3e9d..1a7bda22 100644 --- a/src/any.rs +++ b/src/any.rs @@ -1,19 +1,26 @@ //! Helper module which defines the `Any` trait to to allow dynamic value handling. use crate::stdlib::{ - any::{type_name, Any as StdAny, TypeId}, + any::{type_name, TypeId}, boxed::Box, fmt, }; /// An raw value of any type. +/// +/// Currently, `Variant` is not `Send` nor `Sync`, so it can practically be any type. +/// Turn on the `sync` feature to restrict it to only types that implement `Send + Sync`. pub type Variant = dyn Any; /// A boxed dynamic type containing any value. +/// +/// Currently, `Dynamic` is not `Send` nor `Sync`, so it can practically be any type. +/// Turn on the `sync` feature to restrict it to only types that implement `Send + Sync`. pub type Dynamic = Box; /// A trait covering any type. -pub trait Any: StdAny { +#[cfg(feature = "sync")] +pub trait Any: crate::stdlib::any::Any + Send + Sync { /// Get the `TypeId` of this type. fn type_id(&self) -> TypeId; @@ -28,7 +35,44 @@ pub trait Any: StdAny { fn _closed(&self) -> _Private; } -impl Any for T { +#[cfg(feature = "sync")] +impl Any for T { + fn type_id(&self) -> TypeId { + TypeId::of::() + } + + fn type_name(&self) -> &'static str { + type_name::() + } + + fn into_dynamic(&self) -> Dynamic { + Box::new(self.clone()) + } + + fn _closed(&self) -> _Private { + _Private + } +} + +/// A trait covering any type. +#[cfg(not(feature = "sync"))] +pub trait Any: crate::stdlib::any::Any { + /// Get the `TypeId` of this type. + fn type_id(&self) -> TypeId; + + /// Get the name of this type. + fn type_name(&self) -> &'static str; + + /// Convert into `Dynamic`. + fn into_dynamic(&self) -> Dynamic; + + /// This trait may only be implemented by `rhai`. + #[doc(hidden)] + fn _closed(&self) -> _Private; +} + +#[cfg(not(feature = "sync"))] +impl Any for T { fn type_id(&self) -> TypeId { TypeId::of::() } @@ -48,15 +92,13 @@ impl Any for T { impl Variant { /// Is this `Variant` a specific type? - pub(crate) fn is(&self) -> bool { - let t = TypeId::of::(); - let boxed = ::type_id(self); - - t == boxed + pub fn is(&self) -> bool { + TypeId::of::() == ::type_id(self) } /// Get a reference of a specific type to the `Variant`. - pub(crate) fn downcast_ref(&self) -> Option<&T> { + /// Returns `None` if the cast fails. + pub fn downcast_ref(&self) -> Option<&T> { if self.is::() { unsafe { Some(&*(self as *const Variant as *const T)) } } else { @@ -65,7 +107,8 @@ impl Variant { } /// Get a mutable reference of a specific type to the `Variant`. - pub(crate) fn downcast_mut(&mut self) -> Option<&mut T> { + /// Returns `None` if the cast fails. + pub fn downcast_mut(&mut self) -> Option<&mut T> { if self.is::() { unsafe { Some(&mut *(self as *mut Variant as *mut T)) } } else { @@ -82,14 +125,21 @@ impl fmt::Debug for Variant { impl Clone for Dynamic { fn clone(&self) -> Self { - Any::into_dynamic(self.as_ref()) + self.as_ref().into_dynamic() } } /// An extension trait that allows down-casting a `Dynamic` value to a specific type. pub trait AnyExt: Sized { /// Get a copy of a `Dynamic` value as a specific type. - fn downcast(self) -> Result, Self>; + fn try_cast(self) -> Result; + + /// Get a copy of a `Dynamic` value as a specific type. + /// + /// # Panics + /// + /// Panics if the cast fails (e.g. the type of the actual value is not the same as the specified type). + fn cast(self) -> T; /// This trait may only be implemented by `rhai`. #[doc(hidden)] @@ -106,19 +156,38 @@ impl AnyExt for Dynamic { /// /// let x: Dynamic = 42_u32.into_dynamic(); /// - /// assert_eq!(*x.downcast::().unwrap(), 42); + /// assert_eq!(x.try_cast::().unwrap(), 42); /// ``` - fn downcast(self) -> Result, Self> { + fn try_cast(self) -> Result { if self.is::() { unsafe { let raw: *mut Variant = Box::into_raw(self); - Ok(Box::from_raw(raw as *mut T)) + Ok(*Box::from_raw(raw as *mut T)) } } else { Err(self) } } + /// Get a copy of the `Dynamic` value as a specific type. + /// + /// # Panics + /// + /// Panics if the cast fails (e.g. the type of the actual value is not the same as the specified type). + /// + /// # Example + /// + /// ``` + /// use rhai::{Dynamic, Any, AnyExt}; + /// + /// let x: Dynamic = 42_u32.into_dynamic(); + /// + /// assert_eq!(x.cast::(), 42); + /// ``` + fn cast(self) -> T { + self.try_cast::().expect("cast failed") + } + fn _closed(&self) -> _Private { _Private } diff --git a/src/api.rs b/src/api.rs index 7e010192..047cf525 100644 --- a/src/api.rs +++ b/src/api.rs @@ -5,7 +5,7 @@ use crate::call::FuncArgs; use crate::engine::{make_getter, make_setter, Engine, FnAny, FnSpec}; use crate::error::ParseError; use crate::fn_register::RegisterFn; -use crate::parser::{lex, parse, parse_global_expr, FnDef, Position, AST}; +use crate::parser::{lex, parse, parse_global_expr, Position, AST}; use crate::result::EvalAltResult; use crate::scope::Scope; @@ -15,13 +15,51 @@ use crate::optimize::optimize_into_ast; use crate::stdlib::{ any::{type_name, TypeId}, boxed::Box, + collections::HashMap, string::{String, ToString}, - sync::Arc, vec::Vec, }; #[cfg(not(feature = "no_std"))] use crate::stdlib::{fs::File, io::prelude::*, path::PathBuf}; +// Define callback function types +#[cfg(feature = "sync")] +pub trait ObjectGetCallback: Fn(&mut T) -> U + Send + Sync + 'static {} +#[cfg(feature = "sync")] +impl U + Send + Sync + 'static, T, U> ObjectGetCallback for F {} + +#[cfg(not(feature = "sync"))] +pub trait ObjectGetCallback: Fn(&mut T) -> U + 'static {} +#[cfg(not(feature = "sync"))] +impl U + 'static, T, U> ObjectGetCallback for F {} + +#[cfg(feature = "sync")] +pub trait ObjectSetCallback: Fn(&mut T, U) + Send + Sync + 'static {} +#[cfg(feature = "sync")] +impl ObjectSetCallback for F {} + +#[cfg(not(feature = "sync"))] +pub trait ObjectSetCallback: Fn(&mut T, U) + 'static {} +#[cfg(not(feature = "sync"))] +impl ObjectSetCallback for F {} + +#[cfg(feature = "sync")] +pub trait IteratorCallback: + Fn(&Dynamic) -> Box> + Send + Sync + 'static +{ +} +#[cfg(feature = "sync")] +impl Box> + Send + Sync + 'static> IteratorCallback + for F +{ +} + +#[cfg(not(feature = "sync"))] +pub trait IteratorCallback: Fn(&Dynamic) -> Box> + 'static {} +#[cfg(not(feature = "sync"))] +impl Box> + 'static> IteratorCallback for F {} + +/// Engine public API impl<'e> Engine<'e> { /// Register a custom function. pub(crate) fn register_fn_raw(&mut self, fn_name: &str, args: Vec, f: Box) { @@ -30,7 +68,10 @@ impl<'e> Engine<'e> { args, }; - self.functions.insert(spec, f); + if self.functions.is_none() { + self.functions = Some(HashMap::new()); + } + self.functions.as_mut().unwrap().insert(spec, f); } /// Register a custom type for use with the `Engine`. @@ -119,18 +160,28 @@ impl<'e> Engine<'e> { /// ``` #[cfg(not(feature = "no_object"))] pub fn register_type_with_name(&mut self, name: &str) { + if self.type_names.is_none() { + self.type_names = Some(HashMap::new()); + } + // Add the pretty-print type name into the map self.type_names + .as_mut() + .unwrap() .insert(type_name::().to_string(), name.to_string()); } /// Register an iterator adapter for a type with the `Engine`. /// This is an advanced feature. - pub fn register_iterator(&mut self, f: F) - where - F: Fn(&Dynamic) -> Box> + 'static, - { - self.type_iterators.insert(TypeId::of::(), Box::new(f)); + pub fn register_iterator(&mut self, f: F) { + if self.type_iterators.is_none() { + self.type_iterators = Some(HashMap::new()); + } + + self.type_iterators + .as_mut() + .unwrap() + .insert(TypeId::of::(), Box::new(f)); } /// Register a getter function for a member of a registered type with the `Engine`. @@ -170,11 +221,12 @@ impl<'e> Engine<'e> { /// # } /// ``` #[cfg(not(feature = "no_object"))] - pub fn register_get( - &mut self, - name: &str, - callback: impl Fn(&mut T) -> U + 'static, - ) { + pub fn register_get(&mut self, name: &str, callback: F) + where + T: Any + Clone, + U: Any + Clone, + F: ObjectGetCallback, + { self.register_fn(&make_getter(name), callback); } @@ -215,11 +267,12 @@ impl<'e> Engine<'e> { /// # } /// ``` #[cfg(not(feature = "no_object"))] - pub fn register_set( - &mut self, - name: &str, - callback: impl Fn(&mut T, U) -> () + 'static, - ) { + pub fn register_set(&mut self, name: &str, callback: F) + where + T: Any + Clone, + U: Any + Clone, + F: ObjectSetCallback, + { self.register_fn(&make_setter(name), callback); } @@ -262,12 +315,13 @@ impl<'e> Engine<'e> { /// # } /// ``` #[cfg(not(feature = "no_object"))] - pub fn register_get_set( - &mut self, - name: &str, - get_fn: impl Fn(&mut T) -> U + 'static, - set_fn: impl Fn(&mut T, U) -> () + 'static, - ) { + pub fn register_get_set(&mut self, name: &str, get_fn: G, set_fn: S) + where + T: Any + Clone, + U: Any + Clone, + G: ObjectGetCallback, + S: ObjectSetCallback, + { self.register_get(name, get_fn); self.register_set(name, set_fn); } @@ -691,9 +745,8 @@ impl<'e> Engine<'e> { scope: &mut Scope, ast: &AST, ) -> Result { - self.eval_ast_with_scope_raw(scope, false, ast)? - .downcast::() - .map(|v| *v) + self.eval_ast_with_scope_raw(scope, ast)? + .try_cast::() .map_err(|a| { EvalAltResult::ErrorMismatchOutputType( self.map_type_name((*a).type_name()).to_string(), @@ -705,16 +758,11 @@ impl<'e> Engine<'e> { pub(crate) fn eval_ast_with_scope_raw( &mut self, scope: &mut Scope, - retain_functions: bool, ast: &AST, ) -> Result { - if !retain_functions { - self.clear_functions(); - } - let statements = { let AST(statements, functions) = ast; - self.load_script_functions(functions); + self.fn_lib = Some(functions.clone()); statements }; @@ -722,9 +770,7 @@ impl<'e> Engine<'e> { .iter() .try_fold(().into_dynamic(), |_, stmt| self.eval_stmt(scope, stmt, 0)); - if !retain_functions { - self.clear_functions(); - } + self.fn_lib = None; result.or_else(|err| match err { EvalAltResult::Return(out, _) => Ok(out), @@ -734,56 +780,33 @@ impl<'e> Engine<'e> { /// Evaluate a file, but throw away the result and only return error (if any). /// Useful for when you don't need the result, but still need to keep track of possible errors. - /// - /// # Note - /// - /// If `retain_functions` is set to `true`, functions defined by previous scripts are _retained_ and not cleared from run to run. #[cfg(not(feature = "no_std"))] - pub fn consume_file( - &mut self, - retain_functions: bool, - path: PathBuf, - ) -> Result<(), EvalAltResult> { - Self::read_file(path).and_then(|contents| self.consume(retain_functions, &contents)) + pub fn consume_file(&mut self, path: PathBuf) -> Result<(), EvalAltResult> { + Self::read_file(path).and_then(|contents| self.consume(&contents)) } /// Evaluate a file with own scope, but throw away the result and only return error (if any). /// Useful for when you don't need the result, but still need to keep track of possible errors. - /// - /// # Note - /// - /// If `retain_functions` is set to `true`, functions defined by previous scripts are _retained_ and not cleared from run to run. #[cfg(not(feature = "no_std"))] pub fn consume_file_with_scope( &mut self, scope: &mut Scope, - retain_functions: bool, path: PathBuf, ) -> Result<(), EvalAltResult> { - Self::read_file(path) - .and_then(|contents| self.consume_with_scope(scope, retain_functions, &contents)) + Self::read_file(path).and_then(|contents| self.consume_with_scope(scope, &contents)) } /// Evaluate a string, but throw away the result and only return error (if any). /// Useful for when you don't need the result, but still need to keep track of possible errors. - /// - /// # Note - /// - /// If `retain_functions` is set to `true`, functions defined by previous scripts are _retained_and not cleared from run to run. - pub fn consume(&mut self, retain_functions: bool, input: &str) -> Result<(), EvalAltResult> { - self.consume_with_scope(&mut Scope::new(), retain_functions, input) + pub fn consume(&mut self, input: &str) -> Result<(), EvalAltResult> { + self.consume_with_scope(&mut Scope::new(), input) } /// Evaluate a string with own scope, but throw away the result and only return error (if any). /// Useful for when you don't need the result, but still need to keep track of possible errors. - /// - /// # Note - /// - /// If `retain_functions` is set to `true`, functions defined by previous scripts are _retained_and not cleared from run to run. pub fn consume_with_scope( &mut self, scope: &mut Scope, - retain_functions: bool, input: &str, ) -> Result<(), EvalAltResult> { let tokens_stream = lex(input); @@ -791,38 +814,25 @@ impl<'e> Engine<'e> { let ast = parse(&mut tokens_stream.peekable(), self, scope) .map_err(EvalAltResult::ErrorParsing)?; - self.consume_ast_with_scope(scope, retain_functions, &ast) + self.consume_ast_with_scope(scope, &ast) } /// Evaluate an AST, but throw away the result and only return error (if any). /// Useful for when you don't need the result, but still need to keep track of possible errors. - /// - /// # Note - /// - /// If `retain_functions` is set to `true`, functions defined by previous scripts are _retained_and not cleared from run to run. - pub fn consume_ast(&mut self, retain_functions: bool, ast: &AST) -> Result<(), EvalAltResult> { - self.consume_ast_with_scope(&mut Scope::new(), retain_functions, ast) + pub fn consume_ast(&mut self, ast: &AST) -> Result<(), EvalAltResult> { + self.consume_ast_with_scope(&mut Scope::new(), ast) } /// Evaluate an `AST` with own scope, but throw away the result and only return error (if any). /// Useful for when you don't need the result, but still need to keep track of possible errors. - /// - /// # Note - /// - /// If `retain_functions` is set to `true`, functions defined by previous scripts are _retained_and not cleared from run to run. pub fn consume_ast_with_scope( &mut self, scope: &mut Scope, - retain_functions: bool, ast: &AST, ) -> Result<(), EvalAltResult> { - if !retain_functions { - self.clear_functions(); - } - let statements = { - let AST(ref statements, ref functions) = ast; - self.load_script_functions(functions); + let AST(statements, functions) = ast; + self.fn_lib = Some(functions.clone()); statements }; @@ -830,9 +840,7 @@ impl<'e> Engine<'e> { .iter() .try_fold(().into_dynamic(), |_, stmt| self.eval_stmt(scope, stmt, 0)); - if !retain_functions { - self.clear_functions(); - } + self.fn_lib = None; result.map(|_| ()).or_else(|err| match err { EvalAltResult::Return(_, _) => Ok(()), @@ -840,17 +848,7 @@ impl<'e> Engine<'e> { }) } - /// Load a list of functions into the Engine. - pub(crate) fn load_script_functions<'a>( - &mut self, - functions: impl IntoIterator>, - ) { - functions.into_iter().cloned().for_each(|f| { - self.fn_lib.add_or_replace_function(f); - }); - } - - /// Call a script function retained inside the Engine. + /// Call a script function defined in an `AST` with no argument. /// /// # Example /// @@ -859,17 +857,92 @@ impl<'e> Engine<'e> { /// # #[cfg(not(feature = "no_stdlib"))] /// # #[cfg(not(feature = "no_function"))] /// # { - /// use rhai::Engine; + /// use rhai::{Engine, Scope}; /// /// let mut engine = Engine::new(); /// - /// // Set 'retain_functions' in 'consume' to keep the function definitions - /// engine.consume(true, "fn add(x, y) { len(x) + y }")?; + /// let ast = engine.compile("fn num() { 42 + foo }")?; + /// + /// let mut scope = Scope::new(); + /// scope.push("foo", 42_i64); /// /// // Call the script-defined function - /// let result: i64 = engine.call_fn("add", (String::from("abc"), 123_i64))?; + /// let result: i64 = engine.call_fn0(&mut scope, &ast, "num")?; /// - /// assert_eq!(result, 126); + /// assert_eq!(result, 84); + /// # } + /// # Ok(()) + /// # } + /// ``` + #[cfg(not(feature = "no_function"))] + pub fn call_fn0( + &mut self, + scope: &mut Scope, + ast: &AST, + name: &str, + ) -> Result { + self.call_fn_internal(scope, ast, name, vec![]) + } + + /// Call a script function defined in an `AST` with one argument. + /// + /// # Example + /// + /// ``` + /// # fn main() -> Result<(), rhai::EvalAltResult> { + /// # #[cfg(not(feature = "no_stdlib"))] + /// # #[cfg(not(feature = "no_function"))] + /// # { + /// use rhai::{Engine, Scope}; + /// + /// let mut engine = Engine::new(); + /// + /// let ast = engine.compile("fn inc(x) { x + foo }")?; + /// + /// let mut scope = Scope::new(); + /// scope.push("foo", 42_i64); + /// + /// // Call the script-defined function + /// let result: i64 = engine.call_fn1(&mut scope, &ast, "inc", 123_i64)?; + /// + /// assert_eq!(result, 165); + /// # } + /// # Ok(()) + /// # } + /// ``` + #[cfg(not(feature = "no_function"))] + pub fn call_fn1( + &mut self, + scope: &mut Scope, + ast: &AST, + name: &str, + arg: A, + ) -> Result { + self.call_fn_internal(scope, ast, name, vec![arg.into_dynamic()]) + } + + /// Call a script function defined in an `AST` with multiple arguments. + /// + /// # Example + /// + /// ``` + /// # fn main() -> Result<(), rhai::EvalAltResult> { + /// # #[cfg(not(feature = "no_stdlib"))] + /// # #[cfg(not(feature = "no_function"))] + /// # { + /// use rhai::{Engine, Scope}; + /// + /// let mut engine = Engine::new(); + /// + /// let ast = engine.compile("fn add(x, y) { len(x) + y + foo }")?; + /// + /// let mut scope = Scope::new(); + /// scope.push("foo", 42_i64); + /// + /// // Call the script-defined function + /// let result: i64 = engine.call_fn(&mut scope, &ast, "add", (String::from("abc"), 123_i64))?; + /// + /// assert_eq!(result, 168); /// # } /// # Ok(()) /// # } @@ -877,25 +950,43 @@ impl<'e> Engine<'e> { #[cfg(not(feature = "no_function"))] pub fn call_fn( &mut self, + scope: &mut Scope, + ast: &AST, name: &str, args: A, ) -> Result { - let mut values = args.into_vec(); - let mut arg_values: Vec<_> = values.iter_mut().map(Dynamic::as_mut).collect(); + self.call_fn_internal(scope, ast, name, args.into_vec()) + } - self.call_fn_raw(name, &mut arg_values, None, Position::none(), 0)? - .downcast() - .map(|b| *b) + #[cfg(not(feature = "no_function"))] + fn call_fn_internal( + &mut self, + scope: &mut Scope, + ast: &AST, + name: &str, + mut arg_values: Vec, + ) -> Result { + let mut args: Vec<_> = arg_values.iter_mut().map(Dynamic::as_mut).collect(); + + self.fn_lib = Some(ast.1.clone()); + + let result = self + .call_fn_raw(Some(scope), name, &mut args, None, Position::none(), 0)? + .try_cast() .map_err(|a| { EvalAltResult::ErrorMismatchOutputType( self.map_type_name((*a).type_name()).into(), Position::none(), ) - }) + }); + + self.fn_lib = None; + + result } /// Optimize the `AST` with constants defined in an external Scope. - /// An optimized copy of the `AST` is returned while the original `AST` is untouched. + /// An optimized copy of the `AST` is returned while the original `AST` is consumed. /// /// Although optimization is performed by default during compilation, sometimes it is necessary to /// _re_-optimize an AST. For example, when working with constants that are passed in via an @@ -906,11 +997,13 @@ impl<'e> Engine<'e> { /// compiled just once. Before evaluation, constants are passed into the `Engine` via an external scope /// (i.e. with `scope.push_constant(...)`). Then, the `AST is cloned and the copy re-optimized before running. #[cfg(not(feature = "no_optimize"))] - pub fn optimize_ast(&self, scope: &Scope, ast: &AST) -> AST { - let statements = ast.0.clone(); - let functions = ast.1.iter().map(|f| (**f).clone()).collect(); - - optimize_into_ast(self, scope, statements, functions) + pub fn optimize_ast(&self, scope: &Scope, ast: AST) -> AST { + optimize_into_ast( + self, + scope, + ast.0, + ast.1.iter().map(|fn_def| fn_def.as_ref().clone()).collect(), + ) } /// Override default action of `print` (print to stdout using `println!`) @@ -927,14 +1020,39 @@ impl<'e> Engine<'e> { /// /// // Override action of 'print' function /// engine.on_print(|s| result.push_str(s)); - /// engine.consume(false, "print(40 + 2);")?; + /// engine.consume("print(40 + 2);")?; /// } /// assert_eq!(result, "42"); /// # Ok(()) /// # } /// ``` + #[cfg(feature = "sync")] + pub fn on_print(&mut self, callback: impl FnMut(&str) + Send + Sync + 'e) { + self.on_print = Some(Box::new(callback)); + } + /// Override default action of `print` (print to stdout using `println!`) + /// + /// # Example + /// + /// ``` + /// # fn main() -> Result<(), rhai::EvalAltResult> { + /// use rhai::Engine; + /// + /// let mut result = String::from(""); + /// { + /// let mut engine = Engine::new(); + /// + /// // Override action of 'print' function + /// engine.on_print(|s| result.push_str(s)); + /// engine.consume("print(40 + 2);")?; + /// } + /// assert_eq!(result, "42"); + /// # Ok(()) + /// # } + /// ``` + #[cfg(not(feature = "sync"))] pub fn on_print(&mut self, callback: impl FnMut(&str) + 'e) { - self.on_print = Box::new(callback); + self.on_print = Some(Box::new(callback)); } /// Override default action of `debug` (print to stdout using `println!`) @@ -951,13 +1069,38 @@ impl<'e> Engine<'e> { /// /// // Override action of 'debug' function /// engine.on_debug(|s| result.push_str(s)); - /// engine.consume(false, r#"debug("hello");"#)?; + /// engine.consume(r#"debug("hello");"#)?; /// } /// assert_eq!(result, "\"hello\""); /// # Ok(()) /// # } /// ``` + #[cfg(feature = "sync")] + pub fn on_debug(&mut self, callback: impl FnMut(&str) + Send + Sync + 'e) { + self.on_debug = Some(Box::new(callback)); + } + /// Override default action of `debug` (print to stdout using `println!`) + /// + /// # Example + /// + /// ``` + /// # fn main() -> Result<(), rhai::EvalAltResult> { + /// use rhai::Engine; + /// + /// let mut result = String::from(""); + /// { + /// let mut engine = Engine::new(); + /// + /// // Override action of 'debug' function + /// engine.on_debug(|s| result.push_str(s)); + /// engine.consume(r#"debug("hello");"#)?; + /// } + /// assert_eq!(result, "\"hello\""); + /// # Ok(()) + /// # } + /// ``` + #[cfg(not(feature = "sync"))] pub fn on_debug(&mut self, callback: impl FnMut(&str) + 'e) { - self.on_debug = Box::new(callback); + self.on_debug = Some(Box::new(callback)); } } diff --git a/src/builtin.rs b/src/builtin.rs index ae5feb13..b6baed63 100644 --- a/src/builtin.rs +++ b/src/builtin.rs @@ -1,7 +1,7 @@ //! Helper module that allows registration of the _core library_ and //! _standard library_ of utility functions. -use crate::any::Any; +use crate::any::{Any, Dynamic}; use crate::engine::{Engine, FUNC_TO_STRING, KEYWORD_DEBUG, KEYWORD_PRINT}; use crate::fn_register::{RegisterDynamicFn, RegisterFn, RegisterResultFn}; use crate::parser::{Position, INT}; @@ -612,8 +612,9 @@ impl Engine<'_> { reg_fn1!(self, KEYWORD_DEBUG, to_debug, String, Array); // Register array iterator - self.register_iterator::(|a| { + self.register_iterator::(|a: &Dynamic| { Box::new(a.downcast_ref::().unwrap().clone().into_iter()) + as Box> }); } @@ -628,25 +629,36 @@ impl Engine<'_> { self.register_fn(KEYWORD_DEBUG, |x: &mut Map| -> String { format!("#{:?}", x) }); + + // Register map access functions + self.register_fn("keys", |map: Map| { + map.into_iter() + .map(|(k, _)| k.into_dynamic()) + .collect::>() + }); + + self.register_fn("values", |map: Map| { + map.into_iter().map(|(_, v)| v).collect::>() + }); } } // Register range function - fn reg_iterator(engine: &mut Engine) + fn reg_range(engine: &mut Engine) where Range: Iterator, { - engine.register_iterator::, _>(|a| { + engine.register_iterator::, _>(|a: &Dynamic| { Box::new( a.downcast_ref::>() .unwrap() .clone() - .map(|n| n.into_dynamic()), - ) + .map(|x| x.into_dynamic()), + ) as Box> }); } - reg_iterator::(self); + reg_range::(self); self.register_fn("range", |i1: INT, i2: INT| (i1..i2)); #[cfg(not(feature = "only_i32"))] @@ -655,7 +667,7 @@ impl Engine<'_> { macro_rules! reg_range { ($self:expr, $x:expr, $( $y:ty ),*) => ( $( - reg_iterator::<$y>(self); + reg_range::<$y>(self); $self.register_fn($x, (|x: $y, y: $y| x..y) as fn(x: $y, y: $y)->Range<$y>); )* ) @@ -663,6 +675,67 @@ impl Engine<'_> { reg_range!(self, "range", i8, u8, i16, u16, i32, i64, u32, u64); } + + // Register range function with step + #[derive(Debug, Clone, Copy, Hash, Eq, PartialEq)] + struct StepRange(T, T, T) + where + for<'a> &'a T: Add<&'a T, Output = T>, + T: Any + Clone + PartialOrd; + + impl Iterator for StepRange + where + for<'a> &'a T: Add<&'a T, Output = T>, + T: Any + Clone + PartialOrd, + { + type Item = T; + + fn next(&mut self) -> Option { + if self.0 < self.1 { + let v = self.0.clone(); + self.0 = &v + &self.2; + Some(v) + } else { + None + } + } + } + + fn reg_step(engine: &mut Engine) + where + for<'a> &'a T: Add<&'a T, Output = T>, + T: Any + Clone + PartialOrd, + StepRange: Iterator, + { + engine.register_iterator::, _>(|a: &Dynamic| { + Box::new( + a.downcast_ref::>() + .unwrap() + .clone() + .map(|x| x.into_dynamic()), + ) as Box> + }); + } + + reg_step::(self); + self.register_fn("range", |i1: INT, i2: INT, step: INT| { + StepRange(i1, i2, step) + }); + + #[cfg(not(feature = "only_i32"))] + #[cfg(not(feature = "only_i64"))] + { + macro_rules! reg_step { + ($self:expr, $x:expr, $( $y:ty ),*) => ( + $( + reg_step::<$y>(self); + $self.register_fn($x, (|x: $y, y: $y, step: $y| StepRange(x,y,step)) as fn(x: $y, y: $y, step: $y)->StepRange<$y>); + )* + ) + } + + reg_step!(self, "range", i8, u8, i16, u16, i32, i64, u32, u64); + } } } @@ -813,6 +886,14 @@ impl Engine<'_> { reg_fn3!(self, "pad", pad, &mut Array, INT, (), INT, bool, char); reg_fn3!(self, "pad", pad, &mut Array, INT, (), String, Array, ()); + self.register_fn("append", |list: &mut Array, array: Array| { + list.extend(array) + }); + self.register_fn("+", |mut list: Array, array: Array| { + list.extend(array); + list + }); + #[cfg(not(feature = "only_i32"))] #[cfg(not(feature = "only_i64"))] { @@ -853,6 +934,17 @@ impl Engine<'_> { 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()); + self.register_fn("mixin", |map1: &mut Map, map2: Map| { + map2.into_iter().for_each(|(key, value)| { + map1.insert(key, value); + }); + }); + self.register_fn("+", |mut map1: Map, map2: Map| { + map2.into_iter().for_each(|(key, value)| { + map1.insert(key, value); + }); + map1 + }); } // Register string concatenate functions diff --git a/src/call.rs b/src/call.rs index c1147a53..ac33e2c7 100644 --- a/src/call.rs +++ b/src/call.rs @@ -3,10 +3,6 @@ #![allow(non_snake_case)] use crate::any::{Any, Dynamic}; -use crate::parser::INT; - -#[cfg(not(feature = "no_index"))] -use crate::engine::Array; use crate::stdlib::{string::String, vec, vec::Vec}; @@ -18,36 +14,7 @@ pub trait FuncArgs { fn into_vec(self) -> Vec; } -/// Macro to implement `FuncArgs` for a single standard type that can be converted -/// into `Dynamic`. -macro_rules! impl_std_args { - ($($p:ty),*) => { - $( - impl FuncArgs for $p { - fn into_vec(self) -> Vec { - vec![self.into_dynamic()] - } - } - )* - }; -} - -impl_std_args!(String, char, bool); - -#[cfg(not(feature = "no_index"))] -impl_std_args!(Array); - -#[cfg(not(feature = "only_i32"))] -#[cfg(not(feature = "only_i64"))] -impl_std_args!(u8, i8, u16, i16, u32, i32, u64, i64); - -#[cfg(any(feature = "only_i32", feature = "only_i64"))] -impl_std_args!(INT); - -#[cfg(not(feature = "no_float"))] -impl_std_args!(f32, f64); - -/// Macro to implement `FuncArgs` for tuples of standard types (each can be +// Macro to implement `FuncArgs` for tuples of standard types (each can be /// converted into `Dynamic`). macro_rules! impl_args { ($($p:ident),*) => { diff --git a/src/engine.rs b/src/engine.rs index b20cfabd..8f4f418f 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -1,7 +1,7 @@ //! Main module defining the script evaluation `Engine`. use crate::any::{Any, AnyExt, Dynamic, Variant}; -use crate::parser::{Expr, FnDef, Position, ReturnType, Stmt, INT}; +use crate::parser::{Expr, FnDef, Position, ReturnType, Stmt, AST, INT}; use crate::result::EvalAltResult; use crate::scope::{EntryRef as ScopeSource, EntryType as ScopeEntryType, Scope}; @@ -16,6 +16,8 @@ use crate::stdlib::{ collections::HashMap, format, iter::once, + ops::{Deref, DerefMut}, + rc::Rc, string::{String, ToString}, sync::Arc, vec, @@ -23,17 +25,27 @@ use crate::stdlib::{ }; /// An dynamic array of `Dynamic` values. +/// +/// Not available under the `no_index` feature. #[cfg(not(feature = "no_index"))] pub type Array = Vec; -/// An dynamic hash map of `Dynamic` values. +/// An dynamic hash map of `Dynamic` values with `String` keys. +/// +/// Not available under the `no_object` feature. #[cfg(not(feature = "no_object"))] pub type Map = HashMap; pub type FnCallArgs<'a> = [&'a mut Variant]; +#[cfg(feature = "sync")] +pub type FnAny = dyn Fn(&mut FnCallArgs, Position) -> Result + Send + Sync; +#[cfg(not(feature = "sync"))] pub type FnAny = dyn Fn(&mut FnCallArgs, Position) -> Result; +#[cfg(feature = "sync")] +type IteratorFn = dyn Fn(&Dynamic) -> Box> + Send + Sync; +#[cfg(not(feature = "sync"))] type IteratorFn = dyn Fn(&Dynamic) -> Box>; pub const MAX_CALL_STACK_DEPTH: usize = 64; @@ -120,8 +132,11 @@ pub struct FnSpec<'a> { /// to search for it. /// /// So instead this is implemented as a sorted list and binary searched. -#[derive(Debug)] -pub struct FunctionsLib(Vec>); +#[derive(Debug, Clone)] +pub struct FunctionsLib( + #[cfg(feature = "sync")] Vec>, + #[cfg(not(feature = "sync"))] Vec>, +); impl FnDef { /// Function to order two FnDef records, for binary search. @@ -140,32 +155,75 @@ impl FunctionsLib { pub fn new() -> Self { FunctionsLib(Vec::new()) } - /// Clear the `FunctionsLib`. - pub fn clear(&mut self) { - self.0.clear(); + /// Create a new `FunctionsLib` from a collection of `FnDef`. + pub fn from_vec(vec: Vec) -> Self { + #[cfg(feature = "sync")] + { + FunctionsLib(vec.into_iter().map(Arc::new).collect()) + } + #[cfg(not(feature = "sync"))] + { + FunctionsLib(vec.into_iter().map(Rc::new).collect()) + } } /// Does a certain function exist in the `FunctionsLib`? pub fn has_function(&self, name: &str, params: usize) -> bool { self.0.binary_search_by(|f| f.compare(name, params)).is_ok() } - /// Add a function (or replace an existing one) in the `FunctionsLib`. - pub fn add_or_replace_function(&mut self, fn_def: Arc) { - match self - .0 - .binary_search_by(|f| f.compare(&fn_def.name, fn_def.params.len())) - { - Ok(n) => self.0[n] = fn_def, - Err(n) => self.0.insert(n, fn_def), - } - } /// Get a function definition from the `FunctionsLib`. - pub fn get_function(&self, name: &str, params: usize) -> Option> { + pub fn get_function(&self, name: &str, params: usize) -> Option<&FnDef> { if let Ok(n) = self.0.binary_search_by(|f| f.compare(name, params)) { - Some(self.0[n].clone()) + Some(&self.0[n]) } else { None } } + /// Merge another `FunctionsLib` into this `FunctionsLib`. + pub fn merge(&self, other: &Self) -> Self { + if self.is_empty() { + other.clone() + } else if other.is_empty() { + self.clone() + } else { + let mut functions = self.clone(); + + other.iter().cloned().for_each(|fn_def| { + if let Some((n, _)) = functions + .iter() + .enumerate() + .find(|(_, f)| f.name == fn_def.name && f.params.len() == fn_def.params.len()) + { + functions[n] = fn_def; + } else { + functions.push(fn_def); + } + }); + + functions + } + } +} + +impl Deref for FunctionsLib { + #[cfg(feature = "sync")] + type Target = Vec>; + #[cfg(not(feature = "sync"))] + type Target = Vec>; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl DerefMut for FunctionsLib { + #[cfg(feature = "sync")] + fn deref_mut(&mut self) -> &mut Vec> { + &mut self.0 + } + #[cfg(not(feature = "sync"))] + fn deref_mut(&mut self) -> &mut Vec> { + &mut self.0 + } } /// Rhai main scripting engine. @@ -182,20 +240,37 @@ impl FunctionsLib { /// # Ok(()) /// # } /// ``` +/// +/// Currently, `Engine` is neither `Send` nor `Sync`. Turn on the `sync` feature to make it `Send + Sync`. pub struct Engine<'e> { /// A hashmap containing all compiled functions known to the engine. - pub(crate) functions: HashMap, Box>, + pub(crate) functions: Option, Box>>, + /// A hashmap containing all script-defined functions. - pub(crate) fn_lib: FunctionsLib, + #[cfg(feature = "sync")] + pub(crate) fn_lib: Option>, + /// A hashmap containing all script-defined functions. + #[cfg(not(feature = "sync"))] + pub(crate) fn_lib: Option>, + /// A hashmap containing all iterators known to the engine. - pub(crate) type_iterators: HashMap>, + pub(crate) type_iterators: Option>>, /// A hashmap mapping type names to pretty-print names. - pub(crate) type_names: HashMap, + pub(crate) type_names: Option>, /// Closure for implementing the `print` command. - pub(crate) on_print: Box, + #[cfg(feature = "sync")] + pub(crate) on_print: Option>, + /// Closure for implementing the `print` command. + #[cfg(not(feature = "sync"))] + pub(crate) on_print: Option>, + /// Closure for implementing the `debug` command. - pub(crate) on_debug: Box, + #[cfg(feature = "sync")] + pub(crate) on_debug: Option>, + /// Closure for implementing the `debug` command. + #[cfg(not(feature = "sync"))] + pub(crate) on_debug: Option>, /// Optimize the AST after compilation. #[cfg(not(feature = "no_optimize"))] @@ -217,17 +292,17 @@ impl Default for Engine<'_> { (type_name::(), "dynamic"), ] .iter() - .map(|(k, v)| ((*k).to_string(), (*v).to_string())) + .map(|(k, v)| (k.to_string(), v.to_string())) .collect(); // Create the new scripting Engine let mut engine = Engine { - functions: HashMap::new(), - fn_lib: FunctionsLib::new(), - type_iterators: HashMap::new(), - type_names, - on_print: Box::new(default_print), // default print/debug implementations - on_debug: Box::new(default_print), + functions: None, + fn_lib: None, + type_iterators: None, + type_names: Some(type_names), + on_print: Some(Box::new(default_print)), // default print/debug implementations + on_debug: Some(Box::new(default_print)), #[cfg(not(feature = "no_optimize"))] #[cfg(not(feature = "optimize_full"))] @@ -280,10 +355,46 @@ fn extract_prop_from_setter(fn_name: &str) -> Option<&str> { impl Engine<'_> { /// Create a new `Engine` pub fn new() -> Self { + // fn abc(f: F) { + // f(); + // } + // abc(|| ()); + Default::default() } + /// Create a new `Engine` with minimal configurations - i.e. without pretty-print type names etc. + pub fn new_raw() -> Self { + let mut engine = Engine { + functions: None, + fn_lib: None, + type_iterators: None, + type_names: None, + on_print: None, + on_debug: None, + + #[cfg(not(feature = "no_optimize"))] + #[cfg(not(feature = "optimize_full"))] + optimization_level: OptimizationLevel::Simple, + + #[cfg(not(feature = "no_optimize"))] + #[cfg(feature = "optimize_full")] + optimization_level: OptimizationLevel::Full, + + max_call_stack_depth: MAX_CALL_STACK_DEPTH, + }; + + engine.register_core_lib(); + + #[cfg(not(feature = "no_stdlib"))] + engine.register_stdlib(); // Register the standard library when no_stdlib is not set + + engine + } + /// Control whether and how the `Engine` will optimize an AST after compilation + /// + /// Not available under the `no_optimize` feature. #[cfg(not(feature = "no_optimize"))] pub fn set_optimization_level(&mut self, optimization_level: OptimizationLevel) { self.optimization_level = optimization_level @@ -309,9 +420,13 @@ impl Engine<'_> { }; // Search built-in's and external functions - if let Some(func) = self.functions.get(&spec) { - // Run external function - Ok(Some(func(args, pos)?)) + if let Some(functions) = &self.functions { + if let Some(func) = functions.get(&spec) { + // Run external function + Ok(Some(func(args, pos)?)) + } else { + Ok(None) + } } else { Ok(None) } @@ -320,6 +435,7 @@ impl Engine<'_> { /// Universal method for calling functions either registered with the `Engine` or written in Rhai pub(crate) fn call_fn_raw( &mut self, + scope: Option<&mut Scope>, fn_name: &str, args: &mut FnCallArgs, def_val: Option<&Dynamic>, @@ -327,26 +443,60 @@ impl Engine<'_> { level: usize, ) -> Result { // First search in script-defined functions (can override built-in) - if let Some(fn_def) = self.fn_lib.get_function(fn_name, args.len()) { - let mut scope = Scope::new(); + if let Some(fn_lib_arc) = &self.fn_lib { + if let Some(fn_def) = fn_lib_arc.clone().get_function(fn_name, args.len()) { + match scope { + // Extern scope passed in which is not empty + Some(scope) if scope.len() > 0 => { + let scope_len = scope.len(); - scope.extend( - // Put arguments into scope as variables - fn_def - .params - .iter() - .zip(args.iter().map(|x| (*x).into_dynamic())) - .map(|(name, value)| (name, ScopeEntryType::Normal, value)), - ); + scope.extend( + // Put arguments into scope as variables - variable name is copied + // TODO - avoid copying variable name + fn_def + .params + .iter() + .zip(args.into_iter().map(|x| (*x).into_dynamic())) + .map(|(name, value)| (name.clone(), ScopeEntryType::Normal, value)), + ); - // Evaluate the function at one higher level of call depth - return self - .eval_stmt(&mut scope, &fn_def.body, level + 1) - .or_else(|err| match err { - // Convert return statement to return value - EvalAltResult::Return(x, _) => Ok(x), - err => Err(err.set_position(pos)), - }); + // Evaluate the function at one higher level of call depth + let result = self.eval_stmt(scope, &fn_def.body, level + 1).or_else( + |err| match err { + // Convert return statement to return value + EvalAltResult::Return(x, _) => Ok(x), + err => Err(err.set_position(pos)), + }, + ); + + scope.rewind(scope_len); + + return result; + } + // No new scope - create internal scope + _ => { + let mut scope = Scope::new(); + + scope.extend( + // Put arguments into scope as variables + fn_def + .params + .iter() + .zip(args.into_iter().map(|x| (*x).into_dynamic())) + .map(|(name, value)| (name, ScopeEntryType::Normal, value)), + ); + + // Evaluate the function at one higher level of call depth + return self.eval_stmt(&mut scope, &fn_def.body, level + 1).or_else( + |err| match err { + // Convert return statement to return value + EvalAltResult::Return(x, _) => Ok(x), + err => Err(err.set_position(pos)), + }, + ); + } + } + } } let spec = FnSpec { @@ -362,20 +512,25 @@ impl Engine<'_> { } // Search built-in's and external functions - if let Some(func) = self.functions.get(&spec) { - // Run external function - let result = func(args, pos)?; + if let Some(functions) = &self.functions { + if let Some(func) = functions.get(&spec) { + // Run external function + let result = func(args, pos)?; - // See if the function match print/debug (which requires special processing) - return Ok(match fn_name { - KEYWORD_PRINT => { - self.on_print.as_mut()(cast_to_string(result.as_ref(), pos)?).into_dynamic() - } - KEYWORD_DEBUG => { - self.on_debug.as_mut()(cast_to_string(result.as_ref(), pos)?).into_dynamic() - } - _ => result, - }); + // See if the function match print/debug (which requires special processing) + return Ok(match fn_name { + KEYWORD_PRINT if self.on_print.is_some() => { + self.on_print.as_deref_mut().unwrap()(cast_to_string(result.as_ref(), pos)?) + .into_dynamic() + } + KEYWORD_DEBUG if self.on_debug.is_some() => { + self.on_debug.as_deref_mut().unwrap()(cast_to_string(result.as_ref(), pos)?) + .into_dynamic() + } + KEYWORD_PRINT | KEYWORD_DEBUG => ().into_dynamic(), + _ => result, + }); + } } if let Some(prop) = extract_prop_from_getter(fn_name) { @@ -441,7 +596,7 @@ impl Engine<'_> { level: usize, ) -> Result { match dot_rhs { - // xxx.fn_name(args) + // xxx.fn_name(arg_expr_list) Expr::FunctionCall(fn_name, arg_expr_list, def_val, pos) => { let mut values = arg_expr_list .iter() @@ -450,17 +605,19 @@ impl Engine<'_> { let this_ptr = target.get_mut(scope); - let mut arg_values: Vec<_> = once(this_ptr) + let mut args: Vec<_> = once(this_ptr) .chain(values.iter_mut().map(Dynamic::as_mut)) .collect(); - self.call_fn_raw(fn_name, &mut arg_values, def_val.as_ref(), *pos, 0) + let def_val = def_val.as_ref(); + + self.call_fn_raw(None, fn_name, &mut args, def_val, *pos, 0) } // xxx.id Expr::Property(id, pos) => { - let this_ptr = target.get_mut(scope); - self.call_fn_raw(&make_getter(id), &mut [this_ptr], None, *pos, 0) + let mut args = [target.get_mut(scope)]; + self.call_fn_raw(None, &make_getter(id), &mut args, None, *pos, 0) } // xxx.idx_lhs[idx_expr] @@ -469,8 +626,8 @@ impl Engine<'_> { let value = match idx_lhs.as_ref() { // xxx.id[idx_expr] Expr::Property(id, pos) => { - let this_ptr = target.get_mut(scope); - self.call_fn_raw(&make_getter(id), &mut [this_ptr], None, *pos, 0)? + let mut args = [target.get_mut(scope)]; + self.call_fn_raw(None, &make_getter(id), &mut args, None, *pos, 0)? } // xxx.???[???][idx_expr] Expr::Index(_, _, _) => { @@ -493,8 +650,8 @@ impl Engine<'_> { Expr::Dot(dot_lhs, rhs, _) => match dot_lhs.as_ref() { // xxx.id.rhs Expr::Property(id, pos) => { - let this_ptr = target.get_mut(scope); - self.call_fn_raw(&make_getter(id), &mut [this_ptr], None, *pos, 0) + let mut args = [target.get_mut(scope)]; + self.call_fn_raw(None, &make_getter(id), &mut args, None, *pos, 0) .and_then(|mut val| { self.get_dot_val_helper(scope, Target::from(val.as_mut()), rhs, level) }) @@ -505,8 +662,8 @@ impl Engine<'_> { let val = match idx_lhs.as_ref() { // xxx.id[idx_expr].rhs Expr::Property(id, pos) => { - let this_ptr = target.get_mut(scope); - self.call_fn_raw(&make_getter(id), &mut [this_ptr], None, *pos, 0)? + let mut args = [target.get_mut(scope)]; + self.call_fn_raw(None, &make_getter(id), &mut args, None, *pos, 0)? } // xxx.???[???][idx_expr].rhs Expr::Index(_, _, _) => { @@ -628,9 +785,9 @@ impl Engine<'_> { // val_array[idx] if let Some(arr) = val.downcast_ref::() { - let idx = *self + let idx = self .eval_expr(scope, idx_expr, level)? - .downcast::() + .try_cast::() .map_err(|_| EvalAltResult::ErrorNumericIndexExpr(idx_expr.position()))?; return if idx >= 0 { @@ -647,9 +804,9 @@ impl Engine<'_> { { // val_map[idx] if let Some(map) = val.downcast_ref::() { - let idx = *self + let idx = self .eval_expr(scope, idx_expr, level)? - .downcast::() + .try_cast::() .map_err(|_| EvalAltResult::ErrorStringIndexExpr(idx_expr.position()))?; return Ok(( @@ -662,9 +819,9 @@ impl Engine<'_> { // val_string[idx] if let Some(s) = val.downcast_ref::() { - let idx = *self + let idx = self .eval_expr(scope, idx_expr, level)? - .downcast::() + .try_cast::() .map_err(|_| EvalAltResult::ErrorNumericIndexExpr(idx_expr.position()))?; return if idx >= 0 { @@ -795,9 +952,9 @@ impl Engine<'_> { let s = scope.get_mut_by_type::(src); let pos = new_val.1; // Value must be a character - let ch = *new_val + let ch = new_val .0 - .downcast::() + .try_cast::() .map_err(|_| EvalAltResult::ErrorCharMismatch(pos))?; Self::str_replace_char(s, idx.as_num(), ch); Ok(().into_dynamic()) @@ -830,8 +987,8 @@ impl Engine<'_> { if let Some(s) = target.downcast_mut::() { // Value must be a character - let ch = *new_val - .downcast::() + let ch = new_val + .try_cast::() .map_err(|_| EvalAltResult::ErrorCharMismatch(pos))?; Self::str_replace_char(s, idx.as_num(), ch); return Ok(target); @@ -855,7 +1012,7 @@ impl Engine<'_> { // xxx.id Expr::Property(id, pos) => { let mut args = [this_ptr, new_val.0.as_mut()]; - self.call_fn_raw(&make_setter(id), &mut args, None, *pos, 0) + self.call_fn_raw(None, &make_setter(id), &mut args, None, *pos, 0) } // xxx.lhs[idx_expr] @@ -864,7 +1021,7 @@ impl Engine<'_> { Expr::Index(lhs, idx_expr, op_pos) => match lhs.as_ref() { // xxx.id[idx_expr] Expr::Property(id, pos) => self - .call_fn_raw(&make_getter(id), &mut [this_ptr], None, *pos, 0) + .call_fn_raw(None, &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)?; @@ -873,7 +1030,7 @@ impl Engine<'_> { }) .and_then(|mut val| { let mut args = [this_ptr, val.as_mut()]; - self.call_fn_raw(&make_setter(id), &mut args, None, *pos, 0) + self.call_fn_raw(None, &make_setter(id), &mut args, None, *pos, 0) }), // All others - syntax error for setters chain @@ -887,14 +1044,14 @@ impl Engine<'_> { Expr::Dot(lhs, rhs, _) => match lhs.as_ref() { // xxx.id.rhs Expr::Property(id, pos) => { - self.call_fn_raw(&make_getter(id), &mut [this_ptr], None, *pos, 0) + self.call_fn_raw(None, &make_getter(id), &mut [this_ptr], None, *pos, 0) .and_then(|mut val| { self.set_dot_val_helper(scope, val.as_mut(), rhs, new_val, level) .map(|_| val) // Discard Ok return value }) .and_then(|mut val| { let mut args = [this_ptr, val.as_mut()]; - self.call_fn_raw(&make_setter(id), &mut args, None, *pos, 0) + self.call_fn_raw(None, &make_setter(id), &mut args, None, *pos, 0) }) } @@ -904,7 +1061,7 @@ impl Engine<'_> { Expr::Index(lhs, idx_expr, op_pos) => match lhs.as_ref() { // xxx.id[idx_expr].rhs Expr::Property(id, pos) => { - self.call_fn_raw(&make_getter(id), &mut [this_ptr], None, *pos, 0) + self.call_fn_raw(None, &make_getter(id), &mut [this_ptr], None, *pos, 0) .and_then(|v| { let (mut value, _, idx) = self.get_indexed_value(scope, &v, idx_expr, *op_pos, level)?; @@ -917,13 +1074,8 @@ impl Engine<'_> { Self::update_indexed_value(v, idx, value, val_pos) }) .and_then(|mut v| { - self.call_fn_raw( - &make_setter(id), - &mut [this_ptr, v.as_mut()], - None, - *pos, - 0, - ) + let mut args = [this_ptr, v.as_mut()]; + self.call_fn_raw(None, &make_setter(id), &mut args, None, *pos, 0) }) } @@ -1076,6 +1228,7 @@ impl Engine<'_> { *scope.get_mut(entry) = rhs_val.clone(); Ok(rhs_val) } + ScopeSource { typ: ScopeEntryType::Constant, .. @@ -1166,12 +1319,13 @@ impl Engine<'_> { Expr::FunctionCall(fn_name, args_expr_list, def_val, pos) => { // Has a system function an override? fn has_override(engine: &Engine, name: &str) -> bool { - let spec = FnSpec { - name: name.into(), - args: vec![TypeId::of::()], - }; - - engine.functions.contains_key(&spec) || engine.fn_lib.has_function(name, 1) + (engine.functions.is_some() && { + engine.functions.as_ref().unwrap().contains_key(&FnSpec { + name: name.into(), + args: vec![TypeId::of::()], + }) + }) || (engine.fn_lib.is_some() + && engine.fn_lib.as_ref().unwrap().has_function(name, 1)) } match fn_name.as_str() { @@ -1192,7 +1346,8 @@ impl Engine<'_> { .into_dynamic(); // Redirect call to `print` - self.call_fn_raw(KEYWORD_PRINT, &mut [result.as_mut()], None, pos, level) + let mut args = [result.as_mut()]; + self.call_fn_raw(None, KEYWORD_PRINT, &mut args, None, pos, level) } // type_of @@ -1213,6 +1368,7 @@ impl Engine<'_> { let pos = args_expr_list[0].position(); let r = self.eval_expr(scope, &args_expr_list[0], level)?; + // Get the script text by evaluating the expression let script = r.downcast_ref::() .map(String::as_str) @@ -1223,6 +1379,7 @@ impl Engine<'_> { ) })?; + // Compile the script text #[cfg(not(feature = "no_optimize"))] let ast = { let orig_optimization_level = self.optimization_level; @@ -1237,9 +1394,36 @@ impl Engine<'_> { #[cfg(feature = "no_optimize")] let ast = self.compile(script).map_err(EvalAltResult::ErrorParsing)?; - Ok(self - .eval_ast_with_scope_raw(scope, true, &ast) - .map_err(|err| err.set_position(pos))?) + // If new functions are defined, merge it into the current functions library + let merged = AST( + ast.0, + if let Some(fn_lib) = &self.fn_lib { + #[cfg(feature = "sync")] + { + Arc::new(fn_lib.as_ref().merge(&ast.1)) + } + #[cfg(not(feature = "sync"))] + { + Rc::new(fn_lib.as_ref().merge(&ast.1)) + } + } else { + ast.1 + }, + ); + + // Evaluate the AST + let result = self + .eval_ast_with_scope_raw(scope, &merged) + .map_err(|err| err.set_position(pos)); + + // Update the new functions library if there are new functions + self.fn_lib = if !merged.1.is_empty() { + Some(merged.1) + } else { + None + }; + + Ok(result?) } // Normal function call @@ -1252,38 +1436,40 @@ impl Engine<'_> { let mut arg_values: Vec<_> = values.iter_mut().map(Dynamic::as_mut).collect(); - self.call_fn_raw(fn_name, &mut arg_values, def_val.as_ref(), *pos, level) + let def_val = def_val.as_ref(); + + self.call_fn_raw(None, fn_name, &mut arg_values, def_val, *pos, level) } } } Expr::And(lhs, rhs) => Ok(Box::new( - *self + self .eval_expr(scope, &*lhs, level)? - .downcast::() + .try_cast::() .map_err(|_| { EvalAltResult::ErrorBooleanArgMismatch("AND".into(), lhs.position()) })? && // Short-circuit using && - *self + self .eval_expr(scope, &*rhs, level)? - .downcast::() + .try_cast::() .map_err(|_| { EvalAltResult::ErrorBooleanArgMismatch("AND".into(), rhs.position()) })?, )), Expr::Or(lhs, rhs) => Ok(Box::new( - *self + self .eval_expr(scope, &*lhs, level)? - .downcast::() + .try_cast::() .map_err(|_| { EvalAltResult::ErrorBooleanArgMismatch("OR".into(), lhs.position()) })? || // Short-circuit using || - *self + self .eval_expr(scope, &*rhs, level)? - .downcast::() + .try_cast::() .map_err(|_| { EvalAltResult::ErrorBooleanArgMismatch("OR".into(), rhs.position()) })?, @@ -1334,10 +1520,10 @@ impl Engine<'_> { // If-else statement Stmt::IfThenElse(guard, if_body, else_body) => self .eval_expr(scope, guard, level)? - .downcast::() + .try_cast::() .map_err(|_| EvalAltResult::ErrorLogicGuard(guard.position())) .and_then(|guard_val| { - if *guard_val { + if guard_val { self.eval_stmt(scope, if_body, level) } else if let Some(stmt) = else_body { self.eval_stmt(scope, stmt.as_ref(), level) @@ -1348,20 +1534,13 @@ impl Engine<'_> { // While loop Stmt::While(guard, body) => loop { - match self.eval_expr(scope, guard, level)?.downcast::() { - Ok(guard_val) => { - if *guard_val { - match self.eval_stmt(scope, body, level) { - Ok(_) => (), - Err(EvalAltResult::ErrorLoopBreak(_)) => { - return Ok(().into_dynamic()) - } - Err(x) => return Err(x), - } - } else { - return Ok(().into_dynamic()); - } - } + match self.eval_expr(scope, guard, level)?.try_cast::() { + Ok(guard_val) if guard_val => match self.eval_stmt(scope, body, level) { + Ok(_) | Err(EvalAltResult::ErrorLoopBreak(false, _)) => (), + Err(EvalAltResult::ErrorLoopBreak(true, _)) => return Ok(().into_dynamic()), + Err(x) => return Err(x), + }, + Ok(_) => return Ok(().into_dynamic()), Err(_) => return Err(EvalAltResult::ErrorLogicGuard(guard.position())), } }, @@ -1369,8 +1548,8 @@ impl Engine<'_> { // Loop statement Stmt::Loop(body) => loop { match self.eval_stmt(scope, body, level) { - Ok(_) => (), - Err(EvalAltResult::ErrorLoopBreak(_)) => return Ok(().into_dynamic()), + Ok(_) | Err(EvalAltResult::ErrorLoopBreak(false, _)) => (), + Err(EvalAltResult::ErrorLoopBreak(true, _)) => return Ok(().into_dynamic()), Err(x) => return Err(x), } }, @@ -1380,34 +1559,43 @@ impl Engine<'_> { let arr = self.eval_expr(scope, expr, level)?; let tid = Any::type_id(&*arr); - if let Some(iter_fn) = self.type_iterators.get(&tid) { - scope.push(name.clone(), ()); + if let Some(type_iterators) = &self.type_iterators { + if let Some(iter_fn) = type_iterators.get(&tid) { + // Add the loop variable - variable name is copied + // TODO - avoid copying variable name + scope.push(name.clone(), ()); - let entry = ScopeSource { - name, - index: scope.len() - 1, - typ: ScopeEntryType::Normal, - }; + let entry = ScopeSource { + name, + index: scope.len() - 1, + typ: ScopeEntryType::Normal, + }; - for a in iter_fn(&arr) { - *scope.get_mut(entry) = a; + for a in iter_fn(&arr) { + *scope.get_mut(entry) = a; - match self.eval_stmt(scope, body, level) { - Ok(_) => (), - Err(EvalAltResult::ErrorLoopBreak(_)) => break, - Err(x) => return Err(x), + match self.eval_stmt(scope, body, level) { + Ok(_) | Err(EvalAltResult::ErrorLoopBreak(false, _)) => (), + Err(EvalAltResult::ErrorLoopBreak(true, _)) => break, + Err(x) => return Err(x), + } } - } - scope.rewind(scope.len() - 1); - Ok(().into_dynamic()) + scope.rewind(scope.len() - 1); + Ok(().into_dynamic()) + } else { + Err(EvalAltResult::ErrorFor(expr.position())) + } } else { Err(EvalAltResult::ErrorFor(expr.position())) } } + // Continue statement + Stmt::Continue(pos) => Err(EvalAltResult::ErrorLoopBreak(false, *pos)), + // Break statement - Stmt::Break(pos) => Err(EvalAltResult::ErrorLoopBreak(*pos)), + Stmt::Break(pos) => Err(EvalAltResult::ErrorLoopBreak(true, *pos)), // Empty return Stmt::ReturnWithVal(None, ReturnType::Return, pos) => { @@ -1429,9 +1617,7 @@ impl Engine<'_> { Stmt::ReturnWithVal(Some(a), ReturnType::Exception, pos) => { let val = self.eval_expr(scope, a, level)?; Err(EvalAltResult::ErrorRuntime( - val.downcast::() - .map(|s| *s) - .unwrap_or_else(|_| "".to_string()), + val.try_cast::().unwrap_or_else(|_| "".to_string()), *pos, )) } @@ -1439,11 +1625,13 @@ impl Engine<'_> { // Let statement Stmt::Let(name, Some(expr), _) => { let val = self.eval_expr(scope, expr, level)?; + // TODO - avoid copying variable name in inner block? scope.push_dynamic_value(name.clone(), ScopeEntryType::Normal, val, false); Ok(().into_dynamic()) } Stmt::Let(name, None, _) => { + // TODO - avoid copying variable name in inner block? scope.push(name.clone(), ()); Ok(().into_dynamic()) } @@ -1451,6 +1639,7 @@ impl Engine<'_> { // Const statement Stmt::Const(name, expr, _) if expr.is_constant() => { let val = self.eval_expr(scope, expr, level)?; + // TODO - avoid copying variable name in inner block? scope.push_dynamic_value(name.clone(), ScopeEntryType::Constant, val, true); Ok(().into_dynamic()) } @@ -1461,15 +1650,21 @@ impl Engine<'_> { /// Map a type_name into a pretty-print name pub(crate) fn map_type_name<'a>(&'a self, name: &'a str) -> &'a str { - self.type_names - .get(name) - .map(String::as_str) - .unwrap_or(name) + if self.type_names.is_none() { + name + } else { + self.type_names + .as_ref() + .unwrap() + .get(name) + .map(String::as_str) + .unwrap_or(name) + } } /// Clean up all script-defined functions within the `Engine`. pub fn clear_functions(&mut self) { - self.fn_lib.clear(); + self.fn_lib = None; } } diff --git a/src/error.rs b/src/error.rs index 2a2986b8..0991a19b 100644 --- a/src/error.rs +++ b/src/error.rs @@ -30,9 +30,7 @@ impl fmt::Display for LexError { Self::MalformedEscapeSequence(s) => write!(f, "Invalid escape sequence: '{}'", s), Self::MalformedNumber(s) => write!(f, "Invalid number: '{}'", s), Self::MalformedChar(s) => write!(f, "Invalid character: '{}'", s), - Self::MalformedIdentifier(s) => { - write!(f, "Variable name is not in a legal format: '{}'", s) - } + Self::MalformedIdentifier(s) => write!(f, "Variable name is not proper: '{}'", s), Self::UnterminatedString => write!(f, "Open string is not terminated"), } } @@ -47,37 +45,51 @@ pub enum ParseErrorType { UnexpectedEOF, /// An unknown operator is encountered. Wrapped value is the operator. UnknownOperator(String), - /// Expecting a particular token but not finding one. Wrapped values are the token and usage. + /// Expecting a particular token but not finding one. Wrapped values are the token and description. MissingToken(String, String), - /// An expression in function call arguments `()` has syntax error. + /// An expression in function call arguments `()` has syntax error. Wrapped value is the error description (if any). MalformedCallExpr(String), - /// An expression in indexing brackets `[]` has syntax error. + /// An expression in indexing brackets `[]` has syntax error. Wrapped value is the error description (if any). + /// + /// Not available under the `no_index` feature. #[cfg(not(feature = "no_index"))] MalformedIndexExpr(String), - /// A map definition has duplicated property names. Wrapped is the property name. + /// A map definition has duplicated property names. Wrapped value is the property name. + /// + /// Not available under the `no_object` feature. #[cfg(not(feature = "no_object"))] DuplicatedProperty(String), - /// Invalid expression assigned to constant. + /// Invalid expression assigned to constant. Wrapped value is the name of the constant. ForbiddenConstantExpr(String), /// Missing a property name for custom types and maps. PropertyExpected, /// Missing a variable name after the `let`, `const` or `for` keywords. VariableExpected, - /// Missing an expression. + /// Missing an expression. Wrapped value is the expression type. ExprExpected(String), /// Defining a function `fn` in an appropriate place (e.g. inside another function). + /// + /// Not available under the `no_function` feature. #[cfg(not(feature = "no_function"))] WrongFnDefinition, /// Missing a function name after the `fn` keyword. + /// + /// Not available under the `no_function` feature. #[cfg(not(feature = "no_function"))] FnMissingName, /// A function definition is missing the parameters list. Wrapped value is the function name. + /// + /// Not available under the `no_function` feature. #[cfg(not(feature = "no_function"))] FnMissingParams(String), /// A function definition has duplicated parameters. Wrapped values are the function name and parameter name. + /// + /// Not available under the `no_function` feature. #[cfg(not(feature = "no_function"))] FnDuplicatedParam(String, String), /// A function definition is missing the body. Wrapped value is the function name. + /// + /// Not available under the `no_function` feature. #[cfg(not(feature = "no_function"))] FnMissingBody(String), /// Assignment to an inappropriate LHS (left-hand-side) expression. @@ -118,8 +130,8 @@ impl ParseError { } pub(crate) fn desc(&self) -> &str { - match self.0 { - ParseErrorType::BadInput(ref p) => p, + match &self.0 { + ParseErrorType::BadInput(p) => p, ParseErrorType::UnexpectedEOF => "Script is incomplete", ParseErrorType::UnknownOperator(_) => "Unknown operator", ParseErrorType::MissingToken(_, _) => "Expecting a certain token that is missing", @@ -154,50 +166,48 @@ impl Error for ParseError {} impl fmt::Display for ParseError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self.0 { - ParseErrorType::BadInput(ref s) | ParseErrorType::MalformedCallExpr(ref s) => { + match &self.0 { + ParseErrorType::BadInput(s) | ParseErrorType::MalformedCallExpr(s) => { write!(f, "{}", if s.is_empty() { self.desc() } else { s })? } - ParseErrorType::ForbiddenConstantExpr(ref s) => { + ParseErrorType::ForbiddenConstantExpr(s) => { write!(f, "Expecting a constant to assign to '{}'", s)? } - ParseErrorType::UnknownOperator(ref s) => write!(f, "{}: '{}'", self.desc(), s)?, + ParseErrorType::UnknownOperator(s) => write!(f, "{}: '{}'", self.desc(), s)?, #[cfg(not(feature = "no_index"))] - ParseErrorType::MalformedIndexExpr(ref s) => { + ParseErrorType::MalformedIndexExpr(s) => { write!(f, "{}", if s.is_empty() { self.desc() } else { s })? } #[cfg(not(feature = "no_object"))] - ParseErrorType::DuplicatedProperty(ref s) => { + ParseErrorType::DuplicatedProperty(s) => { write!(f, "Duplicated property '{}' for object map literal", s)? } - ParseErrorType::ExprExpected(ref s) => write!(f, "Expecting {} expression", s)?, + ParseErrorType::ExprExpected(s) => write!(f, "Expecting {} expression", s)?, #[cfg(not(feature = "no_function"))] - ParseErrorType::FnMissingParams(ref s) => { + ParseErrorType::FnMissingParams(s) => { write!(f, "Expecting parameters for function '{}'", s)? } #[cfg(not(feature = "no_function"))] - ParseErrorType::FnMissingBody(ref s) => { + ParseErrorType::FnMissingBody(s) => { write!(f, "Expecting body statement block for function '{}'", s)? } #[cfg(not(feature = "no_function"))] - ParseErrorType::FnDuplicatedParam(ref s, ref arg) => { + ParseErrorType::FnDuplicatedParam(s, arg) => { write!(f, "Duplicated parameter '{}' for function '{}'", arg, s)? } - ParseErrorType::MissingToken(ref token, ref s) => { - write!(f, "Expecting '{}' {}", token, s)? - } + ParseErrorType::MissingToken(token, s) => write!(f, "Expecting '{}' {}", token, s)?, - ParseErrorType::AssignmentToConstant(ref s) if s.is_empty() => { + ParseErrorType::AssignmentToConstant(s) if s.is_empty() => { write!(f, "{}", self.desc())? } - ParseErrorType::AssignmentToConstant(ref s) => { + ParseErrorType::AssignmentToConstant(s) => { write!(f, "Cannot assign to constant '{}'", s)? } _ => write!(f, "{}", self.desc())?, diff --git a/src/fn_register.rs b/src/fn_register.rs index cfc1b345..7ff2cc28 100644 --- a/src/fn_register.rs +++ b/src/fn_register.rs @@ -138,7 +138,13 @@ macro_rules! def_register { // ^ dereferencing function impl< $($par: Any + Clone,)* + + #[cfg(feature = "sync")] + FN: Fn($($param),*) -> RET + Send + Sync + 'static, + + #[cfg(not(feature = "sync"))] FN: Fn($($param),*) -> RET + 'static, + RET: Any > RegisterFn for Engine<'_> { @@ -171,6 +177,11 @@ macro_rules! def_register { impl< $($par: Any + Clone,)* + + #[cfg(feature = "sync")] + FN: Fn($($param),*) -> Dynamic + Send + Sync + 'static, + + #[cfg(not(feature = "sync"))] FN: Fn($($param),*) -> Dynamic + 'static, > RegisterDynamicFn for Engine<'_> { @@ -202,7 +213,12 @@ macro_rules! def_register { impl< $($par: Any + Clone,)* + + #[cfg(feature = "sync")] + FN: Fn($($param),*) -> Result + Send + Sync + 'static, + #[cfg(not(feature = "sync"))] FN: Fn($($param),*) -> Result + 'static, + RET: Any > RegisterResultFn for Engine<'_> { diff --git a/src/lib.rs b/src/lib.rs index a553a3a2..3321fccc 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -36,7 +36,23 @@ //! } //! ``` //! -//! [Check out the README on GitHub for more information!](https://github.com/jonathandturner/rhai) +//! ## Optional features +//! +//! | Feature | Description | +//! | ------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- | +//! | `no_stdlib` | Exclude the standard library of utility functions in the build, and only include the minimum necessary functionalities. Standard types are not affected. | +//! | `unchecked` | Exclude arithmetic checking (such as overflows and division by zero). Beware that a bad script may panic the entire system! | +//! | `no_function` | Disable script-defined functions if not needed. | +//! | `no_index` | Disable arrays and indexing features if not needed. | +//! | `no_object` | Disable support for custom types and objects. | +//! | `no_float` | Disable floating-point numbers and math if not needed. | +//! | `no_optimize` | Disable the script optimizer. | +//! | `only_i32` | Set the system integer type to `i32` and disable all other integer types. `INT` is set to `i32`. | +//! | `only_i64` | Set the system integer type to `i64` and disable all other integer types. `INT` is set to `i64`. | +//! | `no_std` | Build for `no-std`. Notice that additional dependencies will be pulled in to replace `std` features. | +//! | `sync` | Restrict all values types to those that are `Send + Sync`. Under this feature, `Engine`, `Scope` and `AST` are all `Send + Sync`. | +//! +//! [Check out the README on GitHub for details on the Rhai language!](https://github.com/jonathandturner/rhai) #![cfg_attr(feature = "no_std", no_std)] diff --git a/src/optimize.rs b/src/optimize.rs index 0651536c..ec5d3688 100644 --- a/src/optimize.rs +++ b/src/optimize.rs @@ -2,13 +2,15 @@ use crate::any::{Any, Dynamic}; use crate::engine::{ - Engine, KEYWORD_DEBUG, KEYWORD_DUMP_AST, KEYWORD_EVAL, KEYWORD_PRINT, KEYWORD_TYPE_OF, + Engine, FunctionsLib, KEYWORD_DEBUG, KEYWORD_DUMP_AST, KEYWORD_EVAL, KEYWORD_PRINT, + KEYWORD_TYPE_OF, }; use crate::parser::{map_dynamic_to_expr, Expr, FnDef, ReturnType, Stmt, AST}; use crate::scope::{Entry as ScopeEntry, EntryType as ScopeEntryType, Scope}; use crate::stdlib::{ boxed::Box, + rc::Rc, string::{String, ToString}, sync::Arc, vec, @@ -16,6 +18,8 @@ use crate::stdlib::{ }; /// Level of optimization performed. +/// +/// Not available under the `no_optimize` feature. #[derive(Debug, Eq, PartialEq, Hash, Clone, Copy)] pub enum OptimizationLevel { /// No optimization performed. @@ -449,9 +453,11 @@ fn optimize_expr<'a>(expr: Expr, state: &mut State<'a>) -> Expr { && args.iter().all(|expr| expr.is_constant()) // all arguments are constants => { // First search in script-defined functions (can override built-in) - if state.engine.fn_lib.has_function(&id, args.len()) { - // A script-defined function overrides the built-in function - do not make the call - return Expr::FunctionCall(id, args.into_iter().map(|a| optimize_expr(a, state)).collect(), def_value, pos); + if let Some(fn_lib_arc) = &state.engine.fn_lib { + if fn_lib_arc.has_function(&id, args.len()) { + // A script-defined function overrides the built-in function - do not make the call + return Expr::FunctionCall(id, args.into_iter().map(|a| optimize_expr(a, state)).collect(), def_value, pos); + } } let mut arg_values: Vec<_> = args.iter().map(Expr::get_constant_value).collect(); @@ -474,7 +480,7 @@ fn optimize_expr<'a>(expr: Expr, state: &mut State<'a>) -> Expr { // Otherwise use the default value, if any def_value.clone() } - }).and_then(|result| map_dynamic_to_expr(result, pos).0) + }).and_then(|result| map_dynamic_to_expr(result, pos)) .map(|expr| { state.set_dirty(); expr @@ -487,11 +493,11 @@ fn optimize_expr<'a>(expr: Expr, state: &mut State<'a>) -> Expr { Expr::FunctionCall(id, args.into_iter().map(|a| optimize_expr(a, state)).collect(), def_value, pos), // constant-name - Expr::Variable(ref name, _) if state.contains_constant(name) => { + Expr::Variable(name, _) if state.contains_constant(&name) => { state.set_dirty(); // Replace constant with value - state.find_constant(name).expect("should find constant in scope!").clone() + state.find_constant(&name).expect("should find constant in scope!").clone() } // All other expressions - skip @@ -580,15 +586,10 @@ pub fn optimize_into_ast( statements: Vec, functions: Vec, ) -> AST { - AST( - match engine.optimization_level { - OptimizationLevel::None => statements, - OptimizationLevel::Simple | OptimizationLevel::Full => { - optimize(statements, engine, &scope) - } - }, + let fn_lib = FunctionsLib::from_vec( functions - .into_iter() + .iter() + .cloned() .map(|mut fn_def| { if engine.optimization_level != OptimizationLevel::None { let pos = fn_def.body.position(); @@ -608,9 +609,21 @@ pub fn optimize_into_ast( stmt => stmt, }; } - - Arc::new(fn_def) + fn_def }) .collect(), + ); + + AST( + match engine.optimization_level { + OptimizationLevel::None => statements, + OptimizationLevel::Simple | OptimizationLevel::Full => { + optimize(statements, engine, &scope) + } + }, + #[cfg(feature = "sync")] + Arc::new(fn_lib), + #[cfg(not(feature = "sync"))] + Rc::new(fn_lib), ) } diff --git a/src/parser.rs b/src/parser.rs index f44660c4..8dcbbc98 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -1,7 +1,7 @@ //! Main module defining the lexer and parser. use crate::any::{Any, AnyExt, Dynamic}; -use crate::engine::Engine; +use crate::engine::{Engine, FunctionsLib}; use crate::error::{LexError, ParseError, ParseErrorType}; use crate::scope::{EntryType as ScopeEntryType, Scope}; @@ -16,6 +16,7 @@ use crate::stdlib::{ fmt, format, iter::Peekable, ops::Add, + rc::Rc, str::Chars, str::FromStr, string::{String, ToString}, @@ -37,6 +38,8 @@ pub type INT = i64; pub type INT = i32; /// The system floating-point type. +/// +/// Not available under the `no_float` feature. #[cfg(not(feature = "no_float"))] pub type FLOAT = f64; @@ -160,11 +163,22 @@ impl fmt::Debug for Position { } /// Compiled AST (abstract syntax tree) of a Rhai script. +/// +/// Currently, `AST` is neither `Send` nor `Sync`. Turn on the `sync` feature to make it `Send + Sync`. #[derive(Debug, Clone)] -pub struct AST(pub(crate) Vec, pub(crate) Vec>); +pub struct AST( + pub(crate) Vec, + #[cfg(feature = "sync")] pub(crate) Arc, + #[cfg(not(feature = "sync"))] pub(crate) Rc, +); impl AST { - /// Merge two `AST` into one. Both `AST`'s are consumed and a new, merged, version + /// Create a new `AST`. + pub fn new() -> Self { + Default::default() + } + + /// Merge two `AST` into one. Both `AST`'s are untouched and a new, merged, version /// is returned. /// /// The second `AST` is simply appended to the end of the first _without any processing_. @@ -188,10 +202,10 @@ impl AST { /// let ast1 = engine.compile(r#"fn foo(x) { 42 + x } foo(1)"#)?; /// 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' /// /// // Notice that using the '+' operator also works: - /// // let ast = ast1 + ast2; + /// // let ast = &ast1 + &ast2; /// /// // 'ast' is essentially: /// // @@ -206,29 +220,62 @@ impl AST { /// # Ok(()) /// # } /// ``` - pub fn merge(self, mut other: Self) -> Self { - let Self(mut ast, mut functions) = self; + pub fn merge(&self, other: &Self) -> Self { + let Self(statements, functions) = self; - ast.append(&mut other.0); - - for fn_def in other.1 { - if let Some((n, _)) = functions - .iter() - .enumerate() - .find(|(_, f)| f.name == fn_def.name && f.params.len() == fn_def.params.len()) - { - functions[n] = fn_def; - } else { - functions.push(fn_def); + let ast = match (statements.is_empty(), other.0.is_empty()) { + (false, false) => { + let mut statements = statements.clone(); + statements.extend(other.0.iter().cloned()); + statements } - } + (false, true) => statements.clone(), + (true, false) => other.0.clone(), + (true, true) => vec![], + }; - Self(ast, functions) + #[cfg(feature = "sync")] + { + Self(ast, Arc::new(functions.merge(other.1.as_ref()))) + } + #[cfg(not(feature = "sync"))] + { + Self(ast, Rc::new(functions.merge(other.1.as_ref()))) + } + } + + /// Clear all function definitions in the `AST`. + pub fn clear_functions(&mut self) { + #[cfg(feature = "sync")] + { + self.1 = Arc::new(FunctionsLib::new()); + } + #[cfg(not(feature = "sync"))] + { + self.1 = Rc::new(FunctionsLib::new()); + } + } + + /// Clear all statements in the `AST`, leaving only function definitions. + pub fn retain_functions(&mut self) { + self.0 = vec![]; } } -impl Add for AST { - type Output = Self; +impl Default for AST { + fn default() -> Self { + #[cfg(feature = "sync")] + { + Self(vec![], Arc::new(FunctionsLib::new())) + } + #[cfg(not(feature = "sync"))] + { + Self(vec![], Rc::new(FunctionsLib::new())) + } + } +} +impl Add for &AST { + type Output = AST; fn add(self, rhs: Self) -> Self::Output { self.merge(rhs) @@ -278,6 +325,8 @@ pub enum Stmt { Block(Vec, Position), /// { stmt } Expr(Box), + /// continue + Continue(Position), /// break Break(Position), /// `return`/`throw` @@ -292,6 +341,7 @@ impl Stmt { | Stmt::Let(_, _, pos) | Stmt::Const(_, _, pos) | Stmt::Block(_, pos) + | Stmt::Continue(pos) | Stmt::Break(pos) | Stmt::ReturnWithVal(_, _, pos) => *pos, Stmt::IfThenElse(expr, _, _) | Stmt::Expr(expr) => expr.position(), @@ -314,6 +364,7 @@ impl Stmt { Stmt::Let(_, _, _) | Stmt::Const(_, _, _) | Stmt::Expr(_) + | Stmt::Continue(_) | Stmt::Break(_) | Stmt::ReturnWithVal(_, _, _) => false, } @@ -334,7 +385,7 @@ impl Stmt { Stmt::For(_, range, block) => range.is_pure() && block.is_pure(), Stmt::Let(_, _, _) | Stmt::Const(_, _, _) => false, Stmt::Block(statements, _) => statements.iter().all(Stmt::is_pure), - Stmt::Break(_) | Stmt::ReturnWithVal(_, _, _) => false, + Stmt::Continue(_) | Stmt::Break(_) | Stmt::ReturnWithVal(_, _, _) => false, } } } @@ -579,6 +630,7 @@ pub enum Token { And, #[cfg(not(feature = "no_function"))] Fn, + Continue, Break, Return, Throw, @@ -653,6 +705,7 @@ impl Token { And => "&&", #[cfg(not(feature = "no_function"))] Fn => "fn", + Continue => "continue", Break => "break", Return => "return", Throw => "throw", @@ -1100,6 +1153,7 @@ impl<'a> TokenIterator<'a> { "else" => Token::Else, "while" => Token::While, "loop" => Token::Loop, + "continue" => Token::Continue, "break" => Token::Break, "return" => Token::Return, "throw" => Token::Throw, @@ -1679,8 +1733,8 @@ fn parse_map_literal<'a>( 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), + (Token::Identifier(s), pos) => (s, pos), + (Token::StringConst(s), pos) => (s, pos), (_, pos) if map.is_empty() => { return Err(PERR::MissingToken( "}".into(), @@ -2007,7 +2061,7 @@ fn parse_binary_op<'a>( let mut current_lhs = lhs; loop { - let (current_precedence, bind_right) = if let Some((ref current_op, _)) = input.peek() { + let (current_precedence, bind_right) = if let Some((current_op, _)) = input.peek() { (current_op.precedence(), current_op.is_bind_right()) } else { (0, false) @@ -2419,6 +2473,8 @@ fn parse_stmt<'a>( // Semicolon - empty statement (Token::SemiColon, pos) => Ok(Stmt::Noop(*pos)), + (Token::LeftBrace, _) => parse_block(input, breakable, allow_stmt_expr), + // fn ... #[cfg(not(feature = "no_function"))] (Token::Fn, pos) => Err(PERR::WrongFnDefinition.into_err(*pos)), @@ -2427,12 +2483,19 @@ fn parse_stmt<'a>( (Token::While, _) => parse_while(input, allow_stmt_expr), (Token::Loop, _) => parse_loop(input, allow_stmt_expr), (Token::For, _) => parse_for(input, allow_stmt_expr), + + (Token::Continue, pos) if breakable => { + let pos = *pos; + input.next(); + Ok(Stmt::Continue(pos)) + } (Token::Break, pos) if breakable => { let pos = *pos; input.next(); Ok(Stmt::Break(pos)) } - (Token::Break, pos) => Err(PERR::LoopBreak.into_err(*pos)), + (Token::Continue, pos) | (Token::Break, pos) => Err(PERR::LoopBreak.into_err(*pos)), + (token @ Token::Return, pos) | (token @ Token::Throw, pos) => { let return_type = match token { Token::Return => ReturnType::Return, @@ -2456,9 +2519,10 @@ fn parse_stmt<'a>( } } } - (Token::LeftBrace, _) => parse_block(input, breakable, allow_stmt_expr), + (Token::Let, _) => parse_let(input, ScopeEntryType::Normal, allow_stmt_expr), (Token::Const, _) => parse_let(input, ScopeEntryType::Constant, allow_stmt_expr), + _ => parse_expr_stmt(input, allow_stmt_expr), } } @@ -2571,7 +2635,17 @@ pub fn parse_global_expr<'a, 'e>( // // Do not optimize AST if `no_optimize` #[cfg(feature = "no_optimize")] - AST(vec![Stmt::Expr(Box::new(expr))], vec![]), + AST( + vec![Stmt::Expr(Box::new(expr))], + #[cfg(feature = "sync")] + { + Arc::new(FunctionsLib::new()) + }, + #[cfg(not(feature = "sync"))] + { + Rc::new(FunctionsLib::new()) + }, + ), ) } @@ -2646,68 +2720,34 @@ pub fn parse<'a, 'e>( // // Do not optimize AST if `no_optimize` #[cfg(feature = "no_optimize")] - AST(statements, functions.into_iter().map(Arc::new).collect()), + AST(statements, Arc::new(FunctionsLib::from_vec(functions))), ) } /// Map a `Dynamic` value to an expression. /// /// Returns Some(expression) if conversion is successful. Otherwise None. -pub fn map_dynamic_to_expr(value: Dynamic, pos: Position) -> (Option, Dynamic) { +pub fn map_dynamic_to_expr(value: Dynamic, pos: Position) -> Option { if value.is::() { - let value2 = value.clone(); - ( - Some(Expr::IntegerConstant( - *value.downcast::().expect("value should be INT"), - pos, - )), - value2, - ) + Some(Expr::IntegerConstant(value.cast(), pos)) } else if value.is::() { - let value2 = value.clone(); - ( - Some(Expr::CharConstant( - *value.downcast::().expect("value should be char"), - pos, - )), - value2, - ) + Some(Expr::CharConstant(value.cast(), pos)) } else if value.is::() { - let value2 = value.clone(); - ( - Some(Expr::StringConstant( - *value.downcast::().expect("value should be String"), - pos, - )), - value2, - ) + Some(Expr::StringConstant(value.cast(), pos)) } else if value.is::() { - let value2 = value.clone(); - ( - Some( - if *value.downcast::().expect("value should be bool") { - Expr::True(pos) - } else { - Expr::False(pos) - }, - ), - value2, - ) + Some(if value.cast::() { + Expr::True(pos) + } else { + Expr::False(pos) + }) } else { #[cfg(not(feature = "no_float"))] { if value.is::() { - let value2 = value.clone(); - return ( - Some(Expr::FloatConstant( - *value.downcast::().expect("value should be FLOAT"), - pos, - )), - value2, - ); + return Some(Expr::FloatConstant(value.cast(), pos)); } } - (None, value) + None } } diff --git a/src/result.rs b/src/result.rs index 0c882609..57594ccc 100644 --- a/src/result.rs +++ b/src/result.rs @@ -22,6 +22,8 @@ pub enum EvalAltResult { ErrorParsing(ParseError), /// Error reading from a script file. Wrapped value is the path of the script file. + /// + /// Not available under the `no_std` feature. #[cfg(not(feature = "no_std"))] ErrorReadingScriptFile(PathBuf, std::io::Error), @@ -70,7 +72,9 @@ pub enum EvalAltResult { ErrorRuntime(String, Position), /// Breaking out of loops - not an error if within a loop. - ErrorLoopBreak(Position), + /// The wrapped value, if true, means breaking clean out of the loop (i.e. a `break` statement). + /// The wrapped value, if false, means breaking the current context (i.e. a `continue` statement). + ErrorLoopBreak(bool, Position), /// Not an error: Value returned from a script via the `return` keyword. /// Wrapped value is the result value. Return(Dynamic, Position), @@ -118,7 +122,8 @@ impl EvalAltResult { Self::ErrorArithmetic(_, _) => "Arithmetic error", Self::ErrorStackOverflow(_) => "Stack overflow", Self::ErrorRuntime(_, _) => "Runtime error", - Self::ErrorLoopBreak(_) => "Break statement not inside a loop", + Self::ErrorLoopBreak(true, _) => "Break statement not inside a loop", + Self::ErrorLoopBreak(false, _) => "Continue statement not inside a loop", Self::Return(_, _) => "[Not Error] Function returns value", } } @@ -160,7 +165,7 @@ impl fmt::Display for EvalAltResult { 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::ErrorLoopBreak(_, pos) => write!(f, "{} ({})", desc, pos), Self::Return(_, pos) => write!(f, "{} ({})", desc, pos), Self::ErrorFunctionArgsMismatch(fn_name, 0, n, pos) => write!( @@ -255,7 +260,7 @@ impl EvalAltResult { | Self::ErrorArithmetic(_, pos) | Self::ErrorStackOverflow(pos) | Self::ErrorRuntime(_, pos) - | Self::ErrorLoopBreak(pos) + | Self::ErrorLoopBreak(_, pos) | Self::Return(_, pos) => *pos, } } @@ -263,32 +268,32 @@ impl EvalAltResult { /// Consume the current `EvalAltResult` and return a new one /// with the specified `Position`. pub(crate) fn set_position(mut self, new_position: Position) -> Self { - match self { + match &mut self { #[cfg(not(feature = "no_std"))] Self::ErrorReadingScriptFile(_, _) => (), - Self::ErrorParsing(ParseError(_, ref mut pos)) - | Self::ErrorFunctionNotFound(_, ref mut pos) - | Self::ErrorFunctionArgsMismatch(_, _, _, ref mut pos) - | Self::ErrorBooleanArgMismatch(_, ref mut pos) - | Self::ErrorCharMismatch(ref mut pos) - | Self::ErrorArrayBounds(_, _, ref mut pos) - | Self::ErrorStringBounds(_, _, ref mut pos) - | Self::ErrorIndexingType(_, ref mut pos) - | Self::ErrorNumericIndexExpr(ref mut pos) - | Self::ErrorStringIndexExpr(ref mut pos) - | Self::ErrorLogicGuard(ref mut pos) - | Self::ErrorFor(ref mut pos) - | Self::ErrorVariableNotFound(_, ref mut pos) - | Self::ErrorAssignmentToUnknownLHS(ref mut pos) - | Self::ErrorAssignmentToConstant(_, ref mut pos) - | Self::ErrorMismatchOutputType(_, ref mut pos) - | Self::ErrorDotExpr(_, ref mut pos) - | Self::ErrorArithmetic(_, ref mut pos) - | Self::ErrorStackOverflow(ref mut pos) - | Self::ErrorRuntime(_, ref mut pos) - | Self::ErrorLoopBreak(ref mut pos) - | Self::Return(_, ref mut pos) => *pos = new_position, + Self::ErrorParsing(ParseError(_, pos)) + | Self::ErrorFunctionNotFound(_, pos) + | Self::ErrorFunctionArgsMismatch(_, _, _, pos) + | Self::ErrorBooleanArgMismatch(_, pos) + | Self::ErrorCharMismatch(pos) + | Self::ErrorArrayBounds(_, _, pos) + | Self::ErrorStringBounds(_, _, pos) + | Self::ErrorIndexingType(_, pos) + | Self::ErrorNumericIndexExpr(pos) + | Self::ErrorStringIndexExpr(pos) + | Self::ErrorLogicGuard(pos) + | Self::ErrorFor(pos) + | Self::ErrorVariableNotFound(_, pos) + | Self::ErrorAssignmentToUnknownLHS(pos) + | Self::ErrorAssignmentToConstant(_, pos) + | Self::ErrorMismatchOutputType(_, pos) + | Self::ErrorDotExpr(_, pos) + | Self::ErrorArithmetic(_, pos) + | Self::ErrorStackOverflow(pos) + | Self::ErrorRuntime(_, pos) + | Self::ErrorLoopBreak(_, pos) + | Self::Return(_, pos) => *pos = new_position, } self diff --git a/src/scope.rs b/src/scope.rs index bf66564d..15eda16c 100644 --- a/src/scope.rs +++ b/src/scope.rs @@ -52,44 +52,127 @@ pub(crate) struct EntryRef<'a> { /// let mut engine = Engine::new(); /// let mut my_scope = Scope::new(); /// -/// engine.eval_with_scope::<()>(&mut my_scope, "let x = 5;")?; +/// my_scope.push("z", 40_i64); /// -/// assert_eq!(engine.eval_with_scope::(&mut my_scope, "x + 1")?, 6); +/// engine.eval_with_scope::<()>(&mut my_scope, "let x = z + 1; z = 0;")?; +/// +/// assert_eq!(engine.eval_with_scope::(&mut my_scope, "x + 1")?, 42); +/// +/// assert_eq!(my_scope.get_value::("x").unwrap(), 41); +/// assert_eq!(my_scope.get_value::("z").unwrap(), 0); /// # Ok(()) /// # } /// ``` /// /// When searching for entries, newly-added entries are found before similarly-named but older entries, /// allowing for automatic _shadowing_. +/// +/// Currently, `Scope` is neither `Send` nor `Sync`. Turn on the `sync` feature to make it `Send + Sync`. +#[derive(Debug, Clone)] pub struct Scope<'a>(Vec>); impl<'a> Scope<'a> { /// Create a new Scope. + /// + /// # Examples + /// + /// ``` + /// use rhai::Scope; + /// + /// let mut my_scope = Scope::new(); + /// + /// my_scope.push("x", 42_i64); + /// assert_eq!(my_scope.get_value::("x").unwrap(), 42); + /// ``` pub fn new() -> Self { Self(Vec::new()) } /// Empty the Scope. + /// + /// # Examples + /// + /// ``` + /// use rhai::Scope; + /// + /// let mut my_scope = Scope::new(); + /// + /// my_scope.push("x", 42_i64); + /// assert!(my_scope.contains("x")); + /// assert_eq!(my_scope.len(), 1); + /// assert!(!my_scope.is_empty()); + /// + /// my_scope.clear(); + /// assert!(!my_scope.contains("x")); + /// assert_eq!(my_scope.len(), 0); + /// assert!(my_scope.is_empty()); + /// ``` pub fn clear(&mut self) { self.0.clear(); } /// Get the number of entries inside the Scope. + /// + /// # Examples + /// + /// ``` + /// use rhai::Scope; + /// + /// let mut my_scope = Scope::new(); + /// assert_eq!(my_scope.len(), 0); + /// + /// my_scope.push("x", 42_i64); + /// assert_eq!(my_scope.len(), 1); + /// ``` pub fn len(&self) -> usize { self.0.len() } /// Is the Scope empty? + /// + /// # Examples + /// + /// ``` + /// use rhai::Scope; + /// + /// let mut my_scope = Scope::new(); + /// assert!(my_scope.is_empty()); + /// + /// my_scope.push("x", 42_i64); + /// assert!(!my_scope.is_empty()); + /// ``` pub fn is_empty(&self) -> bool { self.0.len() == 0 } /// Add (push) a new entry to the Scope. + /// + /// # Examples + /// + /// ``` + /// use rhai::Scope; + /// + /// let mut my_scope = Scope::new(); + /// + /// my_scope.push("x", 42_i64); + /// assert_eq!(my_scope.get_value::("x").unwrap(), 42); + /// ``` pub fn push>, T: Any + Clone>(&mut self, name: K, value: T) { self.push_dynamic_value(name, EntryType::Normal, value.into_dynamic(), false); } /// Add (push) a new `Dynamic` entry to the Scope. + /// + /// # Examples + /// + /// ``` + /// use rhai::{Any, Scope}; + /// + /// let mut my_scope = Scope::new(); + /// + /// my_scope.push_dynamic("x", (42_i64).into_dynamic()); + /// assert_eq!(my_scope.get_value::("x").unwrap(), 42); + /// ``` pub fn push_dynamic>>(&mut self, name: K, value: Dynamic) { self.push_dynamic_value(name, EntryType::Normal, value, false); } @@ -98,8 +181,20 @@ impl<'a> Scope<'a> { /// /// Constants are immutable and cannot be assigned to. Their values never change. /// Constants propagation is a technique used to optimize an AST. + /// /// However, in order to be used for optimization, constants must be in one of the recognized types: /// `INT` (default to `i64`, `i32` if `only_i32`), `f64`, `String`, `char` and `bool`. + /// + /// # Examples + /// + /// ``` + /// use rhai::Scope; + /// + /// let mut my_scope = Scope::new(); + /// + /// my_scope.push_constant("x", 42_i64); + /// assert_eq!(my_scope.get_value::("x").unwrap(), 42); + /// ``` pub fn push_constant>, T: Any + Clone>(&mut self, name: K, value: T) { self.push_dynamic_value(name, EntryType::Constant, value.into_dynamic(), true); } @@ -108,9 +203,21 @@ impl<'a> Scope<'a> { /// /// Constants are immutable and cannot be assigned to. Their values never change. /// Constants propagation is a technique used to optimize an AST. + /// /// However, in order to be used for optimization, the `Dynamic` value must be in one of the /// recognized types: /// `INT` (default to `i64`, `i32` if `only_i32`), `f64`, `String`, `char` and `bool`. + /// + /// # Examples + /// + /// ``` + /// use rhai::{Any, Scope}; + /// + /// let mut my_scope = Scope::new(); + /// + /// my_scope.push_constant_dynamic("x", (42_i64).into_dynamic()); + /// assert_eq!(my_scope.get_value::("x").unwrap(), 42); + /// ``` pub fn push_constant_dynamic>>(&mut self, name: K, value: Dynamic) { self.push_dynamic_value(name, EntryType::Constant, value, true); } @@ -123,71 +230,163 @@ impl<'a> Scope<'a> { value: Dynamic, map_expr: bool, ) { - let (expr, value) = if map_expr { - map_dynamic_to_expr(value, Position::none()) - } else { - (None, value) - }; - self.0.push(Entry { name: name.into(), typ: entry_type, - value, - expr, + value: value.clone(), + expr: if map_expr { + map_dynamic_to_expr(value, Position::none()) + } else { + None + }, }); } /// Truncate (rewind) the Scope to a previous size. + /// + /// # Examples + /// + /// ``` + /// use rhai::Scope; + /// + /// let mut my_scope = Scope::new(); + /// + /// my_scope.push("x", 42_i64); + /// my_scope.push("y", 123_i64); + /// assert!(my_scope.contains("x")); + /// assert!(my_scope.contains("y")); + /// assert_eq!(my_scope.len(), 2); + /// + /// my_scope.rewind(1); + /// assert!(my_scope.contains("x")); + /// assert!(!my_scope.contains("y")); + /// assert_eq!(my_scope.len(), 1); + /// + /// my_scope.rewind(0); + /// assert!(!my_scope.contains("x")); + /// assert!(!my_scope.contains("y")); + /// assert_eq!(my_scope.len(), 0); + /// assert!(my_scope.is_empty()); + /// ``` pub fn rewind(&mut self, size: usize) { self.0.truncate(size); } /// Does the scope contain the entry? - pub fn contains(&self, key: &str) -> bool { + /// + /// # Examples + /// + /// ``` + /// use rhai::Scope; + /// + /// let mut my_scope = Scope::new(); + /// + /// my_scope.push("x", 42_i64); + /// assert!(my_scope.contains("x")); + /// assert!(!my_scope.contains("y")); + /// ``` + pub fn contains(&self, name: &str) -> bool { self.0 .iter() - .enumerate() .rev() // Always search a Scope in reverse order - .any(|(_, Entry { name, .. })| name == key) + .any(|Entry { name: key, .. }| name == key) } /// Find an entry in the Scope, starting from the last. - pub(crate) fn get(&self, key: &str) -> Option<(EntryRef, Dynamic)> { + pub(crate) fn get(&self, name: &str) -> Option<(EntryRef, Dynamic)> { self.0 .iter() .enumerate() .rev() // Always search a Scope in reverse order - .find(|(_, Entry { name, .. })| name == key) - .map( + .find_map( |( - i, + index, Entry { - name, typ, value, .. + name: key, + typ, + value, + .. }, )| { - ( - EntryRef { - name: name.as_ref(), - index: i, - typ: *typ, - }, - value.clone(), - ) + if name == key { + Some(( + EntryRef { + name: key, + index, + typ: *typ, + }, + value.clone(), + )) + } else { + None + } }, ) } /// Get the value of an entry in the Scope, starting from the last. - pub fn get_value(&self, key: &str) -> Option { + /// + /// # Examples + /// + /// ``` + /// use rhai::Scope; + /// + /// let mut my_scope = Scope::new(); + /// + /// my_scope.push("x", 42_i64); + /// assert_eq!(my_scope.get_value::("x").unwrap(), 42); + /// ``` + pub fn get_value(&self, name: &str) -> Option { self.0 .iter() - .enumerate() - .rev() // Always search a Scope in reverse order - .find(|(_, Entry { name, .. })| name == key) - .and_then(|(_, Entry { value, .. })| value.downcast_ref::()) + .rev() + .find(|Entry { name: key, .. }| name == key) + .and_then(|Entry { value, .. }| value.downcast_ref::()) .map(T::clone) } + /// Update the value of the named entry. + /// Search starts backwards from the last, and only the first entry matching the specified name is updated. + /// If no entry matching the specified name is found, a new one is added. + /// + /// # Panics + /// + /// Panics when trying to update the value of a constant. + /// + /// # Examples + /// + /// ``` + /// use rhai::Scope; + /// + /// let mut my_scope = Scope::new(); + /// + /// my_scope.push("x", 42_i64); + /// assert_eq!(my_scope.get_value::("x").unwrap(), 42); + /// + /// my_scope.set_value("x", 0_i64); + /// assert_eq!(my_scope.get_value::("x").unwrap(), 0); + /// ``` + pub fn set_value(&mut self, name: &'a str, value: T) { + match self.get(name) { + Some(( + EntryRef { + typ: EntryType::Constant, + .. + }, + _, + )) => panic!("variable {} is constant", name), + Some(( + EntryRef { + index, + typ: EntryType::Normal, + .. + }, + _, + )) => self.0.get_mut(index).unwrap().value = value.into_dynamic(), + None => self.push(name, value.into_dynamic()), + } + } + /// Get a mutable reference to an entry in the Scope. pub(crate) fn get_mut(&mut self, key: EntryRef) -> &mut Dynamic { let entry = self.0.get_mut(key.index).expect("invalid index in Scope"); diff --git a/tests/arrays.rs b/tests/arrays.rs index 5cfec145..1a7517ae 100644 --- a/tests/arrays.rs +++ b/tests/arrays.rs @@ -1,5 +1,5 @@ #![cfg(not(feature = "no_index"))] -use rhai::{Engine, EvalAltResult, RegisterFn, INT}; +use rhai::{Array, Engine, EvalAltResult, RegisterFn, INT}; #[test] fn test_arrays() -> Result<(), EvalAltResult> { @@ -12,6 +12,43 @@ fn test_arrays() -> Result<(), EvalAltResult> { '3' ); + #[cfg(not(feature = "no_stdlib"))] + { + assert_eq!( + engine.eval::( + r" + let x = [1, 2, 3]; + let y = [4, 5]; + x.append(y); + x.len() + " + )?, + 5 + ); + assert_eq!( + engine.eval::( + r" + let x = [1, 2, 3]; + x += [4, 5]; + x.len() + " + )?, + 5 + ); + assert_eq!( + engine + .eval::( + r" + let x = [1, 2, 3]; + let y = [4, 5]; + x + y + " + )? + .len(), + 5 + ); + } + Ok(()) } diff --git a/tests/call_fn.rs b/tests/call_fn.rs index c5b8b307..909e8a90 100644 --- a/tests/call_fn.rs +++ b/tests/call_fn.rs @@ -1,5 +1,5 @@ #![cfg(not(feature = "no_function"))] -use rhai::{Engine, EvalAltResult, ParseErrorType, INT}; +use rhai::{Engine, EvalAltResult, ParseErrorType, Scope, INT}; #[test] fn test_fn() -> Result<(), EvalAltResult> { @@ -21,24 +21,41 @@ fn test_fn() -> Result<(), EvalAltResult> { #[test] fn test_call_fn() -> Result<(), EvalAltResult> { let mut engine = Engine::new(); + let mut scope = Scope::new(); - engine.consume( - true, + scope.push("foo", 42 as INT); + + let ast = engine.compile( r" fn hello(x, y) { x + y } fn hello(x) { - x * 2 + x = x * foo; + foo = 1; + x } - ", + fn hello() { + 41 + foo + } + ", )?; - let r: i64 = engine.call_fn("hello", (42 as INT, 123 as INT))?; + let r: i64 = engine.call_fn(&mut scope, &ast, "hello", (42 as INT, 123 as INT))?; assert_eq!(r, 165); - let r: i64 = engine.call_fn("hello", 123 as INT)?; - assert_eq!(r, 246); + let r: i64 = engine.call_fn1(&mut scope, &ast, "hello", 123 as INT)?; + assert_eq!(r, 5166); + + let r: i64 = engine.call_fn0(&mut scope, &ast, "hello")?; + assert_eq!(r, 42); + + assert_eq!( + scope + .get_value::("foo") + .expect("variable foo should exist"), + 1 + ); Ok(()) } diff --git a/tests/for.rs b/tests/for.rs index be476328..448c7fcb 100644 --- a/tests/for.rs +++ b/tests/for.rs @@ -1,8 +1,8 @@ -#![cfg(not(feature = "no_index"))] use rhai::{Engine, EvalAltResult, INT}; +#[cfg(not(feature = "no_index"))] #[test] -fn test_for() -> Result<(), EvalAltResult> { +fn test_for_array() -> Result<(), EvalAltResult> { let mut engine = Engine::new(); let script = r" @@ -18,10 +18,39 @@ fn test_for() -> Result<(), EvalAltResult> { sum2 += x; } + for x in range(1, 6, 3) { + sum2 += x; + } + sum1 + sum2 "; - assert_eq!(engine.eval::(script)?, 30); + assert_eq!(engine.eval::(script)?, 35); + + Ok(()) +} + +#[cfg(not(feature = "no_object"))] +#[test] +fn test_for_object() -> Result<(), EvalAltResult> { + let mut engine = Engine::new(); + + let script = r#" + let sum = 0; + let keys = ""; + let map = #{a: 1, b: 2, c: 3}; + + for key in keys(map) { + keys += key; + } + for value in values(map) { + sum += value; + } + + keys.len() + sum + "#; + + assert_eq!(engine.eval::(script)?, 9); Ok(()) } diff --git a/tests/looping.rs b/tests/looping.rs index 651cbe32..bf2c1a72 100644 --- a/tests/looping.rs +++ b/tests/looping.rs @@ -12,8 +12,9 @@ fn test_loop() -> Result<(), EvalAltResult> { loop { if i < 10 { + i += 1; + if x > 20 { continue; } x = x + i; - i = i + 1; } else { break; } @@ -22,7 +23,7 @@ fn test_loop() -> Result<(), EvalAltResult> { return x; " )?, - 45 + 21 ); Ok(()) diff --git a/tests/maps.rs b/tests/maps.rs index 0da9b589..5ed42519 100644 --- a/tests/maps.rs +++ b/tests/maps.rs @@ -7,29 +7,65 @@ fn test_map_indexing() -> Result<(), EvalAltResult> { let mut engine = Engine::new(); #[cfg(not(feature = "no_index"))] - assert_eq!( - engine.eval::(r#"let x = #{a: 1, b: 2, c: 3}; x["b"]"#)?, - 2 - ); + { + assert_eq!( + engine.eval::(r#"let x = #{a: 1, b: 2, c: 3}; x["b"]"#)?, + 2 + ); + assert_eq!( + engine.eval::( + r#" + let y = #{d: 1, "e": #{a: 42, b: 88, "": "hello"}, " 123 xyz": 9}; + y.e[""][4] + "# + )?, + 'o' + ); + } assert_eq!( engine.eval::("let y = #{a: 1, b: 2, c: 3}; y.a = 5; y.a")?, 5 ); - - #[cfg(not(feature = "no_index"))] - assert_eq!( - engine.eval::( - 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")?; + #[cfg(not(feature = "no_stdlib"))] + { + assert_eq!( + engine.eval::( + r" + let x = #{a: 1, b: 2, c: 3}; + let y = #{b: 42, d: 9}; + x.mixin(y); + x.len() + x.b + " + )?, + 46 + ); + assert_eq!( + engine.eval::( + r" + let x = #{a: 1, b: 2, c: 3}; + x += #{b: 42, d: 9}; + x.len() + x.b + " + )?, + 46 + ); + assert_eq!( + engine + .eval::( + r" + let x = #{a: 1, b: 2, c: 3}; + let y = #{b: 42, d: 9}; + x + y + " + )? + .len(), + 4 + ); + } + Ok(()) } @@ -37,14 +73,14 @@ fn test_map_indexing() -> Result<(), EvalAltResult> { fn test_map_assign() -> Result<(), EvalAltResult> { let mut engine = Engine::new(); - let x = engine.eval::(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(); + let x = engine.eval::(r#"let x = #{a: 1, b: true, "c$": "hello"}; x"#)?; + let a = x.get("a").cloned().expect("should have property a"); + let b = x.get("b").cloned().expect("should have property b"); + let c = x.get("c$").cloned().expect("should have property c$"); - assert_eq!(*a.downcast::().unwrap(), 1); - assert_eq!(*b.downcast::().unwrap(), true); - assert_eq!(*c.downcast::().unwrap(), "hello"); + assert_eq!(a.cast::(), 1); + assert_eq!(b.cast::(), true); + assert_eq!(c.cast::(), "hello"); Ok(()) } @@ -53,14 +89,37 @@ fn test_map_assign() -> Result<(), EvalAltResult> { fn test_map_return() -> Result<(), EvalAltResult> { let mut engine = Engine::new(); - let x = engine.eval::(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(); + let x = engine.eval::(r#"#{a: 1, b: true, "c$": "hello"}"#)?; + let a = x.get("a").cloned().expect("should have property a"); + let b = x.get("b").cloned().expect("should have property b"); + let c = x.get("c$").cloned().expect("should have property c$"); - assert_eq!(*a.downcast::().unwrap(), 1); - assert_eq!(*b.downcast::().unwrap(), true); - assert_eq!(*c.downcast::().unwrap(), "hello"); + assert_eq!(a.cast::(), 1); + assert_eq!(b.cast::(), true); + assert_eq!(c.cast::(), "hello"); + + Ok(()) +} + +#[test] +fn test_map_for() -> Result<(), EvalAltResult> { + let mut engine = Engine::new(); + + assert_eq!( + engine.eval::( + r#" + let map = #{a: 1, b: true, c: 123.456}; + let s = ""; + + for key in keys(map) { + s += key; + } + + s.len() + "# + )?, + 3 + ); Ok(()) } diff --git a/tests/side_effects.rs b/tests/side_effects.rs index 54b25432..d97b6f33 100644 --- a/tests/side_effects.rs +++ b/tests/side_effects.rs @@ -2,8 +2,7 @@ ///! This test simulates an external command object that is driven by a script. use rhai::{Engine, EvalAltResult, RegisterFn, Scope, INT}; -use std::cell::RefCell; -use std::rc::Rc; +use std::sync::{Arc, Mutex}; /// External command. struct Command { @@ -24,19 +23,19 @@ impl Command { /// Wrapper object to wrap a command object. #[derive(Clone)] struct CommandWrapper { - command: Rc>, + command: Arc>, } impl CommandWrapper { /// Delegate command action. pub fn do_action(&mut self, x: i64) { - let mut command = self.command.borrow_mut(); + let mut command = self.command.lock().unwrap(); let val = command.get(); command.action(val + x); } /// Delegate get value action. pub fn get_value(&mut self) -> i64 { - let command = self.command.borrow(); + let command = self.command.lock().unwrap(); command.get() } } @@ -47,8 +46,8 @@ fn test_side_effects() -> Result<(), EvalAltResult> { let mut scope = Scope::new(); // Create the command object with initial state, handled by an `Rc`. - let command = Rc::new(RefCell::new(Command { state: 12 })); - assert_eq!(command.borrow().get(), 12); + let command = Arc::new(Mutex::new(Command { state: 12 })); + assert_eq!(command.lock().unwrap().get(), 12); // Create the wrapper. let wrapper = CommandWrapper { @@ -76,7 +75,7 @@ fn test_side_effects() -> Result<(), EvalAltResult> { ); // Make sure the actions are properly performed - assert_eq!(command.borrow().get(), 42); + assert_eq!(command.lock().unwrap().get(), 42); Ok(()) } diff --git a/tests/var_scope.rs b/tests/var_scope.rs index 6b660d34..39cf5b76 100644 --- a/tests/var_scope.rs +++ b/tests/var_scope.rs @@ -9,8 +9,12 @@ fn test_var_scope() -> Result<(), EvalAltResult> { assert_eq!(engine.eval_with_scope::(&mut scope, "x")?, 9); engine.eval_with_scope::<()>(&mut scope, "x = x + 1; x = x + 2;")?; assert_eq!(engine.eval_with_scope::(&mut scope, "x")?, 12); + + scope.set_value("x", 42 as INT); + assert_eq!(engine.eval_with_scope::(&mut scope, "x")?, 42); + engine.eval_with_scope::<()>(&mut scope, "{let x = 3}")?; - assert_eq!(engine.eval_with_scope::(&mut scope, "x")?, 12); + assert_eq!(engine.eval_with_scope::(&mut scope, "x")?, 42); Ok(()) } diff --git a/tests/while_loop.rs b/tests/while_loop.rs index fa76ea17..18d5a03b 100644 --- a/tests/while_loop.rs +++ b/tests/while_loop.rs @@ -6,8 +6,18 @@ fn test_while() -> Result<(), EvalAltResult> { assert_eq!( engine.eval::( - "let x = 0; while x < 10 { x = x + 1; if x > 5 { \ - break } } x", + r" + let x = 0; + + while x < 10 { + x = x + 1; + if x > 5 { break; } + if x > 3 { continue; } + x = x + 3; + } + + x + ", )?, 6 );