diff --git a/Cargo.toml b/Cargo.toml
index 2ffbd617..6d48bc8d 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,6 +1,6 @@
[package]
name = "rhai"
-version = "0.16.0"
+version = "0.17.0"
edition = "2018"
authors = ["Jonathan Turner", "Lukáš Hozda", "Stephen Chung"]
description = "Embedded scripting for Rust"
@@ -65,5 +65,17 @@ default-features = false
features = ["compile-time-rng"]
optional = true
+[dependencies.serde]
+package = "serde"
+version = "1.0.111"
+features = ["derive"]
+optional = true
+
[target.'cfg(target_arch = "wasm32")'.dependencies]
instant= { version = "0.1.4", features = ["wasm-bindgen"] } # WASM implementation of std::time::Instant
+
+[package.metadata.docs.rs]
+features = ["serde"]
+
+[package.metadata.playground]
+features = ["serde"]
diff --git a/README.md b/README.md
index 952d9bf3..e448ba69 100644
--- a/README.md
+++ b/README.md
@@ -26,18 +26,20 @@ Features
* Freely pass Rust variables/constants into a script via an external [`Scope`](https://schungx.github.io/rhai/rust/scope.html).
* Easily [call a script-defined function](https://schungx.github.io/rhai/engine/call-fn.html) from Rust.
* Fairly low compile-time overhead.
-* Fairly efficient evaluation (1 million iterations in 0.25 sec on a single core, 2.3 GHz Linux VM).
+* Fairly efficient evaluation (1 million iterations in 0.3 sec on a single core, 2.3 GHz Linux VM).
* Relatively little `unsafe` code (yes there are some for performance reasons, and most `unsafe` code is limited to
one single source file, all with names starting with `"unsafe_"`).
-* Re-entrant scripting engine can be made `Send + Sync` (via the [`sync`] feature).
+* Re-entrant scripting engine can be made `Send + Sync` (via the `sync` feature).
* Sand-boxed - the scripting engine, if declared immutable, cannot mutate the containing environment unless explicitly permitted (e.g. via a `RefCell`).
* Rugged - protected against malicious attacks (such as [stack-overflow](https://schungx.github.io/rhai/safety/max-call-stack.html), [over-sized data](https://schungx.github.io/rhai/safety/max-string-size.html), and [runaway scripts](https://schungx.github.io/rhai/safety/max-operations.html) etc.) that may come from untrusted third-party user-land scripts.
* Track script evaluation [progress](https://schungx.github.io/rhai/safety/progress.html) and manually terminate a script run.
* [Function overloading](https://schungx.github.io/rhai/language/overload.html).
-* [Operator overloading](https://schungx.github.io/rhai/rust/operators.html).
+* [Operator overloading](https://schungx.github.io/rhai/rust/operators.html) and [custom operators](https://schungx.github.io/rhai/engine/custom-op.html).
* Dynamic dispatch via [function pointers](https://schungx.github.io/rhai/language/fn-ptr.html).
* Some support for [object-oriented programming (OOP)](https://schungx.github.io/rhai/language/oop.html).
* Organize code base with dynamically-loadable [modules](https://schungx.github.io/rhai/language/modules.html).
+* Serialization/deserialization support via [serde](https://crates.io/crates/serde) (requires the `serde` feature).
+* Surgically disable keywords and operators to restrict the language.
* Scripts are [optimized](https://schungx.github.io/rhai/engine/optimize.html) (useful for template-based machine-generated scripts) for repeated evaluations.
* Support for [minimal builds](https://schungx.github.io/rhai/start/builds/minimal.html) by excluding unneeded language [features](https://schungx.github.io/rhai/start/features.html).
diff --git a/RELEASES.md b/RELEASES.md
index 6f38bb72..7b9d8230 100644
--- a/RELEASES.md
+++ b/RELEASES.md
@@ -1,11 +1,44 @@
Rhai Release Notes
==================
+Version 0.17.0
+==============
+
+This version adds:
+
+* [`serde`](https://crates.io/crates/serde) support for working with `Dynamic` values (particularly _object maps_).
+* Ability to surgically disable keywords and/or operators in the language.
+* Ability to define custom operators (which must be valid identifiers).
+
+Breaking changes
+----------------
+
+* `EvalAltResult::ErrorMismatchOutputType` has an extra argument containing the name of the requested type.
+
+New features
+------------
+
+* New `serde` feature to allow serializating/deserializating to/from `Dynamic` values using [`serde`](https://crates.io/crates/serde).
+ This is particularly useful when converting a Rust `struct` to a `Dynamic` _object map_ and back.
+* `Engine::disable_symbol` to surgically disable keywords and/or operators.
+* `Engine::register_custom_operator` to define a custom operator.
+
+
+Version 0.16.1
+==============
+
+Bug fix release to fix errors when compiling with features.
+
+
Version 0.16.0
==============
The major new feature in this version is OOP - well, poor man's OOP, that is.
+The `README` is officially transferred to [The Rhai Book](https://schungx.github.io/rhai).
+
+An online [Playground](https://alvinhochun.github.io/rhai-demo/) is available.
+
Breaking changes
----------------
diff --git a/doc/src/SUMMARY.md b/doc/src/SUMMARY.md
index f9491dd3..6c483cde 100644
--- a/doc/src/SUMMARY.md
+++ b/doc/src/SUMMARY.md
@@ -96,14 +96,17 @@ The Rhai Scripting Language
9. [Maximum Statement Depth](safety/max-stmt-depth.md)
8. [Advanced Topics](advanced.md)
1. [Object-Oriented Programming (OOP)](language/oop.md)
- 2. [Script Optimization](engine/optimize/index.md)
+ 2. [Serialization/Deserialization of `Dynamic` with `serde`](rust/serde.md)
+ 3. [Script Optimization](engine/optimize/index.md)
1. [Optimization Levels](engine/optimize/optimize-levels.md)
2. [Re-Optimize an AST](engine/optimize/reoptimize.md)
3. [Eager Function Evaluation](engine/optimize/eager.md)
4. [Side-Effect Considerations](engine/optimize/side-effects.md)
5. [Volatility Considerations](engine/optimize/volatility.md)
6. [Subtle Semantic Changes](engine/optimize/semantics.md)
- 3. [Eval Statement](language/eval.md)
+ 4. [Disable Keywords and/or Operators](engine/disable.md)
+ 5. [Custom Operators](engine/custom-op.md)
+ 6. [Eval Statement](language/eval.md)
9. [Appendix](appendix/index.md)
1. [Keywords](appendix/keywords.md)
2. [Operators](appendix/operators.md)
diff --git a/doc/src/about/features.md b/doc/src/about/features.md
index c012353a..5a5739cb 100644
--- a/doc/src/about/features.md
+++ b/doc/src/about/features.md
@@ -22,7 +22,7 @@ Fast
* Fairly low compile-time overhead.
-* Fairly efficient evaluation (1 million iterations in 0.25 sec on a single core, 2.3 GHz Linux VM).
+* Fairly efficient evaluation (1 million iterations in 0.3 sec on a single core, 2.3 GHz Linux VM).
* Scripts are [optimized][script optimization] (useful for template-based machine-generated scripts) for repeated evaluations.
@@ -39,6 +39,8 @@ Dynamic
* Some support for [object-oriented programming (OOP)][OOP].
+* Serialization/deserialization support via [`serde`].
+
Safe
----
@@ -62,3 +64,7 @@ Flexible
* Support for [minimal builds] by excluding unneeded language [features].
* Supports [most build targets](targets.md) including `no-std` and [WASM].
+
+* Surgically [disable keywords and operators] to restrict the language.
+
+* [Custom operators].
diff --git a/doc/src/about/index.md b/doc/src/about/index.md
index 85c55824..76603bde 100644
--- a/doc/src/about/index.md
+++ b/doc/src/about/index.md
@@ -5,3 +5,8 @@ What is Rhai
Rhai is an embedded scripting language and evaluation engine for Rust that gives a safe and easy way
to add scripting to any application.
+
+
+This Book is for version {{version}} of Rhai.
+
+For the latest development version, see [here]({{rootUrl}}/vnext/).
diff --git a/doc/src/appendix/operators.md b/doc/src/appendix/operators.md
index 56782b95..bc0459b8 100644
--- a/doc/src/appendix/operators.md
+++ b/doc/src/appendix/operators.md
@@ -3,28 +3,28 @@ Operators
{{#include ../links.md}}
-| Operator | Description | Binary? |
-| :---------------: | ------------------------------ | :-----: |
-| `+` | Add | Yes |
-| `-` | Subtract, Minus | Yes/No |
-| `*` | Multiply | Yes |
-| `/` | Divide | Yes |
-| `%` | Modulo | Yes |
-| `~` | Power | Yes |
-| `>>` | Right bit-shift | Yes |
-| `<<` | Left bit-shift | Yes |
-| `&` | Bit-wise _And_, Boolean _And_ | Yes |
-| \|
| Bit-wise _Or_, Boolean _Or_ | Yes |
-| `^` | Bit-wise _Xor_ | Yes |
-| `==` | Equals to | Yes |
-| `~=` | Not equals to | Yes |
-| `>` | Greater than | Yes |
-| `>=` | Greater than or equals to | Yes |
-| `<` | Less than | Yes |
-| `<=` | Less than or equals to | Yes |
-| `>=` | Greater than or equals to | Yes |
-| `&&` | Boolean _And_ (short-circuits) | Yes |
-| \|\|
| Boolean _Or_ (short-circuits) | Yes |
-| `!` | Boolean _Not_ | No |
-| `[` .. `]` | Indexing | Yes |
-| `.` | Property access, Method call | Yes |
+| Operator | Description | Binary? | Binding direction |
+| :---------------: | ------------------------------ | :-----: | :---------------: |
+| `+` | Add | Yes | Left |
+| `-` | Subtract, Minus | Yes/No | Left |
+| `*` | Multiply | Yes | Left |
+| `/` | Divide | Yes | Left |
+| `%` | Modulo | Yes | Left |
+| `~` | Power | Yes | Left |
+| `>>` | Right bit-shift | Yes | Left |
+| `<<` | Left bit-shift | Yes | Left |
+| `&` | Bit-wise _And_, Boolean _And_ | Yes | Left |
+| \|
| Bit-wise _Or_, Boolean _Or_ | Yes | Left |
+| `^` | Bit-wise _Xor_ | Yes | Left |
+| `==` | Equals to | Yes | Left |
+| `~=` | Not equals to | Yes | Left |
+| `>` | Greater than | Yes | Left |
+| `>=` | Greater than or equals to | Yes | Left |
+| `<` | Less than | Yes | Left |
+| `<=` | Less than or equals to | Yes | Left |
+| `>=` | Greater than or equals to | Yes | Left |
+| `&&` | Boolean _And_ (short-circuits) | Yes | Left |
+| \|\|
| Boolean _Or_ (short-circuits) | Yes | Left |
+| `!` | Boolean _Not_ | No | Left |
+| `[` .. `]` | Indexing | Yes | Right |
+| `.` | Property access, Method call | Yes | Right |
diff --git a/doc/src/context.json b/doc/src/context.json
index 6da8d6bb..506b72e1 100644
--- a/doc/src/context.json
+++ b/doc/src/context.json
@@ -1,5 +1,6 @@
{
- "version": "0.16.0",
+ "version": "0.17.0",
"rootUrl": "",
- "rootUrlX": "/rhai"
+ "rootUrlX": "/rhai",
+ "rootUrlXX": "/rhai/vnext"
}
\ No newline at end of file
diff --git a/doc/src/engine/custom-op.md b/doc/src/engine/custom-op.md
new file mode 100644
index 00000000..1c12263e
--- /dev/null
+++ b/doc/src/engine/custom-op.md
@@ -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::("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::("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 | `=`, `+=`, `-=`, `*=`, `/=`, `~=`, `%=`, `<<=`, `>>=`, `&=`, \|=
, `^=` | 0 |
+| Logic and bit masks | \|\|
, \|
, `^` | 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.
diff --git a/doc/src/engine/disable.md b/doc/src/engine/disable.md
new file mode 100644
index 00000000..f34e763c
--- /dev/null
+++ b/doc/src/engine/disable.md
@@ -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
+```
diff --git a/doc/src/links.md b/doc/src/links.md
index b7147a50..69a01198 100644
--- a/doc/src/links.md
+++ b/doc/src/links.md
@@ -29,6 +29,7 @@
[package]: {{rootUrl}}/rust/packages/index.md
[packages]: {{rootUrl}}/rust/packages/index.md
[`Scope`]: {{rootUrl}}/rust/scope.md
+[`serde`]: {{rootUrl}}/rust/serde.md
[`type_of()`]: {{rootUrl}}/language/type-of.md
[`to_string()`]: {{rootUrl}}/language/values-and-types.md
@@ -101,3 +102,7 @@
[`OptimizationLevel::Full`]: {{rootUrl}}/engine/optimize/optimize-levels.md
[`OptimizationLevel::Simple`]: {{rootUrl}}/engine/optimize/optimize-levels.md
[`OptimizationLevel::None`]: {{rootUrl}}/engine/optimize/optimize-levels.md
+
+[disable keywords and operators]: {{rootUrl}}/engine/disable.md
+[custom operator]: {{rootUrl}}/engine/custom-op.md
+[custom operators]: {{rootUrl}}/engine/custom-op.md
diff --git a/doc/src/rust/serde.md b/doc/src/rust/serde.md
new file mode 100644
index 00000000..de59dc34
--- /dev/null
+++ b/doc/src/rust/serde.md
@@ -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,
+ 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::() == 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,
+ 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)?;
+```
diff --git a/doc/src/start/examples/rust.md b/doc/src/start/examples/rust.md
index 1803c49b..412589e5 100644
--- a/doc/src/start/examples/rust.md
+++ b/doc/src/start/examples/rust.md
@@ -5,17 +5,18 @@ Rust Examples
A number of examples can be found in the `examples` folder:
-| Example | Description |
-| ---------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------- |
-| [`arrays_and_structs`](https://github.com/jonathandturner/rhai/tree/master/examples/arrays_and_structs.rs) | Shows how to register a custom Rust type and using [arrays] on it. |
-| [`custom_types_and_methods`](https://github.com/jonathandturner/rhai/tree/master/examples/custom_types_and_methods.rs) | Shows how to register a custom Rust type and methods for it. |
-| [`hello`](https://github.com/jonathandturner/rhai/tree/master/examples/hello.rs) | Simple example that evaluates an expression and prints the result. |
-| [`no_std`](https://github.com/jonathandturner/rhai/tree/master/examples/no_std.rs) | Example to test out `no-std` builds. |
-| [`reuse_scope`](https://github.com/jonathandturner/rhai/tree/master/examples/reuse_scope.rs) | Evaluates two pieces of code in separate runs, but using a common [`Scope`]. |
-| [`rhai_runner`](https://github.com/jonathandturner/rhai/tree/master/examples/rhai_runner.rs) | Runs each filename passed to it as a Rhai script. |
-| [`simple_fn`](https://github.com/jonathandturner/rhai/tree/master/examples/simple_fn.rs) | Shows how to register a simple function. |
-| [`strings`](https://github.com/jonathandturner/rhai/tree/master/examples/strings.rs) | Shows different ways to register functions taking string arguments. |
-| [`repl`](https://github.com/jonathandturner/rhai/tree/master/examples/repl.rs) | A simple REPL, interactively evaluate statements from stdin. |
+| Example | Description |
+| ---------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------- |
+| [`arrays_and_structs`](https://github.com/jonathandturner/rhai/tree/master/examples/arrays_and_structs.rs) | Shows how to register a custom Rust type and using [arrays] on it. |
+| [`custom_types_and_methods`](https://github.com/jonathandturner/rhai/tree/master/examples/custom_types_and_methods.rs) | Shows how to register a custom Rust type and methods for it. |
+| [`hello`](https://github.com/jonathandturner/rhai/tree/master/examples/hello.rs) | Simple example that evaluates an expression and prints the result. |
+| [`no_std`](https://github.com/jonathandturner/rhai/tree/master/examples/no_std.rs) | Example to test out `no-std` builds. |
+| [`reuse_scope`](https://github.com/jonathandturner/rhai/tree/master/examples/reuse_scope.rs) | Evaluates two pieces of code in separate runs, but using a common [`Scope`]. |
+| [`rhai_runner`](https://github.com/jonathandturner/rhai/tree/master/examples/rhai_runner.rs) | Runs each filename passed to it as a Rhai script. |
+| [`serde`](https://github.com/jonathandturner/rhai/tree/master/examples/serde.rs) | Example to serialize and deserialize Rust types with [`serde`](https://crates.io/crates/serde). The [`serde`] feature is required to run. |
+| [`simple_fn`](https://github.com/jonathandturner/rhai/tree/master/examples/simple_fn.rs) | Shows how to register a simple function. |
+| [`strings`](https://github.com/jonathandturner/rhai/tree/master/examples/strings.rs) | Shows different ways to register functions taking string arguments. |
+| [`repl`](https://github.com/jonathandturner/rhai/tree/master/examples/repl.rs) | A simple REPL, interactively evaluate statements from stdin. |
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).
diff --git a/doc/src/start/features.md b/doc/src/start/features.md
index b4a415a9..77a17e10 100644
--- a/doc/src/start/features.md
+++ b/doc/src/start/features.md
@@ -24,6 +24,7 @@ more control over what a script can (or cannot) do.
| `no_function` | Disable script-defined [functions]. |
| `no_module` | Disable loading external [modules]. |
| `no_std` | Build for `no-std`. Notice that additional dependencies will be pulled in to replace `std` features. |
+| `serde` | Enable serialization/deserialization via [`serde`]. Notice that the [`serde`](https://crates.io/crates/serde) crate will be pulled in together with its dependencies. |
| `internals` | Expose internal data structures (e.g. [`AST`] nodes). Beware that Rhai internals are volatile and may change from version to version. |
diff --git a/examples/serde.rs b/examples/serde.rs
new file mode 100644
index 00000000..3cb68459
--- /dev/null
+++ b/examples/serde.rs
@@ -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,
+ 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::());
+ 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);
+ }
+}
diff --git a/src/any.rs b/src/any.rs
index 7e8b8072..bd21404b 100644
--- a/src/any.rs
+++ b/src/any.rs
@@ -196,13 +196,38 @@ impl Dynamic {
Union::FnPtr(_) => "Fn",
#[cfg(not(feature = "no_std"))]
- #[cfg(not(target_arch = "wasm32"))]
Union::Variant(value) if value.is::() => "timestamp",
Union::Variant(value) => (***value).type_name(),
}
}
}
+/// Map the name of a standard type into a friendly form.
+pub(crate) fn map_std_type_name(name: &str) -> &str {
+ if name == type_name::() {
+ "string"
+ } else if name == type_name::() {
+ "string"
+ } else if name == type_name::<&str>() {
+ "string"
+ } else if name == type_name::() {
+ "Fn"
+ } else if name == type_name::() {
+ "timestamp"
+ } else {
+ #[cfg(not(feature = "no_index"))]
+ if name == type_name::() {
+ return "array";
+ }
+ #[cfg(not(feature = "no_object"))]
+ if name == type_name::() {
+ return "map";
+ }
+
+ name
+ }
+}
+
impl fmt::Display for Dynamic {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match &self.0 {
@@ -220,7 +245,6 @@ impl fmt::Display for Dynamic {
Union::FnPtr(value) => fmt::Display::fmt(value, f),
#[cfg(not(feature = "no_std"))]
- #[cfg(not(target_arch = "wasm32"))]
Union::Variant(value) if value.is::() => write!(f, ""),
Union::Variant(_) => write!(f, "?"),
}
@@ -244,7 +268,6 @@ impl fmt::Debug for Dynamic {
Union::FnPtr(value) => fmt::Display::fmt(value, f),
#[cfg(not(feature = "no_std"))]
- #[cfg(not(target_arch = "wasm32"))]
Union::Variant(value) if value.is::() => write!(f, ""),
Union::Variant(_) => write!(f, ""),
}
@@ -323,10 +346,8 @@ impl Dynamic {
}
#[cfg(not(feature = "no_float"))]
- {
- if let Some(result) = ::downcast_ref::(&value) {
- return result.clone().into();
- }
+ if let Some(result) = ::downcast_ref::(&value) {
+ return result.clone().into();
}
let mut boxed = Box::new(value);
diff --git a/src/api.rs b/src/api.rs
index 2f0e3e19..6f49ad73 100644
--- a/src/api.rs
+++ b/src/api.rs
@@ -1,10 +1,7 @@
//! Module that defines the extern API of `Engine`.
use crate::any::{Dynamic, Variant};
-use crate::engine::{
- get_script_function_by_signature, make_getter, make_setter, Engine, Imports, State, FN_IDX_GET,
- FN_IDX_SET,
-};
+use crate::engine::{make_getter, make_setter, Engine, Imports, State, FN_IDX_GET, FN_IDX_SET};
use crate::error::ParseError;
use crate::fn_call::FuncArgs;
use crate::fn_native::{IteratorFn, SendSync};
@@ -19,6 +16,9 @@ use crate::utils::StaticVec;
#[cfg(not(feature = "no_object"))]
use crate::engine::Map;
+#[cfg(not(feature = "no_function"))]
+use crate::engine::get_script_function_by_signature;
+
use crate::stdlib::{
any::{type_name, TypeId},
boxed::Box,
@@ -118,8 +118,13 @@ impl Engine {
/// ```
#[cfg(not(feature = "no_object"))]
pub fn register_type_with_name(&mut self, name: &str) {
+ if self.type_names.is_none() {
+ self.type_names = Some(Default::default());
+ }
// Add the pretty-print type name into the map
self.type_names
+ .as_mut()
+ .unwrap()
.insert(type_name::().to_string(), name.to_string());
}
@@ -548,7 +553,7 @@ impl Engine {
scripts: &[&str],
optimization_level: OptimizationLevel,
) -> Result {
- let stream = lex(scripts, self.max_string_size);
+ let stream = lex(scripts, self);
self.parse(&mut stream.peekable(), scope, optimization_level)
}
@@ -673,7 +678,7 @@ impl Engine {
// Trims the JSON string and add a '#' in front
let scripts = ["#", json.trim()];
- let stream = lex(&scripts, self.max_string_size);
+ let stream = lex(&scripts, self);
let ast =
self.parse_global_expr(&mut stream.peekable(), &scope, OptimizationLevel::None)?;
@@ -754,7 +759,7 @@ impl Engine {
script: &str,
) -> Result {
let scripts = [script];
- let stream = lex(&scripts, self.max_string_size);
+ let stream = lex(&scripts, self);
{
let mut peekable = stream.peekable();
self.parse_global_expr(&mut peekable, scope, self.optimization_level)
@@ -909,7 +914,7 @@ impl Engine {
script: &str,
) -> Result> {
let scripts = [script];
- let stream = lex(&scripts, self.max_string_size);
+ let stream = lex(&scripts, self);
// No need to optimize a lone expression
let ast = self.parse_global_expr(&mut stream.peekable(), scope, OptimizationLevel::None)?;
@@ -976,11 +981,12 @@ impl Engine {
let mut mods = Imports::new();
let (result, _) = self.eval_ast_with_scope_raw(scope, &mut mods, ast)?;
- let return_type = self.map_type_name(result.type_name());
+ let typ = self.map_type_name(result.type_name());
return result.try_cast::().ok_or_else(|| {
Box::new(EvalAltResult::ErrorMismatchOutputType(
- return_type.into(),
+ self.map_type_name(type_name::()).into(),
+ typ.into(),
Position::none(),
))
});
@@ -1041,7 +1047,7 @@ impl Engine {
script: &str,
) -> Result<(), Box> {
let scripts = [script];
- let stream = lex(&scripts, self.max_string_size);
+ let stream = lex(&scripts, self);
let ast = self.parse(&mut stream.peekable(), scope, self.optimization_level)?;
self.consume_ast_with_scope(scope, &ast)
}
@@ -1123,11 +1129,12 @@ impl Engine {
let mut arg_values = args.into_vec();
let result = self.call_fn_dynamic_raw(scope, ast, name, arg_values.as_mut())?;
- let return_type = self.map_type_name(result.type_name());
+ let typ = self.map_type_name(result.type_name());
return result.try_cast().ok_or_else(|| {
Box::new(EvalAltResult::ErrorMismatchOutputType(
- return_type.into(),
+ self.map_type_name(type_name::()).into(),
+ typ.into(),
Position::none(),
))
});
@@ -1187,6 +1194,7 @@ impl Engine {
/// This is to avoid unnecessarily cloning the arguments.
/// Do not use the arguments after this call. If they are needed afterwards,
/// clone them _before_ calling this function.
+ #[cfg(not(feature = "no_function"))]
pub(crate) fn call_fn_dynamic_raw(
&self,
scope: &mut Scope,
@@ -1238,6 +1246,7 @@ impl Engine {
mut ast: AST,
optimization_level: OptimizationLevel,
) -> AST {
+ #[cfg(not(feature = "no_function"))]
let lib = ast
.lib()
.iter_fn()
@@ -1245,6 +1254,9 @@ impl Engine {
.map(|(_, _, _, f)| f.get_fn_def().clone())
.collect();
+ #[cfg(feature = "no_function")]
+ let lib = Default::default();
+
let stmt = mem::take(ast.statements_mut());
optimize_into_ast(self, scope, stmt, lib, optimization_level)
}
diff --git a/src/engine.rs b/src/engine.rs
index 0a46bc1b..409b18de 100644
--- a/src/engine.rs
+++ b/src/engine.rs
@@ -1,12 +1,12 @@
//! Main module defining the script evaluation `Engine`.
-use crate::any::{Dynamic, Union, Variant};
+use crate::any::{map_std_type_name, Dynamic, Union, Variant};
use crate::calc_fn_hash;
use crate::error::ParseErrorType;
use crate::fn_native::{CallableFunction, Callback, FnCallArgs, FnPtr};
use crate::module::{resolvers, Module, ModuleRef, ModuleResolver};
use crate::optimize::OptimizationLevel;
-use crate::packages::{Package, PackageLibrary, PackagesCollection, StandardPackage};
+use crate::packages::{Package, PackagesCollection, StandardPackage};
use crate::parser::{Expr, FnAccess, ImmutableString, ReturnType, ScriptFnDef, Stmt, AST, INT};
use crate::r#unsafe::unsafe_cast_var_name_to_lifetime;
use crate::result::EvalAltResult;
@@ -18,10 +18,10 @@ use crate::utils::StaticVec;
use crate::parser::FLOAT;
use crate::stdlib::{
- any::TypeId,
+ any::{type_name, TypeId},
borrow::Cow,
boxed::Box,
- collections::HashMap,
+ collections::{HashMap, HashSet},
format,
iter::{empty, once},
mem,
@@ -35,7 +35,7 @@ use crate::stdlib::{
#[cfg(not(feature = "no_index"))]
pub type Array = Vec;
-/// Hash map of `Dynamic` values with `String` keys.
+/// Hash map of `Dynamic` values with `ImmutableString` keys.
///
/// Not available under the `no_object` feature.
#[cfg(not(feature = "no_object"))]
@@ -216,6 +216,7 @@ impl State {
}
/// Get a script-defined function definition from a module.
+#[cfg(not(feature = "no_function"))]
pub fn get_script_function_by_signature<'a>(
module: &'a Module,
name: &str,
@@ -265,7 +266,12 @@ pub struct Engine {
pub(crate) module_resolver: Option>,
/// A hashmap mapping type names to pretty-print names.
- pub(crate) type_names: HashMap,
+ pub(crate) type_names: Option>,
+
+ /// A hashset containing symbols to disable.
+ pub(crate) disabled_symbols: Option>,
+ /// A hashset containing custom keywords and precedence to recognize.
+ pub(crate) custom_keywords: Option>,
/// Callback closure for implementing the `print` command.
pub(crate) print: Callback,
@@ -312,7 +318,9 @@ impl Default for Engine {
#[cfg(any(feature = "no_module", feature = "no_std", target_arch = "wasm32",))]
module_resolver: None,
- type_names: Default::default(),
+ type_names: None,
+ disabled_symbols: None,
+ custom_keywords: None,
// default print/debug implementations
print: Box::new(default_print),
@@ -352,17 +360,14 @@ pub fn make_getter(id: &str) -> String {
/// Extract the property name from a getter function name.
fn extract_prop_from_getter(fn_name: &str) -> Option<&str> {
#[cfg(not(feature = "no_object"))]
- {
- if fn_name.starts_with(FN_GET) {
- Some(&fn_name[FN_GET.len()..])
- } else {
- None
- }
- }
- #[cfg(feature = "no_object")]
- {
+ if fn_name.starts_with(FN_GET) {
+ Some(&fn_name[FN_GET.len()..])
+ } else {
None
}
+
+ #[cfg(feature = "no_object")]
+ None
}
/// Make setter function
@@ -373,17 +378,14 @@ pub fn make_setter(id: &str) -> String {
/// Extract the property name from a setter function name.
fn extract_prop_from_setter(fn_name: &str) -> Option<&str> {
#[cfg(not(feature = "no_object"))]
- {
- if fn_name.starts_with(FN_SET) {
- Some(&fn_name[FN_SET.len()..])
- } else {
- None
- }
- }
- #[cfg(feature = "no_object")]
- {
+ if fn_name.starts_with(FN_SET) {
+ Some(&fn_name[FN_SET.len()..])
+ } else {
None
}
+
+ #[cfg(feature = "no_object")]
+ None
}
/// Print/debug to stdout
@@ -497,7 +499,10 @@ impl Engine {
global_module: Default::default(),
module_resolver: None,
- type_names: Default::default(),
+ type_names: None,
+ disabled_symbols: None,
+ custom_keywords: None,
+
print: Box::new(|_| {}),
debug: Box::new(|_| {}),
progress: None,
@@ -519,158 +524,6 @@ impl Engine {
}
}
- /// Load a new package into the `Engine`.
- ///
- /// When searching for functions, packages loaded later are preferred.
- /// In other words, loaded packages are searched in reverse order.
- pub fn load_package(&mut self, package: PackageLibrary) {
- // Push the package to the top - packages are searched in reverse order
- self.packages.push(package);
- }
-
- /// Load a new package into the `Engine`.
- ///
- /// When searching for functions, packages loaded later are preferred.
- /// In other words, loaded packages are searched in reverse order.
- pub fn load_packages(&mut self, package: PackageLibrary) {
- // Push the package to the top - packages are searched in reverse order
- self.packages.push(package);
- }
-
- /// Control whether and how the `Engine` will optimize an AST after compilation.
- ///
- /// Not available under the `no_optimize` feature.
- #[cfg(not(feature = "no_optimize"))]
- pub fn set_optimization_level(&mut self, optimization_level: OptimizationLevel) {
- self.optimization_level = optimization_level
- }
-
- /// The current optimization level.
- /// It controls whether and how the `Engine` will optimize an AST after compilation.
- ///
- /// Not available under the `no_optimize` feature.
- #[cfg(not(feature = "no_optimize"))]
- pub fn optimization_level(&self) -> OptimizationLevel {
- self.optimization_level
- }
-
- /// Set the maximum levels of function calls allowed for a script in order to avoid
- /// infinite recursion and stack overflows.
- #[cfg(not(feature = "unchecked"))]
- pub fn set_max_call_levels(&mut self, levels: usize) {
- self.max_call_stack_depth = levels
- }
-
- /// The maximum levels of function calls allowed for a script.
- #[cfg(not(feature = "unchecked"))]
- pub fn max_call_levels(&self) -> usize {
- self.max_call_stack_depth
- }
-
- /// Set the maximum number of operations allowed for a script to run to avoid
- /// consuming too much resources (0 for unlimited).
- #[cfg(not(feature = "unchecked"))]
- pub fn set_max_operations(&mut self, operations: u64) {
- self.max_operations = if operations == u64::MAX {
- 0
- } else {
- operations
- };
- }
-
- /// The maximum number of operations allowed for a script to run (0 for unlimited).
- #[cfg(not(feature = "unchecked"))]
- pub fn max_operations(&self) -> u64 {
- self.max_operations
- }
-
- /// Set the maximum number of imported modules allowed for a script.
- #[cfg(not(feature = "unchecked"))]
- pub fn set_max_modules(&mut self, modules: usize) {
- self.max_modules = modules;
- }
-
- /// The maximum number of imported modules allowed for a script.
- #[cfg(not(feature = "unchecked"))]
- pub fn max_modules(&self) -> usize {
- self.max_modules
- }
-
- /// Set the depth limits for expressions (0 for unlimited).
- #[cfg(not(feature = "unchecked"))]
- pub fn set_max_expr_depths(&mut self, max_expr_depth: usize, max_function_expr_depth: usize) {
- self.max_expr_depth = if max_expr_depth == usize::MAX {
- 0
- } else {
- max_expr_depth
- };
- self.max_function_expr_depth = if max_function_expr_depth == usize::MAX {
- 0
- } else {
- max_function_expr_depth
- };
- }
-
- /// The depth limit for expressions (0 for unlimited).
- #[cfg(not(feature = "unchecked"))]
- pub fn max_expr_depth(&self) -> usize {
- self.max_expr_depth
- }
-
- /// The depth limit for expressions in functions (0 for unlimited).
- #[cfg(not(feature = "unchecked"))]
- pub fn max_function_expr_depth(&self) -> usize {
- self.max_function_expr_depth
- }
-
- /// Set the maximum length of strings (0 for unlimited).
- #[cfg(not(feature = "unchecked"))]
- pub fn set_max_string_size(&mut self, max_size: usize) {
- self.max_string_size = if max_size == usize::MAX { 0 } else { max_size };
- }
-
- /// The maximum length of strings (0 for unlimited).
- #[cfg(not(feature = "unchecked"))]
- pub fn max_string_size(&self) -> usize {
- self.max_string_size
- }
-
- /// Set the maximum length of arrays (0 for unlimited).
- #[cfg(not(feature = "unchecked"))]
- #[cfg(not(feature = "no_index"))]
- pub fn set_max_array_size(&mut self, max_size: usize) {
- self.max_array_size = if max_size == usize::MAX { 0 } else { max_size };
- }
-
- /// The maximum length of arrays (0 for unlimited).
- #[cfg(not(feature = "unchecked"))]
- #[cfg(not(feature = "no_index"))]
- pub fn max_array_size(&self) -> usize {
- self.max_array_size
- }
-
- /// Set the maximum length of object maps (0 for unlimited).
- #[cfg(not(feature = "unchecked"))]
- #[cfg(not(feature = "no_object"))]
- pub fn set_max_map_size(&mut self, max_size: usize) {
- self.max_map_size = if max_size == usize::MAX { 0 } else { max_size };
- }
-
- /// The maximum length of object maps (0 for unlimited).
- #[cfg(not(feature = "unchecked"))]
- #[cfg(not(feature = "no_object"))]
- pub fn max_map_size(&self) -> usize {
- self.max_map_size
- }
-
- /// Set the module resolution service used by the `Engine`.
- ///
- /// Not available under the `no_module` feature.
- #[cfg(not(feature = "no_module"))]
- pub fn set_module_resolver(&mut self, resolver: Option) {
- self.module_resolver = resolver.map(|f| Box::new(f) as Box);
- }
-
/// Universal method for calling functions either registered with the `Engine` or written in Rhai.
/// Position in `EvalAltResult` is None and must be set afterwards.
///
@@ -700,12 +553,10 @@ impl Engine {
// Check for stack overflow
#[cfg(not(feature = "no_function"))]
#[cfg(not(feature = "unchecked"))]
- {
- if level > self.max_call_stack_depth {
- return Err(Box::new(
- EvalAltResult::ErrorStackOverflow(Position::none()),
- ));
- }
+ if level > self.max_call_stack_depth {
+ return Err(Box::new(
+ EvalAltResult::ErrorStackOverflow(Position::none()),
+ ));
}
let mut this_copy: Dynamic = Default::default();
@@ -767,22 +618,23 @@ impl Engine {
.or_else(|| self.packages.get_fn(hash_fn));
if let Some(func) = func {
- // Calling pure function but the first argument is a reference?
- normalize_first_arg(
- is_ref && (func.is_pure() || (func.is_script() && !is_method)),
- &mut this_copy,
- &mut old_this_ptr,
- args,
- );
+ #[cfg(not(feature = "no_function"))]
+ let need_normalize = is_ref && (func.is_pure() || (func.is_script() && !is_method));
+ #[cfg(feature = "no_function")]
+ let need_normalize = is_ref && func.is_pure();
+ // Calling pure function but the first argument is a reference?
+ normalize_first_arg(need_normalize, &mut this_copy, &mut old_this_ptr, args);
+
+ #[cfg(not(feature = "no_function"))]
if func.is_script() {
// Run scripted function
let fn_def = func.get_fn_def();
// Method call of script function - map first argument to `this`
- if is_method {
+ return if is_method {
let (first, rest) = args.split_at_mut(1);
- return Ok((
+ Ok((
self.call_script_fn(
scope,
mods,
@@ -795,7 +647,7 @@ impl Engine {
level,
)?,
false,
- ));
+ ))
} else {
let result = self.call_script_fn(
scope, mods, state, lib, &mut None, fn_name, fn_def, args, level,
@@ -804,40 +656,42 @@ impl Engine {
// Restore the original reference
restore_first_arg(old_this_ptr, args);
- return Ok((result, false));
+ Ok((result, false))
};
- } else {
- // Run external function
- let result = func.get_native_fn()(self, args)?;
-
- // 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::()).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::()).into(),
+ typ.into(),
+ Position::none(),
+ ))
+ })?)
+ .into(),
+ false,
+ ),
+ _ => (result, func.is_method()),
+ });
}
// See if it is built in.
@@ -1064,8 +918,12 @@ impl Engine {
lib: &Module,
script: &Dynamic,
) -> Result> {
- let script = script.as_str().map_err(|type_name| {
- EvalAltResult::ErrorMismatchOutputType(type_name.into(), Position::none())
+ let script = script.as_str().map_err(|typ| {
+ EvalAltResult::ErrorMismatchOutputType(
+ self.map_type_name(type_name::()).into(),
+ typ.into(),
+ Position::none(),
+ )
})?;
// Compile the script text
@@ -1873,9 +1731,10 @@ impl Engine {
self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)?;
return arg_value
.take_immutable_string()
- .map_err(|type_name| {
+ .map_err(|typ| {
Box::new(EvalAltResult::ErrorMismatchOutputType(
- type_name.into(),
+ self.map_type_name(type_name::()).into(),
+ typ.into(),
expr.position(),
))
})
@@ -2009,6 +1868,7 @@ impl Engine {
};
match func {
+ #[cfg(not(feature = "no_function"))]
Ok(f) if f.is_script() => {
let args = args.as_mut();
let fn_def = f.get_fn_def();
@@ -2335,21 +2195,19 @@ impl Engine {
.try_cast::()
{
#[cfg(not(feature = "no_module"))]
- {
- if let Some(resolver) = &self.module_resolver {
- let mut module = resolver.resolve(self, &path, expr.position())?;
- module.index_all_sub_modules();
- mods.push((name.clone().into(), module));
+ if let Some(resolver) = &self.module_resolver {
+ let mut module = resolver.resolve(self, &path, expr.position())?;
+ module.index_all_sub_modules();
+ mods.push((name.clone().into(), module));
- state.modules += 1;
+ state.modules += 1;
- Ok(Default::default())
- } else {
- Err(Box::new(EvalAltResult::ErrorModuleNotFound(
- path.to_string(),
- expr.position(),
- )))
- }
+ Ok(Default::default())
+ } else {
+ Err(Box::new(EvalAltResult::ErrorModuleNotFound(
+ path.to_string(),
+ expr.position(),
+ )))
}
#[cfg(feature = "no_module")]
@@ -2408,7 +2266,13 @@ impl Engine {
let mut maps = 0;
arr.iter().for_each(|value| match value {
- Dynamic(Union::Array(_)) | Dynamic(Union::Map(_)) => {
+ Dynamic(Union::Array(_)) => {
+ let (a, m, _) = calc_size(value);
+ arrays += a;
+ maps += m;
+ }
+ #[cfg(not(feature = "no_object"))]
+ Dynamic(Union::Map(_)) => {
let (a, m, _) = calc_size(value);
arrays += a;
maps += m;
@@ -2424,7 +2288,13 @@ impl Engine {
let mut maps = 0;
map.values().for_each(|value| match value {
- Dynamic(Union::Array(_)) | Dynamic(Union::Map(_)) => {
+ #[cfg(not(feature = "no_index"))]
+ Dynamic(Union::Array(_)) => {
+ let (a, m, _) = calc_size(value);
+ arrays += a;
+ maps += m;
+ }
+ Dynamic(Union::Map(_)) => {
let (a, m, _) = calc_size(value);
arrays += a;
maps += m;
@@ -2488,13 +2358,11 @@ impl Engine {
state.operations += 1;
#[cfg(not(feature = "unchecked"))]
- {
- // Guard against too many operations
- if self.max_operations > 0 && state.operations > self.max_operations {
- return Err(Box::new(EvalAltResult::ErrorTooManyOperations(
- Position::none(),
- )));
- }
+ // Guard against too many operations
+ if self.max_operations > 0 && state.operations > self.max_operations {
+ return Err(Box::new(EvalAltResult::ErrorTooManyOperations(
+ Position::none(),
+ )));
}
// Report progress - only in steps
@@ -2511,9 +2379,9 @@ impl Engine {
/// Map a type_name into a pretty-print name
pub(crate) fn map_type_name<'a>(&'a self, name: &'a str) -> &'a str {
self.type_names
- .get(name)
- .map(String::as_str)
- .unwrap_or(name)
+ .as_ref()
+ .and_then(|t| t.get(name).map(String::as_str))
+ .unwrap_or(map_std_type_name(name))
}
}
@@ -2620,26 +2488,24 @@ fn run_builtin_binary_op(
}
#[cfg(not(feature = "no_float"))]
- {
- if args_type == TypeId::of::() {
- let x = *x.downcast_ref::().unwrap();
- let y = *y.downcast_ref::().unwrap();
+ if args_type == TypeId::of::() {
+ let x = *x.downcast_ref::().unwrap();
+ let y = *y.downcast_ref::().unwrap();
- 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 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())),
- _ => (),
- }
+ 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 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())),
+ _ => (),
}
}
@@ -2716,20 +2582,18 @@ fn run_builtin_op_assignment(
}
#[cfg(not(feature = "no_float"))]
- {
- if args_type == TypeId::of::() {
- let x = x.downcast_mut::().unwrap();
- let y = *y.downcast_ref::().unwrap();
+ if args_type == TypeId::of::() {
+ let x = x.downcast_mut::().unwrap();
+ let y = *y.downcast_ref::().unwrap();
- 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 = pow_f_f(*x, y)?)),
- _ => (),
- }
+ 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 = pow_f_f(*x, y)?)),
+ _ => (),
}
}
diff --git a/src/error.rs b/src/error.rs
index 58423ef9..6b3308ba 100644
--- a/src/error.rs
+++ b/src/error.rs
@@ -5,7 +5,6 @@ use crate::token::Position;
use crate::stdlib::{
boxed::Box,
- char,
error::Error,
fmt,
string::{String, ToString},
@@ -15,8 +14,8 @@ use crate::stdlib::{
#[derive(Debug, Eq, PartialEq, Clone, Hash)]
#[non_exhaustive]
pub enum LexError {
- /// An unexpected character is encountered when tokenizing the script text.
- UnexpectedChar(char),
+ /// An unexpected symbol is encountered when tokenizing the script text.
+ UnexpectedInput(String),
/// A string literal is not terminated before a new-line or EOF.
UnterminatedString,
/// An identifier is in an invalid format.
@@ -38,7 +37,7 @@ impl Error for LexError {}
impl fmt::Display for LexError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
- Self::UnexpectedChar(c) => write!(f, "Unexpected '{}'", c),
+ Self::UnexpectedInput(s) => write!(f, "Unexpected '{}'", s),
Self::MalformedEscapeSequence(s) => write!(f, "Invalid escape sequence: '{}'", s),
Self::MalformedNumber(s) => write!(f, "Invalid number: '{}'", s),
Self::MalformedChar(s) => write!(f, "Invalid character: '{}'", s),
@@ -49,7 +48,7 @@ impl fmt::Display for LexError {
"Length of string literal exceeds the maximum limit ({})",
max
),
- Self::ImproperSymbol(s) => write!(f, "{}", s),
+ Self::ImproperSymbol(s) => f.write_str(s),
}
}
}
@@ -185,18 +184,16 @@ impl fmt::Display for ParseErrorType {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::BadInput(s) | ParseErrorType::MalformedCallExpr(s) => {
- write!(f, "{}", if s.is_empty() { self.desc() } else { s })
+ f.write_str(if s.is_empty() { self.desc() } else { s })
}
Self::ForbiddenConstantExpr(s) => {
write!(f, "Expecting a constant to assign to '{}'", s)
}
Self::UnknownOperator(s) => write!(f, "{}: '{}'", self.desc(), s),
- Self::MalformedIndexExpr(s) => {
- write!(f, "{}", if s.is_empty() { self.desc() } else { s })
- }
+ Self::MalformedIndexExpr(s) => f.write_str(if s.is_empty() { self.desc() } else { s }),
- Self::MalformedInExpr(s) => write!(f, "{}", if s.is_empty() { self.desc() } else { s }),
+ Self::MalformedInExpr(s) => f.write_str(if s.is_empty() { self.desc() } else { s }),
Self::DuplicatedProperty(s) => {
write!(f, "Duplicated property '{}' for object map literal", s)
@@ -222,12 +219,12 @@ impl fmt::Display for ParseErrorType {
Self::MissingToken(token, s) => write!(f, "Expecting '{}' {}", token, s),
- Self::AssignmentToConstant(s) if s.is_empty() => write!(f, "{}", self.desc()),
+ Self::AssignmentToConstant(s) if s.is_empty() => f.write_str(self.desc()),
Self::AssignmentToConstant(s) => write!(f, "Cannot assign to constant '{}'", s),
Self::LiteralTooLarge(typ, max) => {
write!(f, "{} exceeds the maximum limit ({})", typ, max)
}
- _ => write!(f, "{}", self.desc()),
+ _ => f.write_str(self.desc()),
}
}
}
diff --git a/src/fn_native.rs b/src/fn_native.rs
index f3632d1e..b8407b94 100644
--- a/src/fn_native.rs
+++ b/src/fn_native.rs
@@ -28,13 +28,9 @@ pub type Shared = Arc;
/// If the resource is shared (i.e. has other outstanding references), a cloned copy is used.
pub fn shared_make_mut(value: &mut Shared) -> &mut T {
#[cfg(not(feature = "sync"))]
- {
- Rc::make_mut(value)
- }
+ return Rc::make_mut(value);
#[cfg(feature = "sync")]
- {
- Arc::make_mut(value)
- }
+ return Arc::make_mut(value);
}
/// Consume a `Shared` resource, assuming that it is unique (i.e. not shared).
@@ -44,13 +40,9 @@ pub fn shared_make_mut(value: &mut Shared) -> &mut T {
/// Panics if the resource is shared (i.e. has other outstanding references).
pub fn shared_take(value: Shared) -> T {
#[cfg(not(feature = "sync"))]
- {
- Rc::try_unwrap(value).map_err(|_| ()).unwrap()
- }
+ return Rc::try_unwrap(value).map_err(|_| ()).unwrap();
#[cfg(feature = "sync")]
- {
- Arc::try_unwrap(value).map_err(|_| ()).unwrap()
- }
+ return Arc::try_unwrap(value).map_err(|_| ()).unwrap();
}
pub type FnCallArgs<'a> = [&'a mut Dynamic];
@@ -122,6 +114,7 @@ pub enum CallableFunction {
/// A plugin-defined function,
Plugin(SharedPluginFunction),
/// A script-defined function.
+ #[cfg(not(feature = "no_function"))]
Script(Shared),
}
@@ -132,6 +125,8 @@ impl fmt::Debug for CallableFunction {
Self::Method(_) => write!(f, "NativeMethod"),
Self::Iterator(_) => write!(f, "NativeIterator"),
Self::Plugin(_) => write!(f, "PluginFunction"),
+
+ #[cfg(not(feature = "no_function"))]
Self::Script(fn_def) => fmt::Debug::fmt(fn_def, f),
}
}
@@ -144,6 +139,8 @@ impl fmt::Display for CallableFunction {
Self::Method(_) => write!(f, "NativeMethod"),
Self::Iterator(_) => write!(f, "NativeIterator"),
Self::Plugin(_) => write!(f, "PluginFunction"),
+
+ #[cfg(not(feature = "no_function"))]
CallableFunction::Script(s) => fmt::Display::fmt(s, f),
}
}
@@ -154,24 +151,34 @@ impl CallableFunction {
pub fn is_pure(&self) -> bool {
match self {
Self::Pure(_) => true,
- Self::Method(_) | Self::Iterator(_) | Self::Script(_) | Self::Plugin(_) => false,
+ Self::Method(_) | Self::Iterator(_) | Self::Plugin(_) => false,
+
+ #[cfg(not(feature = "no_function"))]
+ Self::Script(_) => false,
}
}
/// Is this a native Rust method function?
pub fn is_method(&self) -> bool {
match self {
Self::Method(_) => true,
- Self::Pure(_) | Self::Iterator(_) | Self::Script(_) | Self::Plugin(_) => false,
+ Self::Pure(_) | Self::Iterator(_) | Self::Plugin(_) => false,
+
+ #[cfg(not(feature = "no_function"))]
+ Self::Script(_) => false,
}
}
/// Is this an iterator function?
pub fn is_iter(&self) -> bool {
match self {
Self::Iterator(_) => true,
- Self::Pure(_) | Self::Method(_) | Self::Script(_) | Self::Plugin(_) => false,
+ Self::Pure(_) | Self::Method(_) | Self::Plugin(_) => false,
+
+ #[cfg(not(feature = "no_function"))]
+ Self::Script(_) => false,
}
}
/// Is this a Rhai-scripted function?
+ #[cfg(not(feature = "no_function"))]
pub fn is_script(&self) -> bool {
match self {
Self::Script(_) => true,
@@ -193,7 +200,10 @@ impl CallableFunction {
pub fn get_native_fn(&self) -> &FnAny {
match self {
Self::Pure(f) | Self::Method(f) => f.as_ref(),
- Self::Iterator(_) | Self::Script(_) | Self::Plugin(_) => unreachable!(),
+ Self::Iterator(_) | Self::Plugin(_) => unreachable!(),
+
+ #[cfg(not(feature = "no_function"))]
+ Self::Script(_) => unreachable!(),
}
}
/// Get a shared reference to a script-defined function definition.
@@ -201,6 +211,7 @@ impl CallableFunction {
/// # Panics
///
/// Panics if the `CallableFunction` is not `Script`.
+ #[cfg(not(feature = "no_function"))]
pub fn get_shared_fn_def(&self) -> Shared {
match self {
Self::Pure(_) | Self::Method(_) | Self::Iterator(_) | Self::Plugin(_) => unreachable!(),
@@ -212,6 +223,7 @@ impl CallableFunction {
/// # Panics
///
/// Panics if the `CallableFunction` is not `Script`.
+ #[cfg(not(feature = "no_function"))]
pub fn get_fn_def(&self) -> &ScriptFnDef {
match self {
Self::Pure(_) | Self::Method(_) | Self::Iterator(_) | Self::Plugin(_) => unreachable!(),
@@ -226,7 +238,10 @@ impl CallableFunction {
pub fn get_iter_fn(&self) -> IteratorFn {
match self {
Self::Iterator(f) => *f,
- Self::Pure(_) | Self::Method(_) | Self::Script(_) | Self::Plugin(_) => unreachable!(),
+ Self::Pure(_) | Self::Method(_) | Self::Plugin(_) => unreachable!(),
+
+ #[cfg(not(feature = "no_function"))]
+ Self::Script(_) => unreachable!(),
}
}
/// Get a reference to a plugin function.
@@ -237,7 +252,10 @@ impl CallableFunction {
pub fn get_plugin_fn<'s>(&'s self) -> SharedPluginFunction {
match self {
Self::Plugin(f) => f.clone(),
- Self::Pure(_) | Self::Method(_) | Self::Script(_) | Self::Iterator(_) => unreachable!(),
+ Self::Pure(_) | Self::Method(_) | Self::Iterator(_) => unreachable!(),
+
+ #[cfg(not(feature = "no_function"))]
+ Self::Script(_) => unreachable!(),
}
}
/// Create a new `CallableFunction::Pure`.
@@ -268,12 +286,14 @@ impl From for CallableFunction {
}
}
+#[cfg(not(feature = "no_function"))]
impl From for CallableFunction {
fn from(func: ScriptFnDef) -> Self {
Self::Script(func.into())
}
}
+#[cfg(not(feature = "no_function"))]
impl From> for CallableFunction {
fn from(func: Shared) -> Self {
Self::Script(func)
diff --git a/src/lib.rs b/src/lib.rs
index 6e441424..5cc54d15 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -63,6 +63,7 @@
//! | `only_i64` | Set the system integer type to `i64` and disable all other integer types. `INT` is set to `i64`. |
//! | `no_std` | Build for `no-std`. Notice that additional dependencies will be pulled in to replace `std` features. |
//! | `sync` | Restrict all values types to those that are `Send + Sync`. Under this feature, `Engine`, `Scope` and [`AST`] are all `Send + Sync`. |
+//! | `serde` | Enable serialization/deserialization via `serde`. Notice that the [`serde`](https://crates.io/crates/serde) crate will be pulled in together with its dependencies. |
//! | `internals` | Expose internal data structures (beware they may be volatile from version to version). |
//!
//! See [The Rhai Book](https://schungx.github.io/rhai) for details on the Rhai script engine and language.
@@ -90,6 +91,9 @@ pub mod plugin;
mod plugin;
mod result;
mod scope;
+#[cfg(feature = "serde")]
+mod serde;
+mod settings;
mod stdlib;
mod token;
mod r#unsafe;
@@ -126,11 +130,28 @@ pub use parser::FLOAT;
pub use module::ModuleResolver;
/// Module containing all built-in _module resolvers_ available to Rhai.
+///
+/// Not available under the `no_module` feature.
#[cfg(not(feature = "no_module"))]
pub mod module_resolvers {
pub use crate::module::resolvers::*;
}
+/// Serialization support for [`serde`](https://crates.io/crates/serde).
+///
+/// Requires the `serde` feature.
+#[cfg(feature = "serde")]
+pub mod ser {
+ pub use crate::serde::ser::to_dynamic;
+}
+/// Deserialization support for [`serde`](https://crates.io/crates/serde).
+///
+/// Requires the `serde` feature.
+#[cfg(feature = "serde")]
+pub mod de {
+ pub use crate::serde::de::from_dynamic;
+}
+
#[cfg(not(feature = "no_optimize"))]
pub use optimize::OptimizationLevel;
diff --git a/src/module.rs b/src/module.rs
index c7198d83..7ae8d355 100644
--- a/src/module.rs
+++ b/src/module.rs
@@ -236,6 +236,7 @@ impl Module {
/// Set a script-defined function into the module.
///
/// If there is an existing function of the same name and number of arguments, it is replaced.
+ #[cfg(not(feature = "no_function"))]
pub(crate) fn set_script_fn(&mut self, fn_def: ScriptFnDef) {
// None + function name + number of arguments.
let hash_script = calc_fn_hash(empty(), &fn_def.name, fn_def.params.len(), empty());
@@ -876,6 +877,7 @@ impl Module {
.functions
.iter()
.filter(|(_, (_, _, _, v))| match v {
+ #[cfg(not(feature = "no_function"))]
CallableFunction::Script(ref f) => {
filter(f.access, f.name.as_str(), f.params.len())
}
@@ -893,6 +895,7 @@ impl Module {
}
/// Filter out the functions, retaining only some based on a filter predicate.
+ #[cfg(not(feature = "no_function"))]
pub(crate) fn retain_functions(&mut self, filter: impl Fn(FnAccess, &str, usize) -> bool) {
self.functions.retain(|_, (_, _, _, v)| match v {
CallableFunction::Script(ref f) => filter(f.access, f.name.as_str(), f.params.len()),
@@ -930,6 +933,7 @@ impl Module {
}
/// Get an iterator over all script-defined functions in the module.
+ #[cfg(not(feature = "no_function"))]
pub fn iter_script_fn<'a>(&'a self) -> impl Iterator- > + 'a {
self.functions
.values()
@@ -1014,6 +1018,7 @@ impl Module {
Public => (),
}
+ #[cfg(not(feature = "no_function"))]
if func.is_script() {
let fn_def = func.get_shared_fn_def();
// Qualifiers + function name + number of arguments.
@@ -1024,20 +1029,21 @@ impl Module {
empty(),
);
functions.push((hash_qualified_script, fn_def.into()));
- } else {
- // Qualified Rust functions are indexed in two steps:
- // 1) Calculate a hash in a similar manner to script-defined functions,
- // i.e. qualifiers + function name + number of arguments.
- let hash_qualified_script =
- calc_fn_hash(qualifiers.iter().map(|&v| v), name, params.len(), empty());
- // 2) Calculate a second hash with no qualifiers, empty function name,
- // zero number of arguments, and the actual list of argument `TypeId`'.s
- let hash_fn_args = calc_fn_hash(empty(), "", 0, params.iter().cloned());
- // 3) The final hash is the XOR of the two hashes.
- let hash_qualified_fn = hash_qualified_script ^ hash_fn_args;
-
- functions.push((hash_qualified_fn, func.clone()));
+ 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()));
}
}
diff --git a/src/optimize.rs b/src/optimize.rs
index c7a1ed71..5676bad4 100644
--- a/src/optimize.rs
+++ b/src/optimize.rs
@@ -551,11 +551,17 @@ fn optimize_expr(expr: Expr, state: &mut State) -> Expr {
// First search in functions lib (can override built-in)
// Cater for both normal function call style and method call style (one additional arguments)
- if state.lib.iter_fn().find(|(_, _, _, f)| {
+ #[cfg(not(feature = "no_function"))]
+ let has_script_fn = state.lib.iter_fn().find(|(_, _, _, f)| {
if !f.is_script() { return false; }
let fn_def = f.get_fn_def();
&fn_def.name == name && (args.len()..=args.len() + 1).contains(&fn_def.params.len())
- }).is_some() {
+ }).is_some();
+
+ #[cfg(feature = "no_function")]
+ const has_script_fn: bool = false;
+
+ if has_script_fn {
// A script-defined function overrides the built-in function - do not make the call
x.3 = x.3.into_iter().map(|a| optimize_expr(a, state)).collect();
return Expr::FnCall(x);
diff --git a/src/packages/arithmetic.rs b/src/packages/arithmetic.rs
index 31808a2a..7a548ff0 100644
--- a/src/packages/arithmetic.rs
+++ b/src/packages/arithmetic.rs
@@ -190,42 +190,38 @@ fn modulo_u
(x: T, y: T) -> FuncReturn<::Output> {
// Checked power
pub(crate) fn pow_i_i(x: INT, y: INT) -> FuncReturn {
#[cfg(not(feature = "only_i32"))]
- {
- if y > (u32::MAX as INT) {
- Err(Box::new(EvalAltResult::ErrorArithmetic(
- format!("Integer raised to too large an index: {} ~ {}", x, y),
+ if y > (u32::MAX as INT) {
+ Err(Box::new(EvalAltResult::ErrorArithmetic(
+ 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(),
- )))
- } 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")]
- {
- if y < 0 {
- Err(Box::new(EvalAltResult::ErrorArithmetic(
- format!("Integer raised to a negative index: {} ~ {}", x, y),
+ 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(),
- )))
- } 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)
diff --git a/src/packages/array_basic.rs b/src/packages/array_basic.rs
index bf06ed47..7c7b5681 100644
--- a/src/packages/array_basic.rs
+++ b/src/packages/array_basic.rs
@@ -30,15 +30,13 @@ fn pad(engine: &Engine, args: &mut [&mut Dynamic]) -> FuncRe
// Check if array will be over max size limit
#[cfg(not(feature = "unchecked"))]
- {
- if engine.max_array_size > 0 && len > 0 && (len as usize) > engine.max_array_size {
- return Err(Box::new(EvalAltResult::ErrorDataTooLarge(
- "Size of array".to_string(),
- engine.max_array_size,
- len as usize,
- Position::none(),
- )));
- }
+ if engine.max_array_size > 0 && len > 0 && (len as usize) > engine.max_array_size {
+ return Err(Box::new(EvalAltResult::ErrorDataTooLarge(
+ "Size of array".to_string(),
+ engine.max_array_size,
+ len as usize,
+ Position::none(),
+ )));
}
if len > 0 {
diff --git a/src/packages/fn_basic.rs b/src/packages/fn_basic.rs
index 01807c9e..dde08fd5 100644
--- a/src/packages/fn_basic.rs
+++ b/src/packages/fn_basic.rs
@@ -3,6 +3,7 @@ use crate::fn_native::FnPtr;
def_package!(crate:BasicFnPackage:"Basic Fn functions.", lib, {
lib.set_fn_1_mut("name", |f: &mut FnPtr| Ok(f.get_fn_name().clone()));
- lib.set_getter_fn("name", |f: &mut FnPtr| Ok(f.get_fn_name().clone()));
+ #[cfg(not(feature = "no_object"))]
+ lib.set_getter_fn("name", |f: &mut FnPtr| Ok(f.get_fn_name().clone()));
});
diff --git a/src/packages/string_more.rs b/src/packages/string_more.rs
index 7bc10012..e6acbbb9 100644
--- a/src/packages/string_more.rs
+++ b/src/packages/string_more.rs
@@ -231,15 +231,13 @@ def_package!(crate:MoreStringPackage:"Additional string utilities, including str
// Check if string will be over max size limit
#[cfg(not(feature = "unchecked"))]
- {
- if engine.max_string_size > 0 && len > 0 && (len as usize) > engine.max_string_size {
- return Err(Box::new(EvalAltResult::ErrorDataTooLarge(
- "Length of string".to_string(),
- engine.max_string_size,
- len as usize,
- Position::none(),
- )));
- }
+ if engine.max_string_size > 0 && len > 0 && (len as usize) > engine.max_string_size {
+ return Err(Box::new(EvalAltResult::ErrorDataTooLarge(
+ "Length of string".to_string(),
+ engine.max_string_size,
+ len as usize,
+ Position::none(),
+ )));
}
if len > 0 {
diff --git a/src/packages/time_basic.rs b/src/packages/time_basic.rs
index 9fd4e21f..cbf13f1b 100644
--- a/src/packages/time_basic.rs
+++ b/src/packages/time_basic.rs
@@ -33,17 +33,16 @@ def_package!(crate:BasicTimePackage:"Basic timing utilities.", lib, {
let seconds = (ts2 - ts1).as_secs();
#[cfg(not(feature = "unchecked"))]
- {
- if seconds > (MAX_INT as u64) {
- return Err(Box::new(EvalAltResult::ErrorArithmetic(
- format!(
- "Integer overflow for timestamp duration: {}",
- -(seconds as i64)
- ),
- Position::none(),
- )));
- }
+ if seconds > (MAX_INT as u64) {
+ return Err(Box::new(EvalAltResult::ErrorArithmetic(
+ format!(
+ "Integer overflow for timestamp duration: {}",
+ -(seconds as i64)
+ ),
+ Position::none(),
+ )));
}
+
return Ok(-(seconds as INT));
}
} else {
@@ -55,14 +54,13 @@ def_package!(crate:BasicTimePackage:"Basic timing utilities.", lib, {
let seconds = (ts1 - ts2).as_secs();
#[cfg(not(feature = "unchecked"))]
- {
- if seconds > (MAX_INT as u64) {
- return Err(Box::new(EvalAltResult::ErrorArithmetic(
- format!("Integer overflow for timestamp duration: {}", seconds),
- Position::none(),
- )));
- }
+ if seconds > (MAX_INT as u64) {
+ return Err(Box::new(EvalAltResult::ErrorArithmetic(
+ format!("Integer overflow for timestamp duration: {}", seconds),
+ Position::none(),
+ )));
}
+
return Ok(seconds as INT);
}
}
@@ -86,14 +84,13 @@ def_package!(crate:BasicTimePackage:"Basic timing utilities.", lib, {
let seconds = timestamp.elapsed().as_secs();
#[cfg(not(feature = "unchecked"))]
- {
- if seconds > (MAX_INT as u64) {
- return Err(Box::new(EvalAltResult::ErrorArithmetic(
- format!("Integer overflow for timestamp.elapsed: {}", seconds),
- Position::none(),
- )));
- }
+ if seconds > (MAX_INT as u64) {
+ return Err(Box::new(EvalAltResult::ErrorArithmetic(
+ format!("Integer overflow for timestamp.elapsed: {}", seconds),
+ Position::none(),
+ )));
}
+
Ok(seconds as INT)
}
diff --git a/src/parser.rs b/src/parser.rs
index 906d99de..4db75d94 100644
--- a/src/parser.rs
+++ b/src/parser.rs
@@ -332,36 +332,26 @@ pub enum ReturnType {
Exception,
}
-#[derive(Debug, Clone, Eq, PartialEq, Hash, Default)]
-struct ParseState {
+#[derive(Clone)]
+struct ParseState<'e> {
+ /// Reference to the scripting `Engine`.
+ engine: &'e Engine,
/// Encapsulates a local stack with variable names to simulate an actual runtime scope.
- pub stack: Vec<(String, ScopeEntryType)>,
+ stack: Vec<(String, ScopeEntryType)>,
/// Encapsulates a local stack with variable names to simulate an actual runtime scope.
- pub modules: Vec,
+ modules: Vec,
/// Maximum levels of expression nesting.
- pub max_expr_depth: usize,
- /// Maximum length of a string.
- pub max_string_size: usize,
- /// Maximum length of an array.
- pub max_array_size: usize,
- /// Maximum number of properties in a map.
- pub max_map_size: usize,
+ max_expr_depth: usize,
}
-impl ParseState {
+impl<'e> ParseState<'e> {
/// Create a new `ParseState`.
- pub fn new(
- max_expr_depth: usize,
- max_string_size: usize,
- max_array_size: usize,
- max_map_size: usize,
- ) -> Self {
+ pub fn new(engine: &'e Engine, max_expr_depth: usize) -> Self {
Self {
+ engine,
max_expr_depth,
- max_string_size,
- max_array_size,
- max_map_size,
- ..Default::default()
+ stack: Default::default(),
+ modules: Default::default(),
}
}
/// Find a variable by name in the `ParseState`, searching in reverse.
@@ -1206,10 +1196,10 @@ fn parse_array_literal(
let mut arr = StaticVec::new();
while !input.peek().unwrap().0.is_eof() {
- if state.max_array_size > 0 && arr.len() >= state.max_array_size {
+ if state.engine.max_array_size > 0 && arr.len() >= state.engine.max_array_size {
return Err(PERR::LiteralTooLarge(
"Size of array literal".to_string(),
- state.max_array_size,
+ state.engine.max_array_size,
)
.into_err(input.peek().unwrap().1));
}
@@ -1272,7 +1262,7 @@ fn parse_map_literal(
_ => {
let (name, pos) = match input.next().unwrap() {
(Token::Identifier(s), pos) => (s, pos),
- (Token::StringConst(s), pos) => (s, pos),
+ (Token::StringConstant(s), pos) => (s, pos),
(Token::LexError(err), pos) => return Err(err.into_err(pos)),
(_, pos) if map.is_empty() => {
return Err(PERR::MissingToken(
@@ -1306,10 +1296,10 @@ fn parse_map_literal(
}
};
- if state.max_map_size > 0 && map.len() >= state.max_map_size {
+ if state.engine.max_map_size > 0 && map.len() >= state.engine.max_map_size {
return Err(PERR::LiteralTooLarge(
"Number of properties in object map literal".to_string(),
- state.max_map_size,
+ state.engine.max_map_size,
)
.into_err(input.peek().unwrap().1));
}
@@ -1380,7 +1370,7 @@ fn parse_primary(
#[cfg(not(feature = "no_float"))]
Token::FloatConstant(x) => Expr::FloatConstant(Box::new((x, settings.pos))),
Token::CharConstant(c) => Expr::CharConstant(Box::new((c, settings.pos))),
- Token::StringConst(s) => Expr::StringConstant(Box::new((s.into(), settings.pos))),
+ Token::StringConstant(s) => Expr::StringConstant(Box::new((s.into(), settings.pos))),
Token::Identifier(s) => {
let index = state.find_var(&s);
Expr::Variable(Box::new(((s, settings.pos), None, 0, index)))
@@ -1495,13 +1485,9 @@ fn parse_unary(
.map(|i| Expr::IntegerConstant(Box::new((i, pos))))
.or_else(|| {
#[cfg(not(feature = "no_float"))]
- {
- Some(Expr::FloatConstant(Box::new((-(x.0 as FLOAT), pos))))
- }
+ return Some(Expr::FloatConstant(Box::new((-(x.0 as FLOAT), pos))));
#[cfg(feature = "no_float")]
- {
- None
- }
+ return None;
})
.ok_or_else(|| LexError::MalformedNumber(format!("-{}", x.0)).into_err(pos))
}
@@ -1701,7 +1687,7 @@ fn make_dot_expr(lhs: Expr, rhs: Expr, op_pos: Position) -> Result Expr::Dot(Box::new((lhs, func, op_pos))),
// lhs.rhs
- _ => unreachable!(),
+ (_, rhs) => return Err(PERR::PropertyExpected.into_err(rhs.position())),
})
}
@@ -1870,7 +1856,8 @@ fn parse_binary_op(
loop {
let (current_op, _) = input.peek().unwrap();
- let precedence = current_op.precedence();
+ let custom = state.engine.custom_keywords.as_ref();
+ let precedence = current_op.precedence(custom);
let bind_right = current_op.is_bind_right();
// Bind left to the parent lhs expression if precedence is higher
@@ -1883,7 +1870,7 @@ fn parse_binary_op(
let rhs = parse_unary(input, state, settings)?;
- let next_precedence = input.peek().unwrap().0.precedence();
+ let next_precedence = input.peek().unwrap().0.precedence(custom);
// Bind to right if the next operator has higher precedence
// If same precedence, then check if the operator binds right
@@ -1953,6 +1940,19 @@ fn parse_binary_op(
make_dot_expr(current_lhs, rhs, pos)?
}
+ Token::Custom(s)
+ if state
+ .engine
+ .custom_keywords
+ .as_ref()
+ .map(|c| c.contains_key(&s))
+ .unwrap_or(false) =>
+ {
+ // Accept non-native functions for custom operators
+ let op = (op.0, false, op.2);
+ Expr::FnCall(Box::new((op, None, hash, args, None)))
+ }
+
op_token => return Err(PERR::UnknownOperator(op_token.into()).into_err(pos)),
};
}
@@ -2194,6 +2194,7 @@ fn parse_let(
}
/// Parse an import statement.
+#[cfg(not(feature = "no_module"))]
fn parse_import(
input: &mut TokenStream,
state: &mut ParseState,
@@ -2444,6 +2445,8 @@ fn parse_stmt(
Token::Let => parse_let(input, state, Normal, settings.level_up()),
Token::Const => parse_let(input, state, Constant, settings.level_up()),
+
+ #[cfg(not(feature = "no_module"))]
Token::Import => parse_import(input, state, settings.level_up()),
#[cfg(not(feature = "no_module"))]
@@ -2468,7 +2471,7 @@ fn parse_fn(
settings.ensure_level_within_max_limit(state.max_expr_depth)?;
let name = match input.next().unwrap() {
- (Token::Identifier(s), _) => s,
+ (Token::Identifier(s), _) | (Token::Custom(s), _) => s,
(_, pos) => return Err(PERR::FnMissingName.into_err(pos)),
};
@@ -2556,12 +2559,7 @@ impl Engine {
scope: &Scope,
optimization_level: OptimizationLevel,
) -> Result {
- let mut state = ParseState::new(
- self.max_expr_depth,
- self.max_string_size,
- self.max_array_size,
- self.max_map_size,
- );
+ let mut state = ParseState::new(self, self.max_expr_depth);
let settings = ParseSettings {
allow_if_expr: false,
allow_stmt_expr: false,
@@ -2597,12 +2595,7 @@ impl Engine {
) -> Result<(Vec, Vec), ParseError> {
let mut statements = Vec::::new();
let mut functions = HashMap::::with_hasher(StraightHasherBuilder);
- let mut state = ParseState::new(
- self.max_expr_depth,
- self.max_string_size,
- self.max_array_size,
- self.max_map_size,
- );
+ let mut state = ParseState::new(self, self.max_expr_depth);
while !input.peek().unwrap().0.is_eof() {
// Collect all the function definitions
@@ -2615,14 +2608,8 @@ impl Engine {
};
match input.peek().unwrap() {
- #[cfg(not(feature = "no_function"))]
(Token::Fn, pos) => {
- let mut state = ParseState::new(
- self.max_function_expr_depth,
- self.max_string_size,
- self.max_array_size,
- self.max_map_size,
- );
+ let mut state = ParseState::new(self, self.max_function_expr_depth);
let settings = ParseSettings {
allow_if_expr: true,
allow_stmt_expr: true,
diff --git a/src/result.rs b/src/result.rs
index 06bc80d3..7dd3d6fa 100644
--- a/src/result.rs
+++ b/src/result.rs
@@ -74,8 +74,8 @@ pub enum EvalAltResult {
/// Assignment to a constant variable.
ErrorAssignmentToConstant(String, Position),
/// Returned type is not the same as the required output type.
- /// Wrapped value is the type of the actual result.
- ErrorMismatchOutputType(String, Position),
+ /// Wrapped values are the type requested and type of the actual result.
+ ErrorMismatchOutputType(String, String, Position),
/// Inappropriate member access.
ErrorDotExpr(String, Position),
/// Arithmetic error encountered. Wrapped value is the error message.
@@ -141,7 +141,7 @@ impl EvalAltResult {
"Assignment to an unsupported left-hand side expression"
}
Self::ErrorAssignmentToConstant(_, _) => "Assignment to a constant variable",
- Self::ErrorMismatchOutputType(_, _) => "Output type is incorrect",
+ Self::ErrorMismatchOutputType(_, _, _) => "Output type is incorrect",
Self::ErrorInExpr(_) => "Malformed 'in' expression",
Self::ErrorDotExpr(_, _) => "Malformed dot expression",
Self::ErrorArithmetic(_, _) => "Arithmetic error",
@@ -197,16 +197,18 @@ impl fmt::Display for EvalAltResult {
| Self::ErrorTooManyOperations(_)
| Self::ErrorTooManyModules(_)
| Self::ErrorStackOverflow(_)
- | Self::ErrorTerminated(_) => write!(f, "{}", desc)?,
+ | Self::ErrorTerminated(_) => f.write_str(desc)?,
- Self::ErrorRuntime(s, _) => write!(f, "{}", if s.is_empty() { desc } else { s })?,
+ Self::ErrorRuntime(s, _) => f.write_str(if s.is_empty() { desc } else { s })?,
Self::ErrorAssignmentToConstant(s, _) => write!(f, "{}: '{}'", desc, s)?,
- Self::ErrorMismatchOutputType(s, _) => write!(f, "{}: {}", desc, s)?,
- Self::ErrorArithmetic(s, _) => write!(f, "{}", s)?,
+ Self::ErrorMismatchOutputType(r, s, _) => {
+ write!(f, "{} (expecting {}): {}", desc, s, r)?
+ }
+ Self::ErrorArithmetic(s, _) => f.write_str(s)?,
- Self::ErrorLoopBreak(_, _) => write!(f, "{}", desc)?,
- Self::Return(_, _) => write!(f, "{}", desc)?,
+ Self::ErrorLoopBreak(_, _) => f.write_str(desc)?,
+ Self::Return(_, _) => f.write_str(desc)?,
Self::ErrorBooleanArgMismatch(op, _) => {
write!(f, "{} operator expects boolean operands", op)?
@@ -215,7 +217,7 @@ impl fmt::Display for EvalAltResult {
Self::ErrorArrayBounds(_, index, _) if *index < 0 => {
write!(f, "{}: {} < 0", desc, index)?
}
- Self::ErrorArrayBounds(0, _, _) => write!(f, "{}", desc)?,
+ Self::ErrorArrayBounds(0, _, _) => f.write_str(desc)?,
Self::ErrorArrayBounds(1, index, _) => write!(
f,
"Array index {} is out of bounds: only one element in the array",
@@ -229,7 +231,7 @@ impl fmt::Display for EvalAltResult {
Self::ErrorStringBounds(_, index, _) if *index < 0 => {
write!(f, "{}: {} < 0", desc, index)?
}
- Self::ErrorStringBounds(0, _, _) => write!(f, "{}", desc)?,
+ Self::ErrorStringBounds(0, _, _) => f.write_str(desc)?,
Self::ErrorStringBounds(1, index, _) => write!(
f,
"String index {} is out of bounds: only one character in the string",
@@ -289,7 +291,7 @@ impl EvalAltResult {
| Self::ErrorModuleNotFound(_, pos)
| Self::ErrorAssignmentToUnknownLHS(pos)
| Self::ErrorAssignmentToConstant(_, pos)
- | Self::ErrorMismatchOutputType(_, pos)
+ | Self::ErrorMismatchOutputType(_, _, pos)
| Self::ErrorInExpr(pos)
| Self::ErrorDotExpr(_, pos)
| Self::ErrorArithmetic(_, pos)
@@ -329,7 +331,7 @@ impl EvalAltResult {
| Self::ErrorModuleNotFound(_, pos)
| Self::ErrorAssignmentToUnknownLHS(pos)
| Self::ErrorAssignmentToConstant(_, pos)
- | Self::ErrorMismatchOutputType(_, pos)
+ | Self::ErrorMismatchOutputType(_, _, pos)
| Self::ErrorInExpr(pos)
| Self::ErrorDotExpr(_, pos)
| Self::ErrorArithmetic(_, pos)
diff --git a/src/serde/de.rs b/src/serde/de.rs
new file mode 100644
index 00000000..82910073
--- /dev/null
+++ b/src/serde/de.rs
@@ -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(&self) -> Result> {
+ Err(Box::new(EvalAltResult::ErrorMismatchOutputType(
+ type_name::().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> {
+/// # #[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,
+/// 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::deserialize(&mut DynamicDeserializer::from_dynamic(value))
+}
+
+impl Error for Box {
+ fn custom(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;
+
+ fn deserialize_any>(self, visitor: V) -> Result> {
+ 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::() => self.type_error(),
+
+ Union::Variant(value) if value.is::() => self.deserialize_i8(visitor),
+ Union::Variant(value) if value.is::() => self.deserialize_i16(visitor),
+ Union::Variant(value) if value.is::() => self.deserialize_i32(visitor),
+ Union::Variant(value) if value.is::() => self.deserialize_i64(visitor),
+ Union::Variant(value) if value.is::() => self.deserialize_u8(visitor),
+ Union::Variant(value) if value.is::() => self.deserialize_u16(visitor),
+ Union::Variant(value) if value.is::() => self.deserialize_u32(visitor),
+ Union::Variant(value) if value.is::() => self.deserialize_u64(visitor),
+
+ Union::Variant(_) => self.type_error(),
+ }
+ }
+
+ fn deserialize_bool>(self, visitor: V) -> Result> {
+ visitor.visit_bool(self.value.as_bool().or_else(|_| self.type_error())?)
+ }
+
+ fn deserialize_i8>(self, visitor: V) -> Result> {
+ self.value
+ .downcast_ref::()
+ .map_or_else(|| self.type_error(), |&x| visitor.visit_i8(x))
+ }
+
+ fn deserialize_i16>(self, visitor: V) -> Result> {
+ self.value
+ .downcast_ref::()
+ .map_or_else(|| self.type_error(), |&x| visitor.visit_i16(x))
+ }
+
+ fn deserialize_i32>(self, visitor: V) -> Result> {
+ self.value
+ .downcast_ref::()
+ .map_or_else(|| self.type_error(), |&x| visitor.visit_i32(x))
+ }
+
+ fn deserialize_i64>(self, visitor: V) -> Result> {
+ self.value
+ .downcast_ref::()
+ .map_or_else(|| self.type_error(), |&x| visitor.visit_i64(x))
+ }
+
+ fn deserialize_u8>(self, visitor: V) -> Result> {
+ self.value
+ .downcast_ref::()
+ .map_or_else(|| self.type_error(), |&x| visitor.visit_u8(x))
+ }
+
+ fn deserialize_u16>(self, visitor: V) -> Result> {
+ self.value
+ .downcast_ref::()
+ .map_or_else(|| self.type_error(), |&x| visitor.visit_u16(x))
+ }
+
+ fn deserialize_u32>(self, visitor: V) -> Result> {
+ self.value
+ .downcast_ref::()
+ .map_or_else(|| self.type_error(), |&x| visitor.visit_u32(x))
+ }
+
+ fn deserialize_u64>(self, visitor: V) -> Result> {
+ self.value
+ .downcast_ref::()
+ .map_or_else(|| self.type_error(), |&x| visitor.visit_u64(x))
+ }
+
+ fn deserialize_f32>(self, visitor: V) -> Result> {
+ #[cfg(not(feature = "no_float"))]
+ return self
+ .value
+ .downcast_ref::()
+ .map_or_else(|| self.type_error(), |&x| visitor.visit_f32(x));
+
+ #[cfg(feature = "no_float")]
+ return self.type_error_str("f32");
+ }
+
+ fn deserialize_f64>(self, visitor: V) -> Result> {
+ #[cfg(not(feature = "no_float"))]
+ return self
+ .value
+ .downcast_ref::()
+ .map_or_else(|| self.type_error(), |&x| visitor.visit_f64(x));
+
+ #[cfg(feature = "no_float")]
+ return self.type_error_str("f64");
+ }
+
+ fn deserialize_char>(self, visitor: V) -> Result> {
+ self.value
+ .downcast_ref::()
+ .map_or_else(|| self.type_error(), |&x| visitor.visit_char(x))
+ }
+
+ fn deserialize_str>(self, visitor: V) -> Result> {
+ self.value.downcast_ref::().map_or_else(
+ || self.type_error(),
+ |x| visitor.visit_borrowed_str(x.as_str()),
+ )
+ }
+
+ fn deserialize_string>(
+ self,
+ visitor: V,
+ ) -> Result> {
+ self.deserialize_str(visitor)
+ }
+
+ fn deserialize_bytes>(self, _: V) -> Result> {
+ self.type_error()
+ }
+
+ fn deserialize_byte_buf>(self, _: V) -> Result> {
+ self.type_error()
+ }
+
+ fn deserialize_option>(self, _: V) -> Result> {
+ self.type_error()
+ }
+
+ fn deserialize_unit>(self, visitor: V) -> Result> {
+ self.value
+ .downcast_ref::<()>()
+ .map_or_else(|| self.type_error(), |_| visitor.visit_unit())
+ }
+
+ fn deserialize_unit_struct>(
+ self,
+ _name: &'static str,
+ visitor: V,
+ ) -> Result> {
+ self.deserialize_unit(visitor)
+ }
+
+ fn deserialize_newtype_struct>(
+ self,
+ _name: &'static str,
+ visitor: V,
+ ) -> Result> {
+ visitor.visit_newtype_struct(self)
+ }
+
+ fn deserialize_seq>(self, visitor: V) -> Result> {
+ #[cfg(not(feature = "no_index"))]
+ return self.value.downcast_ref::().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>(
+ self,
+ _len: usize,
+ visitor: V,
+ ) -> Result> {
+ self.deserialize_seq(visitor)
+ }
+
+ fn deserialize_tuple_struct>(
+ self,
+ _name: &'static str,
+ _len: usize,
+ visitor: V,
+ ) -> Result> {
+ self.deserialize_seq(visitor)
+ }
+
+ fn deserialize_map>(self, visitor: V) -> Result> {
+ #[cfg(not(feature = "no_object"))]
+ return self.value.downcast_ref::().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>(
+ self,
+ _name: &'static str,
+ _fields: &'static [&'static str],
+ visitor: V,
+ ) -> Result> {
+ self.deserialize_map(visitor)
+ }
+
+ fn deserialize_enum>(
+ self,
+ _name: &'static str,
+ _variants: &'static [&'static str],
+ _: V,
+ ) -> Result> {
+ self.type_error()
+ }
+
+ fn deserialize_identifier>(
+ self,
+ visitor: V,
+ ) -> Result> {
+ self.deserialize_str(visitor)
+ }
+
+ fn deserialize_ignored_any>(
+ self,
+ visitor: V,
+ ) -> Result> {
+ self.deserialize_any(visitor)
+ }
+}
+
+/// `SeqAccess` implementation for arrays.
+struct IterateArray<'a, ITER>
+where
+ ITER: Iterator- ,
+{
+ /// Iterator for a stream of `Dynamic` values.
+ iter: ITER,
+}
+
+impl<'a, ITER> IterateArray<'a, ITER>
+where
+ ITER: Iterator
- ,
+{
+ pub fn new(iter: ITER) -> Self {
+ Self { iter }
+ }
+}
+
+impl<'a: 'de, 'de, ITER> SeqAccess<'de> for IterateArray<'a, ITER>
+where
+ ITER: Iterator
- ,
+{
+ type Error = Box
;
+
+ fn next_element_seed>(
+ &mut self,
+ seed: T,
+ ) -> Result, Box> {
+ // 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- ,
+ VALUES: Iterator
- ,
+{
+ // 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
- ,
+ VALUES: Iterator
- ,
+{
+ 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
- ,
+ VALUES: Iterator
- ,
+{
+ type Error = Box
;
+
+ fn next_key_seed>(
+ &mut self,
+ seed: K,
+ ) -> Result, Box> {
+ // 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>(
+ &mut self,
+ seed: V,
+ ) -> Result> {
+ // Deserialize each value item coming out of the iterator.
+ seed.deserialize(&mut DynamicDeserializer::from_dynamic(
+ self.values.next().unwrap(),
+ ))
+ }
+}
diff --git a/src/serde/mod.rs b/src/serde/mod.rs
new file mode 100644
index 00000000..2ed95bb4
--- /dev/null
+++ b/src/serde/mod.rs
@@ -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;
diff --git a/src/serde/ser.rs b/src/serde/ser.rs
new file mode 100644
index 00000000..1477da2d
--- /dev/null
+++ b/src/serde/ser.rs
@@ -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> {
+/// # #[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,
+/// 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::());
+///
+/// let map = value.cast::();
+/// let point = map.get("d").unwrap().downcast_ref::().unwrap();
+/// assert_eq!(*point.get("x").unwrap().downcast_ref::().unwrap(), 123.456);
+/// assert_eq!(*point.get("y").unwrap().downcast_ref::().unwrap(), 999.0);
+/// # }
+/// # Ok(())
+/// # }
+/// ```
+pub fn to_dynamic(value: T) -> Result> {
+ let mut s = DynamicSerializer::new(Default::default());
+ value.serialize(&mut s)
+}
+
+impl Error for Box {
+ fn custom(err: T) -> Self {
+ Box::new(EvalAltResult::ErrorRuntime(
+ err.to_string(),
+ Position::none(),
+ ))
+ }
+}
+
+impl Serializer for &mut DynamicSerializer {
+ type Ok = Dynamic;
+ type Error = Box;
+ 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> {
+ Ok(v.into())
+ }
+
+ fn serialize_i8(self, v: i8) -> Result> {
+ #[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> {
+ #[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> {
+ #[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> {
+ #[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> {
+ #[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> {
+ #[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> {
+ #[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> {
+ #[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> {
+ Ok(Dynamic::from(v))
+ }
+
+ fn serialize_f64(self, v: f64) -> Result> {
+ Ok(Dynamic::from(v))
+ }
+
+ fn serialize_char(self, v: char) -> Result> {
+ Ok(v.into())
+ }
+
+ fn serialize_str(self, v: &str) -> Result> {
+ Ok(v.to_string().into())
+ }
+
+ fn serialize_bytes(self, v: &[u8]) -> Result> {
+ Ok(Dynamic::from(v.to_vec()))
+ }
+
+ fn serialize_none(self) -> Result> {
+ Ok(().into())
+ }
+
+ fn serialize_some(
+ self,
+ value: &T,
+ ) -> Result> {
+ value.serialize(&mut *self)
+ }
+
+ fn serialize_unit(self) -> Result> {
+ Ok(().into())
+ }
+
+ fn serialize_unit_struct(self, _name: &'static str) -> Result> {
+ self.serialize_unit()
+ }
+
+ fn serialize_unit_variant(
+ self,
+ _name: &'static str,
+ _variant_index: u32,
+ variant: &'static str,
+ ) -> Result> {
+ self.serialize_str(variant)
+ }
+
+ fn serialize_newtype_struct(
+ self,
+ _name: &'static str,
+ value: &T,
+ ) -> Result> {
+ value.serialize(&mut *self)
+ }
+
+ fn serialize_newtype_variant(
+ self,
+ _name: &'static str,
+ _variant_index: u32,
+ _variant: &'static str,
+ value: &T,
+ ) -> Result> {
+ value.serialize(&mut *self)
+ }
+
+ fn serialize_seq(self, _len: Option) -> Result> {
+ #[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.serialize_seq(Some(len))
+ }
+
+ fn serialize_tuple_struct(
+ self,
+ _name: &'static str,
+ len: usize,
+ ) -> Result> {
+ self.serialize_seq(Some(len))
+ }
+
+ fn serialize_tuple_variant(
+ self,
+ _name: &'static str,
+ _variant_index: u32,
+ _variant: &'static str,
+ len: usize,
+ ) -> Result> {
+ self.serialize_seq(Some(len))
+ }
+
+ fn serialize_map(self, _len: Option) -> Result> {
+ #[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.serialize_map(Some(len))
+ }
+
+ fn serialize_struct_variant(
+ self,
+ _name: &'static str,
+ _variant_index: u32,
+ _variant: &'static str,
+ len: usize,
+ ) -> Result> {
+ self.serialize_map(Some(len))
+ }
+}
+
+impl SerializeSeq for DynamicSerializer {
+ type Ok = Dynamic;
+ type Error = Box;
+
+ fn serialize_element(
+ &mut self,
+ value: &T,
+ ) -> Result<(), Box> {
+ #[cfg(not(feature = "no_index"))]
+ {
+ let value = value.serialize(&mut *self)?;
+ let arr = self.value.downcast_mut::().unwrap();
+ arr.push(value);
+ Ok(())
+ }
+ #[cfg(feature = "no_index")]
+ unreachable!()
+ }
+
+ // Close the sequence.
+ fn end(self) -> Result> {
+ #[cfg(not(feature = "no_index"))]
+ return Ok(self.value);
+ #[cfg(feature = "no_index")]
+ unreachable!()
+ }
+}
+
+impl SerializeTuple for DynamicSerializer {
+ type Ok = Dynamic;
+ type Error = Box;
+
+ fn serialize_element(
+ &mut self,
+ value: &T,
+ ) -> Result<(), Box> {
+ #[cfg(not(feature = "no_index"))]
+ {
+ let value = value.serialize(&mut *self)?;
+ let arr = self.value.downcast_mut::().unwrap();
+ arr.push(value);
+ Ok(())
+ }
+ #[cfg(feature = "no_index")]
+ unreachable!()
+ }
+
+ fn end(self) -> Result> {
+ #[cfg(not(feature = "no_index"))]
+ return Ok(self.value);
+ #[cfg(feature = "no_index")]
+ unreachable!()
+ }
+}
+
+impl SerializeTupleStruct for DynamicSerializer {
+ type Ok = Dynamic;
+ type Error = Box;
+
+ fn serialize_field(
+ &mut self,
+ value: &T,
+ ) -> Result<(), Box> {
+ #[cfg(not(feature = "no_index"))]
+ {
+ let value = value.serialize(&mut *self)?;
+ let arr = self.value.downcast_mut::().unwrap();
+ arr.push(value);
+ Ok(())
+ }
+ #[cfg(feature = "no_index")]
+ unreachable!()
+ }
+
+ fn end(self) -> Result