Merge pull request #176 from schungx/master

Serde support, disable keywords, custom operators, custom syntax and chainable API.
This commit is contained in:
Stephen Chung 2020-07-12 11:47:34 +08:00 committed by GitHub
commit 0d90be387b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
83 changed files with 5976 additions and 1472 deletions

29
.github/workflows/benchmark.yml vendored Normal file
View File

@ -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'

2
.gitignore vendored
View File

@ -3,3 +3,5 @@ Cargo.lock
.vscode/
.cargo/
doc/book/
before
after

View File

@ -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"]

View File

@ -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).

View File

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

View File

@ -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)

View File

@ -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].

View File

@ -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/).

View File

@ -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 |
| <code>\|</code> | 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 |
| <code>\|\|</code> | 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 |
| <code>\|</code> | 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 |
| <code>\|\|</code> | 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_ |

View File

@ -1,5 +1,6 @@
{
"version": "0.16.0",
"version": "0.17.0",
"rootUrl": "",
"rootUrlX": "/rhai"
"rootUrlX": "/rhai",
"rootUrlXX": "/rhai/vnext"
}

View File

@ -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<Item = Dynamic>` (such as a simple `Vec<Dynamic>`):
anything that implements `AsMut<Dynamic>` (such as a simple array or a `Vec<Dynamic>`):
```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);
```

105
doc/src/engine/custom-op.md Normal file
View File

@ -0,0 +1,105 @@
Custom Operators
================
{{#include ../links.md}}
For use as a DSL (Domain-Specific Languages), it is sometimes more convenient to augment Rhai with
customized operators performing specific logic.
`Engine::register_custom_operator` registers a keyword as a custom operator.
Example
-------
```rust
use rhai::{Engine, RegisterFn};
let mut engine = Engine::new();
// Register a custom operator 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::<i64>("1 + 2 * 3 foo 4 - 5 / 6")?;
// ^ custom operator
// The above is equivalent to: 1 + ((2 * 3) foo 4) - (5 / 6)
result == 15;
```
Alternatives to a Custom Operator
--------------------------------
Custom operators are merely _syntactic sugar_. They map directly to registered functions.
Therefore, the following are equivalent (assuming `foo` has been registered as a custom operator):
```rust
1 + 2 * 3 foo 4 - 5 / 6 // use custom operator
1 + foo(2 * 3, 4) - 5 / 6 // use function call
```
A script using custom operators can always be pre-processed, via a pre-processor application,
into a syntax that uses the corresponding function calls.
Using `Engine::register_custom_operator` merely enables a convenient 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::<i64>("1 + 2 * 3 foo 4 - 5 / 6")?; // error: function 'foo (i64, i64)' not found
```
Operator Precedence
-------------------
All operators in Rhai has a _precedence_ indicating how tightly they bind.
The following _precedence table_ show the built-in precedence of standard Rhai operators:
| Category | Operators | Precedence (0-255) |
| ------------------- | :-------------------------------------------------------------------------------------: | :----------------: |
| Assignments | `=`, `+=`, `-=`, `*=`, `/=`, `~=`, `%=`,<br/>`<<=`, `>>=`, `&=`, <code>\|=</code>, `^=` | 0 |
| Logic and bit masks | <code>\|\|</code>, <code>\|</code>, `^` | 30 |
| Logic and bit masks | `&`, `&&` | 60 |
| Comparisons | `==`, `!=` | 90 |
| 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.

View File

@ -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<Dynamic, Box<EvalAltResult>>
```
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<Dynamic, Box<EvalAltResult>> {
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!
--------------------

29
doc/src/engine/disable.md Normal file
View File

@ -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
```

84
doc/src/engine/dsl.md Normal file
View File

@ -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.

View File

@ -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'
```

View File

@ -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;
```

View File

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

View File

@ -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.

View File

