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" ] categories = [ "no-std", "embedded", "parser-implementations" ]
[dependencies] [dependencies]
num-traits = "0.2.11" num-traits = "*"
[features] [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 = [] default = []
unchecked = [] # unchecked arithmetic unchecked = [] # unchecked arithmetic
no_stdlib = [] # no standard library of utility functions 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 optimize_full = [] # set optimization level to Full (default is Simple) - this is a feature used only to simplify testing
only_i32 = [] # set INT=i32 (useful for 32-bit systems) only_i32 = [] # set INT=i32 (useful for 32-bit systems)
only_i64 = [] # set INT=i64 (default) and disable support for all other integer types 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 # compiling for no-std
no_std = [ "num-traits/libm", "hashbrown", "core-error", "libm" ] no_std = [ "num-traits/libm", "hashbrown", "core-error", "libm" ]

378
README.md
View File

@ -8,18 +8,21 @@ Rhai - Embedded Scripting for Rust
![crates.io](https://img.shields.io/crates/d/rhai) ![crates.io](https://img.shields.io/crates/d/rhai)
[![API Docs](https://docs.rs/rhai/badge.svg)](https://docs.rs/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: Rhai's current features set:
* `no-std` support * `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 * 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) * 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) * Low compile-time overhead (~0.6 sec debug/~3 sec release for script runner app)
* Easy-to-use language similar to JS+Rust * Easy-to-use language similar to JS+Rust
* Support for overloaded functions * Support for overloaded functions
* Compiled script is optimized for repeat evaluations * 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/) * 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 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`. 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_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`. | | `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. | | `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. Excluding unneeded functionalities can result in smaller, faster builds as well as less bugs due to a more restricted language.
[`unchecked`]: #optional-features [`unchecked`]: #optional-features
@ -82,6 +87,7 @@ Excluding unneeded functionalities can result in smaller, faster builds as well
[`only_i32`]: #optional-features [`only_i32`]: #optional-features
[`only_i64`]: #optional-features [`only_i64`]: #optional-features
[`no_std`]: #optional-features [`no_std`]: #optional-features
[`sync`]: #optional-features
Related Related
------- -------
@ -207,12 +213,12 @@ Compiling a script file is also supported:
let ast = engine.compile_file("hello_world.rhai".into())?; 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 ```rust
// Define a function in a script and load it into the Engine. // Define functions in a script.
// Pass true to 'retain_functions' otherwise these functions will be cleared at the end of consume() let ast = engine.compile(true,
engine.consume(true,
r" r"
// a function with two parameters: String and i64 // a function with two parameters: String and i64
fn hello(x, y) { fn hello(x, y) {
@ -223,18 +229,30 @@ engine.consume(true,
fn hello(x) { fn hello(x) {
x * 2 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 // 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, // Rhai does not have built-in type conversions. If arguments of the wrong types are passed,
// the Engine will not find the function. // the Engine will not find the function.
let result: i64 = engine.call_fn("hello", &ast, ( String::from("abc"), 123_i64 ) )?; let result: i64 = engine.call_fn(&mut scope, &ast, "hello", ( String::from("abc"), 123_i64 ) )?;
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ put arguments in a tuple // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
// put arguments in a tuple
let result: i64 = engine.call_fn("hello", 123_i64)? let result: i64 = engine.call_fn1(&mut scope, &ast, "hello", 123_i64)?
// ^^^^^^^ calls 'hello' with one parameter (no need for tuple) // ^^^^^^^^ 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 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. | | **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)_ | | **Nothing/void/nil/null** (or whatever you want to call it) | `()` | `"()"` | `""` _(empty string)_ |
[`Dynamic`]: #values-and-types
[`()`]: #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`. If only 32-bit integers are needed, enabling the [`only_i32`] feature will remove support for all integer types other than `i32`,
This is useful on some 32-bit systems where using 64-bit integers incurs a performance penalty. 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. 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 `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 ```rust
// Use 'type_of()' to get the actual types of values // 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 Value conversions
----------------- -----------------
@ -325,11 +415,11 @@ That's about it. For other conversions, register custom conversion functions.
```rust ```rust
let x = 42; let x = 42;
let y = x * 100.0; // <- error: cannot multiply i64 with f64 let y = x * 100.0; // <- error: cannot multiply i64 with f64
let y = x.to_float() * 100.0; // works let y = x.to_float() * 100.0; // works
let z = y.to_int() + x; // works let z = y.to_int() + x; // works
let c = 'X'; // character let c = 'X'; // character
print("c is '" + c + "' and its code is " + c.to_int()); // prints "c is 'X' and its code is 88" print("c is '" + c + "' and its code is " + c.to_int()); // prints "c is 'X' and its code is 88"
``` ```
@ -342,7 +432,7 @@ To call these functions, they need to be registered with the [`Engine`].
```rust ```rust
use rhai::{Engine, EvalAltResult}; use rhai::{Engine, EvalAltResult};
use rhai::RegisterFn; // use `RegisterFn` trait for `register_fn` use rhai::RegisterFn; // use `RegisterFn` trait for `register_fn`
use rhai::{Dynamic, RegisterDynamicFn}; // use `RegisterDynamicFn` trait for `register_dynamic_fn` use rhai::{Any, Dynamic, RegisterDynamicFn}; // use `RegisterDynamicFn` trait for `register_dynamic_fn`
// Normal function // Normal function
fn add(x: i64, y: i64) -> i64 { fn add(x: i64, y: i64) -> i64 {
@ -351,7 +441,7 @@ fn add(x: i64, y: i64) -> i64 {
// Function that returns a Dynamic value // Function that returns a Dynamic value
fn get_an_any() -> Dynamic { 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> 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 ```rust
use rhai::Any; // Pull in the trait
fn decide(yes_no: bool) -> Dynamic { fn decide(yes_no: bool) -> Dynamic {
if yes_no { if yes_no {
Box::new(42_i64) (42_i64).into_dynamic()
} else { } 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
----------------- -----------------
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 ```rust
use std::fmt::Display; 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 example shows how to register multiple functions (or, in this case, multiple overloaded versions of the same function)
This enables function overloading based on the number and types of parameters. under the same name. This enables function overloading based on the number and types of parameters.
Fallible functions 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 ```rust
use rhai::{Engine, EvalAltResult, Position}; use rhai::{Engine, EvalAltResult, Position};
@ -531,14 +627,15 @@ let mut engine = Engine::new();
engine.register_type::<TestStruct>(); engine.register_type::<TestStruct>();
``` ```
To use methods and functions with the [`Engine`], we need to register them. There are some convenience functions to help with this. To use native types, methods and functions with the [`Engine`], we need to register them.
Below I register update and new with the [`Engine`]. 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 ```rust
engine.register_fn("update", TestStruct::update); // registers 'update(&mut ts)' engine.register_fn("update", TestStruct::update); // registers 'update(&mut TestStruct)'
engine.register_fn("new_ts", TestStruct::new); // registers 'new' engine.register_fn("new_ts", TestStruct::new); // registers 'new()'
``` ```
Finally, we call our script. The script can see the function and method we registered earlier. 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 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: In fact, any function with a first argument (either by copy or via a `&mut` reference) can be used as a method call
methods on a type is implemented as a functions taking an first argument. 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 ```rust
fn foo(ts: &mut TestStruct) -> i64 { 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 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 ```rust
// Below is a syntax error under 'no_object' because 'len' cannot be called in method style. // 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` 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. 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. By default, Rhai treats each [`Engine`] invocation as a fresh one, persisting only the functions that have been defined
This gives each evaluation a clean starting slate. In order to continue using the same global state from one invocation to the next, but no global state. This gives each evaluation a clean starting slate. In order to continue using the same global state
such a state must be manually created and passed in. 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 ```rust
use rhai::{Engine, Scope, EvalAltResult}; use rhai::{Engine, Scope, EvalAltResult};
@ -649,12 +753,14 @@ fn main() -> Result<(), EvalAltResult>
// First create the state // First create the state
let mut scope = Scope::new(); let mut scope = Scope::new();
// Then push some initialized variables into the state // Then push (i.e. add) some initialized variables into the state.
// NOTE: Remember the system number types in Rhai are i64 (i32 if 'only_i32') ond f64. // 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. // Better stick to them or it gets hard working with the script.
scope.push("y", 42_i64); scope.push("y", 42_i64);
scope.push("z", 999_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 // First invocation
engine.eval_with_scope::<()>(&mut scope, r" engine.eval_with_scope::<()>(&mut scope, r"
@ -665,10 +771,14 @@ fn main() -> Result<(), EvalAltResult>
// Second invocation using the same state // Second invocation using the same state
let result = engine.eval_with_scope::<i64>(&mut scope, "x")?; let result = engine.eval_with_scope::<i64>(&mut scope, "x")?;
println!("result: {}", result); // prints 979 println!("result: {}", result); // prints 979
// Variable y is changed in the script // Variable y is changed in the script - read it with 'get_value'
assert_eq!(scope.get_value::<i64>("y").expect("variable x should exist"), 1); 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(()) Ok(())
} }
@ -738,7 +848,8 @@ Variables
Variables in Rhai follow normal C naming rules (i.e. must contain only ASCII letters, digits and underscores '`_`'). Variables in Rhai follow normal C naming rules (i.e. must contain only ASCII letters, digits and underscores '`_`').
Variable names must start with an ASCII letter or an underscore '`_`', must contain at least one ASCII letter, and must start with an ASCII letter before a digit. Variable names must start with an ASCII letter or an underscore '`_`', must contain at least one ASCII letter,
and must start with an ASCII letter before a digit.
Therefore, names like '`_`', '`_42`', '`3a`' etc. are not legal variable names, but '`_c3po`' and '`r2d2`' are. Therefore, names like '`_`', '`_42`', '`3a`' etc. are not legal variable names, but '`_c3po`' and '`r2d2`' are.
Variable names are also case _sensitive_. 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. 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. '`_`' separators can be added freely and are ignored within a number.
@ -845,7 +957,8 @@ number = -5 - +5;
Numeric functions 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 | | Function | Description |
| ------------ | --------------------------------- | | ------------ | --------------------------------- |
@ -871,18 +984,21 @@ The following standard functions (defined in the standard library but excluded i
Strings and Chars 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. 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). 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. 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. 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`]). Strings can be built up from other strings and types via the `+` operator (provided by the standard library but excluded
This is particularly useful when printing output. if [`no_stdlib`]). This is particularly useful when printing output.
[`type_of()`] a string returns `"string"`. [`type_of()`] a string returns `"string"`.
@ -972,24 +1088,25 @@ Arrays
Arrays are first-class citizens in Rhai. Like C, arrays are accessed with zero-based, non-negative integer indices. 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 '`,`'. 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`. The Rust type of a Rhai array is `rhai::Array`. [`type_of()`] an array returns `"array"`.
[`type_of()`] an array returns `"array"`.
Arrays are disabled via the [`no_index`] feature. Arrays are disabled via the [`no_index`] feature.
The following functions (defined in the standard library but excluded if [`no_stdlib`]) operate on arrays: The following functions (defined in the standard library but excluded if [`no_stdlib`]) operate on arrays:
| Function | Description | | Function | Description |
| ---------- | ------------------------------------------------------------------------------------- | | ------------ | ------------------------------------------------------------------------------------- |
| `push` | inserts an element at the end | | `push` | inserts an element at the end |
| `pop` | removes the last element and returns it ([`()`] if empty) | | `append` | concatenates the second array to the end of the first |
| `shift` | removes the first element and returns it ([`()`] if empty) | | `+` operator | concatenates the first array with the second |
| `len` | returns the number of elements | | `pop` | removes the last element and returns it ([`()`] if empty) |
| `pad` | pads the array with an element until a specified length | | `shift` | removes the first element and returns it ([`()`] if empty) |
| `clear` | empties the array | | `len` | returns the number of elements |
| `truncate` | cuts off the array at exactly a specified length (discarding all subsequent elements) | | `pad` | pads the array with an element until a specified length |
| `clear` | empties the array |
| `truncate` | cuts off the array at exactly a specified length (discarding all subsequent elements) |
Examples: Examples:
@ -1029,6 +1146,10 @@ last == 5;
print(y.len()); // prints 3 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 y.pad(10, "hello"); // pad the array up to 10 elements
print(y.len()); // prints 10 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
----------- -----------
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) 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 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. 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. **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`. The Rust type of a Rhai object map is `rhai::Map`. [`type_of()`] an object map returns `"map"`.
[`type_of()`] an object map returns `"map"`.
Object maps are disabled via the [`no_object`] feature. 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: The following functions (defined in the standard library but excluded if [`no_stdlib`]) operate on object maps:
| Function | Description | | Function | Description |
| -------- | ------------------------------------------------------------ | | ------------ | ---------------------------------------------------------------------------------------------------------------------------------------- |
| `has` | does the object map contain a property of a particular name? | | `has` | does the object map contain a property of a particular name? |
| `len` | returns the number of properties | | `len` | returns the number of properties |
| `clear` | empties the object map | | `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: Examples:
@ -1121,6 +1244,14 @@ y["xyz"] == ();
print(y.len()); // prints 3 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 y.clear(); // empty the object map
print(y.len()); // prints 0 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. 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 However, if the [`no_stdlib`] feature is turned on, comparisons can only be made between restricted system types -
types - `INT` (`i64` or `i32` depending on [`only_i32`] and [`only_i64`]), `f64` (if not [`no_float`]), string, array, `bool`, `char`. `INT` (`i64` or `i32` depending on [`only_i32`] and [`only_i64`]), `f64` (if not [`no_float`]), string, array, `bool`, `char`.
```rust ```rust
42 == 42; // true 42 == 42; // true
@ -1242,9 +1373,10 @@ x == ();
let x = 10; let x = 10;
while x > 0 { while x > 0 {
x = x - 1;
if x < 6 { continue; } // skip to the next iteration
print(x); print(x);
if x == 5 { break; } // break out of while loop if x == 5 { break; } // break out of while loop
x = x - 1;
} }
``` ```
@ -1255,8 +1387,9 @@ Infinite `loop`
let x = 10; let x = 10;
loop { loop {
print(x);
x = x - 1; x = x - 1;
if x > 5 { continue; } // skip to the next iteration
print(x);
if x == 0 { break; } // break out of loop if x == 0 { break; } // break out of loop
} }
``` ```
@ -1271,14 +1404,34 @@ let array = [1, 3, 5, 7, 9, 42];
// Iterate through array // Iterate through array
for x in array { for x in array {
if x > 10 { continue; } // skip to the next iteration
print(x); 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 // The 'range' function allows iterating from first to last-1
for x in range(0, 50) { for x in range(0, 50) {
if x > 10 { continue; } // skip to the next iteration
print(x); 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 Errors and `throw`-ing exceptions
-------------------------------- --------------------------------
All of [`Engine`]'s evaluation/consuming methods return `Result<T, rhai::EvalAltResult>` with `EvalAltResult` holding error information. All of [`Engine`]'s evaluation/consuming methods return `Result<T, rhai::EvalAltResult>` with `EvalAltResult`
To deliberately return an error during an evaluation, use the `throw` keyword. holding error information. To deliberately return an error during an evaluation, use the `throw` keyword.
```rust ```rust
if some_bad_condition_has_happened { if some_bad_condition_has_happened {
@ -1305,7 +1458,7 @@ if some_bad_condition_has_happened {
throw; // defaults to empty exception text: "" throw; // defaults to empty exception text: ""
``` ```
Exceptions thrown via `throw` in the script can be captured by matching `Err(EvalAltResult::ErrorRuntime(`_reason_`, `_position_`))` Exceptions thrown via `throw` in the script can be captured by matching `Err(EvalAltResult::ErrorRuntime(` _reason_ `,` _position_ `))`
with the exception text captured by the first parameter. with the exception text captured by the first parameter.
```rust ```rust
@ -1354,7 +1507,8 @@ print(add2(42)); // prints 44
### No access to external scope ### 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 ```rust
let x = 42; let x = 42;
@ -1365,7 +1519,8 @@ fn foo() { x } // <- syntax error: variable 'x' doesn't exist
### Passing arguments by value ### Passing arguments by value
Functions defined in script always take [`Dynamic`] parameters (i.e. the parameter can be of any type). 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. Any update to an argument will **not** be reflected back to the caller. This can introduce subtle bugs, if not careful.
```rust ```rust
@ -1390,7 +1545,7 @@ fn add(x, y) {
// The following will not compile // The following will not compile
fn do_addition(x) { fn do_addition(x) {
fn add_y(n) { // functions cannot be defined inside another function fn add_y(n) { // <- syntax error: functions cannot be defined inside another function
n + y 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). Rhai attempts to eliminate _dead code_ (i.e. code that does nothing, for example an expression by itself as a statement,
The above script optimizes to: which is allowed in Rhai). The above script optimizes to:
```rust ```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 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`] nor cause side any effects, with the exception of `print` and `debug` which are handled specially) so using
is usually quite safe _unless_ you register your own types and functions. [`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, 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` If custom functions are registered to replace built-in operators, they will also be called when the operators are used
statement, for example) and cause side-effects. (in an `if` statement, for example) and cause side-effects.
Function volatility considerations 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 Even if a custom function does not mutate state nor cause side effects, it may still be _volatile_, i.e. it _depends_
environment and not _pure_. A perfect example is a function that gets the current time - obviously each run will return a different value! on the external environment and is not _pure_. A perfect example is a function that gets the current time -
The optimizer, when using [`OptimizationLevel::Full`], _assumes_ that all functions are _pure_, so when it finds constant arguments obviously each run will return a different value! The optimizer, when using [`OptimizationLevel::Full`], _assumes_ that
it will eagerly run execute the function call. This causes the script to behave differently from the intended semantics because all functions are _pure_, so when it finds constant arguments it will eagerly execute the function call.
essentially the result of the function call will always be the same value. 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. 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!"); 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. In the script above, if `my_decision` holds anything other than a boolean value, the script should have been terminated due to
However, after optimization, the entire `if` statement is removed (because an access to `my_decision` produces no side effects), a type error. However, after optimization, the entire `if` statement is removed (because an access to `my_decision` produces
thus the script silently runs to completion without errors. no side effects), thus the script silently runs to completion without errors.
Turning off optimizations 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), 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
turn it off by setting the optimization level to [`OptimizationLevel::None`]. (why? I would never guess), turn it off by setting the optimization level to [`OptimizationLevel::None`].
```rust ```rust
let engine = rhai::Engine::new(); let engine = rhai::Engine::new();

View File

@ -9,10 +9,6 @@ use std::{
}; };
fn print_error(input: &str, err: EvalAltResult) { 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 lines: Vec<_> = input.trim().split('\n').collect();
let line_no = if lines.len() > 1 { let line_no = if lines.len() > 1 {
@ -54,8 +50,9 @@ fn print_error(input: &str, err: EvalAltResult) {
}; };
println!( println!(
"{}^ {}", "{0:>1$} {2}",
padding(" ", line_no.len() + p.position().unwrap() - 1), "^",
line_no.len() + p.position().unwrap(),
err_text.replace(&pos_text, "") err_text.replace(&pos_text, "")
); );
} }
@ -80,8 +77,9 @@ fn main() {
let mut scope = Scope::new(); let mut scope = Scope::new();
let mut input = String::new(); let mut input = String::new();
let mut ast_u: Option<AST> = None; let mut main_ast = AST::new();
let mut ast: Option<AST> = None; let mut ast_u = AST::new();
let mut ast = AST::new();
println!("Rhai REPL tool"); println!("Rhai REPL tool");
println!("=============="); println!("==============");
@ -115,6 +113,10 @@ fn main() {
let script = input.trim(); let script = input.trim();
if script.is_empty() {
continue;
}
// Implement standard commands // Implement standard commands
match script { match script {
"help" => { "help" => {
@ -123,21 +125,13 @@ fn main() {
} }
"exit" | "quit" => break, // quit "exit" | "quit" => break, // quit
"astu" => { "astu" => {
if matches!(&ast_u, Some(_)) { // print the last un-optimized AST
// print the last un-optimized AST println!("{:#?}", &ast_u);
println!("{:#?}", ast_u.as_ref().unwrap());
} else {
println!("()");
}
continue; continue;
} }
"ast" => { "ast" => {
if matches!(&ast, Some(_)) { // print the last AST
// print the last AST println!("{:#?}", &ast);
println!("{:#?}", ast.as_ref().unwrap());
} else {
println!("()");
}
continue; continue;
} }
_ => (), _ => (),
@ -147,26 +141,35 @@ fn main() {
.compile_with_scope(&scope, &script) .compile_with_scope(&scope, &script)
.map_err(EvalAltResult::ErrorParsing) .map_err(EvalAltResult::ErrorParsing)
.and_then(|r| { .and_then(|r| {
ast_u = Some(r); ast_u = r.clone();
#[cfg(not(feature = "no_optimize"))] #[cfg(not(feature = "no_optimize"))]
{ {
engine.set_optimization_level(OptimizationLevel::Full); 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); engine.set_optimization_level(OptimizationLevel::None);
} }
#[cfg(feature = "no_optimize")] #[cfg(feature = "no_optimize")]
{ {
ast = ast_u.clone(); ast = r;
} }
engine // Merge the AST into the main
.consume_ast_with_scope(&mut scope, true, ast.as_ref().unwrap()) main_ast = main_ast.merge(&ast);
// Evaluate
let result = engine
.consume_ast_with_scope(&mut scope, &main_ast)
.or_else(|err| match err { .or_else(|err| match err {
EvalAltResult::Return(_, _) => Ok(()), EvalAltResult::Return(_, _) => Ok(()),
err => Err(err), err => Err(err),
}) });
// Throw away all the statements, leaving only the functions
main_ast.retain_functions();
result
}) })
{ {
println!(); println!();

View File

@ -5,10 +5,6 @@ use rhai::OptimizationLevel;
use std::{env, fs::File, io::Read, iter, process::exit}; 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_error(input: &str, err: EvalAltResult) {
fn eprint_line(lines: &[&str], line: usize, pos: usize, err: &str) { fn eprint_line(lines: &[&str], line: usize, pos: usize, err: &str) {
let line_no = format!("{}: ", line); let line_no = format!("{}: ", line);
@ -16,8 +12,9 @@ fn eprint_error(input: &str, err: EvalAltResult) {
eprintln!("{}{}", line_no, lines[line - 1]); eprintln!("{}{}", line_no, lines[line - 1]);
eprintln!( eprintln!(
"{}^ {}", "{:>1$} {2}",
padding(" ", line_no.len() + pos - 1), "^",
line_no.len() + pos,
err.replace(&pos_text, "") err.replace(&pos_text, "")
); );
eprintln!(""); eprintln!("");
@ -75,10 +72,10 @@ fn main() {
exit(1); exit(1);
} }
if let Err(err) = engine.consume(false, &contents) { if let Err(err) = engine.consume(&contents) {
eprintln!("{}", padding("=", filename.len())); eprintln!("{:=<1$}", "", filename.len());
eprintln!("{}", filename); eprintln!("{}", filename);
eprintln!("{}", padding("=", filename.len())); eprintln!("{:=<1$}", "", filename.len());
eprintln!(""); eprintln!("");
eprint_error(&contents, err); eprint_error(&contents, err);

View File

@ -1,19 +1,26 @@
//! Helper module which defines the `Any` trait to to allow dynamic value handling. //! Helper module which defines the `Any` trait to to allow dynamic value handling.
use crate::stdlib::{ use crate::stdlib::{
any::{type_name, Any as StdAny, TypeId}, any::{type_name, TypeId},
boxed::Box, boxed::Box,
fmt, fmt,
}; };
/// An raw value of any type. /// 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; pub type Variant = dyn Any;
/// A boxed dynamic type containing any value. /// 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>; pub type Dynamic = Box<Variant>;
/// A trait covering any type. /// 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. /// Get the `TypeId` of this type.
fn type_id(&self) -> TypeId; fn type_id(&self) -> TypeId;
@ -28,7 +35,44 @@ pub trait Any: StdAny {
fn _closed(&self) -> _Private; 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 { fn type_id(&self) -> TypeId {
TypeId::of::<T>() TypeId::of::<T>()
} }
@ -48,15 +92,13 @@ impl<T: Clone + StdAny + ?Sized> Any for T {
impl Variant { impl Variant {
/// Is this `Variant` a specific type? /// Is this `Variant` a specific type?
pub(crate) fn is<T: Any>(&self) -> bool { pub fn is<T: Any>(&self) -> bool {
let t = TypeId::of::<T>(); TypeId::of::<T>() == <Variant as Any>::type_id(self)
let boxed = <Variant as Any>::type_id(self);
t == boxed
} }
/// Get a reference of a specific type to the `Variant`. /// 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>() { if self.is::<T>() {
unsafe { Some(&*(self as *const Variant as *const T)) } unsafe { Some(&*(self as *const Variant as *const T)) }
} else { } else {
@ -65,7 +107,8 @@ impl Variant {
} }
/// Get a mutable reference of a specific type to the `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>() { if self.is::<T>() {
unsafe { Some(&mut *(self as *mut Variant as *mut T)) } unsafe { Some(&mut *(self as *mut Variant as *mut T)) }
} else { } else {
@ -82,14 +125,21 @@ impl fmt::Debug for Variant {
impl Clone for Dynamic { impl Clone for Dynamic {
fn clone(&self) -> Self { 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. /// An extension trait that allows down-casting a `Dynamic` value to a specific type.
pub trait AnyExt: Sized { pub trait AnyExt: Sized {
/// Get a copy of a `Dynamic` value as a specific type. /// 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`. /// This trait may only be implemented by `rhai`.
#[doc(hidden)] #[doc(hidden)]
@ -106,19 +156,38 @@ impl AnyExt for Dynamic {
/// ///
/// let x: Dynamic = 42_u32.into_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>() { if self.is::<T>() {
unsafe { unsafe {
let raw: *mut Variant = Box::into_raw(self); 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 { } else {
Err(self) 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 { fn _closed(&self) -> _Private {
_Private _Private
} }

View File

@ -5,7 +5,7 @@ use crate::call::FuncArgs;
use crate::engine::{make_getter, make_setter, Engine, FnAny, FnSpec}; use crate::engine::{make_getter, make_setter, Engine, FnAny, FnSpec};
use crate::error::ParseError; use crate::error::ParseError;
use crate::fn_register::RegisterFn; use crate::fn_register::RegisterFn;
use crate::parser::{lex, parse, parse_global_expr, FnDef, Position, AST}; use crate::parser::{lex, parse, parse_global_expr, Position, AST};
use crate::result::EvalAltResult; use crate::result::EvalAltResult;
use crate::scope::Scope; use crate::scope::Scope;
@ -15,13 +15,51 @@ use crate::optimize::optimize_into_ast;
use crate::stdlib::{ use crate::stdlib::{
any::{type_name, TypeId}, any::{type_name, TypeId},
boxed::Box, boxed::Box,
collections::HashMap,
string::{String, ToString}, string::{String, ToString},
sync::Arc,
vec::Vec, vec::Vec,
}; };
#[cfg(not(feature = "no_std"))] #[cfg(not(feature = "no_std"))]
use crate::stdlib::{fs::File, io::prelude::*, path::PathBuf}; 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> { impl<'e> Engine<'e> {
/// Register a custom function. /// Register a custom function.
pub(crate) fn register_fn_raw(&mut self, fn_name: &str, args: Vec<TypeId>, f: Box<FnAny>) { 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, 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`. /// Register a custom type for use with the `Engine`.
@ -119,18 +160,28 @@ impl<'e> Engine<'e> {
/// ``` /// ```
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
pub fn register_type_with_name<T: Any + Clone>(&mut self, name: &str) { pub fn register_type_with_name<T: Any + Clone>(&mut self, name: &str) {
if self.type_names.is_none() {
self.type_names = Some(HashMap::new());
}
// Add the pretty-print type name into the map // Add the pretty-print type name into the map
self.type_names self.type_names
.as_mut()
.unwrap()
.insert(type_name::<T>().to_string(), name.to_string()); .insert(type_name::<T>().to_string(), name.to_string());
} }
/// Register an iterator adapter for a type with the `Engine`. /// Register an iterator adapter for a type with the `Engine`.
/// This is an advanced feature. /// This is an advanced feature.
pub fn register_iterator<T: Any, F>(&mut self, f: F) pub fn register_iterator<T: Any, F: IteratorCallback>(&mut self, f: F) {
where if self.type_iterators.is_none() {
F: Fn(&Dynamic) -> Box<dyn Iterator<Item = Dynamic>> + 'static, self.type_iterators = Some(HashMap::new());
{ }
self.type_iterators.insert(TypeId::of::<T>(), Box::new(f));
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`. /// 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"))] #[cfg(not(feature = "no_object"))]
pub fn register_get<T: Any + Clone, U: Any + Clone>( pub fn register_get<T, U, F>(&mut self, name: &str, callback: F)
&mut self, where
name: &str, T: Any + Clone,
callback: impl Fn(&mut T) -> U + 'static, U: Any + Clone,
) { F: ObjectGetCallback<T, U>,
{
self.register_fn(&make_getter(name), callback); self.register_fn(&make_getter(name), callback);
} }
@ -215,11 +267,12 @@ impl<'e> Engine<'e> {
/// # } /// # }
/// ``` /// ```
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
pub fn register_set<T: Any + Clone, U: Any + Clone>( pub fn register_set<T, U, F>(&mut self, name: &str, callback: F)
&mut self, where
name: &str, T: Any + Clone,
callback: impl Fn(&mut T, U) -> () + 'static, U: Any + Clone,
) { F: ObjectSetCallback<T, U>,
{
self.register_fn(&make_setter(name), callback); self.register_fn(&make_setter(name), callback);
} }
@ -262,12 +315,13 @@ impl<'e> Engine<'e> {
/// # } /// # }
/// ``` /// ```
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
pub fn register_get_set<T: Any + Clone, U: Any + Clone>( pub fn register_get_set<T, U, G, S>(&mut self, name: &str, get_fn: G, set_fn: S)
&mut self, where
name: &str, T: Any + Clone,
get_fn: impl Fn(&mut T) -> U + 'static, U: Any + Clone,
set_fn: impl Fn(&mut T, U) -> () + 'static, G: ObjectGetCallback<T, U>,
) { S: ObjectSetCallback<T, U>,
{
self.register_get(name, get_fn); self.register_get(name, get_fn);
self.register_set(name, set_fn); self.register_set(name, set_fn);
} }
@ -691,9 +745,8 @@ impl<'e> Engine<'e> {
scope: &mut Scope, scope: &mut Scope,
ast: &AST, ast: &AST,
) -> Result<T, EvalAltResult> { ) -> Result<T, EvalAltResult> {
self.eval_ast_with_scope_raw(scope, false, ast)? self.eval_ast_with_scope_raw(scope, ast)?
.downcast::<T>() .try_cast::<T>()
.map(|v| *v)
.map_err(|a| { .map_err(|a| {
EvalAltResult::ErrorMismatchOutputType( EvalAltResult::ErrorMismatchOutputType(
self.map_type_name((*a).type_name()).to_string(), self.map_type_name((*a).type_name()).to_string(),
@ -705,16 +758,11 @@ impl<'e> Engine<'e> {
pub(crate) fn eval_ast_with_scope_raw( pub(crate) fn eval_ast_with_scope_raw(
&mut self, &mut self,
scope: &mut Scope, scope: &mut Scope,
retain_functions: bool,
ast: &AST, ast: &AST,
) -> Result<Dynamic, EvalAltResult> { ) -> Result<Dynamic, EvalAltResult> {
if !retain_functions {
self.clear_functions();
}
let statements = { let statements = {
let AST(statements, functions) = ast; let AST(statements, functions) = ast;
self.load_script_functions(functions); self.fn_lib = Some(functions.clone());
statements statements
}; };
@ -722,9 +770,7 @@ impl<'e> Engine<'e> {
.iter() .iter()
.try_fold(().into_dynamic(), |_, stmt| self.eval_stmt(scope, stmt, 0)); .try_fold(().into_dynamic(), |_, stmt| self.eval_stmt(scope, stmt, 0));
if !retain_functions { self.fn_lib = None;
self.clear_functions();
}
result.or_else(|err| match err { result.or_else(|err| match err {
EvalAltResult::Return(out, _) => Ok(out), 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). /// Evaluate a file, but throw away the result and only return error (if any).
/// Useful for when you don't need the result, but still need to keep track of possible errors. /// Useful for when you don't need the result, but still need to keep track of possible errors.
///
/// # Note
///
/// If `retain_functions` is set to `true`, functions defined by previous scripts are _retained_ and not cleared from run to run.
#[cfg(not(feature = "no_std"))] #[cfg(not(feature = "no_std"))]
pub fn consume_file( pub fn consume_file(&mut self, path: PathBuf) -> Result<(), EvalAltResult> {
&mut self, Self::read_file(path).and_then(|contents| self.consume(&contents))
retain_functions: bool,
path: PathBuf,
) -> Result<(), EvalAltResult> {
Self::read_file(path).and_then(|contents| self.consume(retain_functions, &contents))
} }
/// Evaluate a file with own scope, but throw away the result and only return error (if any). /// Evaluate a file with own scope, but throw away the result and only return error (if any).
/// Useful for when you don't need the result, but still need to keep track of possible errors. /// Useful for when you don't need the result, but still need to keep track of possible errors.
///
/// # Note
///
/// If `retain_functions` is set to `true`, functions defined by previous scripts are _retained_ and not cleared from run to run.
#[cfg(not(feature = "no_std"))] #[cfg(not(feature = "no_std"))]
pub fn consume_file_with_scope( pub fn consume_file_with_scope(
&mut self, &mut self,
scope: &mut Scope, scope: &mut Scope,
retain_functions: bool,
path: PathBuf, path: PathBuf,
) -> Result<(), EvalAltResult> { ) -> Result<(), EvalAltResult> {
Self::read_file(path) Self::read_file(path).and_then(|contents| self.consume_with_scope(scope, &contents))
.and_then(|contents| self.consume_with_scope(scope, retain_functions, &contents))
} }
/// Evaluate a string, but throw away the result and only return error (if any). /// Evaluate a string, but throw away the result and only return error (if any).
/// Useful for when you don't need the result, but still need to keep track of possible errors. /// Useful for when you don't need the result, but still need to keep track of possible errors.
/// pub fn consume(&mut self, input: &str) -> Result<(), EvalAltResult> {
/// # Note self.consume_with_scope(&mut Scope::new(), input)
///
/// 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)
} }
/// Evaluate a string with own scope, but throw away the result and only return error (if any). /// Evaluate a string with own scope, but throw away the result and only return error (if any).
/// Useful for when you don't need the result, but still need to keep track of possible errors. /// Useful for when you don't need the result, but still need to keep track of possible errors.
///
/// # Note
///
/// If `retain_functions` is set to `true`, functions defined by previous scripts are _retained_and not cleared from run to run.
pub fn consume_with_scope( pub fn consume_with_scope(
&mut self, &mut self,
scope: &mut Scope, scope: &mut Scope,
retain_functions: bool,
input: &str, input: &str,
) -> Result<(), EvalAltResult> { ) -> Result<(), EvalAltResult> {
let tokens_stream = lex(input); let tokens_stream = lex(input);
@ -791,38 +814,25 @@ impl<'e> Engine<'e> {
let ast = parse(&mut tokens_stream.peekable(), self, scope) let ast = parse(&mut tokens_stream.peekable(), self, scope)
.map_err(EvalAltResult::ErrorParsing)?; .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). /// Evaluate an AST, but throw away the result and only return error (if any).
/// Useful for when you don't need the result, but still need to keep track of possible errors. /// Useful for when you don't need the result, but still need to keep track of possible errors.
/// pub fn consume_ast(&mut self, ast: &AST) -> Result<(), EvalAltResult> {
/// # Note self.consume_ast_with_scope(&mut Scope::new(), ast)
///
/// 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)
} }
/// Evaluate an `AST` with own scope, but throw away the result and only return error (if any). /// Evaluate an `AST` with own scope, but throw away the result and only return error (if any).
/// Useful for when you don't need the result, but still need to keep track of possible errors. /// Useful for when you don't need the result, but still need to keep track of possible errors.
///
/// # Note
///
/// If `retain_functions` is set to `true`, functions defined by previous scripts are _retained_and not cleared from run to run.
pub fn consume_ast_with_scope( pub fn consume_ast_with_scope(
&mut self, &mut self,
scope: &mut Scope, scope: &mut Scope,
retain_functions: bool,
ast: &AST, ast: &AST,
) -> Result<(), EvalAltResult> { ) -> Result<(), EvalAltResult> {
if !retain_functions {
self.clear_functions();
}
let statements = { let statements = {
let AST(ref statements, ref functions) = ast; let AST(statements, functions) = ast;
self.load_script_functions(functions); self.fn_lib = Some(functions.clone());
statements statements
}; };
@ -830,9 +840,7 @@ impl<'e> Engine<'e> {
.iter() .iter()
.try_fold(().into_dynamic(), |_, stmt| self.eval_stmt(scope, stmt, 0)); .try_fold(().into_dynamic(), |_, stmt| self.eval_stmt(scope, stmt, 0));
if !retain_functions { self.fn_lib = None;
self.clear_functions();
}
result.map(|_| ()).or_else(|err| match err { result.map(|_| ()).or_else(|err| match err {
EvalAltResult::Return(_, _) => Ok(()), EvalAltResult::Return(_, _) => Ok(()),
@ -840,17 +848,7 @@ impl<'e> Engine<'e> {
}) })
} }
/// Load a list of functions into the Engine. /// Call a script function defined in an `AST` with no argument.
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.
/// ///
/// # Example /// # Example
/// ///
@ -859,17 +857,92 @@ impl<'e> Engine<'e> {
/// # #[cfg(not(feature = "no_stdlib"))] /// # #[cfg(not(feature = "no_stdlib"))]
/// # #[cfg(not(feature = "no_function"))] /// # #[cfg(not(feature = "no_function"))]
/// # { /// # {
/// use rhai::Engine; /// use rhai::{Engine, Scope};
/// ///
/// let mut engine = Engine::new(); /// let mut engine = Engine::new();
/// ///
/// // Set 'retain_functions' in 'consume' to keep the function definitions /// let ast = engine.compile("fn num() { 42 + foo }")?;
/// engine.consume(true, "fn add(x, y) { len(x) + y }")?; ///
/// let mut scope = Scope::new();
/// scope.push("foo", 42_i64);
/// ///
/// // Call the script-defined function /// // Call the script-defined function
/// let result: i64 = engine.call_fn("add", (String::from("abc"), 123_i64))?; /// let result: i64 = engine.call_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(()) /// # Ok(())
/// # } /// # }
@ -877,25 +950,43 @@ impl<'e> Engine<'e> {
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]
pub fn call_fn<A: FuncArgs, T: Any + Clone>( pub fn call_fn<A: FuncArgs, T: Any + Clone>(
&mut self, &mut self,
scope: &mut Scope,
ast: &AST,
name: &str, name: &str,
args: A, args: A,
) -> Result<T, EvalAltResult> { ) -> Result<T, EvalAltResult> {
let mut values = args.into_vec(); self.call_fn_internal(scope, ast, name, args.into_vec())
let mut arg_values: Vec<_> = values.iter_mut().map(Dynamic::as_mut).collect(); }
self.call_fn_raw(name, &mut arg_values, None, Position::none(), 0)? #[cfg(not(feature = "no_function"))]
.downcast() fn call_fn_internal<T: Any + Clone>(
.map(|b| *b) &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| { .map_err(|a| {
EvalAltResult::ErrorMismatchOutputType( EvalAltResult::ErrorMismatchOutputType(
self.map_type_name((*a).type_name()).into(), self.map_type_name((*a).type_name()).into(),
Position::none(), Position::none(),
) )
}) });
self.fn_lib = None;
result
} }
/// Optimize the `AST` with constants defined in an external Scope. /// 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 /// 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 /// _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 /// 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. /// (i.e. with `scope.push_constant(...)`). Then, the `AST is cloned and the copy re-optimized before running.
#[cfg(not(feature = "no_optimize"))] #[cfg(not(feature = "no_optimize"))]
pub fn optimize_ast(&self, scope: &Scope, ast: &AST) -> AST { pub fn optimize_ast(&self, scope: &Scope, ast: AST) -> AST {
let statements = ast.0.clone(); optimize_into_ast(
let functions = ast.1.iter().map(|f| (**f).clone()).collect(); self,
scope,
optimize_into_ast(self, scope, statements, functions) ast.0,
ast.1.iter().map(|fn_def| fn_def.as_ref().clone()).collect(),
)
} }
/// Override default action of `print` (print to stdout using `println!`) /// Override default action of `print` (print to stdout using `println!`)
@ -927,14 +1020,39 @@ impl<'e> Engine<'e> {
/// ///
/// // Override action of 'print' function /// // Override action of 'print' function
/// engine.on_print(|s| result.push_str(s)); /// engine.on_print(|s| result.push_str(s));
/// engine.consume(false, "print(40 + 2);")?; /// engine.consume("print(40 + 2);")?;
/// } /// }
/// assert_eq!(result, "42"); /// assert_eq!(result, "42");
/// # Ok(()) /// # 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) { 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!`) /// Override default action of `debug` (print to stdout using `println!`)
@ -951,13 +1069,38 @@ impl<'e> Engine<'e> {
/// ///
/// // Override action of 'debug' function /// // Override action of 'debug' function
/// engine.on_debug(|s| result.push_str(s)); /// engine.on_debug(|s| result.push_str(s));
/// engine.consume(false, r#"debug("hello");"#)?; /// engine.consume(r#"debug("hello");"#)?;
/// } /// }
/// assert_eq!(result, "\"hello\""); /// assert_eq!(result, "\"hello\"");
/// # Ok(()) /// # 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) { 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 //! Helper module that allows registration of the _core library_ and
//! _standard library_ of utility functions. //! _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::engine::{Engine, FUNC_TO_STRING, KEYWORD_DEBUG, KEYWORD_PRINT};
use crate::fn_register::{RegisterDynamicFn, RegisterFn, RegisterResultFn}; use crate::fn_register::{RegisterDynamicFn, RegisterFn, RegisterResultFn};
use crate::parser::{Position, INT}; use crate::parser::{Position, INT};
@ -612,8 +612,9 @@ impl Engine<'_> {
reg_fn1!(self, KEYWORD_DEBUG, to_debug, String, Array); reg_fn1!(self, KEYWORD_DEBUG, to_debug, String, Array);
// Register array iterator // Register array iterator
self.register_iterator::<Array, _>(|a| { self.register_iterator::<Array, _>(|a: &Dynamic| {
Box::new(a.downcast_ref::<Array>().unwrap().clone().into_iter()) 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 { self.register_fn(KEYWORD_DEBUG, |x: &mut Map| -> String {
format!("#{:?}", x) 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 // Register range function
fn reg_iterator<T: Any + Clone>(engine: &mut Engine) fn reg_range<T: Any + Clone>(engine: &mut Engine)
where where
Range<T>: Iterator<Item = T>, Range<T>: Iterator<Item = T>,
{ {
engine.register_iterator::<Range<T>, _>(|a| { engine.register_iterator::<Range<T>, _>(|a: &Dynamic| {
Box::new( Box::new(
a.downcast_ref::<Range<T>>() a.downcast_ref::<Range<T>>()
.unwrap() .unwrap()
.clone() .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)); self.register_fn("range", |i1: INT, i2: INT| (i1..i2));
#[cfg(not(feature = "only_i32"))] #[cfg(not(feature = "only_i32"))]
@ -655,7 +667,7 @@ impl Engine<'_> {
macro_rules! reg_range { macro_rules! reg_range {
($self:expr, $x:expr, $( $y:ty ),*) => ( ($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>); $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); 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, (), INT, bool, char);
reg_fn3!(self, "pad", pad, &mut Array, INT, (), String, Array, ()); 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_i32"))]
#[cfg(not(feature = "only_i64"))] #[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("has", |map: &mut Map, prop: String| map.contains_key(&prop));
self.register_fn("len", |map: &mut Map| map.len() as INT); self.register_fn("len", |map: &mut Map| map.len() as INT);
self.register_fn("clear", |map: &mut Map| map.clear()); 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 // Register string concatenate functions

View File

@ -3,10 +3,6 @@
#![allow(non_snake_case)] #![allow(non_snake_case)]
use crate::any::{Any, Dynamic}; 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}; use crate::stdlib::{string::String, vec, vec::Vec};
@ -18,36 +14,7 @@ pub trait FuncArgs {
fn into_vec(self) -> Vec<Dynamic>; fn into_vec(self) -> Vec<Dynamic>;
} }
/// Macro to implement `FuncArgs` for a single standard type that can be converted // Macro to implement `FuncArgs` for tuples of standard types (each can be
/// 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
/// converted into `Dynamic`). /// converted into `Dynamic`).
macro_rules! impl_args { macro_rules! impl_args {
($($p:ident),*) => { ($($p:ident),*) => {

View File

@ -1,7 +1,7 @@
//! Main module defining the script evaluation `Engine`. //! Main module defining the script evaluation `Engine`.
use crate::any::{Any, AnyExt, Dynamic, Variant}; 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::result::EvalAltResult;
use crate::scope::{EntryRef as ScopeSource, EntryType as ScopeEntryType, Scope}; use crate::scope::{EntryRef as ScopeSource, EntryType as ScopeEntryType, Scope};
@ -16,6 +16,8 @@ use crate::stdlib::{
collections::HashMap, collections::HashMap,
format, format,
iter::once, iter::once,
ops::{Deref, DerefMut},
rc::Rc,
string::{String, ToString}, string::{String, ToString},
sync::Arc, sync::Arc,
vec, vec,
@ -23,17 +25,27 @@ use crate::stdlib::{
}; };
/// An dynamic array of `Dynamic` values. /// An dynamic array of `Dynamic` values.
///
/// Not available under the `no_index` feature.
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
pub type Array = Vec<Dynamic>; pub type Array = Vec<Dynamic>;
/// An dynamic hash map of `Dynamic` values. /// An dynamic hash map of `Dynamic` values with `String` keys.
///
/// Not available under the `no_object` feature.
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
pub type Map = HashMap<String, Dynamic>; pub type Map = HashMap<String, Dynamic>;
pub type FnCallArgs<'a> = [&'a mut Variant]; 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>; 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>>; type IteratorFn = dyn Fn(&Dynamic) -> Box<dyn Iterator<Item = Dynamic>>;
pub const MAX_CALL_STACK_DEPTH: usize = 64; pub const MAX_CALL_STACK_DEPTH: usize = 64;
@ -120,8 +132,11 @@ pub struct FnSpec<'a> {
/// to search for it. /// to search for it.
/// ///
/// So instead this is implemented as a sorted list and binary searched. /// So instead this is implemented as a sorted list and binary searched.
#[derive(Debug)] #[derive(Debug, Clone)]
pub struct FunctionsLib(Vec<Arc<FnDef>>); pub struct FunctionsLib(
#[cfg(feature = "sync")] Vec<Arc<FnDef>>,
#[cfg(not(feature = "sync"))] Vec<Rc<FnDef>>,
);
impl FnDef { impl FnDef {
/// Function to order two FnDef records, for binary search. /// Function to order two FnDef records, for binary search.
@ -140,32 +155,75 @@ impl FunctionsLib {
pub fn new() -> Self { pub fn new() -> Self {
FunctionsLib(Vec::new()) FunctionsLib(Vec::new())
} }
/// Clear the `FunctionsLib`. /// Create a new `FunctionsLib` from a collection of `FnDef`.
pub fn clear(&mut self) { pub fn from_vec(vec: Vec<FnDef>) -> Self {
self.0.clear(); #[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`? /// Does a certain function exist in the `FunctionsLib`?
pub fn has_function(&self, name: &str, params: usize) -> bool { pub fn has_function(&self, name: &str, params: usize) -> bool {
self.0.binary_search_by(|f| f.compare(name, params)).is_ok() 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`. /// 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)) { if let Ok(n) = self.0.binary_search_by(|f| f.compare(name, params)) {
Some(self.0[n].clone()) Some(&self.0[n])
} else { } else {
None 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. /// Rhai main scripting engine.
@ -182,20 +240,37 @@ impl FunctionsLib {
/// # Ok(()) /// # Ok(())
/// # } /// # }
/// ``` /// ```
///
/// Currently, `Engine` is neither `Send` nor `Sync`. Turn on the `sync` feature to make it `Send + Sync`.
pub struct Engine<'e> { pub struct Engine<'e> {
/// A hashmap containing all compiled functions known to the engine. /// 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. /// 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. /// 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. /// 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. /// 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. /// 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. /// Optimize the AST after compilation.
#[cfg(not(feature = "no_optimize"))] #[cfg(not(feature = "no_optimize"))]
@ -217,17 +292,17 @@ impl Default for Engine<'_> {
(type_name::<Dynamic>(), "dynamic"), (type_name::<Dynamic>(), "dynamic"),
] ]
.iter() .iter()
.map(|(k, v)| ((*k).to_string(), (*v).to_string())) .map(|(k, v)| (k.to_string(), v.to_string()))
.collect(); .collect();
// Create the new scripting Engine // Create the new scripting Engine
let mut engine = Engine { let mut engine = Engine {
functions: HashMap::new(), functions: None,
fn_lib: FunctionsLib::new(), fn_lib: None,
type_iterators: HashMap::new(), type_iterators: None,
type_names, type_names: Some(type_names),
on_print: Box::new(default_print), // default print/debug implementations on_print: Some(Box::new(default_print)), // default print/debug implementations
on_debug: Box::new(default_print), on_debug: Some(Box::new(default_print)),
#[cfg(not(feature = "no_optimize"))] #[cfg(not(feature = "no_optimize"))]
#[cfg(not(feature = "optimize_full"))] #[cfg(not(feature = "optimize_full"))]
@ -280,10 +355,46 @@ fn extract_prop_from_setter(fn_name: &str) -> Option<&str> {
impl Engine<'_> { impl Engine<'_> {
/// Create a new `Engine` /// Create a new `Engine`
pub fn new() -> Self { pub fn new() -> Self {
// fn abc<F: Fn() + Send + Sync>(f: F) {
// f();
// }
// abc(|| ());
Default::default() 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 /// Control whether and how the `Engine` will optimize an AST after compilation
///
/// Not available under the `no_optimize` feature.
#[cfg(not(feature = "no_optimize"))] #[cfg(not(feature = "no_optimize"))]
pub fn set_optimization_level(&mut self, optimization_level: OptimizationLevel) { pub fn set_optimization_level(&mut self, optimization_level: OptimizationLevel) {
self.optimization_level = optimization_level self.optimization_level = optimization_level
@ -309,9 +420,13 @@ impl Engine<'_> {
}; };
// Search built-in's and external functions // Search built-in's and external functions
if let Some(func) = self.functions.get(&spec) { if let Some(functions) = &self.functions {
// Run external function if let Some(func) = functions.get(&spec) {
Ok(Some(func(args, pos)?)) // Run external function
Ok(Some(func(args, pos)?))
} else {
Ok(None)
}
} else { } else {
Ok(None) Ok(None)
} }
@ -320,6 +435,7 @@ impl Engine<'_> {
/// Universal method for calling functions either registered with the `Engine` or written in Rhai /// Universal method for calling functions either registered with the `Engine` or written in Rhai
pub(crate) fn call_fn_raw( pub(crate) fn call_fn_raw(
&mut self, &mut self,
scope: Option<&mut Scope>,
fn_name: &str, fn_name: &str,
args: &mut FnCallArgs, args: &mut FnCallArgs,
def_val: Option<&Dynamic>, def_val: Option<&Dynamic>,
@ -327,26 +443,60 @@ impl Engine<'_> {
level: usize, level: usize,
) -> Result<Dynamic, EvalAltResult> { ) -> Result<Dynamic, EvalAltResult> {
// First search in script-defined functions (can override built-in) // 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 {
let mut scope = Scope::new(); 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( scope.extend(
// Put arguments into scope as variables // Put arguments into scope as variables - variable name is copied
fn_def // TODO - avoid copying variable name
.params fn_def
.iter() .params
.zip(args.iter().map(|x| (*x).into_dynamic())) .iter()
.map(|(name, value)| (name, ScopeEntryType::Normal, value)), .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 // Evaluate the function at one higher level of call depth
return self let result = self.eval_stmt(scope, &fn_def.body, level + 1).or_else(
.eval_stmt(&mut scope, &fn_def.body, level + 1) |err| match err {
.or_else(|err| match err { // Convert return statement to return value
// Convert return statement to return value EvalAltResult::Return(x, _) => Ok(x),
EvalAltResult::Return(x, _) => Ok(x), err => Err(err.set_position(pos)),
err => Err(err.set_position(pos)), },
}); );
scope.rewind(scope_len);
return result;
}
// No new scope - create internal scope
_ => {
let mut scope = Scope::new();
scope.extend(
// Put arguments into scope as variables
fn_def
.params
.iter()
.zip(args.into_iter().map(|x| (*x).into_dynamic()))
.map(|(name, value)| (name, ScopeEntryType::Normal, value)),
);
// Evaluate the function at one higher level of call depth
return self.eval_stmt(&mut scope, &fn_def.body, level + 1).or_else(
|err| match err {
// Convert return statement to return value
EvalAltResult::Return(x, _) => Ok(x),
err => Err(err.set_position(pos)),
},
);
}
}
}
} }
let spec = FnSpec { let spec = FnSpec {
@ -362,20 +512,25 @@ impl Engine<'_> {
} }
// Search built-in's and external functions // Search built-in's and external functions
if let Some(func) = self.functions.get(&spec) { if let Some(functions) = &self.functions {
// Run external function if let Some(func) = functions.get(&spec) {
let result = func(args, pos)?; // Run external function
let result = func(args, pos)?;
// See if the function match print/debug (which requires special processing) // See if the function match print/debug (which requires special processing)
return Ok(match fn_name { return Ok(match fn_name {
KEYWORD_PRINT => { KEYWORD_PRINT if self.on_print.is_some() => {
self.on_print.as_mut()(cast_to_string(result.as_ref(), pos)?).into_dynamic() 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)?)
_ => result, .into_dynamic()
}); }
KEYWORD_PRINT | KEYWORD_DEBUG => ().into_dynamic(),
_ => result,
});
}
} }
if let Some(prop) = extract_prop_from_getter(fn_name) { if let Some(prop) = extract_prop_from_getter(fn_name) {
@ -441,7 +596,7 @@ impl Engine<'_> {
level: usize, level: usize,
) -> Result<Dynamic, EvalAltResult> { ) -> Result<Dynamic, EvalAltResult> {
match dot_rhs { match dot_rhs {
// xxx.fn_name(args) // xxx.fn_name(arg_expr_list)
Expr::FunctionCall(fn_name, arg_expr_list, def_val, pos) => { Expr::FunctionCall(fn_name, arg_expr_list, def_val, pos) => {
let mut values = arg_expr_list let mut values = arg_expr_list
.iter() .iter()
@ -450,17 +605,19 @@ impl Engine<'_> {
let this_ptr = target.get_mut(scope); 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)) .chain(values.iter_mut().map(Dynamic::as_mut))
.collect(); .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 // xxx.id
Expr::Property(id, pos) => { Expr::Property(id, pos) => {
let this_ptr = target.get_mut(scope); let mut args = [target.get_mut(scope)];
self.call_fn_raw(&make_getter(id), &mut [this_ptr], None, *pos, 0) self.call_fn_raw(None, &make_getter(id), &mut args, None, *pos, 0)
} }
// xxx.idx_lhs[idx_expr] // xxx.idx_lhs[idx_expr]
@ -469,8 +626,8 @@ impl Engine<'_> {
let value = match idx_lhs.as_ref() { let value = match idx_lhs.as_ref() {
// xxx.id[idx_expr] // xxx.id[idx_expr]
Expr::Property(id, pos) => { Expr::Property(id, pos) => {
let this_ptr = target.get_mut(scope); let mut args = [target.get_mut(scope)];
self.call_fn_raw(&make_getter(id), &mut [this_ptr], None, *pos, 0)? self.call_fn_raw(None, &make_getter(id), &mut args, None, *pos, 0)?
} }
// xxx.???[???][idx_expr] // xxx.???[???][idx_expr]
Expr::Index(_, _, _) => { Expr::Index(_, _, _) => {
@ -493,8 +650,8 @@ impl Engine<'_> {
Expr::Dot(dot_lhs, rhs, _) => match dot_lhs.as_ref() { Expr::Dot(dot_lhs, rhs, _) => match dot_lhs.as_ref() {
// xxx.id.rhs // xxx.id.rhs
Expr::Property(id, pos) => { Expr::Property(id, pos) => {
let this_ptr = target.get_mut(scope); let mut args = [target.get_mut(scope)];
self.call_fn_raw(&make_getter(id), &mut [this_ptr], None, *pos, 0) self.call_fn_raw(None, &make_getter(id), &mut args, None, *pos, 0)
.and_then(|mut val| { .and_then(|mut val| {
self.get_dot_val_helper(scope, Target::from(val.as_mut()), rhs, level) 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() { let val = match idx_lhs.as_ref() {
// xxx.id[idx_expr].rhs // xxx.id[idx_expr].rhs
Expr::Property(id, pos) => { Expr::Property(id, pos) => {
let this_ptr = target.get_mut(scope); let mut args = [target.get_mut(scope)];
self.call_fn_raw(&make_getter(id), &mut [this_ptr], None, *pos, 0)? self.call_fn_raw(None, &make_getter(id), &mut args, None, *pos, 0)?
} }
// xxx.???[???][idx_expr].rhs // xxx.???[???][idx_expr].rhs
Expr::Index(_, _, _) => { Expr::Index(_, _, _) => {
@ -628,9 +785,9 @@ impl Engine<'_> {
// val_array[idx] // val_array[idx]
if let Some(arr) = val.downcast_ref::<Array>() { if let Some(arr) = val.downcast_ref::<Array>() {
let idx = *self let idx = self
.eval_expr(scope, idx_expr, level)? .eval_expr(scope, idx_expr, level)?
.downcast::<INT>() .try_cast::<INT>()
.map_err(|_| EvalAltResult::ErrorNumericIndexExpr(idx_expr.position()))?; .map_err(|_| EvalAltResult::ErrorNumericIndexExpr(idx_expr.position()))?;
return if idx >= 0 { return if idx >= 0 {
@ -647,9 +804,9 @@ impl Engine<'_> {
{ {
// val_map[idx] // val_map[idx]
if let Some(map) = val.downcast_ref::<Map>() { if let Some(map) = val.downcast_ref::<Map>() {
let idx = *self let idx = self
.eval_expr(scope, idx_expr, level)? .eval_expr(scope, idx_expr, level)?
.downcast::<String>() .try_cast::<String>()
.map_err(|_| EvalAltResult::ErrorStringIndexExpr(idx_expr.position()))?; .map_err(|_| EvalAltResult::ErrorStringIndexExpr(idx_expr.position()))?;
return Ok(( return Ok((
@ -662,9 +819,9 @@ impl Engine<'_> {
// val_string[idx] // val_string[idx]
if let Some(s) = val.downcast_ref::<String>() { if let Some(s) = val.downcast_ref::<String>() {
let idx = *self let idx = self
.eval_expr(scope, idx_expr, level)? .eval_expr(scope, idx_expr, level)?
.downcast::<INT>() .try_cast::<INT>()
.map_err(|_| EvalAltResult::ErrorNumericIndexExpr(idx_expr.position()))?; .map_err(|_| EvalAltResult::ErrorNumericIndexExpr(idx_expr.position()))?;
return if idx >= 0 { return if idx >= 0 {
@ -795,9 +952,9 @@ impl Engine<'_> {
let s = scope.get_mut_by_type::<String>(src); let s = scope.get_mut_by_type::<String>(src);
let pos = new_val.1; let pos = new_val.1;
// Value must be a character // Value must be a character
let ch = *new_val let ch = new_val
.0 .0
.downcast::<char>() .try_cast::<char>()
.map_err(|_| EvalAltResult::ErrorCharMismatch(pos))?; .map_err(|_| EvalAltResult::ErrorCharMismatch(pos))?;
Self::str_replace_char(s, idx.as_num(), ch); Self::str_replace_char(s, idx.as_num(), ch);
Ok(().into_dynamic()) Ok(().into_dynamic())
@ -830,8 +987,8 @@ impl Engine<'_> {
if let Some(s) = target.downcast_mut::<String>() { if let Some(s) = target.downcast_mut::<String>() {
// Value must be a character // Value must be a character
let ch = *new_val let ch = new_val
.downcast::<char>() .try_cast::<char>()
.map_err(|_| EvalAltResult::ErrorCharMismatch(pos))?; .map_err(|_| EvalAltResult::ErrorCharMismatch(pos))?;
Self::str_replace_char(s, idx.as_num(), ch); Self::str_replace_char(s, idx.as_num(), ch);
return Ok(target); return Ok(target);
@ -855,7 +1012,7 @@ impl Engine<'_> {
// xxx.id // xxx.id
Expr::Property(id, pos) => { Expr::Property(id, pos) => {
let mut args = [this_ptr, new_val.0.as_mut()]; 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] // xxx.lhs[idx_expr]
@ -864,7 +1021,7 @@ impl Engine<'_> {
Expr::Index(lhs, idx_expr, op_pos) => match lhs.as_ref() { Expr::Index(lhs, idx_expr, op_pos) => match lhs.as_ref() {
// xxx.id[idx_expr] // xxx.id[idx_expr]
Expr::Property(id, pos) => self 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| { .and_then(|val| {
let (_, _, idx) = let (_, _, idx) =
self.get_indexed_value(scope, &val, idx_expr, *op_pos, level)?; self.get_indexed_value(scope, &val, idx_expr, *op_pos, level)?;
@ -873,7 +1030,7 @@ impl Engine<'_> {
}) })
.and_then(|mut val| { .and_then(|mut val| {
let mut args = [this_ptr, val.as_mut()]; 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 // All others - syntax error for setters chain
@ -887,14 +1044,14 @@ impl Engine<'_> {
Expr::Dot(lhs, rhs, _) => match lhs.as_ref() { Expr::Dot(lhs, rhs, _) => match lhs.as_ref() {
// xxx.id.rhs // xxx.id.rhs
Expr::Property(id, pos) => { Expr::Property(id, pos) => {
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| { .and_then(|mut val| {
self.set_dot_val_helper(scope, val.as_mut(), rhs, new_val, level) self.set_dot_val_helper(scope, val.as_mut(), rhs, new_val, level)
.map(|_| val) // Discard Ok return value .map(|_| val) // Discard Ok return value
}) })
.and_then(|mut val| { .and_then(|mut val| {
let mut args = [this_ptr, val.as_mut()]; 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() { Expr::Index(lhs, idx_expr, op_pos) => match lhs.as_ref() {
// xxx.id[idx_expr].rhs // xxx.id[idx_expr].rhs
Expr::Property(id, pos) => { Expr::Property(id, pos) => {
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| { .and_then(|v| {
let (mut value, _, idx) = let (mut value, _, idx) =
self.get_indexed_value(scope, &v, idx_expr, *op_pos, level)?; 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) Self::update_indexed_value(v, idx, value, val_pos)
}) })
.and_then(|mut v| { .and_then(|mut v| {
self.call_fn_raw( let mut args = [this_ptr, v.as_mut()];
&make_setter(id), self.call_fn_raw(None, &make_setter(id), &mut args, None, *pos, 0)
&mut [this_ptr, v.as_mut()],
None,
*pos,
0,
)
}) })
} }
@ -1076,6 +1228,7 @@ impl Engine<'_> {
*scope.get_mut(entry) = rhs_val.clone(); *scope.get_mut(entry) = rhs_val.clone();
Ok(rhs_val) Ok(rhs_val)
} }
ScopeSource { ScopeSource {
typ: ScopeEntryType::Constant, typ: ScopeEntryType::Constant,
.. ..
@ -1166,12 +1319,13 @@ impl Engine<'_> {
Expr::FunctionCall(fn_name, args_expr_list, def_val, pos) => { Expr::FunctionCall(fn_name, args_expr_list, def_val, pos) => {
// Has a system function an override? // Has a system function an override?
fn has_override(engine: &Engine, name: &str) -> bool { fn has_override(engine: &Engine, name: &str) -> bool {
let spec = FnSpec { (engine.functions.is_some() && {
name: name.into(), engine.functions.as_ref().unwrap().contains_key(&FnSpec {
args: vec![TypeId::of::<String>()], 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() { match fn_name.as_str() {
@ -1192,7 +1346,8 @@ impl Engine<'_> {
.into_dynamic(); .into_dynamic();
// Redirect call to `print` // 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 // type_of
@ -1213,6 +1368,7 @@ impl Engine<'_> {
let pos = args_expr_list[0].position(); let pos = args_expr_list[0].position();
let r = self.eval_expr(scope, &args_expr_list[0], level)?; let r = self.eval_expr(scope, &args_expr_list[0], level)?;
// Get the script text by evaluating the expression
let script = let script =
r.downcast_ref::<String>() r.downcast_ref::<String>()
.map(String::as_str) .map(String::as_str)
@ -1223,6 +1379,7 @@ impl Engine<'_> {
) )
})?; })?;
// Compile the script text
#[cfg(not(feature = "no_optimize"))] #[cfg(not(feature = "no_optimize"))]
let ast = { let ast = {
let orig_optimization_level = self.optimization_level; let orig_optimization_level = self.optimization_level;
@ -1237,9 +1394,36 @@ impl Engine<'_> {
#[cfg(feature = "no_optimize")] #[cfg(feature = "no_optimize")]
let ast = self.compile(script).map_err(EvalAltResult::ErrorParsing)?; let ast = self.compile(script).map_err(EvalAltResult::ErrorParsing)?;
Ok(self // If new functions are defined, merge it into the current functions library
.eval_ast_with_scope_raw(scope, true, &ast) let merged = AST(
.map_err(|err| err.set_position(pos))?) 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 // Normal function call
@ -1252,38 +1436,40 @@ impl Engine<'_> {
let mut arg_values: Vec<_> = let mut arg_values: Vec<_> =
values.iter_mut().map(Dynamic::as_mut).collect(); 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( Expr::And(lhs, rhs) => Ok(Box::new(
*self self
.eval_expr(scope, &*lhs, level)? .eval_expr(scope, &*lhs, level)?
.downcast::<bool>() .try_cast::<bool>()
.map_err(|_| { .map_err(|_| {
EvalAltResult::ErrorBooleanArgMismatch("AND".into(), lhs.position()) EvalAltResult::ErrorBooleanArgMismatch("AND".into(), lhs.position())
})? })?
&& // Short-circuit using && && // Short-circuit using &&
*self self
.eval_expr(scope, &*rhs, level)? .eval_expr(scope, &*rhs, level)?
.downcast::<bool>() .try_cast::<bool>()
.map_err(|_| { .map_err(|_| {
EvalAltResult::ErrorBooleanArgMismatch("AND".into(), rhs.position()) EvalAltResult::ErrorBooleanArgMismatch("AND".into(), rhs.position())
})?, })?,
)), )),
Expr::Or(lhs, rhs) => Ok(Box::new( Expr::Or(lhs, rhs) => Ok(Box::new(
*self self
.eval_expr(scope, &*lhs, level)? .eval_expr(scope, &*lhs, level)?
.downcast::<bool>() .try_cast::<bool>()
.map_err(|_| { .map_err(|_| {
EvalAltResult::ErrorBooleanArgMismatch("OR".into(), lhs.position()) EvalAltResult::ErrorBooleanArgMismatch("OR".into(), lhs.position())
})? })?
|| // Short-circuit using || || // Short-circuit using ||
*self self
.eval_expr(scope, &*rhs, level)? .eval_expr(scope, &*rhs, level)?
.downcast::<bool>() .try_cast::<bool>()
.map_err(|_| { .map_err(|_| {
EvalAltResult::ErrorBooleanArgMismatch("OR".into(), rhs.position()) EvalAltResult::ErrorBooleanArgMismatch("OR".into(), rhs.position())
})?, })?,
@ -1334,10 +1520,10 @@ impl Engine<'_> {
// If-else statement // If-else statement
Stmt::IfThenElse(guard, if_body, else_body) => self Stmt::IfThenElse(guard, if_body, else_body) => self
.eval_expr(scope, guard, level)? .eval_expr(scope, guard, level)?
.downcast::<bool>() .try_cast::<bool>()
.map_err(|_| EvalAltResult::ErrorLogicGuard(guard.position())) .map_err(|_| EvalAltResult::ErrorLogicGuard(guard.position()))
.and_then(|guard_val| { .and_then(|guard_val| {
if *guard_val { if guard_val {
self.eval_stmt(scope, if_body, level) self.eval_stmt(scope, if_body, level)
} else if let Some(stmt) = else_body { } else if let Some(stmt) = else_body {
self.eval_stmt(scope, stmt.as_ref(), level) self.eval_stmt(scope, stmt.as_ref(), level)
@ -1348,20 +1534,13 @@ impl Engine<'_> {
// While loop // While loop
Stmt::While(guard, body) => loop { Stmt::While(guard, body) => loop {
match self.eval_expr(scope, guard, level)?.downcast::<bool>() { match self.eval_expr(scope, guard, level)?.try_cast::<bool>() {
Ok(guard_val) => { Ok(guard_val) if guard_val => match self.eval_stmt(scope, body, level) {
if *guard_val { Ok(_) | Err(EvalAltResult::ErrorLoopBreak(false, _)) => (),
match self.eval_stmt(scope, body, level) { Err(EvalAltResult::ErrorLoopBreak(true, _)) => return Ok(().into_dynamic()),
Ok(_) => (), Err(x) => return Err(x),
Err(EvalAltResult::ErrorLoopBreak(_)) => { },
return Ok(().into_dynamic()) Ok(_) => return Ok(().into_dynamic()),
}
Err(x) => return Err(x),
}
} else {
return Ok(().into_dynamic());
}
}
Err(_) => return Err(EvalAltResult::ErrorLogicGuard(guard.position())), Err(_) => return Err(EvalAltResult::ErrorLogicGuard(guard.position())),
} }
}, },
@ -1369,8 +1548,8 @@ impl Engine<'_> {
// Loop statement // Loop statement
Stmt::Loop(body) => loop { Stmt::Loop(body) => loop {
match self.eval_stmt(scope, body, level) { match self.eval_stmt(scope, body, level) {
Ok(_) => (), Ok(_) | Err(EvalAltResult::ErrorLoopBreak(false, _)) => (),
Err(EvalAltResult::ErrorLoopBreak(_)) => return Ok(().into_dynamic()), Err(EvalAltResult::ErrorLoopBreak(true, _)) => return Ok(().into_dynamic()),
Err(x) => return Err(x), Err(x) => return Err(x),
} }
}, },
@ -1380,34 +1559,43 @@ impl Engine<'_> {
let arr = self.eval_expr(scope, expr, level)?; let arr = self.eval_expr(scope, expr, level)?;
let tid = Any::type_id(&*arr); let tid = Any::type_id(&*arr);
if let Some(iter_fn) = self.type_iterators.get(&tid) { if let Some(type_iterators) = &self.type_iterators {
scope.push(name.clone(), ()); 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 { let entry = ScopeSource {
name, name,
index: scope.len() - 1, index: scope.len() - 1,
typ: ScopeEntryType::Normal, typ: ScopeEntryType::Normal,
}; };
for a in iter_fn(&arr) { for a in iter_fn(&arr) {
*scope.get_mut(entry) = a; *scope.get_mut(entry) = a;
match self.eval_stmt(scope, body, level) { match self.eval_stmt(scope, body, level) {
Ok(_) => (), Ok(_) | Err(EvalAltResult::ErrorLoopBreak(false, _)) => (),
Err(EvalAltResult::ErrorLoopBreak(_)) => break, Err(EvalAltResult::ErrorLoopBreak(true, _)) => break,
Err(x) => return Err(x), Err(x) => return Err(x),
}
} }
}
scope.rewind(scope.len() - 1); scope.rewind(scope.len() - 1);
Ok(().into_dynamic()) Ok(().into_dynamic())
} else {
Err(EvalAltResult::ErrorFor(expr.position()))
}
} else { } else {
Err(EvalAltResult::ErrorFor(expr.position())) Err(EvalAltResult::ErrorFor(expr.position()))
} }
} }
// Continue statement
Stmt::Continue(pos) => Err(EvalAltResult::ErrorLoopBreak(false, *pos)),
// Break statement // Break statement
Stmt::Break(pos) => Err(EvalAltResult::ErrorLoopBreak(*pos)), Stmt::Break(pos) => Err(EvalAltResult::ErrorLoopBreak(true, *pos)),
// Empty return // Empty return
Stmt::ReturnWithVal(None, ReturnType::Return, pos) => { Stmt::ReturnWithVal(None, ReturnType::Return, pos) => {
@ -1429,9 +1617,7 @@ impl Engine<'_> {
Stmt::ReturnWithVal(Some(a), ReturnType::Exception, pos) => { Stmt::ReturnWithVal(Some(a), ReturnType::Exception, pos) => {
let val = self.eval_expr(scope, a, level)?; let val = self.eval_expr(scope, a, level)?;
Err(EvalAltResult::ErrorRuntime( Err(EvalAltResult::ErrorRuntime(
val.downcast::<String>() val.try_cast::<String>().unwrap_or_else(|_| "".to_string()),
.map(|s| *s)
.unwrap_or_else(|_| "".to_string()),
*pos, *pos,
)) ))
} }
@ -1439,11 +1625,13 @@ impl Engine<'_> {
// Let statement // Let statement
Stmt::Let(name, Some(expr), _) => { Stmt::Let(name, Some(expr), _) => {
let val = self.eval_expr(scope, expr, level)?; 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); scope.push_dynamic_value(name.clone(), ScopeEntryType::Normal, val, false);
Ok(().into_dynamic()) Ok(().into_dynamic())
} }
Stmt::Let(name, None, _) => { Stmt::Let(name, None, _) => {
// TODO - avoid copying variable name in inner block?
scope.push(name.clone(), ()); scope.push(name.clone(), ());
Ok(().into_dynamic()) Ok(().into_dynamic())
} }
@ -1451,6 +1639,7 @@ impl Engine<'_> {
// Const statement // Const statement
Stmt::Const(name, expr, _) if expr.is_constant() => { Stmt::Const(name, expr, _) if expr.is_constant() => {
let val = self.eval_expr(scope, expr, level)?; 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); scope.push_dynamic_value(name.clone(), ScopeEntryType::Constant, val, true);
Ok(().into_dynamic()) Ok(().into_dynamic())
} }
@ -1461,15 +1650,21 @@ impl Engine<'_> {
/// Map a type_name into a pretty-print name /// Map a type_name into a pretty-print name
pub(crate) fn map_type_name<'a>(&'a self, name: &'a str) -> &'a str { pub(crate) fn map_type_name<'a>(&'a self, name: &'a str) -> &'a str {
self.type_names if self.type_names.is_none() {
.get(name) name
.map(String::as_str) } else {
.unwrap_or(name) self.type_names
.as_ref()
.unwrap()
.get(name)
.map(String::as_str)
.unwrap_or(name)
}
} }
/// Clean up all script-defined functions within the `Engine`. /// Clean up all script-defined functions within the `Engine`.
pub fn clear_functions(&mut self) { 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::MalformedEscapeSequence(s) => write!(f, "Invalid escape sequence: '{}'", s),
Self::MalformedNumber(s) => write!(f, "Invalid number: '{}'", s), Self::MalformedNumber(s) => write!(f, "Invalid number: '{}'", s),
Self::MalformedChar(s) => write!(f, "Invalid character: '{}'", s), Self::MalformedChar(s) => write!(f, "Invalid character: '{}'", s),
Self::MalformedIdentifier(s) => { Self::MalformedIdentifier(s) => write!(f, "Variable name is not proper: '{}'", s),
write!(f, "Variable name is not in a legal format: '{}'", s)
}
Self::UnterminatedString => write!(f, "Open string is not terminated"), Self::UnterminatedString => write!(f, "Open string is not terminated"),
} }
} }
@ -47,37 +45,51 @@ pub enum ParseErrorType {
UnexpectedEOF, UnexpectedEOF,
/// An unknown operator is encountered. Wrapped value is the operator. /// An unknown operator is encountered. Wrapped value is the operator.
UnknownOperator(String), UnknownOperator(String),
/// 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), 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), 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"))] #[cfg(not(feature = "no_index"))]
MalformedIndexExpr(String), 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"))] #[cfg(not(feature = "no_object"))]
DuplicatedProperty(String), DuplicatedProperty(String),
/// Invalid expression assigned to constant. /// Invalid expression assigned to constant. Wrapped value is the name of the constant.
ForbiddenConstantExpr(String), ForbiddenConstantExpr(String),
/// Missing a property name for custom types and maps. /// Missing a property name for custom types and maps.
PropertyExpected, PropertyExpected,
/// Missing a variable name after the `let`, `const` or `for` keywords. /// Missing a variable name after the `let`, `const` or `for` keywords.
VariableExpected, VariableExpected,
/// Missing an expression. /// Missing an expression. Wrapped value is the expression type.
ExprExpected(String), ExprExpected(String),
/// Defining a function `fn` in an appropriate place (e.g. inside another function). /// Defining a function `fn` in an appropriate place (e.g. inside another function).
///
/// Not available under the `no_function` feature.
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]
WrongFnDefinition, WrongFnDefinition,
/// Missing a function name after the `fn` keyword. /// Missing a function name after the `fn` keyword.
///
/// Not available under the `no_function` feature.
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]
FnMissingName, FnMissingName,
/// A function definition is missing the parameters list. Wrapped value is the function name. /// 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"))] #[cfg(not(feature = "no_function"))]
FnMissingParams(String), FnMissingParams(String),
/// A function definition has duplicated parameters. Wrapped values are the function name and parameter name. /// A function definition has duplicated parameters. Wrapped values are the function name and parameter name.
///
/// Not available under the `no_function` feature.
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]
FnDuplicatedParam(String, String), FnDuplicatedParam(String, String),
/// A function definition is missing the body. Wrapped value is the function name. /// A function definition is missing the body. Wrapped value is the function name.
///
/// Not available under the `no_function` feature.
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]
FnMissingBody(String), FnMissingBody(String),
/// Assignment to an inappropriate LHS (left-hand-side) expression. /// Assignment to an inappropriate LHS (left-hand-side) expression.
@ -118,8 +130,8 @@ impl ParseError {
} }
pub(crate) fn desc(&self) -> &str { pub(crate) fn desc(&self) -> &str {
match self.0 { match &self.0 {
ParseErrorType::BadInput(ref p) => p, ParseErrorType::BadInput(p) => p,
ParseErrorType::UnexpectedEOF => "Script is incomplete", ParseErrorType::UnexpectedEOF => "Script is incomplete",
ParseErrorType::UnknownOperator(_) => "Unknown operator", ParseErrorType::UnknownOperator(_) => "Unknown operator",
ParseErrorType::MissingToken(_, _) => "Expecting a certain token that is missing", ParseErrorType::MissingToken(_, _) => "Expecting a certain token that is missing",
@ -154,50 +166,48 @@ impl Error for ParseError {}
impl fmt::Display for ParseError { impl fmt::Display for ParseError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self.0 { match &self.0 {
ParseErrorType::BadInput(ref s) | ParseErrorType::MalformedCallExpr(ref s) => { ParseErrorType::BadInput(s) | ParseErrorType::MalformedCallExpr(s) => {
write!(f, "{}", if s.is_empty() { self.desc() } else { 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)? 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"))] #[cfg(not(feature = "no_index"))]
ParseErrorType::MalformedIndexExpr(ref s) => { ParseErrorType::MalformedIndexExpr(s) => {
write!(f, "{}", if s.is_empty() { self.desc() } else { s })? write!(f, "{}", if s.is_empty() { self.desc() } else { s })?
} }
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
ParseErrorType::DuplicatedProperty(ref s) => { ParseErrorType::DuplicatedProperty(s) => {
write!(f, "Duplicated property '{}' for object map literal", 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"))] #[cfg(not(feature = "no_function"))]
ParseErrorType::FnMissingParams(ref s) => { ParseErrorType::FnMissingParams(s) => {
write!(f, "Expecting parameters for function '{}'", s)? write!(f, "Expecting parameters for function '{}'", s)?
} }
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]
ParseErrorType::FnMissingBody(ref s) => { ParseErrorType::FnMissingBody(s) => {
write!(f, "Expecting body statement block for function '{}'", s)? write!(f, "Expecting body statement block for function '{}'", s)?
} }
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]
ParseErrorType::FnDuplicatedParam(ref s, ref arg) => { ParseErrorType::FnDuplicatedParam(s, arg) => {
write!(f, "Duplicated parameter '{}' for function '{}'", arg, s)? write!(f, "Duplicated parameter '{}' for function '{}'", arg, s)?
} }
ParseErrorType::MissingToken(ref token, ref s) => { ParseErrorType::MissingToken(token, s) => write!(f, "Expecting '{}' {}", 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())? write!(f, "{}", self.desc())?
} }
ParseErrorType::AssignmentToConstant(ref s) => { ParseErrorType::AssignmentToConstant(s) => {
write!(f, "Cannot assign to constant '{}'", s)? write!(f, "Cannot assign to constant '{}'", s)?
} }
_ => write!(f, "{}", self.desc())?, _ => write!(f, "{}", self.desc())?,

View File

@ -138,7 +138,13 @@ macro_rules! def_register {
// ^ dereferencing function // ^ dereferencing function
impl< impl<
$($par: Any + Clone,)* $($par: Any + Clone,)*
#[cfg(feature = "sync")]
FN: Fn($($param),*) -> RET + Send + Sync + 'static,
#[cfg(not(feature = "sync"))]
FN: Fn($($param),*) -> RET + 'static, FN: Fn($($param),*) -> RET + 'static,
RET: Any RET: Any
> RegisterFn<FN, ($($mark,)*), RET> for Engine<'_> > RegisterFn<FN, ($($mark,)*), RET> for Engine<'_>
{ {
@ -171,6 +177,11 @@ macro_rules! def_register {
impl< impl<
$($par: Any + Clone,)* $($par: Any + Clone,)*
#[cfg(feature = "sync")]
FN: Fn($($param),*) -> Dynamic + Send + Sync + 'static,
#[cfg(not(feature = "sync"))]
FN: Fn($($param),*) -> Dynamic + 'static, FN: Fn($($param),*) -> Dynamic + 'static,
> RegisterDynamicFn<FN, ($($mark,)*)> for Engine<'_> > RegisterDynamicFn<FN, ($($mark,)*)> for Engine<'_>
{ {
@ -202,7 +213,12 @@ macro_rules! def_register {
impl< impl<
$($par: Any + Clone,)* $($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, FN: Fn($($param),*) -> Result<RET, EvalAltResult> + 'static,
RET: Any RET: Any
> RegisterResultFn<FN, ($($mark,)*), RET> for Engine<'_> > 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)] #![cfg_attr(feature = "no_std", no_std)]

View File

@ -2,13 +2,15 @@
use crate::any::{Any, Dynamic}; use crate::any::{Any, Dynamic};
use crate::engine::{ 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::parser::{map_dynamic_to_expr, Expr, FnDef, ReturnType, Stmt, AST};
use crate::scope::{Entry as ScopeEntry, EntryType as ScopeEntryType, Scope}; use crate::scope::{Entry as ScopeEntry, EntryType as ScopeEntryType, Scope};
use crate::stdlib::{ use crate::stdlib::{
boxed::Box, boxed::Box,
rc::Rc,
string::{String, ToString}, string::{String, ToString},
sync::Arc, sync::Arc,
vec, vec,
@ -16,6 +18,8 @@ use crate::stdlib::{
}; };
/// Level of optimization performed. /// Level of optimization performed.
///
/// Not available under the `no_optimize` feature.
#[derive(Debug, Eq, PartialEq, Hash, Clone, Copy)] #[derive(Debug, Eq, PartialEq, Hash, Clone, Copy)]
pub enum OptimizationLevel { pub enum OptimizationLevel {
/// No optimization performed. /// No optimization performed.
@ -449,9 +453,11 @@ fn optimize_expr<'a>(expr: Expr, state: &mut State<'a>) -> Expr {
&& args.iter().all(|expr| expr.is_constant()) // all arguments are constants && args.iter().all(|expr| expr.is_constant()) // all arguments are constants
=> { => {
// First search in script-defined functions (can override built-in) // 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 {
// A script-defined function overrides the built-in function - do not make the call if fn_lib_arc.has_function(&id, args.len()) {
return Expr::FunctionCall(id, args.into_iter().map(|a| optimize_expr(a, state)).collect(), def_value, pos); // 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 arg_values: Vec<_> = args.iter().map(Expr::get_constant_value).collect();
@ -474,7 +480,7 @@ fn optimize_expr<'a>(expr: Expr, state: &mut State<'a>) -> Expr {
// Otherwise use the default value, if any // Otherwise use the default value, if any
def_value.clone() def_value.clone()
} }
}).and_then(|result| map_dynamic_to_expr(result, pos).0) }).and_then(|result| map_dynamic_to_expr(result, pos))
.map(|expr| { .map(|expr| {
state.set_dirty(); state.set_dirty();
expr 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), Expr::FunctionCall(id, args.into_iter().map(|a| optimize_expr(a, state)).collect(), def_value, pos),
// constant-name // constant-name
Expr::Variable(ref name, _) if state.contains_constant(name) => { Expr::Variable(name, _) if state.contains_constant(&name) => {
state.set_dirty(); state.set_dirty();
// Replace constant with value // 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 // All other expressions - skip
@ -580,15 +586,10 @@ pub fn optimize_into_ast(
statements: Vec<Stmt>, statements: Vec<Stmt>,
functions: Vec<FnDef>, functions: Vec<FnDef>,
) -> AST { ) -> AST {
AST( let fn_lib = FunctionsLib::from_vec(
match engine.optimization_level {
OptimizationLevel::None => statements,
OptimizationLevel::Simple | OptimizationLevel::Full => {
optimize(statements, engine, &scope)
}
},
functions functions
.into_iter() .iter()
.cloned()
.map(|mut fn_def| { .map(|mut fn_def| {
if engine.optimization_level != OptimizationLevel::None { if engine.optimization_level != OptimizationLevel::None {
let pos = fn_def.body.position(); let pos = fn_def.body.position();
@ -608,9 +609,21 @@ pub fn optimize_into_ast(
stmt => stmt, stmt => stmt,
}; };
} }
fn_def
Arc::new(fn_def)
}) })
.collect(), .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. //! Main module defining the lexer and parser.
use crate::any::{Any, AnyExt, Dynamic}; use crate::any::{Any, AnyExt, Dynamic};
use crate::engine::Engine; use crate::engine::{Engine, FunctionsLib};
use crate::error::{LexError, ParseError, ParseErrorType}; use crate::error::{LexError, ParseError, ParseErrorType};
use crate::scope::{EntryType as ScopeEntryType, Scope}; use crate::scope::{EntryType as ScopeEntryType, Scope};
@ -16,6 +16,7 @@ use crate::stdlib::{
fmt, format, fmt, format,
iter::Peekable, iter::Peekable,
ops::Add, ops::Add,
rc::Rc,
str::Chars, str::Chars,
str::FromStr, str::FromStr,
string::{String, ToString}, string::{String, ToString},
@ -37,6 +38,8 @@ pub type INT = i64;
pub type INT = i32; pub type INT = i32;
/// The system floating-point type. /// The system floating-point type.
///
/// Not available under the `no_float` feature.
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
pub type FLOAT = f64; pub type FLOAT = f64;
@ -160,11 +163,22 @@ impl fmt::Debug for Position {
} }
/// Compiled AST (abstract syntax tree) of a Rhai script. /// 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)] #[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 { 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. /// is returned.
/// ///
/// The second `AST` is simply appended to the end of the first _without any processing_. /// 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 ast1 = engine.compile(r#"fn foo(x) { 42 + x } foo(1)"#)?;
/// let ast2 = engine.compile(r#"fn foo(n) { "hello" + n } foo("!")"#)?; /// 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: /// // Notice that using the '+' operator also works:
/// // let ast = ast1 + ast2; /// // let ast = &ast1 + &ast2;
/// ///
/// // 'ast' is essentially: /// // 'ast' is essentially:
/// // /// //
@ -206,29 +220,62 @@ impl AST {
/// # Ok(()) /// # Ok(())
/// # } /// # }
/// ``` /// ```
pub fn merge(self, mut other: Self) -> Self { pub fn merge(&self, other: &Self) -> Self {
let Self(mut ast, mut functions) = self; let Self(statements, functions) = self;
ast.append(&mut other.0); let ast = match (statements.is_empty(), other.0.is_empty()) {
(false, false) => {
for fn_def in other.1 { let mut statements = statements.clone();
if let Some((n, _)) = functions statements.extend(other.0.iter().cloned());
.iter() statements
.enumerate()
.find(|(_, f)| f.name == fn_def.name && f.params.len() == fn_def.params.len())
{
functions[n] = fn_def;
} else {
functions.push(fn_def);
} }
} (false, true) => statements.clone(),
(true, false) => other.0.clone(),
(true, true) => vec![],
};
Self(ast, functions) #[cfg(feature = "sync")]
{
Self(ast, Arc::new(functions.merge(other.1.as_ref())))
}
#[cfg(not(feature = "sync"))]
{
Self(ast, Rc::new(functions.merge(other.1.as_ref())))
}
}
/// Clear all function definitions in the `AST`.
pub fn clear_functions(&mut self) {
#[cfg(feature = "sync")]
{
self.1 = Arc::new(FunctionsLib::new());
}
#[cfg(not(feature = "sync"))]
{
self.1 = Rc::new(FunctionsLib::new());
}
}
/// Clear all statements in the `AST`, leaving only function definitions.
pub fn retain_functions(&mut self) {
self.0 = vec![];
} }
} }
impl Add<Self> for AST { impl Default for AST {
type Output = Self; 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 { fn add(self, rhs: Self) -> Self::Output {
self.merge(rhs) self.merge(rhs)
@ -278,6 +325,8 @@ pub enum Stmt {
Block(Vec<Stmt>, Position), Block(Vec<Stmt>, Position),
/// { stmt } /// { stmt }
Expr(Box<Expr>), Expr(Box<Expr>),
/// continue
Continue(Position),
/// break /// break
Break(Position), Break(Position),
/// `return`/`throw` /// `return`/`throw`
@ -292,6 +341,7 @@ impl Stmt {
| Stmt::Let(_, _, pos) | Stmt::Let(_, _, pos)
| Stmt::Const(_, _, pos) | Stmt::Const(_, _, pos)
| Stmt::Block(_, pos) | Stmt::Block(_, pos)
| Stmt::Continue(pos)
| Stmt::Break(pos) | Stmt::Break(pos)
| Stmt::ReturnWithVal(_, _, pos) => *pos, | Stmt::ReturnWithVal(_, _, pos) => *pos,
Stmt::IfThenElse(expr, _, _) | Stmt::Expr(expr) => expr.position(), Stmt::IfThenElse(expr, _, _) | Stmt::Expr(expr) => expr.position(),
@ -314,6 +364,7 @@ impl Stmt {
Stmt::Let(_, _, _) Stmt::Let(_, _, _)
| Stmt::Const(_, _, _) | Stmt::Const(_, _, _)
| Stmt::Expr(_) | Stmt::Expr(_)
| Stmt::Continue(_)
| Stmt::Break(_) | Stmt::Break(_)
| Stmt::ReturnWithVal(_, _, _) => false, | Stmt::ReturnWithVal(_, _, _) => false,
} }
@ -334,7 +385,7 @@ impl Stmt {
Stmt::For(_, range, block) => range.is_pure() && block.is_pure(), Stmt::For(_, range, block) => range.is_pure() && block.is_pure(),
Stmt::Let(_, _, _) | Stmt::Const(_, _, _) => false, Stmt::Let(_, _, _) | Stmt::Const(_, _, _) => false,
Stmt::Block(statements, _) => statements.iter().all(Stmt::is_pure), 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, And,
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]
Fn, Fn,
Continue,
Break, Break,
Return, Return,
Throw, Throw,
@ -653,6 +705,7 @@ impl Token {
And => "&&", And => "&&",
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]
Fn => "fn", Fn => "fn",
Continue => "continue",
Break => "break", Break => "break",
Return => "return", Return => "return",
Throw => "throw", Throw => "throw",
@ -1100,6 +1153,7 @@ impl<'a> TokenIterator<'a> {
"else" => Token::Else, "else" => Token::Else,
"while" => Token::While, "while" => Token::While,
"loop" => Token::Loop, "loop" => Token::Loop,
"continue" => Token::Continue,
"break" => Token::Break, "break" => Token::Break,
"return" => Token::Return, "return" => Token::Return,
"throw" => Token::Throw, "throw" => Token::Throw,
@ -1679,8 +1733,8 @@ fn parse_map_literal<'a>(
PERR::MissingToken("}".into(), "to end this object map literal".into()) PERR::MissingToken("}".into(), "to end this object map literal".into())
.into_err_eof() .into_err_eof()
})? { })? {
(Token::Identifier(s), pos) => (s.clone(), pos), (Token::Identifier(s), pos) => (s, pos),
(Token::StringConst(s), pos) => (s.clone(), pos), (Token::StringConst(s), pos) => (s, pos),
(_, pos) if map.is_empty() => { (_, pos) if map.is_empty() => {
return Err(PERR::MissingToken( return Err(PERR::MissingToken(
"}".into(), "}".into(),
@ -2007,7 +2061,7 @@ fn parse_binary_op<'a>(
let mut current_lhs = lhs; let mut current_lhs = lhs;
loop { 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()) (current_op.precedence(), current_op.is_bind_right())
} else { } else {
(0, false) (0, false)
@ -2419,6 +2473,8 @@ fn parse_stmt<'a>(
// Semicolon - empty statement // Semicolon - empty statement
(Token::SemiColon, pos) => Ok(Stmt::Noop(*pos)), (Token::SemiColon, pos) => Ok(Stmt::Noop(*pos)),
(Token::LeftBrace, _) => parse_block(input, breakable, allow_stmt_expr),
// fn ... // fn ...
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]
(Token::Fn, pos) => Err(PERR::WrongFnDefinition.into_err(*pos)), (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::While, _) => parse_while(input, allow_stmt_expr),
(Token::Loop, _) => parse_loop(input, allow_stmt_expr), (Token::Loop, _) => parse_loop(input, allow_stmt_expr),
(Token::For, _) => parse_for(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 => { (Token::Break, pos) if breakable => {
let pos = *pos; let pos = *pos;
input.next(); input.next();
Ok(Stmt::Break(pos)) 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) => { (token @ Token::Return, pos) | (token @ Token::Throw, pos) => {
let return_type = match token { let return_type = match token {
Token::Return => ReturnType::Return, 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::Let, _) => parse_let(input, ScopeEntryType::Normal, allow_stmt_expr),
(Token::Const, _) => parse_let(input, ScopeEntryType::Constant, allow_stmt_expr), (Token::Const, _) => parse_let(input, ScopeEntryType::Constant, allow_stmt_expr),
_ => parse_expr_stmt(input, 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` // Do not optimize AST if `no_optimize`
#[cfg(feature = "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` // Do not optimize AST if `no_optimize`
#[cfg(feature = "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. /// Map a `Dynamic` value to an expression.
/// ///
/// Returns Some(expression) if conversion is successful. Otherwise None. /// 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>() { if value.is::<INT>() {
let value2 = value.clone(); Some(Expr::IntegerConstant(value.cast(), pos))
(
Some(Expr::IntegerConstant(
*value.downcast::<INT>().expect("value should be INT"),
pos,
)),
value2,
)
} else if value.is::<char>() { } else if value.is::<char>() {
let value2 = value.clone(); Some(Expr::CharConstant(value.cast(), pos))
(
Some(Expr::CharConstant(
*value.downcast::<char>().expect("value should be char"),
pos,
)),
value2,
)
} else if value.is::<String>() { } else if value.is::<String>() {
let value2 = value.clone(); Some(Expr::StringConstant(value.cast(), pos))
(
Some(Expr::StringConstant(
*value.downcast::<String>().expect("value should be String"),
pos,
)),
value2,
)
} else if value.is::<bool>() { } else if value.is::<bool>() {
let value2 = value.clone(); Some(if value.cast::<bool>() {
( Expr::True(pos)
Some( } else {
if *value.downcast::<bool>().expect("value should be bool") { Expr::False(pos)
Expr::True(pos) })
} else {
Expr::False(pos)
},
),
value2,
)
} else { } else {
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
{ {
if value.is::<FLOAT>() { if value.is::<FLOAT>() {
let value2 = value.clone(); return Some(Expr::FloatConstant(value.cast(), pos));
return (
Some(Expr::FloatConstant(
*value.downcast::<FLOAT>().expect("value should be FLOAT"),
pos,
)),
value2,
);
} }
} }
(None, value) None
} }
} }

View File

@ -22,6 +22,8 @@ pub enum EvalAltResult {
ErrorParsing(ParseError), ErrorParsing(ParseError),
/// Error reading from a script file. Wrapped value is the path of the script file. /// 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"))] #[cfg(not(feature = "no_std"))]
ErrorReadingScriptFile(PathBuf, std::io::Error), ErrorReadingScriptFile(PathBuf, std::io::Error),
@ -70,7 +72,9 @@ pub enum EvalAltResult {
ErrorRuntime(String, Position), ErrorRuntime(String, Position),
/// Breaking out of loops - not an error if within a loop. /// Breaking out of loops - not an error if within a loop.
ErrorLoopBreak(Position), /// 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. /// Not an error: Value returned from a script via the `return` keyword.
/// Wrapped value is the result value. /// Wrapped value is the result value.
Return(Dynamic, Position), Return(Dynamic, Position),
@ -118,7 +122,8 @@ impl EvalAltResult {
Self::ErrorArithmetic(_, _) => "Arithmetic error", Self::ErrorArithmetic(_, _) => "Arithmetic error",
Self::ErrorStackOverflow(_) => "Stack overflow", Self::ErrorStackOverflow(_) => "Stack overflow",
Self::ErrorRuntime(_, _) => "Runtime error", 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", 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::ErrorMismatchOutputType(s, pos) => write!(f, "{}: {} ({})", desc, s, pos),
Self::ErrorArithmetic(s, pos) => write!(f, "{} ({})", 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::Return(_, pos) => write!(f, "{} ({})", desc, pos),
Self::ErrorFunctionArgsMismatch(fn_name, 0, n, pos) => write!( Self::ErrorFunctionArgsMismatch(fn_name, 0, n, pos) => write!(
@ -255,7 +260,7 @@ impl EvalAltResult {
| Self::ErrorArithmetic(_, pos) | Self::ErrorArithmetic(_, pos)
| Self::ErrorStackOverflow(pos) | Self::ErrorStackOverflow(pos)
| Self::ErrorRuntime(_, pos) | Self::ErrorRuntime(_, pos)
| Self::ErrorLoopBreak(pos) | Self::ErrorLoopBreak(_, pos)
| Self::Return(_, pos) => *pos, | Self::Return(_, pos) => *pos,
} }
} }
@ -263,32 +268,32 @@ impl EvalAltResult {
/// Consume the current `EvalAltResult` and return a new one /// Consume the current `EvalAltResult` and return a new one
/// with the specified `Position`. /// with the specified `Position`.
pub(crate) fn set_position(mut self, new_position: Position) -> Self { pub(crate) fn set_position(mut self, new_position: Position) -> Self {
match self { match &mut self {
#[cfg(not(feature = "no_std"))] #[cfg(not(feature = "no_std"))]
Self::ErrorReadingScriptFile(_, _) => (), Self::ErrorReadingScriptFile(_, _) => (),
Self::ErrorParsing(ParseError(_, ref mut pos)) Self::ErrorParsing(ParseError(_, pos))
| Self::ErrorFunctionNotFound(_, ref mut pos) | Self::ErrorFunctionNotFound(_, pos)
| Self::ErrorFunctionArgsMismatch(_, _, _, ref mut pos) | Self::ErrorFunctionArgsMismatch(_, _, _, pos)
| Self::ErrorBooleanArgMismatch(_, ref mut pos) | Self::ErrorBooleanArgMismatch(_, pos)
| Self::ErrorCharMismatch(ref mut pos) | Self::ErrorCharMismatch(pos)
| Self::ErrorArrayBounds(_, _, ref mut pos) | Self::ErrorArrayBounds(_, _, pos)
| Self::ErrorStringBounds(_, _, ref mut pos) | Self::ErrorStringBounds(_, _, pos)
| Self::ErrorIndexingType(_, ref mut pos) | Self::ErrorIndexingType(_, pos)
| Self::ErrorNumericIndexExpr(ref mut pos) | Self::ErrorNumericIndexExpr(pos)
| Self::ErrorStringIndexExpr(ref mut pos) | Self::ErrorStringIndexExpr(pos)
| Self::ErrorLogicGuard(ref mut pos) | Self::ErrorLogicGuard(pos)
| Self::ErrorFor(ref mut pos) | Self::ErrorFor(pos)
| Self::ErrorVariableNotFound(_, ref mut pos) | Self::ErrorVariableNotFound(_, pos)
| Self::ErrorAssignmentToUnknownLHS(ref mut pos) | Self::ErrorAssignmentToUnknownLHS(pos)
| Self::ErrorAssignmentToConstant(_, ref mut pos) | Self::ErrorAssignmentToConstant(_, pos)
| Self::ErrorMismatchOutputType(_, ref mut pos) | Self::ErrorMismatchOutputType(_, pos)
| Self::ErrorDotExpr(_, ref mut pos) | Self::ErrorDotExpr(_, pos)
| Self::ErrorArithmetic(_, ref mut pos) | Self::ErrorArithmetic(_, pos)
| Self::ErrorStackOverflow(ref mut pos) | Self::ErrorStackOverflow(pos)
| Self::ErrorRuntime(_, ref mut pos) | Self::ErrorRuntime(_, pos)
| Self::ErrorLoopBreak(ref mut pos) | Self::ErrorLoopBreak(_, pos)
| Self::Return(_, ref mut pos) => *pos = new_position, | Self::Return(_, pos) => *pos = new_position,
} }
self self

View File

@ -52,44 +52,127 @@ pub(crate) struct EntryRef<'a> {
/// let mut engine = Engine::new(); /// let mut engine = Engine::new();
/// let mut my_scope = Scope::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(()) /// # Ok(())
/// # } /// # }
/// ``` /// ```
/// ///
/// When searching for entries, newly-added entries are found before similarly-named but older entries, /// When searching for entries, newly-added entries are found before similarly-named but older entries,
/// allowing for automatic _shadowing_. /// 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>>); pub struct Scope<'a>(Vec<Entry<'a>>);
impl<'a> Scope<'a> { impl<'a> Scope<'a> {
/// Create a new Scope. /// 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 { pub fn new() -> Self {
Self(Vec::new()) Self(Vec::new())
} }
/// Empty the Scope. /// 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) { pub fn clear(&mut self) {
self.0.clear(); self.0.clear();
} }
/// Get the number of entries inside the Scope. /// 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 { pub fn len(&self) -> usize {
self.0.len() self.0.len()
} }
/// Is the Scope empty? /// 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 { pub fn is_empty(&self) -> bool {
self.0.len() == 0 self.0.len() == 0
} }
/// Add (push) a new entry to the Scope. /// 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) { 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); self.push_dynamic_value(name, EntryType::Normal, value.into_dynamic(), false);
} }
/// Add (push) a new `Dynamic` entry to the Scope. /// 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) { pub fn push_dynamic<K: Into<Cow<'a, str>>>(&mut self, name: K, value: Dynamic) {
self.push_dynamic_value(name, EntryType::Normal, value, false); 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 are immutable and cannot be assigned to. Their values never change.
/// Constants propagation is a technique used to optimize an AST. /// 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: /// 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`. /// `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) { 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); 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 are immutable and cannot be assigned to. Their values never change.
/// Constants propagation is a technique used to optimize an AST. /// 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 /// However, in order to be used for optimization, the `Dynamic` value must be in one of the
/// recognized types: /// recognized types:
/// `INT` (default to `i64`, `i32` if `only_i32`), `f64`, `String`, `char` and `bool`. /// `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) { 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); self.push_dynamic_value(name, EntryType::Constant, value, true);
} }
@ -123,71 +230,163 @@ impl<'a> Scope<'a> {
value: Dynamic, value: Dynamic,
map_expr: bool, map_expr: bool,
) { ) {
let (expr, value) = if map_expr {
map_dynamic_to_expr(value, Position::none())
} else {
(None, value)
};
self.0.push(Entry { self.0.push(Entry {
name: name.into(), name: name.into(),
typ: entry_type, typ: entry_type,
value, value: value.clone(),
expr, expr: if map_expr {
map_dynamic_to_expr(value, Position::none())
} else {
None
},
}); });
} }
/// Truncate (rewind) the Scope to a previous size. /// 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) { pub fn rewind(&mut self, size: usize) {
self.0.truncate(size); self.0.truncate(size);
} }
/// Does the scope contain the entry? /// 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 self.0
.iter() .iter()
.enumerate()
.rev() // Always search a Scope in reverse order .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. /// 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 self.0
.iter() .iter()
.enumerate() .enumerate()
.rev() // Always search a Scope in reverse order .rev() // Always search a Scope in reverse order
.find(|(_, Entry { name, .. })| name == key) .find_map(
.map(
|( |(
i, index,
Entry { Entry {
name, typ, value, .. name: key,
typ,
value,
..
}, },
)| { )| {
( if name == key {
EntryRef { Some((
name: name.as_ref(), EntryRef {
index: i, name: key,
typ: *typ, index,
}, typ: *typ,
value.clone(), },
) value.clone(),
))
} else {
None
}
}, },
) )
} }
/// Get the value of an entry in the Scope, starting from the last. /// 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 self.0
.iter() .iter()
.enumerate() .rev()
.rev() // Always search a Scope in reverse order .find(|Entry { name: key, .. }| name == key)
.find(|(_, Entry { name, .. })| name == key) .and_then(|Entry { value, .. }| value.downcast_ref::<T>())
.and_then(|(_, Entry { value, .. })| value.downcast_ref::<T>())
.map(T::clone) .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. /// Get a mutable reference to an entry in the Scope.
pub(crate) fn get_mut(&mut self, key: EntryRef) -> &mut Dynamic { pub(crate) fn get_mut(&mut self, key: EntryRef) -> &mut Dynamic {
let entry = self.0.get_mut(key.index).expect("invalid index in Scope"); let entry = self.0.get_mut(key.index).expect("invalid index in Scope");

View File

@ -1,5 +1,5 @@
#![cfg(not(feature = "no_index"))] #![cfg(not(feature = "no_index"))]
use rhai::{Engine, EvalAltResult, RegisterFn, INT}; use rhai::{Array, Engine, EvalAltResult, RegisterFn, INT};
#[test] #[test]
fn test_arrays() -> Result<(), EvalAltResult> { fn test_arrays() -> Result<(), EvalAltResult> {
@ -12,6 +12,43 @@ fn test_arrays() -> Result<(), EvalAltResult> {
'3' '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(()) Ok(())
} }

View File

@ -1,5 +1,5 @@
#![cfg(not(feature = "no_function"))] #![cfg(not(feature = "no_function"))]
use rhai::{Engine, EvalAltResult, ParseErrorType, INT}; use rhai::{Engine, EvalAltResult, ParseErrorType, Scope, INT};
#[test] #[test]
fn test_fn() -> Result<(), EvalAltResult> { fn test_fn() -> Result<(), EvalAltResult> {
@ -21,24 +21,41 @@ fn test_fn() -> Result<(), EvalAltResult> {
#[test] #[test]
fn test_call_fn() -> Result<(), EvalAltResult> { fn test_call_fn() -> Result<(), EvalAltResult> {
let mut engine = Engine::new(); let mut engine = Engine::new();
let mut scope = Scope::new();
engine.consume( scope.push("foo", 42 as INT);
true,
let ast = engine.compile(
r" r"
fn hello(x, y) { fn hello(x, y) {
x + y x + y
} }
fn hello(x) { 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); assert_eq!(r, 165);
let r: i64 = engine.call_fn("hello", 123 as INT)?; let r: i64 = engine.call_fn1(&mut scope, &ast, "hello", 123 as INT)?;
assert_eq!(r, 246); 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(()) Ok(())
} }

View File

@ -1,8 +1,8 @@
#![cfg(not(feature = "no_index"))]
use rhai::{Engine, EvalAltResult, INT}; use rhai::{Engine, EvalAltResult, INT};
#[cfg(not(feature = "no_index"))]
#[test] #[test]
fn test_for() -> Result<(), EvalAltResult> { fn test_for_array() -> Result<(), EvalAltResult> {
let mut engine = Engine::new(); let mut engine = Engine::new();
let script = r" let script = r"
@ -18,10 +18,39 @@ fn test_for() -> Result<(), EvalAltResult> {
sum2 += x; sum2 += x;
} }
for x in range(1, 6, 3) {
sum2 += x;
}
sum1 + sum2 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(()) Ok(())
} }

View File

@ -12,8 +12,9 @@ fn test_loop() -> Result<(), EvalAltResult> {
loop { loop {
if i < 10 { if i < 10 {
i += 1;
if x > 20 { continue; }
x = x + i; x = x + i;
i = i + 1;
} else { } else {
break; break;
} }
@ -22,7 +23,7 @@ fn test_loop() -> Result<(), EvalAltResult> {
return x; return x;
" "
)?, )?,
45 21
); );
Ok(()) Ok(())

View File

@ -7,29 +7,65 @@ fn test_map_indexing() -> Result<(), EvalAltResult> {
let mut engine = Engine::new(); let mut engine = Engine::new();
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
assert_eq!( {
engine.eval::<INT>(r#"let x = #{a: 1, b: 2, c: 3}; x["b"]"#)?, assert_eq!(
2 engine.eval::<INT>(r#"let x = #{a: 1, b: 2, c: 3}; x["b"]"#)?,
); 2
);
assert_eq!(
engine.eval::<char>(
r#"
let y = #{d: 1, "e": #{a: 42, b: 88, "": "hello"}, " 123 xyz": 9};
y.e[""][4]
"#
)?,
'o'
);
}
assert_eq!( assert_eq!(
engine.eval::<INT>("let y = #{a: 1, b: 2, c: 3}; y.a = 5; y.a")?, engine.eval::<INT>("let y = #{a: 1, b: 2, c: 3}; y.a = 5; y.a")?,
5 5
); );
#[cfg(not(feature = "no_index"))]
assert_eq!(
engine.eval::<char>(
r#"
let y = #{d: 1, "e": #{a: 42, b: 88, "": "hello"}, " 123 xyz": 9};
y.e[""][4]
"#
)?,
'o'
);
engine.eval::<()>("let y = #{a: 1, b: 2, c: 3}; y.z")?; 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(()) Ok(())
} }
@ -37,14 +73,14 @@ fn test_map_indexing() -> Result<(), EvalAltResult> {
fn test_map_assign() -> Result<(), EvalAltResult> { fn test_map_assign() -> Result<(), EvalAltResult> {
let mut engine = Engine::new(); let mut engine = Engine::new();
let x = engine.eval::<Map>(r#"let x = #{a: 1, b: true, "c#": "hello"}; x"#)?; let x = engine.eval::<Map>(r#"let x = #{a: 1, b: true, "c$": "hello"}; x"#)?;
let a = x.get("a").cloned().unwrap(); let a = x.get("a").cloned().expect("should have property a");
let b = x.get("b").cloned().unwrap(); let b = x.get("b").cloned().expect("should have property b");
let c = x.get("c#").cloned().unwrap(); let c = x.get("c$").cloned().expect("should have property c$");
assert_eq!(*a.downcast::<INT>().unwrap(), 1); assert_eq!(a.cast::<INT>(), 1);
assert_eq!(*b.downcast::<bool>().unwrap(), true); assert_eq!(b.cast::<bool>(), true);
assert_eq!(*c.downcast::<String>().unwrap(), "hello"); assert_eq!(c.cast::<String>(), "hello");
Ok(()) Ok(())
} }
@ -53,14 +89,37 @@ fn test_map_assign() -> Result<(), EvalAltResult> {
fn test_map_return() -> Result<(), EvalAltResult> { fn test_map_return() -> Result<(), EvalAltResult> {
let mut engine = Engine::new(); let mut engine = Engine::new();
let x = engine.eval::<Map>(r#"#{a: 1, b: true, c: "hello"}"#)?; let x = engine.eval::<Map>(r#"#{a: 1, b: true, "c$": "hello"}"#)?;
let a = x.get("a").cloned().unwrap(); let a = x.get("a").cloned().expect("should have property a");
let b = x.get("b").cloned().unwrap(); let b = x.get("b").cloned().expect("should have property b");
let c = x.get("c").cloned().unwrap(); let c = x.get("c$").cloned().expect("should have property c$");
assert_eq!(*a.downcast::<INT>().unwrap(), 1); assert_eq!(a.cast::<INT>(), 1);
assert_eq!(*b.downcast::<bool>().unwrap(), true); assert_eq!(b.cast::<bool>(), true);
assert_eq!(*c.downcast::<String>().unwrap(), "hello"); 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(()) Ok(())
} }

View File

@ -2,8 +2,7 @@
///! This test simulates an external command object that is driven by a script. ///! This test simulates an external command object that is driven by a script.
use rhai::{Engine, EvalAltResult, RegisterFn, Scope, INT}; use rhai::{Engine, EvalAltResult, RegisterFn, Scope, INT};
use std::cell::RefCell; use std::sync::{Arc, Mutex};
use std::rc::Rc;
/// External command. /// External command.
struct Command { struct Command {
@ -24,19 +23,19 @@ impl Command {
/// Wrapper object to wrap a command object. /// Wrapper object to wrap a command object.
#[derive(Clone)] #[derive(Clone)]
struct CommandWrapper { struct CommandWrapper {
command: Rc<RefCell<Command>>, command: Arc<Mutex<Command>>,
} }
impl CommandWrapper { impl CommandWrapper {
/// Delegate command action. /// Delegate command action.
pub fn do_action(&mut self, x: i64) { 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(); let val = command.get();
command.action(val + x); command.action(val + x);
} }
/// Delegate get value action. /// Delegate get value action.
pub fn get_value(&mut self) -> i64 { pub fn get_value(&mut self) -> i64 {
let command = self.command.borrow(); let command = self.command.lock().unwrap();
command.get() command.get()
} }
} }
@ -47,8 +46,8 @@ fn test_side_effects() -> Result<(), EvalAltResult> {
let mut scope = Scope::new(); let mut scope = Scope::new();
// Create the command object with initial state, handled by an `Rc`. // Create the command object with initial state, handled by an `Rc`.
let command = Rc::new(RefCell::new(Command { state: 12 })); let command = Arc::new(Mutex::new(Command { state: 12 }));
assert_eq!(command.borrow().get(), 12); assert_eq!(command.lock().unwrap().get(), 12);
// Create the wrapper. // Create the wrapper.
let wrapper = CommandWrapper { let wrapper = CommandWrapper {
@ -76,7 +75,7 @@ fn test_side_effects() -> Result<(), EvalAltResult> {
); );
// Make sure the actions are properly performed // Make sure the actions are properly performed
assert_eq!(command.borrow().get(), 42); assert_eq!(command.lock().unwrap().get(), 42);
Ok(()) Ok(())
} }

View File

@ -9,8 +9,12 @@ fn test_var_scope() -> Result<(), EvalAltResult> {
assert_eq!(engine.eval_with_scope::<INT>(&mut scope, "x")?, 9); assert_eq!(engine.eval_with_scope::<INT>(&mut scope, "x")?, 9);
engine.eval_with_scope::<()>(&mut scope, "x = x + 1; x = x + 2;")?; engine.eval_with_scope::<()>(&mut scope, "x = x + 1; x = x + 2;")?;
assert_eq!(engine.eval_with_scope::<INT>(&mut scope, "x")?, 12); 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}")?; 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(()) Ok(())
} }

View File

@ -6,8 +6,18 @@ fn test_while() -> Result<(), EvalAltResult> {
assert_eq!( assert_eq!(
engine.eval::<INT>( engine.eval::<INT>(
"let x = 0; while x < 10 { x = x + 1; if x > 5 { \ r"
break } } x", let x = 0;
while x < 10 {
x = x + 1;
if x > 5 { break; }
if x > 3 { continue; }
x = x + 3;
}
x
",
)?, )?,
6 6
); );