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