@ -3,6 +3,7 @@ Import a Module
{{#include ../../links.md}}
`import` Statement
-----------------

View File

@ -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::<i64>(&scope, "question::answer + 1")? == 42;
// Call module-qualified functions
engine.eval_expression_with_scope::<i64>(&scope, "question::inc(question::answer)")? == 42;
```

View File

@ -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. |

View File

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

View File

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

View File

@ -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::<TestStruct>();
engine.register_fn("update", TestStruct::update);
engine.register_fn("new_ts", TestStruct::new);
engine
.register_type::<TestStruct>() // most API's can be chained up
.register_fn("update", TestStruct::update)
.register_fn("new_ts", TestStruct::new);
let result = engine.eval::<TestStruct>("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::<i64>(
"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::<TestStruct>();
engine.register_fn("new_ts", TestStruct::new);
let x = new_ts();
print(x.type_of()); // prints "path::to::module::TestStruct"
engine
.register_type::<TestStruct>()
.register_fn("new_ts", TestStruct::new);
engine.register_type_with_name::<TestStruct>("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::<TestStruct>("Hello")
.register_fn("new_ts", TestStruct::new);
let x = new_ts();
x.type_of() == "Hello";
```

View File

@ -16,7 +16,7 @@ use rhai::RegisterResultFn; // use 'RegisterResultFn' trait
fn safe_divide(x: i64, y: i64) -> Result<Dynamic, Box<EvalAltResult>> {
if y == 0 {
// Return an error if y is zero
Err("Division by zero!".into()) // short-cut to create Box<EvalAltResult::ErrorRuntime>
Err("Division by zero!".into()) // shortcut to create Box<EvalAltResult::ErrorRuntime>
} else {
Ok((x / y).into()) // convert result into 'Dynamic'
}

View File

@ -31,8 +31,9 @@ fn get_any_value() -> Result<Dynamic, Box<EvalAltResult>> {
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::<i64>(r#"add(40, "xx")"#)?;

View File

@ -19,9 +19,10 @@ fn show_it<T: Display>(x: &mut T) {
let mut engine = Engine::new();
engine.register_fn("print", show_it::<i64>);
engine.register_fn("print", show_it::<bool>);
engine.register_fn("print", show_it::<ImmutableString>);
engine
.register_fn("print", show_it::<i64>)
.register_fn("print", show_it::<bool>)
.register_fn("print", show_it::<ImmutableString>);
```
The above example shows how to register multiple functions

View File

@ -30,10 +30,10 @@ impl TestStruct {
let mut engine = Engine::new();
engine.register_type::<TestStruct>();
engine.register_get_set("xyz", TestStruct::get_field, TestStruct::set_field);
engine.register_fn("new_ts", TestStruct::new);
engine
.register_type::<TestStruct>()
.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::<String>(r#"let a = new_ts(); a.xyz = "42"; a.xyz"#)?;

View File

@ -38,8 +38,9 @@ engine.register_type::<TestStruct>();
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::<i64>("let a = new_ts(); a[2] = 42; a[2]")?;

View File

@ -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::<i64>(&scope, r#"import "question" as q; q::answer + 1"#)? == 42;
// Call module-qualified functions
engine.eval::<i64>(&scope, r#"import "question" as q; q::inc(q::answer)"#)? == 42;
```

View File

@ -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.<br/>The base directory can be changed via the `FileModuleResolver::new_with_path()` constructor function.<br/>`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.<br/>The base directory can be changed via the `FileModuleResolver::new_with_path()` constructor function.<br/>`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.<br/>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`:

View File

@ -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
```

View File

@ -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]. |

View File

@ -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::<i64>(), // type of first parameter
std::any::TypeId::of::<i64>() // 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::<i64>() // 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::<i64>(); // alternatively, directly 'consume' it
let x: &mut i64 = args[0].downcast_mut::<i64>() // get a '&mut' reference to the
.unwrap(); // first argument
*x += y; // perform the action
Ok(().into()) // must be 'Result<Dynamic, Box<EvalAltResult>>'
}
);
// 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::<i64, i64>(
"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<T, Box<EvalAltResult>> + '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::<T>()` | Copy of value. |
| Custom type | `args[n].downcast_ref::<T>().unwrap()` | Immutable reference to value. |
| Custom type (consumed) | `std::mem::take(args[n]).cast::<T>()` | The _consumed_ value.<br/>The original value becomes `()`. |
| `this` object | `args[0].downcast_mut::<T>().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::<i64>(), // parameter types
std::any::TypeId::of::<FnPtr>(),
std::any::TypeId::of::<i64>(),
],
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::<FnPtr>(); // 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::<i64>(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::<A>().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::<B>().unwrap();
```

View File

@ -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"

104
doc/src/rust/serde.md Normal file
View File

@ -0,0 +1,104 @@
Serialization and Deserialization of `Dynamic` with `serde`
=========================================================
{{#include ../links.md}}
Rhai's [`Dynamic`] type supports serialization and deserialization by [`serde`](https://crates.io/crates/serde)
via the [`serde`][features] feature.
A [`Dynamic`] can be seamlessly converted to and from a type that implements `serde::Serialize` and/or
`serde::Deserialize`.
Serialization
-------------
The function `rhai::ser::to_dynamic` automatically converts any Rust type that implements `serde::Serialize`
into a [`Dynamic`].
This is usually not necessary because using [`Dynamic::from`][`Dynamic`] is much easier and is essentially
the same thing. The only difference is treatment for integer values. `Dynamic::from` will keep the different
integer types intact, while `rhai::ser::to_dynamic` will convert them all into [`INT`][standard types]
(i.e. the system integer type which is `i64` or `i32` depending on the [`only_i32`] feature).
In particular, Rust `struct`'s (or any type that is marked as a `serde` map) are converted into [object maps]
while Rust `Vec`'s (or any type that is marked as a `serde` sequence) are converted into [arrays].
While it is also simple to serialize a Rust type to `JSON` via `serde`,
then use [`Engine::parse_json`]({{rootUrl}}/language/json.md) to convert it into an [object map],
`rhai::ser::to_dynamic` serializes it to [`Dynamic`] directly via `serde` without going through the `JSON` step.
```rust
use rhai::{Dynamic, Map};
use rhai::ser::to_dynamic;
#[derive(Debug, serde::Serialize)]
struct Point {
x: f64,
y: f64
}
#[derive(Debug, serde::Serialize)]
struct MyStruct {
a: i64,
b: Vec<String>,
c: bool,
d: Point
}
let x = MyStruct {
a: 42,
b: vec![ "hello".into(), "world".into() ],
c: true,
d: Point { x: 123.456, y: 999.0 }
};
// Convert the 'MyStruct' into a 'Dynamic'
let map: Dynamic = to_dynamic(x);
map.is::<Map>() == true;
```
Deserialization
---------------
The function `rhai::de::from_dynamic` automatically converts a [`Dynamic`] value into any Rust type
that implements `serde::Deserialize`.
In particular, [object maps] are converted into Rust `struct`'s (or any type that is marked as
a `serde` map) while [arrays] are converted into Rust `Vec`'s (or any type that is marked
as a `serde` sequence).
```rust
use rhai::{Engine, Dynamic};
use rhai::de::from_dynamic;
#[derive(Debug, serde::Deserialize)]
struct Point {
x: f64,
y: f64
}
#[derive(Debug, serde::Deserialize)]
struct MyStruct {
a: i64,
b: Vec<String>,
c: bool,
d: Point
}
let engine = Engine::new();
let result: Dynamic = engine.eval(r#"
#{
a: 42,
b: [ "hello", "world" ],
c: true,
d: #{ x: 123.456, y: 999.0 }
}
"#)?;
// Convert the 'Dynamic' object map into 'MyStruct'
let x: MyStruct = from_dynamic(&result)?;
```

View File

@ -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::<i64>("x.len1()")?; // error: function 'len1 (&str | ImmutableString)' not found
let len = engine.eval::<i64>("x.len2()")?; // works fine

View File

@ -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).<br/>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).

View File

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

76
examples/serde.rs Normal file
View File

@ -0,0 +1,76 @@
#[cfg(not(feature = "serde"))]
fn main() {
println!(r#"This example requires the "serde" feature which is not enabled by default."#);
println!("Try: cargo run --features serde --example serde");
}
#[cfg(feature = "serde")]
fn main() {
example::ser();
println!();
example::de();
}
#[cfg(feature = "serde")]
mod example {
use rhai::{de::from_dynamic, ser::to_dynamic};
use rhai::{Dynamic, Engine, Map};
use serde::{Deserialize, Serialize};
#[derive(Debug, Serialize, Deserialize)]
struct Point {
x: f64,
y: f64,
}
#[derive(Debug, Serialize, Deserialize)]
struct MyStruct {
a: i64,
b: Vec<String>,
c: bool,
d: Point,
}
pub fn ser() {
let x = MyStruct {
a: 42,
b: vec!["hello".into(), "world".into()],
c: true,
d: Point {
x: 123.456,
y: 999.0,
},
};
println!("Source struct: {:#?}", x);
// Convert the 'MyStruct' into a 'Dynamic'
let map: Dynamic = to_dynamic(x).unwrap();
assert!(map.is::<Map>());
println!("Serialized to Dynamic: {:#?}", map);
}
pub fn de() {
let engine = Engine::new();
let result: Dynamic = engine
.eval(
r#"
#{
a: 42,
b: [ "hello", "world" ],
c: true,
d: #{ x: 123.456, y: 999.0 }
}
"#,
)
.unwrap();
println!("Source Dynamic: {:#?}", result);
// Convert the 'Dynamic' object map into 'MyStruct'
let x: MyStruct = from_dynamic(&result).unwrap();
println!("Deserialized to struct: {:#?}", x);
}
}

View File

@ -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::<Instant>() => "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>() {
"string"
} else if name == type_name::<ImmutableString>() {
"string"
} else if name == type_name::<&str>() {
"string"
} else if name == type_name::<FnPtr>() {
"Fn"
} else if name == type_name::<Instant>() {
"timestamp"
} else {
#[cfg(not(feature = "no_index"))]
if name == type_name::<Array>() {
return "array";
}
#[cfg(not(feature = "no_object"))]
if name == type_name::<Map>() {
return "map";
}
name
}
}
impl fmt::Display for Dynamic {
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::<Instant>() => write!(f, "<timestamp>"),
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::<Instant>() => write!(f, "<timestamp>"),
Union::Variant(_) => write!(f, "<dynamic>"),
}
@ -323,10 +346,8 @@ impl Dynamic {
}
#[cfg(not(feature = "no_float"))]
{
if let Some(result) = <dyn Any>::downcast_ref::<FLOAT>(&value) {
return result.clone().into();
}
if let Some(result) = <dyn Any>::downcast_ref::<FLOAT>(&value) {
return result.clone().into();
}
let mut boxed = Box::new(value);

View File

@ -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::<T>()`
///
/// To access a parameter value and avoid cloning, use `std::mem::take(args[n]).cast::<T>()`.
/// 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<T: Variant + Clone>(
&mut self,
name: &str,
arg_types: &[TypeId],
func: impl Fn(&Engine, &Module, &mut [&mut Dynamic]) -> FuncReturn<T> + 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<T: Variant + Clone>(
&mut self,
name: &str,
func: impl Fn(&Engine, &Module, &mut [&mut Dynamic]) -> FuncReturn<T> + 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::<T>()`
///
/// To access a parameter value and avoid cloning, use `std::mem::take(args[n]).cast::<T>()`.
/// 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<A: Variant + Clone, T: Variant + Clone>(
&mut self,
name: &str,
func: impl Fn(&Engine, &Module, &mut [&mut Dynamic]) -> FuncReturn<T> + SendSync + 'static,
) -> &mut Self {
self.global_module
.set_raw_fn(name, &[TypeId::of::<A>()], 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::<T>()`
///
/// To access a parameter value and avoid cloning, use `std::mem::take(args[n]).cast::<T>()`.
/// 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<A: Variant + Clone, B: Variant + Clone, T: Variant + Clone>(
&mut self,
name: &str,
func: impl Fn(&Engine, &Module, &mut [&mut Dynamic]) -> FuncReturn<T> + SendSync + 'static,
) -> &mut Self {
self.global_module
.set_raw_fn(name, &[TypeId::of::<A>(), TypeId::of::<B>()], 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::<T>()`
///
/// To access a parameter value and avoid cloning, use `std::mem::take(args[n]).cast::<T>()`.
/// 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<T> + SendSync + 'static,
) -> &mut Self {
self.global_module.set_raw_fn(
name,
&[TypeId::of::<A>(), TypeId::of::<B>(), TypeId::of::<C>()],
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::<T>()`
///
/// To access a parameter value and avoid cloning, use `std::mem::take(args[n]).cast::<T>()`.
/// 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<T> + SendSync + 'static,
) -> &mut Self {
self.global_module.set_raw_fn(
name,
&[
TypeId::of::<A>(),
TypeId::of::<B>(),
TypeId::of::<C>(),
TypeId::of::<D>(),
],
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<T: Variant + Clone>(&mut self) {
self.register_type_with_name::<T>(type_name::<T>());
pub fn register_type<T: Variant + Clone>(&mut self) -> &mut Self {
self.register_type_with_name::<T>(type_name::<T>())
}
/// 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<T: Variant + Clone>(&mut self, name: &str) {
pub fn register_type_with_name<T: Variant + Clone>(&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::<T>().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<T: Variant + Clone>(&mut self, f: IteratorFn) {
pub fn register_iterator<T: Variant + Clone>(&mut self, f: IteratorFn) -> &mut Self {
self.global_module.set_iter(TypeId::of::<T>(), 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<T, X, U>(
&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<T, X, U>(
&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<AST, ParseError> {
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<AST, ParseError> {
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::<i64>(&mut scope, "x = x + 2; x")?, 42);
/// assert_eq!(engine.eval_with_scope::<i64>(&mut scope, "x = x + 2; x")?, 44);
/// assert_eq!(engine.eval_with_scope::<i64>(&mut scope, "x += 2; x")?, 42);
/// assert_eq!(engine.eval_with_scope::<i64>(&mut scope, "x += 2; x")?, 44);
///
/// // The variable in the scope is modified
/// assert_eq!(scope.get_value::<i64>("x").expect("variable x should exist"), 44);
@ -909,7 +1089,7 @@ impl Engine {
script: &str,
) -> Result<T, Box<EvalAltResult>> {
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::<i64>(&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::<T>().ok_or_else(|| {
Box::new(EvalAltResult::ErrorMismatchOutputType(
return_type.into(),
self.map_type_name(type_name::<T>()).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<EvalAltResult>> {
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<T, Box<EvalAltResult>> {
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::<T>()).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<rhai::EvalAltResult>> {
/// # #[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::<i64>(), 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::<i64>(), 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::<i64>(), 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<Module>,
name: &str,
arg_values: impl IntoIterator<Item = Dynamic>,
) -> Result<Dynamic, Box<EvalAltResult>> {
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<Dynamic> {
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<Module>,
name: &str,
this_ptr: &mut Option<&mut Dynamic>,
arg_values: &mut [Dynamic],
) -> Result<Dynamic, Box<EvalAltResult>> {
) -> FuncReturn<Dynamic> {
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
}
}

File diff suppressed because it is too large Load Diff

View File

@ -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()),
}
}
}

View File

@ -15,8 +15,8 @@ use crate::stdlib::{boxed::Box, string::ToString};
pub trait Func<ARGS, RET> {
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
///

View File

@ -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<T> = Arc<T>;
/// If the resource is shared (i.e. has other outstanding references), a cloned copy is used.
pub fn shared_make_mut<T: Clone>(value: &mut Shared<T>) -> &mut T {
#[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<T: Clone>(value: &mut Shared<T>) -> &mut T {
/// Panics if the resource is shared (i.e. has other outstanding references).
pub fn shared_take<T: Clone>(value: Shared<T>) -> 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<S: Into<ImmutableString>>(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<S: Into<ImmutableString>> From<S> for FnPtr {
fn from(value: S) -> Self {
Self(value.into())
impl TryFrom<ImmutableString> for FnPtr {
type Error = Box<EvalAltResult>;
fn try_from(value: ImmutableString) -> Result<Self, Self::Error> {
if is_valid_identifier(value.chars()) {
Ok(Self(value))
} else {
Err(Box::new(EvalAltResult::ErrorFunctionNotFound(
value.to_string(),
Position::none(),
)))
}
}
}
impl TryFrom<String> for FnPtr {
type Error = Box<EvalAltResult>;
fn try_from(value: String) -> Result<Self, Self::Error> {
let s: ImmutableString = value.into();
Self::try_from(s)
}
}
impl TryFrom<&str> for FnPtr {
type Error = Box<EvalAltResult>;
fn try_from(value: &str) -> Result<Self, Self::Error> {
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<Dynamic, Box<EvalAltResult>>;
pub type FnAny = dyn Fn(&Engine, &Module, &mut FnCallArgs) -> Result<Dynamic, Box<EvalAltResult>>;
/// A general function trail object.
#[cfg(feature = "sync")]
pub type FnAny =
dyn Fn(&Engine, &mut FnCallArgs) -> Result<Dynamic, Box<EvalAltResult>> + Send + Sync;
dyn Fn(&Engine, &Module, &mut FnCallArgs) -> Result<Dynamic, Box<EvalAltResult>> + Send + Sync;
/// A standard function that gets an iterator from a type.
pub type IteratorFn = fn(Dynamic) -> Box<dyn Iterator<Item = Dynamic>>;
@ -114,6 +140,7 @@ pub enum CallableFunction {
/// An iterator function.
Iterator(IteratorFn),
/// A script-defined function.
#[cfg(not(feature = "no_function"))]
Script(Shared<ScriptFnDef>),
}
@ -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<ScriptFnDef> {
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<IteratorFn> for CallableFunction {
}
}
#[cfg(not(feature = "no_function"))]
impl From<ScriptFnDef> for CallableFunction {
fn from(func: ScriptFnDef) -> Self {
Self::Script(func.into())
}
}
#[cfg(not(feature = "no_function"))]
impl From<Shared<ScriptFnDef>> for CallableFunction {
fn from(func: Shared<ScriptFnDef>) -> Self {
Self::Script(func)

View File

@ -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<FN, ARGS, RET> {
/// # 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<Dynamic, Box<EvalAltResult>>` with the `Engine`.
@ -69,7 +70,7 @@ pub trait RegisterResultFn<FN, ARGS> {
/// engine.eval::<i64>("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<FN, ($($mark,)*), RET> 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<Dynamic, Box<EvalAltResult>> + SendSync + 'static,
> RegisterResultFn<FN, ($($mark,)*)> 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
}
}

View File

@ -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")]

View File

@ -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<u64, Dynamic, StraightHasherBuilder>,
/// External Rust functions.
functions: HashMap<
u64,
(String, FnAccess, StaticVec<TypeId>, CallableFunction),
StraightHasherBuilder,
>,
functions: HashMap<u64, (String, FnAccess, StaticVec<TypeId>, Func), StraightHasherBuilder>,
/// Iterator functions, keyed by the type producing the iterator.
type_iterators: HashMap<TypeId, IteratorFn>,
/// Flattened collection of all external Rust functions, native or scripted,
/// including those in sub-modules.
all_functions: HashMap<u64, CallableFunction, StraightHasherBuilder>,
all_functions: HashMap<u64, Func, StraightHasherBuilder>,
/// Is the module indexed?
indexed: bool,
@ -102,6 +98,12 @@ impl Clone for Module {
}
}
impl AsRef<Module> 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::<i64>("answer").unwrap(), 42);
/// ```
pub fn set_var(&mut self, name: impl Into<String>, value: impl Variant + Clone) {
pub fn set_var(&mut self, name: impl Into<String>, 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<String>, sub_module: Module) {
pub fn set_sub_module(&mut self, name: impl Into<String>, 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<String>,
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<T: Variant + Clone>(
///
/// ## 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::<T>()`
///
/// To access a parameter value and avoid cloning, use `std::mem::take(args[n]).cast::<T>()`.
/// 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::<i64>(), std::any::TypeId::of::<bool>() ],
/// // 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::<bool>();
/// // Since it is a primary type, it can also be cheaply copied
/// let double = args[1].clone().cast::<bool>();
/// // Get a mutable reference to the first argument.
/// let x = args[0].downcast_mut::<i64>().unwrap();
///
/// let orig = *x;
///
/// if double {
/// *x *= 2; // the first argument can be mutated
/// }
///
/// Ok(orig) // return Result<T, Box<EvalAltResult>>
/// });
///
/// assert!(module.contains_fn(hash));
/// ```
pub fn set_raw_fn<T: Variant + Clone>(
&mut self,
name: impl Into<String>,
args: &[TypeId],
func: impl Fn(&Engine, &mut [&mut Dynamic]) -> FuncReturn<T> + SendSync + 'static,
arg_types: &[TypeId],
func: impl Fn(&Engine, &Module, &mut [&mut Dynamic]) -> FuncReturn<T> + 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<String>,
func: impl Fn() -> FuncReturn<T> + 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<String>,
func: impl Fn(A) -> FuncReturn<T> + SendSync + 'static,
) -> u64 {
let f = move |_: &Engine, args: &mut FnCallArgs| {
let f = move |_: &Engine, _: &Module, args: &mut FnCallArgs| {
func(mem::take(args[0]).cast::<A>()).map(Dynamic::from)
};
let args = [TypeId::of::<A>()];
self.set_fn(
name,
Public,
&args,
CallableFunction::from_pure(Box::new(f)),
)
let arg_types = [TypeId::of::<A>()];
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<String>,
func: impl Fn(&mut A) -> FuncReturn<T> + SendSync + 'static,
) -> u64 {
let f = move |_: &Engine, args: &mut FnCallArgs| {
let f = move |_: &Engine, _: &Module, args: &mut FnCallArgs| {
func(args[0].downcast_mut::<A>().unwrap()).map(Dynamic::from)
};
let args = [TypeId::of::<A>()];
self.set_fn(
name,
Public,
&args,
CallableFunction::from_method(Box::new(f)),
)
let arg_types = [TypeId::of::<A>()];
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<String>,
func: impl Fn(A, B) -> FuncReturn<T> + 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::<A>();
let b = mem::take(args[1]).cast::<B>();
func(a, b).map(Dynamic::from)
};
let args = [TypeId::of::<A>(), TypeId::of::<B>()];
self.set_fn(
name,
Public,
&args,
CallableFunction::from_pure(Box::new(f)),
)
let arg_types = [TypeId::of::<A>(), TypeId::of::<B>()];
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<String>,
func: impl Fn(&mut A, B) -> FuncReturn<T> + 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::<B>();
let a = args[0].downcast_mut::<A>().unwrap();
func(a, b).map(Dynamic::from)
};
let args = [TypeId::of::<A>(), TypeId::of::<B>()];
self.set_fn(
name,
Public,
&args,
CallableFunction::from_method(Box::new(f)),
)
let arg_types = [TypeId::of::<A>(), TypeId::of::<B>()];
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<String>,
func: impl Fn(A, B, C) -> FuncReturn<T> + 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::<A>();
let b = mem::take(args[1]).cast::<B>();
let c = mem::take(args[2]).cast::<C>();
func(a, b, c).map(Dynamic::from)
};
let args = [TypeId::of::<A>(), TypeId::of::<B>(), TypeId::of::<C>()];
self.set_fn(
name,
Public,
&args,
CallableFunction::from_pure(Box::new(f)),
)
let arg_types = [TypeId::of::<A>(), TypeId::of::<B>(), TypeId::of::<C>()];
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<String>,
func: impl Fn(&mut A, B, C) -> FuncReturn<T> + 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::<B>();
let c = mem::take(args[2]).cast::<C>();
let a = args[0].downcast_mut::<A>().unwrap();
func(a, b, c).map(Dynamic::from)
};
let args = [TypeId::of::<A>(), TypeId::of::<B>(), TypeId::of::<C>()];
self.set_fn(
name,
Public,
&args,
CallableFunction::from_method(Box::new(f)),
)
let arg_types = [TypeId::of::<A>(), TypeId::of::<B>(), TypeId::of::<C>()];
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::<B>();
let c = mem::take(args[2]).cast::<A>();
let a = args[0].downcast_mut::<A>().unwrap();
func(a, b, c).map(Dynamic::from)
};
let args = [TypeId::of::<A>(), TypeId::of::<B>(), TypeId::of::<A>()];
let arg_types = [TypeId::of::<A>(), TypeId::of::<B>(), TypeId::of::<A>()];
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<String>,
func: impl Fn(A, B, C, D) -> FuncReturn<T> + 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::<A>();
let b = mem::take(args[1]).cast::<B>();
let c = mem::take(args[2]).cast::<C>();
@ -769,18 +789,13 @@ impl Module {
func(a, b, c, d).map(Dynamic::from)
};
let args = [
let arg_types = [
TypeId::of::<A>(),
TypeId::of::<B>(),
TypeId::of::<C>(),
TypeId::of::<D>(),
];
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<String>,
func: impl Fn(&mut A, B, C, D) -> FuncReturn<T> + 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::<B>();
let c = mem::take(args[2]).cast::<C>();
let d = mem::take(args[3]).cast::<D>();
@ -818,25 +833,20 @@ impl Module {
func(a, b, c, d).map(Dynamic::from)
};
let args = [
let arg_types = [
TypeId::of::<A>(),
TypeId::of::<B>(),
TypeId::of::<C>(),
TypeId::of::<C>(),
];
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<EvalAltResult>> {
) -> Result<&Func, Box<EvalAltResult>> {
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<Item = &(String, FnAccess, StaticVec<TypeId>, CallableFunction)> {
) -> impl Iterator<Item = &(String, FnAccess, StaticVec<TypeId>, 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<Item = Shared<ScriptFnDef>> + '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<String, Module>`.
/// It can simply be treated as `&HashMap<String, Module>`.
///
/// # 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<Box<dyn ModuleResolver>>);
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<Item = &dyn ModuleResolver> {
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<Module, Box<EvalAltResult>> {
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,
)))
}
}
}

View File

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

View File

@ -190,42 +190,38 @@ fn modulo_u<T: Rem>(x: T, y: T) -> FuncReturn<<T as Rem>::Output> {
// Checked power
pub(crate) fn pow_i_i(x: INT, y: INT) -> FuncReturn<INT> {
#[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)

View File

@ -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<T: Variant + Clone>(list: &mut Array, position: INT, item: T) -> FuncRetu
}
Ok(())
}
fn pad<T: Variant + Clone>(engine: &Engine, args: &mut [&mut Dynamic]) -> FuncReturn<()> {
fn pad<T: Variant + Clone>(
engine: &Engine,
_: &Module,
args: &mut [&mut Dynamic],
) -> FuncReturn<()> {
let len = *args[1].downcast_ref::<INT>().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::<Array>(), TypeId::of::<INT>(), TypeId::of::<$par>()],
$func::<$par>
);

View File

@ -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()));
});

