Merge branch 'master' into plugins

This commit is contained in:
Stephen Chung 2020-07-05 18:12:35 +08:00
commit ef2d28d423
42 changed files with 2615 additions and 583 deletions

View File

@ -1,6 +1,6 @@
[package] [package]
name = "rhai" name = "rhai"
version = "0.16.0" version = "0.17.0"
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"
@ -65,5 +65,17 @@ default-features = false
features = ["compile-time-rng"] features = ["compile-time-rng"]
optional = true optional = true
[dependencies.serde]
package = "serde"
version = "1.0.111"
features = ["derive"]
optional = true
[target.'cfg(target_arch = "wasm32")'.dependencies] [target.'cfg(target_arch = "wasm32")'.dependencies]
instant= { version = "0.1.4", features = ["wasm-bindgen"] } # WASM implementation of std::time::Instant instant= { version = "0.1.4", features = ["wasm-bindgen"] } # WASM implementation of std::time::Instant
[package.metadata.docs.rs]
features = ["serde"]
[package.metadata.playground]
features = ["serde"]

View File

@ -26,18 +26,20 @@ Features
* Freely pass Rust variables/constants into a script via an external [`Scope`](https://schungx.github.io/rhai/rust/scope.html). * Freely pass Rust variables/constants into a script via an external [`Scope`](https://schungx.github.io/rhai/rust/scope.html).
* Easily [call a script-defined function](https://schungx.github.io/rhai/engine/call-fn.html) from Rust. * Easily [call a script-defined function](https://schungx.github.io/rhai/engine/call-fn.html) from Rust.
* Fairly low compile-time overhead. * Fairly low compile-time overhead.
* Fairly efficient evaluation (1 million iterations in 0.25 sec on a single core, 2.3 GHz Linux VM). * Fairly efficient evaluation (1 million iterations in 0.3 sec on a single core, 2.3 GHz Linux VM).
* Relatively little `unsafe` code (yes there are some for performance reasons, and most `unsafe` code is limited to * Relatively little `unsafe` code (yes there are some for performance reasons, and most `unsafe` code is limited to
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 - protected against malicious attacks (such as [stack-overflow](https://schungx.github.io/rhai/safety/max-call-stack.html), [over-sized data](https://schungx.github.io/rhai/safety/max-string-size.html), and [runaway scripts](https://schungx.github.io/rhai/safety/max-operations.html) etc.) that may come from untrusted third-party user-land scripts. * Rugged - protected against malicious attacks (such as [stack-overflow](https://schungx.github.io/rhai/safety/max-call-stack.html), [over-sized data](https://schungx.github.io/rhai/safety/max-string-size.html), and [runaway scripts](https://schungx.github.io/rhai/safety/max-operations.html) etc.) that may come from untrusted third-party user-land scripts.
* Track script evaluation [progress](https://schungx.github.io/rhai/safety/progress.html) and manually terminate a script run. * Track script evaluation [progress](https://schungx.github.io/rhai/safety/progress.html) and manually terminate a script run.
* [Function overloading](https://schungx.github.io/rhai/language/overload.html). * [Function overloading](https://schungx.github.io/rhai/language/overload.html).
* [Operator overloading](https://schungx.github.io/rhai/rust/operators.html). * [Operator overloading](https://schungx.github.io/rhai/rust/operators.html) and [custom operators](https://schungx.github.io/rhai/engine/custom-op.html).
* Dynamic dispatch via [function pointers](https://schungx.github.io/rhai/language/fn-ptr.html). * Dynamic dispatch via [function pointers](https://schungx.github.io/rhai/language/fn-ptr.html).
* Some support for [object-oriented programming (OOP)](https://schungx.github.io/rhai/language/oop.html). * Some support for [object-oriented programming (OOP)](https://schungx.github.io/rhai/language/oop.html).
* Organize code base with dynamically-loadable [modules](https://schungx.github.io/rhai/language/modules.html). * Organize code base with dynamically-loadable [modules](https://schungx.github.io/rhai/language/modules.html).
* Serialization/deserialization support via [serde](https://crates.io/crates/serde) (requires the `serde` feature).
* Surgically disable keywords and operators to restrict the language.
* Scripts are [optimized](https://schungx.github.io/rhai/engine/optimize.html) (useful for template-based machine-generated scripts) for repeated evaluations. * Scripts are [optimized](https://schungx.github.io/rhai/engine/optimize.html) (useful for template-based machine-generated scripts) for repeated evaluations.
* Support for [minimal builds](https://schungx.github.io/rhai/start/builds/minimal.html) by excluding unneeded language [features](https://schungx.github.io/rhai/start/features.html). * Support for [minimal builds](https://schungx.github.io/rhai/start/builds/minimal.html) by excluding unneeded language [features](https://schungx.github.io/rhai/start/features.html).

View File

@ -1,11 +1,44 @@
Rhai Release Notes Rhai Release Notes
================== ==================
Version 0.17.0
==============
This version adds:
* [`serde`](https://crates.io/crates/serde) support for working with `Dynamic` values (particularly _object maps_).
* Ability to surgically disable keywords and/or operators in the language.
* Ability to define custom operators (which must be valid identifiers).
Breaking changes
----------------
* `EvalAltResult::ErrorMismatchOutputType` has an extra argument containing the name of the requested type.
New features
------------
* New `serde` feature to allow serializating/deserializating to/from `Dynamic` values using [`serde`](https://crates.io/crates/serde).
This is particularly useful when converting a Rust `struct` to a `Dynamic` _object map_ and back.
* `Engine::disable_symbol` to surgically disable keywords and/or operators.
* `Engine::register_custom_operator` to define a custom operator.
Version 0.16.1
==============
Bug fix release to fix errors when compiling with features.
Version 0.16.0 Version 0.16.0
============== ==============
The major new feature in this version is OOP - well, poor man's OOP, that is. The major new feature in this version is OOP - well, poor man's OOP, that is.
The `README` is officially transferred to [The Rhai Book](https://schungx.github.io/rhai).
An online [Playground](https://alvinhochun.github.io/rhai-demo/) is available.
Breaking changes Breaking changes
---------------- ----------------

View File

@ -96,14 +96,17 @@ The Rhai Scripting Language
9. [Maximum Statement Depth](safety/max-stmt-depth.md) 9. [Maximum Statement Depth](safety/max-stmt-depth.md)
8. [Advanced Topics](advanced.md) 8. [Advanced Topics](advanced.md)
1. [Object-Oriented Programming (OOP)](language/oop.md) 1. [Object-Oriented Programming (OOP)](language/oop.md)
2. [Script Optimization](engine/optimize/index.md) 2. [Serialization/Deserialization of `Dynamic` with `serde`](rust/serde.md)
3. [Script Optimization](engine/optimize/index.md)
1. [Optimization Levels](engine/optimize/optimize-levels.md) 1. [Optimization Levels](engine/optimize/optimize-levels.md)
2. [Re-Optimize an AST](engine/optimize/reoptimize.md) 2. [Re-Optimize an AST](engine/optimize/reoptimize.md)
3. [Eager Function Evaluation](engine/optimize/eager.md) 3. [Eager Function Evaluation](engine/optimize/eager.md)
4. [Side-Effect Considerations](engine/optimize/side-effects.md) 4. [Side-Effect Considerations](engine/optimize/side-effects.md)
5. [Volatility Considerations](engine/optimize/volatility.md) 5. [Volatility Considerations](engine/optimize/volatility.md)
6. [Subtle Semantic Changes](engine/optimize/semantics.md) 6. [Subtle Semantic Changes](engine/optimize/semantics.md)
3. [Eval Statement](language/eval.md) 4. [Disable Keywords and/or Operators](engine/disable.md)
5. [Custom Operators](engine/custom-op.md)
6. [Eval Statement](language/eval.md)
9. [Appendix](appendix/index.md) 9. [Appendix](appendix/index.md)
1. [Keywords](appendix/keywords.md) 1. [Keywords](appendix/keywords.md)
2. [Operators](appendix/operators.md) 2. [Operators](appendix/operators.md)

View File

@ -22,7 +22,7 @@ Fast
* Fairly low compile-time overhead. * Fairly low compile-time overhead.
* Fairly efficient evaluation (1 million iterations in 0.25 sec on a single core, 2.3 GHz Linux VM). * Fairly efficient evaluation (1 million iterations in 0.3 sec on a single core, 2.3 GHz Linux VM).
* 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.
@ -39,6 +39,8 @@ Dynamic
* Some support for [object-oriented programming (OOP)][OOP]. * Some support for [object-oriented programming (OOP)][OOP].
* Serialization/deserialization support via [`serde`].
Safe Safe
---- ----
@ -62,3 +64,7 @@ Flexible
* Support for [minimal builds] by excluding unneeded language [features]. * Support for [minimal builds] by excluding unneeded language [features].
* Supports [most build targets](targets.md) including `no-std` and [WASM]. * Supports [most build targets](targets.md) including `no-std` and [WASM].
* Surgically [disable keywords and operators] to restrict the language.
* [Custom operators].

View File

@ -5,3 +5,8 @@ What is Rhai
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.
This Book is for version {{version}} of Rhai.
For the latest development version, see [here]({{rootUrl}}/vnext/).

View File

@ -3,28 +3,28 @@ Operators
{{#include ../links.md}} {{#include ../links.md}}
| Operator | Description | Binary? | | Operator | Description | Binary? | Binding direction |
| :---------------: | ------------------------------ | :-----: | | :---------------: | ------------------------------ | :-----: | :---------------: |
| `+` | Add | Yes | | `+` | Add | Yes | Left |
| `-` | Subtract, Minus | Yes/No | | `-` | Subtract, Minus | Yes/No | Left |
| `*` | Multiply | Yes | | `*` | Multiply | Yes | Left |
| `/` | Divide | Yes | | `/` | Divide | Yes | Left |
| `%` | Modulo | Yes | | `%` | Modulo | Yes | Left |
| `~` | Power | Yes | | `~` | Power | Yes | Left |
| `>>` | Right bit-shift | Yes | | `>>` | Right bit-shift | Yes | Left |
| `<<` | Left bit-shift | Yes | | `<<` | Left bit-shift | Yes | Left |
| `&` | Bit-wise _And_, Boolean _And_ | Yes | | `&` | Bit-wise _And_, Boolean _And_ | Yes | Left |
| <code>\|</code> | Bit-wise _Or_, Boolean _Or_ | Yes | | <code>\|</code> | Bit-wise _Or_, Boolean _Or_ | Yes | Left |
| `^` | Bit-wise _Xor_ | Yes | | `^` | Bit-wise _Xor_ | Yes | Left |
| `==` | Equals to | Yes | | `==` | Equals to | Yes | Left |
| `~=` | Not equals to | Yes | | `~=` | Not equals to | Yes | Left |
| `>` | Greater than | Yes | | `>` | Greater than | Yes | Left |
| `>=` | Greater than or equals to | Yes | | `>=` | Greater than or equals to | Yes | Left |
| `<` | Less than | Yes | | `<` | Less than | Yes | Left |
| `<=` | Less than or equals to | Yes | | `<=` | Less than or equals to | Yes | Left |
| `>=` | Greater than or equals to | Yes | | `>=` | Greater than or equals to | Yes | Left |
| `&&` | Boolean _And_ (short-circuits) | Yes | | `&&` | Boolean _And_ (short-circuits) | Yes | Left |
| <code>\|\|</code> | Boolean _Or_ (short-circuits) | Yes | | <code>\|\|</code> | Boolean _Or_ (short-circuits) | Yes | Left |
| `!` | Boolean _Not_ | No | | `!` | Boolean _Not_ | No | Left |
| `[` .. `]` | Indexing | Yes | | `[` .. `]` | Indexing | Yes | Right |
| `.` | Property access, Method call | Yes | | `.` | Property access, Method call | Yes | Right |

View File

@ -1,5 +1,6 @@
{ {
"version": "0.16.0", "version": "0.17.0",
"rootUrl": "", "rootUrl": "",
"rootUrlX": "/rhai" "rootUrlX": "/rhai",
"rootUrlXX": "/rhai/vnext"
} }

105
doc/src/engine/custom-op.md Normal file
View File

@ -0,0 +1,105 @@
Custom Operators
================
{{#include ../links.md}}
For use as a DSL (Domain-Specific Languages), it is sometimes more convenient to augment Rhai with
customized operators performing specific logic.
`Engine::register_custom_operator` registers a keyword as a custom operator.
Example
-------
```rust
use rhai::{Engine, RegisterFn};
let mut engine = Engine::new();
// Register a custom operator called 'foo' and give it
// a precedence of 140 (i.e. between +|- and *|/)
engine.register_custom_operator("foo", 140).unwrap();
// Register the implementation of the customer operator as a function
engine.register_fn("foo", |x: i64, y: i64| (x * y) - (x + y));
// The custom operator can be used in expressions
let result = engine.eval_expression::<i64>("1 + 2 * 3 foo 4 - 5 / 6")?;
// ^ custom operator
// The above is equivalent to: 1 + ((2 * 3) foo 4) - (5 / 6)
result == 15;
```
Alternatives to a Custom Operator
--------------------------------
Custom operators are merely _syntactic sugar_. They map directly to registered functions.
Therefore, the following are equivalent (assuming `foo` has been registered as a custom operator):
```rust
1 + 2 * 3 foo 4 - 5 / 6 // use custom operator
1 + foo(2 * 3, 4) - 5 / 6 // use function call
```
A script using custom operators can always be pre-processed, via a pre-processor application,
into a syntax that uses the corresponding function calls.
Using `Engine::register_custom_operator` merely enables a convenient short-cut.
Must Follow Variable Naming
--------------------------
All custom operators must be _identifiers_ that follow the same naming rules as [variables].
```rust
engine.register_custom_operator("foo", 20); // 'foo' is a valid custom operator
engine.register_custom_operator("=>", 30); // <- error: '=>' is not a valid custom operator
```
Binary Operators Only
---------------------
All custom operators must be _binary_ (i.e. they take two operands).
_Unary_ custom operators are not supported.
```rust
engine.register_custom_operator("foo", 140).unwrap();
engine.register_fn("foo", |x: i64| x * x);
engine.eval::<i64>("1 + 2 * 3 foo 4 - 5 / 6")?; // error: function 'foo (i64, i64)' not found
```
Operator Precedence
-------------------
All operators in Rhai has a _precedence_ indicating how tightly they bind.
The following _precedence table_ show the built-in precedence of standard Rhai operators:
| Category | Operators | Precedence (0-255) |
| ------------------- | :-------------------------------------------------------------------------------------: | :----------------: |
| Assignments | `=`, `+=`, `-=`, `*=`, `/=`, `~=`, `%=`,<br/>`<<=`, `>>=`, `&=`, <code>\|=</code>, `^=` | 0 |
| Logic and bit masks | <code>\|\|</code>, <code>\|</code>, `^` | 30 |
| Logic and bit masks | `&`, `&&` | 60 |
| Comparisons | `==`, `!=`, `>`, `>=`, `<`, `<=` | 90 |
| | `in` | 110 |
| Arithmetic | `+`, `-` | 130 |
| Arithmetic | `*`, `/`, `~` | 160 |
| Bit-shifts | `<<`, `>>` | 190 |
| Arithmetic | `%` | 210 |
| Object | `.` _(binds to right)_ | 240 |
| _Others_ | | 0 |
A higher precedence binds more tightly than a lower precedence, so `*` and `/` binds before `+` and `-` etc.
When registering a custom operator, the operator's precedence must also be provided.

28
doc/src/engine/disable.md Normal file
View File

@ -0,0 +1,28 @@
Disable Certain Keywords and/or Operators
========================================
{{#include ../links.md}}
For certain embedded usage, it is sometimes necessary to restrict the language to a strict subset of Rhai
to prevent usage of certain language features.
Rhai supports surgically disabling a keyword or operator via the `Engine::disable_symbol` method.
```rust
use rhai::Engine;
let mut engine = Engine::new();
engine.disable_symbol("if"); // disable the 'if' keyword
engine.disable_symbol("+="); // disable the '+=' operator
// The following all return parse errors.
engine.compile("let x = if true { 42 } else { 0 };")?;
// ^ missing ';' after statement end
// ^ 'if' is parsed as a variable name
engine.compile("let x = 40 + 2; x += 1;")?;
// ^ '+=' is not recognized as an operator
// ^ other operators are not affected
```

View File

@ -29,6 +29,7 @@
[package]: {{rootUrl}}/rust/packages/index.md [package]: {{rootUrl}}/rust/packages/index.md
[packages]: {{rootUrl}}/rust/packages/index.md [packages]: {{rootUrl}}/rust/packages/index.md
[`Scope`]: {{rootUrl}}/rust/scope.md [`Scope`]: {{rootUrl}}/rust/scope.md
[`serde`]: {{rootUrl}}/rust/serde.md
[`type_of()`]: {{rootUrl}}/language/type-of.md [`type_of()`]: {{rootUrl}}/language/type-of.md
[`to_string()`]: {{rootUrl}}/language/values-and-types.md [`to_string()`]: {{rootUrl}}/language/values-and-types.md
@ -101,3 +102,7 @@
[`OptimizationLevel::Full`]: {{rootUrl}}/engine/optimize/optimize-levels.md [`OptimizationLevel::Full`]: {{rootUrl}}/engine/optimize/optimize-levels.md
[`OptimizationLevel::Simple`]: {{rootUrl}}/engine/optimize/optimize-levels.md [`OptimizationLevel::Simple`]: {{rootUrl}}/engine/optimize/optimize-levels.md
[`OptimizationLevel::None`]: {{rootUrl}}/engine/optimize/optimize-levels.md [`OptimizationLevel::None`]: {{rootUrl}}/engine/optimize/optimize-levels.md
[disable keywords and operators]: {{rootUrl}}/engine/disable.md
[custom operator]: {{rootUrl}}/engine/custom-op.md
[custom operators]: {{rootUrl}}/engine/custom-op.md

104
doc/src/rust/serde.md Normal file
View File

@ -0,0 +1,104 @@
Serialization and Deserialization of `Dynamic` with `serde`
=========================================================
{{#include ../links.md}}
Rhai's [`Dynamic`] type supports serialization and deserialization by [`serde`](https://crates.io/crates/serde)
via the [`serde`][features] feature.
A [`Dynamic`] can be seamlessly converted to and from a type that implements `serde::Serialize` and/or
`serde::Deserialize`.
Serialization
-------------
The function `rhai::ser::to_dynamic` automatically converts any Rust type that implements `serde::Serialize`
into a [`Dynamic`].
This is usually not necessary because using [`Dynamic::from`][`Dynamic`] is much easier and is essentially
the same thing. The only difference is treatment for integer values. `Dynamic::from` will keep the different
integer types intact, while `rhai::ser::to_dynamic` will convert them all into [`INT`][standard types]
(i.e. the system integer type which is `i64` or `i32` depending on the [`only_i32`] feature).
In particular, Rust `struct`'s (or any type that is marked as a `serde` map) are converted into [object maps]
while Rust `Vec`'s (or any type that is marked as a `serde` sequence) are converted into [arrays].
While it is also simple to serialize a Rust type to `JSON` via `serde`,
then use [`Engine::parse_json`]({{rootUrl}}/language/json.md) to convert it into an [object map],
`rhai::ser::to_dynamic` serializes it to [`Dynamic`] directly via `serde` without going through the `JSON` step.
```rust
use rhai::{Dynamic, Map};
use rhai::ser::to_dynamic;
#[derive(Debug, serde::Serialize)]
struct Point {
x: f64,
y: f64
}
#[derive(Debug, serde::Serialize)]
struct MyStruct {
a: i64,
b: Vec<String>,
c: bool,
d: Point
}
let x = MyStruct {
a: 42,
b: vec![ "hello".into(), "world".into() ],
c: true,
d: Point { x: 123.456, y: 999.0 }
};
// Convert the 'MyStruct' into a 'Dynamic'
let map: Dynamic = to_dynamic(x);
map.is::<Map>() == true;
```
Deserialization
---------------
The function `rhai::de::from_dynamic` automatically converts a [`Dynamic`] value into any Rust type
that implements `serde::Deserialize`.
In particular, [object maps] are converted into Rust `struct`'s (or any type that is marked as
a `serde` map) while [arrays] are converted into Rust `Vec`'s (or any type that is marked
as a `serde` sequence).
```rust
use rhai::{Engine, Dynamic};
use rhai::de::from_dynamic;
#[derive(Debug, serde::Deserialize)]
struct Point {
x: f64,
y: f64
}
#[derive(Debug, serde::Deserialize)]
struct MyStruct {
a: i64,
b: Vec<String>,
c: bool,
d: Point
}
let engine = Engine::new();
let result: Dynamic = engine.eval(r#"
#{
a: 42,
b: [ "hello", "world" ],
c: true,
d: #{ x: 123.456, y: 999.0 }
}
"#)?;
// Convert the 'Dynamic' object map into 'MyStruct'
let x: MyStruct = from_dynamic(&result)?;
```

View File

@ -6,13 +6,14 @@ Rust Examples
A number of examples can be found in the `examples` folder: A number of examples can be found in the `examples` folder:
| Example | Description | | Example | Description |
| ---------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------- | | ---------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------- |
| [`arrays_and_structs`](https://github.com/jonathandturner/rhai/tree/master/examples/arrays_and_structs.rs) | Shows how to register a custom Rust type and using [arrays] on it. | | [`arrays_and_structs`](https://github.com/jonathandturner/rhai/tree/master/examples/arrays_and_structs.rs) | Shows how to register a custom Rust type and using [arrays] on it. |
| [`custom_types_and_methods`](https://github.com/jonathandturner/rhai/tree/master/examples/custom_types_and_methods.rs) | Shows how to register a custom Rust type and methods for it. | | [`custom_types_and_methods`](https://github.com/jonathandturner/rhai/tree/master/examples/custom_types_and_methods.rs) | Shows how to register a custom Rust type and methods for it. |
| [`hello`](https://github.com/jonathandturner/rhai/tree/master/examples/hello.rs) | Simple example that evaluates an expression and prints the result. | | [`hello`](https://github.com/jonathandturner/rhai/tree/master/examples/hello.rs) | Simple example that evaluates an expression and prints the result. |
| [`no_std`](https://github.com/jonathandturner/rhai/tree/master/examples/no_std.rs) | Example to test out `no-std` builds. | | [`no_std`](https://github.com/jonathandturner/rhai/tree/master/examples/no_std.rs) | Example to test out `no-std` builds. |
| [`reuse_scope`](https://github.com/jonathandturner/rhai/tree/master/examples/reuse_scope.rs) | Evaluates two pieces of code in separate runs, but using a common [`Scope`]. | | [`reuse_scope`](https://github.com/jonathandturner/rhai/tree/master/examples/reuse_scope.rs) | Evaluates two pieces of code in separate runs, but using a common [`Scope`]. |
| [`rhai_runner`](https://github.com/jonathandturner/rhai/tree/master/examples/rhai_runner.rs) | Runs each filename passed to it as a Rhai script. | | [`rhai_runner`](https://github.com/jonathandturner/rhai/tree/master/examples/rhai_runner.rs) | Runs each filename passed to it as a Rhai script. |
| [`serde`](https://github.com/jonathandturner/rhai/tree/master/examples/serde.rs) | Example to serialize and deserialize Rust types with [`serde`](https://crates.io/crates/serde).<br/>The [`serde`] feature is required to run. |
| [`simple_fn`](https://github.com/jonathandturner/rhai/tree/master/examples/simple_fn.rs) | Shows how to register a simple function. | | [`simple_fn`](https://github.com/jonathandturner/rhai/tree/master/examples/simple_fn.rs) | Shows how to register a simple function. |
| [`strings`](https://github.com/jonathandturner/rhai/tree/master/examples/strings.rs) | Shows different ways to register functions taking string arguments. | | [`strings`](https://github.com/jonathandturner/rhai/tree/master/examples/strings.rs) | Shows different ways to register functions taking string arguments. |
| [`repl`](https://github.com/jonathandturner/rhai/tree/master/examples/repl.rs) | A simple REPL, interactively evaluate statements from stdin. | | [`repl`](https://github.com/jonathandturner/rhai/tree/master/examples/repl.rs) | A simple REPL, interactively evaluate statements from stdin. |

View File

@ -24,6 +24,7 @@ more control over what a script can (or cannot) do.
| `no_function` | Disable script-defined [functions]. | | `no_function` | Disable script-defined [functions]. |
| `no_module` | Disable loading external [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. |
| `serde` | Enable serialization/deserialization via [`serde`]. Notice that the [`serde`](https://crates.io/crates/serde) crate will be pulled in together with its dependencies. |
| `internals` | Expose internal data structures (e.g. [`AST`] nodes). Beware that Rhai internals are volatile and may change from version to version. | | `internals` | Expose internal data structures (e.g. [`AST`] nodes). Beware that Rhai internals are volatile and may change from version to version. |

76
examples/serde.rs Normal file
View File

@ -0,0 +1,76 @@
#[cfg(not(feature = "serde"))]
fn main() {
println!(r#"This example requires the "serde" feature which is not enabled by default."#);
println!("Try: cargo run --features serde --example serde");
}
#[cfg(feature = "serde")]
fn main() {
example::ser();
println!();
example::de();
}
#[cfg(feature = "serde")]
mod example {
use rhai::{de::from_dynamic, ser::to_dynamic};
use rhai::{Dynamic, Engine, Map};
use serde::{Deserialize, Serialize};
#[derive(Debug, Serialize, Deserialize)]
struct Point {
x: f64,
y: f64,
}
#[derive(Debug, Serialize, Deserialize)]
struct MyStruct {
a: i64,
b: Vec<String>,
c: bool,
d: Point,
}
pub fn ser() {
let x = MyStruct {
a: 42,
b: vec!["hello".into(), "world".into()],
c: true,
d: Point {
x: 123.456,
y: 999.0,
},
};
println!("Source struct: {:#?}", x);
// Convert the 'MyStruct' into a 'Dynamic'
let map: Dynamic = to_dynamic(x).unwrap();
assert!(map.is::<Map>());
println!("Serialized to Dynamic: {:#?}", map);
}
pub fn de() {
let engine = Engine::new();
let result: Dynamic = engine
.eval(
r#"
#{
a: 42,
b: [ "hello", "world" ],
c: true,
d: #{ x: 123.456, y: 999.0 }
}
"#,
)
.unwrap();
println!("Source Dynamic: {:#?}", result);
// Convert the 'Dynamic' object map into 'MyStruct'
let x: MyStruct = from_dynamic(&result).unwrap();
println!("Deserialized to struct: {:#?}", x);
}
}

View File

@ -196,13 +196,38 @@ impl Dynamic {
Union::FnPtr(_) => "Fn", Union::FnPtr(_) => "Fn",
#[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(),
} }
} }
} }
/// Map the name of a standard type into a friendly form.
pub(crate) fn map_std_type_name(name: &str) -> &str {
if name == type_name::<String>() {
"string"
} else if name == type_name::<ImmutableString>() {
"string"
} else if name == type_name::<&str>() {
"string"
} else if name == type_name::<FnPtr>() {
"Fn"
} else if name == type_name::<Instant>() {
"timestamp"
} else {
#[cfg(not(feature = "no_index"))]
if name == type_name::<Array>() {
return "array";
}
#[cfg(not(feature = "no_object"))]
if name == type_name::<Map>() {
return "map";
}
name
}
}
impl fmt::Display for Dynamic { 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 {
@ -220,7 +245,6 @@ impl fmt::Display for Dynamic {
Union::FnPtr(value) => fmt::Display::fmt(value, f), Union::FnPtr(value) => fmt::Display::fmt(value, f),
#[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, "?"),
} }
@ -244,7 +268,6 @@ impl fmt::Debug for Dynamic {
Union::FnPtr(value) => fmt::Display::fmt(value, f), Union::FnPtr(value) => fmt::Display::fmt(value, f),
#[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>"),
} }
@ -323,11 +346,9 @@ impl Dynamic {
} }
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
{
if let Some(result) = <dyn Any>::downcast_ref::<FLOAT>(&value) { if let Some(result) = <dyn Any>::downcast_ref::<FLOAT>(&value) {
return result.clone().into(); return result.clone().into();
} }
}
let mut boxed = Box::new(value); let mut boxed = Box::new(value);

View File

@ -1,10 +1,7 @@
//! 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::{ use crate::engine::{make_getter, make_setter, Engine, Imports, State, FN_IDX_GET, FN_IDX_SET};
get_script_function_by_signature, make_getter, make_setter, Engine, Imports, State, FN_IDX_GET,
FN_IDX_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};
@ -19,6 +16,9 @@ use crate::utils::StaticVec;
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
use crate::engine::Map; use crate::engine::Map;
#[cfg(not(feature = "no_function"))]
use crate::engine::get_script_function_by_signature;
use crate::stdlib::{ use crate::stdlib::{
any::{type_name, TypeId}, any::{type_name, TypeId},
boxed::Box, boxed::Box,
@ -118,8 +118,13 @@ impl Engine {
/// ``` /// ```
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
pub fn register_type_with_name<T: Variant + Clone>(&mut self, name: &str) { pub fn register_type_with_name<T: Variant + Clone>(&mut self, name: &str) {
if self.type_names.is_none() {
self.type_names = Some(Default::default());
}
// Add the pretty-print type name into the map // Add the pretty-print type name into the map
self.type_names self.type_names
.as_mut()
.unwrap()
.insert(type_name::<T>().to_string(), name.to_string()); .insert(type_name::<T>().to_string(), name.to_string());
} }
@ -548,7 +553,7 @@ impl Engine {
scripts: &[&str], scripts: &[&str],
optimization_level: OptimizationLevel, optimization_level: OptimizationLevel,
) -> Result<AST, ParseError> { ) -> Result<AST, ParseError> {
let stream = lex(scripts, self.max_string_size); let stream = lex(scripts, self);
self.parse(&mut stream.peekable(), scope, optimization_level) self.parse(&mut stream.peekable(), scope, optimization_level)
} }
@ -673,7 +678,7 @@ 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, self.max_string_size); let stream = lex(&scripts, self);
let ast = let ast =
self.parse_global_expr(&mut stream.peekable(), &scope, OptimizationLevel::None)?; self.parse_global_expr(&mut stream.peekable(), &scope, OptimizationLevel::None)?;
@ -754,7 +759,7 @@ impl Engine {
script: &str, script: &str,
) -> Result<AST, ParseError> { ) -> Result<AST, ParseError> {
let scripts = [script]; let scripts = [script];
let stream = lex(&scripts, self.max_string_size); let stream = lex(&scripts, self);
{ {
let mut peekable = stream.peekable(); let mut peekable = stream.peekable();
self.parse_global_expr(&mut peekable, scope, self.optimization_level) self.parse_global_expr(&mut peekable, scope, self.optimization_level)
@ -909,7 +914,7 @@ impl Engine {
script: &str, script: &str,
) -> Result<T, Box<EvalAltResult>> { ) -> Result<T, Box<EvalAltResult>> {
let scripts = [script]; let scripts = [script];
let stream = lex(&scripts, self.max_string_size); let stream = lex(&scripts, self);
// No need to optimize a lone expression // No need to optimize a lone expression
let ast = self.parse_global_expr(&mut stream.peekable(), scope, OptimizationLevel::None)?; let ast = self.parse_global_expr(&mut stream.peekable(), scope, OptimizationLevel::None)?;
@ -976,11 +981,12 @@ impl Engine {
let mut mods = Imports::new(); let mut mods = Imports::new();
let (result, _) = self.eval_ast_with_scope_raw(scope, &mut mods, ast)?; let (result, _) = self.eval_ast_with_scope_raw(scope, &mut mods, ast)?;
let return_type = self.map_type_name(result.type_name()); let typ = self.map_type_name(result.type_name());
return result.try_cast::<T>().ok_or_else(|| { return result.try_cast::<T>().ok_or_else(|| {
Box::new(EvalAltResult::ErrorMismatchOutputType( Box::new(EvalAltResult::ErrorMismatchOutputType(
return_type.into(), self.map_type_name(type_name::<T>()).into(),
typ.into(),
Position::none(), Position::none(),
)) ))
}); });
@ -1041,7 +1047,7 @@ impl Engine {
script: &str, script: &str,
) -> Result<(), Box<EvalAltResult>> { ) -> Result<(), Box<EvalAltResult>> {
let scripts = [script]; let scripts = [script];
let stream = lex(&scripts, self.max_string_size); let stream = lex(&scripts, self);
let ast = self.parse(&mut stream.peekable(), scope, self.optimization_level)?; let ast = self.parse(&mut stream.peekable(), scope, self.optimization_level)?;
self.consume_ast_with_scope(scope, &ast) self.consume_ast_with_scope(scope, &ast)
} }
@ -1123,11 +1129,12 @@ impl Engine {
let mut arg_values = args.into_vec(); let mut arg_values = args.into_vec();
let result = self.call_fn_dynamic_raw(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 typ = self.map_type_name(result.type_name());
return result.try_cast().ok_or_else(|| { return result.try_cast().ok_or_else(|| {
Box::new(EvalAltResult::ErrorMismatchOutputType( Box::new(EvalAltResult::ErrorMismatchOutputType(
return_type.into(), self.map_type_name(type_name::<T>()).into(),
typ.into(),
Position::none(), Position::none(),
)) ))
}); });
@ -1187,6 +1194,7 @@ impl Engine {
/// This is to avoid unnecessarily cloning the arguments. /// This is to avoid unnecessarily cloning the arguments.
/// Do not use the arguments after this call. If they are needed afterwards, /// Do not use the arguments after this call. If they are needed afterwards,
/// clone them _before_ calling this function. /// clone them _before_ calling this function.
#[cfg(not(feature = "no_function"))]
pub(crate) fn call_fn_dynamic_raw( pub(crate) fn call_fn_dynamic_raw(
&self, &self,
scope: &mut Scope, scope: &mut Scope,
@ -1238,6 +1246,7 @@ impl Engine {
mut ast: AST, mut ast: AST,
optimization_level: OptimizationLevel, optimization_level: OptimizationLevel,
) -> AST { ) -> AST {
#[cfg(not(feature = "no_function"))]
let lib = ast let lib = ast
.lib() .lib()
.iter_fn() .iter_fn()
@ -1245,6 +1254,9 @@ impl Engine {
.map(|(_, _, _, f)| f.get_fn_def().clone()) .map(|(_, _, _, f)| f.get_fn_def().clone())
.collect(); .collect();
#[cfg(feature = "no_function")]
let lib = Default::default();
let stmt = mem::take(ast.statements_mut()); let stmt = mem::take(ast.statements_mut());
optimize_into_ast(self, scope, stmt, lib, optimization_level) optimize_into_ast(self, scope, stmt, lib, optimization_level)
} }

View File

@ -1,12 +1,12 @@
//! Main module defining the script evaluation `Engine`. //! Main module defining the script evaluation `Engine`.
use crate::any::{Dynamic, Union, Variant}; use crate::any::{map_std_type_name, Dynamic, Union, Variant};
use crate::calc_fn_hash; use crate::calc_fn_hash;
use crate::error::ParseErrorType; use crate::error::ParseErrorType;
use crate::fn_native::{CallableFunction, Callback, FnCallArgs, FnPtr}; use crate::fn_native::{CallableFunction, Callback, FnCallArgs, FnPtr};
use crate::module::{resolvers, Module, ModuleRef, ModuleResolver}; use crate::module::{resolvers, Module, ModuleRef, ModuleResolver};
use crate::optimize::OptimizationLevel; use crate::optimize::OptimizationLevel;
use crate::packages::{Package, PackageLibrary, PackagesCollection, StandardPackage}; use crate::packages::{Package, PackagesCollection, StandardPackage};
use crate::parser::{Expr, FnAccess, ImmutableString, ReturnType, ScriptFnDef, Stmt, AST, INT}; use crate::parser::{Expr, FnAccess, ImmutableString, ReturnType, ScriptFnDef, Stmt, AST, INT};
use crate::r#unsafe::unsafe_cast_var_name_to_lifetime; use crate::r#unsafe::unsafe_cast_var_name_to_lifetime;
use crate::result::EvalAltResult; use crate::result::EvalAltResult;
@ -18,10 +18,10 @@ use crate::utils::StaticVec;
use crate::parser::FLOAT; use crate::parser::FLOAT;
use crate::stdlib::{ use crate::stdlib::{
any::TypeId, any::{type_name, TypeId},
borrow::Cow, borrow::Cow,
boxed::Box, boxed::Box,
collections::HashMap, collections::{HashMap, HashSet},
format, format,
iter::{empty, once}, iter::{empty, once},
mem, mem,
@ -35,7 +35,7 @@ use crate::stdlib::{
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
pub type Array = Vec<Dynamic>; pub type Array = Vec<Dynamic>;
/// Hash map of `Dynamic` values with `String` keys. /// Hash map of `Dynamic` values with `ImmutableString` keys.
/// ///
/// Not available under the `no_object` feature. /// Not available under the `no_object` feature.
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
@ -216,6 +216,7 @@ impl State {
} }
/// Get a script-defined function definition from a module. /// Get a script-defined function definition from a module.
#[cfg(not(feature = "no_function"))]
pub fn get_script_function_by_signature<'a>( pub fn get_script_function_by_signature<'a>(
module: &'a Module, module: &'a Module,
name: &str, name: &str,
@ -265,7 +266,12 @@ pub struct Engine {
pub(crate) module_resolver: Option<Box<dyn ModuleResolver>>, pub(crate) module_resolver: Option<Box<dyn ModuleResolver>>,
/// A hashmap mapping type names to pretty-print names. /// A hashmap mapping type names to pretty-print names.
pub(crate) type_names: HashMap<String, String>, pub(crate) type_names: Option<HashMap<String, String>>,
/// A hashset containing symbols to disable.
pub(crate) disabled_symbols: Option<HashSet<String>>,
/// A hashset containing custom keywords and precedence to recognize.
pub(crate) custom_keywords: Option<HashMap<String, u8>>,
/// Callback closure for implementing the `print` command. /// Callback closure for implementing the `print` command.
pub(crate) print: Callback<str, ()>, pub(crate) print: Callback<str, ()>,
@ -312,7 +318,9 @@ impl Default for Engine {
#[cfg(any(feature = "no_module", feature = "no_std", target_arch = "wasm32",))] #[cfg(any(feature = "no_module", feature = "no_std", target_arch = "wasm32",))]
module_resolver: None, module_resolver: None,
type_names: Default::default(), type_names: None,
disabled_symbols: None,
custom_keywords: None,
// default print/debug implementations // default print/debug implementations
print: Box::new(default_print), print: Box::new(default_print),
@ -352,17 +360,14 @@ pub fn make_getter(id: &str) -> String {
/// Extract the property name from a getter function name. /// Extract the property name from a getter function name.
fn extract_prop_from_getter(fn_name: &str) -> Option<&str> { fn extract_prop_from_getter(fn_name: &str) -> Option<&str> {
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
{
if fn_name.starts_with(FN_GET) { if fn_name.starts_with(FN_GET) {
Some(&fn_name[FN_GET.len()..]) Some(&fn_name[FN_GET.len()..])
} else { } else {
None None
} }
}
#[cfg(feature = "no_object")] #[cfg(feature = "no_object")]
{
None None
}
} }
/// Make setter function /// Make setter function
@ -373,17 +378,14 @@ pub fn make_setter(id: &str) -> String {
/// Extract the property name from a setter function name. /// Extract the property name from a setter function name.
fn extract_prop_from_setter(fn_name: &str) -> Option<&str> { fn extract_prop_from_setter(fn_name: &str) -> Option<&str> {
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
{
if fn_name.starts_with(FN_SET) { if fn_name.starts_with(FN_SET) {
Some(&fn_name[FN_SET.len()..]) Some(&fn_name[FN_SET.len()..])
} else { } else {
None None
} }
}
#[cfg(feature = "no_object")] #[cfg(feature = "no_object")]
{
None None
}
} }
/// Print/debug to stdout /// Print/debug to stdout
@ -497,7 +499,10 @@ impl Engine {
global_module: Default::default(), global_module: Default::default(),
module_resolver: None, module_resolver: None,
type_names: Default::default(), type_names: None,
disabled_symbols: None,
custom_keywords: None,
print: Box::new(|_| {}), print: Box::new(|_| {}),
debug: Box::new(|_| {}), debug: Box::new(|_| {}),
progress: None, progress: None,
@ -519,158 +524,6 @@ impl Engine {
} }
} }
/// Load a new package into the `Engine`.
///
/// When searching for functions, packages loaded later are preferred.
/// In other words, loaded packages are searched in reverse order.
pub fn load_package(&mut self, package: PackageLibrary) {
// Push the package to the top - packages are searched in reverse order
self.packages.push(package);
}
/// Load a new package into the `Engine`.
///
/// When searching for functions, packages loaded later are preferred.
/// In other words, loaded packages are searched in reverse order.
pub fn load_packages(&mut self, package: PackageLibrary) {
// Push the package to the top - packages are searched in reverse order
self.packages.push(package);
}
/// Control whether and how the `Engine` will optimize an AST after compilation.
///
/// Not available under the `no_optimize` feature.
#[cfg(not(feature = "no_optimize"))]
pub fn set_optimization_level(&mut self, optimization_level: OptimizationLevel) {
self.optimization_level = optimization_level
}
/// The current optimization level.
/// It controls whether and how the `Engine` will optimize an AST after compilation.
///
/// Not available under the `no_optimize` feature.
#[cfg(not(feature = "no_optimize"))]
pub fn optimization_level(&self) -> OptimizationLevel {
self.optimization_level
}
/// Set the maximum levels of function calls allowed for a script in order to avoid
/// infinite recursion and stack overflows.
#[cfg(not(feature = "unchecked"))]
pub fn set_max_call_levels(&mut self, levels: usize) {
self.max_call_stack_depth = levels
}
/// The maximum levels of function calls allowed for a script.
#[cfg(not(feature = "unchecked"))]
pub fn max_call_levels(&self) -> usize {
self.max_call_stack_depth
}
/// Set the maximum number of operations allowed for a script to run to avoid
/// consuming too much resources (0 for unlimited).
#[cfg(not(feature = "unchecked"))]
pub fn set_max_operations(&mut self, operations: u64) {
self.max_operations = if operations == u64::MAX {
0
} else {
operations
};
}
/// The maximum number of operations allowed for a script to run (0 for unlimited).
#[cfg(not(feature = "unchecked"))]
pub fn max_operations(&self) -> u64 {
self.max_operations
}
/// Set the maximum number of imported modules allowed for a script.
#[cfg(not(feature = "unchecked"))]
pub fn set_max_modules(&mut self, modules: usize) {
self.max_modules = modules;
}
/// The maximum number of imported modules allowed for a script.
#[cfg(not(feature = "unchecked"))]
pub fn max_modules(&self) -> usize {
self.max_modules
}
/// Set the depth limits for expressions (0 for unlimited).
#[cfg(not(feature = "unchecked"))]
pub fn set_max_expr_depths(&mut self, max_expr_depth: usize, max_function_expr_depth: usize) {
self.max_expr_depth = if max_expr_depth == usize::MAX {
0
} else {
max_expr_depth
};
self.max_function_expr_depth = if max_function_expr_depth == usize::MAX {
0
} else {
max_function_expr_depth
};
}
/// The depth limit for expressions (0 for unlimited).
#[cfg(not(feature = "unchecked"))]
pub fn max_expr_depth(&self) -> usize {
self.max_expr_depth
}
/// The depth limit for expressions in functions (0 for unlimited).
#[cfg(not(feature = "unchecked"))]
pub fn max_function_expr_depth(&self) -> usize {
self.max_function_expr_depth
}
/// Set the maximum length of strings (0 for unlimited).
#[cfg(not(feature = "unchecked"))]
pub fn set_max_string_size(&mut self, max_size: usize) {
self.max_string_size = if max_size == usize::MAX { 0 } else { max_size };
}
/// The maximum length of strings (0 for unlimited).
#[cfg(not(feature = "unchecked"))]
pub fn max_string_size(&self) -> usize {
self.max_string_size
}
/// Set the maximum length of arrays (0 for unlimited).
#[cfg(not(feature = "unchecked"))]
#[cfg(not(feature = "no_index"))]
pub fn set_max_array_size(&mut self, max_size: usize) {
self.max_array_size = if max_size == usize::MAX { 0 } else { max_size };
}
/// The maximum length of arrays (0 for unlimited).
#[cfg(not(feature = "unchecked"))]
#[cfg(not(feature = "no_index"))]
pub fn max_array_size(&self) -> usize {
self.max_array_size
}
/// Set the maximum length of object maps (0 for unlimited).
#[cfg(not(feature = "unchecked"))]
#[cfg(not(feature = "no_object"))]
pub fn set_max_map_size(&mut self, max_size: usize) {
self.max_map_size = if max_size == usize::MAX { 0 } else { max_size };
}
/// The maximum length of object maps (0 for unlimited).
#[cfg(not(feature = "unchecked"))]
#[cfg(not(feature = "no_object"))]
pub fn max_map_size(&self) -> usize {
self.max_map_size
}
/// Set the module resolution service used by the `Engine`.
///
/// Not available under the `no_module` feature.
#[cfg(not(feature = "no_module"))]
pub fn set_module_resolver(&mut self, resolver: Option<impl ModuleResolver + 'static>) {
self.module_resolver = resolver.map(|f| Box::new(f) as Box<dyn ModuleResolver>);
}
/// Universal method for calling functions either registered with the `Engine` or written in Rhai. /// Universal method for calling functions either registered with the `Engine` or written in Rhai.
/// Position in `EvalAltResult` is None and must be set afterwards. /// Position in `EvalAltResult` is None and must be set afterwards.
/// ///
@ -700,13 +553,11 @@ impl Engine {
// Check for stack overflow // Check for stack overflow
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]
#[cfg(not(feature = "unchecked"))] #[cfg(not(feature = "unchecked"))]
{
if level > self.max_call_stack_depth { if level > self.max_call_stack_depth {
return Err(Box::new( return Err(Box::new(
EvalAltResult::ErrorStackOverflow(Position::none()), EvalAltResult::ErrorStackOverflow(Position::none()),
)); ));
} }
}
let mut this_copy: Dynamic = Default::default(); let mut this_copy: Dynamic = Default::default();
let mut old_this_ptr: Option<&mut Dynamic> = None; let mut old_this_ptr: Option<&mut Dynamic> = None;
@ -767,22 +618,23 @@ impl Engine {
.or_else(|| self.packages.get_fn(hash_fn)); .or_else(|| self.packages.get_fn(hash_fn));
if let Some(func) = func { if let Some(func) = func {
// Calling pure function but the first argument is a reference? #[cfg(not(feature = "no_function"))]
normalize_first_arg( let need_normalize = is_ref && (func.is_pure() || (func.is_script() && !is_method));
is_ref && (func.is_pure() || (func.is_script() && !is_method)), #[cfg(feature = "no_function")]
&mut this_copy, let need_normalize = is_ref && func.is_pure();
&mut old_this_ptr,
args,
);
// Calling pure function but the first argument is a reference?
normalize_first_arg(need_normalize, &mut this_copy, &mut old_this_ptr, args);
#[cfg(not(feature = "no_function"))]
if func.is_script() { if func.is_script() {
// Run scripted function // Run scripted function
let fn_def = func.get_fn_def(); let fn_def = func.get_fn_def();
// Method call of script function - map first argument to `this` // Method call of script function - map first argument to `this`
if is_method { return if is_method {
let (first, rest) = args.split_at_mut(1); let (first, rest) = args.split_at_mut(1);
return Ok(( Ok((
self.call_script_fn( self.call_script_fn(
scope, scope,
mods, mods,
@ -795,7 +647,7 @@ impl Engine {
level, level,
)?, )?,
false, false,
)); ))
} else { } else {
let result = self.call_script_fn( let result = self.call_script_fn(
scope, mods, state, lib, &mut None, fn_name, fn_def, args, level, scope, mods, state, lib, &mut None, fn_name, fn_def, args, level,
@ -804,9 +656,10 @@ impl Engine {
// Restore the original reference // Restore the original reference
restore_first_arg(old_this_ptr, args); restore_first_arg(old_this_ptr, args);
return Ok((result, false)); Ok((result, false))
}; };
} else { }
// Run external function // Run external function
let result = func.get_native_fn()(self, args)?; let result = func.get_native_fn()(self, args)?;
@ -816,9 +669,10 @@ impl Engine {
// See if the function match print/debug (which requires special processing) // See if the function match print/debug (which requires special processing)
return Ok(match fn_name { return Ok(match fn_name {
KEYWORD_PRINT => ( KEYWORD_PRINT => (
(self.print)(result.as_str().map_err(|type_name| { (self.print)(result.as_str().map_err(|typ| {
Box::new(EvalAltResult::ErrorMismatchOutputType( Box::new(EvalAltResult::ErrorMismatchOutputType(
type_name.into(), self.map_type_name(type_name::<ImmutableString>()).into(),
typ.into(),
Position::none(), Position::none(),
)) ))
})?) })?)
@ -826,9 +680,10 @@ impl Engine {
false, false,
), ),
KEYWORD_DEBUG => ( KEYWORD_DEBUG => (
(self.debug)(result.as_str().map_err(|type_name| { (self.debug)(result.as_str().map_err(|typ| {
Box::new(EvalAltResult::ErrorMismatchOutputType( Box::new(EvalAltResult::ErrorMismatchOutputType(
type_name.into(), self.map_type_name(type_name::<ImmutableString>()).into(),
typ.into(),
Position::none(), Position::none(),
)) ))
})?) })?)
@ -838,7 +693,6 @@ impl Engine {
_ => (result, func.is_method()), _ => (result, func.is_method()),
}); });
} }
}
// See if it is built in. // See if it is built in.
if args.len() == 2 { if args.len() == 2 {
@ -1064,8 +918,12 @@ impl Engine {
lib: &Module, lib: &Module,
script: &Dynamic, script: &Dynamic,
) -> Result<Dynamic, Box<EvalAltResult>> { ) -> Result<Dynamic, Box<EvalAltResult>> {
let script = script.as_str().map_err(|type_name| { let script = script.as_str().map_err(|typ| {
EvalAltResult::ErrorMismatchOutputType(type_name.into(), Position::none()) EvalAltResult::ErrorMismatchOutputType(
self.map_type_name(type_name::<ImmutableString>()).into(),
typ.into(),
Position::none(),
)
})?; })?;
// Compile the script text // Compile the script text
@ -1873,9 +1731,10 @@ impl Engine {
self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)?; self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)?;
return arg_value return arg_value
.take_immutable_string() .take_immutable_string()
.map_err(|type_name| { .map_err(|typ| {
Box::new(EvalAltResult::ErrorMismatchOutputType( Box::new(EvalAltResult::ErrorMismatchOutputType(
type_name.into(), self.map_type_name(type_name::<ImmutableString>()).into(),
typ.into(),
expr.position(), expr.position(),
)) ))
}) })
@ -2009,6 +1868,7 @@ impl Engine {
}; };
match func { match func {
#[cfg(not(feature = "no_function"))]
Ok(f) if f.is_script() => { Ok(f) if f.is_script() => {
let args = args.as_mut(); let args = args.as_mut();
let fn_def = f.get_fn_def(); let fn_def = f.get_fn_def();
@ -2335,7 +2195,6 @@ impl Engine {
.try_cast::<ImmutableString>() .try_cast::<ImmutableString>()
{ {
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
{
if let Some(resolver) = &self.module_resolver { if let Some(resolver) = &self.module_resolver {
let mut module = resolver.resolve(self, &path, expr.position())?; let mut module = resolver.resolve(self, &path, expr.position())?;
module.index_all_sub_modules(); module.index_all_sub_modules();
@ -2350,7 +2209,6 @@ impl Engine {
expr.position(), expr.position(),
))) )))
} }
}
#[cfg(feature = "no_module")] #[cfg(feature = "no_module")]
Ok(Default::default()) Ok(Default::default())
@ -2408,7 +2266,13 @@ impl Engine {
let mut maps = 0; let mut maps = 0;
arr.iter().for_each(|value| match value { arr.iter().for_each(|value| match value {
Dynamic(Union::Array(_)) | Dynamic(Union::Map(_)) => { Dynamic(Union::Array(_)) => {
let (a, m, _) = calc_size(value);
arrays += a;
maps += m;
}
#[cfg(not(feature = "no_object"))]
Dynamic(Union::Map(_)) => {
let (a, m, _) = calc_size(value); let (a, m, _) = calc_size(value);
arrays += a; arrays += a;
maps += m; maps += m;
@ -2424,7 +2288,13 @@ impl Engine {
let mut maps = 0; let mut maps = 0;
map.values().for_each(|value| match value { map.values().for_each(|value| match value {
Dynamic(Union::Array(_)) | Dynamic(Union::Map(_)) => { #[cfg(not(feature = "no_index"))]
Dynamic(Union::Array(_)) => {
let (a, m, _) = calc_size(value);
arrays += a;
maps += m;
}
Dynamic(Union::Map(_)) => {
let (a, m, _) = calc_size(value); let (a, m, _) = calc_size(value);
arrays += a; arrays += a;
maps += m; maps += m;
@ -2488,14 +2358,12 @@ impl Engine {
state.operations += 1; state.operations += 1;
#[cfg(not(feature = "unchecked"))] #[cfg(not(feature = "unchecked"))]
{
// Guard against too many operations // Guard against too many operations
if self.max_operations > 0 && state.operations > self.max_operations { if self.max_operations > 0 && state.operations > self.max_operations {
return Err(Box::new(EvalAltResult::ErrorTooManyOperations( return Err(Box::new(EvalAltResult::ErrorTooManyOperations(
Position::none(), Position::none(),
))); )));
} }
}
// Report progress - only in steps // Report progress - only in steps
if let Some(progress) = &self.progress { if let Some(progress) = &self.progress {
@ -2511,9 +2379,9 @@ impl Engine {
/// Map a type_name into a pretty-print name /// Map a type_name into a pretty-print name
pub(crate) fn map_type_name<'a>(&'a self, name: &'a str) -> &'a str { pub(crate) fn map_type_name<'a>(&'a self, name: &'a str) -> &'a str {
self.type_names self.type_names
.get(name) .as_ref()
.map(String::as_str) .and_then(|t| t.get(name).map(String::as_str))
.unwrap_or(name) .unwrap_or(map_std_type_name(name))
} }
} }
@ -2620,7 +2488,6 @@ fn run_builtin_binary_op(
} }
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
{
if args_type == TypeId::of::<FLOAT>() { if args_type == TypeId::of::<FLOAT>() {
let x = *x.downcast_ref::<FLOAT>().unwrap(); let x = *x.downcast_ref::<FLOAT>().unwrap();
let y = *y.downcast_ref::<FLOAT>().unwrap(); let y = *y.downcast_ref::<FLOAT>().unwrap();
@ -2641,7 +2508,6 @@ fn run_builtin_binary_op(
_ => (), _ => (),
} }
} }
}
Ok(None) Ok(None)
} }
@ -2716,7 +2582,6 @@ fn run_builtin_op_assignment(
} }
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
{
if args_type == TypeId::of::<FLOAT>() { if args_type == TypeId::of::<FLOAT>() {
let x = x.downcast_mut::<FLOAT>().unwrap(); let x = x.downcast_mut::<FLOAT>().unwrap();
let y = *y.downcast_ref::<FLOAT>().unwrap(); let y = *y.downcast_ref::<FLOAT>().unwrap();
@ -2731,7 +2596,6 @@ fn run_builtin_op_assignment(
_ => (), _ => (),
} }
} }
}
Ok(None) Ok(None)
} }

View File

@ -5,7 +5,6 @@ use crate::token::Position;
use crate::stdlib::{ use crate::stdlib::{
boxed::Box, boxed::Box,
char,
error::Error, error::Error,
fmt, fmt,
string::{String, ToString}, string::{String, ToString},
@ -15,8 +14,8 @@ use crate::stdlib::{
#[derive(Debug, Eq, PartialEq, Clone, Hash)] #[derive(Debug, Eq, PartialEq, Clone, Hash)]
#[non_exhaustive] #[non_exhaustive]
pub enum LexError { pub enum LexError {
/// An unexpected character is encountered when tokenizing the script text. /// An unexpected symbol is encountered when tokenizing the script text.
UnexpectedChar(char), UnexpectedInput(String),
/// 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. /// An identifier is in an invalid format.
@ -38,7 +37,7 @@ impl Error for LexError {}
impl fmt::Display for LexError { impl fmt::Display for LexError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self { match self {
Self::UnexpectedChar(c) => write!(f, "Unexpected '{}'", c), Self::UnexpectedInput(s) => write!(f, "Unexpected '{}'", s),
Self::MalformedEscapeSequence(s) => write!(f, "Invalid escape sequence: '{}'", s), Self::MalformedEscapeSequence(s) => write!(f, "Invalid escape sequence: '{}'", s),
Self::MalformedNumber(s) => write!(f, "Invalid number: '{}'", s), Self::MalformedNumber(s) => write!(f, "Invalid number: '{}'", s),
Self::MalformedChar(s) => write!(f, "Invalid character: '{}'", s), Self::MalformedChar(s) => write!(f, "Invalid character: '{}'", s),
@ -49,7 +48,7 @@ impl fmt::Display for LexError {
"Length of string literal exceeds the maximum limit ({})", "Length of string literal exceeds the maximum limit ({})",
max max
), ),
Self::ImproperSymbol(s) => write!(f, "{}", s), Self::ImproperSymbol(s) => f.write_str(s),
} }
} }
} }
@ -185,18 +184,16 @@ impl fmt::Display for ParseErrorType {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self { match self {
Self::BadInput(s) | ParseErrorType::MalformedCallExpr(s) => { Self::BadInput(s) | ParseErrorType::MalformedCallExpr(s) => {
write!(f, "{}", if s.is_empty() { self.desc() } else { s }) f.write_str(if s.is_empty() { self.desc() } else { s })
} }
Self::ForbiddenConstantExpr(s) => { Self::ForbiddenConstantExpr(s) => {
write!(f, "Expecting a constant to assign to '{}'", s) write!(f, "Expecting a constant to assign to '{}'", s)
} }
Self::UnknownOperator(s) => write!(f, "{}: '{}'", self.desc(), s), Self::UnknownOperator(s) => write!(f, "{}: '{}'", self.desc(), s),
Self::MalformedIndexExpr(s) => { Self::MalformedIndexExpr(s) => f.write_str(if s.is_empty() { self.desc() } else { 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::MalformedInExpr(s) => f.write_str(if s.is_empty() { self.desc() } else { s }),
Self::DuplicatedProperty(s) => { Self::DuplicatedProperty(s) => {
write!(f, "Duplicated property '{}' for object map literal", s) write!(f, "Duplicated property '{}' for object map literal", s)
@ -222,12 +219,12 @@ impl fmt::Display for ParseErrorType {
Self::MissingToken(token, s) => write!(f, "Expecting '{}' {}", token, s), Self::MissingToken(token, s) => write!(f, "Expecting '{}' {}", token, s),
Self::AssignmentToConstant(s) if s.is_empty() => write!(f, "{}", self.desc()), Self::AssignmentToConstant(s) if s.is_empty() => f.write_str(self.desc()),
Self::AssignmentToConstant(s) => write!(f, "Cannot assign to constant '{}'", s), Self::AssignmentToConstant(s) => write!(f, "Cannot assign to constant '{}'", s),
Self::LiteralTooLarge(typ, max) => { Self::LiteralTooLarge(typ, max) => {
write!(f, "{} exceeds the maximum limit ({})", typ, max) write!(f, "{} exceeds the maximum limit ({})", typ, max)
} }
_ => write!(f, "{}", self.desc()), _ => f.write_str(self.desc()),
} }
} }
} }

View File

@ -28,13 +28,9 @@ pub type Shared<T> = Arc<T>;
/// If the resource is shared (i.e. has other outstanding references), a cloned copy is used. /// If the resource is shared (i.e. has other outstanding references), a cloned copy is used.
pub fn shared_make_mut<T: Clone>(value: &mut Shared<T>) -> &mut T { pub fn shared_make_mut<T: Clone>(value: &mut Shared<T>) -> &mut T {
#[cfg(not(feature = "sync"))] #[cfg(not(feature = "sync"))]
{ return Rc::make_mut(value);
Rc::make_mut(value)
}
#[cfg(feature = "sync")] #[cfg(feature = "sync")]
{ return Arc::make_mut(value);
Arc::make_mut(value)
}
} }
/// Consume a `Shared` resource, assuming that it is unique (i.e. not shared). /// Consume a `Shared` resource, assuming that it is unique (i.e. not shared).
@ -44,13 +40,9 @@ pub fn shared_make_mut<T: Clone>(value: &mut Shared<T>) -> &mut T {
/// Panics if the resource is shared (i.e. has other outstanding references). /// Panics if the resource is shared (i.e. has other outstanding references).
pub fn shared_take<T: Clone>(value: Shared<T>) -> T { pub fn shared_take<T: Clone>(value: Shared<T>) -> T {
#[cfg(not(feature = "sync"))] #[cfg(not(feature = "sync"))]
{ return Rc::try_unwrap(value).map_err(|_| ()).unwrap();
Rc::try_unwrap(value).map_err(|_| ()).unwrap()
}
#[cfg(feature = "sync")] #[cfg(feature = "sync")]
{ return Arc::try_unwrap(value).map_err(|_| ()).unwrap();
Arc::try_unwrap(value).map_err(|_| ()).unwrap()
}
} }
pub type FnCallArgs<'a> = [&'a mut Dynamic]; pub type FnCallArgs<'a> = [&'a mut Dynamic];
@ -122,6 +114,7 @@ pub enum CallableFunction {
/// A plugin-defined function, /// A plugin-defined function,
Plugin(SharedPluginFunction), Plugin(SharedPluginFunction),
/// A script-defined function. /// A script-defined function.
#[cfg(not(feature = "no_function"))]
Script(Shared<ScriptFnDef>), Script(Shared<ScriptFnDef>),
} }
@ -132,6 +125,8 @@ impl fmt::Debug for CallableFunction {
Self::Method(_) => write!(f, "NativeMethod"), Self::Method(_) => write!(f, "NativeMethod"),
Self::Iterator(_) => write!(f, "NativeIterator"), Self::Iterator(_) => write!(f, "NativeIterator"),
Self::Plugin(_) => write!(f, "PluginFunction"), Self::Plugin(_) => write!(f, "PluginFunction"),
#[cfg(not(feature = "no_function"))]
Self::Script(fn_def) => fmt::Debug::fmt(fn_def, f), Self::Script(fn_def) => fmt::Debug::fmt(fn_def, f),
} }
} }
@ -144,6 +139,8 @@ impl fmt::Display for CallableFunction {
Self::Method(_) => write!(f, "NativeMethod"), Self::Method(_) => write!(f, "NativeMethod"),
Self::Iterator(_) => write!(f, "NativeIterator"), Self::Iterator(_) => write!(f, "NativeIterator"),
Self::Plugin(_) => write!(f, "PluginFunction"), Self::Plugin(_) => write!(f, "PluginFunction"),
#[cfg(not(feature = "no_function"))]
CallableFunction::Script(s) => fmt::Display::fmt(s, f), CallableFunction::Script(s) => fmt::Display::fmt(s, f),
} }
} }
@ -154,24 +151,34 @@ impl CallableFunction {
pub fn is_pure(&self) -> bool { pub fn is_pure(&self) -> bool {
match self { match self {
Self::Pure(_) => true, Self::Pure(_) => true,
Self::Method(_) | Self::Iterator(_) | Self::Script(_) | Self::Plugin(_) => false, Self::Method(_) | Self::Iterator(_) | Self::Plugin(_) => false,
#[cfg(not(feature = "no_function"))]
Self::Script(_) => false,
} }
} }
/// Is this a native Rust method function? /// Is this a native Rust method function?
pub fn is_method(&self) -> bool { pub fn is_method(&self) -> bool {
match self { match self {
Self::Method(_) => true, Self::Method(_) => true,
Self::Pure(_) | Self::Iterator(_) | Self::Script(_) | Self::Plugin(_) => false, Self::Pure(_) | Self::Iterator(_) | Self::Plugin(_) => false,
#[cfg(not(feature = "no_function"))]
Self::Script(_) => false,
} }
} }
/// Is this an iterator function? /// Is this an iterator function?
pub fn is_iter(&self) -> bool { pub fn is_iter(&self) -> bool {
match self { match self {
Self::Iterator(_) => true, Self::Iterator(_) => true,
Self::Pure(_) | Self::Method(_) | Self::Script(_) | Self::Plugin(_) => false, Self::Pure(_) | Self::Method(_) | Self::Plugin(_) => false,
#[cfg(not(feature = "no_function"))]
Self::Script(_) => false,
} }
} }
/// Is this a Rhai-scripted function? /// Is this a Rhai-scripted function?
#[cfg(not(feature = "no_function"))]
pub fn is_script(&self) -> bool { pub fn is_script(&self) -> bool {
match self { match self {
Self::Script(_) => true, Self::Script(_) => true,
@ -193,7 +200,10 @@ 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(_) | Self::Plugin(_) => unreachable!(), Self::Iterator(_) | Self::Plugin(_) => unreachable!(),
#[cfg(not(feature = "no_function"))]
Self::Script(_) => unreachable!(),
} }
} }
/// Get a shared reference to a script-defined function definition. /// Get a shared reference to a script-defined function definition.
@ -201,6 +211,7 @@ impl CallableFunction {
/// # Panics /// # Panics
/// ///
/// Panics if the `CallableFunction` is not `Script`. /// Panics if the `CallableFunction` is not `Script`.
#[cfg(not(feature = "no_function"))]
pub fn get_shared_fn_def(&self) -> Shared<ScriptFnDef> { pub fn get_shared_fn_def(&self) -> Shared<ScriptFnDef> {
match self { match self {
Self::Pure(_) | Self::Method(_) | Self::Iterator(_) | Self::Plugin(_) => unreachable!(), Self::Pure(_) | Self::Method(_) | Self::Iterator(_) | Self::Plugin(_) => unreachable!(),
@ -212,6 +223,7 @@ impl CallableFunction {
/// # Panics /// # Panics
/// ///
/// Panics if the `CallableFunction` is not `Script`. /// Panics if the `CallableFunction` is not `Script`.
#[cfg(not(feature = "no_function"))]
pub fn get_fn_def(&self) -> &ScriptFnDef { pub fn get_fn_def(&self) -> &ScriptFnDef {
match self { match self {
Self::Pure(_) | Self::Method(_) | Self::Iterator(_) | Self::Plugin(_) => unreachable!(), Self::Pure(_) | Self::Method(_) | Self::Iterator(_) | Self::Plugin(_) => unreachable!(),
@ -226,7 +238,10 @@ 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(_) | Self::Plugin(_) => unreachable!(), Self::Pure(_) | Self::Method(_) | Self::Plugin(_) => unreachable!(),
#[cfg(not(feature = "no_function"))]
Self::Script(_) => unreachable!(),
} }
} }
/// Get a reference to a plugin function. /// Get a reference to a plugin function.
@ -237,7 +252,10 @@ impl CallableFunction {
pub fn get_plugin_fn<'s>(&'s self) -> SharedPluginFunction { pub fn get_plugin_fn<'s>(&'s self) -> SharedPluginFunction {
match self { match self {
Self::Plugin(f) => f.clone(), Self::Plugin(f) => f.clone(),
Self::Pure(_) | Self::Method(_) | Self::Script(_) | Self::Iterator(_) => unreachable!(), Self::Pure(_) | Self::Method(_) | Self::Iterator(_) => unreachable!(),
#[cfg(not(feature = "no_function"))]
Self::Script(_) => unreachable!(),
} }
} }
/// Create a new `CallableFunction::Pure`. /// Create a new `CallableFunction::Pure`.
@ -268,12 +286,14 @@ impl From<IteratorFn> for CallableFunction {
} }
} }
#[cfg(not(feature = "no_function"))]
impl From<ScriptFnDef> for CallableFunction { impl From<ScriptFnDef> for CallableFunction {
fn from(func: ScriptFnDef) -> Self { fn from(func: ScriptFnDef) -> Self {
Self::Script(func.into()) Self::Script(func.into())
} }
} }
#[cfg(not(feature = "no_function"))]
impl From<Shared<ScriptFnDef>> for CallableFunction { impl From<Shared<ScriptFnDef>> for CallableFunction {
fn from(func: Shared<ScriptFnDef>) -> Self { fn from(func: Shared<ScriptFnDef>) -> Self {
Self::Script(func) Self::Script(func)

View File

@ -63,6 +63,7 @@
//! | `only_i64` | Set the system integer type to `i64` and disable all other integer types. `INT` is set to `i64`. | //! | `only_i64` | Set the system integer type to `i64` and disable all other integer types. `INT` is set to `i64`. |
//! | `no_std` | Build for `no-std`. Notice that additional dependencies will be pulled in to replace `std` features. | //! | `no_std` | Build for `no-std`. Notice that additional dependencies will be pulled in to replace `std` features. |
//! | `sync` | Restrict all values types to those that are `Send + Sync`. Under this feature, `Engine`, `Scope` and [`AST`] are all `Send + Sync`. | //! | `sync` | Restrict all values types to those that are `Send + Sync`. Under this feature, `Engine`, `Scope` and [`AST`] are all `Send + Sync`. |
//! | `serde` | Enable serialization/deserialization via `serde`. Notice that the [`serde`](https://crates.io/crates/serde) crate will be pulled in together with its dependencies. |
//! | `internals` | Expose internal data structures (beware they may be volatile from version to version). | //! | `internals` | Expose internal data structures (beware they may be volatile from version to version). |
//! //!
//! See [The Rhai Book](https://schungx.github.io/rhai) for details on the Rhai script engine and language. //! See [The Rhai Book](https://schungx.github.io/rhai) for details on the Rhai script engine and language.
@ -90,6 +91,9 @@ pub mod plugin;
mod plugin; mod plugin;
mod result; mod result;
mod scope; mod scope;
#[cfg(feature = "serde")]
mod serde;
mod settings;
mod stdlib; mod stdlib;
mod token; mod token;
mod r#unsafe; mod r#unsafe;
@ -126,11 +130,28 @@ pub use parser::FLOAT;
pub use module::ModuleResolver; pub use module::ModuleResolver;
/// Module containing all built-in _module resolvers_ available to Rhai. /// Module containing all built-in _module resolvers_ available to Rhai.
///
/// Not available under the `no_module` feature.
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
pub mod module_resolvers { pub mod module_resolvers {
pub use crate::module::resolvers::*; pub use crate::module::resolvers::*;
} }
/// Serialization support for [`serde`](https://crates.io/crates/serde).
///
/// Requires the `serde` feature.
#[cfg(feature = "serde")]
pub mod ser {
pub use crate::serde::ser::to_dynamic;
}
/// Deserialization support for [`serde`](https://crates.io/crates/serde).
///
/// Requires the `serde` feature.
#[cfg(feature = "serde")]
pub mod de {
pub use crate::serde::de::from_dynamic;
}
#[cfg(not(feature = "no_optimize"))] #[cfg(not(feature = "no_optimize"))]
pub use optimize::OptimizationLevel; pub use optimize::OptimizationLevel;

View File

@ -236,6 +236,7 @@ impl Module {
/// Set a script-defined function into the module. /// 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. /// If there is an existing function of the same name and number of arguments, it is replaced.
#[cfg(not(feature = "no_function"))]
pub(crate) fn set_script_fn(&mut self, fn_def: ScriptFnDef) { pub(crate) fn set_script_fn(&mut self, fn_def: ScriptFnDef) {
// None + function name + number of arguments. // None + function name + number of arguments.
let hash_script = calc_fn_hash(empty(), &fn_def.name, fn_def.params.len(), empty()); let hash_script = calc_fn_hash(empty(), &fn_def.name, fn_def.params.len(), empty());
@ -876,6 +877,7 @@ impl Module {
.functions .functions
.iter() .iter()
.filter(|(_, (_, _, _, v))| match v { .filter(|(_, (_, _, _, v))| match v {
#[cfg(not(feature = "no_function"))]
CallableFunction::Script(ref f) => { CallableFunction::Script(ref f) => {
filter(f.access, f.name.as_str(), f.params.len()) filter(f.access, f.name.as_str(), f.params.len())
} }
@ -893,6 +895,7 @@ impl Module {
} }
/// Filter out the functions, retaining only some based on a filter predicate. /// Filter out the functions, retaining only some based on a filter predicate.
#[cfg(not(feature = "no_function"))]
pub(crate) fn retain_functions(&mut self, filter: impl Fn(FnAccess, &str, usize) -> bool) { pub(crate) fn retain_functions(&mut self, filter: impl Fn(FnAccess, &str, usize) -> bool) {
self.functions.retain(|_, (_, _, _, v)| match v { self.functions.retain(|_, (_, _, _, v)| match v {
CallableFunction::Script(ref f) => filter(f.access, f.name.as_str(), f.params.len()), CallableFunction::Script(ref f) => filter(f.access, f.name.as_str(), f.params.len()),
@ -930,6 +933,7 @@ impl Module {
} }
/// Get an iterator over all script-defined functions in the module. /// Get an iterator over all script-defined functions in the module.
#[cfg(not(feature = "no_function"))]
pub fn iter_script_fn<'a>(&'a self) -> impl Iterator<Item = Shared<ScriptFnDef>> + 'a { pub fn iter_script_fn<'a>(&'a self) -> impl Iterator<Item = Shared<ScriptFnDef>> + 'a {
self.functions self.functions
.values() .values()
@ -1014,6 +1018,7 @@ impl Module {
Public => (), Public => (),
} }
#[cfg(not(feature = "no_function"))]
if func.is_script() { if func.is_script() {
let fn_def = func.get_shared_fn_def(); let fn_def = func.get_shared_fn_def();
// Qualifiers + function name + number of arguments. // Qualifiers + function name + number of arguments.
@ -1024,7 +1029,9 @@ impl Module {
empty(), empty(),
); );
functions.push((hash_qualified_script, fn_def.into())); functions.push((hash_qualified_script, fn_def.into()));
} else { continue;
}
// Qualified Rust functions are indexed in two steps: // Qualified Rust functions are indexed in two steps:
// 1) Calculate a hash in a similar manner to script-defined functions, // 1) Calculate a hash in a similar manner to script-defined functions,
// i.e. qualifiers + function name + number of arguments. // i.e. qualifiers + function name + number of arguments.
@ -1039,7 +1046,6 @@ impl Module {
functions.push((hash_qualified_fn, func.clone())); functions.push((hash_qualified_fn, func.clone()));
} }
} }
}
if self.indexed { if self.indexed {
return; return;

View File

@ -551,11 +551,17 @@ fn optimize_expr(expr: Expr, state: &mut State) -> Expr {
// First search in functions lib (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 state.lib.iter_fn().find(|(_, _, _, f)| { #[cfg(not(feature = "no_function"))]
let has_script_fn = state.lib.iter_fn().find(|(_, _, _, f)| {
if !f.is_script() { return false; } if !f.is_script() { return false; }
let fn_def = f.get_fn_def(); let fn_def = f.get_fn_def();
&fn_def.name == name && (args.len()..=args.len() + 1).contains(&fn_def.params.len()) &fn_def.name == name && (args.len()..=args.len() + 1).contains(&fn_def.params.len())
}).is_some() { }).is_some();
#[cfg(feature = "no_function")]
const has_script_fn: bool = false;
if has_script_fn {
// 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);

View File

@ -190,7 +190,6 @@ fn modulo_u<T: Rem>(x: T, y: T) -> FuncReturn<<T as Rem>::Output> {
// Checked power // Checked power
pub(crate) fn pow_i_i(x: INT, y: INT) -> FuncReturn<INT> { pub(crate) fn pow_i_i(x: INT, y: INT) -> FuncReturn<INT> {
#[cfg(not(feature = "only_i32"))] #[cfg(not(feature = "only_i32"))]
{
if y > (u32::MAX as INT) { if y > (u32::MAX as INT) {
Err(Box::new(EvalAltResult::ErrorArithmetic( Err(Box::new(EvalAltResult::ErrorArithmetic(
format!("Integer raised to too large an index: {} ~ {}", x, y), format!("Integer raised to too large an index: {} ~ {}", x, y),
@ -209,10 +208,8 @@ pub(crate) fn pow_i_i(x: INT, y: INT) -> FuncReturn<INT> {
)) ))
}) })
} }
}
#[cfg(feature = "only_i32")] #[cfg(feature = "only_i32")]
{
if y < 0 { if y < 0 {
Err(Box::new(EvalAltResult::ErrorArithmetic( Err(Box::new(EvalAltResult::ErrorArithmetic(
format!("Integer raised to a negative index: {} ~ {}", x, y), format!("Integer raised to a negative index: {} ~ {}", x, y),
@ -226,7 +223,6 @@ pub(crate) fn pow_i_i(x: INT, y: INT) -> FuncReturn<INT> {
)) ))
}) })
} }
}
} }
// Unchecked integer power - may panic on overflow or if the power index is too high (> u32::MAX) // Unchecked integer power - may panic on overflow or if the power index is too high (> u32::MAX)
pub(crate) fn pow_i_i_u(x: INT, y: INT) -> FuncReturn<INT> { pub(crate) fn pow_i_i_u(x: INT, y: INT) -> FuncReturn<INT> {

View File

@ -30,7 +30,6 @@ fn pad<T: Variant + Clone>(engine: &Engine, args: &mut [&mut Dynamic]) -> FuncRe
// Check if array will be over max size limit // Check if array will be over max size limit
#[cfg(not(feature = "unchecked"))] #[cfg(not(feature = "unchecked"))]
{
if engine.max_array_size > 0 && len > 0 && (len as usize) > engine.max_array_size { if engine.max_array_size > 0 && len > 0 && (len as usize) > engine.max_array_size {
return Err(Box::new(EvalAltResult::ErrorDataTooLarge( return Err(Box::new(EvalAltResult::ErrorDataTooLarge(
"Size of array".to_string(), "Size of array".to_string(),
@ -39,7 +38,6 @@ fn pad<T: Variant + Clone>(engine: &Engine, args: &mut [&mut Dynamic]) -> FuncRe
Position::none(), Position::none(),
))); )));
} }
}
if len > 0 { if len > 0 {
let item = args[2].clone(); let item = args[2].clone();

View File

@ -3,6 +3,7 @@ use crate::fn_native::FnPtr;
def_package!(crate:BasicFnPackage:"Basic Fn functions.", lib, { def_package!(crate:BasicFnPackage:"Basic Fn functions.", lib, {
lib.set_fn_1_mut("name", |f: &mut FnPtr| Ok(f.get_fn_name().clone())); lib.set_fn_1_mut("name", |f: &mut FnPtr| Ok(f.get_fn_name().clone()));
lib.set_getter_fn("name", |f: &mut FnPtr| Ok(f.get_fn_name().clone()));
#[cfg(not(feature = "no_object"))]
lib.set_getter_fn("name", |f: &mut FnPtr| Ok(f.get_fn_name().clone()));
}); });

View File

@ -231,7 +231,6 @@ def_package!(crate:MoreStringPackage:"Additional string utilities, including str
// Check if string will be over max size limit // Check if string will be over max size limit
#[cfg(not(feature = "unchecked"))] #[cfg(not(feature = "unchecked"))]
{
if engine.max_string_size > 0 && len > 0 && (len as usize) > engine.max_string_size { if engine.max_string_size > 0 && len > 0 && (len as usize) > engine.max_string_size {
return Err(Box::new(EvalAltResult::ErrorDataTooLarge( return Err(Box::new(EvalAltResult::ErrorDataTooLarge(
"Length of string".to_string(), "Length of string".to_string(),
@ -240,7 +239,6 @@ def_package!(crate:MoreStringPackage:"Additional string utilities, including str
Position::none(), Position::none(),
))); )));
} }
}
if len > 0 { if len > 0 {
let ch = *args[2].downcast_ref::< char>().unwrap(); let ch = *args[2].downcast_ref::< char>().unwrap();

View File

@ -33,7 +33,6 @@ def_package!(crate:BasicTimePackage:"Basic timing utilities.", lib, {
let seconds = (ts2 - ts1).as_secs(); let seconds = (ts2 - ts1).as_secs();
#[cfg(not(feature = "unchecked"))] #[cfg(not(feature = "unchecked"))]
{
if seconds > (MAX_INT as u64) { if seconds > (MAX_INT as u64) {
return Err(Box::new(EvalAltResult::ErrorArithmetic( return Err(Box::new(EvalAltResult::ErrorArithmetic(
format!( format!(
@ -43,7 +42,7 @@ def_package!(crate:BasicTimePackage:"Basic timing utilities.", lib, {
Position::none(), Position::none(),
))); )));
} }
}
return Ok(-(seconds as INT)); return Ok(-(seconds as INT));
} }
} else { } else {
@ -55,14 +54,13 @@ def_package!(crate:BasicTimePackage:"Basic timing utilities.", lib, {
let seconds = (ts1 - ts2).as_secs(); let seconds = (ts1 - ts2).as_secs();
#[cfg(not(feature = "unchecked"))] #[cfg(not(feature = "unchecked"))]
{
if seconds > (MAX_INT as u64) { if seconds > (MAX_INT as u64) {
return Err(Box::new(EvalAltResult::ErrorArithmetic( return Err(Box::new(EvalAltResult::ErrorArithmetic(
format!("Integer overflow for timestamp duration: {}", seconds), format!("Integer overflow for timestamp duration: {}", seconds),
Position::none(), Position::none(),
))); )));
} }
}
return Ok(seconds as INT); return Ok(seconds as INT);
} }
} }
@ -86,14 +84,13 @@ def_package!(crate:BasicTimePackage:"Basic timing utilities.", lib, {
let seconds = timestamp.elapsed().as_secs(); let seconds = timestamp.elapsed().as_secs();
#[cfg(not(feature = "unchecked"))] #[cfg(not(feature = "unchecked"))]
{
if seconds > (MAX_INT as u64) { if seconds > (MAX_INT as u64) {
return Err(Box::new(EvalAltResult::ErrorArithmetic( return Err(Box::new(EvalAltResult::ErrorArithmetic(
format!("Integer overflow for timestamp.elapsed: {}", seconds), format!("Integer overflow for timestamp.elapsed: {}", seconds),
Position::none(), Position::none(),
))); )));
} }
}
Ok(seconds as INT) Ok(seconds as INT)
} }

View File

@ -332,36 +332,26 @@ pub enum ReturnType {
Exception, Exception,
} }
#[derive(Debug, Clone, Eq, PartialEq, Hash, Default)] #[derive(Clone)]
struct ParseState { struct ParseState<'e> {
/// Reference to the scripting `Engine`.
engine: &'e Engine,
/// Encapsulates a local stack with variable names to simulate an actual runtime scope. /// Encapsulates a local stack with variable names to simulate an actual runtime scope.
pub stack: Vec<(String, ScopeEntryType)>, stack: Vec<(String, ScopeEntryType)>,
/// Encapsulates a local stack with variable names to simulate an actual runtime scope. /// Encapsulates a local stack with variable names to simulate an actual runtime scope.
pub modules: Vec<String>, modules: Vec<String>,
/// Maximum levels of expression nesting. /// Maximum levels of expression nesting.
pub max_expr_depth: usize, max_expr_depth: usize,
/// Maximum length of a string.
pub max_string_size: usize,
/// Maximum length of an array.
pub max_array_size: usize,
/// Maximum number of properties in a map.
pub max_map_size: usize,
} }
impl ParseState { impl<'e> ParseState<'e> {
/// Create a new `ParseState`. /// Create a new `ParseState`.
pub fn new( pub fn new(engine: &'e Engine, max_expr_depth: usize) -> Self {
max_expr_depth: usize,
max_string_size: usize,
max_array_size: usize,
max_map_size: usize,
) -> Self {
Self { Self {
engine,
max_expr_depth, max_expr_depth,
max_string_size, stack: Default::default(),
max_array_size, modules: Default::default(),
max_map_size,
..Default::default()
} }
} }
/// Find a variable by name in the `ParseState`, searching in reverse. /// Find a variable by name in the `ParseState`, searching in reverse.
@ -1206,10 +1196,10 @@ fn parse_array_literal(
let mut arr = StaticVec::new(); let mut arr = StaticVec::new();
while !input.peek().unwrap().0.is_eof() { while !input.peek().unwrap().0.is_eof() {
if state.max_array_size > 0 && arr.len() >= state.max_array_size { if state.engine.max_array_size > 0 && arr.len() >= state.engine.max_array_size {
return Err(PERR::LiteralTooLarge( return Err(PERR::LiteralTooLarge(
"Size of array literal".to_string(), "Size of array literal".to_string(),
state.max_array_size, state.engine.max_array_size,
) )
.into_err(input.peek().unwrap().1)); .into_err(input.peek().unwrap().1));
} }
@ -1272,7 +1262,7 @@ fn parse_map_literal(
_ => { _ => {
let (name, pos) = match input.next().unwrap() { let (name, pos) = match input.next().unwrap() {
(Token::Identifier(s), pos) => (s, pos), (Token::Identifier(s), pos) => (s, pos),
(Token::StringConst(s), pos) => (s, pos), (Token::StringConstant(s), pos) => (s, pos),
(Token::LexError(err), pos) => return Err(err.into_err(pos)), (Token::LexError(err), pos) => return Err(err.into_err(pos)),
(_, pos) if map.is_empty() => { (_, pos) if map.is_empty() => {
return Err(PERR::MissingToken( return Err(PERR::MissingToken(
@ -1306,10 +1296,10 @@ fn parse_map_literal(
} }
}; };
if state.max_map_size > 0 && map.len() >= state.max_map_size { if state.engine.max_map_size > 0 && map.len() >= state.engine.max_map_size {
return Err(PERR::LiteralTooLarge( return Err(PERR::LiteralTooLarge(
"Number of properties in object map literal".to_string(), "Number of properties in object map literal".to_string(),
state.max_map_size, state.engine.max_map_size,
) )
.into_err(input.peek().unwrap().1)); .into_err(input.peek().unwrap().1));
} }
@ -1380,7 +1370,7 @@ fn parse_primary(
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
Token::FloatConstant(x) => Expr::FloatConstant(Box::new((x, settings.pos))), Token::FloatConstant(x) => Expr::FloatConstant(Box::new((x, settings.pos))),
Token::CharConstant(c) => Expr::CharConstant(Box::new((c, settings.pos))), Token::CharConstant(c) => Expr::CharConstant(Box::new((c, settings.pos))),
Token::StringConst(s) => Expr::StringConstant(Box::new((s.into(), settings.pos))), Token::StringConstant(s) => Expr::StringConstant(Box::new((s.into(), settings.pos))),
Token::Identifier(s) => { Token::Identifier(s) => {
let index = state.find_var(&s); let index = state.find_var(&s);
Expr::Variable(Box::new(((s, settings.pos), None, 0, index))) Expr::Variable(Box::new(((s, settings.pos), None, 0, index)))
@ -1495,13 +1485,9 @@ fn parse_unary(
.map(|i| Expr::IntegerConstant(Box::new((i, pos)))) .map(|i| Expr::IntegerConstant(Box::new((i, pos))))
.or_else(|| { .or_else(|| {
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
{ return Some(Expr::FloatConstant(Box::new((-(x.0 as FLOAT), pos))));
Some(Expr::FloatConstant(Box::new((-(x.0 as FLOAT), pos))))
}
#[cfg(feature = "no_float")] #[cfg(feature = "no_float")]
{ return None;
None
}
}) })
.ok_or_else(|| LexError::MalformedNumber(format!("-{}", x.0)).into_err(pos)) .ok_or_else(|| LexError::MalformedNumber(format!("-{}", x.0)).into_err(pos))
} }
@ -1701,7 +1687,7 @@ fn make_dot_expr(lhs: Expr, rhs: Expr, op_pos: Position) -> Result<Expr, ParseEr
// lhs.func() // lhs.func()
(lhs, func @ Expr::FnCall(_)) => Expr::Dot(Box::new((lhs, func, op_pos))), (lhs, func @ Expr::FnCall(_)) => Expr::Dot(Box::new((lhs, func, op_pos))),
// lhs.rhs // lhs.rhs
_ => unreachable!(), (_, rhs) => return Err(PERR::PropertyExpected.into_err(rhs.position())),
}) })
} }
@ -1870,7 +1856,8 @@ fn parse_binary_op(
loop { loop {
let (current_op, _) = input.peek().unwrap(); let (current_op, _) = input.peek().unwrap();
let precedence = current_op.precedence(); let custom = state.engine.custom_keywords.as_ref();
let precedence = current_op.precedence(custom);
let bind_right = current_op.is_bind_right(); let bind_right = current_op.is_bind_right();
// Bind left to the parent lhs expression if precedence is higher // Bind left to the parent lhs expression if precedence is higher
@ -1883,7 +1870,7 @@ fn parse_binary_op(
let rhs = parse_unary(input, state, settings)?; let rhs = parse_unary(input, state, settings)?;
let next_precedence = input.peek().unwrap().0.precedence(); let next_precedence = input.peek().unwrap().0.precedence(custom);
// Bind to right if the next operator has higher precedence // Bind to right if the next operator has higher precedence
// If same precedence, then check if the operator binds right // If same precedence, then check if the operator binds right
@ -1953,6 +1940,19 @@ fn parse_binary_op(
make_dot_expr(current_lhs, rhs, pos)? make_dot_expr(current_lhs, rhs, pos)?
} }
Token::Custom(s)
if state
.engine
.custom_keywords
.as_ref()
.map(|c| c.contains_key(&s))
.unwrap_or(false) =>
{
// Accept non-native functions for custom operators
let op = (op.0, false, op.2);
Expr::FnCall(Box::new((op, None, hash, args, None)))
}
op_token => return Err(PERR::UnknownOperator(op_token.into()).into_err(pos)), op_token => return Err(PERR::UnknownOperator(op_token.into()).into_err(pos)),
}; };
} }
@ -2194,6 +2194,7 @@ fn parse_let(
} }
/// Parse an import statement. /// Parse an import statement.
#[cfg(not(feature = "no_module"))]
fn parse_import( fn parse_import(
input: &mut TokenStream, input: &mut TokenStream,
state: &mut ParseState, state: &mut ParseState,
@ -2444,6 +2445,8 @@ fn parse_stmt(
Token::Let => parse_let(input, state, Normal, settings.level_up()), Token::Let => parse_let(input, state, Normal, settings.level_up()),
Token::Const => parse_let(input, state, Constant, settings.level_up()), Token::Const => parse_let(input, state, Constant, settings.level_up()),
#[cfg(not(feature = "no_module"))]
Token::Import => parse_import(input, state, settings.level_up()), Token::Import => parse_import(input, state, settings.level_up()),
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
@ -2468,7 +2471,7 @@ fn parse_fn(
settings.ensure_level_within_max_limit(state.max_expr_depth)?; settings.ensure_level_within_max_limit(state.max_expr_depth)?;
let name = match input.next().unwrap() { let name = match input.next().unwrap() {
(Token::Identifier(s), _) => s, (Token::Identifier(s), _) | (Token::Custom(s), _) => s,
(_, pos) => return Err(PERR::FnMissingName.into_err(pos)), (_, pos) => return Err(PERR::FnMissingName.into_err(pos)),
}; };
@ -2556,12 +2559,7 @@ impl Engine {
scope: &Scope, scope: &Scope,
optimization_level: OptimizationLevel, optimization_level: OptimizationLevel,
) -> Result<AST, ParseError> { ) -> Result<AST, ParseError> {
let mut state = ParseState::new( let mut state = ParseState::new(self, self.max_expr_depth);
self.max_expr_depth,
self.max_string_size,
self.max_array_size,
self.max_map_size,
);
let settings = ParseSettings { let settings = ParseSettings {
allow_if_expr: false, allow_if_expr: false,
allow_stmt_expr: false, allow_stmt_expr: false,
@ -2597,12 +2595,7 @@ impl Engine {
) -> Result<(Vec<Stmt>, Vec<ScriptFnDef>), ParseError> { ) -> Result<(Vec<Stmt>, Vec<ScriptFnDef>), ParseError> {
let mut statements = Vec::<Stmt>::new(); let mut statements = Vec::<Stmt>::new();
let mut functions = HashMap::<u64, ScriptFnDef, _>::with_hasher(StraightHasherBuilder); let mut functions = HashMap::<u64, ScriptFnDef, _>::with_hasher(StraightHasherBuilder);
let mut state = ParseState::new( let mut state = ParseState::new(self, self.max_expr_depth);
self.max_expr_depth,
self.max_string_size,
self.max_array_size,
self.max_map_size,
);
while !input.peek().unwrap().0.is_eof() { while !input.peek().unwrap().0.is_eof() {
// Collect all the function definitions // Collect all the function definitions
@ -2615,14 +2608,8 @@ impl Engine {
}; };
match input.peek().unwrap() { match input.peek().unwrap() {
#[cfg(not(feature = "no_function"))]
(Token::Fn, pos) => { (Token::Fn, pos) => {
let mut state = ParseState::new( let mut state = ParseState::new(self, self.max_function_expr_depth);
self.max_function_expr_depth,
self.max_string_size,
self.max_array_size,
self.max_map_size,
);
let settings = ParseSettings { let settings = ParseSettings {
allow_if_expr: true, allow_if_expr: true,
allow_stmt_expr: true, allow_stmt_expr: true,

View File

@ -74,8 +74,8 @@ pub enum EvalAltResult {
/// Assignment to a constant variable. /// Assignment to a constant variable.
ErrorAssignmentToConstant(String, Position), ErrorAssignmentToConstant(String, Position),
/// Returned type is not the same as the required output type. /// Returned type is not the same as the required output type.
/// Wrapped value is the type of the actual result. /// Wrapped values are the type requested and type of the actual result.
ErrorMismatchOutputType(String, Position), ErrorMismatchOutputType(String, String, Position),
/// Inappropriate member access. /// Inappropriate member access.
ErrorDotExpr(String, Position), ErrorDotExpr(String, Position),
/// Arithmetic error encountered. Wrapped value is the error message. /// Arithmetic error encountered. Wrapped value is the error message.
@ -141,7 +141,7 @@ impl EvalAltResult {
"Assignment to an unsupported left-hand side expression" "Assignment to an unsupported left-hand side expression"
} }
Self::ErrorAssignmentToConstant(_, _) => "Assignment to a constant variable", Self::ErrorAssignmentToConstant(_, _) => "Assignment to a constant variable",
Self::ErrorMismatchOutputType(_, _) => "Output type is incorrect", Self::ErrorMismatchOutputType(_, _, _) => "Output type is incorrect",
Self::ErrorInExpr(_) => "Malformed 'in' expression", Self::ErrorInExpr(_) => "Malformed 'in' expression",
Self::ErrorDotExpr(_, _) => "Malformed dot expression", Self::ErrorDotExpr(_, _) => "Malformed dot expression",
Self::ErrorArithmetic(_, _) => "Arithmetic error", Self::ErrorArithmetic(_, _) => "Arithmetic error",
@ -197,16 +197,18 @@ impl fmt::Display for EvalAltResult {
| Self::ErrorTooManyOperations(_) | Self::ErrorTooManyOperations(_)
| Self::ErrorTooManyModules(_) | Self::ErrorTooManyModules(_)
| Self::ErrorStackOverflow(_) | Self::ErrorStackOverflow(_)
| Self::ErrorTerminated(_) => write!(f, "{}", desc)?, | Self::ErrorTerminated(_) => f.write_str(desc)?,
Self::ErrorRuntime(s, _) => write!(f, "{}", if s.is_empty() { desc } else { s })?, Self::ErrorRuntime(s, _) => f.write_str(if s.is_empty() { desc } else { s })?,
Self::ErrorAssignmentToConstant(s, _) => write!(f, "{}: '{}'", desc, s)?, Self::ErrorAssignmentToConstant(s, _) => write!(f, "{}: '{}'", desc, s)?,
Self::ErrorMismatchOutputType(s, _) => write!(f, "{}: {}", desc, s)?, Self::ErrorMismatchOutputType(r, s, _) => {
Self::ErrorArithmetic(s, _) => write!(f, "{}", s)?, write!(f, "{} (expecting {}): {}", desc, s, r)?
}
Self::ErrorArithmetic(s, _) => f.write_str(s)?,
Self::ErrorLoopBreak(_, _) => write!(f, "{}", desc)?, Self::ErrorLoopBreak(_, _) => f.write_str(desc)?,
Self::Return(_, _) => write!(f, "{}", desc)?, Self::Return(_, _) => f.write_str(desc)?,
Self::ErrorBooleanArgMismatch(op, _) => { Self::ErrorBooleanArgMismatch(op, _) => {
write!(f, "{} operator expects boolean operands", op)? write!(f, "{} operator expects boolean operands", op)?
@ -215,7 +217,7 @@ impl fmt::Display for EvalAltResult {
Self::ErrorArrayBounds(_, index, _) if *index < 0 => { Self::ErrorArrayBounds(_, index, _) if *index < 0 => {
write!(f, "{}: {} < 0", desc, index)? write!(f, "{}: {} < 0", desc, index)?
} }
Self::ErrorArrayBounds(0, _, _) => write!(f, "{}", desc)?, Self::ErrorArrayBounds(0, _, _) => f.write_str(desc)?,
Self::ErrorArrayBounds(1, index, _) => write!( Self::ErrorArrayBounds(1, index, _) => 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",
@ -229,7 +231,7 @@ impl fmt::Display for EvalAltResult {
Self::ErrorStringBounds(_, index, _) if *index < 0 => { Self::ErrorStringBounds(_, index, _) if *index < 0 => {
write!(f, "{}: {} < 0", desc, index)? write!(f, "{}: {} < 0", desc, index)?
} }
Self::ErrorStringBounds(0, _, _) => write!(f, "{}", desc)?, Self::ErrorStringBounds(0, _, _) => f.write_str(desc)?,
Self::ErrorStringBounds(1, index, _) => 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",
@ -289,7 +291,7 @@ impl EvalAltResult {
| Self::ErrorModuleNotFound(_, pos) | Self::ErrorModuleNotFound(_, pos)
| Self::ErrorAssignmentToUnknownLHS(pos) | Self::ErrorAssignmentToUnknownLHS(pos)
| Self::ErrorAssignmentToConstant(_, pos) | Self::ErrorAssignmentToConstant(_, pos)
| Self::ErrorMismatchOutputType(_, pos) | Self::ErrorMismatchOutputType(_, _, pos)
| Self::ErrorInExpr(pos) | Self::ErrorInExpr(pos)
| Self::ErrorDotExpr(_, pos) | Self::ErrorDotExpr(_, pos)
| Self::ErrorArithmetic(_, pos) | Self::ErrorArithmetic(_, pos)
@ -329,7 +331,7 @@ impl EvalAltResult {
| Self::ErrorModuleNotFound(_, pos) | Self::ErrorModuleNotFound(_, pos)
| Self::ErrorAssignmentToUnknownLHS(pos) | Self::ErrorAssignmentToUnknownLHS(pos)
| Self::ErrorAssignmentToConstant(_, pos) | Self::ErrorAssignmentToConstant(_, pos)
| Self::ErrorMismatchOutputType(_, pos) | Self::ErrorMismatchOutputType(_, _, pos)
| Self::ErrorInExpr(pos) | Self::ErrorInExpr(pos)
| Self::ErrorDotExpr(_, pos) | Self::ErrorDotExpr(_, pos)
| Self::ErrorArithmetic(_, pos) | Self::ErrorArithmetic(_, pos)

446
src/serde/de.rs Normal file
View File

@ -0,0 +1,446 @@
//! Implement deserialization support of `Dynamic` for [`serde`](https://crates.io/crates/serde).
use super::str::ImmutableStringDeserializer;
use crate::any::{Dynamic, Union};
use crate::error::ParseErrorType;
use crate::result::EvalAltResult;
use crate::token::Position;
use crate::utils::ImmutableString;
use serde::de::{DeserializeSeed, Deserializer, Error, MapAccess, SeqAccess, Visitor};
use serde::Deserialize;
#[cfg(not(feature = "no_index"))]
use crate::engine::Array;
#[cfg(not(feature = "no_object"))]
use crate::engine::Map;
use crate::stdlib::{any::type_name, fmt};
#[cfg(not(feature = "no_std"))]
#[cfg(not(target_arch = "wasm32"))]
use crate::stdlib::time::Instant;
#[cfg(not(feature = "no_std"))]
#[cfg(target_arch = "wasm32")]
use instant::Instant;
/// Deserializer for `Dynamic` which is kept as a reference.
///
/// The reference is necessary because the deserialized type may hold references
/// (especially `&str`) to the source `Dynamic`.
pub struct DynamicDeserializer<'a> {
value: &'a Dynamic,
}
impl<'de> DynamicDeserializer<'de> {
/// Create a `DynamicDeserializer` from a reference to a `Dynamic` value.
///
/// The reference is necessary because the deserialized type may hold references
/// (especially `&str`) to the source `Dynamic`.
pub fn from_dynamic(value: &'de Dynamic) -> Self {
Self { value }
}
/// Shortcut for a type conversion error.
fn type_error<T>(&self) -> Result<T, Box<EvalAltResult>> {
Err(Box::new(EvalAltResult::ErrorMismatchOutputType(
type_name::<T>().into(),
self.value.type_name().into(),
Position::none(),
)))
}
}
/// Deserialize a `Dynamic` value into a Rust type that implements `serde::Deserialize`.
///
/// # Examples
///
/// ```
/// # fn main() -> Result<(), Box<rhai::EvalAltResult>> {
/// # #[cfg(not(feature = "no_index"))]
/// # #[cfg(not(feature = "no_object"))]
/// # {
/// use rhai::{Dynamic, Array, Map, INT};
/// use rhai::de::from_dynamic;
/// use serde::Deserialize;
///
/// #[derive(Debug, Deserialize, PartialEq)]
/// struct Hello {
/// a: INT,
/// b: bool,
/// }
///
/// #[derive(Debug, Deserialize, PartialEq)]
/// struct Test {
/// int: u32,
/// seq: Vec<String>,
/// obj: Hello,
/// }
///
/// let mut map = Map::new();
/// map.insert("int".into(), Dynamic::from(42_u32));
///
/// let mut map2 = Map::new();
/// map2.insert("a".into(), (123 as INT).into());
/// map2.insert("b".into(), true.into());
///
/// map.insert("obj".into(), map2.into());
///
/// let arr: Array = vec!["foo".into(), "bar".into(), "baz".into()];
/// map.insert("seq".into(), arr.into());
///
/// let value: Test = from_dynamic(&map.into())?;
///
/// let expected = Test {
/// int: 42,
/// seq: vec!["foo".into(), "bar".into(), "baz".into()],
/// obj: Hello { a: 123, b: true },
/// };
///
/// assert_eq!(value, expected);
/// # }
/// # Ok(())
/// # }
/// ```
pub fn from_dynamic<'de, T: Deserialize<'de>>(
value: &'de Dynamic,
) -> Result<T, Box<EvalAltResult>> {
T::deserialize(&mut DynamicDeserializer::from_dynamic(value))
}
impl Error for Box<EvalAltResult> {
fn custom<T: fmt::Display>(err: T) -> Self {
Box::new(EvalAltResult::ErrorParsing(
ParseErrorType::BadInput(err.to_string()),
Position::none(),
))
}
}
impl<'de> Deserializer<'de> for &mut DynamicDeserializer<'de> {
type Error = Box<EvalAltResult>;
fn deserialize_any<V: Visitor<'de>>(self, visitor: V) -> Result<V::Value, Box<EvalAltResult>> {
match &self.value.0 {
Union::Unit(_) => self.deserialize_unit(visitor),
Union::Bool(_) => self.deserialize_bool(visitor),
Union::Str(_) => self.deserialize_str(visitor),
Union::Char(_) => self.deserialize_char(visitor),
#[cfg(not(feature = "only_i32"))]
Union::Int(_) => self.deserialize_i64(visitor),
#[cfg(feature = "only_i32")]
Union::Int(_) => self.deserialize_i32(visitor),
#[cfg(not(feature = "no_float"))]
Union::Float(_) => self.deserialize_f64(visitor),
#[cfg(not(feature = "no_index"))]
Union::Array(_) => self.deserialize_seq(visitor),
#[cfg(not(feature = "no_object"))]
Union::Map(_) => self.deserialize_map(visitor),
Union::FnPtr(_) => self.type_error(),
#[cfg(not(feature = "no_std"))]
Union::Variant(value) if value.is::<Instant>() => self.type_error(),
Union::Variant(value) if value.is::<i8>() => self.deserialize_i8(visitor),
Union::Variant(value) if value.is::<i16>() => self.deserialize_i16(visitor),
Union::Variant(value) if value.is::<i32>() => self.deserialize_i32(visitor),
Union::Variant(value) if value.is::<i64>() => self.deserialize_i64(visitor),
Union::Variant(value) if value.is::<u8>() => self.deserialize_u8(visitor),
Union::Variant(value) if value.is::<u16>() => self.deserialize_u16(visitor),
Union::Variant(value) if value.is::<u32>() => self.deserialize_u32(visitor),
Union::Variant(value) if value.is::<u64>() => self.deserialize_u64(visitor),
Union::Variant(_) => self.type_error(),
}
}
fn deserialize_bool<V: Visitor<'de>>(self, visitor: V) -> Result<V::Value, Box<EvalAltResult>> {
visitor.visit_bool(self.value.as_bool().or_else(|_| self.type_error())?)
}
fn deserialize_i8<V: Visitor<'de>>(self, visitor: V) -> Result<V::Value, Box<EvalAltResult>> {
self.value
.downcast_ref::<i8>()
.map_or_else(|| self.type_error(), |&x| visitor.visit_i8(x))
}
fn deserialize_i16<V: Visitor<'de>>(self, visitor: V) -> Result<V::Value, Box<EvalAltResult>> {
self.value
.downcast_ref::<i16>()
.map_or_else(|| self.type_error(), |&x| visitor.visit_i16(x))
}
fn deserialize_i32<V: Visitor<'de>>(self, visitor: V) -> Result<V::Value, Box<EvalAltResult>> {
self.value
.downcast_ref::<i32>()
.map_or_else(|| self.type_error(), |&x| visitor.visit_i32(x))
}
fn deserialize_i64<V: Visitor<'de>>(self, visitor: V) -> Result<V::Value, Box<EvalAltResult>> {
self.value
.downcast_ref::<i64>()
.map_or_else(|| self.type_error(), |&x| visitor.visit_i64(x))
}
fn deserialize_u8<V: Visitor<'de>>(self, visitor: V) -> Result<V::Value, Box<EvalAltResult>> {
self.value
.downcast_ref::<u8>()
.map_or_else(|| self.type_error(), |&x| visitor.visit_u8(x))
}
fn deserialize_u16<V: Visitor<'de>>(self, visitor: V) -> Result<V::Value, Box<EvalAltResult>> {
self.value
.downcast_ref::<u16>()
.map_or_else(|| self.type_error(), |&x| visitor.visit_u16(x))
}
fn deserialize_u32<V: Visitor<'de>>(self, visitor: V) -> Result<V::Value, Box<EvalAltResult>> {
self.value
.downcast_ref::<u32>()
.map_or_else(|| self.type_error(), |&x| visitor.visit_u32(x))
}
fn deserialize_u64<V: Visitor<'de>>(self, visitor: V) -> Result<V::Value, Box<EvalAltResult>> {
self.value
.downcast_ref::<u64>()
.map_or_else(|| self.type_error(), |&x| visitor.visit_u64(x))
}
fn deserialize_f32<V: Visitor<'de>>(self, visitor: V) -> Result<V::Value, Box<EvalAltResult>> {
#[cfg(not(feature = "no_float"))]
return self
.value
.downcast_ref::<f32>()
.map_or_else(|| self.type_error(), |&x| visitor.visit_f32(x));
#[cfg(feature = "no_float")]
return self.type_error_str("f32");
}
fn deserialize_f64<V: Visitor<'de>>(self, visitor: V) -> Result<V::Value, Box<EvalAltResult>> {
#[cfg(not(feature = "no_float"))]
return self
.value
.downcast_ref::<f64>()
.map_or_else(|| self.type_error(), |&x| visitor.visit_f64(x));
#[cfg(feature = "no_float")]
return self.type_error_str("f64");
}
fn deserialize_char<V: Visitor<'de>>(self, visitor: V) -> Result<V::Value, Box<EvalAltResult>> {
self.value
.downcast_ref::<char>()
.map_or_else(|| self.type_error(), |&x| visitor.visit_char(x))
}
fn deserialize_str<V: Visitor<'de>>(self, visitor: V) -> Result<V::Value, Box<EvalAltResult>> {
self.value.downcast_ref::<ImmutableString>().map_or_else(
|| self.type_error(),
|x| visitor.visit_borrowed_str(x.as_str()),
)
}
fn deserialize_string<V: Visitor<'de>>(
self,
visitor: V,
) -> Result<V::Value, Box<EvalAltResult>> {
self.deserialize_str(visitor)
}
fn deserialize_bytes<V: Visitor<'de>>(self, _: V) -> Result<V::Value, Box<EvalAltResult>> {
self.type_error()
}
fn deserialize_byte_buf<V: Visitor<'de>>(self, _: V) -> Result<V::Value, Box<EvalAltResult>> {
self.type_error()
}
fn deserialize_option<V: Visitor<'de>>(self, _: V) -> Result<V::Value, Box<EvalAltResult>> {
self.type_error()
}
fn deserialize_unit<V: Visitor<'de>>(self, visitor: V) -> Result<V::Value, Box<EvalAltResult>> {
self.value
.downcast_ref::<()>()
.map_or_else(|| self.type_error(), |_| visitor.visit_unit())
}
fn deserialize_unit_struct<V: Visitor<'de>>(
self,
_name: &'static str,
visitor: V,
) -> Result<V::Value, Box<EvalAltResult>> {
self.deserialize_unit(visitor)
}
fn deserialize_newtype_struct<V: Visitor<'de>>(
self,
_name: &'static str,
visitor: V,
) -> Result<V::Value, Box<EvalAltResult>> {
visitor.visit_newtype_struct(self)
}
fn deserialize_seq<V: Visitor<'de>>(self, visitor: V) -> Result<V::Value, Box<EvalAltResult>> {
#[cfg(not(feature = "no_index"))]
return self.value.downcast_ref::<Array>().map_or_else(
|| self.type_error(),
|arr| visitor.visit_seq(IterateArray::new(arr.iter())),
);
#[cfg(feature = "no_index")]
return self.type_error();
}
fn deserialize_tuple<V: Visitor<'de>>(
self,
_len: usize,
visitor: V,
) -> Result<V::Value, Box<EvalAltResult>> {
self.deserialize_seq(visitor)
}
fn deserialize_tuple_struct<V: Visitor<'de>>(
self,
_name: &'static str,
_len: usize,
visitor: V,
) -> Result<V::Value, Box<EvalAltResult>> {
self.deserialize_seq(visitor)
}
fn deserialize_map<V: Visitor<'de>>(self, visitor: V) -> Result<V::Value, Box<EvalAltResult>> {
#[cfg(not(feature = "no_object"))]
return self.value.downcast_ref::<Map>().map_or_else(
|| self.type_error(),
|map| visitor.visit_map(IterateMap::new(map.keys(), map.values())),
);
#[cfg(feature = "no_object")]
return self.type_error();
}
fn deserialize_struct<V: Visitor<'de>>(
self,
_name: &'static str,
_fields: &'static [&'static str],
visitor: V,
) -> Result<V::Value, Box<EvalAltResult>> {
self.deserialize_map(visitor)
}
fn deserialize_enum<V: Visitor<'de>>(
self,
_name: &'static str,
_variants: &'static [&'static str],
_: V,
) -> Result<V::Value, Box<EvalAltResult>> {
self.type_error()
}
fn deserialize_identifier<V: Visitor<'de>>(
self,
visitor: V,
) -> Result<V::Value, Box<EvalAltResult>> {
self.deserialize_str(visitor)
}
fn deserialize_ignored_any<V: Visitor<'de>>(
self,
visitor: V,
) -> Result<V::Value, Box<EvalAltResult>> {
self.deserialize_any(visitor)
}
}
/// `SeqAccess` implementation for arrays.
struct IterateArray<'a, ITER>
where
ITER: Iterator<Item = &'a Dynamic>,
{
/// Iterator for a stream of `Dynamic` values.
iter: ITER,
}
impl<'a, ITER> IterateArray<'a, ITER>
where
ITER: Iterator<Item = &'a Dynamic>,
{
pub fn new(iter: ITER) -> Self {
Self { iter }
}
}
impl<'a: 'de, 'de, ITER> SeqAccess<'de> for IterateArray<'a, ITER>
where
ITER: Iterator<Item = &'a Dynamic>,
{
type Error = Box<EvalAltResult>;
fn next_element_seed<T: DeserializeSeed<'de>>(
&mut self,
seed: T,
) -> Result<Option<T::Value>, Box<EvalAltResult>> {
// Deserialize each item coming out of the iterator.
match self.iter.next() {
None => Ok(None),
Some(item) => seed
.deserialize(&mut DynamicDeserializer::from_dynamic(item))
.map(Some),
}
}
}
/// `MapAccess` implementation for maps.
struct IterateMap<'a, KEYS, VALUES>
where
KEYS: Iterator<Item = &'a ImmutableString>,
VALUES: Iterator<Item = &'a Dynamic>,
{
// Iterator for a stream of `Dynamic` keys.
keys: KEYS,
// Iterator for a stream of `Dynamic` values.
values: VALUES,
}
impl<'a, KEYS, VALUES> IterateMap<'a, KEYS, VALUES>
where
KEYS: Iterator<Item = &'a ImmutableString>,
VALUES: Iterator<Item = &'a Dynamic>,
{
pub fn new(keys: KEYS, values: VALUES) -> Self {
Self { keys, values }
}
}
impl<'a: 'de, 'de, KEYS, VALUES> MapAccess<'de> for IterateMap<'a, KEYS, VALUES>
where
KEYS: Iterator<Item = &'a ImmutableString>,
VALUES: Iterator<Item = &'a Dynamic>,
{
type Error = Box<EvalAltResult>;
fn next_key_seed<K: DeserializeSeed<'de>>(
&mut self,
seed: K,
) -> Result<Option<K::Value>, Box<EvalAltResult>> {
// Deserialize each `ImmutableString` key coming out of the keys iterator.
match self.keys.next() {
None => Ok(None),
Some(item) => seed
.deserialize(&mut ImmutableStringDeserializer::from_str(item))
.map(Some),
}
}
fn next_value_seed<V: DeserializeSeed<'de>>(
&mut self,
seed: V,
) -> Result<V::Value, Box<EvalAltResult>> {
// Deserialize each value item coming out of the iterator.
seed.deserialize(&mut DynamicDeserializer::from_dynamic(
self.values.next().unwrap(),
))
}
}

5
src/serde/mod.rs Normal file
View File

@ -0,0 +1,5 @@
//! Helper module defining serialization/deserialization support for [`serde`](https://crates.io/crates/serde).
pub mod de;
pub mod ser;
mod str;

549
src/serde/ser.rs Normal file
View File

@ -0,0 +1,549 @@
//! Implement serialization support of `Dynamic` for [`serde`](https://crates.io/crates/serde).
use crate::any::Dynamic;
use crate::result::EvalAltResult;
use crate::token::Position;
#[cfg(not(feature = "no_index"))]
use crate::engine::Array;
#[cfg(not(feature = "no_object"))]
use crate::engine::Map;
use serde::ser::{
Error, SerializeMap, SerializeSeq, SerializeStruct, SerializeStructVariant, SerializeTuple,
SerializeTupleStruct, SerializeTupleVariant, Serializer,
};
use serde::Serialize;
use crate::stdlib::{any::type_name, fmt, mem};
/// Serializer for `Dynamic` which is kept as a reference.
pub struct DynamicSerializer {
/// Buffer to hold a temporary key.
key: Dynamic,
/// Buffer to hold a temporary value.
value: Dynamic,
}
impl DynamicSerializer {
/// Create a `DynamicSerializer` from a `Dynamic` value.
pub fn new(value: Dynamic) -> Self {
Self {
key: Default::default(),
value,
}
}
}
/// Serialize a Rust type that implements `serde::Serialize` into a `Dynamic`.
///
/// # Examples
///
/// ```
/// # fn main() -> Result<(), Box<rhai::EvalAltResult>> {
/// # #[cfg(not(feature = "no_index"))]
/// # #[cfg(not(feature = "no_object"))]
/// # #[cfg(not(feature = "no_float"))]
/// # {
/// use rhai::{Dynamic, Array, Map, INT};
/// use rhai::ser::to_dynamic;
/// use serde::Serialize;
///
/// #[derive(Debug, serde::Serialize, PartialEq)]
/// struct Point {
/// x: f64,
/// y: f64
/// }
///
/// #[derive(Debug, serde::Serialize, PartialEq)]
/// struct MyStruct {
/// a: i64,
/// b: Vec<String>,
/// c: bool,
/// d: Point
/// }
///
/// let x = MyStruct {
/// a: 42,
/// b: vec![ "hello".into(), "world".into() ],
/// c: true,
/// d: Point { x: 123.456, y: 999.0 }
/// };
///
/// // Convert the 'MyStruct' into a 'Dynamic'
/// let value = to_dynamic(x)?;
///
/// assert!(value.is::<Map>());
///
/// let map = value.cast::<Map>();
/// let point = map.get("d").unwrap().downcast_ref::<Map>().unwrap();
/// assert_eq!(*point.get("x").unwrap().downcast_ref::<f64>().unwrap(), 123.456);
/// assert_eq!(*point.get("y").unwrap().downcast_ref::<f64>().unwrap(), 999.0);
/// # }
/// # Ok(())
/// # }
/// ```
pub fn to_dynamic<T: Serialize>(value: T) -> Result<Dynamic, Box<EvalAltResult>> {
let mut s = DynamicSerializer::new(Default::default());
value.serialize(&mut s)
}
impl Error for Box<EvalAltResult> {
fn custom<T: fmt::Display>(err: T) -> Self {
Box::new(EvalAltResult::ErrorRuntime(
err.to_string(),
Position::none(),
))
}
}
impl Serializer for &mut DynamicSerializer {
type Ok = Dynamic;
type Error = Box<EvalAltResult>;
type SerializeSeq = DynamicSerializer;
type SerializeTuple = DynamicSerializer;
type SerializeTupleStruct = DynamicSerializer;
type SerializeTupleVariant = DynamicSerializer;
type SerializeMap = DynamicSerializer;
type SerializeStruct = DynamicSerializer;
type SerializeStructVariant = DynamicSerializer;
fn serialize_bool(self, v: bool) -> Result<Self::Ok, Box<EvalAltResult>> {
Ok(v.into())
}
fn serialize_i8(self, v: i8) -> Result<Self::Ok, Box<EvalAltResult>> {
#[cfg(not(feature = "only_i32"))]
return self.serialize_i64(i64::from(v));
#[cfg(feature = "only_i32")]
return self.serialize_i32(i32::from(v));
}
fn serialize_i16(self, v: i16) -> Result<Self::Ok, Box<EvalAltResult>> {
#[cfg(not(feature = "only_i32"))]
return self.serialize_i64(i64::from(v));
#[cfg(feature = "only_i32")]
return self.serialize_i32(i32::from(v));
}
fn serialize_i32(self, v: i32) -> Result<Self::Ok, Box<EvalAltResult>> {
#[cfg(not(feature = "only_i32"))]
return self.serialize_i64(i64::from(v));
#[cfg(feature = "only_i32")]
return Ok(v.into());
}
fn serialize_i64(self, v: i64) -> Result<Self::Ok, Box<EvalAltResult>> {
#[cfg(not(feature = "only_i32"))]
return Ok(v.into());
#[cfg(feature = "only_i32")]
if v > i32::MAX as i64 {
return Ok(Dynamic::from(v));
} else {
return self.serialize_i32(v as i32);
}
}
fn serialize_u8(self, v: u8) -> Result<Self::Ok, Box<EvalAltResult>> {
#[cfg(not(feature = "only_i32"))]
return self.serialize_i64(i64::from(v));
#[cfg(feature = "only_i32")]
return self.serialize_i32(i32::from(v));
}
fn serialize_u16(self, v: u16) -> Result<Self::Ok, Box<EvalAltResult>> {
#[cfg(not(feature = "only_i32"))]
return self.serialize_i64(i64::from(v));
#[cfg(feature = "only_i32")]
return self.serialize_i32(i32::from(v));
}
fn serialize_u32(self, v: u32) -> Result<Self::Ok, Box<EvalAltResult>> {
#[cfg(not(feature = "only_i32"))]
return self.serialize_i64(i64::from(v));
#[cfg(feature = "only_i32")]
if v > i32::MAX as u64 {
return Ok(Dynamic::from(v));
} else {
return self.serialize_i32(v as i32);
}
}
fn serialize_u64(self, v: u64) -> Result<Self::Ok, Box<EvalAltResult>> {
#[cfg(not(feature = "only_i32"))]
if v > i64::MAX as u64 {
return Ok(Dynamic::from(v));
} else {
return self.serialize_i64(v as i64);
}
#[cfg(feature = "only_i32")]
if v > i32::MAX as u64 {
return Ok(Dynamic::from(v));
} else {
return self.serialize_i32(v as i32);
}
}
fn serialize_f32(self, v: f32) -> Result<Self::Ok, Box<EvalAltResult>> {
Ok(Dynamic::from(v))
}
fn serialize_f64(self, v: f64) -> Result<Self::Ok, Box<EvalAltResult>> {
Ok(Dynamic::from(v))
}
fn serialize_char(self, v: char) -> Result<Self::Ok, Box<EvalAltResult>> {
Ok(v.into())
}
fn serialize_str(self, v: &str) -> Result<Self::Ok, Box<EvalAltResult>> {
Ok(v.to_string().into())
}
fn serialize_bytes(self, v: &[u8]) -> Result<Self::Ok, Box<EvalAltResult>> {
Ok(Dynamic::from(v.to_vec()))
}
fn serialize_none(self) -> Result<Self::Ok, Box<EvalAltResult>> {
Ok(().into())
}
fn serialize_some<T: ?Sized + Serialize>(
self,
value: &T,
) -> Result<Self::Ok, Box<EvalAltResult>> {
value.serialize(&mut *self)
}
fn serialize_unit(self) -> Result<Self::Ok, Box<EvalAltResult>> {
Ok(().into())
}
fn serialize_unit_struct(self, _name: &'static str) -> Result<Self::Ok, Box<EvalAltResult>> {
self.serialize_unit()
}
fn serialize_unit_variant(
self,
_name: &'static str,
_variant_index: u32,
variant: &'static str,
) -> Result<Self::Ok, Box<EvalAltResult>> {
self.serialize_str(variant)
}
fn serialize_newtype_struct<T: ?Sized + Serialize>(
self,
_name: &'static str,
value: &T,
) -> Result<Self::Ok, Box<EvalAltResult>> {
value.serialize(&mut *self)
}
fn serialize_newtype_variant<T: ?Sized + Serialize>(
self,
_name: &'static str,
_variant_index: u32,
_variant: &'static str,
value: &T,
) -> Result<Self::Ok, Box<EvalAltResult>> {
value.serialize(&mut *self)
}
fn serialize_seq(self, _len: Option<usize>) -> Result<Self::SerializeSeq, Box<EvalAltResult>> {
#[cfg(not(feature = "no_index"))]
return Ok(DynamicSerializer::new(Array::new().into()));
#[cfg(feature = "no_index")]
return Err(Box::new(EvalAltResult::ErrorMismatchOutputType(
"Dynamic".into(),
"array".into(),
Position::none(),
)));
}
fn serialize_tuple(self, len: usize) -> Result<Self::SerializeTuple, Box<EvalAltResult>> {
self.serialize_seq(Some(len))
}
fn serialize_tuple_struct(
self,
_name: &'static str,
len: usize,
) -> Result<Self::SerializeTupleStruct, Box<EvalAltResult>> {
self.serialize_seq(Some(len))
}
fn serialize_tuple_variant(
self,
_name: &'static str,
_variant_index: u32,
_variant: &'static str,
len: usize,
) -> Result<Self::SerializeTupleVariant, Box<EvalAltResult>> {
self.serialize_seq(Some(len))
}
fn serialize_map(self, _len: Option<usize>) -> Result<Self::SerializeMap, Box<EvalAltResult>> {
#[cfg(not(feature = "no_object"))]
return Ok(DynamicSerializer::new(Map::new().into()));
#[cfg(feature = "no_object")]
return Err(Box::new(EvalAltResult::ErrorMismatchOutputType(
"Dynamic".into(),
"map".into(),
Position::none(),
)));
}
fn serialize_struct(
self,
_name: &'static str,
len: usize,
) -> Result<Self::SerializeStruct, Box<EvalAltResult>> {
self.serialize_map(Some(len))
}
fn serialize_struct_variant(
self,
_name: &'static str,
_variant_index: u32,
_variant: &'static str,
len: usize,
) -> Result<Self::SerializeStructVariant, Box<EvalAltResult>> {
self.serialize_map(Some(len))
}
}
impl SerializeSeq for DynamicSerializer {
type Ok = Dynamic;
type Error = Box<EvalAltResult>;
fn serialize_element<T: ?Sized + Serialize>(
&mut self,
value: &T,
) -> Result<(), Box<EvalAltResult>> {
#[cfg(not(feature = "no_index"))]
{
let value = value.serialize(&mut *self)?;
let arr = self.value.downcast_mut::<Array>().unwrap();
arr.push(value);
Ok(())
}
#[cfg(feature = "no_index")]
unreachable!()
}
// Close the sequence.
fn end(self) -> Result<Self::Ok, Box<EvalAltResult>> {
#[cfg(not(feature = "no_index"))]
return Ok(self.value);
#[cfg(feature = "no_index")]
unreachable!()
}
}
impl SerializeTuple for DynamicSerializer {
type Ok = Dynamic;
type Error = Box<EvalAltResult>;
fn serialize_element<T: ?Sized + Serialize>(
&mut self,
value: &T,
) -> Result<(), Box<EvalAltResult>> {
#[cfg(not(feature = "no_index"))]
{
let value = value.serialize(&mut *self)?;
let arr = self.value.downcast_mut::<Array>().unwrap();
arr.push(value);
Ok(())
}
#[cfg(feature = "no_index")]
unreachable!()
}
fn end(self) -> Result<Self::Ok, Box<EvalAltResult>> {
#[cfg(not(feature = "no_index"))]
return Ok(self.value);
#[cfg(feature = "no_index")]
unreachable!()
}
}
impl SerializeTupleStruct for DynamicSerializer {
type Ok = Dynamic;
type Error = Box<EvalAltResult>;
fn serialize_field<T: ?Sized + Serialize>(
&mut self,
value: &T,
) -> Result<(), Box<EvalAltResult>> {
#[cfg(not(feature = "no_index"))]
{
let value = value.serialize(&mut *self)?;
let arr = self.value.downcast_mut::<Array>().unwrap();
arr.push(value);
Ok(())
}
#[cfg(feature = "no_index")]
unreachable!()
}
fn end(self) -> Result<Self::Ok, Box<EvalAltResult>> {
#[cfg(not(feature = "no_index"))]
return Ok(self.value);
#[cfg(feature = "no_index")]
unreachable!()
}
}
impl SerializeTupleVariant for DynamicSerializer {
type Ok = Dynamic;
type Error = Box<EvalAltResult>;
fn serialize_field<T: ?Sized + Serialize>(
&mut self,
value: &T,
) -> Result<(), Box<EvalAltResult>> {
#[cfg(not(feature = "no_index"))]
{
let value = value.serialize(&mut *self)?;
let arr = self.value.downcast_mut::<Array>().unwrap();
arr.push(value);
Ok(())
}
#[cfg(feature = "no_index")]
unreachable!()
}
fn end(self) -> Result<Self::Ok, Box<EvalAltResult>> {
#[cfg(not(feature = "no_index"))]
return Ok(self.value);
#[cfg(feature = "no_index")]
unreachable!()
}
}
impl SerializeMap for DynamicSerializer {
type Ok = Dynamic;
type Error = Box<EvalAltResult>;
fn serialize_key<T: ?Sized + Serialize>(&mut self, key: &T) -> Result<(), Box<EvalAltResult>> {
#[cfg(not(feature = "no_object"))]
{
self.key = key.serialize(&mut *self)?;
Ok(())
}
#[cfg(feature = "no_object")]
unreachable!()
}
fn serialize_value<T: ?Sized + Serialize>(
&mut self,
value: &T,
) -> Result<(), Box<EvalAltResult>> {
#[cfg(not(feature = "no_object"))]
{
let key = mem::take(&mut self.key)
.take_immutable_string()
.map_err(|typ| {
Box::new(EvalAltResult::ErrorMismatchOutputType(
"string".into(),
typ.into(),
Position::none(),
))
})?;
let value = value.serialize(&mut *self)?;
let map = self.value.downcast_mut::<Map>().unwrap();
map.insert(key, value);
Ok(())
}
#[cfg(feature = "no_object")]
unreachable!()
}
fn serialize_entry<K: ?Sized + Serialize, T: ?Sized + Serialize>(
&mut self,
key: &K,
value: &T,
) -> Result<(), Box<EvalAltResult>> {
#[cfg(not(feature = "no_object"))]
{
let key: Dynamic = key.serialize(&mut *self)?;
let key = key.take_immutable_string().map_err(|typ| {
Box::new(EvalAltResult::ErrorMismatchOutputType(
"string".into(),
typ.into(),
Position::none(),
))
})?;
let value = value.serialize(&mut *self)?;
let map = self.value.downcast_mut::<Map>().unwrap();
map.insert(key, value);
Ok(())
}
#[cfg(feature = "no_object")]
unreachable!()
}
fn end(self) -> Result<Self::Ok, Box<EvalAltResult>> {
#[cfg(not(feature = "no_object"))]
return Ok(self.value);
#[cfg(feature = "no_object")]
unreachable!()
}
}
impl SerializeStruct for DynamicSerializer {
type Ok = Dynamic;
type Error = Box<EvalAltResult>;
fn serialize_field<T: ?Sized + Serialize>(
&mut self,
key: &'static str,
value: &T,
) -> Result<(), Box<EvalAltResult>> {
#[cfg(not(feature = "no_object"))]
{
let value = value.serialize(&mut *self)?;
let map = self.value.downcast_mut::<Map>().unwrap();
map.insert(key.into(), value);
Ok(())
}
#[cfg(feature = "no_object")]
unreachable!()
}
fn end(self) -> Result<Self::Ok, Box<EvalAltResult>> {
#[cfg(not(feature = "no_object"))]
return Ok(self.value);
#[cfg(feature = "no_object")]
unreachable!()
}
}
impl SerializeStructVariant for DynamicSerializer {
type Ok = Dynamic;
type Error = Box<EvalAltResult>;
fn serialize_field<T: ?Sized + Serialize>(
&mut self,
key: &'static str,
value: &T,
) -> Result<(), Box<EvalAltResult>> {
#[cfg(not(feature = "no_object"))]
{
let value = value.serialize(&mut *self)?;
let map = self.value.downcast_mut::<Map>().unwrap();
map.insert(key.into(), value);
Ok(())
}
#[cfg(feature = "no_object")]
unreachable!()
}
fn end(self) -> Result<Self::Ok, Box<EvalAltResult>> {
#[cfg(not(feature = "no_object"))]
return Ok(self.value);
#[cfg(feature = "no_object")]
unreachable!()
}
}

155
src/serde/str.rs Normal file
View File

@ -0,0 +1,155 @@
//! Implement deserialization support of `ImmutableString` for [`serde`](https://crates.io/crates/serde).
use crate::result::EvalAltResult;
use crate::token::Position;
use crate::utils::ImmutableString;
use serde::de::{Deserializer, Visitor};
use crate::stdlib::any::type_name;
/// Deserializer for `ImmutableString`.
pub struct ImmutableStringDeserializer<'a> {
value: &'a ImmutableString,
}
impl<'a> ImmutableStringDeserializer<'a> {
/// Create an `ImmutableStringDeserializer` from an `ImmutableString` reference.
pub fn from_str(value: &'a ImmutableString) -> Self {
Self { value }
}
/// Shortcut for a type conversion error.
fn type_error<T>(&self) -> Result<T, Box<EvalAltResult>> {
Err(Box::new(EvalAltResult::ErrorMismatchOutputType(
type_name::<T>().into(),
"string".into(),
Position::none(),
)))
}
}
impl<'de> Deserializer<'de> for &mut ImmutableStringDeserializer<'de> {
type Error = Box<EvalAltResult>;
fn deserialize_any<V: Visitor<'de>>(self, v: V) -> Result<V::Value, Box<EvalAltResult>> {
self.deserialize_str(v)
}
fn deserialize_bool<V: Visitor<'de>>(self, _: V) -> Result<V::Value, Box<EvalAltResult>> {
self.type_error()
}
fn deserialize_i8<V: Visitor<'de>>(self, _: V) -> Result<V::Value, Box<EvalAltResult>> {
self.type_error()
}
fn deserialize_i16<V: Visitor<'de>>(self, _: V) -> Result<V::Value, Box<EvalAltResult>> {
self.type_error()
}
fn deserialize_i32<V: Visitor<'de>>(self, _: V) -> Result<V::Value, Box<EvalAltResult>> {
self.type_error()
}
fn deserialize_i64<V: Visitor<'de>>(self, _: V) -> Result<V::Value, Box<EvalAltResult>> {
self.type_error()
}
fn deserialize_u8<V: Visitor<'de>>(self, _: V) -> Result<V::Value, Box<EvalAltResult>> {
self.type_error()
}
fn deserialize_u16<V: Visitor<'de>>(self, _: V) -> Result<V::Value, Box<EvalAltResult>> {
self.type_error()
}
fn deserialize_u32<V: Visitor<'de>>(self, _: V) -> Result<V::Value, Box<EvalAltResult>> {
self.type_error()
}
fn deserialize_u64<V: Visitor<'de>>(self, _: V) -> Result<V::Value, Box<EvalAltResult>> {
self.type_error()
}
fn deserialize_f32<V: Visitor<'de>>(self, _: V) -> Result<V::Value, Box<EvalAltResult>> {
self.type_error()
}
fn deserialize_f64<V: Visitor<'de>>(self, _: V) -> Result<V::Value, Box<EvalAltResult>> {
self.type_error()
}
fn deserialize_char<V: Visitor<'de>>(self, _: V) -> Result<V::Value, Box<EvalAltResult>> {
self.type_error()
}
fn deserialize_str<V: Visitor<'de>>(self, v: V) -> Result<V::Value, Box<EvalAltResult>> {
// Only allow deserialization into a string.
v.visit_borrowed_str(self.value.as_str())
}
fn deserialize_string<V: Visitor<'de>>(
self,
visitor: V,
) -> Result<V::Value, Box<EvalAltResult>> {
self.deserialize_str(visitor)
}
fn deserialize_bytes<V: Visitor<'de>>(self, _: V) -> Result<V::Value, Box<EvalAltResult>> {
self.type_error()
}
fn deserialize_byte_buf<V: Visitor<'de>>(self, _: V) -> Result<V::Value, Box<EvalAltResult>> {
self.type_error()
}
fn deserialize_option<V: Visitor<'de>>(self, _: V) -> Result<V::Value, Box<EvalAltResult>> {
self.type_error()
}
fn deserialize_unit<V: Visitor<'de>>(self, _: V) -> Result<V::Value, Box<EvalAltResult>> {
self.type_error()
}
fn deserialize_unit_struct<V: Visitor<'de>>(
self,
_name: &'static str,
v: V,
) -> Result<V::Value, Box<EvalAltResult>> {
self.deserialize_unit(v)
}
fn deserialize_newtype_struct<V: Visitor<'de>>(
self,
_name: &'static str,
v: V,
) -> Result<V::Value, Box<EvalAltResult>> {
v.visit_newtype_struct(self)
}
fn deserialize_seq<V: Visitor<'de>>(self, _: V) -> Result<V::Value, Box<EvalAltResult>> {
self.type_error()
}
fn deserialize_tuple<V: Visitor<'de>>(
self,
_len: usize,
v: V,
) -> Result<V::Value, Box<EvalAltResult>> {
self.deserialize_seq(v)
}
fn deserialize_tuple_struct<V: Visitor<'de>>(
self,
_name: &'static str,
_len: usize,
v: V,
) -> Result<V::Value, Box<EvalAltResult>> {
self.deserialize_seq(v)
}
fn deserialize_map<V: Visitor<'de>>(self, _: V) -> Result<V::Value, Box<EvalAltResult>> {
self.type_error()
}
fn deserialize_struct<V: Visitor<'de>>(
self,
_name: &'static str,
_fields: &'static [&'static str],
v: V,
) -> Result<V::Value, Box<EvalAltResult>> {
self.deserialize_map(v)
}
fn deserialize_enum<V: Visitor<'de>>(
self,
_name: &'static str,
_variants: &'static [&'static str],
_: V,
) -> Result<V::Value, Box<EvalAltResult>> {
self.type_error()
}
fn deserialize_identifier<V: Visitor<'de>>(self, v: V) -> Result<V::Value, Box<EvalAltResult>> {
self.deserialize_str(v)
}
fn deserialize_ignored_any<V: Visitor<'de>>(
self,
v: V,
) -> Result<V::Value, Box<EvalAltResult>> {
self.deserialize_any(v)
}
}

254
src/settings.rs Normal file
View File

@ -0,0 +1,254 @@
use crate::engine::Engine;
use crate::module::ModuleResolver;
use crate::optimize::OptimizationLevel;
use crate::packages::PackageLibrary;
use crate::token::is_valid_identifier;
impl Engine {
/// Load a new package into the `Engine`.
///
/// When searching for functions, packages loaded later are preferred.
/// In other words, loaded packages are searched in reverse order.
pub fn load_package(&mut self, package: PackageLibrary) {
// Push the package to the top - packages are searched in reverse order
self.packages.push(package);
}
/// Load a new package into the `Engine`.
///
/// When searching for functions, packages loaded later are preferred.
/// In other words, loaded packages are searched in reverse order.
pub fn load_packages(&mut self, package: PackageLibrary) {
// Push the package to the top - packages are searched in reverse order
self.packages.push(package);
}
/// Control whether and how the `Engine` will optimize an AST after compilation.
///
/// Not available under the `no_optimize` feature.
#[cfg(not(feature = "no_optimize"))]
pub fn set_optimization_level(&mut self, optimization_level: OptimizationLevel) {
self.optimization_level = optimization_level
}
/// The current optimization level.
/// It controls whether and how the `Engine` will optimize an AST after compilation.
///
/// Not available under the `no_optimize` feature.
#[cfg(not(feature = "no_optimize"))]
pub fn optimization_level(&self) -> OptimizationLevel {
self.optimization_level
}
/// Set the maximum levels of function calls allowed for a script in order to avoid
/// infinite recursion and stack overflows.
#[cfg(not(feature = "unchecked"))]
pub fn set_max_call_levels(&mut self, levels: usize) {
self.max_call_stack_depth = levels
}
/// The maximum levels of function calls allowed for a script.
#[cfg(not(feature = "unchecked"))]
pub fn max_call_levels(&self) -> usize {
self.max_call_stack_depth
}
/// Set the maximum number of operations allowed for a script to run to avoid
/// consuming too much resources (0 for unlimited).
#[cfg(not(feature = "unchecked"))]
pub fn set_max_operations(&mut self, operations: u64) {
self.max_operations = if operations == u64::MAX {
0
} else {
operations
};
}
/// The maximum number of operations allowed for a script to run (0 for unlimited).
#[cfg(not(feature = "unchecked"))]
pub fn max_operations(&self) -> u64 {
self.max_operations
}
/// Set the maximum number of imported modules allowed for a script.
#[cfg(not(feature = "unchecked"))]
pub fn set_max_modules(&mut self, modules: usize) {
self.max_modules = modules;
}
/// The maximum number of imported modules allowed for a script.
#[cfg(not(feature = "unchecked"))]
pub fn max_modules(&self) -> usize {
self.max_modules
}
/// Set the depth limits for expressions (0 for unlimited).
#[cfg(not(feature = "unchecked"))]
pub fn set_max_expr_depths(&mut self, max_expr_depth: usize, max_function_expr_depth: usize) {
self.max_expr_depth = if max_expr_depth == usize::MAX {
0
} else {
max_expr_depth
};
self.max_function_expr_depth = if max_function_expr_depth == usize::MAX {
0
} else {
max_function_expr_depth
};
}
/// The depth limit for expressions (0 for unlimited).
#[cfg(not(feature = "unchecked"))]
pub fn max_expr_depth(&self) -> usize {
self.max_expr_depth
}
/// The depth limit for expressions in functions (0 for unlimited).
#[cfg(not(feature = "unchecked"))]
pub fn max_function_expr_depth(&self) -> usize {
self.max_function_expr_depth
}
/// Set the maximum length of strings (0 for unlimited).
#[cfg(not(feature = "unchecked"))]
pub fn set_max_string_size(&mut self, max_size: usize) {
self.max_string_size = if max_size == usize::MAX { 0 } else { max_size };
}
/// The maximum length of strings (0 for unlimited).
#[cfg(not(feature = "unchecked"))]
pub fn max_string_size(&self) -> usize {
self.max_string_size
}
/// Set the maximum length of arrays (0 for unlimited).
#[cfg(not(feature = "unchecked"))]
#[cfg(not(feature = "no_index"))]
pub fn set_max_array_size(&mut self, max_size: usize) {
self.max_array_size = if max_size == usize::MAX { 0 } else { max_size };
}
/// The maximum length of arrays (0 for unlimited).
#[cfg(not(feature = "unchecked"))]
#[cfg(not(feature = "no_index"))]
pub fn max_array_size(&self) -> usize {
self.max_array_size
}
/// Set the maximum length of object maps (0 for unlimited).
#[cfg(not(feature = "unchecked"))]
#[cfg(not(feature = "no_object"))]
pub fn set_max_map_size(&mut self, max_size: usize) {
self.max_map_size = if max_size == usize::MAX { 0 } else { max_size };
}
/// The maximum length of object maps (0 for unlimited).
#[cfg(not(feature = "unchecked"))]
#[cfg(not(feature = "no_object"))]
pub fn max_map_size(&self) -> usize {
self.max_map_size
}
/// Set the module resolution service used by the `Engine`.
///
/// Not available under the `no_module` feature.
#[cfg(not(feature = "no_module"))]
pub fn set_module_resolver(&mut self, resolver: Option<impl ModuleResolver + 'static>) {
self.module_resolver = resolver.map(|f| Box::new(f) as Box<dyn ModuleResolver>);
}
/// Disable a particular keyword or operator in the language.
///
/// # Examples
///
/// The following will raise an error during parsing because the `if` keyword is disabled
/// and is recognized as a variable name!
///
/// ```rust,should_panic
/// # fn main() -> Result<(), rhai::ParseError> {
/// use rhai::Engine;
///
/// let mut engine = Engine::new();
///
/// engine.disable_symbol("if"); // disable the 'if' keyword
///
/// engine.compile("let x = if true { 42 } else { 0 };")?;
/// // ^ 'if' is parsed as a variable name
/// // ^ missing ';' after statement end
/// # Ok(())
/// # }
/// ```
///
/// The following will raise an error during parsing because the `+=` operator is disabled.
///
/// ```rust,should_panic
/// # fn main() -> Result<(), rhai::ParseError> {
/// use rhai::Engine;
///
/// let mut engine = Engine::new();
///
/// engine.disable_symbol("+="); // disable the '+=' operator
///
/// engine.compile("let x = 42; x += 1;")?;
/// // ^ unknown operator
/// # Ok(())
/// # }
/// ```
pub fn disable_symbol(&mut self, symbol: &str) {
if self.disabled_symbols.is_none() {
self.disabled_symbols = Some(Default::default());
}
self.disabled_symbols
.as_mut()
.unwrap()
.insert(symbol.into());
}
/// Register a custom operator into the language.
///
/// The operator must be a valid identifier (i.e. it cannot be a symbol).
///
/// # Examples
///
/// ```rust
/// # fn main() -> Result<(), Box<rhai::EvalAltResult>> {
/// use rhai::{Engine, RegisterFn};
///
/// let mut engine = Engine::new();
///
/// // Register a custom operator called 'foo' and give it
/// // a precedence of 140 (i.e. between +|- and *|/).
/// engine.register_custom_operator("foo", 140).unwrap();
///
/// // Register a binary function named 'foo'
/// engine.register_fn("foo", |x: i64, y: i64| (x * y) - (x + y));
///
/// assert_eq!(
/// engine.eval_expression::<i64>("1 + 2 * 3 foo 4 - 5 / 6")?,
/// 15
/// );
/// # Ok(())
/// # }
/// ```
pub fn register_custom_operator(
&mut self,
keyword: &str,
precedence: u8,
) -> Result<(), String> {
if !is_valid_identifier(keyword.chars()) {
return Err(format!("not a valid identifier: '{}'", keyword).into());
}
if self.custom_keywords.is_none() {
self.custom_keywords = Some(Default::default());
}
self.custom_keywords
.as_mut()
.unwrap()
.insert(keyword.into(), precedence);
Ok(())
}
}

View File

@ -1,5 +1,6 @@
//! Main module defining the lexer and parser. //! Main module defining the lexer and parser.
use crate::engine::Engine;
use crate::error::LexError; use crate::error::LexError;
use crate::parser::INT; use crate::parser::INT;
use crate::utils::StaticVec; use crate::utils::StaticVec;
@ -10,7 +11,9 @@ use crate::parser::FLOAT;
use crate::stdlib::{ use crate::stdlib::{
borrow::Cow, borrow::Cow,
boxed::Box, boxed::Box,
char, fmt, char,
collections::{HashMap, HashSet},
fmt,
iter::Peekable, iter::Peekable,
str::{Chars, FromStr}, str::{Chars, FromStr},
string::{String, ToString}, string::{String, ToString},
@ -19,7 +22,7 @@ use crate::stdlib::{
type LERR = LexError; type LERR = LexError;
pub type TokenStream<'a> = Peekable<TokenIterator<'a>>; pub type TokenStream<'a, 't> = Peekable<TokenIterator<'a, 't>>;
/// A location (line number + character position) in the input script. /// A location (line number + character position) in the input script.
/// ///
@ -137,7 +140,7 @@ pub enum Token {
FloatConstant(FLOAT), FloatConstant(FLOAT),
Identifier(String), Identifier(String),
CharConstant(char), CharConstant(char),
StringConst(String), StringConstant(String),
LeftBrace, LeftBrace,
RightBrace, RightBrace,
LeftParen, LeftParen,
@ -210,6 +213,7 @@ pub enum Token {
As, As,
LexError(Box<LexError>), LexError(Box<LexError>),
Comment(String), Comment(String),
Custom(String),
EOF, EOF,
} }
@ -222,12 +226,13 @@ impl Token {
IntegerConstant(i) => i.to_string().into(), IntegerConstant(i) => i.to_string().into(),
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
FloatConstant(f) => f.to_string().into(), FloatConstant(f) => f.to_string().into(),
Identifier(s) => s.clone().into(), StringConstant(_) => "string".into(),
CharConstant(c) => c.to_string().into(), CharConstant(c) => c.to_string().into(),
Identifier(s) => s.clone().into(),
Custom(s) => s.clone().into(),
LexError(err) => err.to_string().into(), LexError(err) => err.to_string().into(),
token => (match token { token => match token {
StringConst(_) => "string",
LeftBrace => "{", LeftBrace => "{",
RightBrace => "}", RightBrace => "}",
LeftParen => "(", LeftParen => "(",
@ -292,13 +297,15 @@ impl Token {
PowerOfAssign => "~=", PowerOfAssign => "~=",
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]
Private => "private", Private => "private",
#[cfg(not(feature = "no_module"))]
Import => "import", Import => "import",
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
Export => "export", Export => "export",
#[cfg(not(feature = "no_module"))]
As => "as", As => "as",
EOF => "{EOF}", EOF => "{EOF}",
_ => unreachable!("operator should be match in outer scope"), _ => unreachable!("operator should be match in outer scope"),
}) }
.into(), .into(),
} }
} }
@ -320,9 +327,9 @@ impl Token {
match self { match self {
LexError(_) | LexError(_) |
LeftBrace | // (+expr) - is unary LeftBrace | // {+expr} - is unary
// RightBrace | {expr} - expr not unary & is closing // RightBrace | {expr} - expr not unary & is closing
LeftParen | // {-expr} - is unary LeftParen | // (-expr) - is unary
// RightParen | (expr) - expr not unary & is closing // RightParen | (expr) - expr not unary & is closing
LeftBracket | // [-expr] - is unary LeftBracket | // [-expr] - is unary
// RightBracket | [expr] - expr not unary & is closing // RightBracket | [expr] - expr not unary & is closing
@ -374,7 +381,7 @@ impl Token {
} }
/// Get the precedence number of the token. /// Get the precedence number of the token.
pub fn precedence(&self) -> u8 { pub fn precedence(&self, custom: Option<&HashMap<String, u8>>) -> u8 {
use Token::*; use Token::*;
match self { match self {
@ -383,24 +390,27 @@ impl Token {
| RightShiftAssign | AndAssign | OrAssign | XOrAssign | ModuloAssign | RightShiftAssign | AndAssign | OrAssign | XOrAssign | ModuloAssign
| PowerOfAssign => 0, | PowerOfAssign => 0,
Or | XOr | Pipe => 40, Or | XOr | Pipe => 30,
And | Ampersand => 50, And | Ampersand => 60,
LessThan | LessThanEqualsTo | GreaterThan | GreaterThanEqualsTo | EqualsTo LessThan | LessThanEqualsTo | GreaterThan | GreaterThanEqualsTo | EqualsTo
| NotEqualsTo => 60, | NotEqualsTo => 90,
In => 70, In => 110,
Plus | Minus => 80, Plus | Minus => 130,
Divide | Multiply | PowerOf => 90, Divide | Multiply | PowerOf => 160,
LeftShift | RightShift => 100, LeftShift | RightShift => 190,
Modulo => 110, Modulo => 210,
Period => 120, Period => 240,
// Custom operators
Custom(s) => custom.map_or(0, |c| *c.get(s).unwrap()),
_ => 0, _ => 0,
} }
@ -422,6 +432,41 @@ impl Token {
_ => false, _ => false,
} }
} }
/// Is this token an operator?
pub fn is_operator(&self) -> bool {
use Token::*;
match self {
LeftBrace | RightBrace | LeftParen | RightParen | LeftBracket | RightBracket | Plus
| UnaryPlus | Minus | UnaryMinus | Multiply | Divide | Modulo | PowerOf | LeftShift
| RightShift | SemiColon | Colon | DoubleColon | Comma | Period | MapStart | Equals
| LessThan | GreaterThan | LessThanEqualsTo | GreaterThanEqualsTo | EqualsTo
| NotEqualsTo | Bang | Pipe | Or | XOr | Ampersand | And | PlusAssign | MinusAssign
| MultiplyAssign | DivideAssign | LeftShiftAssign | RightShiftAssign | AndAssign
| OrAssign | XOrAssign | ModuloAssign | PowerOfAssign => true,
_ => false,
}
}
/// Is this token a keyword?
pub fn is_keyword(&self) -> bool {
use Token::*;
match self {
#[cfg(not(feature = "no_function"))]
Fn | Private => true,
#[cfg(not(feature = "no_module"))]
Import | Export | As => true,
True | False | Let | Const | If | Else | While | Loop | For | In | Continue | Break
| Return | Throw => true,
_ => false,
}
}
} }
impl From<Token> for String { impl From<Token> for String {
@ -431,7 +476,7 @@ impl From<Token> for String {
} }
/// State of the tokenizer. /// State of the tokenizer.
#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash, Default)] #[derive(Debug, Clone, Eq, PartialEq, Default)]
pub struct TokenizeState { pub struct TokenizeState {
/// Maximum length of a string (0 = unlimited). /// Maximum length of a string (0 = unlimited).
pub max_string_size: usize, pub max_string_size: usize,
@ -644,7 +689,7 @@ pub fn get_next_token(
let result = get_next_token_inner(stream, state, pos); let result = get_next_token_inner(stream, state, pos);
// Save the last token's state // Save the last token's state
if let Some((token, _)) = &result { if let Some((ref token, _)) = result {
state.non_unary = !token.is_next_unary(); state.non_unary = !token.is_next_unary();
} }
@ -848,7 +893,7 @@ fn get_next_token_inner(
('"', _) => return parse_string_literal(stream, state, pos, '"') ('"', _) => return parse_string_literal(stream, state, pos, '"')
.map_or_else( .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), start_pos)), |out| Some((Token::StringConstant(out), start_pos)),
), ),
// ' - character literal // ' - character literal
@ -1118,7 +1163,7 @@ fn get_next_token_inner(
('\0', _) => unreachable!(), ('\0', _) => unreachable!(),
(ch, _) if ch.is_whitespace() => (), (ch, _) if ch.is_whitespace() => (),
(ch, _) => return Some((Token::LexError(Box::new(LERR::UnexpectedChar(ch))), start_pos)), (ch, _) => return Some((Token::LexError(Box::new(LERR::UnexpectedInput(ch.to_string()))), start_pos)),
} }
} }
@ -1172,7 +1217,9 @@ impl InputStream for MultiInputsStream<'_> {
} }
/// An iterator on a `Token` stream. /// An iterator on a `Token` stream.
pub struct TokenIterator<'a> { pub struct TokenIterator<'a, 'e> {
/// Reference to the scripting `Engine`.
engine: &'e Engine,
/// Current state. /// Current state.
state: TokenizeState, state: TokenizeState,
/// Current position. /// Current position.
@ -1181,19 +1228,47 @@ pub struct TokenIterator<'a> {
stream: MultiInputsStream<'a>, stream: MultiInputsStream<'a>,
} }
impl<'a> Iterator for TokenIterator<'a> { impl<'a> Iterator for TokenIterator<'a, '_> {
type Item = (Token, Position); type Item = (Token, Position);
fn next(&mut self) -> Option<Self::Item> { fn next(&mut self) -> Option<Self::Item> {
get_next_token(&mut self.stream, &mut self.state, &mut self.pos) match (
get_next_token(&mut self.stream, &mut self.state, &mut self.pos),
self.engine.disabled_symbols.as_ref(),
self.engine.custom_keywords.as_ref(),
) {
(None, _, _) => None,
(r @ Some(_), None, None) => r,
(Some((token, pos)), Some(disabled), _)
if token.is_operator() && disabled.contains(token.syntax().as_ref()) =>
{
// Convert disallowed operators into lex errors
Some((
Token::LexError(Box::new(LexError::UnexpectedInput(token.syntax().into()))),
pos,
))
}
(Some((token, pos)), Some(disabled), _)
if token.is_keyword() && disabled.contains(token.syntax().as_ref()) =>
{
// Convert disallowed keywords into identifiers
Some((Token::Identifier(token.syntax().into()), pos))
}
(Some((Token::Identifier(s), pos)), _, Some(custom)) if custom.contains_key(&s) => {
// Convert custom keywords
Some((Token::Custom(s), pos))
}
(r, _, _) => r,
}
} }
} }
/// Tokenize an input text stream. /// Tokenize an input text stream.
pub fn lex<'a>(input: &'a [&'a str], max_string_size: usize) -> TokenIterator<'a> { pub fn lex<'a, 'e>(input: &'a [&'a str], engine: &'e Engine) -> TokenIterator<'a, 'e> {
TokenIterator { TokenIterator {
engine,
state: TokenizeState { state: TokenizeState {
max_string_size, max_string_size: engine.max_string_size,
non_unary: false, non_unary: false,
comment_level: 0, comment_level: 0,
end_with_none: false, end_with_none: false,

View File

@ -35,6 +35,7 @@ fn test_max_string_size() -> Result<(), Box<EvalAltResult>> {
EvalAltResult::ErrorDataTooLarge(_, 10, 13, _) EvalAltResult::ErrorDataTooLarge(_, 10, 13, _)
)); ));
#[cfg(not(feature = "no_object"))]
assert!(matches!( assert!(matches!(
*engine *engine
.eval::<String>( .eval::<String>(
@ -92,6 +93,8 @@ fn test_max_array_size() -> Result<(), Box<EvalAltResult>> {
.expect_err("should error"), .expect_err("should error"),
EvalAltResult::ErrorDataTooLarge(_, 10, 12, _) EvalAltResult::ErrorDataTooLarge(_, 10, 12, _)
)); ));
#[cfg(not(feature = "no_object"))]
assert!(matches!( assert!(matches!(
*engine *engine
.eval::<Array>( .eval::<Array>(

View File

@ -39,7 +39,7 @@ fn test_for_string() -> Result<(), Box<EvalAltResult>> {
let sum = 0; let sum = 0;
for ch in s { for ch in s {
sum += ch.to_int(); sum += to_int(ch);
} }
sum sum

View File

@ -52,6 +52,7 @@ fn test_functions() -> Result<(), Box<EvalAltResult>> {
} }
#[test] #[test]
#[cfg(not(feature = "no_object"))]
fn test_function_pointers() -> Result<(), Box<EvalAltResult>> { fn test_function_pointers() -> Result<(), Box<EvalAltResult>> {
let engine = Engine::new(); let engine = Engine::new();

View File

@ -6,14 +6,14 @@ fn test_mismatched_op() {
assert!(matches!( assert!(matches!(
*engine.eval::<INT>(r#""hello, " + "world!""#).expect_err("expects error"), *engine.eval::<INT>(r#""hello, " + "world!""#).expect_err("expects error"),
EvalAltResult::ErrorMismatchOutputType(err, _) if err == "string" EvalAltResult::ErrorMismatchOutputType(need, actual, _) if need == std::any::type_name::<INT>() && actual == "string"
)); ));
} }
#[test] #[test]
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
fn test_mismatched_op_custom_type() { fn test_mismatched_op_custom_type() {
#[derive(Clone)] #[derive(Debug, Clone)]
struct TestStruct { struct TestStruct {
x: INT, x: INT,
} }
@ -28,19 +28,14 @@ fn test_mismatched_op_custom_type() {
engine.register_type_with_name::<TestStruct>("TestStruct"); engine.register_type_with_name::<TestStruct>("TestStruct");
engine.register_fn("new_ts", TestStruct::new); engine.register_fn("new_ts", TestStruct::new);
let r = engine
.eval::<INT>("60 + new_ts()")
.expect_err("expects error");
#[cfg(feature = "only_i32")]
assert!(matches!( assert!(matches!(
*r, *engine.eval::<INT>("60 + new_ts()").expect_err("should error"),
EvalAltResult::ErrorFunctionNotFound(err, _) if err == "+ (i32, TestStruct)" EvalAltResult::ErrorFunctionNotFound(err, _) if err == format!("+ ({}, TestStruct)", std::any::type_name::<INT>())
)); ));
#[cfg(not(feature = "only_i32"))]
assert!(matches!( assert!(matches!(
*r, *engine.eval::<TestStruct>("42").expect_err("should error"),
EvalAltResult::ErrorFunctionNotFound(err, _) if err == "+ (i64, TestStruct)" EvalAltResult::ErrorMismatchOutputType(need, actual, _)
if need == "TestStruct" && actual == std::any::type_name::<INT>()
)); ));
} }

192
tests/serde.rs Normal file
View File

@ -0,0 +1,192 @@
#![cfg(feature = "serde")]
use rhai::{de::from_dynamic, ser::to_dynamic, Dynamic, Engine, EvalAltResult, INT};
use serde::{Deserialize, Serialize};
#[cfg(not(feature = "no_index"))]
use rhai::Array;
#[cfg(not(feature = "no_object"))]
use rhai::Map;
#[test]
fn test_serde_ser_primary_types() -> Result<(), Box<EvalAltResult>> {
assert_eq!(
to_dynamic(42_u64)?.type_name(),
std::any::type_name::<INT>()
);
assert_eq!(to_dynamic(u64::MAX)?.type_name(), "u64");
assert_eq!(
to_dynamic(42 as INT)?.type_name(),
std::any::type_name::<INT>()
);
assert_eq!(to_dynamic(true)?.type_name(), "bool");
assert_eq!(to_dynamic(())?.type_name(), "()");
#[cfg(not(feature = "no_float"))]
{
assert_eq!(to_dynamic(123.456_f64)?.type_name(), "f64");
assert_eq!(to_dynamic(123.456_f32)?.type_name(), "f32");
}
assert_eq!(to_dynamic("hello".to_string())?.type_name(), "string");
Ok(())
}
#[test]
#[cfg(not(feature = "no_index"))]
fn test_serde_ser_array() -> Result<(), Box<EvalAltResult>> {
let arr: Vec<INT> = vec![123, 456, 42, 999];
let d = to_dynamic(arr)?;
assert!(d.is::<Array>());
assert_eq!(d.cast::<Array>().len(), 4);
Ok(())
}
#[test]
#[cfg(not(feature = "no_index"))]
#[cfg(not(feature = "no_object"))]
fn test_serde_ser_struct() -> Result<(), Box<EvalAltResult>> {
#[derive(Debug, Serialize, PartialEq)]
struct Hello {
a: INT,
b: bool,
}
#[derive(Debug, Serialize, PartialEq)]
struct Test {
int: u32,
seq: Vec<String>,
obj: Hello,
}
let x = Test {
int: 42,
seq: vec!["hello".into(), "kitty".into(), "world".into()],
obj: Hello { a: 123, b: true },
};
let d = to_dynamic(x)?;
assert!(d.is::<Map>());
let mut map = d.cast::<Map>();
let mut obj = map.remove("obj").unwrap().cast::<Map>();
let mut seq = map.remove("seq").unwrap().cast::<Array>();
assert_eq!(obj.remove("a").unwrap().cast::<INT>(), 123);
assert!(obj.remove("b").unwrap().cast::<bool>());
assert_eq!(map.remove("int").unwrap().cast::<INT>(), 42);
assert_eq!(seq.len(), 3);
assert_eq!(seq.remove(1).cast::<String>(), "kitty");
Ok(())
}
#[test]
fn test_serde_de_primary_types() -> Result<(), Box<EvalAltResult>> {
assert_eq!(42_u16, from_dynamic(&Dynamic::from(42_u16))?);
assert_eq!(42 as INT, from_dynamic(&(42 as INT).into())?);
assert_eq!(true, from_dynamic(&true.into())?);
assert_eq!((), from_dynamic(&().into())?);
#[cfg(not(feature = "no_float"))]
{
assert_eq!(123.456_f64, from_dynamic(&123.456_f64.into())?);
assert_eq!(123.456_f32, from_dynamic(&Dynamic::from(123.456_f32))?);
}
assert_eq!(
"hello",
from_dynamic::<String>(&"hello".to_string().into())?
);
Ok(())
}
#[test]
#[cfg(not(feature = "no_index"))]
fn test_serde_de_array() -> Result<(), Box<EvalAltResult>> {
let arr: Vec<INT> = vec![123, 456, 42, 999];
assert_eq!(arr, from_dynamic::<Vec<INT>>(&arr.clone().into())?);
Ok(())
}
#[test]
#[cfg(not(feature = "no_index"))]
#[cfg(not(feature = "no_object"))]
fn test_serde_de_struct() -> Result<(), Box<EvalAltResult>> {
#[derive(Debug, Deserialize, PartialEq)]
struct Hello {
a: INT,
b: bool,
}
#[derive(Debug, Deserialize, PartialEq)]
struct Test {
int: u32,
seq: Vec<String>,
obj: Hello,
}
let mut map = Map::new();
map.insert("int".into(), Dynamic::from(42_u32));
let mut map2 = Map::new();
map2.insert("a".into(), (123 as INT).into());
map2.insert("b".into(), true.into());
map.insert("obj".into(), map2.into());
let arr: Array = vec!["hello".into(), "kitty".into(), "world".into()];
map.insert("seq".into(), arr.into());
let expected = Test {
int: 42,
seq: vec!["hello".into(), "kitty".into(), "world".into()],
obj: Hello { a: 123, b: true },
};
assert_eq!(expected, from_dynamic(&map.into())?);
Ok(())
}
#[test]
#[cfg(not(feature = "no_index"))]
#[cfg(not(feature = "no_object"))]
#[cfg(not(feature = "no_float"))]
fn test_serde_de_script() -> Result<(), Box<EvalAltResult>> {
#[derive(Debug, Deserialize)]
struct Point {
x: f64,
y: f64,
}
#[derive(Debug, Deserialize)]
struct MyStruct {
a: i64,
b: Vec<String>,
c: bool,
d: Point,
}
let engine = Engine::new();
let result: Dynamic = engine.eval(
r#"
#{
a: 42,
b: [ "hello", "world" ],
c: true,
d: #{ x: 123.456, y: 999.0 }
}
"#,
)?;
// Convert the 'Dynamic' object map into 'MyStruct'
let _: MyStruct = from_dynamic(&result)?;
Ok(())
}

49
tests/tokens.rs Normal file
View File

@ -0,0 +1,49 @@
use rhai::{Engine, EvalAltResult, ParseErrorType, RegisterFn, INT};
#[test]
fn test_tokens_disabled() {
let mut engine = Engine::new();
engine.disable_symbol("if"); // disable the 'if' keyword
assert!(matches!(
*engine.compile("let x = if true { 42 } else { 0 };").expect_err("should error").0,
ParseErrorType::MissingToken(ref token, _) if token == ";"
));
engine.disable_symbol("+="); // disable the '+=' operator
assert!(matches!(
*engine.compile("let x = 40 + 2; x += 1;").expect_err("should error").0,
ParseErrorType::BadInput(ref s) if s == "Unexpected '+='"
));
}
#[test]
fn test_tokens_custom_operator() -> Result<(), Box<EvalAltResult>> {
let mut engine = Engine::new();
// Register a custom operator called `foo` and give it
// a precedence of 140 (i.e. between +|- and *|/).
engine.register_custom_operator("foo", 140).unwrap();
// Register a binary function named `foo`
engine.register_fn("foo", |x: INT, y: INT| (x * y) - (x + y));
assert_eq!(
engine.eval_expression::<INT>("1 + 2 * 3 foo 4 - 5 / 6")?,
15
);
assert_eq!(
engine.eval::<INT>(
r"
fn foo(x, y) { y - x }
1 + 2 * 3 foo 4 - 5 / 6
"
)?,
-1
);
Ok(())
}