Merge pull request #163 from schungx/master

Minor refinements and enhancements, WASM.
This commit is contained in:
Stephen Chung 2020-06-18 11:04:01 +08:00 committed by GitHub
commit c7df158bba
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
48 changed files with 3380 additions and 1870 deletions

View File

@ -1,6 +1,6 @@
[package] [package]
name = "rhai" name = "rhai"
version = "0.15.0" version = "0.15.1"
edition = "2018" edition = "2018"
authors = ["Jonathan Turner", "Lukáš Hozda", "Stephen Chung"] authors = ["Jonathan Turner", "Lukáš Hozda", "Stephen Chung"]
description = "Embedded scripting for Rust" description = "Embedded scripting for Rust"
@ -22,6 +22,7 @@ num-traits = { version = "0.2.11", default-features = false }
[features] [features]
#default = ["unchecked", "sync", "no_optimize", "no_float", "only_i32", "no_index", "no_object", "no_function", "no_module"] #default = ["unchecked", "sync", "no_optimize", "no_float", "only_i32", "no_index", "no_object", "no_function", "no_module"]
default = [] default = []
plugins = []
unchecked = [] # unchecked arithmetic unchecked = [] # unchecked arithmetic
sync = [] # restrict to only types that implement Send + Sync sync = [] # restrict to only types that implement Send + Sync
no_optimize = [] # no script optimizer no_optimize = [] # no script optimizer
@ -62,3 +63,6 @@ version = "0.3.2"
default-features = false default-features = false
features = ["compile-time-rng"] features = ["compile-time-rng"]
optional = true optional = true
[target.'cfg(target_arch = "wasm32")'.dependencies]
instant= { version = "0.1.4", features = ["wasm-bindgen"] } # WASM implementation of std::time::Instant

393
README.md
View File