View File

@ -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::<ImmutableString>(), TypeId::of::<INT>(), TypeId::of::<char>()],
|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 {

View File

@ -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)
}

File diff suppressed because it is too large Load Diff

View File

@ -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)

View File

@ -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::<i64>("x").unwrap(), 42);
/// ```
pub fn push<K: Into<Cow<'a, str>>, T: Variant + Clone>(&mut self, name: K, value: T) {
self.push_dynamic_value(name, EntryType::Normal, Dynamic::from(value), false);
pub fn push<K: Into<Cow<'a, str>>, 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::<i64>("x").unwrap(), 42);
/// ```
pub fn push_dynamic<K: Into<Cow<'a, str>>>(&mut self, name: K, value: Dynamic) {
self.push_dynamic_value(name, EntryType::Normal, value, false);
pub fn push_dynamic<K: Into<Cow<'a, str>>>(&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::<i64>("x").unwrap(), 42);
/// ```
pub fn push_constant<K: Into<Cow<'a, str>>, T: Variant + Clone>(&mut self, name: K, value: T) {
self.push_dynamic_value(name, EntryType::Constant, Dynamic::from(value), true);
pub fn push_constant<K: Into<Cow<'a, str>>, 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::<i64>("x").unwrap(), 42);
/// ```
pub fn push_constant_dynamic<K: Into<Cow<'a, str>>>(&mut self, name: K, value: Dynamic) {
self.push_dynamic_value(name, EntryType::Constant, value, true);
pub fn push_constant_dynamic<K: Into<Cow<'a, str>>>(
&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::<i64>("x").unwrap(), 0);
/// ```
pub fn set_value<T: Variant + Clone>(&mut self, name: &'a str, value: T) {
pub fn set_value<T: Variant + Clone>(&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.

574
src/serde/de.rs Normal file
View File

@ -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<T>(&self) -> Result<T, Box<EvalAltResult>> {
Err(Box::new(EvalAltResult::ErrorMismatchOutputType(
type_name::<T>().into(),
self.value.type_name().into(),
Position::none(),
)))
}
fn deserialize_int<V: Visitor<'de>>(
&mut self,
v: crate::INT,
visitor: V,
) -> Result<V::Value, Box<EvalAltResult>> {
#[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<rhai::EvalAltResult>> {
/// # #[cfg(not(feature = "no_index"))]
/// # #[cfg(not(feature = "no_object"))]
/// # {
/// use rhai::{Dynamic, Array, Map, INT};
/// use rhai::de::from_dynamic;
/// use serde::Deserialize;
///
/// #[derive(Debug, Deserialize, PartialEq)]
/// struct Hello {
/// a: INT,
/// b: bool,
/// }
///
/// #[derive(Debug, Deserialize, PartialEq)]
/// struct Test {
/// int: u32,
/// seq: Vec<String>,
/// obj: Hello,
/// }
///
/// let mut map = Map::new();
/// map.insert("int".into(), Dynamic::from(42_u32));
///
/// let mut map2 = Map::new();
/// map2.insert("a".into(), (123 as INT).into());
/// map2.insert("b".into(), true.into());
///
/// map.insert("obj".into(), map2.into());
///
/// let arr: Array = vec!["foo".into(), "bar".into(), "baz".into()];
/// map.insert("seq".into(), arr.into());
///
/// let value: Test = from_dynamic(&map.into())?;
///
/// let expected = Test {
/// int: 42,
/// seq: vec!["foo".into(), "bar".into(), "baz".into()],
/// obj: Hello { a: 123, b: true },
/// };
///
/// assert_eq!(value, expected);
/// # }
/// # Ok(())
/// # }
/// ```
pub fn from_dynamic<'de, T: Deserialize<'de>>(
value: &'de Dynamic,
) -> Result<T, Box<EvalAltResult>> {
T::deserialize(&mut DynamicDeserializer::from_dynamic(value))
}
impl Error for Box<EvalAltResult> {
fn custom<T: fmt::Display>(err: T) -> Self {
Box::new(EvalAltResult::ErrorParsing(
ParseErrorType::BadInput(err.to_string()),
Position::none(),
))
}
}
impl<'de> Deserializer<'de> for &mut DynamicDeserializer<'de> {
type Error = Box<EvalAltResult>;
fn deserialize_any<V: Visitor<'de>>(self, visitor: V) -> Result<V::Value, Box<EvalAltResult>> {
match &self.value.0 {
Union::Unit(_) => self.deserialize_unit(visitor),
Union::Bool(_) => self.deserialize_bool(visitor),
Union::Str(_) => self.deserialize_str(visitor),
Union::Char(_) => self.deserialize_char(visitor),
#[cfg(not(feature = "only_i32"))]
Union::Int(_) => self.deserialize_i64(visitor),
#[cfg(feature = "only_i32")]
Union::Int(_) => self.deserialize_i32(visitor),
#[cfg(not(feature = "no_float"))]
Union::Float(_) => self.deserialize_f64(visitor),
#[cfg(not(feature = "no_index"))]
Union::Array(_) => self.deserialize_seq(visitor),
#[cfg(not(feature = "no_object"))]
Union::Map(_) => self.deserialize_map(visitor),
Union::FnPtr(_) => self.type_error(),
#[cfg(not(feature = "no_std"))]
Union::Variant(value) if value.is::<Instant>() => self.type_error(),
Union::Variant(value) if value.is::<i8>() => self.deserialize_i8(visitor),
Union::Variant(value) if value.is::<i16>() => self.deserialize_i16(visitor),
Union::Variant(value) if value.is::<i32>() => self.deserialize_i32(visitor),
Union::Variant(value) if value.is::<i64>() => self.deserialize_i64(visitor),
Union::Variant(value) if value.is::<u8>() => self.deserialize_u8(visitor),
Union::Variant(value) if value.is::<u16>() => self.deserialize_u16(visitor),
Union::Variant(value) if value.is::<u32>() => self.deserialize_u32(visitor),
Union::Variant(value) if value.is::<u64>() => self.deserialize_u64(visitor),
Union::Variant(_) => self.type_error(),
}
}
fn deserialize_bool<V: Visitor<'de>>(self, visitor: V) -> Result<V::Value, Box<EvalAltResult>> {
visitor.visit_bool(self.value.as_bool().or_else(|_| self.type_error())?)
}
fn deserialize_i8<V: Visitor<'de>>(self, visitor: V) -> Result<V::Value, Box<EvalAltResult>> {
if let Ok(v) = self.value.as_int() {
self.deserialize_int(v, visitor)
} else {
self.value
.downcast_ref::<i8>()
.map_or_else(|| self.type_error(), |&x| visitor.visit_i8(x))
}
}
fn deserialize_i16<V: Visitor<'de>>(self, visitor: V) -> Result<V::Value, Box<EvalAltResult>> {
if let Ok(v) = self.value.as_int() {
self.deserialize_int(v, visitor)
} else {
self.value
.downcast_ref::<i16>()
.map_or_else(|| self.type_error(), |&x| visitor.visit_i16(x))
}
}
fn deserialize_i32<V: Visitor<'de>>(self, visitor: V) -> Result<V::Value, Box<EvalAltResult>> {
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::<i32>()
.map_or_else(|| self.type_error(), |&x| visitor.visit_i32(x))
}
}
fn deserialize_i64<V: Visitor<'de>>(self, visitor: V) -> Result<V::Value, Box<EvalAltResult>> {
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::<i64>()
.map_or_else(|| self.type_error(), |&x| visitor.visit_i64(x))
}
}
fn deserialize_u8<V: Visitor<'de>>(self, visitor: V) -> Result<V::Value, Box<EvalAltResult>> {
if let Ok(v) = self.value.as_int() {
self.deserialize_int(v, visitor)
} else {
self.value
.downcast_ref::<u8>()
.map_or_else(|| self.type_error(), |&x| visitor.visit_u8(x))
}
}
fn deserialize_u16<V: Visitor<'de>>(self, visitor: V) -> Result<V::Value, Box<EvalAltResult>> {
if let Ok(v) = self.value.as_int() {
self.deserialize_int(v, visitor)
} else {
self.value
.downcast_ref::<u16>()
.map_or_else(|| self.type_error(), |&x| visitor.visit_u16(x))
}
}
fn deserialize_u32<V: Visitor<'de>>(self, visitor: V) -> Result<V::Value, Box<EvalAltResult>> {
if let Ok(v) = self.value.as_int() {
self.deserialize_int(v, visitor)
} else {
self.value
.downcast_ref::<u32>()
.map_or_else(|| self.type_error(), |&x| visitor.visit_u32(x))
}
}
fn deserialize_u64<V: Visitor<'de>>(self, visitor: V) -> Result<V::Value, Box<EvalAltResult>> {
if let Ok(v) = self.value.as_int() {
self.deserialize_int(v, visitor)
} else {
self.value
.downcast_ref::<u64>()
.map_or_else(|| self.type_error(), |&x| visitor.visit_u64(x))
}
}
fn deserialize_f32<V: Visitor<'de>>(self, visitor: V) -> Result<V::Value, Box<EvalAltResult>> {
#[cfg(not(feature = "no_float"))]
return self
.value
.downcast_ref::<f32>()
.map_or_else(|| self.type_error(), |&x| visitor.visit_f32(x));
#[cfg(feature = "no_float")]
return self.type_error_str("f32");
}
fn deserialize_f64<V: Visitor<'de>>(self, visitor: V) -> Result<V::Value, Box<EvalAltResult>> {
#[cfg(not(feature = "no_float"))]
return self
.value
.downcast_ref::<f64>()
.map_or_else(|| self.type_error(), |&x| visitor.visit_f64(x));
#[cfg(feature = "no_float")]
return self.type_error_str("f64");
}
fn deserialize_char<V: Visitor<'de>>(self, visitor: V) -> Result<V::Value, Box<EvalAltResult>> {
self.value
.downcast_ref::<char>()
.map_or_else(|| self.type_error(), |&x| visitor.visit_char(x))
}
fn deserialize_str<V: Visitor<'de>>(self, visitor: V) -> Result<V::Value, Box<EvalAltResult>> {
self.value.downcast_ref::<ImmutableString>().map_or_else(
|| self.type_error(),
|x| visitor.visit_borrowed_str(x.as_str()),
)
}
fn deserialize_string<V: Visitor<'de>>(
self,
visitor: V,
) -> Result<V::Value, Box<EvalAltResult>> {
self.deserialize_str(visitor)
}
fn deserialize_bytes<V: Visitor<'de>>(self, _: V) -> Result<V::Value, Box<EvalAltResult>> {
self.type_error()
}
fn deserialize_byte_buf<V: Visitor<'de>>(self, _: V) -> Result<V::Value, Box<EvalAltResult>> {
self.type_error()
}
fn deserialize_option<V: Visitor<'de>>(self, _: V) -> Result<V::Value, Box<EvalAltResult>> {
self.type_error()
}
fn deserialize_unit<V: Visitor<'de>>(self, visitor: V) -> Result<V::Value, Box<EvalAltResult>> {
self.value
.downcast_ref::<()>()
.map_or_else(|| self.type_error(), |_| visitor.visit_unit())
}
fn deserialize_unit_struct<V: Visitor<'de>>(
self,
_name: &'static str,
visitor: V,
) -> Result<V::Value, Box<EvalAltResult>> {
self.deserialize_unit(visitor)
}
fn deserialize_newtype_struct<V: Visitor<'de>>(
self,
_name: &'static str,
visitor: V,
) -> Result<V::Value, Box<EvalAltResult>> {
visitor.visit_newtype_struct(self)
}
fn deserialize_seq<V: Visitor<'de>>(self, visitor: V) -> Result<V::Value, Box<EvalAltResult>> {
#[cfg(not(feature = "no_index"))]
return self.value.downcast_ref::<Array>().map_or_else(
|| self.type_error(),
|arr| visitor.visit_seq(IterateArray::new(arr.iter())),
);
#[cfg(feature = "no_index")]
return self.type_error();
}
fn deserialize_tuple<V: Visitor<'de>>(
self,
_len: usize,
visitor: V,
) -> Result<V::Value, Box<EvalAltResult>> {
self.deserialize_seq(visitor)
}
fn deserialize_tuple_struct<V: Visitor<'de>>(
self,
_name: &'static str,
_len: usize,
visitor: V,
) -> Result<V::Value, Box<EvalAltResult>> {
self.deserialize_seq(visitor)
}
fn deserialize_map<V: Visitor<'de>>(self, visitor: V) -> Result<V::Value, Box<EvalAltResult>> {
#[cfg(not(feature = "no_object"))]
return self.value.downcast_ref::<Map>().map_or_else(
|| self.type_error(),
|map| visitor.visit_map(IterateMap::new(map.keys(), map.values())),
);
#[cfg(feature = "no_object")]
return self.type_error();
}
fn deserialize_struct<V: Visitor<'de>>(
self,
_name: &'static str,
_fields: &'static [&'static str],
visitor: V,
) -> Result<V::Value, Box<EvalAltResult>> {
self.deserialize_map(visitor)
}
fn deserialize_enum<V: Visitor<'de>>(
self,
_name: &'static str,
_variants: &'static [&'static str],
visitor: V,
) -> Result<V::Value, Box<EvalAltResult>> {
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::<Map>() {
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<V: Visitor<'de>>(
self,
visitor: V,
) -> Result<V::Value, Box<EvalAltResult>> {
self.deserialize_str(visitor)
}
fn deserialize_ignored_any<V: Visitor<'de>>(
self,
visitor: V,
) -> Result<V::Value, Box<EvalAltResult>> {
self.deserialize_any(visitor)
}
}
/// `SeqAccess` implementation for arrays.
struct IterateArray<'a, ITER>
where
ITER: Iterator<Item = &'a Dynamic>,
{
/// Iterator for a stream of `Dynamic` values.
iter: ITER,
}
impl<'a, ITER> IterateArray<'a, ITER>
where
ITER: Iterator<Item = &'a Dynamic>,
{
pub fn new(iter: ITER) -> Self {
Self { iter }
}
}
impl<'a: 'de, 'de, ITER> SeqAccess<'de> for IterateArray<'a, ITER>
where
ITER: Iterator<Item = &'a Dynamic>,
{
type Error = Box<EvalAltResult>;
fn next_element_seed<T: DeserializeSeed<'de>>(
&mut self,
seed: T,
) -> Result<Option<T::Value>, Box<EvalAltResult>> {
// Deserialize each item coming out of the iterator.
match self.iter.next() {
None => Ok(None),
Some(item) => seed
.deserialize(&mut DynamicDeserializer::from_dynamic(item))
.map(Some),
}
}
}
/// `MapAccess` implementation for maps.
struct IterateMap<'a, KEYS, VALUES>
where
KEYS: Iterator<Item = &'a ImmutableString>,
VALUES: Iterator<Item = &'a Dynamic>,
{
// Iterator for a stream of `Dynamic` keys.
keys: KEYS,
// Iterator for a stream of `Dynamic` values.
values: VALUES,
}
impl<'a, KEYS, VALUES> IterateMap<'a, KEYS, VALUES>
where
KEYS: Iterator<Item = &'a ImmutableString>,
VALUES: Iterator<Item = &'a Dynamic>,
{
pub fn new(keys: KEYS, values: VALUES) -> Self {
Self { keys, values }
}
}
impl<'a: 'de, 'de, KEYS, VALUES> MapAccess<'de> for IterateMap<'a, KEYS, VALUES>
where
KEYS: Iterator<Item = &'a ImmutableString>,
VALUES: Iterator<Item = &'a Dynamic>,
{
type Error = Box<EvalAltResult>;
fn next_key_seed<K: DeserializeSeed<'de>>(
&mut self,
seed: K,
) -> Result<Option<K::Value>, Box<EvalAltResult>> {
// Deserialize each `ImmutableString` key coming out of the keys iterator.
match self.keys.next() {
None => Ok(None),
Some(item) => seed
.deserialize(&mut ImmutableStringDeserializer::from_str(item))
.map(Some),
}
}
fn next_value_seed<V: DeserializeSeed<'de>>(
&mut self,
seed: V,
) -> Result<V::Value, Box<EvalAltResult>> {
// Deserialize each value item coming out of the iterator.
seed.deserialize(&mut DynamicDeserializer::from_dynamic(
self.values.next().unwrap(),
))
}
}
#[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<EvalAltResult>;
type Variant = Self;
fn variant_seed<V>(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<EvalAltResult>;
fn unit_variant(mut self) -> Result<(), Self::Error> {
Deserialize::deserialize(&mut self.content)
}
fn newtype_variant_seed<T>(mut self, seed: T) -> Result<T::Value, Self::Error>
where
T: DeserializeSeed<'de>,
{
seed.deserialize(&mut self.content)
}
fn tuple_variant<V>(mut self, len: usize, visitor: V) -> Result<V::Value, Self::Error>
where
V: Visitor<'de>,
{
self.content.deserialize_tuple(len, visitor)
}
fn struct_variant<V>(
mut self,
fields: &'static [&'static str],
visitor: V,
) -> Result<V::Value, Self::Error>
where
V: Visitor<'de>,
{
self.content.deserialize_struct("", fields, visitor)
}
}

