Merge pull request #128 from schungx/master

Reentrant Engine, Rust anonymous functions, `in` expression, timestamps, bug fixes
This commit is contained in:
Stephen Chung 2020-04-13 10:55:43 +08:00 committed by GitHub
commit 2330dcb94a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
55 changed files with 2453 additions and 1507 deletions

View File

@ -1,6 +1,6 @@
[package] [package]
name = "rhai" name = "rhai"
version = "0.11.0" version = "0.12.0"
edition = "2018" edition = "2018"
authors = ["Jonathan Turner", "Lukáš Hozda", "Stephen Chung"] authors = ["Jonathan Turner", "Lukáš Hozda", "Stephen Chung"]
description = "Embedded scripting for Rust" description = "Embedded scripting for Rust"
@ -17,19 +17,17 @@ keywords = [ "scripting" ]
categories = [ "no-std", "embedded", "parser-implementations" ] categories = [ "no-std", "embedded", "parser-implementations" ]
[dependencies] [dependencies]
num-traits = "*" num-traits = { version = "0.2.11", default-features = false }
[features] [features]
#default = ["no_function", "no_index", "no_object", "no_float", "only_i32", "no_stdlib", "unchecked", "no_optimize", "sync"] #default = ["no_stdlib", "no_function", "no_index", "no_object", "no_float", "only_i32", "unchecked", "no_optimize", "sync"]
default = [] default = []
unchecked = [] # unchecked arithmetic unchecked = [] # unchecked arithmetic
no_stdlib = [] # no standard library of utility functions
no_index = [] # no arrays and indexing no_index = [] # no arrays and indexing
no_float = [] # no floating-point no_float = [] # no floating-point
no_function = [] # no script-defined functions no_function = [] # no script-defined functions
no_object = [] # no custom objects no_object = [] # no custom objects
no_optimize = [] # no script optimizer no_optimize = [] # no script optimizer
optimize_full = [] # set optimization level to Full (default is Simple) - this is a feature used only to simplify testing
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 sync = [] # restrict to only types that implement Send + Sync
@ -37,6 +35,10 @@ 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" ]
# other developer features
no_stdlib = [] # do not register the standard library
optimize_full = [] # set optimization level to Full (default is Simple) - this is a feature used only to simplify testing
[profile.release] [profile.release]
lto = "fat" lto = "fat"
codegen-units = 1 codegen-units = 1
@ -44,21 +46,21 @@ codegen-units = 1
#panic = 'abort' # remove stack backtrace for no-std #panic = 'abort' # remove stack backtrace for no-std
[dependencies.libm] [dependencies.libm]
version = "*" version = "0.2.1"
optional = true optional = true
[dependencies.core-error] [dependencies.core-error]
version = "*" version = "0.0.0"
features = ["alloc"] features = ["alloc"]
optional = true optional = true
[dependencies.hashbrown] [dependencies.hashbrown]
version = "*" version = "0.7.1"
default-features = false default-features = false
features = ["ahash", "nightly", "inline-more"] features = ["ahash", "nightly", "inline-more"]
optional = true optional = true
[dependencies.ahash] [dependencies.ahash]
version = "*" version = "0.3.2"
default-features = false default-features = false
optional = true optional = true

467
README.md
View File

