Merge branch 'master' into plugins
This commit is contained in:
commit
ef2d28d423
14
Cargo.toml
14
Cargo.toml
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "rhai"
|
||||
version = "0.16.0"
|
||||
version = "0.17.0"
|
||||
edition = "2018"
|
||||
authors = ["Jonathan Turner", "Lukáš Hozda", "Stephen Chung"]
|
||||
description = "Embedded scripting for Rust"
|
||||
@ -65,5 +65,17 @@ default-features = false
|
||||
features = ["compile-time-rng"]
|
||||
optional = true
|
||||
|
||||
[dependencies.serde]
|
||||
package = "serde"
|
||||
version = "1.0.111"
|
||||
features = ["derive"]
|
||||
optional = true
|
||||
|
||||
[target.'cfg(target_arch = "wasm32")'.dependencies]
|
||||
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"]
|
||||
|
@ -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).
|
||||
* Easily [call a script-defined function](https://schungx.github.io/rhai/engine/call-fn.html) from Rust.
|
||||
* 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
|
||||
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`).
|
||||
* 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.
|
||||
* [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).
|
||||
* 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).
|
||||
* 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.
|
||||
* 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).
|
||||
|
||||
|
33
RELEASES.md
33
RELEASES.md
@ -1,11 +1,44 @@
|
||||
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
|
||||
==============
|
||||
|
||||
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
|
||||
----------------
|
||||
|
||||
|
@ -96,14 +96,17 @@ The Rhai Scripting Language
|
||||
9. [Maximum Statement Depth](safety/max-stmt-depth.md)
|
||||
8. [Advanced Topics](advanced.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)
|
||||
2. [Re-Optimize an AST](engine/optimize/reoptimize.md)
|
||||
3. [Eager Function Evaluation](engine/optimize/eager.md)
|
||||
4. [Side-Effect Considerations](engine/optimize/side-effects.md)
|
||||
5. [Volatility Considerations](engine/optimize/volatility.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)
|
||||
1. [Keywords](appendix/keywords.md)
|
||||
2. [Operators](appendix/operators.md)
|
||||
|
@ -22,7 +22,7 @@ Fast
|
||||
|
||||
* 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.
|
||||
|
||||
@ -39,6 +39,8 @@ Dynamic
|
||||
|
||||
* Some support for [object-oriented programming (OOP)][OOP].
|
||||
|
||||
* Serialization/deserialization support via [`serde`].
|
||||
|
||||
Safe
|
||||
----
|
||||
|
||||
@ -62,3 +64,7 @@ Flexible
|
||||
* Support for [minimal builds] by excluding unneeded language [features].
|
||||
|
||||
* Supports [most build targets](targets.md) including `no-std` and [WASM].
|
||||
|
||||
* Surgically [disable keywords and operators] to restrict the language.
|
||||
|
||||
* [Custom operators].
|
||||
|
@ -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
|
||||
to add scripting to any application.
|
||||
|
||||
|
||||
This Book is for version {{version}} of Rhai.
|
||||
|
||||
For the latest development version, see [here]({{rootUrl}}/vnext/).
|
||||
|
@ -3,28 +3,28 @@ Operators
|
||||
|
||||
{{#include ../links.md}}
|
||||
|
||||
| Operator | Description | Binary? |
|
||||
| :---------------: | ------------------------------ | :-----: |
|
||||
| `+` | Add | Yes |
|
||||
| `-` | Subtract, Minus | Yes/No |
|
||||
| `*` | Multiply | Yes |
|
||||
| `/` | Divide | Yes |
|
||||
| `%` | Modulo | Yes |
|
||||
| `~` | Power | Yes |
|
||||
| `>>` | Right bit-shift | Yes |
|
||||
| `<<` | Left bit-shift | Yes |
|
||||
| `&` | Bit-wise _And_, Boolean _And_ | Yes |
|
||||
| <code>\|</code> | Bit-wise _Or_, Boolean _Or_ | Yes |
|
||||
| `^` | Bit-wise _Xor_ | Yes |
|
||||
| `==` | Equals to | Yes |
|
||||
| `~=` | Not equals to | Yes |
|
||||
| `>` | Greater than | Yes |
|
||||
| `>=` | Greater than or equals to | Yes |
|
||||
| `<` | Less than | Yes |
|
||||
| `<=` | Less than or equals to | Yes |
|
||||
| `>=` | Greater than or equals to | Yes |
|
||||
| `&&` | Boolean _And_ (short-circuits) | Yes |
|
||||
| <code>\|\|</code> | Boolean _Or_ (short-circuits) | Yes |
|
||||
| `!` | Boolean _Not_ | No |
|
||||
| `[` .. `]` | Indexing | Yes |
|
||||
| `.` | Property access, Method call | Yes |
|
||||
| Operator | Description | Binary? | Binding direction |
|
||||
| :---------------: | ------------------------------ | :-----: | :---------------: |
|
||||
| `+` | Add | Yes | Left |
|
||||
| `-` | Subtract, Minus | Yes/No | Left |
|
||||
| `*` | Multiply | Yes | Left |
|
||||
| `/` | Divide | Yes | Left |
|
||||
| `%` | Modulo | Yes | Left |
|
||||
| `~` | Power | Yes | Left |
|
||||
| `>>` | Right bit-shift | Yes | Left |
|
||||
| `<<` | Left bit-shift | Yes | Left |
|
||||
| `&` | Bit-wise _And_, Boolean _And_ | Yes | Left |
|
||||
| <code>\|</code> | Bit-wise _Or_, Boolean _Or_ | Yes | Left |
|
||||
| `^` | Bit-wise _Xor_ | Yes | Left |
|
||||
| `==` | Equals to | Yes | Left |
|
||||
| `~=` | Not equals to | Yes | Left |
|
||||
| `>` | Greater than | Yes | Left |
|
||||
| `>=` | Greater than or equals to | Yes | Left |
|
||||
| `<` | Less than | Yes | Left |
|
||||
| `<=` | Less than or equals to | Yes | Left |
|
||||
| `>=` | Greater than or equals to | Yes | Left |
|
||||
| `&&` | Boolean _And_ (short-circuits) | Yes | Left |
|
||||
| <code>\|\|</code> | Boolean _Or_ (short-circuits) | Yes | Left |
|
||||
| `!` | Boolean _Not_ | No | Left |
|
||||
| `[` .. `]` | Indexing | Yes | Right |
|
||||
| `.` | Property access, Method call | Yes | Right |
|
||||
|
@ -1,5 +1,6 @@
|
||||
{
|
||||
"version": "0.16.0",
|
||||
"version": "0.17.0",
|
||||
"rootUrl": "",
|
||||
"rootUrlX": "/rhai"
|
||||
"rootUrlX": "/rhai",
|
||||
"rootUrlXX": "/rhai/vnext"
|
||||
}
|
105
doc/src/engine/custom-op.md
Normal file
105
doc/src/engine/custom-op.md
Normal 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
28
doc/src/engine/disable.md
Normal 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
|
||||
```
|
@ -29,6 +29,7 @@
|
||||
[package]: {{rootUrl}}/rust/packages/index.md
|
||||
[packages]: {{rootUrl}}/rust/packages/index.md
|
||||
[`Scope`]: {{rootUrl}}/rust/scope.md
|
||||
[`serde`]: {{rootUrl}}/rust/serde.md
|
||||
|
||||
[`type_of()`]: {{rootUrl}}/language/type-of.md
|
||||
[`to_string()`]: {{rootUrl}}/language/values-and-types.md
|
||||
@ -101,3 +102,7 @@
|
||||
[`OptimizationLevel::Full`]: {{rootUrl}}/engine/optimize/optimize-levels.md
|
||||
[`OptimizationLevel::Simple`]: {{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
104
doc/src/rust/serde.md
Normal 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)?;
|
||||
```
|
@ -6,13 +6,14 @@ Rust Examples
|
||||
A number of examples can be found in the `examples` folder:
|
||||
|
||||
| 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. |
|
||||
| [`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. |
|
||||
| [`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`]. |
|
||||
| [`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. |
|
||||
| [`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. |
|
||||
|
@ -24,6 +24,7 @@ more control over what a script can (or cannot) do.
|
||||
| `no_function` | Disable script-defined [functions]. |
|
||||
| `no_module` | Disable loading external [modules]. |
|
||||
| `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. |
|
||||
|
||||
|
||||
|
76
examples/serde.rs
Normal file
76
examples/serde.rs
Normal 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);
|
||||
}
|
||||
}
|
31
src/any.rs
31
src/any.rs
@ -196,13 +196,38 @@ impl Dynamic {
|
||||
Union::FnPtr(_) => "Fn",
|
||||
|
||||
#[cfg(not(feature = "no_std"))]
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
Union::Variant(value) if value.is::<Instant>() => "timestamp",
|
||||
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 {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match &self.0 {
|
||||
@ -220,7 +245,6 @@ impl fmt::Display for Dynamic {
|
||||
Union::FnPtr(value) => fmt::Display::fmt(value, f),
|
||||
|
||||
#[cfg(not(feature = "no_std"))]
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
Union::Variant(value) if value.is::<Instant>() => write!(f, "<timestamp>"),
|
||||
Union::Variant(_) => write!(f, "?"),
|
||||
}
|
||||
@ -244,7 +268,6 @@ impl fmt::Debug for Dynamic {
|
||||
Union::FnPtr(value) => fmt::Display::fmt(value, f),
|
||||
|
||||
#[cfg(not(feature = "no_std"))]
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
Union::Variant(value) if value.is::<Instant>() => write!(f, "<timestamp>"),
|
||||
Union::Variant(_) => write!(f, "<dynamic>"),
|
||||
}
|
||||
@ -323,11 +346,9 @@ impl Dynamic {
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "no_float"))]
|
||||
{
|
||||
if let Some(result) = <dyn Any>::downcast_ref::<FLOAT>(&value) {
|
||||
return result.clone().into();
|
||||
}
|
||||
}
|
||||
|
||||
let mut boxed = Box::new(value);
|
||||
|
||||
|
38
src/api.rs
38
src/api.rs
@ -1,10 +1,7 @@
|
||||
//! Module that defines the extern API of `Engine`.
|
||||
|
||||
use crate::any::{Dynamic, Variant};
|
||||
use crate::engine::{
|
||||
get_script_function_by_signature, make_getter, make_setter, Engine, Imports, State, FN_IDX_GET,
|
||||
FN_IDX_SET,
|
||||
};
|
||||
use crate::engine::{make_getter, make_setter, Engine, Imports, State, FN_IDX_GET, FN_IDX_SET};
|
||||
use crate::error::ParseError;
|
||||
use crate::fn_call::FuncArgs;
|
||||
use crate::fn_native::{IteratorFn, SendSync};
|
||||
@ -19,6 +16,9 @@ use crate::utils::StaticVec;
|
||||
#[cfg(not(feature = "no_object"))]
|
||||
use crate::engine::Map;
|
||||
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
use crate::engine::get_script_function_by_signature;
|
||||
|
||||
use crate::stdlib::{
|
||||
any::{type_name, TypeId},
|
||||
boxed::Box,
|
||||
@ -118,8 +118,13 @@ impl Engine {
|
||||
/// ```
|
||||
#[cfg(not(feature = "no_object"))]
|
||||
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
|
||||
self.type_names
|
||||
.as_mut()
|
||||
.unwrap()
|
||||
.insert(type_name::<T>().to_string(), name.to_string());
|
||||
}
|
||||
|
||||
@ -548,7 +553,7 @@ impl Engine {
|
||||
scripts: &[&str],
|
||||
optimization_level: OptimizationLevel,
|
||||
) -> Result<AST, ParseError> {
|
||||
let stream = lex(scripts, self.max_string_size);
|
||||
let stream = lex(scripts, self);
|
||||
self.parse(&mut stream.peekable(), scope, optimization_level)
|
||||
}
|
||||
|
||||
@ -673,7 +678,7 @@ impl Engine {
|
||||
|
||||
// Trims the JSON string and add a '#' in front
|
||||
let scripts = ["#", json.trim()];
|
||||
let stream = lex(&scripts, self.max_string_size);
|
||||
let stream = lex(&scripts, self);
|
||||
let ast =
|
||||
self.parse_global_expr(&mut stream.peekable(), &scope, OptimizationLevel::None)?;
|
||||
|
||||
@ -754,7 +759,7 @@ impl Engine {
|
||||
script: &str,
|
||||
) -> Result<AST, ParseError> {
|
||||
let scripts = [script];
|
||||
let stream = lex(&scripts, self.max_string_size);
|
||||
let stream = lex(&scripts, self);
|
||||
{
|
||||
let mut peekable = stream.peekable();
|
||||
self.parse_global_expr(&mut peekable, scope, self.optimization_level)
|
||||
@ -909,7 +914,7 @@ impl Engine {
|
||||
script: &str,
|
||||
) -> Result<T, Box<EvalAltResult>> {
|
||||
let scripts = [script];
|
||||
let stream = lex(&scripts, self.max_string_size);
|
||||
let stream = lex(&scripts, self);
|
||||
|
||||
// No need to optimize a lone expression
|
||||
let ast = self.parse_global_expr(&mut stream.peekable(), scope, OptimizationLevel::None)?;
|
||||
@ -976,11 +981,12 @@ impl Engine {
|
||||
let mut mods = Imports::new();
|
||||
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(|| {
|
||||
Box::new(EvalAltResult::ErrorMismatchOutputType(
|
||||
return_type.into(),
|
||||
self.map_type_name(type_name::<T>()).into(),
|
||||
typ.into(),
|
||||
Position::none(),
|
||||
))
|
||||
});
|
||||
@ -1041,7 +1047,7 @@ impl Engine {
|
||||
script: &str,
|
||||
) -> Result<(), Box<EvalAltResult>> {
|
||||
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)?;
|
||||
self.consume_ast_with_scope(scope, &ast)
|
||||
}
|
||||
@ -1123,11 +1129,12 @@ impl Engine {
|
||||
let mut arg_values = args.into_vec();
|
||||
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(|| {
|
||||
Box::new(EvalAltResult::ErrorMismatchOutputType(
|
||||
return_type.into(),
|
||||
self.map_type_name(type_name::<T>()).into(),
|
||||
typ.into(),
|
||||
Position::none(),
|
||||
))
|
||||
});
|
||||
@ -1187,6 +1194,7 @@ impl Engine {
|
||||
/// This is to avoid unnecessarily cloning the arguments.
|
||||
/// Do not use the arguments after this call. If they are needed afterwards,
|
||||
/// clone them _before_ calling this function.
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
pub(crate) fn call_fn_dynamic_raw(
|
||||
&self,
|
||||
scope: &mut Scope,
|
||||
@ -1238,6 +1246,7 @@ impl Engine {
|
||||
mut ast: AST,
|
||||
optimization_level: OptimizationLevel,
|
||||
) -> AST {
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
let lib = ast
|
||||
.lib()
|
||||
.iter_fn()
|
||||
@ -1245,6 +1254,9 @@ impl Engine {
|
||||
.map(|(_, _, _, f)| f.get_fn_def().clone())
|
||||
.collect();
|
||||
|
||||
#[cfg(feature = "no_function")]
|
||||
let lib = Default::default();
|
||||
|
||||
let stmt = mem::take(ast.statements_mut());
|
||||
optimize_into_ast(self, scope, stmt, lib, optimization_level)
|
||||
}
|
||||
|
272
src/engine.rs
272
src/engine.rs
@ -1,12 +1,12 @@
|
||||
//! 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::error::ParseErrorType;
|
||||
use crate::fn_native::{CallableFunction, Callback, FnCallArgs, FnPtr};
|
||||
use crate::module::{resolvers, Module, ModuleRef, ModuleResolver};
|
||||
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::r#unsafe::unsafe_cast_var_name_to_lifetime;
|
||||
use crate::result::EvalAltResult;
|
||||
@ -18,10 +18,10 @@ use crate::utils::StaticVec;
|
||||
use crate::parser::FLOAT;
|
||||
|
||||
use crate::stdlib::{
|
||||
any::TypeId,
|
||||
any::{type_name, TypeId},
|
||||
borrow::Cow,
|
||||
boxed::Box,
|
||||
collections::HashMap,
|
||||
collections::{HashMap, HashSet},
|
||||
format,
|
||||
iter::{empty, once},
|
||||
mem,
|
||||
@ -35,7 +35,7 @@ use crate::stdlib::{
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
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.
|
||||
#[cfg(not(feature = "no_object"))]
|
||||
@ -216,6 +216,7 @@ impl State {
|
||||
}
|
||||
|
||||
/// Get a script-defined function definition from a module.
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
pub fn get_script_function_by_signature<'a>(
|
||||
module: &'a Module,
|
||||
name: &str,
|
||||
@ -265,7 +266,12 @@ pub struct Engine {
|
||||
pub(crate) module_resolver: Option<Box<dyn ModuleResolver>>,
|
||||
|
||||
/// 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.
|
||||
pub(crate) print: Callback<str, ()>,
|
||||
@ -312,7 +318,9 @@ impl Default for Engine {
|
||||
#[cfg(any(feature = "no_module", feature = "no_std", target_arch = "wasm32",))]
|
||||
module_resolver: None,
|
||||
|
||||
type_names: Default::default(),
|
||||
type_names: None,
|
||||
disabled_symbols: None,
|
||||
custom_keywords: None,
|
||||
|
||||
// default print/debug implementations
|
||||
print: Box::new(default_print),
|
||||
@ -352,18 +360,15 @@ pub fn make_getter(id: &str) -> String {
|
||||
/// Extract the property name from a getter function name.
|
||||
fn extract_prop_from_getter(fn_name: &str) -> Option<&str> {
|
||||
#[cfg(not(feature = "no_object"))]
|
||||
{
|
||||
if fn_name.starts_with(FN_GET) {
|
||||
Some(&fn_name[FN_GET.len()..])
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "no_object")]
|
||||
{
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// Make setter function
|
||||
pub fn make_setter(id: &str) -> String {
|
||||
@ -373,18 +378,15 @@ pub fn make_setter(id: &str) -> String {
|
||||
/// Extract the property name from a setter function name.
|
||||
fn extract_prop_from_setter(fn_name: &str) -> Option<&str> {
|
||||
#[cfg(not(feature = "no_object"))]
|
||||
{
|
||||
if fn_name.starts_with(FN_SET) {
|
||||
Some(&fn_name[FN_SET.len()..])
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "no_object")]
|
||||
{
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// Print/debug to stdout
|
||||
fn default_print(s: &str) {
|
||||
@ -497,7 +499,10 @@ impl Engine {
|
||||
global_module: Default::default(),
|
||||
module_resolver: None,
|
||||
|
||||
type_names: Default::default(),
|
||||
type_names: None,
|
||||
disabled_symbols: None,
|
||||
custom_keywords: None,
|
||||
|
||||
print: Box::new(|_| {}),
|
||||
debug: Box::new(|_| {}),
|
||||
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.
|
||||
/// Position in `EvalAltResult` is None and must be set afterwards.
|
||||
///
|
||||
@ -700,13 +553,11 @@ impl Engine {
|
||||
// Check for stack overflow
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
#[cfg(not(feature = "unchecked"))]
|
||||
{
|
||||
if level > self.max_call_stack_depth {
|
||||
return Err(Box::new(
|
||||
EvalAltResult::ErrorStackOverflow(Position::none()),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
let mut this_copy: Dynamic = Default::default();
|
||||
let mut old_this_ptr: Option<&mut Dynamic> = None;
|
||||
@ -767,22 +618,23 @@ impl Engine {
|
||||
.or_else(|| self.packages.get_fn(hash_fn));
|
||||
|
||||
if let Some(func) = func {
|
||||
// Calling pure function but the first argument is a reference?
|
||||
normalize_first_arg(
|
||||
is_ref && (func.is_pure() || (func.is_script() && !is_method)),
|
||||
&mut this_copy,
|
||||
&mut old_this_ptr,
|
||||
args,
|
||||
);
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
let need_normalize = is_ref && (func.is_pure() || (func.is_script() && !is_method));
|
||||
#[cfg(feature = "no_function")]
|
||||
let need_normalize = is_ref && func.is_pure();
|
||||
|
||||
// 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() {
|
||||
// Run scripted function
|
||||
let fn_def = func.get_fn_def();
|
||||
|
||||
// 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);
|
||||
return Ok((
|
||||
Ok((
|
||||
self.call_script_fn(
|
||||
scope,
|
||||
mods,
|
||||
@ -795,7 +647,7 @@ impl Engine {
|
||||
level,
|
||||
)?,
|
||||
false,
|
||||
));
|
||||
))
|
||||
} else {
|
||||
let result = self.call_script_fn(
|
||||
scope, mods, state, lib, &mut None, fn_name, fn_def, args, level,
|
||||
@ -804,9 +656,10 @@ impl Engine {
|
||||
// Restore the original reference
|
||||
restore_first_arg(old_this_ptr, args);
|
||||
|
||||
return Ok((result, false));
|
||||
Ok((result, false))
|
||||
};
|
||||
} else {
|
||||
}
|
||||
|
||||
// Run external function
|
||||
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)
|
||||
return Ok(match fn_name {
|
||||
KEYWORD_PRINT => (
|
||||
(self.print)(result.as_str().map_err(|type_name| {
|
||||
(self.print)(result.as_str().map_err(|typ| {
|
||||
Box::new(EvalAltResult::ErrorMismatchOutputType(
|
||||
type_name.into(),
|
||||
self.map_type_name(type_name::<ImmutableString>()).into(),
|
||||
typ.into(),
|
||||
Position::none(),
|
||||
))
|
||||
})?)
|
||||
@ -826,9 +680,10 @@ impl Engine {
|
||||
false,
|
||||
),
|
||||
KEYWORD_DEBUG => (
|
||||
(self.debug)(result.as_str().map_err(|type_name| {
|
||||
(self.debug)(result.as_str().map_err(|typ| {
|
||||
Box::new(EvalAltResult::ErrorMismatchOutputType(
|
||||
type_name.into(),
|
||||
self.map_type_name(type_name::<ImmutableString>()).into(),
|
||||
typ.into(),
|
||||
Position::none(),
|
||||
))
|
||||
})?)
|
||||
@ -838,7 +693,6 @@ impl Engine {
|
||||
_ => (result, func.is_method()),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// See if it is built in.
|
||||
if args.len() == 2 {
|
||||
@ -1064,8 +918,12 @@ impl Engine {
|
||||
lib: &Module,
|
||||
script: &Dynamic,
|
||||
) -> Result<Dynamic, Box<EvalAltResult>> {
|
||||
let script = script.as_str().map_err(|type_name| {
|
||||
EvalAltResult::ErrorMismatchOutputType(type_name.into(), Position::none())
|
||||
let script = script.as_str().map_err(|typ| {
|
||||
EvalAltResult::ErrorMismatchOutputType(
|
||||
self.map_type_name(type_name::<ImmutableString>()).into(),
|
||||
typ.into(),
|
||||
Position::none(),
|
||||
)
|
||||
})?;
|
||||
|
||||
// Compile the script text
|
||||
@ -1873,9 +1731,10 @@ impl Engine {
|
||||
self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)?;
|
||||
return arg_value
|
||||
.take_immutable_string()
|
||||
.map_err(|type_name| {
|
||||
.map_err(|typ| {
|
||||
Box::new(EvalAltResult::ErrorMismatchOutputType(
|
||||
type_name.into(),
|
||||
self.map_type_name(type_name::<ImmutableString>()).into(),
|
||||
typ.into(),
|
||||
expr.position(),
|
||||
))
|
||||
})
|
||||
@ -2009,6 +1868,7 @@ impl Engine {
|
||||
};
|
||||
|
||||
match func {
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
Ok(f) if f.is_script() => {
|
||||
let args = args.as_mut();
|
||||
let fn_def = f.get_fn_def();
|
||||
@ -2335,7 +2195,6 @@ impl Engine {
|
||||
.try_cast::<ImmutableString>()
|
||||
{
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
{
|
||||
if let Some(resolver) = &self.module_resolver {
|
||||
let mut module = resolver.resolve(self, &path, expr.position())?;
|
||||
module.index_all_sub_modules();
|
||||
@ -2350,7 +2209,6 @@ impl Engine {
|
||||
expr.position(),
|
||||
)))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "no_module")]
|
||||
Ok(Default::default())
|
||||
@ -2408,7 +2266,13 @@ impl Engine {
|
||||
let mut maps = 0;
|
||||
|
||||
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);
|
||||
arrays += a;
|
||||
maps += m;
|
||||
@ -2424,7 +2288,13 @@ impl Engine {
|
||||
let mut maps = 0;
|
||||
|
||||
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);
|
||||
arrays += a;
|
||||
maps += m;
|
||||
@ -2488,14 +2358,12 @@ impl Engine {
|
||||
state.operations += 1;
|
||||
|
||||
#[cfg(not(feature = "unchecked"))]
|
||||
{
|
||||
// Guard against too many operations
|
||||
if self.max_operations > 0 && state.operations > self.max_operations {
|
||||
return Err(Box::new(EvalAltResult::ErrorTooManyOperations(
|
||||
Position::none(),
|
||||
)));
|
||||
}
|
||||
}
|
||||
|
||||
// Report progress - only in steps
|
||||
if let Some(progress) = &self.progress {
|
||||
@ -2511,9 +2379,9 @@ impl Engine {
|
||||
/// Map a type_name into a pretty-print name
|
||||
pub(crate) fn map_type_name<'a>(&'a self, name: &'a str) -> &'a str {
|
||||
self.type_names
|
||||
.get(name)
|
||||
.map(String::as_str)
|
||||
.unwrap_or(name)
|
||||
.as_ref()
|
||||
.and_then(|t| t.get(name).map(String::as_str))
|
||||
.unwrap_or(map_std_type_name(name))
|
||||
}
|
||||
}
|
||||
|
||||
@ -2620,7 +2488,6 @@ fn run_builtin_binary_op(
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "no_float"))]
|
||||
{
|
||||
if args_type == TypeId::of::<FLOAT>() {
|
||||
let x = *x.downcast_ref::<FLOAT>().unwrap();
|
||||
let y = *y.downcast_ref::<FLOAT>().unwrap();
|
||||
@ -2641,7 +2508,6 @@ fn run_builtin_binary_op(
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(None)
|
||||
}
|
||||
@ -2716,7 +2582,6 @@ fn run_builtin_op_assignment(
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "no_float"))]
|
||||
{
|
||||
if args_type == TypeId::of::<FLOAT>() {
|
||||
let x = x.downcast_mut::<FLOAT>().unwrap();
|
||||
let y = *y.downcast_ref::<FLOAT>().unwrap();
|
||||
@ -2731,7 +2596,6 @@ fn run_builtin_op_assignment(
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(None)
|
||||
}
|
||||
|
21
src/error.rs
21
src/error.rs
@ -5,7 +5,6 @@ use crate::token::Position;
|
||||
|
||||
use crate::stdlib::{
|
||||
boxed::Box,
|
||||
char,
|
||||
error::Error,
|
||||
fmt,
|
||||
string::{String, ToString},
|
||||
@ -15,8 +14,8 @@ use crate::stdlib::{
|
||||
#[derive(Debug, Eq, PartialEq, Clone, Hash)]
|
||||
#[non_exhaustive]
|
||||
pub enum LexError {
|
||||
/// An unexpected character is encountered when tokenizing the script text.
|
||||
UnexpectedChar(char),
|
||||
/// An unexpected symbol is encountered when tokenizing the script text.
|
||||
UnexpectedInput(String),
|
||||
/// A string literal is not terminated before a new-line or EOF.
|
||||
UnterminatedString,
|
||||
/// An identifier is in an invalid format.
|
||||
@ -38,7 +37,7 @@ impl Error for LexError {}
|
||||
impl fmt::Display for LexError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
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::MalformedNumber(s) => write!(f, "Invalid number: '{}'", 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 ({})",
|
||||
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 {
|
||||
match self {
|
||||
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) => {
|
||||
write!(f, "Expecting a constant to assign to '{}'", s)
|
||||
}
|
||||
Self::UnknownOperator(s) => write!(f, "{}: '{}'", self.desc(), s),
|
||||
|
||||
Self::MalformedIndexExpr(s) => {
|
||||
write!(f, "{}", if s.is_empty() { self.desc() } else { s })
|
||||
}
|
||||
Self::MalformedIndexExpr(s) => f.write_str(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) => {
|
||||
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::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::LiteralTooLarge(typ, max) => {
|
||||
write!(f, "{} exceeds the maximum limit ({})", typ, max)
|
||||
}
|
||||
_ => write!(f, "{}", self.desc()),
|
||||
_ => f.write_str(self.desc()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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.
|
||||
pub fn shared_make_mut<T: Clone>(value: &mut Shared<T>) -> &mut T {
|
||||
#[cfg(not(feature = "sync"))]
|
||||
{
|
||||
Rc::make_mut(value)
|
||||
}
|
||||
return Rc::make_mut(value);
|
||||
#[cfg(feature = "sync")]
|
||||
{
|
||||
Arc::make_mut(value)
|
||||
}
|
||||
return Arc::make_mut(value);
|
||||
}
|
||||
|
||||
/// 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).
|
||||
pub fn shared_take<T: Clone>(value: Shared<T>) -> T {
|
||||
#[cfg(not(feature = "sync"))]
|
||||
{
|
||||
Rc::try_unwrap(value).map_err(|_| ()).unwrap()
|
||||
}
|
||||
return Rc::try_unwrap(value).map_err(|_| ()).unwrap();
|
||||
#[cfg(feature = "sync")]
|
||||
{
|
||||
Arc::try_unwrap(value).map_err(|_| ()).unwrap()
|
||||
}
|
||||
return Arc::try_unwrap(value).map_err(|_| ()).unwrap();
|
||||
}
|
||||
|
||||
pub type FnCallArgs<'a> = [&'a mut Dynamic];
|
||||
@ -122,6 +114,7 @@ pub enum CallableFunction {
|
||||
/// A plugin-defined function,
|
||||
Plugin(SharedPluginFunction),
|
||||
/// A script-defined function.
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
Script(Shared<ScriptFnDef>),
|
||||
}
|
||||
|
||||
@ -132,6 +125,8 @@ impl fmt::Debug for CallableFunction {
|
||||
Self::Method(_) => write!(f, "NativeMethod"),
|
||||
Self::Iterator(_) => write!(f, "NativeIterator"),
|
||||
Self::Plugin(_) => write!(f, "PluginFunction"),
|
||||
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
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::Iterator(_) => write!(f, "NativeIterator"),
|
||||
Self::Plugin(_) => write!(f, "PluginFunction"),
|
||||
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
CallableFunction::Script(s) => fmt::Display::fmt(s, f),
|
||||
}
|
||||
}
|
||||
@ -154,24 +151,34 @@ impl CallableFunction {
|
||||
pub fn is_pure(&self) -> bool {
|
||||
match self {
|
||||
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?
|
||||
pub fn is_method(&self) -> bool {
|
||||
match self {
|
||||
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?
|
||||
pub fn is_iter(&self) -> bool {
|
||||
match self {
|
||||
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?
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
pub fn is_script(&self) -> bool {
|
||||
match self {
|
||||
Self::Script(_) => true,
|
||||
@ -193,7 +200,10 @@ impl CallableFunction {
|
||||
pub fn get_native_fn(&self) -> &FnAny {
|
||||
match self {
|
||||
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.
|
||||
@ -201,6 +211,7 @@ impl CallableFunction {
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics if the `CallableFunction` is not `Script`.
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
pub fn get_shared_fn_def(&self) -> Shared<ScriptFnDef> {
|
||||
match self {
|
||||
Self::Pure(_) | Self::Method(_) | Self::Iterator(_) | Self::Plugin(_) => unreachable!(),
|
||||
@ -212,6 +223,7 @@ impl CallableFunction {
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics if the `CallableFunction` is not `Script`.
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
pub fn get_fn_def(&self) -> &ScriptFnDef {
|
||||
match self {
|
||||
Self::Pure(_) | Self::Method(_) | Self::Iterator(_) | Self::Plugin(_) => unreachable!(),
|
||||
@ -226,7 +238,10 @@ impl CallableFunction {
|
||||
pub fn get_iter_fn(&self) -> IteratorFn {
|
||||
match self {
|
||||
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.
|
||||
@ -237,7 +252,10 @@ impl CallableFunction {
|
||||
pub fn get_plugin_fn<'s>(&'s self) -> SharedPluginFunction {
|
||||
match self {
|
||||
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`.
|
||||
@ -268,12 +286,14 @@ impl From<IteratorFn> for CallableFunction {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
impl From<ScriptFnDef> for CallableFunction {
|
||||
fn from(func: ScriptFnDef) -> Self {
|
||||
Self::Script(func.into())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
impl From<Shared<ScriptFnDef>> for CallableFunction {
|
||||
fn from(func: Shared<ScriptFnDef>) -> Self {
|
||||
Self::Script(func)
|
||||
|
21
src/lib.rs
21
src/lib.rs
@ -63,6 +63,7 @@
|
||||
//! | `only_i64` | Set the system integer type to `i64` and disable all other integer types. `INT` is set to `i64`. |
|
||||
//! | `no_std` | Build for `no-std`. Notice that additional dependencies will be pulled in to replace `std` features. |
|
||||
//! | `sync` | Restrict all values types to those that are `Send + Sync`. Under this feature, `Engine`, `Scope` and [`AST`] are all `Send + Sync`. |
|
||||
//! | `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). |
|
||||
//!
|
||||
//! 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 result;
|
||||
mod scope;
|
||||
#[cfg(feature = "serde")]
|
||||
mod serde;
|
||||
mod settings;
|
||||
mod stdlib;
|
||||
mod token;
|
||||
mod r#unsafe;
|
||||
@ -126,11 +130,28 @@ pub use parser::FLOAT;
|
||||
pub use module::ModuleResolver;
|
||||
|
||||
/// Module containing all built-in _module resolvers_ available to Rhai.
|
||||
///
|
||||
/// Not available under the `no_module` feature.
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
pub mod 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"))]
|
||||
pub use optimize::OptimizationLevel;
|
||||
|
||||
|
@ -236,6 +236,7 @@ impl 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.
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
pub(crate) fn set_script_fn(&mut self, fn_def: ScriptFnDef) {
|
||||
// None + function name + number of arguments.
|
||||
let hash_script = calc_fn_hash(empty(), &fn_def.name, fn_def.params.len(), empty());
|
||||
@ -876,6 +877,7 @@ impl Module {
|
||||
.functions
|
||||
.iter()
|
||||
.filter(|(_, (_, _, _, v))| match v {
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
CallableFunction::Script(ref f) => {
|
||||
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.
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
pub(crate) fn retain_functions(&mut self, filter: impl Fn(FnAccess, &str, usize) -> bool) {
|
||||
self.functions.retain(|_, (_, _, _, v)| match v {
|
||||
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.
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
pub fn iter_script_fn<'a>(&'a self) -> impl Iterator<Item = Shared<ScriptFnDef>> + 'a {
|
||||
self.functions
|
||||
.values()
|
||||
@ -1014,6 +1018,7 @@ impl Module {
|
||||
Public => (),
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
if func.is_script() {
|
||||
let fn_def = func.get_shared_fn_def();
|
||||
// Qualifiers + function name + number of arguments.
|
||||
@ -1024,7 +1029,9 @@ impl Module {
|
||||
empty(),
|
||||
);
|
||||
functions.push((hash_qualified_script, fn_def.into()));
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Qualified Rust functions are indexed in two steps:
|
||||
// 1) Calculate a hash in a similar manner to script-defined functions,
|
||||
// i.e. qualifiers + function name + number of arguments.
|
||||
@ -1039,7 +1046,6 @@ impl Module {
|
||||
functions.push((hash_qualified_fn, func.clone()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if self.indexed {
|
||||
return;
|
||||
|
@ -551,11 +551,17 @@ fn optimize_expr(expr: Expr, state: &mut State) -> Expr {
|
||||
|
||||
// First search in functions lib (can override built-in)
|
||||
// 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; }
|
||||
let fn_def = f.get_fn_def();
|
||||
&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
|
||||
x.3 = x.3.into_iter().map(|a| optimize_expr(a, state)).collect();
|
||||
return Expr::FnCall(x);
|
||||
|
@ -190,7 +190,6 @@ fn modulo_u<T: Rem>(x: T, y: T) -> FuncReturn<<T as Rem>::Output> {
|
||||
// Checked power
|
||||
pub(crate) fn pow_i_i(x: INT, y: INT) -> FuncReturn<INT> {
|
||||
#[cfg(not(feature = "only_i32"))]
|
||||
{
|
||||
if y > (u32::MAX as INT) {
|
||||
Err(Box::new(EvalAltResult::ErrorArithmetic(
|
||||
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")]
|
||||
{
|
||||
if y < 0 {
|
||||
Err(Box::new(EvalAltResult::ErrorArithmetic(
|
||||
format!("Integer raised to a negative index: {} ~ {}", x, y),
|
||||
@ -227,7 +224,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)
|
||||
pub(crate) fn pow_i_i_u(x: INT, y: INT) -> FuncReturn<INT> {
|
||||
Ok(x.pow(y as u32))
|
||||
|
@ -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
|
||||
#[cfg(not(feature = "unchecked"))]
|
||||
{
|
||||
if engine.max_array_size > 0 && len > 0 && (len as usize) > engine.max_array_size {
|
||||
return Err(Box::new(EvalAltResult::ErrorDataTooLarge(
|
||||
"Size of array".to_string(),
|
||||
@ -39,7 +38,6 @@ fn pad<T: Variant + Clone>(engine: &Engine, args: &mut [&mut Dynamic]) -> FuncRe
|
||||
Position::none(),
|
||||
)));
|
||||
}
|
||||
}
|
||||
|
||||
if len > 0 {
|
||||
let item = args[2].clone();
|
||||
|
@ -3,6 +3,7 @@ use crate::fn_native::FnPtr;
|
||||
|
||||
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_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()));
|
||||
});
|
||||
|
@ -231,7 +231,6 @@ def_package!(crate:MoreStringPackage:"Additional string utilities, including str
|
||||
|
||||
// Check if string will be over max size limit
|
||||
#[cfg(not(feature = "unchecked"))]
|
||||
{
|
||||
if engine.max_string_size > 0 && len > 0 && (len as usize) > engine.max_string_size {
|
||||
return Err(Box::new(EvalAltResult::ErrorDataTooLarge(
|
||||
"Length of string".to_string(),
|
||||
@ -240,7 +239,6 @@ def_package!(crate:MoreStringPackage:"Additional string utilities, including str
|
||||
Position::none(),
|
||||
)));
|
||||
}
|
||||
}
|
||||
|
||||
if len > 0 {
|
||||
let ch = *args[2].downcast_ref::< char>().unwrap();
|
||||
|
@ -33,7 +33,6 @@ def_package!(crate:BasicTimePackage:"Basic timing utilities.", lib, {
|
||||
let seconds = (ts2 - ts1).as_secs();
|
||||
|
||||
#[cfg(not(feature = "unchecked"))]
|
||||
{
|
||||
if seconds > (MAX_INT as u64) {
|
||||
return Err(Box::new(EvalAltResult::ErrorArithmetic(
|
||||
format!(
|
||||
@ -43,7 +42,7 @@ def_package!(crate:BasicTimePackage:"Basic timing utilities.", lib, {
|
||||
Position::none(),
|
||||
)));
|
||||
}
|
||||
}
|
||||
|
||||
return Ok(-(seconds as INT));
|
||||
}
|
||||
} else {
|
||||
@ -55,14 +54,13 @@ def_package!(crate:BasicTimePackage:"Basic timing utilities.", lib, {
|
||||
let seconds = (ts1 - ts2).as_secs();
|
||||
|
||||
#[cfg(not(feature = "unchecked"))]
|
||||
{
|
||||
if seconds > (MAX_INT as u64) {
|
||||
return Err(Box::new(EvalAltResult::ErrorArithmetic(
|
||||
format!("Integer overflow for timestamp duration: {}", seconds),
|
||||
Position::none(),
|
||||
)));
|
||||
}
|
||||
}
|
||||
|
||||
return Ok(seconds as INT);
|
||||
}
|
||||
}
|
||||
@ -86,14 +84,13 @@ def_package!(crate:BasicTimePackage:"Basic timing utilities.", lib, {
|
||||
let seconds = timestamp.elapsed().as_secs();
|
||||
|
||||
#[cfg(not(feature = "unchecked"))]
|
||||
{
|
||||
if seconds > (MAX_INT as u64) {
|
||||
return Err(Box::new(EvalAltResult::ErrorArithmetic(
|
||||
format!("Integer overflow for timestamp.elapsed: {}", seconds),
|
||||
Position::none(),
|
||||
)));
|
||||
}
|
||||
}
|
||||
|
||||
Ok(seconds as INT)
|
||||
}
|
||||
|
||||
|
101
src/parser.rs
101
src/parser.rs
@ -332,36 +332,26 @@ pub enum ReturnType {
|
||||
Exception,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Eq, PartialEq, Hash, Default)]
|
||||
struct ParseState {
|
||||
#[derive(Clone)]
|
||||
struct ParseState<'e> {
|
||||
/// Reference to the scripting `Engine`.
|
||||
engine: &'e Engine,
|
||||
/// 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.
|
||||
pub modules: Vec<String>,
|
||||
modules: Vec<String>,
|
||||
/// Maximum levels of expression nesting.
|
||||
pub 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,
|
||||
max_expr_depth: usize,
|
||||
}
|
||||
|
||||
impl ParseState {
|
||||
impl<'e> ParseState<'e> {
|
||||
/// Create a new `ParseState`.
|
||||
pub fn new(
|
||||
max_expr_depth: usize,
|
||||
max_string_size: usize,
|
||||
max_array_size: usize,
|
||||
max_map_size: usize,
|
||||
) -> Self {
|
||||
pub fn new(engine: &'e Engine, max_expr_depth: usize) -> Self {
|
||||
Self {
|
||||
engine,
|
||||
max_expr_depth,
|
||||
max_string_size,
|
||||
max_array_size,
|
||||
max_map_size,
|
||||
..Default::default()
|
||||
stack: Default::default(),
|
||||
modules: Default::default(),
|
||||
}
|
||||
}
|
||||
/// Find a variable by name in the `ParseState`, searching in reverse.
|
||||
@ -1206,10 +1196,10 @@ fn parse_array_literal(
|
||||
let mut arr = StaticVec::new();
|
||||
|
||||
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(
|
||||
"Size of array literal".to_string(),
|
||||
state.max_array_size,
|
||||
state.engine.max_array_size,
|
||||
)
|
||||
.into_err(input.peek().unwrap().1));
|
||||
}
|
||||
@ -1272,7 +1262,7 @@ fn parse_map_literal(
|
||||
_ => {
|
||||
let (name, pos) = match input.next().unwrap() {
|
||||
(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)),
|
||||
(_, pos) if map.is_empty() => {
|
||||
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(
|
||||
"Number of properties in object map literal".to_string(),
|
||||
state.max_map_size,
|
||||
state.engine.max_map_size,
|
||||
)
|
||||
.into_err(input.peek().unwrap().1));
|
||||
}
|
||||
@ -1380,7 +1370,7 @@ fn parse_primary(
|
||||
#[cfg(not(feature = "no_float"))]
|
||||
Token::FloatConstant(x) => Expr::FloatConstant(Box::new((x, 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) => {
|
||||
let index = state.find_var(&s);
|
||||
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))))
|
||||
.or_else(|| {
|
||||
#[cfg(not(feature = "no_float"))]
|
||||
{
|
||||
Some(Expr::FloatConstant(Box::new((-(x.0 as FLOAT), pos))))
|
||||
}
|
||||
return Some(Expr::FloatConstant(Box::new((-(x.0 as FLOAT), pos))));
|
||||
#[cfg(feature = "no_float")]
|
||||
{
|
||||
None
|
||||
}
|
||||
return None;
|
||||
})
|
||||
.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 @ Expr::FnCall(_)) => Expr::Dot(Box::new((lhs, func, op_pos))),
|
||||
// lhs.rhs
|
||||
_ => unreachable!(),
|
||||
(_, rhs) => return Err(PERR::PropertyExpected.into_err(rhs.position())),
|
||||
})
|
||||
}
|
||||
|
||||
@ -1870,7 +1856,8 @@ fn parse_binary_op(
|
||||
|
||||
loop {
|
||||
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();
|
||||
|
||||
// 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 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
|
||||
// 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)?
|
||||
}
|
||||
|
||||
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)),
|
||||
};
|
||||
}
|
||||
@ -2194,6 +2194,7 @@ fn parse_let(
|
||||
}
|
||||
|
||||
/// Parse an import statement.
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
fn parse_import(
|
||||
input: &mut TokenStream,
|
||||
state: &mut ParseState,
|
||||
@ -2444,6 +2445,8 @@ fn parse_stmt(
|
||||
|
||||
Token::Let => parse_let(input, state, Normal, 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()),
|
||||
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
@ -2468,7 +2471,7 @@ fn parse_fn(
|
||||
settings.ensure_level_within_max_limit(state.max_expr_depth)?;
|
||||
|
||||
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)),
|
||||
};
|
||||
|
||||
@ -2556,12 +2559,7 @@ impl Engine {
|
||||
scope: &Scope,
|
||||
optimization_level: OptimizationLevel,
|
||||
) -> Result<AST, ParseError> {
|
||||
let mut state = ParseState::new(
|
||||
self.max_expr_depth,
|
||||
self.max_string_size,
|
||||
self.max_array_size,
|
||||
self.max_map_size,
|
||||
);
|
||||
let mut state = ParseState::new(self, self.max_expr_depth);
|
||||
let settings = ParseSettings {
|
||||
allow_if_expr: false,
|
||||
allow_stmt_expr: false,
|
||||
@ -2597,12 +2595,7 @@ impl Engine {
|
||||
) -> Result<(Vec<Stmt>, Vec<ScriptFnDef>), ParseError> {
|
||||
let mut statements = Vec::<Stmt>::new();
|
||||
let mut functions = HashMap::<u64, ScriptFnDef, _>::with_hasher(StraightHasherBuilder);
|
||||
let mut state = ParseState::new(
|
||||
self.max_expr_depth,
|
||||
self.max_string_size,
|
||||
self.max_array_size,
|
||||
self.max_map_size,
|
||||
);
|
||||
let mut state = ParseState::new(self, self.max_expr_depth);
|
||||
|
||||
while !input.peek().unwrap().0.is_eof() {
|
||||
// Collect all the function definitions
|
||||
@ -2615,14 +2608,8 @@ impl Engine {
|
||||
};
|
||||
|
||||
match input.peek().unwrap() {
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
(Token::Fn, pos) => {
|
||||
let mut state = ParseState::new(
|
||||
self.max_function_expr_depth,
|
||||
self.max_string_size,
|
||||
self.max_array_size,
|
||||
self.max_map_size,
|
||||
);
|
||||
let mut state = ParseState::new(self, self.max_function_expr_depth);
|
||||
let settings = ParseSettings {
|
||||
allow_if_expr: true,
|
||||
allow_stmt_expr: true,
|
||||
|
@ -74,8 +74,8 @@ pub enum EvalAltResult {
|
||||
/// Assignment to a constant variable.
|
||||
ErrorAssignmentToConstant(String, Position),
|
||||
/// Returned type is not the same as the required output type.
|
||||
/// Wrapped value is the type of the actual result.
|
||||
ErrorMismatchOutputType(String, Position),
|
||||
/// Wrapped values are the type requested and type of the actual result.
|
||||
ErrorMismatchOutputType(String, String, Position),
|
||||
/// Inappropriate member access.
|
||||
ErrorDotExpr(String, Position),
|
||||
/// Arithmetic error encountered. Wrapped value is the error message.
|
||||
@ -141,7 +141,7 @@ impl EvalAltResult {
|
||||
"Assignment to an unsupported left-hand side expression"
|
||||
}
|
||||
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::ErrorDotExpr(_, _) => "Malformed dot expression",
|
||||
Self::ErrorArithmetic(_, _) => "Arithmetic error",
|
||||
@ -197,16 +197,18 @@ impl fmt::Display for EvalAltResult {
|
||||
| Self::ErrorTooManyOperations(_)
|
||||
| Self::ErrorTooManyModules(_)
|
||||
| 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::ErrorMismatchOutputType(s, _) => write!(f, "{}: {}", desc, s)?,
|
||||
Self::ErrorArithmetic(s, _) => write!(f, "{}", s)?,
|
||||
Self::ErrorMismatchOutputType(r, s, _) => {
|
||||
write!(f, "{} (expecting {}): {}", desc, s, r)?
|
||||
}
|
||||
Self::ErrorArithmetic(s, _) => f.write_str(s)?,
|
||||
|
||||
Self::ErrorLoopBreak(_, _) => write!(f, "{}", desc)?,
|
||||
Self::Return(_, _) => write!(f, "{}", desc)?,
|
||||
Self::ErrorLoopBreak(_, _) => f.write_str(desc)?,
|
||||
Self::Return(_, _) => f.write_str(desc)?,
|
||||
|
||||
Self::ErrorBooleanArgMismatch(op, _) => {
|
||||
write!(f, "{} operator expects boolean operands", op)?
|
||||
@ -215,7 +217,7 @@ impl fmt::Display for EvalAltResult {
|
||||
Self::ErrorArrayBounds(_, index, _) if *index < 0 => {
|
||||
write!(f, "{}: {} < 0", desc, index)?
|
||||
}
|
||||
Self::ErrorArrayBounds(0, _, _) => write!(f, "{}", desc)?,
|
||||
Self::ErrorArrayBounds(0, _, _) => f.write_str(desc)?,
|
||||
Self::ErrorArrayBounds(1, index, _) => write!(
|
||||
f,
|
||||
"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 => {
|
||||
write!(f, "{}: {} < 0", desc, index)?
|
||||
}
|
||||
Self::ErrorStringBounds(0, _, _) => write!(f, "{}", desc)?,
|
||||
Self::ErrorStringBounds(0, _, _) => f.write_str(desc)?,
|
||||
Self::ErrorStringBounds(1, index, _) => write!(
|
||||
f,
|
||||
"String index {} is out of bounds: only one character in the string",
|
||||
@ -289,7 +291,7 @@ impl EvalAltResult {
|
||||
| Self::ErrorModuleNotFound(_, pos)
|
||||
| Self::ErrorAssignmentToUnknownLHS(pos)
|
||||
| Self::ErrorAssignmentToConstant(_, pos)
|
||||
| Self::ErrorMismatchOutputType(_, pos)
|
||||
| Self::ErrorMismatchOutputType(_, _, pos)
|
||||
| Self::ErrorInExpr(pos)
|
||||
| Self::ErrorDotExpr(_, pos)
|
||||
| Self::ErrorArithmetic(_, pos)
|
||||
@ -329,7 +331,7 @@ impl EvalAltResult {
|
||||
| Self::ErrorModuleNotFound(_, pos)
|
||||
| Self::ErrorAssignmentToUnknownLHS(pos)
|
||||
| Self::ErrorAssignmentToConstant(_, pos)
|
||||
| Self::ErrorMismatchOutputType(_, pos)
|
||||
| Self::ErrorMismatchOutputType(_, _, pos)
|
||||
| Self::ErrorInExpr(pos)
|
||||
| Self::ErrorDotExpr(_, pos)
|
||||
| Self::ErrorArithmetic(_, pos)
|
||||
|
446
src/serde/de.rs
Normal file
446
src/serde/de.rs
Normal 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
5
src/serde/mod.rs
Normal 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
549
src/serde/ser.rs
Normal 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
155
src/serde/str.rs
Normal 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
254
src/settings.rs
Normal 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(())
|
||||
}
|
||||
}
|
131
src/token.rs
131
src/token.rs
@ -1,5 +1,6 @@
|
||||
//! Main module defining the lexer and parser.
|
||||
|
||||
use crate::engine::Engine;
|
||||
use crate::error::LexError;
|
||||
use crate::parser::INT;
|
||||
use crate::utils::StaticVec;
|
||||
@ -10,7 +11,9 @@ use crate::parser::FLOAT;
|
||||
use crate::stdlib::{
|
||||
borrow::Cow,
|
||||
boxed::Box,
|
||||
char, fmt,
|
||||
char,
|
||||
collections::{HashMap, HashSet},
|
||||
fmt,
|
||||
iter::Peekable,
|
||||
str::{Chars, FromStr},
|
||||
string::{String, ToString},
|
||||
@ -19,7 +22,7 @@ use crate::stdlib::{
|
||||
|
||||
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.
|
||||
///
|
||||
@ -137,7 +140,7 @@ pub enum Token {
|
||||
FloatConstant(FLOAT),
|
||||
Identifier(String),
|
||||
CharConstant(char),
|
||||
StringConst(String),
|
||||
StringConstant(String),
|
||||
LeftBrace,
|
||||
RightBrace,
|
||||
LeftParen,
|
||||
@ -210,6 +213,7 @@ pub enum Token {
|
||||
As,
|
||||
LexError(Box<LexError>),
|
||||
Comment(String),
|
||||
Custom(String),
|
||||
EOF,
|
||||
}
|
||||
|
||||
@ -222,12 +226,13 @@ impl Token {
|
||||
IntegerConstant(i) => i.to_string().into(),
|
||||
#[cfg(not(feature = "no_float"))]
|
||||
FloatConstant(f) => f.to_string().into(),
|
||||
Identifier(s) => s.clone().into(),
|
||||
StringConstant(_) => "string".into(),
|
||||
CharConstant(c) => c.to_string().into(),
|
||||
Identifier(s) => s.clone().into(),
|
||||
Custom(s) => s.clone().into(),
|
||||
LexError(err) => err.to_string().into(),
|
||||
|
||||
token => (match token {
|
||||
StringConst(_) => "string",
|
||||
token => match token {
|
||||
LeftBrace => "{",
|
||||
RightBrace => "}",
|
||||
LeftParen => "(",
|
||||
@ -292,13 +297,15 @@ impl Token {
|
||||
PowerOfAssign => "~=",
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
Private => "private",
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
Import => "import",
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
Export => "export",
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
As => "as",
|
||||
EOF => "{EOF}",
|
||||
_ => unreachable!("operator should be match in outer scope"),
|
||||
})
|
||||
}
|
||||
.into(),
|
||||
}
|
||||
}
|
||||
@ -320,9 +327,9 @@ impl Token {
|
||||
|
||||
match self {
|
||||
LexError(_) |
|
||||
LeftBrace | // (+expr) - is unary
|
||||
LeftBrace | // {+expr} - is unary
|
||||
// RightBrace | {expr} - expr not unary & is closing
|
||||
LeftParen | // {-expr} - is unary
|
||||
LeftParen | // (-expr) - is unary
|
||||
// RightParen | (expr) - expr not unary & is closing
|
||||
LeftBracket | // [-expr] - is unary
|
||||
// RightBracket | [expr] - expr not unary & is closing
|
||||
@ -374,7 +381,7 @@ impl 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::*;
|
||||
|
||||
match self {
|
||||
@ -383,24 +390,27 @@ impl Token {
|
||||
| RightShiftAssign | AndAssign | OrAssign | XOrAssign | ModuloAssign
|
||||
| PowerOfAssign => 0,
|
||||
|
||||
Or | XOr | Pipe => 40,
|
||||
Or | XOr | Pipe => 30,
|
||||
|
||||
And | Ampersand => 50,
|
||||
And | Ampersand => 60,
|
||||
|
||||
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,
|
||||
}
|
||||
@ -422,6 +432,41 @@ impl Token {
|
||||
_ => 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 {
|
||||
@ -431,7 +476,7 @@ impl From<Token> for String {
|
||||
}
|
||||
|
||||
/// State of the tokenizer.
|
||||
#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash, Default)]
|
||||
#[derive(Debug, Clone, Eq, PartialEq, Default)]
|
||||
pub struct TokenizeState {
|
||||
/// Maximum length of a string (0 = unlimited).
|
||||
pub max_string_size: usize,
|
||||
@ -644,7 +689,7 @@ pub fn get_next_token(
|
||||
let result = get_next_token_inner(stream, state, pos);
|
||||
|
||||
// Save the last token's state
|
||||
if let Some((token, _)) = &result {
|
||||
if let Some((ref token, _)) = result {
|
||||
state.non_unary = !token.is_next_unary();
|
||||
}
|
||||
|
||||
@ -848,7 +893,7 @@ fn get_next_token_inner(
|
||||
('"', _) => return parse_string_literal(stream, state, pos, '"')
|
||||
.map_or_else(
|
||||
|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
|
||||
@ -1118,7 +1163,7 @@ fn get_next_token_inner(
|
||||
('\0', _) => unreachable!(),
|
||||
|
||||
(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.
|
||||
pub struct TokenIterator<'a> {
|
||||
pub struct TokenIterator<'a, 'e> {
|
||||
/// Reference to the scripting `Engine`.
|
||||
engine: &'e Engine,
|
||||
/// Current state.
|
||||
state: TokenizeState,
|
||||
/// Current position.
|
||||
@ -1181,19 +1228,47 @@ pub struct TokenIterator<'a> {
|
||||
stream: MultiInputsStream<'a>,
|
||||
}
|
||||
|
||||
impl<'a> Iterator for TokenIterator<'a> {
|
||||
impl<'a> Iterator for TokenIterator<'a, '_> {
|
||||
type Item = (Token, Position);
|
||||
|
||||
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.
|
||||
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 {
|
||||
engine,
|
||||
state: TokenizeState {
|
||||
max_string_size,
|
||||
max_string_size: engine.max_string_size,
|
||||
non_unary: false,
|
||||
comment_level: 0,
|
||||
end_with_none: false,
|
||||
|
@ -35,6 +35,7 @@ fn test_max_string_size() -> Result<(), Box<EvalAltResult>> {
|
||||
EvalAltResult::ErrorDataTooLarge(_, 10, 13, _)
|
||||
));
|
||||
|
||||
#[cfg(not(feature = "no_object"))]
|
||||
assert!(matches!(
|
||||
*engine
|
||||
.eval::<String>(
|
||||
@ -92,6 +93,8 @@ fn test_max_array_size() -> Result<(), Box<EvalAltResult>> {
|
||||
.expect_err("should error"),
|
||||
EvalAltResult::ErrorDataTooLarge(_, 10, 12, _)
|
||||
));
|
||||
|
||||
#[cfg(not(feature = "no_object"))]
|
||||
assert!(matches!(
|
||||
*engine
|
||||
.eval::<Array>(
|
||||
|
@ -39,7 +39,7 @@ fn test_for_string() -> Result<(), Box<EvalAltResult>> {
|
||||
let sum = 0;
|
||||
|
||||
for ch in s {
|
||||
sum += ch.to_int();
|
||||
sum += to_int(ch);
|
||||
}
|
||||
|
||||
sum
|
||||
|
@ -52,6 +52,7 @@ fn test_functions() -> Result<(), Box<EvalAltResult>> {
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(not(feature = "no_object"))]
|
||||
fn test_function_pointers() -> Result<(), Box<EvalAltResult>> {
|
||||
let engine = Engine::new();
|
||||
|
||||
|
@ -6,14 +6,14 @@ fn test_mismatched_op() {
|
||||
|
||||
assert!(matches!(
|
||||
*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]
|
||||
#[cfg(not(feature = "no_object"))]
|
||||
fn test_mismatched_op_custom_type() {
|
||||
#[derive(Clone)]
|
||||
#[derive(Debug, Clone)]
|
||||
struct TestStruct {
|
||||
x: INT,
|
||||
}
|
||||
@ -28,19 +28,14 @@ fn test_mismatched_op_custom_type() {
|
||||
engine.register_type_with_name::<TestStruct>("TestStruct");
|
||||
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!(
|
||||
*r,
|
||||
EvalAltResult::ErrorFunctionNotFound(err, _) if err == "+ (i32, TestStruct)"
|
||||
*engine.eval::<INT>("60 + new_ts()").expect_err("should error"),
|
||||
EvalAltResult::ErrorFunctionNotFound(err, _) if err == format!("+ ({}, TestStruct)", std::any::type_name::<INT>())
|
||||
));
|
||||
|
||||
#[cfg(not(feature = "only_i32"))]
|
||||
assert!(matches!(
|
||||
*r,
|
||||
EvalAltResult::ErrorFunctionNotFound(err, _) if err == "+ (i64, TestStruct)"
|
||||
*engine.eval::<TestStruct>("42").expect_err("should error"),
|
||||
EvalAltResult::ErrorMismatchOutputType(need, actual, _)
|
||||
if need == "TestStruct" && actual == std::any::type_name::<INT>()
|
||||
));
|
||||
}
|
||||
|
192
tests/serde.rs
Normal file
192
tests/serde.rs
Normal 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
49
tests/tokens.rs
Normal 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(())
|
||||
}
|
Loading…
Reference in New Issue
Block a user