5
src/serde/mod.rs Normal file
View File

@ -0,0 +1,5 @@
//! Helper module defining serialization/deserialization support for [`serde`](https://crates.io/crates/serde).
pub mod de;
pub mod ser;
mod str;

594
src/serde/ser.rs Normal file
View File

@ -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<rhai::EvalAltResult>> {
/// # #[cfg(not(feature = "no_index"))]
/// # #[cfg(not(feature = "no_object"))]
/// # #[cfg(not(feature = "no_float"))]
/// # {
/// use rhai::{Dynamic, Array, Map, INT};
/// use rhai::ser::to_dynamic;
/// use serde::Serialize;
///
/// #[derive(Debug, serde::Serialize, PartialEq)]
/// struct Point {
/// x: f64,
/// y: f64
/// }
///
/// #[derive(Debug, serde::Serialize, PartialEq)]
/// struct MyStruct {
/// a: i64,
/// b: Vec<String>,
/// c: bool,
/// d: Point
/// }
///
/// let x = MyStruct {
/// a: 42,
/// b: vec![ "hello".into(), "world".into() ],
/// c: true,
/// d: Point { x: 123.456, y: 999.0 }
/// };
///
/// // Convert the 'MyStruct' into a 'Dynamic'
/// let value = to_dynamic(x)?;
///
/// assert!(value.is::<Map>());
///
/// let map = value.cast::<Map>();
/// let point = map.get("d").unwrap().downcast_ref::<Map>().unwrap();
/// assert_eq!(*point.get("x").unwrap().downcast_ref::<f64>().unwrap(), 123.456);
/// assert_eq!(*point.get("y").unwrap().downcast_ref::<f64>().unwrap(), 999.0);
/// # }
/// # Ok(())
/// # }
/// ```
pub fn to_dynamic<T: Serialize>(value: T) -> Result<Dynamic, Box<EvalAltResult>> {
let mut s = DynamicSerializer::new(Default::default());
value.serialize(&mut s)
}
impl Error for Box<EvalAltResult> {
fn custom<T: fmt::Display>(err: T) -> Self {
Box::new(EvalAltResult::ErrorRuntime(
err.to_string(),
Position::none(),
))
}
}
impl Serializer for &mut DynamicSerializer {
type Ok = Dynamic;
type Error = Box<EvalAltResult>;
type SerializeSeq = DynamicSerializer;
type SerializeTuple = DynamicSerializer;
type SerializeTupleStruct = DynamicSerializer;
#[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<Dynamic, Box<EvalAltResult>>;
type SerializeMap = DynamicSerializer;
type SerializeStruct = DynamicSerializer;
#[cfg(not(feature = "no_object"))]
type SerializeStructVariant = StructVariantSerializer;
#[cfg(feature = "no_object")]
type SerializeStructVariant = serde::ser::Impossible<Dynamic, Box<EvalAltResult>>;
fn serialize_bool(self, v: bool) -> Result<Self::Ok, Box<EvalAltResult>> {
Ok(v.into())
}
fn serialize_i8(self, v: i8) -> Result<Self::Ok, Box<EvalAltResult>> {
#[cfg(not(feature = "only_i32"))]
return self.serialize_i64(i64::from(v));
#[cfg(feature = "only_i32")]
return self.serialize_i32(i32::from(v));
}
fn serialize_i16(self, v: i16) -> Result<Self::Ok, Box<EvalAltResult>> {
#[cfg(not(feature = "only_i32"))]
return self.serialize_i64(i64::from(v));
#[cfg(feature = "only_i32")]
return self.serialize_i32(i32::from(v));
}
fn serialize_i32(self, v: i32) -> Result<Self::Ok, Box<EvalAltResult>> {
#[cfg(not(feature = "only_i32"))]
return self.serialize_i64(i64::from(v));
#[cfg(feature = "only_i32")]
return Ok(v.into());
}
fn serialize_i64(self, v: i64) -> Result<Self::Ok, Box<EvalAltResult>> {
#[cfg(not(feature = "only_i32"))]
return Ok(v.into());
#[cfg(feature = "only_i32")]
if v > i32::MAX as i64 {
return Ok(Dynamic::from(v));
} else {
return self.serialize_i32(v as i32);
}
}
fn serialize_u8(self, v: u8) -> Result<Self::Ok, Box<EvalAltResult>> {
#[cfg(not(feature = "only_i32"))]
return self.serialize_i64(i64::from(v));
#[cfg(feature = "only_i32")]
return self.serialize_i32(i32::from(v));
}
fn serialize_u16(self, v: u16) -> Result<Self::Ok, Box<EvalAltResult>> {
#[cfg(not(feature = "only_i32"))]
return self.serialize_i64(i64::from(v));
#[cfg(feature = "only_i32")]
return self.serialize_i32(i32::from(v));
}
fn serialize_u32(self, v: u32) -> Result<Self::Ok, Box<EvalAltResult>> {
#[cfg(not(feature = "only_i32"))]
return self.serialize_i64(i64::from(v));
#[cfg(feature = "only_i32")]
if v > i32::MAX as u32 {
return Ok(Dynamic::from(v));
} else {
return self.serialize_i32(v as i32);
}
}
fn serialize_u64(self, v: u64) -> Result<Self::Ok, Box<EvalAltResult>> {
#[cfg(not(feature = "only_i32"))]
if v > i64::MAX as u64 {
return Ok(Dynamic::from(v));
} else {
return self.serialize_i64(v as i64);
}
#[cfg(feature = "only_i32")]
if v > i32::MAX as u64 {
return Ok(Dynamic::from(v));
} else {
return self.serialize_i32(v as i32);
}
}
fn serialize_f32(self, v: f32) -> Result<Self::Ok, Box<EvalAltResult>> {
Ok(Dynamic::from(v))
}
fn serialize_f64(self, v: f64) -> Result<Self::Ok, Box<EvalAltResult>> {
Ok(Dynamic::from(v))
}
fn serialize_char(self, v: char) -> Result<Self::Ok, Box<EvalAltResult>> {
Ok(v.into())
}
fn serialize_str(self, v: &str) -> Result<Self::Ok, Box<EvalAltResult>> {
Ok(v.to_string().into())
}
fn serialize_bytes(self, v: &[u8]) -> Result<Self::Ok, Box<EvalAltResult>> {
Ok(Dynamic::from(v.to_vec()))
}
fn serialize_none(self) -> Result<Self::Ok, Box<EvalAltResult>> {
Ok(().into())
}
fn serialize_some<T: ?Sized + Serialize>(
self,
value: &T,
) -> Result<Self::Ok, Box<EvalAltResult>> {
value.serialize(&mut *self)
}
fn serialize_unit(self) -> Result<Self::Ok, Box<EvalAltResult>> {
Ok(().into())
}
fn serialize_unit_struct(self, _name: &'static str) -> Result<Self::Ok, Box<EvalAltResult>> {
self.serialize_unit()
}
fn serialize_unit_variant(
self,
_name: &'static str,
_variant_index: u32,
variant: &'static str,
) -> Result<Self::Ok, Box<EvalAltResult>> {
self.serialize_str(variant)
}
fn serialize_newtype_struct<T: ?Sized + Serialize>(
self,
_name: &'static str,
value: &T,
) -> Result<Self::Ok, Box<EvalAltResult>> {
value.serialize(&mut *self)
}
fn serialize_newtype_variant<T: ?Sized + Serialize>(
self,
_name: &'static str,
_variant_index: u32,
variant: &'static str,
value: &T,
) -> Result<Self::Ok, Box<EvalAltResult>> {
#[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<usize>) -> Result<Self::SerializeSeq, Box<EvalAltResult>> {
#[cfg(not(feature = "no_index"))]
return Ok(DynamicSerializer::new(Array::new().into()));
#[cfg(feature = "no_index")]
return Err(Box::new(EvalAltResult::ErrorMismatchOutputType(
"Dynamic".into(),
"array".into(),
Position::none(),
)));
}
fn serialize_tuple(self, len: usize) -> Result<Self::SerializeTuple, Box<EvalAltResult>> {
self.serialize_seq(Some(len))
}
fn serialize_tuple_struct(
self,
_name: &'static str,
len: usize,
) -> Result<Self::SerializeTupleStruct, Box<EvalAltResult>> {
self.serialize_seq(Some(len))
}
fn serialize_tuple_variant(
self,
_name: &'static str,
_variant_index: u32,
variant: &'static str,
len: usize,
) -> Result<Self::SerializeTupleVariant, Box<EvalAltResult>> {
#[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<usize>) -> Result<Self::SerializeMap, Box<EvalAltResult>> {
#[cfg(not(feature = "no_object"))]
return Ok(DynamicSerializer::new(Map::new().into()));
#[cfg(feature = "no_object")]
return Err(Box::new(EvalAltResult::ErrorMismatchOutputType(
"Dynamic".into(),
"map".into(),
Position::none(),
)));
}
fn serialize_struct(
self,
_name: &'static str,
len: usize,
) -> Result<Self::SerializeStruct, Box<EvalAltResult>> {
self.serialize_map(Some(len))
}
fn serialize_struct_variant(
self,
_name: &'static str,
_variant_index: u32,
variant: &'static str,
len: usize,
) -> Result<Self::SerializeStructVariant, Box<EvalAltResult>> {
#[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<EvalAltResult>;
fn serialize_element<T: ?Sized + Serialize>(
&mut self,
value: &T,
) -> Result<(), Box<EvalAltResult>> {
#[cfg(not(feature = "no_index"))]
{
let value = value.serialize(&mut *self)?;
let arr = self.value.downcast_mut::<Array>().unwrap();
arr.push(value);
Ok(())
}
#[cfg(feature = "no_index")]
unreachable!()
}
// Close the sequence.
fn end(self) -> Result<Self::Ok, Box<EvalAltResult>> {
#[cfg(not(feature = "no_index"))]
return Ok(self.value);
#[cfg(feature = "no_index")]
unreachable!()
}
}
impl SerializeTuple for DynamicSerializer {
type Ok = Dynamic;
type Error = Box<EvalAltResult>;
fn serialize_element<T: ?Sized + Serialize>(
&mut self,
value: &T,
) -> Result<(), Box<EvalAltResult>> {
#[cfg(not(feature = "no_index"))]
{
let value = value.serialize(&mut *self)?;
let arr = self.value.downcast_mut::<Array>().unwrap();
arr.push(value);
Ok(())
}
#[cfg(feature = "no_index")]
unreachable!()
}
fn end(self) -> Result<Self::Ok, Box<EvalAltResult>> {
#[cfg(not(feature = "no_index"))]
return Ok(self.value);
#[cfg(feature = "no_index")]
unreachable!()
}
}
impl SerializeTupleStruct for DynamicSerializer {
type Ok = Dynamic;
type Error = Box<EvalAltResult>;
fn serialize_field<T: ?Sized + Serialize>(
&mut self,
value: &T,
) -> Result<(), Box<EvalAltResult>> {
#[cfg(not(feature = "no_index"))]
{
let value = value.serialize(&mut *self)?;
let arr = self.value.downcast_mut::<Array>().unwrap();
arr.push(value);
Ok(())
}
#[cfg(feature = "no_index")]
unreachable!()
}
fn end(self) -> Result<Self::Ok, Box<EvalAltResult>> {
#[cfg(not(feature = "no_index"))]
return Ok(self.value);
#[cfg(feature = "no_index")]
unreachable!()
}
}
impl SerializeMap for DynamicSerializer {
type Ok = Dynamic;
type Error = Box<EvalAltResult>;
fn serialize_key<T: ?Sized + Serialize>(&mut self, key: &T) -> Result<(), Box<EvalAltResult>> {
#[cfg(not(feature = "no_object"))]
{
self.key = key.serialize(&mut *self)?;
Ok(())
}
#[cfg(feature = "no_object")]
unreachable!()
}
fn serialize_value<T: ?Sized + Serialize>(
&mut self,
value: &T,
) -> Result<(), Box<EvalAltResult>> {
#[cfg(not(feature = "no_object"))]
{
let key = mem::take(&mut self.key)
.take_immutable_string()
.map_err(|typ| {
Box::new(EvalAltResult::ErrorMismatchOutputType(
"string".into(),
typ.into(),
Position::none(),
))
})?;
let value = value.serialize(&mut *self)?;
let map = self.value.downcast_mut::<Map>().unwrap();
map.insert(key, value);
Ok(())
}
#[cfg(feature = "no_object")]
unreachable!()
}
fn serialize_entry<K: ?Sized + Serialize, T: ?Sized + Serialize>(
&mut self,
key: &K,
value: &T,
) -> Result<(), Box<EvalAltResult>> {
#[cfg(not(feature = "no_object"))]
{
let key: Dynamic = key.serialize(&mut *self)?;
let key = key.take_immutable_string().map_err(|typ| {
Box::new(EvalAltResult::ErrorMismatchOutputType(
"string".into(),
typ.into(),
Position::none(),
))
})?;
let value = value.serialize(&mut *self)?;
let map = self.value.downcast_mut::<Map>().unwrap();
map.insert(key, value);
Ok(())
}
#[cfg(feature = "no_object")]
unreachable!()
}
fn end(self) -> Result<Self::Ok, Box<EvalAltResult>> {
#[cfg(not(feature = "no_object"))]
return Ok(self.value);
#[cfg(feature = "no_object")]
unreachable!()
}
}
impl SerializeStruct for DynamicSerializer {
type Ok = Dynamic;
type Error = Box<EvalAltResult>;
fn serialize_field<T: ?Sized + Serialize>(
&mut self,
key: &'static str,
value: &T,
) -> Result<(), Box<EvalAltResult>> {
#[cfg(not(feature = "no_object"))]
{
let value = value.serialize(&mut *self)?;
let map = self.value.downcast_mut::<Map>().unwrap();
map.insert(key.into(), value);
Ok(())
}
#[cfg(feature = "no_object")]
unreachable!()
}
fn end(self) -> Result<Self::Ok, Box<EvalAltResult>> {
#[cfg(not(feature = "no_object"))]
return Ok(self.value);
#[cfg(feature = "no_object")]
unreachable!()
}
}
#[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<EvalAltResult>;
fn serialize_field<T: ?Sized + Serialize>(
&mut self,
value: &T,
) -> Result<(), Box<EvalAltResult>> {
let value = to_dynamic(value)?;
self.array.push(value);
Ok(())
}
fn end(self) -> Result<Self::Ok, Box<EvalAltResult>> {
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<EvalAltResult>;
fn serialize_field<T: ?Sized + Serialize>(
&mut self,
key: &'static str,
value: &T,
) -> Result<(), Box<EvalAltResult>> {
let value = to_dynamic(value)?;
self.map.insert(key.into(), value);
Ok(())
}
fn end(self) -> Result<Self::Ok, Box<EvalAltResult>> {
make_variant(self.variant, self.map.into())
}
}
#[cfg(not(feature = "no_object"))]
fn make_variant(variant: &'static str, value: Dynamic) -> Result<Dynamic, Box<EvalAltResult>> {
let mut map = Map::with_capacity(1);
map.insert(variant.into(), value);
Ok(map.into())
}

155
src/serde/str.rs Normal file
View File

@ -0,0 +1,155 @@
//! Implement deserialization support of `ImmutableString` for [`serde`](https://crates.io/crates/serde).
use crate::result::EvalAltResult;
use crate::token::Position;
use crate::utils::ImmutableString;
use serde::de::{Deserializer, Visitor};
use crate::stdlib::any::type_name;
/// Deserializer for `ImmutableString`.
pub struct ImmutableStringDeserializer<'a> {
value: &'a ImmutableString,
}
impl<'a> ImmutableStringDeserializer<'a> {
/// Create an `ImmutableStringDeserializer` from an `ImmutableString` reference.
pub fn from_str(value: &'a ImmutableString) -> Self {
Self { value }
}
/// Shortcut for a type conversion error.
fn type_error<T>(&self) -> Result<T, Box<EvalAltResult>> {
Err(Box::new(EvalAltResult::ErrorMismatchOutputType(
type_name::<T>().into(),
"string".into(),
Position::none(),
)))
}
}
impl<'de> Deserializer<'de> for &mut ImmutableStringDeserializer<'de> {
type Error = Box<EvalAltResult>;
fn deserialize_any<V: Visitor<'de>>(self, v: V) -> Result<V::Value, Box<EvalAltResult>> {
self.deserialize_str(v)
}
fn deserialize_bool<V: Visitor<'de>>(self, _: V) -> Result<V::Value, Box<EvalAltResult>> {
self.type_error()
}
fn deserialize_i8<V: Visitor<'de>>(self, _: V) -> Result<V::Value, Box<EvalAltResult>> {
self.type_error()
}
fn deserialize_i16<V: Visitor<'de>>(self, _: V) -> Result<V::Value, Box<EvalAltResult>> {
self.type_error()
}
fn deserialize_i32<V: Visitor<'de>>(self, _: V) -> Result<V::Value, Box<EvalAltResult>> {
self.type_error()
}
fn deserialize_i64<V: Visitor<'de>>(self, _: V) -> Result<V::Value, Box<EvalAltResult>> {
self.type_error()
}
fn deserialize_u8<V: Visitor<'de>>(self, _: V) -> Result<V::Value, Box<EvalAltResult>> {
self.type_error()
}
fn deserialize_u16<V: Visitor<'de>>(self, _: V) -> Result<V::Value, Box<EvalAltResult>> {
self.type_error()
}
fn deserialize_u32<V: Visitor<'de>>(self, _: V) -> Result<V::Value, Box<EvalAltResult>> {
self.type_error()
}
fn deserialize_u64<V: Visitor<'de>>(self, _: V) -> Result<V::Value, Box<EvalAltResult>> {
self.type_error()
}
fn deserialize_f32<V: Visitor<'de>>(self, _: V) -> Result<V::Value, Box<EvalAltResult>> {
self.type_error()
}
fn deserialize_f64<V: Visitor<'de>>(self, _: V) -> Result<V::Value, Box<EvalAltResult>> {
self.type_error()
}
fn deserialize_char<V: Visitor<'de>>(self, _: V) -> Result<V::Value, Box<EvalAltResult>> {
self.type_error()
}
fn deserialize_str<V: Visitor<'de>>(self, v: V) -> Result<V::Value, Box<EvalAltResult>> {
// Only allow deserialization into a string.
v.visit_borrowed_str(self.value.as_str())
}
fn deserialize_string<V: Visitor<'de>>(
self,
visitor: V,
) -> Result<V::Value, Box<EvalAltResult>> {
self.deserialize_str(visitor)
}
fn deserialize_bytes<V: Visitor<'de>>(self, _: V) -> Result<V::Value, Box<EvalAltResult>> {
self.type_error()
}
fn deserialize_byte_buf<V: Visitor<'de>>(self, _: V) -> Result<V::Value, Box<EvalAltResult>> {
self.type_error()
}
fn deserialize_option<V: Visitor<'de>>(self, _: V) -> Result<V::Value, Box<EvalAltResult>> {
self.type_error()
}
fn deserialize_unit<V: Visitor<'de>>(self, _: V) -> Result<V::Value, Box<EvalAltResult>> {
self.type_error()
}
fn deserialize_unit_struct<V: Visitor<'de>>(
self,
_name: &'static str,
v: V,
) -> Result<V::Value, Box<EvalAltResult>> {
self.deserialize_unit(v)
}
fn deserialize_newtype_struct<V: Visitor<'de>>(
self,
_name: &'static str,
v: V,
) -> Result<V::Value, Box<EvalAltResult>> {
v.visit_newtype_struct(self)
}
fn deserialize_seq<V: Visitor<'de>>(self, _: V) -> Result<V::Value, Box<EvalAltResult>> {
self.type_error()
}
fn deserialize_tuple<V: Visitor<'de>>(
self,
_len: usize,
v: V,
) -> Result<V::Value, Box<EvalAltResult>> {
self.deserialize_seq(v)
}
fn deserialize_tuple_struct<V: Visitor<'de>>(
self,
_name: &'static str,
_len: usize,
v: V,
) -> Result<V::Value, Box<EvalAltResult>> {
self.deserialize_seq(v)
}
fn deserialize_map<V: Visitor<'de>>(self, _: V) -> Result<V::Value, Box<EvalAltResult>> {
self.type_error()
}
fn deserialize_struct<V: Visitor<'de>>(
self,
_name: &'static str,
_fields: &'static [&'static str],
v: V,
) -> Result<V::Value, Box<EvalAltResult>> {
self.deserialize_map(v)
}
fn deserialize_enum<V: Visitor<'de>>(
self,
_name: &'static str,
_variants: &'static [&'static str],
_: V,
) -> Result<V::Value, Box<EvalAltResult>> {
self.type_error()
}
fn deserialize_identifier<V: Visitor<'de>>(self, v: V) -> Result<V::Value, Box<EvalAltResult>> {
self.deserialize_str(v)
}
fn deserialize_ignored_any<V: Visitor<'de>>(
self,
v: V,
) -> Result<V::Value, Box<EvalAltResult>> {
self.deserialize_any(v)
}
}

