Merge pull request #128 from schungx/master
Reentrant Engine, Rust anonymous functions, `in` expression, timestamps, bug fixes
This commit is contained in:
commit
2330dcb94a
20
Cargo.toml
20
Cargo.toml
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "rhai"
|
||||
version = "0.11.0"
|
||||
version = "0.12.0"
|
||||
edition = "2018"
|
||||
authors = ["Jonathan Turner", "Lukáš Hozda", "Stephen Chung"]
|
||||
description = "Embedded scripting for Rust"
|
||||
@ -17,19 +17,17 @@ keywords = [ "scripting" ]
|
||||
categories = [ "no-std", "embedded", "parser-implementations" ]
|
||||
|
||||
[dependencies]
|
||||
num-traits = "*"
|
||||
num-traits = { version = "0.2.11", default-features = false }
|
||||
|
||||
[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 = []
|
||||
unchecked = [] # unchecked arithmetic
|
||||
no_stdlib = [] # no standard library of utility functions
|
||||
no_index = [] # no arrays and indexing
|
||||
no_float = [] # no floating-point
|
||||
no_function = [] # no script-defined functions
|
||||
no_object = [] # no custom objects
|
||||
no_optimize = [] # no script optimizer
|
||||
optimize_full = [] # set optimization level to Full (default is Simple) - this is a feature used only to simplify testing
|
||||
only_i32 = [] # set INT=i32 (useful for 32-bit systems)
|
||||
only_i64 = [] # set INT=i64 (default) and disable support for all other integer types
|
||||
sync = [] # restrict to only types that implement Send + Sync
|
||||
@ -37,6 +35,10 @@ sync = [] # restrict to only types that implement Send + Sync
|
||||
# compiling for no-std
|
||||
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]
|
||||
lto = "fat"
|
||||
codegen-units = 1
|
||||
@ -44,21 +46,21 @@ codegen-units = 1
|
||||
#panic = 'abort' # remove stack backtrace for no-std
|
||||
|
||||
[dependencies.libm]
|
||||
version = "*"
|
||||
version = "0.2.1"
|
||||
optional = true
|
||||
|
||||
[dependencies.core-error]
|
||||
version = "*"
|
||||
version = "0.0.0"
|
||||
features = ["alloc"]
|
||||
optional = true
|
||||
|
||||
[dependencies.hashbrown]
|
||||
version = "*"
|
||||
version = "0.7.1"
|
||||
default-features = false
|
||||
features = ["ahash", "nightly", "inline-more"]
|
||||
optional = true
|
||||
|
||||
[dependencies.ahash]
|
||||
version = "*"
|
||||
version = "0.3.2"
|
||||
default-features = false
|
||||
optional = true
|
||||
|
495
README.md
495
README.md
@ -27,7 +27,7 @@ Rhai's current features set:
|
||||
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`.
|
||||
|
||||
**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
|
||||
------------
|
||||
@ -36,7 +36,7 @@ Install the Rhai crate by adding this line to `dependencies`:
|
||||
|
||||
```toml
|
||||
[dependencies]
|
||||
rhai = "0.11.0"
|
||||
rhai = "0.12.0"
|
||||
```
|
||||
|
||||
Use the latest released crate version on [`crates.io`](https::/crates.io/crates/rhai/):
|
||||
@ -59,26 +59,24 @@ Beware that in order to use pre-releases (e.g. alpha and beta), the exact versio
|
||||
Optional features
|
||||
-----------------
|
||||
|
||||
| Feature | Description |
|
||||
| ------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `no_stdlib` | Exclude the standard library of utility functions in the build, and only include the minimum necessary functionalities. Standard types are not affected. |
|
||||
| `unchecked` | Exclude arithmetic checking (such as overflows and division by zero). Beware that a bad script may panic the entire system! |
|
||||
| `no_function` | Disable script-defined functions if not needed. |
|
||||
| `no_index` | Disable arrays and indexing features if not needed. |
|
||||
| `no_object` | Disable support for custom types and objects. |
|
||||
| `no_float` | Disable floating-point numbers and math if not needed. |
|
||||
| `no_optimize` | Disable the script optimizer. |
|
||||
| `only_i32` | Set the system integer type to `i32` and disable all other integer types. `INT` is set to `i32`. |
|
||||
| `only_i64` | Set the system integer type to `i64` and disable all other integer types. `INT` is set to `i64`. |
|
||||
| `no_std` | Build for `no-std`. Notice that additional dependencies will be pulled in to replace `std` features. |
|
||||
| `sync` | Restrict all values types to those that are `Send + Sync`. Under this feature, [`Engine`], [`Scope`] and `AST` are all `Send + Sync`. |
|
||||
| Feature | Description |
|
||||
| ------------- | ------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `unchecked` | Exclude arithmetic checking (such as overflows and division by zero). Beware that a bad script may panic the entire system! |
|
||||
| `no_function` | Disable script-defined functions if not needed. |
|
||||
| `no_index` | Disable [arrays] and indexing features if not needed. |
|
||||
| `no_object` | Disable support for custom types and objects. |
|
||||
| `no_float` | Disable floating-point numbers and math if not needed. |
|
||||
| `no_optimize` | Disable the script optimizer. |
|
||||
| `only_i32` | Set the system integer type to `i32` and disable all other integer types. `INT` is set to `i32`. |
|
||||
| `only_i64` | Set the system integer type to `i64` and disable all other integer types. `INT` is set to `i64`. |
|
||||
| `no_std` | Build for `no-std`. Notice that additional dependencies will be pulled in to replace `std` features. |
|
||||
| `sync` | Restrict all values types to those that are `Send + Sync`. Under this feature, [`Engine`], [`Scope`] and `AST` are all `Send + Sync`. |
|
||||
|
||||
By default, Rhai includes all the standard functionalities in a small, tight package.
|
||||
Most features are here to opt-**out** of certain functionalities that are not needed.
|
||||
Excluding unneeded functionalities can result in smaller, faster builds as well as less bugs due to a more restricted language.
|
||||
|
||||
[`unchecked`]: #optional-features
|
||||
[`no_stdlib`]: #optional-features
|
||||
[`no_index`]: #optional-features
|
||||
[`no_float`]: #optional-features
|
||||
[`no_function`]: #optional-features
|
||||
@ -104,7 +102,7 @@ A number of examples can be found in the `examples` folder:
|
||||
|
||||
| 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 |
|
||||
| [`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 |
|
||||
@ -129,7 +127,7 @@ There are also a number of examples scripts that showcase Rhai's features, all i
|
||||
|
||||
| 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 |
|
||||
| [`comments.rhai`](scripts/comments.rhai) | just comments |
|
||||
| [`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 |
|
||||
| [`op2.rhai`](scripts/op2.rhai) | simple addition and multiplication |
|
||||
| [`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 |
|
||||
|
||||
| Example scripts | Description |
|
||||
@ -160,23 +158,27 @@ 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
|
||||
use rhai::{Engine, EvalAltResult};
|
||||
|
||||
fn main() -> Result<(), EvalAltResult>
|
||||
{
|
||||
let mut engine = Engine::new();
|
||||
let engine = Engine::new();
|
||||
|
||||
let result = engine.eval::<i64>("40 + 2")?;
|
||||
|
||||
println!("Answer: {}", result); // prints 42
|
||||
println!("Answer: {}", result); // prints 42
|
||||
|
||||
Ok(())
|
||||
}
|
||||
```
|
||||
|
||||
`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.
|
||||
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'
|
||||
```
|
||||
|
||||
### Compiling scripts (to AST)
|
||||
|
||||
To repeatedly evaluate a script, _compile_ it first into an AST (abstract syntax tree) form:
|
||||
|
||||
```rust
|
||||
@ -203,7 +207,7 @@ let ast = engine.compile("40 + 2")?;
|
||||
for _ in 0..42 {
|
||||
let result: i64 = engine.eval_ast(&ast)?;
|
||||
|
||||
println!("Answer #{}: {}", i, result); // prints 42
|
||||
println!("Answer #{}: {}", i, result); // prints 42
|
||||
}
|
||||
```
|
||||
|
||||
@ -213,8 +217,9 @@ Compiling a script file is also supported:
|
||||
let ast = engine.compile_file("hello_world.rhai".into())?;
|
||||
```
|
||||
|
||||
Rhai also allows working _backwards_ from the other direction - i.e. calling a Rhai-scripted function from Rust -
|
||||
via `call_fn` or its cousins `call_fn1` (one argument) and `call_fn0` (no argument).
|
||||
### Calling Rhai functions from Rust
|
||||
|
||||
Rhai also allows working _backwards_ from the other direction - i.e. calling a Rhai-scripted function from Rust via `call_fn`.
|
||||
|
||||
```rust
|
||||
// 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
|
||||
let mut scope = Scope::new();
|
||||
|
||||
// Evaluate a function defined in the script, passing arguments into the script as a tuple
|
||||
// if there are more than one. Beware, arguments must be of the correct types because
|
||||
// Rhai does not have built-in type conversions. If arguments of the wrong types are passed,
|
||||
// the Engine will not find the function.
|
||||
// Evaluate a function defined in the script, passing arguments into the script as a tuple.
|
||||
// Beware, arguments must be of the correct types because Rhai does not have built-in type conversions.
|
||||
// If arguments of the wrong types are passed, the Engine will not find the function.
|
||||
|
||||
let result: i64 = engine.call_fn(&mut scope, &ast, "hello", ( String::from("abc"), 123_i64 ) )?;
|
||||
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
// put arguments in a tuple
|
||||
|
||||
let result: i64 = engine.call_fn1(&mut scope, &ast, "hello", 123_i64)?
|
||||
// ^^^^^^^^ use 'call_fn1' for one argument
|
||||
let result: i64 = engine.call_fn(&mut scope, &ast, "hello", (123_i64,) )?
|
||||
// ^^^^^^^^^^ tuple of one
|
||||
|
||||
let result: i64 = engine.call_fn0(&mut scope, &ast, "hello")?
|
||||
// ^^^^^^^^ use 'call_fn0' for no arguments
|
||||
let result: i64 = engine.call_fn(&mut scope, &ast, "hello", () )?
|
||||
// ^^ 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
|
||||
-------------------------
|
||||
|
||||
[`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_.
|
||||
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")?;
|
||||
```
|
||||
|
||||
When evaluation _expressions_, no control-flow statement (e.g. `if`, `while`, `for`) is not supported and will be
|
||||
parse errors when encountered - not even variable assignments.
|
||||
When evaluation _expressions_, no full-blown statement (e.g. `if`, `while`, `for`) - not even variable assignments -
|
||||
is supported and will be considered parse errors when encountered.
|
||||
|
||||
```rust
|
||||
// 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
|
||||
[`to_string()`]: #values-and-types
|
||||
[`()`]: #values-and-types
|
||||
|
||||
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. |
|
||||
| **Array** (disabled with [`no_index`]) | `rhai::Array` | `"array"` | `"[ ? ? ? ]"` |
|
||||
| **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_ |
|
||||
| **System integer** (current configuration) | `rhai::INT` (`i32` or `i64`) | `"i32"` or `"i64"` | `"42"`, `"123"` etc. |
|
||||
| **System floating-point** (current configuration, disabled with [`no_float`]) | `rhai::FLOAT` (`f32` or `f64`) | `"f32"` or `"f64"` | `"123.456"` etc. |
|
||||
| **Nothing/void/nil/null** (or whatever you want to call it) | `()` | `"()"` | `""` _(empty string)_ |
|
||||
|
||||
[`()`]: #values-and-types
|
||||
|
||||
All types are treated strictly separate by Rhai, meaning that `i32` and `i64` and `u32` are completely different -
|
||||
they even cannot be added together. This is very similar to Rust.
|
||||
|
||||
The default integer type is `i64`. If other integer types are not needed, it is possible to exclude them and make a
|
||||
smaller build with the [`only_i64`] feature.
|
||||
|
||||
If only 32-bit integers are needed, enabling the [`only_i32`] feature will remove support for all integer types other than `i32`,
|
||||
including `i64`. This is useful on some 32-bit systems where using 64-bit integers incurs a performance penalty.
|
||||
If only 32-bit integers are needed, enabling the [`only_i32`] feature will remove support for all integer types other than `i32`, including `i64`.
|
||||
This is useful on some 32-bit systems where using 64-bit integers incurs a performance penalty.
|
||||
|
||||
If no floating-point is needed or supported, use the [`no_float`] feature to remove it.
|
||||
|
||||
The `to_string` function converts a standard type into a string for display purposes.
|
||||
The `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.
|
||||
|
||||
@ -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,
|
||||
or an object map with `Dynamic` property values. To get the _real_ values, the actual value types _must_ be known in advance.
|
||||
In Rust, sometimes a `Dynamic` forms part of a returned value - a good example is an [array] with `Dynamic` elements,
|
||||
or an [object map] with `Dynamic` property values. To get the _real_ values, the actual value types _must_ be known in advance.
|
||||
There is no easy way for Rust to decide, at run-time, what type the `Dynamic` value is (short of using the `type_name`
|
||||
function and match against the name).
|
||||
|
||||
@ -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.
|
||||
|
||||
```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 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.
|
||||
|
||||
```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 item = list[0]; // an element in an 'Array' is 'Dynamic'
|
||||
|
||||
match item.type_name() { // 'type_name' returns the name of the actual Rust type
|
||||
"i64" => ...
|
||||
"std::string::String" => ...
|
||||
"alloc::string::String" => ...
|
||||
"bool" => ...
|
||||
"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"
|
||||
```
|
||||
|
||||
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
|
||||
----------------------
|
||||
|
||||
@ -431,8 +514,8 @@ To call these functions, they need to be registered with the [`Engine`].
|
||||
|
||||
```rust
|
||||
use rhai::{Engine, EvalAltResult};
|
||||
use rhai::RegisterFn; // use `RegisterFn` trait for `register_fn`
|
||||
use rhai::{Any, Dynamic, RegisterDynamicFn}; // use `RegisterDynamicFn` trait for `register_dynamic_fn`
|
||||
use rhai::RegisterFn; // use 'RegisterFn' trait for 'register_fn'
|
||||
use rhai::{Any, Dynamic, RegisterDynamicFn}; // use 'RegisterDynamicFn' trait for 'register_dynamic_fn'
|
||||
|
||||
// Normal function
|
||||
fn add(x: i64, y: i64) -> i64 {
|
||||
@ -446,7 +529,7 @@ fn get_an_any() -> Dynamic {
|
||||
|
||||
fn main() -> Result<(), EvalAltResult>
|
||||
{
|
||||
let mut engine = Engine::new();
|
||||
let engine = Engine::new();
|
||||
|
||||
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.
|
||||
|
||||
```rust
|
||||
use rhai::Any; // Pull in the trait
|
||||
use rhai::Any; // pull in the trait
|
||||
|
||||
fn decide(yes_no: bool) -> Dynamic {
|
||||
if yes_no {
|
||||
@ -497,7 +580,7 @@ fn show_it<T: Display>(x: &mut T) -> () {
|
||||
|
||||
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 bool)->());
|
||||
@ -519,13 +602,13 @@ and the error text gets converted into `EvalAltResult::ErrorRuntime`.
|
||||
|
||||
```rust
|
||||
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
|
||||
fn safe_divide(x: i64, y: i64) -> Result<i64, EvalAltResult> {
|
||||
if y == 0 {
|
||||
// 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 {
|
||||
Ok(x / y)
|
||||
}
|
||||
@ -533,13 +616,13 @@ fn safe_divide(x: i64, y: i64) -> Result<i64, EvalAltResult> {
|
||||
|
||||
fn main()
|
||||
{
|
||||
let mut engine = Engine::new();
|
||||
let engine = Engine::new();
|
||||
|
||||
// Fallible functions that return Result values must use register_result_fn()
|
||||
engine.register_result_fn("divide", safe_divide);
|
||||
|
||||
if let Err(error) = engine.eval::<i64>("divide(40, 0)") {
|
||||
println!("Error: {:?}", error); // prints ErrorRuntime("Division by zero detected!", (1, 1)")
|
||||
println!("Error: {:?}", error); // prints ErrorRuntime("Division by zero detected!", (1, 1)")
|
||||
}
|
||||
}
|
||||
```
|
||||
@ -584,7 +667,7 @@ impl TestStruct {
|
||||
|
||||
fn main() -> Result<(), EvalAltResult>
|
||||
{
|
||||
let mut engine = Engine::new();
|
||||
let engine = Engine::new();
|
||||
|
||||
engine.register_type::<TestStruct>();
|
||||
|
||||
@ -593,7 +676,7 @@ fn main() -> Result<(), EvalAltResult>
|
||||
|
||||
let result = engine.eval::<TestStruct>("let x = new_ts(); x.update(); x")?;
|
||||
|
||||
println!("result: {}", result.field); // prints 42
|
||||
println!("result: {}", result.field); // prints 42
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@ -622,7 +705,7 @@ impl TestStruct {
|
||||
}
|
||||
}
|
||||
|
||||
let mut engine = Engine::new();
|
||||
let engine = Engine::new();
|
||||
|
||||
engine.register_type::<TestStruct>();
|
||||
```
|
||||
@ -712,7 +795,7 @@ impl TestStruct {
|
||||
}
|
||||
}
|
||||
|
||||
let mut engine = Engine::new();
|
||||
let engine = Engine::new();
|
||||
|
||||
engine.register_type::<TestStruct>();
|
||||
|
||||
@ -748,7 +831,7 @@ use rhai::{Engine, Scope, EvalAltResult};
|
||||
|
||||
fn main() -> Result<(), EvalAltResult>
|
||||
{
|
||||
let mut engine = Engine::new();
|
||||
let engine = Engine::new();
|
||||
|
||||
// First create the state
|
||||
let mut scope = Scope::new();
|
||||
@ -957,7 +1040,7 @@ number = -5 - +5;
|
||||
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:
|
||||
|
||||
| Function | Description |
|
||||
@ -968,7 +1051,7 @@ The following standard functions (defined in the standard library but excluded i
|
||||
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 |
|
||||
| ---------------- | ------------------------------------------------------------ |
|
||||
@ -984,13 +1067,31 @@ The following standard functions (defined in the standard library but excluded i
|
||||
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
|
||||
hex ('`\x`_xx_') escape sequences.
|
||||
|
||||
Hex sequences map to ASCII characters, while '`\u`' maps to 16-bit common Unicode code points and '`\U`' maps the full,
|
||||
32-bit extended Unicode code points.
|
||||
|
||||
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).
|
||||
This is similar to most other languages where strings are internally represented not as UTF-8 but as arrays of multi-byte
|
||||
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.
|
||||
|
||||
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"`.
|
||||
|
||||
@ -1039,22 +1140,29 @@ record == "Bob C. Davis: age 42 ❤\n"; // '\n' = new-line
|
||||
// (disabled with 'no_index')
|
||||
record[4] = '\x58'; // 0x58 = 'X'
|
||||
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 |
|
||||
| ---------- | ------------------------------------------------------------------------ |
|
||||
| `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 |
|
||||
The following standard methods (defined in the standard library but excluded if using a [raw `Engine`]) operate on strings:
|
||||
|
||||
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
|
||||
let full_name == " Bob C. Davis ";
|
||||
@ -1086,6 +1194,10 @@ full_name.len() == 0;
|
||||
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.
|
||||
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.
|
||||
@ -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.
|
||||
|
||||
The following functions (defined in the standard library but excluded if [`no_stdlib`]) operate on arrays:
|
||||
### Built-in functions
|
||||
|
||||
| Function | Description |
|
||||
| ------------ | ------------------------------------------------------------------------------------- |
|
||||
| `push` | inserts an element at the end |
|
||||
| `append` | concatenates the second array to the end of the first |
|
||||
| `+` operator | concatenates the first array with the second |
|
||||
| `pop` | removes the last element and returns it ([`()`] if empty) |
|
||||
| `shift` | removes the first element and returns it ([`()`] if empty) |
|
||||
| `len` | returns the number of elements |
|
||||
| `pad` | pads the array with an element until a specified length |
|
||||
| `clear` | empties the array |
|
||||
| `truncate` | cuts off the array at exactly a specified length (discarding all subsequent elements) |
|
||||
The following methods (defined in the standard library but excluded if using a [raw `Engine`]) operate on arrays:
|
||||
|
||||
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
|
||||
let y = [1, 2, 3]; // array literal with 3 elements
|
||||
y[1] = 42;
|
||||
let y = [2, 3]; // array literal with 2 elements
|
||||
|
||||
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)
|
||||
let foo = ts.list[1];
|
||||
@ -1136,7 +1272,7 @@ foo == 1;
|
||||
y.push(4); // 4 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
|
||||
first == 1;
|
||||
@ -1144,7 +1280,7 @@ first == 1;
|
||||
let last = y.pop(); // remove the last element, 3 elements remaining
|
||||
last == 5;
|
||||
|
||||
print(y.len()); // prints 3
|
||||
y.len() == 3;
|
||||
|
||||
for item in y { // arrays can be iterated with a 'for' statement
|
||||
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
|
||||
|
||||
print(y.len()); // prints 10
|
||||
y.len() == 10;
|
||||
|
||||
y.truncate(5); // truncate the array to 5 elements
|
||||
|
||||
print(y.len()); // prints 5
|
||||
y.len() == 5;
|
||||
|
||||
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:
|
||||
@ -1172,34 +1308,40 @@ engine.register_fn("push", |list: &mut Array, item: MyType| list.push(Box::new(i
|
||||
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 map literals are built within braces '`#{`' ... '`}`' (_name_ `:` _value_ syntax similar to Rust)
|
||||
and separated by commas '`,`'. The property _name_ can be a simple variable name following the same
|
||||
naming rules as [variables], or an arbitrary string literal.
|
||||
naming rules as [variables], or an arbitrary [string] literal.
|
||||
|
||||
Property values can be accessed via the dot notation (_object_ `.` _property_) or index notation (_object_ `[` _property_ `]`).
|
||||
The dot notation allows only property names that follow the same naming rules as [variables].
|
||||
The index notation allows setting/getting properties of arbitrary names (even the empty string).
|
||||
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"`.
|
||||
|
||||
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 |
|
||||
| ------------ | ---------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `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) |
|
||||
The following methods (defined in the standard library but excluded if using a [raw `Engine`]) operate on object maps:
|
||||
|
||||
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
|
||||
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: 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)
|
||||
let foo = ts.list.a;
|
||||
@ -1239,10 +1384,15 @@ foo == 42;
|
||||
y.has("a") == true;
|
||||
y.has("xyz") == false;
|
||||
|
||||
y.xyz == (); // A non-existing property returns '()'
|
||||
y.xyz == (); // a non-existing property returns '()'
|
||||
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
|
||||
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
|
||||
|
||||
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
|
||||
@ -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.
|
||||
|
||||
However, if the [`no_stdlib`] feature is turned on, comparisons can only be made between restricted system types -
|
||||
`INT` (`i64` or `i32` depending on [`only_i32`] and [`only_i64`]), `f64` (if not [`no_float`]), string, array, `bool`, `char`.
|
||||
However, if 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`.
|
||||
|
||||
```rust
|
||||
42 == 42; // true
|
||||
@ -1321,7 +1543,7 @@ number <<= 2; // number = number << 2
|
||||
number >>= 1; // number = number >> 1
|
||||
```
|
||||
|
||||
The `+=` operator can also be used to build strings:
|
||||
The `+=` operator can also be used to build [strings]:
|
||||
|
||||
```rust
|
||||
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.
|
||||
|
||||
```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;
|
||||
|
||||
let x = if false { 42 }; // No else branch defaults to '()'
|
||||
let x = if decision { 42 }; // no else branch defaults to '()'
|
||||
x == ();
|
||||
```
|
||||
|
||||
@ -1397,7 +1620,7 @@ loop {
|
||||
`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
|
||||
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
|
||||
}
|
||||
|
||||
|
||||
// The 'range' function also takes a step
|
||||
for x in range(0, 50, 3) { // step by 3
|
||||
if x > 10 { continue; } // skip to the next iteration
|
||||
@ -1424,15 +1646,20 @@ for x in range(0, 50, 3) { // step by 3
|
||||
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};
|
||||
|
||||
// Remember that keys are returned in random order
|
||||
// Property names are returned in random order
|
||||
for x in keys(map) {
|
||||
if x > 10 { continue; } // skip to the next iteration
|
||||
print(x);
|
||||
if x == 42 { break; } // break out of for loop
|
||||
}
|
||||
|
||||
// Property values are returned in random order
|
||||
for val in values(map) {
|
||||
print(val);
|
||||
}
|
||||
```
|
||||
|
||||
`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 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);
|
||||
```
|
||||
|
||||
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_
|
||||
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:
|
||||
@ -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_,
|
||||
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
|
||||
let script = "x += 32";
|
||||
|
@ -1,7 +1,7 @@
|
||||
use rhai::{Engine, EvalAltResult, INT};
|
||||
|
||||
fn main() -> Result<(), EvalAltResult> {
|
||||
let mut engine = Engine::new();
|
||||
let engine = Engine::new();
|
||||
|
||||
let result = engine.eval::<INT>("40 + 2")?;
|
||||
|
||||
|
@ -3,7 +3,7 @@
|
||||
use rhai::{Engine, EvalAltResult, INT};
|
||||
|
||||
fn main() -> Result<(), EvalAltResult> {
|
||||
let mut engine = Engine::new();
|
||||
let engine = Engine::new();
|
||||
|
||||
let result = engine.eval::<INT>("40 + 2")?;
|
||||
|
||||
|
@ -145,9 +145,7 @@ fn main() {
|
||||
|
||||
#[cfg(not(feature = "no_optimize"))]
|
||||
{
|
||||
engine.set_optimization_level(OptimizationLevel::Full);
|
||||
ast = engine.optimize_ast(&scope, r);
|
||||
engine.set_optimization_level(OptimizationLevel::None);
|
||||
ast = engine.optimize_ast(&scope, r, OptimizationLevel::Full);
|
||||
}
|
||||
|
||||
#[cfg(feature = "no_optimize")]
|
||||
|
@ -1,7 +1,7 @@
|
||||
use rhai::{Engine, EvalAltResult, Scope, INT};
|
||||
|
||||
fn main() -> Result<(), EvalAltResult> {
|
||||
let mut engine = Engine::new();
|
||||
let engine = Engine::new();
|
||||
let mut scope = Scope::new();
|
||||
|
||||
engine.eval_with_scope::<()>(&mut scope, "let x = 4 + 5")?;
|
||||
|
@ -1,5 +1,7 @@
|
||||
// This script uses the Sieve of Eratosthenes to calculate prime numbers.
|
||||
|
||||
let now = timestamp();
|
||||
|
||||
const MAX_NUMBER_TO_CHECK = 10_000; // 1229 primes <= 10000
|
||||
|
||||
let prime_mask = [];
|
||||
@ -24,3 +26,4 @@ for p in range(2, MAX_NUMBER_TO_CHECK) {
|
||||
}
|
||||
|
||||
print("Total " + total_primes_found + " primes.");
|
||||
print("Run time = " + now.elapsed() + " seconds.");
|
||||
|
@ -1,6 +1,7 @@
|
||||
// This script runs 1 million iterations
|
||||
// to test the speed of the scripting engine.
|
||||
|
||||
let now = timestamp();
|
||||
let x = 1_000_000;
|
||||
|
||||
print("Ready... Go!");
|
||||
@ -9,4 +10,4 @@ while x > 0 {
|
||||
x = x - 1;
|
||||
}
|
||||
|
||||
print("Finished.");
|
||||
print("Finished. Run time = " + now.elapsed() + " seconds.");
|
||||
|
423
src/api.rs
423
src/api.rs
@ -1,17 +1,15 @@
|
||||
//! Module that defines the extern API of `Engine`.
|
||||
|
||||
use crate::any::{Any, AnyExt, Dynamic};
|
||||
use crate::call::FuncArgs;
|
||||
use crate::engine::{make_getter, make_setter, Engine, FnAny, FnSpec};
|
||||
use crate::engine::{make_getter, make_setter, Engine, FnAny, FnSpec, Map};
|
||||
use crate::error::ParseError;
|
||||
use crate::fn_call::FuncArgs;
|
||||
use crate::fn_register::RegisterFn;
|
||||
use crate::optimize::{optimize_into_ast, OptimizationLevel};
|
||||
use crate::parser::{lex, parse, parse_global_expr, Position, AST};
|
||||
use crate::result::EvalAltResult;
|
||||
use crate::scope::Scope;
|
||||
|
||||
#[cfg(not(feature = "no_optimize"))]
|
||||
use crate::optimize::optimize_into_ast;
|
||||
|
||||
use crate::stdlib::{
|
||||
any::{type_name, TypeId},
|
||||
boxed::Box,
|
||||
@ -334,7 +332,7 @@ impl<'e> Engine<'e> {
|
||||
/// # fn main() -> Result<(), rhai::EvalAltResult> {
|
||||
/// use rhai::Engine;
|
||||
///
|
||||
/// let mut engine = Engine::new();
|
||||
/// let engine = Engine::new();
|
||||
///
|
||||
/// // Compile a script to an AST and store it for later evaluation
|
||||
/// let ast = engine.compile("40 + 2")?;
|
||||
@ -345,8 +343,8 @@ impl<'e> Engine<'e> {
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// ```
|
||||
pub fn compile(&self, input: &str) -> Result<AST, ParseError> {
|
||||
self.compile_with_scope(&Scope::new(), input)
|
||||
pub fn compile(&self, script: &str) -> Result<AST, ParseError> {
|
||||
self.compile_with_scope(&Scope::new(), script)
|
||||
}
|
||||
|
||||
/// 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(())
|
||||
/// # }
|
||||
/// ```
|
||||
pub fn compile_with_scope(&self, scope: &Scope, input: &str) -> Result<AST, ParseError> {
|
||||
let tokens_stream = lex(input);
|
||||
parse(&mut tokens_stream.peekable(), self, scope)
|
||||
pub fn compile_with_scope(&self, scope: &Scope, script: &str) -> Result<AST, ParseError> {
|
||||
self.compile_with_scope_and_optimization_level(scope, script, self.optimization_level)
|
||||
}
|
||||
|
||||
/// 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.
|
||||
@ -413,7 +422,7 @@ impl<'e> Engine<'e> {
|
||||
/// # fn main() -> Result<(), rhai::EvalAltResult> {
|
||||
/// 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.
|
||||
/// // Notice that a PathBuf is required which can easily be constructed from a string.
|
||||
@ -466,10 +475,52 @@ impl<'e> Engine<'e> {
|
||||
scope: &Scope,
|
||||
path: PathBuf,
|
||||
) -> Result<AST, EvalAltResult> {
|
||||
Self::read_file(path).and_then(|contents| {
|
||||
self.compile_with_scope(scope, &contents)
|
||||
.map_err(|err| err.into())
|
||||
})
|
||||
Self::read_file(path).and_then(|contents| Ok(self.compile_with_scope(scope, &contents)?))
|
||||
}
|
||||
|
||||
/// 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`,
|
||||
@ -481,7 +532,7 @@ impl<'e> Engine<'e> {
|
||||
/// # fn main() -> Result<(), rhai::EvalAltResult> {
|
||||
/// use rhai::Engine;
|
||||
///
|
||||
/// let mut engine = Engine::new();
|
||||
/// let engine = Engine::new();
|
||||
///
|
||||
/// // Compile a script to an AST and store it for later evaluation
|
||||
/// let ast = engine.compile_expression("40 + 2")?;
|
||||
@ -492,8 +543,8 @@ impl<'e> Engine<'e> {
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// ```
|
||||
pub fn compile_expression(&self, input: &str) -> Result<AST, ParseError> {
|
||||
self.compile_expression_with_scope(&Scope::new(), input)
|
||||
pub fn compile_expression(&self, script: &str) -> Result<AST, ParseError> {
|
||||
self.compile_expression_with_scope(&Scope::new(), script)
|
||||
}
|
||||
|
||||
/// 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(
|
||||
&self,
|
||||
scope: &Scope,
|
||||
input: &str,
|
||||
script: &str,
|
||||
) -> Result<AST, ParseError> {
|
||||
let tokens_stream = lex(input);
|
||||
parse_global_expr(&mut tokens_stream.peekable(), self, scope)
|
||||
let scripts = [script];
|
||||
let stream = lex(&scripts);
|
||||
parse_global_expr(&mut stream.peekable(), self, scope, self.optimization_level)
|
||||
}
|
||||
|
||||
/// Evaluate a script file.
|
||||
@ -552,7 +604,7 @@ impl<'e> Engine<'e> {
|
||||
/// # fn main() -> Result<(), rhai::EvalAltResult> {
|
||||
/// 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.
|
||||
/// let result = engine.eval_file::<i64>("script.rhai".into())?;
|
||||
@ -560,7 +612,7 @@ impl<'e> Engine<'e> {
|
||||
/// # }
|
||||
/// ```
|
||||
#[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))
|
||||
}
|
||||
|
||||
@ -572,7 +624,7 @@ impl<'e> Engine<'e> {
|
||||
/// # fn main() -> Result<(), rhai::EvalAltResult> {
|
||||
/// use rhai::{Engine, Scope};
|
||||
///
|
||||
/// let mut engine = Engine::new();
|
||||
/// let engine = Engine::new();
|
||||
///
|
||||
/// // Create initialized scope
|
||||
/// let mut scope = Scope::new();
|
||||
@ -585,7 +637,7 @@ impl<'e> Engine<'e> {
|
||||
/// ```
|
||||
#[cfg(not(feature = "no_std"))]
|
||||
pub fn eval_file_with_scope<T: Any + Clone>(
|
||||
&mut self,
|
||||
&self,
|
||||
scope: &mut Scope,
|
||||
path: PathBuf,
|
||||
) -> Result<T, EvalAltResult> {
|
||||
@ -600,14 +652,14 @@ impl<'e> Engine<'e> {
|
||||
/// # fn main() -> Result<(), rhai::EvalAltResult> {
|
||||
/// use rhai::Engine;
|
||||
///
|
||||
/// let mut engine = Engine::new();
|
||||
/// let engine = Engine::new();
|
||||
///
|
||||
/// assert_eq!(engine.eval::<i64>("40 + 2")?, 42);
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// ```
|
||||
pub fn eval<T: Any + Clone>(&mut self, input: &str) -> Result<T, EvalAltResult> {
|
||||
self.eval_with_scope(&mut Scope::new(), input)
|
||||
pub fn eval<T: Any + Clone>(&self, script: &str) -> Result<T, EvalAltResult> {
|
||||
self.eval_with_scope(&mut Scope::new(), script)
|
||||
}
|
||||
|
||||
/// Evaluate a string with own scope.
|
||||
@ -618,7 +670,7 @@ impl<'e> Engine<'e> {
|
||||
/// # fn main() -> Result<(), rhai::EvalAltResult> {
|
||||
/// use rhai::{Engine, Scope};
|
||||
///
|
||||
/// let mut engine = Engine::new();
|
||||
/// let engine = Engine::new();
|
||||
///
|
||||
/// // Create initialized scope
|
||||
/// let mut scope = Scope::new();
|
||||
@ -633,11 +685,13 @@ impl<'e> Engine<'e> {
|
||||
/// # }
|
||||
/// ```
|
||||
pub fn eval_with_scope<T: Any + Clone>(
|
||||
&mut self,
|
||||
&self,
|
||||
scope: &mut Scope,
|
||||
input: &str,
|
||||
script: &str,
|
||||
) -> 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)
|
||||
}
|
||||
|
||||
@ -649,14 +703,14 @@ impl<'e> Engine<'e> {
|
||||
/// # fn main() -> Result<(), rhai::EvalAltResult> {
|
||||
/// use rhai::Engine;
|
||||
///
|
||||
/// let mut engine = Engine::new();
|
||||
/// let engine = Engine::new();
|
||||
///
|
||||
/// assert_eq!(engine.eval_expression::<i64>("40 + 2")?, 42);
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// ```
|
||||
pub fn eval_expression<T: Any + Clone>(&mut self, input: &str) -> Result<T, EvalAltResult> {
|
||||
self.eval_expression_with_scope(&mut Scope::new(), input)
|
||||
pub fn eval_expression<T: Any + Clone>(&self, script: &str) -> Result<T, EvalAltResult> {
|
||||
self.eval_expression_with_scope(&mut Scope::new(), script)
|
||||
}
|
||||
|
||||
/// Evaluate a string containing an expression with own scope.
|
||||
@ -667,7 +721,7 @@ impl<'e> Engine<'e> {
|
||||
/// # fn main() -> Result<(), rhai::EvalAltResult> {
|
||||
/// use rhai::{Engine, Scope};
|
||||
///
|
||||
/// let mut engine = Engine::new();
|
||||
/// let engine = Engine::new();
|
||||
///
|
||||
/// // Create initialized scope
|
||||
/// let mut scope = Scope::new();
|
||||
@ -678,14 +732,14 @@ impl<'e> Engine<'e> {
|
||||
/// # }
|
||||
/// ```
|
||||
pub fn eval_expression_with_scope<T: Any + Clone>(
|
||||
&mut self,
|
||||
&self,
|
||||
scope: &mut Scope,
|
||||
input: &str,
|
||||
script: &str,
|
||||
) -> Result<T, EvalAltResult> {
|
||||
let ast = self
|
||||
.compile_expression(input)
|
||||
.map_err(EvalAltResult::ErrorParsing)?;
|
||||
|
||||
let scripts = [script];
|
||||
let stream = lex(&scripts);
|
||||
// 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)
|
||||
}
|
||||
|
||||
@ -697,7 +751,7 @@ impl<'e> Engine<'e> {
|
||||
/// # fn main() -> Result<(), rhai::EvalAltResult> {
|
||||
/// use rhai::Engine;
|
||||
///
|
||||
/// let mut engine = Engine::new();
|
||||
/// let engine = Engine::new();
|
||||
///
|
||||
/// // Compile a script to an AST and store it for later evaluation
|
||||
/// let ast = engine.compile("40 + 2")?;
|
||||
@ -707,7 +761,7 @@ impl<'e> Engine<'e> {
|
||||
/// # 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)
|
||||
}
|
||||
|
||||
@ -719,7 +773,7 @@ impl<'e> Engine<'e> {
|
||||
/// # fn main() -> Result<(), rhai::EvalAltResult> {
|
||||
/// 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
|
||||
/// let ast = engine.compile("x + 2")?;
|
||||
@ -741,7 +795,7 @@ impl<'e> Engine<'e> {
|
||||
/// # }
|
||||
/// ```
|
||||
pub fn eval_ast_with_scope<T: Any + Clone>(
|
||||
&mut self,
|
||||
&self,
|
||||
scope: &mut Scope,
|
||||
ast: &AST,
|
||||
) -> Result<T, EvalAltResult> {
|
||||
@ -756,32 +810,25 @@ impl<'e> Engine<'e> {
|
||||
}
|
||||
|
||||
pub(crate) fn eval_ast_with_scope_raw(
|
||||
&mut self,
|
||||
&self,
|
||||
scope: &mut Scope,
|
||||
ast: &AST,
|
||||
) -> Result<Dynamic, EvalAltResult> {
|
||||
let statements = {
|
||||
let AST(statements, functions) = ast;
|
||||
self.fn_lib = Some(functions.clone());
|
||||
statements
|
||||
};
|
||||
|
||||
let result = statements
|
||||
ast.0
|
||||
.iter()
|
||||
.try_fold(().into_dynamic(), |_, stmt| self.eval_stmt(scope, stmt, 0));
|
||||
|
||||
self.fn_lib = None;
|
||||
|
||||
result.or_else(|err| match err {
|
||||
EvalAltResult::Return(out, _) => Ok(out),
|
||||
_ => Err(err),
|
||||
})
|
||||
.try_fold(().into_dynamic(), |_, stmt| {
|
||||
self.eval_stmt(scope, Some(ast.1.as_ref()), stmt, 0)
|
||||
})
|
||||
.or_else(|err| match err {
|
||||
EvalAltResult::Return(out, _) => Ok(out),
|
||||
_ => Err(err),
|
||||
})
|
||||
}
|
||||
|
||||
/// 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.
|
||||
#[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))
|
||||
}
|
||||
|
||||
@ -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.
|
||||
#[cfg(not(feature = "no_std"))]
|
||||
pub fn consume_file_with_scope(
|
||||
&mut self,
|
||||
&self,
|
||||
scope: &mut Scope,
|
||||
path: PathBuf,
|
||||
) -> Result<(), EvalAltResult> {
|
||||
@ -798,127 +845,44 @@ impl<'e> Engine<'e> {
|
||||
|
||||
/// 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.
|
||||
pub fn consume(&mut self, input: &str) -> Result<(), EvalAltResult> {
|
||||
self.consume_with_scope(&mut Scope::new(), input)
|
||||
pub fn consume(&self, script: &str) -> Result<(), EvalAltResult> {
|
||||
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).
|
||||
/// Useful for when you don't need the result, but still need to keep track of possible errors.
|
||||
pub fn consume_with_scope(
|
||||
&mut self,
|
||||
scope: &mut Scope,
|
||||
input: &str,
|
||||
) -> Result<(), EvalAltResult> {
|
||||
let tokens_stream = lex(input);
|
||||
|
||||
let ast = parse(&mut tokens_stream.peekable(), self, scope)
|
||||
.map_err(EvalAltResult::ErrorParsing)?;
|
||||
pub fn consume_with_scope(&self, scope: &mut Scope, script: &str) -> Result<(), EvalAltResult> {
|
||||
let scripts = [script];
|
||||
let stream = lex(&scripts);
|
||||
|
||||
// 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)
|
||||
}
|
||||
|
||||
/// 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.
|
||||
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)
|
||||
}
|
||||
|
||||
/// 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.
|
||||
pub fn consume_ast_with_scope(
|
||||
&mut self,
|
||||
&self,
|
||||
scope: &mut Scope,
|
||||
ast: &AST,
|
||||
) -> Result<(), EvalAltResult> {
|
||||
let statements = {
|
||||
let AST(statements, functions) = ast;
|
||||
self.fn_lib = Some(functions.clone());
|
||||
statements
|
||||
};
|
||||
|
||||
let result = statements
|
||||
ast.0
|
||||
.iter()
|
||||
.try_fold(().into_dynamic(), |_, stmt| self.eval_stmt(scope, stmt, 0));
|
||||
|
||||
self.fn_lib = None;
|
||||
|
||||
result.map(|_| ()).or_else(|err| match err {
|
||||
EvalAltResult::Return(_, _) => Ok(()),
|
||||
_ => 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()])
|
||||
.try_fold(().into_dynamic(), |_, stmt| {
|
||||
self.eval_stmt(scope, Some(ast.1.as_ref()), stmt, 0)
|
||||
})
|
||||
.map(|_| ())
|
||||
.or_else(|err| match err {
|
||||
EvalAltResult::Return(_, _) => Ok(()),
|
||||
_ => Err(err),
|
||||
})
|
||||
}
|
||||
|
||||
/// Call a script function defined in an `AST` with multiple arguments.
|
||||
@ -927,62 +891,56 @@ impl<'e> Engine<'e> {
|
||||
///
|
||||
/// ```
|
||||
/// # 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 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();
|
||||
/// scope.push("foo", 42_i64);
|
||||
///
|
||||
/// // Call the script-defined function
|
||||
/// let result: i64 = engine.call_fn(&mut scope, &ast, "add", (String::from("abc"), 123_i64))?;
|
||||
///
|
||||
/// let result: i64 = engine.call_fn(&mut scope, &ast, "add", ( String::from("abc"), 123_i64 ) )?;
|
||||
/// 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(())
|
||||
/// # }
|
||||
/// ```
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
pub fn call_fn<A: FuncArgs, T: Any + Clone>(
|
||||
&mut self,
|
||||
&self,
|
||||
scope: &mut Scope,
|
||||
ast: &AST,
|
||||
name: &str,
|
||||
args: A,
|
||||
) -> Result<T, EvalAltResult> {
|
||||
self.call_fn_internal(scope, ast, name, 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 arg_values = args.into_vec();
|
||||
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());
|
||||
|
||||
let result = self
|
||||
.call_fn_raw(Some(scope), name, &mut args, None, Position::none(), 0)?
|
||||
self.call_fn_raw(Some(scope), fn_lib, name, &mut args, None, pos, 0)?
|
||||
.try_cast()
|
||||
.map_err(|a| {
|
||||
EvalAltResult::ErrorMismatchOutputType(
|
||||
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.
|
||||
@ -997,13 +955,14 @@ impl<'e> Engine<'e> {
|
||||
/// compiled just once. Before evaluation, constants are passed into the `Engine` via an external scope
|
||||
/// (i.e. with `scope.push_constant(...)`). Then, the `AST is cloned and the copy re-optimized before running.
|
||||
#[cfg(not(feature = "no_optimize"))]
|
||||
pub fn optimize_ast(&self, scope: &Scope, ast: AST) -> AST {
|
||||
optimize_into_ast(
|
||||
self,
|
||||
scope,
|
||||
ast.0,
|
||||
ast.1.iter().map(|fn_def| fn_def.as_ref().clone()).collect(),
|
||||
)
|
||||
pub fn optimize_ast(
|
||||
&self,
|
||||
scope: &Scope,
|
||||
ast: AST,
|
||||
optimization_level: OptimizationLevel,
|
||||
) -> 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!`)
|
||||
@ -1012,22 +971,24 @@ impl<'e> Engine<'e> {
|
||||
///
|
||||
/// ```
|
||||
/// # fn main() -> Result<(), rhai::EvalAltResult> {
|
||||
/// # use std::sync::RwLock;
|
||||
/// 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
|
||||
/// engine.on_print(|s| result.push_str(s));
|
||||
/// engine.consume("print(40 + 2);")?;
|
||||
/// // Override action of 'print' function
|
||||
/// engine.on_print(|s| result.write().unwrap().push_str(s));
|
||||
///
|
||||
/// engine.consume("print(40 + 2);")?;
|
||||
/// }
|
||||
/// assert_eq!(result, "42");
|
||||
/// assert_eq!(*result.read().unwrap(), "42");
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// ```
|
||||
#[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));
|
||||
}
|
||||
/// Override default action of `print` (print to stdout using `println!`)
|
||||
@ -1036,22 +997,24 @@ impl<'e> Engine<'e> {
|
||||
///
|
||||
/// ```
|
||||
/// # fn main() -> Result<(), rhai::EvalAltResult> {
|
||||
/// # use std::sync::RwLock;
|
||||
/// 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
|
||||
/// engine.on_print(|s| result.push_str(s));
|
||||
/// engine.consume("print(40 + 2);")?;
|
||||
/// // Override action of 'print' function
|
||||
/// engine.on_print(|s| result.write().unwrap().push_str(s));
|
||||
///
|
||||
/// engine.consume("print(40 + 2);")?;
|
||||
/// }
|
||||
/// assert_eq!(result, "42");
|
||||
/// assert_eq!(*result.read().unwrap(), "42");
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// ```
|
||||
#[cfg(not(feature = "sync"))]
|
||||
pub fn on_print(&mut self, callback: impl FnMut(&str) + 'e) {
|
||||
pub fn on_print(&mut self, callback: impl Fn(&str) + 'e) {
|
||||
self.on_print = Some(Box::new(callback));
|
||||
}
|
||||
|
||||
@ -1061,22 +1024,24 @@ impl<'e> Engine<'e> {
|
||||
///
|
||||
/// ```
|
||||
/// # fn main() -> Result<(), rhai::EvalAltResult> {
|
||||
/// # use std::sync::RwLock;
|
||||
/// 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
|
||||
/// engine.on_debug(|s| result.push_str(s));
|
||||
/// engine.consume(r#"debug("hello");"#)?;
|
||||
/// // Override action of 'print' function
|
||||
/// engine.on_debug(|s| result.write().unwrap().push_str(s));
|
||||
///
|
||||
/// engine.consume(r#"debug("hello");"#)?;
|
||||
/// }
|
||||
/// assert_eq!(result, "\"hello\"");
|
||||
/// assert_eq!(*result.read().unwrap(), r#""hello""#);
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// ```
|
||||
#[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));
|
||||
}
|
||||
/// Override default action of `debug` (print to stdout using `println!`)
|
||||
@ -1085,22 +1050,24 @@ impl<'e> Engine<'e> {
|
||||
///
|
||||
/// ```
|
||||
/// # fn main() -> Result<(), rhai::EvalAltResult> {
|
||||
/// # use std::sync::RwLock;
|
||||
/// 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
|
||||
/// engine.on_debug(|s| result.push_str(s));
|
||||
/// engine.consume(r#"debug("hello");"#)?;
|
||||
/// // Override action of 'print' function
|
||||
/// engine.on_debug(|s| result.write().unwrap().push_str(s));
|
||||
///
|
||||
/// engine.consume(r#"debug("hello");"#)?;
|
||||
/// }
|
||||
/// assert_eq!(result, "\"hello\"");
|
||||
/// assert_eq!(*result.read().unwrap(), r#""hello""#);
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// ```
|
||||
#[cfg(not(feature = "sync"))]
|
||||
pub fn on_debug(&mut self, callback: impl FnMut(&str) + 'e) {
|
||||
pub fn on_debug(&mut self, callback: impl Fn(&str) + 'e) {
|
||||
self.on_debug = Some(Box::new(callback));
|
||||
}
|
||||
}
|
||||
|
133
src/builtin.rs
133
src/builtin.rs
@ -27,6 +27,7 @@ use crate::stdlib::{
|
||||
format,
|
||||
ops::{Add, BitAnd, BitOr, BitXor, Div, Mul, Neg, Range, Rem, Shl, Shr, Sub},
|
||||
string::{String, ToString},
|
||||
time::Instant,
|
||||
vec::Vec,
|
||||
{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<'_> {
|
||||
/// Register the core built-in library.
|
||||
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
|
||||
fn and(x: bool, y: bool) -> bool {
|
||||
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, "<=", lte, INT, String, char);
|
||||
reg_cmp!(self, ">", gt, INT, String, char);
|
||||
@ -433,7 +434,7 @@ impl Engine<'_> {
|
||||
}
|
||||
|
||||
// `&&` 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, "&&", and, bool);
|
||||
|
||||
@ -620,23 +621,19 @@ impl Engine<'_> {
|
||||
|
||||
#[cfg(not(feature = "no_object"))]
|
||||
{
|
||||
self.register_fn(KEYWORD_PRINT, |x: &mut Map| -> String {
|
||||
format!("#{:?}", x)
|
||||
});
|
||||
self.register_fn(FUNC_TO_STRING, |x: &mut Map| -> String {
|
||||
format!("#{:?}", x)
|
||||
});
|
||||
self.register_fn(KEYWORD_DEBUG, |x: &mut Map| -> String {
|
||||
format!("#{:?}", x)
|
||||
});
|
||||
self.register_fn(KEYWORD_PRINT, |x: &mut Map| format!("#{:?}", x));
|
||||
self.register_fn(FUNC_TO_STRING, |x: &mut Map| format!("#{:?}", x));
|
||||
self.register_fn(KEYWORD_DEBUG, |x: &mut Map| format!("#{:?}", x));
|
||||
|
||||
// Register map access functions
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
self.register_fn("keys", |map: Map| {
|
||||
map.into_iter()
|
||||
.map(|(k, _)| k.into_dynamic())
|
||||
.collect::<Vec<_>>()
|
||||
});
|
||||
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
self.register_fn("values", |map: Map| {
|
||||
map.into_iter().map(|(_, v)| v).collect::<Vec<_>>()
|
||||
});
|
||||
@ -757,8 +754,7 @@ macro_rules! reg_fn2y {
|
||||
|
||||
/// Register the built-in library.
|
||||
impl Engine<'_> {
|
||||
#[cfg(not(feature = "no_stdlib"))]
|
||||
pub(crate) fn register_stdlib(&mut self) {
|
||||
pub fn register_stdlib(&mut self) {
|
||||
#[cfg(not(feature = "no_float"))]
|
||||
{
|
||||
// Advanced math functions
|
||||
@ -873,6 +869,15 @@ impl Engine<'_> {
|
||||
fn push<T: Any>(list: &mut Array, item: T) {
|
||||
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) {
|
||||
if len >= 0 {
|
||||
while list.len() < len as usize {
|
||||
@ -885,6 +890,7 @@ impl Engine<'_> {
|
||||
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, (), String, Array, ());
|
||||
reg_fn3!(self, "insert", ins, &mut Array, INT, (), String, Array, ());
|
||||
|
||||
self.register_fn("append", |list: &mut Array, array: Array| {
|
||||
list.extend(array)
|
||||
@ -901,12 +907,15 @@ impl Engine<'_> {
|
||||
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, (), 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"))]
|
||||
{
|
||||
reg_fn2x!(self, "push", push, &mut Array, (), 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| {
|
||||
@ -919,6 +928,13 @@ impl Engine<'_> {
|
||||
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("clear", |list: &mut Array| list.clear());
|
||||
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("len", |map: &mut Map| map.len() as INT);
|
||||
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| {
|
||||
map2.into_iter().for_each(|(key, value)| {
|
||||
map1.insert(key, value);
|
||||
@ -1013,5 +1032,39 @@ impl Engine<'_> {
|
||||
*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;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
1079
src/engine.rs
1079
src/engine.rs
File diff suppressed because it is too large
Load Diff
48
src/error.rs
48
src/error.rs
@ -37,6 +37,10 @@ impl fmt::Display for LexError {
|
||||
}
|
||||
|
||||
/// 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)]
|
||||
pub enum ParseErrorType {
|
||||
/// Error in the script text. Wrapped value is the error message.
|
||||
@ -51,17 +55,21 @@ pub enum ParseErrorType {
|
||||
MalformedCallExpr(String),
|
||||
/// An expression in indexing brackets `[]` has syntax error. Wrapped value is the error description (if any).
|
||||
///
|
||||
/// Not available under the `no_index` feature.
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
/// Never appears under the `no_index` feature.
|
||||
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.
|
||||
///
|
||||
/// Not available under the `no_object` feature.
|
||||
#[cfg(not(feature = "no_object"))]
|
||||
/// Never appears under the `no_object` feature.
|
||||
DuplicatedProperty(String),
|
||||
/// Invalid expression assigned to constant. Wrapped value is the name of the constant.
|
||||
ForbiddenConstantExpr(String),
|
||||
/// Missing a property name for custom types and maps.
|
||||
///
|
||||
/// Never appears under the `no_object` feature.
|
||||
PropertyExpected,
|
||||
/// Missing a variable name after the `let`, `const` or `for` keywords.
|
||||
VariableExpected,
|
||||
@ -69,28 +77,23 @@ pub enum ParseErrorType {
|
||||
ExprExpected(String),
|
||||
/// Defining a function `fn` in an appropriate place (e.g. inside another function).
|
||||
///
|
||||
/// Not available under the `no_function` feature.
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
/// Never appears under the `no_function` feature.
|
||||
WrongFnDefinition,
|
||||
/// Missing a function name after the `fn` keyword.
|
||||
///
|
||||
/// Not available under the `no_function` feature.
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
/// Never appears under the `no_function` feature.
|
||||
FnMissingName,
|
||||
/// A function definition is missing the parameters list. Wrapped value is the function name.
|
||||
///
|
||||
/// Not available under the `no_function` feature.
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
/// Never appears under the `no_function` feature.
|
||||
FnMissingParams(String),
|
||||
/// A function definition has duplicated parameters. Wrapped values are the function name and parameter name.
|
||||
///
|
||||
/// Not available under the `no_function` feature.
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
/// Never appears under the `no_function` feature.
|
||||
FnDuplicatedParam(String, String),
|
||||
/// A function definition is missing the body. Wrapped value is the function name.
|
||||
///
|
||||
/// Not available under the `no_function` feature.
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
/// Never appears under the `no_function` feature.
|
||||
FnMissingBody(String),
|
||||
/// Assignment to an inappropriate LHS (left-hand-side) expression.
|
||||
AssignmentToInvalidLHS,
|
||||
@ -136,23 +139,17 @@ impl ParseError {
|
||||
ParseErrorType::UnknownOperator(_) => "Unknown operator",
|
||||
ParseErrorType::MissingToken(_, _) => "Expecting a certain token that is missing",
|
||||
ParseErrorType::MalformedCallExpr(_) => "Invalid expression in function call arguments",
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
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::ForbiddenConstantExpr(_) => "Expecting a constant",
|
||||
ParseErrorType::PropertyExpected => "Expecting name of a property",
|
||||
ParseErrorType::VariableExpected => "Expecting name of a variable",
|
||||
ParseErrorType::ExprExpected(_) => "Expecting an expression",
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
ParseErrorType::FnMissingName => "Expecting name in function declaration",
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
ParseErrorType::FnMissingParams(_) => "Expecting parameters in function declaration",
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
ParseErrorType::FnDuplicatedParam(_,_) => "Duplicated parameters in function declaration",
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
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::AssignmentToInvalidLHS => "Cannot assign to this expression",
|
||||
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)?,
|
||||
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
ParseErrorType::MalformedIndexExpr(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) => {
|
||||
write!(f, "Duplicated property '{}' for object map literal", s)?
|
||||
}
|
||||
|
||||
ParseErrorType::ExprExpected(s) => write!(f, "Expecting {} expression", s)?,
|
||||
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
ParseErrorType::FnMissingParams(s) => {
|
||||
write!(f, "Expecting parameters for function '{}'", s)?
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
ParseErrorType::FnMissingBody(s) => {
|
||||
write!(f, "Expecting body statement block for function '{}'", s)?
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
ParseErrorType::FnDuplicatedParam(s, arg) => {
|
||||
write!(f, "Duplicated parameter '{}' for function '{}'", arg, s)?
|
||||
}
|
||||
|
@ -36,6 +36,7 @@ macro_rules! impl_args {
|
||||
(@pop) => {
|
||||
};
|
||||
(@pop $head:ident) => {
|
||||
impl_args!();
|
||||
};
|
||||
(@pop $head:ident $(, $tail:ident)+) => {
|
||||
impl_args!($($tail),*);
|
120
src/fn_func.rs
Normal file
120
src/fn_func.rs
Normal 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);
|
@ -132,9 +132,9 @@ macro_rules! def_register {
|
||||
def_register!(imp);
|
||||
};
|
||||
(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 actual type
|
||||
// ^ function parameter actual type (T, &T or &mut T)
|
||||
// ^ dereferencing function
|
||||
impl<
|
||||
$($par: Any + Clone,)*
|
||||
@ -171,6 +171,7 @@ macro_rules! def_register {
|
||||
let r = f($(($clone)($par)),*);
|
||||
Ok(Box::new(r) as Dynamic)
|
||||
};
|
||||
|
||||
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 => Mut<$p0> => &mut $p0 => identity $(, $p => $p => $p => Clone::clone)*);
|
||||
// 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!($($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]
|
||||
|
30
src/lib.rs
30
src/lib.rs
@ -3,15 +3,19 @@
|
||||
//! 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.
|
||||
//! 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
|
||||
//! // Brute force factorial function
|
||||
//! fn factorial(x) {
|
||||
//! if x == 1 { return 1; }
|
||||
//! x * factorial(x - 1)
|
||||
//! }
|
||||
//!
|
||||
//! compute_something(factorial(10))
|
||||
//! // Calling an external function 'compute'
|
||||
//! compute(factorial(10))
|
||||
//! ```
|
||||
//!
|
||||
//! And the Rust part:
|
||||
@ -21,16 +25,23 @@
|
||||
//!
|
||||
//! fn main() -> Result<(), EvalAltResult>
|
||||
//! {
|
||||
//! // Define external function
|
||||
//! fn compute_something(x: i64) -> bool {
|
||||
//! (x % 40) == 0
|
||||
//! }
|
||||
//!
|
||||
//! // Create scripting engine
|
||||
//! 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"))]
|
||||
//! assert_eq!(engine.eval_file::<bool>("my_script.rhai".into())?, true);
|
||||
//! # #[cfg(not(feature = "no_std"))]
|
||||
//! assert_eq!(
|
||||
//! // Evaluate the script, expects a 'bool' return
|
||||
//! engine.eval_file::<bool>("my_script.rhai".into())?,
|
||||
//! true
|
||||
//! );
|
||||
//!
|
||||
//! Ok(())
|
||||
//! }
|
||||
@ -40,7 +51,6 @@
|
||||
//!
|
||||
//! | Feature | Description |
|
||||
//! | ------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
//! | `no_stdlib` | Exclude the standard library of utility functions in the build, and only include the minimum necessary functionalities. Standard types are not affected. |
|
||||
//! | `unchecked` | Exclude arithmetic checking (such as overflows and division by zero). Beware that a bad script may panic the entire system! |
|
||||
//! | `no_function` | Disable script-defined functions if not needed. |
|
||||
//! | `no_index` | Disable arrays and indexing features if not needed. |
|
||||
@ -62,9 +72,10 @@ extern crate alloc;
|
||||
mod any;
|
||||
mod api;
|
||||
mod builtin;
|
||||
mod call;
|
||||
mod engine;
|
||||
mod error;
|
||||
mod fn_call;
|
||||
mod fn_func;
|
||||
mod fn_register;
|
||||
mod optimize;
|
||||
mod parser;
|
||||
@ -73,14 +84,17 @@ mod scope;
|
||||
mod stdlib;
|
||||
|
||||
pub use any::{Any, AnyExt, Dynamic, Variant};
|
||||
pub use call::FuncArgs;
|
||||
pub use engine::Engine;
|
||||
pub use error::{ParseError, ParseErrorType};
|
||||
pub use fn_call::FuncArgs;
|
||||
pub use fn_register::{RegisterDynamicFn, RegisterFn, RegisterResultFn};
|
||||
pub use parser::{Position, AST, INT};
|
||||
pub use result::EvalAltResult;
|
||||
pub use scope::Scope;
|
||||
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
pub use fn_func::Func;
|
||||
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
pub use engine::Array;
|
||||
|
||||
|
249
src/optimize.rs
249
src/optimize.rs
@ -1,15 +1,15 @@
|
||||
#![cfg(not(feature = "no_optimize"))]
|
||||
|
||||
use crate::any::{Any, Dynamic};
|
||||
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,
|
||||
};
|
||||
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::stdlib::{
|
||||
boxed::Box,
|
||||
collections::HashMap,
|
||||
rc::Rc,
|
||||
string::{String, ToString},
|
||||
sync::Arc,
|
||||
@ -31,6 +31,17 @@ pub enum OptimizationLevel {
|
||||
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.
|
||||
struct State<'a> {
|
||||
/// Has the AST been changed during this pass?
|
||||
@ -39,15 +50,25 @@ struct State<'a> {
|
||||
constants: Vec<(String, Expr)>,
|
||||
/// An `Engine` instance for eager function evaluation.
|
||||
engine: &'a Engine<'a>,
|
||||
/// Library of script-defined functions.
|
||||
fn_lib: &'a [(&'a str, usize)],
|
||||
/// Optimization level.
|
||||
optimization_level: OptimizationLevel,
|
||||
}
|
||||
|
||||
impl<'a> State<'a> {
|
||||
/// 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 {
|
||||
changed: false,
|
||||
constants: vec![],
|
||||
engine,
|
||||
fn_lib,
|
||||
optimization_level: level,
|
||||
}
|
||||
}
|
||||
/// 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.
|
||||
fn optimize_stmt<'a>(stmt: Stmt, state: &mut State<'a>, preserve_result: bool) -> Stmt {
|
||||
match stmt {
|
||||
@ -345,25 +385,51 @@ fn optimize_expr<'a>(expr: Expr, state: &mut State<'a>) -> Expr {
|
||||
// id = expr
|
||||
expr => Expr::Assignment(id, Box::new(optimize_expr(expr, state)), pos),
|
||||
},
|
||||
|
||||
// lhs.rhs
|
||||
#[cfg(not(feature = "no_object"))]
|
||||
Expr::Dot(lhs, rhs, pos) => Expr::Dot(
|
||||
Box::new(optimize_expr(*lhs, state)),
|
||||
Box::new(optimize_expr(*rhs, state)),
|
||||
pos,
|
||||
),
|
||||
Expr::Dot(lhs, rhs, pos) => match (*lhs, *rhs) {
|
||||
// map.string
|
||||
(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,
|
||||
)
|
||||
}
|
||||
|
||||
// lhs[rhs]
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
Expr::Index(lhs, rhs, pos) => match (*lhs, *rhs) {
|
||||
// array[int]
|
||||
(Expr::Array(mut items, _), Expr::IntegerConstant(i, _))
|
||||
if i >= 0 && (i as usize) < items.len() && items.iter().all(|x| x.is_pure()) =>
|
||||
(Expr::Array(mut items, pos), Expr::IntegerConstant(i, _))
|
||||
if i >= 0 && (i as usize) < items.len() && items.iter().all(Expr::is_pure) =>
|
||||
{
|
||||
// Array literal where everything is pure - promote the indexed item.
|
||||
// All other items can be thrown away.
|
||||
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]
|
||||
(Expr::StringConstant(s, pos), Expr::IntegerConstant(i, _))
|
||||
@ -392,8 +458,55 @@ fn optimize_expr<'a>(expr: Expr, state: &mut State<'a>) -> Expr {
|
||||
.into_iter()
|
||||
.map(|(key, expr, pos)| (key, optimize_expr(expr, state), 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
|
||||
Expr::And(lhs, rhs) => match (*lhs, *rhs) {
|
||||
Expr::And(lhs, rhs, pos) => match (*lhs, *rhs) {
|
||||
// true && rhs -> rhs
|
||||
(Expr::True(_), rhs) => {
|
||||
state.set_dirty();
|
||||
@ -413,10 +526,11 @@ fn optimize_expr<'a>(expr: Expr, state: &mut State<'a>) -> Expr {
|
||||
(lhs, rhs) => Expr::And(
|
||||
Box::new(optimize_expr(lhs, state)),
|
||||
Box::new(optimize_expr(rhs, state)),
|
||||
pos
|
||||
),
|
||||
},
|
||||
// lhs || rhs
|
||||
Expr::Or(lhs, rhs) => match (*lhs, *rhs) {
|
||||
Expr::Or(lhs, rhs, pos) => match (*lhs, *rhs) {
|
||||
// false || rhs -> rhs
|
||||
(Expr::False(_), rhs) => {
|
||||
state.set_dirty();
|
||||
@ -436,28 +550,23 @@ fn optimize_expr<'a>(expr: Expr, state: &mut State<'a>) -> Expr {
|
||||
(lhs, rhs) => Expr::Or(
|
||||
Box::new(optimize_expr(lhs, 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
|
||||
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),
|
||||
|
||||
// Eagerly call functions
|
||||
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
|
||||
=> {
|
||||
// First search in script-defined functions (can override built-in)
|
||||
if let Some(fn_lib_arc) = &state.engine.fn_lib {
|
||||
if fn_lib_arc.has_function(&id, args.len()) {
|
||||
// A script-defined function overrides the built-in function - do not make the call
|
||||
return Expr::FunctionCall(id, args.into_iter().map(|a| optimize_expr(a, state)).collect(), def_value, pos);
|
||||
}
|
||||
if state.fn_lib.iter().find(|(name, len)| name == &id && *len == args.len()).is_some() {
|
||||
// A script-defined function overrides the built-in function - do not make the call
|
||||
return Expr::FunctionCall(id, args.into_iter().map(|a| optimize_expr(a, state)).collect(), def_value, pos);
|
||||
}
|
||||
|
||||
let mut arg_values: Vec<_> = args.iter().map(Expr::get_constant_value).collect();
|
||||
@ -471,21 +580,25 @@ 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|
|
||||
result.or_else(|| {
|
||||
if !arg_for_type_of.is_empty() {
|
||||
// Handle `type_of()`
|
||||
Some(arg_for_type_of.to_string().into_dynamic())
|
||||
} else {
|
||||
// Otherwise use the default value, if any
|
||||
def_value.clone()
|
||||
}
|
||||
}).and_then(|result| map_dynamic_to_expr(result, pos))
|
||||
call_fn(state.engine.functions.as_ref(), &id, &mut call_args, pos).ok()
|
||||
.and_then(|result|
|
||||
result.or_else(|| {
|
||||
if !arg_for_type_of.is_empty() {
|
||||
// Handle `type_of()`
|
||||
Some(arg_for_type_of.to_string().into_dynamic())
|
||||
} else {
|
||||
// Otherwise use the default value, if any
|
||||
def_value.clone()
|
||||
}
|
||||
}).and_then(|result| map_dynamic_to_expr(result, pos))
|
||||
.map(|expr| {
|
||||
state.set_dirty();
|
||||
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
|
||||
@ -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),
|
||||
|
||||
// constant-name
|
||||
Expr::Variable(name, _) if state.contains_constant(&name) => {
|
||||
Expr::Variable(name, pos) if state.contains_constant(&name) => {
|
||||
state.set_dirty();
|
||||
|
||||
// Replace constant with value
|
||||
state.find_constant(&name).expect("should find constant in scope!").clone()
|
||||
state.find_constant(&name).expect("should find constant in scope!").clone().set_position(pos)
|
||||
}
|
||||
|
||||
// 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 engine.optimization_level == OptimizationLevel::None {
|
||||
if level == OptimizationLevel::None {
|
||||
return statements;
|
||||
}
|
||||
|
||||
// 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
|
||||
scope
|
||||
@ -544,16 +663,18 @@ pub(crate) fn optimize<'a>(statements: Vec<Stmt>, engine: &Engine<'a>, scope: &S
|
||||
.into_iter()
|
||||
.enumerate()
|
||||
.map(|(i, stmt)| {
|
||||
if let Stmt::Const(name, value, _) = &stmt {
|
||||
// Load constants
|
||||
state.push_constant(name, value.as_ref().clone());
|
||||
stmt // Keep it in the global scope
|
||||
} else {
|
||||
// Keep all variable declarations at this level
|
||||
// and always keep the last return value
|
||||
let keep = matches!(stmt, Stmt::Let(_, _, _)) || i == num_statements - 1;
|
||||
|
||||
optimize_stmt(stmt, &mut state, keep)
|
||||
match stmt {
|
||||
Stmt::Const(ref name, ref value, _) => {
|
||||
// Load constants
|
||||
state.push_constant(name.as_ref(), value.as_ref().clone());
|
||||
stmt // Keep it in the global scope
|
||||
}
|
||||
_ => {
|
||||
// Keep all variable declarations at this level
|
||||
// and always keep the last return value
|
||||
let keep = matches!(stmt, Stmt::Let(_, _, _)) || i == num_statements - 1;
|
||||
optimize_stmt(stmt, &mut state, keep)
|
||||
}
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
@ -585,17 +706,27 @@ pub fn optimize_into_ast(
|
||||
scope: &Scope,
|
||||
statements: Vec<Stmt>,
|
||||
functions: Vec<FnDef>,
|
||||
level: OptimizationLevel,
|
||||
) -> 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
|
||||
.iter()
|
||||
.cloned()
|
||||
.map(|mut fn_def| {
|
||||
if engine.optimization_level != OptimizationLevel::None {
|
||||
if !level.is_none() {
|
||||
let pos = fn_def.body.position();
|
||||
|
||||
// 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
|
||||
fn_def.body = match body.pop().unwrap_or_else(|| Stmt::Noop(pos)) {
|
||||
@ -615,15 +746,15 @@ pub fn optimize_into_ast(
|
||||
);
|
||||
|
||||
AST(
|
||||
match engine.optimization_level {
|
||||
match level {
|
||||
OptimizationLevel::None => statements,
|
||||
OptimizationLevel::Simple | OptimizationLevel::Full => {
|
||||
optimize(statements, engine, &scope)
|
||||
optimize(statements, engine, &scope, &fn_lib, level)
|
||||
}
|
||||
},
|
||||
#[cfg(feature = "sync")]
|
||||
Arc::new(fn_lib),
|
||||
Arc::new(lib),
|
||||
#[cfg(not(feature = "sync"))]
|
||||
Rc::new(fn_lib),
|
||||
Rc::new(lib),
|
||||
)
|
||||
}
|
||||
|
746
src/parser.rs
746
src/parser.rs
File diff suppressed because it is too large
Load Diff
@ -16,6 +16,8 @@ use crate::stdlib::path::PathBuf;
|
||||
/// Evaluation result.
|
||||
///
|
||||
/// 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)]
|
||||
pub enum EvalAltResult {
|
||||
/// Syntax error.
|
||||
@ -23,7 +25,7 @@ pub enum EvalAltResult {
|
||||
|
||||
/// 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"))]
|
||||
ErrorReadingScriptFile(PathBuf, std::io::Error),
|
||||
|
||||
@ -49,6 +51,8 @@ pub enum EvalAltResult {
|
||||
ErrorNumericIndexExpr(Position),
|
||||
/// Trying to index into a map with an index that is not `String`.
|
||||
ErrorStringIndexExpr(Position),
|
||||
/// Invalid arguments for `in` operator.
|
||||
ErrorInExpr(Position),
|
||||
/// The guard expression in an `if` or `while` statement does not return a boolean value.
|
||||
ErrorLogicGuard(Position),
|
||||
/// The `for` statement encounters a type that is not an iterator.
|
||||
@ -103,21 +107,22 @@ impl EvalAltResult {
|
||||
Self::ErrorArrayBounds(_, index, _) if *index < 0 => {
|
||||
"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::ErrorStringBounds(_, index, _) if *index < 0 => {
|
||||
"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::ErrorLogicGuard(_) => "Boolean expression expected",
|
||||
Self::ErrorFor(_) => "For loop expects array or range",
|
||||
Self::ErrorLogicGuard(_) => "Boolean value expected",
|
||||
Self::ErrorFor(_) => "For loop expects an array, object map, or range",
|
||||
Self::ErrorVariableNotFound(_, _) => "Variable not found",
|
||||
Self::ErrorAssignmentToUnknownLHS(_) => {
|
||||
"Assignment to an unsupported left-hand side expression"
|
||||
}
|
||||
Self::ErrorAssignmentToConstant(_, _) => "Assignment to a constant variable",
|
||||
Self::ErrorMismatchOutputType(_, _) => "Output type is incorrect",
|
||||
Self::ErrorInExpr(_) => "Malformed 'in' expression",
|
||||
Self::ErrorDotExpr(_, _) => "Malformed dot expression",
|
||||
Self::ErrorArithmetic(_, _) => "Arithmetic error",
|
||||
Self::ErrorStackOverflow(_) => "Stack overflow",
|
||||
@ -154,6 +159,7 @@ impl fmt::Display for EvalAltResult {
|
||||
| Self::ErrorLogicGuard(pos)
|
||||
| Self::ErrorFor(pos)
|
||||
| Self::ErrorAssignmentToUnknownLHS(pos)
|
||||
| Self::ErrorInExpr(pos)
|
||||
| Self::ErrorDotExpr(_, pos)
|
||||
| Self::ErrorStackOverflow(pos) => write!(f, "{} ({})", desc, pos),
|
||||
|
||||
@ -256,6 +262,7 @@ impl EvalAltResult {
|
||||
| Self::ErrorAssignmentToUnknownLHS(pos)
|
||||
| Self::ErrorAssignmentToConstant(_, pos)
|
||||
| Self::ErrorMismatchOutputType(_, pos)
|
||||
| Self::ErrorInExpr(pos)
|
||||
| Self::ErrorDotExpr(_, pos)
|
||||
| Self::ErrorArithmetic(_, pos)
|
||||
| Self::ErrorStackOverflow(pos)
|
||||
@ -288,6 +295,7 @@ impl EvalAltResult {
|
||||
| Self::ErrorAssignmentToUnknownLHS(pos)
|
||||
| Self::ErrorAssignmentToConstant(_, pos)
|
||||
| Self::ErrorMismatchOutputType(_, pos)
|
||||
| Self::ErrorInExpr(pos)
|
||||
| Self::ErrorDotExpr(_, pos)
|
||||
| Self::ErrorArithmetic(_, pos)
|
||||
| Self::ErrorStackOverflow(pos)
|
||||
|
@ -49,7 +49,7 @@ pub(crate) struct EntryRef<'a> {
|
||||
/// # fn main() -> Result<(), rhai::EvalAltResult> {
|
||||
/// use rhai::{Engine, Scope};
|
||||
///
|
||||
/// let mut engine = Engine::new();
|
||||
/// let engine = Engine::new();
|
||||
/// let mut my_scope = Scope::new();
|
||||
///
|
||||
/// my_scope.push("z", 40_i64);
|
||||
|
@ -3,7 +3,7 @@ use rhai::{Array, Engine, EvalAltResult, RegisterFn, INT};
|
||||
|
||||
#[test]
|
||||
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 y = [1, 2, 3]; y[1] = 5; y[1]")?, 5);
|
||||
@ -11,43 +11,47 @@ fn test_arrays() -> Result<(), EvalAltResult> {
|
||||
engine.eval::<char>(r#"let y = [1, [ 42, 88, "93" ], 3]; y[1][2][1]"#)?,
|
||||
'3'
|
||||
);
|
||||
assert!(engine.eval::<bool>("let y = [1, 2, 3]; 2 in y")?);
|
||||
|
||||
#[cfg(not(feature = "no_stdlib"))]
|
||||
{
|
||||
assert_eq!(
|
||||
engine.eval::<INT>(
|
||||
assert_eq!(
|
||||
engine.eval::<INT>(
|
||||
r"
|
||||
let x = [2, 9];
|
||||
x.insert(-1, 1);
|
||||
x.insert(999, 3);
|
||||
|
||||
let r = x.remove(2);
|
||||
|
||||
let y = [4, 5];
|
||||
x.append(y);
|
||||
|
||||
x.len() + r
|
||||
"
|
||||
)?,
|
||||
14
|
||||
);
|
||||
assert_eq!(
|
||||
engine.eval::<INT>(
|
||||
r"
|
||||
let x = [1, 2, 3];
|
||||
x += [4, 5];
|
||||
x.len()
|
||||
"
|
||||
)?,
|
||||
5
|
||||
);
|
||||
assert_eq!(
|
||||
engine
|
||||
.eval::<Array>(
|
||||
r"
|
||||
let x = [1, 2, 3];
|
||||
let y = [4, 5];
|
||||
x.append(y);
|
||||
x.len()
|
||||
let x = [1, 2, 3];
|
||||
let y = [4, 5];
|
||||
x + y
|
||||
"
|
||||
)?,
|
||||
5
|
||||
);
|
||||
assert_eq!(
|
||||
engine.eval::<INT>(
|
||||
r"
|
||||
let x = [1, 2, 3];
|
||||
x += [4, 5];
|
||||
x.len()
|
||||
"
|
||||
)?,
|
||||
5
|
||||
);
|
||||
assert_eq!(
|
||||
engine
|
||||
.eval::<Array>(
|
||||
r"
|
||||
let x = [1, 2, 3];
|
||||
let y = [4, 5];
|
||||
x + y
|
||||
"
|
||||
)?
|
||||
.len(),
|
||||
5
|
||||
);
|
||||
}
|
||||
)?
|
||||
.len(),
|
||||
5
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
@ -2,7 +2,7 @@ use rhai::{Engine, EvalAltResult, INT};
|
||||
|
||||
#[test]
|
||||
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")?, 160);
|
||||
|
@ -2,14 +2,14 @@ use rhai::{Engine, EvalAltResult, INT};
|
||||
|
||||
#[test]
|
||||
fn test_left_shift() -> Result<(), EvalAltResult> {
|
||||
let mut engine = Engine::new();
|
||||
let engine = Engine::new();
|
||||
assert_eq!(engine.eval::<INT>("4 << 2")?, 16);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_right_shift() -> Result<(), EvalAltResult> {
|
||||
let mut engine = Engine::new();
|
||||
let engine = Engine::new();
|
||||
assert_eq!(engine.eval::<INT>("9 >> 1")?, 4);
|
||||
Ok(())
|
||||
}
|
||||
|
@ -2,7 +2,7 @@ use rhai::{Engine, EvalAltResult};
|
||||
|
||||
#[test]
|
||||
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);
|
||||
@ -12,7 +12,7 @@ fn test_bool_op1() -> Result<(), EvalAltResult> {
|
||||
|
||||
#[test]
|
||||
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);
|
||||
@ -22,7 +22,7 @@ fn test_bool_op2() -> Result<(), EvalAltResult> {
|
||||
|
||||
#[test]
|
||||
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_eq!(engine.eval::<bool>("true && (true || 123)")?, true);
|
||||
@ -34,7 +34,7 @@ fn test_bool_op3() -> Result<(), EvalAltResult> {
|
||||
|
||||
#[test]
|
||||
fn test_bool_op_short_circuit() -> Result<(), EvalAltResult> {
|
||||
let mut engine = Engine::new();
|
||||
let engine = Engine::new();
|
||||
|
||||
assert_eq!(
|
||||
engine.eval::<bool>(
|
||||
@ -63,7 +63,7 @@ fn test_bool_op_short_circuit() -> Result<(), EvalAltResult> {
|
||||
|
||||
#[test]
|
||||
fn test_bool_op_no_short_circuit1() {
|
||||
let mut engine = Engine::new();
|
||||
let engine = Engine::new();
|
||||
|
||||
assert!(engine
|
||||
.eval::<bool>(
|
||||
@ -78,7 +78,7 @@ fn test_bool_op_no_short_circuit1() {
|
||||
|
||||
#[test]
|
||||
fn test_bool_op_no_short_circuit2() {
|
||||
let mut engine = Engine::new();
|
||||
let engine = Engine::new();
|
||||
|
||||
assert!(engine
|
||||
.eval::<bool>(
|
||||
|
@ -1,5 +1,5 @@
|
||||
#![cfg(not(feature = "no_function"))]
|
||||
use rhai::{Engine, EvalAltResult, ParseErrorType, Scope, INT};
|
||||
use rhai::{Engine, EvalAltResult, Func, ParseErrorType, Scope, INT};
|
||||
|
||||
#[test]
|
||||
fn test_fn() -> Result<(), EvalAltResult> {
|
||||
@ -20,7 +20,7 @@ fn test_fn() -> Result<(), EvalAltResult> {
|
||||
|
||||
#[test]
|
||||
fn test_call_fn() -> Result<(), EvalAltResult> {
|
||||
let mut engine = Engine::new();
|
||||
let engine = Engine::new();
|
||||
let mut scope = Scope::new();
|
||||
|
||||
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))?;
|
||||
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);
|
||||
|
||||
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!(
|
||||
@ -59,3 +59,16 @@ fn test_call_fn() -> Result<(), EvalAltResult> {
|
||||
|
||||
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(())
|
||||
}
|
||||
|
@ -2,9 +2,11 @@ use rhai::{Engine, EvalAltResult};
|
||||
|
||||
#[test]
|
||||
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>(r"'\''")?, '\'');
|
||||
assert_eq!(engine.eval::<char>(r#"'"'"#)?, '"');
|
||||
assert_eq!(engine.eval::<char>("'\\u2764'")?, '❤');
|
||||
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
|
@ -2,13 +2,13 @@ use rhai::{Engine, INT};
|
||||
|
||||
#[test]
|
||||
fn test_comments() {
|
||||
let mut engine = Engine::new();
|
||||
let engine = Engine::new();
|
||||
|
||||
assert!(engine
|
||||
.eval::<INT>("let x = 5; x // I am a single line comment, yay!")
|
||||
.is_ok());
|
||||
|
||||
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());
|
||||
}
|
||||
|
@ -2,7 +2,7 @@ use rhai::{Engine, EvalAltResult, INT};
|
||||
|
||||
#[test]
|
||||
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::<bool>("let x = true; x |= false; x")?, true);
|
||||
@ -13,7 +13,7 @@ fn test_or_equals() -> Result<(), EvalAltResult> {
|
||||
|
||||
#[test]
|
||||
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::<bool>("let x = true; x &= false; x")?, false);
|
||||
@ -25,42 +25,42 @@ fn test_and_equals() -> Result<(), EvalAltResult> {
|
||||
|
||||
#[test]
|
||||
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);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
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);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
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);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
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);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
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);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
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);
|
||||
Ok(())
|
||||
}
|
||||
|
@ -2,7 +2,7 @@ use rhai::{Engine, EvalAltResult, INT};
|
||||
|
||||
#[test]
|
||||
fn test_constant() -> Result<(), EvalAltResult> {
|
||||
let mut engine = Engine::new();
|
||||
let engine = Engine::new();
|
||||
|
||||
assert_eq!(engine.eval::<INT>("const x = 123; x")?, 123);
|
||||
|
||||
|
@ -2,7 +2,7 @@ use rhai::{Engine, EvalAltResult, INT};
|
||||
|
||||
#[test]
|
||||
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);
|
||||
|
||||
|
@ -2,7 +2,7 @@ use rhai::{Engine, EvalAltResult, Scope, INT};
|
||||
|
||||
#[test]
|
||||
fn test_eval() -> Result<(), EvalAltResult> {
|
||||
let mut engine = Engine::new();
|
||||
let engine = Engine::new();
|
||||
|
||||
assert_eq!(
|
||||
engine.eval::<INT>(
|
||||
@ -19,7 +19,7 @@ fn test_eval() -> Result<(), EvalAltResult> {
|
||||
#[test]
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
fn test_eval_function() -> Result<(), EvalAltResult> {
|
||||
let mut engine = Engine::new();
|
||||
let engine = Engine::new();
|
||||
let mut scope = Scope::new();
|
||||
|
||||
assert_eq!(
|
||||
@ -62,7 +62,7 @@ fn test_eval_function() -> Result<(), EvalAltResult> {
|
||||
#[test]
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
fn test_eval_override() -> Result<(), EvalAltResult> {
|
||||
let mut engine = Engine::new();
|
||||
let engine = Engine::new();
|
||||
|
||||
assert_eq!(
|
||||
engine.eval::<String>(
|
||||
|
@ -2,7 +2,7 @@ use rhai::{Engine, EvalAltResult, Scope, INT};
|
||||
|
||||
#[test]
|
||||
fn test_expressions() -> Result<(), EvalAltResult> {
|
||||
let mut engine = Engine::new();
|
||||
let engine = Engine::new();
|
||||
let mut scope = Scope::new();
|
||||
|
||||
scope.push("x", 10 as INT);
|
||||
|
@ -5,7 +5,7 @@ const EPSILON: FLOAT = 0.000_000_000_1;
|
||||
|
||||
#[test]
|
||||
fn test_float() -> Result<(), EvalAltResult> {
|
||||
let mut engine = Engine::new();
|
||||
let engine = Engine::new();
|
||||
|
||||
assert_eq!(
|
||||
engine.eval::<bool>("let x = 0.0; let y = 1.0; x < y")?,
|
||||
|
@ -3,7 +3,7 @@ use rhai::{Engine, EvalAltResult, INT};
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
#[test]
|
||||
fn test_for_array() -> Result<(), EvalAltResult> {
|
||||
let mut engine = Engine::new();
|
||||
let engine = Engine::new();
|
||||
|
||||
let script = r"
|
||||
let sum1 = 0;
|
||||
@ -33,7 +33,7 @@ fn test_for_array() -> Result<(), EvalAltResult> {
|
||||
#[cfg(not(feature = "no_object"))]
|
||||
#[test]
|
||||
fn test_for_object() -> Result<(), EvalAltResult> {
|
||||
let mut engine = Engine::new();
|
||||
let engine = Engine::new();
|
||||
|
||||
let script = r#"
|
||||
let sum = 0;
|
||||
|
@ -2,7 +2,7 @@ use rhai::{Engine, EvalAltResult, INT};
|
||||
|
||||
#[test]
|
||||
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 false { 55 } else { 44 }")?, 44);
|
||||
@ -30,7 +30,7 @@ fn test_if() -> Result<(), EvalAltResult> {
|
||||
|
||||
#[test]
|
||||
fn test_if_expr() -> Result<(), EvalAltResult> {
|
||||
let mut engine = Engine::new();
|
||||
let engine = Engine::new();
|
||||
|
||||
assert_eq!(
|
||||
engine.eval::<INT>(
|
||||
|
@ -2,7 +2,7 @@ use rhai::{Engine, EvalAltResult, INT};
|
||||
|
||||
#[test]
|
||||
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!(
|
||||
|
@ -4,9 +4,12 @@ use rhai::{Engine, EvalAltResult, INT};
|
||||
|
||||
#[test]
|
||||
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);
|
||||
|
||||
Ok(())
|
||||
@ -14,15 +17,15 @@ fn test_internal_fn() -> Result<(), EvalAltResult> {
|
||||
|
||||
#[test]
|
||||
fn test_big_internal_fn() -> Result<(), EvalAltResult> {
|
||||
let mut engine = Engine::new();
|
||||
let engine = Engine::new();
|
||||
|
||||
assert_eq!(
|
||||
engine.eval::<INT>(
|
||||
r"
|
||||
fn mathme(a, b, c, d, e, f) {
|
||||
fn math_me(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
|
||||
@ -33,7 +36,7 @@ fn test_big_internal_fn() -> Result<(), EvalAltResult> {
|
||||
|
||||
#[test]
|
||||
fn test_internal_fn_overloading() -> Result<(), EvalAltResult> {
|
||||
let mut engine = Engine::new();
|
||||
let engine = Engine::new();
|
||||
|
||||
assert_eq!(
|
||||
engine.eval::<INT>(
|
||||
|
@ -2,7 +2,7 @@ use rhai::{Engine, EvalAltResult, INT};
|
||||
|
||||
#[test]
|
||||
fn test_loop() -> Result<(), EvalAltResult> {
|
||||
let mut engine = Engine::new();
|
||||
let engine = Engine::new();
|
||||
|
||||
assert_eq!(
|
||||
engine.eval::<INT>(
|
||||
|
236
tests/maps.rs
236
tests/maps.rs
@ -1,10 +1,10 @@
|
||||
#![cfg(not(feature = "no_object"))]
|
||||
|
||||
use rhai::{AnyExt, Engine, EvalAltResult, Map, INT};
|
||||
use rhai::{AnyExt, Engine, EvalAltResult, Map, Scope, INT};
|
||||
|
||||
#[test]
|
||||
fn test_map_indexing() -> Result<(), EvalAltResult> {
|
||||
let mut engine = Engine::new();
|
||||
let engine = Engine::new();
|
||||
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
{
|
||||
@ -29,97 +29,215 @@ fn test_map_indexing() -> Result<(), EvalAltResult> {
|
||||
);
|
||||
engine.eval::<()>("let y = #{a: 1, b: 2, c: 3}; y.z")?;
|
||||
|
||||
#[cfg(not(feature = "no_stdlib"))]
|
||||
{
|
||||
assert_eq!(
|
||||
engine.eval::<INT>(
|
||||
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!(
|
||||
engine.eval::<INT>(
|
||||
r"
|
||||
let x = #{a: 1, b: 2, c: 3};
|
||||
let y = #{b: 42, d: 9};
|
||||
x.mixin(y);
|
||||
x.len() + x.b
|
||||
"
|
||||
)?,
|
||||
46
|
||||
);
|
||||
assert_eq!(
|
||||
engine.eval::<INT>(
|
||||
r"
|
||||
let x = #{a: 1, b: 2, c: 3};
|
||||
x += #{b: 42, d: 9};
|
||||
x.len() + x.b
|
||||
"
|
||||
)?,
|
||||
46
|
||||
);
|
||||
assert_eq!(
|
||||
engine
|
||||
.eval::<Map>(
|
||||
r"
|
||||
let x = #{a: 1, b: 2, c: 3};
|
||||
let y = #{b: 42, d: 9};
|
||||
x.mixin(y);
|
||||
x.len() + x.b
|
||||
x + y
|
||||
"
|
||||
)?,
|
||||
46
|
||||
);
|
||||
assert_eq!(
|
||||
engine.eval::<INT>(
|
||||
r"
|
||||
let x = #{a: 1, b: 2, c: 3};
|
||||
x += #{b: 42, d: 9};
|
||||
x.len() + x.b
|
||||
"
|
||||
)?,
|
||||
46
|
||||
);
|
||||
assert_eq!(
|
||||
engine
|
||||
.eval::<Map>(
|
||||
r"
|
||||
let x = #{a: 1, b: 2, c: 3};
|
||||
let y = #{b: 42, d: 9};
|
||||
x + y
|
||||
"
|
||||
)?
|
||||
.len(),
|
||||
4
|
||||
);
|
||||
}
|
||||
)?
|
||||
.len(),
|
||||
4
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
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 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!(b.cast::<bool>(), true);
|
||||
assert_eq!(c.cast::<String>(), "hello");
|
||||
assert_eq!(
|
||||
x.get("a")
|
||||
.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(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
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 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!(b.cast::<bool>(), true);
|
||||
assert_eq!(c.cast::<String>(), "hello");
|
||||
assert_eq!(
|
||||
x.get("a")
|
||||
.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(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_map_for() -> Result<(), EvalAltResult> {
|
||||
let mut engine = Engine::new();
|
||||
let engine = Engine::new();
|
||||
|
||||
assert_eq!(
|
||||
engine.eval::<INT>(
|
||||
r#"
|
||||
let map = #{a: 1, b: true, c: 123.456};
|
||||
let s = "";
|
||||
engine
|
||||
.eval::<String>(
|
||||
r#"
|
||||
let map = #{a: 1, b_x: true, "$c d e!": "hello"};
|
||||
let s = "";
|
||||
|
||||
for key in keys(map) {
|
||||
s += key;
|
||||
}
|
||||
for key in keys(map) {
|
||||
s += key;
|
||||
}
|
||||
|
||||
s.len()
|
||||
s
|
||||
"#
|
||||
)?,
|
||||
3
|
||||
)?
|
||||
.len(),
|
||||
11
|
||||
);
|
||||
|
||||
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(())
|
||||
}
|
||||
|
@ -2,7 +2,7 @@ use rhai::{Engine, EvalAltResult, INT};
|
||||
|
||||
#[test]
|
||||
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")?, -1);
|
||||
|
@ -1,12 +1,11 @@
|
||||
use rhai::{Engine, EvalAltResult, RegisterFn, INT};
|
||||
|
||||
#[test]
|
||||
#[cfg(not(feature = "no_stdlib"))]
|
||||
fn test_mismatched_op() {
|
||||
let mut engine = Engine::new();
|
||||
let engine = Engine::new();
|
||||
|
||||
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")
|
||||
);
|
||||
}
|
||||
|
@ -2,7 +2,7 @@ use rhai::{Engine, EvalAltResult};
|
||||
|
||||
#[test]
|
||||
fn test_not() -> Result<(), EvalAltResult> {
|
||||
let mut engine = Engine::new();
|
||||
let engine = Engine::new();
|
||||
|
||||
assert_eq!(
|
||||
engine.eval::<bool>("let not_true = !true; not_true")?,
|
||||
|
@ -2,7 +2,7 @@ use rhai::{Engine, EvalAltResult, INT};
|
||||
|
||||
#[test]
|
||||
fn test_number_literal() -> Result<(), EvalAltResult> {
|
||||
let mut engine = Engine::new();
|
||||
let engine = Engine::new();
|
||||
|
||||
assert_eq!(engine.eval::<INT>("65")?, 65);
|
||||
|
||||
@ -11,7 +11,7 @@ fn test_number_literal() -> Result<(), EvalAltResult> {
|
||||
|
||||
#[test]
|
||||
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 = 0xff; x")?, 255);
|
||||
@ -21,7 +21,7 @@ fn test_hex_literal() -> Result<(), EvalAltResult> {
|
||||
|
||||
#[test]
|
||||
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 = 0o1234; x")?, 668);
|
||||
@ -31,7 +31,7 @@ fn test_octal_literal() -> Result<(), EvalAltResult> {
|
||||
|
||||
#[test]
|
||||
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!(
|
||||
|
@ -2,7 +2,7 @@ use rhai::{Engine, EvalAltResult, INT};
|
||||
|
||||
#[test]
|
||||
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>("(1 + 2) * (6 - 4) / 2")?, 3);
|
||||
@ -11,8 +11,8 @@ fn test_ops() -> Result<(), EvalAltResult> {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_op_prec() -> Result<(), EvalAltResult> {
|
||||
let mut engine = Engine::new();
|
||||
fn test_op_precedence() -> Result<(), EvalAltResult> {
|
||||
let engine = Engine::new();
|
||||
|
||||
assert_eq!(
|
||||
engine.eval::<INT>("let x = 0; if x == 10 || true { x = 1} x")?,
|
||||
|
@ -8,7 +8,7 @@ const EPSILON: FLOAT = 0.000_000_000_1;
|
||||
|
||||
#[test]
|
||||
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);
|
||||
@ -29,7 +29,7 @@ fn test_power_of() -> Result<(), EvalAltResult> {
|
||||
|
||||
#[test]
|
||||
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);
|
||||
|
@ -1,5 +1,3 @@
|
||||
#![cfg(not(feature = "no_object"))]
|
||||
|
||||
///! This test simulates an external command object that is driven by a script.
|
||||
use rhai::{Engine, EvalAltResult, RegisterFn, Scope, INT};
|
||||
use std::sync::{Arc, Mutex};
|
||||
@ -40,8 +38,9 @@ impl CommandWrapper {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "no_object"))]
|
||||
#[test]
|
||||
fn test_side_effects() -> Result<(), EvalAltResult> {
|
||||
fn test_side_effects_command() -> Result<(), EvalAltResult> {
|
||||
let mut engine = Engine::new();
|
||||
let mut scope = Scope::new();
|
||||
|
||||
@ -79,3 +78,22 @@ fn test_side_effects() -> Result<(), EvalAltResult> {
|
||||
|
||||
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
30
tests/stack.rs
Normal 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(())
|
||||
}
|
@ -2,7 +2,7 @@ use rhai::{Engine, EvalAltResult};
|
||||
|
||||
#[test]
|
||||
fn test_string() -> Result<(), EvalAltResult> {
|
||||
let mut engine = Engine::new();
|
||||
let engine = Engine::new();
|
||||
|
||||
assert_eq!(
|
||||
engine.eval::<String>(r#""Test string: \u2764""#)?,
|
||||
@ -12,18 +12,24 @@ fn test_string() -> Result<(), EvalAltResult> {
|
||||
engine.eval::<String>(r#""Test string: \x58""#)?,
|
||||
"Test string: X"
|
||||
);
|
||||
assert_eq!(engine.eval::<String>(r#""\"hello\"""#)?, r#""hello""#);
|
||||
|
||||
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"))]
|
||||
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_stdlib"))]
|
||||
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(())
|
||||
}
|
||||
|
@ -2,7 +2,7 @@ use rhai::{Engine, EvalAltResult};
|
||||
|
||||
#[test]
|
||||
fn test_throw() {
|
||||
let mut engine = Engine::new();
|
||||
let engine = Engine::new();
|
||||
|
||||
assert!(matches!(
|
||||
engine.eval::<()>(r#"if true { throw "hello" }"#).expect_err("expects error"),
|
||||
|
39
tests/time.rs
Normal file
39
tests/time.rs
Normal 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(())
|
||||
}
|
@ -4,7 +4,7 @@ use rhai::{Engine, EvalAltResult, INT};
|
||||
// TODO also add test case for unary after compound
|
||||
// Hah, turns out unary + has a good use after all!
|
||||
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")?, 160);
|
||||
|
@ -2,7 +2,7 @@ use rhai::{Engine, EvalAltResult, INT};
|
||||
|
||||
#[test]
|
||||
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);
|
||||
|
||||
|
@ -2,21 +2,21 @@ use rhai::{Engine, EvalAltResult};
|
||||
|
||||
#[test]
|
||||
fn test_unit() -> Result<(), EvalAltResult> {
|
||||
let mut engine = Engine::new();
|
||||
let engine = Engine::new();
|
||||
engine.eval::<()>("let x = (); x")?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
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);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_unit_with_spaces() -> Result<(), EvalAltResult> {
|
||||
let mut engine = Engine::new();
|
||||
let engine = Engine::new();
|
||||
engine.eval::<()>("let x = ( ); x")?;
|
||||
Ok(())
|
||||
}
|
||||
|
@ -2,7 +2,7 @@ use rhai::{Engine, EvalAltResult, Scope, INT};
|
||||
|
||||
#[test]
|
||||
fn test_var_scope() -> Result<(), EvalAltResult> {
|
||||
let mut engine = Engine::new();
|
||||
let engine = Engine::new();
|
||||
let mut scope = Scope::new();
|
||||
|
||||
engine.eval_with_scope::<()>(&mut scope, "let x = 4 + 5")?;
|
||||
@ -21,7 +21,7 @@ fn test_var_scope() -> Result<(), EvalAltResult> {
|
||||
|
||||
#[test]
|
||||
fn test_scope_eval() -> Result<(), EvalAltResult> {
|
||||
let mut engine = Engine::new();
|
||||
let engine = Engine::new();
|
||||
|
||||
// First create the state
|
||||
let mut scope = Scope::new();
|
||||
|
@ -2,7 +2,7 @@ use rhai::{Engine, EvalAltResult, INT};
|
||||
|
||||
#[test]
|
||||
fn test_while() -> Result<(), EvalAltResult> {
|
||||
let mut engine = Engine::new();
|
||||
let engine = Engine::new();
|
||||
|
||||
assert_eq!(
|
||||
engine.eval::<INT>(
|
||||
|
Loading…
Reference in New Issue
Block a user