@ -11,10 +11,17 @@ Rhai - Embedded Scripting for Rust
Rhai is an embedded scripting language and evaluation engine for Rust that gives a safe and easy way Rhai is an embedded scripting language and evaluation engine for Rust that gives a safe and easy way
to add scripting to any application. to add scripting to any application.
Supported targets
-----------------
* All common CPU targets for Windows, Linux and MacOS.
* [WASM]
* `no-std`
Features Features
-------- --------
* Easy-to-use language similar to JS+Rust with dynamic typing but _no_ garbage collector. * Easy-to-use language similar to JS+Rust with dynamic typing.
* Tight integration with native Rust [functions](#working-with-functions) and [types](#custom-types-and-methods), * Tight integration with native Rust [functions](#working-with-functions) and [types](#custom-types-and-methods),
including [getters/setters](#getters-and-setters), [methods](#members-and-methods) and [indexers](#indexers). including [getters/setters](#getters-and-setters), [methods](#members-and-methods) and [indexers](#indexers).
* Freely pass Rust variables/constants into a script via an external [`Scope`]. * Freely pass Rust variables/constants into a script via an external [`Scope`].
@ -25,19 +32,18 @@ Features
one single source file, all with names starting with `"unsafe_"`). one single source file, all with names starting with `"unsafe_"`).
* Re-entrant scripting [`Engine`] can be made `Send + Sync` (via the [`sync`] feature). * Re-entrant scripting [`Engine`] can be made `Send + Sync` (via the [`sync`] feature).
* Sand-boxed - the scripting [`Engine`], if declared immutable, cannot mutate the containing environment unless explicitly permitted (e.g. via a `RefCell`). * Sand-boxed - the scripting [`Engine`], if declared immutable, cannot mutate the containing environment unless explicitly permitted (e.g. via a `RefCell`).
* Rugged (protection against [stack-overflow](#maximum-call-stack-depth) and [runaway scripts](#maximum-number-of-operations) etc.). * Rugged - protection against malicious attacks (such as [stack-overflow](#maximum-call-stack-depth), [over-sized data](#maximum-length-of-strings), and [runaway scripts](#maximum-number-of-operations) etc.) that may come from untrusted third-party user-land scripts.
* Track script evaluation [progress](#tracking-progress) and manually terminate a script run. * Track script evaluation [progress](#tracking-progress-and-force-terminate-script-run) and manually terminate a script run.
* [`no-std`](#optional-features) support.
* [Function overloading](#function-overloading). * [Function overloading](#function-overloading).
* [Operator overloading](#operator-overloading). * [Operator overloading](#operator-overloading).
* Organize code base with dynamically-loadable [Modules]. * Organize code base with dynamically-loadable [modules].
* Scripts are [optimized](#script-optimization) (useful for template-based machine-generated scripts) for repeated evaluations. * Scripts are [optimized](#script-optimization) (useful for template-based machine-generated scripts) for repeated evaluations.
* Support for [minimal builds](#minimal-builds) by excluding unneeded language [features](#optional-features). * Support for [minimal builds] by excluding unneeded language [features](#optional-features).
* Very few additional dependencies (right now only [`num-traits`](https://crates.io/crates/num-traits/) * Very few additional dependencies (right now only [`num-traits`](https://crates.io/crates/num-traits/)
to do checked arithmetic operations); for [`no-std`](#optional-features) builds, a number of additional dependencies are to do checked arithmetic operations); for [`no-std`](#optional-features) builds, a number of additional dependencies are
pulled in to provide for functionalities that used to be in `std`. pulled in to provide for functionalities that used to be in `std`.
**Note:** Currently, the version is `0.15.0`, so the language and API's may change before they stabilize. **Note:** Currently, the version is `0.15.1`, so the language and API's may change before they stabilize.
What Rhai doesn't do What Rhai doesn't do
-------------------- --------------------
@ -71,7 +77,7 @@ Install the Rhai crate on [`crates.io`](https::/crates.io/crates/rhai/) by addin
```toml ```toml
[dependencies] [dependencies]
rhai = "0.15.0" rhai = "0.15.1"
``` ```
Use the latest released crate version on [`crates.io`](https::/crates.io/crates/rhai/): Use the latest released crate version on [`crates.io`](https::/crates.io/crates/rhai/):
@ -105,7 +111,7 @@ Optional features
| `no_index` | Disable [arrays] and indexing features. | | `no_index` | Disable [arrays] and indexing features. |
| `no_object` | Disable support for custom types and [object maps]. | | `no_object` | Disable support for custom types and [object maps]. |
| `no_function` | Disable script-defined functions. | | `no_function` | Disable script-defined functions. |
| `no_module` | Disable loading modules. | | `no_module` | Disable loading external modules. |
| `no_std` | Build for `no-std`. Notice that additional dependencies will be pulled in to replace `std` features. | | `no_std` | Build for `no-std`. Notice that additional dependencies will be pulled in to replace `std` features. |
By default, Rhai includes all the standard functionalities in a small, tight package. By default, Rhai includes all the standard functionalities in a small, tight package.
@ -141,8 +147,10 @@ Making [`Dynamic`] small helps performance due to better cache efficiency.
### Minimal builds ### Minimal builds
In order to compile a _minimal_build - i.e. a build optimized for size - perhaps for embedded targets, it is essential that [minimal builds]: #minimal-builds
the correct linker flags are used in `cargo.toml`:
In order to compile a _minimal_build - i.e. a build optimized for size - perhaps for `no-std` embedded targets or for
compiling to [WASM], it is essential that the correct linker flags are used in `cargo.toml`:
```toml ```toml
[profile.release] [profile.release]
@ -156,7 +164,9 @@ all code is compiled in as what a script requires cannot be predicted. If a lang
omitting them via special features is a prudent strategy to optimize the build for size. omitting them via special features is a prudent strategy to optimize the build for size.
Omitting arrays (`no_index`) yields the most code-size savings, followed by floating-point support Omitting arrays (`no_index`) yields the most code-size savings, followed by floating-point support
(`no_float`), checked arithmetic (`unchecked`) and finally object maps and custom types (`no_object`). (`no_float`), checked arithmetic/script resource limits (`unchecked`) and finally object maps and custom types (`no_object`).
Where the usage scenario does not call for loading externally-defined modules, use `no_module` to save some bytes.
Disable script-defined functions (`no_function`) only when the feature is not needed because code size savings is minimal. Disable script-defined functions (`no_function`) only when the feature is not needed because code size savings is minimal.
[`Engine::new_raw`](#raw-engine) creates a _raw_ engine. [`Engine::new_raw`](#raw-engine) creates a _raw_ engine.
@ -164,8 +174,26 @@ A _raw_ engine supports, out of the box, only a very [restricted set](#built-in-
Selectively include other necessary functionalities by loading specific [packages] to minimize the footprint. Selectively include other necessary functionalities by loading specific [packages] to minimize the footprint.
Packages are sharable (even across threads via the [`sync`] feature), so they only have to be created once. Packages are sharable (even across threads via the [`sync`] feature), so they only have to be created once.
Related ### Building to WebAssembly (WASM)
-------
[WASM]: #building-to-WebAssembly-wasm
It is possible to use Rhai when compiling to WebAssembly (WASM). This yields a scripting engine (and language)
that can be run in a standard web browser. Why you would want to is another matter... as there is already
a nice, fast, complete scripting language for the the common WASM environment (i.e. a browser) - and it is called JavaScript.
But anyhow, do it because you _can_!
When building for WASM, certain features will not be available, such as the script file API's and loading modules
from external script files.
Also look into [minimal builds] to reduce generated WASM size. As of this version, a typical, full-featured
Rhai scripting engine compiles to a single WASM file less than 200KB gzipped. When excluding features that are
marginal in WASM environment, the gzipped payload can be further shrunk to 160KB.
In benchmark tests, a WASM build runs scripts roughly 1.7-2.2x slower than a native optimized release build.
Related Resources
-----------------
Other cool projects to check out: Other cool projects to check out:
@ -179,13 +207,14 @@ A number of examples can be found in the `examples` folder:
| Example | Description | | Example | Description |
| ------------------------------------------------------------------ | --------------------------------------------------------------------------- | | ------------------------------------------------------------------ | --------------------------------------------------------------------------- |
| [`arrays_and_structs`](examples/arrays_and_structs.rs) | demonstrates registering a new type to Rhai and the usage of [arrays] on it | | [`arrays_and_structs`](examples/arrays_and_structs.rs) | shows how to register a custom Rust type and using [arrays] on it |
| [`custom_types_and_methods`](examples/custom_types_and_methods.rs) | shows how to register a type and methods for it | | [`custom_types_and_methods`](examples/custom_types_and_methods.rs) | shows how to register a custom Rust type and methods for it |
| [`hello`](examples/hello.rs) | simple example that evaluates an expression and prints the result | | [`hello`](examples/hello.rs) | simple example that evaluates an expression and prints the result |
| [`no_std`](examples/no_std.rs) | example to test out `no-std` builds | | [`no_std`](examples/no_std.rs) | example to test out `no-std` builds |
| [`reuse_scope`](examples/reuse_scope.rs) | evaluates two pieces of code in separate runs, but using a common [`Scope`] | | [`reuse_scope`](examples/reuse_scope.rs) | evaluates two pieces of code in separate runs, but using a common [`Scope`] |
| [`rhai_runner`](examples/rhai_runner.rs) | runs each filename passed to it as a Rhai script | | [`rhai_runner`](examples/rhai_runner.rs) | runs each filename passed to it as a Rhai script |
| [`simple_fn`](examples/simple_fn.rs) | shows how to register a Rust function to a Rhai [`Engine`] | | [`simple_fn`](examples/simple_fn.rs) | shows how to register a simple function |
| [`strings`](examples/strings.rs) | shows different ways to register functions taking string arguments |
| [`repl`](examples/repl.rs) | a simple REPL, interactively evaluate statements from stdin | | [`repl`](examples/repl.rs) | a simple REPL, interactively evaluate statements from stdin |
Examples can be run with the following command: Examples can be run with the following command:
@ -314,8 +343,8 @@ Functions declared with `private` are hidden and cannot be called from Rust (see
```rust ```rust
// Define functions in a script. // Define functions in a script.
let ast = engine.compile(true, let ast = engine.compile(true,
r" r#"
// a function with two parameters: String and i64 // a function with two parameters: string and i64
fn hello(x, y) { fn hello(x, y) {
x.len + y x.len + y
} }
@ -334,7 +363,7 @@ let ast = engine.compile(true,
private hidden() { private hidden() {
throw "you shouldn't see me!"; throw "you shouldn't see me!";
} }
")?; "#)?;
// A custom scope can also contain any variables/constants available to the functions // A custom scope can also contain any variables/constants available to the functions
let mut scope = Scope::new(); let mut scope = Scope::new();
@ -358,16 +387,14 @@ let result: i64 = engine.call_fn(&mut scope, &ast, "hello", () )?;
let result: () = engine.call_fn(&mut scope, &ast, "hidden", ())?; let result: () = engine.call_fn(&mut scope, &ast, "hidden", ())?;
``` ```
For more control, construct all arguments as `Dynamic` values and use `Engine::call_fn_dynamic`: For more control, construct all arguments as `Dynamic` values and use `Engine::call_fn_dynamic`, passing it
anything that implements `IntoIterator<Item = Dynamic>` (such as a simple `Vec<Dynamic>`):
```rust ```rust
let result: Dynamic = engine.call_fn_dynamic(&mut scope, &ast, "hello", let result: Dynamic = engine.call_fn_dynamic(&mut scope, &ast, "hello",
&mut [ String::from("abc").into(), 123_i64.into() ])?; vec![ String::from("abc").into(), 123_i64.into() ])?;
``` ```
However, beware that `Engine::call_fn_dynamic` _consumes_ its arguments, meaning that all arguments passed to it
will be replaced by `()` afterwards. To re-use the arguments, clone them beforehand and pass in the clone.
### Creating Rust anonymous functions from Rhai script ### Creating Rust anonymous functions from Rhai script
[`Func`]: #creating-rust-anonymous-functions-from-rhai-script [`Func`]: #creating-rust-anonymous-functions-from-rhai-script
@ -427,7 +454,7 @@ are supported.
| `+`, | `+=` | `INT`, `FLOAT` (if not [`no_float`]), `ImmutableString` | | `+`, | `+=` | `INT`, `FLOAT` (if not [`no_float`]), `ImmutableString` |
| `-`, `*`, `/`, `%`, `~`, | `-=`, `*=`, `/=`, `%=`, `~=` | `INT`, `FLOAT` (if not [`no_float`]) | | `-`, `*`, `/`, `%`, `~`, | `-=`, `*=`, `/=`, `%=`, `~=` | `INT`, `FLOAT` (if not [`no_float`]) |
| `<<`, `>>`, `^`, | `<<=`, `>>=`, `^=` | `INT` | | `<<`, `>>`, `^`, | `<<=`, `>>=`, `^=` | `INT` |
| `&`, `\|`, | `&=`, `|=` | `INT`, `bool` | | `&`, `\|`, | `&=`, `\|=` | `INT`, `bool` |
| `&&`, `\|\|` | | `bool` | | `&&`, `\|\|` | | `bool` |
| `==`, `!=` | | `INT`, `FLOAT` (if not [`no_float`]), `bool`, `char`, `()`, `ImmutableString` | | `==`, `!=` | | `INT`, `FLOAT` (if not [`no_float`]), `bool`, `char`, `()`, `ImmutableString` |
| `>`, `>=`, `<`, `<=` | | `INT`, `FLOAT` (if not [`no_float`]), `char`, `()`, `ImmutableString` | | `>`, `>=`, `<`, `<=` | | `INT`, `FLOAT` (if not [`no_float`]), `char`, `()`, `ImmutableString` |
@ -472,8 +499,7 @@ The follow packages are available:
Packages typically contain Rust functions that are callable within a Rhai script. Packages typically contain Rust functions that are callable within a Rhai script.
All functions registered in a package is loaded under the _global namespace_ (i.e. they're available without module qualifiers). All functions registered in a package is loaded under the _global namespace_ (i.e. they're available without module qualifiers).
Once a package is created (e.g. via `new`), it can be _shared_ (via `get`) among multiple instances of [`Engine`], Once a package is created (e.g. via `new`), it can be _shared_ (via `get`) among multiple instances of [`Engine`],
even across threads (if the [`sync`] feature is turned on). even across threads (under [`sync`]). Therefore, a package only has to be created _once_.
Therefore, a package only has to be created _once_.
Packages are actually implemented as [modules], so they share a lot of behavior and characteristics. Packages are actually implemented as [modules], so they share a lot of behavior and characteristics.
The main difference is that a package loads under the _global_ namespace, while a module loads under its own The main difference is that a package loads under the _global_ namespace, while a module loads under its own
@ -496,7 +522,7 @@ In these cases, use the `compile_expression` and `eval_expression` methods or th
let result = engine.eval_expression::<i64>("2 + (10 + 10) * 2")?; let result = engine.eval_expression::<i64>("2 + (10 + 10) * 2")?;
``` ```
When evaluation _expressions_, no full-blown statement (e.g. `if`, `while`, `for`) - not even variable assignments - When evaluating _expressions_, no full-blown statement (e.g. `if`, `while`, `for`) - not even variable assignments -
is supported and will be considered parse errors when encountered. is supported and will be considered parse errors when encountered.
```rust ```rust
@ -522,7 +548,7 @@ The following primitive types are supported natively:
| **Floating-point number** (disabled with [`no_float`]) | `f32`, `f64` _(default)_ | `"f32"` or `"f64"` | `"123.4567"` etc. | | **Floating-point number** (disabled with [`no_float`]) | `f32`, `f64` _(default)_ | `"f32"` or `"f64"` | `"123.4567"` etc. |
| **Boolean value** | `bool` | `"bool"` | `"true"` or `"false"` | | **Boolean value** | `bool` | `"bool"` | `"true"` or `"false"` |
| **Unicode character** | `char` | `"char"` | `"A"`, `"x"` etc. | | **Unicode character** | `char` | `"char"` | `"A"`, `"x"` etc. |
| **Immutable Unicode string** | `rhai::ImmutableString` (implemented as `Rc<String>` or `Arc<String>`, _not_ `&str`) | `"string"` | `"hello"` etc. | | **Immutable Unicode string** | `rhai::ImmutableString` (implemented as `Rc<String>` or `Arc<String>`) | `"string"` | `"hello"` etc. |
| **Array** (disabled with [`no_index`]) | `rhai::Array` | `"array"` | `"[ ?, ?, ? ]"` | | **Array** (disabled with [`no_index`]) | `rhai::Array` | `"array"` | `"[ ?, ?, ? ]"` |
| **Object map** (disabled with [`no_object`]) | `rhai::Map` | `"map"` | `#{ "a": 1, "b": 2 }` | | **Object map** (disabled with [`no_object`]) | `rhai::Map` | `"map"` | `#{ "a": 1, "b": 2 }` |
| **Timestamp** (implemented in the [`BasicTimePackage`](#packages)) | `std::time::Instant` | `"timestamp"` | _not supported_ | | **Timestamp** (implemented in the [`BasicTimePackage`](#packages)) | `std::time::Instant` | `"timestamp"` | _not supported_ |
@ -573,7 +599,7 @@ if type_of(x) == "string" {
[`Dynamic`]: #dynamic-values [`Dynamic`]: #dynamic-values
A `Dynamic` value can be _any_ type. However, if the [`sync`] feature is used, then all types must be `Send + Sync`. A `Dynamic` value can be _any_ type. However, under [`sync`], all types must be `Send + Sync`.
Because [`type_of()`] a `Dynamic` value returns the type of the actual value, it is usually used to perform type-specific Because [`type_of()`] a `Dynamic` value returns the type of the actual value, it is usually used to perform type-specific
actions based on the actual value's type. actions based on the actual value's type.
@ -685,13 +711,18 @@ Rhai's scripting engine is very lightweight. It gets most of its abilities from
To call these functions, they need to be registered with the [`Engine`]. To call these functions, they need to be registered with the [`Engine`].
```rust ```rust
use rhai::{Dynamic, Engine, EvalAltResult}; use rhai::{Dynamic, Engine, EvalAltResult, ImmutableString};
use rhai::RegisterFn; // use 'RegisterFn' trait for 'register_fn' use rhai::RegisterFn; // use 'RegisterFn' trait for 'register_fn'
use rhai::RegisterResultFn; // use 'RegisterResultFn' trait for 'register_result_fn' use rhai::RegisterResultFn; // use 'RegisterResultFn' trait for 'register_result_fn'
// Normal function that returns any value type // Normal function that returns a standard type
fn add(x: i64, y: i64) -> i64 { // Remember to use 'ImmutableString' and not 'String'
x + y fn add_len(x: i64, s: ImmutableString) -> i64 {
x + s.len()
}
// Alternatively, '&str' maps directly to 'ImmutableString'
fn add_len_str(x: i64, s: &str) -> i64 {
x + s.len()
} }
// Function that returns a 'Dynamic' value - must return a 'Result' // Function that returns a 'Dynamic' value - must return a 'Result'
@ -703,9 +734,14 @@ fn main() -> Result<(), Box<EvalAltResult>>
{ {
let engine = Engine::new(); let engine = Engine::new();
engine.register_fn("add", add); engine.register_fn("add", add_len);
engine.register_fn("add_str", add_len_str);
let result = engine.eval::<i64>("add(40, 2)")?; let result = engine.eval::<i64>(r#"add(40, "xx")"#)?;
println!("Answer: {}", result); // prints 42
let result = engine.eval::<i64>(r#"add_str(40, "xx")"#)?;
println!("Answer: {}", result); // prints 42 println!("Answer: {}", result); // prints 42
@ -728,7 +764,7 @@ use rhai::Dynamic;
let x = (42_i64).into(); // 'into()' works for standard types let x = (42_i64).into(); // 'into()' works for standard types
let y = Dynamic::from(String::from("hello!")); // remember &str is not supported by Rhai let y = Dynamic::from("hello!".to_string()); // remember &str is not supported by Rhai
``` ```
Functions registered with the [`Engine`] can be _overloaded_ as long as the _signature_ is unique, Functions registered with the [`Engine`] can be _overloaded_ as long as the _signature_ is unique,
@ -736,6 +772,25 @@ i.e. different functions can have the same name as long as their parameters are
and/or different number. and/or different number.
New definitions _overwrite_ previous definitions of the same name and same number/types of parameters. New definitions _overwrite_ previous definitions of the same name and same number/types of parameters.
### `String` parameters
Functions accepting a parameter of `String` should use `&str` instead because it maps directly to `ImmutableString`
which is the type that Rhai uses to represent strings internally.
```rust
fn get_len1(s: String) -> i64 { s.len() as i64 } // <- Rhai will not find this function
fn get_len2(s: &str) -> i64 { s.len() as i64 } // <- Rhai finds this function fine
fn get_len3(s: ImmutableString) -> i64 { s.len() as i64 } // <- the above is equivalent to this
engine.register_fn("len1", get_len1);
engine.register_fn("len2", get_len2);
engine.register_fn("len3", get_len3);
let len = engine.eval::<i64>("x.len1()")?; // error: function 'len1 (string)' not found
let len = engine.eval::<i64>("x.len2()")?; // works fine
let len = engine.eval::<i64>("x.len3()")?; // works fine
```
Generic functions Generic functions
----------------- -----------------
@ -976,8 +1031,8 @@ let result = engine.eval::<i64>(
println!("result: {}", result); // prints 1 println!("result: {}", result); // prints 1
``` ```
If the [`no_object`] feature is turned on, however, the _method_ style of function calls Under [`no_object`], however, the _method_ style of function calls (i.e. calling a function as an object-method)
(i.e. calling a function as an object-method) is no longer supported. is no longer supported.
```rust ```rust
// Below is a syntax error under 'no_object' because 'clear' cannot be called in method style. // Below is a syntax error under 'no_object' because 'clear' cannot be called in method style.
@ -1000,8 +1055,7 @@ let x = new_ts();
print(x.type_of()); // prints "Hello" print(x.type_of()); // prints "Hello"
``` ```
Getters and setters ### Getters and setters
-------------------
Similarly, custom types can expose members by registering a `get` and/or `set` function. Similarly, custom types can expose members by registering a `get` and/or `set` function.
@ -1011,13 +1065,13 @@ struct TestStruct {
field: String field: String
} }
// Remember Rhai uses 'ImmutableString' instead of 'String'
impl TestStruct { impl TestStruct {
fn get_field(&mut self) -> ImmutableString { // Returning a 'String' is OK - Rhai converts it into 'ImmutableString'
// Make an 'ImmutableString' from a 'String' fn get_field(&mut self) -> String {
self.field.into(0) self.field.clone()
} }
// Remember Rhai uses 'ImmutableString' or '&str' instead of 'String'
fn set_field(&mut self, new_val: ImmutableString) { fn set_field(&mut self, new_val: ImmutableString) {
// Get a 'String' from an 'ImmutableString' // Get a 'String' from an 'ImmutableString'
self.field = (*new_val).clone(); self.field = (*new_val).clone();
@ -1041,12 +1095,10 @@ let result = engine.eval::<String>(r#"let a = new_ts(); a.xyz = "42"; a.xyz"#)?;
println!("Answer: {}", result); // prints 42 println!("Answer: {}", result); // prints 42
``` ```
Indexers ### Indexers
--------
Custom types can also expose an _indexer_ by registering an indexer function. Custom types can also expose an _indexer_ by registering an indexer function.
A custom type with an indexer function defined can use the bracket '`[]`' notation to get a property value A custom type with an indexer function defined can use the bracket '`[]`' notation to get a property value
(but not update it - indexers are read-only).
```rust ```rust
#[derive(Clone)] #[derive(Clone)]
@ -1058,9 +1110,12 @@ impl TestStruct {
fn get_field(&mut self, index: i64) -> i64 { fn get_field(&mut self, index: i64) -> i64 {
self.fields[index as usize] self.fields[index as usize]
} }
fn set_field(&mut self, index: i64, value: i64) {
self.fields[index as usize] = value
}
fn new() -> Self { fn new() -> Self {
TestStruct { fields: vec![1, 2, 42, 4, 5] } TestStruct { fields: vec![1, 2, 3, 4, 5] }
} }
} }
@ -1069,16 +1124,40 @@ let engine = Engine::new();
engine.register_type::<TestStruct>(); engine.register_type::<TestStruct>();
engine.register_fn("new_ts", TestStruct::new); engine.register_fn("new_ts", TestStruct::new);
engine.register_indexer(TestStruct::get_field);
let result = engine.eval::<i64>("let a = new_ts(); a[2]")?; // Shorthand: engine.register_indexer_get_set(TestStruct::get_field, TestStruct::set_field);
engine.register_indexer_get(TestStruct::get_field);
engine.register_indexer_set(TestStruct::set_field);
let result = engine.eval::<i64>("let a = new_ts(); a[2] = 42; a[2]")?;
println!("Answer: {}", result); // prints 42 println!("Answer: {}", result); // prints 42
``` ```
Needless to say, `register_type`, `register_type_with_name`, `register_get`, `register_set`, `register_get_set` For efficiency reasons, indexers **cannot** be used to overload (i.e. override) built-in indexing operations for
and `register_indexer` are not available when the [`no_object`] feature is turned on. [arrays] and [object maps].
`register_indexer` is also not available when the [`no_index`] feature is turned on.
### Disabling custom types
The custom types API `register_type`, `register_type_with_name`, `register_get`, `register_set`, `register_get_set`,
`register_indexer_get`, `register_indexer_set` and `register_indexer_get_set` are not available under [`no_object`].
The indexers API `register_indexer_get`, `register_indexer_set` and `register_indexer_get_set` are also
not available under [`no_index`].
### Printing for custom types
To use custom types for `print` and `debug`, or convert its value into a [string], it is necessary that the following
functions be registered (assuming the custom type is `T : Display + Debug`):
| Function | Signature | Typical implementation | Usage |
| ----------- | ------------------------------------------------ | ------------------------------------- | --------------------------------------------------------------------------------------- |
| `to_string` | `|s: &mut T| -> ImmutableString` | `s.to_string().into()` | Converts the custom type into a [string] |
| `print` | `|s: &mut T| -> ImmutableString` | `s.to_string().into()` | Converts the custom type into a [string] for the [`print`](#print-and-debug) statement |
| `debug` | `|s: &mut T| -> ImmutableString` | `format!("{:?}", s).into()` | Converts the custom type into a [string] for the [`debug`](#print-and-debug) statement |
| `+` | `|s1: ImmutableString, s: T| -> ImmutableString` | `s1 + s` | Append the custom type to another [string], for `print("Answer: " + type);` usage |
| `+` | `|s: T, s2: ImmutableString| -> ImmutableString` | `s.to_string().push_str(&s2).into();` | Append another [string] to the custom type, for `print(type + " is the answer");` usage |
| `+=` | `|s1: &mut ImmutableString, s: T|` | `s1 += s.to_string()` | Append the custom type to an existing [string], for `s += type;` usage |
`Scope` - Initializing and maintaining state `Scope` - Initializing and maintaining state
------------------------------------------- -------------------------------------------
@ -1089,8 +1168,8 @@ By default, Rhai treats each [`Engine`] invocation as a fresh one, persisting on
but no global state. This gives each evaluation a clean starting slate. In order to continue using the same global state but no global state. This gives each evaluation a clean starting slate. In order to continue using the same global state
from one invocation to the next, such a state must be manually created and passed in. from one invocation to the next, such a state must be manually created and passed in.
All `Scope` variables are [`Dynamic`], meaning they can store values of any type. If the [`sync`] feature is used, however, All `Scope` variables are [`Dynamic`], meaning they can store values of any type. Under [`sync`], however,
then only types that are `Send + Sync` are supported, and the entire `Scope` itself will also be `Send + Sync`. only types that are `Send + Sync` are supported, and the entire `Scope` itself will also be `Send + Sync`.
This is extremely useful in multi-threaded applications. This is extremely useful in multi-threaded applications.
In this example, a global state object (a `Scope`) is created with a few initialized variables, then the same state is In this example, a global state object (a `Scope`) is created with a few initialized variables, then the same state is
@ -1140,13 +1219,16 @@ fn main() -> Result<(), Box<EvalAltResult>>
Engine configuration options Engine configuration options
--------------------------- ---------------------------
| Method | Description | | Method | Not available under | Description |
| ------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------- | | ------------------------ | ---------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------- |
| `set_optimization_level` | Set the amount of script _optimizations_ performed. See [script optimization]. | | `set_optimization_level` | [`no_optimize`] | Set the amount of script _optimizations_ performed. See [script optimization]. |
| `set_max_expr_depths` | Set the maximum nesting levels of an expression/statement. See [maximum statement depth](#maximum-statement-depth). | | `set_max_expr_depths` | [`unchecked`] | Set the maximum nesting levels of an expression/statement. See [maximum statement depth](#maximum-statement-depth). |
| `set_max_call_levels` | Set the maximum number of function call levels (default 50) to avoid infinite recursion. See [maximum call stack depth](#maximum-call-stack-depth). | | `set_max_call_levels` | [`unchecked`] | Set the maximum number of function call levels (default 50) to avoid infinite recursion. See [maximum call stack depth](#maximum-call-stack-depth). |
| `set_max_operations` | Set the maximum number of _operations_ that a script is allowed to consume. See [maximum number of operations](#maximum-number-of-operations). | | `set_max_operations` | [`unchecked`] | Set the maximum number of _operations_ that a script is allowed to consume. See [maximum number of operations](#maximum-number-of-operations). |
| `set_max_modules` | Set the maximum number of [modules] that a script is allowed to load. See [maximum number of modules](#maximum-number-of-modules). | | `set_max_modules` | [`unchecked`] | Set the maximum number of [modules] that a script is allowed to load. See [maximum number of modules](#maximum-number-of-modules). |
| `set_max_string_size` | [`unchecked`] | Set the maximum length (in UTF-8 bytes) for [strings]. See [maximum length of strings](#maximum-length-of-strings). |
| `set_max_array_size` | [`unchecked`], [`no_index`] | Set the maximum size for [arrays]. See [maximum size of arrays](#maximum-size-of-arrays). |
| `set_max_map_size` | [`unchecked`], [`no_object`] | Set the maximum number of properties for [object maps]. See [maximum size of object maps](#maximum-size-of-object-maps). |
------- -------
@ -1191,7 +1273,7 @@ The following are reserved keywords in Rhai:
| `import`, `export`, `as` | Modules | [`no_module`] | | `import`, `export`, `as` | Modules | [`no_module`] |
Keywords cannot be the name of a [function] or [variable], unless the relevant exclusive feature is enabled. Keywords cannot be the name of a [function] or [variable], unless the relevant exclusive feature is enabled.
For example, `fn` is a valid variable name if the [`no_function`] feature is used. For example, `fn` is a valid variable name under [`no_function`].
Statements Statements
---------- ----------
@ -1365,6 +1447,9 @@ Strings and Chars
[strings]: #strings-and-chars [strings]: #strings-and-chars
[char]: #strings-and-chars [char]: #strings-and-chars
All strings in Rhai are implemented as `ImmutableString` (see [standard types]).
`ImmutableString` should be used in place of the standard Rust type `String` when registering functions.
String and character literals follow C-style formatting, with support for Unicode ('`\u`_xxxx_' or '`\U`_xxxxxxxx_') String and character literals follow C-style formatting, with support for Unicode ('`\u`_xxxx_' or '`\U`_xxxxxxxx_')
and hex ('`\x`_xx_') escape sequences. and hex ('`\x`_xx_') escape sequences.
@ -1442,8 +1527,16 @@ record == "Bob X. Davis: age 42 ❤\n";
"Davis" in record == true; "Davis" in record == true;
'X' in record == true; 'X' in record == true;
'C' in record == false; 'C' in record == false;
// Strings can be iterated with a 'for' statement, yielding characters
for ch in record {
print(ch);
}
``` ```
The maximum allowed length of a string can be controlled via `Engine::set_max_string_size`
(see [maximum length of strings](#maximum-length-of-strings)).
### Built-in functions ### Built-in functions
The following standard methods (mostly defined in the [`MoreStringPackage`](#packages) but excluded if using a [raw `Engine`]) operate on strings: The following standard methods (mostly defined in the [`MoreStringPackage`](#packages) but excluded if using a [raw `Engine`]) operate on strings:
@ -1542,6 +1635,8 @@ The following methods (mostly defined in the [`BasicArrayPackage`](#packages) bu
```rust ```rust
let y = [2, 3]; // array literal with 2 elements let y = [2, 3]; // array literal with 2 elements
let y = [2, 3,]; // trailing comma is OK
y.insert(0, 1); // insert element at the beginning y.insert(0, 1); // insert element at the beginning
y.insert(999, 4); // insert element at the end y.insert(999, 4); // insert element at the end
@ -1619,6 +1714,9 @@ y.len == 0;
engine.register_fn("push", |list: &mut Array, item: MyType| list.push(Box::new(item)) ); engine.register_fn("push", |list: &mut Array, item: MyType| list.push(Box::new(item)) );
``` ```
The maximum allowed size of an array can be controlled via `Engine::set_max_array_size`
(see [maximum size of arrays](#maximum-size-of-arrays)).
Object maps Object maps
----------- -----------
@ -1683,6 +1781,8 @@ ts.obj = y; // object maps can be assigned completely (by value copy
let foo = ts.list.a; let foo = ts.list.a;
foo == 42; foo == 42;
let foo = #{ a:1,}; // trailing comma is OK
let foo = #{ a:1, b:2, c:3 }["a"]; let foo = #{ a:1, b:2, c:3 }["a"];
foo == 1; foo == 1;
@ -1722,6 +1822,9 @@ y.clear(); // empty the object map
y.len() == 0; y.len() == 0;
``` ```
The maximum allowed size of an object map can be controlled via `Engine::set_max_map_size`
(see [maximum size of object maps](#maximum-size-of-object-maps)).
### Parsing from JSON ### Parsing from JSON
The syntax for an object map is extremely similar to JSON, with the exception of `null` values which can The syntax for an object map is extremely similar to JSON, with the exception of `null` values which can
@ -1729,7 +1832,7 @@ technically be mapped to [`()`]. A valid JSON string does not start with a hash
Rhai object map does - that's the major difference! 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 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 the [`no_float`] feature is not enabled. Most common generators of JSON data distinguish between
integer and floating-point values by always serializing a floating-point number with a decimal point 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 (i.e. `123.0` instead of `123` which is assumed to be an integer). This style can be used successfully
with Rhai object maps. with Rhai object maps.
@ -1945,9 +2048,18 @@ loop {
Iterating through a range or an [array] is provided by the `for` ... `in` loop. Iterating through a range or an [array] is provided by the `for` ... `in` loop.
```rust ```rust
let array = [1, 3, 5, 7, 9, 42]; // Iterate through string, yielding characters
let s = "hello, world!";
for ch in s {
if ch > 'z' { continue; } // skip to the next iteration
print(ch);
if x == '@' { break; } // break out of for loop
}
// Iterate through array // Iterate through array
let array = [1, 3, 5, 7, 9, 42];
for x in array { for x in array {
if x > 10 { continue; } // skip to the next iteration if x > 10 { continue; } // skip to the next iteration
print(x); print(x);
@ -2035,7 +2147,12 @@ fn add(x, y) {
return x + y; return x + y;
} }
print(add(2, 3)); fn sub(x, y,) { // trailing comma in parameters list is OK
return x - y;
}
print(add(2, 3)); // prints 5
print(sub(2, 3,)); // prints -1 - trailing comma in arguments list is OK
``` ```
### Implicit return ### Implicit return
@ -2141,7 +2258,7 @@ let a = new_ts(); // constructor function
a.field = 500; // property setter a.field = 500; // property setter
a.update(); // method call, 'a' can be modified a.update(); // method call, 'a' can be modified
update(a); // <- this de-sugars to 'a.update()' this if 'a' is a simple variable update(a); // <- this de-sugars to 'a.update()' thus if 'a' is a simple variable
// unlike scripted functions, 'a' can be modified and is not a copy // unlike scripted functions, 'a' can be modified and is not a copy
let array = [ a ]; let array = [ a ];
@ -2362,9 +2479,9 @@ which simply loads a script file based on the path (with `.rhai` extension attac
Built-in module resolvers are grouped under the `rhai::module_resolvers` module namespace. Built-in module resolvers are grouped under the `rhai::module_resolvers` module namespace.
| Module Resolver | Description | | Module Resolver | Description |
| ---------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `FileModuleResolver` | The default module resolution service, not available under the [`no_std`] feature. Loads a script file (based off the current directory) with `.rhai` extension.<br/>The base directory can be changed via the `FileModuleResolver::new_with_path()` constructor function.<br/>`FileModuleResolver::create_module()` loads a script file and returns a module. | | `FileModuleResolver` | The default module resolution service, not available under [`no_std`] or [WASM] builds. Loads a script file (based off the current directory) with `.rhai` extension.<br/>The base directory can be changed via the `FileModuleResolver::new_with_path()` constructor function.<br/>`FileModuleResolver::create_module()` loads a script file and returns a module. |
| `StaticModuleResolver` | Loads modules that are statically added. This can be used when the [`no_std`] feature is turned on. | | `StaticModuleResolver` | Loads modules that are statically added. This can be used under [`no_std`]. |
An [`Engine`]'s module resolver is set via a call to `Engine::set_module_resolver`: An [`Engine`]'s module resolver is set via a call to `Engine::set_module_resolver`:
@ -2380,12 +2497,12 @@ engine.set_module_resolver(None);
Ruggedization - protect against DoS attacks Ruggedization - protect against DoS attacks
------------------------------------------ ------------------------------------------
For scripting systems open to user-land scripts, it is always best to limit the amount of resources used by a script For scripting systems open to untrusted user-land scripts, it is always best to limit the amount of resources used by
so that it does not consume more resources that it is allowed to. a script so that it does not consume more resources that it is allowed to.
The most important resources to watch out for are: The most important resources to watch out for are:
* **Memory**: A malicous script may continuously grow an [array] or [object map] until all memory is consumed. * **Memory**: A malicous script may continuously grow a [string], an [array] or [object map] until all memory is consumed.
It may also create a large [array] or [object map] literal that exhausts all memory during parsing. It may also create a large [array] or [object map] literal that exhausts all memory during parsing.
* **CPU**: A malicous script may run an infinite tight loop that consumes all CPU cycles. * **CPU**: A malicous script may run an infinite tight loop that consumes all CPU cycles.
* **Time**: A malicous script may run indefinitely, thereby blocking the calling system which is waiting for a result. * **Time**: A malicous script may run indefinitely, thereby blocking the calling system which is waiting for a result.
@ -2401,6 +2518,85 @@ The most important resources to watch out for are:
* **Data**: A malicous script may attempt to read from and/or write to data that it does not own. If this happens, * **Data**: A malicous script may attempt to read from and/or write to data that it does not own. If this happens,
it is a severe security breach and may put the entire system at risk. it is a severe security breach and may put the entire system at risk.
### Maximum length of strings
Rhai by default does not limit how long a [string] can be.
This can be changed via the `Engine::set_max_string_size` method, with zero being unlimited (the default).
```rust
let mut engine = Engine::new();
engine.set_max_string_size(500); // allow strings only up to 500 bytes long (in UTF-8 format)
engine.set_max_string_size(0); // allow unlimited string length
```
A script attempting to create a string literal longer than the maximum length will terminate with a parse error.
Any script operation that produces a string longer than the maximum also terminates the script with an error result.
This check can be disabled via the [`unchecked`] feature for higher performance
(but higher risks as well).
Be conservative when setting a maximum limit and always consider the fact that a registered function may grow
a string's length without Rhai noticing until the very end. For instance, the built-in '`+`' operator for strings
concatenates two strings together to form one longer string; if both strings are _slightly_ below the maximum
length limit, the resultant string may be almost _twice_ the maximum length.
### Maximum size of arrays
Rhai by default does not limit how large an [array] can be.
This can be changed via the `Engine::set_max_array_size` method, with zero being unlimited (the default).
```rust
let mut engine = Engine::new();
engine.set_max_array_size(500); // allow arrays only up to 500 items
engine.set_max_array_size(0); // allow unlimited arrays
```
A script attempting to create an array literal larger than the maximum will terminate with a parse error.
Any script operation that produces an array larger than the maximum also terminates the script with an error result.
This check can be disabled via the [`unchecked`] feature for higher performance
(but higher risks as well).
Be conservative when setting a maximum limit and always consider the fact that a registered function may grow
an array's size without Rhai noticing until the very end.
For instance, the built-in '`+`' operator for arrays concatenates two arrays together to form one larger array;
if both arrays are _slightly_ below the maximum size limit, the resultant array may be almost _twice_ the maximum size.
As a malicious script may create a deeply-nested array which consumes huge amounts of memory while each individual
array still stays under the maximum size limit, Rhai also recursively adds up the sizes of all strings, arrays
and object maps contained within each array to make sure that the _aggregate_ sizes of none of these data structures
exceed their respective maximum size limits (if any).
### Maximum size of object maps
Rhai by default does not limit how large (i.e. the number of properties) an [object map] can be.
This can be changed via the `Engine::set_max_map_size` method, with zero being unlimited (the default).
```rust
let mut engine = Engine::new();
engine.set_max_map_size(500); // allow object maps with only up to 500 properties
engine.set_max_map_size(0); // allow unlimited object maps
```
A script attempting to create an object map literal with more properties than the maximum will terminate with a parse error.
Any script operation that produces an object map with more properties than the maximum also terminates the script with an error result.
This check can be disabled via the [`unchecked`] feature for higher performance
(but higher risks as well).
Be conservative when setting a maximum limit and always consider the fact that a registered function may grow
an object map's size without Rhai noticing until the very end. For instance, the built-in '`+`' operator for object maps
concatenates two object maps together to form one larger object map; if both object maps are _slightly_ below the maximum
size limit, the resultant object map may be almost _twice_ the maximum size.
As a malicious script may create a deeply-nested object map which consumes huge amounts of memory while each individual
object map still stays under the maximum size limit, Rhai also recursively adds up the sizes of all strings, arrays
and object maps contained within each object map to make sure that the _aggregate_ sizes of none of these data structures
exceed their respective maximum size limits (if any).
### Maximum number of operations ### Maximum number of operations
Rhai by default does not limit how much time or CPU a script consumes. Rhai by default does not limit how much time or CPU a script consumes.
@ -2422,46 +2618,57 @@ A good rule-of-thumb is that one simple non-trivial expression consumes on avera
One _operation_ can take an unspecified amount of time and real CPU cycles, depending on the particulars. One _operation_ can take an unspecified amount of time and real CPU cycles, depending on the particulars.
For example, loading a constant consumes very few CPU cycles, while calling an external Rust function, For example, loading a constant consumes very few CPU cycles, while calling an external Rust function,
though also counted as only one operation, may consume much more computing resources. though also counted as only one operation, may consume much more computing resources.
If it helps to visualize, think of an _operation_ as roughly equals to one _instruction_ of a hypothetical CPU. To help visualize, think of an _operation_ as roughly equals to one _instruction_ of a hypothetical CPU
which includes _specialized_ instructions, such as _function call_, _load module_ etc., each taking up
one CPU cycle to execute.
The _operation count_ is intended to be a very course-grained measurement of the amount of CPU that a script The _operations count_ is intended to be a very course-grained measurement of the amount of CPU that a script
is consuming, and allows the system to impose a hard upper limit. has consumed, allowing the system to impose a hard upper limit on computing resources.
A script exceeding the maximum operations count will terminate with an error result. A script exceeding the maximum operations count terminates with an error result.
This check can be disabled via the [`unchecked`] feature for higher performance This can be disabled via the [`unchecked`] feature for higher performance (but higher risks as well).
(but higher risks as well).
### Tracking progress ### Tracking progress and force-terminate script run
To track script evaluation progress and to force-terminate a script prematurely (for any reason), It is impossible to know when, or even whether, a script run will end
provide a closure to the `Engine::on_progress` method: (a.k.a. the [Halting Problem](http://en.wikipedia.org/wiki/Halting_problem)).
When dealing with third-party untrusted scripts that may be malicious, to track evaluation progress and
to force-terminate a script prematurely (for any reason), provide a closure to the `Engine::on_progress` method:
```rust ```rust
let mut engine = Engine::new(); let mut engine = Engine::new();
engine.on_progress(|count| { // 'count' is the number of operations performed engine.on_progress(|&count| { // parameter is '&u64' - number of operations already performed
if count % 1000 == 0 { if count % 1000 == 0 {
println!("{}", count); // print out a progress log every 1,000 operations println!("{}", count); // print out a progress log every 1,000 operations
} }
true // return 'true' to continue the script true // return 'true' to continue running the script
// returning 'false' will terminate the script // return 'false' to immediately terminate the script
}); });
``` ```
The closure passed to `Engine::on_progress` will be called once every operation. The closure passed to `Engine::on_progress` will be called once for every operation.
Return `false` to terminate the script immediately. Return `false` to terminate the script immediately.
Notice that the _operations count_ value passed into the closure does not indicate the _percentage_ of work
already done by the script (and thus it is not real _progress_ tracking), because it is impossible to determine
how long a script may run. It is possible, however, to calculate this percentage based on an estimated
total number of operations for a typical run.
### Maximum number of modules ### Maximum number of modules
Rhai by default does not limit how many [modules] are loaded via the [`import`] statement. Rhai by default does not limit how many [modules] can be loaded via [`import`] statements.
This can be changed via the `Engine::set_max_modules` method, with zero being unlimited (the default). This can be changed via the `Engine::set_max_modules` method. Notice that setting the maximum number
of modules to zero does _not_ indicate unlimited modules, but disallows loading any module altogether.
```rust ```rust
let mut engine = Engine::new(); let mut engine = Engine::new();
engine.set_max_modules(5); // allow loading only up to 5 modules engine.set_max_modules(5); // allow loading only up to 5 modules
engine.set_max_modules(0); // allow unlimited modules engine.set_max_modules(0); // disallow loading any module (maximum = zero)
engine.set_max_modules(1000); // set to a large number for effectively unlimited modules
``` ```
A script attempting to load more than the maximum number of modules will terminate with an error result. A script attempting to load more than the maximum number of modules will terminate with an error result.
@ -2474,7 +2681,7 @@ Rhai by default limits function calls to a maximum depth of 128 levels (16 level
This limit may be changed via the `Engine::set_max_call_levels` method. This limit may be changed via the `Engine::set_max_call_levels` method.
When setting this limit, care must be also taken to the evaluation depth of each _statement_ When setting this limit, care must be also taken to the evaluation depth of each _statement_
within the function. It is entirely possible for a malicous script to embed an recursive call deep within the function. It is entirely possible for a malicous script to embed a recursive call deep
inside a nested expression or statement block (see [maximum statement depth](#maximum-statement-depth)). inside a nested expression or statement block (see [maximum statement depth](#maximum-statement-depth)).
The limit can be disabled via the [`unchecked`] feature for higher performance The limit can be disabled via the [`unchecked`] feature for higher performance
@ -2573,7 +2780,7 @@ For example, in the following:
```rust ```rust
{ {
let x = 999; // NOT eliminated: Rhai doesn't check yet whether a variable is used later on let x = 999; // NOT eliminated: variable may be used later on (perhaps even an 'eval')
123; // eliminated: no effect 123; // eliminated: no effect
"hello"; // eliminated: no effect "hello"; // eliminated: no effect
[1, 2, x, x*2, 5]; // eliminated: no effect [1, 2, x, x*2, 5]; // eliminated: no effect

View File

@ -1,9 +1,40 @@
Rhai Release Notes Rhai Release Notes
================== ==================
Version 0.14.2 Version 0.15.1
============== ==============
This is a minor release which enables updating indexers (via registered indexer setters) and supports functions
with `&str` parameters (maps transparently to `ImmutableString`). WASM is also a tested target.
Buf fix
-------
* `let s="abc"; s[1].change_to('X');` now correctly sets the character '`X`' into '`s`' yielding `"aXc"`.
Breaking changes
----------------
* Callback closure passed to `Engine::on_progress` now takes `&u64` instead of `u64` to be consistent with other callback signatures.
* `Engine::register_indexer` is renamed to `Engine::register_indexer_get`.
* `Module::set_indexer_fn` is renamed to `Module::set_indexer_get_fn`.
* The tuple `ParseError` now exposes the internal fields and the `ParseError::error_type` and `ParseError::position` methods are removed. The first tuple field is the `ParseErrorType` and the second tuple field is the `Position`.
* `Engine::call_fn_dynamic` now takes any type that implements `IntoIterator<Item = Dynamic>`.
New features
------------
* Indexers are now split into getters and setters (which now support updates). The API is split into `Engine::register_indexer_get` and `Engine::register_indexer_set` with `Engine::register_indexer_get_set` being a shorthand. Similarly, `Module::set_indexer_get_fn` and `Module::set_indexer_set_fn` are added.
* `Engine:register_fn` and `Engine:register_result_fn` accepts functions that take parameters of type `&str` (immutable string slice), which maps directly to `ImmutableString`. This is to avoid needing wrappers for functions taking string parameters.
* Set maximum limit on data sizes: `Engine::set_max_string_size`, `Engine::set_max_array_size` and `Engine::set_max_map_size`.
* Supports trailing commas on array literals, object map literals, function definitions and function calls.
* Enhances support for compiling to WASM.
Version 0.15.0
==============
This version uses immutable strings (`ImmutableString` type) and built-in operator functions (e.g. `+`, `>`, `+=`) to improve speed, plus some bug fixes.
Regression fix Regression fix
-------------- --------------
@ -14,26 +45,18 @@ Bug fixes
* Indexing with an index or dot expression now works property (it compiled wrongly before). * Indexing with an index or dot expression now works property (it compiled wrongly before).
For example, `let s = "hello"; s[s.len-1] = 'x';` now works property instead of causing a runtime error. For example, `let s = "hello"; s[s.len-1] = 'x';` now works property instead of causing a runtime error.
* `if` expressions are not supposed to be allowed when compiling for expressions only. This is fixed.
Breaking changes Breaking changes
---------------- ----------------
* `Engine::compile_XXX` functions now return `ParseError` instead of `Box<ParseError>`. * `Engine::compile_XXX` functions now return `ParseError` instead of `Box<ParseError>`.
* The `RegisterDynamicFn` trait is merged into the `RegisterResultFn` trait which now always returns * The `RegisterDynamicFn` trait is merged into the `RegisterResultFn` trait which now always returns `Result<Dynamic, Box<EvalAltResult>>`.
`Result<Dynamic, Box<EvalAltResult>>`.
* Default maximum limit on levels of nested function calls is fine-tuned and set to a different value. * Default maximum limit on levels of nested function calls is fine-tuned and set to a different value.
* Some operator functions are now built in (see _Speed enhancements_ below), so they are available even * Some operator functions are now built in (see _Speed enhancements_ below), so they are available even under `Engine::new_raw`.
under `Engine::new_raw`. * Strings are now immutable. The type `rhai::ImmutableString` is used instead of `std::string::String`. This is to avoid excessive cloning of strings. All native-Rust functions taking string parameters should switch to `rhai::ImmutableString` (which is either `Rc<String>` or `Arc<String>` depending on whether the `sync` feature is used).
* Strings are now immutable. The type `rhai::ImmutableString` is used instead of `std::string::String`. * Native Rust functions registered with the `Engine` also mutates the first argument when called in normal function-call style (previously the first argument will be passed by _value_ if not called in method-call style). Of course, if the first argument is a calculated value (e.g. result of an expression), then mutating it has no effect, but at least it is not cloned.
This is to avoid excessive cloning of strings. All native-Rust functions taking string parameters * Some built-in methods (e.g. `len` for string, `floor` for `FLOAT`) now have _property_ versions in addition to methods to simplify coding.
should switch to `rhai::ImmutableString` (which is either `Rc<String>` or `Arc<String>` depending on
whether the `sync` feature is used).
* Native Rust functions registered with the `Engine` also mutates the first argument when called in
normal function-call style (previously the first argument will be passed by _value_ if not called
in method-call style). Of course, if the first argument is a calculated value (e.g. result of an
expression), then mutating it has no effect, but at least it is not cloned.
* Some built-in methods (e.g. `len` for string, `floor` for `FLOAT`) now have _property_ versions in
addition to methods to simplify coding.
New features New features
------------ ------------
@ -46,23 +69,13 @@ New features
Speed enhancements Speed enhancements
------------------ ------------------
* Common operators (e.g. `+`, `>`, `==`) now call into highly efficient built-in implementations for standard types * Common operators (e.g. `+`, `>`, `==`) now call into highly efficient built-in implementations for standard types (i.e. `INT`, `FLOAT`, `bool`, `char`, `()` and `ImmutableString`) if not overridden by a registered function. This yields a 5-10% speed benefit depending on script operator usage. Scripts running tight loops will see significant speed-up.
(i.e. `INT`, `FLOAT`, `bool`, `char`, `()` and `ImmutableString`) if not overridden by a registered function. * Common assignment operators (e.g. `+=`, `%=`) now call into highly efficient built-in implementations for standard types (i.e. `INT`, `FLOAT`, `bool`, `char`, `()` and `ImmutableString`) if not overridden by a registered function.
This yields a 5-10% speed benefit depending on script operator usage. Scripts running tight loops will see * Implementations of common operators for standard types are removed from the `ArithmeticPackage` and `LogicPackage` (and therefore the `CorePackage`) because they are now always available, even under `Engine::new_raw`.
significant speed-up.
* Common assignment operators (e.g. `+=`, `%=`) now call into highly efficient built-in implementations for
standard types (i.e. `INT`, `FLOAT`, `bool`, `char`, `()` and `ImmutableString`) if not overridden by a registered function.
* Implementations of common operators for standard types are removed from the `ArithmeticPackage` and `LogicPackage`
(and therefore the `CorePackage`) because they are now always available, even under `Engine::new_raw`.
* Operator-assignment statements (e.g. `+=`) are now handled directly and much faster. * Operator-assignment statements (e.g. `+=`) are now handled directly and much faster.
* Strings are now _immutable_ and use the `rhai::ImmutableString` type, eliminating large amounts of cloning. * Strings are now _immutable_ and use the `rhai::ImmutableString` type, eliminating large amounts of cloning.
* For Native Rust functions taking a first `&mut` parameter, the first argument is passed by reference instead of * For Native Rust functions taking a first `&mut` parameter, the first argument is passed by reference instead of by value, even if not called in method-call style. This allows many functions declared with `&mut` parameter to avoid excessive cloning. For example, if `a` is a large array, getting its length in this manner: `len(a)` used to result in a full clone of `a` before taking the length and throwing the copy away. Now, `a` is simply passed by reference, avoiding the cloning altogether.
by value, even if not called in method-call style. This allows many functions declared with `&mut` parameter to avoid * A custom hasher simply passes through `u64` keys without hashing to avoid function call hash keys (which are by themselves `u64`) being hashed twice.
excessive cloning. For example, if `a` is a large array, getting its length in this manner: `len(a)` used to result
in a full clone of `a` before taking the length and throwing the copy away. Now, `a` is simply passed by reference,
avoiding the cloning altogether.
* A custom hasher simply passes through `u64` keys without hashing to avoid function call hash keys
(which are by themselves `u64`) being hashed twice.
Version 0.14.1 Version 0.14.1
@ -74,15 +87,13 @@ The major features for this release is modules, script resource limits, and spee
New features New features
------------ ------------
* Modules and _module resolvers_ allow loading external scripts under a module namespace. * Modules and _module resolvers_ allow loading external scripts under a module namespace. A module can contain constant variables, Rust functions and Rhai functions.
A module can contain constant variables, Rust functions and Rhai functions.
* `export` variables and `private` functions. * `export` variables and `private` functions.
* _Indexers_ for Rust types. * _Indexers_ for Rust types.
* Track script evaluation progress and terminate script run. * Track script evaluation progress and terminate script run.
* Set limit on maximum number of operations allowed per script run. * Set limit on maximum number of operations allowed per script run.
* Set limit on maximum number of modules loaded per script run. * Set limit on maximum number of modules loaded per script run.
* A new API, `Engine::compile_scripts_with_scope`, can compile a list of script segments without needing to * A new API, `Engine::compile_scripts_with_scope`, can compile a list of script segments without needing to first concatenate them together into one large string.
first concatenate them together into one large string.
* Stepped `range` function with a custom step. * Stepped `range` function with a custom step.
Speed improvements Speed improvements

View File

@ -1 +0,0 @@
theme: jekyll-theme-slate

View File

@ -7,28 +7,27 @@ use std::io::{stdin, stdout, Write};
fn print_error(input: &str, err: EvalAltResult) { fn print_error(input: &str, err: EvalAltResult) {
let lines: Vec<_> = input.trim().split('\n').collect(); let lines: Vec<_> = input.trim().split('\n').collect();
let pos = err.position();
let line_no = if lines.len() > 1 { let line_no = if lines.len() > 1 {
match err.position() { if pos.is_none() {
p if p.is_none() => "".to_string(), "".to_string()
p => format!("{}: ", p.line().unwrap()), } else {
format!("{}: ", pos.line().unwrap())
} }
} else { } else {
"".to_string() "".to_string()
}; };
// Print error // Print error
let pos = err.position();
let pos_text = format!(" ({})", pos); let pos_text = format!(" ({})", pos);
match pos { if pos.is_none() {
p if p.is_none() => {
// No position // No position
println!("{}", err); println!("{}", err);
} } else {
p => {
// Specific position // Specific position
println!("{}{}", line_no, lines[p.line().unwrap() - 1]); println!("{}{}", line_no, lines[pos.line().unwrap() - 1]);
let err_text = match err { let err_text = match err {
EvalAltResult::ErrorRuntime(err, _) if !err.is_empty() => { EvalAltResult::ErrorRuntime(err, _) if !err.is_empty() => {
@ -40,12 +39,11 @@ fn print_error(input: &str, err: EvalAltResult) {
println!( println!(
"{0:>1$} {2}", "{0:>1$} {2}",
"^", "^",
line_no.len() + p.position().unwrap(), line_no.len() + pos.position().unwrap(),
err_text.replace(&pos_text, "") err_text.replace(&pos_text, "")
); );
} }
} }
}
fn print_help() { fn print_help() {
println!("help => print this help"); println!("help => print this help");
@ -127,7 +125,7 @@ fn main() {
match engine match engine
.compile_with_scope(&scope, &script) .compile_with_scope(&scope, &script)
.map_err(|err| err.into()) .map_err(Into::into)
.and_then(|r| { .and_then(|r| {
ast_u = r.clone(); ast_u = r.clone();

View File

@ -1,4 +1,4 @@
use rhai::{Engine, EvalAltResult}; use rhai::{Engine, EvalAltResult, Position};
#[cfg(not(feature = "no_optimize"))] #[cfg(not(feature = "no_optimize"))]
use rhai::OptimizationLevel; use rhai::OptimizationLevel;
@ -6,15 +6,17 @@ use rhai::OptimizationLevel;
use std::{env, fs::File, io::Read, process::exit}; use std::{env, fs::File, io::Read, process::exit};
fn eprint_error(input: &str, err: EvalAltResult) { fn eprint_error(input: &str, err: EvalAltResult) {
fn eprint_line(lines: &[&str], line: usize, pos: usize, err: &str) { fn eprint_line(lines: &[&str], pos: Position, err: &str) {
let line = pos.line().unwrap();
let line_no = format!("{}: ", line); let line_no = format!("{}: ", line);
let pos_text = format!(" (line {}, position {})", line, pos); let pos_text = format!(" ({})", pos);
eprintln!("{}{}", line_no, lines[line - 1]); eprintln!("{}{}", line_no, lines[line - 1]);
eprintln!( eprintln!(
"{:>1$} {2}", "{:>1$} {2}",
"^", "^",
line_no.len() + pos, line_no.len() + pos.position().unwrap(),
err.replace(&pos_text, "") err.replace(&pos_text, "")
); );
eprintln!(""); eprintln!("");
@ -25,12 +27,10 @@ fn eprint_error(input: &str, err: EvalAltResult) {
// Print error // Print error
let pos = err.position(); let pos = err.position();
match pos { if pos.is_none() {
p if p.is_none() => {
// No position // No position
eprintln!("{}", err); eprintln!("{}", err);
} } else {
p => {
// Specific position // Specific position
let err_text = match err { let err_text = match err {
EvalAltResult::ErrorRuntime(err, _) if !err.is_empty() => { EvalAltResult::ErrorRuntime(err, _) if !err.is_empty() => {
@ -39,8 +39,7 @@ fn eprint_error(input: &str, err: EvalAltResult) {
err => err.to_string(), err => err.to_string(),
}; };
eprint_line(&lines, p.line().unwrap(), p.position().unwrap(), &err_text) eprint_line(&lines, pos, &err_text)
}
} }
} }

77
examples/strings.rs Normal file
View File

@ -0,0 +1,77 @@
///! This example registers a variety of functions that operate on strings.
///! Remember to use `ImmutableString` or `&str` instead of `String` as parameters.
use rhai::{Engine, EvalAltResult, ImmutableString, RegisterFn, Scope, INT};
use std::io::{stdin, stdout, Write};
/// Trim whitespace from a string. The original string argument is changed.
///
/// This version uses `&mut ImmutableString`
fn trim_string(s: &mut ImmutableString) {
*s = s.trim().into();
}
/// Notice this is different from the built-in Rhai 'len' function for strings
/// which counts the actual number of Unicode _characters_ in a string.
/// This version simply counts the number of _bytes_ in the UTF-8 representation.
///
/// This version uses `&str`.
fn count_string_bytes(s: &str) -> INT {
s.len() as INT
}
/// This version uses `ImmutableString` and `&str`.
fn find_substring(s: ImmutableString, sub: &str) -> INT {
s.as_str().find(sub).map(|x| x as INT).unwrap_or(-1)
}
fn main() -> Result<(), Box<EvalAltResult>> {
// Create a `raw` Engine with no built-in string functions.
let mut engine = Engine::new_raw();
// Register string functions
engine.register_fn("trim", trim_string);
engine.register_fn("len", count_string_bytes);
engine.register_fn("index_of", find_substring);
// Register string functions using closures
engine.register_fn("display", |label: &str, x: INT| {
println!("{}: {}", label, x)
});
engine.register_fn("display", |label: ImmutableString, x: &str| {
println!(r#"{}: "{}""#, label, x) // Quote the input string
});
let mut scope = Scope::new();
let mut input = String::new();
loop {
scope.clear();
println!("Type something. Press Ctrl-C to exit.");
print!("strings> ");
stdout().flush().expect("couldn't flush stdout");
input.clear();
if let Err(err) = stdin().read_line(&mut input) {
panic!("input error: {}", err);
}
scope.push("x", input.clone());
println!("Line: {}", input.replace('\r', "\\r").replace('\n', "\\n"));
engine.consume_with_scope(
&mut scope,
r#"
display("Length", x.len());
x.trim();
display("Trimmed", x);
display("Trimmed Length", x.len());
display("Index of \"!!!\"", x.index_of("!!!"));
"#,
)?;
println!();
}
}

View File

@ -1,11 +1,10 @@
//! Helper module which defines the `Any` trait to to allow dynamic value handling. //! Helper module which defines the `Any` trait to to allow dynamic value handling.
use crate::fn_native::SendSync;
use crate::module::Module;
use crate::parser::{ImmutableString, INT}; use crate::parser::{ImmutableString, INT};
use crate::r#unsafe::{unsafe_cast_box, unsafe_try_cast}; use crate::r#unsafe::{unsafe_cast_box, unsafe_try_cast};
#[cfg(not(feature = "no_module"))]
use crate::module::Module;
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
use crate::parser::FLOAT; use crate::parser::FLOAT;
@ -25,8 +24,13 @@ use crate::stdlib::{
}; };
#[cfg(not(feature = "no_std"))] #[cfg(not(feature = "no_std"))]
#[cfg(not(target_arch = "wasm32"))]
use crate::stdlib::time::Instant; use crate::stdlib::time::Instant;
#[cfg(not(feature = "no_std"))]
#[cfg(target_arch = "wasm32")]
use instant::Instant;
/// Trait to represent any type. /// Trait to represent any type.
/// ///
/// Currently, `Variant` is not `Send` nor `Sync`, so it can practically be any type. /// Currently, `Variant` is not `Send` nor `Sync`, so it can practically be any type.
@ -56,31 +60,6 @@ pub trait Variant: Any {
fn _closed(&self) -> _Private; fn _closed(&self) -> _Private;
} }
#[cfg(not(feature = "sync"))]
impl<T: Any + Clone> Variant for T {
fn as_any(&self) -> &dyn Any {
self as &dyn Any
}
fn as_mut_any(&mut self) -> &mut dyn Any {
self as &mut dyn Any
}
fn as_box_any(self: Box<Self>) -> Box<dyn Any> {
self as Box<dyn Any>
}
fn type_name(&self) -> &'static str {
type_name::<T>()
}
fn into_dynamic(self) -> Dynamic {
Dynamic::from(self)
}
fn clone_into_dynamic(&self) -> Dynamic {
Dynamic::from(self.clone())
}
fn _closed(&self) -> _Private {
_Private
}
}
/// Trait to represent any type. /// Trait to represent any type.
/// ///
/// `From<_>` is implemented for `i64` (`i32` if `only_i32`), `f64` (if not `no_float`), /// `From<_>` is implemented for `i64` (`i32` if `only_i32`), `f64` (if not `no_float`),
@ -110,8 +89,7 @@ pub trait Variant: Any + Send + Sync {
fn _closed(&self) -> _Private; fn _closed(&self) -> _Private;
} }
#[cfg(feature = "sync")] impl<T: Any + Clone + SendSync> Variant for T {
impl<T: Any + Clone + Send + Sync> Variant for T {
fn as_any(&self) -> &dyn Any { fn as_any(&self) -> &dyn Any {
self as &dyn Any self as &dyn Any
} }
@ -160,7 +138,6 @@ pub enum Union {
Array(Box<Array>), Array(Box<Array>),
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
Map(Box<Map>), Map(Box<Map>),
#[cfg(not(feature = "no_module"))]
Module(Box<Module>), Module(Box<Module>),
Variant(Box<Box<dyn Variant>>), Variant(Box<Box<dyn Variant>>),
} }
@ -198,7 +175,6 @@ impl Dynamic {
Union::Array(_) => TypeId::of::<Array>(), Union::Array(_) => TypeId::of::<Array>(),
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
Union::Map(_) => TypeId::of::<Map>(), Union::Map(_) => TypeId::of::<Map>(),
#[cfg(not(feature = "no_module"))]
Union::Module(_) => TypeId::of::<Module>(), Union::Module(_) => TypeId::of::<Module>(),
Union::Variant(value) => (***value).type_id(), Union::Variant(value) => (***value).type_id(),
} }
@ -218,10 +194,10 @@ impl Dynamic {
Union::Array(_) => "array", Union::Array(_) => "array",
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
Union::Map(_) => "map", Union::Map(_) => "map",
#[cfg(not(feature = "no_module"))]
Union::Module(_) => "sub-scope", Union::Module(_) => "sub-scope",
#[cfg(not(feature = "no_std"))] #[cfg(not(feature = "no_std"))]
#[cfg(not(target_arch = "wasm32"))]
Union::Variant(value) if value.is::<Instant>() => "timestamp", Union::Variant(value) if value.is::<Instant>() => "timestamp",
Union::Variant(value) => (***value).type_name(), Union::Variant(value) => (***value).type_name(),
} }
@ -232,20 +208,20 @@ impl fmt::Display for Dynamic {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match &self.0 { match &self.0 {
Union::Unit(_) => write!(f, ""), Union::Unit(_) => write!(f, ""),
Union::Bool(value) => write!(f, "{}", value), Union::Bool(value) => fmt::Display::fmt(value, f),
Union::Str(value) => write!(f, "{}", value), Union::Str(value) => fmt::Display::fmt(value, f),
Union::Char(value) => write!(f, "{}", value), Union::Char(value) => fmt::Display::fmt(value, f),
Union::Int(value) => write!(f, "{}", value), Union::Int(value) => fmt::Display::fmt(value, f),
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
Union::Float(value) => write!(f, "{}", value), Union::Float(value) => fmt::Display::fmt(value, f),
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
Union::Array(value) => write!(f, "{:?}", value), Union::Array(value) => fmt::Debug::fmt(value, f),
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
Union::Map(value) => write!(f, "#{:?}", value), Union::Map(value) => write!(f, "#{:?}", value),
#[cfg(not(feature = "no_module"))] Union::Module(value) => fmt::Debug::fmt(value, f),
Union::Module(value) => write!(f, "{:?}", value),
#[cfg(not(feature = "no_std"))] #[cfg(not(feature = "no_std"))]
#[cfg(not(target_arch = "wasm32"))]
Union::Variant(value) if value.is::<Instant>() => write!(f, "<timestamp>"), Union::Variant(value) if value.is::<Instant>() => write!(f, "<timestamp>"),
Union::Variant(_) => write!(f, "?"), Union::Variant(_) => write!(f, "?"),
} }
@ -255,21 +231,21 @@ impl fmt::Display for Dynamic {
impl fmt::Debug for Dynamic { impl fmt::Debug for Dynamic {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match &self.0 { match &self.0 {
Union::Unit(value) => write!(f, "{:?}", value), Union::Unit(value) => fmt::Debug::fmt(value, f),
Union::Bool(value) => write!(f, "{:?}", value), Union::Bool(value) => fmt::Debug::fmt(value, f),
Union::Str(value) => write!(f, "{:?}", value), Union::Str(value) => fmt::Debug::fmt(value, f),
Union::Char(value) => write!(f, "{:?}", value), Union::Char(value) => fmt::Debug::fmt(value, f),
Union::Int(value) => write!(f, "{:?}", value), Union::Int(value) => fmt::Debug::fmt(value, f),
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
Union::Float(value) => write!(f, "{:?}", value), Union::Float(value) => fmt::Debug::fmt(value, f),
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
Union::Array(value) => write!(f, "{:?}", value), Union::Array(value) => fmt::Debug::fmt(value, f),
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
Union::Map(value) => write!(f, "#{:?}", value), Union::Map(value) => write!(f, "#{:?}", value),
#[cfg(not(feature = "no_module"))] Union::Module(value) => fmt::Debug::fmt(value, f),
Union::Module(value) => write!(f, "{:?}", value),
#[cfg(not(feature = "no_std"))] #[cfg(not(feature = "no_std"))]
#[cfg(not(target_arch = "wasm32"))]
Union::Variant(value) if value.is::<Instant>() => write!(f, "<timestamp>"), Union::Variant(value) if value.is::<Instant>() => write!(f, "<timestamp>"),
Union::Variant(_) => write!(f, "<dynamic>"), Union::Variant(_) => write!(f, "<dynamic>"),
} }
@ -290,7 +266,6 @@ impl Clone for Dynamic {
Union::Array(ref value) => Self(Union::Array(value.clone())), Union::Array(ref value) => Self(Union::Array(value.clone())),
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
Union::Map(ref value) => Self(Union::Map(value.clone())), Union::Map(ref value) => Self(Union::Map(value.clone())),
#[cfg(not(feature = "no_module"))]
Union::Module(ref value) => Self(Union::Module(value.clone())), Union::Module(ref value) => Self(Union::Module(value.clone())),
Union::Variant(ref value) => (***value).clone_into_dynamic(), Union::Variant(ref value) => (***value).clone_into_dynamic(),
} }
@ -426,7 +401,6 @@ impl Dynamic {
Union::Array(value) => unsafe_cast_box::<_, T>(value).ok().map(|v| *v), Union::Array(value) => unsafe_cast_box::<_, T>(value).ok().map(|v| *v),
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
Union::Map(value) => unsafe_cast_box::<_, T>(value).ok().map(|v| *v), Union::Map(value) => unsafe_cast_box::<_, T>(value).ok().map(|v| *v),
#[cfg(not(feature = "no_module"))]
Union::Module(value) => unsafe_cast_box::<_, T>(value).ok().map(|v| *v), Union::Module(value) => unsafe_cast_box::<_, T>(value).ok().map(|v| *v),
Union::Variant(value) => (*value).as_box_any().downcast().map(|x| *x).ok(), Union::Variant(value) => (*value).as_box_any().downcast().map(|x| *x).ok(),
} }
@ -470,7 +444,6 @@ impl Dynamic {
Union::Array(value) => *unsafe_cast_box::<_, T>(value).unwrap(), Union::Array(value) => *unsafe_cast_box::<_, T>(value).unwrap(),
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
Union::Map(value) => *unsafe_cast_box::<_, T>(value).unwrap(), Union::Map(value) => *unsafe_cast_box::<_, T>(value).unwrap(),
#[cfg(not(feature = "no_module"))]
Union::Module(value) => *unsafe_cast_box::<_, T>(value).unwrap(), Union::Module(value) => *unsafe_cast_box::<_, T>(value).unwrap(),
Union::Variant(value) => (*value).as_box_any().downcast().map(|x| *x).unwrap(), Union::Variant(value) => (*value).as_box_any().downcast().map(|x| *x).unwrap(),
} }
@ -498,7 +471,6 @@ impl Dynamic {
Union::Array(value) => (value.as_ref() as &dyn Any).downcast_ref::<T>(), Union::Array(value) => (value.as_ref() as &dyn Any).downcast_ref::<T>(),
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
Union::Map(value) => (value.as_ref() as &dyn Any).downcast_ref::<T>(), Union::Map(value) => (value.as_ref() as &dyn Any).downcast_ref::<T>(),
#[cfg(not(feature = "no_module"))]
Union::Module(value) => (value.as_ref() as &dyn Any).downcast_ref::<T>(), Union::Module(value) => (value.as_ref() as &dyn Any).downcast_ref::<T>(),
Union::Variant(value) => value.as_ref().as_ref().as_any().downcast_ref::<T>(), Union::Variant(value) => value.as_ref().as_ref().as_any().downcast_ref::<T>(),
} }
@ -524,7 +496,6 @@ impl Dynamic {
Union::Array(value) => (value.as_mut() as &mut dyn Any).downcast_mut::<T>(), Union::Array(value) => (value.as_mut() as &mut dyn Any).downcast_mut::<T>(),
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
Union::Map(value) => (value.as_mut() as &mut dyn Any).downcast_mut::<T>(), Union::Map(value) => (value.as_mut() as &mut dyn Any).downcast_mut::<T>(),
#[cfg(not(feature = "no_module"))]
Union::Module(value) => (value.as_mut() as &mut dyn Any).downcast_mut::<T>(), Union::Module(value) => (value.as_mut() as &mut dyn Any).downcast_mut::<T>(),
Union::Variant(value) => value.as_mut().as_mut_any().downcast_mut::<T>(), Union::Variant(value) => value.as_mut().as_mut_any().downcast_mut::<T>(),
} }

View File

@ -1,13 +1,16 @@
//! Module that defines the extern API of `Engine`. //! Module that defines the extern API of `Engine`.
use crate::any::{Dynamic, Variant}; use crate::any::{Dynamic, Variant};
use crate::engine::{make_getter, make_setter, Engine, State, FUNC_INDEXER}; use crate::engine::{
get_script_function_by_signature, make_getter, make_setter, Engine, State, FUNC_INDEXER_GET,
FUNC_INDEXER_SET,
};
use crate::error::ParseError; use crate::error::ParseError;
use crate::fn_call::FuncArgs; use crate::fn_call::FuncArgs;
use crate::fn_native::{IteratorFn, SendSync}; use crate::fn_native::{IteratorFn, SendSync};
use crate::fn_register::RegisterFn; use crate::fn_register::RegisterFn;
use crate::optimize::{optimize_into_ast, OptimizationLevel}; use crate::optimize::{optimize_into_ast, OptimizationLevel};
use crate::parser::{parse, parse_global_expr, AST}; use crate::parser::AST;
use crate::result::EvalAltResult; use crate::result::EvalAltResult;
use crate::scope::Scope; use crate::scope::Scope;
use crate::token::{lex, Position}; use crate::token::{lex, Position};
@ -24,6 +27,7 @@ use crate::stdlib::{
}; };
#[cfg(not(feature = "no_std"))] #[cfg(not(feature = "no_std"))]
#[cfg(not(target_arch = "wasm32"))]
use crate::stdlib::{fs::File, io::prelude::*, path::PathBuf}; use crate::stdlib::{fs::File, io::prelude::*, path::PathBuf};
/// Engine public API /// Engine public API
@ -273,7 +277,7 @@ impl Engine {
self.register_set(name, set_fn); self.register_set(name, set_fn);
} }
/// Register an indexer function for a registered type with the `Engine`. /// Register an index getter for a registered type with the `Engine`.
/// ///
/// The function signature must start with `&mut self` and not `&self`. /// The function signature must start with `&mut self` and not `&self`.
/// ///
@ -303,7 +307,7 @@ impl Engine {
/// engine.register_fn("new_ts", TestStruct::new); /// engine.register_fn("new_ts", TestStruct::new);
/// ///
/// // Register an indexer. /// // Register an indexer.
/// engine.register_indexer(TestStruct::get_field); /// engine.register_indexer_get(TestStruct::get_field);
/// ///
/// assert_eq!(engine.eval::<i64>("let a = new_ts(); a[2]")?, 3); /// assert_eq!(engine.eval::<i64>("let a = new_ts(); a[2]")?, 3);
/// # Ok(()) /// # Ok(())
@ -311,7 +315,7 @@ impl Engine {
/// ``` /// ```
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
pub fn register_indexer<T, X, U>( pub fn register_indexer_get<T, X, U>(
&mut self, &mut self,
callback: impl Fn(&mut T, X) -> U + SendSync + 'static, callback: impl Fn(&mut T, X) -> U + SendSync + 'static,
) where ) where
@ -319,7 +323,103 @@ impl Engine {
U: Variant + Clone, U: Variant + Clone,
X: Variant + Clone, X: Variant + Clone,
{ {
self.register_fn(FUNC_INDEXER, callback); self.register_fn(FUNC_INDEXER_GET, callback);
}
/// Register an index setter for a registered type with the `Engine`.
///
/// # Example
///
/// ```
/// #[derive(Clone)]
/// struct TestStruct {
/// fields: Vec<i64>
/// }
///
/// impl TestStruct {
/// fn new() -> Self { TestStruct { fields: vec![1, 2, 3, 4, 5] } }
/// fn set_field(&mut self, index: i64, value: i64) { self.fields[index as usize] = value; }
/// }
///
/// # fn main() -> Result<(), Box<rhai::EvalAltResult>> {
/// use rhai::{Engine, RegisterFn};
///
/// let mut engine = Engine::new();
///
/// // Register the custom type.
/// engine.register_type::<TestStruct>();
///
/// engine.register_fn("new_ts", TestStruct::new);
///
/// // Register an indexer.
/// engine.register_indexer_set(TestStruct::set_field);
///
/// assert_eq!(
/// engine.eval::<TestStruct>("let a = new_ts(); a[2] = 42; a")?.fields[2],
/// 42
/// );
/// # Ok(())
/// # }
/// ```
#[cfg(not(feature = "no_object"))]
#[cfg(not(feature = "no_index"))]
pub fn register_indexer_set<T, X, U>(
&mut self,
callback: impl Fn(&mut T, X, U) -> () + SendSync + 'static,
) where
T: Variant + Clone,
U: Variant + Clone,
X: Variant + Clone,
{
self.register_fn(FUNC_INDEXER_SET, callback);
}
/// Shorthand for register both index getter and setter functions for a registered type with the `Engine`.
///
/// # Example
///
/// ```
/// #[derive(Clone)]
/// struct TestStruct {
/// fields: Vec<i64>
/// }
///
/// impl TestStruct {
/// fn new() -> Self { TestStruct { fields: vec![1, 2, 3, 4, 5] } }
/// fn get_field(&mut self, index: i64) -> i64 { self.fields[index as usize] }
/// fn set_field(&mut self, index: i64, value: i64) { self.fields[index as usize] = value; }
/// }
///
/// # fn main() -> Result<(), Box<rhai::EvalAltResult>> {
/// use rhai::{Engine, RegisterFn};
///
/// let mut engine = Engine::new();
///
/// // Register the custom type.
/// engine.register_type::<TestStruct>();
///
/// engine.register_fn("new_ts", TestStruct::new);
///
/// // Register an indexer.
/// engine.register_indexer_get_set(TestStruct::get_field, TestStruct::set_field);
///
/// assert_eq!(engine.eval::<i64>("let a = new_ts(); a[2] = 42; a[2]")?, 42);
/// # Ok(())
/// # }
/// ```
#[cfg(not(feature = "no_object"))]
#[cfg(not(feature = "no_index"))]
pub fn register_indexer_get_set<T, X, U>(
&mut self,
getter: impl Fn(&mut T, X) -> U + SendSync + 'static,
setter: impl Fn(&mut T, X, U) -> () + SendSync + 'static,
) where
T: Variant + Clone,
U: Variant + Clone,
X: Variant + Clone,
{
self.register_indexer_get(getter);
self.register_indexer_set(setter);
} }
/// Compile a string into an `AST`, which can be used later for evaluation. /// Compile a string into an `AST`, which can be used later for evaluation.
@ -448,19 +548,13 @@ impl Engine {
scripts: &[&str], scripts: &[&str],
optimization_level: OptimizationLevel, optimization_level: OptimizationLevel,
) -> Result<AST, ParseError> { ) -> Result<AST, ParseError> {
let stream = lex(scripts); let stream = lex(scripts, self.max_string_size);
self.parse(&mut stream.peekable(), scope, optimization_level)
parse(
&mut stream.peekable(),
self,
scope,
optimization_level,
(self.max_expr_depth, self.max_function_expr_depth),
)
} }
/// Read the contents of a file into a string. /// Read the contents of a file into a string.
#[cfg(not(feature = "no_std"))] #[cfg(not(feature = "no_std"))]
#[cfg(not(target_arch = "wasm32"))]
fn read_file(path: PathBuf) -> Result<String, Box<EvalAltResult>> { fn read_file(path: PathBuf) -> Result<String, Box<EvalAltResult>> {
let mut f = File::open(path.clone()).map_err(|err| { let mut f = File::open(path.clone()).map_err(|err| {
Box::new(EvalAltResult::ErrorReadingScriptFile( Box::new(EvalAltResult::ErrorReadingScriptFile(
@ -504,6 +598,7 @@ impl Engine {
/// # } /// # }
/// ``` /// ```
#[cfg(not(feature = "no_std"))] #[cfg(not(feature = "no_std"))]
#[cfg(not(target_arch = "wasm32"))]
pub fn compile_file(&self, path: PathBuf) -> Result<AST, Box<EvalAltResult>> { pub fn compile_file(&self, path: PathBuf) -> Result<AST, Box<EvalAltResult>> {
self.compile_file_with_scope(&Scope::new(), path) self.compile_file_with_scope(&Scope::new(), path)
} }
@ -540,6 +635,7 @@ impl Engine {
/// # } /// # }
/// ``` /// ```
#[cfg(not(feature = "no_std"))] #[cfg(not(feature = "no_std"))]
#[cfg(not(target_arch = "wasm32"))]
pub fn compile_file_with_scope( pub fn compile_file_with_scope(
&self, &self,
scope: &Scope, scope: &Scope,
@ -577,14 +673,9 @@ impl Engine {
// Trims the JSON string and add a '#' in front // Trims the JSON string and add a '#' in front
let scripts = ["#", json.trim()]; let scripts = ["#", json.trim()];
let stream = lex(&scripts); let stream = lex(&scripts, self.max_string_size);
let ast = parse_global_expr( let ast =
&mut stream.peekable(), self.parse_global_expr(&mut stream.peekable(), &scope, OptimizationLevel::None)?;
self,
&scope,
OptimizationLevel::None,
self.max_expr_depth,
)?;
// Handle null - map to () // Handle null - map to ()
if has_null { if has_null {
@ -663,17 +754,10 @@ impl Engine {
script: &str, script: &str,
) -> Result<AST, ParseError> { ) -> Result<AST, ParseError> {
let scripts = [script]; let scripts = [script];
let stream = lex(&scripts); let stream = lex(&scripts, self.max_string_size);
{ {
let mut peekable = stream.peekable(); let mut peekable = stream.peekable();
parse_global_expr( self.parse_global_expr(&mut peekable, scope, self.optimization_level)
&mut peekable,
self,
scope,
self.optimization_level,
self.max_expr_depth,
)
} }
} }
@ -693,6 +777,7 @@ impl Engine {
/// # } /// # }
/// ``` /// ```
#[cfg(not(feature = "no_std"))] #[cfg(not(feature = "no_std"))]
#[cfg(not(target_arch = "wasm32"))]
pub fn eval_file<T: Variant + Clone>(&self, path: PathBuf) -> Result<T, Box<EvalAltResult>> { pub fn eval_file<T: Variant + Clone>(&self, path: PathBuf) -> Result<T, Box<EvalAltResult>> {
Self::read_file(path).and_then(|contents| self.eval::<T>(&contents)) Self::read_file(path).and_then(|contents| self.eval::<T>(&contents))
} }
@ -717,6 +802,7 @@ impl Engine {
/// # } /// # }
/// ``` /// ```
#[cfg(not(feature = "no_std"))] #[cfg(not(feature = "no_std"))]
#[cfg(not(target_arch = "wasm32"))]
pub fn eval_file_with_scope<T: Variant + Clone>( pub fn eval_file_with_scope<T: Variant + Clone>(
&self, &self,
scope: &mut Scope, scope: &mut Scope,
@ -823,15 +909,10 @@ impl Engine {
script: &str, script: &str,
) -> Result<T, Box<EvalAltResult>> { ) -> Result<T, Box<EvalAltResult>> {
let scripts = [script]; let scripts = [script];
let stream = lex(&scripts); let stream = lex(&scripts, self.max_string_size);
let ast = parse_global_expr( // No need to optimize a lone expression
&mut stream.peekable(), let ast = self.parse_global_expr(&mut stream.peekable(), scope, OptimizationLevel::None)?;
self,
scope,
OptimizationLevel::None, // No need to optimize a lone expression
self.max_expr_depth,
)?;
self.eval_ast_with_scope(scope, &ast) self.eval_ast_with_scope(scope, &ast)
} }
@ -904,6 +985,7 @@ impl Engine {
}); });
} }
/// Evaluate an `AST` with own scope.
pub(crate) fn eval_ast_with_scope_raw( pub(crate) fn eval_ast_with_scope_raw(
&self, &self,
scope: &mut Scope, scope: &mut Scope,
@ -926,6 +1008,7 @@ impl Engine {
/// Evaluate a file, but throw away the result and only return error (if any). /// Evaluate a file, but throw away the result and only return error (if any).
/// Useful for when you don't need the result, but still need to keep track of possible errors. /// Useful for when you don't need the result, but still need to keep track of possible errors.
#[cfg(not(feature = "no_std"))] #[cfg(not(feature = "no_std"))]
#[cfg(not(target_arch = "wasm32"))]
pub fn consume_file(&self, path: PathBuf) -> Result<(), Box<EvalAltResult>> { pub fn consume_file(&self, path: PathBuf) -> Result<(), Box<EvalAltResult>> {
Self::read_file(path).and_then(|contents| self.consume(&contents)) Self::read_file(path).and_then(|contents| self.consume(&contents))
} }
@ -933,6 +1016,7 @@ impl Engine {
/// Evaluate a file with own scope, but throw away the result and only return error (if any). /// Evaluate a file with own scope, but throw away the result and only return error (if any).
/// Useful for when you don't need the result, but still need to keep track of possible errors. /// Useful for when you don't need the result, but still need to keep track of possible errors.
#[cfg(not(feature = "no_std"))] #[cfg(not(feature = "no_std"))]
#[cfg(not(target_arch = "wasm32"))]
pub fn consume_file_with_scope( pub fn consume_file_with_scope(
&self, &self,
scope: &mut Scope, scope: &mut Scope,
@ -955,15 +1039,8 @@ impl Engine {
script: &str, script: &str,
) -> Result<(), Box<EvalAltResult>> { ) -> Result<(), Box<EvalAltResult>> {
let scripts = [script]; let scripts = [script];
let stream = lex(&scripts); let stream = lex(&scripts, self.max_string_size);
let ast = self.parse(&mut stream.peekable(), scope, self.optimization_level)?;
let ast = parse(
&mut stream.peekable(),
self,
scope,
self.optimization_level,
(self.max_expr_depth, self.max_function_expr_depth),
)?;
self.consume_ast_with_scope(scope, &ast) self.consume_ast_with_scope(scope, &ast)
} }
@ -1041,7 +1118,7 @@ impl Engine {
args: A, args: A,
) -> Result<T, Box<EvalAltResult>> { ) -> Result<T, Box<EvalAltResult>> {
let mut arg_values = args.into_vec(); let mut arg_values = args.into_vec();
let result = self.call_fn_dynamic(scope, ast, name, arg_values.as_mut())?; let result = self.call_fn_dynamic_raw(scope, ast, name, arg_values.as_mut())?;
let return_type = self.map_type_name(result.type_name()); let return_type = self.map_type_name(result.type_name());
@ -1055,13 +1132,6 @@ impl Engine {
/// Call a script function defined in an `AST` with multiple `Dynamic` arguments. /// Call a script function defined in an `AST` with multiple `Dynamic` arguments.
/// ///
/// ## WARNING
///
/// All the arguments are _consumed_, meaning that they're replaced by `()`.
/// This is to avoid unnecessarily cloning the arguments.
/// Do you use the arguments after this call. If you need them afterwards,
/// clone them _before_ calling this function.
///
/// # Example /// # Example
/// ///
/// ``` /// ```
@ -1082,13 +1152,13 @@ impl Engine {
/// scope.push("foo", 42_i64); /// scope.push("foo", 42_i64);
/// ///
/// // Call the script-defined function /// // Call the script-defined function
/// let result = engine.call_fn_dynamic(&mut scope, &ast, "add", &mut [ String::from("abc").into(), 123_i64.into() ])?; /// let result = engine.call_fn_dynamic(&mut scope, &ast, "add", vec![ String::from("abc").into(), 123_i64.into() ])?;
/// assert_eq!(result.cast::<i64>(), 168); /// assert_eq!(result.cast::<i64>(), 168);
/// ///
/// let result = engine.call_fn_dynamic(&mut scope, &ast, "add1", &mut [ String::from("abc").into() ])?; /// let result = engine.call_fn_dynamic(&mut scope, &ast, "add1", vec![ String::from("abc").into() ])?;
/// assert_eq!(result.cast::<i64>(), 46); /// assert_eq!(result.cast::<i64>(), 46);
/// ///
/// let result= engine.call_fn_dynamic(&mut scope, &ast, "bar", &mut [])?; /// let result= engine.call_fn_dynamic(&mut scope, &ast, "bar", vec![])?;
/// assert_eq!(result.cast::<i64>(), 21); /// assert_eq!(result.cast::<i64>(), 21);
/// # } /// # }
/// # Ok(()) /// # Ok(())
@ -1096,6 +1166,25 @@ impl Engine {
/// ``` /// ```
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]
pub fn call_fn_dynamic( pub fn call_fn_dynamic(
&self,
scope: &mut Scope,
ast: &AST,
name: &str,
arg_values: impl IntoIterator<Item = Dynamic>,
) -> Result<Dynamic, Box<EvalAltResult>> {
let mut arg_values: StaticVec<_> = arg_values.into_iter().collect();
self.call_fn_dynamic_raw(scope, ast, name, arg_values.as_mut())
}
/// Call a script function defined in an `AST` with multiple `Dynamic` arguments.
///
/// ## WARNING
///
/// All the arguments are _consumed_, meaning that they're replaced by `()`.
/// This is to avoid unnecessarily cloning the arguments.
/// Do not use the arguments after this call. If they are needed afterwards,
/// clone them _before_ calling this function.
pub(crate) fn call_fn_dynamic_raw(
&self, &self,
scope: &mut Scope, scope: &mut Scope,
ast: &AST, ast: &AST,
@ -1103,17 +1192,18 @@ impl Engine {
arg_values: &mut [Dynamic], arg_values: &mut [Dynamic],
) -> Result<Dynamic, Box<EvalAltResult>> { ) -> Result<Dynamic, Box<EvalAltResult>> {
let mut args: StaticVec<_> = arg_values.iter_mut().collect(); let mut args: StaticVec<_> = arg_values.iter_mut().collect();
let lib = ast.lib(); let fn_def = get_script_function_by_signature(ast.lib(), name, args.len(), true)
let pos = Position::none(); .ok_or_else(|| {
Box::new(EvalAltResult::ErrorFunctionNotFound(
let fn_def = lib name.into(),
.get_function_by_signature(name, args.len(), true) Position::none(),
.ok_or_else(|| Box::new(EvalAltResult::ErrorFunctionNotFound(name.into(), pos)))?; ))
})?;
let mut state = State::new(); let mut state = State::new();
let args = args.as_mut(); let args = args.as_mut();
self.call_script_fn(scope, &mut state, &lib, name, fn_def, args, pos, 0) self.call_script_fn(scope, &mut state, ast.lib(), name, fn_def, args, 0)
} }
/// Optimize the `AST` with constants defined in an external Scope. /// Optimize the `AST` with constants defined in an external Scope.
@ -1136,8 +1226,9 @@ impl Engine {
) -> AST { ) -> AST {
let lib = ast let lib = ast
.lib() .lib()
.iter() .iter_fn()
.map(|(_, fn_def)| fn_def.as_ref().clone()) .filter(|(_, _, _, f)| f.is_script())
.map(|(_, _, _, f)| f.get_fn_def().clone())
.collect(); .collect();
let stmt = mem::take(ast.statements_mut()); let stmt = mem::take(ast.statements_mut());
@ -1159,7 +1250,7 @@ impl Engine {
/// ///
/// let mut engine = Engine::new(); /// let mut engine = Engine::new();
/// ///
/// engine.on_progress(move |ops| { /// engine.on_progress(move |&ops| {
/// if ops > 10000 { /// if ops > 10000 {
/// false /// false
/// } else if ops % 800 == 0 { /// } else if ops % 800 == 0 {
@ -1178,7 +1269,7 @@ impl Engine {
/// # Ok(()) /// # Ok(())
/// # } /// # }
/// ``` /// ```
pub fn on_progress(&mut self, callback: impl Fn(u64) -> bool + SendSync + 'static) { pub fn on_progress(&mut self, callback: impl Fn(&u64) -> bool + SendSync + 'static) {
self.progress = Some(Box::new(callback)); self.progress = Some(Box::new(callback));
} }

File diff suppressed because it is too large Load Diff

View File

@ -1,5 +1,6 @@
//! Module containing error definitions for the parsing process. //! Module containing error definitions for the parsing process.
use crate::result::EvalAltResult;
use crate::token::Position; use crate::token::Position;
use crate::stdlib::{boxed::Box, char, error::Error, fmt, string::String}; use crate::stdlib::{boxed::Box, char, error::Error, fmt, string::String};
@ -11,6 +12,8 @@ pub enum LexError {
UnexpectedChar(char), UnexpectedChar(char),
/// A string literal is not terminated before a new-line or EOF. /// A string literal is not terminated before a new-line or EOF.
UnterminatedString, UnterminatedString,
/// An identifier is in an invalid format.
StringTooLong(usize),
/// An string/character/numeric escape sequence is in an invalid format. /// An string/character/numeric escape sequence is in an invalid format.
MalformedEscapeSequence(String), MalformedEscapeSequence(String),
/// An numeric literal is in an invalid format. /// An numeric literal is in an invalid format.
@ -19,8 +22,8 @@ pub enum LexError {
MalformedChar(String), MalformedChar(String),
/// An identifier is in an invalid format. /// An identifier is in an invalid format.
MalformedIdentifier(String), MalformedIdentifier(String),
/// Bad keyword encountered when tokenizing the script text. /// Bad symbol encountered when tokenizing the script text.
ImproperKeyword(String), ImproperSymbol(String),
} }
impl Error for LexError {} impl Error for LexError {}
@ -34,11 +37,23 @@ impl fmt::Display for LexError {
Self::MalformedChar(s) => write!(f, "Invalid character: '{}'", s), Self::MalformedChar(s) => write!(f, "Invalid character: '{}'", s),
Self::MalformedIdentifier(s) => write!(f, "Variable name is not proper: '{}'", s), Self::MalformedIdentifier(s) => write!(f, "Variable name is not proper: '{}'", s),
Self::UnterminatedString => write!(f, "Open string is not terminated"), Self::UnterminatedString => write!(f, "Open string is not terminated"),
Self::ImproperKeyword(s) => write!(f, "{}", s), Self::StringTooLong(max) => write!(
f,
"Length of string literal exceeds the maximum limit ({})",
max
),
Self::ImproperSymbol(s) => write!(f, "{}", s),
} }
} }
} }
impl LexError {
/// Convert a `LexError` into a `ParseError`.
pub fn into_err(&self, pos: Position) -> ParseError {
ParseError(Box::new(self.into()), pos)
}
}
/// Type of error encountered when parsing a script. /// Type of error encountered when parsing a script.
/// ///
/// Some errors never appear when certain features are turned on. /// Some errors never appear when certain features are turned on.
@ -108,12 +123,16 @@ pub enum ParseErrorType {
WrongExport, WrongExport,
/// Assignment to a copy of a value. /// Assignment to a copy of a value.
AssignmentToCopy, AssignmentToCopy,
/// Assignment to an a constant variable. /// Assignment to an a constant variable. Wrapped value is the constant variable name.
AssignmentToConstant(String), AssignmentToConstant(String),
/// Expression exceeding the maximum levels of complexity. /// Expression exceeding the maximum levels of complexity.
/// ///
/// Never appears under the `unchecked` feature. /// Never appears under the `unchecked` feature.
ExprTooDeep, ExprTooDeep,
/// Literal exceeding the maximum size. Wrapped values are the data type name and the maximum size.
///
/// Never appears under the `unchecked` feature.
LiteralTooLarge(String, usize),
/// Break statement not inside a loop. /// Break statement not inside a loop.
LoopBreak, LoopBreak,
} }
@ -123,113 +142,126 @@ impl ParseErrorType {
pub(crate) fn into_err(self, pos: Position) -> ParseError { pub(crate) fn into_err(self, pos: Position) -> ParseError {
ParseError(Box::new(self), pos) ParseError(Box::new(self), pos)
} }
pub(crate) fn desc(&self) -> &str {
match self {
Self::BadInput(p) => p,
Self::UnexpectedEOF => "Script is incomplete",
Self::UnknownOperator(_) => "Unknown operator",
Self::MissingToken(_, _) => "Expecting a certain token that is missing",
Self::MalformedCallExpr(_) => "Invalid expression in function call arguments",
Self::MalformedIndexExpr(_) => "Invalid index in indexing expression",
Self::MalformedInExpr(_) => "Invalid 'in' expression",
Self::DuplicatedProperty(_) => "Duplicated property in object map literal",
Self::ForbiddenConstantExpr(_) => "Expecting a constant",
Self::PropertyExpected => "Expecting name of a property",
Self::VariableExpected => "Expecting name of a variable",
Self::ExprExpected(_) => "Expecting an expression",
Self::FnMissingName => "Expecting name in function declaration",
Self::FnMissingParams(_) => "Expecting parameters in function declaration",
Self::FnDuplicatedParam(_,_) => "Duplicated parameters in function declaration",
Self::FnMissingBody(_) => "Expecting body statement block for function declaration",
Self::WrongFnDefinition => "Function definitions must be at global level and cannot be inside a block or another function",
Self::DuplicatedExport(_) => "Duplicated variable/function in export statement",
Self::WrongExport => "Export statement can only appear at global level",
Self::AssignmentToCopy => "Only a copy of the value is change with this assignment",
Self::AssignmentToConstant(_) => "Cannot assign to a constant value",
Self::ExprTooDeep => "Expression exceeds maximum complexity",
Self::LiteralTooLarge(_, _) => "Literal exceeds maximum limit",
Self::LoopBreak => "Break statement should only be used inside a loop"
}
}
}
impl fmt::Display for ParseErrorType {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::BadInput(s) | ParseErrorType::MalformedCallExpr(s) => {
write!(f, "{}", if s.is_empty() { self.desc() } else { s })
}
Self::ForbiddenConstantExpr(s) => {
write!(f, "Expecting a constant to assign to '{}'", s)
}
Self::UnknownOperator(s) => write!(f, "{}: '{}'", self.desc(), s),
Self::MalformedIndexExpr(s) => {
write!(f, "{}", if s.is_empty() { self.desc() } else { s })
}
Self::MalformedInExpr(s) => write!(f, "{}", if s.is_empty() { self.desc() } else { s }),
Self::DuplicatedProperty(s) => {
write!(f, "Duplicated property '{}' for object map literal", s)
}
Self::ExprExpected(s) => write!(f, "Expecting {} expression", s),
Self::FnMissingParams(s) => write!(f, "Expecting parameters for function '{}'", s),
Self::FnMissingBody(s) => {
write!(f, "Expecting body statement block for function '{}'", s)
}
Self::FnDuplicatedParam(s, arg) => {
write!(f, "Duplicated parameter '{}' for function '{}'", arg, s)
}
Self::DuplicatedExport(s) => write!(
f,
"Duplicated variable/function '{}' in export statement",
s
),
Self::MissingToken(token, s) => write!(f, "Expecting '{}' {}", token, s),
Self::AssignmentToConstant(s) if s.is_empty() => write!(f, "{}", self.desc()),
Self::AssignmentToConstant(s) => write!(f, "Cannot assign to constant '{}'", s),
Self::LiteralTooLarge(typ, max) => {
write!(f, "{} exceeds the maximum limit ({})", typ, max)
}
_ => write!(f, "{}", self.desc()),
}
}
}
impl From<&LexError> for ParseErrorType {
fn from(err: &LexError) -> Self {
match err {
LexError::StringTooLong(max) => {
Self::LiteralTooLarge("Length of string literal".to_string(), *max)
}
_ => Self::BadInput(err.to_string()),
}
}
} }
/// Error when parsing a script. /// Error when parsing a script.
#[derive(Debug, Eq, PartialEq, Clone, Hash)] #[derive(Debug, Eq, PartialEq, Clone, Hash)]
pub struct ParseError(pub(crate) Box<ParseErrorType>, pub(crate) Position); pub struct ParseError(pub Box<ParseErrorType>, pub Position);
impl ParseError {
/// Get the parse error.
pub fn error_type(&self) -> &ParseErrorType {
&self.0
}
/// Get the location in the script of the error.
pub fn position(&self) -> Position {
self.1
}
pub(crate) fn desc(&self) -> &str {
match self.0.as_ref() {
ParseErrorType::BadInput(p) => p,
ParseErrorType::UnexpectedEOF => "Script is incomplete",
ParseErrorType::UnknownOperator(_) => "Unknown operator",
ParseErrorType::MissingToken(_, _) => "Expecting a certain token that is missing",
ParseErrorType::MalformedCallExpr(_) => "Invalid expression in function call arguments",
ParseErrorType::MalformedIndexExpr(_) => "Invalid index in indexing expression",
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",
ParseErrorType::FnMissingName => "Expecting name in function declaration",
ParseErrorType::FnMissingParams(_) => "Expecting parameters in function declaration",
ParseErrorType::FnDuplicatedParam(_,_) => "Duplicated parameters in function declaration",
ParseErrorType::FnMissingBody(_) => "Expecting body statement block for function declaration",
ParseErrorType::WrongFnDefinition => "Function definitions must be at global level and cannot be inside a block or another function",
ParseErrorType::DuplicatedExport(_) => "Duplicated variable/function in export statement",
ParseErrorType::WrongExport => "Export statement can only appear at global level",
ParseErrorType::AssignmentToCopy => "Only a copy of the value is change with this assignment",
ParseErrorType::AssignmentToConstant(_) => "Cannot assign to a constant value",
ParseErrorType::ExprTooDeep => "Expression exceeds maximum complexity",
ParseErrorType::LoopBreak => "Break statement should only be used inside a loop"
}
}
}
impl Error for ParseError {} impl Error for ParseError {}
impl fmt::Display for ParseError { impl fmt::Display for ParseError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self.0.as_ref() { fmt::Display::fmt(&self.0, f)?;
ParseErrorType::BadInput(s) | ParseErrorType::MalformedCallExpr(s) => {
write!(f, "{}", if s.is_empty() { self.desc() } else { s })?
}
ParseErrorType::ForbiddenConstantExpr(s) => {
write!(f, "Expecting a constant to assign to '{}'", s)?
}
ParseErrorType::UnknownOperator(s) => write!(f, "{}: '{}'", self.desc(), s)?,
ParseErrorType::MalformedIndexExpr(s) => {
write!(f, "{}", if s.is_empty() { self.desc() } else { s })?
}
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)?,
ParseErrorType::FnMissingParams(s) => {
write!(f, "Expecting parameters for function '{}'", s)?
}
ParseErrorType::FnMissingBody(s) => {
write!(f, "Expecting body statement block for function '{}'", s)?
}
ParseErrorType::FnDuplicatedParam(s, arg) => {
write!(f, "Duplicated parameter '{}' for function '{}'", arg, s)?
}
ParseErrorType::DuplicatedExport(s) => write!(
f,
"Duplicated variable/function '{}' in export statement",
s
)?,
ParseErrorType::MissingToken(token, s) => write!(f, "Expecting '{}' {}", token, s)?,
ParseErrorType::AssignmentToConstant(s) if s.is_empty() => {
write!(f, "{}", self.desc())?
}
ParseErrorType::AssignmentToConstant(s) => {
write!(f, "Cannot assign to constant '{}'", s)?
}
_ => write!(f, "{}", self.desc())?,
}
if !self.1.is_none() {
// Do not write any position if None // Do not write any position if None
if !self.1.is_none() {
write!(f, " ({})", self.1)?;
}
Ok(()) Ok(())
} else {
write!(f, " ({})", self.1)
} }
} }
impl From<ParseErrorType> for Box<EvalAltResult> {
fn from(err: ParseErrorType) -> Self {
Box::new(EvalAltResult::ErrorParsing(err, Position::none()))
}
}
impl From<ParseError> for Box<EvalAltResult> {
fn from(err: ParseError) -> Self {
Box::new(EvalAltResult::ErrorParsing(*err.0, err.1))
}
} }

View File

@ -1,8 +1,9 @@
use crate::any::Dynamic; use crate::any::Dynamic;
use crate::parser::FnDef; use crate::engine::Engine;
use crate::parser::ScriptFnDef;
use crate::result::EvalAltResult; use crate::result::EvalAltResult;
use crate::stdlib::{boxed::Box, rc::Rc, sync::Arc}; use crate::stdlib::{boxed::Box, fmt, rc::Rc, sync::Arc};
#[cfg(feature = "sync")] #[cfg(feature = "sync")]
pub trait SendSync: Send + Sync {} pub trait SendSync: Send + Sync {}
@ -51,12 +52,18 @@ pub fn shared_take<T: Clone>(value: Shared<T>) -> T {
pub type FnCallArgs<'a> = [&'a mut Dynamic]; pub type FnCallArgs<'a> = [&'a mut Dynamic];
#[cfg(not(feature = "sync"))] #[cfg(not(feature = "sync"))]
pub type FnAny = dyn Fn(&mut FnCallArgs) -> Result<Dynamic, Box<EvalAltResult>>; pub type FnAny = dyn Fn(&Engine, &mut FnCallArgs) -> Result<Dynamic, Box<EvalAltResult>>;
#[cfg(feature = "sync")] #[cfg(feature = "sync")]
pub type FnAny = dyn Fn(&mut FnCallArgs) -> Result<Dynamic, Box<EvalAltResult>> + Send + Sync; pub type FnAny =
dyn Fn(&Engine, &mut FnCallArgs) -> Result<Dynamic, Box<EvalAltResult>> + Send + Sync;
pub type IteratorFn = fn(Dynamic) -> Box<dyn Iterator<Item = Dynamic>>; pub type IteratorFn = fn(Dynamic) -> Box<dyn Iterator<Item = Dynamic>>;
#[cfg(not(feature = "sync"))]
pub type Callback<T, R> = Box<dyn Fn(&T) -> R + 'static>;
#[cfg(feature = "sync")]
pub type Callback<T, R> = Box<dyn Fn(&T) -> R + Send + Sync + 'static>;
/// A type encapsulating a function callable by Rhai. /// A type encapsulating a function callable by Rhai.
#[derive(Clone)] #[derive(Clone)]
pub enum CallableFunction { pub enum CallableFunction {
@ -68,7 +75,18 @@ pub enum CallableFunction {
/// An iterator function. /// An iterator function.
Iterator(IteratorFn), Iterator(IteratorFn),
/// A script-defined function. /// A script-defined function.
Script(Shared<FnDef>), Script(Shared<ScriptFnDef>),
}
impl fmt::Debug for CallableFunction {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Pure(_) => write!(f, "NativePureFunction"),
Self::Method(_) => write!(f, "NativeMethod"),
Self::Iterator(_) => write!(f, "NativeIterator"),
Self::Script(fn_def) => fmt::Debug::fmt(fn_def, f),
}
}
} }
impl CallableFunction { impl CallableFunction {
@ -108,7 +126,18 @@ impl CallableFunction {
pub fn get_native_fn(&self) -> &FnAny { pub fn get_native_fn(&self) -> &FnAny {
match self { match self {
Self::Pure(f) | Self::Method(f) => f.as_ref(), Self::Pure(f) | Self::Method(f) => f.as_ref(),
Self::Iterator(_) | Self::Script(_) => panic!(), Self::Iterator(_) | Self::Script(_) => unreachable!(),
}
}
/// Get a shared reference to a script-defined function definition.
///
/// # Panics
///
/// Panics if the `CallableFunction` is not `Script`.
pub fn get_shared_fn_def(&self) -> Shared<ScriptFnDef> {
match self {
Self::Pure(_) | Self::Method(_) | Self::Iterator(_) => unreachable!(),
Self::Script(f) => f.clone(),
} }
} }
/// Get a reference to a script-defined function definition. /// Get a reference to a script-defined function definition.
@ -116,9 +145,9 @@ impl CallableFunction {
/// # Panics /// # Panics
/// ///
/// Panics if the `CallableFunction` is not `Script`. /// Panics if the `CallableFunction` is not `Script`.
pub fn get_fn_def(&self) -> &FnDef { pub fn get_fn_def(&self) -> &ScriptFnDef {
match self { match self {
Self::Pure(_) | Self::Method(_) | Self::Iterator(_) => panic!(), Self::Pure(_) | Self::Method(_) | Self::Iterator(_) => unreachable!(),
Self::Script(f) => f, Self::Script(f) => f,
} }
} }
@ -130,7 +159,7 @@ impl CallableFunction {
pub fn get_iter_fn(&self) -> IteratorFn { pub fn get_iter_fn(&self) -> IteratorFn {
match self { match self {
Self::Iterator(f) => *f, Self::Iterator(f) => *f,
Self::Pure(_) | Self::Method(_) | Self::Script(_) => panic!(), Self::Pure(_) | Self::Method(_) | Self::Script(_) => unreachable!(),
} }
} }
/// Create a new `CallableFunction::Pure`. /// Create a new `CallableFunction::Pure`.
@ -142,3 +171,21 @@ impl CallableFunction {
Self::Method(func.into()) Self::Method(func.into())
} }
} }
impl From<IteratorFn> for CallableFunction {
fn from(func: IteratorFn) -> Self {
Self::Iterator(func)
}
}
impl From<ScriptFnDef> for CallableFunction {
fn from(func: ScriptFnDef) -> Self {
Self::Script(func.into())
}
}
impl From<Shared<ScriptFnDef>> for CallableFunction {
fn from(func: Shared<ScriptFnDef>) -> Self {
Self::Script(func)
}
}

View File

@ -1,12 +1,12 @@
//! Module which defines the function registration mechanism. //! Module which defines the function registration mechanism.
#![allow(non_snake_case)] #![allow(non_snake_case)]
use crate::any::{Dynamic, Variant}; use crate::any::{Dynamic, Variant};
use crate::engine::Engine; use crate::engine::Engine;
use crate::fn_native::{CallableFunction, FnAny, FnCallArgs}; use crate::fn_native::{CallableFunction, FnAny, FnCallArgs, SendSync};
use crate::parser::FnAccess; use crate::parser::FnAccess;
use crate::result::EvalAltResult; use crate::result::EvalAltResult;
use crate::utils::ImmutableString;
use crate::stdlib::{any::TypeId, boxed::Box, mem}; use crate::stdlib::{any::TypeId, boxed::Box, mem};
@ -99,10 +99,17 @@ pub fn by_ref<T: Variant + Clone>(data: &mut Dynamic) -> &mut T {
/// Dereference into value. /// Dereference into value.
#[inline(always)] #[inline(always)]
pub fn by_value<T: Variant + Clone>(data: &mut Dynamic) -> T { pub fn by_value<T: Variant + Clone>(data: &mut Dynamic) -> T {
if TypeId::of::<T>() == TypeId::of::<&str>() {
// If T is &str, data must be ImmutableString, so map directly to it
let ref_str = data.as_str().unwrap();
let ref_T = unsafe { mem::transmute::<_, &T>(&ref_str) };
ref_T.clone()
} else {
// We consume the argument and then replace it with () - the argument is not supposed to be used again. // We consume the argument and then replace it with () - the argument is not supposed to be used again.
// This way, we avoid having to clone the argument again, because it is already a clone when passed here. // This way, we avoid having to clone the argument again, because it is already a clone when passed here.
mem::take(data).cast::<T>() mem::take(data).cast::<T>()
} }
}
/// This macro creates a closure wrapping a registered function. /// This macro creates a closure wrapping a registered function.
macro_rules! make_func { macro_rules! make_func {
@ -112,7 +119,7 @@ macro_rules! make_func {
// ^ function parameter generic type name (A, B, C etc.) // ^ function parameter generic type name (A, B, C etc.)
// ^ dereferencing function // ^ dereferencing function
Box::new(move |args: &mut FnCallArgs| { Box::new(move |_: &Engine, args: &mut FnCallArgs| {
// The arguments are assumed to be of the correct number and types! // The arguments are assumed to be of the correct number and types!
#[allow(unused_variables, unused_mut)] #[allow(unused_variables, unused_mut)]
@ -146,6 +153,18 @@ pub fn map_result(
data data
} }
/// Remap `&str` to `ImmutableString`.
#[inline(always)]
fn map_type_id<T: 'static>() -> TypeId {
let id = TypeId::of::<T>();
if id == TypeId::of::<&str>() {
TypeId::of::<ImmutableString>()
} else {
id
}
}
macro_rules! def_register { macro_rules! def_register {
() => { () => {
def_register!(imp from_pure :); def_register!(imp from_pure :);
@ -158,19 +177,13 @@ macro_rules! def_register {
// ^ dereferencing function // ^ dereferencing function
impl< impl<
$($par: Variant + Clone,)* $($par: Variant + Clone,)*
FN: Fn($($param),*) -> RET + SendSync + 'static,
#[cfg(feature = "sync")]
FN: Fn($($param),*) -> RET + Send + Sync + 'static,
#[cfg(not(feature = "sync"))]
FN: Fn($($param),*) -> RET + 'static,
RET: Variant + Clone RET: Variant + Clone
> RegisterFn<FN, ($($mark,)*), RET> for Engine > RegisterFn<FN, ($($mark,)*), RET> for Engine
{ {
fn register_fn(&mut self, name: &str, f: FN) { fn register_fn(&mut self, name: &str, f: FN) {
self.global_module.set_fn(name, FnAccess::Public, self.global_module.set_fn(name, FnAccess::Public,
&[$(TypeId::of::<$par>()),*], &[$(map_type_id::<$par>()),*],
CallableFunction::$abi(make_func!(f : map_dynamic ; $($par => $clone),*)) CallableFunction::$abi(make_func!(f : map_dynamic ; $($par => $clone),*))
); );
} }
@ -178,16 +191,12 @@ macro_rules! def_register {
impl< impl<
$($par: Variant + Clone,)* $($par: Variant + Clone,)*
FN: Fn($($param),*) -> Result<Dynamic, Box<EvalAltResult>> + SendSync + 'static,
#[cfg(feature = "sync")]
FN: Fn($($param),*) -> Result<Dynamic, Box<EvalAltResult>> + Send + Sync + 'static,
#[cfg(not(feature = "sync"))]
FN: Fn($($param),*) -> Result<Dynamic, Box<EvalAltResult>> + 'static,
> RegisterResultFn<FN, ($($mark,)*)> for Engine > RegisterResultFn<FN, ($($mark,)*)> for Engine
{ {
fn register_result_fn(&mut self, name: &str, f: FN) { fn register_result_fn(&mut self, name: &str, f: FN) {
self.global_module.set_fn(name, FnAccess::Public, self.global_module.set_fn(name, FnAccess::Public,
&[$(TypeId::of::<$par>()),*], &[$(map_type_id::<$par>()),*],
CallableFunction::$abi(make_func!(f : map_result ; $($par => $clone),*)) CallableFunction::$abi(make_func!(f : map_result ; $($par => $clone),*))
); );
} }

View File

@ -37,6 +37,7 @@
//! engine.register_fn("compute", compute_something); //! engine.register_fn("compute", compute_something);
//! //!
//! # #[cfg(not(feature = "no_std"))] //! # #[cfg(not(feature = "no_std"))]
//! # #[cfg(not(target_arch = "wasm32"))]
//! assert_eq!( //! assert_eq!(
//! // Evaluate the script, expects a 'bool' return //! // Evaluate the script, expects a 'bool' return
//! engine.eval_file::<bool>("my_script.rhai".into())?, //! engine.eval_file::<bool>("my_script.rhai".into())?,
@ -75,7 +76,7 @@ mod engine;
mod error; mod error;
mod fn_call; mod fn_call;
mod fn_func; mod fn_func;
mod fn_native; pub mod fn_native;
mod fn_register; mod fn_register;
mod module; mod module;
mod optimize; mod optimize;

View File

@ -2,12 +2,12 @@
use crate::any::{Dynamic, Variant}; use crate::any::{Dynamic, Variant};
use crate::calc_fn_hash; use crate::calc_fn_hash;
use crate::engine::{make_getter, make_setter, Engine, FunctionsLib, FUNC_INDEXER}; use crate::engine::{make_getter, make_setter, Engine, FUNC_INDEXER_GET, FUNC_INDEXER_SET};
use crate::fn_native::{CallableFunction, FnCallArgs, IteratorFn}; use crate::fn_native::{CallableFunction, FnCallArgs, IteratorFn, SendSync};
use crate::parser::{ use crate::parser::{
FnAccess, FnAccess,
FnAccess::{Private, Public}, FnAccess::{Private, Public},
AST, ScriptFnDef, AST,
}; };
use crate::result::EvalAltResult; use crate::result::EvalAltResult;
use crate::scope::{Entry as ScopeEntry, EntryType as ScopeEntryType, Scope}; use crate::scope::{Entry as ScopeEntry, EntryType as ScopeEntryType, Scope};
@ -53,9 +53,6 @@ pub struct Module {
StraightHasherBuilder, StraightHasherBuilder,
>, >,
/// Script-defined functions.
lib: FunctionsLib,
/// Iterator functions, keyed by the type producing the iterator. /// Iterator functions, keyed by the type producing the iterator.
type_iterators: HashMap<TypeId, IteratorFn>, type_iterators: HashMap<TypeId, IteratorFn>,
@ -68,10 +65,9 @@ impl fmt::Debug for Module {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!( write!(
f, f,
"<module {:?}, functions={}, lib={}>", "<module vars={:?}, functions={}>",
self.variables, self.variables,
self.functions.len(), self.functions.len(),
self.lib.len()
) )
} }
} }
@ -186,6 +182,23 @@ impl Module {
.ok_or_else(|| Box::new(EvalAltResult::ErrorVariableNotFound(name.to_string(), pos))) .ok_or_else(|| Box::new(EvalAltResult::ErrorVariableNotFound(name.to_string(), pos)))
} }
/// Set a script-defined function into the module.
///
/// If there is an existing function of the same name and number of arguments, it is replaced.
pub(crate) fn set_script_fn(&mut self, fn_def: ScriptFnDef) {
// None + function name + number of arguments.
let hash_script = calc_fn_hash(empty(), &fn_def.name, fn_def.params.len(), empty());
self.functions.insert(
hash_script,
(
fn_def.name.to_string(),
fn_def.access,
Default::default(),
fn_def.into(),
),
);
}
/// Does a sub-module exist in the module? /// Does a sub-module exist in the module?
/// ///
/// # Examples /// # Examples
@ -292,6 +305,29 @@ impl Module {
hash_fn hash_fn
} }
/// Set a Rust function taking a reference to the scripting `Engine`, plus a list of
/// mutable `Dynamic` references into the module, returning a hash key.
/// A list of `TypeId`'s is taken as the argument types.
///
/// Use this to register a built-in function which must reference settings on the scripting
/// `Engine` (e.g. to prevent growing an array beyond the allowed maximum size).
///
/// If there is a similar existing Rust function, it is replaced.
pub(crate) fn set_fn_var_args<T: Variant + Clone>(
&mut self,
name: impl Into<String>,
args: &[TypeId],
func: impl Fn(&Engine, &mut [&mut Dynamic]) -> FuncReturn<T> + SendSync + 'static,
) -> u64 {
let f = move |engine: &Engine, args: &mut FnCallArgs| func(engine, args).map(Dynamic::from);
self.set_fn(
name,
Public,
args,
CallableFunction::from_method(Box::new(f)),
)
}
/// Set a Rust function taking no parameters into the module, returning a hash key. /// Set a Rust function taking no parameters into the module, returning a hash key.
/// ///
/// If there is a similar existing Rust function, it is replaced. /// If there is a similar existing Rust function, it is replaced.
@ -308,10 +344,9 @@ impl Module {
pub fn set_fn_0<T: Variant + Clone>( pub fn set_fn_0<T: Variant + Clone>(
&mut self, &mut self,
name: impl Into<String>, name: impl Into<String>,
#[cfg(not(feature = "sync"))] func: impl Fn() -> FuncReturn<T> + 'static, func: impl Fn() -> FuncReturn<T> + SendSync + 'static,
#[cfg(feature = "sync")] func: impl Fn() -> FuncReturn<T> + Send + Sync + 'static,
) -> u64 { ) -> u64 {
let f = move |_: &mut FnCallArgs| func().map(Dynamic::from); let f = move |_: &Engine, _: &mut FnCallArgs| func().map(Dynamic::from);
let args = []; let args = [];
self.set_fn( self.set_fn(
name, name,
@ -337,11 +372,11 @@ impl Module {
pub fn set_fn_1<A: Variant + Clone, T: Variant + Clone>( pub fn set_fn_1<A: Variant + Clone, T: Variant + Clone>(
&mut self, &mut self,
name: impl Into<String>, name: impl Into<String>,
#[cfg(not(feature = "sync"))] func: impl Fn(A) -> FuncReturn<T> + 'static, func: impl Fn(A) -> FuncReturn<T> + SendSync + 'static,
#[cfg(feature = "sync")] func: impl Fn(A) -> FuncReturn<T> + Send + Sync + 'static,
) -> u64 { ) -> u64 {
let f = let f = move |_: &Engine, args: &mut FnCallArgs| {
move |args: &mut FnCallArgs| func(mem::take(args[0]).cast::<A>()).map(Dynamic::from); func(mem::take(args[0]).cast::<A>()).map(Dynamic::from)
};
let args = [TypeId::of::<A>()]; let args = [TypeId::of::<A>()];
self.set_fn( self.set_fn(
name, name,
@ -367,10 +402,9 @@ impl Module {
pub fn set_fn_1_mut<A: Variant + Clone, T: Variant + Clone>( pub fn set_fn_1_mut<A: Variant + Clone, T: Variant + Clone>(
&mut self, &mut self,
name: impl Into<String>, name: impl Into<String>,
#[cfg(not(feature = "sync"))] func: impl Fn(&mut A) -> FuncReturn<T> + 'static, func: impl Fn(&mut A) -> FuncReturn<T> + SendSync + 'static,
#[cfg(feature = "sync")] func: impl Fn(&mut A) -> FuncReturn<T> + Send + Sync + 'static,
) -> u64 { ) -> u64 {
let f = move |args: &mut FnCallArgs| { let f = move |_: &Engine, args: &mut FnCallArgs| {
func(args[0].downcast_mut::<A>().unwrap()).map(Dynamic::from) func(args[0].downcast_mut::<A>().unwrap()).map(Dynamic::from)
}; };
let args = [TypeId::of::<A>()]; let args = [TypeId::of::<A>()];
@ -399,8 +433,7 @@ impl Module {
pub fn set_getter_fn<A: Variant + Clone, T: Variant + Clone>( pub fn set_getter_fn<A: Variant + Clone, T: Variant + Clone>(
&mut self, &mut self,
name: impl Into<String>, name: impl Into<String>,
#[cfg(not(feature = "sync"))] func: impl Fn(&mut A) -> FuncReturn<T> + 'static, func: impl Fn(&mut A) -> FuncReturn<T> + SendSync + 'static,
#[cfg(feature = "sync")] func: impl Fn(&mut A) -> FuncReturn<T> + Send + Sync + 'static,
) -> u64 { ) -> u64 {
self.set_fn_1_mut(make_getter(&name.into()), func) self.set_fn_1_mut(make_getter(&name.into()), func)
} }
@ -423,10 +456,9 @@ impl Module {
pub fn set_fn_2<A: Variant + Clone, B: Variant + Clone, T: Variant + Clone>( pub fn set_fn_2<A: Variant + Clone, B: Variant + Clone, T: Variant + Clone>(
&mut self, &mut self,
name: impl Into<String>, name: impl Into<String>,
#[cfg(not(feature = "sync"))] func: impl Fn(A, B) -> FuncReturn<T> + 'static, func: impl Fn(A, B) -> FuncReturn<T> + SendSync + 'static,
#[cfg(feature = "sync")] func: impl Fn(A, B) -> FuncReturn<T> + Send + Sync + 'static,
) -> u64 { ) -> u64 {
let f = move |args: &mut FnCallArgs| { let f = move |_: &Engine, args: &mut FnCallArgs| {
let a = mem::take(args[0]).cast::<A>(); let a = mem::take(args[0]).cast::<A>();
let b = mem::take(args[1]).cast::<B>(); let b = mem::take(args[1]).cast::<B>();
@ -460,10 +492,9 @@ impl Module {
pub fn set_fn_2_mut<A: Variant + Clone, B: Variant + Clone, T: Variant + Clone>( pub fn set_fn_2_mut<A: Variant + Clone, B: Variant + Clone, T: Variant + Clone>(
&mut self, &mut self,
name: impl Into<String>, name: impl Into<String>,
#[cfg(not(feature = "sync"))] func: impl Fn(&mut A, B) -> FuncReturn<T> + 'static, func: impl Fn(&mut A, B) -> FuncReturn<T> + SendSync + 'static,
#[cfg(feature = "sync")] func: impl Fn(&mut A, B) -> FuncReturn<T> + Send + Sync + 'static,
) -> u64 { ) -> u64 {
let f = move |args: &mut FnCallArgs| { let f = move |_: &Engine, args: &mut FnCallArgs| {
let b = mem::take(args[1]).cast::<B>(); let b = mem::take(args[1]).cast::<B>();
let a = args[0].downcast_mut::<A>().unwrap(); let a = args[0].downcast_mut::<A>().unwrap();
@ -499,13 +530,12 @@ impl Module {
pub fn set_setter_fn<A: Variant + Clone, B: Variant + Clone>( pub fn set_setter_fn<A: Variant + Clone, B: Variant + Clone>(
&mut self, &mut self,
name: impl Into<String>, name: impl Into<String>,
#[cfg(not(feature = "sync"))] func: impl Fn(&mut A, B) -> FuncReturn<()> + 'static, func: impl Fn(&mut A, B) -> FuncReturn<()> + SendSync + 'static,
#[cfg(feature = "sync")] func: impl Fn(&mut A, B) -> FuncReturn<()> + Send + Sync + 'static,
) -> u64 { ) -> u64 {
self.set_fn_2_mut(make_setter(&name.into()), func) self.set_fn_2_mut(make_setter(&name.into()), func)
} }
/// Set a Rust indexer function taking two parameters (the first one mutable) into the module, /// Set a Rust index getter taking two parameters (the first one mutable) into the module,
/// returning a hash key. /// returning a hash key.
/// ///
/// If there is a similar existing setter Rust function, it is replaced. /// If there is a similar existing setter Rust function, it is replaced.
@ -516,19 +546,18 @@ impl Module {
/// use rhai::{Module, ImmutableString}; /// use rhai::{Module, ImmutableString};
/// ///
/// let mut module = Module::new(); /// let mut module = Module::new();
/// let hash = module.set_indexer_fn(|x: &mut i64, y: ImmutableString| { /// let hash = module.set_indexer_get_fn(|x: &mut i64, y: ImmutableString| {
/// Ok(*x + y.len() as i64) /// Ok(*x + y.len() as i64)
/// }); /// });
/// assert!(module.get_fn(hash).is_some()); /// assert!(module.get_fn(hash).is_some());
/// ``` /// ```
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
pub fn set_indexer_fn<A: Variant + Clone, B: Variant + Clone, T: Variant + Clone>( pub fn set_indexer_get_fn<A: Variant + Clone, B: Variant + Clone, T: Variant + Clone>(
&mut self, &mut self,
#[cfg(not(feature = "sync"))] func: impl Fn(&mut A, B) -> FuncReturn<T> + 'static, func: impl Fn(&mut A, B) -> FuncReturn<T> + SendSync + 'static,
#[cfg(feature = "sync")] func: impl Fn(&mut A, B) -> FuncReturn<T> + Send + Sync + 'static,
) -> u64 { ) -> u64 {
self.set_fn_2_mut(FUNC_INDEXER, func) self.set_fn_2_mut(FUNC_INDEXER_GET, func)
} }
/// Set a Rust function taking three parameters into the module, returning a hash key. /// Set a Rust function taking three parameters into the module, returning a hash key.
@ -554,10 +583,9 @@ impl Module {
>( >(
&mut self, &mut self,
name: impl Into<String>, name: impl Into<String>,
#[cfg(not(feature = "sync"))] func: impl Fn(A, B, C) -> FuncReturn<T> + 'static, func: impl Fn(A, B, C) -> FuncReturn<T> + SendSync + 'static,
#[cfg(feature = "sync")] func: impl Fn(A, B, C) -> FuncReturn<T> + Send + Sync + 'static,
) -> u64 { ) -> u64 {
let f = move |args: &mut FnCallArgs| { let f = move |_: &Engine, args: &mut FnCallArgs| {
let a = mem::take(args[0]).cast::<A>(); let a = mem::take(args[0]).cast::<A>();
let b = mem::take(args[1]).cast::<B>(); let b = mem::take(args[1]).cast::<B>();
let c = mem::take(args[2]).cast::<C>(); let c = mem::take(args[2]).cast::<C>();
@ -597,10 +625,9 @@ impl Module {
>( >(
&mut self, &mut self,
name: impl Into<String>, name: impl Into<String>,
#[cfg(not(feature = "sync"))] func: impl Fn(&mut A, B, C) -> FuncReturn<T> + 'static, func: impl Fn(&mut A, B, C) -> FuncReturn<T> + SendSync + 'static,
#[cfg(feature = "sync")] func: impl Fn(&mut A, B, C) -> FuncReturn<T> + Send + Sync + 'static,
) -> u64 { ) -> u64 {
let f = move |args: &mut FnCallArgs| { let f = move |_: &Engine, args: &mut FnCallArgs| {
let b = mem::take(args[1]).cast::<B>(); let b = mem::take(args[1]).cast::<B>();
let c = mem::take(args[2]).cast::<C>(); let c = mem::take(args[2]).cast::<C>();
let a = args[0].downcast_mut::<A>().unwrap(); let a = args[0].downcast_mut::<A>().unwrap();
@ -616,6 +643,43 @@ impl Module {
) )
} }
/// Set a Rust index setter taking three parameters (the first one mutable) into the module,
/// returning a hash key.
///
/// If there is a similar existing Rust function, it is replaced.
///
/// # Examples
///
/// ```
/// use rhai::{Module, ImmutableString};
///
/// let mut module = Module::new();
/// let hash = module.set_indexer_set_fn(|x: &mut i64, y: ImmutableString, value: i64| {
/// *x = y.len() as i64 + value;
/// Ok(())
/// });
/// assert!(module.get_fn(hash).is_some());
/// ```
pub fn set_indexer_set_fn<A: Variant + Clone, B: Variant + Clone>(
&mut self,
func: impl Fn(&mut A, B, A) -> FuncReturn<()> + SendSync + 'static,
) -> u64 {
let f = move |_: &Engine, args: &mut FnCallArgs| {
let b = mem::take(args[1]).cast::<B>();
let c = mem::take(args[2]).cast::<A>();
let a = args[0].downcast_mut::<A>().unwrap();
func(a, b, c).map(Dynamic::from)
};
let args = [TypeId::of::<A>(), TypeId::of::<B>(), TypeId::of::<A>()];
self.set_fn(
FUNC_INDEXER_SET,
Public,
&args,
CallableFunction::from_method(Box::new(f)),
)
}
/// Set a Rust function taking four parameters into the module, returning a hash key. /// Set a Rust function taking four parameters into the module, returning a hash key.
/// ///
/// If there is a similar existing Rust function, it is replaced. /// If there is a similar existing Rust function, it is replaced.
@ -640,10 +704,9 @@ impl Module {
>( >(
&mut self, &mut self,
name: impl Into<String>, name: impl Into<String>,
#[cfg(not(feature = "sync"))] func: impl Fn(A, B, C, D) -> FuncReturn<T> + 'static, func: impl Fn(A, B, C, D) -> FuncReturn<T> + SendSync + 'static,
#[cfg(feature = "sync")] func: impl Fn(A, B, C, D) -> FuncReturn<T> + Send + Sync + 'static,
) -> u64 { ) -> u64 {
let f = move |args: &mut FnCallArgs| { let f = move |_: &Engine, args: &mut FnCallArgs| {
let a = mem::take(args[0]).cast::<A>(); let a = mem::take(args[0]).cast::<A>();
let b = mem::take(args[1]).cast::<B>(); let b = mem::take(args[1]).cast::<B>();
let c = mem::take(args[2]).cast::<C>(); let c = mem::take(args[2]).cast::<C>();
@ -690,10 +753,9 @@ impl Module {
>( >(
&mut self, &mut self,
name: impl Into<String>, name: impl Into<String>,
#[cfg(not(feature = "sync"))] func: impl Fn(&mut A, B, C, D) -> FuncReturn<T> + 'static, func: impl Fn(&mut A, B, C, D) -> FuncReturn<T> + SendSync + 'static,
#[cfg(feature = "sync")] func: impl Fn(&mut A, B, C, D) -> FuncReturn<T> + Send + Sync + 'static,
) -> u64 { ) -> u64 {
let f = move |args: &mut FnCallArgs| { let f = move |_: &Engine, args: &mut FnCallArgs| {
let b = mem::take(args[1]).cast::<B>(); let b = mem::take(args[1]).cast::<B>();
let c = mem::take(args[2]).cast::<C>(); let c = mem::take(args[2]).cast::<C>();
let d = mem::take(args[3]).cast::<D>(); let d = mem::take(args[3]).cast::<D>();
@ -740,9 +802,9 @@ impl Module {
pub(crate) fn get_qualified_fn( pub(crate) fn get_qualified_fn(
&mut self, &mut self,
name: &str, name: &str,
hash_fn_native: u64, hash_qualified_fn: u64,
) -> Result<&CallableFunction, Box<EvalAltResult>> { ) -> Result<&CallableFunction, Box<EvalAltResult>> {
self.all_functions.get(&hash_fn_native).ok_or_else(|| { self.all_functions.get(&hash_qualified_fn).ok_or_else(|| {
Box::new(EvalAltResult::ErrorFunctionNotFound( Box::new(EvalAltResult::ErrorFunctionNotFound(
name.to_string(), name.to_string(),
Position::none(), Position::none(),
@ -750,6 +812,41 @@ impl Module {
}) })
} }
/// Merge another module into this module.
pub fn merge(&mut self, other: &Self) {
self.variables
.extend(other.variables.iter().map(|(k, v)| (k.clone(), v.clone())));
self.functions
.extend(other.functions.iter().map(|(&k, v)| (k, v.clone())));
self.type_iterators
.extend(other.type_iterators.iter().map(|(&k, v)| (k, v.clone())));
}
/// Get the number of variables in the module.
pub fn num_var(&self) -> usize {
self.variables.len()
}
/// Get the number of functions in the module.
pub fn num_fn(&self) -> usize {
self.variables.len()
}
/// Get the number of type iterators in the module.
pub fn num_iter(&self) -> usize {
self.variables.len()
}
/// Get an iterator to the variables in the module.
pub fn iter_var(&self) -> impl Iterator<Item = (&String, &Dynamic)> {
self.variables.iter()
}
/// Get an iterator to the functions in the module.
pub(crate) fn iter_fn(
&self,
) -> impl Iterator<Item = &(String, FnAccess, StaticVec<TypeId>, CallableFunction)> {
self.functions.values()
}
/// Create a new `Module` by evaluating an `AST`. /// Create a new `Module` by evaluating an `AST`.
/// ///
/// # Examples /// # Examples
@ -795,12 +892,12 @@ impl Module {
}, },
); );
module.lib = module.lib.merge(ast.lib()); module.merge(ast.lib());
Ok(module) Ok(module)
} }
/// Scan through all the sub-modules in the `Module` build an index of all /// Scan through all the sub-modules in the module build an index of all
/// variables and external Rust functions via hashing. /// variables and external Rust functions via hashing.
pub(crate) fn index_all_sub_modules(&mut self) { pub(crate) fn index_all_sub_modules(&mut self) {
// Collect a particular module. // Collect a particular module.
@ -830,34 +927,31 @@ impl Module {
Private => continue, Private => continue,
Public => (), Public => (),
} }
// Rust functions are indexed in two steps:
// 1) Calculate a hash in a similar manner to script-defined functions,
// i.e. qualifiers + function name + number of arguments.
let hash_fn_def =
calc_fn_hash(qualifiers.iter().map(|&v| v), name, params.len(), empty());
// 2) Calculate a second hash with no qualifiers, empty function name,
// zero number of arguments, and the actual list of argument `TypeId`'.s
let hash_fn_args = calc_fn_hash(empty(), "", 0, params.iter().cloned());
// 3) The final hash is the XOR of the two hashes.
let hash_fn_native = hash_fn_def ^ hash_fn_args;
functions.push((hash_fn_native, func.clone())); if func.is_script() {
} let fn_def = func.get_shared_fn_def();
// Index all script-defined functions
for fn_def in module.lib.values() {
match fn_def.access {
// Private functions are not exported
Private => continue,
Public => (),
}
// Qualifiers + function name + number of arguments. // Qualifiers + function name + number of arguments.
let hash_fn_def = calc_fn_hash( let hash_qualified_script = calc_fn_hash(
qualifiers.iter().map(|&v| v), qualifiers.iter().map(|&v| v),
&fn_def.name, &fn_def.name,
fn_def.params.len(), fn_def.params.len(),
empty(), empty(),
); );
functions.push((hash_fn_def, CallableFunction::Script(fn_def.clone()).into())); functions.push((hash_qualified_script, fn_def.into()));
} else {
// Qualified Rust functions are indexed in two steps:
// 1) Calculate a hash in a similar manner to script-defined functions,
// i.e. qualifiers + function name + number of arguments.
let hash_qualified_script =
calc_fn_hash(qualifiers.iter().map(|&v| v), name, params.len(), empty());
// 2) Calculate a second hash with no qualifiers, empty function name,
// zero number of arguments, and the actual list of argument `TypeId`'.s
let hash_fn_args = calc_fn_hash(empty(), "", 0, params.iter().cloned());
// 3) The final hash is the XOR of the two hashes.
let hash_qualified_fn = hash_qualified_script ^ hash_fn_args;
functions.push((hash_qualified_fn, func.clone()));
}
} }
} }
@ -945,27 +1039,11 @@ impl ModuleRef {
} }
/// Trait that encapsulates a module resolution service. /// Trait that encapsulates a module resolution service.
#[cfg(not(feature = "no_module"))] pub trait ModuleResolver: SendSync {
#[cfg(not(feature = "sync"))]
pub trait ModuleResolver {
/// Resolve a module based on a path string. /// Resolve a module based on a path string.
fn resolve( fn resolve(
&self, &self,
engine: &Engine, _: &Engine,
scope: Scope,
path: &str,
pos: Position,
) -> Result<Module, Box<EvalAltResult>>;
}
/// Trait that encapsulates a module resolution service.
#[cfg(not(feature = "no_module"))]
#[cfg(feature = "sync")]
pub trait ModuleResolver: Send + Sync {
/// Resolve a module based on a path string.
fn resolve(
&self,
engine: &Engine,
scope: Scope, scope: Scope,
path: &str, path: &str,
pos: Position, pos: Position,
@ -976,13 +1054,17 @@ pub trait ModuleResolver: Send + Sync {
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
pub mod resolvers { pub mod resolvers {
#[cfg(not(feature = "no_std"))] #[cfg(not(feature = "no_std"))]
#[cfg(not(target_arch = "wasm32"))]
pub use super::file::FileModuleResolver; pub use super::file::FileModuleResolver;
pub use super::stat::StaticModuleResolver; pub use super::stat::StaticModuleResolver;
} }
#[cfg(feature = "no_module")]
pub mod resolvers {}
/// Script file-based module resolver. /// Script file-based module resolver.
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
#[cfg(not(feature = "no_std"))] #[cfg(not(feature = "no_std"))]
#[cfg(not(target_arch = "wasm32"))]
mod file { mod file {
use super::*; use super::*;
use crate::stdlib::path::PathBuf; use crate::stdlib::path::PathBuf;

View File

@ -1,13 +1,9 @@
use crate::any::Dynamic; use crate::any::Dynamic;
use crate::calc_fn_hash; use crate::calc_fn_hash;
use crate::engine::{ use crate::engine::{Engine, KEYWORD_DEBUG, KEYWORD_EVAL, KEYWORD_PRINT, KEYWORD_TYPE_OF};
Engine, FunctionsLib, KEYWORD_DEBUG, KEYWORD_EVAL, KEYWORD_PRINT, use crate::module::Module;
KEYWORD_TYPE_OF, use crate::parser::{map_dynamic_to_expr, Expr, ReturnType, ScriptFnDef, Stmt, AST};
};
use crate::parser::{map_dynamic_to_expr, Expr, FnDef, ReturnType, Stmt, AST};
use crate::result::EvalAltResult;
use crate::scope::{Entry as ScopeEntry, EntryType as ScopeEntryType, Scope}; use crate::scope::{Entry as ScopeEntry, EntryType as ScopeEntryType, Scope};
use crate::token::Position;
use crate::utils::StaticVec; use crate::utils::StaticVec;
use crate::stdlib::{ use crate::stdlib::{
@ -56,14 +52,14 @@ struct State<'a> {
/// An `Engine` instance for eager function evaluation. /// An `Engine` instance for eager function evaluation.
engine: &'a Engine, engine: &'a Engine,
/// Library of script-defined functions. /// Library of script-defined functions.
lib: &'a FunctionsLib, lib: &'a Module,
/// Optimization level. /// Optimization level.
optimization_level: OptimizationLevel, optimization_level: OptimizationLevel,
} }
impl<'a> State<'a> { impl<'a> State<'a> {
/// Create a new State. /// Create a new State.
pub fn new(engine: &'a Engine, lib: &'a FunctionsLib, level: OptimizationLevel) -> Self { pub fn new(engine: &'a Engine, lib: &'a Module, level: OptimizationLevel) -> Self {
Self { Self {
changed: false, changed: false,
constants: vec![], constants: vec![],
@ -113,8 +109,7 @@ fn call_fn_with_constant_arguments(
state: &State, state: &State,
fn_name: &str, fn_name: &str,
arg_values: &mut [Dynamic], arg_values: &mut [Dynamic],
pos: Position, ) -> Option<Dynamic> {
) -> Result<Option<Dynamic>, Box<EvalAltResult>> {
// Search built-in's and external functions // Search built-in's and external functions
let hash_fn = calc_fn_hash( let hash_fn = calc_fn_hash(
empty(), empty(),
@ -134,11 +129,10 @@ fn call_fn_with_constant_arguments(
arg_values.iter_mut().collect::<StaticVec<_>>().as_mut(), arg_values.iter_mut().collect::<StaticVec<_>>().as_mut(),
false, false,
None, None,
pos,
0, 0,
) )
.map(|(v, _)| Some(v)) .map(|(v, _)| Some(v))
.or_else(|_| Ok(None)) .unwrap_or_else(|_| None)
} }
/// Optimize a statement. /// Optimize a statement.
@ -551,14 +545,15 @@ fn optimize_expr(expr: Expr, state: &mut State) -> Expr {
&& state.optimization_level == OptimizationLevel::Full // full optimizations && state.optimization_level == OptimizationLevel::Full // full optimizations
&& x.3.iter().all(|expr| expr.is_constant()) // all arguments are constants && x.3.iter().all(|expr| expr.is_constant()) // all arguments are constants
=> { => {
let ((name, native_only, pos), _, _, args, def_value) = x.as_mut(); let ((name, _, pos), _, _, args, def_value) = x.as_mut();
// First search in script-defined functions (can override built-in) // First search in functions lib (can override built-in)
// Cater for both normal function call style and method call style (one additional arguments) // Cater for both normal function call style and method call style (one additional arguments)
if !*native_only && state.lib.values().find(|f| if state.lib.iter_fn().find(|(_, _, _, f)| {
&f.name == name if !f.is_script() { return false; }
&& (args.len()..=args.len() + 1).contains(&f.params.len()) let fn_def = f.get_fn_def();
).is_some() { &fn_def.name == name && (args.len()..=args.len() + 1).contains(&fn_def.params.len())
}).is_some() {
// A script-defined function overrides the built-in function - do not make the call // A script-defined function overrides the built-in function - do not make the call
x.3 = x.3.into_iter().map(|a| optimize_expr(a, state)).collect(); x.3 = x.3.into_iter().map(|a| optimize_expr(a, state)).collect();
return Expr::FnCall(x); return Expr::FnCall(x);
@ -574,9 +569,8 @@ fn optimize_expr(expr: Expr, state: &mut State) -> Expr {
"" ""
}; };
call_fn_with_constant_arguments(&state, name, arg_values.as_mut(), *pos).ok() call_fn_with_constant_arguments(&state, name, arg_values.as_mut())
.and_then(|result| .or_else(|| {
result.or_else(|| {
if !arg_for_type_of.is_empty() { if !arg_for_type_of.is_empty() {
// Handle `type_of()` // Handle `type_of()`
Some(arg_for_type_of.to_string().into()) Some(arg_for_type_of.to_string().into())
@ -584,12 +578,13 @@ fn optimize_expr(expr: Expr, state: &mut State) -> Expr {
// Otherwise use the default value, if any // Otherwise use the default value, if any
def_value.clone() def_value.clone()
} }
}).and_then(|result| map_dynamic_to_expr(result, *pos)) })
.and_then(|result| map_dynamic_to_expr(result, *pos))
.map(|expr| { .map(|expr| {
state.set_dirty(); state.set_dirty();
expr expr
}) })
).unwrap_or_else(|| { .unwrap_or_else(|| {
// Optimize function call arguments // Optimize function call arguments
x.3 = x.3.into_iter().map(|a| optimize_expr(a, state)).collect(); x.3 = x.3.into_iter().map(|a| optimize_expr(a, state)).collect();
Expr::FnCall(x) Expr::FnCall(x)
@ -620,7 +615,7 @@ fn optimize(
statements: Vec<Stmt>, statements: Vec<Stmt>,
engine: &Engine, engine: &Engine,
scope: &Scope, scope: &Scope,
lib: &FunctionsLib, lib: &Module,
level: OptimizationLevel, level: OptimizationLevel,
) -> Vec<Stmt> { ) -> Vec<Stmt> {
// If optimization level is None then skip optimizing // If optimization level is None then skip optimizing
@ -707,7 +702,7 @@ pub fn optimize_into_ast(
engine: &Engine, engine: &Engine,
scope: &Scope, scope: &Scope,
statements: Vec<Stmt>, statements: Vec<Stmt>,
functions: Vec<FnDef>, functions: Vec<ScriptFnDef>,
level: OptimizationLevel, level: OptimizationLevel,
) -> AST { ) -> AST {
#[cfg(feature = "no_optimize")] #[cfg(feature = "no_optimize")]
@ -715,37 +710,65 @@ pub fn optimize_into_ast(
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]
let lib = { let lib = {
if !level.is_none() { let mut module = Module::new();
let lib = FunctionsLib::from_iter(functions.iter().cloned());
FunctionsLib::from_iter(functions.into_iter().map(|mut fn_def| { if !level.is_none() {
// We only need the script library's signatures for optimization purposes
let mut lib2 = Module::new();
functions
.iter()
.map(|fn_def| {
ScriptFnDef {
name: fn_def.name.clone(),
access: fn_def.access,
body: Default::default(),
params: fn_def.params.clone(),
pos: fn_def.pos,
}
.into()
})
.for_each(|fn_def| lib2.set_script_fn(fn_def));
functions
.into_iter()
.map(|mut fn_def| {
let pos = fn_def.body.position(); let pos = fn_def.body.position();
// Optimize the function body // Optimize the function body
let mut body = optimize(vec![fn_def.body], engine, &Scope::new(), &lib, level); let mut body = optimize(vec![fn_def.body], engine, &Scope::new(), &lib2, level);
// {} -> Noop // {} -> Noop
fn_def.body = match body.pop().unwrap_or_else(|| Stmt::Noop(pos)) { fn_def.body = match body.pop().unwrap_or_else(|| Stmt::Noop(pos)) {
// { return val; } -> val // { return val; } -> val
Stmt::ReturnWithVal(x) if x.1.is_some() && (x.0).0 == ReturnType::Return => { Stmt::ReturnWithVal(x)
if x.1.is_some() && (x.0).0 == ReturnType::Return =>
{
Stmt::Expr(Box::new(x.1.unwrap())) Stmt::Expr(Box::new(x.1.unwrap()))
} }
// { return; } -> () // { return; } -> ()
Stmt::ReturnWithVal(x) if x.1.is_none() && (x.0).0 == ReturnType::Return => { Stmt::ReturnWithVal(x)
if x.1.is_none() && (x.0).0 == ReturnType::Return =>
{
Stmt::Expr(Box::new(Expr::Unit((x.0).1))) Stmt::Expr(Box::new(Expr::Unit((x.0).1)))
} }
// All others // All others
stmt => stmt, stmt => stmt,
}; };
fn_def fn_def.into()
})) })
.for_each(|fn_def| module.set_script_fn(fn_def));
} else { } else {
FunctionsLib::from_iter(functions.into_iter()) functions
.into_iter()
.for_each(|fn_def| module.set_script_fn(fn_def));
} }
module
}; };
#[cfg(feature = "no_function")] #[cfg(feature = "no_function")]
let lib: FunctionsLib = Default::default(); let lib = Default::default();
AST::new( AST::new(
match level { match level {

View File

@ -275,27 +275,51 @@ def_package!(crate:ArithmeticPackage:"Basic arithmetic", lib, {
#[cfg(not(feature = "unchecked"))] #[cfg(not(feature = "unchecked"))]
{ {
// Checked basic arithmetic // Checked basic arithmetic
reg_op!(lib, "+", add, i8, u8, i16, u16, i32, u32, u64, i128, u128); reg_op!(lib, "+", add, i8, u8, i16, u16, i32, u32, u64);
reg_op!(lib, "-", sub, i8, u8, i16, u16, i32, u32, u64, i128, u128); reg_op!(lib, "-", sub, i8, u8, i16, u16, i32, u32, u64);
reg_op!(lib, "*", mul, i8, u8, i16, u16, i32, u32, u64, i128, u128); reg_op!(lib, "*", mul, i8, u8, i16, u16, i32, u32, u64);
reg_op!(lib, "/", div, i8, u8, i16, u16, i32, u32, u64, i128, u128); reg_op!(lib, "/", div, i8, u8, i16, u16, i32, u32, u64);
// Checked bit shifts // Checked bit shifts
reg_op!(lib, "<<", shl, i8, u8, i16, u16, i32, u32, u64, i128, u128); reg_op!(lib, "<<", shl, i8, u8, i16, u16, i32, u32, u64);
reg_op!(lib, ">>", shr, i8, u8, i16, u16, i32, u32, u64, i128, u128); reg_op!(lib, ">>", shr, i8, u8, i16, u16, i32, u32, u64);
reg_op!(lib, "%", modulo, i8, u8, i16, u16, i32, u32, u64, i128, u128); reg_op!(lib, "%", modulo, i8, u8, i16, u16, i32, u32, u64);
#[cfg(not(target_arch = "wasm32"))]
{
reg_op!(lib, "+", add, i128, u128);
reg_op!(lib, "-", sub, i128, u128);
reg_op!(lib, "*", mul, i128, u128);
reg_op!(lib, "/", div, i128, u128);
// Checked bit shifts
reg_op!(lib, "<<", shl, i128, u128);
reg_op!(lib, ">>", shr, i128, u128);
reg_op!(lib, "%", modulo, i128, u128);
}
} }
#[cfg(feature = "unchecked")] #[cfg(feature = "unchecked")]
{ {
// Unchecked basic arithmetic // Unchecked basic arithmetic
reg_op!(lib, "+", add_u, i8, u8, i16, u16, i32, u32, u64, i128, u128); reg_op!(lib, "+", add_u, i8, u8, i16, u16, i32, u32, u64);
reg_op!(lib, "-", sub_u, i8, u8, i16, u16, i32, u32, u64, i128, u128); reg_op!(lib, "-", sub_u, i8, u8, i16, u16, i32, u32, u64);
reg_op!(lib, "*", mul_u, i8, u8, i16, u16, i32, u32, u64, i128, u128); reg_op!(lib, "*", mul_u, i8, u8, i16, u16, i32, u32, u64);
reg_op!(lib, "/", div_u, i8, u8, i16, u16, i32, u32, u64, i128, u128); reg_op!(lib, "/", div_u, i8, u8, i16, u16, i32, u32, u64);
// Unchecked bit shifts // Unchecked bit shifts
reg_op!(lib, "<<", shl_u, i64, i8, u8, i16, u16, i32, u32, u64, i128, u128); reg_op!(lib, "<<", shl_u, i64, i8, u8, i16, u16, i32, u32, u64);
reg_op!(lib, ">>", shr_u, i64, i8, u8, i16, u16, i32, u32, u64, i128, u128); reg_op!(lib, ">>", shr_u, i64, i8, u8, i16, u16, i32, u32, u64);
reg_op!(lib, "%", modulo_u, i8, u8, i16, u16, i32, u32, u64, i128, u128); reg_op!(lib, "%", modulo_u, i8, u8, i16, u16, i32, u32, u64);
#[cfg(not(target_arch = "wasm32"))]
{
reg_op!(lib, "+", add_u, i128, u128);
reg_op!(lib, "-", sub_u, i128, u128);
reg_op!(lib, "*", mul_u, i128, u128);
reg_op!(lib, "/", div_u, i128, u128);
// Unchecked bit shifts
reg_op!(lib, "<<", shl_u, i128, u128);
reg_op!(lib, ">>", shr_u, i128, u128);
reg_op!(lib, "%", modulo_u, i128, u128);
}
} }
} }
@ -311,9 +335,16 @@ def_package!(crate:ArithmeticPackage:"Basic arithmetic", lib, {
#[cfg(not(feature = "only_i32"))] #[cfg(not(feature = "only_i32"))]
#[cfg(not(feature = "only_i64"))] #[cfg(not(feature = "only_i64"))]
{ {
reg_op!(lib, "|", binary_or, i8, u8, i16, u16, i32, u32, u64, i128, u128); reg_op!(lib, "|", binary_or, i8, u8, i16, u16, i32, u32, u64);
reg_op!(lib, "&", binary_and, i8, u8, i16, u16, i32, u32, u64, i128, u128); reg_op!(lib, "&", binary_and, i8, u8, i16, u16, i32, u32, u64);
reg_op!(lib, "^", binary_xor, i8, u8, i16, u16, i32, u32, u64, i128, u128); reg_op!(lib, "^", binary_xor, i8, u8, i16, u16, i32, u32, u64);
#[cfg(not(target_arch = "wasm32"))]
{
reg_op!(lib, "|", binary_or, i128, u128);
reg_op!(lib, "&", binary_and, i128, u128);
reg_op!(lib, "^", binary_xor, i128, u128);
}
} }
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
@ -343,8 +374,14 @@ def_package!(crate:ArithmeticPackage:"Basic arithmetic", lib, {
#[cfg(not(feature = "only_i32"))] #[cfg(not(feature = "only_i32"))]
#[cfg(not(feature = "only_i64"))] #[cfg(not(feature = "only_i64"))]
{ {
reg_unary!(lib, "-", neg, i8, i16, i32, i64, i128); reg_unary!(lib, "-", neg, i8, i16, i32, i64);
reg_unary!(lib, "abs", abs, i8, i16, i32, i64, i128); reg_unary!(lib, "abs", abs, i8, i16, i32, i64);
#[cfg(not(target_arch = "wasm32"))]
{
reg_unary!(lib, "-", neg, i128);
reg_unary!(lib, "abs", abs, i128);
}
} }
} }
@ -357,8 +394,14 @@ def_package!(crate:ArithmeticPackage:"Basic arithmetic", lib, {
#[cfg(not(feature = "only_i32"))] #[cfg(not(feature = "only_i32"))]
#[cfg(not(feature = "only_i64"))] #[cfg(not(feature = "only_i64"))]
{ {
reg_unary!(lib, "-", neg_u, i8, i16, i32, i64, i128); reg_unary!(lib, "-", neg_u, i8, i16, i32, i64);
reg_unary!(lib, "abs", abs_u, i8, i16, i32, i64, i128); reg_unary!(lib, "abs", abs_u, i8, i16, i32, i64);
#[cfg(not(target_arch = "wasm32"))]
{
reg_unary!(lib, "-", neg_u, i128);
reg_unary!(lib, "abs", abs_u, i128);
}
} }
} }
}); });

View File

@ -2,9 +2,11 @@
use crate::any::{Dynamic, Variant}; use crate::any::{Dynamic, Variant};
use crate::def_package; use crate::def_package;
use crate::engine::Array; use crate::engine::{Array, Engine};
use crate::module::FuncReturn; use crate::module::FuncReturn;
use crate::parser::{ImmutableString, INT}; use crate::parser::{ImmutableString, INT};
use crate::result::EvalAltResult;
use crate::token::Position;
use crate::stdlib::{any::TypeId, boxed::Box}; use crate::stdlib::{any::TypeId, boxed::Box};
@ -23,8 +25,26 @@ fn ins<T: Variant + Clone>(list: &mut Array, position: INT, item: T) -> FuncRetu
} }
Ok(()) Ok(())
} }
fn pad<T: Variant + Clone>(list: &mut Array, len: INT, item: T) -> FuncReturn<()> { fn pad<T: Variant + Clone>(engine: &Engine, args: &mut [&mut Dynamic]) -> FuncReturn<()> {
let len = *args[1].downcast_ref::<INT>().unwrap();
// Check if array will be over max size limit
#[cfg(not(feature = "unchecked"))]
{
if engine.max_array_size > 0 && len > 0 && (len as usize) > engine.max_array_size {
return Err(Box::new(EvalAltResult::ErrorDataTooLarge(
"Size of array".to_string(),
engine.max_array_size,
len as usize,
Position::none(),
)));
}
}
if len >= 0 { if len >= 0 {
let item = args[2].downcast_ref::<T>().unwrap().clone();
let list = args[0].downcast_mut::<Array>().unwrap();
while list.len() < len as usize { while list.len() < len as usize {
push(list, item.clone())?; push(list, item.clone())?;
} }
@ -42,11 +62,21 @@ macro_rules! reg_tri {
$( $lib.set_fn_3_mut($op, $func::<$par>); )* $( $lib.set_fn_3_mut($op, $func::<$par>); )*
}; };
} }
macro_rules! reg_pad {
($lib:expr, $op:expr, $func:ident, $($par:ty),*) => {
$({
$lib.set_fn_var_args($op,
&[TypeId::of::<Array>(), TypeId::of::<INT>(), TypeId::of::<$par>()],
$func::<$par>
);
})*
};
}
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
def_package!(crate:BasicArrayPackage:"Basic array utilities.", lib, { def_package!(crate:BasicArrayPackage:"Basic array utilities.", lib, {
reg_op!(lib, "push", push, INT, bool, char, ImmutableString, Array, ()); reg_op!(lib, "push", push, INT, bool, char, ImmutableString, Array, ());
reg_tri!(lib, "pad", pad, INT, bool, char, ImmutableString, Array, ()); reg_pad!(lib, "pad", pad, INT, bool, char, ImmutableString, Array, ());
reg_tri!(lib, "insert", ins, INT, bool, char, ImmutableString, Array, ()); reg_tri!(lib, "insert", ins, INT, bool, char, ImmutableString, Array, ());
lib.set_fn_2_mut("append", |x: &mut Array, y: Array| { lib.set_fn_2_mut("append", |x: &mut Array, y: Array| {
@ -68,15 +98,22 @@ def_package!(crate:BasicArrayPackage:"Basic array utilities.", lib, {
#[cfg(not(feature = "only_i32"))] #[cfg(not(feature = "only_i32"))]
#[cfg(not(feature = "only_i64"))] #[cfg(not(feature = "only_i64"))]
{ {
reg_op!(lib, "push", push, i8, u8, i16, u16, i32, i64, u32, u64, i128, u128); reg_op!(lib, "push", push, i8, u8, i16, u16, i32, i64, u32, u64);
reg_tri!(lib, "pad", pad, i8, u8, i16, u16, i32, u32, i64, u64, i128, u128); reg_pad!(lib, "pad", pad, i8, u8, i16, u16, i32, u32, i64, u64);
reg_tri!(lib, "insert", ins, i8, u8, i16, u16, i32, i64, u32, u64, i128, u128); reg_tri!(lib, "insert", ins, i8, u8, i16, u16, i32, i64, u32, u64);
#[cfg(not(target_arch = "wasm32"))]
{
reg_op!(lib, "push", push, i128, u128);
reg_pad!(lib, "pad", pad, i128, u128);
reg_tri!(lib, "insert", ins, i128, u128);
}
} }
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
{ {
reg_op!(lib, "push", push, f32, f64); reg_op!(lib, "push", push, f32, f64);
reg_tri!(lib, "pad", pad, f32, f64); reg_pad!(lib, "pad", pad, f32, f64);
reg_tri!(lib, "insert", ins, f32, f64); reg_tri!(lib, "insert", ins, f32, f64);
} }

View File

@ -85,7 +85,10 @@ def_package!(crate:BasicIteratorPackage:"Basic range iterators.", lib, {
) )
} }
reg_range!(lib, "range", i8, u8, i16, u16, i32, i64, u32, u64, i128, u128); reg_range!(lib, "range", i8, u8, i16, u16, i32, i64, u32, u64);
#[cfg(not(target_arch = "wasm32"))]
reg_range!(lib, "range", i128, u128);
} }
reg_step::<INT>(lib); reg_step::<INT>(lib);
@ -103,6 +106,9 @@ def_package!(crate:BasicIteratorPackage:"Basic range iterators.", lib, {
) )
} }
reg_step!(lib, "range", i8, u8, i16, u16, i32, i64, u32, u64, i128, u128); reg_step!(lib, "range", i8, u8, i16, u16, i32, i64, u32, u64);
#[cfg(not(target_arch = "wasm32"))]
reg_step!(lib, "range", i128, u128);
} }
}); });

View File

@ -36,12 +36,22 @@ def_package!(crate:LogicPackage:"Logical operators.", lib, {
#[cfg(not(feature = "only_i32"))] #[cfg(not(feature = "only_i32"))]
#[cfg(not(feature = "only_i64"))] #[cfg(not(feature = "only_i64"))]
{ {
reg_op!(lib, "<", lt, i8, u8, i16, u16, i32, u32, u64, i128, u128); reg_op!(lib, "<", lt, i8, u8, i16, u16, i32, u32, u64);
reg_op!(lib, "<=", lte, i8, u8, i16, u16, i32, u32, u64, i128, u128); reg_op!(lib, "<=", lte, i8, u8, i16, u16, i32, u32, u64);
reg_op!(lib, ">", gt, i8, u8, i16, u16, i32, u32, u64, i128, u128); reg_op!(lib, ">", gt, i8, u8, i16, u16, i32, u32, u64);
reg_op!(lib, ">=", gte, i8, u8, i16, u16, i32, u32, u64, i128, u128); reg_op!(lib, ">=", gte, i8, u8, i16, u16, i32, u32, u64);
reg_op!(lib, "==", eq, i8, u8, i16, u16, i32, u32, u64, i128, u128); reg_op!(lib, "==", eq, i8, u8, i16, u16, i32, u32, u64);
reg_op!(lib, "!=", ne, i8, u8, i16, u16, i32, u32, u64, i128, u128); reg_op!(lib, "!=", ne, i8, u8, i16, u16, i32, u32, u64);
#[cfg(not(target_arch = "wasm32"))]
{
reg_op!(lib, "<", lt, i128, u128);
reg_op!(lib, "<=", lte, i128, u128);
reg_op!(lib, ">", gt, i128, u128);
reg_op!(lib, ">=", gte, i128, u128);
reg_op!(lib, "==", eq, i128, u128);
reg_op!(lib, "!=", ne, i128, u128);
}
} }
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]

View File

@ -70,10 +70,14 @@ def_package!(crate:BasicMathPackage:"Basic mathematic functions.", lib, {
lib.set_fn_1("to_float", |x: u32| Ok(x as FLOAT)); lib.set_fn_1("to_float", |x: u32| Ok(x as FLOAT));
lib.set_fn_1("to_float", |x: i64| Ok(x as FLOAT)); lib.set_fn_1("to_float", |x: i64| Ok(x as FLOAT));
lib.set_fn_1("to_float", |x: u64| Ok(x as FLOAT)); lib.set_fn_1("to_float", |x: u64| Ok(x as FLOAT));
#[cfg(not(target_arch = "wasm32"))]
{
lib.set_fn_1("to_float", |x: i128| Ok(x as FLOAT)); lib.set_fn_1("to_float", |x: i128| Ok(x as FLOAT));
lib.set_fn_1("to_float", |x: u128| Ok(x as FLOAT)); lib.set_fn_1("to_float", |x: u128| Ok(x as FLOAT));
} }
} }
}
lib.set_fn_1("to_int", |ch: char| Ok(ch as INT)); lib.set_fn_1("to_int", |ch: char| Ok(ch as INT));

View File

@ -52,9 +52,16 @@ def_package!(crate:BasicStringPackage:"Basic string utilities, including printin
reg_op!(lib, KEYWORD_PRINT, to_string, i8, u8, i16, u16, i32, u32); reg_op!(lib, KEYWORD_PRINT, to_string, i8, u8, i16, u16, i32, u32);
reg_op!(lib, FUNC_TO_STRING, to_string, i8, u8, i16, u16, i32, u32); reg_op!(lib, FUNC_TO_STRING, to_string, i8, u8, i16, u16, i32, u32);
reg_op!(lib, KEYWORD_DEBUG, to_debug, i8, u8, i16, u16, i32, u32); reg_op!(lib, KEYWORD_DEBUG, to_debug, i8, u8, i16, u16, i32, u32);
reg_op!(lib, KEYWORD_PRINT, to_string, i64, u64, i128, u128); reg_op!(lib, KEYWORD_PRINT, to_string, i64, u64);
reg_op!(lib, FUNC_TO_STRING, to_string, i64, u64, i128, u128); reg_op!(lib, FUNC_TO_STRING, to_string, i64, u64);
reg_op!(lib, KEYWORD_DEBUG, to_debug, i64, u64, i128, u128); reg_op!(lib, KEYWORD_DEBUG, to_debug, i64, u64);
#[cfg(not(target_arch = "wasm32"))]
{
reg_op!(lib, KEYWORD_PRINT, to_string, i128, u128);
reg_op!(lib, FUNC_TO_STRING, to_string, i128, u128);
reg_op!(lib, KEYWORD_DEBUG, to_debug, i128, u128);
}
} }
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]

View File

@ -1,15 +1,21 @@
use crate::any::Dynamic;
use crate::def_package; use crate::def_package;
use crate::engine::Engine;
use crate::module::FuncReturn; use crate::module::FuncReturn;
use crate::parser::{ImmutableString, INT}; use crate::parser::{ImmutableString, INT};
use crate::result::EvalAltResult;
use crate::token::Position;
use crate::utils::StaticVec; use crate::utils::StaticVec;
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
use crate::engine::Array; use crate::engine::Array;
use crate::stdlib::{ use crate::stdlib::{
any::TypeId,
fmt::Display, fmt::Display,
format, format,
string::{String, ToString}, string::{String, ToString},
vec::Vec,
}; };
fn prepend<T: Display>(x: T, y: ImmutableString) -> FuncReturn<ImmutableString> { fn prepend<T: Display>(x: T, y: ImmutableString) -> FuncReturn<ImmutableString> {
@ -89,8 +95,14 @@ def_package!(crate:MoreStringPackage:"Additional string utilities, including str
#[cfg(not(feature = "only_i32"))] #[cfg(not(feature = "only_i32"))]
#[cfg(not(feature = "only_i64"))] #[cfg(not(feature = "only_i64"))]
{ {
reg_op!(lib, "+", append, i8, u8, i16, u16, i32, i64, u32, u64, i128, u128); reg_op!(lib, "+", append, i8, u8, i16, u16, i32, i64, u32, u64);
reg_op!(lib, "+", prepend, i8, u8, i16, u16, i32, i64, u32, u64, i128, u128); reg_op!(lib, "+", prepend, i8, u8, i16, u16, i32, i64, u32, u64);
#[cfg(not(target_arch = "wasm32"))]
{
reg_op!(lib, "+", append, i128, u128);
reg_op!(lib, "+", prepend, i128, u128);
}
} }
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
@ -210,14 +222,43 @@ def_package!(crate:MoreStringPackage:"Additional string utilities, including str
Ok(()) Ok(())
}, },
); );
lib.set_fn_3_mut( lib.set_fn_var_args(
"pad", "pad",
|s: &mut ImmutableString, len: INT, ch: char| { &[TypeId::of::<ImmutableString>(), TypeId::of::<INT>(), TypeId::of::<char>()],
|engine: &Engine, args: &mut [&mut Dynamic]| {
let len = *args[1].downcast_ref::< INT>().unwrap();
// Check if string will be over max size limit
#[cfg(not(feature = "unchecked"))]
{
if engine.max_string_size > 0 && len > 0 && (len as usize) > engine.max_string_size {
return Err(Box::new(EvalAltResult::ErrorDataTooLarge(
"Length of string".to_string(),
engine.max_string_size,
len as usize,
Position::none(),
)));
}
}
let ch = *args[2].downcast_ref::< char>().unwrap();
let s = args[0].downcast_mut::<ImmutableString>().unwrap();
let copy = s.make_mut(); let copy = s.make_mut();
for _ in 0..copy.chars().count() - len as usize { for _ in 0..copy.chars().count() - len as usize {
copy.push(ch); copy.push(ch);
} }
if engine.max_string_size > 0 && copy.len() > engine.max_string_size {
Err(Box::new(EvalAltResult::ErrorDataTooLarge(
"Length of string".to_string(),
engine.max_string_size,
copy.len(),
Position::none(),
)))
} else {
Ok(()) Ok(())
}
}, },
); );
lib.set_fn_3_mut( lib.set_fn_3_mut(
@ -259,4 +300,12 @@ def_package!(crate:MoreStringPackage:"Additional string utilities, including str
Ok(()) Ok(())
}, },
); );
// Register string iterator
lib.set_iter(
TypeId::of::<ImmutableString>(),
|arr| Box::new(
arr.cast::<ImmutableString>().chars().collect::<Vec<_>>().into_iter().map(Into::into)
) as Box<dyn Iterator<Item = Dynamic>>,
);
}); });

View File

@ -1,3 +1,4 @@
#![cfg(not(feature = "no_std"))]
use super::logic::{eq, gt, gte, lt, lte, ne}; use super::logic::{eq, gt, gte, lt, lte, ne};
use super::math_basic::MAX_INT; use super::math_basic::MAX_INT;
@ -7,13 +8,15 @@ use crate::parser::INT;
use crate::result::EvalAltResult; use crate::result::EvalAltResult;
use crate::token::Position; use crate::token::Position;
#[cfg(not(feature = "no_std"))] #[cfg(not(target_arch = "wasm32"))]
use crate::stdlib::time::Instant; use crate::stdlib::time::Instant;
#[cfg(target_arch = "wasm32")]
use instant::Instant;
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
use crate::parser::FLOAT; use crate::parser::FLOAT;
#[cfg(not(feature = "no_std"))]
def_package!(crate:BasicTimePackage:"Basic timing utilities.", lib, { def_package!(crate:BasicTimePackage:"Basic timing utilities.", lib, {
// Register date/time functions // Register date/time functions
lib.set_fn_0("timestamp", || Ok(Instant::now())); lib.set_fn_0("timestamp", || Ok(Instant::now()));

File diff suppressed because it is too large Load Diff

View File

@ -1,7 +1,7 @@
//! Module containing error definitions for the evaluation process. //! Module containing error definitions for the evaluation process.
use crate::any::Dynamic; use crate::any::Dynamic;
use crate::error::ParseError; use crate::error::ParseErrorType;
use crate::parser::INT; use crate::parser::INT;
use crate::token::Position; use crate::token::Position;
@ -13,6 +13,7 @@ use crate::stdlib::{
}; };
#[cfg(not(feature = "no_std"))] #[cfg(not(feature = "no_std"))]
#[cfg(not(target_arch = "wasm32"))]
use crate::stdlib::path::PathBuf; use crate::stdlib::path::PathBuf;
/// Evaluation result. /// Evaluation result.
@ -23,12 +24,13 @@ use crate::stdlib::path::PathBuf;
#[derive(Debug)] #[derive(Debug)]
pub enum EvalAltResult { pub enum EvalAltResult {
/// Syntax error. /// Syntax error.
ErrorParsing(ParseError), ErrorParsing(ParseErrorType, Position),
/// Error reading from a script file. Wrapped value is the path of the script file. /// Error reading from a script file. Wrapped value is the path of the script file.
/// ///
/// Never appears under the `no_std` feature. /// Never appears under the `no_std` feature.
#[cfg(not(feature = "no_std"))] #[cfg(not(feature = "no_std"))]
#[cfg(not(target_arch = "wasm32"))]
ErrorReadingScriptFile(PathBuf, Position, std::io::Error), ErrorReadingScriptFile(PathBuf, Position, std::io::Error),
/// Call to an unknown function. Wrapped value is the name of the function. /// Call to an unknown function. Wrapped value is the name of the function.
@ -81,6 +83,8 @@ pub enum EvalAltResult {
ErrorTooManyModules(Position), ErrorTooManyModules(Position),
/// Call stack over maximum limit. /// Call stack over maximum limit.
ErrorStackOverflow(Position), ErrorStackOverflow(Position),
/// Data value over maximum size limit. Wrapped values are the data type, maximum size and current size.
ErrorDataTooLarge(String, usize, usize, Position),
/// The script is prematurely terminated. /// The script is prematurely terminated.
ErrorTerminated(Position), ErrorTerminated(Position),
/// Run-time error encountered. Wrapped value is the error message. /// Run-time error encountered. Wrapped value is the error message.
@ -99,9 +103,10 @@ impl EvalAltResult {
pub(crate) fn desc(&self) -> &str { pub(crate) fn desc(&self) -> &str {
match self { match self {
#[cfg(not(feature = "no_std"))] #[cfg(not(feature = "no_std"))]
#[cfg(not(target_arch = "wasm32"))]
Self::ErrorReadingScriptFile(_, _, _) => "Cannot read from script file", Self::ErrorReadingScriptFile(_, _, _) => "Cannot read from script file",
Self::ErrorParsing(p) => p.desc(), Self::ErrorParsing(p, _) => p.desc(),
Self::ErrorInFunctionCall(_, _, _) => "Error in called function", Self::ErrorInFunctionCall(_, _, _) => "Error in called function",
Self::ErrorFunctionNotFound(_, _) => "Function not found", Self::ErrorFunctionNotFound(_, _) => "Function not found",
Self::ErrorBooleanArgMismatch(_, _) => "Boolean operator expects boolean operands", Self::ErrorBooleanArgMismatch(_, _) => "Boolean operator expects boolean operands",
@ -139,6 +144,7 @@ impl EvalAltResult {
Self::ErrorTooManyOperations(_) => "Too many operations", Self::ErrorTooManyOperations(_) => "Too many operations",
Self::ErrorTooManyModules(_) => "Too many modules imported", Self::ErrorTooManyModules(_) => "Too many modules imported",
Self::ErrorStackOverflow(_) => "Stack overflow", Self::ErrorStackOverflow(_) => "Stack overflow",
Self::ErrorDataTooLarge(_, _, _, _) => "Data size exceeds maximum limit",
Self::ErrorTerminated(_) => "Script terminated.", Self::ErrorTerminated(_) => "Script terminated.",
Self::ErrorRuntime(_, _) => "Runtime error", Self::ErrorRuntime(_, _) => "Runtime error",
Self::ErrorLoopBreak(true, _) => "Break statement not inside a loop", Self::ErrorLoopBreak(true, _) => "Break statement not inside a loop",
@ -153,95 +159,93 @@ impl Error for EvalAltResult {}
impl fmt::Display for EvalAltResult { impl fmt::Display for EvalAltResult {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let desc = self.desc(); let desc = self.desc();
let pos = self.position();
match self { match self {
#[cfg(not(feature = "no_std"))] #[cfg(not(feature = "no_std"))]
Self::ErrorReadingScriptFile(path, pos, err) if pos.is_none() => { #[cfg(not(target_arch = "wasm32"))]
write!(f, "{} '{}': {}", desc, path.display(), err) Self::ErrorReadingScriptFile(path, _, err) => {
} write!(f, "{} '{}': {}", desc, path.display(), err)?
#[cfg(not(feature = "no_std"))]
Self::ErrorReadingScriptFile(path, pos, err) => {
write!(f, "{} '{}': {} ({})", desc, path.display(), err, pos)
} }
Self::ErrorParsing(p) => write!(f, "Syntax error: {}", p), Self::ErrorParsing(p, _) => write!(f, "Syntax error: {}", p)?,
Self::ErrorInFunctionCall(s, err, pos) => { Self::ErrorInFunctionCall(s, err, _) => {
write!(f, "Error in call to function '{}' ({}): {}", s, pos, err) write!(f, "Error in call to function '{}' : {}", s, err)?
} }
Self::ErrorFunctionNotFound(s, pos) Self::ErrorFunctionNotFound(s, _)
| Self::ErrorVariableNotFound(s, pos) | Self::ErrorVariableNotFound(s, _)
| Self::ErrorModuleNotFound(s, pos) => write!(f, "{}: '{}' ({})", desc, s, pos), | Self::ErrorModuleNotFound(s, _) => write!(f, "{}: '{}'", desc, s)?,
Self::ErrorDotExpr(s, pos) if !s.is_empty() => write!(f, "{} {} ({})", desc, s, pos), Self::ErrorDotExpr(s, _) if !s.is_empty() => write!(f, "{} {}", desc, s)?,
Self::ErrorIndexingType(_, pos) Self::ErrorIndexingType(_, _)
| Self::ErrorNumericIndexExpr(pos) | Self::ErrorNumericIndexExpr(_)
| Self::ErrorStringIndexExpr(pos) | Self::ErrorStringIndexExpr(_)
| Self::ErrorImportExpr(pos) | Self::ErrorImportExpr(_)
| Self::ErrorLogicGuard(pos) | Self::ErrorLogicGuard(_)
| Self::ErrorFor(pos) | Self::ErrorFor(_)
| Self::ErrorAssignmentToUnknownLHS(pos) | Self::ErrorAssignmentToUnknownLHS(_)
| Self::ErrorInExpr(pos) | Self::ErrorInExpr(_)
| Self::ErrorDotExpr(_, pos) | Self::ErrorDotExpr(_, _)
| Self::ErrorTooManyOperations(pos) | Self::ErrorTooManyOperations(_)
| Self::ErrorTooManyModules(pos) | Self::ErrorTooManyModules(_)
| Self::ErrorStackOverflow(pos) | Self::ErrorStackOverflow(_)
| Self::ErrorTerminated(pos) => write!(f, "{} ({})", desc, pos), | Self::ErrorTerminated(_) => write!(f, "{}", desc)?,
Self::ErrorRuntime(s, pos) => { Self::ErrorRuntime(s, _) => write!(f, "{}", if s.is_empty() { desc } else { s })?,
write!(f, "{} ({})", if s.is_empty() { desc } else { s }, pos)
Self::ErrorAssignmentToConstant(s, _) => write!(f, "{}: '{}'", desc, s)?,
Self::ErrorMismatchOutputType(s, _) => write!(f, "{}: {}", desc, s)?,
Self::ErrorArithmetic(s, _) => write!(f, "{}", s)?,
Self::ErrorLoopBreak(_, _) => write!(f, "{}", desc)?,
Self::Return(_, _) => write!(f, "{}", desc)?,
Self::ErrorBooleanArgMismatch(op, _) => {
write!(f, "{} operator expects boolean operands", op)?
} }
Self::ErrorCharMismatch(_) => write!(f, "string indexing expects a character value")?,
Self::ErrorAssignmentToConstant(s, pos) => write!(f, "{}: '{}' ({})", desc, s, pos), Self::ErrorArrayBounds(_, index, _) if *index < 0 => {
Self::ErrorMismatchOutputType(s, pos) => write!(f, "{}: {} ({})", desc, s, pos), write!(f, "{}: {} < 0", desc, index)?
Self::ErrorArithmetic(s, pos) => write!(f, "{} ({})", s, pos),
Self::ErrorLoopBreak(_, pos) => write!(f, "{} ({})", desc, pos),
Self::Return(_, pos) => write!(f, "{} ({})", desc, pos),
Self::ErrorBooleanArgMismatch(op, pos) => {
write!(f, "{} operator expects boolean operands ({})", op, pos)
} }
Self::ErrorCharMismatch(pos) => { Self::ErrorArrayBounds(0, _, _) => write!(f, "{}", desc)?,
write!(f, "string indexing expects a character value ({})", pos) Self::ErrorArrayBounds(1, index, _) => write!(
}
Self::ErrorArrayBounds(_, index, pos) if *index < 0 => {
write!(f, "{}: {} < 0 ({})", desc, index, pos)
}
Self::ErrorArrayBounds(0, _, pos) => write!(f, "{} ({})", desc, pos),
Self::ErrorArrayBounds(1, index, pos) => write!(
f, f,
"Array index {} is out of bounds: only one element in the array ({})", "Array index {} is out of bounds: only one element in the array",
index, pos index
), )?,
Self::ErrorArrayBounds(max, index, pos) => write!( Self::ErrorArrayBounds(max, index, _) => write!(
f, f,
"Array index {} is out of bounds: only {} elements in the array ({})", "Array index {} is out of bounds: only {} elements in the array",
index, max, pos index, max
), )?,
Self::ErrorStringBounds(_, index, pos) if *index < 0 => { Self::ErrorStringBounds(_, index, _) if *index < 0 => {
write!(f, "{}: {} < 0 ({})", desc, index, pos) write!(f, "{}: {} < 0", desc, index)?
} }
Self::ErrorStringBounds(0, _, pos) => write!(f, "{} ({})", desc, pos), Self::ErrorStringBounds(0, _, _) => write!(f, "{}", desc)?,
Self::ErrorStringBounds(1, index, pos) => write!( Self::ErrorStringBounds(1, index, _) => write!(
f, f,
"String index {} is out of bounds: only one character in the string ({})", "String index {} is out of bounds: only one character in the string",
index, pos index
), )?,
Self::ErrorStringBounds(max, index, pos) => write!( Self::ErrorStringBounds(max, index, _) => write!(
f, f,
"String index {} is out of bounds: only {} characters in the string ({})", "String index {} is out of bounds: only {} characters in the string",
index, max, pos index, max
), )?,
} Self::ErrorDataTooLarge(typ, max, size, _) => {
write!(f, "{} ({}) exceeds the maximum limit ({})", typ, size, max)?
} }
} }
impl From<ParseError> for Box<EvalAltResult> { // Do not write any position if None
fn from(err: ParseError) -> Self { if !pos.is_none() {
Box::new(EvalAltResult::ErrorParsing(err)) write!(f, " ({})", pos)?;
}
Ok(())
} }
} }
@ -259,11 +263,11 @@ impl EvalAltResult {
pub fn position(&self) -> Position { pub fn position(&self) -> Position {
match self { match self {
#[cfg(not(feature = "no_std"))] #[cfg(not(feature = "no_std"))]
#[cfg(not(target_arch = "wasm32"))]
Self::ErrorReadingScriptFile(_, pos, _) => *pos, Self::ErrorReadingScriptFile(_, pos, _) => *pos,
Self::ErrorParsing(err) => err.position(), Self::ErrorParsing(_, pos)
| Self::ErrorFunctionNotFound(_, pos)
Self::ErrorFunctionNotFound(_, pos)
| Self::ErrorInFunctionCall(_, _, pos) | Self::ErrorInFunctionCall(_, _, pos)
| Self::ErrorBooleanArgMismatch(_, pos) | Self::ErrorBooleanArgMismatch(_, pos)
| Self::ErrorCharMismatch(pos) | Self::ErrorCharMismatch(pos)
@ -286,6 +290,7 @@ impl EvalAltResult {
| Self::ErrorTooManyOperations(pos) | Self::ErrorTooManyOperations(pos)
| Self::ErrorTooManyModules(pos) | Self::ErrorTooManyModules(pos)
| Self::ErrorStackOverflow(pos) | Self::ErrorStackOverflow(pos)
| Self::ErrorDataTooLarge(_, _, _, pos)
| Self::ErrorTerminated(pos) | Self::ErrorTerminated(pos)
| Self::ErrorRuntime(_, pos) | Self::ErrorRuntime(_, pos)
| Self::ErrorLoopBreak(_, pos) | Self::ErrorLoopBreak(_, pos)
@ -297,11 +302,11 @@ impl EvalAltResult {
pub fn set_position(&mut self, new_position: Position) { pub fn set_position(&mut self, new_position: Position) {
match self { match self {
#[cfg(not(feature = "no_std"))] #[cfg(not(feature = "no_std"))]
#[cfg(not(target_arch = "wasm32"))]
Self::ErrorReadingScriptFile(_, pos, _) => *pos = new_position, Self::ErrorReadingScriptFile(_, pos, _) => *pos = new_position,
Self::ErrorParsing(err) => err.1 = new_position, Self::ErrorParsing(_, pos)
| Self::ErrorFunctionNotFound(_, pos)
Self::ErrorFunctionNotFound(_, pos)
| Self::ErrorInFunctionCall(_, _, pos) | Self::ErrorInFunctionCall(_, _, pos)
| Self::ErrorBooleanArgMismatch(_, pos) | Self::ErrorBooleanArgMismatch(_, pos)
| Self::ErrorCharMismatch(pos) | Self::ErrorCharMismatch(pos)
@ -324,6 +329,7 @@ impl EvalAltResult {
| Self::ErrorTooManyOperations(pos) | Self::ErrorTooManyOperations(pos)
| Self::ErrorTooManyModules(pos) | Self::ErrorTooManyModules(pos)
| Self::ErrorStackOverflow(pos) | Self::ErrorStackOverflow(pos)
| Self::ErrorDataTooLarge(_, _, _, pos)
| Self::ErrorTerminated(pos) | Self::ErrorTerminated(pos)
| Self::ErrorRuntime(_, pos) | Self::ErrorRuntime(_, pos)
| Self::ErrorLoopBreak(_, pos) | Self::ErrorLoopBreak(_, pos)
@ -331,10 +337,12 @@ impl EvalAltResult {
} }
} }
/// Consume the current `EvalAltResult` and return a new one /// Consume the current `EvalAltResult` and return a new one with the specified `Position`
/// with the specified `Position`. /// if the current position is `Position::None`.
pub(crate) fn new_position(mut self: Box<Self>, new_position: Position) -> Box<Self> { pub(crate) fn new_position(mut self: Box<Self>, new_position: Position) -> Box<Self> {
if self.position().is_none() {
self.set_position(new_position); self.set_position(new_position);
}
self self
} }
} }

View File

@ -1,12 +1,10 @@
//! Module that defines the `Scope` type representing a function call-stack scope. //! Module that defines the `Scope` type representing a function call-stack scope.
use crate::any::{Dynamic, Union, Variant}; use crate::any::{Dynamic, Union, Variant};
use crate::module::Module;
use crate::parser::{map_dynamic_to_expr, Expr}; use crate::parser::{map_dynamic_to_expr, Expr};
use crate::token::Position; use crate::token::Position;
#[cfg(not(feature = "no_module"))]
use crate::module::Module;
use crate::stdlib::{borrow::Cow, boxed::Box, iter, string::String, vec::Vec}; use crate::stdlib::{borrow::Cow, boxed::Box, iter, string::String, vec::Vec};
/// Type of an entry in the Scope. /// Type of an entry in the Scope.
@ -177,7 +175,18 @@ impl<'a> Scope<'a> {
/// ///
/// Modules are used for accessing member variables, functions and plugins under a namespace. /// Modules are used for accessing member variables, functions and plugins under a namespace.
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
pub fn push_module<K: Into<Cow<'a, str>>>(&mut self, name: K, mut value: Module) { pub fn push_module<K: Into<Cow<'a, str>>>(&mut self, name: K, value: Module) {
self.push_module_internal(name, value);
}
/// Add (push) a new module to the Scope.
///
/// Modules are used for accessing member variables, functions and plugins under a namespace.
pub(crate) fn push_module_internal<K: Into<Cow<'a, str>>>(
&mut self,
name: K,
mut value: Module,
) {
value.index_all_sub_modules(); value.index_all_sub_modules();
self.push_dynamic_value( self.push_dynamic_value(
@ -350,6 +359,11 @@ impl<'a> Scope<'a> {
/// Find a module in the Scope, starting from the last entry. /// Find a module in the Scope, starting from the last entry.
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
pub fn find_module(&mut self, name: &str) -> Option<&mut Module> { pub fn find_module(&mut self, name: &str) -> Option<&mut Module> {
self.find_module_internal(name)
}
/// Find a module in the Scope, starting from the last entry.
pub(crate) fn find_module_internal(&mut self, name: &str) -> Option<&mut Module> {
let index = self.get_module_index(name)?; let index = self.get_module_index(name)?;
self.get_mut(index).0.downcast_mut::<Module>() self.get_mut(index).0.downcast_mut::<Module>()
} }
@ -403,7 +417,7 @@ impl<'a> Scope<'a> {
pub fn set_value<T: Variant + Clone>(&mut self, name: &'a str, value: T) { pub fn set_value<T: Variant + Clone>(&mut self, name: &'a str, value: T) {
match self.get_index(name) { match self.get_index(name) {
None => self.push(name, value), None => self.push(name, value),
Some((_, EntryType::Constant)) => panic!("variable {} is constant", name), Some((_, EntryType::Constant)) => unreachable!("variable {} is constant", name),
Some((index, EntryType::Normal)) => { Some((index, EntryType::Normal)) => {
self.0.get_mut(index).unwrap().value = Dynamic::from(value) self.0.get_mut(index).unwrap().value = Dynamic::from(value)
} }

View File

@ -4,10 +4,13 @@
mod inner { mod inner {
pub use core::{ pub use core::{
any, arch, array, ascii, cell, char, clone, cmp, convert, default, f32, f64, ffi, fmt, any, arch, array, ascii, cell, char, clone, cmp, convert, default, f32, f64, ffi, fmt,
future, hash, hint, i128, i16, i32, i64, i8, isize, iter, marker, mem, num, ops, option, future, hash, hint, i16, i32, i64, i8, isize, iter, marker, mem, num, ops, option, panic,
panic, pin, prelude, ptr, result, slice, str, task, time, u128, u16, u32, u64, u8, usize, pin, prelude, ptr, result, slice, str, task, time, u16, u32, u64, u8, usize,
}; };
#[cfg(not(target_arch = "wasm32"))]
pub use core::{i128, u128};
pub use alloc::{borrow, boxed, format, rc, string, sync, vec}; pub use alloc::{borrow, boxed, format, rc, string, sync, vec};
pub use core_error as error; pub use core_error as error;

View File

@ -19,6 +19,8 @@ use crate::stdlib::{
type LERR = LexError; type LERR = LexError;
pub type TokenStream<'a> = Peekable<TokenIterator<'a>>;
/// A location (line number + character position) in the input script. /// A location (line number + character position) in the input script.
/// ///
/// In order to keep footprint small, both line number and character position have 16-bit unsigned resolution, /// In order to keep footprint small, both line number and character position have 16-bit unsigned resolution,
@ -181,6 +183,7 @@ pub enum Token {
XOr, XOr,
Ampersand, Ampersand,
And, And,
#[cfg(not(feature = "no_function"))]
Fn, Fn,
Continue, Continue,
Break, Break,
@ -197,8 +200,10 @@ pub enum Token {
XOrAssign, XOrAssign,
ModuloAssign, ModuloAssign,
PowerOfAssign, PowerOfAssign,
#[cfg(not(feature = "no_function"))]
Private, Private,
Import, Import,
#[cfg(not(feature = "no_module"))]
Export, Export,
As, As,
LexError(Box<LexError>), LexError(Box<LexError>),
@ -260,6 +265,7 @@ impl Token {
Or => "||", Or => "||",
Ampersand => "&", Ampersand => "&",
And => "&&", And => "&&",
#[cfg(not(feature = "no_function"))]
Fn => "fn", Fn => "fn",
Continue => "continue", Continue => "continue",
Break => "break", Break => "break",
@ -281,12 +287,14 @@ impl Token {
ModuloAssign => "%=", ModuloAssign => "%=",
PowerOf => "~", PowerOf => "~",
PowerOfAssign => "~=", PowerOfAssign => "~=",
#[cfg(not(feature = "no_function"))]
Private => "private", Private => "private",
Import => "import", Import => "import",
#[cfg(not(feature = "no_module"))]
Export => "export", Export => "export",
As => "as", As => "as",
EOF => "{EOF}", EOF => "{EOF}",
_ => panic!("operator should be match in outer scope"), _ => unreachable!("operator should be match in outer scope"),
}) })
.into(), .into(),
} }
@ -421,6 +429,8 @@ impl From<Token> for String {
/// An iterator on a `Token` stream. /// An iterator on a `Token` stream.
pub struct TokenIterator<'a> { pub struct TokenIterator<'a> {
/// Maximum length of a string (0 = unlimited).
max_string_size: usize,
/// Can the next token be a unary operator? /// Can the next token be a unary operator?
can_be_unary: bool, can_be_unary: bool,
/// Current position. /// Current position.
@ -486,6 +496,7 @@ impl<'a> TokenIterator<'a> {
pub fn parse_string_literal( pub fn parse_string_literal(
&mut self, &mut self,
enclosing_char: char, enclosing_char: char,
max_length: usize,
) -> Result<String, (LexError, Position)> { ) -> Result<String, (LexError, Position)> {
let mut result = Vec::new(); let mut result = Vec::new();
let mut escape = String::with_capacity(12); let mut escape = String::with_capacity(12);
@ -497,6 +508,10 @@ impl<'a> TokenIterator<'a> {
self.advance(); self.advance();
if max_length > 0 && result.len() > max_length {
return Err((LexError::StringTooLong(max_length), self.pos));
}
match next_char { match next_char {
// \... // \...
'\\' if escape.is_empty() => { '\\' if escape.is_empty() => {
@ -533,7 +548,7 @@ impl<'a> TokenIterator<'a> {
'x' => 2, 'x' => 2,
'u' => 4, 'u' => 4,
'U' => 8, 'U' => 8,
_ => panic!("should be 'x', 'u' or 'U'"), _ => unreachable!(),
}; };
for _ in 0..len { for _ in 0..len {
@ -584,7 +599,13 @@ impl<'a> TokenIterator<'a> {
} }
} }
Ok(result.iter().collect()) let s = result.iter().collect::<String>();
if max_length > 0 && s.len() > max_length {
return Err((LexError::StringTooLong(max_length), self.pos));
}
Ok(s)
} }
/// Get the next token. /// Get the next token.
@ -646,14 +667,14 @@ impl<'a> TokenIterator<'a> {
'0', '1', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '0', '1', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_',
'_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_',
], ],
_ => panic!("unexpected character {}", ch), _ => unreachable!(),
}; };
radix_base = Some(match ch { radix_base = Some(match ch {
'x' | 'X' => 16, 'x' | 'X' => 16,
'o' | 'O' => 8, 'o' | 'O' => 8,
'b' | 'B' => 2, 'b' | 'B' => 2,
_ => panic!("unexpected character {}", ch), _ => unreachable!(),
}); });
while let Some(next_char_in_hex) = self.peek_next() { while let Some(next_char_in_hex) = self.peek_next() {
@ -753,13 +774,11 @@ impl<'a> TokenIterator<'a> {
"throw" => Token::Throw, "throw" => Token::Throw,
"for" => Token::For, "for" => Token::For,
"in" => Token::In, "in" => Token::In,
#[cfg(not(feature = "no_function"))]
"private" => Token::Private, "private" => Token::Private,
#[cfg(not(feature = "no_module"))]
"import" => Token::Import, "import" => Token::Import,
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
"export" => Token::Export, "export" => Token::Export,
#[cfg(not(feature = "no_module"))]
"as" => Token::As, "as" => Token::As,
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]
@ -773,7 +792,9 @@ impl<'a> TokenIterator<'a> {
// " - string literal // " - string literal
('"', _) => { ('"', _) => {
return self.parse_string_literal('"').map_or_else( return self
.parse_string_literal('"', self.max_string_size)
.map_or_else(
|err| Some((Token::LexError(Box::new(err.0)), err.1)), |err| Some((Token::LexError(Box::new(err.0)), err.1)),
|out| Some((Token::StringConst(out), pos)), |out| Some((Token::StringConst(out), pos)),
); );
@ -787,19 +808,25 @@ impl<'a> TokenIterator<'a> {
)); ));
} }
('\'', _) => { ('\'', _) => {
return Some(self.parse_string_literal('\'').map_or_else( return Some(
self.parse_string_literal('\'', self.max_string_size)
.map_or_else(
|err| (Token::LexError(Box::new(err.0)), err.1), |err| (Token::LexError(Box::new(err.0)), err.1),
|result| { |result| {
let mut chars = result.chars(); let mut chars = result.chars();
let first = chars.next(); let first = chars.next();
if chars.next().is_some() { if chars.next().is_some() {
(Token::LexError(Box::new(LERR::MalformedChar(result))), pos) (
Token::LexError(Box::new(LERR::MalformedChar(result))),
pos,
)
} else { } else {
(Token::CharConstant(first.expect("should be Some")), pos) (Token::CharConstant(first.expect("should be Some")), pos)
} }
}, },
)); ),
);
} }
// Braces // Braces
@ -835,6 +862,14 @@ impl<'a> TokenIterator<'a> {
self.eat_next(); self.eat_next();
return Some((Token::MinusAssign, pos)); return Some((Token::MinusAssign, pos));
} }
('-', '>') => {
return Some((
Token::LexError(Box::new(LERR::ImproperSymbol(
"'->' is not a valid symbol. This is not C or C++!".to_string(),
))),
pos,
))
}
('-', _) if self.can_be_unary => return Some((Token::UnaryMinus, pos)), ('-', _) if self.can_be_unary => return Some((Token::UnaryMinus, pos)),
('-', _) => return Some((Token::Minus, pos)), ('-', _) => return Some((Token::Minus, pos)),
@ -904,7 +939,7 @@ impl<'a> TokenIterator<'a> {
// Warn against `===` // Warn against `===`
if self.peek_next() == Some('=') { if self.peek_next() == Some('=') {
return Some(( return Some((
Token::LexError(Box::new(LERR::ImproperKeyword( Token::LexError(Box::new(LERR::ImproperSymbol(
"'===' is not a valid operator. This is not JavaScript! Should it be '=='?" "'===' is not a valid operator. This is not JavaScript! Should it be '=='?"
.to_string(), .to_string(),
))), ))),
@ -914,19 +949,44 @@ impl<'a> TokenIterator<'a> {
return Some((Token::EqualsTo, pos)); return Some((Token::EqualsTo, pos));
} }
('=', '>') => {
return Some((
Token::LexError(Box::new(LERR::ImproperSymbol(
"'=>' is not a valid symbol. This is not Rust! Should it be '>='?"
.to_string(),
))),
pos,
))
}
('=', _) => return Some((Token::Equals, pos)), ('=', _) => return Some((Token::Equals, pos)),
#[cfg(not(feature = "no_module"))]
(':', ':') => { (':', ':') => {
self.eat_next(); self.eat_next();
return Some((Token::DoubleColon, pos)); return Some((Token::DoubleColon, pos));
} }
(':', '=') => {
return Some((
Token::LexError(Box::new(LERR::ImproperSymbol(
"':=' is not a valid assignment operator. This is not Pascal! Should it be simply '='?"
.to_string(),
))),
pos,
))
}
(':', _) => return Some((Token::Colon, pos)), (':', _) => return Some((Token::Colon, pos)),
('<', '=') => { ('<', '=') => {
self.eat_next(); self.eat_next();
return Some((Token::LessThanEqualsTo, pos)); return Some((Token::LessThanEqualsTo, pos));
} }
('<', '-') => {
return Some((
Token::LexError(Box::new(LERR::ImproperSymbol(
"'<-' is not a valid symbol. Should it be '<='?".to_string(),
))),
pos,
))
}
('<', '<') => { ('<', '<') => {
self.eat_next(); self.eat_next();
@ -967,7 +1027,7 @@ impl<'a> TokenIterator<'a> {
// Warn against `!==` // Warn against `!==`
if self.peek_next() == Some('=') { if self.peek_next() == Some('=') {
return Some(( return Some((
Token::LexError(Box::new(LERR::ImproperKeyword( Token::LexError(Box::new(LERR::ImproperSymbol(
"'!==' is not a valid operator. This is not JavaScript! Should it be '!='?" "'!==' is not a valid operator. This is not JavaScript! Should it be '!='?"
.to_string(), .to_string(),
))), ))),
@ -1017,7 +1077,7 @@ impl<'a> TokenIterator<'a> {
} }
('~', _) => return Some((Token::PowerOf, pos)), ('~', _) => return Some((Token::PowerOf, pos)),
('\0', _) => panic!("should not be EOF"), ('\0', _) => unreachable!(),
(ch, _) if ch.is_whitespace() => (), (ch, _) if ch.is_whitespace() => (),
(ch, _) => return Some((Token::LexError(Box::new(LERR::UnexpectedChar(ch))), pos)), (ch, _) => return Some((Token::LexError(Box::new(LERR::UnexpectedChar(ch))), pos)),
@ -1042,8 +1102,9 @@ impl<'a> Iterator for TokenIterator<'a> {
} }
/// Tokenize an input text stream. /// Tokenize an input text stream.
pub fn lex<'a>(input: &'a [&'a str]) -> TokenIterator<'a> { pub fn lex<'a>(input: &'a [&'a str], max_string_size: usize) -> TokenIterator<'a> {
TokenIterator { TokenIterator {
max_string_size,
can_be_unary: true, can_be_unary: true,
pos: Position::new(1, 0), pos: Position::new(1, 0),
streams: input.iter().map(|s| s.chars().peekable()).collect(), streams: input.iter().map(|s| s.chars().peekable()).collect(),

View File

@ -190,8 +190,9 @@ impl<T: Clone> Clone for StaticVec<T> {
if self.is_fixed_storage() { if self.is_fixed_storage() {
for x in 0..self.len { for x in 0..self.len {
let item: &T = unsafe { mem::transmute(self.list.get(x).unwrap()) }; let item = self.list.get(x).unwrap();
value.list[x] = MaybeUninit::new(item.clone()); let item_value = unsafe { mem::transmute::<_, &T>(item) };
value.list[x] = MaybeUninit::new(item_value.clone());
} }
} else { } else {
value.more = self.more.clone(); value.more = self.more.clone();
@ -215,6 +216,15 @@ impl<T> FromIterator<T> for StaticVec<T> {
} }
} }
impl<T: 'static> IntoIterator for StaticVec<T> {
type Item = T;
type IntoIter = Box<dyn Iterator<Item = T>>;
fn into_iter(self) -> Self::IntoIter {
self.into_iter()
}
}
impl<T> StaticVec<T> { impl<T> StaticVec<T> {
/// Create a new `StaticVec`. /// Create a new `StaticVec`.
#[inline(always)] #[inline(always)]
@ -424,7 +434,7 @@ impl<T> StaticVec<T> {
panic!("index OOB in StaticVec"); panic!("index OOB in StaticVec");
} }
let list: &[T; MAX_STATIC_VEC] = unsafe { mem::transmute(&self.list) }; let list = unsafe { mem::transmute::<_, &[T; MAX_STATIC_VEC]>(&self.list) };
if self.is_fixed_storage() { if self.is_fixed_storage() {
list.get(index).unwrap() list.get(index).unwrap()
@ -442,7 +452,7 @@ impl<T> StaticVec<T> {
panic!("index OOB in StaticVec"); panic!("index OOB in StaticVec");
} }
let list: &mut [T; MAX_STATIC_VEC] = unsafe { mem::transmute(&mut self.list) }; let list = unsafe { mem::transmute::<_, &mut [T; MAX_STATIC_VEC]>(&mut self.list) };
if self.is_fixed_storage() { if self.is_fixed_storage() {
list.get_mut(index).unwrap() list.get_mut(index).unwrap()
@ -452,7 +462,7 @@ impl<T> StaticVec<T> {
} }
/// Get an iterator to entries in the `StaticVec`. /// Get an iterator to entries in the `StaticVec`.
pub fn iter(&self) -> impl Iterator<Item = &T> { pub fn iter(&self) -> impl Iterator<Item = &T> {
let list: &[T; MAX_STATIC_VEC] = unsafe { mem::transmute(&self.list) }; let list = unsafe { mem::transmute::<_, &[T; MAX_STATIC_VEC]>(&self.list) };
if self.is_fixed_storage() { if self.is_fixed_storage() {
list[..self.len].iter() list[..self.len].iter()
@ -462,7 +472,7 @@ impl<T> StaticVec<T> {
} }
/// Get a mutable iterator to entries in the `StaticVec`. /// Get a mutable iterator to entries in the `StaticVec`.
pub fn iter_mut(&mut self) -> impl Iterator<Item = &mut T> { pub fn iter_mut(&mut self) -> impl Iterator<Item = &mut T> {
let list: &mut [T; MAX_STATIC_VEC] = unsafe { mem::transmute(&mut self.list) }; let list = unsafe { mem::transmute::<_, &mut [T; MAX_STATIC_VEC]>(&mut self.list) };
if self.is_fixed_storage() { if self.is_fixed_storage() {
list[..self.len].iter_mut() list[..self.len].iter_mut()
@ -541,15 +551,13 @@ impl<T: Default> StaticVec<T> {
impl<T: fmt::Debug> fmt::Debug for StaticVec<T> { impl<T: fmt::Debug> fmt::Debug for StaticVec<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "[ ")?; fmt::Debug::fmt(&self.iter().collect::<Vec<_>>(), f)
self.iter().try_for_each(|v| write!(f, "{:?}, ", v))?;
write!(f, "]")
} }
} }
impl<T> AsRef<[T]> for StaticVec<T> { impl<T> AsRef<[T]> for StaticVec<T> {
fn as_ref(&self) -> &[T] { fn as_ref(&self) -> &[T] {
let list: &[T; MAX_STATIC_VEC] = unsafe { mem::transmute(&self.list) }; let list = unsafe { mem::transmute::<_, &[T; MAX_STATIC_VEC]>(&self.list) };
if self.is_fixed_storage() { if self.is_fixed_storage() {
&list[..self.len] &list[..self.len]
@ -561,7 +569,7 @@ impl<T> AsRef<[T]> for StaticVec<T> {
impl<T> AsMut<[T]> for StaticVec<T> { impl<T> AsMut<[T]> for StaticVec<T> {
fn as_mut(&mut self) -> &mut [T] { fn as_mut(&mut self) -> &mut [T] {
let list: &mut [T; MAX_STATIC_VEC] = unsafe { mem::transmute(&mut self.list) }; let list = unsafe { mem::transmute::<_, &mut [T; MAX_STATIC_VEC]>(&mut self.list) };
if self.is_fixed_storage() { if self.is_fixed_storage() {
&mut list[..self.len] &mut list[..self.len]

View File

@ -6,6 +6,7 @@ fn test_arrays() -> Result<(), Box<EvalAltResult>> {
let engine = Engine::new(); let engine = Engine::new();
assert_eq!(engine.eval::<INT>("let x = [1, 2, 3]; x[1]")?, 2); assert_eq!(engine.eval::<INT>("let x = [1, 2, 3]; x[1]")?, 2);
assert_eq!(engine.eval::<INT>("let x = [1, 2, 3,]; x[1]")?, 2);
assert_eq!(engine.eval::<INT>("let y = [1, 2, 3]; y[1] = 5; y[1]")?, 5); assert_eq!(engine.eval::<INT>("let y = [1, 2, 3]; y[1] = 5; y[1]")?, 5);
assert_eq!( assert_eq!(
engine.eval::<char>(r#"let y = [1, [ 42, 88, "93" ], 3]; y[1][2][1]"#)?, engine.eval::<char>(r#"let y = [1, [ 42, 88, "93" ], 3]; y[1][2][1]"#)?,

View File

@ -1,19 +1,17 @@
#![cfg(not(feature = "no_function"))] #![cfg(not(feature = "no_function"))]
use rhai::{Engine, EvalAltResult, Func, ParseErrorType, Scope, INT}; use rhai::{Engine, EvalAltResult, Func, ParseError, ParseErrorType, Scope, INT};
#[test] #[test]
fn test_fn() -> Result<(), Box<EvalAltResult>> { fn test_fn() -> Result<(), Box<EvalAltResult>> {
let engine = Engine::new(); let engine = Engine::new();
// Expect duplicated parameters error // Expect duplicated parameters error
match engine assert!(matches!(
engine
.compile("fn hello(x, x) { x }") .compile("fn hello(x, x) { x }")
.expect_err("should be error") .expect_err("should be error"),
.error_type() ParseError(x, _) if *x == ParseErrorType::FnDuplicatedParam("hello".to_string(), "x".to_string())
{ ));
ParseErrorType::FnDuplicatedParam(f, p) if f == "hello" && p == "x" => (),
_ => assert!(false, "wrong error"),
}
Ok(()) Ok(())
} }
@ -70,7 +68,7 @@ fn test_call_fn_private() -> Result<(), Box<EvalAltResult>> {
let r: INT = engine.call_fn(&mut scope, &ast, "add", (40 as INT, 2 as INT))?; let r: INT = engine.call_fn(&mut scope, &ast, "add", (40 as INT, 2 as INT))?;
assert_eq!(r, 42); assert_eq!(r, 42);
let ast = engine.compile("private fn add(x, n) { x + n }")?; let ast = engine.compile("private fn add(x, n, ) { x + n }")?;
assert!(matches!( assert!(matches!(
*engine.call_fn::<_, INT>(&mut scope, &ast, "add", (40 as INT, 2 as INT)) *engine.call_fn::<_, INT>(&mut scope, &ast, "add", (40 as INT, 2 as INT))
@ -85,12 +83,20 @@ fn test_call_fn_private() -> Result<(), Box<EvalAltResult>> {
fn test_anonymous_fn() -> Result<(), Box<EvalAltResult>> { fn test_anonymous_fn() -> Result<(), Box<EvalAltResult>> {
let calc_func = Func::<(INT, INT, INT), INT>::create_from_script( let calc_func = Func::<(INT, INT, INT), INT>::create_from_script(
Engine::new(), Engine::new(),
"fn calc(x, y, z) { (x + y) * z }", "fn calc(x, y, z,) { (x + y) * z }",
"calc", "calc",
)?; )?;
assert_eq!(calc_func(42, 123, 9)?, 1485); assert_eq!(calc_func(42, 123, 9)?, 1485);
let calc_func = Func::<(INT, String, INT), INT>::create_from_script(
Engine::new(),
"fn calc(x, y, z) { (x + len(y)) * z }",
"calc",
)?;
assert_eq!(calc_func(42, "hello".to_string(), 9)?, 423);
let calc_func = Func::<(INT, INT, INT), INT>::create_from_script( let calc_func = Func::<(INT, INT, INT), INT>::create_from_script(
Engine::new(), Engine::new(),
"private fn calc(x, y, z) { (x + y) * z }", "private fn calc(x, y, z) { (x + y) * z }",

View File

@ -7,14 +7,16 @@ fn test_constant() -> Result<(), Box<EvalAltResult>> {
assert_eq!(engine.eval::<INT>("const x = 123; x")?, 123); assert_eq!(engine.eval::<INT>("const x = 123; x")?, 123);
assert!(matches!( assert!(matches!(
*engine.eval::<INT>("const x = 123; x = 42;").expect_err("expects error"), *engine
EvalAltResult::ErrorParsing(err) if err.error_type() == &ParseErrorType::AssignmentToConstant("x".to_string()) .eval::<INT>("const x = 123; x = 42;")
.expect_err("expects error"),
EvalAltResult::ErrorParsing(ParseErrorType::AssignmentToConstant(x), _) if x == "x"
)); ));
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
assert!(matches!( assert!(matches!(
*engine.eval::<INT>("const x = [1, 2, 3, 4, 5]; x[2] = 42;").expect_err("expects error"), *engine.eval::<INT>("const x = [1, 2, 3, 4, 5]; x[2] = 42;").expect_err("expects error"),
EvalAltResult::ErrorParsing(err) if err.error_type() == &ParseErrorType::AssignmentToConstant("x".to_string()) EvalAltResult::ErrorParsing(ParseErrorType::AssignmentToConstant(x), _) if x == "x"
)); ));
Ok(()) Ok(())

259
tests/data_size.rs Normal file
View File

@ -0,0 +1,259 @@
#![cfg(not(feature = "unchecked"))]
use rhai::{Engine, EvalAltResult, ParseError, ParseErrorType};
#[cfg(not(feature = "no_index"))]
use rhai::Array;
#[cfg(not(feature = "no_object"))]
use rhai::Map;
#[test]
fn test_max_string_size() -> Result<(), Box<EvalAltResult>> {
let mut engine = Engine::new();
engine.set_max_string_size(10);
assert!(matches!(
engine.compile(r#"let x = "hello, world!";"#).expect_err("should error"),
ParseError(x, _) if *x == ParseErrorType::LiteralTooLarge("Length of string literal".to_string(), 10)
));
assert!(matches!(
engine.compile(r#"let x = "朝に紅顔、暮に白骨";"#).expect_err("should error"),
ParseError(x, _) if *x == ParseErrorType::LiteralTooLarge("Length of string literal".to_string(), 10)
));
assert!(matches!(
*engine
.eval::<String>(
r#"
let x = "hello, ";
let y = "world!";
x + y
"#
)
.expect_err("should error"),
EvalAltResult::ErrorDataTooLarge(_, 10, 13, _)
));
assert!(matches!(
*engine
.eval::<String>(
r#"
let x = "hello";
x.pad(100, '!');
x
"#
)
.expect_err("should error"),
EvalAltResult::ErrorDataTooLarge(_, 10, 100, _)
));
engine.set_max_string_size(0);
assert_eq!(
engine.eval::<String>(
r#"
let x = "hello, ";
let y = "world!";
x + y
"#
)?,
"hello, world!"
);
Ok(())
}
#[test]
#[cfg(not(feature = "no_index"))]
fn test_max_array_size() -> Result<(), Box<EvalAltResult>> {
let mut engine = Engine::new();
engine.set_max_array_size(10);
#[cfg(not(feature = "no_object"))]
engine.set_max_map_size(10);
assert!(matches!(
engine
.compile("let x = [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15];")
.expect_err("should error"),
ParseError(x, _) if *x == ParseErrorType::LiteralTooLarge("Size of array literal".to_string(), 10)
));
assert!(matches!(
*engine
.eval::<Array>(
r"
let x = [1,2,3,4,5,6];
let y = [7,8,9,10,11,12];
x + y
"
)
.expect_err("should error"),
EvalAltResult::ErrorDataTooLarge(_, 10, 12, _)
));
assert!(matches!(
*engine
.eval::<Array>(
r"
let x = [1,2,3,4,5,6];
x.pad(100, 42);
x
"
)
.expect_err("should error"),
EvalAltResult::ErrorDataTooLarge(_, 10, 100, _)
));
assert!(matches!(
*engine
.eval::<Array>(
r"
let x = [1,2,3];
[x, x, x, x]
"
)
.expect_err("should error"),
EvalAltResult::ErrorDataTooLarge(_, 10, 12, _)
));
#[cfg(not(feature = "no_object"))]
assert!(matches!(
*engine
.eval::<Array>(
r"
let x = #{a:1, b:2, c:3};
[x, x, x, x]
"
)
.expect_err("should error"),
EvalAltResult::ErrorDataTooLarge(_, 10, 12, _)
));
assert!(matches!(
*engine
.eval::<Array>(
r"
let x = [1];
let y = [x, x];
let z = [y, y];
[z, z, z]
"
)
.expect_err("should error"),
EvalAltResult::ErrorDataTooLarge(_, 10, 12, _)
));
engine.set_max_array_size(0);
assert_eq!(
engine
.eval::<Array>(
r"
let x = [1,2,3,4,5,6];
let y = [7,8,9,10,11,12];
x + y
"
)?
.len(),
12
);
assert_eq!(
engine
.eval::<Array>(
r"
let x = [1,2,3];
[x, x, x, x]
"
)?
.len(),
4
);
Ok(())
}
#[test]
#[cfg(not(feature = "no_object"))]
fn test_max_map_size() -> Result<(), Box<EvalAltResult>> {
let mut engine = Engine::new();
engine.set_max_map_size(10);
#[cfg(not(feature = "no_index"))]
engine.set_max_array_size(10);
assert!(matches!(
engine
.compile("let x = #{a:1,b:2,c:3,d:4,e:5,f:6,g:7,h:8,i:9,j:10,k:11,l:12,m:13,n:14,o:15};")
.expect_err("should error"),
ParseError(x, _) if *x == ParseErrorType::LiteralTooLarge("Number of properties in object map literal".to_string(), 10)
));
assert!(matches!(
*engine
.eval::<Map>(
r"
let x = #{a:1,b:2,c:3,d:4,e:5,f:6};
let y = #{g:7,h:8,i:9,j:10,k:11,l:12};
x + y
"
)
.expect_err("should error"),
EvalAltResult::ErrorDataTooLarge(_, 10, 12, _)
));
assert!(matches!(
*engine
.eval::<Map>(
r"
let x = #{a:1,b:2,c:3};
#{u:x, v:x, w:x, z:x}
"
)
.expect_err("should error"),
EvalAltResult::ErrorDataTooLarge(_, 10, 12, _)
));
#[cfg(not(feature = "no_index"))]
assert!(matches!(
*engine
.eval::<Map>(
r"
let x = [1, 2, 3];
#{u:x, v:x, w:x, z:x}
"
)
.expect_err("should error"),
EvalAltResult::ErrorDataTooLarge(_, 10, 12, _)
));
engine.set_max_map_size(0);
assert_eq!(
engine
.eval::<Map>(
r"
let x = #{a:1,b:2,c:3,d:4,e:5,f:6};
let y = #{g:7,h:8,i:9,j:10,k:11,l:12};
x + y
"
)?
.len(),
12
);
assert_eq!(
engine
.eval::<Map>(
r"
let x = #{a:1,b:2,c:3};
#{u:x, v:x, w:x, z:x}
"
)?
.len(),
4
);
Ok(())
}

View File

@ -8,7 +8,7 @@ fn test_decrement() -> Result<(), Box<EvalAltResult>> {
assert!(matches!( assert!(matches!(
*engine.eval::<String>(r#"let s = "test"; s -= "ing"; s"#).expect_err("expects error"), *engine.eval::<String>(r#"let s = "test"; s -= "ing"; s"#).expect_err("expects error"),
EvalAltResult::ErrorFunctionNotFound(err, _) if err == "- (string, string)" EvalAltResult::ErrorFunctionNotFound(err, _) if err == "- (&str | ImmutableString, &str | ImmutableString)"
)); ));
Ok(()) Ok(())

View File

@ -12,16 +12,17 @@ fn test_expressions() -> Result<(), Box<EvalAltResult>> {
engine.eval_expression_with_scope::<INT>(&mut scope, "2 + (x + 10) * 2")?, engine.eval_expression_with_scope::<INT>(&mut scope, "2 + (x + 10) * 2")?,
42 42
); );
assert_eq!( assert!(engine
engine.eval_expression_with_scope::<INT>(&mut scope, "if x > 0 { 42 } else { 123 }")?, .eval_expression_with_scope::<INT>(&mut scope, "if x > 0 { 42 } else { 123 }")
42 .is_err());
);
assert!(engine.eval_expression::<()>("40 + 2;").is_err()); assert!(engine.eval_expression::<()>("40 + 2;").is_err());
assert!(engine.eval_expression::<()>("40 + { 2 }").is_err()); assert!(engine.eval_expression::<()>("40 + { 2 }").is_err());
assert!(engine.eval_expression::<()>("x = 42").is_err()); assert!(engine.eval_expression::<()>("x = 42").is_err());
assert!(engine.compile_expression("let x = 42").is_err()); assert!(engine.compile_expression("let x = 42").is_err());
engine.compile("40 + { let x = 2; x }")?;
Ok(()) Ok(())
} }

View File

@ -30,6 +30,26 @@ fn test_for_array() -> Result<(), Box<EvalAltResult>> {
Ok(()) Ok(())
} }
#[test]
fn test_for_string() -> Result<(), Box<EvalAltResult>> {
let engine = Engine::new();
let script = r#"
let s = "hello";
let sum = 0;
for ch in s {
sum += ch.to_int();
}
sum
"#;
assert_eq!(engine.eval::<INT>(script)?, 532);
Ok(())
}
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
#[test] #[test]

View File

@ -7,6 +7,11 @@ fn test_functions() -> Result<(), Box<EvalAltResult>> {
assert_eq!(engine.eval::<INT>("fn add(x, n) { x + n } add(40, 2)")?, 42); assert_eq!(engine.eval::<INT>("fn add(x, n) { x + n } add(40, 2)")?, 42);
assert_eq!(
engine.eval::<INT>("fn add(x, n,) { x + n } add(40, 2,)")?,
42
);
assert_eq!( assert_eq!(
engine.eval::<INT>("fn add(x, n) { x + n } let a = 40; add(a, 2); a")?, engine.eval::<INT>("fn add(x, n) { x + n } let a = 40; add(a, 2); a")?,
40 40

View File

@ -42,18 +42,25 @@ fn test_get_set() -> Result<(), Box<EvalAltResult>> {
engine.register_fn("add", |value: &mut INT| *value += 41); engine.register_fn("add", |value: &mut INT| *value += 41);
engine.register_fn("new_ts", TestStruct::new); engine.register_fn("new_ts", TestStruct::new);
#[cfg(not(feature = "no_index"))]
engine.register_indexer(|value: &mut TestStruct, index: ImmutableString| {
value.array[index.len()]
});
assert_eq!(engine.eval::<INT>("let a = new_ts(); a.x = 500; a.x")?, 500); assert_eq!(engine.eval::<INT>("let a = new_ts(); a.x = 500; a.x")?, 500);
assert_eq!(engine.eval::<INT>("let a = new_ts(); a.x.add(); a.x")?, 42); assert_eq!(engine.eval::<INT>("let a = new_ts(); a.x.add(); a.x")?, 42);
assert_eq!(engine.eval::<INT>("let a = new_ts(); a.y.add(); a.y")?, 0); assert_eq!(engine.eval::<INT>("let a = new_ts(); a.y.add(); a.y")?, 0);
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
assert_eq!(engine.eval::<INT>(r#"let a = new_ts(); a["abc"]"#)?, 4); {
engine.register_indexer_get_set(
|value: &mut TestStruct, index: ImmutableString| value.array[index.len()],
|value: &mut TestStruct, index: ImmutableString, new_val: INT| {
value.array[index.len()] = new_val
},
);
assert_eq!(engine.eval::<INT>(r#"let a = new_ts(); a["abc"]"#)?, 4);
assert_eq!(
engine.eval::<INT>(r#"let a = new_ts(); a["abc"] = 42; a["abc"]"#)?,
42
);
}
Ok(()) Ok(())
} }

View File

@ -10,6 +10,12 @@ fn test_internal_fn() -> Result<(), Box<EvalAltResult>> {
engine.eval::<INT>("fn add_me(a, b) { a+b } add_me(3, 4)")?, engine.eval::<INT>("fn add_me(a, b) { a+b } add_me(3, 4)")?,
7 7
); );
assert_eq!(
engine.eval::<INT>("fn add_me(a, b,) { a+b } add_me(3, 4,)")?,
7
);
assert_eq!(engine.eval::<INT>("fn bob() { return 4; 5 } bob()")?, 4); assert_eq!(engine.eval::<INT>("fn bob() { return 4; 5 } bob()")?, 4);
Ok(()) Ok(())

View File

@ -1,4 +1,4 @@
use rhai::{Engine, EvalAltResult, INT}; use rhai::{Engine, EvalAltResult, ParseError, ParseErrorType, INT};
#[test] #[test]
fn test_loop() -> Result<(), Box<EvalAltResult>> { fn test_loop() -> Result<(), Box<EvalAltResult>> {
@ -26,5 +26,15 @@ fn test_loop() -> Result<(), Box<EvalAltResult>> {
21 21
); );
assert!(matches!(
engine.compile("let x = 0; break;").expect_err("should error"),
ParseError(x, _) if *x == ParseErrorType::LoopBreak
));
assert!(matches!(
engine.compile("let x = 0; if x > 0 { continue; }").expect_err("should error"),
ParseError(x, _) if *x == ParseErrorType::LoopBreak
));
Ok(()) Ok(())
} }

View File

@ -12,6 +12,10 @@ fn test_map_indexing() -> Result<(), Box<EvalAltResult>> {
engine.eval::<INT>(r#"let x = #{a: 1, b: 2, c: 3}; x["b"]"#)?, engine.eval::<INT>(r#"let x = #{a: 1, b: 2, c: 3}; x["b"]"#)?,
2 2
); );
assert_eq!(
engine.eval::<INT>(r#"let x = #{a: 1, b: 2, c: 3,}; x["b"]"#)?,
2
);
assert_eq!( assert_eq!(
engine.eval::<char>( engine.eval::<char>(
r#" r#"

View File

@ -1,5 +1,9 @@
#![cfg(not(feature = "no_module"))] #![cfg(not(feature = "no_module"))]
use rhai::{module_resolvers, Engine, EvalAltResult, Module, Scope, INT}; use rhai::{
module_resolvers, Dynamic, Engine, EvalAltResult, Module, ParseError, ParseErrorType, Scope,
INT,
};
use std::any::TypeId;
#[test] #[test]
fn test_module() { fn test_module() {
@ -18,6 +22,7 @@ fn test_module_sub_module() -> Result<(), Box<EvalAltResult>> {
let mut sub_module2 = Module::new(); let mut sub_module2 = Module::new();
sub_module2.set_var("answer", 41 as INT); sub_module2.set_var("answer", 41 as INT);
let hash_inc = sub_module2.set_fn_1("inc", |x: INT| Ok(x + 1)); let hash_inc = sub_module2.set_fn_1("inc", |x: INT| Ok(x + 1));
sub_module.set_sub_module("universe", sub_module2); sub_module.set_sub_module("universe", sub_module2);
@ -128,7 +133,7 @@ fn test_module_resolver() -> Result<(), Box<EvalAltResult>> {
EvalAltResult::ErrorInFunctionCall(fn_name, _, _) if fn_name == "foo" EvalAltResult::ErrorInFunctionCall(fn_name, _, _) if fn_name == "foo"
)); ));
engine.set_max_modules(0); engine.set_max_modules(1000);
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]
engine.eval::<()>( engine.eval::<()>(
@ -195,6 +200,7 @@ fn test_module_from_ast() -> Result<(), Box<EvalAltResult>> {
let module = Module::eval_ast_as_new(Scope::new(), &ast, &engine)?; let module = Module::eval_ast_as_new(Scope::new(), &ast, &engine)?;
let mut scope = Scope::new(); let mut scope = Scope::new();
scope.push_module("testing", module); scope.push_module("testing", module);
assert_eq!( assert_eq!(
@ -230,3 +236,20 @@ fn test_module_from_ast() -> Result<(), Box<EvalAltResult>> {
Ok(()) Ok(())
} }
#[test]
fn test_module_export() -> Result<(), Box<EvalAltResult>> {
let engine = Engine::new();
assert!(matches!(
engine.compile(r"let x = 10; { export x; }").expect_err("should error"),
ParseError(x, _) if *x == ParseErrorType::WrongExport
));
assert!(matches!(
engine.compile(r"fn abc(x) { export x; }").expect_err("should error"),
ParseError(x, _) if *x == ParseErrorType::WrongExport
));
Ok(())
}

View File

@ -6,7 +6,7 @@ fn test_max_operations() -> Result<(), Box<EvalAltResult>> {
let mut engine = Engine::new(); let mut engine = Engine::new();
engine.set_max_operations(500); engine.set_max_operations(500);
engine.on_progress(|count| { engine.on_progress(|&count| {
if count % 100 == 0 { if count % 100 == 0 {
println!("{}", count); println!("{}", count);
} }
@ -34,7 +34,7 @@ fn test_max_operations_functions() -> Result<(), Box<EvalAltResult>> {
let mut engine = Engine::new(); let mut engine = Engine::new();
engine.set_max_operations(500); engine.set_max_operations(500);
engine.on_progress(|count| { engine.on_progress(|&count| {
if count % 100 == 0 { if count % 100 == 0 {
println!("{}", count); println!("{}", count);
} }
@ -90,7 +90,7 @@ fn test_max_operations_eval() -> Result<(), Box<EvalAltResult>> {
let mut engine = Engine::new(); let mut engine = Engine::new();
engine.set_max_operations(500); engine.set_max_operations(500);
engine.on_progress(|count| { engine.on_progress(|&count| {
if count % 100 == 0 { if count % 100 == 0 {
println!("{}", count); println!("{}", count);
} }
@ -111,3 +111,20 @@ fn test_max_operations_eval() -> Result<(), Box<EvalAltResult>> {
Ok(()) Ok(())
} }
#[test]
fn test_max_operations_progress() -> Result<(), Box<EvalAltResult>> {
let mut engine = Engine::new();
engine.set_max_operations(500);
engine.on_progress(|&count| count < 100);
assert!(matches!(
*engine
.eval::<()>("for x in range(0, 500) {}")
.expect_err("should error"),
EvalAltResult::ErrorTerminated(_)
));
Ok(())
}

View File

@ -3,7 +3,7 @@
use rhai::{Engine, EvalAltResult, OptimizationLevel, INT}; use rhai::{Engine, EvalAltResult, OptimizationLevel, INT};
#[test] #[test]
fn test_optimizer() -> Result<(), Box<EvalAltResult>> { fn test_optimizer_run() -> Result<(), Box<EvalAltResult>> {
fn run_test(engine: &mut Engine) -> Result<(), Box<EvalAltResult>> { fn run_test(engine: &mut Engine) -> Result<(), Box<EvalAltResult>> {
assert_eq!(engine.eval::<INT>(r"if true { 42 } else { 123 }")?, 42); assert_eq!(engine.eval::<INT>(r"if true { 42 } else { 123 }")?, 42);
assert_eq!( assert_eq!(
@ -30,3 +30,27 @@ fn test_optimizer() -> Result<(), Box<EvalAltResult>> {
Ok(()) Ok(())
} }
#[test]
fn test_optimizer_parse() -> Result<(), Box<EvalAltResult>> {
let mut engine = Engine::new();
engine.set_optimization_level(OptimizationLevel::Simple);
let ast = engine.compile("{ const DECISION = false; if DECISION { 42 } }")?;
assert_eq!(
format!("{:?}", ast),
"AST([], <module vars={}, functions=0>)"
);
engine.set_optimization_level(OptimizationLevel::Full);
let ast = engine.compile("if 1 == 2 { 42 }")?;
assert_eq!(
format!("{:?}", ast),
"AST([], <module vars={}, functions=0>)"
);
Ok(())
}

File diff suppressed because one or more lines are too long

View File

@ -1,4 +1,4 @@
use rhai::{Engine, EvalAltResult, INT}; use rhai::{Engine, EvalAltResult, ImmutableString, RegisterFn, INT};
#[test] #[test]
fn test_string() -> Result<(), Box<EvalAltResult>> { fn test_string() -> Result<(), Box<EvalAltResult>> {
@ -23,9 +23,24 @@ fn test_string() -> Result<(), Box<EvalAltResult>> {
assert_eq!(engine.eval::<String>(r#""foo" + 123"#)?, "foo123"); assert_eq!(engine.eval::<String>(r#""foo" + 123"#)?, "foo123");
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
assert_eq!(engine.eval::<String>("(42).to_string()")?, "42"); assert_eq!(engine.eval::<String>("to_string(42)")?, "42");
#[cfg(not(feature = "no_index"))]
assert_eq!(engine.eval::<char>(r#"let y = "hello"; y[1]"#)?, 'e');
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
assert_eq!(engine.eval::<INT>(r#"let y = "hello"; y.len"#)?, 5);
#[cfg(not(feature = "no_object"))]
assert_eq!(
engine.eval::<INT>(r#"let y = "hello"; y.clear(); y.len"#)?,
0
);
assert_eq!(engine.eval::<INT>(r#"let y = "hello"; len(y)"#)?, 5);
#[cfg(not(feature = "no_object"))]
#[cfg(not(feature = "no_index"))]
assert_eq!(engine.eval::<char>(r#"let y = "hello"; y[y.len-1]"#)?, 'o'); assert_eq!(engine.eval::<char>(r#"let y = "hello"; y[y.len-1]"#)?, 'o');
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
@ -139,3 +154,36 @@ fn test_string_substring() -> Result<(), Box<EvalAltResult>> {
Ok(()) Ok(())
} }
#[test]
fn test_string_fn() -> Result<(), Box<EvalAltResult>> {
let mut engine = Engine::new();
engine.register_fn("set_to_x", |ch: &mut char| *ch = 'X');
#[cfg(not(feature = "no_index"))]
#[cfg(not(feature = "no_object"))]
assert_eq!(
engine.eval::<String>(r#"let x="foo"; x[0].set_to_x(); x"#)?,
"Xoo"
);
#[cfg(not(feature = "no_index"))]
assert_eq!(
engine.eval::<String>(r#"let x="foo"; set_to_x(x[0]); x"#)?,
"foo"
);
engine.register_fn("foo1", |s: &str| s.len() as INT);
engine.register_fn("foo2", |s: ImmutableString| s.len() as INT);
engine.register_fn("foo3", |s: String| s.len() as INT);
assert_eq!(engine.eval::<INT>(r#"foo1("hello")"#)?, 5);
assert_eq!(engine.eval::<INT>(r#"foo2("hello")"#)?, 5);
assert!(matches!(
*engine.eval::<INT>(r#"foo3("hello")"#).expect_err("should error"),
EvalAltResult::ErrorFunctionNotFound(err, _) if err == "foo3 (&str | ImmutableString)"
));
Ok(())
}

View File

@ -1,4 +1,5 @@
#![cfg(not(feature = "no_std"))] #![cfg(not(feature = "no_std"))]
#![cfg(not(target_arch = "wasm32"))]
use rhai::{Engine, EvalAltResult, INT}; use rhai::{Engine, EvalAltResult, INT};