264
src/settings.rs Normal file
View File

@ -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<impl ModuleResolver + 'static>,
) -> &mut Self {
self.module_resolver = resolver.map(|f| Box::new(f) as Box<dyn ModuleResolver>);
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<rhai::EvalAltResult>> {
/// 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::<i64>("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)
}
}

150
src/syntax.rs Normal file
View File

@ -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<Dynamic, Box<EvalAltResult>>;
/// 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<Dynamic, Box<EvalAltResult>>
+ Send
+ Sync;
#[derive(Clone)]
pub struct CustomSyntax {
pub segments: StaticVec<String>,
pub func: Shared<FnCustomSyntaxEval>,
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<S: AsRef<str> + 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<Dynamic, Box<EvalAltResult>>
+ SendSync
+ 'static,
) -> Result<self, Box<LexError>> {
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)
}
}

View File

@ -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<TokenIterator<'a>>;
pub type TokenStream<'a, 't> = Peekable<TokenIterator<'a, 't>>;
/// 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<LexError>),
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<Self> {
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<String, u8>>) -> 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<Token> for String {
@ -431,7 +573,7 @@ impl From<Token> 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<char> {
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<Peekable<Chars<'a>>>,
/// The current stream index.
index: usize,
}
impl InputStream for MultiInputsStream<'_> {
/// Get the next character
fn get_next(&mut self) -> Option<char> {
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<char> {
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<Self::Item> {
get_next_token(&mut self.stream, &mut self.state, &mut self.pos)
match (
get_next_token(&mut self.stream, &mut self.state, &mut self.pos),
self.engine.disabled_symbols.as_ref(),
self.engine.custom_keywords.as_ref(),
) {
(None, _, _) => None,
(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,
},
}
}

View File

@ -356,10 +356,13 @@ impl<T> StaticVec<T> {
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<T> StaticVec<T> {
}
value
};
self.len -= 1;
result
}
}
/// Remove a value from this `StaticVec` at a particular position.
///
@ -386,18 +385,20 @@ impl<T> StaticVec<T> {
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<T> StaticVec<T> {
}
value
};
self.len -= 1;
result
}
}
/// Get the number of items in this `StaticVec`.
#[inline(always)]

