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> { + #[cfg(not(feature = "no_index"))] + return Ok(self.value); + #[cfg(feature = "no_index")] + unreachable!() + } +} + +impl SerializeTupleVariant 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> { + #[cfg(not(feature = "no_index"))] + return Ok(self.value); + #[cfg(feature = "no_index")] + unreachable!() + } +} + +impl SerializeMap for DynamicSerializer { + type Ok = Dynamic; + type Error = Box; + + fn serialize_key(&mut self, key: &T) -> Result<(), Box> { + #[cfg(not(feature = "no_object"))] + { + self.key = key.serialize(&mut *self)?; + Ok(()) + } + #[cfg(feature = "no_object")] + unreachable!() + } + + fn serialize_value( + &mut self, + value: &T, + ) -> Result<(), Box> { + #[cfg(not(feature = "no_object"))] + { + let key = mem::take(&mut self.key) + .take_immutable_string() + .map_err(|typ| { + Box::new(EvalAltResult::ErrorMismatchOutputType( + "string".into(), + typ.into(), + Position::none(), + )) + })?; + let value = value.serialize(&mut *self)?; + let map = self.value.downcast_mut::().unwrap(); + map.insert(key, value); + Ok(()) + } + #[cfg(feature = "no_object")] + unreachable!() + } + + fn serialize_entry( + &mut self, + key: &K, + value: &T, + ) -> Result<(), Box> { + #[cfg(not(feature = "no_object"))] + { + let key: Dynamic = key.serialize(&mut *self)?; + let key = key.take_immutable_string().map_err(|typ| { + Box::new(EvalAltResult::ErrorMismatchOutputType( + "string".into(), + typ.into(), + Position::none(), + )) + })?; + let value = value.serialize(&mut *self)?; + let map = self.value.downcast_mut::().unwrap(); + map.insert(key, value); + Ok(()) + } + #[cfg(feature = "no_object")] + unreachable!() + } + + fn end(self) -> Result> { + #[cfg(not(feature = "no_object"))] + return Ok(self.value); + #[cfg(feature = "no_object")] + unreachable!() + } +} + +impl SerializeStruct for DynamicSerializer { + type Ok = Dynamic; + type Error = Box; + + fn serialize_field( + &mut self, + key: &'static str, + value: &T, + ) -> Result<(), Box> { + #[cfg(not(feature = "no_object"))] + { + let value = value.serialize(&mut *self)?; + let map = self.value.downcast_mut::().unwrap(); + map.insert(key.into(), value); + Ok(()) + } + #[cfg(feature = "no_object")] + unreachable!() + } + + fn end(self) -> Result> { + #[cfg(not(feature = "no_object"))] + return Ok(self.value); + #[cfg(feature = "no_object")] + unreachable!() + } +} + +impl SerializeStructVariant for DynamicSerializer { + type Ok = Dynamic; + type Error = Box; + + fn serialize_field( + &mut self, + key: &'static str, + value: &T, + ) -> Result<(), Box> { + #[cfg(not(feature = "no_object"))] + { + let value = value.serialize(&mut *self)?; + let map = self.value.downcast_mut::().unwrap(); + map.insert(key.into(), value); + Ok(()) + } + #[cfg(feature = "no_object")] + unreachable!() + } + + fn end(self) -> Result> { + #[cfg(not(feature = "no_object"))] + return Ok(self.value); + #[cfg(feature = "no_object")] + unreachable!() + } +} diff --git a/src/serde/str.rs b/src/serde/str.rs new file mode 100644 index 00000000..23e2c3d2 --- /dev/null +++ b/src/serde/str.rs @@ -0,0 +1,155 @@ +//! Implement deserialization support of `ImmutableString` for [`serde`](https://crates.io/crates/serde). + +use crate::result::EvalAltResult; +use crate::token::Position; +use crate::utils::ImmutableString; + +use serde::de::{Deserializer, Visitor}; + +use crate::stdlib::any::type_name; + +/// Deserializer for `ImmutableString`. +pub struct ImmutableStringDeserializer<'a> { + value: &'a ImmutableString, +} + +impl<'a> ImmutableStringDeserializer<'a> { + /// Create an `ImmutableStringDeserializer` from an `ImmutableString` reference. + pub fn from_str(value: &'a ImmutableString) -> Self { + Self { value } + } + /// Shortcut for a type conversion error. + fn type_error(&self) -> Result> { + Err(Box::new(EvalAltResult::ErrorMismatchOutputType( + type_name::().into(), + "string".into(), + Position::none(), + ))) + } +} + +impl<'de> Deserializer<'de> for &mut ImmutableStringDeserializer<'de> { + type Error = Box; + + fn deserialize_any>(self, v: V) -> Result> { + self.deserialize_str(v) + } + fn deserialize_bool>(self, _: V) -> Result> { + self.type_error() + } + fn deserialize_i8>(self, _: V) -> Result> { + self.type_error() + } + fn deserialize_i16>(self, _: V) -> Result> { + self.type_error() + } + fn deserialize_i32>(self, _: V) -> Result> { + self.type_error() + } + fn deserialize_i64>(self, _: V) -> Result> { + self.type_error() + } + fn deserialize_u8>(self, _: V) -> Result> { + self.type_error() + } + fn deserialize_u16>(self, _: V) -> Result> { + self.type_error() + } + fn deserialize_u32>(self, _: V) -> Result> { + self.type_error() + } + fn deserialize_u64>(self, _: V) -> Result> { + self.type_error() + } + fn deserialize_f32>(self, _: V) -> Result> { + self.type_error() + } + fn deserialize_f64>(self, _: V) -> Result> { + self.type_error() + } + fn deserialize_char>(self, _: V) -> Result> { + self.type_error() + } + fn deserialize_str>(self, v: V) -> Result> { + // Only allow deserialization into a string. + v.visit_borrowed_str(self.value.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, _: V) -> Result> { + self.type_error() + } + fn deserialize_unit_struct>( + self, + _name: &'static str, + v: V, + ) -> Result> { + self.deserialize_unit(v) + } + fn deserialize_newtype_struct>( + self, + _name: &'static str, + v: V, + ) -> Result> { + v.visit_newtype_struct(self) + } + fn deserialize_seq>(self, _: V) -> Result> { + self.type_error() + } + fn deserialize_tuple>( + self, + _len: usize, + v: V, + ) -> Result> { + self.deserialize_seq(v) + } + fn deserialize_tuple_struct>( + self, + _name: &'static str, + _len: usize, + v: V, + ) -> Result> { + self.deserialize_seq(v) + } + fn deserialize_map>(self, _: V) -> Result> { + self.type_error() + } + fn deserialize_struct>( + self, + _name: &'static str, + _fields: &'static [&'static str], + v: V, + ) -> Result> { + self.deserialize_map(v) + } + fn deserialize_enum>( + self, + _name: &'static str, + _variants: &'static [&'static str], + _: V, + ) -> Result> { + self.type_error() + } + fn deserialize_identifier>(self, v: V) -> Result> { + self.deserialize_str(v) + } + fn deserialize_ignored_any>( + self, + v: V, + ) -> Result> { + self.deserialize_any(v) + } +} diff --git a/src/settings.rs b/src/settings.rs new file mode 100644 index 00000000..daa714bd --- /dev/null +++ b/src/settings.rs @@ -0,0 +1,254 @@ +use crate::engine::Engine; +use crate::module::ModuleResolver; +use crate::optimize::OptimizationLevel; +use crate::packages::PackageLibrary; +use crate::token::is_valid_identifier; + +impl Engine { + /// Load a new package into the `Engine`. + /// + /// When searching for functions, packages loaded later are preferred. + /// In other words, loaded packages are searched in reverse order. + pub fn load_package(&mut self, package: PackageLibrary) { + // Push the package to the top - packages are searched in reverse order + self.packages.push(package); + } + + /// Load a new package into the `Engine`. + /// + /// When searching for functions, packages loaded later are preferred. + /// In other words, loaded packages are searched in reverse order. + pub fn load_packages(&mut self, package: PackageLibrary) { + // Push the package to the top - packages are searched in reverse order + self.packages.push(package); + } + + /// Control whether and how the `Engine` will optimize an AST after compilation. + /// + /// Not available under the `no_optimize` feature. + #[cfg(not(feature = "no_optimize"))] + pub fn set_optimization_level(&mut self, optimization_level: OptimizationLevel) { + self.optimization_level = optimization_level + } + + /// The current optimization level. + /// It controls whether and how the `Engine` will optimize an AST after compilation. + /// + /// Not available under the `no_optimize` feature. + #[cfg(not(feature = "no_optimize"))] + pub fn optimization_level(&self) -> OptimizationLevel { + self.optimization_level + } + + /// Set the maximum levels of function calls allowed for a script in order to avoid + /// infinite recursion and stack overflows. + #[cfg(not(feature = "unchecked"))] + pub fn set_max_call_levels(&mut self, levels: usize) { + self.max_call_stack_depth = levels + } + + /// The maximum levels of function calls allowed for a script. + #[cfg(not(feature = "unchecked"))] + pub fn max_call_levels(&self) -> usize { + self.max_call_stack_depth + } + + /// Set the maximum number of operations allowed for a script to run to avoid + /// consuming too much resources (0 for unlimited). + #[cfg(not(feature = "unchecked"))] + pub fn set_max_operations(&mut self, operations: u64) { + self.max_operations = if operations == u64::MAX { + 0 + } else { + operations + }; + } + + /// The maximum number of operations allowed for a script to run (0 for unlimited). + #[cfg(not(feature = "unchecked"))] + pub fn max_operations(&self) -> u64 { + self.max_operations + } + + /// Set the maximum number of imported modules allowed for a script. + #[cfg(not(feature = "unchecked"))] + pub fn set_max_modules(&mut self, modules: usize) { + self.max_modules = modules; + } + + /// The maximum number of imported modules allowed for a script. + #[cfg(not(feature = "unchecked"))] + pub fn max_modules(&self) -> usize { + self.max_modules + } + + /// Set the depth limits for expressions (0 for unlimited). + #[cfg(not(feature = "unchecked"))] + pub fn set_max_expr_depths(&mut self, max_expr_depth: usize, max_function_expr_depth: usize) { + self.max_expr_depth = if max_expr_depth == usize::MAX { + 0 + } else { + max_expr_depth + }; + self.max_function_expr_depth = if max_function_expr_depth == usize::MAX { + 0 + } else { + max_function_expr_depth + }; + } + + /// The depth limit for expressions (0 for unlimited). + #[cfg(not(feature = "unchecked"))] + pub fn max_expr_depth(&self) -> usize { + self.max_expr_depth + } + + /// The depth limit for expressions in functions (0 for unlimited). + #[cfg(not(feature = "unchecked"))] + pub fn max_function_expr_depth(&self) -> usize { + self.max_function_expr_depth + } + + /// Set the maximum length of strings (0 for unlimited). + #[cfg(not(feature = "unchecked"))] + pub fn set_max_string_size(&mut self, max_size: usize) { + self.max_string_size = if max_size == usize::MAX { 0 } else { max_size }; + } + + /// The maximum length of strings (0 for unlimited). + #[cfg(not(feature = "unchecked"))] + pub fn max_string_size(&self) -> usize { + self.max_string_size + } + + /// Set the maximum length of arrays (0 for unlimited). + #[cfg(not(feature = "unchecked"))] + #[cfg(not(feature = "no_index"))] + pub fn set_max_array_size(&mut self, max_size: usize) { + self.max_array_size = if max_size == usize::MAX { 0 } else { max_size }; + } + + /// The maximum length of arrays (0 for unlimited). + #[cfg(not(feature = "unchecked"))] + #[cfg(not(feature = "no_index"))] + pub fn max_array_size(&self) -> usize { + self.max_array_size + } + + /// Set the maximum length of object maps (0 for unlimited). + #[cfg(not(feature = "unchecked"))] + #[cfg(not(feature = "no_object"))] + pub fn set_max_map_size(&mut self, max_size: usize) { + self.max_map_size = if max_size == usize::MAX { 0 } else { max_size }; + } + + /// The maximum length of object maps (0 for unlimited). + #[cfg(not(feature = "unchecked"))] + #[cfg(not(feature = "no_object"))] + pub fn max_map_size(&self) -> usize { + self.max_map_size + } + + /// Set the module resolution service used by the `Engine`. + /// + /// Not available under the `no_module` feature. + #[cfg(not(feature = "no_module"))] + pub fn set_module_resolver(&mut self, resolver: Option) { + self.module_resolver = resolver.map(|f| Box::new(f) as Box); + } + + /// Disable a particular keyword or operator in the language. + /// + /// # Examples + /// + /// The following will raise an error during parsing because the `if` keyword is disabled + /// and is recognized as a variable name! + /// + /// ```rust,should_panic + /// # fn main() -> Result<(), rhai::ParseError> { + /// use rhai::Engine; + /// + /// let mut engine = Engine::new(); + /// + /// engine.disable_symbol("if"); // disable the 'if' keyword + /// + /// engine.compile("let x = if true { 42 } else { 0 };")?; + /// // ^ 'if' is parsed as a variable name + /// // ^ missing ';' after statement end + /// # Ok(()) + /// # } + /// ``` + /// + /// The following will raise an error during parsing because the `+=` operator is disabled. + /// + /// ```rust,should_panic + /// # fn main() -> Result<(), rhai::ParseError> { + /// use rhai::Engine; + /// + /// let mut engine = Engine::new(); + /// + /// engine.disable_symbol("+="); // disable the '+=' operator + /// + /// engine.compile("let x = 42; x += 1;")?; + /// // ^ unknown operator + /// # Ok(()) + /// # } + /// ``` + pub fn disable_symbol(&mut self, symbol: &str) { + if self.disabled_symbols.is_none() { + self.disabled_symbols = Some(Default::default()); + } + + self.disabled_symbols + .as_mut() + .unwrap() + .insert(symbol.into()); + } + + /// Register a custom operator into the language. + /// + /// The operator must be a valid identifier (i.e. it cannot be a symbol). + /// + /// # Examples + /// + /// ```rust + /// # fn main() -> Result<(), Box> { + /// use rhai::{Engine, RegisterFn}; + /// + /// let mut engine = Engine::new(); + /// + /// // Register a custom operator called 'foo' and give it + /// // a precedence of 140 (i.e. between +|- and *|/). + /// engine.register_custom_operator("foo", 140).unwrap(); + /// + /// // Register a binary function named 'foo' + /// engine.register_fn("foo", |x: i64, y: i64| (x * y) - (x + y)); + /// + /// assert_eq!( + /// engine.eval_expression::("1 + 2 * 3 foo 4 - 5 / 6")?, + /// 15 + /// ); + /// # Ok(()) + /// # } + /// ``` + pub fn register_custom_operator( + &mut self, + keyword: &str, + precedence: u8, + ) -> Result<(), String> { + if !is_valid_identifier(keyword.chars()) { + return Err(format!("not a valid identifier: '{}'", keyword).into()); + } + + if self.custom_keywords.is_none() { + self.custom_keywords = Some(Default::default()); + } + + self.custom_keywords + .as_mut() + .unwrap() + .insert(keyword.into(), precedence); + + Ok(()) + } +} diff --git a/src/token.rs b/src/token.rs index 4b19ab49..056f9714 100644 --- a/src/token.rs +++ b/src/token.rs @@ -1,5 +1,6 @@ //! Main module defining the lexer and parser. +use crate::engine::Engine; use crate::error::LexError; use crate::parser::INT; use crate::utils::StaticVec; @@ -10,7 +11,9 @@ use crate::parser::FLOAT; use crate::stdlib::{ borrow::Cow, boxed::Box, - char, fmt, + char, + collections::{HashMap, HashSet}, + fmt, iter::Peekable, str::{Chars, FromStr}, string::{String, ToString}, @@ -19,7 +22,7 @@ use crate::stdlib::{ type LERR = LexError; -pub type TokenStream<'a> = Peekable>; +pub type TokenStream<'a, 't> = Peekable>; /// A location (line number + character position) in the input script. /// @@ -137,7 +140,7 @@ pub enum Token { FloatConstant(FLOAT), Identifier(String), CharConstant(char), - StringConst(String), + StringConstant(String), LeftBrace, RightBrace, LeftParen, @@ -210,6 +213,7 @@ pub enum Token { As, LexError(Box), Comment(String), + Custom(String), EOF, } @@ -222,12 +226,13 @@ impl Token { IntegerConstant(i) => i.to_string().into(), #[cfg(not(feature = "no_float"))] FloatConstant(f) => f.to_string().into(), - Identifier(s) => s.clone().into(), + StringConstant(_) => "string".into(), CharConstant(c) => c.to_string().into(), + Identifier(s) => s.clone().into(), + Custom(s) => s.clone().into(), LexError(err) => err.to_string().into(), - token => (match token { - StringConst(_) => "string", + token => match token { LeftBrace => "{", RightBrace => "}", LeftParen => "(", @@ -292,13 +297,15 @@ impl Token { PowerOfAssign => "~=", #[cfg(not(feature = "no_function"))] Private => "private", + #[cfg(not(feature = "no_module"))] Import => "import", #[cfg(not(feature = "no_module"))] Export => "export", + #[cfg(not(feature = "no_module"))] As => "as", EOF => "{EOF}", _ => unreachable!("operator should be match in outer scope"), - }) + } .into(), } } @@ -320,9 +327,9 @@ impl Token { match self { LexError(_) | - LeftBrace | // (+expr) - is unary + LeftBrace | // {+expr} - is unary // RightBrace | {expr} - expr not unary & is closing - LeftParen | // {-expr} - is unary + LeftParen | // (-expr) - is unary // RightParen | (expr) - expr not unary & is closing LeftBracket | // [-expr] - is unary // RightBracket | [expr] - expr not unary & is closing @@ -367,14 +374,14 @@ impl Token { Throw | PowerOf | In | - PowerOfAssign => true, + PowerOfAssign => true, _ => false, } } /// Get the precedence number of the token. - pub fn precedence(&self) -> u8 { + pub fn precedence(&self, custom: Option<&HashMap>) -> u8 { use Token::*; match self { @@ -383,24 +390,27 @@ impl Token { | RightShiftAssign | AndAssign | OrAssign | XOrAssign | ModuloAssign | PowerOfAssign => 0, - Or | XOr | Pipe => 40, + Or | XOr | Pipe => 30, - And | Ampersand => 50, + And | Ampersand => 60, LessThan | LessThanEqualsTo | GreaterThan | GreaterThanEqualsTo | EqualsTo - | NotEqualsTo => 60, + | NotEqualsTo => 90, - In => 70, + In => 110, - Plus | Minus => 80, + Plus | Minus => 130, - Divide | Multiply | PowerOf => 90, + Divide | Multiply | PowerOf => 160, - LeftShift | RightShift => 100, + LeftShift | RightShift => 190, - Modulo => 110, + Modulo => 210, - Period => 120, + Period => 240, + + // Custom operators + Custom(s) => custom.map_or(0, |c| *c.get(s).unwrap()), _ => 0, } @@ -422,6 +432,41 @@ impl Token { _ => false, } } + + /// Is this token an operator? + pub fn is_operator(&self) -> bool { + use Token::*; + + match self { + LeftBrace | RightBrace | LeftParen | RightParen | LeftBracket | RightBracket | Plus + | UnaryPlus | Minus | UnaryMinus | Multiply | Divide | Modulo | PowerOf | LeftShift + | RightShift | SemiColon | Colon | DoubleColon | Comma | Period | MapStart | Equals + | LessThan | GreaterThan | LessThanEqualsTo | GreaterThanEqualsTo | EqualsTo + | NotEqualsTo | Bang | Pipe | Or | XOr | Ampersand | And | PlusAssign | MinusAssign + | MultiplyAssign | DivideAssign | LeftShiftAssign | RightShiftAssign | AndAssign + | OrAssign | XOrAssign | ModuloAssign | PowerOfAssign => true, + + _ => false, + } + } + + /// Is this token a keyword? + pub fn is_keyword(&self) -> bool { + use Token::*; + + match self { + #[cfg(not(feature = "no_function"))] + Fn | Private => true, + + #[cfg(not(feature = "no_module"))] + Import | Export | As => true, + + True | False | Let | Const | If | Else | While | Loop | For | In | Continue | Break + | Return | Throw => true, + + _ => false, + } + } } impl From for String { @@ -431,7 +476,7 @@ impl From for String { } /// State of the tokenizer. -#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash, Default)] +#[derive(Debug, Clone, Eq, PartialEq, Default)] pub struct TokenizeState { /// Maximum length of a string (0 = unlimited). pub max_string_size: usize, @@ -644,7 +689,7 @@ pub fn get_next_token( let result = get_next_token_inner(stream, state, pos); // Save the last token's state - if let Some((token, _)) = &result { + if let Some((ref token, _)) = result { state.non_unary = !token.is_next_unary(); } @@ -848,7 +893,7 @@ fn get_next_token_inner( ('"', _) => return parse_string_literal(stream, state, pos, '"') .map_or_else( |err| Some((Token::LexError(Box::new(err.0)), err.1)), - |out| Some((Token::StringConst(out), start_pos)), + |out| Some((Token::StringConstant(out), start_pos)), ), // ' - character literal @@ -1118,7 +1163,7 @@ fn get_next_token_inner( ('\0', _) => unreachable!(), (ch, _) if ch.is_whitespace() => (), - (ch, _) => return Some((Token::LexError(Box::new(LERR::UnexpectedChar(ch))), start_pos)), + (ch, _) => return Some((Token::LexError(Box::new(LERR::UnexpectedInput(ch.to_string()))), start_pos)), } } @@ -1172,7 +1217,9 @@ impl InputStream for MultiInputsStream<'_> { } /// An iterator on a `Token` stream. -pub struct TokenIterator<'a> { +pub struct TokenIterator<'a, 'e> { + /// Reference to the scripting `Engine`. + engine: &'e Engine, /// Current state. state: TokenizeState, /// Current position. @@ -1181,19 +1228,47 @@ pub struct TokenIterator<'a> { stream: MultiInputsStream<'a>, } -impl<'a> Iterator for TokenIterator<'a> { +impl<'a> Iterator for TokenIterator<'a, '_> { type Item = (Token, Position); fn next(&mut self) -> Option { - get_next_token(&mut self.stream, &mut self.state, &mut self.pos) + match ( + get_next_token(&mut self.stream, &mut self.state, &mut self.pos), + self.engine.disabled_symbols.as_ref(), + self.engine.custom_keywords.as_ref(), + ) { + (None, _, _) => None, + (r @ Some(_), None, None) => r, + (Some((token, pos)), Some(disabled), _) + if token.is_operator() && disabled.contains(token.syntax().as_ref()) => + { + // Convert disallowed operators into lex errors + Some(( + Token::LexError(Box::new(LexError::UnexpectedInput(token.syntax().into()))), + pos, + )) + } + (Some((token, pos)), Some(disabled), _) + if token.is_keyword() && disabled.contains(token.syntax().as_ref()) => + { + // Convert disallowed keywords into identifiers + Some((Token::Identifier(token.syntax().into()), pos)) + } + (Some((Token::Identifier(s), pos)), _, Some(custom)) if custom.contains_key(&s) => { + // Convert custom keywords + Some((Token::Custom(s), pos)) + } + (r, _, _) => r, + } } } /// Tokenize an input text stream. -pub fn lex<'a>(input: &'a [&'a str], max_string_size: usize) -> TokenIterator<'a> { +pub fn lex<'a, 'e>(input: &'a [&'a str], engine: &'e Engine) -> TokenIterator<'a, 'e> { TokenIterator { + engine, state: TokenizeState { - max_string_size, + max_string_size: engine.max_string_size, non_unary: false, comment_level: 0, end_with_none: false, diff --git a/tests/data_size.rs b/tests/data_size.rs index e073d443..33257cfd 100644 --- a/tests/data_size.rs +++ b/tests/data_size.rs @@ -35,6 +35,7 @@ fn test_max_string_size() -> Result<(), Box> { EvalAltResult::ErrorDataTooLarge(_, 10, 13, _) )); + #[cfg(not(feature = "no_object"))] assert!(matches!( *engine .eval::( @@ -92,6 +93,8 @@ fn test_max_array_size() -> Result<(), Box> { .expect_err("should error"), EvalAltResult::ErrorDataTooLarge(_, 10, 12, _) )); + + #[cfg(not(feature = "no_object"))] assert!(matches!( *engine .eval::( diff --git a/tests/for.rs b/tests/for.rs index 4f99c06f..e3b561d6 100644 --- a/tests/for.rs +++ b/tests/for.rs @@ -39,7 +39,7 @@ fn test_for_string() -> Result<(), Box> { let sum = 0; for ch in s { - sum += ch.to_int(); + sum += to_int(ch); } sum diff --git a/tests/functions.rs b/tests/functions.rs index 49789da4..ed3eeb73 100644 --- a/tests/functions.rs +++ b/tests/functions.rs @@ -52,6 +52,7 @@ fn test_functions() -> Result<(), Box> { } #[test] +#[cfg(not(feature = "no_object"))] fn test_function_pointers() -> Result<(), Box> { let engine = Engine::new(); diff --git a/tests/mismatched_op.rs b/tests/mismatched_op.rs index b129f29b..1199bf94 100644 --- a/tests/mismatched_op.rs +++ b/tests/mismatched_op.rs @@ -6,14 +6,14 @@ fn test_mismatched_op() { assert!(matches!( *engine.eval::(r#""hello, " + "world!""#).expect_err("expects error"), - EvalAltResult::ErrorMismatchOutputType(err, _) if err == "string" + EvalAltResult::ErrorMismatchOutputType(need, actual, _) if need == std::any::type_name::() && actual == "string" )); } #[test] #[cfg(not(feature = "no_object"))] fn test_mismatched_op_custom_type() { - #[derive(Clone)] + #[derive(Debug, Clone)] struct TestStruct { x: INT, } @@ -28,19 +28,14 @@ fn test_mismatched_op_custom_type() { engine.register_type_with_name::("TestStruct"); engine.register_fn("new_ts", TestStruct::new); - let r = engine - .eval::("60 + new_ts()") - .expect_err("expects error"); - - #[cfg(feature = "only_i32")] assert!(matches!( - *r, - EvalAltResult::ErrorFunctionNotFound(err, _) if err == "+ (i32, TestStruct)" + *engine.eval::("60 + new_ts()").expect_err("should error"), + EvalAltResult::ErrorFunctionNotFound(err, _) if err == format!("+ ({}, TestStruct)", std::any::type_name::()) )); - #[cfg(not(feature = "only_i32"))] assert!(matches!( - *r, - EvalAltResult::ErrorFunctionNotFound(err, _) if err == "+ (i64, TestStruct)" + *engine.eval::("42").expect_err("should error"), + EvalAltResult::ErrorMismatchOutputType(need, actual, _) + if need == "TestStruct" && actual == std::any::type_name::() )); } diff --git a/tests/serde.rs b/tests/serde.rs new file mode 100644 index 00000000..82ec330b --- /dev/null +++ b/tests/serde.rs @@ -0,0 +1,192 @@ +#![cfg(feature = "serde")] + +use rhai::{de::from_dynamic, ser::to_dynamic, Dynamic, Engine, EvalAltResult, INT}; +use serde::{Deserialize, Serialize}; + +#[cfg(not(feature = "no_index"))] +use rhai::Array; +#[cfg(not(feature = "no_object"))] +use rhai::Map; + +#[test] +fn test_serde_ser_primary_types() -> Result<(), Box> { + assert_eq!( + to_dynamic(42_u64)?.type_name(), + std::any::type_name::() + ); + assert_eq!(to_dynamic(u64::MAX)?.type_name(), "u64"); + assert_eq!( + to_dynamic(42 as INT)?.type_name(), + std::any::type_name::() + ); + assert_eq!(to_dynamic(true)?.type_name(), "bool"); + assert_eq!(to_dynamic(())?.type_name(), "()"); + + #[cfg(not(feature = "no_float"))] + { + assert_eq!(to_dynamic(123.456_f64)?.type_name(), "f64"); + assert_eq!(to_dynamic(123.456_f32)?.type_name(), "f32"); + } + + assert_eq!(to_dynamic("hello".to_string())?.type_name(), "string"); + + Ok(()) +} + +#[test] +#[cfg(not(feature = "no_index"))] +fn test_serde_ser_array() -> Result<(), Box> { + let arr: Vec = vec![123, 456, 42, 999]; + + let d = to_dynamic(arr)?; + assert!(d.is::()); + assert_eq!(d.cast::().len(), 4); + + Ok(()) +} + +#[test] +#[cfg(not(feature = "no_index"))] +#[cfg(not(feature = "no_object"))] +fn test_serde_ser_struct() -> Result<(), Box> { + #[derive(Debug, Serialize, PartialEq)] + struct Hello { + a: INT, + b: bool, + } + + #[derive(Debug, Serialize, PartialEq)] + struct Test { + int: u32, + seq: Vec, + obj: Hello, + } + + let x = Test { + int: 42, + seq: vec!["hello".into(), "kitty".into(), "world".into()], + obj: Hello { a: 123, b: true }, + }; + + let d = to_dynamic(x)?; + + assert!(d.is::()); + + let mut map = d.cast::(); + let mut obj = map.remove("obj").unwrap().cast::(); + let mut seq = map.remove("seq").unwrap().cast::(); + + assert_eq!(obj.remove("a").unwrap().cast::(), 123); + assert!(obj.remove("b").unwrap().cast::()); + assert_eq!(map.remove("int").unwrap().cast::(), 42); + assert_eq!(seq.len(), 3); + assert_eq!(seq.remove(1).cast::(), "kitty"); + + Ok(()) +} + +#[test] +fn test_serde_de_primary_types() -> Result<(), Box> { + assert_eq!(42_u16, from_dynamic(&Dynamic::from(42_u16))?); + assert_eq!(42 as INT, from_dynamic(&(42 as INT).into())?); + assert_eq!(true, from_dynamic(&true.into())?); + assert_eq!((), from_dynamic(&().into())?); + + #[cfg(not(feature = "no_float"))] + { + assert_eq!(123.456_f64, from_dynamic(&123.456_f64.into())?); + assert_eq!(123.456_f32, from_dynamic(&Dynamic::from(123.456_f32))?); + } + + assert_eq!( + "hello", + from_dynamic::(&"hello".to_string().into())? + ); + + Ok(()) +} + +#[test] +#[cfg(not(feature = "no_index"))] +fn test_serde_de_array() -> Result<(), Box> { + let arr: Vec = vec![123, 456, 42, 999]; + assert_eq!(arr, from_dynamic::>(&arr.clone().into())?); + Ok(()) +} + +#[test] +#[cfg(not(feature = "no_index"))] +#[cfg(not(feature = "no_object"))] +fn test_serde_de_struct() -> Result<(), Box> { + #[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!["hello".into(), "kitty".into(), "world".into()]; + map.insert("seq".into(), arr.into()); + + let expected = Test { + int: 42, + seq: vec!["hello".into(), "kitty".into(), "world".into()], + obj: Hello { a: 123, b: true }, + }; + assert_eq!(expected, from_dynamic(&map.into())?); + + Ok(()) +} + +#[test] +#[cfg(not(feature = "no_index"))] +#[cfg(not(feature = "no_object"))] +#[cfg(not(feature = "no_float"))] +fn test_serde_de_script() -> Result<(), Box> { + #[derive(Debug, Deserialize)] + struct Point { + x: f64, + y: f64, + } + + #[derive(Debug, 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 _: MyStruct = from_dynamic(&result)?; + + Ok(()) +} diff --git a/tests/tokens.rs b/tests/tokens.rs new file mode 100644 index 00000000..b86f6a0e --- /dev/null +++ b/tests/tokens.rs @@ -0,0 +1,49 @@ +use rhai::{Engine, EvalAltResult, ParseErrorType, RegisterFn, INT}; + +#[test] +fn test_tokens_disabled() { + let mut engine = Engine::new(); + + engine.disable_symbol("if"); // disable the 'if' keyword + + assert!(matches!( + *engine.compile("let x = if true { 42 } else { 0 };").expect_err("should error").0, + ParseErrorType::MissingToken(ref token, _) if token == ";" + )); + + engine.disable_symbol("+="); // disable the '+=' operator + + assert!(matches!( + *engine.compile("let x = 40 + 2; x += 1;").expect_err("should error").0, + ParseErrorType::BadInput(ref s) if s == "Unexpected '+='" + )); +} + +#[test] +fn test_tokens_custom_operator() -> Result<(), Box> { + let mut engine = Engine::new(); + + // Register a custom operator called `foo` and give it + // a precedence of 140 (i.e. between +|- and *|/). + engine.register_custom_operator("foo", 140).unwrap(); + + // Register a binary function named `foo` + engine.register_fn("foo", |x: INT, y: INT| (x * y) - (x + y)); + + assert_eq!( + engine.eval_expression::("1 + 2 * 3 foo 4 - 5 / 6")?, + 15 + ); + + assert_eq!( + engine.eval::( + r" + fn foo(x, y) { y - x } + 1 + 2 * 3 foo 4 - 5 / 6 + " + )?, + -1 + ); + + Ok(()) +}