Merge pull request #163 from schungx/master
Minor refinements and enhancements, WASM.
This commit is contained in:
commit
c7df158bba
@ -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
393
README.md
@ -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
|
||||||
|
79
RELEASES.md
79
RELEASES.md
@ -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
|
||||||
|
@ -1 +0,0 @@
|
|||||||
theme: jekyll-theme-slate
|
|
@ -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,11 +39,10 @@ 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() {
|
||||||
@ -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();
|
||||||
|
|
||||||
|
@ -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
77
examples/strings.rs
Normal 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!();
|
||||||
|
}
|
||||||
|
}
|
81
src/any.rs
81
src/any.rs
@ -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>(),
|
||||||
}
|
}
|
||||||
|
233
src/api.rs
233
src/api.rs
@ -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));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
995
src/engine.rs
995
src/engine.rs
File diff suppressed because it is too large
Load Diff
232
src/error.rs
232
src/error.rs
@ -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
|
||||||
Ok(())
|
if !self.1.is_none() {
|
||||||
} else {
|
write!(f, " ({})", self.1)?;
|
||||||
write!(f, " ({})", self.1)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -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,9 +99,16 @@ 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.
|
||||||
@ -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),*))
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
|
260
src/module.rs
260
src/module.rs
@ -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;
|
||||||
|
@ -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 {
|
||||||
|
@ -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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -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"))]
|
||||||
|
@ -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));
|
||||||
|
|
||||||
|
@ -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"))]
|
||||||
|
@ -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>>,
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
@ -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()));
|
||||||
|
917
src/parser.rs
917
src/parser.rs
File diff suppressed because it is too large
Load Diff
166
src/result.rs
166
src/result.rs
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
24
src/scope.rs
24
src/scope.rs
@ -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)
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
|
95
src/token.rs
95
src/token.rs
@ -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(),
|
||||||
|
30
src/utils.rs
30
src/utils.rs
@ -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]
|
||||||
|
@ -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]"#)?,
|
||||||
|
@ -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 }",
|
||||||
|
@ -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
259
tests/data_size.rs
Normal 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(())
|
||||||
|
}
|
@ -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(())
|
||||||
|
@ -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(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
20
tests/for.rs
20
tests/for.rs
@ -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]
|
||||||
|
@ -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
|
||||||
|
@ -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(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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(())
|
||||||
|
@ -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(())
|
||||||
}
|
}
|
||||||
|
@ -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#"
|
||||||
|
@ -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(())
|
||||||
|
}
|
||||||
|
@ -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(())
|
||||||
|
}
|
||||||
|
@ -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
@ -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(())
|
||||||
|
}
|
||||||
|
@ -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};
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user