View File

@ -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<EvalAltResult>> {
@ -29,7 +32,7 @@ fn test_call_fn() -> Result<(), Box<EvalAltResult>> {
x + y
}
fn hello(x) {
x = x * foo;
x *= foo;
foo = 1;
x
}
@ -110,3 +113,47 @@ fn test_anonymous_fn() -> Result<(), Box<EvalAltResult>> {
Ok(())
}
#[test]
fn test_fn_ptr() -> Result<(), Box<EvalAltResult>> {
let mut engine = Engine::new();
engine.register_raw_fn(
"bar",
&[
std::any::TypeId::of::<INT>(),
std::any::TypeId::of::<FnPtr>(),
std::any::TypeId::of::<INT>(),
],
move |engine: &Engine, lib: &Module, args: &mut [&mut Dynamic]| {
let fp = std::mem::take(args[1]).cast::<FnPtr>();
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::<INT>(
r#"
fn foo(x) { this += x; }
let x = 41;
x.bar(Fn("foo"), 1);
x
"#
)?,
42
);
Ok(())
}

View File

@ -35,6 +35,7 @@ fn test_max_string_size() -> Result<(), Box<EvalAltResult>> {
EvalAltResult::ErrorDataTooLarge(_, 10, 13, _)
));
#[cfg(not(feature = "no_object"))]
assert!(matches!(
*engine
.eval::<String>(
@ -92,6 +93,8 @@ fn test_max_array_size() -> Result<(), Box<EvalAltResult>> {
.expect_err("should error"),
EvalAltResult::ErrorDataTooLarge(_, 10, 12, _)
));
#[cfg(not(feature = "no_object"))]
assert!(matches!(
*engine
.eval::<Array>(

View File

@ -39,7 +39,7 @@ fn test_for_string() -> Result<(), Box<EvalAltResult>> {
let sum = 0;
for ch in s {
sum += ch.to_int();
sum += to_int(ch);
}
sum

View File

@ -52,6 +52,7 @@ fn test_functions() -> Result<(), Box<EvalAltResult>> {
}
#[test]
#[cfg(not(feature = "no_object"))]
fn test_function_pointers() -> Result<(), Box<EvalAltResult>> {
let engine = Engine::new();

View File

@ -14,7 +14,7 @@ fn test_loop() -> Result<(), Box<EvalAltResult>> {
if i < 10 {
i += 1;
if x > 20 { continue; }
x = x + i;
x += i;
} else {
break;
}

View File

@ -21,7 +21,7 @@ fn test_map_indexing() -> Result<(), Box<EvalAltResult>> {
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<EvalAltResult>> {
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<EvalAltResult>> {
let y = #{b: 42, d: 9};
x.mixin(y);
x.len() + x.b
"
"
)?,
46
);
@ -68,7 +68,7 @@ fn test_map_indexing() -> Result<(), Box<EvalAltResult>> {
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<EvalAltResult>> {
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<EvalAltResult>> {
let x = engine.eval::<Map>(r#"let x = #{a: 1, b: true, "c$": "hello"}; x"#)?;
assert_eq!(
x.get("a")
.cloned()
.expect("should have property a")
.cast::<INT>(),
1
);
assert_eq!(
x.get("b")
.cloned()
.expect("should have property b")
.cast::<bool>(),
true
);
assert_eq!(
x.get("c$")
.cloned()
.expect("should have property c$")
.cast::<String>(),
"hello"
);
assert_eq!(x["a"].clone().cast::<INT>(), 1);
assert_eq!(x["b"].clone().cast::<bool>(), true);
assert_eq!(x["c$"].clone().cast::<String>(), "hello");
Ok(())
}
@ -125,27 +107,9 @@ fn test_map_return() -> Result<(), Box<EvalAltResult>> {
let x = engine.eval::<Map>(r#"#{a: 1, b: true, "c$": "hello"}"#)?;
assert_eq!(
x.get("a")
.cloned()
.expect("should have property a")
.cast::<INT>(),
1
);
assert_eq!(
x.get("b")
.cloned()
.expect("should have property b")
.cast::<bool>(),
true
);
assert_eq!(
x.get("c$")
.cloned()
.expect("should have property c$")
.cast::<String>(),
"hello"
);
assert_eq!(x["a"].clone().cast::<INT>(), 1);
assert_eq!(x["b"].clone().cast::<bool>(), true);
assert_eq!(x["c$"].clone().cast::<String>(), "hello");
Ok(())
}
@ -167,7 +131,7 @@ fn test_map_for() -> Result<(), Box<EvalAltResult>> {
}
s
"#
"#
)?
.len(),
11
@ -188,41 +152,11 @@ fn test_map_json() -> Result<(), Box<EvalAltResult>> {
assert!(!map.contains_key("x"));
assert_eq!(
map.get("a")
.cloned()
.expect("should have property a")
.cast::<INT>(),
1
);
assert_eq!(
map.get("b")
.cloned()
.expect("should have property b")
.cast::<bool>(),
true
);
assert_eq!(
map.get("c")
.cloned()
.expect("should have property a")
.cast::<INT>(),
42
);
assert_eq!(
map.get("$d e f!")
.cloned()
.expect("should have property $d e f!")
.cast::<String>(),
"hello"
);
assert_eq!(
map.get("z")
.cloned()
.expect("should have property z")
.cast::<()>(),
()
);
assert_eq!(map["a"].clone().cast::<INT>(), 1);
assert_eq!(map["b"].clone().cast::<bool>(), true);
assert_eq!(map["c"].clone().cast::<INT>(), 42);
assert_eq!(map["$d e f!"].clone().cast::<String>(), "hello");
assert_eq!(map["z"].clone().cast::<()>(), ());
#[cfg(not(feature = "no_index"))]
{
@ -241,7 +175,7 @@ fn test_map_json() -> Result<(), Box<EvalAltResult>> {
}
s
"#
"#
)?
.len(),
11
@ -265,7 +199,7 @@ fn test_map_oop() -> Result<(), Box<EvalAltResult>> {
obj.action(2);
obj.data
"#,
"#,
)?,
42
);

View File

@ -21,10 +21,10 @@ fn test_method_call() -> Result<(), Box<EvalAltResult>> {
let mut engine = Engine::new();
engine.register_type::<TestStruct>();
engine.register_fn("update", TestStruct::update);
engine.register_fn("new_ts", TestStruct::new);
engine
.register_type::<TestStruct>()
.register_fn("update", TestStruct::update)
.register_fn("new_ts", TestStruct::new);
assert_eq!(
engine.eval::<TestStruct>("let x = new_ts(); x.update(1000); x")?,

View File

@ -6,14 +6,14 @@ fn test_mismatched_op() {
assert!(matches!(
*engine.eval::<INT>(r#""hello, " + "world!""#).expect_err("expects error"),
EvalAltResult::ErrorMismatchOutputType(err, _) if err == "string"
EvalAltResult::ErrorMismatchOutputType(need, actual, _) if need == std::any::type_name::<INT>() && actual == "string"
));
}
#[test]
#[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>("TestStruct");
engine.register_fn("new_ts", TestStruct::new);
let r = engine
.eval::<INT>("60 + new_ts()")
.expect_err("expects error");
engine
.register_type_with_name::<TestStruct>("TestStruct")
.register_fn("new_ts", TestStruct::new);
#[cfg(feature = "only_i32")]
assert!(matches!(
*r,
EvalAltResult::ErrorFunctionNotFound(err, _) if err == "+ (i32, TestStruct)"
*engine.eval::<INT>("60 + new_ts()").expect_err("should error"),
EvalAltResult::ErrorFunctionNotFound(err, _) if err == format!("+ ({}, TestStruct)", std::any::type_name::<INT>())
));
#[cfg(not(feature = "only_i32"))]
assert!(matches!(
*r,
EvalAltResult::ErrorFunctionNotFound(err, _) if err == "+ (i64, TestStruct)"
*engine.eval::<TestStruct>("42").expect_err("should error"),
EvalAltResult::ErrorMismatchOutputType(need, actual, _)
if need == "TestStruct" && actual == std::any::type_name::<INT>()
));
}

View File

@ -69,10 +69,15 @@ fn test_module_resolver() -> Result<(), Box<EvalAltResult>> {
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<EvalAltResult>> {
42
);
assert_eq!(
engine.eval::<INT>(
r#"
import "hello" as h;
let x = 21;
h::double(x);
x
"#
)?,
42
);
#[cfg(not(feature = "unchecked"))]
{
engine.set_max_modules(5);

View File

@ -3,16 +3,17 @@ use std::sync::{Arc, RwLock};
#[test]
fn test_print() -> Result<(), Box<EvalAltResult>> {
let mut engine = Engine::new();
let logbook = Arc::new(RwLock::new(Vec::<String>::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)")?;

684
tests/serde.rs Normal file
View File

@ -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<EvalAltResult>> {
assert!(to_dynamic(42_u64)?.is::<INT>());
assert!(to_dynamic(u64::MAX)?.is::<u64>());
assert!(to_dynamic(42 as INT)?.is::<INT>());
assert!(to_dynamic(true)?.is::<bool>());
assert!(to_dynamic(())?.is::<()>());
#[cfg(not(feature = "no_float"))]
{
assert!(to_dynamic(123.456_f64)?.is::<f64>());
assert!(to_dynamic(123.456_f32)?.is::<f32>());
}
assert!(to_dynamic("hello".to_string())?.is::<String>());
Ok(())
}
#[test]
fn test_serde_ser_integer_types() -> Result<(), Box<EvalAltResult>> {
assert!(to_dynamic(42_i8)?.is::<INT>());
assert!(to_dynamic(42_i16)?.is::<INT>());
assert!(to_dynamic(42_i32)?.is::<INT>());
assert!(to_dynamic(42_i64)?.is::<INT>());
assert!(to_dynamic(42_u8)?.is::<INT>());
assert!(to_dynamic(42_u16)?.is::<INT>());
assert!(to_dynamic(42_u32)?.is::<INT>());
assert!(to_dynamic(42_u64)?.is::<INT>());
Ok(())
}
#[test]
#[cfg(not(feature = "no_index"))]
fn test_serde_ser_array() -> Result<(), Box<EvalAltResult>> {
let arr: Vec<INT> = vec![123, 456, 42, 999];
let d = to_dynamic(arr)?;
assert!(d.is::<Array>());
assert_eq!(4, d.cast::<Array>().len());
Ok(())
}
#[test]
#[cfg(not(feature = "no_index"))]
#[cfg(not(feature = "no_object"))]
fn test_serde_ser_struct() -> Result<(), Box<EvalAltResult>> {
#[derive(Debug, Serialize, PartialEq)]
struct Hello {
a: INT,
b: bool,
}
#[derive(Debug, Serialize, PartialEq)]
struct Test {
int: u32,
seq: Vec<String>,
obj: Hello,
}
let x = Test {
int: 42,
seq: vec!["hello".into(), "kitty".into(), "world".into()],
obj: Hello { a: 123, b: true },
};
let d = to_dynamic(x)?;
assert!(d.is::<Map>());
let mut map = d.cast::<Map>();
let obj = map.remove("obj").unwrap().cast::<Map>();
let seq = map.remove("seq").unwrap().cast::<Array>();
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<EvalAltResult>> {
#[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<EvalAltResult>> {
#[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::<Map>();
let content = map.remove("VariantUnitTuple").unwrap().cast::<Array>();
assert!(map.is_empty());
assert!(content.is_empty());
}
{
let mut map = to_dynamic(MyEnum::VariantNewtype(123))?.cast::<Map>();
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::<Map>();
let content = map.remove("VariantTuple").unwrap().cast::<Array>();
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::<Map>();
let map_inner = map.remove("VariantEmptyStruct").unwrap().cast::<Map>();
assert!(map.is_empty());
assert!(map_inner.is_empty());
}
{
let mut map = to_dynamic(MyEnum::VariantStruct { a: 123 })?.cast::<Map>();
let mut map_inner = map.remove("VariantStruct").unwrap().cast::<Map>();
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<EvalAltResult>> {
#[derive(Serialize)]
#[serde(tag = "tag")]
enum MyEnum {
VariantEmptyStruct {},
VariantStruct { a: i32 },
}
let mut map = to_dynamic(MyEnum::VariantEmptyStruct {})?.cast::<Map>();
assert_eq!(
Ok("VariantEmptyStruct"),
map.remove("tag").unwrap().as_str()
);
assert!(map.is_empty());
let mut map = to_dynamic(MyEnum::VariantStruct { a: 123 })?.cast::<Map>();
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<EvalAltResult>> {
#[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::<Map>();
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::<Map>();
assert_eq!(Ok("VariantUnitTuple"), map.remove("tag").unwrap().as_str());
let content = map.remove("content").unwrap().cast::<Array>();
assert!(map.is_empty());
assert!(content.is_empty());
}
{
let mut map = to_dynamic(MyEnum::VariantNewtype(123))?.cast::<Map>();
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::<Map>();
assert_eq!(Ok("VariantTuple"), map.remove("tag").unwrap().as_str());
let content = map.remove("content").unwrap().cast::<Array>();
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::<Map>();
assert_eq!(
Ok("VariantEmptyStruct"),
map.remove("tag").unwrap().as_str()
);
let map_inner = map.remove("content").unwrap().cast::<Map>();
assert!(map.is_empty());
assert!(map_inner.is_empty());
}
{
let mut map = to_dynamic(MyEnum::VariantStruct { a: 123 })?.cast::<Map>();
assert_eq!(Ok("VariantStruct"), map.remove("tag").unwrap().as_str());
let mut map_inner = map.remove("content").unwrap().cast::<Map>();
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<EvalAltResult>> {
#[derive(Serialize)]
#[serde(untagged)]
enum MyEnum {
VariantEmptyStruct {},
VariantStruct1 { a: i32 },
VariantStruct2 { b: i32 },
}
{
let map = to_dynamic(MyEnum::VariantEmptyStruct {})?.cast::<Map>();
assert!(map.is_empty());
}
{
let mut map = to_dynamic(MyEnum::VariantStruct1 { a: 123 })?.cast::<Map>();
assert_eq!(Ok(123), map.remove("a").unwrap().as_int());
assert!(map.is_empty());
}
{
let mut map = to_dynamic(MyEnum::VariantStruct2 { b: 123 })?.cast::<Map>();
assert_eq!(Ok(123), map.remove("b").unwrap().as_int());
assert!(map.is_empty());
}
Ok(())
}
#[test]
fn test_serde_de_primary_types() -> Result<(), Box<EvalAltResult>> {
assert_eq!(42_u16, from_dynamic(&Dynamic::from(42_u16))?);
assert_eq!(42 as INT, from_dynamic(&(42 as INT).into())?);
assert_eq!(true, from_dynamic(&true.into())?);
assert_eq!((), from_dynamic(&().into())?);
#[cfg(not(feature = "no_float"))]
{
assert_eq!(123.456_f64, from_dynamic(&123.456_f64.into())?);
assert_eq!(123.456_f32, from_dynamic(&Dynamic::from(123.456_f32))?);
}
assert_eq!(
"hello",
from_dynamic::<String>(&"hello".to_string().into())?
);
Ok(())
}
#[test]
fn test_serde_de_integer_types() -> Result<(), Box<EvalAltResult>> {
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<EvalAltResult>> {
let arr: Vec<INT> = vec![123, 456, 42, 999];
assert_eq!(arr, from_dynamic::<Vec<INT>>(&arr.clone().into())?);
Ok(())
}
#[test]
#[cfg(not(feature = "no_index"))]
#[cfg(not(feature = "no_object"))]
fn test_serde_de_struct() -> Result<(), Box<EvalAltResult>> {
#[derive(Debug, Deserialize, PartialEq)]
struct Hello {
a: INT,
b: bool,
}
#[derive(Debug, Deserialize, PartialEq)]
struct Test {
int: u32,
seq: Vec<String>,
obj: Hello,
}
let mut map = Map::new();
map.insert("int".into(), Dynamic::from(42_u32));
let mut map2 = Map::new();
map2.insert("a".into(), (123 as INT).into());
map2.insert("b".into(), true.into());
map.insert("obj".into(), map2.into());
let arr: Array = vec!["hello".into(), "kitty".into(), "world".into()];
map.insert("seq".into(), arr.into());
let expected = Test {
int: 42,
seq: vec!["hello".into(), "kitty".into(), "world".into()],
obj: Hello { a: 123, b: true },
};
assert_eq!(expected, from_dynamic(&map.into())?);
Ok(())
}
#[test]
#[cfg(not(feature = "no_index"))]
#[cfg(not(feature = "no_object"))]
#[cfg(not(feature = "no_float"))]
fn test_serde_de_script() -> Result<(), Box<EvalAltResult>> {
#[derive(Debug, Deserialize)]
struct Point {
x: f64,
y: f64,
}
#[derive(Debug, Deserialize)]
struct MyStruct {
a: i64,
b: Vec<String>,
c: bool,
d: Point,
}
let engine = Engine::new();
let result: Dynamic = engine.eval(
r#"
#{
a: 42,
b: [ "hello", "world" ],
c: true,
d: #{ x: 123.456, y: 999.0 }
}
"#,
)?;
// Convert the 'Dynamic' object map into 'MyStruct'
let _: MyStruct = from_dynamic(&result)?;
Ok(())
}
#[test]
fn test_serde_de_unit_enum() -> Result<(), Box<EvalAltResult>> {
#[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<EvalAltResult>> {
#[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<EvalAltResult>> {
#[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<EvalAltResult>> {
#[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<EvalAltResult>> {
#[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(())
}

77
tests/syntax.rs Normal file
View File

@ -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<EvalAltResult>> {
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::<INT>(
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(())
}

49
tests/tokens.rs Normal file
View File

@ -0,0 +1,49 @@
use rhai::{Engine, EvalAltResult, ParseErrorType, RegisterFn, INT};
#[test]
fn test_tokens_disabled() {
let mut engine = Engine::new();
engine.disable_symbol("if"); // disable the 'if' keyword
assert!(matches!(
*engine.compile("let x = if true { 42 } else { 0 };").expect_err("should error").0,
ParseErrorType::MissingToken(ref token, _) if token == ";"
));
engine.disable_symbol("+="); // disable the '+=' operator
assert!(matches!(
*engine.compile("let x = 40 + 2; x += 1;").expect_err("should error").0,
ParseErrorType::BadInput(ref s) if s == "Unexpected '+='"
));
}
#[test]
fn test_tokens_custom_operator() -> Result<(), Box<EvalAltResult>> {
let mut engine = Engine::new();
// Register a custom operator called `foo` and give it
// a precedence of 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::<INT>("1 + 2 * 3 foo 4 - 5 / 6")?,
15
);
assert_eq!(
engine.eval::<INT>(
r"
fn foo(x, y) { y - x }
1 + 2 * 3 foo 4 - 5 / 6
"
)?,
-1
);
Ok(())
}

View File

@ -7,7 +7,7 @@ fn test_var_scope() -> Result<(), Box<EvalAltResult>> {
engine.eval_with_scope::<()>(&mut scope, "let x = 4 + 5")?;
assert_eq!(engine.eval_with_scope::<INT>(&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::<INT>(&mut scope, "x")?, 12);
scope.set_value("x", 42 as INT);

View File

@ -10,10 +10,10 @@ fn test_while() -> Result<(), Box<EvalAltResult>> {
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