Merge pull request #123 from schungx/master

Send+Sync
This commit is contained in:
Stephen Chung 2020-04-05 23:45:41 +08:00 committed by GitHub
commit 66a49561f0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
24 changed files with 1794 additions and 716 deletions

View File

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

340
README.md
View File

@ -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::<i64>() == true; // 'is' returns whether a 'Dynamic' value is of a particular type
let value = item.cast::<i64>(); // 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::<i64>()?; // '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
-----------------
@ -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<String>` etc. and the error text gets converted into `EvalAltResult::ErrorRuntime`.
The function must return `Result<_, EvalAltResult>`. `EvalAltResult` implements `From<&str>` and `From<String>` 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::<TestStruct>();
```
To use methods and functions with the [`Engine`], we need to register them. There are some convenience functions to help with this.
Below I register update and new with the [`Engine`].
To use 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::<TestStruct>("let x = new_ts(); x.update(); x")?;
println!("result: {}", result.field); // prints 42
```
In fact, any function with a first argument (either by copy or via a `&mut` reference) can be used as a method-call on that type because internally they are the same thing:
methods on a type is implemented as a functions taking an first argument.
In fact, any function with a first argument (either by copy or via a `&mut` reference) can be used as a method call
on that type because internally they are the same thing:
methods on a type is implemented as a functions taking a `&mut` first argument.
```rust
fn foo(ts: &mut TestStruct) -> i64 {
@ -565,7 +663,8 @@ let result = engine.eval::<i64>("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.
// 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"
@ -667,8 +773,12 @@ fn main() -> Result<(), EvalAltResult>
println!("result: {}", result); // prints 979
// Variable y is changed in the script
assert_eq!(scope.get_value::<i64>("y").expect("variable x should exist"), 1);
// Variable y is changed in the script - read it with 'get_value'
assert_eq!(scope.get_value::<i64>("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::<i64>("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,18 +1088,19 @@ 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 |
| `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 |
@ -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 |
| `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<T, rhai::EvalAltResult>` 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<T, rhai::EvalAltResult>` with `EvalAltResult`
holding error information. To deliberately return an error during an evaluation, use the `throw` keyword.
```rust
if some_bad_condition_has_happened {
@ -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();

View File

@ -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::<String>()
}
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<AST> = None;
let mut ast: Option<AST> = 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!("()");
}
println!("{:#?}", &ast_u);
continue;
}
"ast" => {
if matches!(&ast, Some(_)) {
// print the last AST
println!("{:#?}", ast.as_ref().unwrap());
} else {
println!("()");
}
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!();

View File

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

View File

@ -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<Variant>;
/// 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<T: Clone + StdAny + ?Sized> Any for T {
#[cfg(feature = "sync")]
impl<T: crate::stdlib::any::Any + Clone + Send + Sync + ?Sized> Any for T {
fn type_id(&self) -> TypeId {
TypeId::of::<T>()
}
fn type_name(&self) -> &'static str {
type_name::<T>()
}
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<T: crate::stdlib::any::Any + Clone + ?Sized> Any for T {
fn type_id(&self) -> TypeId {
TypeId::of::<T>()
}
@ -48,15 +92,13 @@ impl<T: Clone + StdAny + ?Sized> Any for T {
impl Variant {
/// Is this `Variant` a specific type?
pub(crate) fn is<T: Any>(&self) -> bool {
let t = TypeId::of::<T>();
let boxed = <Variant as Any>::type_id(self);
t == boxed
pub fn is<T: Any>(&self) -> bool {
TypeId::of::<T>() == <Variant as Any>::type_id(self)
}
/// Get a reference of a specific type to the `Variant`.
pub(crate) fn downcast_ref<T: Any>(&self) -> Option<&T> {
/// Returns `None` if the cast fails.
pub fn downcast_ref<T: Any>(&self) -> Option<&T> {
if self.is::<T>() {
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<T: Any>(&mut self) -> Option<&mut T> {
/// Returns `None` if the cast fails.
pub fn downcast_mut<T: Any>(&mut self) -> Option<&mut T> {
if self.is::<T>() {
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<T: Any + Clone>(self) -> Result<Box<T>, Self>;
fn try_cast<T: Any + Clone>(self) -> Result<T, Self>;
/// 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<T: Any + Clone>(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::<u32>().unwrap(), 42);
/// assert_eq!(x.try_cast::<u32>().unwrap(), 42);
/// ```
fn downcast<T: Any + Clone>(self) -> Result<Box<T>, Self> {
fn try_cast<T: Any + Clone>(self) -> Result<T, Self> {
if self.is::<T>() {
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::<u32>(), 42);
/// ```
fn cast<T: Any + Clone>(self) -> T {
self.try_cast::<T>().expect("cast failed")
}
fn _closed(&self) -> _Private {
_Private
}

View File

@ -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<T, U>: Fn(&mut T) -> U + Send + Sync + 'static {}
#[cfg(feature = "sync")]
impl<F: Fn(&mut T) -> U + Send + Sync + 'static, T, U> ObjectGetCallback<T, U> for F {}
#[cfg(not(feature = "sync"))]
pub trait ObjectGetCallback<T, U>: Fn(&mut T) -> U + 'static {}
#[cfg(not(feature = "sync"))]
impl<F: Fn(&mut T) -> U + 'static, T, U> ObjectGetCallback<T, U> for F {}
#[cfg(feature = "sync")]
pub trait ObjectSetCallback<T, U>: Fn(&mut T, U) + Send + Sync + 'static {}
#[cfg(feature = "sync")]
impl<F: Fn(&mut T, U) + Send + Sync + 'static, T, U> ObjectSetCallback<T, U> for F {}
#[cfg(not(feature = "sync"))]
pub trait ObjectSetCallback<T, U>: Fn(&mut T, U) + 'static {}
#[cfg(not(feature = "sync"))]
impl<F: Fn(&mut T, U) + 'static, T, U> ObjectSetCallback<T, U> for F {}
#[cfg(feature = "sync")]
pub trait IteratorCallback:
Fn(&Dynamic) -> Box<dyn Iterator<Item = Dynamic>> + Send + Sync + 'static
{
}
#[cfg(feature = "sync")]
impl<F: Fn(&Dynamic) -> Box<dyn Iterator<Item = Dynamic>> + Send + Sync + 'static> IteratorCallback
for F
{
}
#[cfg(not(feature = "sync"))]
pub trait IteratorCallback: Fn(&Dynamic) -> Box<dyn Iterator<Item = Dynamic>> + 'static {}
#[cfg(not(feature = "sync"))]
impl<F: Fn(&Dynamic) -> Box<dyn Iterator<Item = Dynamic>> + '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<TypeId>, f: Box<FnAny>) {
@ -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<T: Any + Clone>(&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::<T>().to_string(), name.to_string());
}
/// Register an iterator adapter for a type with the `Engine`.
/// This is an advanced feature.
pub fn register_iterator<T: Any, F>(&mut self, f: F)
where
F: Fn(&Dynamic) -> Box<dyn Iterator<Item = Dynamic>> + 'static,
{
self.type_iterators.insert(TypeId::of::<T>(), Box::new(f));
pub fn register_iterator<T: Any, F: IteratorCallback>(&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::<T>(), 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<T: Any + Clone, U: Any + Clone>(
&mut self,
name: &str,
callback: impl Fn(&mut T) -> U + 'static,
) {
pub fn register_get<T, U, F>(&mut self, name: &str, callback: F)
where
T: Any + Clone,
U: Any + Clone,
F: ObjectGetCallback<T, U>,
{
self.register_fn(&make_getter(name), callback);
}
@ -215,11 +267,12 @@ impl<'e> Engine<'e> {
/// # }
/// ```
#[cfg(not(feature = "no_object"))]
pub fn register_set<T: Any + Clone, U: Any + Clone>(
&mut self,
name: &str,
callback: impl Fn(&mut T, U) -> () + 'static,
) {
pub fn register_set<T, U, F>(&mut self, name: &str, callback: F)
where
T: Any + Clone,
U: Any + Clone,
F: ObjectSetCallback<T, U>,
{
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<T: Any + Clone, U: Any + Clone>(
&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<T, U, G, S>(&mut self, name: &str, get_fn: G, set_fn: S)
where
T: Any + Clone,
U: Any + Clone,
G: ObjectGetCallback<T, U>,
S: ObjectSetCallback<T, U>,
{
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<T, EvalAltResult> {
self.eval_ast_with_scope_raw(scope, false, ast)?
.downcast::<T>()
.map(|v| *v)
self.eval_ast_with_scope_raw(scope, ast)?
.try_cast::<T>()
.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<Dynamic, EvalAltResult> {
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<Item = &'a Arc<FnDef>>,
) {
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<T: Any + Clone>(
&mut self,
scope: &mut Scope,
ast: &AST,
name: &str,
) -> Result<T, EvalAltResult> {
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<A: Any + Clone, T: Any + Clone>(
&mut self,
scope: &mut Scope,
ast: &AST,
name: &str,
arg: A,
) -> Result<T, EvalAltResult> {
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<A: FuncArgs, T: Any + Clone>(
&mut self,
scope: &mut Scope,
ast: &AST,
name: &str,
args: A,
) -> Result<T, EvalAltResult> {
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<T: Any + Clone>(
&mut self,
scope: &mut Scope,
ast: &AST,
name: &str,
mut arg_values: Vec<Dynamic>,
) -> Result<T, EvalAltResult> {
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));
}
}

View File

@ -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::<Array, _>(|a| {
self.register_iterator::<Array, _>(|a: &Dynamic| {
Box::new(a.downcast_ref::<Array>().unwrap().clone().into_iter())
as Box<dyn Iterator<Item = Dynamic>>
});
}
@ -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::<Vec<_>>()
});
self.register_fn("values", |map: Map| {
map.into_iter().map(|(_, v)| v).collect::<Vec<_>>()
});
}
}
// Register range function
fn reg_iterator<T: Any + Clone>(engine: &mut Engine)
fn reg_range<T: Any + Clone>(engine: &mut Engine)
where
Range<T>: Iterator<Item = T>,
{
engine.register_iterator::<Range<T>, _>(|a| {
engine.register_iterator::<Range<T>, _>(|a: &Dynamic| {
Box::new(
a.downcast_ref::<Range<T>>()
.unwrap()
.clone()
.map(|n| n.into_dynamic()),
)
.map(|x| x.into_dynamic()),
) as Box<dyn Iterator<Item = Dynamic>>
});
}
reg_iterator::<INT>(self);
reg_range::<INT>(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, T)
where
for<'a> &'a T: Add<&'a T, Output = T>,
T: Any + Clone + PartialOrd;
impl<T> Iterator for StepRange<T>
where
for<'a> &'a T: Add<&'a T, Output = T>,
T: Any + Clone + PartialOrd,
{
type Item = T;
fn next(&mut self) -> Option<T> {
if self.0 < self.1 {
let v = self.0.clone();
self.0 = &v + &self.2;
Some(v)
} else {
None
}
}
}
fn reg_step<T>(engine: &mut Engine)
where
for<'a> &'a T: Add<&'a T, Output = T>,
T: Any + Clone + PartialOrd,
StepRange<T>: Iterator<Item = T>,
{
engine.register_iterator::<StepRange<T>, _>(|a: &Dynamic| {
Box::new(
a.downcast_ref::<StepRange<T>>()
.unwrap()
.clone()
.map(|x| x.into_dynamic()),
) as Box<dyn Iterator<Item = Dynamic>>
});
}
reg_step::<INT>(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

View File

@ -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<Dynamic>;
}
/// 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<Dynamic> {
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),*) => {

View File

@ -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<Dynamic>;
/// 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<String, Dynamic>;
pub type FnCallArgs<'a> = [&'a mut Variant];
#[cfg(feature = "sync")]
pub type FnAny = dyn Fn(&mut FnCallArgs, Position) -> Result<Dynamic, EvalAltResult> + Send + Sync;
#[cfg(not(feature = "sync"))]
pub type FnAny = dyn Fn(&mut FnCallArgs, Position) -> Result<Dynamic, EvalAltResult>;
#[cfg(feature = "sync")]
type IteratorFn = dyn Fn(&Dynamic) -> Box<dyn Iterator<Item = Dynamic>> + Send + Sync;
#[cfg(not(feature = "sync"))]
type IteratorFn = dyn Fn(&Dynamic) -> Box<dyn Iterator<Item = Dynamic>>;
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<Arc<FnDef>>);
#[derive(Debug, Clone)]
pub struct FunctionsLib(
#[cfg(feature = "sync")] Vec<Arc<FnDef>>,
#[cfg(not(feature = "sync"))] Vec<Rc<FnDef>>,
);
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<FnDef>) -> 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<FnDef>) {
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<Arc<FnDef>> {
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<Arc<FnDef>>;
#[cfg(not(feature = "sync"))]
type Target = Vec<Rc<FnDef>>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl DerefMut for FunctionsLib {
#[cfg(feature = "sync")]
fn deref_mut(&mut self) -> &mut Vec<Arc<FnDef>> {
&mut self.0
}
#[cfg(not(feature = "sync"))]
fn deref_mut(&mut self) -> &mut Vec<Rc<FnDef>> {
&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<FnSpec<'e>, Box<FnAny>>,
pub(crate) functions: Option<HashMap<FnSpec<'e>, Box<FnAny>>>,
/// A hashmap containing all script-defined functions.
pub(crate) fn_lib: FunctionsLib,
#[cfg(feature = "sync")]
pub(crate) fn_lib: Option<Arc<FunctionsLib>>,
/// A hashmap containing all script-defined functions.
#[cfg(not(feature = "sync"))]
pub(crate) fn_lib: Option<Rc<FunctionsLib>>,
/// A hashmap containing all iterators known to the engine.
pub(crate) type_iterators: HashMap<TypeId, Box<IteratorFn>>,
pub(crate) type_iterators: Option<HashMap<TypeId, Box<IteratorFn>>>,
/// A hashmap mapping type names to pretty-print names.
pub(crate) type_names: HashMap<String, String>,
pub(crate) type_names: Option<HashMap<String, String>>,
/// Closure for implementing the `print` command.
pub(crate) on_print: Box<dyn FnMut(&str) + 'e>,
#[cfg(feature = "sync")]
pub(crate) on_print: Option<Box<dyn FnMut(&str) + Send + Sync + 'e>>,
/// Closure for implementing the `print` command.
#[cfg(not(feature = "sync"))]
pub(crate) on_print: Option<Box<dyn FnMut(&str) + 'e>>,
/// Closure for implementing the `debug` command.
pub(crate) on_debug: Box<dyn FnMut(&str) + 'e>,
#[cfg(feature = "sync")]
pub(crate) on_debug: Option<Box<dyn FnMut(&str) + Send + Sync + 'e>>,
/// Closure for implementing the `debug` command.
#[cfg(not(feature = "sync"))]
pub(crate) on_debug: Option<Box<dyn FnMut(&str) + 'e>>,
/// Optimize the AST after compilation.
#[cfg(not(feature = "no_optimize"))]
@ -217,17 +292,17 @@ impl Default for Engine<'_> {
(type_name::<Dynamic>(), "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: Fn() + Send + Sync>(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,17 +420,22 @@ impl Engine<'_> {
};
// Search built-in's and external functions
if let Some(func) = self.functions.get(&spec) {
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)
}
}
/// 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,7 +443,38 @@ impl Engine<'_> {
level: usize,
) -> Result<Dynamic, EvalAltResult> {
// First search in script-defined functions (can override built-in)
if let Some(fn_def) = self.fn_lib.get_function(fn_name, args.len()) {
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 - 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
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(
@ -335,18 +482,21 @@ impl Engine<'_> {
fn_def
.params
.iter()
.zip(args.iter().map(|x| (*x).into_dynamic()))
.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 {
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,21 +512,26 @@ impl Engine<'_> {
}
// Search built-in's and external functions
if let Some(func) = self.functions.get(&spec) {
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_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 => {
self.on_debug.as_mut()(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) {
#[cfg(not(feature = "no_object"))]
@ -441,7 +596,7 @@ impl Engine<'_> {
level: usize,
) -> Result<Dynamic, EvalAltResult> {
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::<Array>() {
let idx = *self
let idx = self
.eval_expr(scope, idx_expr, level)?
.downcast::<INT>()
.try_cast::<INT>()
.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::<Map>() {
let idx = *self
let idx = self
.eval_expr(scope, idx_expr, level)?
.downcast::<String>()
.try_cast::<String>()
.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::<String>() {
let idx = *self
let idx = self
.eval_expr(scope, idx_expr, level)?
.downcast::<INT>()
.try_cast::<INT>()
.map_err(|_| EvalAltResult::ErrorNumericIndexExpr(idx_expr.position()))?;
return if idx >= 0 {
@ -795,9 +952,9 @@ impl Engine<'_> {
let s = scope.get_mut_by_type::<String>(src);
let pos = new_val.1;
// Value must be a character
let ch = *new_val
let ch = new_val
.0
.downcast::<char>()
.try_cast::<char>()
.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::<String>() {
// Value must be a character
let ch = *new_val
.downcast::<char>()
let ch = new_val
.try_cast::<char>()
.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 {
(engine.functions.is_some() && {
engine.functions.as_ref().unwrap().contains_key(&FnSpec {
name: name.into(),
args: vec![TypeId::of::<String>()],
};
engine.functions.contains_key(&spec) || engine.fn_lib.has_function(name, 1)
})
}) || (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::<String>()
.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::<bool>()
.try_cast::<bool>()
.map_err(|_| {
EvalAltResult::ErrorBooleanArgMismatch("AND".into(), lhs.position())
})?
&& // Short-circuit using &&
*self
self
.eval_expr(scope, &*rhs, level)?
.downcast::<bool>()
.try_cast::<bool>()
.map_err(|_| {
EvalAltResult::ErrorBooleanArgMismatch("AND".into(), rhs.position())
})?,
)),
Expr::Or(lhs, rhs) => Ok(Box::new(
*self
self
.eval_expr(scope, &*lhs, level)?
.downcast::<bool>()
.try_cast::<bool>()
.map_err(|_| {
EvalAltResult::ErrorBooleanArgMismatch("OR".into(), lhs.position())
})?
|| // Short-circuit using ||
*self
self
.eval_expr(scope, &*rhs, level)?
.downcast::<bool>()
.try_cast::<bool>()
.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::<bool>()
.try_cast::<bool>()
.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::<bool>() {
Ok(guard_val) => {
if *guard_val {
match self.eval_stmt(scope, body, level) {
Ok(_) => (),
Err(EvalAltResult::ErrorLoopBreak(_)) => {
return Ok(().into_dynamic())
}
match self.eval_expr(scope, guard, level)?.try_cast::<bool>() {
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),
}
} else {
return Ok(().into_dynamic());
}
}
},
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,7 +1559,10 @@ 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) {
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 {
@ -1393,8 +1575,8 @@ impl Engine<'_> {
*scope.get_mut(entry) = a;
match self.eval_stmt(scope, body, level) {
Ok(_) => (),
Err(EvalAltResult::ErrorLoopBreak(_)) => break,
Ok(_) | Err(EvalAltResult::ErrorLoopBreak(false, _)) => (),
Err(EvalAltResult::ErrorLoopBreak(true, _)) => break,
Err(x) => return Err(x),
}
}
@ -1404,10 +1586,16 @@ impl Engine<'_> {
} 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::<String>()
.map(|s| *s)
.unwrap_or_else(|_| "".to_string()),
val.try_cast::<String>().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 {
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;
}
}

View File

@ -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())?,

View File

@ -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<FN, ($($mark,)*), RET> 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<FN, ($($mark,)*)> for Engine<'_>
{
@ -202,7 +213,12 @@ macro_rules! def_register {
impl<
$($par: Any + Clone,)*
#[cfg(feature = "sync")]
FN: Fn($($param),*) -> Result<RET, EvalAltResult> + Send + Sync + 'static,
#[cfg(not(feature = "sync"))]
FN: Fn($($param),*) -> Result<RET, EvalAltResult> + 'static,
RET: Any
> RegisterResultFn<FN, ($($mark,)*), RET> for Engine<'_>
{

View File

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

View File

@ -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,10 +453,12 @@ 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()) {
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();
let mut call_args: Vec<_> = arg_values.iter_mut().map(Dynamic::as_mut).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<Stmt>,
functions: Vec<FnDef>,
) -> 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),
)
}

View File

@ -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<Stmt>, pub(crate) Vec<Arc<FnDef>>);
pub struct AST(
pub(crate) Vec<Stmt>,
#[cfg(feature = "sync")] pub(crate) Arc<FunctionsLib>,
#[cfg(not(feature = "sync"))] pub(crate) Rc<FunctionsLib>,
);
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);
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![],
};
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())
#[cfg(feature = "sync")]
{
functions[n] = fn_def;
} else {
functions.push(fn_def);
Self(ast, Arc::new(functions.merge(other.1.as_ref())))
}
#[cfg(not(feature = "sync"))]
{
Self(ast, Rc::new(functions.merge(other.1.as_ref())))
}
}
Self(ast, functions)
/// 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());
}
}
impl Add<Self> for AST {
type Output = Self;
/// Clear all statements in the `AST`, leaving only function definitions.
pub fn retain_functions(&mut self) {
self.0 = vec![];
}
}
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<Self> for &AST {
type Output = AST;
fn add(self, rhs: Self) -> Self::Output {
self.merge(rhs)
@ -278,6 +325,8 @@ pub enum Stmt {
Block(Vec<Stmt>, Position),
/// { stmt }
Expr(Box<Expr>),
/// 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<Expr>, Dynamic) {
pub fn map_dynamic_to_expr(value: Dynamic, pos: Position) -> Option<Expr> {
if value.is::<INT>() {
let value2 = value.clone();
(
Some(Expr::IntegerConstant(
*value.downcast::<INT>().expect("value should be INT"),
pos,
)),
value2,
)
Some(Expr::IntegerConstant(value.cast(), pos))
} else if value.is::<char>() {
let value2 = value.clone();
(
Some(Expr::CharConstant(
*value.downcast::<char>().expect("value should be char"),
pos,
)),
value2,
)
Some(Expr::CharConstant(value.cast(), pos))
} else if value.is::<String>() {
let value2 = value.clone();
(
Some(Expr::StringConstant(
*value.downcast::<String>().expect("value should be String"),
pos,
)),
value2,
)
Some(Expr::StringConstant(value.cast(), pos))
} else if value.is::<bool>() {
let value2 = value.clone();
(
Some(
if *value.downcast::<bool>().expect("value should be bool") {
Some(if value.cast::<bool>() {
Expr::True(pos)
} else {
Expr::False(pos)
},
),
value2,
)
})
} else {
#[cfg(not(feature = "no_float"))]
{
if value.is::<FLOAT>() {
let value2 = value.clone();
return (
Some(Expr::FloatConstant(
*value.downcast::<FLOAT>().expect("value should be FLOAT"),
pos,
)),
value2,
);
return Some(Expr::FloatConstant(value.cast(), pos));
}
}
(None, value)
None
}
}

View File

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

View File

@ -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::<i64>(&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::<i64>(&mut my_scope, "x + 1")?, 42);
///
/// assert_eq!(my_scope.get_value::<i64>("x").unwrap(), 41);
/// assert_eq!(my_scope.get_value::<i64>("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<Entry<'a>>);
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::<i64>("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::<i64>("x").unwrap(), 42);
/// ```
pub fn push<K: Into<Cow<'a, str>>, 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::<i64>("x").unwrap(), 42);
/// ```
pub fn push_dynamic<K: Into<Cow<'a, str>>>(&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::<i64>("x").unwrap(), 42);
/// ```
pub fn push_constant<K: Into<Cow<'a, str>>, 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::<i64>("x").unwrap(), 42);
/// ```
pub fn push_constant_dynamic<K: Into<Cow<'a, str>>>(&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,
..
},
)| {
(
if name == key {
Some((
EntryRef {
name: name.as_ref(),
index: i,
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<T: Any + Clone>(&self, key: &str) -> Option<T> {
///
/// # Examples
///
/// ```
/// use rhai::Scope;
///
/// let mut my_scope = Scope::new();
///
/// my_scope.push("x", 42_i64);
/// assert_eq!(my_scope.get_value::<i64>("x").unwrap(), 42);
/// ```
pub fn get_value<T: Any + Clone>(&self, name: &str) -> Option<T> {
self.0
.iter()
.enumerate()
.rev() // Always search a Scope in reverse order
.find(|(_, Entry { name, .. })| name == key)
.and_then(|(_, Entry { value, .. })| value.downcast_ref::<T>())
.rev()
.find(|Entry { name: key, .. }| name == key)
.and_then(|Entry { value, .. }| value.downcast_ref::<T>())
.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::<i64>("x").unwrap(), 42);
///
/// my_scope.set_value("x", 0_i64);
/// assert_eq!(my_scope.get_value::<i64>("x").unwrap(), 0);
/// ```
pub fn set_value<T: Any + Clone>(&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");

View File

@ -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::<INT>(
r"
let x = [1, 2, 3];
let y = [4, 5];
x.append(y);
x.len()
"
)?,
5
);
assert_eq!(
engine.eval::<INT>(
r"
let x = [1, 2, 3];
x += [4, 5];
x.len()
"
)?,
5
);
assert_eq!(
engine
.eval::<Array>(
r"
let x = [1, 2, 3];
let y = [4, 5];
x + y
"
)?
.len(),
5
);
}
Ok(())
}

View File

@ -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::<INT>("foo")
.expect("variable foo should exist"),
1
);
Ok(())
}

View File

@ -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::<INT>(script)?, 30);
assert_eq!(engine.eval::<INT>(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::<INT>(script)?, 9);
Ok(())
}

View File

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

View File

@ -7,17 +7,11 @@ fn test_map_indexing() -> Result<(), EvalAltResult> {
let mut engine = Engine::new();
#[cfg(not(feature = "no_index"))]
{
assert_eq!(
engine.eval::<INT>(r#"let x = #{a: 1, b: 2, c: 3}; x["b"]"#)?,
2
);
assert_eq!(
engine.eval::<INT>("let y = #{a: 1, b: 2, c: 3}; y.a = 5; y.a")?,
5
);
#[cfg(not(feature = "no_index"))]
assert_eq!(
engine.eval::<char>(
r#"
@ -27,9 +21,51 @@ fn test_map_indexing() -> Result<(), EvalAltResult> {
)?,
'o'
);
}
assert_eq!(
engine.eval::<INT>("let y = #{a: 1, b: 2, c: 3}; y.a = 5; y.a")?,
5
);
engine.eval::<()>("let y = #{a: 1, b: 2, c: 3}; y.z")?;
#[cfg(not(feature = "no_stdlib"))]
{
assert_eq!(
engine.eval::<INT>(
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::<INT>(
r"
let x = #{a: 1, b: 2, c: 3};
x += #{b: 42, d: 9};
x.len() + x.b
"
)?,
46
);
assert_eq!(
engine
.eval::<Map>(
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::<Map>(r#"let x = #{a: 1, b: true, "c#": "hello"}; x"#)?;
let a = x.get("a").cloned().unwrap();
let b = x.get("b").cloned().unwrap();
let c = x.get("c#").cloned().unwrap();
let x = engine.eval::<Map>(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::<INT>().unwrap(), 1);
assert_eq!(*b.downcast::<bool>().unwrap(), true);
assert_eq!(*c.downcast::<String>().unwrap(), "hello");
assert_eq!(a.cast::<INT>(), 1);
assert_eq!(b.cast::<bool>(), true);
assert_eq!(c.cast::<String>(), "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::<Map>(r#"#{a: 1, b: true, c: "hello"}"#)?;
let a = x.get("a").cloned().unwrap();
let b = x.get("b").cloned().unwrap();
let c = x.get("c").cloned().unwrap();
let x = engine.eval::<Map>(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::<INT>().unwrap(), 1);
assert_eq!(*b.downcast::<bool>().unwrap(), true);
assert_eq!(*c.downcast::<String>().unwrap(), "hello");
assert_eq!(a.cast::<INT>(), 1);
assert_eq!(b.cast::<bool>(), true);
assert_eq!(c.cast::<String>(), "hello");
Ok(())
}
#[test]
fn test_map_for() -> Result<(), EvalAltResult> {
let mut engine = Engine::new();
assert_eq!(
engine.eval::<INT>(
r#"
let map = #{a: 1, b: true, c: 123.456};
let s = "";
for key in keys(map) {
s += key;
}
s.len()
"#
)?,
3
);
Ok(())
}

View File

@ -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<RefCell<Command>>,
command: Arc<Mutex<Command>>,
}
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(())
}

View File

@ -9,8 +9,12 @@ fn test_var_scope() -> Result<(), EvalAltResult> {
assert_eq!(engine.eval_with_scope::<INT>(&mut scope, "x")?, 9);
engine.eval_with_scope::<()>(&mut scope, "x = x + 1; x = x + 2;")?;
assert_eq!(engine.eval_with_scope::<INT>(&mut scope, "x")?, 12);
scope.set_value("x", 42 as INT);
assert_eq!(engine.eval_with_scope::<INT>(&mut scope, "x")?, 42);
engine.eval_with_scope::<()>(&mut scope, "{let x = 3}")?;
assert_eq!(engine.eval_with_scope::<INT>(&mut scope, "x")?, 12);
assert_eq!(engine.eval_with_scope::<INT>(&mut scope, "x")?, 42);
Ok(())
}

View File

@ -6,8 +6,18 @@ fn test_while() -> Result<(), EvalAltResult> {
assert_eq!(
engine.eval::<INT>(
"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
);