Merge pull request #128 from schungx/master

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

View File

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

495
README.md
View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

File diff suppressed because it is too large Load Diff

View File

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

View File

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

120
src/fn_func.rs Normal file
View File

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

View File

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

View File

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

View File

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

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

30
tests/stack.rs Normal file
View File

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

View File

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

View File

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

39
tests/time.rs Normal file
View File

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

View File

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

View File

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

View File

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

View File

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

View File

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