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::