@ -27,7 +27,7 @@ Rhai's current features set:
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`.
**Note:** Currently, the version is 0.11.0, so the language and API's may change before they stabilize. **Note:** Currently, the version is 0.12.0, so the language and API's may change before they stabilize.
Installation Installation
------------ ------------
@ -36,7 +36,7 @@ Install the Rhai crate by adding this line to `dependencies`:
```toml ```toml
[dependencies] [dependencies]
rhai = "0.11.0" rhai = "0.12.0"
``` ```
Use the latest released crate version on [`crates.io`](https::/crates.io/crates/rhai/): Use the latest released crate version on [`crates.io`](https::/crates.io/crates/rhai/):
@ -60,11 +60,10 @@ Optional features
----------------- -----------------
| Feature | Description | | 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! | | `unchecked` | Exclude arithmetic checking (such as overflows and division by zero). Beware that a bad script may panic the entire system! |
| `no_function` | Disable script-defined functions if not needed. | | `no_function` | Disable script-defined functions if not needed. |
| `no_index` | Disable arrays and indexing features if not needed. | | `no_index` | Disable [arrays] and indexing features if not needed. |
| `no_object` | Disable support for custom types and objects. | | `no_object` | Disable support for custom types and objects. |
| `no_float` | Disable floating-point numbers and math if not needed. | | `no_float` | Disable floating-point numbers and math if not needed. |
| `no_optimize` | Disable the script optimizer. | | `no_optimize` | Disable the script optimizer. |
@ -78,7 +77,6 @@ Most features are here to opt-**out** of certain functionalities that are not ne
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
[`no_stdlib`]: #optional-features
[`no_index`]: #optional-features [`no_index`]: #optional-features
[`no_float`]: #optional-features [`no_float`]: #optional-features
[`no_function`]: #optional-features [`no_function`]: #optional-features
@ -104,7 +102,7 @@ A number of examples can be found in the `examples` folder:
| Example | Description | | Example | Description |
| ------------------------------------------------------------------ | --------------------------------------------------------------------------- | | ------------------------------------------------------------------ | --------------------------------------------------------------------------- |
| [`arrays_and_structs`](examples/arrays_and_structs.rs) | demonstrates registering a new type to Rhai and the usage of arrays on it | | [`arrays_and_structs`](examples/arrays_and_structs.rs) | demonstrates registering a new type to Rhai and the usage of [arrays] on it |
| [`custom_types_and_methods`](examples/custom_types_and_methods.rs) | shows how to register a type and methods for it | | [`custom_types_and_methods`](examples/custom_types_and_methods.rs) | shows how to register a type and methods for it |
| [`hello`](examples/hello.rs) | simple example that evaluates an expression and prints the result | | [`hello`](examples/hello.rs) | simple example that evaluates an expression and prints the result |
| [`no_std`](examples/no_std.rs) | example to test out `no-std` builds | | [`no_std`](examples/no_std.rs) | example to test out `no-std` builds |
@ -129,7 +127,7 @@ There are also a number of examples scripts that showcase Rhai's features, all i
| Language feature scripts | Description | | Language feature scripts | Description |
| ---------------------------------------------------- | ------------------------------------------------------------- | | ---------------------------------------------------- | ------------------------------------------------------------- |
| [`array.rhai`](scripts/array.rhai) | arrays in Rhai | | [`array.rhai`](scripts/array.rhai) | [arrays] in Rhai |
| [`assignment.rhai`](scripts/assignment.rhai) | variable declarations | | [`assignment.rhai`](scripts/assignment.rhai) | variable declarations |
| [`comments.rhai`](scripts/comments.rhai) | just comments | | [`comments.rhai`](scripts/comments.rhai) | just comments |
| [`for1.rhai`](scripts/for1.rhai) | for loops | | [`for1.rhai`](scripts/for1.rhai) | for loops |
@ -141,7 +139,7 @@ There are also a number of examples scripts that showcase Rhai's features, all i
| [`op1.rhai`](scripts/op1.rhai) | just a simple addition | | [`op1.rhai`](scripts/op1.rhai) | just a simple addition |
| [`op2.rhai`](scripts/op2.rhai) | simple addition and multiplication | | [`op2.rhai`](scripts/op2.rhai) | simple addition and multiplication |
| [`op3.rhai`](scripts/op3.rhai) | change evaluation order with parenthesis | | [`op3.rhai`](scripts/op3.rhai) | change evaluation order with parenthesis |
| [`string.rhai`](scripts/string.rhai) | string operations | | [`string.rhai`](scripts/string.rhai) | [string] operations |
| [`while.rhai`](scripts/while.rhai) | while loop | | [`while.rhai`](scripts/while.rhai) | while loop |
| Example scripts | Description | | Example scripts | Description |
@ -160,14 +158,14 @@ Hello world
[`Engine`]: #hello-world [`Engine`]: #hello-world
To get going with Rhai, create an instance of the scripting engine and then call `eval`: To get going with Rhai, create an instance of the scripting engine via `Engine::new` and then call the `eval` method:
```rust ```rust
use rhai::{Engine, EvalAltResult}; use rhai::{Engine, EvalAltResult};
fn main() -> Result<(), EvalAltResult> fn main() -> Result<(), EvalAltResult>
{ {
let mut engine = Engine::new(); let engine = Engine::new();
let result = engine.eval::<i64>("40 + 2")?; let result = engine.eval::<i64>("40 + 2")?;
@ -177,6 +175,10 @@ fn main() -> Result<(), EvalAltResult>
} }
``` ```
`EvalAltResult` is a Rust `enum` containing all errors encountered during the parsing or evaluation process.
### Script evaluation
The type parameter is used to specify the type of the return value, which _must_ match the actual type or an error is returned. The type parameter is used to specify the type of the return value, which _must_ match the actual type or an error is returned.
Rhai is very strict here. There are two ways to specify the return type - _turbofish_ notation, or type inference. Rhai is very strict here. There are two ways to specify the return type - _turbofish_ notation, or type inference.
@ -194,6 +196,8 @@ Evaluate a script file directly:
let result = engine.eval_file::<i64>("hello_world.rhai".into())?; // 'eval_file' takes a 'PathBuf' let result = engine.eval_file::<i64>("hello_world.rhai".into())?; // 'eval_file' takes a 'PathBuf'
``` ```
### Compiling scripts (to AST)
To repeatedly evaluate a script, _compile_ it first into an AST (abstract syntax tree) form: To repeatedly evaluate a script, _compile_ it first into an AST (abstract syntax tree) form:
```rust ```rust
@ -213,8 +217,9 @@ 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 - ### Calling Rhai functions from Rust
via `call_fn` or its cousins `call_fn1` (one argument) and `call_fn0` (no argument).
Rhai also allows working _backwards_ from the other direction - i.e. calling a Rhai-scripted function from Rust via `call_fn`.
```rust ```rust
// Define functions in a script. // Define functions in a script.
@ -239,25 +244,89 @@ let ast = engine.compile(true,
// A custom scope can also contain any variables/constants available to the functions // A custom scope can also contain any variables/constants available to the functions
let mut scope = Scope::new(); let mut scope = Scope::new();
// Evaluate a function defined in the script, passing arguments into the script as a tuple // 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 // Beware, arguments must be of the correct types because Rhai does not have built-in type conversions.
// Rhai does not have built-in type conversions. If arguments of the wrong types are passed, // 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(&mut scope, &ast, "hello", ( 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_fn1(&mut scope, &ast, "hello", 123_i64)? let result: i64 = engine.call_fn(&mut scope, &ast, "hello", (123_i64,) )?
// ^^^^^^^^ use 'call_fn1' for one argument // ^^^^^^^^^^ tuple of one
let result: i64 = engine.call_fn0(&mut scope, &ast, "hello")? let result: i64 = engine.call_fn(&mut scope, &ast, "hello", () )?
// ^^^^^^^^ use 'call_fn0' for no arguments // ^^ unit = tuple of zero
```
### Creating Rust anonymous functions from Rhai script
[`Func`]: #creating-rust-anonymous-functions-from-rhai-script
It is possible to further encapsulate a script in Rust such that it becomes a normal Rust function.
Such an _anonymous function_ is basically a boxed closure, very useful as call-back functions.
Creating them is accomplished via the `Func` trait which contains `create_from_script`
(as well as its companion method `create_from_ast`):
```rust
use rhai::{Engine, Func}; // use 'Func' for 'create_from_script'
let engine = Engine::new(); // create a new 'Engine' just for this
let script = "fn calc(x, y) { x + y.len() < 42 }";
// Func takes two type parameters:
// 1) a tuple made up of the types of the script function's parameters
// 2) the return type of the script function
//
// 'func' will have type Box<dyn Fn(i64, String) -> Result<bool, EvalAltResult>> and is callable!
let func = Func::<(i64, String), bool>::create_from_script(
// ^^^^^^^^^^^^^ function parameter types in tuple
engine, // the 'Engine' is consumed into the closure
script, // the script, notice number of parameters must match
"calc" // the entry-point function name
)?;
func(123, "hello".to_string())? == false; // call the anonymous function
schedule_callback(func); // pass it as a callback to another function
// Although there is nothing you can't do by manually writing out the closure yourself...
let engine = Engine::new();
let ast = engine.compile(script)?;
schedule_callback(Box::new(move |x: i64, y: String| -> Result<bool, EvalAltResult> {
engine.call_fn(&mut Scope::new(), &ast, "calc", (x, y))
}));
```
Raw `Engine`
------------
[raw `Engine`]: #raw-engine
`Engine::new` creates a scripting [`Engine`] with common functionalities (e.g. printing to the console via `print`).
In many controlled embedded environments, however, these are not needed.
Use `Engine::new_raw` to create a _raw_ `Engine`, in which:
* the `print` and `debug` statements do nothing instead of displaying to the console (see [`print` and `debug`](#print-and-debug) below)
* the _standard library_ of utility functions is _not_ loaded by default (load it using the `register_stdlib` method).
```rust
let mut engine = Engine::new_raw(); // create a 'raw' Engine
engine.register_stdlib(); // register the standard library manually
engine.
``` ```
Evaluate expressions only Evaluate expressions only
------------------------- -------------------------
[`eval_expression`]: #evaluate-expressions-only
[`eval_expression_with_scope`]: #evaluate-expressions-only
Sometimes a use case does not require a full-blown scripting _language_, but only needs to evaluate _expressions_. Sometimes a use case does not require a full-blown scripting _language_, but only needs to evaluate _expressions_.
In these cases, use the `compile_expression` and `eval_expression` methods or their `_with_scope` variants. In these cases, use the `compile_expression` and `eval_expression` methods or their `_with_scope` variants.
@ -265,8 +334,8 @@ In these cases, use the `compile_expression` and `eval_expression` methods or th
let result = engine.eval_expression::<i64>("2 + (10 + 10) * 2")?; let result = engine.eval_expression::<i64>("2 + (10 + 10) * 2")?;
``` ```
When evaluation _expressions_, no control-flow statement (e.g. `if`, `while`, `for`) is not supported and will be When evaluation _expressions_, no full-blown statement (e.g. `if`, `while`, `for`) - not even variable assignments -
parse errors when encountered - not even variable assignments. is supported and will be considered parse errors when encountered.
```rust ```rust
// The following are all syntax errors because the script is not an expression. // The following are all syntax errors because the script is not an expression.
@ -280,6 +349,7 @@ Values and types
[`type_of()`]: #values-and-types [`type_of()`]: #values-and-types
[`to_string()`]: #values-and-types [`to_string()`]: #values-and-types
[`()`]: #values-and-types
The following primitive types are supported natively: The following primitive types are supported natively:
@ -292,25 +362,24 @@ The following primitive types are supported natively:
| **Unicode string** | `String` (_not_ `&str`) | `"string"` | `"hello"` etc. | | **Unicode string** | `String` (_not_ `&str`) | `"string"` | `"hello"` etc. |
| **Array** (disabled with [`no_index`]) | `rhai::Array` | `"array"` | `"[ ? ? ? ]"` | | **Array** (disabled with [`no_index`]) | `rhai::Array` | `"array"` | `"[ ? ? ? ]"` |
| **Object map** (disabled with [`no_object`]) | `rhai::Map` | `"map"` | `#{ "a": 1, "b": 2 }` | | **Object map** (disabled with [`no_object`]) | `rhai::Map` | `"map"` | `#{ "a": 1, "b": 2 }` |
| **Timestamp** (implemented in standard library) | `std::time::Instant` | `"timestamp"` | _not supported_ |
| **Dynamic value** (i.e. can be anything) | `rhai::Dynamic` | _the actual type_ | _actual value_ | | **Dynamic value** (i.e. can be anything) | `rhai::Dynamic` | _the actual type_ | _actual value_ |
| **System integer** (current configuration) | `rhai::INT` (`i32` or `i64`) | `"i32"` or `"i64"` | `"42"`, `"123"` etc. | | **System integer** (current configuration) | `rhai::INT` (`i32` or `i64`) | `"i32"` or `"i64"` | `"42"`, `"123"` etc. |
| **System floating-point** (current configuration, disabled with [`no_float`]) | `rhai::FLOAT` (`f32` or `f64`) | `"f32"` or `"f64"` | `"123.456"` etc. | | **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)_ |
[`()`]: #values-and-types
All types are treated strictly separate by Rhai, meaning that `i32` and `i64` and `u32` are completely different - 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. 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 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. 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`, If only 32-bit integers are needed, enabling the [`only_i32`] feature will remove support for all integer types other than `i32`, including `i64`.
including `i64`. This is useful on some 32-bit systems where using 64-bit integers incurs a performance penalty. 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`] in nature. The `type_of` function detects the actual type of a value. This is useful because all variables are [`Dynamic`] in nature.
@ -364,8 +433,8 @@ if type_of(mystery) == "i64" {
} }
``` ```
In Rust, sometimes a `Dynamic` forms part of a returned value - a good example is an array with `Dynamic` elements, 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. 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` 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). function and match against the name).
@ -374,7 +443,7 @@ The `cast` method (from the `rhai::AnyExt` trait) then converts the value into a
Alternatively, use the `try_cast` method which does not panic but returns an error when the cast fails. Alternatively, use the `try_cast` method which does not panic but returns an error when the cast fails.
```rust ```rust
use rhai::AnyExt; // Pull in the trait. use rhai::AnyExt; // pull in the trait.
let list: Array = engine.eval("...")?; // return type is 'Array' let list: Array = engine.eval("...")?; // return type is 'Array'
let item = list[0]; // an element in an 'Array' is 'Dynamic' let item = list[0]; // an element in an 'Array' is 'Dynamic'
@ -390,14 +459,14 @@ let value = item.try_cast::<i64>()?; // 'try_cast' does not panic whe
The `type_name` method gets the name of the actual type as a static string slice, which you may match against. The `type_name` method gets the name of the actual type as a static string slice, which you may match against.
```rust ```rust
use rhai::Any; // Pull in the trait. use rhai::Any; // pull in the trait.
let list: Array = engine.eval("...")?; // return type is 'Array' let list: Array = engine.eval("...")?; // return type is 'Array'
let item = list[0]; // an element in an 'Array' is 'Dynamic' 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 match item.type_name() { // 'type_name' returns the name of the actual Rust type
"i64" => ... "i64" => ...
"std::string::String" => ... "alloc::string::String" => ...
"bool" => ... "bool" => ...
"path::to::module::TestStruct" => ... "path::to::module::TestStruct" => ...
} }
@ -423,6 +492,20 @@ 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"
``` ```
Traits
------
A number of traits, under the `rhai::` module namespace, provide additional functionalities.
| Trait | Description | Methods |
| ------------------- | --------------------------------------------------------------------------------- | --------------------------------------- |
| `Any` | Generic trait that represents a [`Dynamic`] type | `type_id`, `type_name`, `into_dynamic` |
| `AnyExt` | Extension trait to allows casting of a [`Dynamic`] value to Rust types | `cast`, `try_cast` |
| `RegisterFn` | Trait for registering functions | `register_fn` |
| `RegisterDynamicFn` | Trait for registering functions returning [`Dynamic`] | `register_dynamic_fn` |
| `RegisterResultFn` | Trait for registering fallible functions returning `Result<`_T_`, EvalAltResult>` | `register_result_fn` |
| `Func` | Trait for creating anonymous functions from script | `create_from_ast`, `create_from_script` |
Working with functions Working with functions
---------------------- ----------------------
@ -431,8 +514,8 @@ To call these functions, they need to be registered with the [`Engine`].
```rust ```rust
use rhai::{Engine, EvalAltResult}; use rhai::{Engine, EvalAltResult};
use rhai::RegisterFn; // use `RegisterFn` trait for `register_fn` use rhai::RegisterFn; // use 'RegisterFn' trait for 'register_fn'
use rhai::{Any, 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 {
@ -446,7 +529,7 @@ fn get_an_any() -> Dynamic {
fn main() -> Result<(), EvalAltResult> fn main() -> Result<(), EvalAltResult>
{ {
let mut engine = Engine::new(); let engine = Engine::new();
engine.register_fn("add", add); engine.register_fn("add", add);
@ -469,7 +552,7 @@ To return a [`Dynamic`] value from a Rust function, use the `into_dynamic()` met
(under the `rhai::Any` trait) to convert it. (under the `rhai::Any` trait) to convert it.
```rust ```rust
use rhai::Any; // Pull in the trait 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 {
@ -497,7 +580,7 @@ fn show_it<T: Display>(x: &mut T) -> () {
fn main() fn main()
{ {
let mut engine = Engine::new(); let engine = Engine::new();
engine.register_fn("print", show_it as fn(x: &mut i64)->()); engine.register_fn("print", show_it as fn(x: &mut i64)->());
engine.register_fn("print", show_it as fn(x: &mut bool)->()); engine.register_fn("print", show_it as fn(x: &mut bool)->());
@ -519,13 +602,13 @@ and the error text gets converted into `EvalAltResult::ErrorRuntime`.
```rust ```rust
use rhai::{Engine, EvalAltResult, Position}; use rhai::{Engine, EvalAltResult, Position};
use rhai::RegisterResultFn; // use `RegisterResultFn` trait for `register_result_fn` use rhai::RegisterResultFn; // use 'RegisterResultFn' trait for 'register_result_fn'
// Function that may fail // Function that may fail
fn safe_divide(x: i64, y: i64) -> Result<i64, EvalAltResult> { fn safe_divide(x: i64, y: i64) -> Result<i64, EvalAltResult> {
if y == 0 { if y == 0 {
// Return an error if y is zero // Return an error if y is zero
Err("Division by zero detected!".into()) // short-cut to create EvalAltResult Err("Division by zero!".into()) // short-cut to create EvalAltResult
} else { } else {
Ok(x / y) Ok(x / y)
} }
@ -533,7 +616,7 @@ fn safe_divide(x: i64, y: i64) -> Result<i64, EvalAltResult> {
fn main() fn main()
{ {
let mut engine = Engine::new(); let engine = Engine::new();
// Fallible functions that return Result values must use register_result_fn() // Fallible functions that return Result values must use register_result_fn()
engine.register_result_fn("divide", safe_divide); engine.register_result_fn("divide", safe_divide);
@ -584,7 +667,7 @@ impl TestStruct {
fn main() -> Result<(), EvalAltResult> fn main() -> Result<(), EvalAltResult>
{ {
let mut engine = Engine::new(); let engine = Engine::new();
engine.register_type::<TestStruct>(); engine.register_type::<TestStruct>();
@ -622,7 +705,7 @@ impl TestStruct {
} }
} }
let mut engine = Engine::new(); let engine = Engine::new();
engine.register_type::<TestStruct>(); engine.register_type::<TestStruct>();
``` ```
@ -712,7 +795,7 @@ impl TestStruct {
} }
} }
let mut engine = Engine::new(); let engine = Engine::new();
engine.register_type::<TestStruct>(); engine.register_type::<TestStruct>();
@ -748,7 +831,7 @@ use rhai::{Engine, Scope, EvalAltResult};
fn main() -> Result<(), EvalAltResult> fn main() -> Result<(), EvalAltResult>
{ {
let mut engine = Engine::new(); let engine = Engine::new();
// First create the state // First create the state
let mut scope = Scope::new(); let mut scope = Scope::new();
@ -957,7 +1040,7 @@ number = -5 - +5;
Numeric functions Numeric functions
----------------- -----------------
The following standard functions (defined in the standard library but excluded if [`no_stdlib`]) operate on The following standard functions (defined in the standard library but excluded if using a [raw `Engine`]) operate on
`i8`, `i16`, `i32`, `i64`, `f32` and `f64` only: `i8`, `i16`, `i32`, `i64`, `f32` and `f64` only:
| Function | Description | | Function | Description |
@ -968,7 +1051,7 @@ The following standard functions (defined in the standard library but excluded i
Floating-point functions Floating-point functions
------------------------ ------------------------
The following standard functions (defined in the standard library but excluded if [`no_stdlib`]) operate on `f64` only: The following standard functions (defined in the standard library but excluded if using a [raw `Engine`]) operate on `f64` only:
| Category | Functions | | Category | Functions |
| ---------------- | ------------------------------------------------------------ | | ---------------- | ------------------------------------------------------------ |
@ -984,13 +1067,31 @@ The following standard functions (defined in the standard library but excluded i
Strings and Chars Strings and Chars
----------------- -----------------
[string]: #strings-and-chars
[strings]: #strings-and-chars
[char]: #strings-and-chars
String and char literals follow C-style formatting, with support for Unicode ('`\u`_xxxx_' or '`\U`_xxxxxxxx_') and String and char literals follow C-style formatting, with support for Unicode ('`\u`_xxxx_' or '`\U`_xxxxxxxx_') and
hex ('`\x`_xx_') escape sequences. 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, 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. 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. Standard escape sequences:
| Escape sequence | Meaning |
| --------------- | ------------------------------ |
| `\\` | back-slash `\` |
| `\t` | tab |
| `\r` | carriage-return `CR` |
| `\n` | line-feed `LF` |
| `\"` | double-quote `"` in strings |
| `\'` | single-quote `'` in characters |
| `\x`_xx_ | Unicode in 2-digit hex |
| `\u`_xxxx_ | Unicode in 4-digit hex |
| `\U`_xxxxxxxx_ | Unicode in 8-digit hex |
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 This is similar to most other languages where strings are internally represented not as UTF-8 but as arrays of multi-byte
Unicode characters. Unicode characters.
@ -998,7 +1099,7 @@ Individual characters within a Rhai string can also be replaced just as if the s
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 Strings can be built up from other strings and types via the `+` operator (provided by the standard library but excluded
if [`no_stdlib`]). This is particularly useful when printing output. if using a [raw `Engine`]). This is particularly useful when printing output.
[`type_of()`] a string returns `"string"`. [`type_of()`] a string returns `"string"`.
@ -1039,22 +1140,29 @@ record == "Bob C. Davis: age 42 ❤\n"; // '\n' = new-line
// (disabled with 'no_index') // (disabled with 'no_index')
record[4] = '\x58'; // 0x58 = 'X' record[4] = '\x58'; // 0x58 = 'X'
record == "Bob X. Davis: age 42 ❤\n"; record == "Bob X. Davis: age 42 ❤\n";
// Use 'in' to test if a substring (or character) exists in a string
"Davis" in record == true;
'X' in record == true;
'C' in record == false;
``` ```
The following standard functions (defined in the standard library but excluded if [`no_stdlib`]) operate on strings: ### Built-in functions
| Function | Description | The following standard methods (defined in the standard library but excluded if using a [raw `Engine`]) operate on strings:
| ---------- | ------------------------------------------------------------------------ |
| `len` | returns the number of characters (not number of bytes) in the string |
| `pad` | pads the string with an character until a specified number of characters |
| `append` | Adds a character or a string to the end of another string |
| `clear` | empties the string |
| `truncate` | cuts off the string at exactly a specified number of characters |
| `contains` | checks if a certain character or sub-string occurs in the string |
| `replace` | replaces a substring with another |
| `trim` | trims the string |
Examples: | Function | Parameter(s) | Description |
| ---------- | ------------------------------------- | -------------------------------------------------------------------- |
| `len` | _none_ | returns the number of characters (not number of bytes) in the string |
| `pad` | character to pad, target length | pads the string with an character to a specified length |
| `append` | character/string to append | Adds a character or a string to the end of another string |
| `clear` | _none_ | empties the string |
| `truncate` | target length | cuts off the string at exactly a specified number of characters |
| `contains` | character/sub-string to search for | checks if a certain character or sub-string occurs in the string |
| `replace` | target sub-string, replacement string | replaces a substring with another |
| `trim` | _none_ | trims the string of whitespace at the beginning and end |
### Examples
```rust ```rust
let full_name == " Bob C. Davis "; let full_name == " Bob C. Davis ";
@ -1086,6 +1194,10 @@ full_name.len() == 0;
Arrays Arrays
------ ------
[array]: #arrays
[arrays]: #arrays
[`Array`]: #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. All elements stored in an array are [`Dynamic`], and the array can freely grow or shrink with elements added or removed.
@ -1094,27 +1206,51 @@ The Rust type of a Rhai array is `rhai::Array`. [`type_of()`] an array returns `
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: ### Built-in functions
| Function | Description | The following methods (defined in the standard library but excluded if using a [raw `Engine`]) operate on arrays:
| ------------ | ------------------------------------------------------------------------------------- |
| `push` | inserts an element at the end |
| `append` | concatenates the second array to the end of the first |
| `+` operator | concatenates the first array with the second |
| `pop` | removes the last element and returns it ([`()`] if empty) |
| `shift` | removes the first element and returns it ([`()`] if empty) |
| `len` | returns the number of elements |
| `pad` | pads the array with an element until a specified length |
| `clear` | empties the array |
| `truncate` | cuts off the array at exactly a specified length (discarding all subsequent elements) |
Examples: | Function | Parameter(s) | Description |
| ------------ | --------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------- |
| `push` | element to insert | inserts an element at the end |
| `append` | array to append | concatenates the second array to the end of the first |
| `+` operator | first array, second array | concatenates the first array with the second |
| `insert` | element to insert, position<br/>(beginning if <= 0, end if >= length) | insert an element at a certain index |
| `pop` | _none_ | removes the last element and returns it ([`()`] if empty) |
| `shift` | _none_ | removes the first element and returns it ([`()`] if empty) |
| `remove` | index | removes an element at a particular index and returns it, or returns [`()`] if the index is not valid |
| `len` | _none_ | returns the number of elements |
| `pad` | element to pad, target length | pads the array with an element until a specified length |
| `clear` | _none_ | empties the array |
| `truncate` | target length | cuts off the array at exactly a specified length (discarding all subsequent elements) |
### Examples
```rust ```rust
let y = [1, 2, 3]; // array literal with 3 elements let y = [2, 3]; // array literal with 2 elements
y[1] = 42;
print(y[1]); // prints 42 y.insert(0, 1); // insert element at the beginning
y.insert(999, 4); // insert element at the end
y.len() == 4;
y[0] == 1;
y[1] == 2;
y[2] == 3;
y[3] == 4;
(1 in y) == true; // use 'in' to test if an item exists in the array
(42 in y) == false;
y[1] = 42; // array elements can be reassigned
(42 in y) == true;
y.remove(2) == 3; // remove element
y.len() == 3;
y[2] == 4; // elements after the removed element are shifted
ts.list = y; // arrays can be assigned completely (by value copy) ts.list = y; // arrays can be assigned completely (by value copy)
let foo = ts.list[1]; let foo = ts.list[1];
@ -1136,7 +1272,7 @@ foo == 1;
y.push(4); // 4 elements y.push(4); // 4 elements
y.push(5); // 5 elements y.push(5); // 5 elements
print(y.len()); // prints 5 y.len() == 5;
let first = y.shift(); // remove the first element, 4 elements remaining let first = y.shift(); // remove the first element, 4 elements remaining
first == 1; first == 1;
@ -1144,7 +1280,7 @@ first == 1;
let last = y.pop(); // remove the last element, 3 elements remaining let last = y.pop(); // remove the last element, 3 elements remaining
last == 5; last == 5;
print(y.len()); // prints 3 y.len() == 3;
for item in y { // arrays can be iterated with a 'for' statement for item in y { // arrays can be iterated with a 'for' statement
print(item); print(item);
@ -1152,15 +1288,15 @@ for item in y { // arrays can be iterated with a 'for' statement
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 y.len() == 10;
y.truncate(5); // truncate the array to 5 elements y.truncate(5); // truncate the array to 5 elements
print(y.len()); // prints 5 y.len() == 5;
y.clear(); // empty the array y.clear(); // empty the array
print(y.len()); // prints 0 y.len() == 0;
``` ```
`push` and `pad` are only defined for standard built-in types. For custom types, type-specific versions must be registered: `push` and `pad` are only defined for standard built-in types. For custom types, type-specific versions must be registered:
@ -1172,34 +1308,40 @@ engine.register_fn("push", |list: &mut Array, item: MyType| list.push(Box::new(i
Object maps Object maps
----------- -----------
[object map]: #object-maps
[object maps]: #object-maps
Object maps are dictionaries. Properties are all [`Dynamic`] and 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.
Property values can be accessed via the dot notation (_object_ `.` _property_) or index notation (_object_ `[` _property_ `]`). Property values can be accessed via the dot notation (_object_ `.` _property_) or index notation (_object_ `[` _property_ `]`).
The dot notation allows only property names that follow the same naming rules as [variables]. The dot notation allows only property names that follow the same naming rules as [variables].
The index notation allows setting/getting properties of arbitrary names (even the empty string). The index notation allows setting/getting properties of arbitrary names (even the empty [string]).
**Important:** Trying to read a non-existent property returns `()` instead of causing an error. **Important:** Trying to read a non-existent property returns [`()`] instead of causing an error.
The Rust type of a Rhai object map is `rhai::Map`. [`type_of()`] an object map returns `"map"`. The Rust type of a Rhai object map is `rhai::Map`. [`type_of()`] an object map returns `"map"`.
Object maps are disabled via the [`no_object`] feature. 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: ### Built-in functions
| Function | Description | The following methods (defined in the standard library but excluded if using a [raw `Engine`]) operate on object maps:
| ------------ | ---------------------------------------------------------------------------------------------------------------------------------------- |
| `has` | does the object map contain a property of a particular name? |
| `len` | returns the number of properties |
| `clear` | empties the object map |
| `mixin` | mixes in all the properties of the second object map to the first (values of properties with the same names replace the existing values) |
| `+` operator | merges the first object map with the second |
| `keys` | returns an array of all the property names (in random order) |
| `values` | returns an array of all the property values (in random order) |
Examples: | Function | Parameter(s) | Description |
| ------------ | ----------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------- |
| `has` | property name | does the object map contain a property of a particular name? |
| `len` | _none_ | returns the number of properties |
| `clear` | _none_ | empties the object map |
| `remove` | property name | removes a certain property and returns it ([`()`] if the property does not exist) |
| `mixin` | second object map | 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 | first object map, second object map | merges the first object map with the second |
| `keys` | _none_ | returns an [array] of all the property names (in random order) |
| `values` | _none_ | returns an [array] of all the property values (in random order) |
### Examples
```rust ```rust
let y = #{ // object map literal with 3 properties let y = #{ // object map literal with 3 properties
@ -1215,9 +1357,12 @@ y.a = 42; // access via dot notation
y.baz!$@ = 42; // <- syntax error: only proper variable names allowed in dot notation y.baz!$@ = 42; // <- syntax error: only proper variable names allowed in dot notation
y."baz!$@" = 42; // <- syntax error: strings not allowed in dot notation y."baz!$@" = 42; // <- syntax error: strings not allowed in dot notation
print(y.a); // prints 42 y.a == 42;
print(y["baz!$@"]); // prints 123.456 - access via index notation y["baz!$@"] == 123.456; // access via index notation
"baz!$@" in y == true; // use 'in' to test if a property exists in the object map, prints true
("z" in y) == false;
ts.obj = y; // object maps can be assigned completely (by value copy) ts.obj = y; // object maps can be assigned completely (by value copy)
let foo = ts.list.a; let foo = ts.list.a;
@ -1239,10 +1384,15 @@ foo == 42;
y.has("a") == true; y.has("a") == true;
y.has("xyz") == false; y.has("xyz") == false;
y.xyz == (); // A non-existing property returns '()' y.xyz == (); // a non-existing property returns '()'
y["xyz"] == (); y["xyz"] == ();
print(y.len()); // prints 3 y.len() == 3;
y.remove("a") == 1; // remove property
y.len() == 2;
y.has("a") == false;
for name in keys(y) { // get an array of all the property names via the 'keys' function for name in keys(y) { // get an array of all the property names via the 'keys' function
print(name); print(name);
@ -1254,7 +1404,79 @@ for val in values(y) { // get an array of all the property values via the 'valu
y.clear(); // empty the object map y.clear(); // empty the object map
print(y.len()); // prints 0 y.len() == 0;
```
### Parsing from JSON
The syntax for an object map is extremely similar to JSON, with the exception of `null` values which can
technically be mapped to [`()`]. A valid JSON string does not start with a hash character `#` while a
Rhai object map does - that's the major difference!
JSON numbers are all floating-point while Rhai supports integers (`INT`) and floating-point (`FLOAT`) if
the [`no_float`] feature is not turned on. Most common generators of JSON data distinguish between
integer and floating-point values by always serializing a floating-point number with a decimal point
(i.e. `123.0` instead of `123` which is assumed to be an integer). This style can be used successfully
with Rhai object maps.
Use the `parse_json` method to parse a piece of JSON into an object map:
```rust
// JSON string - notice that JSON property names are always quoted
// notice also that comments are acceptable within the JSON string
let json = r#"{
"a": 1, // <- this is an integer number
"b": true,
"c": 123.0, // <- this is a floating-point number
"$d e f!": "hello", // <- any text can be a property name
"^^^!!!": [1,42,"999"], // <- value can be array or another hash
"z": null // <- JSON 'null' value
}
"#;
// Parse the JSON expression as an object map
// Set the second boolean parameter to true in order to map 'null' to '()'
let map = engine.parse_json(json, true)?;
map.len() == 6; // 'map' contains all properties int the JSON string
// Put the object map into a 'Scope'
let mut scope = Scope::new();
scope.push("map", map);
let result = engine.eval_with_scope::<INT>(r#"map["^^^!!!"].len()"#)?;
result == 3; // the object map is successfully used in the script
```
`timestamp`'s
-------------
[`timestamp`]: #timestamp-s
Timestamps are provided by the standard library (excluded if using a [raw `Engine`]) via the `timestamp`
function.
The Rust type of a timestamp is `std::time::Instant`. [`type_of()`] a timestamp returns `"timestamp"`.
### Built-in functions
The following methods (defined in the standard library but excluded if using a [raw `Engine`]) operate on timestamps:
| Function | Parameter(s) | Description |
| ------------ | ---------------------------------- | -------------------------------------------------------- |
| `elapsed` | _none_ | returns the number of seconds since the timestamp |
| `-` operator | later timestamp, earlier timestamp | returns the number of seconds between the two timestamps |
### Examples
```rust
let now = timestamp();
// Do some lengthy operation...
if now.elapsed() > 30.0 {
print("takes too long (over 30 seconds)!")
}
``` ```
Comparison operators Comparison operators
@ -1262,8 +1484,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 types - However, if using a [raw `Engine`], comparisons can only be made between restricted system types -
`INT` (`i64` or `i32` depending on [`only_i32`] and [`only_i64`]), `f64` (if not [`no_float`]), string, array, `bool`, `char`. `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
@ -1321,7 +1543,7 @@ number <<= 2; // number = number << 2
number >>= 1; // number = number >> 1 number >>= 1; // number = number >> 1
``` ```
The `+=` operator can also be used to build strings: The `+=` operator can also be used to build [strings]:
```rust ```rust
let my_str = "abc"; let my_str = "abc";
@ -1359,10 +1581,11 @@ if (decision) print("I've decided!");
Like Rust, `if` statements can also be used as _expressions_, replacing the `? :` conditional operators in other C-like languages. Like Rust, `if` statements can also be used as _expressions_, replacing the `? :` conditional operators in other C-like languages.
```rust ```rust
let x = 1 + if true { 42 } else { 123 } / 2; // The following is equivalent to C: int x = 1 + (decision ? 42 : 123) / 2;
let x = 1 + if decision { 42 } else { 123 } / 2;
x == 22; x == 22;
let x = if false { 42 }; // No else branch defaults to '()' let x = if decision { 42 }; // no else branch defaults to '()'
x == (); x == ();
``` ```
@ -1397,7 +1620,7 @@ loop {
`for` loops `for` loops
----------- -----------
Iterating through a range or an array is provided by the `for` ... `in` loop. Iterating through a range or an [array] is provided by the `for` ... `in` loop.
```rust ```rust
let array = [1, 3, 5, 7, 9, 42]; let array = [1, 3, 5, 7, 9, 42];
@ -1416,7 +1639,6 @@ for x in range(0, 50) {
if x == 42 { break; } // break out of for loop if x == 42 { break; } // break out of for loop
} }
// The 'range' function also takes a step // The 'range' function also takes a step
for x in range(0, 50, 3) { // step by 3 for x in range(0, 50, 3) { // step by 3
if x > 10 { continue; } // skip to the next iteration if x > 10 { continue; } // skip to the next iteration
@ -1424,15 +1646,20 @@ for x in range(0, 50, 3) { // step by 3
if x == 42 { break; } // break out of for loop if x == 42 { break; } // break out of for loop
} }
// Iterate through the values of an object map // Iterate through object map
let map = #{a:1, b:3, c:5, d:7, e:9}; let map = #{a:1, b:3, c:5, d:7, e:9};
// Remember that keys are returned in random order // Property names are returned in random order
for x in keys(map) { for x in keys(map) {
if x > 10 { continue; } // skip to the next iteration if x > 10 { continue; } // skip to the next iteration
print(x); print(x);
if x == 42 { break; } // break out of for loop if x == 42 { break; } // break out of for loop
} }
// Property values are returned in random order
for val in values(map) {
print(val);
}
``` ```
`return`-ing values `return`-ing values
@ -1553,6 +1780,10 @@ fn do_addition(x) {
} }
``` ```
Unlike C/C++, functions can be defined _anywhere_ within the global level. A function does not need to be defined
prior to being used in a script; a statement in the script can freely call a function defined afterwards.
This is similar to Rust and many other modern languages.
### Functions overloading ### Functions overloading
Functions can be _overloaded_ and are resolved purely upon the function's _name_ and the _number_ of parameters Functions can be _overloaded_ and are resolved purely upon the function's _name_ and the _number_ of parameters
@ -1744,6 +1975,19 @@ An [`Engine`]'s optimization level is set via a call to `set_optimization_level`
engine.set_optimization_level(rhai::OptimizationLevel::Full); engine.set_optimization_level(rhai::OptimizationLevel::Full);
``` ```
If it is ever needed to _re_-optimize an `AST`, use the `optimize_ast` method:
```rust
// Compile script to AST
let ast = engine.compile("40 + 2")?;
// Create a new 'Scope' - put constants in it to aid optimization if using 'OptimizationLevel::Full'
let scope = Scope::new();
// Re-optimize the AST
let ast = engine.optimize_ast(&scope, &ast, OptimizationLevel::Full);
```
When the optimization level is [`OptimizationLevel::Full`], the [`Engine`] assumes all functions to be _pure_ and will _eagerly_ When the optimization level is [`OptimizationLevel::Full`], the [`Engine`] assumes all functions to be _pure_ and will _eagerly_
evaluated all function calls with constant arguments, using the result to replace the call. This also applies to all operators evaluated all function calls with constant arguments, using the result to replace the call. This also applies to all operators
(which are implemented as functions). For instance, the same example above: (which are implemented as functions). For instance, the same example above:
@ -1874,7 +2118,8 @@ print("z = " + z); // <- error: variable 'z' not found
Script segments passed to `eval` execute inside the current [`Scope`], so they can access and modify _everything_, Script segments passed to `eval` execute inside the current [`Scope`], so they can access and modify _everything_,
including all variables that are visible at that position in code! It is almost as if the script segments were including all variables that are visible at that position in code! It is almost as if the script segments were
physically pasted in at the position of the `eval` call. physically pasted in at the position of the `eval` call. But because of this, new functions cannot be defined
within an `eval` call, since functions can only be defined at the global level, not inside a function call!
```rust ```rust
let script = "x += 32"; let script = "x += 32";

View File

@ -1,7 +1,7 @@
use rhai::{Engine, EvalAltResult, INT}; use rhai::{Engine, EvalAltResult, INT};
fn main() -> Result<(), EvalAltResult> { fn main() -> Result<(), EvalAltResult> {
let mut engine = Engine::new(); let engine = Engine::new();
let result = engine.eval::<INT>("40 + 2")?; let result = engine.eval::<INT>("40 + 2")?;

View File

@ -3,7 +3,7 @@
use rhai::{Engine, EvalAltResult, INT}; use rhai::{Engine, EvalAltResult, INT};
fn main() -> Result<(), EvalAltResult> { fn main() -> Result<(), EvalAltResult> {
let mut engine = Engine::new(); let engine = Engine::new();
let result = engine.eval::<INT>("40 + 2")?; let result = engine.eval::<INT>("40 + 2")?;

View File

@ -145,9 +145,7 @@ fn main() {
#[cfg(not(feature = "no_optimize"))] #[cfg(not(feature = "no_optimize"))]
{ {
engine.set_optimization_level(OptimizationLevel::Full); ast = engine.optimize_ast(&scope, r, OptimizationLevel::Full);
ast = engine.optimize_ast(&scope, r);
engine.set_optimization_level(OptimizationLevel::None);
} }
#[cfg(feature = "no_optimize")] #[cfg(feature = "no_optimize")]

View File

@ -1,7 +1,7 @@
use rhai::{Engine, EvalAltResult, Scope, INT}; use rhai::{Engine, EvalAltResult, Scope, INT};
fn main() -> Result<(), EvalAltResult> { fn main() -> Result<(), EvalAltResult> {
let mut engine = Engine::new(); let engine = Engine::new();
let mut scope = Scope::new(); let mut scope = Scope::new();
engine.eval_with_scope::<()>(&mut scope, "let x = 4 + 5")?; engine.eval_with_scope::<()>(&mut scope, "let x = 4 + 5")?;

View File

@ -1,5 +1,7 @@
// This script uses the Sieve of Eratosthenes to calculate prime numbers. // This script uses the Sieve of Eratosthenes to calculate prime numbers.
let now = timestamp();
const MAX_NUMBER_TO_CHECK = 10_000; // 1229 primes <= 10000 const MAX_NUMBER_TO_CHECK = 10_000; // 1229 primes <= 10000
let prime_mask = []; let prime_mask = [];
@ -24,3 +26,4 @@ for p in range(2, MAX_NUMBER_TO_CHECK) {
} }
print("Total " + total_primes_found + " primes."); print("Total " + total_primes_found + " primes.");
print("Run time = " + now.elapsed() + " seconds.");

View File

@ -1,6 +1,7 @@
// This script runs 1 million iterations // This script runs 1 million iterations
// to test the speed of the scripting engine. // to test the speed of the scripting engine.
let now = timestamp();
let x = 1_000_000; let x = 1_000_000;
print("Ready... Go!"); print("Ready... Go!");
@ -9,4 +10,4 @@ while x > 0 {
x = x - 1; x = x - 1;
} }
print("Finished."); print("Finished. Run time = " + now.elapsed() + " seconds.");

View File

@ -1,17 +1,15 @@
//! Module that defines the extern API of `Engine`. //! Module that defines the extern API of `Engine`.
use crate::any::{Any, AnyExt, Dynamic}; use crate::any::{Any, AnyExt, Dynamic};
use crate::call::FuncArgs; use crate::engine::{make_getter, make_setter, Engine, FnAny, FnSpec, Map};
use crate::engine::{make_getter, make_setter, Engine, FnAny, FnSpec};
use crate::error::ParseError; use crate::error::ParseError;
use crate::fn_call::FuncArgs;
use crate::fn_register::RegisterFn; use crate::fn_register::RegisterFn;
use crate::optimize::{optimize_into_ast, OptimizationLevel};
use crate::parser::{lex, parse, parse_global_expr, 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;
#[cfg(not(feature = "no_optimize"))]
use crate::optimize::optimize_into_ast;
use crate::stdlib::{ use crate::stdlib::{
any::{type_name, TypeId}, any::{type_name, TypeId},
boxed::Box, boxed::Box,
@ -334,7 +332,7 @@ impl<'e> Engine<'e> {
/// # fn main() -> Result<(), rhai::EvalAltResult> { /// # fn main() -> Result<(), rhai::EvalAltResult> {
/// use rhai::Engine; /// use rhai::Engine;
/// ///
/// let mut engine = Engine::new(); /// let engine = Engine::new();
/// ///
/// // Compile a script to an AST and store it for later evaluation /// // Compile a script to an AST and store it for later evaluation
/// let ast = engine.compile("40 + 2")?; /// let ast = engine.compile("40 + 2")?;
@ -345,8 +343,8 @@ impl<'e> Engine<'e> {
/// # Ok(()) /// # Ok(())
/// # } /// # }
/// ``` /// ```
pub fn compile(&self, input: &str) -> Result<AST, ParseError> { pub fn compile(&self, script: &str) -> Result<AST, ParseError> {
self.compile_with_scope(&Scope::new(), input) self.compile_with_scope(&Scope::new(), script)
} }
/// Compile a string into an `AST` using own scope, which can be used later for evaluation. /// Compile a string into an `AST` using own scope, which can be used later for evaluation.
@ -386,9 +384,20 @@ impl<'e> Engine<'e> {
/// # Ok(()) /// # Ok(())
/// # } /// # }
/// ``` /// ```
pub fn compile_with_scope(&self, scope: &Scope, input: &str) -> Result<AST, ParseError> { pub fn compile_with_scope(&self, scope: &Scope, script: &str) -> Result<AST, ParseError> {
let tokens_stream = lex(input); self.compile_with_scope_and_optimization_level(scope, script, self.optimization_level)
parse(&mut tokens_stream.peekable(), self, scope) }
/// Compile a string into an `AST` using own scope at a specific optimization level.
pub(crate) fn compile_with_scope_and_optimization_level(
&self,
scope: &Scope,
script: &str,
optimization_level: OptimizationLevel,
) -> Result<AST, ParseError> {
let scripts = [script];
let stream = lex(&scripts);
parse(&mut stream.peekable(), self, scope, optimization_level)
} }
/// Read the contents of a file into a string. /// Read the contents of a file into a string.
@ -413,7 +422,7 @@ impl<'e> Engine<'e> {
/// # fn main() -> Result<(), rhai::EvalAltResult> { /// # fn main() -> Result<(), rhai::EvalAltResult> {
/// use rhai::Engine; /// use rhai::Engine;
/// ///
/// let mut engine = Engine::new(); /// let engine = Engine::new();
/// ///
/// // Compile a script file to an AST and store it for later evaluation. /// // Compile a script file to an AST and store it for later evaluation.
/// // Notice that a PathBuf is required which can easily be constructed from a string. /// // Notice that a PathBuf is required which can easily be constructed from a string.
@ -466,10 +475,52 @@ impl<'e> Engine<'e> {
scope: &Scope, scope: &Scope,
path: PathBuf, path: PathBuf,
) -> Result<AST, EvalAltResult> { ) -> Result<AST, EvalAltResult> {
Self::read_file(path).and_then(|contents| { Self::read_file(path).and_then(|contents| Ok(self.compile_with_scope(scope, &contents)?))
self.compile_with_scope(scope, &contents) }
.map_err(|err| err.into())
}) /// Parse a JSON string into a map.
///
/// Set `has_null` to `true` in order to map `null` values to `()`.
/// Setting it to `false` will cause a _variable not found_ error during parsing.
///
/// # Example
///
/// ```
/// # fn main() -> Result<(), rhai::EvalAltResult> {
/// use rhai::{Engine, AnyExt};
///
/// let engine = Engine::new();
///
/// let map = engine.parse_json(r#"{"a":123, "b":42, "c":false, "d":null}"#, true)?;
///
/// assert_eq!(map.len(), 4);
/// assert_eq!(map.get("a").cloned().unwrap().cast::<i64>(), 123);
/// assert_eq!(map.get("b").cloned().unwrap().cast::<i64>(), 42);
/// assert_eq!(map.get("c").cloned().unwrap().cast::<bool>(), false);
/// assert_eq!(map.get("d").cloned().unwrap().cast::<()>(), ());
/// # Ok(())
/// # }
/// ```
#[cfg(not(feature = "no_object"))]
pub fn parse_json(&self, json: &str, has_null: bool) -> Result<Map, EvalAltResult> {
let mut scope = Scope::new();
// Trims the JSON string and add a '#' in front
let scripts = ["#", json.trim()];
let stream = lex(&scripts);
let ast = parse_global_expr(
&mut stream.peekable(),
self,
&scope,
OptimizationLevel::None,
)?;
// Handle null - map to ()
if has_null {
scope.push_constant("null", ());
}
self.eval_ast_with_scope(&mut scope, &ast)
} }
/// Compile a string containing an expression into an `AST`, /// Compile a string containing an expression into an `AST`,
@ -481,7 +532,7 @@ impl<'e> Engine<'e> {
/// # fn main() -> Result<(), rhai::EvalAltResult> { /// # fn main() -> Result<(), rhai::EvalAltResult> {
/// use rhai::Engine; /// use rhai::Engine;
/// ///
/// let mut engine = Engine::new(); /// let engine = Engine::new();
/// ///
/// // Compile a script to an AST and store it for later evaluation /// // Compile a script to an AST and store it for later evaluation
/// let ast = engine.compile_expression("40 + 2")?; /// let ast = engine.compile_expression("40 + 2")?;
@ -492,8 +543,8 @@ impl<'e> Engine<'e> {
/// # Ok(()) /// # Ok(())
/// # } /// # }
/// ``` /// ```
pub fn compile_expression(&self, input: &str) -> Result<AST, ParseError> { pub fn compile_expression(&self, script: &str) -> Result<AST, ParseError> {
self.compile_expression_with_scope(&Scope::new(), input) self.compile_expression_with_scope(&Scope::new(), script)
} }
/// Compile a string containing an expression into an `AST` using own scope, /// Compile a string containing an expression into an `AST` using own scope,
@ -538,10 +589,11 @@ impl<'e> Engine<'e> {
pub fn compile_expression_with_scope( pub fn compile_expression_with_scope(
&self, &self,
scope: &Scope, scope: &Scope,
input: &str, script: &str,
) -> Result<AST, ParseError> { ) -> Result<AST, ParseError> {
let tokens_stream = lex(input); let scripts = [script];
parse_global_expr(&mut tokens_stream.peekable(), self, scope) let stream = lex(&scripts);
parse_global_expr(&mut stream.peekable(), self, scope, self.optimization_level)
} }
/// Evaluate a script file. /// Evaluate a script file.
@ -552,7 +604,7 @@ impl<'e> Engine<'e> {
/// # fn main() -> Result<(), rhai::EvalAltResult> { /// # fn main() -> Result<(), rhai::EvalAltResult> {
/// use rhai::Engine; /// use rhai::Engine;
/// ///
/// let mut engine = Engine::new(); /// let engine = Engine::new();
/// ///
/// // Notice that a PathBuf is required which can easily be constructed from a string. /// // Notice that a PathBuf is required which can easily be constructed from a string.
/// let result = engine.eval_file::<i64>("script.rhai".into())?; /// let result = engine.eval_file::<i64>("script.rhai".into())?;
@ -560,7 +612,7 @@ impl<'e> Engine<'e> {
/// # } /// # }
/// ``` /// ```
#[cfg(not(feature = "no_std"))] #[cfg(not(feature = "no_std"))]
pub fn eval_file<T: Any + Clone>(&mut self, path: PathBuf) -> Result<T, EvalAltResult> { pub fn eval_file<T: Any + Clone>(&self, path: PathBuf) -> Result<T, EvalAltResult> {
Self::read_file(path).and_then(|contents| self.eval::<T>(&contents)) Self::read_file(path).and_then(|contents| self.eval::<T>(&contents))
} }
@ -572,7 +624,7 @@ impl<'e> Engine<'e> {
/// # fn main() -> Result<(), rhai::EvalAltResult> { /// # fn main() -> Result<(), rhai::EvalAltResult> {
/// use rhai::{Engine, Scope}; /// use rhai::{Engine, Scope};
/// ///
/// let mut engine = Engine::new(); /// let engine = Engine::new();
/// ///
/// // Create initialized scope /// // Create initialized scope
/// let mut scope = Scope::new(); /// let mut scope = Scope::new();
@ -585,7 +637,7 @@ impl<'e> Engine<'e> {
/// ``` /// ```
#[cfg(not(feature = "no_std"))] #[cfg(not(feature = "no_std"))]
pub fn eval_file_with_scope<T: Any + Clone>( pub fn eval_file_with_scope<T: Any + Clone>(
&mut self, &self,
scope: &mut Scope, scope: &mut Scope,
path: PathBuf, path: PathBuf,
) -> Result<T, EvalAltResult> { ) -> Result<T, EvalAltResult> {
@ -600,14 +652,14 @@ impl<'e> Engine<'e> {
/// # fn main() -> Result<(), rhai::EvalAltResult> { /// # fn main() -> Result<(), rhai::EvalAltResult> {
/// use rhai::Engine; /// use rhai::Engine;
/// ///
/// let mut engine = Engine::new(); /// let engine = Engine::new();
/// ///
/// assert_eq!(engine.eval::<i64>("40 + 2")?, 42); /// assert_eq!(engine.eval::<i64>("40 + 2")?, 42);
/// # Ok(()) /// # Ok(())
/// # } /// # }
/// ``` /// ```
pub fn eval<T: Any + Clone>(&mut self, input: &str) -> Result<T, EvalAltResult> { pub fn eval<T: Any + Clone>(&self, script: &str) -> Result<T, EvalAltResult> {
self.eval_with_scope(&mut Scope::new(), input) self.eval_with_scope(&mut Scope::new(), script)
} }
/// Evaluate a string with own scope. /// Evaluate a string with own scope.
@ -618,7 +670,7 @@ impl<'e> Engine<'e> {
/// # fn main() -> Result<(), rhai::EvalAltResult> { /// # fn main() -> Result<(), rhai::EvalAltResult> {
/// use rhai::{Engine, Scope}; /// use rhai::{Engine, Scope};
/// ///
/// let mut engine = Engine::new(); /// let engine = Engine::new();
/// ///
/// // Create initialized scope /// // Create initialized scope
/// let mut scope = Scope::new(); /// let mut scope = Scope::new();
@ -633,11 +685,13 @@ impl<'e> Engine<'e> {
/// # } /// # }
/// ``` /// ```
pub fn eval_with_scope<T: Any + Clone>( pub fn eval_with_scope<T: Any + Clone>(
&mut self, &self,
scope: &mut Scope, scope: &mut Scope,
input: &str, script: &str,
) -> Result<T, EvalAltResult> { ) -> Result<T, EvalAltResult> {
let ast = self.compile(input).map_err(EvalAltResult::ErrorParsing)?; // Since the AST will be thrown away afterwards, don't bother to optimize it
let ast =
self.compile_with_scope_and_optimization_level(scope, script, OptimizationLevel::None)?;
self.eval_ast_with_scope(scope, &ast) self.eval_ast_with_scope(scope, &ast)
} }
@ -649,14 +703,14 @@ impl<'e> Engine<'e> {
/// # fn main() -> Result<(), rhai::EvalAltResult> { /// # fn main() -> Result<(), rhai::EvalAltResult> {
/// use rhai::Engine; /// use rhai::Engine;
/// ///
/// let mut engine = Engine::new(); /// let engine = Engine::new();
/// ///
/// assert_eq!(engine.eval_expression::<i64>("40 + 2")?, 42); /// assert_eq!(engine.eval_expression::<i64>("40 + 2")?, 42);
/// # Ok(()) /// # Ok(())
/// # } /// # }
/// ``` /// ```
pub fn eval_expression<T: Any + Clone>(&mut self, input: &str) -> Result<T, EvalAltResult> { pub fn eval_expression<T: Any + Clone>(&self, script: &str) -> Result<T, EvalAltResult> {
self.eval_expression_with_scope(&mut Scope::new(), input) self.eval_expression_with_scope(&mut Scope::new(), script)
} }
/// Evaluate a string containing an expression with own scope. /// Evaluate a string containing an expression with own scope.
@ -667,7 +721,7 @@ impl<'e> Engine<'e> {
/// # fn main() -> Result<(), rhai::EvalAltResult> { /// # fn main() -> Result<(), rhai::EvalAltResult> {
/// use rhai::{Engine, Scope}; /// use rhai::{Engine, Scope};
/// ///
/// let mut engine = Engine::new(); /// let engine = Engine::new();
/// ///
/// // Create initialized scope /// // Create initialized scope
/// let mut scope = Scope::new(); /// let mut scope = Scope::new();
@ -678,14 +732,14 @@ impl<'e> Engine<'e> {
/// # } /// # }
/// ``` /// ```
pub fn eval_expression_with_scope<T: Any + Clone>( pub fn eval_expression_with_scope<T: Any + Clone>(
&mut self, &self,
scope: &mut Scope, scope: &mut Scope,
input: &str, script: &str,
) -> Result<T, EvalAltResult> { ) -> Result<T, EvalAltResult> {
let ast = self let scripts = [script];
.compile_expression(input) let stream = lex(&scripts);
.map_err(EvalAltResult::ErrorParsing)?; // Since the AST will be thrown away afterwards, don't bother to optimize it
let ast = parse_global_expr(&mut stream.peekable(), self, scope, OptimizationLevel::None)?;
self.eval_ast_with_scope(scope, &ast) self.eval_ast_with_scope(scope, &ast)
} }
@ -697,7 +751,7 @@ impl<'e> Engine<'e> {
/// # fn main() -> Result<(), rhai::EvalAltResult> { /// # fn main() -> Result<(), rhai::EvalAltResult> {
/// use rhai::Engine; /// use rhai::Engine;
/// ///
/// let mut engine = Engine::new(); /// let engine = Engine::new();
/// ///
/// // Compile a script to an AST and store it for later evaluation /// // Compile a script to an AST and store it for later evaluation
/// let ast = engine.compile("40 + 2")?; /// let ast = engine.compile("40 + 2")?;
@ -707,7 +761,7 @@ impl<'e> Engine<'e> {
/// # Ok(()) /// # Ok(())
/// # } /// # }
/// ``` /// ```
pub fn eval_ast<T: Any + Clone>(&mut self, ast: &AST) -> Result<T, EvalAltResult> { pub fn eval_ast<T: Any + Clone>(&self, ast: &AST) -> Result<T, EvalAltResult> {
self.eval_ast_with_scope(&mut Scope::new(), ast) self.eval_ast_with_scope(&mut Scope::new(), ast)
} }
@ -719,7 +773,7 @@ impl<'e> Engine<'e> {
/// # fn main() -> Result<(), rhai::EvalAltResult> { /// # fn main() -> Result<(), rhai::EvalAltResult> {
/// use rhai::{Engine, Scope}; /// use rhai::{Engine, Scope};
/// ///
/// let mut engine = Engine::new(); /// let engine = Engine::new();
/// ///
/// // Compile a script to an AST and store it for later evaluation /// // Compile a script to an AST and store it for later evaluation
/// let ast = engine.compile("x + 2")?; /// let ast = engine.compile("x + 2")?;
@ -741,7 +795,7 @@ impl<'e> Engine<'e> {
/// # } /// # }
/// ``` /// ```
pub fn eval_ast_with_scope<T: Any + Clone>( pub fn eval_ast_with_scope<T: Any + Clone>(
&mut self, &self,
scope: &mut Scope, scope: &mut Scope,
ast: &AST, ast: &AST,
) -> Result<T, EvalAltResult> { ) -> Result<T, EvalAltResult> {
@ -756,23 +810,16 @@ impl<'e> Engine<'e> {
} }
pub(crate) fn eval_ast_with_scope_raw( pub(crate) fn eval_ast_with_scope_raw(
&mut self, &self,
scope: &mut Scope, scope: &mut Scope,
ast: &AST, ast: &AST,
) -> Result<Dynamic, EvalAltResult> { ) -> Result<Dynamic, EvalAltResult> {
let statements = { ast.0
let AST(statements, functions) = ast;
self.fn_lib = Some(functions.clone());
statements
};
let result = statements
.iter() .iter()
.try_fold(().into_dynamic(), |_, stmt| self.eval_stmt(scope, stmt, 0)); .try_fold(().into_dynamic(), |_, stmt| {
self.eval_stmt(scope, Some(ast.1.as_ref()), stmt, 0)
self.fn_lib = None; })
.or_else(|err| match err {
result.or_else(|err| match err {
EvalAltResult::Return(out, _) => Ok(out), EvalAltResult::Return(out, _) => Ok(out),
_ => Err(err), _ => Err(err),
}) })
@ -781,7 +828,7 @@ 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.
#[cfg(not(feature = "no_std"))] #[cfg(not(feature = "no_std"))]
pub fn consume_file(&mut self, path: PathBuf) -> Result<(), EvalAltResult> { pub fn consume_file(&self, path: PathBuf) -> Result<(), EvalAltResult> {
Self::read_file(path).and_then(|contents| self.consume(&contents)) Self::read_file(path).and_then(|contents| self.consume(&contents))
} }
@ -789,7 +836,7 @@ impl<'e> Engine<'e> {
/// 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.
#[cfg(not(feature = "no_std"))] #[cfg(not(feature = "no_std"))]
pub fn consume_file_with_scope( pub fn consume_file_with_scope(
&mut self, &self,
scope: &mut Scope, scope: &mut Scope,
path: PathBuf, path: PathBuf,
) -> Result<(), EvalAltResult> { ) -> Result<(), EvalAltResult> {
@ -798,191 +845,102 @@ impl<'e> Engine<'e> {
/// Evaluate a string, but throw away the result and only return error (if any). /// Evaluate a string, but throw away the result and only return error (if any).
/// Useful for when you don't need the result, but still need to keep track of possible errors. /// Useful for when you don't need the result, but still need to keep track of possible errors.
pub fn consume(&mut self, input: &str) -> Result<(), EvalAltResult> { pub fn consume(&self, script: &str) -> Result<(), EvalAltResult> {
self.consume_with_scope(&mut Scope::new(), input) self.consume_with_scope(&mut Scope::new(), script)
} }
/// 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.
pub fn consume_with_scope( pub fn consume_with_scope(&self, scope: &mut Scope, script: &str) -> Result<(), EvalAltResult> {
&mut self, let scripts = [script];
scope: &mut Scope, let stream = lex(&scripts);
input: &str,
) -> Result<(), EvalAltResult> {
let tokens_stream = lex(input);
let ast = parse(&mut tokens_stream.peekable(), self, scope)
.map_err(EvalAltResult::ErrorParsing)?;
// Since the AST will be thrown away afterwards, don't bother to optimize it
let ast = parse(&mut stream.peekable(), self, scope, OptimizationLevel::None)?;
self.consume_ast_with_scope(scope, &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> { pub fn consume_ast(&self, ast: &AST) -> Result<(), EvalAltResult> {
self.consume_ast_with_scope(&mut Scope::new(), ast) self.consume_ast_with_scope(&mut Scope::new(), ast)
} }
/// Evaluate an `AST` with own scope, but throw away the result and only return error (if any). /// 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.
pub fn consume_ast_with_scope( pub fn consume_ast_with_scope(
&mut self, &self,
scope: &mut Scope, scope: &mut Scope,
ast: &AST, ast: &AST,
) -> Result<(), EvalAltResult> { ) -> Result<(), EvalAltResult> {
let statements = { ast.0
let AST(statements, functions) = ast;
self.fn_lib = Some(functions.clone());
statements
};
let result = statements
.iter() .iter()
.try_fold(().into_dynamic(), |_, stmt| self.eval_stmt(scope, stmt, 0)); .try_fold(().into_dynamic(), |_, stmt| {
self.eval_stmt(scope, Some(ast.1.as_ref()), stmt, 0)
self.fn_lib = None; })
.map(|_| ())
result.map(|_| ()).or_else(|err| match err { .or_else(|err| match err {
EvalAltResult::Return(_, _) => Ok(()), EvalAltResult::Return(_, _) => Ok(()),
_ => Err(err), _ => Err(err),
}) })
} }
/// Call a script function defined in an `AST` with no 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 num() { 42 + foo }")?;
///
/// let mut scope = Scope::new();
/// scope.push("foo", 42_i64);
///
/// // Call the script-defined function
/// let result: i64 = engine.call_fn0(&mut scope, &ast, "num")?;
///
/// 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. /// Call a script function defined in an `AST` with multiple arguments.
/// ///
/// # Example /// # Example
/// ///
/// ``` /// ```
/// # fn main() -> Result<(), rhai::EvalAltResult> { /// # fn main() -> Result<(), rhai::EvalAltResult> {
/// # #[cfg(not(feature = "no_stdlib"))]
/// # #[cfg(not(feature = "no_function"))] /// # #[cfg(not(feature = "no_function"))]
/// # { /// # {
/// use rhai::{Engine, Scope}; /// use rhai::{Engine, Scope};
/// ///
/// let mut engine = Engine::new(); /// let engine = Engine::new();
/// ///
/// let ast = engine.compile("fn add(x, y) { len(x) + y + foo }")?; /// let ast = engine.compile(r"
/// fn add(x, y) { len(x) + y + foo }
/// fn add1(x) { len(x) + 1 + foo }
/// fn bar() { foo/2 }
/// ")?;
/// ///
/// let mut scope = Scope::new(); /// let mut scope = Scope::new();
/// scope.push("foo", 42_i64); /// scope.push("foo", 42_i64);
/// ///
/// // Call the script-defined function /// // Call the script-defined function
/// let result: i64 = engine.call_fn(&mut scope, &ast, "add", (String::from("abc"), 123_i64))?; /// let result: i64 = engine.call_fn(&mut scope, &ast, "add", ( String::from("abc"), 123_i64 ) )?;
///
/// assert_eq!(result, 168); /// assert_eq!(result, 168);
///
/// let result: i64 = engine.call_fn(&mut scope, &ast, "add1", ( String::from("abc"), ) )?;
/// // ^^^^^^^^^^^^^^^^^^^^^^^^ tuple of one
/// assert_eq!(result, 46);
///
/// let result: i64 = engine.call_fn(&mut scope, &ast, "bar", () )?;
/// assert_eq!(result, 21);
/// # } /// # }
/// # Ok(()) /// # Ok(())
/// # } /// # }
/// ``` /// ```
#[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, &self,
scope: &mut Scope, scope: &mut Scope,
ast: &AST, ast: &AST,
name: &str, name: &str,
args: A, args: A,
) -> Result<T, EvalAltResult> { ) -> Result<T, EvalAltResult> {
self.call_fn_internal(scope, ast, name, args.into_vec()) let mut arg_values = args.into_vec();
}
#[cfg(not(feature = "no_function"))]
fn call_fn_internal<T: Any + Clone>(
&mut self,
scope: &mut Scope,
ast: &AST,
name: &str,
mut arg_values: Vec<Dynamic>,
) -> Result<T, EvalAltResult> {
let mut args: Vec<_> = arg_values.iter_mut().map(Dynamic::as_mut).collect(); let mut args: Vec<_> = arg_values.iter_mut().map(Dynamic::as_mut).collect();
let fn_lib = Some(ast.1.as_ref());
let pos = Position::none();
self.fn_lib = Some(ast.1.clone()); self.call_fn_raw(Some(scope), fn_lib, name, &mut args, None, pos, 0)?
let result = self
.call_fn_raw(Some(scope), name, &mut args, None, Position::none(), 0)?
.try_cast() .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(), pos,
) )
}); })
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.
@ -997,13 +955,14 @@ 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(
optimize_into_ast( &self,
self, scope: &Scope,
scope, ast: AST,
ast.0, optimization_level: OptimizationLevel,
ast.1.iter().map(|fn_def| fn_def.as_ref().clone()).collect(), ) -> AST {
) let fn_lib = ast.1.iter().map(|fn_def| fn_def.as_ref().clone()).collect();
optimize_into_ast(self, scope, ast.0, fn_lib, optimization_level)
} }
/// Override default action of `print` (print to stdout using `println!`) /// Override default action of `print` (print to stdout using `println!`)
@ -1012,22 +971,24 @@ impl<'e> Engine<'e> {
/// ///
/// ``` /// ```
/// # fn main() -> Result<(), rhai::EvalAltResult> { /// # fn main() -> Result<(), rhai::EvalAltResult> {
/// # use std::sync::RwLock;
/// use rhai::Engine; /// use rhai::Engine;
/// ///
/// let mut result = String::from(""); /// let result = RwLock::new(String::from(""));
/// { /// {
/// let mut engine = Engine::new(); /// let mut engine = Engine::new();
/// ///
/// // Override action of 'print' function /// // Override action of 'print' function
/// engine.on_print(|s| result.push_str(s)); /// engine.on_print(|s| result.write().unwrap().push_str(s));
///
/// engine.consume("print(40 + 2);")?; /// engine.consume("print(40 + 2);")?;
/// } /// }
/// assert_eq!(result, "42"); /// assert_eq!(*result.read().unwrap(), "42");
/// # Ok(()) /// # Ok(())
/// # } /// # }
/// ``` /// ```
#[cfg(feature = "sync")] #[cfg(feature = "sync")]
pub fn on_print(&mut self, callback: impl FnMut(&str) + Send + Sync + 'e) { pub fn on_print(&mut self, callback: impl Fn(&str) + Send + Sync + 'e) {
self.on_print = Some(Box::new(callback)); self.on_print = Some(Box::new(callback));
} }
/// Override default action of `print` (print to stdout using `println!`) /// Override default action of `print` (print to stdout using `println!`)
@ -1036,22 +997,24 @@ impl<'e> Engine<'e> {
/// ///
/// ``` /// ```
/// # fn main() -> Result<(), rhai::EvalAltResult> { /// # fn main() -> Result<(), rhai::EvalAltResult> {
/// # use std::sync::RwLock;
/// use rhai::Engine; /// use rhai::Engine;
/// ///
/// let mut result = String::from(""); /// let result = RwLock::new(String::from(""));
/// { /// {
/// let mut engine = Engine::new(); /// let mut engine = Engine::new();
/// ///
/// // Override action of 'print' function /// // Override action of 'print' function
/// engine.on_print(|s| result.push_str(s)); /// engine.on_print(|s| result.write().unwrap().push_str(s));
///
/// engine.consume("print(40 + 2);")?; /// engine.consume("print(40 + 2);")?;
/// } /// }
/// assert_eq!(result, "42"); /// assert_eq!(*result.read().unwrap(), "42");
/// # Ok(()) /// # Ok(())
/// # } /// # }
/// ``` /// ```
#[cfg(not(feature = "sync"))] #[cfg(not(feature = "sync"))]
pub fn on_print(&mut self, callback: impl FnMut(&str) + 'e) { pub fn on_print(&mut self, callback: impl Fn(&str) + 'e) {
self.on_print = Some(Box::new(callback)); self.on_print = Some(Box::new(callback));
} }
@ -1061,22 +1024,24 @@ impl<'e> Engine<'e> {
/// ///
/// ``` /// ```
/// # fn main() -> Result<(), rhai::EvalAltResult> { /// # fn main() -> Result<(), rhai::EvalAltResult> {
/// # use std::sync::RwLock;
/// use rhai::Engine; /// use rhai::Engine;
/// ///
/// let mut result = String::from(""); /// let result = RwLock::new(String::from(""));
/// { /// {
/// let mut engine = Engine::new(); /// let mut engine = Engine::new();
/// ///
/// // Override action of 'debug' function /// // Override action of 'print' function
/// engine.on_debug(|s| result.push_str(s)); /// engine.on_debug(|s| result.write().unwrap().push_str(s));
///
/// engine.consume(r#"debug("hello");"#)?; /// engine.consume(r#"debug("hello");"#)?;
/// } /// }
/// assert_eq!(result, "\"hello\""); /// assert_eq!(*result.read().unwrap(), r#""hello""#);
/// # Ok(()) /// # Ok(())
/// # } /// # }
/// ``` /// ```
#[cfg(feature = "sync")] #[cfg(feature = "sync")]
pub fn on_debug(&mut self, callback: impl FnMut(&str) + Send + Sync + 'e) { pub fn on_debug(&mut self, callback: impl Fn(&str) + Send + Sync + 'e) {
self.on_debug = Some(Box::new(callback)); self.on_debug = Some(Box::new(callback));
} }
/// Override default action of `debug` (print to stdout using `println!`) /// Override default action of `debug` (print to stdout using `println!`)
@ -1085,22 +1050,24 @@ impl<'e> Engine<'e> {
/// ///
/// ``` /// ```
/// # fn main() -> Result<(), rhai::EvalAltResult> { /// # fn main() -> Result<(), rhai::EvalAltResult> {
/// # use std::sync::RwLock;
/// use rhai::Engine; /// use rhai::Engine;
/// ///
/// let mut result = String::from(""); /// let result = RwLock::new(String::from(""));
/// { /// {
/// let mut engine = Engine::new(); /// let mut engine = Engine::new();
/// ///
/// // Override action of 'debug' function /// // Override action of 'print' function
/// engine.on_debug(|s| result.push_str(s)); /// engine.on_debug(|s| result.write().unwrap().push_str(s));
///
/// engine.consume(r#"debug("hello");"#)?; /// engine.consume(r#"debug("hello");"#)?;
/// } /// }
/// assert_eq!(result, "\"hello\""); /// assert_eq!(*result.read().unwrap(), r#""hello""#);
/// # Ok(()) /// # Ok(())
/// # } /// # }
/// ``` /// ```
#[cfg(not(feature = "sync"))] #[cfg(not(feature = "sync"))]
pub fn on_debug(&mut self, callback: impl FnMut(&str) + 'e) { pub fn on_debug(&mut self, callback: impl Fn(&str) + 'e) {
self.on_debug = Some(Box::new(callback)); self.on_debug = Some(Box::new(callback));
} }
} }

View File

@ -27,6 +27,7 @@ use crate::stdlib::{
format, format,
ops::{Add, BitAnd, BitOr, BitXor, Div, Mul, Neg, Range, Rem, Shl, Shr, Sub}, ops::{Add, BitAnd, BitOr, BitXor, Div, Mul, Neg, Range, Rem, Shl, Shr, Sub},
string::{String, ToString}, string::{String, ToString},
time::Instant,
vec::Vec, vec::Vec,
{i32, i64, u32}, {i32, i64, u32},
}; };
@ -57,6 +58,34 @@ macro_rules! reg_op_result1 {
) )
} }
macro_rules! reg_cmp {
($self:expr, $x:expr, $op:expr, $( $y:ty ),*) => (
$(
$self.register_fn($x, $op as fn(x: $y, y: $y)->bool);
)*
)
}
// Comparison operators
fn lt<T: PartialOrd>(x: T, y: T) -> bool {
x < y
}
fn lte<T: PartialOrd>(x: T, y: T) -> bool {
x <= y
}
fn gt<T: PartialOrd>(x: T, y: T) -> bool {
x > y
}
fn gte<T: PartialOrd>(x: T, y: T) -> bool {
x >= y
}
fn eq<T: PartialEq>(x: T, y: T) -> bool {
x == y
}
fn ne<T: PartialEq>(x: T, y: T) -> bool {
x != y
}
impl Engine<'_> { impl Engine<'_> {
/// Register the core built-in library. /// Register the core built-in library.
pub(crate) fn register_core_lib(&mut self) { pub(crate) fn register_core_lib(&mut self) {
@ -176,26 +205,6 @@ impl Engine<'_> {
} }
} }
// Comparison operators
fn lt<T: PartialOrd>(x: T, y: T) -> bool {
x < y
}
fn lte<T: PartialOrd>(x: T, y: T) -> bool {
x <= y
}
fn gt<T: PartialOrd>(x: T, y: T) -> bool {
x > y
}
fn gte<T: PartialOrd>(x: T, y: T) -> bool {
x >= y
}
fn eq<T: PartialEq>(x: T, y: T) -> bool {
x == y
}
fn ne<T: PartialEq>(x: T, y: T) -> bool {
x != y
}
// Logic operators // Logic operators
fn and(x: bool, y: bool) -> bool { fn and(x: bool, y: bool) -> bool {
x && y x && y
@ -395,14 +404,6 @@ impl Engine<'_> {
} }
{ {
macro_rules! reg_cmp {
($self:expr, $x:expr, $op:expr, $( $y:ty ),*) => (
$(
$self.register_fn($x, $op as fn(x: $y, y: $y)->bool);
)*
)
}
reg_cmp!(self, "<", lt, INT, String, char); reg_cmp!(self, "<", lt, INT, String, char);
reg_cmp!(self, "<=", lte, INT, String, char); reg_cmp!(self, "<=", lte, INT, String, char);
reg_cmp!(self, ">", gt, INT, String, char); reg_cmp!(self, ">", gt, INT, String, char);
@ -433,7 +434,7 @@ impl Engine<'_> {
} }
// `&&` and `||` are treated specially as they short-circuit. // `&&` and `||` are treated specially as they short-circuit.
// They are implemented as special `Expr` instances, not function calls. // They are implemented as special `Expr` Instants, not function calls.
//reg_op!(self, "||", or, bool); //reg_op!(self, "||", or, bool);
//reg_op!(self, "&&", and, bool); //reg_op!(self, "&&", and, bool);
@ -620,23 +621,19 @@ impl Engine<'_> {
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
{ {
self.register_fn(KEYWORD_PRINT, |x: &mut Map| -> String { self.register_fn(KEYWORD_PRINT, |x: &mut Map| format!("#{:?}", x));
format!("#{:?}", x) self.register_fn(FUNC_TO_STRING, |x: &mut Map| format!("#{:?}", x));
}); self.register_fn(KEYWORD_DEBUG, |x: &mut Map| format!("#{:?}", x));
self.register_fn(FUNC_TO_STRING, |x: &mut Map| -> String {
format!("#{:?}", x)
});
self.register_fn(KEYWORD_DEBUG, |x: &mut Map| -> String {
format!("#{:?}", x)
});
// Register map access functions // Register map access functions
#[cfg(not(feature = "no_index"))]
self.register_fn("keys", |map: Map| { self.register_fn("keys", |map: Map| {
map.into_iter() map.into_iter()
.map(|(k, _)| k.into_dynamic()) .map(|(k, _)| k.into_dynamic())
.collect::<Vec<_>>() .collect::<Vec<_>>()
}); });
#[cfg(not(feature = "no_index"))]
self.register_fn("values", |map: Map| { self.register_fn("values", |map: Map| {
map.into_iter().map(|(_, v)| v).collect::<Vec<_>>() map.into_iter().map(|(_, v)| v).collect::<Vec<_>>()
}); });
@ -757,8 +754,7 @@ macro_rules! reg_fn2y {
/// Register the built-in library. /// Register the built-in library.
impl Engine<'_> { impl Engine<'_> {
#[cfg(not(feature = "no_stdlib"))] pub fn register_stdlib(&mut self) {
pub(crate) fn register_stdlib(&mut self) {
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
{ {
// Advanced math functions // Advanced math functions
@ -873,6 +869,15 @@ impl Engine<'_> {
fn push<T: Any>(list: &mut Array, item: T) { fn push<T: Any>(list: &mut Array, item: T) {
list.push(Box::new(item)); list.push(Box::new(item));
} }
fn ins<T: Any>(list: &mut Array, position: INT, item: T) {
if position <= 0 {
list.insert(0, Box::new(item));
} else if (position as usize) >= list.len() - 1 {
push(list, item);
} else {
list.insert(position as usize, Box::new(item));
}
}
fn pad<T: Any + Clone>(list: &mut Array, len: INT, item: T) { fn pad<T: Any + Clone>(list: &mut Array, len: INT, item: T) {
if len >= 0 { if len >= 0 {
while list.len() < len as usize { while list.len() < len as usize {
@ -885,6 +890,7 @@ impl Engine<'_> {
reg_fn2x!(self, "push", push, &mut Array, (), String, Array, ()); reg_fn2x!(self, "push", push, &mut Array, (), String, Array, ());
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, ());
reg_fn3!(self, "insert", ins, &mut Array, INT, (), String, Array, ());
self.register_fn("append", |list: &mut Array, array: Array| { self.register_fn("append", |list: &mut Array, array: Array| {
list.extend(array) list.extend(array)
@ -901,12 +907,15 @@ impl Engine<'_> {
reg_fn2x!(self, "push", push, &mut Array, (), i32, i64, u32, u64); reg_fn2x!(self, "push", push, &mut Array, (), i32, i64, u32, u64);
reg_fn3!(self, "pad", pad, &mut Array, INT, (), i8, u8, i16, u16); reg_fn3!(self, "pad", pad, &mut Array, INT, (), i8, u8, i16, u16);
reg_fn3!(self, "pad", pad, &mut Array, INT, (), i32, u32, i64, u64); reg_fn3!(self, "pad", pad, &mut Array, INT, (), i32, u32, i64, u64);
reg_fn3!(self, "insert", ins, &mut Array, INT, (), i8, u8, i16, u16);
reg_fn3!(self, "insert", ins, &mut Array, INT, (), i32, i64, u32, u64);
} }
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
{ {
reg_fn2x!(self, "push", push, &mut Array, (), f32, f64); reg_fn2x!(self, "push", push, &mut Array, (), f32, f64);
reg_fn3!(self, "pad", pad, &mut Array, INT, (), f32, f64); reg_fn3!(self, "pad", pad, &mut Array, INT, (), f32, f64);
reg_fn3!(self, "insert", ins, &mut Array, INT, (), f32, f64);
} }
self.register_dynamic_fn("pop", |list: &mut Array| { self.register_dynamic_fn("pop", |list: &mut Array| {
@ -919,6 +928,13 @@ impl Engine<'_> {
list.remove(0) list.remove(0)
} }
}); });
self.register_dynamic_fn("remove", |list: &mut Array, len: INT| {
if len < 0 || (len as usize) >= list.len() {
().into_dynamic()
} else {
list.remove(len as usize)
}
});
self.register_fn("len", |list: &mut Array| list.len() as INT); self.register_fn("len", |list: &mut Array| list.len() as INT);
self.register_fn("clear", |list: &mut Array| list.clear()); self.register_fn("clear", |list: &mut Array| list.clear());
self.register_fn("truncate", |list: &mut Array, len: INT| { self.register_fn("truncate", |list: &mut Array, len: INT| {
@ -934,6 +950,9 @@ 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_dynamic_fn("remove", |x: &mut Map, name: String| {
x.remove(&name).unwrap_or(().into_dynamic())
});
self.register_fn("mixin", |map1: &mut Map, map2: Map| { self.register_fn("mixin", |map1: &mut Map, map2: Map| {
map2.into_iter().for_each(|(key, value)| { map2.into_iter().for_each(|(key, value)| {
map1.insert(key, value); map1.insert(key, value);
@ -1013,5 +1032,39 @@ impl Engine<'_> {
*s = trimmed.to_string(); *s = trimmed.to_string();
} }
}); });
// Register date/time functions
self.register_fn("timestamp", || Instant::now());
self.register_fn("-", |ts1: Instant, ts2: Instant| {
if ts2 > ts1 {
#[cfg(not(feature = "no_float"))]
return -(ts2 - ts1).as_secs_f64();
#[cfg(feature = "no_float")]
return -((ts2 - ts1).as_secs() as INT);
} else {
#[cfg(not(feature = "no_float"))]
return (ts1 - ts2).as_secs_f64();
#[cfg(feature = "no_float")]
return (ts1 - ts2).as_secs() as INT;
}
});
reg_cmp!(self, "<", lt, Instant);
reg_cmp!(self, "<=", lte, Instant);
reg_cmp!(self, ">", gt, Instant);
reg_cmp!(self, ">=", gte, Instant);
reg_cmp!(self, "==", eq, Instant);
reg_cmp!(self, "!=", ne, Instant);
self.register_fn("elapsed", |timestamp: Instant| {
#[cfg(not(feature = "no_float"))]
return timestamp.elapsed().as_secs_f64();
#[cfg(feature = "no_float")]
return timestamp.elapsed().as_secs() as INT;
});
} }
} }

File diff suppressed because it is too large Load Diff

View File

@ -37,6 +37,10 @@ impl fmt::Display for LexError {
} }
/// Type of error encountered when parsing a script. /// Type of error encountered when parsing a script.
///
/// Some errors never appear when certain features are turned on.
/// They still exist so that the application can turn features on and off without going through
/// massive code changes to remove/add back enum variants in match statements.
#[derive(Debug, PartialEq, Clone)] #[derive(Debug, PartialEq, Clone)]
pub enum ParseErrorType { pub enum ParseErrorType {
/// Error in the script text. Wrapped value is the error message. /// Error in the script text. Wrapped value is the error message.
@ -51,17 +55,21 @@ pub enum ParseErrorType {
MalformedCallExpr(String), MalformedCallExpr(String),
/// An expression in indexing brackets `[]` has syntax error. Wrapped value is the error description (if any). /// An expression in indexing brackets `[]` has syntax error. Wrapped value is the error description (if any).
/// ///
/// Not available under the `no_index` feature. /// Never appears under the `no_index` feature.
#[cfg(not(feature = "no_index"))]
MalformedIndexExpr(String), MalformedIndexExpr(String),
/// An expression in an `in` expression has syntax error. Wrapped value is the error description (if any).
///
/// Never appears under the `no_object` and `no_index` features combination.
MalformedInExpr(String),
/// A map definition has duplicated property names. Wrapped value is the property name. /// A map definition has duplicated property names. Wrapped value is the property name.
/// ///
/// Not available under the `no_object` feature. /// Never appears under the `no_object` feature.
#[cfg(not(feature = "no_object"))]
DuplicatedProperty(String), DuplicatedProperty(String),
/// Invalid expression assigned to constant. Wrapped value is the name of the 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.
///
/// Never appears under the `no_object` feature.
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,
@ -69,28 +77,23 @@ pub enum ParseErrorType {
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. /// Never appears under the `no_function` feature.
#[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. /// Never appears under the `no_function` feature.
#[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. /// Never appears under the `no_function` feature.
#[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. /// Never appears under the `no_function` feature.
#[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. /// Never appears under the `no_function` feature.
#[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.
AssignmentToInvalidLHS, AssignmentToInvalidLHS,
@ -136,23 +139,17 @@ impl ParseError {
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",
ParseErrorType::MalformedCallExpr(_) => "Invalid expression in function call arguments", ParseErrorType::MalformedCallExpr(_) => "Invalid expression in function call arguments",
#[cfg(not(feature = "no_index"))]
ParseErrorType::MalformedIndexExpr(_) => "Invalid index in indexing expression", ParseErrorType::MalformedIndexExpr(_) => "Invalid index in indexing expression",
#[cfg(not(feature = "no_object"))] ParseErrorType::MalformedInExpr(_) => "Invalid 'in' expression",
ParseErrorType::DuplicatedProperty(_) => "Duplicated property in object map literal", ParseErrorType::DuplicatedProperty(_) => "Duplicated property in object map literal",
ParseErrorType::ForbiddenConstantExpr(_) => "Expecting a constant", ParseErrorType::ForbiddenConstantExpr(_) => "Expecting a constant",
ParseErrorType::PropertyExpected => "Expecting name of a property", ParseErrorType::PropertyExpected => "Expecting name of a property",
ParseErrorType::VariableExpected => "Expecting name of a variable", ParseErrorType::VariableExpected => "Expecting name of a variable",
ParseErrorType::ExprExpected(_) => "Expecting an expression", ParseErrorType::ExprExpected(_) => "Expecting an expression",
#[cfg(not(feature = "no_function"))]
ParseErrorType::FnMissingName => "Expecting name in function declaration", ParseErrorType::FnMissingName => "Expecting name in function declaration",
#[cfg(not(feature = "no_function"))]
ParseErrorType::FnMissingParams(_) => "Expecting parameters in function declaration", ParseErrorType::FnMissingParams(_) => "Expecting parameters in function declaration",
#[cfg(not(feature = "no_function"))]
ParseErrorType::FnDuplicatedParam(_,_) => "Duplicated parameters in function declaration", ParseErrorType::FnDuplicatedParam(_,_) => "Duplicated parameters in function declaration",
#[cfg(not(feature = "no_function"))]
ParseErrorType::FnMissingBody(_) => "Expecting body statement block for function declaration", ParseErrorType::FnMissingBody(_) => "Expecting body statement block for function declaration",
#[cfg(not(feature = "no_function"))]
ParseErrorType::WrongFnDefinition => "Function definitions must be at global level and cannot be inside a block or another function", ParseErrorType::WrongFnDefinition => "Function definitions must be at global level and cannot be inside a block or another function",
ParseErrorType::AssignmentToInvalidLHS => "Cannot assign to this expression", ParseErrorType::AssignmentToInvalidLHS => "Cannot assign to this expression",
ParseErrorType::AssignmentToCopy => "Cannot assign to this expression because it will only be changing a copy of the value", ParseErrorType::AssignmentToCopy => "Cannot assign to this expression because it will only be changing a copy of the value",
@ -175,29 +172,28 @@ impl fmt::Display for ParseError {
} }
ParseErrorType::UnknownOperator(s) => write!(f, "{}: '{}'", self.desc(), s)?, ParseErrorType::UnknownOperator(s) => write!(f, "{}: '{}'", self.desc(), s)?,
#[cfg(not(feature = "no_index"))]
ParseErrorType::MalformedIndexExpr(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"))] ParseErrorType::MalformedInExpr(s) => {
write!(f, "{}", if s.is_empty() { self.desc() } else { s })?
}
ParseErrorType::DuplicatedProperty(s) => { ParseErrorType::DuplicatedProperty(s) => {
write!(f, "Duplicated property '{}' for object map literal", s)? write!(f, "Duplicated property '{}' for object map literal", s)?
} }
ParseErrorType::ExprExpected(s) => write!(f, "Expecting {} expression", s)?, ParseErrorType::ExprExpected(s) => write!(f, "Expecting {} expression", s)?,
#[cfg(not(feature = "no_function"))]
ParseErrorType::FnMissingParams(s) => { ParseErrorType::FnMissingParams(s) => {
write!(f, "Expecting parameters for function '{}'", s)? write!(f, "Expecting parameters for function '{}'", s)?
} }
#[cfg(not(feature = "no_function"))]
ParseErrorType::FnMissingBody(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"))]
ParseErrorType::FnDuplicatedParam(s, arg) => { ParseErrorType::FnDuplicatedParam(s, arg) => {
write!(f, "Duplicated parameter '{}' for function '{}'", arg, s)? write!(f, "Duplicated parameter '{}' for function '{}'", arg, s)?
} }

View File

@ -36,6 +36,7 @@ macro_rules! impl_args {
(@pop) => { (@pop) => {
}; };
(@pop $head:ident) => { (@pop $head:ident) => {
impl_args!();
}; };
(@pop $head:ident $(, $tail:ident)+) => { (@pop $head:ident $(, $tail:ident)+) => {
impl_args!($($tail),*); impl_args!($($tail),*);

120
src/fn_func.rs Normal file
View File

@ -0,0 +1,120 @@
//! Module which defines the function registration mechanism.
#![cfg(not(feature = "no_function"))]
#![allow(non_snake_case)]
use crate::any::Any;
use crate::engine::Engine;
use crate::error::ParseError;
use crate::parser::AST;
use crate::result::EvalAltResult;
use crate::scope::Scope;
use crate::stdlib::{boxed::Box, string::ToString};
/// A trait to create a Rust anonymous function from a script.
pub trait Func<ARGS, RET> {
type Output;
/// Create a Rust anonymous function from an `AST`.
/// The `Engine` and `AST` are consumed and basically embedded into the closure.
///
/// # Examples
///
/// ```
/// # fn main() -> Result<(), rhai::EvalAltResult> {
/// use rhai::{Engine, Func}; // use 'Func' for 'create_from_ast'
///
/// let engine = Engine::new(); // create a new 'Engine' just for this
///
/// let ast = engine.compile("fn calc(x, y) { x + y.len() < 42 }")?;
///
/// // Func takes two type parameters:
/// // 1) a tuple made up of the types of the script function's parameters
/// // 2) the return type of the script function
/// //
/// // 'func' will have type Box<dyn Fn(i64, String) -> Result<bool, EvalAltResult>> and is callable!
/// let func = Func::<(i64, String), bool>::create_from_ast(
/// // ^^^^^^^^^^^^^ function parameter types in tuple
///
/// engine, // the 'Engine' is consumed into the closure
/// ast, // the 'AST'
/// "calc" // the entry-point function name
/// );
///
/// func(123, "hello".to_string())? == false; // call the anonymous function
/// # Ok(())
/// # }
fn create_from_ast(self, ast: AST, entry_point: &str) -> Self::Output;
/// Create a Rust anonymous function from a script.
/// The `Engine` is consumed and basically embedded into the closure.
///
/// # Examples
///
/// ```
/// # fn main() -> Result<(), rhai::EvalAltResult> {
/// use rhai::{Engine, Func}; // use 'Func' for 'create_from_script'
///
/// let engine = Engine::new(); // create a new 'Engine' just for this
///
/// let script = "fn calc(x, y) { x + y.len() < 42 }";
///
/// // Func takes two type parameters:
/// // 1) a tuple made up of the types of the script function's parameters
/// // 2) the return type of the script function
/// //
/// // 'func' will have type Box<dyn Fn(i64, String) -> Result<bool, EvalAltResult>> and is callable!
/// let func = Func::<(i64, String), bool>::create_from_script(
/// // ^^^^^^^^^^^^^ function parameter types in tuple
///
/// engine, // the 'Engine' is consumed into the closure
/// script, // the script, notice number of parameters must match
/// "calc" // the entry-point function name
/// )?;
///
/// func(123, "hello".to_string())? == false; // call the anonymous function
/// # Ok(())
/// # }
/// ```
fn create_from_script(
self,
script: &str,
entry_point: &str,
) -> Result<Self::Output, ParseError>;
}
macro_rules! def_anonymous_fn {
() => {
def_anonymous_fn!(imp);
};
(imp $($par:ident),*) => {
impl<'e, $($par: Any + Clone,)* RET: Any + Clone> Func<($($par,)*), RET> for Engine<'e>
{
#[cfg(feature = "sync")]
type Output = Box<dyn Fn($($par),*) -> Result<RET, EvalAltResult> + Send + Sync + 'e>;
#[cfg(not(feature = "sync"))]
type Output = Box<dyn Fn($($par),*) -> Result<RET, EvalAltResult> + 'e>;
fn create_from_ast(self, ast: AST, entry_point: &str) -> Self::Output {
let name = entry_point.to_string();
Box::new(move |$($par: $par),*| {
self.call_fn(&mut Scope::new(), &ast, &name, ($($par,)*))
})
}
fn create_from_script(self, script: &str, entry_point: &str) -> Result<Self::Output, ParseError> {
let ast = self.compile(script)?;
Ok(Func::<($($par,)*), RET>::create_from_ast(self, ast, entry_point))
}
}
};
($p0:ident $(, $p:ident)*) => {
def_anonymous_fn!(imp $p0 $(, $p)*);
def_anonymous_fn!($($p),*);
};
}
#[rustfmt::skip]
def_anonymous_fn!(A, B, C, D, E, F, G, H, J, K, L, M, N, P, Q, R, S, T, U, V);

View File

@ -132,9 +132,9 @@ macro_rules! def_register {
def_register!(imp); def_register!(imp);
}; };
(imp $($par:ident => $mark:ty => $param:ty => $clone:expr),*) => { (imp $($par:ident => $mark:ty => $param:ty => $clone:expr),*) => {
// ^ function parameter generic type name // ^ function parameter generic type name (A, B, C etc.)
// ^ function parameter marker type (T, Ref<T> or Mut<T>) // ^ function parameter marker type (T, Ref<T> or Mut<T>)
// ^ function parameter actual type // ^ function parameter actual type (T, &T or &mut T)
// ^ dereferencing function // ^ dereferencing function
impl< impl<
$($par: Any + Clone,)* $($par: Any + Clone,)*
@ -171,6 +171,7 @@ macro_rules! def_register {
let r = f($(($clone)($par)),*); let r = f($(($clone)($par)),*);
Ok(Box::new(r) as Dynamic) Ok(Box::new(r) as Dynamic)
}; };
self.register_fn_raw(name, vec![$(TypeId::of::<$par>()),*], Box::new(func)); self.register_fn_raw(name, vec![$(TypeId::of::<$par>()),*], Box::new(func));
} }
} }
@ -255,17 +256,14 @@ macro_rules! def_register {
def_register!(imp $p0 => $p0 => $p0 => Clone::clone $(, $p => $p => $p => Clone::clone)*); def_register!(imp $p0 => $p0 => $p0 => Clone::clone $(, $p => $p => $p => Clone::clone)*);
def_register!(imp $p0 => Mut<$p0> => &mut $p0 => identity $(, $p => $p => $p => Clone::clone)*); def_register!(imp $p0 => Mut<$p0> => &mut $p0 => identity $(, $p => $p => $p => Clone::clone)*);
// handle the first parameter ^ first parameter passed through // handle the first parameter ^ first parameter passed through
// others passed by value (cloned) ^ // ^ others passed by value (cloned)
// No support for functions where the first argument is a reference // Currently does not support first argument which is a reference, as there will be
// conflicting implementations since &T: Any and T: Any cannot be distinguished
//def_register!(imp $p0 => Ref<$p0> => &$p0 => identity $(, $p => $p => $p => Clone::clone)*); //def_register!(imp $p0 => Ref<$p0> => &$p0 => identity $(, $p => $p => $p => Clone::clone)*);
def_register!($($p),*); def_register!($($p),*);
}; };
// (imp_pop) => {};
// (imp_pop $head:ident => $head_mark:ty => $head_param:ty $(,$tail:ident => $tail_mark:ty => $tp:ty)*) => {
// def_register!(imp $($tail => $tail_mark => $tp),*);
// };
} }
#[rustfmt::skip] #[rustfmt::skip]

View File

@ -3,15 +3,19 @@
//! Rhai is a tiny, simple and very fast embedded scripting language for Rust //! Rhai is a tiny, simple and very fast embedded scripting language for Rust
//! that gives you a safe and easy way to add scripting to your applications. //! that gives you a safe and easy way to add scripting to your applications.
//! It provides a familiar syntax based on JS and Rust and a simple Rust interface. //! It provides a familiar syntax based on JS and Rust and a simple Rust interface.
//! Here is a quick example. First, the contents of `my_script.rhai`: //! Here is a quick example.
//!
//! First, the contents of `my_script.rhai`:
//! //!
//! ```,ignore //! ```,ignore
//! // Brute force factorial function
//! fn factorial(x) { //! fn factorial(x) {
//! if x == 1 { return 1; } //! if x == 1 { return 1; }
//! x * factorial(x - 1) //! x * factorial(x - 1)
//! } //! }
//! //!
//! compute_something(factorial(10)) //! // Calling an external function 'compute'
//! compute(factorial(10))
//! ``` //! ```
//! //!
//! And the Rust part: //! And the Rust part:
@ -21,16 +25,23 @@
//! //!
//! fn main() -> Result<(), EvalAltResult> //! fn main() -> Result<(), EvalAltResult>
//! { //! {
//! // Define external function
//! fn compute_something(x: i64) -> bool { //! fn compute_something(x: i64) -> bool {
//! (x % 40) == 0 //! (x % 40) == 0
//! } //! }
//! //!
//! // Create scripting engine
//! let mut engine = Engine::new(); //! let mut engine = Engine::new();
//! //!
//! engine.register_fn("compute_something", compute_something); //! // Register external function as 'compute'
//! engine.register_fn("compute", compute_something);
//! //!
//! # #[cfg(not(feature = "no_std"))] //! # #[cfg(not(feature = "no_std"))]
//! assert_eq!(engine.eval_file::<bool>("my_script.rhai".into())?, true); //! assert_eq!(
//! // Evaluate the script, expects a 'bool' return
//! engine.eval_file::<bool>("my_script.rhai".into())?,
//! true
//! );
//! //!
//! Ok(()) //! Ok(())
//! } //! }
@ -40,7 +51,6 @@
//! //!
//! | Feature | Description | //! | 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! | //! | `unchecked` | Exclude arithmetic checking (such as overflows and division by zero). Beware that a bad script may panic the entire system! |
//! | `no_function` | Disable script-defined functions if not needed. | //! | `no_function` | Disable script-defined functions if not needed. |
//! | `no_index` | Disable arrays and indexing features if not needed. | //! | `no_index` | Disable arrays and indexing features if not needed. |
@ -62,9 +72,10 @@ extern crate alloc;
mod any; mod any;
mod api; mod api;
mod builtin; mod builtin;
mod call;
mod engine; mod engine;
mod error; mod error;
mod fn_call;
mod fn_func;
mod fn_register; mod fn_register;
mod optimize; mod optimize;
mod parser; mod parser;
@ -73,14 +84,17 @@ mod scope;
mod stdlib; mod stdlib;
pub use any::{Any, AnyExt, Dynamic, Variant}; pub use any::{Any, AnyExt, Dynamic, Variant};
pub use call::FuncArgs;
pub use engine::Engine; pub use engine::Engine;
pub use error::{ParseError, ParseErrorType}; pub use error::{ParseError, ParseErrorType};
pub use fn_call::FuncArgs;
pub use fn_register::{RegisterDynamicFn, RegisterFn, RegisterResultFn}; pub use fn_register::{RegisterDynamicFn, RegisterFn, RegisterResultFn};
pub use parser::{Position, AST, INT}; pub use parser::{Position, AST, INT};
pub use result::EvalAltResult; pub use result::EvalAltResult;
pub use scope::Scope; pub use scope::Scope;
#[cfg(not(feature = "no_function"))]
pub use fn_func::Func;
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
pub use engine::Array; pub use engine::Array;

View File

@ -1,15 +1,15 @@
#![cfg(not(feature = "no_optimize"))]
use crate::any::{Any, Dynamic}; use crate::any::{Any, Dynamic};
use crate::engine::{ use crate::engine::{
Engine, FunctionsLib, KEYWORD_DEBUG, KEYWORD_DUMP_AST, KEYWORD_EVAL, KEYWORD_PRINT, Engine, FnAny, FnCallArgs, FnSpec, FunctionsLib, KEYWORD_DEBUG, KEYWORD_EVAL, KEYWORD_PRINT,
KEYWORD_TYPE_OF, KEYWORD_TYPE_OF,
}; };
use crate::parser::{map_dynamic_to_expr, Expr, FnDef, ReturnType, Stmt, AST}; use crate::parser::{map_dynamic_to_expr, Expr, FnDef, Position, ReturnType, Stmt, AST};
use crate::result::EvalAltResult;
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,
collections::HashMap,
rc::Rc, rc::Rc,
string::{String, ToString}, string::{String, ToString},
sync::Arc, sync::Arc,
@ -31,6 +31,17 @@ pub enum OptimizationLevel {
Full, Full,
} }
impl OptimizationLevel {
/// Is the `OptimizationLevel` None.
pub fn is_none(self) -> bool {
self == Self::None
}
/// Is the `OptimizationLevel` Full.
pub fn is_full(self) -> bool {
self == Self::Full
}
}
/// Mutable state throughout an optimization pass. /// Mutable state throughout an optimization pass.
struct State<'a> { struct State<'a> {
/// Has the AST been changed during this pass? /// Has the AST been changed during this pass?
@ -39,15 +50,25 @@ struct State<'a> {
constants: Vec<(String, Expr)>, constants: Vec<(String, Expr)>,
/// An `Engine` instance for eager function evaluation. /// An `Engine` instance for eager function evaluation.
engine: &'a Engine<'a>, engine: &'a Engine<'a>,
/// Library of script-defined functions.
fn_lib: &'a [(&'a str, usize)],
/// Optimization level.
optimization_level: OptimizationLevel,
} }
impl<'a> State<'a> { impl<'a> State<'a> {
/// Create a new State. /// Create a new State.
pub fn new(engine: &'a Engine<'a>) -> Self { pub fn new(
engine: &'a Engine<'a>,
fn_lib: &'a [(&'a str, usize)],
level: OptimizationLevel,
) -> Self {
Self { Self {
changed: false, changed: false,
constants: vec![], constants: vec![],
engine, engine,
fn_lib,
optimization_level: level,
} }
} }
/// Reset the state from dirty to clean. /// Reset the state from dirty to clean.
@ -86,6 +107,25 @@ impl<'a> State<'a> {
} }
} }
/// Call a registered function
fn call_fn(
functions: Option<&HashMap<FnSpec, Box<FnAny>>>,
fn_name: &str,
args: &mut FnCallArgs,
pos: Position,
) -> Result<Option<Dynamic>, EvalAltResult> {
let spec = FnSpec {
name: fn_name.into(),
args: args.iter().map(|a| Any::type_id(*a)).collect(),
};
// Search built-in's and external functions
functions
.and_then(|f| f.get(&spec))
.map(|func| func(args, pos))
.transpose()
}
/// Optimize a statement. /// Optimize a statement.
fn optimize_stmt<'a>(stmt: Stmt, state: &mut State<'a>, preserve_result: bool) -> Stmt { fn optimize_stmt<'a>(stmt: Stmt, state: &mut State<'a>, preserve_result: bool) -> Stmt {
match stmt { match stmt {
@ -345,25 +385,51 @@ fn optimize_expr<'a>(expr: Expr, state: &mut State<'a>) -> Expr {
// id = expr // id = expr
expr => Expr::Assignment(id, Box::new(optimize_expr(expr, state)), pos), expr => Expr::Assignment(id, Box::new(optimize_expr(expr, state)), pos),
}, },
// lhs.rhs // lhs.rhs
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
Expr::Dot(lhs, rhs, pos) => Expr::Dot( Expr::Dot(lhs, rhs, pos) => match (*lhs, *rhs) {
Box::new(optimize_expr(*lhs, state)), // map.string
Box::new(optimize_expr(*rhs, state)), (Expr::Map(items, pos), Expr::Property(s, _))
if items.iter().all(|(_, x, _)| x.is_pure()) =>
{
// Map literal where everything is pure - promote the indexed item.
// All other items can be thrown away.
state.set_dirty();
items.into_iter().find(|(name, _, _)| name == s.as_ref())
.map(|(_, expr, _)| expr.set_position(pos))
.unwrap_or_else(|| Expr::Unit(pos))
}
// lhs.rhs
(lhs, rhs) => Expr::Dot(
Box::new(optimize_expr(lhs, state)),
Box::new(optimize_expr(rhs, state)),
pos, pos,
), )
}
// lhs[rhs] // lhs[rhs]
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
Expr::Index(lhs, rhs, pos) => match (*lhs, *rhs) { Expr::Index(lhs, rhs, pos) => match (*lhs, *rhs) {
// array[int] // array[int]
(Expr::Array(mut items, _), Expr::IntegerConstant(i, _)) (Expr::Array(mut items, pos), Expr::IntegerConstant(i, _))
if i >= 0 && (i as usize) < items.len() && items.iter().all(|x| x.is_pure()) => if i >= 0 && (i as usize) < items.len() && items.iter().all(Expr::is_pure) =>
{ {
// Array literal where everything is pure - promote the indexed item. // Array literal where everything is pure - promote the indexed item.
// All other items can be thrown away. // All other items can be thrown away.
state.set_dirty(); state.set_dirty();
items.remove(i as usize) items.remove(i as usize).set_position(pos)
}
// map[string]
(Expr::Map(items, pos), Expr::StringConstant(s, _))
if items.iter().all(|(_, x, _)| x.is_pure()) =>
{
// Map literal where everything is pure - promote the indexed item.
// All other items can be thrown away.
state.set_dirty();
items.into_iter().find(|(name, _, _)| name == s.as_ref())
.map(|(_, expr, _)| expr.set_position(pos))
.unwrap_or_else(|| Expr::Unit(pos))
} }
// string[int] // string[int]
(Expr::StringConstant(s, pos), Expr::IntegerConstant(i, _)) (Expr::StringConstant(s, pos), Expr::IntegerConstant(i, _))
@ -392,8 +458,55 @@ fn optimize_expr<'a>(expr: Expr, state: &mut State<'a>) -> Expr {
.into_iter() .into_iter()
.map(|(key, expr, pos)| (key, optimize_expr(expr, state), pos)) .map(|(key, expr, pos)| (key, optimize_expr(expr, state), pos))
.collect(), pos), .collect(), pos),
// lhs in rhs
Expr::In(lhs, rhs, pos) => match (*lhs, *rhs) {
// "xxx" in "xxxxx"
(Expr::StringConstant(lhs, pos), Expr::StringConstant(rhs, _)) => {
state.set_dirty();
if rhs.contains(lhs.as_ref()) {
Expr::True(pos)
} else {
Expr::False(pos)
}
}
// 'x' in "xxxxx"
(Expr::CharConstant(lhs, pos), Expr::StringConstant(rhs, _)) => {
state.set_dirty();
if rhs.contains(&lhs.to_string()) {
Expr::True(pos)
} else {
Expr::False(pos)
}
}
// "xxx" in #{...}
(Expr::StringConstant(lhs, pos), Expr::Map(items, _)) => {
state.set_dirty();
if items.iter().find(|(name, _, _)| name == &lhs).is_some() {
Expr::True(pos)
} else {
Expr::False(pos)
}
}
// 'x' in #{...}
(Expr::CharConstant(lhs, pos), Expr::Map(items, _)) => {
state.set_dirty();
let lhs = lhs.to_string();
if items.iter().find(|(name, _, _)| name == &lhs).is_some() {
Expr::True(pos)
} else {
Expr::False(pos)
}
}
// lhs in rhs
(lhs, rhs) => Expr::In(
Box::new(optimize_expr(lhs, state)),
Box::new(optimize_expr(rhs, state)),
pos
),
},
// lhs && rhs // lhs && rhs
Expr::And(lhs, rhs) => match (*lhs, *rhs) { Expr::And(lhs, rhs, pos) => match (*lhs, *rhs) {
// true && rhs -> rhs // true && rhs -> rhs
(Expr::True(_), rhs) => { (Expr::True(_), rhs) => {
state.set_dirty(); state.set_dirty();
@ -413,10 +526,11 @@ fn optimize_expr<'a>(expr: Expr, state: &mut State<'a>) -> Expr {
(lhs, rhs) => Expr::And( (lhs, rhs) => Expr::And(
Box::new(optimize_expr(lhs, state)), Box::new(optimize_expr(lhs, state)),
Box::new(optimize_expr(rhs, state)), Box::new(optimize_expr(rhs, state)),
pos
), ),
}, },
// lhs || rhs // lhs || rhs
Expr::Or(lhs, rhs) => match (*lhs, *rhs) { Expr::Or(lhs, rhs, pos) => match (*lhs, *rhs) {
// false || rhs -> rhs // false || rhs -> rhs
(Expr::False(_), rhs) => { (Expr::False(_), rhs) => {
state.set_dirty(); state.set_dirty();
@ -436,29 +550,24 @@ fn optimize_expr<'a>(expr: Expr, state: &mut State<'a>) -> Expr {
(lhs, rhs) => Expr::Or( (lhs, rhs) => Expr::Or(
Box::new(optimize_expr(lhs, state)), Box::new(optimize_expr(lhs, state)),
Box::new(optimize_expr(rhs, state)), Box::new(optimize_expr(rhs, state)),
pos
), ),
}, },
// Do not optimize anything within dump_ast
Expr::FunctionCall(id, args, def_value, pos) if id == KEYWORD_DUMP_AST =>
Expr::FunctionCall(id, args, def_value, pos),
// Do not call some special keywords // Do not call some special keywords
Expr::FunctionCall(id, args, def_value, pos) if DONT_EVAL_KEYWORDS.contains(&id.as_str())=> Expr::FunctionCall(id, args, def_value, pos) if DONT_EVAL_KEYWORDS.contains(&id.as_ref())=>
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),
// Eagerly call functions // Eagerly call functions
Expr::FunctionCall(id, args, def_value, pos) Expr::FunctionCall(id, args, def_value, pos)
if state.engine.optimization_level == OptimizationLevel::Full // full optimizations if state.optimization_level == OptimizationLevel::Full // full optimizations
&& 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 let Some(fn_lib_arc) = &state.engine.fn_lib { if state.fn_lib.iter().find(|(name, len)| name == &id && *len == args.len()).is_some() {
if fn_lib_arc.has_function(&id, args.len()) {
// A script-defined function overrides the built-in function - do not make the call // 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); 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();
let mut call_args: Vec<_> = arg_values.iter_mut().map(Dynamic::as_mut).collect(); let mut call_args: Vec<_> = arg_values.iter_mut().map(Dynamic::as_mut).collect();
@ -471,7 +580,8 @@ fn optimize_expr<'a>(expr: Expr, state: &mut State<'a>) -> Expr {
"" ""
}; };
state.engine.call_ext_fn_raw(&id, &mut call_args, pos).ok().map(|result| call_fn(state.engine.functions.as_ref(), &id, &mut call_args, pos).ok()
.and_then(|result|
result.or_else(|| { result.or_else(|| {
if !arg_for_type_of.is_empty() { if !arg_for_type_of.is_empty() {
// Handle `type_of()` // Handle `type_of()`
@ -485,7 +595,10 @@ fn optimize_expr<'a>(expr: Expr, state: &mut State<'a>) -> Expr {
state.set_dirty(); state.set_dirty();
expr expr
}) })
).flatten().unwrap_or_else(|| Expr::FunctionCall(id, args, def_value, pos)) ).unwrap_or_else(||
// Optimize function call arguments
Expr::FunctionCall(id, args.into_iter().map(|a| optimize_expr(a, state)).collect(), def_value, pos)
)
} }
// id(args ..) -> optimize function call arguments // id(args ..) -> optimize function call arguments
@ -493,11 +606,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(name, _) if state.contains_constant(&name) => { Expr::Variable(name, pos) 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().set_position(pos)
} }
// All other expressions - skip // All other expressions - skip
@ -505,14 +618,20 @@ fn optimize_expr<'a>(expr: Expr, state: &mut State<'a>) -> Expr {
} }
} }
pub(crate) fn optimize<'a>(statements: Vec<Stmt>, engine: &Engine<'a>, scope: &Scope) -> Vec<Stmt> { fn optimize<'a>(
statements: Vec<Stmt>,
engine: &Engine<'a>,
scope: &Scope,
fn_lib: &'a [(&'a str, usize)],
level: OptimizationLevel,
) -> Vec<Stmt> {
// If optimization level is None then skip optimizing // If optimization level is None then skip optimizing
if engine.optimization_level == OptimizationLevel::None { if level == OptimizationLevel::None {
return statements; return statements;
} }
// Set up the state // Set up the state
let mut state = State::new(engine); let mut state = State::new(engine, fn_lib, level);
// Add constants from the scope into the state // Add constants from the scope into the state
scope scope
@ -544,17 +663,19 @@ pub(crate) fn optimize<'a>(statements: Vec<Stmt>, engine: &Engine<'a>, scope: &S
.into_iter() .into_iter()
.enumerate() .enumerate()
.map(|(i, stmt)| { .map(|(i, stmt)| {
if let Stmt::Const(name, value, _) = &stmt { match stmt {
Stmt::Const(ref name, ref value, _) => {
// Load constants // Load constants
state.push_constant(name, value.as_ref().clone()); state.push_constant(name.as_ref(), value.as_ref().clone());
stmt // Keep it in the global scope stmt // Keep it in the global scope
} else { }
_ => {
// Keep all variable declarations at this level // Keep all variable declarations at this level
// and always keep the last return value // and always keep the last return value
let keep = matches!(stmt, Stmt::Let(_, _, _)) || i == num_statements - 1; let keep = matches!(stmt, Stmt::Let(_, _, _)) || i == num_statements - 1;
optimize_stmt(stmt, &mut state, keep) optimize_stmt(stmt, &mut state, keep)
} }
}
}) })
.collect(); .collect();
@ -585,17 +706,27 @@ pub fn optimize_into_ast(
scope: &Scope, scope: &Scope,
statements: Vec<Stmt>, statements: Vec<Stmt>,
functions: Vec<FnDef>, functions: Vec<FnDef>,
level: OptimizationLevel,
) -> AST { ) -> AST {
let fn_lib = FunctionsLib::from_vec( #[cfg(feature = "no_optimize")]
const level: OptimizationLevel = OptimizationLevel::None;
let fn_lib: Vec<_> = functions
.iter()
.map(|fn_def| (fn_def.name.as_str(), fn_def.params.len()))
.collect();
let lib = FunctionsLib::from_vec(
functions functions
.iter() .iter()
.cloned() .cloned()
.map(|mut fn_def| { .map(|mut fn_def| {
if engine.optimization_level != OptimizationLevel::None { if !level.is_none() {
let pos = fn_def.body.position(); let pos = fn_def.body.position();
// Optimize the function body // Optimize the function body
let mut body = optimize(vec![fn_def.body], engine, &Scope::new()); let mut body =
optimize(vec![fn_def.body], engine, &Scope::new(), &fn_lib, level);
// {} -> Noop // {} -> Noop
fn_def.body = match body.pop().unwrap_or_else(|| Stmt::Noop(pos)) { fn_def.body = match body.pop().unwrap_or_else(|| Stmt::Noop(pos)) {
@ -615,15 +746,15 @@ pub fn optimize_into_ast(
); );
AST( AST(
match engine.optimization_level { match level {
OptimizationLevel::None => statements, OptimizationLevel::None => statements,
OptimizationLevel::Simple | OptimizationLevel::Full => { OptimizationLevel::Simple | OptimizationLevel::Full => {
optimize(statements, engine, &scope) optimize(statements, engine, &scope, &fn_lib, level)
} }
}, },
#[cfg(feature = "sync")] #[cfg(feature = "sync")]
Arc::new(fn_lib), Arc::new(lib),
#[cfg(not(feature = "sync"))] #[cfg(not(feature = "sync"))]
Rc::new(fn_lib), Rc::new(lib),
) )
} }

File diff suppressed because it is too large Load Diff

View File

@ -16,6 +16,8 @@ use crate::stdlib::path::PathBuf;
/// Evaluation result. /// Evaluation result.
/// ///
/// All wrapped `Position` values represent the location in the script where the error occurs. /// All wrapped `Position` values represent the location in the script where the error occurs.
///
/// Currently, `EvalAltResult` is neither `Send` nor `Sync`. Turn on the `sync` feature to make it `Send + Sync`.
#[derive(Debug)] #[derive(Debug)]
pub enum EvalAltResult { pub enum EvalAltResult {
/// Syntax error. /// Syntax error.
@ -23,7 +25,7 @@ pub enum EvalAltResult {
/// 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. /// Never appears 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),
@ -49,6 +51,8 @@ pub enum EvalAltResult {
ErrorNumericIndexExpr(Position), ErrorNumericIndexExpr(Position),
/// Trying to index into a map with an index that is not `String`. /// Trying to index into a map with an index that is not `String`.
ErrorStringIndexExpr(Position), ErrorStringIndexExpr(Position),
/// Invalid arguments for `in` operator.
ErrorInExpr(Position),
/// The guard expression in an `if` or `while` statement does not return a boolean value. /// The guard expression in an `if` or `while` statement does not return a boolean value.
ErrorLogicGuard(Position), ErrorLogicGuard(Position),
/// The `for` statement encounters a type that is not an iterator. /// The `for` statement encounters a type that is not an iterator.
@ -103,21 +107,22 @@ impl EvalAltResult {
Self::ErrorArrayBounds(_, index, _) if *index < 0 => { Self::ErrorArrayBounds(_, index, _) if *index < 0 => {
"Array access expects non-negative index" "Array access expects non-negative index"
} }
Self::ErrorArrayBounds(0, _, _) => "Access of empty array", Self::ErrorArrayBounds(0, _, _) => "Empty array has nothing to access",
Self::ErrorArrayBounds(_, _, _) => "Array index out of bounds", Self::ErrorArrayBounds(_, _, _) => "Array index out of bounds",
Self::ErrorStringBounds(_, index, _) if *index < 0 => { Self::ErrorStringBounds(_, index, _) if *index < 0 => {
"Indexing a string expects a non-negative index" "Indexing a string expects a non-negative index"
} }
Self::ErrorStringBounds(0, _, _) => "Indexing of empty string", Self::ErrorStringBounds(0, _, _) => "Empty string has nothing to index",
Self::ErrorStringBounds(_, _, _) => "String index out of bounds", Self::ErrorStringBounds(_, _, _) => "String index out of bounds",
Self::ErrorLogicGuard(_) => "Boolean expression expected", Self::ErrorLogicGuard(_) => "Boolean value expected",
Self::ErrorFor(_) => "For loop expects array or range", Self::ErrorFor(_) => "For loop expects an array, object map, or range",
Self::ErrorVariableNotFound(_, _) => "Variable not found", Self::ErrorVariableNotFound(_, _) => "Variable not found",
Self::ErrorAssignmentToUnknownLHS(_) => { Self::ErrorAssignmentToUnknownLHS(_) => {
"Assignment to an unsupported left-hand side expression" "Assignment to an unsupported left-hand side expression"
} }
Self::ErrorAssignmentToConstant(_, _) => "Assignment to a constant variable", Self::ErrorAssignmentToConstant(_, _) => "Assignment to a constant variable",
Self::ErrorMismatchOutputType(_, _) => "Output type is incorrect", Self::ErrorMismatchOutputType(_, _) => "Output type is incorrect",
Self::ErrorInExpr(_) => "Malformed 'in' expression",
Self::ErrorDotExpr(_, _) => "Malformed dot expression", Self::ErrorDotExpr(_, _) => "Malformed dot expression",
Self::ErrorArithmetic(_, _) => "Arithmetic error", Self::ErrorArithmetic(_, _) => "Arithmetic error",
Self::ErrorStackOverflow(_) => "Stack overflow", Self::ErrorStackOverflow(_) => "Stack overflow",
@ -154,6 +159,7 @@ impl fmt::Display for EvalAltResult {
| Self::ErrorLogicGuard(pos) | Self::ErrorLogicGuard(pos)
| Self::ErrorFor(pos) | Self::ErrorFor(pos)
| Self::ErrorAssignmentToUnknownLHS(pos) | Self::ErrorAssignmentToUnknownLHS(pos)
| Self::ErrorInExpr(pos)
| Self::ErrorDotExpr(_, pos) | Self::ErrorDotExpr(_, pos)
| Self::ErrorStackOverflow(pos) => write!(f, "{} ({})", desc, pos), | Self::ErrorStackOverflow(pos) => write!(f, "{} ({})", desc, pos),
@ -256,6 +262,7 @@ impl EvalAltResult {
| Self::ErrorAssignmentToUnknownLHS(pos) | Self::ErrorAssignmentToUnknownLHS(pos)
| Self::ErrorAssignmentToConstant(_, pos) | Self::ErrorAssignmentToConstant(_, pos)
| Self::ErrorMismatchOutputType(_, pos) | Self::ErrorMismatchOutputType(_, pos)
| Self::ErrorInExpr(pos)
| Self::ErrorDotExpr(_, pos) | Self::ErrorDotExpr(_, pos)
| Self::ErrorArithmetic(_, pos) | Self::ErrorArithmetic(_, pos)
| Self::ErrorStackOverflow(pos) | Self::ErrorStackOverflow(pos)
@ -288,6 +295,7 @@ impl EvalAltResult {
| Self::ErrorAssignmentToUnknownLHS(pos) | Self::ErrorAssignmentToUnknownLHS(pos)
| Self::ErrorAssignmentToConstant(_, pos) | Self::ErrorAssignmentToConstant(_, pos)
| Self::ErrorMismatchOutputType(_, pos) | Self::ErrorMismatchOutputType(_, pos)
| Self::ErrorInExpr(pos)
| Self::ErrorDotExpr(_, pos) | Self::ErrorDotExpr(_, pos)
| Self::ErrorArithmetic(_, pos) | Self::ErrorArithmetic(_, pos)
| Self::ErrorStackOverflow(pos) | Self::ErrorStackOverflow(pos)

View File

@ -49,7 +49,7 @@ pub(crate) struct EntryRef<'a> {
/// # fn main() -> Result<(), rhai::EvalAltResult> { /// # fn main() -> Result<(), rhai::EvalAltResult> {
/// use rhai::{Engine, Scope}; /// use rhai::{Engine, Scope};
/// ///
/// let mut engine = Engine::new(); /// let engine = Engine::new();
/// let mut my_scope = Scope::new(); /// let mut my_scope = Scope::new();
/// ///
/// my_scope.push("z", 40_i64); /// my_scope.push("z", 40_i64);

View File

@ -3,7 +3,7 @@ use rhai::{Array, Engine, EvalAltResult, RegisterFn, INT};
#[test] #[test]
fn test_arrays() -> Result<(), EvalAltResult> { fn test_arrays() -> Result<(), EvalAltResult> {
let mut engine = Engine::new(); let engine = Engine::new();
assert_eq!(engine.eval::<INT>("let x = [1, 2, 3]; x[1]")?, 2); assert_eq!(engine.eval::<INT>("let x = [1, 2, 3]; x[1]")?, 2);
assert_eq!(engine.eval::<INT>("let y = [1, 2, 3]; y[1] = 5; y[1]")?, 5); assert_eq!(engine.eval::<INT>("let y = [1, 2, 3]; y[1] = 5; y[1]")?, 5);
@ -11,19 +11,24 @@ fn test_arrays() -> Result<(), EvalAltResult> {
engine.eval::<char>(r#"let y = [1, [ 42, 88, "93" ], 3]; y[1][2][1]"#)?, engine.eval::<char>(r#"let y = [1, [ 42, 88, "93" ], 3]; y[1][2][1]"#)?,
'3' '3'
); );
assert!(engine.eval::<bool>("let y = [1, 2, 3]; 2 in y")?);
#[cfg(not(feature = "no_stdlib"))]
{
assert_eq!( assert_eq!(
engine.eval::<INT>( engine.eval::<INT>(
r" r"
let x = [1, 2, 3]; let x = [2, 9];
x.insert(-1, 1);
x.insert(999, 3);
let r = x.remove(2);
let y = [4, 5]; let y = [4, 5];
x.append(y); x.append(y);
x.len()
x.len() + r
" "
)?, )?,
5 14
); );
assert_eq!( assert_eq!(
engine.eval::<INT>( engine.eval::<INT>(
@ -47,7 +52,6 @@ fn test_arrays() -> Result<(), EvalAltResult> {
.len(), .len(),
5 5
); );
}
Ok(()) Ok(())
} }

View File

@ -2,7 +2,7 @@ use rhai::{Engine, EvalAltResult, INT};
#[test] #[test]
fn test_binary_ops() -> Result<(), EvalAltResult> { fn test_binary_ops() -> Result<(), EvalAltResult> {
let mut engine = Engine::new(); let engine = Engine::new();
assert_eq!(engine.eval::<INT>("10 % 4")?, 2); assert_eq!(engine.eval::<INT>("10 % 4")?, 2);
assert_eq!(engine.eval::<INT>("10 << 4")?, 160); assert_eq!(engine.eval::<INT>("10 << 4")?, 160);

View File

@ -2,14 +2,14 @@ use rhai::{Engine, EvalAltResult, INT};
#[test] #[test]
fn test_left_shift() -> Result<(), EvalAltResult> { fn test_left_shift() -> Result<(), EvalAltResult> {
let mut engine = Engine::new(); let engine = Engine::new();
assert_eq!(engine.eval::<INT>("4 << 2")?, 16); assert_eq!(engine.eval::<INT>("4 << 2")?, 16);
Ok(()) Ok(())
} }
#[test] #[test]
fn test_right_shift() -> Result<(), EvalAltResult> { fn test_right_shift() -> Result<(), EvalAltResult> {
let mut engine = Engine::new(); let engine = Engine::new();
assert_eq!(engine.eval::<INT>("9 >> 1")?, 4); assert_eq!(engine.eval::<INT>("9 >> 1")?, 4);
Ok(()) Ok(())
} }

View File

@ -2,7 +2,7 @@ use rhai::{Engine, EvalAltResult};
#[test] #[test]
fn test_bool_op1() -> Result<(), EvalAltResult> { fn test_bool_op1() -> Result<(), EvalAltResult> {
let mut engine = Engine::new(); let engine = Engine::new();
assert_eq!(engine.eval::<bool>("true && (false || true)")?, true); assert_eq!(engine.eval::<bool>("true && (false || true)")?, true);
assert_eq!(engine.eval::<bool>("true & (false | true)")?, true); assert_eq!(engine.eval::<bool>("true & (false | true)")?, true);
@ -12,7 +12,7 @@ fn test_bool_op1() -> Result<(), EvalAltResult> {
#[test] #[test]
fn test_bool_op2() -> Result<(), EvalAltResult> { fn test_bool_op2() -> Result<(), EvalAltResult> {
let mut engine = Engine::new(); let engine = Engine::new();
assert_eq!(engine.eval::<bool>("false && (false || true)")?, false); assert_eq!(engine.eval::<bool>("false && (false || true)")?, false);
assert_eq!(engine.eval::<bool>("false & (false | true)")?, false); assert_eq!(engine.eval::<bool>("false & (false | true)")?, false);
@ -22,7 +22,7 @@ fn test_bool_op2() -> Result<(), EvalAltResult> {
#[test] #[test]
fn test_bool_op3() -> Result<(), EvalAltResult> { fn test_bool_op3() -> Result<(), EvalAltResult> {
let mut engine = Engine::new(); let engine = Engine::new();
assert!(engine.eval::<bool>("true && (false || 123)").is_err()); assert!(engine.eval::<bool>("true && (false || 123)").is_err());
assert_eq!(engine.eval::<bool>("true && (true || 123)")?, true); assert_eq!(engine.eval::<bool>("true && (true || 123)")?, true);
@ -34,7 +34,7 @@ fn test_bool_op3() -> Result<(), EvalAltResult> {
#[test] #[test]
fn test_bool_op_short_circuit() -> Result<(), EvalAltResult> { fn test_bool_op_short_circuit() -> Result<(), EvalAltResult> {
let mut engine = Engine::new(); let engine = Engine::new();
assert_eq!( assert_eq!(
engine.eval::<bool>( engine.eval::<bool>(
@ -63,7 +63,7 @@ fn test_bool_op_short_circuit() -> Result<(), EvalAltResult> {
#[test] #[test]
fn test_bool_op_no_short_circuit1() { fn test_bool_op_no_short_circuit1() {
let mut engine = Engine::new(); let engine = Engine::new();
assert!(engine assert!(engine
.eval::<bool>( .eval::<bool>(
@ -78,7 +78,7 @@ fn test_bool_op_no_short_circuit1() {
#[test] #[test]
fn test_bool_op_no_short_circuit2() { fn test_bool_op_no_short_circuit2() {
let mut engine = Engine::new(); let engine = Engine::new();
assert!(engine assert!(engine
.eval::<bool>( .eval::<bool>(

View File

@ -1,5 +1,5 @@
#![cfg(not(feature = "no_function"))] #![cfg(not(feature = "no_function"))]
use rhai::{Engine, EvalAltResult, ParseErrorType, Scope, INT}; use rhai::{Engine, EvalAltResult, Func, ParseErrorType, Scope, INT};
#[test] #[test]
fn test_fn() -> Result<(), EvalAltResult> { fn test_fn() -> Result<(), EvalAltResult> {
@ -20,7 +20,7 @@ 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 engine = Engine::new();
let mut scope = Scope::new(); let mut scope = Scope::new();
scope.push("foo", 42 as INT); scope.push("foo", 42 as INT);
@ -44,10 +44,10 @@ fn test_call_fn() -> Result<(), EvalAltResult> {
let r: i64 = engine.call_fn(&mut scope, &ast, "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_fn1(&mut scope, &ast, "hello", 123 as INT)?; let r: i64 = engine.call_fn(&mut scope, &ast, "hello", (123 as INT,))?;
assert_eq!(r, 5166); assert_eq!(r, 5166);
let r: i64 = engine.call_fn0(&mut scope, &ast, "hello")?; let r: i64 = engine.call_fn(&mut scope, &ast, "hello", ())?;
assert_eq!(r, 42); assert_eq!(r, 42);
assert_eq!( assert_eq!(
@ -59,3 +59,16 @@ fn test_call_fn() -> Result<(), EvalAltResult> {
Ok(()) Ok(())
} }
#[test]
fn test_anonymous_fn() -> Result<(), EvalAltResult> {
let calc_func = Func::<(INT, INT, INT), INT>::create_from_script(
Engine::new(),
"fn calc(x, y, z) { (x + y) * z }",
"calc",
)?;
assert_eq!(calc_func(42, 123, 9)?, 1485);
Ok(())
}

View File

@ -2,9 +2,11 @@ use rhai::{Engine, EvalAltResult};
#[test] #[test]
fn test_chars() -> Result<(), EvalAltResult> { fn test_chars() -> Result<(), EvalAltResult> {
let mut engine = Engine::new(); let engine = Engine::new();
assert_eq!(engine.eval::<char>("'y'")?, 'y'); assert_eq!(engine.eval::<char>("'y'")?, 'y');
assert_eq!(engine.eval::<char>(r"'\''")?, '\'');
assert_eq!(engine.eval::<char>(r#"'"'"#)?, '"');
assert_eq!(engine.eval::<char>("'\\u2764'")?, '❤'); assert_eq!(engine.eval::<char>("'\\u2764'")?, '❤');
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]

View File

@ -2,13 +2,13 @@ use rhai::{Engine, INT};
#[test] #[test]
fn test_comments() { fn test_comments() {
let mut engine = Engine::new(); let engine = Engine::new();
assert!(engine assert!(engine
.eval::<INT>("let x = 5; x // I am a single line comment, yay!") .eval::<INT>("let x = 5; x // I am a single line comment, yay!")
.is_ok()); .is_ok());
assert!(engine assert!(engine
.eval::<INT>("let /* I am a multiline comment, yay! */ x = 5; x") .eval::<INT>("let /* I am a multi-line comment, yay! */ x = 5; x")
.is_ok()); .is_ok());
} }

View File

@ -2,7 +2,7 @@ use rhai::{Engine, EvalAltResult, INT};
#[test] #[test]
fn test_or_equals() -> Result<(), EvalAltResult> { fn test_or_equals() -> Result<(), EvalAltResult> {
let mut engine = Engine::new(); let engine = Engine::new();
assert_eq!(engine.eval::<INT>("let x = 16; x |= 74; x")?, 90); assert_eq!(engine.eval::<INT>("let x = 16; x |= 74; x")?, 90);
assert_eq!(engine.eval::<bool>("let x = true; x |= false; x")?, true); assert_eq!(engine.eval::<bool>("let x = true; x |= false; x")?, true);
@ -13,7 +13,7 @@ fn test_or_equals() -> Result<(), EvalAltResult> {
#[test] #[test]
fn test_and_equals() -> Result<(), EvalAltResult> { fn test_and_equals() -> Result<(), EvalAltResult> {
let mut engine = Engine::new(); let engine = Engine::new();
assert_eq!(engine.eval::<INT>("let x = 16; x &= 31; x")?, 16); assert_eq!(engine.eval::<INT>("let x = 16; x &= 31; x")?, 16);
assert_eq!(engine.eval::<bool>("let x = true; x &= false; x")?, false); assert_eq!(engine.eval::<bool>("let x = true; x &= false; x")?, false);
@ -25,42 +25,42 @@ fn test_and_equals() -> Result<(), EvalAltResult> {
#[test] #[test]
fn test_xor_equals() -> Result<(), EvalAltResult> { fn test_xor_equals() -> Result<(), EvalAltResult> {
let mut engine = Engine::new(); let engine = Engine::new();
assert_eq!(engine.eval::<INT>("let x = 90; x ^= 12; x")?, 86); assert_eq!(engine.eval::<INT>("let x = 90; x ^= 12; x")?, 86);
Ok(()) Ok(())
} }
#[test] #[test]
fn test_multiply_equals() -> Result<(), EvalAltResult> { fn test_multiply_equals() -> Result<(), EvalAltResult> {
let mut engine = Engine::new(); let engine = Engine::new();
assert_eq!(engine.eval::<INT>("let x = 2; x *= 3; x")?, 6); assert_eq!(engine.eval::<INT>("let x = 2; x *= 3; x")?, 6);
Ok(()) Ok(())
} }
#[test] #[test]
fn test_divide_equals() -> Result<(), EvalAltResult> { fn test_divide_equals() -> Result<(), EvalAltResult> {
let mut engine = Engine::new(); let engine = Engine::new();
assert_eq!(engine.eval::<INT>("let x = 6; x /= 2; x")?, 3); assert_eq!(engine.eval::<INT>("let x = 6; x /= 2; x")?, 3);
Ok(()) Ok(())
} }
#[test] #[test]
fn test_left_shift_equals() -> Result<(), EvalAltResult> { fn test_left_shift_equals() -> Result<(), EvalAltResult> {
let mut engine = Engine::new(); let engine = Engine::new();
assert_eq!(engine.eval::<INT>("let x = 9; x >>=1; x")?, 4); assert_eq!(engine.eval::<INT>("let x = 9; x >>=1; x")?, 4);
Ok(()) Ok(())
} }
#[test] #[test]
fn test_right_shift_equals() -> Result<(), EvalAltResult> { fn test_right_shift_equals() -> Result<(), EvalAltResult> {
let mut engine = Engine::new(); let engine = Engine::new();
assert_eq!(engine.eval::<INT>("let x = 4; x<<= 2; x")?, 16); assert_eq!(engine.eval::<INT>("let x = 4; x<<= 2; x")?, 16);
Ok(()) Ok(())
} }
#[test] #[test]
fn test_modulo_equals() -> Result<(), EvalAltResult> { fn test_modulo_equals() -> Result<(), EvalAltResult> {
let mut engine = Engine::new(); let engine = Engine::new();
assert_eq!(engine.eval::<INT>("let x = 10; x %= 4; x")?, 2); assert_eq!(engine.eval::<INT>("let x = 10; x %= 4; x")?, 2);
Ok(()) Ok(())
} }

View File

@ -2,7 +2,7 @@ use rhai::{Engine, EvalAltResult, INT};
#[test] #[test]
fn test_constant() -> Result<(), EvalAltResult> { fn test_constant() -> Result<(), EvalAltResult> {
let mut engine = Engine::new(); let engine = Engine::new();
assert_eq!(engine.eval::<INT>("const x = 123; x")?, 123); assert_eq!(engine.eval::<INT>("const x = 123; x")?, 123);

View File

@ -2,7 +2,7 @@ use rhai::{Engine, EvalAltResult, INT};
#[test] #[test]
fn test_decrement() -> Result<(), EvalAltResult> { fn test_decrement() -> Result<(), EvalAltResult> {
let mut engine = Engine::new(); let engine = Engine::new();
assert_eq!(engine.eval::<INT>("let x = 10; x -= 7; x")?, 3); assert_eq!(engine.eval::<INT>("let x = 10; x -= 7; x")?, 3);

View File

@ -2,7 +2,7 @@ use rhai::{Engine, EvalAltResult, Scope, INT};
#[test] #[test]
fn test_eval() -> Result<(), EvalAltResult> { fn test_eval() -> Result<(), EvalAltResult> {
let mut engine = Engine::new(); let engine = Engine::new();
assert_eq!( assert_eq!(
engine.eval::<INT>( engine.eval::<INT>(
@ -19,7 +19,7 @@ fn test_eval() -> Result<(), EvalAltResult> {
#[test] #[test]
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]
fn test_eval_function() -> Result<(), EvalAltResult> { fn test_eval_function() -> Result<(), EvalAltResult> {
let mut engine = Engine::new(); let engine = Engine::new();
let mut scope = Scope::new(); let mut scope = Scope::new();
assert_eq!( assert_eq!(
@ -62,7 +62,7 @@ fn test_eval_function() -> Result<(), EvalAltResult> {
#[test] #[test]
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]
fn test_eval_override() -> Result<(), EvalAltResult> { fn test_eval_override() -> Result<(), EvalAltResult> {
let mut engine = Engine::new(); let engine = Engine::new();
assert_eq!( assert_eq!(
engine.eval::<String>( engine.eval::<String>(

View File

@ -2,7 +2,7 @@ use rhai::{Engine, EvalAltResult, Scope, INT};
#[test] #[test]
fn test_expressions() -> Result<(), EvalAltResult> { fn test_expressions() -> Result<(), EvalAltResult> {
let mut engine = Engine::new(); let engine = Engine::new();
let mut scope = Scope::new(); let mut scope = Scope::new();
scope.push("x", 10 as INT); scope.push("x", 10 as INT);

View File

@ -5,7 +5,7 @@ const EPSILON: FLOAT = 0.000_000_000_1;
#[test] #[test]
fn test_float() -> Result<(), EvalAltResult> { fn test_float() -> Result<(), EvalAltResult> {
let mut engine = Engine::new(); let engine = Engine::new();
assert_eq!( assert_eq!(
engine.eval::<bool>("let x = 0.0; let y = 1.0; x < y")?, engine.eval::<bool>("let x = 0.0; let y = 1.0; x < y")?,

View File

@ -3,7 +3,7 @@ use rhai::{Engine, EvalAltResult, INT};
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
#[test] #[test]
fn test_for_array() -> Result<(), EvalAltResult> { fn test_for_array() -> Result<(), EvalAltResult> {
let mut engine = Engine::new(); let engine = Engine::new();
let script = r" let script = r"
let sum1 = 0; let sum1 = 0;
@ -33,7 +33,7 @@ fn test_for_array() -> Result<(), EvalAltResult> {
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
#[test] #[test]
fn test_for_object() -> Result<(), EvalAltResult> { fn test_for_object() -> Result<(), EvalAltResult> {
let mut engine = Engine::new(); let engine = Engine::new();
let script = r#" let script = r#"
let sum = 0; let sum = 0;

View File

@ -2,7 +2,7 @@ use rhai::{Engine, EvalAltResult, INT};
#[test] #[test]
fn test_if() -> Result<(), EvalAltResult> { fn test_if() -> Result<(), EvalAltResult> {
let mut engine = Engine::new(); let engine = Engine::new();
assert_eq!(engine.eval::<INT>("if true { 55 }")?, 55); assert_eq!(engine.eval::<INT>("if true { 55 }")?, 55);
assert_eq!(engine.eval::<INT>("if false { 55 } else { 44 }")?, 44); assert_eq!(engine.eval::<INT>("if false { 55 } else { 44 }")?, 44);
@ -30,7 +30,7 @@ fn test_if() -> Result<(), EvalAltResult> {
#[test] #[test]
fn test_if_expr() -> Result<(), EvalAltResult> { fn test_if_expr() -> Result<(), EvalAltResult> {
let mut engine = Engine::new(); let engine = Engine::new();
assert_eq!( assert_eq!(
engine.eval::<INT>( engine.eval::<INT>(

View File

@ -2,7 +2,7 @@ use rhai::{Engine, EvalAltResult, INT};
#[test] #[test]
fn test_increment() -> Result<(), EvalAltResult> { fn test_increment() -> Result<(), EvalAltResult> {
let mut engine = Engine::new(); let engine = Engine::new();
assert_eq!(engine.eval::<INT>("let x = 1; x += 2; x")?, 3); assert_eq!(engine.eval::<INT>("let x = 1; x += 2; x")?, 3);
assert_eq!( assert_eq!(

View File

@ -4,9 +4,12 @@ use rhai::{Engine, EvalAltResult, INT};
#[test] #[test]
fn test_internal_fn() -> Result<(), EvalAltResult> { fn test_internal_fn() -> Result<(), EvalAltResult> {
let mut engine = Engine::new(); let engine = Engine::new();
assert_eq!(engine.eval::<INT>("fn addme(a, b) { a+b } addme(3, 4)")?, 7); assert_eq!(
engine.eval::<INT>("fn add_me(a, b) { a+b } add_me(3, 4)")?,
7
);
assert_eq!(engine.eval::<INT>("fn bob() { return 4; 5 } bob()")?, 4); assert_eq!(engine.eval::<INT>("fn bob() { return 4; 5 } bob()")?, 4);
Ok(()) Ok(())
@ -14,15 +17,15 @@ fn test_internal_fn() -> Result<(), EvalAltResult> {
#[test] #[test]
fn test_big_internal_fn() -> Result<(), EvalAltResult> { fn test_big_internal_fn() -> Result<(), EvalAltResult> {
let mut engine = Engine::new(); let engine = Engine::new();
assert_eq!( assert_eq!(
engine.eval::<INT>( engine.eval::<INT>(
r" r"
fn mathme(a, b, c, d, e, f) { fn math_me(a, b, c, d, e, f) {
a - b * c + d * e - f a - b * c + d * e - f
} }
mathme(100, 5, 2, 9, 6, 32) math_me(100, 5, 2, 9, 6, 32)
", ",
)?, )?,
112 112
@ -33,7 +36,7 @@ fn test_big_internal_fn() -> Result<(), EvalAltResult> {
#[test] #[test]
fn test_internal_fn_overloading() -> Result<(), EvalAltResult> { fn test_internal_fn_overloading() -> Result<(), EvalAltResult> {
let mut engine = Engine::new(); let engine = Engine::new();
assert_eq!( assert_eq!(
engine.eval::<INT>( engine.eval::<INT>(

View File

@ -2,7 +2,7 @@ use rhai::{Engine, EvalAltResult, INT};
#[test] #[test]
fn test_loop() -> Result<(), EvalAltResult> { fn test_loop() -> Result<(), EvalAltResult> {
let mut engine = Engine::new(); let engine = Engine::new();
assert_eq!( assert_eq!(
engine.eval::<INT>( engine.eval::<INT>(

View File

@ -1,10 +1,10 @@
#![cfg(not(feature = "no_object"))] #![cfg(not(feature = "no_object"))]
use rhai::{AnyExt, Engine, EvalAltResult, Map, INT}; use rhai::{AnyExt, Engine, EvalAltResult, Map, Scope, INT};
#[test] #[test]
fn test_map_indexing() -> Result<(), EvalAltResult> { fn test_map_indexing() -> Result<(), EvalAltResult> {
let mut engine = Engine::new(); let engine = Engine::new();
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
{ {
@ -29,8 +29,20 @@ fn test_map_indexing() -> Result<(), EvalAltResult> {
); );
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!(engine.eval::<bool>(r#"let y = #{a: 1, b: 2, c: 3}; "c" in y"#)?);
{ assert!(engine.eval::<bool>("let y = #{a: 1, b: 2, c: 3}; 'b' in y")?);
assert!(!engine.eval::<bool>(r#"let y = #{a: 1, b: 2, c: 3}; "z" in y"#)?);
assert_eq!(
engine.eval::<INT>(
r#"
let x = #{a: 1, b: 2, c: 3};
let c = x.remove("c");
x.len() + c
"#
)?,
5
);
assert_eq!( assert_eq!(
engine.eval::<INT>( engine.eval::<INT>(
r" r"
@ -64,62 +76,168 @@ fn test_map_indexing() -> Result<(), EvalAltResult> {
.len(), .len(),
4 4
); );
}
Ok(()) Ok(())
} }
#[test] #[test]
fn test_map_assign() -> Result<(), EvalAltResult> { fn test_map_assign() -> Result<(), EvalAltResult> {
let mut engine = Engine::new(); let 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().expect("should have property a");
let b = x.get("b").cloned().expect("should have property b");
let c = x.get("c$").cloned().expect("should have property c$");
assert_eq!(a.cast::<INT>(), 1); assert_eq!(
assert_eq!(b.cast::<bool>(), true); x.get("a")
assert_eq!(c.cast::<String>(), "hello"); .cloned()
.expect("should have property a")
.cast::<INT>(),
1
);
assert_eq!(
x.get("b")
.cloned()
.expect("should have property b")
.cast::<bool>(),
true
);
assert_eq!(
x.get("c$")
.cloned()
.expect("should have property c$")
.cast::<String>(),
"hello"
);
Ok(()) Ok(())
} }
#[test] #[test]
fn test_map_return() -> Result<(), EvalAltResult> { fn test_map_return() -> Result<(), EvalAltResult> {
let mut engine = Engine::new(); let 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().expect("should have property a");
let b = x.get("b").cloned().expect("should have property b");
let c = x.get("c$").cloned().expect("should have property c$");
assert_eq!(a.cast::<INT>(), 1); assert_eq!(
assert_eq!(b.cast::<bool>(), true); x.get("a")
assert_eq!(c.cast::<String>(), "hello"); .cloned()
.expect("should have property a")
.cast::<INT>(),
1
);
assert_eq!(
x.get("b")
.cloned()
.expect("should have property b")
.cast::<bool>(),
true
);
assert_eq!(
x.get("c$")
.cloned()
.expect("should have property c$")
.cast::<String>(),
"hello"
);
Ok(()) Ok(())
} }
#[test] #[test]
fn test_map_for() -> Result<(), EvalAltResult> { fn test_map_for() -> Result<(), EvalAltResult> {
let mut engine = Engine::new(); let engine = Engine::new();
assert_eq!( assert_eq!(
engine.eval::<INT>( engine
.eval::<String>(
r#" r#"
let map = #{a: 1, b: true, c: 123.456}; let map = #{a: 1, b_x: true, "$c d e!": "hello"};
let s = ""; let s = "";
for key in keys(map) { for key in keys(map) {
s += key; s += key;
} }
s.len() s
"# "#
)?, )?
3 .len(),
11
); );
Ok(()) Ok(())
} }
#[test]
/// Because a Rhai object map literal is almost the same as JSON,
/// it is possible to convert from JSON into a Rhai object map.
fn test_map_json() -> Result<(), EvalAltResult> {
let engine = Engine::new();
let json = r#"{"a":1, "b":true, "c":42, "$d e f!":"hello", "z":null}"#;
let map = engine.parse_json(json, true)?;
assert!(!map.contains_key("x"));
assert_eq!(
map.get("a")
.cloned()
.expect("should have property a")
.cast::<INT>(),
1
);
assert_eq!(
map.get("b")
.cloned()
.expect("should have property b")
.cast::<bool>(),
true
);
assert_eq!(
map.get("c")
.cloned()
.expect("should have property a")
.cast::<INT>(),
42
);
assert_eq!(
map.get("$d e f!")
.cloned()
.expect("should have property $d e f!")
.cast::<String>(),
"hello"
);
assert_eq!(
map.get("z")
.cloned()
.expect("should have property z")
.cast::<()>(),
()
);
#[cfg(not(feature = "no_index"))]
{
let mut scope = Scope::new();
scope.push_constant("map", map);
assert_eq!(
engine
.eval_with_scope::<String>(
&mut scope,
r#"
let s = "";
for key in keys(map) {
s += key;
}
s
"#
)?
.len(),
11
);
}
Ok(())
}

View File

@ -2,7 +2,7 @@ use rhai::{Engine, EvalAltResult, INT};
#[test] #[test]
fn test_math() -> Result<(), EvalAltResult> { fn test_math() -> Result<(), EvalAltResult> {
let mut engine = Engine::new(); let engine = Engine::new();
assert_eq!(engine.eval::<INT>("1 + 2")?, 3); assert_eq!(engine.eval::<INT>("1 + 2")?, 3);
assert_eq!(engine.eval::<INT>("1 - 2")?, -1); assert_eq!(engine.eval::<INT>("1 - 2")?, -1);

View File

@ -1,12 +1,11 @@
use rhai::{Engine, EvalAltResult, RegisterFn, INT}; use rhai::{Engine, EvalAltResult, RegisterFn, INT};
#[test] #[test]
#[cfg(not(feature = "no_stdlib"))]
fn test_mismatched_op() { fn test_mismatched_op() {
let mut engine = Engine::new(); let engine = Engine::new();
assert!( assert!(
matches!(engine.eval::<INT>(r#"60 + "hello""#).expect_err("expects error"), matches!(engine.eval::<INT>(r#""hello, " + "world!""#).expect_err("expects error"),
EvalAltResult::ErrorMismatchOutputType(err, _) if err == "string") EvalAltResult::ErrorMismatchOutputType(err, _) if err == "string")
); );
} }

View File

@ -2,7 +2,7 @@ use rhai::{Engine, EvalAltResult};
#[test] #[test]
fn test_not() -> Result<(), EvalAltResult> { fn test_not() -> Result<(), EvalAltResult> {
let mut engine = Engine::new(); let engine = Engine::new();
assert_eq!( assert_eq!(
engine.eval::<bool>("let not_true = !true; not_true")?, engine.eval::<bool>("let not_true = !true; not_true")?,

View File

@ -2,7 +2,7 @@ use rhai::{Engine, EvalAltResult, INT};
#[test] #[test]
fn test_number_literal() -> Result<(), EvalAltResult> { fn test_number_literal() -> Result<(), EvalAltResult> {
let mut engine = Engine::new(); let engine = Engine::new();
assert_eq!(engine.eval::<INT>("65")?, 65); assert_eq!(engine.eval::<INT>("65")?, 65);
@ -11,7 +11,7 @@ fn test_number_literal() -> Result<(), EvalAltResult> {
#[test] #[test]
fn test_hex_literal() -> Result<(), EvalAltResult> { fn test_hex_literal() -> Result<(), EvalAltResult> {
let mut engine = Engine::new(); let engine = Engine::new();
assert_eq!(engine.eval::<INT>("let x = 0xf; x")?, 15); assert_eq!(engine.eval::<INT>("let x = 0xf; x")?, 15);
assert_eq!(engine.eval::<INT>("let x = 0xff; x")?, 255); assert_eq!(engine.eval::<INT>("let x = 0xff; x")?, 255);
@ -21,7 +21,7 @@ fn test_hex_literal() -> Result<(), EvalAltResult> {
#[test] #[test]
fn test_octal_literal() -> Result<(), EvalAltResult> { fn test_octal_literal() -> Result<(), EvalAltResult> {
let mut engine = Engine::new(); let engine = Engine::new();
assert_eq!(engine.eval::<INT>("let x = 0o77; x")?, 63); assert_eq!(engine.eval::<INT>("let x = 0o77; x")?, 63);
assert_eq!(engine.eval::<INT>("let x = 0o1234; x")?, 668); assert_eq!(engine.eval::<INT>("let x = 0o1234; x")?, 668);
@ -31,7 +31,7 @@ fn test_octal_literal() -> Result<(), EvalAltResult> {
#[test] #[test]
fn test_binary_literal() -> Result<(), EvalAltResult> { fn test_binary_literal() -> Result<(), EvalAltResult> {
let mut engine = Engine::new(); let engine = Engine::new();
assert_eq!(engine.eval::<INT>("let x = 0b1111; x")?, 15); assert_eq!(engine.eval::<INT>("let x = 0b1111; x")?, 15);
assert_eq!( assert_eq!(

View File

@ -2,7 +2,7 @@ use rhai::{Engine, EvalAltResult, INT};
#[test] #[test]
fn test_ops() -> Result<(), EvalAltResult> { fn test_ops() -> Result<(), EvalAltResult> {
let mut engine = Engine::new(); let engine = Engine::new();
assert_eq!(engine.eval::<INT>("60 + 5")?, 65); assert_eq!(engine.eval::<INT>("60 + 5")?, 65);
assert_eq!(engine.eval::<INT>("(1 + 2) * (6 - 4) / 2")?, 3); assert_eq!(engine.eval::<INT>("(1 + 2) * (6 - 4) / 2")?, 3);
@ -11,8 +11,8 @@ fn test_ops() -> Result<(), EvalAltResult> {
} }
#[test] #[test]
fn test_op_prec() -> Result<(), EvalAltResult> { fn test_op_precedence() -> Result<(), EvalAltResult> {
let mut engine = Engine::new(); let engine = Engine::new();
assert_eq!( assert_eq!(
engine.eval::<INT>("let x = 0; if x == 10 || true { x = 1} x")?, engine.eval::<INT>("let x = 0; if x == 10 || true { x = 1} x")?,

View File

@ -8,7 +8,7 @@ const EPSILON: FLOAT = 0.000_000_000_1;
#[test] #[test]
fn test_power_of() -> Result<(), EvalAltResult> { fn test_power_of() -> Result<(), EvalAltResult> {
let mut engine = Engine::new(); let engine = Engine::new();
assert_eq!(engine.eval::<INT>("2 ~ 3")?, 8); assert_eq!(engine.eval::<INT>("2 ~ 3")?, 8);
assert_eq!(engine.eval::<INT>("(-2 ~ 3)")?, -8); assert_eq!(engine.eval::<INT>("(-2 ~ 3)")?, -8);
@ -29,7 +29,7 @@ fn test_power_of() -> Result<(), EvalAltResult> {
#[test] #[test]
fn test_power_of_equals() -> Result<(), EvalAltResult> { fn test_power_of_equals() -> Result<(), EvalAltResult> {
let mut engine = Engine::new(); let engine = Engine::new();
assert_eq!(engine.eval::<INT>("let x = 2; x ~= 3; x")?, 8); assert_eq!(engine.eval::<INT>("let x = 2; x ~= 3; x")?, 8);
assert_eq!(engine.eval::<INT>("let x = -2; x ~= 3; x")?, -8); assert_eq!(engine.eval::<INT>("let x = -2; x ~= 3; x")?, -8);

View File

@ -1,5 +1,3 @@
#![cfg(not(feature = "no_object"))]
///! This test simulates an external command object that is driven by a script. ///! This test simulates an external command object that is driven by a script.
use rhai::{Engine, EvalAltResult, RegisterFn, Scope, INT}; use rhai::{Engine, EvalAltResult, RegisterFn, Scope, INT};
use std::sync::{Arc, Mutex}; use std::sync::{Arc, Mutex};
@ -40,8 +38,9 @@ impl CommandWrapper {
} }
} }
#[cfg(not(feature = "no_object"))]
#[test] #[test]
fn test_side_effects() -> Result<(), EvalAltResult> { fn test_side_effects_command() -> Result<(), EvalAltResult> {
let mut engine = Engine::new(); let mut engine = Engine::new();
let mut scope = Scope::new(); let mut scope = Scope::new();
@ -79,3 +78,22 @@ fn test_side_effects() -> Result<(), EvalAltResult> {
Ok(()) Ok(())
} }
#[test]
fn test_side_effects_print() -> Result<(), EvalAltResult> {
use std::sync::RwLock;
let result = RwLock::new(String::from(""));
{
let mut engine = Engine::new();
// Override action of 'print' function
engine.on_print(|s| result.write().unwrap().push_str(s));
engine.consume("print(40 + 2);")?;
}
assert_eq!(*result.read().unwrap(), "42");
Ok(())
}

30
tests/stack.rs Normal file
View File

@ -0,0 +1,30 @@
#![cfg(not(feature = "no_function"))]
use rhai::{Engine, EvalAltResult};
#[test]
fn test_stack_overflow() -> Result<(), EvalAltResult> {
let engine = Engine::new();
assert_eq!(
engine.eval::<i64>(
r"
fn foo(n) { if n == 0 { 0 } else { n + foo(n-1) } }
foo(30)
",
)?,
465
);
match engine.eval::<()>(
r"
fn foo(n) { if n == 0 { 0 } else { n + foo(n-1) } }
foo(1000)
",
) {
Ok(_) => panic!("should be stack overflow"),
Err(EvalAltResult::ErrorStackOverflow(_)) => (),
Err(_) => panic!("should be stack overflow"),
}
Ok(())
}

View File

@ -2,7 +2,7 @@ use rhai::{Engine, EvalAltResult};
#[test] #[test]
fn test_string() -> Result<(), EvalAltResult> { fn test_string() -> Result<(), EvalAltResult> {
let mut engine = Engine::new(); let engine = Engine::new();
assert_eq!( assert_eq!(
engine.eval::<String>(r#""Test string: \u2764""#)?, engine.eval::<String>(r#""Test string: \u2764""#)?,
@ -12,18 +12,24 @@ fn test_string() -> Result<(), EvalAltResult> {
engine.eval::<String>(r#""Test string: \x58""#)?, engine.eval::<String>(r#""Test string: \x58""#)?,
"Test string: X" "Test string: X"
); );
assert_eq!(engine.eval::<String>(r#""\"hello\"""#)?, r#""hello""#);
assert_eq!(engine.eval::<String>(r#""foo" + "bar""#)?, "foobar"); assert_eq!(engine.eval::<String>(r#""foo" + "bar""#)?, "foobar");
assert!(engine.eval::<bool>(r#"let y = "hello, world!"; "world" in y"#)?);
assert!(engine.eval::<bool>(r#"let y = "hello, world!"; 'w' in y"#)?);
assert!(!engine.eval::<bool>(r#"let y = "hello, world!"; "hey" in y"#)?);
#[cfg(not(feature = "no_stdlib"))] #[cfg(not(feature = "no_stdlib"))]
assert_eq!(engine.eval::<String>(r#""foo" + 123"#)?, "foo123"); assert_eq!(engine.eval::<String>(r#""foo" + 123"#)?, "foo123");
#[cfg(not(feature = "no_stdlib"))]
#[cfg(not(feature = "no_object"))]
assert_eq!(engine.eval::<String>("(42).to_string()")?, "42");
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
#[cfg(not(feature = "no_stdlib"))] #[cfg(not(feature = "no_stdlib"))]
assert_eq!(engine.eval::<String>(r#""foo" + 123.4556"#)?, "foo123.4556"); assert_eq!(engine.eval::<String>(r#""foo" + 123.4556"#)?, "foo123.4556");
#[cfg(not(feature = "no_stdlib"))]
assert_eq!(engine.eval::<String>("(42).to_string()")?, "42");
Ok(()) Ok(())
} }

View File

@ -2,7 +2,7 @@ use rhai::{Engine, EvalAltResult};
#[test] #[test]
fn test_throw() { fn test_throw() {
let mut engine = Engine::new(); let engine = Engine::new();
assert!(matches!( assert!(matches!(
engine.eval::<()>(r#"if true { throw "hello" }"#).expect_err("expects error"), engine.eval::<()>(r#"if true { throw "hello" }"#).expect_err("expects error"),

39
tests/time.rs Normal file
View File

@ -0,0 +1,39 @@
#![cfg(not(feature = "no_stdlib"))]
use rhai::{Engine, EvalAltResult, INT};
#[cfg(not(feature = "no_float"))]
use rhai::FLOAT;
#[test]
fn test_timestamp() -> Result<(), EvalAltResult> {
let engine = Engine::new();
assert_eq!(engine.eval::<String>("type_of(timestamp())")?, "timestamp");
#[cfg(not(feature = "no_float"))]
assert!(
engine.eval::<FLOAT>(
r#"
let time = timestamp();
let x = 10_000;
while x > 0 { x -= 1; }
elapsed(time)
"#
)? < 10.0
);
#[cfg(feature = "no_float")]
assert!(
engine.eval::<INT>(
r#"
let time = timestamp();
let x = 10_000;
while x > 0 { x -= 1; }
elapsed(time)
"#
)? < 10
);
Ok(())
}

View File

@ -4,7 +4,7 @@ use rhai::{Engine, EvalAltResult, INT};
// TODO also add test case for unary after compound // TODO also add test case for unary after compound
// Hah, turns out unary + has a good use after all! // Hah, turns out unary + has a good use after all!
fn test_unary_after_binary() -> Result<(), EvalAltResult> { fn test_unary_after_binary() -> Result<(), EvalAltResult> {
let mut engine = Engine::new(); let engine = Engine::new();
assert_eq!(engine.eval::<INT>("10 % +4")?, 2); assert_eq!(engine.eval::<INT>("10 % +4")?, 2);
assert_eq!(engine.eval::<INT>("10 << +4")?, 160); assert_eq!(engine.eval::<INT>("10 << +4")?, 160);

View File

@ -2,7 +2,7 @@ use rhai::{Engine, EvalAltResult, INT};
#[test] #[test]
fn test_unary_minus() -> Result<(), EvalAltResult> { fn test_unary_minus() -> Result<(), EvalAltResult> {
let mut engine = Engine::new(); let engine = Engine::new();
assert_eq!(engine.eval::<INT>("let x = -5; x")?, -5); assert_eq!(engine.eval::<INT>("let x = -5; x")?, -5);

View File

@ -2,21 +2,21 @@ use rhai::{Engine, EvalAltResult};
#[test] #[test]
fn test_unit() -> Result<(), EvalAltResult> { fn test_unit() -> Result<(), EvalAltResult> {
let mut engine = Engine::new(); let engine = Engine::new();
engine.eval::<()>("let x = (); x")?; engine.eval::<()>("let x = (); x")?;
Ok(()) Ok(())
} }
#[test] #[test]
fn test_unit_eq() -> Result<(), EvalAltResult> { fn test_unit_eq() -> Result<(), EvalAltResult> {
let mut engine = Engine::new(); let engine = Engine::new();
assert_eq!(engine.eval::<bool>("let x = (); let y = (); x == y")?, true); assert_eq!(engine.eval::<bool>("let x = (); let y = (); x == y")?, true);
Ok(()) Ok(())
} }
#[test] #[test]
fn test_unit_with_spaces() -> Result<(), EvalAltResult> { fn test_unit_with_spaces() -> Result<(), EvalAltResult> {
let mut engine = Engine::new(); let engine = Engine::new();
engine.eval::<()>("let x = ( ); x")?; engine.eval::<()>("let x = ( ); x")?;
Ok(()) Ok(())
} }

View File

@ -2,7 +2,7 @@ use rhai::{Engine, EvalAltResult, Scope, INT};
#[test] #[test]
fn test_var_scope() -> Result<(), EvalAltResult> { fn test_var_scope() -> Result<(), EvalAltResult> {
let mut engine = Engine::new(); let engine = Engine::new();
let mut scope = Scope::new(); let mut scope = Scope::new();
engine.eval_with_scope::<()>(&mut scope, "let x = 4 + 5")?; engine.eval_with_scope::<()>(&mut scope, "let x = 4 + 5")?;
@ -21,7 +21,7 @@ fn test_var_scope() -> Result<(), EvalAltResult> {
#[test] #[test]
fn test_scope_eval() -> Result<(), EvalAltResult> { fn test_scope_eval() -> Result<(), EvalAltResult> {
let mut engine = Engine::new(); let engine = Engine::new();
// First create the state // First create the state
let mut scope = Scope::new(); let mut scope = Scope::new();

View File

@ -2,7 +2,7 @@ use rhai::{Engine, EvalAltResult, INT};
#[test] #[test]
fn test_while() -> Result<(), EvalAltResult> { fn test_while() -> Result<(), EvalAltResult> {
let mut engine = Engine::new(); let engine = Engine::new();
assert_eq!( assert_eq!(
engine.eval::<INT>( engine.eval::<INT>(