Merge branch 'master' into plugins
This commit is contained in:
commit
ef2d28d423
14
Cargo.toml
14
Cargo.toml
@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "rhai"
|
name = "rhai"
|
||||||
version = "0.16.0"
|
version = "0.17.0"
|
||||||
edition = "2018"
|
edition = "2018"
|
||||||
authors = ["Jonathan Turner", "Lukáš Hozda", "Stephen Chung"]
|
authors = ["Jonathan Turner", "Lukáš Hozda", "Stephen Chung"]
|
||||||
description = "Embedded scripting for Rust"
|
description = "Embedded scripting for Rust"
|
||||||
@ -65,5 +65,17 @@ default-features = false
|
|||||||
features = ["compile-time-rng"]
|
features = ["compile-time-rng"]
|
||||||
optional = true
|
optional = true
|
||||||
|
|
||||||
|
[dependencies.serde]
|
||||||
|
package = "serde"
|
||||||
|
version = "1.0.111"
|
||||||
|
features = ["derive"]
|
||||||
|
optional = true
|
||||||
|
|
||||||
[target.'cfg(target_arch = "wasm32")'.dependencies]
|
[target.'cfg(target_arch = "wasm32")'.dependencies]
|
||||||
instant= { version = "0.1.4", features = ["wasm-bindgen"] } # WASM implementation of std::time::Instant
|
instant= { version = "0.1.4", features = ["wasm-bindgen"] } # WASM implementation of std::time::Instant
|
||||||
|
|
||||||
|
[package.metadata.docs.rs]
|
||||||
|
features = ["serde"]
|
||||||
|
|
||||||
|
[package.metadata.playground]
|
||||||
|
features = ["serde"]
|
||||||
|
@ -26,18 +26,20 @@ Features
|
|||||||
* Freely pass Rust variables/constants into a script via an external [`Scope`](https://schungx.github.io/rhai/rust/scope.html).
|
* Freely pass Rust variables/constants into a script via an external [`Scope`](https://schungx.github.io/rhai/rust/scope.html).
|
||||||
* Easily [call a script-defined function](https://schungx.github.io/rhai/engine/call-fn.html) from Rust.
|
* Easily [call a script-defined function](https://schungx.github.io/rhai/engine/call-fn.html) from Rust.
|
||||||
* Fairly low compile-time overhead.
|
* Fairly low compile-time overhead.
|
||||||
* Fairly efficient evaluation (1 million iterations in 0.25 sec on a single core, 2.3 GHz Linux VM).
|
* Fairly efficient evaluation (1 million iterations in 0.3 sec on a single core, 2.3 GHz Linux VM).
|
||||||
* Relatively little `unsafe` code (yes there are some for performance reasons, and most `unsafe` code is limited to
|
* Relatively little `unsafe` code (yes there are some for performance reasons, and most `unsafe` code is limited to
|
||||||
one single source file, all with names starting with `"unsafe_"`).
|
one single source file, all with names starting with `"unsafe_"`).
|
||||||
* Re-entrant scripting engine can be made `Send + Sync` (via the [`sync`] feature).
|
* Re-entrant scripting engine can be made `Send + Sync` (via the `sync` feature).
|
||||||
* Sand-boxed - the scripting engine, if declared immutable, cannot mutate the containing environment unless explicitly permitted (e.g. via a `RefCell`).
|
* Sand-boxed - the scripting engine, if declared immutable, cannot mutate the containing environment unless explicitly permitted (e.g. via a `RefCell`).
|
||||||
* Rugged - protected against malicious attacks (such as [stack-overflow](https://schungx.github.io/rhai/safety/max-call-stack.html), [over-sized data](https://schungx.github.io/rhai/safety/max-string-size.html), and [runaway scripts](https://schungx.github.io/rhai/safety/max-operations.html) etc.) that may come from untrusted third-party user-land scripts.
|
* Rugged - protected against malicious attacks (such as [stack-overflow](https://schungx.github.io/rhai/safety/max-call-stack.html), [over-sized data](https://schungx.github.io/rhai/safety/max-string-size.html), and [runaway scripts](https://schungx.github.io/rhai/safety/max-operations.html) etc.) that may come from untrusted third-party user-land scripts.
|
||||||
* Track script evaluation [progress](https://schungx.github.io/rhai/safety/progress.html) and manually terminate a script run.
|
* Track script evaluation [progress](https://schungx.github.io/rhai/safety/progress.html) and manually terminate a script run.
|
||||||
* [Function overloading](https://schungx.github.io/rhai/language/overload.html).
|
* [Function overloading](https://schungx.github.io/rhai/language/overload.html).
|
||||||
* [Operator overloading](https://schungx.github.io/rhai/rust/operators.html).
|
* [Operator overloading](https://schungx.github.io/rhai/rust/operators.html) and [custom operators](https://schungx.github.io/rhai/engine/custom-op.html).
|
||||||
* Dynamic dispatch via [function pointers](https://schungx.github.io/rhai/language/fn-ptr.html).
|
* Dynamic dispatch via [function pointers](https://schungx.github.io/rhai/language/fn-ptr.html).
|
||||||
* Some support for [object-oriented programming (OOP)](https://schungx.github.io/rhai/language/oop.html).
|
* Some support for [object-oriented programming (OOP)](https://schungx.github.io/rhai/language/oop.html).
|
||||||
* Organize code base with dynamically-loadable [modules](https://schungx.github.io/rhai/language/modules.html).
|
* Organize code base with dynamically-loadable [modules](https://schungx.github.io/rhai/language/modules.html).
|
||||||
|
* Serialization/deserialization support via [serde](https://crates.io/crates/serde) (requires the `serde` feature).
|
||||||
|
* Surgically disable keywords and operators to restrict the language.
|
||||||
* Scripts are [optimized](https://schungx.github.io/rhai/engine/optimize.html) (useful for template-based machine-generated scripts) for repeated evaluations.
|
* Scripts are [optimized](https://schungx.github.io/rhai/engine/optimize.html) (useful for template-based machine-generated scripts) for repeated evaluations.
|
||||||
* Support for [minimal builds](https://schungx.github.io/rhai/start/builds/minimal.html) by excluding unneeded language [features](https://schungx.github.io/rhai/start/features.html).
|
* Support for [minimal builds](https://schungx.github.io/rhai/start/builds/minimal.html) by excluding unneeded language [features](https://schungx.github.io/rhai/start/features.html).
|
||||||
|
|
||||||
|
33
RELEASES.md
33
RELEASES.md
@ -1,11 +1,44 @@
|
|||||||
Rhai Release Notes
|
Rhai Release Notes
|
||||||
==================
|
==================
|
||||||
|
|
||||||
|
Version 0.17.0
|
||||||
|
==============
|
||||||
|
|
||||||
|
This version adds:
|
||||||
|
|
||||||
|
* [`serde`](https://crates.io/crates/serde) support for working with `Dynamic` values (particularly _object maps_).
|
||||||
|
* Ability to surgically disable keywords and/or operators in the language.
|
||||||
|
* Ability to define custom operators (which must be valid identifiers).
|
||||||
|
|
||||||
|
Breaking changes
|
||||||
|
----------------
|
||||||
|
|
||||||
|
* `EvalAltResult::ErrorMismatchOutputType` has an extra argument containing the name of the requested type.
|
||||||
|
|
||||||
|
New features
|
||||||
|
------------
|
||||||
|
|
||||||
|
* New `serde` feature to allow serializating/deserializating to/from `Dynamic` values using [`serde`](https://crates.io/crates/serde).
|
||||||
|
This is particularly useful when converting a Rust `struct` to a `Dynamic` _object map_ and back.
|
||||||
|
* `Engine::disable_symbol` to surgically disable keywords and/or operators.
|
||||||
|
* `Engine::register_custom_operator` to define a custom operator.
|
||||||
|
|
||||||
|
|
||||||
|
Version 0.16.1
|
||||||
|
==============
|
||||||
|
|
||||||
|
Bug fix release to fix errors when compiling with features.
|
||||||
|
|
||||||
|
|
||||||
Version 0.16.0
|
Version 0.16.0
|
||||||
==============
|
==============
|
||||||
|
|
||||||
The major new feature in this version is OOP - well, poor man's OOP, that is.
|
The major new feature in this version is OOP - well, poor man's OOP, that is.
|
||||||
|
|
||||||
|
The `README` is officially transferred to [The Rhai Book](https://schungx.github.io/rhai).
|
||||||
|
|
||||||
|
An online [Playground](https://alvinhochun.github.io/rhai-demo/) is available.
|
||||||
|
|
||||||
Breaking changes
|
Breaking changes
|
||||||
----------------
|
----------------
|
||||||
|
|
||||||
|
@ -96,14 +96,17 @@ The Rhai Scripting Language
|
|||||||
9. [Maximum Statement Depth](safety/max-stmt-depth.md)
|
9. [Maximum Statement Depth](safety/max-stmt-depth.md)
|
||||||
8. [Advanced Topics](advanced.md)
|
8. [Advanced Topics](advanced.md)
|
||||||
1. [Object-Oriented Programming (OOP)](language/oop.md)
|
1. [Object-Oriented Programming (OOP)](language/oop.md)
|
||||||
2. [Script Optimization](engine/optimize/index.md)
|
2. [Serialization/Deserialization of `Dynamic` with `serde`](rust/serde.md)
|
||||||
|
3. [Script Optimization](engine/optimize/index.md)
|
||||||
1. [Optimization Levels](engine/optimize/optimize-levels.md)
|
1. [Optimization Levels](engine/optimize/optimize-levels.md)
|
||||||
2. [Re-Optimize an AST](engine/optimize/reoptimize.md)
|
2. [Re-Optimize an AST](engine/optimize/reoptimize.md)
|
||||||
3. [Eager Function Evaluation](engine/optimize/eager.md)
|
3. [Eager Function Evaluation](engine/optimize/eager.md)
|
||||||
4. [Side-Effect Considerations](engine/optimize/side-effects.md)
|
4. [Side-Effect Considerations](engine/optimize/side-effects.md)
|
||||||
5. [Volatility Considerations](engine/optimize/volatility.md)
|
5. [Volatility Considerations](engine/optimize/volatility.md)
|
||||||
6. [Subtle Semantic Changes](engine/optimize/semantics.md)
|
6. [Subtle Semantic Changes](engine/optimize/semantics.md)
|
||||||
3. [Eval Statement](language/eval.md)
|
4. [Disable Keywords and/or Operators](engine/disable.md)
|
||||||
|
5. [Custom Operators](engine/custom-op.md)
|
||||||
|
6. [Eval Statement](language/eval.md)
|
||||||
9. [Appendix](appendix/index.md)
|
9. [Appendix](appendix/index.md)
|
||||||
1. [Keywords](appendix/keywords.md)
|
1. [Keywords](appendix/keywords.md)
|
||||||
2. [Operators](appendix/operators.md)
|
2. [Operators](appendix/operators.md)
|
||||||
|
@ -22,7 +22,7 @@ Fast
|
|||||||
|
|
||||||
* Fairly low compile-time overhead.
|
* Fairly low compile-time overhead.
|
||||||
|
|
||||||
* Fairly efficient evaluation (1 million iterations in 0.25 sec on a single core, 2.3 GHz Linux VM).
|
* Fairly efficient evaluation (1 million iterations in 0.3 sec on a single core, 2.3 GHz Linux VM).
|
||||||
|
|
||||||
* Scripts are [optimized][script optimization] (useful for template-based machine-generated scripts) for repeated evaluations.
|
* Scripts are [optimized][script optimization] (useful for template-based machine-generated scripts) for repeated evaluations.
|
||||||
|
|
||||||
@ -39,6 +39,8 @@ Dynamic
|
|||||||
|
|
||||||
* Some support for [object-oriented programming (OOP)][OOP].
|
* Some support for [object-oriented programming (OOP)][OOP].
|
||||||
|
|
||||||
|
* Serialization/deserialization support via [`serde`].
|
||||||
|
|
||||||
Safe
|
Safe
|
||||||
----
|
----
|
||||||
|
|
||||||
@ -62,3 +64,7 @@ Flexible
|
|||||||
* Support for [minimal builds] by excluding unneeded language [features].
|
* Support for [minimal builds] by excluding unneeded language [features].
|
||||||
|
|
||||||
* Supports [most build targets](targets.md) including `no-std` and [WASM].
|
* Supports [most build targets](targets.md) including `no-std` and [WASM].
|
||||||
|
|
||||||
|
* Surgically [disable keywords and operators] to restrict the language.
|
||||||
|
|
||||||
|
* [Custom operators].
|
||||||
|
@ -5,3 +5,8 @@ What is Rhai
|
|||||||
|
|
||||||
Rhai is an embedded scripting language and evaluation engine for Rust that gives a safe and easy way
|
Rhai is an embedded scripting language and evaluation engine for Rust that gives a safe and easy way
|
||||||
to add scripting to any application.
|
to add scripting to any application.
|
||||||
|
|
||||||
|
|
||||||
|
This Book is for version {{version}} of Rhai.
|
||||||
|
|
||||||
|
For the latest development version, see [here]({{rootUrl}}/vnext/).
|
||||||
|
@ -3,28 +3,28 @@ Operators
|
|||||||
|
|
||||||
{{#include ../links.md}}
|
{{#include ../links.md}}
|
||||||
|
|
||||||
| Operator | Description | Binary? |
|
| Operator | Description | Binary? | Binding direction |
|
||||||
| :---------------: | ------------------------------ | :-----: |
|
| :---------------: | ------------------------------ | :-----: | :---------------: |
|
||||||
| `+` | Add | Yes |
|
| `+` | Add | Yes | Left |
|
||||||
| `-` | Subtract, Minus | Yes/No |
|
| `-` | Subtract, Minus | Yes/No | Left |
|
||||||
| `*` | Multiply | Yes |
|
| `*` | Multiply | Yes | Left |
|
||||||
| `/` | Divide | Yes |
|
| `/` | Divide | Yes | Left |
|
||||||
| `%` | Modulo | Yes |
|
| `%` | Modulo | Yes | Left |
|
||||||
| `~` | Power | Yes |
|
| `~` | Power | Yes | Left |
|
||||||
| `>>` | Right bit-shift | Yes |
|
| `>>` | Right bit-shift | Yes | Left |
|
||||||
| `<<` | Left bit-shift | Yes |
|
| `<<` | Left bit-shift | Yes | Left |
|
||||||
| `&` | Bit-wise _And_, Boolean _And_ | Yes |
|
| `&` | Bit-wise _And_, Boolean _And_ | Yes | Left |
|
||||||
| <code>\|</code> | Bit-wise _Or_, Boolean _Or_ | Yes |
|
| <code>\|</code> | Bit-wise _Or_, Boolean _Or_ | Yes | Left |
|
||||||
| `^` | Bit-wise _Xor_ | Yes |
|
| `^` | Bit-wise _Xor_ | Yes | Left |
|
||||||
| `==` | Equals to | Yes |
|
| `==` | Equals to | Yes | Left |
|
||||||
| `~=` | Not equals to | Yes |
|
| `~=` | Not equals to | Yes | Left |
|
||||||
| `>` | Greater than | Yes |
|
| `>` | Greater than | Yes | Left |
|
||||||
| `>=` | Greater than or equals to | Yes |
|
| `>=` | Greater than or equals to | Yes | Left |
|
||||||
| `<` | Less than | Yes |
|
| `<` | Less than | Yes | Left |
|
||||||
| `<=` | Less than or equals to | Yes |
|
| `<=` | Less than or equals to | Yes | Left |
|
||||||
| `>=` | Greater than or equals to | Yes |
|
| `>=` | Greater than or equals to | Yes | Left |
|
||||||
| `&&` | Boolean _And_ (short-circuits) | Yes |
|
| `&&` | Boolean _And_ (short-circuits) | Yes | Left |
|
||||||
| <code>\|\|</code> | Boolean _Or_ (short-circuits) | Yes |
|
| <code>\|\|</code> | Boolean _Or_ (short-circuits) | Yes | Left |
|
||||||
| `!` | Boolean _Not_ | No |
|
| `!` | Boolean _Not_ | No | Left |
|
||||||
| `[` .. `]` | Indexing | Yes |
|
| `[` .. `]` | Indexing | Yes | Right |
|
||||||
| `.` | Property access, Method call | Yes |
|
| `.` | Property access, Method call | Yes | Right |
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
{
|
{
|
||||||
"version": "0.16.0",
|
"version": "0.17.0",
|
||||||
"rootUrl": "",
|
"rootUrl": "",
|
||||||
"rootUrlX": "/rhai"
|
"rootUrlX": "/rhai",
|
||||||
|
"rootUrlXX": "/rhai/vnext"
|
||||||
}
|
}
|
105
doc/src/engine/custom-op.md
Normal file
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
|
[package]: {{rootUrl}}/rust/packages/index.md
|
||||||
[packages]: {{rootUrl}}/rust/packages/index.md
|
[packages]: {{rootUrl}}/rust/packages/index.md
|
||||||
[`Scope`]: {{rootUrl}}/rust/scope.md
|
[`Scope`]: {{rootUrl}}/rust/scope.md
|
||||||
|
[`serde`]: {{rootUrl}}/rust/serde.md
|
||||||
|
|
||||||
[`type_of()`]: {{rootUrl}}/language/type-of.md
|
[`type_of()`]: {{rootUrl}}/language/type-of.md
|
||||||
[`to_string()`]: {{rootUrl}}/language/values-and-types.md
|
[`to_string()`]: {{rootUrl}}/language/values-and-types.md
|
||||||
@ -101,3 +102,7 @@
|
|||||||
[`OptimizationLevel::Full`]: {{rootUrl}}/engine/optimize/optimize-levels.md
|
[`OptimizationLevel::Full`]: {{rootUrl}}/engine/optimize/optimize-levels.md
|
||||||
[`OptimizationLevel::Simple`]: {{rootUrl}}/engine/optimize/optimize-levels.md
|
[`OptimizationLevel::Simple`]: {{rootUrl}}/engine/optimize/optimize-levels.md
|
||||||
[`OptimizationLevel::None`]: {{rootUrl}}/engine/optimize/optimize-levels.md
|
[`OptimizationLevel::None`]: {{rootUrl}}/engine/optimize/optimize-levels.md
|
||||||
|
|
||||||
|
[disable keywords and operators]: {{rootUrl}}/engine/disable.md
|
||||||
|
[custom operator]: {{rootUrl}}/engine/custom-op.md
|
||||||
|
[custom operators]: {{rootUrl}}/engine/custom-op.md
|
||||||
|
104
doc/src/rust/serde.md
Normal file
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)?;
|
||||||
|
```
|
@ -5,17 +5,18 @@ Rust Examples
|
|||||||
|
|
||||||
A number of examples can be found in the `examples` folder:
|
A number of examples can be found in the `examples` folder:
|
||||||
|
|
||||||
| Example | Description |
|
| Example | Description |
|
||||||
| ---------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------- |
|
| ---------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||||
| [`arrays_and_structs`](https://github.com/jonathandturner/rhai/tree/master/examples/arrays_and_structs.rs) | Shows how to register a custom Rust type and using [arrays] on it. |
|
| [`arrays_and_structs`](https://github.com/jonathandturner/rhai/tree/master/examples/arrays_and_structs.rs) | Shows how to register a custom Rust type and using [arrays] on it. |
|
||||||
| [`custom_types_and_methods`](https://github.com/jonathandturner/rhai/tree/master/examples/custom_types_and_methods.rs) | Shows how to register a custom Rust type and methods for it. |
|
| [`custom_types_and_methods`](https://github.com/jonathandturner/rhai/tree/master/examples/custom_types_and_methods.rs) | Shows how to register a custom Rust type and methods for it. |
|
||||||
| [`hello`](https://github.com/jonathandturner/rhai/tree/master/examples/hello.rs) | Simple example that evaluates an expression and prints the result. |
|
| [`hello`](https://github.com/jonathandturner/rhai/tree/master/examples/hello.rs) | Simple example that evaluates an expression and prints the result. |
|
||||||
| [`no_std`](https://github.com/jonathandturner/rhai/tree/master/examples/no_std.rs) | Example to test out `no-std` builds. |
|
| [`no_std`](https://github.com/jonathandturner/rhai/tree/master/examples/no_std.rs) | Example to test out `no-std` builds. |
|
||||||
| [`reuse_scope`](https://github.com/jonathandturner/rhai/tree/master/examples/reuse_scope.rs) | Evaluates two pieces of code in separate runs, but using a common [`Scope`]. |
|
| [`reuse_scope`](https://github.com/jonathandturner/rhai/tree/master/examples/reuse_scope.rs) | Evaluates two pieces of code in separate runs, but using a common [`Scope`]. |
|
||||||
| [`rhai_runner`](https://github.com/jonathandturner/rhai/tree/master/examples/rhai_runner.rs) | Runs each filename passed to it as a Rhai script. |
|
| [`rhai_runner`](https://github.com/jonathandturner/rhai/tree/master/examples/rhai_runner.rs) | Runs each filename passed to it as a Rhai script. |
|
||||||
| [`simple_fn`](https://github.com/jonathandturner/rhai/tree/master/examples/simple_fn.rs) | Shows how to register a simple function. |
|
| [`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. |
|
||||||
| [`strings`](https://github.com/jonathandturner/rhai/tree/master/examples/strings.rs) | Shows different ways to register functions taking string arguments. |
|
| [`simple_fn`](https://github.com/jonathandturner/rhai/tree/master/examples/simple_fn.rs) | Shows how to register a simple function. |
|
||||||
| [`repl`](https://github.com/jonathandturner/rhai/tree/master/examples/repl.rs) | A simple REPL, interactively evaluate statements from stdin. |
|
| [`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. |
|
||||||
|
|
||||||
The `repl` example is a particularly good one as it allows one to interactively try out Rhai's
|
The `repl` example is a particularly good one as it allows one to interactively try out Rhai's
|
||||||
language features in a standard REPL (**R**ead-**E**val-**P**rint **L**oop).
|
language features in a standard REPL (**R**ead-**E**val-**P**rint **L**oop).
|
||||||
|
@ -24,6 +24,7 @@ more control over what a script can (or cannot) do.
|
|||||||
| `no_function` | Disable script-defined [functions]. |
|
| `no_function` | Disable script-defined [functions]. |
|
||||||
| `no_module` | Disable loading external [modules]. |
|
| `no_module` | Disable loading external [modules]. |
|
||||||
| `no_std` | Build for `no-std`. Notice that additional dependencies will be pulled in to replace `std` features. |
|
| `no_std` | Build for `no-std`. Notice that additional dependencies will be pulled in to replace `std` features. |
|
||||||
|
| `serde` | Enable serialization/deserialization via [`serde`]. Notice that the [`serde`](https://crates.io/crates/serde) crate will be pulled in together with its dependencies. |
|
||||||
| `internals` | Expose internal data structures (e.g. [`AST`] nodes). Beware that Rhai internals are volatile and may change from version to version. |
|
| `internals` | Expose internal data structures (e.g. [`AST`] nodes). Beware that Rhai internals are volatile and may change from version to version. |
|
||||||
|
|
||||||
|
|
||||||
|
76
examples/serde.rs
Normal file
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);
|
||||||
|
}
|
||||||
|
}
|
35
src/any.rs
35
src/any.rs
@ -196,13 +196,38 @@ impl Dynamic {
|
|||||||
Union::FnPtr(_) => "Fn",
|
Union::FnPtr(_) => "Fn",
|
||||||
|
|
||||||
#[cfg(not(feature = "no_std"))]
|
#[cfg(not(feature = "no_std"))]
|
||||||
#[cfg(not(target_arch = "wasm32"))]
|
|
||||||
Union::Variant(value) if value.is::<Instant>() => "timestamp",
|
Union::Variant(value) if value.is::<Instant>() => "timestamp",
|
||||||
Union::Variant(value) => (***value).type_name(),
|
Union::Variant(value) => (***value).type_name(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Map the name of a standard type into a friendly form.
|
||||||
|
pub(crate) fn map_std_type_name(name: &str) -> &str {
|
||||||
|
if name == type_name::<String>() {
|
||||||
|
"string"
|
||||||
|
} else if name == type_name::<ImmutableString>() {
|
||||||
|
"string"
|
||||||
|
} else if name == type_name::<&str>() {
|
||||||
|
"string"
|
||||||
|
} else if name == type_name::<FnPtr>() {
|
||||||
|
"Fn"
|
||||||
|
} else if name == type_name::<Instant>() {
|
||||||
|
"timestamp"
|
||||||
|
} else {
|
||||||
|
#[cfg(not(feature = "no_index"))]
|
||||||
|
if name == type_name::<Array>() {
|
||||||
|
return "array";
|
||||||
|
}
|
||||||
|
#[cfg(not(feature = "no_object"))]
|
||||||
|
if name == type_name::<Map>() {
|
||||||
|
return "map";
|
||||||
|
}
|
||||||
|
|
||||||
|
name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl fmt::Display for Dynamic {
|
impl fmt::Display for Dynamic {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
match &self.0 {
|
match &self.0 {
|
||||||
@ -220,7 +245,6 @@ impl fmt::Display for Dynamic {
|
|||||||
Union::FnPtr(value) => fmt::Display::fmt(value, f),
|
Union::FnPtr(value) => fmt::Display::fmt(value, f),
|
||||||
|
|
||||||
#[cfg(not(feature = "no_std"))]
|
#[cfg(not(feature = "no_std"))]
|
||||||
#[cfg(not(target_arch = "wasm32"))]
|
|
||||||
Union::Variant(value) if value.is::<Instant>() => write!(f, "<timestamp>"),
|
Union::Variant(value) if value.is::<Instant>() => write!(f, "<timestamp>"),
|
||||||
Union::Variant(_) => write!(f, "?"),
|
Union::Variant(_) => write!(f, "?"),
|
||||||
}
|
}
|
||||||
@ -244,7 +268,6 @@ impl fmt::Debug for Dynamic {
|
|||||||
Union::FnPtr(value) => fmt::Display::fmt(value, f),
|
Union::FnPtr(value) => fmt::Display::fmt(value, f),
|
||||||
|
|
||||||
#[cfg(not(feature = "no_std"))]
|
#[cfg(not(feature = "no_std"))]
|
||||||
#[cfg(not(target_arch = "wasm32"))]
|
|
||||||
Union::Variant(value) if value.is::<Instant>() => write!(f, "<timestamp>"),
|
Union::Variant(value) if value.is::<Instant>() => write!(f, "<timestamp>"),
|
||||||
Union::Variant(_) => write!(f, "<dynamic>"),
|
Union::Variant(_) => write!(f, "<dynamic>"),
|
||||||
}
|
}
|
||||||
@ -323,10 +346,8 @@ impl Dynamic {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(not(feature = "no_float"))]
|
#[cfg(not(feature = "no_float"))]
|
||||||
{
|
if let Some(result) = <dyn Any>::downcast_ref::<FLOAT>(&value) {
|
||||||
if let Some(result) = <dyn Any>::downcast_ref::<FLOAT>(&value) {
|
return result.clone().into();
|
||||||
return result.clone().into();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut boxed = Box::new(value);
|
let mut boxed = Box::new(value);
|
||||||
|
38
src/api.rs
38
src/api.rs
@ -1,10 +1,7 @@
|
|||||||
//! Module that defines the extern API of `Engine`.
|
//! Module that defines the extern API of `Engine`.
|
||||||
|
|
||||||
use crate::any::{Dynamic, Variant};
|
use crate::any::{Dynamic, Variant};
|
||||||
use crate::engine::{
|
use crate::engine::{make_getter, make_setter, Engine, Imports, State, FN_IDX_GET, FN_IDX_SET};
|
||||||
get_script_function_by_signature, make_getter, make_setter, Engine, Imports, State, FN_IDX_GET,
|
|
||||||
FN_IDX_SET,
|
|
||||||
};
|
|
||||||
use crate::error::ParseError;
|
use crate::error::ParseError;
|
||||||
use crate::fn_call::FuncArgs;
|
use crate::fn_call::FuncArgs;
|
||||||
use crate::fn_native::{IteratorFn, SendSync};
|
use crate::fn_native::{IteratorFn, SendSync};
|
||||||
@ -19,6 +16,9 @@ use crate::utils::StaticVec;
|
|||||||
#[cfg(not(feature = "no_object"))]
|
#[cfg(not(feature = "no_object"))]
|
||||||
use crate::engine::Map;
|
use crate::engine::Map;
|
||||||
|
|
||||||
|
#[cfg(not(feature = "no_function"))]
|
||||||
|
use crate::engine::get_script_function_by_signature;
|
||||||
|
|
||||||
use crate::stdlib::{
|
use crate::stdlib::{
|
||||||
any::{type_name, TypeId},
|
any::{type_name, TypeId},
|
||||||
boxed::Box,
|
boxed::Box,
|
||||||
@ -118,8 +118,13 @@ impl Engine {
|
|||||||
/// ```
|
/// ```
|
||||||
#[cfg(not(feature = "no_object"))]
|
#[cfg(not(feature = "no_object"))]
|
||||||
pub fn register_type_with_name<T: Variant + Clone>(&mut self, name: &str) {
|
pub fn register_type_with_name<T: Variant + Clone>(&mut self, name: &str) {
|
||||||
|
if self.type_names.is_none() {
|
||||||
|
self.type_names = Some(Default::default());
|
||||||
|
}
|
||||||
// Add the pretty-print type name into the map
|
// Add the pretty-print type name into the map
|
||||||
self.type_names
|
self.type_names
|
||||||
|
.as_mut()
|
||||||
|
.unwrap()
|
||||||
.insert(type_name::<T>().to_string(), name.to_string());
|
.insert(type_name::<T>().to_string(), name.to_string());
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -548,7 +553,7 @@ impl Engine {
|
|||||||
scripts: &[&str],
|
scripts: &[&str],
|
||||||
optimization_level: OptimizationLevel,
|
optimization_level: OptimizationLevel,
|
||||||
) -> Result<AST, ParseError> {
|
) -> Result<AST, ParseError> {
|
||||||
let stream = lex(scripts, self.max_string_size);
|
let stream = lex(scripts, self);
|
||||||
self.parse(&mut stream.peekable(), scope, optimization_level)
|
self.parse(&mut stream.peekable(), scope, optimization_level)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -673,7 +678,7 @@ impl Engine {
|
|||||||
|
|
||||||
// Trims the JSON string and add a '#' in front
|
// Trims the JSON string and add a '#' in front
|
||||||
let scripts = ["#", json.trim()];
|
let scripts = ["#", json.trim()];
|
||||||
let stream = lex(&scripts, self.max_string_size);
|
let stream = lex(&scripts, self);
|
||||||
let ast =
|
let ast =
|
||||||
self.parse_global_expr(&mut stream.peekable(), &scope, OptimizationLevel::None)?;
|
self.parse_global_expr(&mut stream.peekable(), &scope, OptimizationLevel::None)?;
|
||||||
|
|
||||||
@ -754,7 +759,7 @@ impl Engine {
|
|||||||
script: &str,
|
script: &str,
|
||||||
) -> Result<AST, ParseError> {
|
) -> Result<AST, ParseError> {
|
||||||
let scripts = [script];
|
let scripts = [script];
|
||||||
let stream = lex(&scripts, self.max_string_size);
|
let stream = lex(&scripts, self);
|
||||||
{
|
{
|
||||||
let mut peekable = stream.peekable();
|
let mut peekable = stream.peekable();
|
||||||
self.parse_global_expr(&mut peekable, scope, self.optimization_level)
|
self.parse_global_expr(&mut peekable, scope, self.optimization_level)
|
||||||
@ -909,7 +914,7 @@ impl Engine {
|
|||||||
script: &str,
|
script: &str,
|
||||||
) -> Result<T, Box<EvalAltResult>> {
|
) -> Result<T, Box<EvalAltResult>> {
|
||||||
let scripts = [script];
|
let scripts = [script];
|
||||||
let stream = lex(&scripts, self.max_string_size);
|
let stream = lex(&scripts, self);
|
||||||
|
|
||||||
// No need to optimize a lone expression
|
// No need to optimize a lone expression
|
||||||
let ast = self.parse_global_expr(&mut stream.peekable(), scope, OptimizationLevel::None)?;
|
let ast = self.parse_global_expr(&mut stream.peekable(), scope, OptimizationLevel::None)?;
|
||||||
@ -976,11 +981,12 @@ impl Engine {
|
|||||||
let mut mods = Imports::new();
|
let mut mods = Imports::new();
|
||||||
let (result, _) = self.eval_ast_with_scope_raw(scope, &mut mods, ast)?;
|
let (result, _) = self.eval_ast_with_scope_raw(scope, &mut mods, ast)?;
|
||||||
|
|
||||||
let return_type = self.map_type_name(result.type_name());
|
let typ = self.map_type_name(result.type_name());
|
||||||
|
|
||||||
return result.try_cast::<T>().ok_or_else(|| {
|
return result.try_cast::<T>().ok_or_else(|| {
|
||||||
Box::new(EvalAltResult::ErrorMismatchOutputType(
|
Box::new(EvalAltResult::ErrorMismatchOutputType(
|
||||||
return_type.into(),
|
self.map_type_name(type_name::<T>()).into(),
|
||||||
|
typ.into(),
|
||||||
Position::none(),
|
Position::none(),
|
||||||
))
|
))
|
||||||
});
|
});
|
||||||
@ -1041,7 +1047,7 @@ impl Engine {
|
|||||||
script: &str,
|
script: &str,
|
||||||
) -> Result<(), Box<EvalAltResult>> {
|
) -> Result<(), Box<EvalAltResult>> {
|
||||||
let scripts = [script];
|
let scripts = [script];
|
||||||
let stream = lex(&scripts, self.max_string_size);
|
let stream = lex(&scripts, self);
|
||||||
let ast = self.parse(&mut stream.peekable(), scope, self.optimization_level)?;
|
let ast = self.parse(&mut stream.peekable(), scope, self.optimization_level)?;
|
||||||
self.consume_ast_with_scope(scope, &ast)
|
self.consume_ast_with_scope(scope, &ast)
|
||||||
}
|
}
|
||||||
@ -1123,11 +1129,12 @@ impl Engine {
|
|||||||
let mut arg_values = args.into_vec();
|
let mut arg_values = args.into_vec();
|
||||||
let result = self.call_fn_dynamic_raw(scope, ast, name, arg_values.as_mut())?;
|
let result = self.call_fn_dynamic_raw(scope, ast, name, arg_values.as_mut())?;
|
||||||
|
|
||||||
let return_type = self.map_type_name(result.type_name());
|
let typ = self.map_type_name(result.type_name());
|
||||||
|
|
||||||
return result.try_cast().ok_or_else(|| {
|
return result.try_cast().ok_or_else(|| {
|
||||||
Box::new(EvalAltResult::ErrorMismatchOutputType(
|
Box::new(EvalAltResult::ErrorMismatchOutputType(
|
||||||
return_type.into(),
|
self.map_type_name(type_name::<T>()).into(),
|
||||||
|
typ.into(),
|
||||||
Position::none(),
|
Position::none(),
|
||||||
))
|
))
|
||||||
});
|
});
|
||||||
@ -1187,6 +1194,7 @@ impl Engine {
|
|||||||
/// This is to avoid unnecessarily cloning the arguments.
|
/// This is to avoid unnecessarily cloning the arguments.
|
||||||
/// Do not use the arguments after this call. If they are needed afterwards,
|
/// Do not use the arguments after this call. If they are needed afterwards,
|
||||||
/// clone them _before_ calling this function.
|
/// clone them _before_ calling this function.
|
||||||
|
#[cfg(not(feature = "no_function"))]
|
||||||
pub(crate) fn call_fn_dynamic_raw(
|
pub(crate) fn call_fn_dynamic_raw(
|
||||||
&self,
|
&self,
|
||||||
scope: &mut Scope,
|
scope: &mut Scope,
|
||||||
@ -1238,6 +1246,7 @@ impl Engine {
|
|||||||
mut ast: AST,
|
mut ast: AST,
|
||||||
optimization_level: OptimizationLevel,
|
optimization_level: OptimizationLevel,
|
||||||
) -> AST {
|
) -> AST {
|
||||||
|
#[cfg(not(feature = "no_function"))]
|
||||||
let lib = ast
|
let lib = ast
|
||||||
.lib()
|
.lib()
|
||||||
.iter_fn()
|
.iter_fn()
|
||||||
@ -1245,6 +1254,9 @@ impl Engine {
|
|||||||
.map(|(_, _, _, f)| f.get_fn_def().clone())
|
.map(|(_, _, _, f)| f.get_fn_def().clone())
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
|
#[cfg(feature = "no_function")]
|
||||||
|
let lib = Default::default();
|
||||||
|
|
||||||
let stmt = mem::take(ast.statements_mut());
|
let stmt = mem::take(ast.statements_mut());
|
||||||
optimize_into_ast(self, scope, stmt, lib, optimization_level)
|
optimize_into_ast(self, scope, stmt, lib, optimization_level)
|
||||||
}
|
}
|
||||||
|
438
src/engine.rs
438
src/engine.rs
@ -1,12 +1,12 @@
|
|||||||
//! Main module defining the script evaluation `Engine`.
|
//! Main module defining the script evaluation `Engine`.
|
||||||
|
|
||||||
use crate::any::{Dynamic, Union, Variant};
|
use crate::any::{map_std_type_name, Dynamic, Union, Variant};
|
||||||
use crate::calc_fn_hash;
|
use crate::calc_fn_hash;
|
||||||
use crate::error::ParseErrorType;
|
use crate::error::ParseErrorType;
|
||||||
use crate::fn_native::{CallableFunction, Callback, FnCallArgs, FnPtr};
|
use crate::fn_native::{CallableFunction, Callback, FnCallArgs, FnPtr};
|
||||||
use crate::module::{resolvers, Module, ModuleRef, ModuleResolver};
|
use crate::module::{resolvers, Module, ModuleRef, ModuleResolver};
|
||||||
use crate::optimize::OptimizationLevel;
|
use crate::optimize::OptimizationLevel;
|
||||||
use crate::packages::{Package, PackageLibrary, PackagesCollection, StandardPackage};
|
use crate::packages::{Package, PackagesCollection, StandardPackage};
|
||||||
use crate::parser::{Expr, FnAccess, ImmutableString, ReturnType, ScriptFnDef, Stmt, AST, INT};
|
use crate::parser::{Expr, FnAccess, ImmutableString, ReturnType, ScriptFnDef, Stmt, AST, INT};
|
||||||
use crate::r#unsafe::unsafe_cast_var_name_to_lifetime;
|
use crate::r#unsafe::unsafe_cast_var_name_to_lifetime;
|
||||||
use crate::result::EvalAltResult;
|
use crate::result::EvalAltResult;
|
||||||
@ -18,10 +18,10 @@ use crate::utils::StaticVec;
|
|||||||
use crate::parser::FLOAT;
|
use crate::parser::FLOAT;
|
||||||
|
|
||||||
use crate::stdlib::{
|
use crate::stdlib::{
|
||||||
any::TypeId,
|
any::{type_name, TypeId},
|
||||||
borrow::Cow,
|
borrow::Cow,
|
||||||
boxed::Box,
|
boxed::Box,
|
||||||
collections::HashMap,
|
collections::{HashMap, HashSet},
|
||||||
format,
|
format,
|
||||||
iter::{empty, once},
|
iter::{empty, once},
|
||||||
mem,
|
mem,
|
||||||
@ -35,7 +35,7 @@ use crate::stdlib::{
|
|||||||
#[cfg(not(feature = "no_index"))]
|
#[cfg(not(feature = "no_index"))]
|
||||||
pub type Array = Vec<Dynamic>;
|
pub type Array = Vec<Dynamic>;
|
||||||
|
|
||||||
/// Hash map of `Dynamic` values with `String` keys.
|
/// Hash map of `Dynamic` values with `ImmutableString` keys.
|
||||||
///
|
///
|
||||||
/// Not available under the `no_object` feature.
|
/// Not available under the `no_object` feature.
|
||||||
#[cfg(not(feature = "no_object"))]
|
#[cfg(not(feature = "no_object"))]
|
||||||
@ -216,6 +216,7 @@ impl State {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Get a script-defined function definition from a module.
|
/// Get a script-defined function definition from a module.
|
||||||
|
#[cfg(not(feature = "no_function"))]
|
||||||
pub fn get_script_function_by_signature<'a>(
|
pub fn get_script_function_by_signature<'a>(
|
||||||
module: &'a Module,
|
module: &'a Module,
|
||||||
name: &str,
|
name: &str,
|
||||||
@ -265,7 +266,12 @@ pub struct Engine {
|
|||||||
pub(crate) module_resolver: Option<Box<dyn ModuleResolver>>,
|
pub(crate) module_resolver: Option<Box<dyn ModuleResolver>>,
|
||||||
|
|
||||||
/// A hashmap mapping type names to pretty-print names.
|
/// A hashmap mapping type names to pretty-print names.
|
||||||
pub(crate) type_names: HashMap<String, String>,
|
pub(crate) type_names: Option<HashMap<String, String>>,
|
||||||
|
|
||||||
|
/// A hashset containing symbols to disable.
|
||||||
|
pub(crate) disabled_symbols: Option<HashSet<String>>,
|
||||||
|
/// A hashset containing custom keywords and precedence to recognize.
|
||||||
|
pub(crate) custom_keywords: Option<HashMap<String, u8>>,
|
||||||
|
|
||||||
/// Callback closure for implementing the `print` command.
|
/// Callback closure for implementing the `print` command.
|
||||||
pub(crate) print: Callback<str, ()>,
|
pub(crate) print: Callback<str, ()>,
|
||||||
@ -312,7 +318,9 @@ impl Default for Engine {
|
|||||||
#[cfg(any(feature = "no_module", feature = "no_std", target_arch = "wasm32",))]
|
#[cfg(any(feature = "no_module", feature = "no_std", target_arch = "wasm32",))]
|
||||||
module_resolver: None,
|
module_resolver: None,
|
||||||
|
|
||||||
type_names: Default::default(),
|
type_names: None,
|
||||||
|
disabled_symbols: None,
|
||||||
|
custom_keywords: None,
|
||||||
|
|
||||||
// default print/debug implementations
|
// default print/debug implementations
|
||||||
print: Box::new(default_print),
|
print: Box::new(default_print),
|
||||||
@ -352,17 +360,14 @@ pub fn make_getter(id: &str) -> String {
|
|||||||
/// Extract the property name from a getter function name.
|
/// Extract the property name from a getter function name.
|
||||||
fn extract_prop_from_getter(fn_name: &str) -> Option<&str> {
|
fn extract_prop_from_getter(fn_name: &str) -> Option<&str> {
|
||||||
#[cfg(not(feature = "no_object"))]
|
#[cfg(not(feature = "no_object"))]
|
||||||
{
|
if fn_name.starts_with(FN_GET) {
|
||||||
if fn_name.starts_with(FN_GET) {
|
Some(&fn_name[FN_GET.len()..])
|
||||||
Some(&fn_name[FN_GET.len()..])
|
} else {
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#[cfg(feature = "no_object")]
|
|
||||||
{
|
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "no_object")]
|
||||||
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Make setter function
|
/// Make setter function
|
||||||
@ -373,17 +378,14 @@ pub fn make_setter(id: &str) -> String {
|
|||||||
/// Extract the property name from a setter function name.
|
/// Extract the property name from a setter function name.
|
||||||
fn extract_prop_from_setter(fn_name: &str) -> Option<&str> {
|
fn extract_prop_from_setter(fn_name: &str) -> Option<&str> {
|
||||||
#[cfg(not(feature = "no_object"))]
|
#[cfg(not(feature = "no_object"))]
|
||||||
{
|
if fn_name.starts_with(FN_SET) {
|
||||||
if fn_name.starts_with(FN_SET) {
|
Some(&fn_name[FN_SET.len()..])
|
||||||
Some(&fn_name[FN_SET.len()..])
|
} else {
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#[cfg(feature = "no_object")]
|
|
||||||
{
|
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "no_object")]
|
||||||
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Print/debug to stdout
|
/// Print/debug to stdout
|
||||||
@ -497,7 +499,10 @@ impl Engine {
|
|||||||
global_module: Default::default(),
|
global_module: Default::default(),
|
||||||
module_resolver: None,
|
module_resolver: None,
|
||||||
|
|
||||||
type_names: Default::default(),
|
type_names: None,
|
||||||
|
disabled_symbols: None,
|
||||||
|
custom_keywords: None,
|
||||||
|
|
||||||
print: Box::new(|_| {}),
|
print: Box::new(|_| {}),
|
||||||
debug: Box::new(|_| {}),
|
debug: Box::new(|_| {}),
|
||||||
progress: None,
|
progress: None,
|
||||||
@ -519,158 +524,6 @@ impl Engine {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Load a new package into the `Engine`.
|
|
||||||
///
|
|
||||||
/// When searching for functions, packages loaded later are preferred.
|
|
||||||
/// In other words, loaded packages are searched in reverse order.
|
|
||||||
pub fn load_package(&mut self, package: PackageLibrary) {
|
|
||||||
// Push the package to the top - packages are searched in reverse order
|
|
||||||
self.packages.push(package);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Load a new package into the `Engine`.
|
|
||||||
///
|
|
||||||
/// When searching for functions, packages loaded later are preferred.
|
|
||||||
/// In other words, loaded packages are searched in reverse order.
|
|
||||||
pub fn load_packages(&mut self, package: PackageLibrary) {
|
|
||||||
// Push the package to the top - packages are searched in reverse order
|
|
||||||
self.packages.push(package);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Control whether and how the `Engine` will optimize an AST after compilation.
|
|
||||||
///
|
|
||||||
/// Not available under the `no_optimize` feature.
|
|
||||||
#[cfg(not(feature = "no_optimize"))]
|
|
||||||
pub fn set_optimization_level(&mut self, optimization_level: OptimizationLevel) {
|
|
||||||
self.optimization_level = optimization_level
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The current optimization level.
|
|
||||||
/// It controls whether and how the `Engine` will optimize an AST after compilation.
|
|
||||||
///
|
|
||||||
/// Not available under the `no_optimize` feature.
|
|
||||||
#[cfg(not(feature = "no_optimize"))]
|
|
||||||
pub fn optimization_level(&self) -> OptimizationLevel {
|
|
||||||
self.optimization_level
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Set the maximum levels of function calls allowed for a script in order to avoid
|
|
||||||
/// infinite recursion and stack overflows.
|
|
||||||
#[cfg(not(feature = "unchecked"))]
|
|
||||||
pub fn set_max_call_levels(&mut self, levels: usize) {
|
|
||||||
self.max_call_stack_depth = levels
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The maximum levels of function calls allowed for a script.
|
|
||||||
#[cfg(not(feature = "unchecked"))]
|
|
||||||
pub fn max_call_levels(&self) -> usize {
|
|
||||||
self.max_call_stack_depth
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Set the maximum number of operations allowed for a script to run to avoid
|
|
||||||
/// consuming too much resources (0 for unlimited).
|
|
||||||
#[cfg(not(feature = "unchecked"))]
|
|
||||||
pub fn set_max_operations(&mut self, operations: u64) {
|
|
||||||
self.max_operations = if operations == u64::MAX {
|
|
||||||
0
|
|
||||||
} else {
|
|
||||||
operations
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The maximum number of operations allowed for a script to run (0 for unlimited).
|
|
||||||
#[cfg(not(feature = "unchecked"))]
|
|
||||||
pub fn max_operations(&self) -> u64 {
|
|
||||||
self.max_operations
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Set the maximum number of imported modules allowed for a script.
|
|
||||||
#[cfg(not(feature = "unchecked"))]
|
|
||||||
pub fn set_max_modules(&mut self, modules: usize) {
|
|
||||||
self.max_modules = modules;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The maximum number of imported modules allowed for a script.
|
|
||||||
#[cfg(not(feature = "unchecked"))]
|
|
||||||
pub fn max_modules(&self) -> usize {
|
|
||||||
self.max_modules
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Set the depth limits for expressions (0 for unlimited).
|
|
||||||
#[cfg(not(feature = "unchecked"))]
|
|
||||||
pub fn set_max_expr_depths(&mut self, max_expr_depth: usize, max_function_expr_depth: usize) {
|
|
||||||
self.max_expr_depth = if max_expr_depth == usize::MAX {
|
|
||||||
0
|
|
||||||
} else {
|
|
||||||
max_expr_depth
|
|
||||||
};
|
|
||||||
self.max_function_expr_depth = if max_function_expr_depth == usize::MAX {
|
|
||||||
0
|
|
||||||
} else {
|
|
||||||
max_function_expr_depth
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The depth limit for expressions (0 for unlimited).
|
|
||||||
#[cfg(not(feature = "unchecked"))]
|
|
||||||
pub fn max_expr_depth(&self) -> usize {
|
|
||||||
self.max_expr_depth
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The depth limit for expressions in functions (0 for unlimited).
|
|
||||||
#[cfg(not(feature = "unchecked"))]
|
|
||||||
pub fn max_function_expr_depth(&self) -> usize {
|
|
||||||
self.max_function_expr_depth
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Set the maximum length of strings (0 for unlimited).
|
|
||||||
#[cfg(not(feature = "unchecked"))]
|
|
||||||
pub fn set_max_string_size(&mut self, max_size: usize) {
|
|
||||||
self.max_string_size = if max_size == usize::MAX { 0 } else { max_size };
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The maximum length of strings (0 for unlimited).
|
|
||||||
#[cfg(not(feature = "unchecked"))]
|
|
||||||
pub fn max_string_size(&self) -> usize {
|
|
||||||
self.max_string_size
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Set the maximum length of arrays (0 for unlimited).
|
|
||||||
#[cfg(not(feature = "unchecked"))]
|
|
||||||
#[cfg(not(feature = "no_index"))]
|
|
||||||
pub fn set_max_array_size(&mut self, max_size: usize) {
|
|
||||||
self.max_array_size = if max_size == usize::MAX { 0 } else { max_size };
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The maximum length of arrays (0 for unlimited).
|
|
||||||
#[cfg(not(feature = "unchecked"))]
|
|
||||||
#[cfg(not(feature = "no_index"))]
|
|
||||||
pub fn max_array_size(&self) -> usize {
|
|
||||||
self.max_array_size
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Set the maximum length of object maps (0 for unlimited).
|
|
||||||
#[cfg(not(feature = "unchecked"))]
|
|
||||||
#[cfg(not(feature = "no_object"))]
|
|
||||||
pub fn set_max_map_size(&mut self, max_size: usize) {
|
|
||||||
self.max_map_size = if max_size == usize::MAX { 0 } else { max_size };
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The maximum length of object maps (0 for unlimited).
|
|
||||||
#[cfg(not(feature = "unchecked"))]
|
|
||||||
#[cfg(not(feature = "no_object"))]
|
|
||||||
pub fn max_map_size(&self) -> usize {
|
|
||||||
self.max_map_size
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Set the module resolution service used by the `Engine`.
|
|
||||||
///
|
|
||||||
/// Not available under the `no_module` feature.
|
|
||||||
#[cfg(not(feature = "no_module"))]
|
|
||||||
pub fn set_module_resolver(&mut self, resolver: Option<impl ModuleResolver + 'static>) {
|
|
||||||
self.module_resolver = resolver.map(|f| Box::new(f) as Box<dyn ModuleResolver>);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Universal method for calling functions either registered with the `Engine` or written in Rhai.
|
/// Universal method for calling functions either registered with the `Engine` or written in Rhai.
|
||||||
/// Position in `EvalAltResult` is None and must be set afterwards.
|
/// Position in `EvalAltResult` is None and must be set afterwards.
|
||||||
///
|
///
|
||||||
@ -700,12 +553,10 @@ impl Engine {
|
|||||||
// Check for stack overflow
|
// Check for stack overflow
|
||||||
#[cfg(not(feature = "no_function"))]
|
#[cfg(not(feature = "no_function"))]
|
||||||
#[cfg(not(feature = "unchecked"))]
|
#[cfg(not(feature = "unchecked"))]
|
||||||
{
|
if level > self.max_call_stack_depth {
|
||||||
if level > self.max_call_stack_depth {
|
return Err(Box::new(
|
||||||
return Err(Box::new(
|
EvalAltResult::ErrorStackOverflow(Position::none()),
|
||||||
EvalAltResult::ErrorStackOverflow(Position::none()),
|
));
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut this_copy: Dynamic = Default::default();
|
let mut this_copy: Dynamic = Default::default();
|
||||||
@ -767,22 +618,23 @@ impl Engine {
|
|||||||
.or_else(|| self.packages.get_fn(hash_fn));
|
.or_else(|| self.packages.get_fn(hash_fn));
|
||||||
|
|
||||||
if let Some(func) = func {
|
if let Some(func) = func {
|
||||||
// Calling pure function but the first argument is a reference?
|
#[cfg(not(feature = "no_function"))]
|
||||||
normalize_first_arg(
|
let need_normalize = is_ref && (func.is_pure() || (func.is_script() && !is_method));
|
||||||
is_ref && (func.is_pure() || (func.is_script() && !is_method)),
|
#[cfg(feature = "no_function")]
|
||||||
&mut this_copy,
|
let need_normalize = is_ref && func.is_pure();
|
||||||
&mut old_this_ptr,
|
|
||||||
args,
|
|
||||||
);
|
|
||||||
|
|
||||||
|
// Calling pure function but the first argument is a reference?
|
||||||
|
normalize_first_arg(need_normalize, &mut this_copy, &mut old_this_ptr, args);
|
||||||
|
|
||||||
|
#[cfg(not(feature = "no_function"))]
|
||||||
if func.is_script() {
|
if func.is_script() {
|
||||||
// Run scripted function
|
// Run scripted function
|
||||||
let fn_def = func.get_fn_def();
|
let fn_def = func.get_fn_def();
|
||||||
|
|
||||||
// Method call of script function - map first argument to `this`
|
// Method call of script function - map first argument to `this`
|
||||||
if is_method {
|
return if is_method {
|
||||||
let (first, rest) = args.split_at_mut(1);
|
let (first, rest) = args.split_at_mut(1);
|
||||||
return Ok((
|
Ok((
|
||||||
self.call_script_fn(
|
self.call_script_fn(
|
||||||
scope,
|
scope,
|
||||||
mods,
|
mods,
|
||||||
@ -795,7 +647,7 @@ impl Engine {
|
|||||||
level,
|
level,
|
||||||
)?,
|
)?,
|
||||||
false,
|
false,
|
||||||
));
|
))
|
||||||
} else {
|
} else {
|
||||||
let result = self.call_script_fn(
|
let result = self.call_script_fn(
|
||||||
scope, mods, state, lib, &mut None, fn_name, fn_def, args, level,
|
scope, mods, state, lib, &mut None, fn_name, fn_def, args, level,
|
||||||
@ -804,40 +656,42 @@ impl Engine {
|
|||||||
// Restore the original reference
|
// Restore the original reference
|
||||||
restore_first_arg(old_this_ptr, args);
|
restore_first_arg(old_this_ptr, args);
|
||||||
|
|
||||||
return Ok((result, false));
|
Ok((result, false))
|
||||||
};
|
};
|
||||||
} else {
|
|
||||||
// Run external function
|
|
||||||
let result = func.get_native_fn()(self, args)?;
|
|
||||||
|
|
||||||
// Restore the original reference
|
|
||||||
restore_first_arg(old_this_ptr, args);
|
|
||||||
|
|
||||||
// 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| {
|
|
||||||
Box::new(EvalAltResult::ErrorMismatchOutputType(
|
|
||||||
type_name.into(),
|
|
||||||
Position::none(),
|
|
||||||
))
|
|
||||||
})?)
|
|
||||||
.into(),
|
|
||||||
false,
|
|
||||||
),
|
|
||||||
KEYWORD_DEBUG => (
|
|
||||||
(self.debug)(result.as_str().map_err(|type_name| {
|
|
||||||
Box::new(EvalAltResult::ErrorMismatchOutputType(
|
|
||||||
type_name.into(),
|
|
||||||
Position::none(),
|
|
||||||
))
|
|
||||||
})?)
|
|
||||||
.into(),
|
|
||||||
false,
|
|
||||||
),
|
|
||||||
_ => (result, func.is_method()),
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Run external function
|
||||||
|
let result = func.get_native_fn()(self, args)?;
|
||||||
|
|
||||||
|
// Restore the original reference
|
||||||
|
restore_first_arg(old_this_ptr, args);
|
||||||
|
|
||||||
|
// 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(|typ| {
|
||||||
|
Box::new(EvalAltResult::ErrorMismatchOutputType(
|
||||||
|
self.map_type_name(type_name::<ImmutableString>()).into(),
|
||||||
|
typ.into(),
|
||||||
|
Position::none(),
|
||||||
|
))
|
||||||
|
})?)
|
||||||
|
.into(),
|
||||||
|
false,
|
||||||
|
),
|
||||||
|
KEYWORD_DEBUG => (
|
||||||
|
(self.debug)(result.as_str().map_err(|typ| {
|
||||||
|
Box::new(EvalAltResult::ErrorMismatchOutputType(
|
||||||
|
self.map_type_name(type_name::<ImmutableString>()).into(),
|
||||||
|
typ.into(),
|
||||||
|
Position::none(),
|
||||||
|
))
|
||||||
|
})?)
|
||||||
|
.into(),
|
||||||
|
false,
|
||||||
|
),
|
||||||
|
_ => (result, func.is_method()),
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// See if it is built in.
|
// See if it is built in.
|
||||||
@ -1064,8 +918,12 @@ impl Engine {
|
|||||||
lib: &Module,
|
lib: &Module,
|
||||||
script: &Dynamic,
|
script: &Dynamic,
|
||||||
) -> Result<Dynamic, Box<EvalAltResult>> {
|
) -> Result<Dynamic, Box<EvalAltResult>> {
|
||||||
let script = script.as_str().map_err(|type_name| {
|
let script = script.as_str().map_err(|typ| {
|
||||||
EvalAltResult::ErrorMismatchOutputType(type_name.into(), Position::none())
|
EvalAltResult::ErrorMismatchOutputType(
|
||||||
|
self.map_type_name(type_name::<ImmutableString>()).into(),
|
||||||
|
typ.into(),
|
||||||
|
Position::none(),
|
||||||
|
)
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
// Compile the script text
|
// Compile the script text
|
||||||
@ -1873,9 +1731,10 @@ impl Engine {
|
|||||||
self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)?;
|
self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)?;
|
||||||
return arg_value
|
return arg_value
|
||||||
.take_immutable_string()
|
.take_immutable_string()
|
||||||
.map_err(|type_name| {
|
.map_err(|typ| {
|
||||||
Box::new(EvalAltResult::ErrorMismatchOutputType(
|
Box::new(EvalAltResult::ErrorMismatchOutputType(
|
||||||
type_name.into(),
|
self.map_type_name(type_name::<ImmutableString>()).into(),
|
||||||
|
typ.into(),
|
||||||
expr.position(),
|
expr.position(),
|
||||||
))
|
))
|
||||||
})
|
})
|
||||||
@ -2009,6 +1868,7 @@ impl Engine {
|
|||||||
};
|
};
|
||||||
|
|
||||||
match func {
|
match func {
|
||||||
|
#[cfg(not(feature = "no_function"))]
|
||||||
Ok(f) if f.is_script() => {
|
Ok(f) if f.is_script() => {
|
||||||
let args = args.as_mut();
|
let args = args.as_mut();
|
||||||
let fn_def = f.get_fn_def();
|
let fn_def = f.get_fn_def();
|
||||||
@ -2335,21 +2195,19 @@ impl Engine {
|
|||||||
.try_cast::<ImmutableString>()
|
.try_cast::<ImmutableString>()
|
||||||
{
|
{
|
||||||
#[cfg(not(feature = "no_module"))]
|
#[cfg(not(feature = "no_module"))]
|
||||||
{
|
if let Some(resolver) = &self.module_resolver {
|
||||||
if let Some(resolver) = &self.module_resolver {
|
let mut module = resolver.resolve(self, &path, expr.position())?;
|
||||||
let mut module = resolver.resolve(self, &path, expr.position())?;
|
module.index_all_sub_modules();
|
||||||
module.index_all_sub_modules();
|
mods.push((name.clone().into(), module));
|
||||||
mods.push((name.clone().into(), module));
|
|
||||||
|
|
||||||
state.modules += 1;
|
state.modules += 1;
|
||||||
|
|
||||||
Ok(Default::default())
|
Ok(Default::default())
|
||||||
} else {
|
} else {
|
||||||
Err(Box::new(EvalAltResult::ErrorModuleNotFound(
|
Err(Box::new(EvalAltResult::ErrorModuleNotFound(
|
||||||
path.to_string(),
|
path.to_string(),
|
||||||
expr.position(),
|
expr.position(),
|
||||||
)))
|
)))
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "no_module")]
|
#[cfg(feature = "no_module")]
|
||||||
@ -2408,7 +2266,13 @@ impl Engine {
|
|||||||
let mut maps = 0;
|
let mut maps = 0;
|
||||||
|
|
||||||
arr.iter().for_each(|value| match value {
|
arr.iter().for_each(|value| match value {
|
||||||
Dynamic(Union::Array(_)) | Dynamic(Union::Map(_)) => {
|
Dynamic(Union::Array(_)) => {
|
||||||
|
let (a, m, _) = calc_size(value);
|
||||||
|
arrays += a;
|
||||||
|
maps += m;
|
||||||
|
}
|
||||||
|
#[cfg(not(feature = "no_object"))]
|
||||||
|
Dynamic(Union::Map(_)) => {
|
||||||
let (a, m, _) = calc_size(value);
|
let (a, m, _) = calc_size(value);
|
||||||
arrays += a;
|
arrays += a;
|
||||||
maps += m;
|
maps += m;
|
||||||
@ -2424,7 +2288,13 @@ impl Engine {
|
|||||||
let mut maps = 0;
|
let mut maps = 0;
|
||||||
|
|
||||||
map.values().for_each(|value| match value {
|
map.values().for_each(|value| match value {
|
||||||
Dynamic(Union::Array(_)) | Dynamic(Union::Map(_)) => {
|
#[cfg(not(feature = "no_index"))]
|
||||||
|
Dynamic(Union::Array(_)) => {
|
||||||
|
let (a, m, _) = calc_size(value);
|
||||||
|
arrays += a;
|
||||||
|
maps += m;
|
||||||
|
}
|
||||||
|
Dynamic(Union::Map(_)) => {
|
||||||
let (a, m, _) = calc_size(value);
|
let (a, m, _) = calc_size(value);
|
||||||
arrays += a;
|
arrays += a;
|
||||||
maps += m;
|
maps += m;
|
||||||
@ -2488,13 +2358,11 @@ impl Engine {
|
|||||||
state.operations += 1;
|
state.operations += 1;
|
||||||
|
|
||||||
#[cfg(not(feature = "unchecked"))]
|
#[cfg(not(feature = "unchecked"))]
|
||||||
{
|
// Guard against too many operations
|
||||||
// Guard against too many operations
|
if self.max_operations > 0 && state.operations > self.max_operations {
|
||||||
if self.max_operations > 0 && state.operations > self.max_operations {
|
return Err(Box::new(EvalAltResult::ErrorTooManyOperations(
|
||||||
return Err(Box::new(EvalAltResult::ErrorTooManyOperations(
|
Position::none(),
|
||||||
Position::none(),
|
)));
|
||||||
)));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Report progress - only in steps
|
// Report progress - only in steps
|
||||||
@ -2511,9 +2379,9 @@ impl Engine {
|
|||||||
/// Map a type_name into a pretty-print name
|
/// Map a type_name into a pretty-print name
|
||||||
pub(crate) fn map_type_name<'a>(&'a self, name: &'a str) -> &'a str {
|
pub(crate) fn map_type_name<'a>(&'a self, name: &'a str) -> &'a str {
|
||||||
self.type_names
|
self.type_names
|
||||||
.get(name)
|
.as_ref()
|
||||||
.map(String::as_str)
|
.and_then(|t| t.get(name).map(String::as_str))
|
||||||
.unwrap_or(name)
|
.unwrap_or(map_std_type_name(name))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2620,26 +2488,24 @@ fn run_builtin_binary_op(
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(not(feature = "no_float"))]
|
#[cfg(not(feature = "no_float"))]
|
||||||
{
|
if args_type == TypeId::of::<FLOAT>() {
|
||||||
if args_type == TypeId::of::<FLOAT>() {
|
let x = *x.downcast_ref::<FLOAT>().unwrap();
|
||||||
let x = *x.downcast_ref::<FLOAT>().unwrap();
|
let y = *y.downcast_ref::<FLOAT>().unwrap();
|
||||||
let y = *y.downcast_ref::<FLOAT>().unwrap();
|
|
||||||
|
|
||||||
match op {
|
match op {
|
||||||
"+" => return Ok(Some((x + y).into())),
|
"+" => return Ok(Some((x + y).into())),
|
||||||
"-" => return Ok(Some((x - y).into())),
|
"-" => return Ok(Some((x - y).into())),
|
||||||
"*" => return Ok(Some((x * y).into())),
|
"*" => return Ok(Some((x * y).into())),
|
||||||
"/" => return Ok(Some((x / y).into())),
|
"/" => return Ok(Some((x / y).into())),
|
||||||
"%" => return Ok(Some((x % y).into())),
|
"%" => return Ok(Some((x % y).into())),
|
||||||
"~" => return pow_f_f(x, y).map(Into::into).map(Some),
|
"~" => return pow_f_f(x, y).map(Into::into).map(Some),
|
||||||
"==" => return Ok(Some((x == y).into())),
|
"==" => return Ok(Some((x == y).into())),
|
||||||
"!=" => return Ok(Some((x != y).into())),
|
"!=" => return Ok(Some((x != y).into())),
|
||||||
">" => return Ok(Some((x > y).into())),
|
">" => return Ok(Some((x > y).into())),
|
||||||
">=" => return Ok(Some((x >= y).into())),
|
">=" => return Ok(Some((x >= y).into())),
|
||||||
"<" => return Ok(Some((x < y).into())),
|
"<" => return Ok(Some((x < y).into())),
|
||||||
"<=" => return Ok(Some((x <= y).into())),
|
"<=" => return Ok(Some((x <= y).into())),
|
||||||
_ => (),
|
_ => (),
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2716,20 +2582,18 @@ fn run_builtin_op_assignment(
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(not(feature = "no_float"))]
|
#[cfg(not(feature = "no_float"))]
|
||||||
{
|
if args_type == TypeId::of::<FLOAT>() {
|
||||||
if args_type == TypeId::of::<FLOAT>() {
|
let x = x.downcast_mut::<FLOAT>().unwrap();
|
||||||
let x = x.downcast_mut::<FLOAT>().unwrap();
|
let y = *y.downcast_ref::<FLOAT>().unwrap();
|
||||||
let y = *y.downcast_ref::<FLOAT>().unwrap();
|
|
||||||
|
|
||||||
match op {
|
match op {
|
||||||
"+=" => return Ok(Some(*x += y)),
|
"+=" => return Ok(Some(*x += y)),
|
||||||
"-=" => return Ok(Some(*x -= y)),
|
"-=" => return Ok(Some(*x -= y)),
|
||||||
"*=" => return Ok(Some(*x *= y)),
|
"*=" => return Ok(Some(*x *= y)),
|
||||||
"/=" => return Ok(Some(*x /= y)),
|
"/=" => return Ok(Some(*x /= y)),
|
||||||
"%=" => return Ok(Some(*x %= y)),
|
"%=" => return Ok(Some(*x %= y)),
|
||||||
"~=" => return Ok(Some(*x = pow_f_f(*x, y)?)),
|
"~=" => return Ok(Some(*x = pow_f_f(*x, y)?)),
|
||||||
_ => (),
|
_ => (),
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
21
src/error.rs
21
src/error.rs
@ -5,7 +5,6 @@ use crate::token::Position;
|
|||||||
|
|
||||||
use crate::stdlib::{
|
use crate::stdlib::{
|
||||||
boxed::Box,
|
boxed::Box,
|
||||||
char,
|
|
||||||
error::Error,
|
error::Error,
|
||||||
fmt,
|
fmt,
|
||||||
string::{String, ToString},
|
string::{String, ToString},
|
||||||
@ -15,8 +14,8 @@ use crate::stdlib::{
|
|||||||
#[derive(Debug, Eq, PartialEq, Clone, Hash)]
|
#[derive(Debug, Eq, PartialEq, Clone, Hash)]
|
||||||
#[non_exhaustive]
|
#[non_exhaustive]
|
||||||
pub enum LexError {
|
pub enum LexError {
|
||||||
/// An unexpected character is encountered when tokenizing the script text.
|
/// An unexpected symbol is encountered when tokenizing the script text.
|
||||||
UnexpectedChar(char),
|
UnexpectedInput(String),
|
||||||
/// A string literal is not terminated before a new-line or EOF.
|
/// A string literal is not terminated before a new-line or EOF.
|
||||||
UnterminatedString,
|
UnterminatedString,
|
||||||
/// An identifier is in an invalid format.
|
/// An identifier is in an invalid format.
|
||||||
@ -38,7 +37,7 @@ impl Error for LexError {}
|
|||||||
impl fmt::Display for LexError {
|
impl fmt::Display for LexError {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
match self {
|
match self {
|
||||||
Self::UnexpectedChar(c) => write!(f, "Unexpected '{}'", c),
|
Self::UnexpectedInput(s) => write!(f, "Unexpected '{}'", s),
|
||||||
Self::MalformedEscapeSequence(s) => write!(f, "Invalid escape sequence: '{}'", s),
|
Self::MalformedEscapeSequence(s) => write!(f, "Invalid escape sequence: '{}'", s),
|
||||||
Self::MalformedNumber(s) => write!(f, "Invalid number: '{}'", s),
|
Self::MalformedNumber(s) => write!(f, "Invalid number: '{}'", s),
|
||||||
Self::MalformedChar(s) => write!(f, "Invalid character: '{}'", s),
|
Self::MalformedChar(s) => write!(f, "Invalid character: '{}'", s),
|
||||||
@ -49,7 +48,7 @@ impl fmt::Display for LexError {
|
|||||||
"Length of string literal exceeds the maximum limit ({})",
|
"Length of string literal exceeds the maximum limit ({})",
|
||||||
max
|
max
|
||||||
),
|
),
|
||||||
Self::ImproperSymbol(s) => write!(f, "{}", s),
|
Self::ImproperSymbol(s) => f.write_str(s),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -185,18 +184,16 @@ impl fmt::Display for ParseErrorType {
|
|||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
match self {
|
match self {
|
||||||
Self::BadInput(s) | ParseErrorType::MalformedCallExpr(s) => {
|
Self::BadInput(s) | ParseErrorType::MalformedCallExpr(s) => {
|
||||||
write!(f, "{}", if s.is_empty() { self.desc() } else { s })
|
f.write_str(if s.is_empty() { self.desc() } else { s })
|
||||||
}
|
}
|
||||||
Self::ForbiddenConstantExpr(s) => {
|
Self::ForbiddenConstantExpr(s) => {
|
||||||
write!(f, "Expecting a constant to assign to '{}'", s)
|
write!(f, "Expecting a constant to assign to '{}'", s)
|
||||||
}
|
}
|
||||||
Self::UnknownOperator(s) => write!(f, "{}: '{}'", self.desc(), s),
|
Self::UnknownOperator(s) => write!(f, "{}: '{}'", self.desc(), s),
|
||||||
|
|
||||||
Self::MalformedIndexExpr(s) => {
|
Self::MalformedIndexExpr(s) => f.write_str(if s.is_empty() { self.desc() } else { s }),
|
||||||
write!(f, "{}", if s.is_empty() { self.desc() } else { s })
|
|
||||||
}
|
|
||||||
|
|
||||||
Self::MalformedInExpr(s) => write!(f, "{}", if s.is_empty() { self.desc() } else { s }),
|
Self::MalformedInExpr(s) => f.write_str(if s.is_empty() { self.desc() } else { s }),
|
||||||
|
|
||||||
Self::DuplicatedProperty(s) => {
|
Self::DuplicatedProperty(s) => {
|
||||||
write!(f, "Duplicated property '{}' for object map literal", s)
|
write!(f, "Duplicated property '{}' for object map literal", s)
|
||||||
@ -222,12 +219,12 @@ impl fmt::Display for ParseErrorType {
|
|||||||
|
|
||||||
Self::MissingToken(token, s) => write!(f, "Expecting '{}' {}", token, s),
|
Self::MissingToken(token, s) => write!(f, "Expecting '{}' {}", token, s),
|
||||||
|
|
||||||
Self::AssignmentToConstant(s) if s.is_empty() => write!(f, "{}", self.desc()),
|
Self::AssignmentToConstant(s) if s.is_empty() => f.write_str(self.desc()),
|
||||||
Self::AssignmentToConstant(s) => write!(f, "Cannot assign to constant '{}'", s),
|
Self::AssignmentToConstant(s) => write!(f, "Cannot assign to constant '{}'", s),
|
||||||
Self::LiteralTooLarge(typ, max) => {
|
Self::LiteralTooLarge(typ, max) => {
|
||||||
write!(f, "{} exceeds the maximum limit ({})", typ, max)
|
write!(f, "{} exceeds the maximum limit ({})", typ, max)
|
||||||
}
|
}
|
||||||
_ => write!(f, "{}", self.desc()),
|
_ => f.write_str(self.desc()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -28,13 +28,9 @@ pub type Shared<T> = Arc<T>;
|
|||||||
/// If the resource is shared (i.e. has other outstanding references), a cloned copy is used.
|
/// If the resource is shared (i.e. has other outstanding references), a cloned copy is used.
|
||||||
pub fn shared_make_mut<T: Clone>(value: &mut Shared<T>) -> &mut T {
|
pub fn shared_make_mut<T: Clone>(value: &mut Shared<T>) -> &mut T {
|
||||||
#[cfg(not(feature = "sync"))]
|
#[cfg(not(feature = "sync"))]
|
||||||
{
|
return Rc::make_mut(value);
|
||||||
Rc::make_mut(value)
|
|
||||||
}
|
|
||||||
#[cfg(feature = "sync")]
|
#[cfg(feature = "sync")]
|
||||||
{
|
return Arc::make_mut(value);
|
||||||
Arc::make_mut(value)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Consume a `Shared` resource, assuming that it is unique (i.e. not shared).
|
/// Consume a `Shared` resource, assuming that it is unique (i.e. not shared).
|
||||||
@ -44,13 +40,9 @@ pub fn shared_make_mut<T: Clone>(value: &mut Shared<T>) -> &mut T {
|
|||||||
/// Panics if the resource is shared (i.e. has other outstanding references).
|
/// Panics if the resource is shared (i.e. has other outstanding references).
|
||||||
pub fn shared_take<T: Clone>(value: Shared<T>) -> T {
|
pub fn shared_take<T: Clone>(value: Shared<T>) -> T {
|
||||||
#[cfg(not(feature = "sync"))]
|
#[cfg(not(feature = "sync"))]
|
||||||
{
|
return Rc::try_unwrap(value).map_err(|_| ()).unwrap();
|
||||||
Rc::try_unwrap(value).map_err(|_| ()).unwrap()
|
|
||||||
}
|
|
||||||
#[cfg(feature = "sync")]
|
#[cfg(feature = "sync")]
|
||||||
{
|
return Arc::try_unwrap(value).map_err(|_| ()).unwrap();
|
||||||
Arc::try_unwrap(value).map_err(|_| ()).unwrap()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub type FnCallArgs<'a> = [&'a mut Dynamic];
|
pub type FnCallArgs<'a> = [&'a mut Dynamic];
|
||||||
@ -122,6 +114,7 @@ pub enum CallableFunction {
|
|||||||
/// A plugin-defined function,
|
/// A plugin-defined function,
|
||||||
Plugin(SharedPluginFunction),
|
Plugin(SharedPluginFunction),
|
||||||
/// A script-defined function.
|
/// A script-defined function.
|
||||||
|
#[cfg(not(feature = "no_function"))]
|
||||||
Script(Shared<ScriptFnDef>),
|
Script(Shared<ScriptFnDef>),
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -132,6 +125,8 @@ impl fmt::Debug for CallableFunction {
|
|||||||
Self::Method(_) => write!(f, "NativeMethod"),
|
Self::Method(_) => write!(f, "NativeMethod"),
|
||||||
Self::Iterator(_) => write!(f, "NativeIterator"),
|
Self::Iterator(_) => write!(f, "NativeIterator"),
|
||||||
Self::Plugin(_) => write!(f, "PluginFunction"),
|
Self::Plugin(_) => write!(f, "PluginFunction"),
|
||||||
|
|
||||||
|
#[cfg(not(feature = "no_function"))]
|
||||||
Self::Script(fn_def) => fmt::Debug::fmt(fn_def, f),
|
Self::Script(fn_def) => fmt::Debug::fmt(fn_def, f),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -144,6 +139,8 @@ impl fmt::Display for CallableFunction {
|
|||||||
Self::Method(_) => write!(f, "NativeMethod"),
|
Self::Method(_) => write!(f, "NativeMethod"),
|
||||||
Self::Iterator(_) => write!(f, "NativeIterator"),
|
Self::Iterator(_) => write!(f, "NativeIterator"),
|
||||||
Self::Plugin(_) => write!(f, "PluginFunction"),
|
Self::Plugin(_) => write!(f, "PluginFunction"),
|
||||||
|
|
||||||
|
#[cfg(not(feature = "no_function"))]
|
||||||
CallableFunction::Script(s) => fmt::Display::fmt(s, f),
|
CallableFunction::Script(s) => fmt::Display::fmt(s, f),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -154,24 +151,34 @@ impl CallableFunction {
|
|||||||
pub fn is_pure(&self) -> bool {
|
pub fn is_pure(&self) -> bool {
|
||||||
match self {
|
match self {
|
||||||
Self::Pure(_) => true,
|
Self::Pure(_) => true,
|
||||||
Self::Method(_) | Self::Iterator(_) | Self::Script(_) | Self::Plugin(_) => false,
|
Self::Method(_) | Self::Iterator(_) | Self::Plugin(_) => false,
|
||||||
|
|
||||||
|
#[cfg(not(feature = "no_function"))]
|
||||||
|
Self::Script(_) => false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/// Is this a native Rust method function?
|
/// Is this a native Rust method function?
|
||||||
pub fn is_method(&self) -> bool {
|
pub fn is_method(&self) -> bool {
|
||||||
match self {
|
match self {
|
||||||
Self::Method(_) => true,
|
Self::Method(_) => true,
|
||||||
Self::Pure(_) | Self::Iterator(_) | Self::Script(_) | Self::Plugin(_) => false,
|
Self::Pure(_) | Self::Iterator(_) | Self::Plugin(_) => false,
|
||||||
|
|
||||||
|
#[cfg(not(feature = "no_function"))]
|
||||||
|
Self::Script(_) => false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/// Is this an iterator function?
|
/// Is this an iterator function?
|
||||||
pub fn is_iter(&self) -> bool {
|
pub fn is_iter(&self) -> bool {
|
||||||
match self {
|
match self {
|
||||||
Self::Iterator(_) => true,
|
Self::Iterator(_) => true,
|
||||||
Self::Pure(_) | Self::Method(_) | Self::Script(_) | Self::Plugin(_) => false,
|
Self::Pure(_) | Self::Method(_) | Self::Plugin(_) => false,
|
||||||
|
|
||||||
|
#[cfg(not(feature = "no_function"))]
|
||||||
|
Self::Script(_) => false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/// Is this a Rhai-scripted function?
|
/// Is this a Rhai-scripted function?
|
||||||
|
#[cfg(not(feature = "no_function"))]
|
||||||
pub fn is_script(&self) -> bool {
|
pub fn is_script(&self) -> bool {
|
||||||
match self {
|
match self {
|
||||||
Self::Script(_) => true,
|
Self::Script(_) => true,
|
||||||
@ -193,7 +200,10 @@ impl CallableFunction {
|
|||||||
pub fn get_native_fn(&self) -> &FnAny {
|
pub fn get_native_fn(&self) -> &FnAny {
|
||||||
match self {
|
match self {
|
||||||
Self::Pure(f) | Self::Method(f) => f.as_ref(),
|
Self::Pure(f) | Self::Method(f) => f.as_ref(),
|
||||||
Self::Iterator(_) | Self::Script(_) | Self::Plugin(_) => unreachable!(),
|
Self::Iterator(_) | Self::Plugin(_) => unreachable!(),
|
||||||
|
|
||||||
|
#[cfg(not(feature = "no_function"))]
|
||||||
|
Self::Script(_) => unreachable!(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/// Get a shared reference to a script-defined function definition.
|
/// Get a shared reference to a script-defined function definition.
|
||||||
@ -201,6 +211,7 @@ impl CallableFunction {
|
|||||||
/// # Panics
|
/// # Panics
|
||||||
///
|
///
|
||||||
/// Panics if the `CallableFunction` is not `Script`.
|
/// Panics if the `CallableFunction` is not `Script`.
|
||||||
|
#[cfg(not(feature = "no_function"))]
|
||||||
pub fn get_shared_fn_def(&self) -> Shared<ScriptFnDef> {
|
pub fn get_shared_fn_def(&self) -> Shared<ScriptFnDef> {
|
||||||
match self {
|
match self {
|
||||||
Self::Pure(_) | Self::Method(_) | Self::Iterator(_) | Self::Plugin(_) => unreachable!(),
|
Self::Pure(_) | Self::Method(_) | Self::Iterator(_) | Self::Plugin(_) => unreachable!(),
|
||||||
@ -212,6 +223,7 @@ impl CallableFunction {
|
|||||||
/// # Panics
|
/// # Panics
|
||||||
///
|
///
|
||||||
/// Panics if the `CallableFunction` is not `Script`.
|
/// Panics if the `CallableFunction` is not `Script`.
|
||||||
|
#[cfg(not(feature = "no_function"))]
|
||||||
pub fn get_fn_def(&self) -> &ScriptFnDef {
|
pub fn get_fn_def(&self) -> &ScriptFnDef {
|
||||||
match self {
|
match self {
|
||||||
Self::Pure(_) | Self::Method(_) | Self::Iterator(_) | Self::Plugin(_) => unreachable!(),
|
Self::Pure(_) | Self::Method(_) | Self::Iterator(_) | Self::Plugin(_) => unreachable!(),
|
||||||
@ -226,7 +238,10 @@ impl CallableFunction {
|
|||||||
pub fn get_iter_fn(&self) -> IteratorFn {
|
pub fn get_iter_fn(&self) -> IteratorFn {
|
||||||
match self {
|
match self {
|
||||||
Self::Iterator(f) => *f,
|
Self::Iterator(f) => *f,
|
||||||
Self::Pure(_) | Self::Method(_) | Self::Script(_) | Self::Plugin(_) => unreachable!(),
|
Self::Pure(_) | Self::Method(_) | Self::Plugin(_) => unreachable!(),
|
||||||
|
|
||||||
|
#[cfg(not(feature = "no_function"))]
|
||||||
|
Self::Script(_) => unreachable!(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/// Get a reference to a plugin function.
|
/// Get a reference to a plugin function.
|
||||||
@ -237,7 +252,10 @@ impl CallableFunction {
|
|||||||
pub fn get_plugin_fn<'s>(&'s self) -> SharedPluginFunction {
|
pub fn get_plugin_fn<'s>(&'s self) -> SharedPluginFunction {
|
||||||
match self {
|
match self {
|
||||||
Self::Plugin(f) => f.clone(),
|
Self::Plugin(f) => f.clone(),
|
||||||
Self::Pure(_) | Self::Method(_) | Self::Script(_) | Self::Iterator(_) => unreachable!(),
|
Self::Pure(_) | Self::Method(_) | Self::Iterator(_) => unreachable!(),
|
||||||
|
|
||||||
|
#[cfg(not(feature = "no_function"))]
|
||||||
|
Self::Script(_) => unreachable!(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/// Create a new `CallableFunction::Pure`.
|
/// Create a new `CallableFunction::Pure`.
|
||||||
@ -268,12 +286,14 @@ impl From<IteratorFn> for CallableFunction {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(not(feature = "no_function"))]
|
||||||
impl From<ScriptFnDef> for CallableFunction {
|
impl From<ScriptFnDef> for CallableFunction {
|
||||||
fn from(func: ScriptFnDef) -> Self {
|
fn from(func: ScriptFnDef) -> Self {
|
||||||
Self::Script(func.into())
|
Self::Script(func.into())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(not(feature = "no_function"))]
|
||||||
impl From<Shared<ScriptFnDef>> for CallableFunction {
|
impl From<Shared<ScriptFnDef>> for CallableFunction {
|
||||||
fn from(func: Shared<ScriptFnDef>) -> Self {
|
fn from(func: Shared<ScriptFnDef>) -> Self {
|
||||||
Self::Script(func)
|
Self::Script(func)
|
||||||
|
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`. |
|
//! | `only_i64` | Set the system integer type to `i64` and disable all other integer types. `INT` is set to `i64`. |
|
||||||
//! | `no_std` | Build for `no-std`. Notice that additional dependencies will be pulled in to replace `std` features. |
|
//! | `no_std` | Build for `no-std`. Notice that additional dependencies will be pulled in to replace `std` features. |
|
||||||
//! | `sync` | Restrict all values types to those that are `Send + Sync`. Under this feature, `Engine`, `Scope` and [`AST`] are all `Send + Sync`. |
|
//! | `sync` | Restrict all values types to those that are `Send + Sync`. Under this feature, `Engine`, `Scope` and [`AST`] are all `Send + Sync`. |
|
||||||
|
//! | `serde` | Enable serialization/deserialization via `serde`. Notice that the [`serde`](https://crates.io/crates/serde) crate will be pulled in together with its dependencies. |
|
||||||
//! | `internals` | Expose internal data structures (beware they may be volatile from version to version). |
|
//! | `internals` | Expose internal data structures (beware they may be volatile from version to version). |
|
||||||
//!
|
//!
|
||||||
//! See [The Rhai Book](https://schungx.github.io/rhai) for details on the Rhai script engine and language.
|
//! See [The Rhai Book](https://schungx.github.io/rhai) for details on the Rhai script engine and language.
|
||||||
@ -90,6 +91,9 @@ pub mod plugin;
|
|||||||
mod plugin;
|
mod plugin;
|
||||||
mod result;
|
mod result;
|
||||||
mod scope;
|
mod scope;
|
||||||
|
#[cfg(feature = "serde")]
|
||||||
|
mod serde;
|
||||||
|
mod settings;
|
||||||
mod stdlib;
|
mod stdlib;
|
||||||
mod token;
|
mod token;
|
||||||
mod r#unsafe;
|
mod r#unsafe;
|
||||||
@ -126,11 +130,28 @@ pub use parser::FLOAT;
|
|||||||
pub use module::ModuleResolver;
|
pub use module::ModuleResolver;
|
||||||
|
|
||||||
/// Module containing all built-in _module resolvers_ available to Rhai.
|
/// Module containing all built-in _module resolvers_ available to Rhai.
|
||||||
|
///
|
||||||
|
/// Not available under the `no_module` feature.
|
||||||
#[cfg(not(feature = "no_module"))]
|
#[cfg(not(feature = "no_module"))]
|
||||||
pub mod module_resolvers {
|
pub mod module_resolvers {
|
||||||
pub use crate::module::resolvers::*;
|
pub use crate::module::resolvers::*;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Serialization support for [`serde`](https://crates.io/crates/serde).
|
||||||
|
///
|
||||||
|
/// Requires the `serde` feature.
|
||||||
|
#[cfg(feature = "serde")]
|
||||||
|
pub mod ser {
|
||||||
|
pub use crate::serde::ser::to_dynamic;
|
||||||
|
}
|
||||||
|
/// Deserialization support for [`serde`](https://crates.io/crates/serde).
|
||||||
|
///
|
||||||
|
/// Requires the `serde` feature.
|
||||||
|
#[cfg(feature = "serde")]
|
||||||
|
pub mod de {
|
||||||
|
pub use crate::serde::de::from_dynamic;
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(not(feature = "no_optimize"))]
|
#[cfg(not(feature = "no_optimize"))]
|
||||||
pub use optimize::OptimizationLevel;
|
pub use optimize::OptimizationLevel;
|
||||||
|
|
||||||
|
@ -236,6 +236,7 @@ impl Module {
|
|||||||
/// Set a script-defined function into the module.
|
/// Set a script-defined function into the module.
|
||||||
///
|
///
|
||||||
/// If there is an existing function of the same name and number of arguments, it is replaced.
|
/// If there is an existing function of the same name and number of arguments, it is replaced.
|
||||||
|
#[cfg(not(feature = "no_function"))]
|
||||||
pub(crate) fn set_script_fn(&mut self, fn_def: ScriptFnDef) {
|
pub(crate) fn set_script_fn(&mut self, fn_def: ScriptFnDef) {
|
||||||
// None + function name + number of arguments.
|
// None + function name + number of arguments.
|
||||||
let hash_script = calc_fn_hash(empty(), &fn_def.name, fn_def.params.len(), empty());
|
let hash_script = calc_fn_hash(empty(), &fn_def.name, fn_def.params.len(), empty());
|
||||||
@ -876,6 +877,7 @@ impl Module {
|
|||||||
.functions
|
.functions
|
||||||
.iter()
|
.iter()
|
||||||
.filter(|(_, (_, _, _, v))| match v {
|
.filter(|(_, (_, _, _, v))| match v {
|
||||||
|
#[cfg(not(feature = "no_function"))]
|
||||||
CallableFunction::Script(ref f) => {
|
CallableFunction::Script(ref f) => {
|
||||||
filter(f.access, f.name.as_str(), f.params.len())
|
filter(f.access, f.name.as_str(), f.params.len())
|
||||||
}
|
}
|
||||||
@ -893,6 +895,7 @@ impl Module {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Filter out the functions, retaining only some based on a filter predicate.
|
/// Filter out the functions, retaining only some based on a filter predicate.
|
||||||
|
#[cfg(not(feature = "no_function"))]
|
||||||
pub(crate) fn retain_functions(&mut self, filter: impl Fn(FnAccess, &str, usize) -> bool) {
|
pub(crate) fn retain_functions(&mut self, filter: impl Fn(FnAccess, &str, usize) -> bool) {
|
||||||
self.functions.retain(|_, (_, _, _, v)| match v {
|
self.functions.retain(|_, (_, _, _, v)| match v {
|
||||||
CallableFunction::Script(ref f) => filter(f.access, f.name.as_str(), f.params.len()),
|
CallableFunction::Script(ref f) => filter(f.access, f.name.as_str(), f.params.len()),
|
||||||
@ -930,6 +933,7 @@ impl Module {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Get an iterator over all script-defined functions in the module.
|
/// Get an iterator over all script-defined functions in the module.
|
||||||
|
#[cfg(not(feature = "no_function"))]
|
||||||
pub fn iter_script_fn<'a>(&'a self) -> impl Iterator<Item = Shared<ScriptFnDef>> + 'a {
|
pub fn iter_script_fn<'a>(&'a self) -> impl Iterator<Item = Shared<ScriptFnDef>> + 'a {
|
||||||
self.functions
|
self.functions
|
||||||
.values()
|
.values()
|
||||||
@ -1014,6 +1018,7 @@ impl Module {
|
|||||||
Public => (),
|
Public => (),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(not(feature = "no_function"))]
|
||||||
if func.is_script() {
|
if func.is_script() {
|
||||||
let fn_def = func.get_shared_fn_def();
|
let fn_def = func.get_shared_fn_def();
|
||||||
// Qualifiers + function name + number of arguments.
|
// Qualifiers + function name + number of arguments.
|
||||||
@ -1024,20 +1029,21 @@ impl Module {
|
|||||||
empty(),
|
empty(),
|
||||||
);
|
);
|
||||||
functions.push((hash_qualified_script, fn_def.into()));
|
functions.push((hash_qualified_script, fn_def.into()));
|
||||||
} else {
|
continue;
|
||||||
// Qualified Rust functions are indexed in two steps:
|
|
||||||
// 1) Calculate a hash in a similar manner to script-defined functions,
|
|
||||||
// i.e. qualifiers + function name + number of arguments.
|
|
||||||
let hash_qualified_script =
|
|
||||||
calc_fn_hash(qualifiers.iter().map(|&v| v), name, params.len(), empty());
|
|
||||||
// 2) Calculate a second hash with no qualifiers, empty function name,
|
|
||||||
// zero number of arguments, and the actual list of argument `TypeId`'.s
|
|
||||||
let hash_fn_args = calc_fn_hash(empty(), "", 0, params.iter().cloned());
|
|
||||||
// 3) The final hash is the XOR of the two hashes.
|
|
||||||
let hash_qualified_fn = hash_qualified_script ^ hash_fn_args;
|
|
||||||
|
|
||||||
functions.push((hash_qualified_fn, func.clone()));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Qualified Rust functions are indexed in two steps:
|
||||||
|
// 1) Calculate a hash in a similar manner to script-defined functions,
|
||||||
|
// i.e. qualifiers + function name + number of arguments.
|
||||||
|
let hash_qualified_script =
|
||||||
|
calc_fn_hash(qualifiers.iter().map(|&v| v), name, params.len(), empty());
|
||||||
|
// 2) Calculate a second hash with no qualifiers, empty function name,
|
||||||
|
// zero number of arguments, and the actual list of argument `TypeId`'.s
|
||||||
|
let hash_fn_args = calc_fn_hash(empty(), "", 0, params.iter().cloned());
|
||||||
|
// 3) The final hash is the XOR of the two hashes.
|
||||||
|
let hash_qualified_fn = hash_qualified_script ^ hash_fn_args;
|
||||||
|
|
||||||
|
functions.push((hash_qualified_fn, func.clone()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -551,11 +551,17 @@ fn optimize_expr(expr: Expr, state: &mut State) -> Expr {
|
|||||||
|
|
||||||
// First search in functions lib (can override built-in)
|
// First search in functions lib (can override built-in)
|
||||||
// Cater for both normal function call style and method call style (one additional arguments)
|
// Cater for both normal function call style and method call style (one additional arguments)
|
||||||
if state.lib.iter_fn().find(|(_, _, _, f)| {
|
#[cfg(not(feature = "no_function"))]
|
||||||
|
let has_script_fn = state.lib.iter_fn().find(|(_, _, _, f)| {
|
||||||
if !f.is_script() { return false; }
|
if !f.is_script() { return false; }
|
||||||
let fn_def = f.get_fn_def();
|
let fn_def = f.get_fn_def();
|
||||||
&fn_def.name == name && (args.len()..=args.len() + 1).contains(&fn_def.params.len())
|
&fn_def.name == name && (args.len()..=args.len() + 1).contains(&fn_def.params.len())
|
||||||
}).is_some() {
|
}).is_some();
|
||||||
|
|
||||||
|
#[cfg(feature = "no_function")]
|
||||||
|
const has_script_fn: bool = false;
|
||||||
|
|
||||||
|
if has_script_fn {
|
||||||
// A script-defined function overrides the built-in function - do not make the call
|
// A script-defined function overrides the built-in function - do not make the call
|
||||||
x.3 = x.3.into_iter().map(|a| optimize_expr(a, state)).collect();
|
x.3 = x.3.into_iter().map(|a| optimize_expr(a, state)).collect();
|
||||||
return Expr::FnCall(x);
|
return Expr::FnCall(x);
|
||||||
|
@ -190,42 +190,38 @@ fn modulo_u<T: Rem>(x: T, y: T) -> FuncReturn<<T as Rem>::Output> {
|
|||||||
// Checked power
|
// Checked power
|
||||||
pub(crate) fn pow_i_i(x: INT, y: INT) -> FuncReturn<INT> {
|
pub(crate) fn pow_i_i(x: INT, y: INT) -> FuncReturn<INT> {
|
||||||
#[cfg(not(feature = "only_i32"))]
|
#[cfg(not(feature = "only_i32"))]
|
||||||
{
|
if y > (u32::MAX as INT) {
|
||||||
if y > (u32::MAX as INT) {
|
Err(Box::new(EvalAltResult::ErrorArithmetic(
|
||||||
Err(Box::new(EvalAltResult::ErrorArithmetic(
|
format!("Integer raised to too large an index: {} ~ {}", x, y),
|
||||||
format!("Integer raised to too large an index: {} ~ {}", x, y),
|
Position::none(),
|
||||||
|
)))
|
||||||
|
} else if y < 0 {
|
||||||
|
Err(Box::new(EvalAltResult::ErrorArithmetic(
|
||||||
|
format!("Integer raised to a negative index: {} ~ {}", x, y),
|
||||||
|
Position::none(),
|
||||||
|
)))
|
||||||
|
} else {
|
||||||
|
x.checked_pow(y as u32).ok_or_else(|| {
|
||||||
|
Box::new(EvalAltResult::ErrorArithmetic(
|
||||||
|
format!("Power overflow: {} ~ {}", x, y),
|
||||||
Position::none(),
|
Position::none(),
|
||||||
)))
|
))
|
||||||
} else if y < 0 {
|
})
|
||||||
Err(Box::new(EvalAltResult::ErrorArithmetic(
|
|
||||||
format!("Integer raised to a negative index: {} ~ {}", x, y),
|
|
||||||
Position::none(),
|
|
||||||
)))
|
|
||||||
} else {
|
|
||||||
x.checked_pow(y as u32).ok_or_else(|| {
|
|
||||||
Box::new(EvalAltResult::ErrorArithmetic(
|
|
||||||
format!("Power overflow: {} ~ {}", x, y),
|
|
||||||
Position::none(),
|
|
||||||
))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "only_i32")]
|
#[cfg(feature = "only_i32")]
|
||||||
{
|
if y < 0 {
|
||||||
if y < 0 {
|
Err(Box::new(EvalAltResult::ErrorArithmetic(
|
||||||
Err(Box::new(EvalAltResult::ErrorArithmetic(
|
format!("Integer raised to a negative index: {} ~ {}", x, y),
|
||||||
format!("Integer raised to a negative index: {} ~ {}", x, y),
|
Position::none(),
|
||||||
|
)))
|
||||||
|
} else {
|
||||||
|
x.checked_pow(y as u32).ok_or_else(|| {
|
||||||
|
Box::new(EvalAltResult::ErrorArithmetic(
|
||||||
|
format!("Power overflow: {} ~ {}", x, y),
|
||||||
Position::none(),
|
Position::none(),
|
||||||
)))
|
))
|
||||||
} else {
|
})
|
||||||
x.checked_pow(y as u32).ok_or_else(|| {
|
|
||||||
Box::new(EvalAltResult::ErrorArithmetic(
|
|
||||||
format!("Power overflow: {} ~ {}", x, y),
|
|
||||||
Position::none(),
|
|
||||||
))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Unchecked integer power - may panic on overflow or if the power index is too high (> u32::MAX)
|
// Unchecked integer power - may panic on overflow or if the power index is too high (> u32::MAX)
|
||||||
|
@ -30,15 +30,13 @@ fn pad<T: Variant + Clone>(engine: &Engine, args: &mut [&mut Dynamic]) -> FuncRe
|
|||||||
|
|
||||||
// Check if array will be over max size limit
|
// Check if array will be over max size limit
|
||||||
#[cfg(not(feature = "unchecked"))]
|
#[cfg(not(feature = "unchecked"))]
|
||||||
{
|
if engine.max_array_size > 0 && len > 0 && (len as usize) > engine.max_array_size {
|
||||||
if engine.max_array_size > 0 && len > 0 && (len as usize) > engine.max_array_size {
|
return Err(Box::new(EvalAltResult::ErrorDataTooLarge(
|
||||||
return Err(Box::new(EvalAltResult::ErrorDataTooLarge(
|
"Size of array".to_string(),
|
||||||
"Size of array".to_string(),
|
engine.max_array_size,
|
||||||
engine.max_array_size,
|
len as usize,
|
||||||
len as usize,
|
Position::none(),
|
||||||
Position::none(),
|
)));
|
||||||
)));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if len > 0 {
|
if len > 0 {
|
||||||
|
@ -3,6 +3,7 @@ use crate::fn_native::FnPtr;
|
|||||||
|
|
||||||
def_package!(crate:BasicFnPackage:"Basic Fn functions.", lib, {
|
def_package!(crate:BasicFnPackage:"Basic Fn functions.", lib, {
|
||||||
lib.set_fn_1_mut("name", |f: &mut FnPtr| Ok(f.get_fn_name().clone()));
|
lib.set_fn_1_mut("name", |f: &mut FnPtr| Ok(f.get_fn_name().clone()));
|
||||||
lib.set_getter_fn("name", |f: &mut FnPtr| Ok(f.get_fn_name().clone()));
|
|
||||||
|
|
||||||
|
#[cfg(not(feature = "no_object"))]
|
||||||
|
lib.set_getter_fn("name", |f: &mut FnPtr| Ok(f.get_fn_name().clone()));
|
||||||
});
|
});
|
||||||
|
@ -231,15 +231,13 @@ def_package!(crate:MoreStringPackage:"Additional string utilities, including str
|
|||||||
|
|
||||||
// Check if string will be over max size limit
|
// Check if string will be over max size limit
|
||||||
#[cfg(not(feature = "unchecked"))]
|
#[cfg(not(feature = "unchecked"))]
|
||||||
{
|
if engine.max_string_size > 0 && len > 0 && (len as usize) > engine.max_string_size {
|
||||||
if engine.max_string_size > 0 && len > 0 && (len as usize) > engine.max_string_size {
|
return Err(Box::new(EvalAltResult::ErrorDataTooLarge(
|
||||||
return Err(Box::new(EvalAltResult::ErrorDataTooLarge(
|
"Length of string".to_string(),
|
||||||
"Length of string".to_string(),
|
engine.max_string_size,
|
||||||
engine.max_string_size,
|
len as usize,
|
||||||
len as usize,
|
Position::none(),
|
||||||
Position::none(),
|
)));
|
||||||
)));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if len > 0 {
|
if len > 0 {
|
||||||
|
@ -33,17 +33,16 @@ def_package!(crate:BasicTimePackage:"Basic timing utilities.", lib, {
|
|||||||
let seconds = (ts2 - ts1).as_secs();
|
let seconds = (ts2 - ts1).as_secs();
|
||||||
|
|
||||||
#[cfg(not(feature = "unchecked"))]
|
#[cfg(not(feature = "unchecked"))]
|
||||||
{
|
if seconds > (MAX_INT as u64) {
|
||||||
if seconds > (MAX_INT as u64) {
|
return Err(Box::new(EvalAltResult::ErrorArithmetic(
|
||||||
return Err(Box::new(EvalAltResult::ErrorArithmetic(
|
format!(
|
||||||
format!(
|
"Integer overflow for timestamp duration: {}",
|
||||||
"Integer overflow for timestamp duration: {}",
|
-(seconds as i64)
|
||||||
-(seconds as i64)
|
),
|
||||||
),
|
Position::none(),
|
||||||
Position::none(),
|
)));
|
||||||
)));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return Ok(-(seconds as INT));
|
return Ok(-(seconds as INT));
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@ -55,14 +54,13 @@ def_package!(crate:BasicTimePackage:"Basic timing utilities.", lib, {
|
|||||||
let seconds = (ts1 - ts2).as_secs();
|
let seconds = (ts1 - ts2).as_secs();
|
||||||
|
|
||||||
#[cfg(not(feature = "unchecked"))]
|
#[cfg(not(feature = "unchecked"))]
|
||||||
{
|
if seconds > (MAX_INT as u64) {
|
||||||
if seconds > (MAX_INT as u64) {
|
return Err(Box::new(EvalAltResult::ErrorArithmetic(
|
||||||
return Err(Box::new(EvalAltResult::ErrorArithmetic(
|
format!("Integer overflow for timestamp duration: {}", seconds),
|
||||||
format!("Integer overflow for timestamp duration: {}", seconds),
|
Position::none(),
|
||||||
Position::none(),
|
)));
|
||||||
)));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return Ok(seconds as INT);
|
return Ok(seconds as INT);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -86,14 +84,13 @@ def_package!(crate:BasicTimePackage:"Basic timing utilities.", lib, {
|
|||||||
let seconds = timestamp.elapsed().as_secs();
|
let seconds = timestamp.elapsed().as_secs();
|
||||||
|
|
||||||
#[cfg(not(feature = "unchecked"))]
|
#[cfg(not(feature = "unchecked"))]
|
||||||
{
|
if seconds > (MAX_INT as u64) {
|
||||||
if seconds > (MAX_INT as u64) {
|
return Err(Box::new(EvalAltResult::ErrorArithmetic(
|
||||||
return Err(Box::new(EvalAltResult::ErrorArithmetic(
|
format!("Integer overflow for timestamp.elapsed: {}", seconds),
|
||||||
format!("Integer overflow for timestamp.elapsed: {}", seconds),
|
Position::none(),
|
||||||
Position::none(),
|
)));
|
||||||
)));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(seconds as INT)
|
Ok(seconds as INT)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
101
src/parser.rs
101
src/parser.rs
@ -332,36 +332,26 @@ pub enum ReturnType {
|
|||||||
Exception,
|
Exception,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Eq, PartialEq, Hash, Default)]
|
#[derive(Clone)]
|
||||||
struct ParseState {
|
struct ParseState<'e> {
|
||||||
|
/// Reference to the scripting `Engine`.
|
||||||
|
engine: &'e Engine,
|
||||||
/// Encapsulates a local stack with variable names to simulate an actual runtime scope.
|
/// Encapsulates a local stack with variable names to simulate an actual runtime scope.
|
||||||
pub stack: Vec<(String, ScopeEntryType)>,
|
stack: Vec<(String, ScopeEntryType)>,
|
||||||
/// Encapsulates a local stack with variable names to simulate an actual runtime scope.
|
/// Encapsulates a local stack with variable names to simulate an actual runtime scope.
|
||||||
pub modules: Vec<String>,
|
modules: Vec<String>,
|
||||||
/// Maximum levels of expression nesting.
|
/// Maximum levels of expression nesting.
|
||||||
pub max_expr_depth: usize,
|
max_expr_depth: usize,
|
||||||
/// Maximum length of a string.
|
|
||||||
pub max_string_size: usize,
|
|
||||||
/// Maximum length of an array.
|
|
||||||
pub max_array_size: usize,
|
|
||||||
/// Maximum number of properties in a map.
|
|
||||||
pub max_map_size: usize,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ParseState {
|
impl<'e> ParseState<'e> {
|
||||||
/// Create a new `ParseState`.
|
/// Create a new `ParseState`.
|
||||||
pub fn new(
|
pub fn new(engine: &'e Engine, max_expr_depth: usize) -> Self {
|
||||||
max_expr_depth: usize,
|
|
||||||
max_string_size: usize,
|
|
||||||
max_array_size: usize,
|
|
||||||
max_map_size: usize,
|
|
||||||
) -> Self {
|
|
||||||
Self {
|
Self {
|
||||||
|
engine,
|
||||||
max_expr_depth,
|
max_expr_depth,
|
||||||
max_string_size,
|
stack: Default::default(),
|
||||||
max_array_size,
|
modules: Default::default(),
|
||||||
max_map_size,
|
|
||||||
..Default::default()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/// Find a variable by name in the `ParseState`, searching in reverse.
|
/// Find a variable by name in the `ParseState`, searching in reverse.
|
||||||
@ -1206,10 +1196,10 @@ fn parse_array_literal(
|
|||||||
let mut arr = StaticVec::new();
|
let mut arr = StaticVec::new();
|
||||||
|
|
||||||
while !input.peek().unwrap().0.is_eof() {
|
while !input.peek().unwrap().0.is_eof() {
|
||||||
if state.max_array_size > 0 && arr.len() >= state.max_array_size {
|
if state.engine.max_array_size > 0 && arr.len() >= state.engine.max_array_size {
|
||||||
return Err(PERR::LiteralTooLarge(
|
return Err(PERR::LiteralTooLarge(
|
||||||
"Size of array literal".to_string(),
|
"Size of array literal".to_string(),
|
||||||
state.max_array_size,
|
state.engine.max_array_size,
|
||||||
)
|
)
|
||||||
.into_err(input.peek().unwrap().1));
|
.into_err(input.peek().unwrap().1));
|
||||||
}
|
}
|
||||||
@ -1272,7 +1262,7 @@ fn parse_map_literal(
|
|||||||
_ => {
|
_ => {
|
||||||
let (name, pos) = match input.next().unwrap() {
|
let (name, pos) = match input.next().unwrap() {
|
||||||
(Token::Identifier(s), pos) => (s, pos),
|
(Token::Identifier(s), pos) => (s, pos),
|
||||||
(Token::StringConst(s), pos) => (s, pos),
|
(Token::StringConstant(s), pos) => (s, pos),
|
||||||
(Token::LexError(err), pos) => return Err(err.into_err(pos)),
|
(Token::LexError(err), pos) => return Err(err.into_err(pos)),
|
||||||
(_, pos) if map.is_empty() => {
|
(_, pos) if map.is_empty() => {
|
||||||
return Err(PERR::MissingToken(
|
return Err(PERR::MissingToken(
|
||||||
@ -1306,10 +1296,10 @@ fn parse_map_literal(
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
if state.max_map_size > 0 && map.len() >= state.max_map_size {
|
if state.engine.max_map_size > 0 && map.len() >= state.engine.max_map_size {
|
||||||
return Err(PERR::LiteralTooLarge(
|
return Err(PERR::LiteralTooLarge(
|
||||||
"Number of properties in object map literal".to_string(),
|
"Number of properties in object map literal".to_string(),
|
||||||
state.max_map_size,
|
state.engine.max_map_size,
|
||||||
)
|
)
|
||||||
.into_err(input.peek().unwrap().1));
|
.into_err(input.peek().unwrap().1));
|
||||||
}
|
}
|
||||||
@ -1380,7 +1370,7 @@ fn parse_primary(
|
|||||||
#[cfg(not(feature = "no_float"))]
|
#[cfg(not(feature = "no_float"))]
|
||||||
Token::FloatConstant(x) => Expr::FloatConstant(Box::new((x, settings.pos))),
|
Token::FloatConstant(x) => Expr::FloatConstant(Box::new((x, settings.pos))),
|
||||||
Token::CharConstant(c) => Expr::CharConstant(Box::new((c, settings.pos))),
|
Token::CharConstant(c) => Expr::CharConstant(Box::new((c, settings.pos))),
|
||||||
Token::StringConst(s) => Expr::StringConstant(Box::new((s.into(), settings.pos))),
|
Token::StringConstant(s) => Expr::StringConstant(Box::new((s.into(), settings.pos))),
|
||||||
Token::Identifier(s) => {
|
Token::Identifier(s) => {
|
||||||
let index = state.find_var(&s);
|
let index = state.find_var(&s);
|
||||||
Expr::Variable(Box::new(((s, settings.pos), None, 0, index)))
|
Expr::Variable(Box::new(((s, settings.pos), None, 0, index)))
|
||||||
@ -1495,13 +1485,9 @@ fn parse_unary(
|
|||||||
.map(|i| Expr::IntegerConstant(Box::new((i, pos))))
|
.map(|i| Expr::IntegerConstant(Box::new((i, pos))))
|
||||||
.or_else(|| {
|
.or_else(|| {
|
||||||
#[cfg(not(feature = "no_float"))]
|
#[cfg(not(feature = "no_float"))]
|
||||||
{
|
return Some(Expr::FloatConstant(Box::new((-(x.0 as FLOAT), pos))));
|
||||||
Some(Expr::FloatConstant(Box::new((-(x.0 as FLOAT), pos))))
|
|
||||||
}
|
|
||||||
#[cfg(feature = "no_float")]
|
#[cfg(feature = "no_float")]
|
||||||
{
|
return None;
|
||||||
None
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
.ok_or_else(|| LexError::MalformedNumber(format!("-{}", x.0)).into_err(pos))
|
.ok_or_else(|| LexError::MalformedNumber(format!("-{}", x.0)).into_err(pos))
|
||||||
}
|
}
|
||||||
@ -1701,7 +1687,7 @@ fn make_dot_expr(lhs: Expr, rhs: Expr, op_pos: Position) -> Result<Expr, ParseEr
|
|||||||
// lhs.func()
|
// lhs.func()
|
||||||
(lhs, func @ Expr::FnCall(_)) => Expr::Dot(Box::new((lhs, func, op_pos))),
|
(lhs, func @ Expr::FnCall(_)) => Expr::Dot(Box::new((lhs, func, op_pos))),
|
||||||
// lhs.rhs
|
// lhs.rhs
|
||||||
_ => unreachable!(),
|
(_, rhs) => return Err(PERR::PropertyExpected.into_err(rhs.position())),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1870,7 +1856,8 @@ fn parse_binary_op(
|
|||||||
|
|
||||||
loop {
|
loop {
|
||||||
let (current_op, _) = input.peek().unwrap();
|
let (current_op, _) = input.peek().unwrap();
|
||||||
let precedence = current_op.precedence();
|
let custom = state.engine.custom_keywords.as_ref();
|
||||||
|
let precedence = current_op.precedence(custom);
|
||||||
let bind_right = current_op.is_bind_right();
|
let bind_right = current_op.is_bind_right();
|
||||||
|
|
||||||
// Bind left to the parent lhs expression if precedence is higher
|
// Bind left to the parent lhs expression if precedence is higher
|
||||||
@ -1883,7 +1870,7 @@ fn parse_binary_op(
|
|||||||
|
|
||||||
let rhs = parse_unary(input, state, settings)?;
|
let rhs = parse_unary(input, state, settings)?;
|
||||||
|
|
||||||
let next_precedence = input.peek().unwrap().0.precedence();
|
let next_precedence = input.peek().unwrap().0.precedence(custom);
|
||||||
|
|
||||||
// Bind to right if the next operator has higher precedence
|
// Bind to right if the next operator has higher precedence
|
||||||
// If same precedence, then check if the operator binds right
|
// If same precedence, then check if the operator binds right
|
||||||
@ -1953,6 +1940,19 @@ fn parse_binary_op(
|
|||||||
make_dot_expr(current_lhs, rhs, pos)?
|
make_dot_expr(current_lhs, rhs, pos)?
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Token::Custom(s)
|
||||||
|
if state
|
||||||
|
.engine
|
||||||
|
.custom_keywords
|
||||||
|
.as_ref()
|
||||||
|
.map(|c| c.contains_key(&s))
|
||||||
|
.unwrap_or(false) =>
|
||||||
|
{
|
||||||
|
// Accept non-native functions for custom operators
|
||||||
|
let op = (op.0, false, op.2);
|
||||||
|
Expr::FnCall(Box::new((op, None, hash, args, None)))
|
||||||
|
}
|
||||||
|
|
||||||
op_token => return Err(PERR::UnknownOperator(op_token.into()).into_err(pos)),
|
op_token => return Err(PERR::UnknownOperator(op_token.into()).into_err(pos)),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -2194,6 +2194,7 @@ fn parse_let(
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Parse an import statement.
|
/// Parse an import statement.
|
||||||
|
#[cfg(not(feature = "no_module"))]
|
||||||
fn parse_import(
|
fn parse_import(
|
||||||
input: &mut TokenStream,
|
input: &mut TokenStream,
|
||||||
state: &mut ParseState,
|
state: &mut ParseState,
|
||||||
@ -2444,6 +2445,8 @@ fn parse_stmt(
|
|||||||
|
|
||||||
Token::Let => parse_let(input, state, Normal, settings.level_up()),
|
Token::Let => parse_let(input, state, Normal, settings.level_up()),
|
||||||
Token::Const => parse_let(input, state, Constant, settings.level_up()),
|
Token::Const => parse_let(input, state, Constant, settings.level_up()),
|
||||||
|
|
||||||
|
#[cfg(not(feature = "no_module"))]
|
||||||
Token::Import => parse_import(input, state, settings.level_up()),
|
Token::Import => parse_import(input, state, settings.level_up()),
|
||||||
|
|
||||||
#[cfg(not(feature = "no_module"))]
|
#[cfg(not(feature = "no_module"))]
|
||||||
@ -2468,7 +2471,7 @@ fn parse_fn(
|
|||||||
settings.ensure_level_within_max_limit(state.max_expr_depth)?;
|
settings.ensure_level_within_max_limit(state.max_expr_depth)?;
|
||||||
|
|
||||||
let name = match input.next().unwrap() {
|
let name = match input.next().unwrap() {
|
||||||
(Token::Identifier(s), _) => s,
|
(Token::Identifier(s), _) | (Token::Custom(s), _) => s,
|
||||||
(_, pos) => return Err(PERR::FnMissingName.into_err(pos)),
|
(_, pos) => return Err(PERR::FnMissingName.into_err(pos)),
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -2556,12 +2559,7 @@ impl Engine {
|
|||||||
scope: &Scope,
|
scope: &Scope,
|
||||||
optimization_level: OptimizationLevel,
|
optimization_level: OptimizationLevel,
|
||||||
) -> Result<AST, ParseError> {
|
) -> Result<AST, ParseError> {
|
||||||
let mut state = ParseState::new(
|
let mut state = ParseState::new(self, self.max_expr_depth);
|
||||||
self.max_expr_depth,
|
|
||||||
self.max_string_size,
|
|
||||||
self.max_array_size,
|
|
||||||
self.max_map_size,
|
|
||||||
);
|
|
||||||
let settings = ParseSettings {
|
let settings = ParseSettings {
|
||||||
allow_if_expr: false,
|
allow_if_expr: false,
|
||||||
allow_stmt_expr: false,
|
allow_stmt_expr: false,
|
||||||
@ -2597,12 +2595,7 @@ impl Engine {
|
|||||||
) -> Result<(Vec<Stmt>, Vec<ScriptFnDef>), ParseError> {
|
) -> Result<(Vec<Stmt>, Vec<ScriptFnDef>), ParseError> {
|
||||||
let mut statements = Vec::<Stmt>::new();
|
let mut statements = Vec::<Stmt>::new();
|
||||||
let mut functions = HashMap::<u64, ScriptFnDef, _>::with_hasher(StraightHasherBuilder);
|
let mut functions = HashMap::<u64, ScriptFnDef, _>::with_hasher(StraightHasherBuilder);
|
||||||
let mut state = ParseState::new(
|
let mut state = ParseState::new(self, self.max_expr_depth);
|
||||||
self.max_expr_depth,
|
|
||||||
self.max_string_size,
|
|
||||||
self.max_array_size,
|
|
||||||
self.max_map_size,
|
|
||||||
);
|
|
||||||
|
|
||||||
while !input.peek().unwrap().0.is_eof() {
|
while !input.peek().unwrap().0.is_eof() {
|
||||||
// Collect all the function definitions
|
// Collect all the function definitions
|
||||||
@ -2615,14 +2608,8 @@ impl Engine {
|
|||||||
};
|
};
|
||||||
|
|
||||||
match input.peek().unwrap() {
|
match input.peek().unwrap() {
|
||||||
#[cfg(not(feature = "no_function"))]
|
|
||||||
(Token::Fn, pos) => {
|
(Token::Fn, pos) => {
|
||||||
let mut state = ParseState::new(
|
let mut state = ParseState::new(self, self.max_function_expr_depth);
|
||||||
self.max_function_expr_depth,
|
|
||||||
self.max_string_size,
|
|
||||||
self.max_array_size,
|
|
||||||
self.max_map_size,
|
|
||||||
);
|
|
||||||
let settings = ParseSettings {
|
let settings = ParseSettings {
|
||||||
allow_if_expr: true,
|
allow_if_expr: true,
|
||||||
allow_stmt_expr: true,
|
allow_stmt_expr: true,
|
||||||
|
@ -74,8 +74,8 @@ pub enum EvalAltResult {
|
|||||||
/// Assignment to a constant variable.
|
/// Assignment to a constant variable.
|
||||||
ErrorAssignmentToConstant(String, Position),
|
ErrorAssignmentToConstant(String, Position),
|
||||||
/// Returned type is not the same as the required output type.
|
/// Returned type is not the same as the required output type.
|
||||||
/// Wrapped value is the type of the actual result.
|
/// Wrapped values are the type requested and type of the actual result.
|
||||||
ErrorMismatchOutputType(String, Position),
|
ErrorMismatchOutputType(String, String, Position),
|
||||||
/// Inappropriate member access.
|
/// Inappropriate member access.
|
||||||
ErrorDotExpr(String, Position),
|
ErrorDotExpr(String, Position),
|
||||||
/// Arithmetic error encountered. Wrapped value is the error message.
|
/// Arithmetic error encountered. Wrapped value is the error message.
|
||||||
@ -141,7 +141,7 @@ impl EvalAltResult {
|
|||||||
"Assignment to an unsupported left-hand side expression"
|
"Assignment to an unsupported left-hand side expression"
|
||||||
}
|
}
|
||||||
Self::ErrorAssignmentToConstant(_, _) => "Assignment to a constant variable",
|
Self::ErrorAssignmentToConstant(_, _) => "Assignment to a constant variable",
|
||||||
Self::ErrorMismatchOutputType(_, _) => "Output type is incorrect",
|
Self::ErrorMismatchOutputType(_, _, _) => "Output type is incorrect",
|
||||||
Self::ErrorInExpr(_) => "Malformed 'in' expression",
|
Self::ErrorInExpr(_) => "Malformed 'in' expression",
|
||||||
Self::ErrorDotExpr(_, _) => "Malformed dot expression",
|
Self::ErrorDotExpr(_, _) => "Malformed dot expression",
|
||||||
Self::ErrorArithmetic(_, _) => "Arithmetic error",
|
Self::ErrorArithmetic(_, _) => "Arithmetic error",
|
||||||
@ -197,16 +197,18 @@ impl fmt::Display for EvalAltResult {
|
|||||||
| Self::ErrorTooManyOperations(_)
|
| Self::ErrorTooManyOperations(_)
|
||||||
| Self::ErrorTooManyModules(_)
|
| Self::ErrorTooManyModules(_)
|
||||||
| Self::ErrorStackOverflow(_)
|
| Self::ErrorStackOverflow(_)
|
||||||
| Self::ErrorTerminated(_) => write!(f, "{}", desc)?,
|
| Self::ErrorTerminated(_) => f.write_str(desc)?,
|
||||||
|
|
||||||
Self::ErrorRuntime(s, _) => write!(f, "{}", if s.is_empty() { desc } else { s })?,
|
Self::ErrorRuntime(s, _) => f.write_str(if s.is_empty() { desc } else { s })?,
|
||||||
|
|
||||||
Self::ErrorAssignmentToConstant(s, _) => write!(f, "{}: '{}'", desc, s)?,
|
Self::ErrorAssignmentToConstant(s, _) => write!(f, "{}: '{}'", desc, s)?,
|
||||||
Self::ErrorMismatchOutputType(s, _) => write!(f, "{}: {}", desc, s)?,
|
Self::ErrorMismatchOutputType(r, s, _) => {
|
||||||
Self::ErrorArithmetic(s, _) => write!(f, "{}", s)?,
|
write!(f, "{} (expecting {}): {}", desc, s, r)?
|
||||||
|
}
|
||||||
|
Self::ErrorArithmetic(s, _) => f.write_str(s)?,
|
||||||
|
|
||||||
Self::ErrorLoopBreak(_, _) => write!(f, "{}", desc)?,
|
Self::ErrorLoopBreak(_, _) => f.write_str(desc)?,
|
||||||
Self::Return(_, _) => write!(f, "{}", desc)?,
|
Self::Return(_, _) => f.write_str(desc)?,
|
||||||
|
|
||||||
Self::ErrorBooleanArgMismatch(op, _) => {
|
Self::ErrorBooleanArgMismatch(op, _) => {
|
||||||
write!(f, "{} operator expects boolean operands", op)?
|
write!(f, "{} operator expects boolean operands", op)?
|
||||||
@ -215,7 +217,7 @@ impl fmt::Display for EvalAltResult {
|
|||||||
Self::ErrorArrayBounds(_, index, _) if *index < 0 => {
|
Self::ErrorArrayBounds(_, index, _) if *index < 0 => {
|
||||||
write!(f, "{}: {} < 0", desc, index)?
|
write!(f, "{}: {} < 0", desc, index)?
|
||||||
}
|
}
|
||||||
Self::ErrorArrayBounds(0, _, _) => write!(f, "{}", desc)?,
|
Self::ErrorArrayBounds(0, _, _) => f.write_str(desc)?,
|
||||||
Self::ErrorArrayBounds(1, index, _) => write!(
|
Self::ErrorArrayBounds(1, index, _) => write!(
|
||||||
f,
|
f,
|
||||||
"Array index {} is out of bounds: only one element in the array",
|
"Array index {} is out of bounds: only one element in the array",
|
||||||
@ -229,7 +231,7 @@ impl fmt::Display for EvalAltResult {
|
|||||||
Self::ErrorStringBounds(_, index, _) if *index < 0 => {
|
Self::ErrorStringBounds(_, index, _) if *index < 0 => {
|
||||||
write!(f, "{}: {} < 0", desc, index)?
|
write!(f, "{}: {} < 0", desc, index)?
|
||||||
}
|
}
|
||||||
Self::ErrorStringBounds(0, _, _) => write!(f, "{}", desc)?,
|
Self::ErrorStringBounds(0, _, _) => f.write_str(desc)?,
|
||||||
Self::ErrorStringBounds(1, index, _) => write!(
|
Self::ErrorStringBounds(1, index, _) => write!(
|
||||||
f,
|
f,
|
||||||
"String index {} is out of bounds: only one character in the string",
|
"String index {} is out of bounds: only one character in the string",
|
||||||
@ -289,7 +291,7 @@ impl EvalAltResult {
|
|||||||
| Self::ErrorModuleNotFound(_, pos)
|
| Self::ErrorModuleNotFound(_, pos)
|
||||||
| Self::ErrorAssignmentToUnknownLHS(pos)
|
| Self::ErrorAssignmentToUnknownLHS(pos)
|
||||||
| Self::ErrorAssignmentToConstant(_, pos)
|
| Self::ErrorAssignmentToConstant(_, pos)
|
||||||
| Self::ErrorMismatchOutputType(_, pos)
|
| Self::ErrorMismatchOutputType(_, _, pos)
|
||||||
| Self::ErrorInExpr(pos)
|
| Self::ErrorInExpr(pos)
|
||||||
| Self::ErrorDotExpr(_, pos)
|
| Self::ErrorDotExpr(_, pos)
|
||||||
| Self::ErrorArithmetic(_, pos)
|
| Self::ErrorArithmetic(_, pos)
|
||||||
@ -329,7 +331,7 @@ impl EvalAltResult {
|
|||||||
| Self::ErrorModuleNotFound(_, pos)
|
| Self::ErrorModuleNotFound(_, pos)
|
||||||
| Self::ErrorAssignmentToUnknownLHS(pos)
|
| Self::ErrorAssignmentToUnknownLHS(pos)
|
||||||
| Self::ErrorAssignmentToConstant(_, pos)
|
| Self::ErrorAssignmentToConstant(_, pos)
|
||||||
| Self::ErrorMismatchOutputType(_, pos)
|
| Self::ErrorMismatchOutputType(_, _, pos)
|
||||||
| Self::ErrorInExpr(pos)
|
| Self::ErrorInExpr(pos)
|
||||||
| Self::ErrorDotExpr(_, pos)
|
| Self::ErrorDotExpr(_, pos)
|
||||||
| Self::ErrorArithmetic(_, pos)
|
| Self::ErrorArithmetic(_, pos)
|
||||||
|
446
src/serde/de.rs
Normal file
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(())
|
||||||
|
}
|
||||||
|
}
|
133
src/token.rs
133
src/token.rs
@ -1,5 +1,6 @@
|
|||||||
//! Main module defining the lexer and parser.
|
//! Main module defining the lexer and parser.
|
||||||
|
|
||||||
|
use crate::engine::Engine;
|
||||||
use crate::error::LexError;
|
use crate::error::LexError;
|
||||||
use crate::parser::INT;
|
use crate::parser::INT;
|
||||||
use crate::utils::StaticVec;
|
use crate::utils::StaticVec;
|
||||||
@ -10,7 +11,9 @@ use crate::parser::FLOAT;
|
|||||||
use crate::stdlib::{
|
use crate::stdlib::{
|
||||||
borrow::Cow,
|
borrow::Cow,
|
||||||
boxed::Box,
|
boxed::Box,
|
||||||
char, fmt,
|
char,
|
||||||
|
collections::{HashMap, HashSet},
|
||||||
|
fmt,
|
||||||
iter::Peekable,
|
iter::Peekable,
|
||||||
str::{Chars, FromStr},
|
str::{Chars, FromStr},
|
||||||
string::{String, ToString},
|
string::{String, ToString},
|
||||||
@ -19,7 +22,7 @@ use crate::stdlib::{
|
|||||||
|
|
||||||
type LERR = LexError;
|
type LERR = LexError;
|
||||||
|
|
||||||
pub type TokenStream<'a> = Peekable<TokenIterator<'a>>;
|
pub type TokenStream<'a, 't> = Peekable<TokenIterator<'a, 't>>;
|
||||||
|
|
||||||
/// A location (line number + character position) in the input script.
|
/// A location (line number + character position) in the input script.
|
||||||
///
|
///
|
||||||
@ -137,7 +140,7 @@ pub enum Token {
|
|||||||
FloatConstant(FLOAT),
|
FloatConstant(FLOAT),
|
||||||
Identifier(String),
|
Identifier(String),
|
||||||
CharConstant(char),
|
CharConstant(char),
|
||||||
StringConst(String),
|
StringConstant(String),
|
||||||
LeftBrace,
|
LeftBrace,
|
||||||
RightBrace,
|
RightBrace,
|
||||||
LeftParen,
|
LeftParen,
|
||||||
@ -210,6 +213,7 @@ pub enum Token {
|
|||||||
As,
|
As,
|
||||||
LexError(Box<LexError>),
|
LexError(Box<LexError>),
|
||||||
Comment(String),
|
Comment(String),
|
||||||
|
Custom(String),
|
||||||
EOF,
|
EOF,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -222,12 +226,13 @@ impl Token {
|
|||||||
IntegerConstant(i) => i.to_string().into(),
|
IntegerConstant(i) => i.to_string().into(),
|
||||||
#[cfg(not(feature = "no_float"))]
|
#[cfg(not(feature = "no_float"))]
|
||||||
FloatConstant(f) => f.to_string().into(),
|
FloatConstant(f) => f.to_string().into(),
|
||||||
Identifier(s) => s.clone().into(),
|
StringConstant(_) => "string".into(),
|
||||||
CharConstant(c) => c.to_string().into(),
|
CharConstant(c) => c.to_string().into(),
|
||||||
|
Identifier(s) => s.clone().into(),
|
||||||
|
Custom(s) => s.clone().into(),
|
||||||
LexError(err) => err.to_string().into(),
|
LexError(err) => err.to_string().into(),
|
||||||
|
|
||||||
token => (match token {
|
token => match token {
|
||||||
StringConst(_) => "string",
|
|
||||||
LeftBrace => "{",
|
LeftBrace => "{",
|
||||||
RightBrace => "}",
|
RightBrace => "}",
|
||||||
LeftParen => "(",
|
LeftParen => "(",
|
||||||
@ -292,13 +297,15 @@ impl Token {
|
|||||||
PowerOfAssign => "~=",
|
PowerOfAssign => "~=",
|
||||||
#[cfg(not(feature = "no_function"))]
|
#[cfg(not(feature = "no_function"))]
|
||||||
Private => "private",
|
Private => "private",
|
||||||
|
#[cfg(not(feature = "no_module"))]
|
||||||
Import => "import",
|
Import => "import",
|
||||||
#[cfg(not(feature = "no_module"))]
|
#[cfg(not(feature = "no_module"))]
|
||||||
Export => "export",
|
Export => "export",
|
||||||
|
#[cfg(not(feature = "no_module"))]
|
||||||
As => "as",
|
As => "as",
|
||||||
EOF => "{EOF}",
|
EOF => "{EOF}",
|
||||||
_ => unreachable!("operator should be match in outer scope"),
|
_ => unreachable!("operator should be match in outer scope"),
|
||||||
})
|
}
|
||||||
.into(),
|
.into(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -320,9 +327,9 @@ impl Token {
|
|||||||
|
|
||||||
match self {
|
match self {
|
||||||
LexError(_) |
|
LexError(_) |
|
||||||
LeftBrace | // (+expr) - is unary
|
LeftBrace | // {+expr} - is unary
|
||||||
// RightBrace | {expr} - expr not unary & is closing
|
// RightBrace | {expr} - expr not unary & is closing
|
||||||
LeftParen | // {-expr} - is unary
|
LeftParen | // (-expr) - is unary
|
||||||
// RightParen | (expr) - expr not unary & is closing
|
// RightParen | (expr) - expr not unary & is closing
|
||||||
LeftBracket | // [-expr] - is unary
|
LeftBracket | // [-expr] - is unary
|
||||||
// RightBracket | [expr] - expr not unary & is closing
|
// RightBracket | [expr] - expr not unary & is closing
|
||||||
@ -367,14 +374,14 @@ impl Token {
|
|||||||
Throw |
|
Throw |
|
||||||
PowerOf |
|
PowerOf |
|
||||||
In |
|
In |
|
||||||
PowerOfAssign => true,
|
PowerOfAssign => true,
|
||||||
|
|
||||||
_ => false,
|
_ => false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get the precedence number of the token.
|
/// Get the precedence number of the token.
|
||||||
pub fn precedence(&self) -> u8 {
|
pub fn precedence(&self, custom: Option<&HashMap<String, u8>>) -> u8 {
|
||||||
use Token::*;
|
use Token::*;
|
||||||
|
|
||||||
match self {
|
match self {
|
||||||
@ -383,24 +390,27 @@ impl Token {
|
|||||||
| RightShiftAssign | AndAssign | OrAssign | XOrAssign | ModuloAssign
|
| RightShiftAssign | AndAssign | OrAssign | XOrAssign | ModuloAssign
|
||||||
| PowerOfAssign => 0,
|
| PowerOfAssign => 0,
|
||||||
|
|
||||||
Or | XOr | Pipe => 40,
|
Or | XOr | Pipe => 30,
|
||||||
|
|
||||||
And | Ampersand => 50,
|
And | Ampersand => 60,
|
||||||
|
|
||||||
LessThan | LessThanEqualsTo | GreaterThan | GreaterThanEqualsTo | EqualsTo
|
LessThan | LessThanEqualsTo | GreaterThan | GreaterThanEqualsTo | EqualsTo
|
||||||
| NotEqualsTo => 60,
|
| NotEqualsTo => 90,
|
||||||
|
|
||||||
In => 70,
|
In => 110,
|
||||||
|
|
||||||
Plus | Minus => 80,
|
Plus | Minus => 130,
|
||||||
|
|
||||||
Divide | Multiply | PowerOf => 90,
|
Divide | Multiply | PowerOf => 160,
|
||||||
|
|
||||||
LeftShift | RightShift => 100,
|
LeftShift | RightShift => 190,
|
||||||
|
|
||||||
Modulo => 110,
|
Modulo => 210,
|
||||||
|
|
||||||
Period => 120,
|
Period => 240,
|
||||||
|
|
||||||
|
// Custom operators
|
||||||
|
Custom(s) => custom.map_or(0, |c| *c.get(s).unwrap()),
|
||||||
|
|
||||||
_ => 0,
|
_ => 0,
|
||||||
}
|
}
|
||||||
@ -422,6 +432,41 @@ impl Token {
|
|||||||
_ => false,
|
_ => false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Is this token an operator?
|
||||||
|
pub fn is_operator(&self) -> bool {
|
||||||
|
use Token::*;
|
||||||
|
|
||||||
|
match self {
|
||||||
|
LeftBrace | RightBrace | LeftParen | RightParen | LeftBracket | RightBracket | Plus
|
||||||
|
| UnaryPlus | Minus | UnaryMinus | Multiply | Divide | Modulo | PowerOf | LeftShift
|
||||||
|
| RightShift | SemiColon | Colon | DoubleColon | Comma | Period | MapStart | Equals
|
||||||
|
| LessThan | GreaterThan | LessThanEqualsTo | GreaterThanEqualsTo | EqualsTo
|
||||||
|
| NotEqualsTo | Bang | Pipe | Or | XOr | Ampersand | And | PlusAssign | MinusAssign
|
||||||
|
| MultiplyAssign | DivideAssign | LeftShiftAssign | RightShiftAssign | AndAssign
|
||||||
|
| OrAssign | XOrAssign | ModuloAssign | PowerOfAssign => true,
|
||||||
|
|
||||||
|
_ => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Is this token a keyword?
|
||||||
|
pub fn is_keyword(&self) -> bool {
|
||||||
|
use Token::*;
|
||||||
|
|
||||||
|
match self {
|
||||||
|
#[cfg(not(feature = "no_function"))]
|
||||||
|
Fn | Private => true,
|
||||||
|
|
||||||
|
#[cfg(not(feature = "no_module"))]
|
||||||
|
Import | Export | As => true,
|
||||||
|
|
||||||
|
True | False | Let | Const | If | Else | While | Loop | For | In | Continue | Break
|
||||||
|
| Return | Throw => true,
|
||||||
|
|
||||||
|
_ => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<Token> for String {
|
impl From<Token> for String {
|
||||||
@ -431,7 +476,7 @@ impl From<Token> for String {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// State of the tokenizer.
|
/// State of the tokenizer.
|
||||||
#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash, Default)]
|
#[derive(Debug, Clone, Eq, PartialEq, Default)]
|
||||||
pub struct TokenizeState {
|
pub struct TokenizeState {
|
||||||
/// Maximum length of a string (0 = unlimited).
|
/// Maximum length of a string (0 = unlimited).
|
||||||
pub max_string_size: usize,
|
pub max_string_size: usize,
|
||||||
@ -644,7 +689,7 @@ pub fn get_next_token(
|
|||||||
let result = get_next_token_inner(stream, state, pos);
|
let result = get_next_token_inner(stream, state, pos);
|
||||||
|
|
||||||
// Save the last token's state
|
// Save the last token's state
|
||||||
if let Some((token, _)) = &result {
|
if let Some((ref token, _)) = result {
|
||||||
state.non_unary = !token.is_next_unary();
|
state.non_unary = !token.is_next_unary();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -848,7 +893,7 @@ fn get_next_token_inner(
|
|||||||
('"', _) => return parse_string_literal(stream, state, pos, '"')
|
('"', _) => return parse_string_literal(stream, state, pos, '"')
|
||||||
.map_or_else(
|
.map_or_else(
|
||||||
|err| Some((Token::LexError(Box::new(err.0)), err.1)),
|
|err| Some((Token::LexError(Box::new(err.0)), err.1)),
|
||||||
|out| Some((Token::StringConst(out), start_pos)),
|
|out| Some((Token::StringConstant(out), start_pos)),
|
||||||
),
|
),
|
||||||
|
|
||||||
// ' - character literal
|
// ' - character literal
|
||||||
@ -1118,7 +1163,7 @@ fn get_next_token_inner(
|
|||||||
('\0', _) => unreachable!(),
|
('\0', _) => unreachable!(),
|
||||||
|
|
||||||
(ch, _) if ch.is_whitespace() => (),
|
(ch, _) if ch.is_whitespace() => (),
|
||||||
(ch, _) => return Some((Token::LexError(Box::new(LERR::UnexpectedChar(ch))), start_pos)),
|
(ch, _) => return Some((Token::LexError(Box::new(LERR::UnexpectedInput(ch.to_string()))), start_pos)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1172,7 +1217,9 @@ impl InputStream for MultiInputsStream<'_> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// An iterator on a `Token` stream.
|
/// An iterator on a `Token` stream.
|
||||||
pub struct TokenIterator<'a> {
|
pub struct TokenIterator<'a, 'e> {
|
||||||
|
/// Reference to the scripting `Engine`.
|
||||||
|
engine: &'e Engine,
|
||||||
/// Current state.
|
/// Current state.
|
||||||
state: TokenizeState,
|
state: TokenizeState,
|
||||||
/// Current position.
|
/// Current position.
|
||||||
@ -1181,19 +1228,47 @@ pub struct TokenIterator<'a> {
|
|||||||
stream: MultiInputsStream<'a>,
|
stream: MultiInputsStream<'a>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> Iterator for TokenIterator<'a> {
|
impl<'a> Iterator for TokenIterator<'a, '_> {
|
||||||
type Item = (Token, Position);
|
type Item = (Token, Position);
|
||||||
|
|
||||||
fn next(&mut self) -> Option<Self::Item> {
|
fn next(&mut self) -> Option<Self::Item> {
|
||||||
get_next_token(&mut self.stream, &mut self.state, &mut self.pos)
|
match (
|
||||||
|
get_next_token(&mut self.stream, &mut self.state, &mut self.pos),
|
||||||
|
self.engine.disabled_symbols.as_ref(),
|
||||||
|
self.engine.custom_keywords.as_ref(),
|
||||||
|
) {
|
||||||
|
(None, _, _) => None,
|
||||||
|
(r @ Some(_), None, None) => r,
|
||||||
|
(Some((token, pos)), Some(disabled), _)
|
||||||
|
if token.is_operator() && disabled.contains(token.syntax().as_ref()) =>
|
||||||
|
{
|
||||||
|
// Convert disallowed operators into lex errors
|
||||||
|
Some((
|
||||||
|
Token::LexError(Box::new(LexError::UnexpectedInput(token.syntax().into()))),
|
||||||
|
pos,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
(Some((token, pos)), Some(disabled), _)
|
||||||
|
if token.is_keyword() && disabled.contains(token.syntax().as_ref()) =>
|
||||||
|
{
|
||||||
|
// Convert disallowed keywords into identifiers
|
||||||
|
Some((Token::Identifier(token.syntax().into()), pos))
|
||||||
|
}
|
||||||
|
(Some((Token::Identifier(s), pos)), _, Some(custom)) if custom.contains_key(&s) => {
|
||||||
|
// Convert custom keywords
|
||||||
|
Some((Token::Custom(s), pos))
|
||||||
|
}
|
||||||
|
(r, _, _) => r,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Tokenize an input text stream.
|
/// Tokenize an input text stream.
|
||||||
pub fn lex<'a>(input: &'a [&'a str], max_string_size: usize) -> TokenIterator<'a> {
|
pub fn lex<'a, 'e>(input: &'a [&'a str], engine: &'e Engine) -> TokenIterator<'a, 'e> {
|
||||||
TokenIterator {
|
TokenIterator {
|
||||||
|
engine,
|
||||||
state: TokenizeState {
|
state: TokenizeState {
|
||||||
max_string_size,
|
max_string_size: engine.max_string_size,
|
||||||
non_unary: false,
|
non_unary: false,
|
||||||
comment_level: 0,
|
comment_level: 0,
|
||||||
end_with_none: false,
|
end_with_none: false,
|
||||||
|
@ -35,6 +35,7 @@ fn test_max_string_size() -> Result<(), Box<EvalAltResult>> {
|
|||||||
EvalAltResult::ErrorDataTooLarge(_, 10, 13, _)
|
EvalAltResult::ErrorDataTooLarge(_, 10, 13, _)
|
||||||
));
|
));
|
||||||
|
|
||||||
|
#[cfg(not(feature = "no_object"))]
|
||||||
assert!(matches!(
|
assert!(matches!(
|
||||||
*engine
|
*engine
|
||||||
.eval::<String>(
|
.eval::<String>(
|
||||||
@ -92,6 +93,8 @@ fn test_max_array_size() -> Result<(), Box<EvalAltResult>> {
|
|||||||
.expect_err("should error"),
|
.expect_err("should error"),
|
||||||
EvalAltResult::ErrorDataTooLarge(_, 10, 12, _)
|
EvalAltResult::ErrorDataTooLarge(_, 10, 12, _)
|
||||||
));
|
));
|
||||||
|
|
||||||
|
#[cfg(not(feature = "no_object"))]
|
||||||
assert!(matches!(
|
assert!(matches!(
|
||||||
*engine
|
*engine
|
||||||
.eval::<Array>(
|
.eval::<Array>(
|
||||||
|
@ -39,7 +39,7 @@ fn test_for_string() -> Result<(), Box<EvalAltResult>> {
|
|||||||
let sum = 0;
|
let sum = 0;
|
||||||
|
|
||||||
for ch in s {
|
for ch in s {
|
||||||
sum += ch.to_int();
|
sum += to_int(ch);
|
||||||
}
|
}
|
||||||
|
|
||||||
sum
|
sum
|
||||||
|
@ -52,6 +52,7 @@ fn test_functions() -> Result<(), Box<EvalAltResult>> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
#[cfg(not(feature = "no_object"))]
|
||||||
fn test_function_pointers() -> Result<(), Box<EvalAltResult>> {
|
fn test_function_pointers() -> Result<(), Box<EvalAltResult>> {
|
||||||
let engine = Engine::new();
|
let engine = Engine::new();
|
||||||
|
|
||||||
|
@ -6,14 +6,14 @@ fn test_mismatched_op() {
|
|||||||
|
|
||||||
assert!(matches!(
|
assert!(matches!(
|
||||||
*engine.eval::<INT>(r#""hello, " + "world!""#).expect_err("expects error"),
|
*engine.eval::<INT>(r#""hello, " + "world!""#).expect_err("expects error"),
|
||||||
EvalAltResult::ErrorMismatchOutputType(err, _) if err == "string"
|
EvalAltResult::ErrorMismatchOutputType(need, actual, _) if need == std::any::type_name::<INT>() && actual == "string"
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
#[cfg(not(feature = "no_object"))]
|
#[cfg(not(feature = "no_object"))]
|
||||||
fn test_mismatched_op_custom_type() {
|
fn test_mismatched_op_custom_type() {
|
||||||
#[derive(Clone)]
|
#[derive(Debug, Clone)]
|
||||||
struct TestStruct {
|
struct TestStruct {
|
||||||
x: INT,
|
x: INT,
|
||||||
}
|
}
|
||||||
@ -28,19 +28,14 @@ fn test_mismatched_op_custom_type() {
|
|||||||
engine.register_type_with_name::<TestStruct>("TestStruct");
|
engine.register_type_with_name::<TestStruct>("TestStruct");
|
||||||
engine.register_fn("new_ts", TestStruct::new);
|
engine.register_fn("new_ts", TestStruct::new);
|
||||||
|
|
||||||
let r = engine
|
|
||||||
.eval::<INT>("60 + new_ts()")
|
|
||||||
.expect_err("expects error");
|
|
||||||
|
|
||||||
#[cfg(feature = "only_i32")]
|
|
||||||
assert!(matches!(
|
assert!(matches!(
|
||||||
*r,
|
*engine.eval::<INT>("60 + new_ts()").expect_err("should error"),
|
||||||
EvalAltResult::ErrorFunctionNotFound(err, _) if err == "+ (i32, TestStruct)"
|
EvalAltResult::ErrorFunctionNotFound(err, _) if err == format!("+ ({}, TestStruct)", std::any::type_name::<INT>())
|
||||||
));
|
));
|
||||||
|
|
||||||
#[cfg(not(feature = "only_i32"))]
|
|
||||||
assert!(matches!(
|
assert!(matches!(
|
||||||
*r,
|
*engine.eval::<TestStruct>("42").expect_err("should error"),
|
||||||
EvalAltResult::ErrorFunctionNotFound(err, _) if err == "+ (i64, TestStruct)"
|
EvalAltResult::ErrorMismatchOutputType(need, actual, _)
|
||||||
|
if need == "TestStruct" && actual == std::any::type_name::<INT>()
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
192
tests/serde.rs
Normal file
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