diff --git a/.github/workflows/benchmark.yml b/.github/workflows/benchmark.yml new file mode 100644 index 00000000..df310705 --- /dev/null +++ b/.github/workflows/benchmark.yml @@ -0,0 +1,29 @@ +name: Benchmark +on: + push: + branches: + - master + +jobs: + benchmark: + name: Run Rust benchmark + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - run: rustup toolchain update nightly && rustup default nightly + - name: Run benchmark + run: cargo +nightly bench | tee output.txt + - name: Store benchmark result + uses: rhysd/github-action-benchmark@v1 + with: + name: Rust Benchmark + tool: 'cargo' + output-file-path: output.txt + # Use personal access token instead of GITHUB_TOKEN due to https://github.community/t5/GitHub-Actions/Github-action-not-triggering-gh-pages-upon-push/td-p/26869/highlight/false + github-token: ${{ secrets.RHAI }} + auto-push: true + # Show alert with commit comment on detecting possible performance regression + alert-threshold: '200%' + comment-on-alert: true + fail-on-alert: true + alert-comment-cc-users: '@schungx' diff --git a/.gitignore b/.gitignore index 140811c2..e884ce0f 100644 --- a/.gitignore +++ b/.gitignore @@ -2,4 +2,6 @@ target/ Cargo.lock .vscode/ .cargo/ -doc/book/ \ No newline at end of file +doc/book/ +before +after 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 82f9ea09..285595d0 100644 --- a/README.md +++ b/README.md @@ -26,18 +26,21 @@ 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). +* Support for use as a [DSL](https://schungx.github.io/rhai/engine/dsl.html) - [disabling keywords/operators](https://schungx.github.io/rhai/engine/disable.html), [custom operators](https://schungx.github.io/rhai/engine/custom-op.html) and extending the language with [custom syntax](https://schungx.github.io/rhai/engine/custom-syntax.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..c1bbc632 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -1,11 +1,60 @@ 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). +* Low-level API to register functions. + +Bug fixes +--------- + +* Fixed method calls in the middle of a dot chain. + +Breaking changes +---------------- + +* `EvalAltResult::ErrorMismatchOutputType` has an extra argument containing the name of the requested type. +* `Engine::call_fn_dynamic` take an extra argument, allowing a `Dynamic` value to be bound to the `this` pointer. +* Precedence of the `%` (modulo) operator is lowered to below `<<` ad `>>`. This is to handle the case of `x << 3 % 10`. +* Many configuration/setting API's now returns `&mut Self` so that the calls can be chained. This should not affect most code. + +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. +* New low-level API `Engine::register_raw_fn` and `Engine::register_raw_fn_XXX`. +* New low-level API `Module::set_raw_fn` mirroring `Engine::register_raw_fn`. +* `AST::clone_functions_only`, `AST::clone_functions_only_filtered` and `AST::clone_statements_only` to clone only part of an `AST`. +* The boolean `^` (XOR) operator is added. +* `FnPtr` is exposed as the function pointer type. +* `rhai::module_resolvers::ModuleResolversCollection` added to try a list of module resolvers. +* It is now possible to mutate the first argument of a module-qualified function call when the argument is a simple variable (but not a module constant). + + +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..e344c306 100644 --- a/doc/src/SUMMARY.md +++ b/doc/src/SUMMARY.md @@ -79,10 +79,10 @@ The Rhai Scripting Language 16. [Modules](language/modules/index.md) 1. [Export Variables, Functions and Sub-Modules](language/modules/export.md) 2. [Import Modules](language/modules/import.md) - 3. [Create from Rust](language/modules/rust.md) + 3. [Create from Rust](rust/modules/index.md) 4. [Create from AST](language/modules/ast.md) - 5. [Module Resolvers](language/modules/resolvers.md) - 1. [Implement a Custom Module Resolver](language/modules/imp-resolver.md) + 5. [Module Resolvers](rust/modules/resolvers.md) + 1. [Custom Implementation](rust/modules/imp-resolver.md) 7. [Safety and Protection](safety/index.md) 1. [Checked Arithmetic](safety/checked.md) 2. [Sand-Boxing](safety/sandbox.md) @@ -96,15 +96,21 @@ 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. [Low-Level API](rust/register-raw.md) + 5. [Use as DSL](engine/dsl.md) + 1. [Disable Keywords and/or Operators](engine/disable.md) + 2. [Custom Operators](engine/custom-op.md) + 3. [Extending with Custom Syntax](engine/custom-syntax.md) + 6. [Eval Statement](language/eval.md) 9. [Appendix](appendix/index.md) 1. [Keywords](appendix/keywords.md) - 2. [Operators](appendix/operators.md) + 2. [Operators and Symbols](appendix/operators.md) 3. [Literals](appendix/literals.md) diff --git a/doc/src/about/features.md b/doc/src/about/features.md index c012353a..13b2e4c1 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,8 @@ 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. + +* Use as a [DSL] by [disabling keywords/operators][disable keywords and operators], [custom operators] + and extending the language with [custom syntax]. 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..b4303d79 100644 --- a/doc/src/appendix/operators.md +++ b/doc/src/appendix/operators.md @@ -1,30 +1,53 @@ -Operators -========= +Operators and Symbols +==================== {{#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 | + +Operators +--------- + +| 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_, Boolean _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 | + + +Symbols +------- + +| Symbol | Description | +| ------------ | ------------------------ | +| `:` | Property value separator | +| `::` | Module path separator | +| `#` | _Reserved_ | +| `=>` | _Reserved_ | +| `->` | _Reserved_ | +| `<-` | _Reserved_ | +| `===` | _Reserved_ | +| `!==` | _Reserved_ | +| `:=` | _Reserved_ | +| `::<` .. `>` | _Reserved_ | +| `@` | _Reserved_ | +| `(*` .. `*)` | _Reserved_ | 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/call-fn.md b/doc/src/engine/call-fn.md index d881d7bf..7a49d6e4 100644 --- a/doc/src/engine/call-fn.md +++ b/doc/src/engine/call-fn.md @@ -55,10 +55,41 @@ let result: i64 = engine.call_fn(&mut scope, &ast, "hello", () )?; let result: () = engine.call_fn(&mut scope, &ast, "hidden", ())?; ``` + +Low-Level API - `Engine::call_fn_dynamic` +---------------------------------------- + For more control, construct all arguments as `Dynamic` values and use `Engine::call_fn_dynamic`, passing it -anything that implements `IntoIterator` (such as a simple `Vec`): +anything that implements `AsMut` (such as a simple array or a `Vec`): ```rust -let result: Dynamic = engine.call_fn_dynamic(&mut scope, &ast, "hello", - vec![ String::from("abc").into(), 123_i64.into() ])?; +let result = engine.call_fn_dynamic( + &mut scope, // scope to use + ast.into(), // get 'Module' from 'AST' + "hello", // function entry-point + None, // 'this' pointer, if any + [ String::from("abc").into(), 123_i64.into() ] // arguments + )?; +``` + + +Binding the `this` Pointer +------------------------- + +`Engine::call_fn_dynamic` can also bind a value to the `this` pointer of a script-defined function. + +```rust +let ast = engine.compile("fn action(x) { this += x; }")?; + +let mut value: Dynamic = 1_i64.into(); + +let result = engine.call_fn_dynamic( + &mut scope, + ast.into(), + "action", + Some(&mut value), // binding the 'this' pointer + [ 41_i64.into() ] + )?; + +assert_eq!(value.as_int().unwrap(), 42); ``` diff --git a/doc/src/engine/custom-op.md b/doc/src/engine/custom-op.md new file mode 100644 index 00000000..0b92c8a9 --- /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 named 'foo' and give it a precedence of 160 +// (i.e. between +|- and *|/) +// Also register the implementation of the customer operator as a function +engine + .register_custom_operator("foo", 160).unwrap() + .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 shortcut. + + +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", 160).unwrap() + .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 | +| Comparisons | `>`, `>=`, `<`, `<=` | 110 | +| | `in` | 130 | +| Arithmetic | `+`, `-` | 150 | +| Arithmetic | `*`, `/`, `~`, `%` | 180 | +| Bit-shifts | `<<`, `>>` | 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/custom-syntax.md b/doc/src/engine/custom-syntax.md new file mode 100644 index 00000000..e0529841 --- /dev/null +++ b/doc/src/engine/custom-syntax.md @@ -0,0 +1,282 @@ +Extend Rhai with Custom Syntax +============================= + +{{#include ../links.md}} + + +For the ultimate advantageous, there is a built-in facility to _extend_ the Rhai language +with custom-defined _syntax_. + +But before going off to define the next weird statement type, heed this warning: + + +Don't Do It™ +------------ + +Stick with standard language syntax as much as possible. + +Having to learn Rhai is bad enough, no sane user would ever want to learn _yet_ another +obscure language syntax just to do something. + +Try to use [custom operators] first. Defining a custom syntax should be considered a _last resort_. + + +Where This Might Be Useful +------------------------- + +* Where an operation is used a _LOT_ and a custom syntax saves a lot of typing. + +* Where a custom syntax _significantly_ simplifies the code and _significantly_ enhances understanding of the code's intent. + +* Where certain logic cannot be easily encapsulated inside a function. This is usually the case where _closures_ are required, because Rhai does not have closures. + +* Where you just want to confuse your user and make their lives miserable, because you can. + + +Step One - Start With `internals` +-------------------------------- + +Since a custom syntax taps deeply into the `AST` and evaluation process of the `Engine`, +the [`internals`] feature must be on in order to expose these necessary internal data structures. + +Beware that Rhai internal data structures are _volatile_ and may change without warning. + +Caveat emptor. + + +Step Two - Design The Syntax +--------------------------- + +A custom syntax is simply a list of symbols. + +These symbol types can be used: + +* Standard [keywords]({{rootUrl}}/appendix/keywords.md) + +* Standard [operators]({{rootUrl}}/appendix/operators.md#operators). + +* Reserved [symbols]({{rootUrl}}/appendix/operators.md#symbols). + +* Identifiers following the [variable] naming rules. + +* `$expr$` - any valid expression, statement or statement block. + +* `$block$` - any valid statement block (i.e. must be enclosed by `'{'` .. `'}'`). + +* `$ident$` - any [variable] name. + +### The First Symbol Must be a Keyword + +There is no specific limit on the combination and sequencing of each symbol type, +except the _first_ symbol which must be a [custom keyword]. + +It _cannot_ be a [built-in keyword]({{rootUrl}}/appendix/keywords.md). + +However, it _may_ be a built-in keyword that has been [disabled][disable keywords and operators]. + +### The First Symbol Must be Unique + +Rhai uses the _first_ symbol as a clue to parse custom syntax. + +Therefore, at any one time, there can only be _one_ custom syntax starting with each unique symbol. + +Any new custom syntax definition using the same first symbol simply _overwrites_ the previous one. + +### Example + +```rust +exec $ident$ <- $expr$ : $block$ +``` + +The above syntax is made up of a stream of symbols: + +| Position | Input | Symbol | Description | +| :------: | :---: | :-------: | -------------------------------------------------------------------------------------------------------- | +| 1 | | `exec` | custom keyword | +| 2 | 1 | `$ident$` | a variable name | +| 3 | | `<-` | the left-arrow symbol (which is a [reserved symbol]({{rootUrl}}/appendix/operators.md#symbols) in Rhai). | +| 4 | 2 | `$expr$` | an expression, which may be enclosed with `{` .. `}`, or not. | +| 5 | | `:` | the colon symbol | +| 6 | 3 | `$block$` | a statement block, which must be enclosed with `{` .. `}`. | + +This syntax matches the following sample code and generates three inputs (one for each non-keyword): + +```rust +// Assuming the 'exec' custom syntax implementation declares the variable 'hello': +let x = exec hello <- foo(1, 2) : { + hello += bar(hello); + baz(hello); + }; + +print(x); // variable 'x' has a value returned by the custom syntax + +print(hello); // variable declared by a custom syntax persists! +``` + + +Step Three - Implementation +-------------------------- + +Any custom syntax must include an _implementation_ of it. + +### Function Signature + +The function signature of an implementation is: + +```rust +Fn( + engine: &Engine, + scope: &mut Scope, + mods: &mut Imports, + state: &mut State, + lib: &Module, + this_ptr: &mut Option<&mut Dynamic>, + inputs: &[Expression], + level: usize +) -> Result> +``` + +where: + +* `engine : &Engine` - reference to the current [`Engine`]. +* `scope : &mut Scope` - mutable reference to the current [`Scope`]; variables can be added to it. +* `mods : &mut Imports` - mutable reference to the current collection of imported [`Module`]'s; **do not touch**. +* `state : &mut State` - mutable reference to the current evaluation state; **do not touch**. +* `lib : &Module` - reference to the current collection of script-defined functions. +* `this_ptr : &mut Option<&mut Dynamic>` - mutable reference to the current binding of the `this` pointer; **do not touch**. +* `inputs : &[Expression]` - a list of input expression trees. +* `level : usize` - the current function call level. + +There are a lot of parameters, most of which should not be touched or Bad Things Happen™. +They represent the running _content_ of a script evaluation and should simply be passed +straight-through the the [`Engine`]. + +### Access Arguments + +The most important argument is `inputs` where the matched identifiers (`$ident$`), expressions/statements (`$expr$`) +and statement blocks (`$block$) are provided. + +To access a particular argument, use the following patterns: + +| Argument type | Pattern (`n` = slot in `inputs`) | Result type | Description | +| :-----------: | ---------------------------------------- | :----------: | ------------------ | +| `$ident$` | `inputs[n].get_variable_name().unwrap()` | `&str` | name of a variable | +| `$expr$` | `inputs.get(n).unwrap()` | `Expression` | an expression tree | +| `$block$` | `inputs.get(n).unwrap()` | `Expression` | an expression tree | + +### Evaluate an Expression Tree + +Use the `engine::eval_expression_tree` method to evaluate an expression tree. + +```rust +let expr = inputs.get(0).unwrap(); +let result = engine.eval_expression_tree(scope, mods, state, lib, this_ptr, expr, level)?; +``` + +As can be seem above, most arguments are simply passed straight-through to `engine::eval_expression_tree`. + +### Declare Variables + +New variables maybe declared (usually with a variable name that is passed in via `$ident$). + +It can simply be pushed into the [`scope`]. + +However, beware that all new variables must be declared _prior_ to evaluating any expression tree. +In other words, any `scope.push(...)` calls must come _before_ any `engine::eval_expression_tree(...)` calls. + +```rust +let var_name = inputs[0].get_variable_name().unwrap().to_string(); +let expr = inputs.get(1).unwrap(); + +scope.push(var_name, 0 as INT); // do this BEFORE engine.eval_expression_tree! + +let result = engine.eval_expression_tree(scope, mods, state, lib, this_ptr, expr, level)?; +``` + + +Step Four - Register the Custom Syntax +------------------------------------- + +Use `Engine::register_custom_syntax` to register a custom syntax. + +Again, beware that the _first_ symbol must be unique. If there already exists a custom syntax starting +with that symbol, the previous syntax will be overwritten. + +The syntax is passed simply as a slice of `&str`. + +```rust +// Custom syntax implementation +fn implementation_func( + engine: &Engine, + scope: &mut Scope, + mods: &mut Imports, + state: &mut State, + lib: &Module, + this_ptr: &mut Option<&mut Dynamic>, + inputs: &[Expression], + level: usize +) -> Result> { + let var_name = inputs[0].get_variable_name().unwrap().to_string(); + let stmt = inputs.get(1).unwrap(); + let condition = inputs.get(2).unwrap(); + + // Push one new variable into the 'scope' BEFORE 'eval_expression_tree' + scope.push(var_name, 0 as INT); + + loop { + // Evaluate the statement block + engine.eval_expression_tree(scope, mods, state, lib, this_ptr, stmt, level)?; + + // Evaluate the condition expression + let stop = !engine + .eval_expression_tree(scope, mods, state, lib, this_ptr, condition, level)? + .as_bool() + .map_err(|_| EvalAltResult::ErrorBooleanArgMismatch( + "do-while".into(), expr.position() + ))?; + + if stop { + break; + } + } + + Ok(().into()) +} + +// Register the custom syntax (sample): do |x| -> { x += 1 } while x < 0; +engine.register_custom_syntax( + &[ "do", "|", "$ident$", "|", "->", "$block$", "while", "$expr$" ], // the custom syntax + 1, // the number of new variables declared within this custom syntax + implementation_func +)?; +``` + + +Step Five - Disable Unneeded Statement Types +------------------------------------------- + +When a DSL needs a custom syntax, most likely than not it is extremely specialized. +Therefore, many statement types actually may not make sense under the same usage scenario. + +So, while at it, better [disable][disable keywords and operators] those built-in keywords +and operators that should not be used by the user. The would leave only the bare minimum +language surface exposed, together with the custom syntax that is tailor-designed for +the scenario. + +A keyword or operator that is disabled can still be used in a custom syntax. + +In an extreme case, it is possible to disable _every_ keyword in the language, leaving only +custom syntax (plus possibly expressions). But again, Don't Do It™ - unless you are certain +of what you're doing. + + +Step Six - Document +------------------- + +For custom syntax, documentation is crucial. + +Make sure there are _lots_ of examples for users to follow. + + +Step Seven - Profit! +-------------------- diff --git a/doc/src/engine/disable.md b/doc/src/engine/disable.md new file mode 100644 index 00000000..3810fa5e --- /dev/null +++ b/doc/src/engine/disable.md @@ -0,0 +1,29 @@ +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 + .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/engine/dsl.md b/doc/src/engine/dsl.md new file mode 100644 index 00000000..3585936b --- /dev/null +++ b/doc/src/engine/dsl.md @@ -0,0 +1,84 @@ +Use Rhai as a Domain-Specific Language (DSL) +=========================================== + +{{#include ../links.md}} + +Rhai can be successfully used as a domain-specific language (DSL). + + +Expressions Only +---------------- + +In many DSL scenarios, only evaluation of expressions is needed. + +The `Engine::eval_expression_XXX`[`eval_expression`] API can be used to restrict +a script to expressions only. + + +Disable Keywords and/or Operators +-------------------------------- + +In some DSL scenarios, it is necessary to further restrict the language to exclude certain +language features that are not necessary or dangerous to the application. + +For example, a DSL may disable the `while` loop altogether while keeping all other statement +types intact. + +It is possible, in Rhai, to surgically [disable keywords and operators]. + + +Custom Operators +---------------- + +On the other hand, some DSL scenarios require special operators that make sense only for +that specific environment. In such cases, it is possible to define [custom operators] in Rhai. + +For example: + +```rust +let animal = "rabbit"; +let food = "carrot"; + +animal eats food // custom operator - 'eats' + +eats(animal, food) // <- the above really de-sugars to this +``` + +Although a [custom operator] always de-sugars to a simple function call, +nevertheless it makes the DSL syntax much simpler and expressive. + + +Custom Syntax +------------- + +For advanced DSL scenarios, it is possible to define entire expression [_syntax_][custom syntax] - +essentially custom statement types. + +The [`internals`] feature is needed to be able to define [custom syntax] in Rhai. + +For example, the following is a SQL like syntax for some obscure DSL operation: + +```rust +let table = [..., ..., ..., ...]; + +// Syntax = "calculate" $ident$ $ident$ "from" $expr$ "->" $ident$ ":" $expr$ +let total = calculate sum price from table -> row : row.weight > 50; + +// Note: There is nothing special about the use of symbols; to make it look exactly like SQL: +// Syntax = "SELECT" $ident$ "(" $ident$ ")" "FROM" $expr$ "AS" $ident$ "WHERE" $expr$ +let total = SELECT sum(price) FROM table AS row WHERE row.weight > 50; +``` + +After registering this custom syntax with Rhai, it can be used anywhere inside a script as +a normal expression. + +For its evaluation, the callback function will receive the following list of parameters: + +`exprs[0] = "sum"` - math operator +`exprs[1] = "price"` - field name +`exprs[2] = Expression(table)` - data source +`exprs[3] = "row"` - loop variable name +`exprs[4] = Expression(row.wright > 50)` - expression + +The other identified, such as `"select"`, `"from"`, as as as symbols `->` and `:` are +parsed in the order defined within the custom syntax. diff --git a/doc/src/language/eval.md b/doc/src/language/eval.md index a7e5b979..0af40142 100644 --- a/doc/src/language/eval.md +++ b/doc/src/language/eval.md @@ -19,14 +19,14 @@ script += "x + y"; let result = eval(script); // <- look, JavaScript, we can also do this! -print("Answer: " + result); // prints 42 +result == 42; -print("x = " + x); // prints 10: functions call arguments are passed by value -print("y = " + y); // prints 32: variables defined in 'eval' persist! +x == 10; // prints 10: functions call arguments are passed by value +y == 32; // prints 32: variables defined in 'eval' persist! eval("{ let z = y }"); // to keep a variable local, use a statement block -print("z = " + z); // <- error: variable 'z' not found +print(z); // <- error: variable 'z' not found "print(42)".eval(); // <- nope... method-call style doesn't work with 'eval' ``` diff --git a/doc/src/language/functions.md b/doc/src/language/functions.md index 72dac0b4..b6c0a85f 100644 --- a/doc/src/language/functions.md +++ b/doc/src/language/functions.md @@ -14,8 +14,9 @@ fn sub(x, y,) { // trailing comma in parameters list is OK return x - y; } -print(add(2, 3)); // prints 5 -print(sub(2, 3,)); // prints -1 - trailing comma in arguments list is OK +add(2, 3) == 5; + +sub(2, 3,) == -1; // trailing comma in arguments list is OK ``` @@ -35,8 +36,9 @@ fn add2(x) { return x + 2; // explicit return } -print(add(2, 3)); // prints 5 -print(add2(42)); // prints 44 +add(2, 3) == 5; + +add2(42) == 44; ``` diff --git a/doc/src/language/loop.md b/doc/src/language/loop.md index b92c3f7e..3fd9b5fb 100644 --- a/doc/src/language/loop.md +++ b/doc/src/language/loop.md @@ -3,16 +3,16 @@ Infinite `loop` {{#include ../links.md}} -Infinite loops follow C syntax. +Infinite loops follow Rust syntax. -Like C, `continue` can be used to skip to the next iteration, by-passing all following statements; +Like Rust, `continue` can be used to skip to the next iteration, by-passing all following statements; `break` can be used to break out of the loop unconditionally. ```rust let x = 10; loop { - x = x - 1; + x -= 1; if x > 5 { continue; } // skip to the next iteration diff --git a/doc/src/language/modules/export.md b/doc/src/language/modules/export.md index dbb4ddca..f05e008c 100644 --- a/doc/src/language/modules/export.md +++ b/doc/src/language/modules/export.md @@ -22,6 +22,8 @@ The `export` statement, which can only be at global level, exposes selected vari Variables not exported are _private_ and hidden to the outside. +Everything exported from a module is **constant** (**read-only**). + ```rust // This is a module script. @@ -49,8 +51,6 @@ All functions are automatically exported, _unless_ it is explicitly opt-out with Functions declared [`private`] are hidden to the outside. -Everything exported from a module is **constant** (**read-only**). - ```rust // This is a module script. diff --git a/doc/src/language/modules/import.md b/doc/src/language/modules/import.md index 4ede1f52..dc94b5a8 100644 --- a/doc/src/language/modules/import.md +++ b/doc/src/language/modules/import.md @@ -3,6 +3,7 @@ Import a Module {{#include ../../links.md}} + `import` Statement ----------------- diff --git a/doc/src/language/modules/rust.md b/doc/src/language/modules/rust.md deleted file mode 100644 index 74f90896..00000000 --- a/doc/src/language/modules/rust.md +++ /dev/null @@ -1,30 +0,0 @@ -Create a Module from Rust -======================== - -{{#include ../../links.md}} - -To load a custom module (written in Rust) into an [`Engine`], first create a [`Module`] type, -add variables/functions into it, then finally push it into a custom [`Scope`]. - -This has the equivalent effect of putting an [`import`] statement at the beginning of any script run. - -```rust -use rhai::{Engine, Scope, Module, i64}; - -let mut engine = Engine::new(); -let mut scope = Scope::new(); - -let mut module = Module::new(); // new module -module.set_var("answer", 41_i64); // variable 'answer' under module -module.set_fn_1("inc", |x: i64| Ok(x+1)); // use the 'set_fn_XXX' API to add functions - -// Push the module into the custom scope under the name 'question' -// This is equivalent to 'import "..." as question;' -scope.push_module("question", module); - -// Use module-qualified variables -engine.eval_expression_with_scope::(&scope, "question::answer + 1")? == 42; - -// Call module-qualified functions -engine.eval_expression_with_scope::(&scope, "question::inc(question::answer)")? == 42; -``` diff --git a/doc/src/language/values-and-types.md b/doc/src/language/values-and-types.md index 70c276a9..55b03229 100644 --- a/doc/src/language/values-and-types.md +++ b/doc/src/language/values-and-types.md @@ -15,7 +15,7 @@ The following primitive types are supported natively: | **[`Array`]** (disabled with [`no_index`]) | `rhai::Array` | `"array"` | `"[ ?, ?, ? ]"` | | **[Object map]** (disabled with [`no_object`]) | `rhai::Map` | `"map"` | `"#{ "a": 1, "b": 2 }"` | | **[Timestamp]** (implemented in the [`BasicTimePackage`][packages], disabled with [`no_std`]) | `std::time::Instant` ([`instant::Instant`] if not [WASM] build) | `"timestamp"` | _not supported_ | -| **[Function pointer]** | _None_ | `Fn` | `"Fn(foo)"` | +| **[Function pointer]** | `rhai::FnPtr` | `Fn` | `"Fn(foo)"` | | **[`Dynamic`] value** (i.e. can be anything) | `rhai::Dynamic` | _the actual type_ | _actual value_ | | **System integer** (current configuration) | `rhai::INT` (`i32` or `i64`) | `"i32"` or `"i64"` | `"42"`, `"123"` etc. | | **System floating-point** (current configuration, disabled with [`no_float`]) | `rhai::FLOAT` (`f32` or `f64`) | `"f32"` or `"f64"` | `"123.456"` etc. | diff --git a/doc/src/language/while.md b/doc/src/language/while.md index e912175e..5b7a5ac8 100644 --- a/doc/src/language/while.md +++ b/doc/src/language/while.md @@ -12,7 +12,7 @@ Like C, `continue` can be used to skip to the next iteration, by-passing all fol let x = 10; while x > 0 { - x = x - 1; + x -= 1; if x < 6 { continue; } // skip to the next iteration print(x); if x == 5 { break; } // break out of while loop diff --git a/doc/src/links.md b/doc/src/links.md index b7147a50..82ceab3b 100644 --- a/doc/src/links.md +++ b/doc/src/links.md @@ -20,6 +20,7 @@ [`Engine`]: {{rootUrl}}/engine/hello-world.md [traits]: {{rootUrl}}/rust/traits.md [`private`]: {{rootUrl}}/engine/call-fn.md +[`call_fn`]: {{rootUrl}}/engine/call-fn.md [`Func`]: {{rootUrl}}/engine/func.md [`AST`]: {{rootUrl}}/engine/compile.md [`eval_expression`]: {{rootUrl}}/engine/expressions.md @@ -29,6 +30,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 @@ -80,13 +82,14 @@ [`Module`]: {{rootUrl}}/language/modules/index.md [module]: {{rootUrl}}/language/modules/index.md [modules]: {{rootUrl}}/language/modules/index.md -[module resolver]: {{rootUrl}}/language/modules/imp-resolver.md +[module resolver]: {{rootUrl}}/rust/modules/resolvers.md [`export`]: {{rootUrl}}/language/modules/export.md [`import`]: {{rootUrl}}/language/modules/import.md [`eval`]: {{rootUrl}}/language/eval.md [OOP]: {{rootUrl}}/language/oop.md +[DSL]: {{rootUrl}}/engine/dsl.md [maximum statement depth]: {{rootUrl}}/safety/max-stmt-depth.md [maximum call stack depth]: {{rootUrl}}/safety/max-call-stack.md @@ -101,3 +104,8 @@ [`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 +[custom syntax]: {{rootUrl}}/engine/custom-syntax.md diff --git a/doc/src/rust/custom.md b/doc/src/rust/custom.md index b87f6409..7460f50e 100644 --- a/doc/src/rust/custom.md +++ b/doc/src/rust/custom.md @@ -9,7 +9,7 @@ Support for custom types can be turned off via the [`no_object`] feature. ```rust use rhai::{Engine, EvalAltResult}; -use rhai::RegisterFn; +use rhai::RegisterFn; // remember 'RegisterFn' is needed #[derive(Clone)] struct TestStruct { @@ -28,14 +28,14 @@ impl TestStruct { let mut engine = Engine::new(); -engine.register_type::(); - -engine.register_fn("update", TestStruct::update); -engine.register_fn("new_ts", TestStruct::new); +engine + .register_type::() // most API's can be chained up + .register_fn("update", TestStruct::update) + .register_fn("new_ts", TestStruct::new); let result = engine.eval::("let x = new_ts(); x.update(); x")?; -println!("result: {}", result.field); // prints 42 +println!("result: {}", result.field); // prints 42 ``` Register a Custom Type @@ -52,7 +52,7 @@ struct TestStruct { } impl TestStruct { - fn update(&mut self) { // methods take &mut as first parameter + fn update(&mut self) { // methods take &mut as first parameter self.field += 41; } @@ -75,8 +75,9 @@ using one of the `Engine::register_XXX` API. Below, the `update` and `new` methods are registered using `Engine::register_fn`. ```rust -engine.register_fn("update", TestStruct::update); // registers 'update(&mut TestStruct)' -engine.register_fn("new_ts", TestStruct::new); // registers 'new()' +engine + .register_fn("update", TestStruct::update) // registers 'update(&mut TestStruct)' + .register_fn("new_ts", TestStruct::new); // registers 'new()' ``` ***Note**: Rhai follows the convention that methods of custom types take a `&mut` first parameter @@ -107,13 +108,13 @@ fn foo(ts: &mut TestStruct) -> i64 { ts.field } -engine.register_fn("foo", foo); // register a Rust native function +engine.register_fn("foo", foo); // register a Rust native function let result = engine.eval::( - "let x = new_ts(); x.foo()" // 'foo' can be called like a method on 'x' + "let x = new_ts(); x.foo()" // 'foo' can be called like a method on 'x' )?; -println!("result: {}", result); // prints 1 +println!("result: {}", result); // prints 1 ``` Under [`no_object`], however, the _method_ style of function calls @@ -133,13 +134,17 @@ If `Engine::register_type_with_name` is used to register the custom type with a special "pretty-print" name, [`type_of()`] will return that name instead. ```rust -engine.register_type::(); -engine.register_fn("new_ts", TestStruct::new); -let x = new_ts(); -print(x.type_of()); // prints "path::to::module::TestStruct" +engine + .register_type::() + .register_fn("new_ts", TestStruct::new); -engine.register_type_with_name::("Hello"); -engine.register_fn("new_ts", TestStruct::new); let x = new_ts(); -print(x.type_of()); // prints "Hello" +x.type_of() == "path::to::module::TestStruct"; + +engine + .register_type_with_name::("Hello") + .register_fn("new_ts", TestStruct::new); + +let x = new_ts(); +x.type_of() == "Hello"; ``` diff --git a/doc/src/rust/fallible.md b/doc/src/rust/fallible.md index 16c16826..86d15576 100644 --- a/doc/src/rust/fallible.md +++ b/doc/src/rust/fallible.md @@ -16,7 +16,7 @@ use rhai::RegisterResultFn; // use 'RegisterResultFn' trait fn safe_divide(x: i64, y: i64) -> Result> { if y == 0 { // Return an error if y is zero - Err("Division by zero!".into()) // short-cut to create Box + Err("Division by zero!".into()) // shortcut to create Box } else { Ok((x / y).into()) // convert result into 'Dynamic' } diff --git a/doc/src/rust/functions.md b/doc/src/rust/functions.md index aca6fe8e..a929484c 100644 --- a/doc/src/rust/functions.md +++ b/doc/src/rust/functions.md @@ -31,8 +31,9 @@ fn get_any_value() -> Result> { let mut engine = Engine::new(); -engine.register_fn("add", add_len); -engine.register_fn("add_str", add_len_str); +engine + .register_fn("add", add_len) + .register_fn("add_str", add_len_str); let result = engine.eval::(r#"add(40, "xx")"#)?; diff --git a/doc/src/rust/generic.md b/doc/src/rust/generic.md index c68bf562..d2bea789 100644 --- a/doc/src/rust/generic.md +++ b/doc/src/rust/generic.md @@ -19,9 +19,10 @@ fn show_it(x: &mut T) { let mut engine = Engine::new(); -engine.register_fn("print", show_it::); -engine.register_fn("print", show_it::); -engine.register_fn("print", show_it::); +engine + .register_fn("print", show_it::) + .register_fn("print", show_it::) + .register_fn("print", show_it::); ``` The above example shows how to register multiple functions diff --git a/doc/src/rust/getters-setters.md b/doc/src/rust/getters-setters.md index 92c659b9..b6afd6f0 100644 --- a/doc/src/rust/getters-setters.md +++ b/doc/src/rust/getters-setters.md @@ -30,10 +30,10 @@ impl TestStruct { let mut engine = Engine::new(); -engine.register_type::(); - -engine.register_get_set("xyz", TestStruct::get_field, TestStruct::set_field); -engine.register_fn("new_ts", TestStruct::new); + engine + .register_type::() + .register_get_set("xyz", TestStruct::get_field, TestStruct::set_field) + .register_fn("new_ts", TestStruct::new); // Return result can be 'String' - Rhai will automatically convert it from 'ImmutableString' let result = engine.eval::(r#"let a = new_ts(); a.xyz = "42"; a.xyz"#)?; diff --git a/doc/src/rust/indexers.md b/doc/src/rust/indexers.md index 800c43b8..35f50ba2 100644 --- a/doc/src/rust/indexers.md +++ b/doc/src/rust/indexers.md @@ -38,8 +38,9 @@ engine.register_type::(); engine.register_fn("new_ts", TestStruct::new); // Shorthand: engine.register_indexer_get_set(TestStruct::get_field, TestStruct::set_field); -engine.register_indexer_get(TestStruct::get_field); -engine.register_indexer_set(TestStruct::set_field); +engine + .register_indexer_get(TestStruct::get_field) + .register_indexer_set(TestStruct::set_field); let result = engine.eval::("let a = new_ts(); a[2] = 42; a[2]")?; diff --git a/doc/src/language/modules/imp-resolver.md b/doc/src/rust/modules/imp-resolver.md similarity index 100% rename from doc/src/language/modules/imp-resolver.md rename to doc/src/rust/modules/imp-resolver.md diff --git a/doc/src/rust/modules/index.md b/doc/src/rust/modules/index.md new file mode 100644 index 00000000..be6576bb --- /dev/null +++ b/doc/src/rust/modules/index.md @@ -0,0 +1,45 @@ +Create a Module from Rust +======================== + +{{#include ../../links.md}} + +Manually creating a [`Module`] is possible via the `Module` API. + +For the complete `Module` API, refer to the [documentation](https://docs.rs/rhai/{{version}}/rhai/struct.Module.html) online. + + +Make the Module Available to the Engine +-------------------------------------- + +In order to _use_ a custom module, there must be a [module resolver]. + +The easiest way is to use, for example, the [`StaticModuleResolver`][module resolver] to hold such +a custom module. + +```rust +use rhai::{Engine, Scope, Module, i64}; +use rhai::module_resolvers::StaticModuleResolver; + +let mut engine = Engine::new(); +let mut scope = Scope::new(); + +let mut module = Module::new(); // new module +module.set_var("answer", 41_i64); // variable 'answer' under module +module.set_fn_1("inc", |x: i64| Ok(x+1)); // use the 'set_fn_XXX' API to add functions + +// Create the module resolver +let mut resolver = StaticModuleResolver::new(); + +// Add the module into the module resolver under the name 'question' +// They module can then be accessed via: 'import "question" as q;' +resolver.insert("question", module); + +// Set the module resolver into the 'Engine' +engine.set_module_resolver(Some(resolver)); + +// Use module-qualified variables +engine.eval::(&scope, r#"import "question" as q; q::answer + 1"#)? == 42; + +// Call module-qualified functions +engine.eval::(&scope, r#"import "question" as q; q::inc(q::answer)"#)? == 42; +``` diff --git a/doc/src/language/modules/resolvers.md b/doc/src/rust/modules/resolvers.md similarity index 55% rename from doc/src/language/modules/resolvers.md rename to doc/src/rust/modules/resolvers.md index ed2bf54c..4a3b97e7 100644 --- a/doc/src/language/modules/resolvers.md +++ b/doc/src/rust/modules/resolvers.md @@ -7,15 +7,24 @@ When encountering an [`import`] statement, Rhai attempts to _resolve_ the module _Module Resolvers_ are service types that implement the [`ModuleResolver`][traits] trait. + +Built-In Module Resolvers +------------------------ + There are a number of standard resolvers built into Rhai, the default being the `FileModuleResolver` which simply loads a script file based on the path (with `.rhai` extension attached) and execute it to form a module. Built-in module resolvers are grouped under the `rhai::module_resolvers` module namespace. -| Module Resolver | Description | -| ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `FileModuleResolver` | The default module resolution service, not available under [`no_std`] or [WASM] builds. Loads a script file (based off the current directory) with `.rhai` extension.
The base directory can be changed via the `FileModuleResolver::new_with_path()` constructor function.
`FileModuleResolver::create_module()` loads a script file and returns a module. | -| `StaticModuleResolver` | Loads modules that are statically added. This can be used under [`no_std`]. | +| Module Resolver | Description | +| --------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `FileModuleResolver` | The default module resolution service, not available under [`no_std`] or [WASM] builds. Loads a script file (based off the current directory) with `.rhai` extension.
The base directory can be changed via the `FileModuleResolver::new_with_path()` constructor function.
`FileModuleResolver::create_module()` loads a script file and returns a module. | +| `StaticModuleResolver` | Loads modules that are statically added. This can be used under [`no_std`]. | +| `ModuleResolversCollection` | A collection of module resolvers. Modules will be resolved from each resolver in sequential order.
This is useful when multiple types of modules are needed simultaneously. | + + +Set into `Engine` +----------------- An [`Engine`]'s module resolver is set via a call to `Engine::set_module_resolver`: diff --git a/doc/src/rust/operators.md b/doc/src/rust/operators.md index 8374bfb4..e4ce9b65 100644 --- a/doc/src/rust/operators.md +++ b/doc/src/rust/operators.md @@ -39,17 +39,19 @@ engine.register_fn("+", strange_add); // overload '+' operator for let result: i64 = engine.eval("1 + 0"); // the overloading version is used -println!("result: {}", result); // prints 42 +result == 42; let result: f64 = engine.eval("1.0 + 0.0"); // '+' operator for two floats not overloaded -println!("result: {}", result); // prints 1.0 +result == 1.0; fn mixed_add(a: i64, b: f64) -> f64 { (a as f64) + b } engine.register_fn("+", mixed_add); // register '+' operator for an integer and a float -let result: i64 = engine.eval("1 + 1.0"); // prints 2.0 (normally an error) +let result: i64 = engine.eval("1 + 1.0"); // <- normally an error... + +result == 2.0; // ... but not now ``` diff --git a/doc/src/rust/options.md b/doc/src/rust/options.md index f0a14b97..a87e1d21 100644 --- a/doc/src/rust/options.md +++ b/doc/src/rust/options.md @@ -15,3 +15,4 @@ A number of other configuration options are available from the `Engine` to fine- | `set_max_string_size` | [`unchecked`] | Set the maximum length (in UTF-8 bytes) for [strings]. See [maximum length of strings]. | | `set_max_array_size` | [`unchecked`], [`no_index`] | Set the maximum size for [arrays]. See [maximum size of arrays]. | | `set_max_map_size` | [`unchecked`], [`no_object`] | Set the maximum number of properties for [object maps]. See [maximum size of object maps]. | +| `disable_symbol` | | Disable a certain keyword or operator. See [disable keywords and operators]. | diff --git a/doc/src/rust/register-raw.md b/doc/src/rust/register-raw.md new file mode 100644 index 00000000..6d3f22c5 --- /dev/null +++ b/doc/src/rust/register-raw.md @@ -0,0 +1,171 @@ +Use the Low-Level API to Register a Rust Function +================================================ + +{{#include ../links.md}} + +When a native Rust function is registered with an `Engine` using the `Engine::register_XXX` API, +Rhai transparently converts all function arguments from [`Dynamic`] into the correct types before +calling the function. + +For more power and flexibility, there is a _low-level_ API to work directly with [`Dynamic`] values +without the conversions. + + +Raw Function Registration +------------------------- + +The `Engine::register_raw_fn` method is marked _volatile_, meaning that it may be changed without warning. + +If this is acceptable, then using this method to register a Rust function opens up more opportunities. + +In particular, a reference to the current `Engine` instance is passed as an argument so the Rust function +can also use `Engine` facilities (like evaluating a script). + +```rust +engine.register_raw_fn( + "increment_by", // function name + &[ // a slice containing parameter types + std::any::TypeId::of::(), // type of first parameter + std::any::TypeId::of::() // type of second parameter + ], + |engine: &Engine, lib: &Module, args: &mut [&mut Dynamic]| { // fixed function signature + // Arguments are guaranteed to be correct in number and of the correct types. + + // But remember this is Rust, so you can keep only one mutable reference at any one time! + // Therefore, get a '&mut' reference to the first argument _last_. + // Alternatively, use `args.split_at_mut(1)` etc. to split the slice first. + + let y: i64 = *args[1].downcast_ref::() // get a reference to the second argument + .unwrap(); // then copying it because it is a primary type + + let y: i64 = std::mem::take(args[1]).cast::(); // alternatively, directly 'consume' it + + let x: &mut i64 = args[0].downcast_mut::() // get a '&mut' reference to the + .unwrap(); // first argument + + *x += y; // perform the action + + Ok(().into()) // must be 'Result>' + } +); + +// The above is the same as (in fact, internally they are equivalent): + +engine.register_fn("increment_by", |x: &mut i64, y: i64| x += y); +``` + + +Shortcuts +--------- + +As usual with Rhai, there are shortcuts. For functions of zero to four parameters, which should be +the majority, use one of the `Engine::register_raw_fn_n` (where `n = 0..4`) methods: + +```rust +// Specify parameter types as generics +engine.register_raw_fn_2::( + "increment_by", + |engine: &Engine, lib: &Module, args: &mut [&mut Dynamic]| { ... } +); +``` + + +Closure Signature +----------------- + +The closure passed to `Engine::register_raw_fn` takes the following form: + +`Fn(engine: &Engine, lib: &Module, args: &mut [&mut Dynamic]) -> Result> + 'static` + +where: + +* `T : Variant + Clone` - return type of the function. + +* `engine : &Engine` - the current [`Engine`], with all configurations and settings. + +* `lib : &Module` - the current global library of script-defined functions, as a [`Module`]. + This is sometimes useful for calling a script-defined function within the same evaluation context using [`Engine::call_fn`][`call_fn`]. + +* `args : &mut [&mut Dynamic]` - a slice containing `&mut` references to [`Dynamic`] values. + The slice is guaranteed to contain enough arguments _of the correct types_. + +Remember, in Rhai, all arguments _except_ the _first_ one are always passed by _value_ (i.e. cloned). +Therefore, it is unnecessary to ever mutate any argument except the first one, as all mutations +will be on the cloned copy. + + +Extract Arguments +----------------- + +To extract an argument from the `args` parameter (`&mut [&mut Dynamic]`), use the following: + +| Argument type | Access (`n` = argument position) | Result | +| ------------------------------ | -------------------------------------- | ---------------------------------------------------------- | +| [Primary type][standard types] | `args[n].clone().cast::()` | Copy of value. | +| Custom type | `args[n].downcast_ref::().unwrap()` | Immutable reference to value. | +| Custom type (consumed) | `std::mem::take(args[n]).cast::()` | The _consumed_ value.
The original value becomes `()`. | +| `this` object | `args[0].downcast_mut::().unwrap()` | Mutable reference to value. | + +When there is a mutable reference to the `this` object (i.e. the first argument), +there can be no other immutable references to `args`, otherwise the Rust borrow checker will complain. + + +Example - Passing a Function Pointer to a Rust Function +------------------------------------------------------ + +```rust +use rhai::{Engine, Module, Dynamic, FnPtr}; + +let mut engine = Engine::new(); + +// Register a Rust function +engine.register_raw_fn( + "bar", + &[ + std::any::TypeId::of::(), // parameter types + std::any::TypeId::of::(), + std::any::TypeId::of::(), + ], + move |engine: &Engine, lib: &Module, args: &mut [&mut Dynamic]| { + // 'args' is guaranteed to contain enough arguments of the correct types + + let fp = std::mem::take(args[1]).cast::(); // 2nd argument - function pointer + let value = args[2].clone(); // 3rd argument - function argument + let this_ptr = args.get_mut(0).unwrap(); // 1st argument - this pointer + + // Use 'call_fn_dynamic' to call the function name. + // Pass 'lib' as the current global library of functions. + engine.call_fn_dynamic(&mut Scope::new(), lib, fp.fn_name(), Some(this_ptr), [value])?; + + Ok(()) + }, +); + +let result = engine.eval::(r#" + fn foo(x) { this += x; } // script-defined function 'foo' + + let x = 41; // object + x.bar(Fn("foo"), 1); // pass 'foo' as function pointer + x +"#)?; +``` + + +Hold Multiple References +------------------------ + +In order to access a value argument that is expensive to clone _while_ holding a mutable reference +to the first argument, either _consume_ that argument via `mem::take` as above, or use `args.split_at` +to partition the slice: + +```rust +// Partition the slice +let (first, rest) = args.split_at_mut(1); + +// Mutable reference to the first parameter +let this_ptr = first[0].downcast_mut::().unwrap(); + +// Immutable reference to the second value parameter +// This can be mutable but there is no point because the parameter is passed by value +let value = rest[0].downcast_ref::().unwrap(); +``` diff --git a/doc/src/rust/scope.md b/doc/src/rust/scope.md index a33e4b4c..daa08a15 100644 --- a/doc/src/rust/scope.md +++ b/doc/src/rust/scope.md @@ -28,11 +28,11 @@ let mut scope = Scope::new(); // Then push (i.e. add) some initialized variables into the state. // Remember the system number types in Rhai are i64 (i32 if 'only_i32') ond f64. // Better stick to them or it gets hard working with the script. -scope.push("y", 42_i64); -scope.push("z", 999_i64); - -// 'set_value' adds a variable when one doesn't exist -scope.set_value("s", "hello, world!".to_string()); // remember to use 'String', not '&str' +scope + .push("y", 42_i64) + .push("z", 999_i64) + .set_value("s", "hello, world!".to_string()); //'set_value' adds a variable when one doesn't exist + // remember to use 'String', not '&str' // First invocation engine.eval_with_scope::<()>(&mut scope, r" 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/rust/strings.md b/doc/src/rust/strings.md index ac03c874..5b72fe6e 100644 --- a/doc/src/rust/strings.md +++ b/doc/src/rust/strings.md @@ -11,9 +11,10 @@ fn get_len1(s: String) -> i64 { s.len() as i64 } // <- Rhai will not fn get_len2(s: &str) -> i64 { s.len() as i64 } // <- Rhai finds this function fine fn get_len3(s: ImmutableString) -> i64 { s.len() as i64 } // <- the above is equivalent to this -engine.register_fn("len1", get_len1); -engine.register_fn("len2", get_len2); -engine.register_fn("len3", get_len3); +engine + .register_fn("len1", get_len1) + .register_fn("len2", get_len2) + .register_fn("len3", get_len3); let len = engine.eval::("x.len1()")?; // error: function 'len1 (&str | ImmutableString)' not found let len = engine.eval::("x.len2()")?; // works fine 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..023834fd 100644 --- a/doc/src/start/features.md +++ b/doc/src/start/features.md @@ -24,7 +24,8 @@ 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. | -| `internals` | Expose internal data structures (e.g. [`AST`] nodes). Beware that Rhai internals are volatile and may change from version to version. | +| `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) and enable defining [custom syntax]. Beware that Rhai internals are volatile and may change from version to version. | Example 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..6b275a71 100644 --- a/src/api.rs +++ b/src/api.rs @@ -1,14 +1,12 @@ //! 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}; use crate::fn_register::RegisterFn; +use crate::module::{FuncReturn, Module}; use crate::optimize::{optimize_into_ast, OptimizationLevel}; use crate::parser::AST; use crate::result::EvalAltResult; @@ -19,6 +17,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, @@ -32,6 +33,173 @@ use crate::stdlib::{fs::File, io::prelude::*, path::PathBuf}; /// Engine public API impl Engine { + /// Register a function of the `Engine`. + /// + /// ## WARNING - Low Level API + /// + /// This function is very low level. It takes a list of `TypeId`'s indicating the actual types of the parameters. + /// + /// Arguments are simply passed in as a mutable array of `&mut Dynamic`, + /// The arguments are guaranteed to be of the correct types matching the `TypeId`'s. + /// + /// To access a primary parameter value (i.e. cloning is cheap), use: `args[n].clone().cast::()` + /// + /// To access a parameter value and avoid cloning, use `std::mem::take(args[n]).cast::()`. + /// Notice that this will _consume_ the argument, replacing it with `()`. + /// + /// To access the first mutable parameter, use `args.get_mut(0).unwrap()` + #[deprecated(note = "this function is volatile and may change")] + pub fn register_raw_fn( + &mut self, + name: &str, + arg_types: &[TypeId], + func: impl Fn(&Engine, &Module, &mut [&mut Dynamic]) -> FuncReturn + SendSync + 'static, + ) -> &mut Self { + self.global_module.set_raw_fn(name, arg_types, func); + self + } + + /// Register a function of no parameters with the `Engine`. + /// + /// ## WARNING - Low Level API + /// + /// This function is very low level. + #[deprecated(note = "this function is volatile and may change")] + pub fn register_raw_fn_0( + &mut self, + name: &str, + func: impl Fn(&Engine, &Module, &mut [&mut Dynamic]) -> FuncReturn + SendSync + 'static, + ) -> &mut Self { + self.global_module.set_raw_fn(name, &[], func); + self + } + + /// Register a function of one parameter with the `Engine`. + /// + /// ## WARNING - Low Level API + /// + /// This function is very low level. + /// + /// Arguments are simply passed in as a mutable array of `&mut Dynamic`, + /// which is guaranteed to contain enough arguments of the correct types. + /// + /// To access a primary parameter value (i.e. cloning is cheap), use: `args[n].clone().cast::()` + /// + /// To access a parameter value and avoid cloning, use `std::mem::take(args[n]).cast::()`. + /// Notice that this will _consume_ the argument, replacing it with `()`. + /// + /// To access the first mutable parameter, use `args.get_mut(0).unwrap()` + #[deprecated(note = "this function is volatile and may change")] + pub fn register_raw_fn_1( + &mut self, + name: &str, + func: impl Fn(&Engine, &Module, &mut [&mut Dynamic]) -> FuncReturn + SendSync + 'static, + ) -> &mut Self { + self.global_module + .set_raw_fn(name, &[TypeId::of::
()], func); + self + } + + /// Register a function of two parameters with the `Engine`. + /// + /// ## WARNING - Low Level API + /// + /// This function is very low level. + /// + /// Arguments are simply passed in as a mutable array of `&mut Dynamic`, + /// which is guaranteed to contain enough arguments of the correct types. + /// + /// To access a primary parameter value (i.e. cloning is cheap), use: `args[n].clone().cast::()` + /// + /// To access a parameter value and avoid cloning, use `std::mem::take(args[n]).cast::()`. + /// Notice that this will _consume_ the argument, replacing it with `()`. + /// + /// To access the first mutable parameter, use `args.get_mut(0).unwrap()` + #[deprecated(note = "this function is volatile and may change")] + pub fn register_raw_fn_2( + &mut self, + name: &str, + func: impl Fn(&Engine, &Module, &mut [&mut Dynamic]) -> FuncReturn + SendSync + 'static, + ) -> &mut Self { + self.global_module + .set_raw_fn(name, &[TypeId::of::(), TypeId::of::()], func); + self + } + + /// Register a function of three parameters with the `Engine`. + /// + /// ## WARNING - Low Level API + /// + /// This function is very low level. + /// + /// Arguments are simply passed in as a mutable array of `&mut Dynamic`, + /// which is guaranteed to contain enough arguments of the correct types. + /// + /// To access a primary parameter value (i.e. cloning is cheap), use: `args[n].clone().cast::()` + /// + /// To access a parameter value and avoid cloning, use `std::mem::take(args[n]).cast::()`. + /// Notice that this will _consume_ the argument, replacing it with `()`. + /// + /// To access the first mutable parameter, use `args.get_mut(0).unwrap()` + #[deprecated(note = "this function is volatile and may change")] + pub fn register_raw_fn_3< + A: Variant + Clone, + B: Variant + Clone, + C: Variant + Clone, + T: Variant + Clone, + >( + &mut self, + name: &str, + func: impl Fn(&Engine, &Module, &mut [&mut Dynamic]) -> FuncReturn + SendSync + 'static, + ) -> &mut Self { + self.global_module.set_raw_fn( + name, + &[TypeId::of::(), TypeId::of::(), TypeId::of::()], + func, + ); + self + } + + /// Register a function of four parameters with the `Engine`. + /// + /// ## WARNING - Low Level API + /// + /// This function is very low level. + /// + /// Arguments are simply passed in as a mutable array of `&mut Dynamic`, + /// which is guaranteed to contain enough arguments of the correct types. + /// + /// To access a primary parameter value (i.e. cloning is cheap), use: `args[n].clone().cast::()` + /// + /// To access a parameter value and avoid cloning, use `std::mem::take(args[n]).cast::()`. + /// Notice that this will _consume_ the argument, replacing it with `()`. + /// + /// To access the first mutable parameter, use `args.get_mut(0).unwrap()` + #[deprecated(note = "this function is volatile and may change")] + pub fn register_raw_fn_4< + A: Variant + Clone, + B: Variant + Clone, + C: Variant + Clone, + D: Variant + Clone, + T: Variant + Clone, + >( + &mut self, + name: &str, + func: impl Fn(&Engine, &Module, &mut [&mut Dynamic]) -> FuncReturn + SendSync + 'static, + ) -> &mut Self { + self.global_module.set_raw_fn( + name, + &[ + TypeId::of::(), + TypeId::of::(), + TypeId::of::(), + TypeId::of::(), + ], + func, + ); + self + } + /// Register a custom type for use with the `Engine`. /// The type must implement `Clone`. /// @@ -69,8 +237,8 @@ impl Engine { /// # } /// ``` #[cfg(not(feature = "no_object"))] - pub fn register_type(&mut self) { - self.register_type_with_name::(type_name::()); + pub fn register_type(&mut self) -> &mut Self { + self.register_type_with_name::(type_name::()) } /// Register a custom type for use with the `Engine`, with a pretty-print name @@ -117,16 +285,23 @@ impl Engine { /// # } /// ``` #[cfg(not(feature = "no_object"))] - pub fn register_type_with_name(&mut self, name: &str) { + pub fn register_type_with_name(&mut self, name: &str) -> &mut Self { + 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()); + self } /// Register an iterator adapter for a type with the `Engine`. /// This is an advanced feature. - pub fn register_iterator(&mut self, f: IteratorFn) { + pub fn register_iterator(&mut self, f: IteratorFn) -> &mut Self { self.global_module.set_iter(TypeId::of::(), f); + self } /// Register a getter function for a member of a registered type with the `Engine`. @@ -170,11 +345,12 @@ impl Engine { &mut self, name: &str, callback: impl Fn(&mut T) -> U + SendSync + 'static, - ) where + ) -> &mut Self + where T: Variant + Clone, U: Variant + Clone, { - self.register_fn(&make_getter(name), callback); + self.register_fn(&make_getter(name), callback) } /// Register a setter function for a member of a registered type with the `Engine`. @@ -218,11 +394,12 @@ impl Engine { &mut self, name: &str, callback: impl Fn(&mut T, U) + SendSync + 'static, - ) where + ) -> &mut Self + where T: Variant + Clone, U: Variant + Clone, { - self.register_fn(&make_setter(name), callback); + self.register_fn(&make_setter(name), callback) } /// Shorthand for registering both getter and setter functions @@ -269,12 +446,12 @@ impl Engine { name: &str, get_fn: impl Fn(&mut T) -> U + SendSync + 'static, set_fn: impl Fn(&mut T, U) + SendSync + 'static, - ) where + ) -> &mut Self + where T: Variant + Clone, U: Variant + Clone, { - self.register_get(name, get_fn); - self.register_set(name, set_fn); + self.register_get(name, get_fn).register_set(name, set_fn) } /// Register an index getter for a registered type with the `Engine`. @@ -318,12 +495,13 @@ impl Engine { pub fn register_indexer_get( &mut self, callback: impl Fn(&mut T, X) -> U + SendSync + 'static, - ) where + ) -> &mut Self + where T: Variant + Clone, U: Variant + Clone, X: Variant + Clone, { - self.register_fn(FN_IDX_GET, callback); + self.register_fn(FN_IDX_GET, callback) } /// Register an index setter for a registered type with the `Engine`. @@ -366,12 +544,13 @@ impl Engine { pub fn register_indexer_set( &mut self, callback: impl Fn(&mut T, X, U) -> () + SendSync + 'static, - ) where + ) -> &mut Self + where T: Variant + Clone, U: Variant + Clone, X: Variant + Clone, { - self.register_fn(FN_IDX_SET, callback); + self.register_fn(FN_IDX_SET, callback) } /// Shorthand for register both index getter and setter functions for a registered type with the `Engine`. @@ -413,16 +592,17 @@ impl Engine { &mut self, getter: impl Fn(&mut T, X) -> U + SendSync + 'static, setter: impl Fn(&mut T, X, U) -> () + SendSync + 'static, - ) where + ) -> &mut Self + where T: Variant + Clone, U: Variant + Clone, X: Variant + Clone, { - self.register_indexer_get(getter); - self.register_indexer_set(setter); + self.register_indexer_get(getter) + .register_indexer_set(setter) } - /// Compile a string into an [`AST`], which can be used later for evaluation. + /// Compile a string into an `AST`, which can be used later for evaluation. /// /// # Example /// @@ -445,7 +625,7 @@ impl Engine { self.compile_with_scope(&Scope::new(), script) } - /// Compile a string into an [`AST`] using own scope, which can be used later for evaluation. + /// Compile a string into an `AST` using own scope, which can be used later for evaluation. /// /// The scope is useful for passing constants into the script for optimization /// when using `OptimizationLevel::Full`. @@ -488,7 +668,7 @@ impl Engine { } /// When passed a list of strings, first join the strings into one large script, - /// and then compile them into an [`AST`] using own scope, which can be used later for evaluation. + /// and then compile them into an `AST` using own scope, which can be used later for evaluation. /// /// The scope is useful for passing constants into the script for optimization /// when using `OptimizationLevel::Full`. @@ -541,14 +721,14 @@ impl Engine { self.compile_with_scope_and_optimization_level(scope, scripts, self.optimization_level) } - /// Join a list of strings and compile into an [`AST`] using own scope at a specific optimization level. + /// Join a list of strings and compile into an `AST` using own scope at a specific optimization level. pub(crate) fn compile_with_scope_and_optimization_level( &self, scope: &Scope, 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) } @@ -577,7 +757,7 @@ impl Engine { Ok(contents) } - /// Compile a script file into an [`AST`], which can be used later for evaluation. + /// Compile a script file into an `AST`, which can be used later for evaluation. /// /// # Example /// @@ -603,7 +783,7 @@ impl Engine { self.compile_file_with_scope(&Scope::new(), path) } - /// Compile a script file into an [`AST`] using own scope, which can be used later for evaluation. + /// Compile a script file into an `AST` using own scope, which can be used later for evaluation. /// /// The scope is useful for passing constants into the script for optimization /// when using `OptimizationLevel::Full`. @@ -673,7 +853,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)?; @@ -685,7 +865,7 @@ impl Engine { self.eval_ast_with_scope(&mut scope, &ast) } - /// Compile a string containing an expression into an [`AST`], + /// Compile a string containing an expression into an `AST`, /// which can be used later for evaluation. /// /// # Example @@ -709,7 +889,7 @@ impl Engine { self.compile_expression_with_scope(&Scope::new(), script) } - /// Compile a string containing an expression into an [`AST`] using own scope, + /// Compile a string containing an expression into an `AST` using own scope, /// which can be used later for evaluation. /// /// The scope is useful for passing constants into the script for optimization @@ -754,7 +934,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) @@ -843,8 +1023,8 @@ impl Engine { /// let mut scope = Scope::new(); /// scope.push("x", 40_i64); /// - /// assert_eq!(engine.eval_with_scope::(&mut scope, "x = x + 2; x")?, 42); - /// assert_eq!(engine.eval_with_scope::(&mut scope, "x = x + 2; x")?, 44); + /// assert_eq!(engine.eval_with_scope::(&mut scope, "x += 2; x")?, 42); + /// assert_eq!(engine.eval_with_scope::(&mut scope, "x += 2; x")?, 44); /// /// // The variable in the scope is modified /// assert_eq!(scope.get_value::("x").expect("variable x should exist"), 44); @@ -909,7 +1089,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)?; @@ -917,7 +1097,7 @@ impl Engine { self.eval_ast_with_scope(scope, &ast) } - /// Evaluate an [`AST`]. + /// Evaluate an `AST`. /// /// # Example /// @@ -939,7 +1119,7 @@ impl Engine { self.eval_ast_with_scope(&mut Scope::new(), ast) } - /// Evaluate an [`AST`] with own scope. + /// Evaluate an `AST` with own scope. /// /// # Example /// @@ -957,7 +1137,7 @@ impl Engine { /// scope.push("x", 40_i64); /// /// // Compile a script to an AST and store it for later evaluation - /// let ast = engine.compile("x = x + 2; x")?; + /// let ast = engine.compile("x += 2; x")?; /// /// // Evaluate it /// assert_eq!(engine.eval_ast_with_scope::(&mut scope, &ast)?, 42); @@ -976,17 +1156,18 @@ 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(), )) }); } - /// Evaluate an [`AST`] with own scope. + /// Evaluate an `AST` with own scope. pub(crate) fn eval_ast_with_scope_raw<'a>( &self, scope: &mut Scope, @@ -1041,7 +1222,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) } @@ -1052,7 +1233,7 @@ impl Engine { self.consume_ast_with_scope(&mut Scope::new(), ast) } - /// Evaluate an [`AST`] with own scope, but throw away the result and only return error (if any). + /// Evaluate an `AST` with own scope, but throw away the result and only return error (if any). /// Useful for when you don't need the result, but still need to keep track of possible errors. pub fn consume_ast_with_scope( &self, @@ -1076,7 +1257,7 @@ impl Engine { ) } - /// Call a script function defined in an [`AST`] with multiple arguments. + /// Call a script function defined in an `AST` with multiple arguments. /// Arguments are passed as a tuple. /// /// # Example @@ -1121,19 +1302,28 @@ impl Engine { args: A, ) -> Result> { let mut arg_values = args.into_vec(); - let result = self.call_fn_dynamic_raw(scope, ast, name, arg_values.as_mut())?; + let result = self.call_fn_dynamic_raw(scope, ast, name, &mut None, 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(), )) }); } - /// Call a script function defined in an [`AST`] with multiple `Dynamic` arguments. + /// Call a script function defined in an `AST` with multiple `Dynamic` arguments + /// and optionally a value for binding to the 'this' pointer. + /// + /// ## WARNING + /// + /// All the arguments are _consumed_, meaning that they're replaced by `()`. + /// 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. /// /// # Example /// @@ -1141,7 +1331,7 @@ impl Engine { /// # fn main() -> Result<(), Box> { /// # #[cfg(not(feature = "no_function"))] /// # { - /// use rhai::{Engine, Scope}; + /// use rhai::{Engine, Scope, Dynamic}; /// /// let engine = Engine::new(); /// @@ -1149,20 +1339,27 @@ impl Engine { /// fn add(x, y) { len(x) + y + foo } /// fn add1(x) { len(x) + 1 + foo } /// fn bar() { foo/2 } + /// fn action(x) { this += x; } // function using 'this' pointer /// ")?; /// /// let mut scope = Scope::new(); /// scope.push("foo", 42_i64); /// /// // Call the script-defined function - /// let result = engine.call_fn_dynamic(&mut scope, &ast, "add", vec![ String::from("abc").into(), 123_i64.into() ])?; + /// let result = engine.call_fn_dynamic(&mut scope, &ast, "add", None, [ String::from("abc").into(), 123_i64.into() ])?; + /// // ^^^^ no 'this' pointer /// assert_eq!(result.cast::(), 168); /// - /// let result = engine.call_fn_dynamic(&mut scope, &ast, "add1", vec![ String::from("abc").into() ])?; + /// let result = engine.call_fn_dynamic(&mut scope, &ast, "add1", None, [ String::from("abc").into() ])?; /// assert_eq!(result.cast::(), 46); /// - /// let result= engine.call_fn_dynamic(&mut scope, &ast, "bar", vec![])?; + /// let result = engine.call_fn_dynamic(&mut scope, &ast, "bar", None, [])?; /// assert_eq!(result.cast::(), 21); + /// + /// let mut value: Dynamic = 1_i64.into(); + /// let result = engine.call_fn_dynamic(&mut scope, &ast, "action", Some(&mut value), [ 41_i64.into() ])?; + /// // ^^^^^^^^^^^^^^^^ binding the 'this' pointer + /// assert_eq!(value.as_int().unwrap(), 42); /// # } /// # Ok(()) /// # } @@ -1171,15 +1368,15 @@ impl Engine { pub fn call_fn_dynamic( &self, scope: &mut Scope, - ast: &AST, + lib: impl AsRef, name: &str, - arg_values: impl IntoIterator, - ) -> Result> { - let mut arg_values: StaticVec<_> = arg_values.into_iter().collect(); - self.call_fn_dynamic_raw(scope, ast, name, arg_values.as_mut()) + mut this_ptr: Option<&mut Dynamic>, + mut arg_values: impl AsMut<[Dynamic]>, + ) -> FuncReturn { + self.call_fn_dynamic_raw(scope, lib, name, &mut this_ptr, arg_values.as_mut()) } - /// Call a script function defined in an [`AST`] with multiple `Dynamic` arguments. + /// Call a script function defined in an `AST` with multiple `Dynamic` arguments. /// /// ## WARNING /// @@ -1187,16 +1384,19 @@ 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, - ast: &AST, + lib: impl AsRef, name: &str, + this_ptr: &mut Option<&mut Dynamic>, arg_values: &mut [Dynamic], - ) -> Result> { + ) -> FuncReturn { + let lib = lib.as_ref(); let mut args: StaticVec<_> = arg_values.iter_mut().collect(); - let fn_def = get_script_function_by_signature(ast.lib(), name, args.len(), true) - .ok_or_else(|| { + let fn_def = + get_script_function_by_signature(lib, name, args.len(), true).ok_or_else(|| { Box::new(EvalAltResult::ErrorFunctionNotFound( name.into(), Position::none(), @@ -1208,27 +1408,19 @@ impl Engine { let args = args.as_mut(); self.call_script_fn( - scope, - &mut mods, - &mut state, - ast.lib(), - &mut None, - name, - fn_def, - args, - 0, + scope, &mut mods, &mut state, lib, this_ptr, name, fn_def, args, 0, ) } - /// Optimize the [`AST`] with constants defined in an external Scope. - /// An optimized copy of the [`AST`] is returned while the original [`AST`] is consumed. + /// Optimize the `AST` with constants defined in an external Scope. + /// An optimized copy of the `AST` is returned while the original `AST` is consumed. /// /// Although optimization is performed by default during compilation, sometimes it is necessary to /// _re_-optimize an AST. For example, when working with constants that are passed in via an - /// external scope, it will be more efficient to optimize the [`AST`] once again to take advantage + /// external scope, it will be more efficient to optimize the `AST` once again to take advantage /// of the new constants. /// - /// With this method, it is no longer necessary to recompile a large script. The script [`AST`] can be + /// With this method, it is no longer necessary to recompile a large script. The script `AST` can be /// compiled just once. Before evaluation, constants are passed into the `Engine` via an external scope /// (i.e. with `scope.push_constant(...)`). Then, the `AST is cloned and the copy re-optimized before running. #[cfg(not(feature = "no_optimize"))] @@ -1238,6 +1430,7 @@ impl Engine { mut ast: AST, optimization_level: OptimizationLevel, ) -> AST { + #[cfg(not(feature = "no_function"))] let lib = ast .lib() .iter_fn() @@ -1245,6 +1438,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) } @@ -1283,8 +1479,12 @@ impl Engine { /// # Ok(()) /// # } /// ``` - pub fn on_progress(&mut self, callback: impl Fn(&u64) -> bool + SendSync + 'static) { + pub fn on_progress( + &mut self, + callback: impl Fn(&u64) -> bool + SendSync + 'static, + ) -> &mut Self { self.progress = Some(Box::new(callback)); + self } /// Override default action of `print` (print to stdout using `println!`) @@ -1311,8 +1511,9 @@ impl Engine { /// # Ok(()) /// # } /// ``` - pub fn on_print(&mut self, callback: impl Fn(&str) + SendSync + 'static) { + pub fn on_print(&mut self, callback: impl Fn(&str) + SendSync + 'static) -> &mut Self { self.print = Box::new(callback); + self } /// Override default action of `debug` (print to stdout using `println!`) @@ -1339,7 +1540,8 @@ impl Engine { /// # Ok(()) /// # } /// ``` - pub fn on_debug(&mut self, callback: impl Fn(&str) + SendSync + 'static) { + pub fn on_debug(&mut self, callback: impl Fn(&str) + SendSync + 'static) -> &mut Self { self.debug = Box::new(callback); + self } } diff --git a/src/engine.rs b/src/engine.rs index 65c91735..0455c6f1 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -1,27 +1,31 @@ //! 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; use crate::scope::{EntryType as ScopeEntryType, Scope}; -use crate::token::{is_valid_identifier, Position}; +use crate::token::Position; use crate::utils::StaticVec; #[cfg(not(feature = "no_float"))] use crate::parser::FLOAT; +#[cfg(feature = "internals")] +use crate::syntax::CustomSyntax; + use crate::stdlib::{ - any::TypeId, + any::{type_name, TypeId}, borrow::Cow, boxed::Box, - collections::HashMap, + collections::{HashMap, HashSet}, + convert::TryFrom, format, iter::{empty, once}, mem, @@ -35,7 +39,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"))] @@ -81,8 +85,45 @@ pub const KEYWORD_THIS: &str = "this"; pub const FN_TO_STRING: &str = "to_string"; pub const FN_GET: &str = "get$"; pub const FN_SET: &str = "set$"; -pub const FN_IDX_GET: &str = "$index$get$"; -pub const FN_IDX_SET: &str = "$index$set$"; +pub const FN_IDX_GET: &str = "index$get$"; +pub const FN_IDX_SET: &str = "index$set$"; + +#[cfg(feature = "internals")] +pub const MARKER_EXPR: &str = "$expr$"; +#[cfg(feature = "internals")] +pub const MARKER_BLOCK: &str = "$block$"; +#[cfg(feature = "internals")] +pub const MARKER_IDENT: &str = "$ident$"; + +#[cfg(feature = "internals")] +pub struct Expression<'a>(&'a Expr); + +#[cfg(feature = "internals")] +impl<'a> From<&'a Expr> for Expression<'a> { + fn from(expr: &'a Expr) -> Self { + Self(expr) + } +} + +#[cfg(feature = "internals")] +impl Expression<'_> { + /// If this expression is a variable name, return it. Otherwise `None`. + #[cfg(feature = "internals")] + pub fn get_variable_name(&self) -> Option<&str> { + match self.0 { + Expr::Variable(x) => Some((x.0).0.as_str()), + _ => None, + } + } + /// Get the expression. + pub(crate) fn expr(&self) -> &Expr { + &self.0 + } + /// Get the position of this expression. + pub fn position(&self) -> Position { + self.0.position() + } +} /// A type specifying the method of chaining. #[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)] @@ -216,6 +257,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 +307,15 @@ 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>, + /// Custom syntax. + #[cfg(feature = "internals")] + pub(crate) custom_syntax: Option>, /// Callback closure for implementing the `print` command. pub(crate) print: Callback, @@ -312,7 +362,12 @@ 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, + + #[cfg(feature = "internals")] + custom_syntax: None, // default print/debug implementations print: Box::new(default_print), @@ -352,17 +407,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 +425,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 @@ -396,6 +445,39 @@ fn default_print(s: &str) { /// Search for a module within an imports stack. /// Position in `EvalAltResult` is None and must be set afterwards. fn search_imports<'s>( + mods: &'s Imports, + state: &mut State, + modules: &Box, +) -> Result<&'s Module, Box> { + let (root, root_pos) = modules.get(0); + + // Qualified - check if the root module is directly indexed + let index = if state.always_search { + None + } else { + modules.index() + }; + + Ok(if let Some(index) = index { + let offset = mods.len() - index.get(); + &mods.get(offset).unwrap().1 + } else { + mods.iter() + .rev() + .find(|(n, _)| n == root) + .map(|(_, m)| m) + .ok_or_else(|| { + Box::new(EvalAltResult::ErrorModuleNotFound( + root.to_string(), + *root_pos, + )) + })? + }) +} + +/// Search for a module within an imports stack. +/// Position in `EvalAltResult` is None and must be set afterwards. +fn search_imports_mut<'s>( mods: &'s mut Imports, state: &mut State, modules: &Box, @@ -426,15 +508,49 @@ fn search_imports<'s>( }) } -/// Search for a variable within the scope -fn search_scope<'s, 'a>( +/// Search for a variable within the scope and imports +fn search_namespace<'s, 'a>( scope: &'s mut Scope, mods: &'s mut Imports, state: &mut State, this_ptr: &'s mut Option<&mut Dynamic>, expr: &'a Expr, ) -> Result<(&'s mut Dynamic, &'a str, ScopeEntryType, Position), Box> { - let ((name, pos), modules, hash_var, index) = match expr { + match expr { + Expr::Variable(v) => match v.as_ref() { + // Qualified variable + ((name, pos), Some(modules), hash_var, _) => { + let module = search_imports_mut(mods, state, modules)?; + let target = module + .get_qualified_var_mut(*hash_var) + .map_err(|err| match *err { + EvalAltResult::ErrorVariableNotFound(_, _) => { + Box::new(EvalAltResult::ErrorVariableNotFound( + format!("{}{}", modules, name), + *pos, + )) + } + _ => err.new_position(*pos), + })?; + + // Module variables are constant + Ok((target, name, ScopeEntryType::Constant, *pos)) + } + // Normal variable access + _ => search_scope_only(scope, state, this_ptr, expr), + }, + _ => unreachable!(), + } +} + +/// Search for a variable within the scope +fn search_scope_only<'s, 'a>( + scope: &'s mut Scope, + state: &mut State, + this_ptr: &'s mut Option<&mut Dynamic>, + expr: &'a Expr, +) -> Result<(&'s mut Dynamic, &'a str, ScopeEntryType, Position), Box> { + let ((name, pos), _, _, index) = match expr { Expr::Variable(v) => v.as_ref(), _ => unreachable!(), }; @@ -448,37 +564,21 @@ fn search_scope<'s, 'a>( } } - // Check if it is qualified - if let Some(modules) = modules { - let module = search_imports(mods, state, modules)?; - let target = module - .get_qualified_var_mut(*hash_var) - .map_err(|err| match *err { - EvalAltResult::ErrorVariableNotFound(_, _) => Box::new( - EvalAltResult::ErrorVariableNotFound(format!("{}{}", modules, name), *pos), - ), - _ => err.new_position(*pos), - })?; + // Check if it is directly indexed + let index = if state.always_search { None } else { *index }; - // Module variables are constant - Ok((target, name, ScopeEntryType::Constant, *pos)) + let index = if let Some(index) = index { + scope.len() - index.get() } else { - // Unqualified - check if it is directly indexed - let index = if state.always_search { None } else { *index }; + // Find the variable in the scope + scope + .get_index(name) + .ok_or_else(|| Box::new(EvalAltResult::ErrorVariableNotFound(name.into(), *pos)))? + .0 + }; - let index = if let Some(index) = index { - scope.len() - index.get() - } else { - // Find the variable in the scope - scope - .get_index(name) - .ok_or_else(|| Box::new(EvalAltResult::ErrorVariableNotFound(name.into(), *pos)))? - .0 - }; - - let (val, typ) = scope.get_mut(index); - Ok((val, name, typ, *pos)) - } + let (val, typ) = scope.get_mut(index); + Ok((val, name, typ, *pos)) } impl Engine { @@ -497,7 +597,13 @@ impl Engine { global_module: Default::default(), module_resolver: None, - type_names: Default::default(), + type_names: None, + disabled_symbols: None, + custom_keywords: None, + + #[cfg(feature = "internals")] + custom_syntax: None, + print: Box::new(|_| {}), debug: Box::new(|_| {}), progress: None, @@ -519,158 +625,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 +654,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 +719,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 +748,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 +757,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, lib, 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 +1019,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 @@ -1093,6 +1052,81 @@ impl Engine { return Ok(result); } + /// Call a dot method. + fn call_method( + &self, + state: &mut State, + lib: &Module, + target: &mut Target, + expr: &Expr, + mut idx_val: Dynamic, + level: usize, + ) -> Result<(Dynamic, bool), Box> { + let ((name, native, pos), _, hash, _, def_val) = match expr { + Expr::FnCall(x) => x.as_ref(), + _ => unreachable!(), + }; + + let is_ref = target.is_ref(); + let is_value = target.is_value(); + let def_val = def_val.as_ref(); + + // Get a reference to the mutation target Dynamic + let obj = target.as_mut(); + let idx = idx_val.downcast_mut::>().unwrap(); + let mut fn_name = name.as_ref(); + + // Check if it is a FnPtr call + let (result, updated) = if fn_name == KEYWORD_FN_PTR_CALL && obj.is::() { + // Redirect function name + fn_name = obj.as_str().unwrap(); + // Recalculate hash + let hash = calc_fn_hash(empty(), fn_name, idx.len(), empty()); + // Arguments are passed as-is + let mut arg_values = idx.iter_mut().collect::>(); + let args = arg_values.as_mut(); + + // Map it to name(args) in function-call style + self.exec_fn_call( + state, lib, fn_name, *native, hash, args, false, false, def_val, level, + ) + } else { + let redirected: Option; + let mut hash = *hash; + + // Check if it is a map method call in OOP style + if let Some(map) = obj.downcast_ref::() { + if let Some(val) = map.get(fn_name) { + if let Some(f) = val.downcast_ref::() { + // Remap the function name + redirected = Some(f.get_fn_name().clone()); + fn_name = redirected.as_ref().unwrap(); + + // Recalculate the hash based on the new function name + hash = calc_fn_hash(empty(), fn_name, idx.len(), empty()); + } + } + }; + + // Attached object pointer in front of the arguments + let mut arg_values = once(obj).chain(idx.iter_mut()).collect::>(); + let args = arg_values.as_mut(); + + self.exec_fn_call( + state, lib, fn_name, *native, hash, args, is_ref, true, def_val, level, + ) + } + .map_err(|err| err.new_position(*pos))?; + + // Feed the changed temp value back + if updated && !is_ref && !is_value { + let new_val = target.as_mut().clone(); + target.set_value(new_val)?; + } + + Ok((result, updated)) + } + /// Chain-evaluate a dot/index chain. /// Position in `EvalAltResult` is None and must be set afterwards. fn eval_dot_index_chain_helper( @@ -1112,7 +1146,6 @@ impl Engine { } let is_ref = target.is_ref(); - let is_value = target.is_value(); let next_chain = match rhs { Expr::Index(_) => ChainType::Index, @@ -1121,7 +1154,7 @@ impl Engine { }; // Pop the last index value - let mut idx_val = idx_values.pop(); + let idx_val = idx_values.pop(); match chain_type { #[cfg(not(feature = "no_index"))] @@ -1205,69 +1238,7 @@ impl Engine { match rhs { // xxx.fn_name(arg_expr_list) Expr::FnCall(x) if x.1.is_none() => { - let ((name, native, pos), _, hash, _, def_val) = x.as_ref(); - let def_val = def_val.as_ref(); - - // Get a reference to the mutation target Dynamic - let (result, updated) = { - let obj = target.as_mut(); - let idx = idx_val.downcast_mut::>().unwrap(); - let mut fn_name = name.as_ref(); - - // Check if it is a FnPtr call - if fn_name == KEYWORD_FN_PTR_CALL && obj.is::() { - // Redirect function name - fn_name = obj.as_str().unwrap(); - // Recalculate hash - let hash = calc_fn_hash(empty(), fn_name, idx.len(), empty()); - // Arguments are passed as-is - let mut arg_values = idx.iter_mut().collect::>(); - let args = arg_values.as_mut(); - - // Map it to name(args) in function-call style - self.exec_fn_call( - state, lib, fn_name, *native, hash, args, false, false, - def_val, level, - ) - } else { - let redirected: Option; - let mut hash = *hash; - - // Check if it is a map method call in OOP style - if let Some(map) = obj.downcast_ref::() { - if let Some(val) = map.get(fn_name) { - if let Some(f) = val.downcast_ref::() { - // Remap the function name - redirected = Some(f.get_fn_name().clone()); - fn_name = redirected.as_ref().unwrap(); - - // Recalculate the hash based on the new function name - hash = - calc_fn_hash(empty(), fn_name, idx.len(), empty()); - } - } - }; - - // Attached object pointer in front of the arguments - let mut arg_values = - once(obj).chain(idx.iter_mut()).collect::>(); - let args = arg_values.as_mut(); - - self.exec_fn_call( - state, lib, fn_name, *native, hash, args, is_ref, true, - def_val, level, - ) - } - .map_err(|err| err.new_position(*pos))? - }; - - // Feed the changed temp value back - if updated && !is_ref && !is_value { - let new_val = target.as_mut().clone(); - target.set_value(new_val)?; - } - - Ok((result, updated)) + self.call_method(state, lib, target, rhs, idx_val, level) } // xxx.module::fn_name(...) - syntax error Expr::FnCall(_) => unreachable!(), @@ -1311,16 +1282,26 @@ impl Engine { .map(|(v, _)| (v, false)) .map_err(|err| err.new_position(*pos)) } - // {xxx:map}.prop[expr] | {xxx:map}.prop.expr + // {xxx:map}.sub_lhs[expr] | {xxx:map}.sub_lhs.expr Expr::Index(x) | Expr::Dot(x) if target.is::() => { - let (prop, expr, pos) = x.as_ref(); + let (sub_lhs, expr, pos) = x.as_ref(); - let mut val = if let Expr::Property(p) = prop { - let ((prop, _, _), _) = p.as_ref(); - let index = prop.clone().into(); - self.get_indexed_mut(state, lib, target, index, *pos, false, level)? - } else { - unreachable!(); + let mut val = match sub_lhs { + Expr::Property(p) => { + let ((prop, _, _), _) = p.as_ref(); + let index = prop.clone().into(); + self.get_indexed_mut(state, lib, target, index, *pos, false, level)? + } + // {xxx:map}.fn_name(arg_expr_list)[expr] | {xxx:map}.fn_name(arg_expr_list).expr + Expr::FnCall(x) if x.1.is_none() => { + let (val, _) = + self.call_method(state, lib, target, sub_lhs, idx_val, level)?; + val.into() + } + // {xxx:map}.module::fn_name(...) - syntax error + Expr::FnCall(_) => unreachable!(), + // Others - syntax error + _ => unreachable!(), }; self.eval_dot_index_chain_helper( @@ -1329,49 +1310,72 @@ impl Engine { ) .map_err(|err| err.new_position(*pos)) } - // xxx.prop[expr] | xxx.prop.expr + // xxx.sub_lhs[expr] | xxx.sub_lhs.expr Expr::Index(x) | Expr::Dot(x) => { - let (prop, expr, pos) = x.as_ref(); - let args = &mut [target.as_mut(), &mut Default::default()]; + let (sub_lhs, expr, pos) = x.as_ref(); - let (mut val, updated) = if let Expr::Property(p) = prop { - let ((_, getter, _), _) = p.as_ref(); - let args = &mut args[..1]; - self.exec_fn_call( - state, lib, getter, true, 0, args, is_ref, true, None, level, - ) - .map_err(|err| err.new_position(*pos))? - } else { - unreachable!(); - }; - let val = &mut val; - let target = &mut val.into(); + match sub_lhs { + // xxx.prop[expr] | xxx.prop.expr + Expr::Property(p) => { + let ((_, getter, setter), _) = p.as_ref(); + let arg_values = &mut [target.as_mut(), &mut Default::default()]; + let args = &mut arg_values[..1]; + let (mut val, updated) = self + .exec_fn_call( + state, lib, getter, true, 0, args, is_ref, true, None, + level, + ) + .map_err(|err| err.new_position(*pos))?; - let (result, may_be_changed) = self - .eval_dot_index_chain_helper( - state, lib, this_ptr, target, expr, idx_values, next_chain, level, - new_val, - ) - .map_err(|err| err.new_position(*pos))?; + let val = &mut val; + let target = &mut val.into(); - // Feed the value back via a setter just in case it has been updated - if updated || may_be_changed { - if let Expr::Property(p) = prop { - let ((_, _, setter), _) = p.as_ref(); - // Re-use args because the first &mut parameter will not be consumed - args[1] = val; - self.exec_fn_call( - state, lib, setter, true, 0, args, is_ref, true, None, level, - ) - .or_else(|err| match *err { - // If there is no setter, no need to feed it back because the property is read-only - EvalAltResult::ErrorDotExpr(_, _) => Ok(Default::default()), - _ => Err(err.new_position(*pos)), - })?; + let (result, may_be_changed) = self + .eval_dot_index_chain_helper( + state, lib, this_ptr, target, expr, idx_values, next_chain, + level, new_val, + ) + .map_err(|err| err.new_position(*pos))?; + + // Feed the value back via a setter just in case it has been updated + if updated || may_be_changed { + // Re-use args because the first &mut parameter will not be consumed + arg_values[1] = val; + self.exec_fn_call( + state, lib, setter, true, 0, arg_values, is_ref, true, + None, level, + ) + .or_else( + |err| match *err { + // If there is no setter, no need to feed it back because the property is read-only + EvalAltResult::ErrorDotExpr(_, _) => { + Ok(Default::default()) + } + _ => Err(err.new_position(*pos)), + }, + )?; + } + + Ok((result, may_be_changed)) } - } + // xxx.fn_name(arg_expr_list)[expr] | xxx.fn_name(arg_expr_list).expr + Expr::FnCall(x) if x.1.is_none() => { + let (mut val, _) = + self.call_method(state, lib, target, sub_lhs, idx_val, level)?; + let val = &mut val; + let target = &mut val.into(); - Ok((result, may_be_changed)) + self.eval_dot_index_chain_helper( + state, lib, this_ptr, target, expr, idx_values, next_chain, + level, new_val, + ) + .map_err(|err| err.new_position(*pos)) + } + // xxx.module::fn_name(...) - syntax error + Expr::FnCall(_) => unreachable!(), + // Others - syntax error + _ => unreachable!(), + } } // Syntax error _ => Err(Box::new(EvalAltResult::ErrorDotExpr( @@ -1406,7 +1410,7 @@ impl Engine { let idx_values = &mut StaticVec::new(); self.eval_indexed_chain( - scope, mods, state, lib, this_ptr, dot_rhs, idx_values, 0, level, + scope, mods, state, lib, this_ptr, dot_rhs, chain_type, idx_values, 0, level, )?; match dot_lhs { @@ -1417,7 +1421,8 @@ impl Engine { self.inc_operations(state) .map_err(|err| err.new_position(*var_pos))?; - let (target, _, typ, pos) = search_scope(scope, mods, state, this_ptr, dot_lhs)?; + let (target, _, typ, pos) = + search_namespace(scope, mods, state, this_ptr, dot_lhs)?; // Constants cannot be modified match typ { @@ -1469,6 +1474,7 @@ impl Engine { lib: &Module, this_ptr: &mut Option<&mut Dynamic>, expr: &Expr, + chain_type: ChainType, idx_values: &mut StaticVec, size: usize, level: usize, @@ -1495,12 +1501,29 @@ impl Engine { // Evaluate in left-to-right order let lhs_val = match lhs { Expr::Property(_) => Default::default(), // Store a placeholder in case of a property + Expr::FnCall(x) if chain_type == ChainType::Dot && x.1.is_none() => { + let arg_values = x + .3 + .iter() + .map(|arg_expr| { + self.eval_expr(scope, mods, state, lib, this_ptr, arg_expr, level) + }) + .collect::, _>>()?; + + Dynamic::from(arg_values) + } + Expr::FnCall(_) => unreachable!(), _ => self.eval_expr(scope, mods, state, lib, this_ptr, lhs, level)?, }; // Push in reverse order + let chain_type = match expr { + Expr::Index(_) => ChainType::Index, + Expr::Dot(_) => ChainType::Dot, + _ => unreachable!(), + }; self.eval_indexed_chain( - scope, mods, state, lib, this_ptr, rhs, idx_values, size, level, + scope, mods, state, lib, this_ptr, rhs, chain_type, idx_values, size, level, )?; idx_values.push(lhs_val); @@ -1684,6 +1707,26 @@ impl Engine { } } + /// Evaluate an expression tree. + /// + /// ## WARNING - Low Level API + /// + /// This function is very low level. It evaluates an expression from an AST. + #[cfg(feature = "internals")] + #[deprecated(note = "this method is volatile and may change")] + pub fn eval_expression_tree( + &self, + scope: &mut Scope, + mods: &mut Imports, + state: &mut State, + lib: &Module, + this_ptr: &mut Option<&mut Dynamic>, + expr: &Expression, + level: usize, + ) -> Result> { + self.eval_expr(scope, mods, state, lib, this_ptr, expr.expr(), level) + } + /// Evaluate an expression fn eval_expr( &self, @@ -1714,7 +1757,7 @@ impl Engine { } } Expr::Variable(_) => { - let (val, _, _, _) = search_scope(scope, mods, state, this_ptr, expr)?; + let (val, _, _, _) = search_namespace(scope, mods, state, this_ptr, expr)?; Ok(val.clone()) } Expr::Property(_) => unreachable!(), @@ -1728,7 +1771,7 @@ impl Engine { let mut rhs_val = self.eval_expr(scope, mods, state, lib, this_ptr, rhs_expr, level)?; let (lhs_ptr, name, typ, pos) = - search_scope(scope, mods, state, this_ptr, lhs_expr)?; + search_namespace(scope, mods, state, this_ptr, lhs_expr)?; self.inc_operations(state) .map_err(|err| err.new_position(pos))?; @@ -1758,7 +1801,7 @@ impl Engine { .or_else(|| self.packages.get_fn(hash_fn)) { // Overriding exact implementation - func(self, &mut [lhs_ptr, &mut rhs_val])?; + func(self, lib, &mut [lhs_ptr, &mut rhs_val])?; } else if run_builtin_op_assignment(op, lhs_ptr, &rhs_val)?.is_none() { // Not built in, map to `var = var op rhs` let op = &op[..op.len() - 1]; // extract operator without = @@ -1805,14 +1848,20 @@ impl Engine { Expr::Variable(_) => unreachable!(), // idx_lhs[idx_expr] op= rhs #[cfg(not(feature = "no_index"))] - Expr::Index(_) => self.eval_dot_index_chain( - scope, mods, state, lib, this_ptr, lhs_expr, level, new_val, - ), + Expr::Index(_) => { + self.eval_dot_index_chain( + scope, mods, state, lib, this_ptr, lhs_expr, level, new_val, + )?; + Ok(Default::default()) + } // dot_lhs.dot_rhs op= rhs #[cfg(not(feature = "no_object"))] - Expr::Dot(_) => self.eval_dot_index_chain( - scope, mods, state, lib, this_ptr, lhs_expr, level, new_val, - ), + Expr::Dot(_) => { + self.eval_dot_index_chain( + scope, mods, state, lib, this_ptr, lhs_expr, level, new_val, + )?; + Ok(Default::default()) + } // Error assignment to constant expr if expr.is_constant() => { Err(Box::new(EvalAltResult::ErrorAssignmentToConstant( @@ -1871,25 +1920,19 @@ impl Engine { let expr = args_expr.get(0); let arg_value = 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(), )) }) - .and_then(|s| { - if is_valid_identifier(s.chars()) { - Ok(s) - } else { - Err(Box::new(EvalAltResult::ErrorFunctionNotFound( - s.to_string(), - expr.position(), - ))) - } - }) - .map(|s| FnPtr::from(s).into()); + .and_then(|s| FnPtr::try_from(s)) + .map(Into::::into) + .map_err(|err| err.new_position(*pos)); } } @@ -1941,7 +1984,7 @@ impl Engine { .collect::>()?; let (target, _, _, pos) = - search_scope(scope, mods, state, this_ptr, lhs)?; + search_namespace(scope, mods, state, this_ptr, lhs)?; self.inc_operations(state) .map_err(|err| err.new_position(pos))?; @@ -1977,12 +2020,48 @@ impl Engine { let ((name, _, pos), modules, hash_script, args_expr, def_val) = x.as_ref(); let modules = modules.as_ref().unwrap(); - let mut arg_values = args_expr - .iter() - .map(|expr| self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)) - .collect::, _>>()?; + let mut arg_values: StaticVec; + let mut args: StaticVec<_>; - let mut args: StaticVec<_> = arg_values.iter_mut().collect(); + if args_expr.is_empty() { + // No arguments + args = Default::default(); + } else { + // See if the first argument is a variable (not module-qualified). + // If so, convert to method-call style in order to leverage potential + // &mut first argument and avoid cloning the value + match args_expr.get(0) { + // func(x, ...) -> x.func(...) + Expr::Variable(x) if x.1.is_none() => { + arg_values = args_expr + .iter() + .skip(1) + .map(|expr| { + self.eval_expr(scope, mods, state, lib, this_ptr, expr, level) + }) + .collect::>()?; + + let (target, _, _, pos) = + search_scope_only(scope, state, this_ptr, args_expr.get(0))?; + + self.inc_operations(state) + .map_err(|err| err.new_position(pos))?; + + args = once(target).chain(arg_values.iter_mut()).collect(); + } + // func(..., ...) or func(mod::x, ...) + _ => { + arg_values = args_expr + .iter() + .map(|expr| { + self.eval_expr(scope, mods, state, lib, this_ptr, expr, level) + }) + .collect::>()?; + + args = arg_values.iter_mut().collect(); + } + } + } let module = search_imports(mods, state, modules)?; @@ -2009,6 +2088,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(); @@ -2019,9 +2099,8 @@ impl Engine { ) .map_err(|err| err.new_position(*pos)) } - Ok(f) => { - f.get_native_fn()(self, args.as_mut()).map_err(|err| err.new_position(*pos)) - } + Ok(f) => f.get_native_fn()(self, lib, args.as_mut()) + .map_err(|err| err.new_position(*pos)), Err(err) => match *err { EvalAltResult::ErrorFunctionNotFound(_, _) if def_val.is_some() => { Ok(def_val.clone().unwrap()) @@ -2079,6 +2158,13 @@ impl Engine { Expr::False(_) => Ok(false.into()), Expr::Unit(_) => Ok(().into()), + #[cfg(feature = "internals")] + Expr::Custom(x) => { + let func = (x.0).1.as_ref(); + let ep: StaticVec<_> = (x.0).0.iter().map(|e| e.into()).collect(); + func(self, scope, mods, state, lib, this_ptr, ep.as_ref(), level) + } + _ => unreachable!(), }; @@ -2105,15 +2191,7 @@ impl Engine { Stmt::Noop(_) => Ok(Default::default()), // Expression as statement - Stmt::Expr(expr) => { - let result = self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)?; - - Ok(match expr.as_ref() { - // If it is a simple assignment, erase the result at the root - Expr::Assignment(_) => Default::default(), - _ => result, - }) - } + Stmt::Expr(expr) => self.eval_expr(scope, mods, state, lib, this_ptr, expr, level), // Block scope Stmt::Block(x) => { @@ -2334,21 +2412,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")] @@ -2407,7 +2483,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; @@ -2423,7 +2505,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; @@ -2487,13 +2575,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 @@ -2510,9 +2596,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)) } } @@ -2579,6 +2665,7 @@ fn run_builtin_binary_op( match op { "&" => return Ok(Some((x && y).into())), "|" => return Ok(Some((x || y).into())), + "^" => return Ok(Some((x ^ y).into())), "==" => return Ok(Some((x == y).into())), "!=" => return Ok(Some((x != y).into())), _ => (), @@ -2619,26 +2706,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())), + _ => (), } } @@ -2715,20 +2800,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_func.rs b/src/fn_func.rs index ccb861a6..bbbab4aa 100644 --- a/src/fn_func.rs +++ b/src/fn_func.rs @@ -15,8 +15,8 @@ use crate::stdlib::{boxed::Box, string::ToString}; pub trait Func { type Output; - /// Create a Rust anonymous function from an [`AST`]. - /// The `Engine` and [`AST`] are consumed and basically embedded into the closure. + /// Create a Rust anonymous function from an `AST`. + /// The `Engine` and `AST` are consumed and basically embedded into the closure. /// /// # Examples /// diff --git a/src/fn_native.rs b/src/fn_native.rs index e41ae7db..399d1746 100644 --- a/src/fn_native.rs +++ b/src/fn_native.rs @@ -1,10 +1,13 @@ +//! Module containing interfaces with native-Rust functions. use crate::any::Dynamic; use crate::engine::Engine; +use crate::module::Module; use crate::parser::ScriptFnDef; use crate::result::EvalAltResult; +use crate::token::{is_valid_identifier, Position}; use crate::utils::ImmutableString; -use crate::stdlib::{boxed::Box, fmt, rc::Rc, sync::Arc}; +use crate::stdlib::{boxed::Box, convert::TryFrom, fmt, rc::Rc, sync::Arc}; /// Trait that maps to `Send + Sync` only under the `sync` feature. #[cfg(feature = "sync")] @@ -27,13 +30,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). @@ -43,13 +42,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]; @@ -59,6 +54,10 @@ pub type FnCallArgs<'a> = [&'a mut Dynamic]; pub struct FnPtr(ImmutableString); impl FnPtr { + /// Create a new function pointer. + pub(crate) fn new_unchecked>(name: S) -> Self { + Self(name.into()) + } /// Get the name of the function. pub fn fn_name(&self) -> &str { self.get_fn_name().as_ref() @@ -79,19 +78,46 @@ impl fmt::Display for FnPtr { } } -impl> From for FnPtr { - fn from(value: S) -> Self { - Self(value.into()) +impl TryFrom for FnPtr { + type Error = Box; + + fn try_from(value: ImmutableString) -> Result { + if is_valid_identifier(value.chars()) { + Ok(Self(value)) + } else { + Err(Box::new(EvalAltResult::ErrorFunctionNotFound( + value.to_string(), + Position::none(), + ))) + } + } +} + +impl TryFrom for FnPtr { + type Error = Box; + + fn try_from(value: String) -> Result { + let s: ImmutableString = value.into(); + Self::try_from(s) + } +} + +impl TryFrom<&str> for FnPtr { + type Error = Box; + + fn try_from(value: &str) -> Result { + let s: ImmutableString = value.into(); + Self::try_from(s) } } /// A general function trail object. #[cfg(not(feature = "sync"))] -pub type FnAny = dyn Fn(&Engine, &mut FnCallArgs) -> Result>; +pub type FnAny = dyn Fn(&Engine, &Module, &mut FnCallArgs) -> Result>; /// A general function trail object. #[cfg(feature = "sync")] pub type FnAny = - dyn Fn(&Engine, &mut FnCallArgs) -> Result> + Send + Sync; + dyn Fn(&Engine, &Module, &mut FnCallArgs) -> Result> + Send + Sync; /// A standard function that gets an iterator from a type. pub type IteratorFn = fn(Dynamic) -> Box>; @@ -114,6 +140,7 @@ pub enum CallableFunction { /// An iterator function. Iterator(IteratorFn), /// A script-defined function. + #[cfg(not(feature = "no_function"))] Script(Shared), } @@ -123,6 +150,8 @@ impl fmt::Debug for CallableFunction { Self::Pure(_) => write!(f, "NativePureFunction"), Self::Method(_) => write!(f, "NativeMethod"), Self::Iterator(_) => write!(f, "NativeIterator"), + + #[cfg(not(feature = "no_function"))] Self::Script(fn_def) => fmt::Debug::fmt(fn_def, f), } } @@ -134,6 +163,8 @@ impl fmt::Display for CallableFunction { Self::Pure(_) => write!(f, "NativePureFunction"), Self::Method(_) => write!(f, "NativeMethod"), Self::Iterator(_) => write!(f, "NativeIterator"), + + #[cfg(not(feature = "no_function"))] CallableFunction::Script(s) => fmt::Display::fmt(s, f), } } @@ -144,24 +175,34 @@ impl CallableFunction { pub fn is_pure(&self) -> bool { match self { Self::Pure(_) => true, - Self::Method(_) | Self::Iterator(_) | Self::Script(_) => false, + Self::Method(_) | Self::Iterator(_) => 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(_) => false, + Self::Pure(_) | Self::Iterator(_) => 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(_) => false, + Self::Pure(_) | Self::Method(_) => 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, @@ -176,7 +217,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(_) => unreachable!(), + Self::Iterator(_) => unreachable!(), + + #[cfg(not(feature = "no_function"))] + Self::Script(_) => unreachable!(), } } /// Get a shared reference to a script-defined function definition. @@ -184,6 +228,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(_) => unreachable!(), @@ -195,6 +240,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(_) => unreachable!(), @@ -209,7 +255,10 @@ impl CallableFunction { pub fn get_iter_fn(&self) -> IteratorFn { match self { Self::Iterator(f) => *f, - Self::Pure(_) | Self::Method(_) | Self::Script(_) => unreachable!(), + Self::Pure(_) | Self::Method(_) => unreachable!(), + + #[cfg(not(feature = "no_function"))] + Self::Script(_) => unreachable!(), } } /// Create a new `CallableFunction::Pure`. @@ -228,12 +277,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/fn_register.rs b/src/fn_register.rs index 1b07cbe8..7cf7be74 100644 --- a/src/fn_register.rs +++ b/src/fn_register.rs @@ -4,6 +4,7 @@ use crate::any::{Dynamic, Variant}; use crate::engine::Engine; use crate::fn_native::{CallableFunction, FnAny, FnCallArgs, SendSync}; +use crate::module::Module; use crate::parser::FnAccess; use crate::result::EvalAltResult; use crate::utils::ImmutableString; @@ -39,7 +40,7 @@ pub trait RegisterFn { /// # Ok(()) /// # } /// ``` - fn register_fn(&mut self, name: &str, f: FN); + fn register_fn(&mut self, name: &str, f: FN) -> &mut Self; } /// Trait to register fallible custom functions returning `Result>` with the `Engine`. @@ -69,7 +70,7 @@ pub trait RegisterResultFn { /// engine.eval::("div(42, 0)") /// .expect_err("expecting division by zero error!"); /// ``` - fn register_result_fn(&mut self, name: &str, f: FN); + fn register_result_fn(&mut self, name: &str, f: FN) -> &mut Self; } // These types are used to build a unique _marker_ tuple type for each combination @@ -119,7 +120,7 @@ macro_rules! make_func { // ^ function parameter generic type name (A, B, C etc.) // ^ dereferencing function - Box::new(move |_: &Engine, args: &mut FnCallArgs| { + Box::new(move |_: &Engine, _: &Module, args: &mut FnCallArgs| { // The arguments are assumed to be of the correct number and types! #[allow(unused_variables, unused_mut)] @@ -181,11 +182,12 @@ macro_rules! def_register { RET: Variant + Clone > RegisterFn for Engine { - fn register_fn(&mut self, name: &str, f: FN) { + fn register_fn(&mut self, name: &str, f: FN) -> &mut Self { self.global_module.set_fn(name, FnAccess::Public, &[$(map_type_id::<$par>()),*], CallableFunction::$abi(make_func!(f : map_dynamic ; $($par => $clone),*)) ); + self } } @@ -194,11 +196,12 @@ macro_rules! def_register { FN: Fn($($param),*) -> Result> + SendSync + 'static, > RegisterResultFn for Engine { - fn register_result_fn(&mut self, name: &str, f: FN) { + fn register_result_fn(&mut self, name: &str, f: FN) -> &mut Self { self.global_module.set_fn(name, FnAccess::Public, &[$(map_type_id::<$par>()),*], CallableFunction::$abi(make_func!(f : map_result ; $($par => $clone),*)) ); + self } } diff --git a/src/lib.rs b/src/lib.rs index aa205dec..a3edc8ca 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -62,7 +62,8 @@ //! | `only_i32` | Set the system integer type to `i32` and disable all other integer types. `INT` is set to `i32`. | //! | `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`. | +//! | `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. @@ -86,7 +87,12 @@ pub mod packages; mod parser; mod result; mod scope; +#[cfg(feature = "serde")] +mod serde; +mod settings; mod stdlib; +#[cfg(feature = "internals")] +mod syntax; mod token; mod r#unsafe; mod utils; @@ -94,7 +100,7 @@ mod utils; pub use any::Dynamic; pub use engine::Engine; pub use error::{ParseError, ParseErrorType}; -pub use fn_native::IteratorFn; +pub use fn_native::{FnPtr, IteratorFn}; pub use fn_register::{RegisterFn, RegisterResultFn}; pub use module::Module; pub use parser::{ImmutableString, AST, INT}; @@ -122,23 +128,48 @@ 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; // Expose internal data structures. +#[cfg(feature = "internals")] +#[deprecated(note = "this type is volatile and may change")] +pub use error::LexError; + #[cfg(feature = "internals")] #[deprecated(note = "this type is volatile and may change")] pub use token::{get_next_token, parse_string_literal, InputStream, Token, TokenizeState}; #[cfg(feature = "internals")] #[deprecated(note = "this type is volatile and may change")] -pub use parser::{Expr, ReturnType, ScriptFnDef, Stmt}; +pub use parser::{CustomExpr, Expr, ReturnType, ScriptFnDef, Stmt}; + +#[cfg(feature = "internals")] +#[deprecated(note = "this type is volatile and may change")] +pub use engine::{Expression, Imports, State as EvalState}; #[cfg(feature = "internals")] #[deprecated(note = "this type is volatile and may change")] diff --git a/src/module.rs b/src/module.rs index 3cdeb711..c64d16e2 100644 --- a/src/module.rs +++ b/src/module.rs @@ -3,7 +3,7 @@ use crate::any::{Dynamic, Variant}; use crate::calc_fn_hash; use crate::engine::{make_getter, make_setter, Engine, Imports, FN_IDX_GET, FN_IDX_SET}; -use crate::fn_native::{CallableFunction, FnCallArgs, IteratorFn, SendSync, Shared}; +use crate::fn_native::{CallableFunction as Func, FnCallArgs, IteratorFn, SendSync, Shared}; use crate::parser::{ FnAccess, FnAccess::{Private, Public}, @@ -49,18 +49,14 @@ pub struct Module { all_variables: HashMap, /// External Rust functions. - functions: HashMap< - u64, - (String, FnAccess, StaticVec, CallableFunction), - StraightHasherBuilder, - >, + functions: HashMap, Func), StraightHasherBuilder>, /// Iterator functions, keyed by the type producing the iterator. type_iterators: HashMap, /// Flattened collection of all external Rust functions, native or scripted, /// including those in sub-modules. - all_functions: HashMap, + all_functions: HashMap, /// Is the module indexed? indexed: bool, @@ -102,6 +98,12 @@ impl Clone for Module { } } +impl AsRef for Module { + fn as_ref(&self) -> &Module { + self + } +} + impl Module { /// Create a new module. /// @@ -212,9 +214,10 @@ impl Module { /// module.set_var("answer", 42_i64); /// assert_eq!(module.get_var_value::("answer").unwrap(), 42); /// ``` - pub fn set_var(&mut self, name: impl Into, value: impl Variant + Clone) { + pub fn set_var(&mut self, name: impl Into, value: impl Variant + Clone) -> &mut Self { self.variables.insert(name.into(), Dynamic::from(value)); self.indexed = false; + self } /// Get a mutable reference to a modules-qualified variable. @@ -236,7 +239,8 @@ 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. - pub(crate) fn set_script_fn(&mut self, fn_def: ScriptFnDef) { + #[cfg(not(feature = "no_function"))] + pub(crate) fn set_script_fn(&mut self, fn_def: ScriptFnDef) -> &mut Self { // None + function name + number of arguments. let hash_script = calc_fn_hash(empty(), &fn_def.name, fn_def.params.len(), empty()); self.functions.insert( @@ -249,6 +253,7 @@ impl Module { ), ); self.indexed = false; + self } /// Does a sub-module exist in the module? @@ -313,9 +318,10 @@ impl Module { /// module.set_sub_module("question", sub_module); /// assert!(module.get_sub_module("question").is_some()); /// ``` - pub fn set_sub_module(&mut self, name: impl Into, sub_module: Module) { + pub fn set_sub_module(&mut self, name: impl Into, sub_module: Module) -> &mut Self { self.modules.insert(name.into(), sub_module.into()); self.indexed = false; + self } /// Does the particular Rust function exist in the module? @@ -339,18 +345,22 @@ impl Module { /// Set a Rust function into the module, returning a hash key. /// /// If there is an existing Rust function of the same hash, it is replaced. + /// + /// ## WARNING - Low Level API + /// + /// This function is very low level. pub fn set_fn( &mut self, name: impl Into, access: FnAccess, - params: &[TypeId], - func: CallableFunction, + arg_types: &[TypeId], + func: Func, ) -> u64 { let name = name.into(); - let hash_fn = calc_fn_hash(empty(), &name, params.len(), params.iter().cloned()); + let hash_fn = calc_fn_hash(empty(), &name, arg_types.len(), arg_types.iter().cloned()); - let params = params.into_iter().cloned().collect(); + let params = arg_types.into_iter().cloned().collect(); self.functions .insert(hash_fn, (name, access, params, func.into())); @@ -360,27 +370,72 @@ impl Module { hash_fn } - /// Set a Rust function taking a reference to the scripting `Engine`, plus a list of - /// mutable `Dynamic` references into the module, returning a hash key. - /// A list of `TypeId`'s is taken as the argument types. + /// Set a Rust function taking a reference to the scripting `Engine`, the current set of functions, + /// plus a list of mutable `Dynamic` references into the module, returning a hash key. /// /// Use this to register a built-in function which must reference settings on the scripting - /// `Engine` (e.g. to prevent growing an array beyond the allowed maximum size). + /// `Engine` (e.g. to prevent growing an array beyond the allowed maximum size), or to call a + /// script-defined function in the current evaluation context. /// /// If there is a similar existing Rust function, it is replaced. - pub(crate) fn set_fn_var_args( + /// + /// ## WARNING - Low Level API + /// + /// This function is very low level. + /// + /// A list of `TypeId`'s is taken as the argument types. + /// + /// Arguments are simply passed in as a mutable array of `&mut Dynamic`, + /// which is guaranteed to contain enough arguments of the correct types. + /// + /// To access a primary parameter value (i.e. cloning is cheap), use: `args[n].clone().cast::()` + /// + /// To access a parameter value and avoid cloning, use `std::mem::take(args[n]).cast::()`. + /// Notice that this will _consume_ the argument, replacing it with `()`. + /// + /// To access the first mutable parameter, use `args.get_mut(0).unwrap()` + /// + /// # Examples + /// + /// ``` + /// use rhai::Module; + /// + /// let mut module = Module::new(); + /// let hash = module.set_raw_fn("double_or_not", + /// // Pass parameter types via a slice with TypeId's + /// &[std::any::TypeId::of::(), std::any::TypeId::of::() ], + /// // Fixed closure signature + /// |engine, lib, args| { + /// // 'args' is guaranteed to be the right length and of the correct types + /// + /// // Get the second parameter by 'consuming' it + /// let double = std::mem::take(args[1]).cast::(); + /// // Since it is a primary type, it can also be cheaply copied + /// let double = args[1].clone().cast::(); + /// // Get a mutable reference to the first argument. + /// let x = args[0].downcast_mut::().unwrap(); + /// + /// let orig = *x; + /// + /// if double { + /// *x *= 2; // the first argument can be mutated + /// } + /// + /// Ok(orig) // return Result> + /// }); + /// + /// assert!(module.contains_fn(hash)); + /// ``` + pub fn set_raw_fn( &mut self, name: impl Into, - args: &[TypeId], - func: impl Fn(&Engine, &mut [&mut Dynamic]) -> FuncReturn + SendSync + 'static, + arg_types: &[TypeId], + func: impl Fn(&Engine, &Module, &mut [&mut Dynamic]) -> FuncReturn + SendSync + 'static, ) -> u64 { - let f = move |engine: &Engine, args: &mut FnCallArgs| func(engine, args).map(Dynamic::from); - self.set_fn( - name, - Public, - args, - CallableFunction::from_method(Box::new(f)), - ) + let f = move |engine: &Engine, lib: &Module, args: &mut FnCallArgs| { + func(engine, lib, args).map(Dynamic::from) + }; + self.set_fn(name, Public, arg_types, Func::from_method(Box::new(f))) } /// Set a Rust function taking no parameters into the module, returning a hash key. @@ -401,14 +456,9 @@ impl Module { name: impl Into, func: impl Fn() -> FuncReturn + SendSync + 'static, ) -> u64 { - let f = move |_: &Engine, _: &mut FnCallArgs| func().map(Dynamic::from); - let args = []; - self.set_fn( - name, - Public, - &args, - CallableFunction::from_pure(Box::new(f)), - ) + let f = move |_: &Engine, _: &Module, _: &mut FnCallArgs| func().map(Dynamic::from); + let arg_types = []; + self.set_fn(name, Public, &arg_types, Func::from_pure(Box::new(f))) } /// Set a Rust function taking one parameter into the module, returning a hash key. @@ -429,16 +479,11 @@ impl Module { name: impl Into, func: impl Fn(A) -> FuncReturn + SendSync + 'static, ) -> u64 { - let f = move |_: &Engine, args: &mut FnCallArgs| { + let f = move |_: &Engine, _: &Module, args: &mut FnCallArgs| { func(mem::take(args[0]).cast::()).map(Dynamic::from) }; - let args = [TypeId::of::()]; - self.set_fn( - name, - Public, - &args, - CallableFunction::from_pure(Box::new(f)), - ) + let arg_types = [TypeId::of::()]; + self.set_fn(name, Public, &arg_types, Func::from_pure(Box::new(f))) } /// Set a Rust function taking one mutable parameter into the module, returning a hash key. @@ -459,16 +504,11 @@ impl Module { name: impl Into, func: impl Fn(&mut A) -> FuncReturn + SendSync + 'static, ) -> u64 { - let f = move |_: &Engine, args: &mut FnCallArgs| { + let f = move |_: &Engine, _: &Module, args: &mut FnCallArgs| { func(args[0].downcast_mut::().unwrap()).map(Dynamic::from) }; - let args = [TypeId::of::()]; - self.set_fn( - name, - Public, - &args, - CallableFunction::from_method(Box::new(f)), - ) + let arg_types = [TypeId::of::()]; + self.set_fn(name, Public, &arg_types, Func::from_method(Box::new(f))) } /// Set a Rust getter function taking one mutable parameter, returning a hash key. @@ -513,19 +553,14 @@ impl Module { name: impl Into, func: impl Fn(A, B) -> FuncReturn + SendSync + 'static, ) -> u64 { - let f = move |_: &Engine, args: &mut FnCallArgs| { + let f = move |_: &Engine, _: &Module, args: &mut FnCallArgs| { let a = mem::take(args[0]).cast::(); let b = mem::take(args[1]).cast::(); func(a, b).map(Dynamic::from) }; - let args = [TypeId::of::(), TypeId::of::()]; - self.set_fn( - name, - Public, - &args, - CallableFunction::from_pure(Box::new(f)), - ) + let arg_types = [TypeId::of::(), TypeId::of::()]; + self.set_fn(name, Public, &arg_types, Func::from_pure(Box::new(f))) } /// Set a Rust function taking two parameters (the first one mutable) into the module, @@ -549,19 +584,14 @@ impl Module { name: impl Into, func: impl Fn(&mut A, B) -> FuncReturn + SendSync + 'static, ) -> u64 { - let f = move |_: &Engine, args: &mut FnCallArgs| { + let f = move |_: &Engine, _: &Module, args: &mut FnCallArgs| { let b = mem::take(args[1]).cast::(); let a = args[0].downcast_mut::().unwrap(); func(a, b).map(Dynamic::from) }; - let args = [TypeId::of::(), TypeId::of::()]; - self.set_fn( - name, - Public, - &args, - CallableFunction::from_method(Box::new(f)), - ) + let arg_types = [TypeId::of::(), TypeId::of::()]; + self.set_fn(name, Public, &arg_types, Func::from_method(Box::new(f))) } /// Set a Rust setter function taking two parameters (the first one mutable) into the module, @@ -640,20 +670,15 @@ impl Module { name: impl Into, func: impl Fn(A, B, C) -> FuncReturn + SendSync + 'static, ) -> u64 { - let f = move |_: &Engine, args: &mut FnCallArgs| { + let f = move |_: &Engine, _: &Module, args: &mut FnCallArgs| { let a = mem::take(args[0]).cast::(); let b = mem::take(args[1]).cast::(); let c = mem::take(args[2]).cast::(); func(a, b, c).map(Dynamic::from) }; - let args = [TypeId::of::(), TypeId::of::(), TypeId::of::()]; - self.set_fn( - name, - Public, - &args, - CallableFunction::from_pure(Box::new(f)), - ) + let arg_types = [TypeId::of::(), TypeId::of::(), TypeId::of::()]; + self.set_fn(name, Public, &arg_types, Func::from_pure(Box::new(f))) } /// Set a Rust function taking three parameters (the first one mutable) into the module, @@ -682,20 +707,15 @@ impl Module { name: impl Into, func: impl Fn(&mut A, B, C) -> FuncReturn + SendSync + 'static, ) -> u64 { - let f = move |_: &Engine, args: &mut FnCallArgs| { + let f = move |_: &Engine, _: &Module, args: &mut FnCallArgs| { let b = mem::take(args[1]).cast::(); let c = mem::take(args[2]).cast::(); let a = args[0].downcast_mut::().unwrap(); func(a, b, c).map(Dynamic::from) }; - let args = [TypeId::of::(), TypeId::of::(), TypeId::of::()]; - self.set_fn( - name, - Public, - &args, - CallableFunction::from_method(Box::new(f)), - ) + let arg_types = [TypeId::of::(), TypeId::of::(), TypeId::of::()]; + self.set_fn(name, Public, &arg_types, Func::from_method(Box::new(f))) } /// Set a Rust index setter taking three parameters (the first one mutable) into the module, @@ -719,19 +739,19 @@ impl Module { &mut self, func: impl Fn(&mut A, B, A) -> FuncReturn<()> + SendSync + 'static, ) -> u64 { - let f = move |_: &Engine, args: &mut FnCallArgs| { + let f = move |_: &Engine, _: &Module, args: &mut FnCallArgs| { let b = mem::take(args[1]).cast::(); let c = mem::take(args[2]).cast::(); let a = args[0].downcast_mut::().unwrap(); func(a, b, c).map(Dynamic::from) }; - let args = [TypeId::of::(), TypeId::of::(), TypeId::of::()]; + let arg_types = [TypeId::of::(), TypeId::of::(), TypeId::of::()]; self.set_fn( FN_IDX_SET, Public, - &args, - CallableFunction::from_method(Box::new(f)), + &arg_types, + Func::from_method(Box::new(f)), ) } @@ -761,7 +781,7 @@ impl Module { name: impl Into, func: impl Fn(A, B, C, D) -> FuncReturn + SendSync + 'static, ) -> u64 { - let f = move |_: &Engine, args: &mut FnCallArgs| { + let f = move |_: &Engine, _: &Module, args: &mut FnCallArgs| { let a = mem::take(args[0]).cast::(); let b = mem::take(args[1]).cast::(); let c = mem::take(args[2]).cast::(); @@ -769,18 +789,13 @@ impl Module { func(a, b, c, d).map(Dynamic::from) }; - let args = [ + let arg_types = [ TypeId::of::(), TypeId::of::(), TypeId::of::(), TypeId::of::(), ]; - self.set_fn( - name, - Public, - &args, - CallableFunction::from_pure(Box::new(f)), - ) + self.set_fn(name, Public, &arg_types, Func::from_pure(Box::new(f))) } /// Set a Rust function taking four parameters (the first one mutable) into the module, @@ -810,7 +825,7 @@ impl Module { name: impl Into, func: impl Fn(&mut A, B, C, D) -> FuncReturn + SendSync + 'static, ) -> u64 { - let f = move |_: &Engine, args: &mut FnCallArgs| { + let f = move |_: &Engine, _: &Module, args: &mut FnCallArgs| { let b = mem::take(args[1]).cast::(); let c = mem::take(args[2]).cast::(); let d = mem::take(args[3]).cast::(); @@ -818,25 +833,20 @@ impl Module { func(a, b, c, d).map(Dynamic::from) }; - let args = [ + let arg_types = [ TypeId::of::(), TypeId::of::(), TypeId::of::(), TypeId::of::(), ]; - self.set_fn( - name, - Public, - &args, - CallableFunction::from_method(Box::new(f)), - ) + self.set_fn(name, Public, &arg_types, Func::from_method(Box::new(f))) } /// Get a Rust function. /// /// The `u64` hash is calculated by the function `crate::calc_fn_hash`. /// It is also returned by the `set_fn_XXX` calls. - pub(crate) fn get_fn(&self, hash_fn: u64) -> Option<&CallableFunction> { + pub(crate) fn get_fn(&self, hash_fn: u64) -> Option<&Func> { self.functions.get(&hash_fn).map(|(_, _, _, v)| v) } @@ -846,9 +856,9 @@ impl Module { /// The `u64` hash is calculated by the function `crate::calc_fn_hash` and must match /// the hash calculated by `index_all_sub_modules`. pub(crate) fn get_qualified_fn( - &mut self, + &self, hash_qualified_fn: u64, - ) -> Result<&CallableFunction, Box> { + ) -> Result<&Func, Box> { self.all_functions.get(&hash_qualified_fn).ok_or_else(|| { Box::new(EvalAltResult::ErrorFunctionNotFound( String::new(), @@ -858,7 +868,7 @@ impl Module { } /// Merge another module into this module. - pub fn merge(&mut self, other: &Self) { + pub fn merge(&mut self, other: &Self) -> &mut Self { self.merge_filtered(other, |_, _, _| true) } @@ -867,7 +877,7 @@ impl Module { &mut self, other: &Self, filter: impl Fn(FnAccess, &str, usize) -> bool, - ) { + ) -> &mut Self { self.variables .extend(other.variables.iter().map(|(k, v)| (k.clone(), v.clone()))); @@ -876,12 +886,9 @@ impl Module { .functions .iter() .filter(|(_, (_, _, _, v))| match v { - CallableFunction::Pure(_) - | CallableFunction::Method(_) - | CallableFunction::Iterator(_) => true, - CallableFunction::Script(ref f) => { - filter(f.access, f.name.as_str(), f.params.len()) - } + #[cfg(not(feature = "no_function"))] + Func::Script(ref f) => filter(f.access, f.name.as_str(), f.params.len()), + _ => true, }) .map(|(&k, v)| (k, v.clone())), ); @@ -892,20 +899,24 @@ impl Module { self.all_functions.clear(); self.all_variables.clear(); self.indexed = false; + self } /// Filter out the functions, retaining only some based on a filter predicate. - pub(crate) fn retain_functions(&mut self, filter: impl Fn(FnAccess, &str, usize) -> bool) { + #[cfg(not(feature = "no_function"))] + pub(crate) fn retain_functions( + &mut self, + filter: impl Fn(FnAccess, &str, usize) -> bool, + ) -> &mut Self { self.functions.retain(|_, (_, _, _, v)| match v { - CallableFunction::Pure(_) - | CallableFunction::Method(_) - | CallableFunction::Iterator(_) => true, - CallableFunction::Script(ref f) => filter(f.access, f.name.as_str(), f.params.len()), + Func::Script(ref f) => filter(f.access, f.name.as_str(), f.params.len()), + _ => true, }); self.all_functions.clear(); self.all_variables.clear(); self.indexed = false; + self } /// Get the number of variables in the module. @@ -929,11 +940,12 @@ impl Module { /// Get an iterator to the functions in the module. pub(crate) fn iter_fn( &self, - ) -> impl Iterator, CallableFunction)> { + ) -> impl Iterator, Func)> { self.functions.values() } /// 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() @@ -942,7 +954,7 @@ impl Module { .map(|f| f.get_shared_fn_def()) } - /// Create a new `Module` by evaluating an [`AST`]. + /// Create a new `Module` by evaluating an `AST`. /// /// # Examples /// @@ -995,7 +1007,7 @@ impl Module { module: &'a Module, qualifiers: &mut Vec<&'a str>, variables: &mut Vec<(u64, Dynamic)>, - functions: &mut Vec<(u64, CallableFunction)>, + functions: &mut Vec<(u64, Func)>, ) { for (name, m) in &module.modules { // Index all the sub-modules first. @@ -1018,6 +1030,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. @@ -1028,20 +1041,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())); } } @@ -1065,9 +1079,10 @@ impl Module { } /// Set a type iterator into the module. - pub fn set_iter(&mut self, typ: TypeId, func: IteratorFn) { + pub fn set_iter(&mut self, typ: TypeId, func: IteratorFn) -> &mut Self { self.type_iterators.insert(typ, func); self.indexed = false; + self } /// Get the specified type iterator. @@ -1143,6 +1158,7 @@ pub trait ModuleResolver: SendSync { /// Re-export module resolvers. #[cfg(not(feature = "no_module"))] pub mod resolvers { + pub use super::collection::ModuleResolversCollection; #[cfg(not(feature = "no_std"))] #[cfg(not(target_arch = "wasm32"))] pub use super::file::FileModuleResolver; @@ -1338,9 +1354,6 @@ mod stat { /// Module resolution service that serves modules added into it. /// - /// `StaticModuleResolver` is a smart pointer to a `HashMap`. - /// It can simply be treated as `&HashMap`. - /// /// # Examples /// /// ``` @@ -1434,3 +1447,86 @@ mod stat { } } } + +/// Module resolver collection. +#[cfg(not(feature = "no_module"))] +mod collection { + use super::*; + + /// Module resolution service that holds a collection of module resolves, + /// to be searched in sequential order. + /// + /// # Examples + /// + /// ``` + /// use rhai::{Engine, Module}; + /// use rhai::module_resolvers::{StaticModuleResolver, ModuleResolversCollection}; + /// + /// let mut collection = ModuleResolversCollection::new(); + /// + /// let resolver = StaticModuleResolver::new(); + /// collection.push(resolver); + /// + /// let mut engine = Engine::new(); + /// engine.set_module_resolver(Some(collection)); + /// ``` + #[derive(Default)] + pub struct ModuleResolversCollection(Vec>); + + impl ModuleResolversCollection { + /// Create a new `ModuleResolversCollection`. + /// + /// # Examples + /// + /// ``` + /// use rhai::{Engine, Module}; + /// use rhai::module_resolvers::{StaticModuleResolver, ModuleResolversCollection}; + /// + /// let mut collection = ModuleResolversCollection::new(); + /// + /// let resolver = StaticModuleResolver::new(); + /// collection.push(resolver); + /// + /// let mut engine = Engine::new(); + /// engine.set_module_resolver(Some(collection)); + /// ``` + pub fn new() -> Self { + Default::default() + } + } + + impl ModuleResolversCollection { + /// Add a module keyed by its path. + pub fn push(&mut self, resolver: impl ModuleResolver + 'static) { + self.0.push(Box::new(resolver)); + } + /// Get an iterator of all the module resolvers. + pub fn iter(&self) -> impl Iterator { + self.0.iter().map(|v| v.as_ref()) + } + /// Remove all module resolvers. + pub fn clear(&mut self) { + self.0.clear(); + } + } + + impl ModuleResolver for ModuleResolversCollection { + fn resolve( + &self, + engine: &Engine, + path: &str, + pos: Position, + ) -> Result> { + for resolver in self.0.iter() { + if let Ok(module) = resolver.resolve(engine, path, pos) { + return Ok(module); + } + } + + Err(Box::new(EvalAltResult::ErrorModuleNotFound( + path.into(), + pos, + ))) + } + } +} diff --git a/src/optimize.rs b/src/optimize.rs index c7a1ed71..3da46ffa 100644 --- a/src/optimize.rs +++ b/src/optimize.rs @@ -6,6 +6,9 @@ use crate::parser::{map_dynamic_to_expr, Expr, ReturnType, ScriptFnDef, Stmt, AS use crate::scope::{Entry as ScopeEntry, EntryType as ScopeEntryType, Scope}; use crate::utils::StaticVec; +#[cfg(feature = "internals")] +use crate::parser::CustomExpr; + use crate::stdlib::{ boxed::Box, iter::empty, @@ -382,23 +385,7 @@ fn optimize_expr(expr: Expr, state: &mut State) -> Expr { stmt => Expr::Stmt(Box::new((stmt, x.1))), }, // id op= expr - Expr::Assignment(x) => match x.2 { - //id = id2 op= rhs - Expr::Assignment(x2) if x.1.is_empty() => match (x.0, &x2.0) { - // var = var op= expr2 -> var op= expr2 - (Expr::Variable(a), Expr::Variable(b)) - if a.1.is_none() && b.1.is_none() && a.0 == b.0 && a.3 == b.3 => - { - // Assignment to the same variable - fold - state.set_dirty(); - Expr::Assignment(Box::new((Expr::Variable(a), x2.1, optimize_expr(x2.2, state), x.3))) - } - // expr1 = expr2 op= rhs - (expr1, _) => Expr::Assignment(Box::new((expr1, x.1, optimize_expr(Expr::Assignment(x2), state), x.3))), - }, - // expr = rhs - expr => Expr::Assignment(Box::new((x.0, x.1, optimize_expr(expr, state), x.3))), - }, + Expr::Assignment(x) => Expr::Assignment(Box::new((x.0, x.1, optimize_expr(x.2, state), x.3))), // lhs.rhs #[cfg(not(feature = "no_object"))] @@ -551,11 +538,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); @@ -608,6 +601,15 @@ fn optimize_expr(expr: Expr, state: &mut State) -> Expr { state.find_constant(&name).expect("should find constant in scope!").clone().set_position(pos) } + // Custom syntax + #[cfg(feature = "internals")] + Expr::Custom(x) => Expr::Custom(Box::new(( + CustomExpr( + (x.0).0.into_iter().map(|expr| optimize_expr(expr, state)).collect(), + (x.0).1), + x.1 + ))), + // All other expressions - skip expr => expr, } @@ -730,7 +732,9 @@ pub fn optimize_into_ast( } .into() }) - .for_each(|fn_def| lib2.set_script_fn(fn_def)); + .for_each(|fn_def| { + lib2.set_script_fn(fn_def); + }); functions .into_iter() @@ -759,11 +763,13 @@ pub fn optimize_into_ast( }; fn_def.into() }) - .for_each(|fn_def| module.set_script_fn(fn_def)); + .for_each(|fn_def| { + module.set_script_fn(fn_def); + }); } else { - functions - .into_iter() - .for_each(|fn_def| module.set_script_fn(fn_def)); + functions.into_iter().for_each(|fn_def| { + module.set_script_fn(fn_def); + }); } module 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..0a580933 100644 --- a/src/packages/array_basic.rs +++ b/src/packages/array_basic.rs @@ -3,7 +3,7 @@ use crate::any::{Dynamic, Variant}; use crate::def_package; use crate::engine::{Array, Engine}; -use crate::module::FuncReturn; +use crate::module::{FuncReturn, Module}; use crate::parser::{ImmutableString, INT}; use crate::result::EvalAltResult; use crate::token::Position; @@ -25,20 +25,22 @@ fn ins(list: &mut Array, position: INT, item: T) -> FuncRetu } Ok(()) } -fn pad(engine: &Engine, args: &mut [&mut Dynamic]) -> FuncReturn<()> { +fn pad( + engine: &Engine, + _: &Module, + args: &mut [&mut Dynamic], +) -> FuncReturn<()> { let len = *args[1].downcast_ref::().unwrap(); // 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 { @@ -65,7 +67,7 @@ macro_rules! reg_tri { macro_rules! reg_pad { ($lib:expr, $op:expr, $func:ident, $($par:ty),*) => { $({ - $lib.set_fn_var_args($op, + $lib.set_raw_fn($op, &[TypeId::of::(), TypeId::of::(), TypeId::of::<$par>()], $func::<$par> ); 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..248b0e93 100644 --- a/src/packages/string_more.rs +++ b/src/packages/string_more.rs @@ -1,7 +1,7 @@ use crate::any::Dynamic; use crate::def_package; use crate::engine::Engine; -use crate::module::FuncReturn; +use crate::module::{FuncReturn, Module}; use crate::parser::{ImmutableString, INT}; use crate::result::EvalAltResult; use crate::token::Position; @@ -223,23 +223,21 @@ def_package!(crate:MoreStringPackage:"Additional string utilities, including str Ok(()) }, ); - lib.set_fn_var_args( + lib.set_raw_fn( "pad", &[TypeId::of::(), TypeId::of::(), TypeId::of::()], - |engine: &Engine, args: &mut [&mut Dynamic]| { + |engine: &Engine, _: &Module, args: &mut [&mut Dynamic]| { let len = *args[1].downcast_ref::< INT>().unwrap(); // 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..55fbdabf 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -10,6 +10,15 @@ use crate::scope::{EntryType as ScopeEntryType, Scope}; use crate::token::{Position, Token, TokenStream}; use crate::utils::{StaticVec, StraightHasherBuilder}; +#[cfg(feature = "internals")] +use crate::engine::{MARKER_BLOCK, MARKER_EXPR, MARKER_IDENT}; + +#[cfg(feature = "internals")] +use crate::fn_native::Shared; + +#[cfg(feature = "internals")] +use crate::syntax::FnCustomSyntaxEval; + use crate::stdlib::{ borrow::Cow, boxed::Box, @@ -47,9 +56,11 @@ type PERR = ParseErrorType; pub use crate::utils::ImmutableString; +type FunctionsLib = HashMap; + /// Compiled AST (abstract syntax tree) of a Rhai script. /// -/// Currently, [`AST`] is neither `Send` nor `Sync`. Turn on the `sync` feature to make it `Send + Sync`. +/// Currently, `AST` is neither `Send` nor `Sync`. Turn on the `sync` feature to make it `Send + Sync`. #[derive(Debug, Clone, Default)] pub struct AST( /// Global statements. @@ -59,21 +70,21 @@ pub struct AST( ); impl AST { - /// Create a new [`AST`]. + /// Create a new `AST`. pub fn new(statements: Vec, lib: Module) -> Self { Self(statements, lib) } /// Get the statements. #[cfg(not(feature = "internals"))] - pub(crate) fn statements(&self) -> &Vec { + pub(crate) fn statements(&self) -> &[Stmt] { &self.0 } /// Get the statements. #[cfg(feature = "internals")] #[deprecated(note = "this method is volatile and may change")] - pub fn statements(&self) -> &Vec { + pub fn statements(&self) -> &[Stmt] { &self.0 } @@ -95,16 +106,43 @@ impl AST { &self.1 } - /// Merge two [`AST`] into one. Both [`AST`]'s are untouched and a new, merged, version + /// Clone the `AST`'s functions into a new `AST`. + /// No statements are cloned. + /// + /// This operation is cheap because functions are shared. + pub fn clone_functions_only(&self) -> Self { + self.clone_functions_only_filtered(|_, _, _| true) + } + + /// Clone the `AST`'s functions into a new `AST` based on a filter predicate. + /// No statements are cloned. + /// + /// This operation is cheap because functions are shared. + pub fn clone_functions_only_filtered( + &self, + filter: impl Fn(FnAccess, &str, usize) -> bool, + ) -> Self { + let mut functions: Module = Default::default(); + functions.merge_filtered(&self.1, filter); + Self(Default::default(), functions) + } + + /// Clone the `AST`'s script statements into a new `AST`. + /// No functions are cloned. + pub fn clone_statements_only(&self) -> Self { + Self(self.0.clone(), Default::default()) + } + + /// Merge two `AST` into one. Both `AST`'s are untouched and a new, merged, version /// is returned. /// - /// The second [`AST`] is simply appended to the end of the first _without any processing_. - /// Thus, the return value of the first [`AST`] (if using expression-statement syntax) is buried. - /// Of course, if the first [`AST`] uses a `return` statement at the end, then - /// the second [`AST`] will essentially be dead code. + /// The second `AST` is simply appended to the end of the first _without any processing_. + /// Thus, the return value of the first `AST` (if using expression-statement syntax) is buried. + /// Of course, if the first `AST` uses a `return` statement at the end, then + /// the second `AST` will essentially be dead code. /// - /// All script-defined functions in the second [`AST`] overwrite similarly-named functions - /// in the first [`AST`] with the same number of parameters. + /// All script-defined functions in the second `AST` overwrite similarly-named functions + /// in the first `AST` with the same number of parameters. /// /// # Example /// @@ -148,16 +186,16 @@ impl AST { self.merge_filtered(other, |_, _, _| true) } - /// Merge two [`AST`] into one. Both [`AST`]'s are untouched and a new, merged, version + /// Merge two `AST` into one. Both `AST`'s are untouched and a new, merged, version /// is returned. /// - /// The second [`AST`] is simply appended to the end of the first _without any processing_. - /// Thus, the return value of the first [`AST`] (if using expression-statement syntax) is buried. - /// Of course, if the first [`AST`] uses a `return` statement at the end, then - /// the second [`AST`] will essentially be dead code. + /// The second `AST` is simply appended to the end of the first _without any processing_. + /// Thus, the return value of the first `AST` (if using expression-statement syntax) is buried. + /// Of course, if the first `AST` uses a `return` statement at the end, then + /// the second `AST` will essentially be dead code. /// - /// All script-defined functions in the second [`AST`] are first selected based on a filter - /// predicate, then overwrite similarly-named functions in the first [`AST`] with the + /// All script-defined functions in the second `AST` are first selected based on a filter + /// predicate, then overwrite similarly-named functions in the first `AST` with the /// same number of parameters. /// /// # Example @@ -251,13 +289,13 @@ impl AST { self.1.retain_functions(filter); } - /// Clear all function definitions in the [`AST`]. + /// Clear all function definitions in the `AST`. #[cfg(not(feature = "no_function"))] pub fn clear_functions(&mut self) { self.1 = Default::default(); } - /// Clear all statements in the [`AST`], leaving only function definitions. + /// Clear all statements in the `AST`, leaving only function definitions. pub fn clear_statements(&mut self) { self.0 = vec![]; } @@ -271,6 +309,18 @@ impl Add for &AST { } } +impl AsRef<[Stmt]> for AST { + fn as_ref(&self) -> &[Stmt] { + self.statements() + } +} + +impl AsRef for AST { + fn as_ref(&self) -> &Module { + self.lib() + } +} + /// A type representing the access mode of a scripted function. #[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)] pub enum FnAccess { @@ -332,36 +382,29 @@ 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, + /// Maximum levels of expression nesting in functions. + max_function_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, max_function_expr_depth: usize) -> Self { Self { + engine, max_expr_depth, - max_string_size, - max_array_size, - max_map_size, - ..Default::default() + max_function_expr_depth, + stack: Default::default(), + modules: Default::default(), } } /// Find a variable by name in the `ParseState`, searching in reverse. @@ -399,6 +442,8 @@ struct ParseSettings { is_global: bool, /// Is the current position inside a loop? is_breakable: bool, + /// Is anonymous function allowed? + allow_anonymous_fn: bool, /// Is if-expression allowed? allow_if_expr: bool, /// Is statement-expression allowed? @@ -532,6 +577,17 @@ impl Stmt { } } +#[derive(Clone)] +#[cfg(feature = "internals")] +pub struct CustomExpr(pub StaticVec, pub Shared); + +#[cfg(feature = "internals")] +impl fmt::Debug for CustomExpr { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt::Debug::fmt(&self.0, f) + } +} + /// An expression. /// /// Each variant is at most one pointer in size (for speed), @@ -596,6 +652,9 @@ pub enum Expr { False(Position), /// () Unit(Position), + /// Custom syntax + #[cfg(feature = "internals")] + Custom(Box<(CustomExpr, Position)>), } impl Default for Expr { @@ -690,6 +749,9 @@ impl Expr { Self::True(pos) | Self::False(pos) | Self::Unit(pos) => *pos, Self::Dot(x) | Self::Index(x) => x.0.position(), + + #[cfg(feature = "internals")] + Self::Custom(x) => x.1, } } @@ -722,6 +784,9 @@ impl Expr { Self::Assignment(x) => x.3 = new_pos, Self::Dot(x) => x.2 = new_pos, Self::Index(x) => x.2 = new_pos, + + #[cfg(feature = "internals")] + Self::Custom(x) => x.1 = new_pos, } self @@ -825,6 +890,9 @@ impl Expr { Token::LeftParen => true, _ => false, }, + + #[cfg(feature = "internals")] + Self::Custom(_) => false, } } @@ -872,6 +940,7 @@ fn match_token(input: &mut TokenStream, token: Token) -> Result Result { settings.ensure_level_within_max_limit(state.max_expr_depth)?; @@ -880,7 +949,7 @@ fn parse_paren_expr( return Ok(Expr::Unit(settings.pos)); } - let expr = parse_expr(input, state, settings.level_up())?; + let expr = parse_expr(input, state, lib, settings.level_up())?; match input.next().unwrap() { // ( xxx ) @@ -900,6 +969,7 @@ fn parse_paren_expr( fn parse_call_expr( input: &mut TokenStream, state: &mut ParseState, + lib: &mut FunctionsLib, id: String, mut modules: Option>, settings: ParseSettings, @@ -958,7 +1028,7 @@ fn parse_call_expr( match input.peek().unwrap() { // id(...args, ) - handle trailing comma (Token::RightParen, _) => (), - _ => args.push(parse_expr(input, state, settings)?), + _ => args.push(parse_expr(input, state, lib, settings)?), } match input.peek().unwrap() { @@ -1021,12 +1091,13 @@ fn parse_call_expr( fn parse_index_chain( input: &mut TokenStream, state: &mut ParseState, + lib: &mut FunctionsLib, lhs: Expr, mut settings: ParseSettings, ) -> Result { settings.ensure_level_within_max_limit(state.max_expr_depth)?; - let idx_expr = parse_expr(input, state, settings.level_up())?; + let idx_expr = parse_expr(input, state, lib, settings.level_up())?; // Check type of indexing - must be integer or string match &idx_expr { @@ -1168,7 +1239,8 @@ fn parse_index_chain( let prev_pos = settings.pos; settings.pos = eat_token(input, Token::LeftBracket); // Recursively parse the indexing chain, right-binding each - let idx_expr = parse_index_chain(input, state, idx_expr, settings.level_up())?; + let idx_expr = + parse_index_chain(input, state, lib, idx_expr, settings.level_up())?; // Indexing binds to right Ok(Expr::Index(Box::new((lhs, idx_expr, prev_pos)))) } @@ -1199,6 +1271,7 @@ fn parse_index_chain( fn parse_array_literal( input: &mut TokenStream, state: &mut ParseState, + lib: &mut FunctionsLib, settings: ParseSettings, ) -> Result { settings.ensure_level_within_max_limit(state.max_expr_depth)?; @@ -1206,10 +1279,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)); } @@ -1220,7 +1293,7 @@ fn parse_array_literal( break; } _ => { - let expr = parse_expr(input, state, settings.level_up())?; + let expr = parse_expr(input, state, lib, settings.level_up())?; arr.push(expr); } } @@ -1255,6 +1328,7 @@ fn parse_array_literal( fn parse_map_literal( input: &mut TokenStream, state: &mut ParseState, + lib: &mut FunctionsLib, settings: ParseSettings, ) -> Result { settings.ensure_level_within_max_limit(state.max_expr_depth)?; @@ -1272,7 +1346,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,15 +1380,15 @@ 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)); } - let expr = parse_expr(input, state, settings.level_up())?; + let expr = parse_expr(input, state, lib, settings.level_up())?; map.push(((Into::::into(name), pos), expr)); } } @@ -1359,6 +1433,7 @@ fn parse_map_literal( fn parse_primary( input: &mut TokenStream, state: &mut ParseState, + lib: &mut FunctionsLib, mut settings: ParseSettings, ) -> Result { let (token, token_pos) = input.peek().unwrap(); @@ -1368,7 +1443,7 @@ fn parse_primary( let (token, _) = match token { // { - block statement as expression Token::LeftBrace if settings.allow_stmt_expr => { - return parse_block(input, state, settings.level_up()) + return parse_block(input, state, lib, settings.level_up()) .map(|block| Expr::Stmt(Box::new((block, settings.pos)))) } Token::EOF => return Err(PERR::UnexpectedEOF.into_err(settings.pos)), @@ -1380,16 +1455,16 @@ 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))) } - Token::LeftParen => parse_paren_expr(input, state, settings.level_up())?, + Token::LeftParen => parse_paren_expr(input, state, lib, settings.level_up())?, #[cfg(not(feature = "no_index"))] - Token::LeftBracket => parse_array_literal(input, state, settings.level_up())?, + Token::LeftBracket => parse_array_literal(input, state, lib, settings.level_up())?, #[cfg(not(feature = "no_object"))] - Token::MapStart => parse_map_literal(input, state, settings.level_up())?, + Token::MapStart => parse_map_literal(input, state, lib, settings.level_up())?, Token::True => Expr::True(settings.pos), Token::False => Expr::False(settings.pos), Token::LexError(err) => return Err(err.into_err(settings.pos)), @@ -1416,7 +1491,7 @@ fn parse_primary( (Expr::Variable(x), Token::LeftParen) => { let ((name, pos), modules, _, _) = *x; settings.pos = pos; - parse_call_expr(input, state, name, modules, settings.level_up())? + parse_call_expr(input, state, lib, name, modules, settings.level_up())? } (Expr::Property(_), _) => unreachable!(), // module access @@ -1439,7 +1514,7 @@ fn parse_primary( // Indexing #[cfg(not(feature = "no_index"))] (expr, Token::LeftBracket) => { - parse_index_chain(input, state, expr, settings.level_up())? + parse_index_chain(input, state, lib, expr, settings.level_up())? } // Unknown postfix operator (expr, token) => unreachable!( @@ -1470,6 +1545,7 @@ fn parse_primary( fn parse_unary( input: &mut TokenStream, state: &mut ParseState, + lib: &mut FunctionsLib, mut settings: ParseSettings, ) -> Result { let (token, token_pos) = input.peek().unwrap(); @@ -1479,14 +1555,14 @@ fn parse_unary( match token { // If statement is allowed to act as expressions Token::If if settings.allow_if_expr => Ok(Expr::Stmt(Box::new(( - parse_if(input, state, settings.level_up())?, + parse_if(input, state, lib, settings.level_up())?, settings.pos, )))), // -expr Token::UnaryMinus => { let pos = eat_token(input, Token::UnaryMinus); - match parse_unary(input, state, settings.level_up())? { + match parse_unary(input, state, lib, settings.level_up())? { // Negative integer Expr::IntegerConstant(x) => { let (num, pos) = *x; @@ -1495,13 +1571,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)) } @@ -1530,13 +1602,13 @@ fn parse_unary( // +expr Token::UnaryPlus => { eat_token(input, Token::UnaryPlus); - parse_unary(input, state, settings.level_up()) + parse_unary(input, state, lib, settings.level_up()) } // !expr Token::Bang => { let pos = eat_token(input, Token::Bang); let mut args = StaticVec::new(); - let expr = parse_primary(input, state, settings.level_up())?; + let expr = parse_primary(input, state, lib, settings.level_up())?; args.push(expr); let op = "!"; @@ -1553,7 +1625,7 @@ fn parse_unary( // Token::EOF => Err(PERR::UnexpectedEOF.into_err(settings.pos)), // All other tokens - _ => parse_primary(input, state, settings.level_up()), + _ => parse_primary(input, state, lib, settings.level_up()), } } @@ -1621,6 +1693,7 @@ fn make_assignment_stmt<'a>( fn parse_op_assignment_stmt( input: &mut TokenStream, state: &mut ParseState, + lib: &mut FunctionsLib, lhs: Expr, mut settings: ParseSettings, ) -> Result { @@ -1647,7 +1720,7 @@ fn parse_op_assignment_stmt( }; let (_, pos) = input.next().unwrap(); - let rhs = parse_expr(input, state, settings.level_up())?; + let rhs = parse_expr(input, state, lib, settings.level_up())?; make_assignment_stmt(op, state, lhs, rhs, pos) } @@ -1701,7 +1774,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())), }) } @@ -1859,6 +1932,7 @@ fn make_in_expr(lhs: Expr, rhs: Expr, op_pos: Position) -> Result + { + // 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)), }; } @@ -1962,13 +2050,88 @@ fn parse_binary_op( fn parse_expr( input: &mut TokenStream, state: &mut ParseState, + lib: &mut FunctionsLib, mut settings: ParseSettings, ) -> Result { settings.pos = input.peek().unwrap().1; settings.ensure_level_within_max_limit(state.max_expr_depth)?; - let lhs = parse_unary(input, state, settings.level_up())?; - parse_binary_op(input, state, 1, lhs, settings.level_up()) + // Check if it is a custom syntax. + #[cfg(feature = "internals")] + if let Some(ref custom) = state.engine.custom_syntax { + let (token, pos) = input.peek().unwrap(); + let token_pos = *pos; + + match token { + Token::Custom(key) if custom.contains_key(key) => { + let custom = custom.get_key_value(key).unwrap(); + let (key, syntax) = custom; + + input.next().unwrap(); + + let mut exprs: StaticVec = Default::default(); + + // Adjust the variables stack + match syntax.scope_delta { + delta if delta > 0 => { + state.stack.push(("".to_string(), ScopeEntryType::Normal)) + } + delta if delta < 0 && state.stack.len() <= delta.abs() as usize => { + state.stack.clear() + } + delta if delta < 0 => state + .stack + .truncate(state.stack.len() - delta.abs() as usize), + _ => (), + } + + for segment in syntax.segments.iter() { + settings.pos = input.peek().unwrap().1; + let settings = settings.level_up(); + + match segment.as_str() { + MARKER_IDENT => match input.next().unwrap() { + (Token::Identifier(s), pos) => { + exprs.push(Expr::Variable(Box::new(((s, pos), None, 0, None)))); + } + (_, pos) => return Err(PERR::VariableExpected.into_err(pos)), + }, + MARKER_EXPR => exprs.push(parse_expr(input, state, lib, settings)?), + MARKER_BLOCK => { + let stmt = parse_block(input, state, lib, settings)?; + let pos = stmt.position(); + exprs.push(Expr::Stmt(Box::new((stmt, pos)))) + } + s => match input.peek().unwrap() { + (Token::Custom(custom), _) if custom == s => { + input.next().unwrap(); + } + (t, _) if t.syntax().as_ref() == s => { + input.next().unwrap(); + } + (_, pos) => { + return Err(PERR::MissingToken( + s.to_string(), + format!("for '{}' expression", key), + ) + .into_err(*pos)) + } + }, + } + } + + return Ok(Expr::Custom(Box::new(( + CustomExpr(exprs, syntax.func.clone()), + token_pos, + )))); + } + _ => (), + } + } + + // Parse expression normally. + let lhs = parse_unary(input, state, lib, settings.level_up())?; + parse_binary_op(input, state, lib, 1, lhs, settings.level_up()) } /// Make sure that the expression is not a statement expression (i.e. wrapped in `{}`). @@ -2014,6 +2177,7 @@ fn ensure_not_assignment(input: &mut TokenStream) -> Result<(), ParseError> { fn parse_if( input: &mut TokenStream, state: &mut ParseState, + lib: &mut FunctionsLib, mut settings: ParseSettings, ) -> Result { // if ... @@ -2022,18 +2186,18 @@ fn parse_if( // if guard { if_body } ensure_not_statement_expr(input, "a boolean")?; - let guard = parse_expr(input, state, settings.level_up())?; + let guard = parse_expr(input, state, lib, settings.level_up())?; ensure_not_assignment(input)?; - let if_body = parse_block(input, state, settings.level_up())?; + let if_body = parse_block(input, state, lib, settings.level_up())?; // if guard { if_body } else ... let else_body = if match_token(input, Token::Else).unwrap_or(false) { Some(if let (Token::If, _) = input.peek().unwrap() { // if guard { if_body } else if ... - parse_if(input, state, settings.level_up())? + parse_if(input, state, lib, settings.level_up())? } else { // if guard { if_body } else { else-body } - parse_block(input, state, settings.level_up())? + parse_block(input, state, lib, settings.level_up())? }) } else { None @@ -2046,6 +2210,7 @@ fn parse_if( fn parse_while( input: &mut TokenStream, state: &mut ParseState, + lib: &mut FunctionsLib, mut settings: ParseSettings, ) -> Result { // while ... @@ -2054,11 +2219,11 @@ fn parse_while( // while guard { body } ensure_not_statement_expr(input, "a boolean")?; - let guard = parse_expr(input, state, settings.level_up())?; + let guard = parse_expr(input, state, lib, settings.level_up())?; ensure_not_assignment(input)?; settings.is_breakable = true; - let body = parse_block(input, state, settings.level_up())?; + let body = parse_block(input, state, lib, settings.level_up())?; Ok(Stmt::While(Box::new((guard, body)))) } @@ -2067,6 +2232,7 @@ fn parse_while( fn parse_loop( input: &mut TokenStream, state: &mut ParseState, + lib: &mut FunctionsLib, mut settings: ParseSettings, ) -> Result { // loop ... @@ -2075,7 +2241,7 @@ fn parse_loop( // loop { body } settings.is_breakable = true; - let body = parse_block(input, state, settings.level_up())?; + let body = parse_block(input, state, lib, settings.level_up())?; Ok(Stmt::Loop(Box::new(body))) } @@ -2084,6 +2250,7 @@ fn parse_loop( fn parse_for( input: &mut TokenStream, state: &mut ParseState, + lib: &mut FunctionsLib, mut settings: ParseSettings, ) -> Result { // for ... @@ -2116,13 +2283,13 @@ fn parse_for( // for name in expr { body } ensure_not_statement_expr(input, "a boolean")?; - let expr = parse_expr(input, state, settings.level_up())?; + let expr = parse_expr(input, state, lib, settings.level_up())?; let prev_stack_len = state.stack.len(); state.stack.push((name.clone(), ScopeEntryType::Normal)); settings.is_breakable = true; - let body = parse_block(input, state, settings.level_up())?; + let body = parse_block(input, state, lib, settings.level_up())?; state.stack.truncate(prev_stack_len); @@ -2133,6 +2300,7 @@ fn parse_for( fn parse_let( input: &mut TokenStream, state: &mut ParseState, + lib: &mut FunctionsLib, var_type: ScopeEntryType, mut settings: ParseSettings, ) -> Result { @@ -2160,7 +2328,7 @@ fn parse_let( // let name = ... if match_token(input, Token::Equals)? { // let name = expr - let init_value = parse_expr(input, state, settings.level_up())?; + let init_value = parse_expr(input, state, lib, settings.level_up())?; match var_type { // let name = expr @@ -2194,9 +2362,11 @@ fn parse_let( } /// Parse an import statement. +#[cfg(not(feature = "no_module"))] fn parse_import( input: &mut TokenStream, state: &mut ParseState, + lib: &mut FunctionsLib, mut settings: ParseSettings, ) -> Result { // import ... @@ -2204,7 +2374,7 @@ fn parse_import( settings.ensure_level_within_max_limit(state.max_expr_depth)?; // import expr ... - let expr = parse_expr(input, state, settings.level_up())?; + let expr = parse_expr(input, state, lib, settings.level_up())?; // import expr as ... match input.next().unwrap() { @@ -2233,6 +2403,7 @@ fn parse_import( fn parse_export( input: &mut TokenStream, state: &mut ParseState, + _lib: &mut FunctionsLib, mut settings: ParseSettings, ) -> Result { settings.pos = eat_token(input, Token::Export); @@ -2293,6 +2464,7 @@ fn parse_export( fn parse_block( input: &mut TokenStream, state: &mut ParseState, + lib: &mut FunctionsLib, mut settings: ParseSettings, ) -> Result { // Must start with { @@ -2317,7 +2489,12 @@ fn parse_block( while !match_token(input, Token::RightBrace)? { // Parse statements inside the block settings.is_global = false; - let stmt = parse_stmt(input, state, settings.level_up())?; + + let stmt = if let Some(s) = parse_stmt(input, state, lib, settings.level_up())? { + s + } else { + continue; + }; // See if it needs a terminating semicolon let need_semicolon = !stmt.is_self_terminated(); @@ -2362,13 +2539,14 @@ fn parse_block( fn parse_expr_stmt( input: &mut TokenStream, state: &mut ParseState, + lib: &mut FunctionsLib, mut settings: ParseSettings, ) -> Result { settings.pos = input.peek().unwrap().1; settings.ensure_level_within_max_limit(state.max_expr_depth)?; - let expr = parse_expr(input, state, settings.level_up())?; - let expr = parse_op_assignment_stmt(input, state, expr, settings.level_up())?; + let expr = parse_expr(input, state, lib, settings.level_up())?; + let expr = parse_op_assignment_stmt(input, state, lib, expr, settings.level_up())?; Ok(Stmt::Expr(Box::new(expr))) } @@ -2376,12 +2554,13 @@ fn parse_expr_stmt( fn parse_stmt( input: &mut TokenStream, state: &mut ParseState, + lib: &mut FunctionsLib, mut settings: ParseSettings, -) -> Result { +) -> Result, ParseError> { use ScopeEntryType::{Constant, Normal}; let (token, token_pos) = match input.peek().unwrap() { - (Token::EOF, pos) => return Ok(Stmt::Noop(*pos)), + (Token::EOF, pos) => return Ok(Some(Stmt::Noop(*pos))), x => x, }; settings.pos = *token_pos; @@ -2389,28 +2568,71 @@ fn parse_stmt( match token { // Semicolon - empty statement - Token::SemiColon => Ok(Stmt::Noop(settings.pos)), + Token::SemiColon => Ok(Some(Stmt::Noop(settings.pos))), - Token::LeftBrace => parse_block(input, state, settings.level_up()), + Token::LeftBrace => parse_block(input, state, lib, settings.level_up()).map(Some), // fn ... #[cfg(not(feature = "no_function"))] Token::Fn if !settings.is_global => Err(PERR::WrongFnDefinition.into_err(settings.pos)), - #[cfg(not(feature = "no_function"))] - Token::Fn => unreachable!(), - Token::If => parse_if(input, state, settings.level_up()), - Token::While => parse_while(input, state, settings.level_up()), - Token::Loop => parse_loop(input, state, settings.level_up()), - Token::For => parse_for(input, state, settings.level_up()), + #[cfg(not(feature = "no_function"))] + Token::Fn | Token::Private => { + let access = if matches!(token, Token::Private) { + eat_token(input, Token::Private); + FnAccess::Private + } else { + FnAccess::Public + }; + + match input.next().unwrap() { + (Token::Fn, pos) => { + let mut state = ParseState::new( + state.engine, + state.max_function_expr_depth, + state.max_function_expr_depth, + ); + + let settings = ParseSettings { + allow_if_expr: true, + allow_stmt_expr: true, + allow_anonymous_fn: true, + is_global: false, + is_breakable: false, + level: 0, + pos: pos, + }; + + let func = parse_fn(input, &mut state, lib, access, settings)?; + + // Qualifiers (none) + function name + number of arguments. + let hash = calc_fn_hash(empty(), &func.name, func.params.len(), empty()); + + lib.insert(hash, func); + + Ok(None) + } + + (_, pos) => Err(PERR::MissingToken( + Token::Fn.into(), + format!("following '{}'", Token::Private.syntax()), + ) + .into_err(pos)), + } + } + + Token::If => parse_if(input, state, lib, settings.level_up()).map(Some), + Token::While => parse_while(input, state, lib, settings.level_up()).map(Some), + Token::Loop => parse_loop(input, state, lib, settings.level_up()).map(Some), + Token::For => parse_for(input, state, lib, settings.level_up()).map(Some), Token::Continue if settings.is_breakable => { let pos = eat_token(input, Token::Continue); - Ok(Stmt::Continue(pos)) + Ok(Some(Stmt::Continue(pos))) } Token::Break if settings.is_breakable => { let pos = eat_token(input, Token::Break); - Ok(Stmt::Break(pos)) + Ok(Some(Stmt::Break(pos))) } Token::Continue | Token::Break => Err(PERR::LoopBreak.into_err(settings.pos)), @@ -2423,36 +2645,41 @@ fn parse_stmt( match input.peek().unwrap() { // `return`/`throw` at - (Token::EOF, pos) => Ok(Stmt::ReturnWithVal(Box::new(((return_type, *pos), None)))), + (Token::EOF, pos) => Ok(Some(Stmt::ReturnWithVal(Box::new(( + (return_type, *pos), + None, + ))))), // `return;` or `throw;` - (Token::SemiColon, _) => Ok(Stmt::ReturnWithVal(Box::new(( + (Token::SemiColon, _) => Ok(Some(Stmt::ReturnWithVal(Box::new(( (return_type, settings.pos), None, - )))), + ))))), // `return` or `throw` with expression (_, _) => { - let expr = parse_expr(input, state, settings.level_up())?; + let expr = parse_expr(input, state, lib, settings.level_up())?; let pos = expr.position(); - Ok(Stmt::ReturnWithVal(Box::new(( + Ok(Some(Stmt::ReturnWithVal(Box::new(( (return_type, pos), Some(expr), - )))) + ))))) } } } - Token::Let => parse_let(input, state, Normal, settings.level_up()), - Token::Const => parse_let(input, state, Constant, settings.level_up()), - Token::Import => parse_import(input, state, settings.level_up()), + Token::Let => parse_let(input, state, lib, Normal, settings.level_up()).map(Some), + Token::Const => parse_let(input, state, lib, Constant, settings.level_up()).map(Some), + + #[cfg(not(feature = "no_module"))] + Token::Import => parse_import(input, state, lib, settings.level_up()).map(Some), #[cfg(not(feature = "no_module"))] Token::Export if !settings.is_global => Err(PERR::WrongExport.into_err(settings.pos)), #[cfg(not(feature = "no_module"))] - Token::Export => parse_export(input, state, settings.level_up()), + Token::Export => parse_export(input, state, lib, settings.level_up()).map(Some), - _ => parse_expr_stmt(input, state, settings.level_up()), + _ => parse_expr_stmt(input, state, lib, settings.level_up()).map(Some), } } @@ -2461,14 +2688,14 @@ fn parse_stmt( fn parse_fn( input: &mut TokenStream, state: &mut ParseState, + lib: &mut FunctionsLib, access: FnAccess, mut settings: ParseSettings, ) -> Result { - settings.pos = eat_token(input, Token::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)), }; @@ -2533,7 +2760,7 @@ fn parse_fn( let body = match input.peek().unwrap() { (Token::LeftBrace, _) => { settings.is_breakable = false; - parse_block(input, state, settings.level_up())? + parse_block(input, state, lib, settings.level_up())? } (_, pos) => return Err(PERR::FnMissingBody(name).into_err(*pos)), }; @@ -2556,21 +2783,18 @@ 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 functions = Default::default(); + let mut state = ParseState::new(self, self.max_expr_depth, self.max_function_expr_depth); let settings = ParseSettings { allow_if_expr: false, allow_stmt_expr: false, + allow_anonymous_fn: false, is_global: true, is_breakable: false, level: 0, pos: Position::none(), }; - let expr = parse_expr(input, &mut state, settings)?; + let expr = parse_expr(input, &mut state, &mut functions, settings)?; match input.peek().unwrap() { (Token::EOF, _) => (), @@ -2595,71 +2819,26 @@ impl Engine { &self, input: &mut TokenStream, ) -> 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 statements: Vec = Default::default(); + let mut functions = Default::default(); + let mut state = ParseState::new(self, self.max_expr_depth, self.max_function_expr_depth); while !input.peek().unwrap().0.is_eof() { - // Collect all the function definitions - #[cfg(not(feature = "no_function"))] - { - let (access, must_be_fn) = if match_token(input, Token::Private)? { - (FnAccess::Private, true) - } else { - (FnAccess::Public, false) - }; - - 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 settings = ParseSettings { - allow_if_expr: true, - allow_stmt_expr: true, - is_global: false, - is_breakable: false, - level: 0, - pos: *pos, - }; - let func = parse_fn(input, &mut state, access, settings)?; - - // Qualifiers (none) + function name + number of arguments. - let hash = calc_fn_hash(empty(), &func.name, func.params.len(), empty()); - - functions.insert(hash, func); - continue; - } - (_, pos) if must_be_fn => { - return Err(PERR::MissingToken( - Token::Fn.into(), - format!("following '{}'", Token::Private.syntax()), - ) - .into_err(*pos)) - } - _ => (), - } - } - - // Actual statement let settings = ParseSettings { allow_if_expr: true, allow_stmt_expr: true, + allow_anonymous_fn: true, is_global: true, is_breakable: false, level: 0, pos: Position::none(), }; - let stmt = parse_stmt(input, &mut state, settings)?; + + let stmt = if let Some(s) = parse_stmt(input, &mut state, &mut functions, settings)? { + s + } else { + continue; + }; let need_semicolon = !stmt.is_self_terminated(); 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/scope.rs b/src/scope.rs index 56b3a8cc..8342c792 100644 --- a/src/scope.rs +++ b/src/scope.rs @@ -97,8 +97,9 @@ impl<'a> Scope<'a> { /// assert_eq!(my_scope.len(), 0); /// assert!(my_scope.is_empty()); /// ``` - pub fn clear(&mut self) { + pub fn clear(&mut self) -> &mut Self { self.0.clear(); + self } /// Get the number of entries inside the Scope. @@ -147,8 +148,12 @@ impl<'a> Scope<'a> { /// my_scope.push("x", 42_i64); /// assert_eq!(my_scope.get_value::("x").unwrap(), 42); /// ``` - pub fn push>, T: Variant + Clone>(&mut self, name: K, value: T) { - self.push_dynamic_value(name, EntryType::Normal, Dynamic::from(value), false); + pub fn push>, T: Variant + Clone>( + &mut self, + name: K, + value: T, + ) -> &mut Self { + self.push_dynamic_value(name, EntryType::Normal, Dynamic::from(value), false) } /// Add (push) a new `Dynamic` entry to the Scope. @@ -163,8 +168,8 @@ impl<'a> Scope<'a> { /// my_scope.push_dynamic("x", Dynamic::from(42_i64)); /// assert_eq!(my_scope.get_value::("x").unwrap(), 42); /// ``` - pub fn push_dynamic>>(&mut self, name: K, value: Dynamic) { - self.push_dynamic_value(name, EntryType::Normal, value, false); + pub fn push_dynamic>>(&mut self, name: K, value: Dynamic) -> &mut Self { + self.push_dynamic_value(name, EntryType::Normal, value, false) } /// Add (push) a new constant to the Scope. @@ -185,8 +190,12 @@ impl<'a> Scope<'a> { /// my_scope.push_constant("x", 42_i64); /// assert_eq!(my_scope.get_value::("x").unwrap(), 42); /// ``` - pub fn push_constant>, T: Variant + Clone>(&mut self, name: K, value: T) { - self.push_dynamic_value(name, EntryType::Constant, Dynamic::from(value), true); + pub fn push_constant>, T: Variant + Clone>( + &mut self, + name: K, + value: T, + ) -> &mut Self { + self.push_dynamic_value(name, EntryType::Constant, Dynamic::from(value), true) } /// Add (push) a new constant with a `Dynamic` value to the Scope. @@ -208,8 +217,12 @@ impl<'a> Scope<'a> { /// my_scope.push_constant_dynamic("x", Dynamic::from(42_i64)); /// assert_eq!(my_scope.get_value::("x").unwrap(), 42); /// ``` - pub fn push_constant_dynamic>>(&mut self, name: K, value: Dynamic) { - self.push_dynamic_value(name, EntryType::Constant, value, true); + pub fn push_constant_dynamic>>( + &mut self, + name: K, + value: Dynamic, + ) -> &mut Self { + self.push_dynamic_value(name, EntryType::Constant, value, true) } /// Add (push) a new entry with a `Dynamic` value to the Scope. @@ -219,7 +232,7 @@ impl<'a> Scope<'a> { entry_type: EntryType, value: Dynamic, map_expr: bool, - ) { + ) -> &mut Self { let expr = if map_expr { map_dynamic_to_expr(value.clone(), Position::none()).map(Box::new) } else { @@ -233,6 +246,8 @@ impl<'a> Scope<'a> { value: value.into(), expr, }); + + self } /// Truncate (rewind) the Scope to a previous size. @@ -261,8 +276,9 @@ impl<'a> Scope<'a> { /// assert_eq!(my_scope.len(), 0); /// assert!(my_scope.is_empty()); /// ``` - pub fn rewind(&mut self, size: usize) { + pub fn rewind(&mut self, size: usize) -> &mut Self { self.0.truncate(size); + self } /// Does the scope contain the entry? @@ -341,14 +357,17 @@ impl<'a> Scope<'a> { /// my_scope.set_value("x", 0_i64); /// assert_eq!(my_scope.get_value::("x").unwrap(), 0); /// ``` - pub fn set_value(&mut self, name: &'a str, value: T) { + pub fn set_value(&mut self, name: &'a str, value: T) -> &mut Self { match self.get_index(name) { - None => self.push(name, value), + None => { + self.push(name, value); + } Some((_, EntryType::Constant)) => panic!("variable {} is constant", name), Some((index, EntryType::Normal)) => { - self.0.get_mut(index).unwrap().value = Dynamic::from(value) + self.0.get_mut(index).unwrap().value = Dynamic::from(value); } } + self } /// Get a mutable reference to an entry in the Scope. @@ -358,9 +377,10 @@ impl<'a> Scope<'a> { } /// Update the access type of an entry in the Scope. - pub(crate) fn set_entry_alias(&mut self, index: usize, alias: String) { + pub(crate) fn set_entry_alias(&mut self, index: usize, alias: String) -> &mut Self { let entry = self.0.get_mut(index).expect("invalid index in Scope"); entry.alias = Some(Box::new(alias)); + self } /// Get an iterator to entries in the Scope. diff --git a/src/serde/de.rs b/src/serde/de.rs new file mode 100644 index 00000000..88533718 --- /dev/null +++ b/src/serde/de.rs @@ -0,0 +1,574 @@ +//! 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, EnumAccess, Error, IntoDeserializer, MapAccess, SeqAccess, + VariantAccess, 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(), + ))) + } + fn deserialize_int>( + &mut self, + v: crate::INT, + visitor: V, + ) -> Result> { + #[cfg(not(feature = "only_i32"))] + { + visitor.visit_i64(v) + } + #[cfg(feature = "only_i32")] + { + visitor.visit_i32(v) + } + } +} + +/// 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> { + if let Ok(v) = self.value.as_int() { + self.deserialize_int(v, visitor) + } else { + self.value + .downcast_ref::() + .map_or_else(|| self.type_error(), |&x| visitor.visit_i8(x)) + } + } + + fn deserialize_i16>(self, visitor: V) -> Result> { + if let Ok(v) = self.value.as_int() { + self.deserialize_int(v, visitor) + } else { + self.value + .downcast_ref::() + .map_or_else(|| self.type_error(), |&x| visitor.visit_i16(x)) + } + } + + fn deserialize_i32>(self, visitor: V) -> Result> { + if let Ok(v) = self.value.as_int() { + self.deserialize_int(v, visitor) + } else if cfg!(feature = "only_i32") { + self.type_error() + } else { + self.value + .downcast_ref::() + .map_or_else(|| self.type_error(), |&x| visitor.visit_i32(x)) + } + } + + fn deserialize_i64>(self, visitor: V) -> Result> { + if let Ok(v) = self.value.as_int() { + self.deserialize_int(v, visitor) + } else if cfg!(not(feature = "only_i32")) { + self.type_error() + } else { + self.value + .downcast_ref::() + .map_or_else(|| self.type_error(), |&x| visitor.visit_i64(x)) + } + } + + fn deserialize_u8>(self, visitor: V) -> Result> { + if let Ok(v) = self.value.as_int() { + self.deserialize_int(v, visitor) + } else { + self.value + .downcast_ref::() + .map_or_else(|| self.type_error(), |&x| visitor.visit_u8(x)) + } + } + + fn deserialize_u16>(self, visitor: V) -> Result> { + if let Ok(v) = self.value.as_int() { + self.deserialize_int(v, visitor) + } else { + self.value + .downcast_ref::() + .map_or_else(|| self.type_error(), |&x| visitor.visit_u16(x)) + } + } + + fn deserialize_u32>(self, visitor: V) -> Result> { + if let Ok(v) = self.value.as_int() { + self.deserialize_int(v, visitor) + } else { + self.value + .downcast_ref::() + .map_or_else(|| self.type_error(), |&x| visitor.visit_u32(x)) + } + } + + fn deserialize_u64>(self, visitor: V) -> Result> { + if let Ok(v) = self.value.as_int() { + self.deserialize_int(v, visitor) + } else { + 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], + visitor: V, + ) -> Result> { + if let Ok(s) = self.value.as_str() { + visitor.visit_enum(s.into_deserializer()) + } else { + #[cfg(not(feature = "no_object"))] + if let Some(map) = self.value.downcast_ref::() { + let mut iter = map.iter(); + let first = iter.next(); + let second = iter.next(); + if let (Some((key, value)), None) = (first, second) { + visitor.visit_enum(EnumDeserializer { + tag: &key, + content: DynamicDeserializer::from_dynamic(value), + }) + } else { + self.type_error() + } + } else { + self.type_error() + } + #[cfg(feature = "no_object")] + return 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(), + )) + } +} + +#[cfg(not(feature = "no_object"))] +struct EnumDeserializer<'t, 'de: 't> { + tag: &'t str, + content: DynamicDeserializer<'de>, +} + +#[cfg(not(feature = "no_object"))] +impl<'t, 'de> EnumAccess<'de> for EnumDeserializer<'t, 'de> { + type Error = Box; + type Variant = Self; + + fn variant_seed(self, seed: V) -> Result<(V::Value, Self::Variant), Self::Error> + where + V: DeserializeSeed<'de>, + { + seed.deserialize(self.tag.into_deserializer()) + .map(|v| (v, self)) + } +} + +#[cfg(not(feature = "no_object"))] +impl<'t, 'de> VariantAccess<'de> for EnumDeserializer<'t, 'de> { + type Error = Box; + + fn unit_variant(mut self) -> Result<(), Self::Error> { + Deserialize::deserialize(&mut self.content) + } + + fn newtype_variant_seed(mut self, seed: T) -> Result + where + T: DeserializeSeed<'de>, + { + seed.deserialize(&mut self.content) + } + + fn tuple_variant(mut self, len: usize, visitor: V) -> Result + where + V: Visitor<'de>, + { + self.content.deserialize_tuple(len, visitor) + } + + fn struct_variant( + mut self, + fields: &'static [&'static str], + visitor: V, + ) -> Result + where + V: Visitor<'de>, + { + self.content.deserialize_struct("", fields, visitor) + } +} 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..181573b0 --- /dev/null +++ b/src/serde/ser.rs @@ -0,0 +1,594 @@ +//! 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; + #[cfg(not(any(feature = "no_object", feature = "no_index")))] + type SerializeTupleVariant = TupleVariantSerializer; + #[cfg(any(feature = "no_object", feature = "no_index"))] + type SerializeTupleVariant = serde::ser::Impossible>; + type SerializeMap = DynamicSerializer; + type SerializeStruct = DynamicSerializer; + #[cfg(not(feature = "no_object"))] + type SerializeStructVariant = StructVariantSerializer; + #[cfg(feature = "no_object")] + type SerializeStructVariant = serde::ser::Impossible>; + + 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 u32 { + 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> { + #[cfg(not(feature = "no_object"))] + { + let content = to_dynamic(value)?; + make_variant(variant, content) + } + #[cfg(feature = "no_object")] + return Err(Box::new(EvalAltResult::ErrorMismatchOutputType( + "Dynamic".into(), + "map".into(), + Position::none(), + ))); + } + + 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> { + #[cfg(not(any(feature = "no_object", feature = "no_index")))] + return Ok(TupleVariantSerializer { + variant, + array: Array::with_capacity(len), + }); + #[cfg(any(feature = "no_object", feature = "no_index"))] + { + #[cfg(feature = "no_object")] + let err_type = "map"; + #[cfg(not(feature = "no_object"))] + let err_type = "array"; + Err(Box::new(EvalAltResult::ErrorMismatchOutputType( + "Dynamic".into(), + err_type.into(), + Position::none(), + ))) + } + } + + 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> { + #[cfg(not(feature = "no_object"))] + return Ok(StructVariantSerializer { + variant, + map: Map::with_capacity(len), + }); + #[cfg(feature = "no_object")] + return Err(Box::new(EvalAltResult::ErrorMismatchOutputType( + "Dynamic".into(), + "map".into(), + Position::none(), + ))); + } +} + +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 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!() + } +} + +#[cfg(not(any(feature = "no_object", feature = "no_index")))] +pub struct TupleVariantSerializer { + variant: &'static str, + array: Array, +} + +#[cfg(not(any(feature = "no_object", feature = "no_index")))] +impl SerializeTupleVariant for TupleVariantSerializer { + type Ok = Dynamic; + type Error = Box; + + fn serialize_field( + &mut self, + value: &T, + ) -> Result<(), Box> { + let value = to_dynamic(value)?; + self.array.push(value); + Ok(()) + } + + fn end(self) -> Result> { + make_variant(self.variant, self.array.into()) + } +} + +#[cfg(not(feature = "no_object"))] +pub struct StructVariantSerializer { + variant: &'static str, + map: Map, +} + +#[cfg(not(feature = "no_object"))] +impl SerializeStructVariant for StructVariantSerializer { + type Ok = Dynamic; + type Error = Box; + + fn serialize_field( + &mut self, + key: &'static str, + value: &T, + ) -> Result<(), Box> { + let value = to_dynamic(value)?; + self.map.insert(key.into(), value); + Ok(()) + } + + fn end(self) -> Result> { + make_variant(self.variant, self.map.into()) + } +} + +#[cfg(not(feature = "no_object"))] +fn make_variant(variant: &'static str, value: Dynamic) -> Result> { + let mut map = Map::with_capacity(1); + map.insert(variant.into(), value); + Ok(map.into()) +} 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..e3265c4a --- /dev/null +++ b/src/settings.rs @@ -0,0 +1,264 @@ +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) -> &mut Self { + // Push the package to the top - packages are searched in reverse order + self.packages.push(package); + self + } + + /// 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) -> &mut Self { + self.optimization_level = optimization_level; + self + } + + /// 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) -> &mut Self { + self.max_call_stack_depth = levels; + self + } + + /// 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) -> &mut Self { + self.max_operations = if operations == u64::MAX { + 0 + } else { + operations + }; + self + } + + /// 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) -> &mut Self { + self.max_modules = modules; + self + } + + /// 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, + ) -> &mut Self { + 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 + }; + self + } + + /// 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) -> &mut Self { + self.max_string_size = if max_size == usize::MAX { 0 } else { max_size }; + self + } + + /// 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) -> &mut Self { + self.max_array_size = if max_size == usize::MAX { 0 } else { max_size }; + self + } + + /// 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) -> &mut Self { + self.max_map_size = if max_size == usize::MAX { 0 } else { max_size }; + self + } + + /// 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, + ) -> &mut Self { + self.module_resolver = resolver.map(|f| Box::new(f) as Box); + self + } + + /// 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) -> &mut Self { + if self.disabled_symbols.is_none() { + self.disabled_symbols = Some(Default::default()); + } + + self.disabled_symbols + .as_mut() + .unwrap() + .insert(symbol.into()); + + self + } + + /// 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 160 (i.e. between +|- and *|/). + /// engine.register_custom_operator("foo", 160).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<&mut Self, 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(self) + } +} diff --git a/src/syntax.rs b/src/syntax.rs new file mode 100644 index 00000000..62aa20bd --- /dev/null +++ b/src/syntax.rs @@ -0,0 +1,150 @@ +//! Module containing implementation for custom syntax. +#![cfg(feature = "internals")] + +use crate::any::Dynamic; +use crate::engine::{Engine, Expression, Imports, State, MARKER_BLOCK, MARKER_EXPR, MARKER_IDENT}; +use crate::error::LexError; +use crate::fn_native::{SendSync, Shared}; +use crate::module::Module; +use crate::result::EvalAltResult; +use crate::scope::Scope; +use crate::token::{is_valid_identifier, Token}; +use crate::utils::StaticVec; + +use crate::stdlib::{ + fmt, + rc::Rc, + string::{String, ToString}, + sync::Arc, +}; + +/// A general function trail object. +#[cfg(not(feature = "sync"))] +pub type FnCustomSyntaxEval = dyn Fn( + &Engine, + &mut Scope, + &mut Imports, + &mut State, + &Module, + &mut Option<&mut Dynamic>, + &[Expression], + usize, +) -> Result>; +/// A general function trail object. +#[cfg(feature = "sync")] +pub type FnCustomSyntaxEval = dyn Fn( + &Engine, + &mut Scope, + &mut Imports, + &mut State, + &Module, + &mut Option<&mut Dynamic>, + &[Expression], + usize, + ) -> Result> + + Send + + Sync; + +#[derive(Clone)] +pub struct CustomSyntax { + pub segments: StaticVec, + pub func: Shared, + pub scope_delta: isize, +} + +impl fmt::Debug for CustomSyntax { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt::Debug::fmt(&self.segments, f) + } +} + +impl Engine { + pub fn register_custom_syntax + ToString>( + &mut self, + value: &[S], + scope_delta: isize, + func: impl Fn( + &Engine, + &mut Scope, + &mut Imports, + &mut State, + &Module, + &mut Option<&mut Dynamic>, + &[Expression], + usize, + ) -> Result> + + SendSync + + 'static, + ) -> Result> { + if value.is_empty() { + return Err(Box::new(LexError::ImproperSymbol("".to_string()))); + } + + let mut segments: StaticVec<_> = Default::default(); + + for s in value { + let seg = match s.as_ref() { + // Markers not in first position + MARKER_EXPR | MARKER_BLOCK | MARKER_IDENT if !segments.is_empty() => s.to_string(), + // Standard symbols not in first position + s if !segments.is_empty() && Token::lookup_from_syntax(s).is_some() => { + if self + .disabled_symbols + .as_ref() + .map(|d| d.contains(s)) + .unwrap_or(false) + { + // If symbol is disabled, make it a custom keyword + if self.custom_keywords.is_none() { + self.custom_keywords = Some(Default::default()); + } + + if !self.custom_keywords.as_ref().unwrap().contains_key(s) { + self.custom_keywords.as_mut().unwrap().insert(s.into(), 0); + } + } + + s.into() + } + // Identifier + s if is_valid_identifier(s.chars()) => { + if self.custom_keywords.is_none() { + self.custom_keywords = Some(Default::default()); + } + + if !self.custom_keywords.as_ref().unwrap().contains_key(s) { + self.custom_keywords.as_mut().unwrap().insert(s.into(), 0); + } + + s.into() + } + // Anything else is an error + _ => return Err(Box::new(LexError::ImproperSymbol(s.to_string()))), + }; + + segments.push(seg); + } + + let key = segments.remove(0); + + let syntax = CustomSyntax { + segments, + #[cfg(not(feature = "sync"))] + func: Rc::new(func), + #[cfg(feature = "sync")] + func: Arc::new(func), + scope_delta, + }; + + if self.custom_syntax.is_none() { + self.custom_syntax = Some(Default::default()); + } + + self.custom_syntax + .as_mut() + .unwrap() + .insert(key, syntax.into()); + + Ok(self) + } +} diff --git a/src/token.rs b/src/token.rs index 4b19ab49..460ddcb5 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, + 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,8 @@ pub enum Token { As, LexError(Box), Comment(String), + Reserved(String), + Custom(String), EOF, } @@ -222,12 +227,14 @@ 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(), + Reserved(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,17 +299,100 @@ 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(), } } + /// Reverse lookup a token from a piece of syntax. + pub fn lookup_from_syntax(syntax: &str) -> Option { + use Token::*; + + Some(match syntax { + "{" => LeftBrace, + "}" => RightBrace, + "(" => LeftParen, + ")" => RightParen, + "[" => LeftBracket, + "]" => RightBracket, + "+" => Plus, + "-" => Minus, + "*" => Multiply, + "/" => Divide, + ";" => SemiColon, + ":" => Colon, + "::" => DoubleColon, + "," => Comma, + "." => Period, + "#{" => MapStart, + "=" => Equals, + "true" => True, + "false" => False, + "let" => Let, + "const" => Const, + "if" => If, + "else" => Else, + "while" => While, + "loop" => Loop, + "for" => For, + "in" => In, + "<" => LessThan, + ">" => GreaterThan, + "!" => Bang, + "<=" => LessThanEqualsTo, + ">=" => GreaterThanEqualsTo, + "==" => EqualsTo, + "!=" => NotEqualsTo, + "|" => Pipe, + "||" => Or, + "&" => Ampersand, + "&&" => And, + #[cfg(not(feature = "no_function"))] + "fn" => Fn, + "continue" => Continue, + "break" => Break, + "return" => Return, + "throw" => Throw, + "+=" => PlusAssign, + "-=" => MinusAssign, + "*=" => MultiplyAssign, + "/=" => DivideAssign, + "<<=" => LeftShiftAssign, + ">>=" => RightShiftAssign, + "&=" => AndAssign, + "|=" => OrAssign, + "^=" => XOrAssign, + "<<" => LeftShift, + ">>" => RightShift, + "^" => XOr, + "%" => Modulo, + "%=" => ModuloAssign, + "~" => PowerOf, + "~=" => 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, + "===" | "!==" | "->" | "<-" | "=>" | ":=" | "::<" | "(*" | "*)" | "#" => { + Reserved(syntax.into()) + } + + _ => return None, + }) + } + // Is this token EOF? pub fn is_eof(&self) -> bool { use Token::*; @@ -320,9 +410,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 @@ -332,7 +422,6 @@ impl Token { UnaryMinus | Multiply | Divide | - Colon | Comma | Period | Equals | @@ -367,14 +456,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 +472,26 @@ 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, + EqualsTo | NotEqualsTo => 90, - In => 70, + LessThan | LessThanEqualsTo | GreaterThan | GreaterThanEqualsTo => 110, - Plus | Minus => 80, + In => 130, - Divide | Multiply | PowerOf => 90, + Plus | Minus => 150, - LeftShift | RightShift => 100, + Divide | Multiply | PowerOf | Modulo => 180, - Modulo => 110, + LeftShift | RightShift => 210, - Period => 120, + Period => 240, + + // Custom operators + Custom(s) => custom.map_or(0, |c| *c.get(s).unwrap()), _ => 0, } @@ -422,6 +513,57 @@ 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 standard 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, + } + } + + /// Is this token a reserved keyword? + pub fn is_reserved(&self) -> bool { + match self { + Self::Reserved(_) => true, + _ => false, + } + } + + /// Is this token a custom keyword? + pub fn is_custom(&self) -> bool { + match self { + Self::Custom(_) => true, + _ => false, + } + } } impl From for String { @@ -431,7 +573,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, @@ -583,9 +725,9 @@ pub fn parse_string_literal( } /// Consume the next character. -fn eat_next(stream: &mut impl InputStream, pos: &mut Position) { - stream.get_next(); +fn eat_next(stream: &mut impl InputStream, pos: &mut Position) -> Option { pos.advance(); + stream.get_next() } /// Scan for a block comment until the end. @@ -644,7 +786,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(); } @@ -706,7 +848,9 @@ fn get_next_token_inner( } } // 0x????, 0o????, 0b???? - ch @ 'x' | ch @ 'X' | ch @ 'o' | ch @ 'O' | ch @ 'b' | ch @ 'B' if c == '0' => { + ch @ 'x' | ch @ 'X' | ch @ 'o' | ch @ 'O' | ch @ 'b' | ch @ 'B' + if c == '0' => + { result.push(next_char); eat_next(stream, pos); @@ -811,76 +955,58 @@ fn get_next_token_inner( } return Some(( - match identifier.as_str() { - "true" => Token::True, - "false" => Token::False, - "let" => Token::Let, - "const" => Token::Const, - "if" => Token::If, - "else" => Token::Else, - "while" => Token::While, - "loop" => Token::Loop, - "continue" => Token::Continue, - "break" => Token::Break, - "return" => Token::Return, - "throw" => Token::Throw, - "for" => Token::For, - "in" => Token::In, - #[cfg(not(feature = "no_function"))] - "private" => Token::Private, - #[cfg(not(feature = "no_module"))] - "import" => Token::Import, - #[cfg(not(feature = "no_module"))] - "export" => Token::Export, - #[cfg(not(feature = "no_module"))] - "as" => Token::As, - - #[cfg(not(feature = "no_function"))] - "fn" => Token::Fn, - - _ => Token::Identifier(identifier), - }, + Token::lookup_from_syntax(&identifier) + .unwrap_or_else(|| Token::Identifier(identifier)), start_pos, )); } // " - string literal - ('"', _) => 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)), - ), + ('"', _) => { + return parse_string_literal(stream, state, pos, '"').map_or_else( + |err| Some((Token::LexError(Box::new(err.0)), err.1)), + |out| Some((Token::StringConstant(out), start_pos)), + ) + } // ' - character literal - ('\'', '\'') => return Some(( - Token::LexError(Box::new(LERR::MalformedChar("".to_string()))), - start_pos, - )), - ('\'', _) => return Some( - parse_string_literal(stream, state, pos, '\'') - .map_or_else( - |err| (Token::LexError(Box::new(err.0)), err.1), - |result| { - let mut chars = result.chars(); - let first = chars.next(); + ('\'', '\'') => { + return Some(( + Token::LexError(Box::new(LERR::MalformedChar("".to_string()))), + start_pos, + )) + } + ('\'', _) => { + return Some(parse_string_literal(stream, state, pos, '\'').map_or_else( + |err| (Token::LexError(Box::new(err.0)), err.1), + |result| { + let mut chars = result.chars(); + let first = chars.next(); - if chars.next().is_some() { - ( - Token::LexError(Box::new(LERR::MalformedChar(result))), - start_pos, - ) - } else { - (Token::CharConstant(first.expect("should be Some")), start_pos) - } - }, - ), - ), + if chars.next().is_some() { + ( + Token::LexError(Box::new(LERR::MalformedChar(result))), + start_pos, + ) + } else { + ( + Token::CharConstant(first.expect("should be Some")), + start_pos, + ) + } + }, + )) + } // Braces ('{', _) => return Some((Token::LeftBrace, start_pos)), ('}', _) => return Some((Token::RightBrace, start_pos)), // Parentheses + ('(', '*') => { + eat_next(stream, pos); + return Some((Token::Reserved("(*".into()), start_pos)); + } ('(', _) => return Some((Token::LeftParen, start_pos)), (')', _) => return Some((Token::RightParen, start_pos)), @@ -894,6 +1020,7 @@ fn get_next_token_inner( eat_next(stream, pos); return Some((Token::MapStart, start_pos)); } + ('#', _) => return Some((Token::Reserved("#".into()), start_pos)), // Operators ('+', '=') => { @@ -909,15 +1036,17 @@ fn get_next_token_inner( eat_next(stream, pos); return Some((Token::MinusAssign, start_pos)); } - ('-', '>') => return Some(( - Token::LexError(Box::new(LERR::ImproperSymbol( - "'->' is not a valid symbol. This is not C or C++!".to_string(), - ))), - start_pos, - )), + ('-', '>') => { + eat_next(stream, pos); + return Some((Token::Reserved("->".into()), start_pos)); + } ('-', _) if !state.non_unary => return Some((Token::UnaryMinus, start_pos)), ('-', _) => return Some((Token::Minus, start_pos)), + ('*', ')') => { + eat_next(stream, pos); + return Some((Token::Reserved("*)".into()), start_pos)); + } ('*', '=') => { eat_next(stream, pos); return Some((Token::MultiplyAssign, start_pos)); @@ -982,49 +1111,42 @@ fn get_next_token_inner( // Warn against `===` if stream.peek_next() == Some('=') { - return Some(( - Token::LexError(Box::new(LERR::ImproperSymbol( - "'===' is not a valid operator. This is not JavaScript! Should it be '=='?" - .to_string(), - ))), - start_pos, - )); + eat_next(stream, pos); + return Some((Token::Reserved("===".into()), start_pos)); } return Some((Token::EqualsTo, start_pos)); } - ('=', '>') => return Some(( - Token::LexError(Box::new(LERR::ImproperSymbol( - "'=>' is not a valid symbol. This is not Rust! Should it be '>='?" - .to_string(), - ))), - start_pos, - )), + ('=', '>') => { + eat_next(stream, pos); + return Some((Token::Reserved("=>".into()), start_pos)); + } ('=', _) => return Some((Token::Equals, start_pos)), (':', ':') => { eat_next(stream, pos); + + if stream.peek_next() == Some('<') { + eat_next(stream, pos); + return Some((Token::Reserved("::<".into()), start_pos)); + } + return Some((Token::DoubleColon, start_pos)); } - (':', '=') => return Some(( - Token::LexError(Box::new(LERR::ImproperSymbol( - "':=' is not a valid assignment operator. This is not Pascal! Should it be simply '='?" - .to_string(), - ))), - start_pos, - )), + (':', '=') => { + eat_next(stream, pos); + return Some((Token::Reserved(":=".into()), start_pos)); + } (':', _) => return Some((Token::Colon, start_pos)), ('<', '=') => { eat_next(stream, pos); return Some((Token::LessThanEqualsTo, start_pos)); } - ('<', '-') => return Some(( - Token::LexError(Box::new(LERR::ImproperSymbol( - "'<-' is not a valid symbol. Should it be '<='?".to_string(), - ))), - start_pos, - )), + ('<', '-') => { + eat_next(stream, pos); + return Some((Token::Reserved("<-".into()), start_pos)); + } ('<', '<') => { eat_next(stream, pos); @@ -1062,15 +1184,9 @@ fn get_next_token_inner( ('!', '=') => { eat_next(stream, pos); - // Warn against `!==` if stream.peek_next() == Some('=') { - return Some(( - Token::LexError(Box::new(LERR::ImproperSymbol( - "'!==' is not a valid operator. This is not JavaScript! Should it be '!='?" - .to_string(), - ))), - start_pos, - )); + eat_next(stream, pos); + return Some((Token::Reserved("!==".into()), start_pos)); } return Some((Token::NotEqualsTo, start_pos)); @@ -1115,10 +1231,17 @@ fn get_next_token_inner( } ('~', _) => return Some((Token::PowerOf, start_pos)), + ('@', _) => return Some((Token::Reserved("@".into()), start_pos)), + ('\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, + )) + } } } @@ -1132,47 +1255,51 @@ fn get_next_token_inner( } /// A type that implements the `InputStream` trait. -/// Multiple charaacter streams are jointed together to form one single stream. +/// Multiple character streams are jointed together to form one single stream. pub struct MultiInputsStream<'a> { /// The input character streams. streams: StaticVec>>, + /// The current stream index. + index: usize, } impl InputStream for MultiInputsStream<'_> { /// Get the next character fn get_next(&mut self) -> Option { loop { - if self.streams.is_empty() { + if self.index >= self.streams.len() { // No more streams return None; - } else if let Some(ch) = self.streams[0].next() { + } else if let Some(ch) = self.streams[self.index].next() { // Next character in current stream return Some(ch); } else { // Jump to the next stream - let _ = self.streams.remove(0); + self.index += 1; } } } /// Peek the next character fn peek_next(&mut self) -> Option { loop { - if self.streams.is_empty() { + if self.index >= self.streams.len() { // No more streams return None; - } else if let Some(ch) = self.streams[0].peek() { + } else if let Some(&ch) = self.streams[self.index].peek() { // Next character in current stream - return Some(*ch); + return Some(ch); } else { // Jump to the next stream - let _ = self.streams.remove(0); + self.index += 1; } } } } /// 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 +1308,93 @@ 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, + (Some((Token::Reserved(s), pos)), None, None) => return Some((match s.as_str() { + "===" => Token::LexError(Box::new(LERR::ImproperSymbol( + "'===' is not a valid operator. This is not JavaScript! Should it be '=='?" + .to_string(), + ))), + "!==" => Token::LexError(Box::new(LERR::ImproperSymbol( + "'!==' is not a valid operator. This is not JavaScript! Should it be '!='?" + .to_string(), + ))), + "->" => Token::LexError(Box::new(LERR::ImproperSymbol( + "'->' is not a valid symbol. This is not C or C++!".to_string(), + ))), + "<-" => Token::LexError(Box::new(LERR::ImproperSymbol( + "'<-' is not a valid symbol. This is not Go! Should it be '<='?".to_string(), + ))), + "=>" => Token::LexError(Box::new(LERR::ImproperSymbol( + "'=>' is not a valid symbol. This is not Rust! Should it be '>='?" + .to_string(), + ))), + ":=" => Token::LexError(Box::new(LERR::ImproperSymbol( + "':=' is not a valid assignment operator. This is not Go! Should it be simply '='?" + .to_string(), + ))), + "::<" => Token::LexError(Box::new(LERR::ImproperSymbol( + "'::<>' is not a valid symbol. This is not Rust! Should it be '::'?" + .to_string(), + ))), + "(*" | "*)" => Token::LexError(Box::new(LERR::ImproperSymbol( + "'(* .. *)' is not a valid comment format. This is not Pascal! Should it be '/* .. */'?" + .to_string(), + ))), + "#" => Token::LexError(Box::new(LERR::ImproperSymbol( + "'#' is not a valid symbol. Should it be '#{'?" + .to_string(), + ))), + token => Token::LexError(Box::new(LERR::ImproperSymbol( + format!("'{}' is not a valid symbol.", token) + ))), + }, pos)), + (r @ Some(_), None, None) => r, + (Some((Token::Identifier(s), pos)), _, Some(custom)) if custom.contains_key(&s) => { + // Convert custom keywords + Some((Token::Custom(s), pos)) + } + (Some((token, pos)), _, Some(custom)) + if (token.is_keyword() || token.is_operator() || token.is_reserved()) + && custom.contains_key(token.syntax().as_ref()) => + { + // Convert into custom keywords + Some((Token::Custom(token.syntax().into()), pos)) + } + (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)) + } + (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, @@ -1202,6 +1403,7 @@ pub fn lex<'a>(input: &'a [&'a str], max_string_size: usize) -> TokenIterator<'a pos: Position::new(1, 0), stream: MultiInputsStream { streams: input.iter().map(|s| s.chars().peekable()).collect(), + index: 0, }, } } diff --git a/src/utils.rs b/src/utils.rs index 77cef0e7..5dd619ba 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -356,10 +356,13 @@ impl StaticVec { panic!("nothing to pop!"); } - let result = if self.is_fixed_storage() { - self.extract_from_list(self.len - 1) + if self.is_fixed_storage() { + let value = self.extract_from_list(self.len - 1); + self.len -= 1; + value } else { let value = self.more.pop().unwrap(); + self.len -= 1; // Move back to the fixed list if self.more.len() == MAX_STATIC_VEC { @@ -370,11 +373,7 @@ impl StaticVec { } value - }; - - self.len -= 1; - - result + } } /// Remove a value from this `StaticVec` at a particular position. /// @@ -386,18 +385,20 @@ impl StaticVec { panic!("index OOB in StaticVec"); } - let result = if self.is_fixed_storage() { + if self.is_fixed_storage() { let value = self.extract_from_list(index); // Move all items one slot to the left - for x in index..self.len - 1 { - let orig_value = self.extract_from_list(x + 1); - self.set_into_list(x, orig_value, false); + for x in index + 1..self.len - 1 { + let orig_value = self.extract_from_list(x); + self.set_into_list(x - 1, orig_value, false); } + self.len -= 1; value } else { let value = self.more.remove(index); + self.len -= 1; // Move back to the fixed list if self.more.len() == MAX_STATIC_VEC { @@ -408,11 +409,7 @@ impl StaticVec { } value - }; - - self.len -= 1; - - result + } } /// Get the number of items in this `StaticVec`. #[inline(always)] diff --git a/tests/call_fn.rs b/tests/call_fn.rs index dd46450d..62291830 100644 --- a/tests/call_fn.rs +++ b/tests/call_fn.rs @@ -1,5 +1,8 @@ #![cfg(not(feature = "no_function"))] -use rhai::{Engine, EvalAltResult, Func, ParseError, ParseErrorType, Scope, INT}; +use rhai::{ + Dynamic, Engine, EvalAltResult, FnPtr, Func, ImmutableString, Module, ParseError, + ParseErrorType, Scope, INT, +}; #[test] fn test_fn() -> Result<(), Box> { @@ -29,7 +32,7 @@ fn test_call_fn() -> Result<(), Box> { x + y } fn hello(x) { - x = x * foo; + x *= foo; foo = 1; x } @@ -110,3 +113,47 @@ fn test_anonymous_fn() -> Result<(), Box> { Ok(()) } + +#[test] +fn test_fn_ptr() -> Result<(), Box> { + let mut engine = Engine::new(); + + engine.register_raw_fn( + "bar", + &[ + std::any::TypeId::of::(), + std::any::TypeId::of::(), + std::any::TypeId::of::(), + ], + move |engine: &Engine, lib: &Module, args: &mut [&mut Dynamic]| { + let fp = std::mem::take(args[1]).cast::(); + let value = args[2].clone(); + let this_ptr = args.get_mut(0).unwrap(); + + engine.call_fn_dynamic( + &mut Scope::new(), + lib, + fp.fn_name(), + Some(this_ptr), + [value], + )?; + + Ok(()) + }, + ); + + assert_eq!( + engine.eval::( + r#" + fn foo(x) { this += x; } + + let x = 41; + x.bar(Fn("foo"), 1); + x + "# + )?, + 42 + ); + + Ok(()) +} 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/looping.rs b/tests/looping.rs index 3a4804ce..951f8cd7 100644 --- a/tests/looping.rs +++ b/tests/looping.rs @@ -14,7 +14,7 @@ fn test_loop() -> Result<(), Box> { if i < 10 { i += 1; if x > 20 { continue; } - x = x + i; + x += i; } else { break; } diff --git a/tests/maps.rs b/tests/maps.rs index bc2b24a9..c57deaa6 100644 --- a/tests/maps.rs +++ b/tests/maps.rs @@ -21,7 +21,7 @@ fn test_map_indexing() -> Result<(), Box> { r#" let y = #{d: 1, "e": #{a: 42, b: 88, "": "hello"}, " 123 xyz": 9}; y.e[""][4] - "# + "# )?, 'o' ); @@ -47,7 +47,7 @@ fn test_map_indexing() -> Result<(), Box> { let x = #{a: 1, b: 2, c: 3}; let c = x.remove("c"); x.len() + c - "# + "# )?, 5 ); @@ -58,7 +58,7 @@ fn test_map_indexing() -> Result<(), Box> { let y = #{b: 42, d: 9}; x.mixin(y); x.len() + x.b - " + " )?, 46 ); @@ -68,7 +68,7 @@ fn test_map_indexing() -> Result<(), Box> { let x = #{a: 1, b: 2, c: 3}; x += #{b: 42, d: 9}; x.len() + x.b - " + " )?, 46 ); @@ -79,7 +79,7 @@ fn test_map_indexing() -> Result<(), Box> { let x = #{a: 1, b: 2, c: 3}; let y = #{b: 42, d: 9}; x + y - " + " )? .len(), 4 @@ -94,27 +94,9 @@ fn test_map_assign() -> Result<(), Box> { let x = engine.eval::(r#"let x = #{a: 1, b: true, "c$": "hello"}; x"#)?; - assert_eq!( - x.get("a") - .cloned() - .expect("should have property a") - .cast::(), - 1 - ); - assert_eq!( - x.get("b") - .cloned() - .expect("should have property b") - .cast::(), - true - ); - assert_eq!( - x.get("c$") - .cloned() - .expect("should have property c$") - .cast::(), - "hello" - ); + assert_eq!(x["a"].clone().cast::(), 1); + assert_eq!(x["b"].clone().cast::(), true); + assert_eq!(x["c$"].clone().cast::(), "hello"); Ok(()) } @@ -125,27 +107,9 @@ fn test_map_return() -> Result<(), Box> { let x = engine.eval::(r#"#{a: 1, b: true, "c$": "hello"}"#)?; - assert_eq!( - x.get("a") - .cloned() - .expect("should have property a") - .cast::(), - 1 - ); - assert_eq!( - x.get("b") - .cloned() - .expect("should have property b") - .cast::(), - true - ); - assert_eq!( - x.get("c$") - .cloned() - .expect("should have property c$") - .cast::(), - "hello" - ); + assert_eq!(x["a"].clone().cast::(), 1); + assert_eq!(x["b"].clone().cast::(), true); + assert_eq!(x["c$"].clone().cast::(), "hello"); Ok(()) } @@ -167,7 +131,7 @@ fn test_map_for() -> Result<(), Box> { } s - "# + "# )? .len(), 11 @@ -188,41 +152,11 @@ fn test_map_json() -> Result<(), Box> { assert!(!map.contains_key("x")); - assert_eq!( - map.get("a") - .cloned() - .expect("should have property a") - .cast::(), - 1 - ); - assert_eq!( - map.get("b") - .cloned() - .expect("should have property b") - .cast::(), - true - ); - assert_eq!( - map.get("c") - .cloned() - .expect("should have property a") - .cast::(), - 42 - ); - assert_eq!( - map.get("$d e f!") - .cloned() - .expect("should have property $d e f!") - .cast::(), - "hello" - ); - assert_eq!( - map.get("z") - .cloned() - .expect("should have property z") - .cast::<()>(), - () - ); + assert_eq!(map["a"].clone().cast::(), 1); + assert_eq!(map["b"].clone().cast::(), true); + assert_eq!(map["c"].clone().cast::(), 42); + assert_eq!(map["$d e f!"].clone().cast::(), "hello"); + assert_eq!(map["z"].clone().cast::<()>(), ()); #[cfg(not(feature = "no_index"))] { @@ -241,7 +175,7 @@ fn test_map_json() -> Result<(), Box> { } s - "# + "# )? .len(), 11 @@ -265,7 +199,7 @@ fn test_map_oop() -> Result<(), Box> { obj.action(2); obj.data - "#, + "#, )?, 42 ); diff --git a/tests/method_call.rs b/tests/method_call.rs index 02c7a950..20612146 100644 --- a/tests/method_call.rs +++ b/tests/method_call.rs @@ -21,10 +21,10 @@ fn test_method_call() -> Result<(), Box> { let mut engine = Engine::new(); - engine.register_type::(); - - engine.register_fn("update", TestStruct::update); - engine.register_fn("new_ts", TestStruct::new); + engine + .register_type::() + .register_fn("update", TestStruct::update) + .register_fn("new_ts", TestStruct::new); assert_eq!( engine.eval::("let x = new_ts(); x.update(1000); x")?, diff --git a/tests/mismatched_op.rs b/tests/mismatched_op.rs index b129f29b..01373df2 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, } @@ -25,22 +25,19 @@ fn test_mismatched_op_custom_type() { } let mut engine = Engine::new(); - engine.register_type_with_name::("TestStruct"); - engine.register_fn("new_ts", TestStruct::new); - let r = engine - .eval::("60 + new_ts()") - .expect_err("expects error"); + engine + .register_type_with_name::("TestStruct") + .register_fn("new_ts", TestStruct::new); - #[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/modules.rs b/tests/modules.rs index 0e1eb3ba..3d0fc48d 100644 --- a/tests/modules.rs +++ b/tests/modules.rs @@ -69,10 +69,15 @@ fn test_module_resolver() -> Result<(), Box> { let mut resolver = StaticModuleResolver::new(); let mut module = Module::new(); + module.set_var("answer", 42 as INT); module.set_fn_4("sum".to_string(), |x: INT, y: INT, z: INT, w: INT| { Ok(x + y + z + w) }); + module.set_fn_1_mut("double".to_string(), |x: &mut INT| { + *x *= 2; + Ok(()) + }); resolver.insert("hello", module); @@ -90,6 +95,18 @@ fn test_module_resolver() -> Result<(), Box> { 42 ); + assert_eq!( + engine.eval::( + r#" + import "hello" as h; + let x = 21; + h::double(x); + x + "# + )?, + 42 + ); + #[cfg(not(feature = "unchecked"))] { engine.set_max_modules(5); diff --git a/tests/print.rs b/tests/print.rs index ec707e8b..8c580f42 100644 --- a/tests/print.rs +++ b/tests/print.rs @@ -3,16 +3,17 @@ use std::sync::{Arc, RwLock}; #[test] fn test_print() -> Result<(), Box> { - let mut engine = Engine::new(); - let logbook = Arc::new(RwLock::new(Vec::::new())); // Redirect print/debug output to 'log' - let log = logbook.clone(); - engine.on_print(move |s| log.write().unwrap().push(format!("entry: {}", s))); + let log1 = logbook.clone(); + let log2 = logbook.clone(); - let log = logbook.clone(); - engine.on_debug(move |s| log.write().unwrap().push(format!("DEBUG: {}", s))); + let mut engine = Engine::new(); + + engine + .on_print(move |s| log1.write().unwrap().push(format!("entry: {}", s))) + .on_debug(move |s| log2.write().unwrap().push(format!("DEBUG: {}", s))); // Evaluate script engine.eval::<()>("print(40 + 2)")?; diff --git a/tests/serde.rs b/tests/serde.rs new file mode 100644 index 00000000..005e4e8c --- /dev/null +++ b/tests/serde.rs @@ -0,0 +1,684 @@ +#![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!(to_dynamic(42_u64)?.is::()); + assert!(to_dynamic(u64::MAX)?.is::()); + assert!(to_dynamic(42 as INT)?.is::()); + assert!(to_dynamic(true)?.is::()); + assert!(to_dynamic(())?.is::<()>()); + + #[cfg(not(feature = "no_float"))] + { + assert!(to_dynamic(123.456_f64)?.is::()); + assert!(to_dynamic(123.456_f32)?.is::()); + } + + assert!(to_dynamic("hello".to_string())?.is::()); + + Ok(()) +} + +#[test] +fn test_serde_ser_integer_types() -> Result<(), Box> { + assert!(to_dynamic(42_i8)?.is::()); + assert!(to_dynamic(42_i16)?.is::()); + assert!(to_dynamic(42_i32)?.is::()); + assert!(to_dynamic(42_i64)?.is::()); + assert!(to_dynamic(42_u8)?.is::()); + assert!(to_dynamic(42_u16)?.is::()); + assert!(to_dynamic(42_u32)?.is::()); + assert!(to_dynamic(42_u64)?.is::()); + + 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!(4, d.cast::().len()); + + 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 obj = map.remove("obj").unwrap().cast::(); + let seq = map.remove("seq").unwrap().cast::(); + + assert_eq!(Ok(123), obj["a"].as_int()); + assert!(obj["b"].as_bool().unwrap()); + assert_eq!(Ok(42), map["int"].as_int()); + assert_eq!(seq.len(), 3); + assert_eq!(Ok("kitty"), seq[1].as_str()); + + Ok(()) +} + +#[test] +fn test_serde_ser_unit_enum() -> Result<(), Box> { + #[derive(Serialize)] + enum MyEnum { + VariantFoo, + VariantBar, + } + + let d = to_dynamic(MyEnum::VariantFoo)?; + assert_eq!(Ok("VariantFoo"), d.as_str()); + + let d = to_dynamic(MyEnum::VariantBar)?; + assert_eq!(Ok("VariantBar"), d.as_str()); + + Ok(()) +} + +#[test] +#[cfg(not(feature = "no_object"))] +fn test_serde_ser_externally_tagged_enum() -> Result<(), Box> { + #[derive(Serialize)] + enum MyEnum { + VariantUnit, + #[cfg(not(feature = "no_index"))] + VariantUnitTuple(), + VariantNewtype(i32), + #[cfg(not(feature = "no_index"))] + VariantTuple(i32, i32), + VariantEmptyStruct {}, + VariantStruct { + a: i32, + }, + } + + { + assert_eq!(Ok("VariantUnit"), to_dynamic(MyEnum::VariantUnit)?.as_str()); + } + + #[cfg(not(feature = "no_index"))] + { + let mut map = to_dynamic(MyEnum::VariantUnitTuple())?.cast::(); + let content = map.remove("VariantUnitTuple").unwrap().cast::(); + assert!(map.is_empty()); + assert!(content.is_empty()); + } + + { + let mut map = to_dynamic(MyEnum::VariantNewtype(123))?.cast::(); + let content = map.remove("VariantNewtype").unwrap(); + assert!(map.is_empty()); + assert_eq!(Ok(123), content.as_int()); + } + + #[cfg(not(feature = "no_index"))] + { + let mut map = to_dynamic(MyEnum::VariantTuple(123, 456))?.cast::(); + let content = map.remove("VariantTuple").unwrap().cast::(); + assert!(map.is_empty()); + assert_eq!(2, content.len()); + assert_eq!(Ok(123), content[0].as_int()); + assert_eq!(Ok(456), content[1].as_int()); + } + + { + let mut map = to_dynamic(MyEnum::VariantEmptyStruct {})?.cast::(); + let map_inner = map.remove("VariantEmptyStruct").unwrap().cast::(); + assert!(map.is_empty()); + assert!(map_inner.is_empty()); + } + + { + let mut map = to_dynamic(MyEnum::VariantStruct { a: 123 })?.cast::(); + let mut map_inner = map.remove("VariantStruct").unwrap().cast::(); + assert!(map.is_empty()); + assert_eq!(Ok(123), map_inner.remove("a").unwrap().as_int()); + assert!(map_inner.is_empty()); + } + + Ok(()) +} + +#[test] +#[cfg(not(feature = "no_object"))] +fn test_serde_ser_internally_tagged_enum() -> Result<(), Box> { + #[derive(Serialize)] + #[serde(tag = "tag")] + enum MyEnum { + VariantEmptyStruct {}, + VariantStruct { a: i32 }, + } + + let mut map = to_dynamic(MyEnum::VariantEmptyStruct {})?.cast::(); + assert_eq!( + Ok("VariantEmptyStruct"), + map.remove("tag").unwrap().as_str() + ); + assert!(map.is_empty()); + + let mut map = to_dynamic(MyEnum::VariantStruct { a: 123 })?.cast::(); + assert_eq!(Ok("VariantStruct"), map.remove("tag").unwrap().as_str()); + assert_eq!(Ok(123), map.remove("a").unwrap().as_int()); + assert!(map.is_empty()); + + Ok(()) +} + +#[test] +#[cfg(not(feature = "no_object"))] +fn test_serde_ser_adjacently_tagged_enum() -> Result<(), Box> { + #[derive(Serialize)] + #[serde(tag = "tag", content = "content")] + enum MyEnum { + VariantUnit, + #[cfg(not(feature = "no_index"))] + VariantUnitTuple(), + VariantNewtype(i32), + #[cfg(not(feature = "no_index"))] + VariantTuple(i32, i32), + VariantEmptyStruct {}, + VariantStruct { + a: i32, + }, + } + + { + let mut map = to_dynamic(MyEnum::VariantUnit)?.cast::(); + assert_eq!(Ok("VariantUnit"), map.remove("tag").unwrap().as_str()); + assert!(map.is_empty()); + } + + #[cfg(not(feature = "no_index"))] + { + let mut map = to_dynamic(MyEnum::VariantUnitTuple())?.cast::(); + assert_eq!(Ok("VariantUnitTuple"), map.remove("tag").unwrap().as_str()); + let content = map.remove("content").unwrap().cast::(); + assert!(map.is_empty()); + assert!(content.is_empty()); + } + + { + let mut map = to_dynamic(MyEnum::VariantNewtype(123))?.cast::(); + assert_eq!(Ok("VariantNewtype"), map.remove("tag").unwrap().as_str()); + let content = map.remove("content").unwrap(); + assert!(map.is_empty()); + assert_eq!(Ok(123), content.as_int()); + } + + #[cfg(not(feature = "no_index"))] + { + let mut map = to_dynamic(MyEnum::VariantTuple(123, 456))?.cast::(); + assert_eq!(Ok("VariantTuple"), map.remove("tag").unwrap().as_str()); + let content = map.remove("content").unwrap().cast::(); + assert!(map.is_empty()); + assert_eq!(2, content.len()); + assert_eq!(Ok(123), content[0].as_int()); + assert_eq!(Ok(456), content[1].as_int()); + } + + { + let mut map = to_dynamic(MyEnum::VariantEmptyStruct {})?.cast::(); + assert_eq!( + Ok("VariantEmptyStruct"), + map.remove("tag").unwrap().as_str() + ); + let map_inner = map.remove("content").unwrap().cast::(); + assert!(map.is_empty()); + assert!(map_inner.is_empty()); + } + + { + let mut map = to_dynamic(MyEnum::VariantStruct { a: 123 })?.cast::(); + assert_eq!(Ok("VariantStruct"), map.remove("tag").unwrap().as_str()); + let mut map_inner = map.remove("content").unwrap().cast::(); + assert!(map.is_empty()); + assert_eq!(Ok(123), map_inner.remove("a").unwrap().as_int()); + assert!(map_inner.is_empty()); + } + + Ok(()) +} + +#[test] +#[cfg(not(feature = "no_object"))] +fn test_serde_ser_untagged_enum() -> Result<(), Box> { + #[derive(Serialize)] + #[serde(untagged)] + enum MyEnum { + VariantEmptyStruct {}, + VariantStruct1 { a: i32 }, + VariantStruct2 { b: i32 }, + } + + { + let map = to_dynamic(MyEnum::VariantEmptyStruct {})?.cast::(); + assert!(map.is_empty()); + } + + { + let mut map = to_dynamic(MyEnum::VariantStruct1 { a: 123 })?.cast::(); + assert_eq!(Ok(123), map.remove("a").unwrap().as_int()); + assert!(map.is_empty()); + } + + { + let mut map = to_dynamic(MyEnum::VariantStruct2 { b: 123 })?.cast::(); + assert_eq!(Ok(123), map.remove("b").unwrap().as_int()); + assert!(map.is_empty()); + } + + 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] +fn test_serde_de_integer_types() -> Result<(), Box> { + assert_eq!(42_i8, from_dynamic(&Dynamic::from(42 as INT))?); + assert_eq!(42_i16, from_dynamic(&Dynamic::from(42 as INT))?); + assert_eq!(42_i32, from_dynamic(&Dynamic::from(42 as INT))?); + assert_eq!(42_i64, from_dynamic(&Dynamic::from(42 as INT))?); + assert_eq!(42_u8, from_dynamic(&Dynamic::from(42 as INT))?); + assert_eq!(42_u16, from_dynamic(&Dynamic::from(42 as INT))?); + assert_eq!(42_u32, from_dynamic(&Dynamic::from(42 as INT))?); + assert_eq!(42_u64, from_dynamic(&Dynamic::from(42 as INT))?); + + 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(()) +} + +#[test] +fn test_serde_de_unit_enum() -> Result<(), Box> { + #[derive(Debug, PartialEq, Deserialize)] + enum MyEnum { + VariantFoo, + VariantBar, + } + + { + let d = Dynamic::from("VariantFoo".to_string()); + assert_eq!(MyEnum::VariantFoo, from_dynamic(&d)?); + } + + { + let d = Dynamic::from("VariantBar".to_string()); + assert_eq!(MyEnum::VariantBar, from_dynamic(&d)?); + } + + Ok(()) +} + +#[test] +#[cfg(not(feature = "no_object"))] +fn test_serde_de_externally_tagged_enum() -> Result<(), Box> { + #[derive(Debug, PartialEq, Deserialize)] + #[serde(deny_unknown_fields)] + enum MyEnum { + VariantUnit, + #[cfg(not(feature = "no_index"))] + VariantUnitTuple(), + VariantNewtype(i32), + #[cfg(not(feature = "no_index"))] + VariantTuple(i32, i32), + VariantEmptyStruct {}, + VariantStruct { + a: i32, + }, + } + + { + let d = Dynamic::from("VariantUnit".to_string()); + assert_eq!(MyEnum::VariantUnit, from_dynamic(&d).unwrap()); + } + + #[cfg(not(feature = "no_index"))] + { + let array: Array = vec![]; + let mut map_outer = Map::new(); + map_outer.insert("VariantUnitTuple".into(), array.into()); + assert_eq!( + MyEnum::VariantUnitTuple(), + from_dynamic(&map_outer.into()).unwrap() + ); + } + + { + let mut map_outer = Map::new(); + map_outer.insert("VariantNewtype".into(), (123 as INT).into()); + assert_eq!( + MyEnum::VariantNewtype(123), + from_dynamic(&map_outer.into()).unwrap() + ); + } + + #[cfg(not(feature = "no_index"))] + { + let array: Array = vec![(123 as INT).into(), (456 as INT).into()]; + let mut map_outer = Map::new(); + map_outer.insert("VariantTuple".into(), array.into()); + assert_eq!( + MyEnum::VariantTuple(123, 456), + from_dynamic(&map_outer.into()).unwrap() + ); + } + + { + let map_inner = Map::new(); + let mut map_outer = Map::new(); + map_outer.insert("VariantEmptyStruct".into(), map_inner.into()); + assert_eq!( + MyEnum::VariantEmptyStruct {}, + from_dynamic(&map_outer.into()).unwrap() + ); + } + + { + let mut map_inner = Map::new(); + map_inner.insert("a".into(), (123 as INT).into()); + let mut map_outer = Map::new(); + map_outer.insert("VariantStruct".into(), map_inner.into()); + assert_eq!( + MyEnum::VariantStruct { a: 123 }, + from_dynamic(&map_outer.into()).unwrap() + ); + } + + Ok(()) +} + +#[test] +#[cfg(not(feature = "no_object"))] +fn test_serde_de_internally_tagged_enum() -> Result<(), Box> { + #[derive(Debug, PartialEq, Deserialize)] + #[serde(tag = "tag", deny_unknown_fields)] + enum MyEnum { + VariantEmptyStruct {}, + VariantStruct { a: i32 }, + } + + { + let mut map = Map::new(); + map.insert("tag".into(), "VariantStruct".into()); + map.insert("a".into(), (123 as INT).into()); + assert_eq!( + MyEnum::VariantStruct { a: 123 }, + from_dynamic(&map.into()).unwrap() + ); + } + + { + let mut map = Map::new(); + map.insert("tag".into(), "VariantEmptyStruct".into()); + assert_eq!( + MyEnum::VariantEmptyStruct {}, + from_dynamic(&map.into()).unwrap() + ); + } + + Ok(()) +} + +#[test] +#[cfg(not(feature = "no_object"))] +fn test_serde_de_adjacently_tagged_enum() -> Result<(), Box> { + #[derive(Debug, PartialEq, Deserialize)] + #[serde(tag = "tag", content = "content", deny_unknown_fields)] + enum MyEnum { + VariantUnit, + #[cfg(not(feature = "no_index"))] + VariantUnitTuple(), + VariantNewtype(i32), + #[cfg(not(feature = "no_index"))] + VariantTuple(i32, i32), + VariantEmptyStruct {}, + VariantStruct { + a: i32, + }, + } + + { + let mut map_outer = Map::new(); + map_outer.insert("tag".into(), "VariantUnit".into()); + assert_eq!( + MyEnum::VariantUnit, + from_dynamic(&map_outer.into()).unwrap() + ); + } + + #[cfg(not(feature = "no_index"))] + { + let array: Array = vec![]; + let mut map_outer = Map::new(); + map_outer.insert("tag".into(), "VariantUnitTuple".into()); + map_outer.insert("content".into(), array.into()); + assert_eq!( + MyEnum::VariantUnitTuple(), + from_dynamic(&map_outer.into()).unwrap() + ); + } + + { + let mut map_outer = Map::new(); + map_outer.insert("tag".into(), "VariantNewtype".into()); + map_outer.insert("content".into(), (123 as INT).into()); + assert_eq!( + MyEnum::VariantNewtype(123), + from_dynamic(&map_outer.into()).unwrap() + ); + } + + #[cfg(not(feature = "no_index"))] + { + let array: Array = vec![(123 as INT).into(), (456 as INT).into()]; + let mut map_outer = Map::new(); + map_outer.insert("tag".into(), "VariantTuple".into()); + map_outer.insert("content".into(), array.into()); + assert_eq!( + MyEnum::VariantTuple(123, 456), + from_dynamic(&map_outer.into()).unwrap() + ); + } + + { + let map_inner = Map::new(); + let mut map_outer = Map::new(); + map_outer.insert("tag".into(), "VariantEmptyStruct".into()); + map_outer.insert("content".into(), map_inner.into()); + assert_eq!( + MyEnum::VariantEmptyStruct {}, + from_dynamic(&map_outer.into()).unwrap() + ); + } + + { + let mut map_inner = Map::new(); + map_inner.insert("a".into(), (123 as INT).into()); + let mut map_outer = Map::new(); + map_outer.insert("tag".into(), "VariantStruct".into()); + map_outer.insert("content".into(), map_inner.into()); + assert_eq!( + MyEnum::VariantStruct { a: 123 }, + from_dynamic(&map_outer.into()).unwrap() + ); + } + + Ok(()) +} + +#[test] +#[cfg(not(feature = "no_object"))] +fn test_serde_de_untagged_enum() -> Result<(), Box> { + #[derive(Debug, PartialEq, Deserialize)] + #[serde(untagged, deny_unknown_fields)] + enum MyEnum { + VariantEmptyStruct {}, + VariantStruct1 { a: i32 }, + VariantStruct2 { b: i32 }, + } + + { + let map = Map::new(); + assert_eq!( + MyEnum::VariantEmptyStruct {}, + from_dynamic(&map.into()).unwrap() + ); + } + + { + let mut map = Map::new(); + map.insert("a".into(), (123 as INT).into()); + assert_eq!( + MyEnum::VariantStruct1 { a: 123 }, + from_dynamic(&map.into()).unwrap() + ); + } + + { + let mut map = Map::new(); + map.insert("b".into(), (123 as INT).into()); + assert_eq!( + MyEnum::VariantStruct2 { b: 123 }, + from_dynamic(&map.into()).unwrap() + ); + } + + Ok(()) +} diff --git a/tests/syntax.rs b/tests/syntax.rs new file mode 100644 index 00000000..0fdf3c07 --- /dev/null +++ b/tests/syntax.rs @@ -0,0 +1,77 @@ +#![cfg(feature = "internals")] +use rhai::{ + Dynamic, Engine, EvalAltResult, EvalState, Expression, Imports, LexError, Module, Scope, INT, +}; + +#[test] +fn test_custom_syntax() -> Result<(), Box> { + let mut engine = Engine::new(); + + // Disable 'while' and make sure it still works with custom syntax + engine.disable_symbol("while"); + engine.consume("while false {}").expect_err("should error"); + engine.consume("let while = 0")?; + + engine + .register_custom_syntax( + &[ + "do", "|", "$ident$", "|", "->", "$block$", "while", "$expr$", + ], + 1, + |engine: &Engine, + scope: &mut Scope, + mods: &mut Imports, + state: &mut EvalState, + lib: &Module, + this_ptr: &mut Option<&mut Dynamic>, + inputs: &[Expression], + level: usize| { + let var_name = inputs[0].get_variable_name().unwrap().to_string(); + let stmt = inputs.get(1).unwrap(); + let expr = inputs.get(2).unwrap(); + + scope.push(var_name, 0 as INT); + + loop { + engine.eval_expression_tree(scope, mods, state, lib, this_ptr, stmt, level)?; + + if !engine + .eval_expression_tree(scope, mods, state, lib, this_ptr, expr, level)? + .as_bool() + .map_err(|_| { + EvalAltResult::ErrorBooleanArgMismatch( + "do-while".into(), + expr.position(), + ) + })? + { + break; + } + } + + Ok(().into()) + }, + ) + .unwrap(); + + // 'while' is now a custom keyword so this it can no longer be a variable + engine.consume("let while = 0").expect_err("should error"); + + assert_eq!( + engine.eval::( + r" + do |x| -> { x += 1 } while x < 42; + x + " + )?, + 42 + ); + + // The first symbol must be an identifier + assert!(matches!( + *engine.register_custom_syntax(&["!"], 0, |_, _, _, _, _, _, _, _| Ok(().into())).expect_err("should error"), + LexError::ImproperSymbol(s) if s == "!" + )); + + Ok(()) +} diff --git a/tests/tokens.rs b/tests/tokens.rs new file mode 100644 index 00000000..bb033619 --- /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 160 (i.e. between +|- and *|/). + engine.register_custom_operator("foo", 160).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(()) +} diff --git a/tests/var_scope.rs b/tests/var_scope.rs index fde83a05..0b3cc1b9 100644 --- a/tests/var_scope.rs +++ b/tests/var_scope.rs @@ -7,7 +7,7 @@ fn test_var_scope() -> Result<(), Box> { engine.eval_with_scope::<()>(&mut scope, "let x = 4 + 5")?; assert_eq!(engine.eval_with_scope::(&mut scope, "x")?, 9); - engine.eval_with_scope::<()>(&mut scope, "x = x + 1; x = x + 2;")?; + engine.eval_with_scope::<()>(&mut scope, "x += 1; x += 2;")?; assert_eq!(engine.eval_with_scope::(&mut scope, "x")?, 12); scope.set_value("x", 42 as INT); diff --git a/tests/while_loop.rs b/tests/while_loop.rs index 8916cd7c..bbcd091b 100644 --- a/tests/while_loop.rs +++ b/tests/while_loop.rs @@ -10,10 +10,10 @@ fn test_while() -> Result<(), Box> { let x = 0; while x < 10 { - x = x + 1; + x += 1; if x > 5 { break; } if x > 3 { continue; } - x = x + 3; + x += 3; } x