Merge pull request #263 from schungx/master

Refactor.
This commit is contained in:
Stephen Chung 2020-10-19 21:16:09 +08:00 committed by GitHub
commit 1f9fb34e75
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
31 changed files with 390 additions and 322 deletions

View File

@ -1,16 +1,19 @@
Rhai - Embedded Scripting for Rust Rhai - Embedded Scripting for Rust
================================= =================================
![GitHub last commit](https://img.shields.io/github/last-commit/jonathandturner/rhai) ![GitHub last commit](https://img.shields.io/github/last-commit/jonathandturner/rhai?logo=github)
[![Build Status](https://github.com/jonathandturner/rhai/workflows/Build/badge.svg)](https://github.com/jonathandturner/rhai/actions) [![Build Status](https://github.com/jonathandturner/rhai/workflows/Build/badge.svg)](https://github.com/jonathandturner/rhai/actions)
[![license](https://img.shields.io/github/license/jonathandturner/rhai)](https://github.com/license/jonathandturner/rhai) [![license](https://img.shields.io/crates/l/rhai)](https://github.com/license/jonathandturner/rhai)
[![crates.io](https://img.shields.io/crates/v/rhai.svg)](https://crates.io/crates/rhai/) [![crates.io](https://img.shields.io/crates/v/rhai?logo=rust)](https://crates.io/crates/rhai/)
[![crates.io](https://img.shields.io/crates/d/rhai)](https://crates.io/crates/rhai/) [![crates.io](https://img.shields.io/crates/d/rhai?logo=rust)](https://crates.io/crates/rhai/)
[![API Docs](https://docs.rs/rhai/badge.svg)](https://docs.rs/rhai/) [![API Docs](https://docs.rs/rhai/badge.svg?logo=docs.rs)](https://docs.rs/rhai/)
[![chat](https://img.shields.io/discord/767611025456889857.svg?logo=discord)](https://discord.gg/yZMKAQ)
[![Reddit](https://img.shields.io/reddit/subreddit-subscribers/Rhai?logo=reddit)](https://www.reddit.com/r/Rhai)
Rhai is an embedded scripting language and evaluation engine for Rust that gives a safe and easy way Rhai is an embedded scripting language and evaluation engine for Rust that gives a safe and easy way
to add scripting to any application. to add scripting to any application.
Supported targets and builds Supported targets and builds
--------------------------- ---------------------------
@ -19,6 +22,7 @@ Supported targets and builds
* `no-std` * `no-std`
* Minimum Rust version 1.45 * Minimum Rust version 1.45
Standard features Standard features
----------------- -----------------
@ -41,13 +45,15 @@ Standard features
* Serialization/deserialization support via [serde](https://crates.io/crates/serde) (requires the `serde` feature). * Serialization/deserialization support via [serde](https://crates.io/crates/serde) (requires the `serde` feature).
* 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). * 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).
Protection against attacks
-------------------------- Protected against attacks
-------------------------
* Sand-boxed - the scripting engine, if declared immutable, cannot mutate the containing environment unless [explicitly permitted](https://schungx.github.io/rhai/patterns/control.html). * Sand-boxed - the scripting engine, if declared immutable, cannot mutate the containing environment unless [explicitly permitted](https://schungx.github.io/rhai/patterns/control.html).
* 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. * 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. * Track script evaluation [progress](https://schungx.github.io/rhai/safety/progress.html) and manually terminate a script run.
For those who actually want their own language For those who actually want their own language
--------------------------------------------- ---------------------------------------------
@ -56,6 +62,7 @@ For those who actually want their own language
* Define [custom operators](https://schungx.github.io/rhai/engine/custom-op.html). * Define [custom operators](https://schungx.github.io/rhai/engine/custom-op.html).
* Extend the language with [custom syntax](https://schungx.github.io/rhai/engine/custom-syntax.html). * Extend the language with [custom syntax](https://schungx.github.io/rhai/engine/custom-syntax.html).
Documentation Documentation
------------- -------------
@ -65,12 +72,14 @@ To build _The Book_, first install [`mdbook`](https://github.com/rust-lang/mdBoo
and [`mdbook-tera`](https://github.com/avitex/mdbook-tera) (for templating). and [`mdbook-tera`](https://github.com/avitex/mdbook-tera) (for templating).
Running `mdbook build` builds it. Running `mdbook build` builds it.
Playground Playground
---------- ----------
An [Online Playground](https://alvinhochun.github.io/rhai-demo/) is available with syntax-highlighting editor. An [Online Playground](https://alvinhochun.github.io/rhai-demo/) is available with syntax-highlighting editor.
Scripts can be evaluated directly from the editor. Scripts can be evaluated directly from the editor.
License License
------- -------

View File

@ -11,6 +11,7 @@ Breaking changes
* `EvalAltResult::ErrorReadingScriptFile` is removed in favor of the new `EvalAltResult::ErrorSystem`. * `EvalAltResult::ErrorReadingScriptFile` is removed in favor of the new `EvalAltResult::ErrorSystem`.
* `EvalAltResult::ErrorLoopBreak` is renamed to `EvalAltResult::LoopBreak`. * `EvalAltResult::ErrorLoopBreak` is renamed to `EvalAltResult::LoopBreak`.
* `Engine::register_raw_fn` and `FnPtr::call_dynamic` function signatures have changed. * `Engine::register_raw_fn` and `FnPtr::call_dynamic` function signatures have changed.
* Callback signatures to `Engine::on_var` and `Engine::register_custom_syntax` have changed.
New features New features
------------ ------------

View File

@ -36,8 +36,8 @@ The Rhai Scripting Language
4. [Register a Fallible Rust Function](rust/fallible.md) 4. [Register a Fallible Rust Function](rust/fallible.md)
6. [Override a Built-in Function](rust/override.md) 6. [Override a Built-in Function](rust/override.md)
7. [Operator Overloading](rust/operators.md) 7. [Operator Overloading](rust/operators.md)
8. [Register a Custom Type and its Methods](rust/custom.md) 8. [Register any Rust Type and its Methods](rust/custom.md)
1. [Getters and Setters](rust/getters-setters.md) 1. [Property Getters and Setters](rust/getters-setters.md)
2. [Indexers](rust/indexers.md) 2. [Indexers](rust/indexers.md)
3. [Disable Custom Types](rust/disable-custom.md) 3. [Disable Custom Types](rust/disable-custom.md)
4. [Printing Custom Types](rust/print-custom.md) 4. [Printing Custom Types](rust/print-custom.md)

View File

@ -3,7 +3,9 @@ Related Resources
{{#include ../links.md}} {{#include ../links.md}}
Other online documentation resources for Rhai:
Other Online Resources for Rhai
------------------------------
* [`crates.io`](https://crates.io/crates/rhai) - Rhai crate * [`crates.io`](https://crates.io/crates/rhai) - Rhai crate
@ -13,7 +15,13 @@ Other online documentation resources for Rhai:
* [Online Playground][playground] - Run scripts directly from editor * [Online Playground][playground] - Run scripts directly from editor
Other cool projects to check out: * [Discord Chat](https://discord.gg/yZMKAQ) - Rhai channel
* [Reddit](https://www.reddit.com/r/Rhai) - Rhai community
Other Cool Projects
-------------------
* [ChaiScript](http://chaiscript.com) - A strong inspiration for Rhai. An embedded scripting language for C++. * [ChaiScript](http://chaiscript.com) - A strong inspiration for Rhai. An embedded scripting language for C++.

View File

@ -114,25 +114,19 @@ Any custom syntax must include an _implementation_ of it.
The function signature of an implementation is: The function signature of an implementation is:
> `Fn(scope: &mut Scope, context: &mut EvalContext, inputs: &[Expression])` > `Fn(context: &mut EvalContext, inputs: &[Expression]) -> Result<Dynamic, Box<EvalAltResult>>`
> `-> Result<Dynamic, Box<EvalAltResult>>`
where: where:
* `scope: &mut Scope` - mutable reference to the current [`Scope`]; variables can be added to it. * `context: &mut EvalContext` - mutable reference to the current evaluation _context_, exposing the following:
* `context.scope: &mut Scope` - mutable reference to the current [`Scope`]; variables can be added to/removed from it.
* `context: &mut EvalContext` - mutable reference to the current evaluation _context_ (**do not touch**) which exposes the following fields:
* `context.engine(): &Engine` - reference to the current [`Engine`]. * `context.engine(): &Engine` - reference to the current [`Engine`].
* `context.namespace(): &Module` - reference to the current _global namespace_ (as a [module]) containing all script-defined functions. * `context.namespace(): &Module` - reference to the current _global namespace_ (as a [module]) containing all script-defined functions.
* `context.this_ptr(): Option<&Dynamic>` - reference to the current bound [`this`] pointer, if any.
* `context.call_level(): usize` - the current nesting level of function calls. * `context.call_level(): usize` - the current nesting level of function calls.
* `inputs: &[Expression]` - a list of input expression trees. * `inputs: &[Expression]` - a list of input expression trees.
#### WARNING - Lark's Vomit
The `context` parameter contains the evaluation _context_ and should not be touched or Bad Things Happen™.
It should simply be passed straight-through the the [`Engine`].
### Access Arguments ### Access Arguments
The most important argument is `inputs` where the matched identifiers (`$ident$`), expressions/statements (`$expr$`) The most important argument is `inputs` where the matched identifiers (`$ident$`), expressions/statements (`$expr$`)
@ -152,8 +146,8 @@ Use the `EvalContext::eval_expression_tree` method to evaluate an arbitrary expr
within the current evaluation context. within the current evaluation context.
```rust ```rust
let expr = inputs.get(0).unwrap(); let expression = inputs.get(0).unwrap();
let result = context.eval_expression_tree(scope, expr)?; let result = context.eval_expression_tree(expression)?;
``` ```
### Declare Variables ### Declare Variables
@ -163,15 +157,16 @@ New variables maybe declared (usually with a variable name that is passed in via
It can simply be pushed into the [`Scope`]. It can simply be pushed into the [`Scope`].
However, beware that all new variables must be declared _prior_ to evaluating any expression tree. 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 `EvalContext::eval_expression_tree` calls. In other words, any [`Scope`] calls that change the list of must come _before_ any
`EvalContext::eval_expression_tree` calls.
```rust ```rust
let var_name = inputs[0].get_variable_name().unwrap().to_string(); let var_name = inputs[0].get_variable_name().unwrap();
let expr = inputs.get(1).unwrap(); let expression = inputs.get(1).unwrap();
scope.push(var_name, 0 as INT); // do this BEFORE 'context.eval_expression_tree'! context.scope.push(var_name, 0 as INT); // do this BEFORE 'context.eval_expression_tree'!
let result = context.eval_expression_tree(context, scope, expr)?; let result = context.eval_expression_tree(expression)?;
``` ```
@ -188,7 +183,6 @@ The syntax is passed simply as a slice of `&str`.
```rust ```rust
// Custom syntax implementation // Custom syntax implementation
fn implementation_func( fn implementation_func(
scope: &mut Scope,
context: &mut EvalContext, context: &mut EvalContext,
inputs: &[Expression] inputs: &[Expression]
) -> Result<Dynamic, Box<EvalAltResult>> { ) -> Result<Dynamic, Box<EvalAltResult>> {
@ -196,15 +190,15 @@ fn implementation_func(
let stmt = inputs.get(1).unwrap(); let stmt = inputs.get(1).unwrap();
let condition = inputs.get(2).unwrap(); let condition = inputs.get(2).unwrap();
// Push one new variable into the 'scope' BEFORE 'context.eval_expression_tree' // Push one new variable into the scope BEFORE 'context.eval_expression_tree'
scope.push(var_name, 0 as INT); context.scope.push(var_name, 0 as INT);
loop { loop {
// Evaluate the statement block // Evaluate the statement block
context.eval_expression_tree(scope, stmt)?; context.eval_expression_tree(stmt)?;
// Evaluate the condition expression // Evaluate the condition expression
let stop = !context.eval_expression_tree(scope, condition)? let stop = !context.eval_expression_tree(condition)?
.as_bool().map_err(|err| Box::new( .as_bool().map_err(|err| Box::new(
EvalAltResult::ErrorMismatchDataType( EvalAltResult::ErrorMismatchDataType(
"bool".to_string(), "bool".to_string(),

View File

@ -62,7 +62,7 @@ Function Signature
The function signature passed to `Engine::on_var` takes the following form: The function signature passed to `Engine::on_var` takes the following form:
> `Fn(name: &str, index: usize, scope: &Scope, context: &EvalContext)` > `Fn(name: &str, index: usize, context: &EvalContext)`
> `-> Result<Option<Dynamic>, Box<EvalAltResult>> + 'static` > `-> Result<Option<Dynamic>, Box<EvalAltResult>> + 'static`
where: where:
@ -74,11 +74,11 @@ where:
If `index` is zero, then there is no pre-calculated offset position and a search through the current [`Scope`] must be performed. If `index` is zero, then there is no pre-calculated offset position and a search through the current [`Scope`] must be performed.
* `scope: &Scope` - reference to the current [`Scope`] containing all variables up to the current evaluation position.
* `context: &EvalContext` - reference to the current evaluation _context_, which exposes the following fields: * `context: &EvalContext` - reference to the current evaluation _context_, which exposes the following fields:
* `context.scope: &Scope` - reference to the current [`Scope`] containing all variables up to the current evaluation position.
* `context.engine(): &Engine` - reference to the current [`Engine`]. * `context.engine(): &Engine` - reference to the current [`Engine`].
* `context.namespace(): &Module` - reference to the current _global namespace_ (as a [module]) containing all script-defined functions. * `context.namespace(): &Module` - reference to the current _global namespace_ (as a [module]) containing all script-defined functions.
* `context.this_ptr(): Option<&Dynamic>` - reference to the current bound [`this`] pointer, if any.
* `context.call_level(): usize` - the current nesting level of function calls. * `context.call_level(): usize` - the current nesting level of function calls.
### Return Value ### Return Value

BIN
doc/src/images/rhai.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.0 KiB

View File

@ -8,7 +8,7 @@ Prelude
------- -------
When using the plugins system, the entire `rhai::plugin` module must be imported as a prelude When using the plugins system, the entire `rhai::plugin` module must be imported as a prelude
because code generated will these imports. because code generated will need these imports.
```rust ```rust
use rhai::plugin::*; use rhai::plugin::*;

View File

@ -1,12 +1,47 @@
Register a Custom Type and its Methods Register any Rust Type and its Methods
===================================== =====================================
{{#include ../links.md}} {{#include ../links.md}}
Rhai works seamlessly with _any_ complex Rust type. The type can be registered with the `Engine`, as below.
Free Typing
-----------
Rhai works seamlessly with _any_ Rust type. The type can be _anything_; it does not
have any prerequisites other than being `Clone`. It does not need to implement
any other trait or use any custom `#[derive]`.
This allows Rhai to be integrated into an existing code base with as little plumbing
as possible, usually silently and seamlessly. External types that are not defined
within the same crate (and thus cannot implement special Rhai traits or
use special `#[derive]`) can also be used easily with Rhai.
The reason why it is termed a _custom_ type throughout this documentation is that
Rhai natively supports a number of data types with fast, internal treatment (see
the list of [standard types]). Any type outside of this list is considered _custom_.
Any type not supported natively by Rhai is stored as a Rust _trait object_, with no
restrictions other than being `Clone` (plus `Send + Sync` under the [`sync`] feature).
It runs slightly slower than natively-supported types as it does not have built-in,
optimized implementations for commonly-used functions, but for all other purposes has
no difference.
Support for custom types can be turned off via the [`no_object`] feature. Support for custom types can be turned off via the [`no_object`] feature.
Register a Custom Type and its Methods
-------------------------------------
Any custom type must implement the `Clone` trait as this allows the [`Engine`] to pass by value.
If the [`sync`] feature is used, it must also be `Send + Sync`.
Notice that the custom type needs to be _registered_ using `Engine::register_type`
or `Engine::register_type_with_name`.
To use native methods on custom types in Rhai scripts, it is common to register an API
for the type using one of the `Engine::register_XXX` functions.
```rust ```rust
use rhai::{Engine, EvalAltResult}; use rhai::{Engine, EvalAltResult};
use rhai::RegisterFn; // remember 'RegisterFn' is needed use rhai::RegisterFn; // remember 'RegisterFn' is needed
@ -17,108 +52,67 @@ struct TestStruct {
} }
impl TestStruct { impl TestStruct {
fn update(&mut self) { fn new() -> Self {
self.field += 41; Self { field: 1 }
} }
fn new() -> Self { fn update(&mut self, x: i64) { // methods take &mut as first parameter
TestStruct { field: 1 } self.field += x;
} }
} }
let mut engine = Engine::new(); let mut engine = Engine::new();
// Most Engine API's can be chained up.
engine engine
.register_type::<TestStruct>() // most API's can be chained up .register_type::<TestStruct>() // register custom type
.register_fn("update", TestStruct::update) .register_fn("new_ts", TestStruct::new)
.register_fn("new_ts", TestStruct::new); .register_fn("update", TestStruct::update);
let result = engine.eval::<TestStruct>("let x = new_ts(); x.update(); x")?; // Cast result back to custom type.
let result = engine.eval::<TestStruct>(
r"
let x = new_ts(); // calls 'TestStruct::new'
x.update(41); // calls 'TestStruct::update'
x // 'x' holds a 'TestStruct'
"
)?;
println!("result: {}", result.field); // prints 42 println!("result: {}", result.field); // prints 42
``` ```
Rhai follows the convention that methods of custom types take a `&mut` first parameter
to that type, so that invoking methods can always update it.
Register a Custom Type All other parameters in Rhai are passed by value (i.e. clones).
---------------------
A custom type must implement `Clone` as this allows the [`Engine`] to pass by value.
Notice that the custom type needs to be _registered_ using `Engine::register_type`
or `Engine::register_type_with_name`.
```rust
#[derive(Clone)]
struct TestStruct {
field: i64
}
impl TestStruct {
fn update(&mut self) { // methods take &mut as first parameter
self.field += 41;
}
fn new() -> Self {
TestStruct { field: 1 }
}
}
let mut engine = Engine::new();
engine.register_type::<TestStruct>();
```
Methods on the Custom Type
-------------------------
To use native custom types, methods and functions in Rhai scripts, simply register them
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)'
.register_fn("new_ts", TestStruct::new); // registers 'new()'
```
***Note**: Rhai follows the convention that methods of custom types take a `&mut` first parameter
so that invoking methods can update the types. All other parameters in Rhai are passed by value (i.e. clones).*
**IMPORTANT: Rhai does NOT support normal references (i.e. `&T`) as parameters.** **IMPORTANT: Rhai does NOT support normal references (i.e. `&T`) as parameters.**
Use the Custom Type in Scripts
-----------------------------
The custom type is then ready for use in scripts. Scripts can see the functions and methods registered earlier.
Get the evaluation result back out just as before, this time casting to the custom type:
```rust
let result = engine.eval::<TestStruct>("let x = new_ts(); x.update(); x")?;
println!("result: {}", result.field); // prints 42
```
Method-Call Style vs. Function-Call Style Method-Call Style vs. Function-Call Style
---------------------------------------- ----------------------------------------
Any function with a first argument that is a `&mut` reference can be used Any function with a first argument that is a `&mut` reference can be used
as method calls because internally they are the same thing: methods on a type is as method calls because internally they are the same thing: methods on a type is
implemented as a functions taking a `&mut` first argument. implemented as a functions taking a `&mut` first argument.
This design is similar to Rust. This design is similar to Rust.
```rust ```rust
fn foo(ts: &mut TestStruct) -> i64 { impl TestStruct {
ts.field fn foo(&mut self) -> i64 {
self.field
}
} }
engine.register_fn("foo", foo); // register a Rust native function engine.register_fn("foo", TestStruct::foo);
let result = engine.eval::<i64>( let result = engine.eval::<i64>(
"let x = new_ts(); x.foo()" // 'foo' can be called like a method on 'x' r"
let x = new_ts();
foo(x); // normal call to 'foo'
x.foo() // 'foo' can also be called like a method on 'x'
"
)?; )?;
println!("result: {}", result); // prints 1 println!("result: {}", result); // prints 1
@ -128,8 +122,9 @@ Under [`no_object`], however, the _method_ style of function calls
(i.e. calling a function as an object-method) is no longer supported. (i.e. calling a function as an object-method) is no longer supported.
```rust ```rust
// Below is a syntax error under 'no_object' because 'clear' cannot be called in method style. // Below is a syntax error under 'no_object'.
let result = engine.eval::<()>("let x = [1, 2, 3]; x.clear()")?; let result = engine.eval("let x = [1, 2, 3]; x.clear();")?;
// ^ cannot call in method style under 'no_object'
``` ```
@ -143,18 +138,16 @@ with a special "pretty-print" name, [`type_of()`] will return that name instead.
```rust ```rust
engine engine
.register_type::<TestStruct>() .register_type::<TestStruct1>()
.register_fn("new_ts", TestStruct::new); .register_fn("new_ts1", TestStruct1::new)
.register_type_with_name::<TestStruct2>("MyType")
.register_fn("new_ts2", TestStruct2::new);
let x = new_ts(); let ts1_type = engine.eval::<String>(r#"let x = new_ts1(); x.type_of()"#)?;
x.type_of() == "path::to::module::TestStruct"; let ts2_type = engine.eval::<String>(r#"let x = new_ts2(); x.type_of()"#)?;
engine println!("{}", ts1_type); // prints 'path::to::TestStruct'
.register_type_with_name::<TestStruct>("Hello") println!("{}", ts1_type); // prints 'MyType'
.register_fn("new_ts", TestStruct::new);
let x = new_ts();
x.type_of() == "Hello";
``` ```
@ -190,7 +183,9 @@ the `==` operator must be registered for the custom type:
```rust ```rust
// Assume 'TestStruct' implements `PartialEq` // Assume 'TestStruct' implements `PartialEq`
engine.register_fn("==", |item1: &mut TestStruct, item2: TestStruct| item1 == item2); engine.register_fn("==",
|item1: &mut TestStruct, item2: TestStruct| item1 == &item2
);
// Then this works in Rhai: // Then this works in Rhai:
let item = new_ts(); // construct a new 'TestStruct' let item = new_ts(); // construct a new 'TestStruct'

View File

@ -1,29 +1,32 @@
Custom Type Getters and Setters Custom Type Property Getters and Setters
============================== =======================================
{{#include ../links.md}} {{#include ../links.md}}
A custom type can also expose members by registering `get` and/or `set` functions. A [custom type] can also expose properties by registering `get` and/or `set` functions.
Getters and setters each take a `&mut` reference to the first parameter. Getters and setters each take a `&mut` reference to the first parameter.
Getters and setters are disabled when the [`no_object`] feature is used. Getters and setters are disabled when the [`no_object`] feature is used.
| `Engine` API | Description | Return Value of Function | | `Engine` API | Function signature(s)<br/>(`T: Clone` = custom type,<br/>`V: Clone` = data type) | Can mutate `T`? |
| --------------------- | ------------------------------------------------- | :-----------------------------------: | | --------------------- | -------------------------------------------------------------------------------- | :----------------------------: |
| `register_get` | register a getter | _any_ `T: Clone` | | `register_get` | `Fn(&mut T) -> V` | yes, but not advised |
| `register_set` | register a setter | _none_ | | `register_set` | `Fn(&mut T, V)` | yes |
| `register_get_set` | short-hand to register both a getter and a setter | _none_ | | `register_get_set` | getter: `Fn(&mut T) -> V`</br>setter: `Fn(&mut T, V)` | yes, but not advised in getter |
| `register_get_result` | register a getter | `Result<Dynamic, Box<EvalAltResult>>` | | `register_get_result` | `Fn(&mut T) -> Result<Dynamic, Box<EvalAltResult>>` | yes, but not advised |
| `register_set_result` | register a setter | `Result<(), Box<EvalAltResult>>` | | `register_set_result` | `Fn(&mut T, V) -> Result<(), Box<EvalAltResult>>` | yes |
By convention, property getters are not supposed to mutate the [custom type], although there is nothing
that prevents this mutation.
Cannot Override Object Maps Cannot Override Object Maps
-------------------------- --------------------------
Getters and setters are only intended for [custom types]. Property getters and setters are mainly intended for [custom types].
Any getter or setter function registered for [object maps] is simply ignored because Any getter or setter function registered for [object maps] is simply _ignored_ because
the get/set calls will be interpreted as properties on the [object maps]. the get/set calls will be interpreted as properties on the [object maps].
@ -47,7 +50,7 @@ impl TestStruct {
} }
fn new() -> Self { fn new() -> Self {
TestStruct { field: "hello" } Self { field: "hello" }
} }
} }

View File

@ -3,23 +3,30 @@ Custom Type Indexers
{{#include ../links.md}} {{#include ../links.md}}
A custom type can also expose an _indexer_ by registering an indexer function. A [custom type] can also expose an _indexer_ by registering an indexer function.
A custom type with an indexer function defined can use the bracket notation to get a property value: A [custom type] with an indexer function defined can use the bracket notation to get a property value:
> _object_ `[` _index_ `]` > _object_ `[` _index_ `]`
Like getters and setters, indexers take a `&mut` reference to the first parameter. Like property [getters/setters], indexers take a `&mut` reference to the first parameter.
They also take an additional parameter of any type that serves as the _index_ within brackets.
Indexers are disabled when the [`no_index`] feature is used. Indexers are disabled when the [`no_index`] feature is used.
| `Engine` API | Description | Return Value of Function | | `Engine` API | Function signature(s)<br/>(`T: Clone` = custom type,<br/>`X: Clone` = index type,<br/>`V: Clone` = data type) | Can mutate `T`? |
| ----------------------------- | -------------------------------------------------------- | :-----------------------------------: | | ----------------------------- | ------------------------------------------------------------------------------------------------------------- | :----------------------------: |
| `register_indexer_get` | register an index getter | _any_ `T: Clone` | | `register_indexer_get` | `Fn(&mut T, X) -> V` | yes, but not advised |
| `register_indexer_set` | register an index setter | _none_ | | `register_indexer_set` | `Fn(&mut T, X, V)` | yes |
| `register_indexer_get_set` | short-hand to register both an index getter and a setter | _none_ | | `register_indexer_get_set` | getter: `Fn(&mut T, X) -> V`<br/>setter: `Fn(&mut T, X, V)` | yes, but not advised in getter |
| `register_indexer_get_result` | register an index getter | `Result<Dynamic, Box<EvalAltResult>>` | | `register_indexer_get_result` | `Fn(&mut T, X) -> Result<Dynamic, Box<EvalAltResult>>` | yes, but not advised |
| `register_indexer_set_result` | register an index setter | `Result<(), Box<EvalAltResult>>` | | `register_indexer_set_result` | `Fn(&mut T, X, V) -> Result<(), Box<EvalAltResult>>` | yes |
By convention, index getters are not supposed to mutate the [custom type], although there is nothing
that prevents this mutation.
**IMPORTANT: Rhai does NOT support normal references (i.e. `&T`) as parameters.**
Cannot Override Arrays, Object Maps and Strings Cannot Override Arrays, Object Maps and Strings
@ -42,15 +49,15 @@ struct TestStruct {
impl TestStruct { impl TestStruct {
// Remember &mut must be used even for getters // Remember &mut must be used even for getters
fn get_field(&mut self, index: i64) -> i64 { fn get_field(&mut self, index: String) -> i64 {
self.fields[index as usize] self.fields[index.len()]
} }
fn set_field(&mut self, index: i64, value: i64) { fn set_field(&mut self, index: String, value: i64) {
self.fields[index as usize] = value self.fields[index.len()] = value
} }
fn new() -> Self { fn new() -> Self {
TestStruct { fields: vec![1, 2, 3, 4, 5] } Self { fields: vec![1, 2, 3, 4, 5] }
} }
} }
@ -63,9 +70,13 @@ engine
.register_indexer_get(TestStruct::get_field) .register_indexer_get(TestStruct::get_field)
.register_indexer_set(TestStruct::set_field); .register_indexer_set(TestStruct::set_field);
let result = engine.eval::<i64>("let a = new_ts(); a[2] = 42; a[2]")?; let result = engine.eval::<i64>(
r#"
let a = new_ts();
a["xyz"] = 42; // these indexers use strings
a["xyz"] // as the index type
"#
)?;
println!("Answer: {}", result); // prints 42 println!("Answer: {}", result); // prints 42
``` ```
**IMPORTANT: Rhai does NOT support normal references (i.e. `&T`) as parameters.**

View File

@ -65,7 +65,7 @@ The function signature passed to `Engine::register_raw_fn` takes the following f
where: where:
* `T: Variant + Clone` - return type of the function. * `T: Clone` - return type of the function.
* `context: NativeCallContext` - the current _native call context_, which exposes the following: * `context: NativeCallContext` - the current _native call context_, which exposes the following:

View File

@ -11,7 +11,7 @@ impl TestStruct {
} }
fn new() -> Self { fn new() -> Self {
TestStruct { x: 1 } Self { x: 1 }
} }
} }

View File

@ -11,7 +11,7 @@ impl TestStruct {
} }
fn new() -> Self { fn new() -> Self {
TestStruct { x: 1 } Self { x: 1 }
} }
} }

View File

@ -3,12 +3,12 @@
use crate::any::{Dynamic, Variant}; use crate::any::{Dynamic, Variant};
use crate::engine::{Engine, EvalContext, Imports, State}; use crate::engine::{Engine, EvalContext, Imports, State};
use crate::error::ParseError; use crate::error::ParseError;
use crate::fn_native::{NativeCallContext, SendSync}; use crate::fn_native::{FnCallArgs, NativeCallContext, SendSync};
use crate::optimize::OptimizationLevel; use crate::optimize::OptimizationLevel;
use crate::parser::AST; use crate::parser::AST;
use crate::result::EvalAltResult; use crate::result::EvalAltResult;
use crate::scope::Scope; use crate::scope::Scope;
use crate::token::{lex, Position}; use crate::token::Position;
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
use crate::{ use crate::{
@ -68,7 +68,7 @@ impl Engine {
&mut self, &mut self,
name: &str, name: &str,
arg_types: &[TypeId], arg_types: &[TypeId],
func: impl Fn(NativeCallContext, &mut [&mut Dynamic]) -> Result<T, Box<EvalAltResult>> func: impl Fn(NativeCallContext, &mut FnCallArgs) -> Result<T, Box<EvalAltResult>>
+ SendSync + SendSync
+ 'static, + 'static,
) -> &mut Self { ) -> &mut Self {
@ -88,7 +88,7 @@ impl Engine {
/// } /// }
/// ///
/// impl TestStruct { /// impl TestStruct {
/// fn new() -> Self { TestStruct { field: 1 } } /// fn new() -> Self { Self { field: 1 } }
/// fn update(&mut self, offset: i64) { self.field += offset; } /// fn update(&mut self, offset: i64) { self.field += offset; }
/// } /// }
/// ///
@ -130,7 +130,7 @@ impl Engine {
/// } /// }
/// ///
/// impl TestStruct { /// impl TestStruct {
/// fn new() -> Self { TestStruct { field: 1 } } /// fn new() -> Self { Self { field: 1 } }
/// } /// }
/// ///
/// # fn main() -> Result<(), Box<rhai::EvalAltResult>> { /// # fn main() -> Result<(), Box<rhai::EvalAltResult>> {
@ -200,7 +200,7 @@ impl Engine {
/// } /// }
/// ///
/// impl TestStruct { /// impl TestStruct {
/// fn new() -> Self { TestStruct { field: 1 } } /// fn new() -> Self { Self { field: 1 } }
/// // Even a getter must start with `&mut self` and not `&self`. /// // Even a getter must start with `&mut self` and not `&self`.
/// fn get_field(&mut self) -> i64 { self.field } /// fn get_field(&mut self) -> i64 { self.field }
/// } /// }
@ -252,7 +252,7 @@ impl Engine {
/// } /// }
/// ///
/// impl TestStruct { /// impl TestStruct {
/// fn new() -> Self { TestStruct { field: 1 } } /// fn new() -> Self { Self { field: 1 } }
/// // Even a getter must start with `&mut self` and not `&self`. /// // Even a getter must start with `&mut self` and not `&self`.
/// fn get_field(&mut self) -> Result<Dynamic, Box<EvalAltResult>> { /// fn get_field(&mut self) -> Result<Dynamic, Box<EvalAltResult>> {
/// Ok(self.field.into()) /// Ok(self.field.into())
@ -295,7 +295,7 @@ impl Engine {
/// } /// }
/// ///
/// impl TestStruct { /// impl TestStruct {
/// fn new() -> Self { TestStruct { field: 1 } } /// fn new() -> Self { Self { field: 1 } }
/// fn set_field(&mut self, new_val: i64) { self.field = new_val; } /// fn set_field(&mut self, new_val: i64) { self.field = new_val; }
/// } /// }
/// ///
@ -348,7 +348,7 @@ impl Engine {
/// } /// }
/// ///
/// impl TestStruct { /// impl TestStruct {
/// fn new() -> Self { TestStruct { field: 1 } } /// fn new() -> Self { Self { field: 1 } }
/// fn set_field(&mut self, new_val: i64) -> Result<(), Box<EvalAltResult>> { /// fn set_field(&mut self, new_val: i64) -> Result<(), Box<EvalAltResult>> {
/// self.field = new_val; /// self.field = new_val;
/// Ok(()) /// Ok(())
@ -386,8 +386,7 @@ impl Engine {
U: Variant + Clone, U: Variant + Clone,
{ {
self.register_result_fn(&make_setter(name), move |obj: &mut T, value: U| { self.register_result_fn(&make_setter(name), move |obj: &mut T, value: U| {
callback(obj, value)?; callback(obj, value).map(Into::into)
Ok(().into())
}) })
} }
@ -405,7 +404,7 @@ impl Engine {
/// } /// }
/// ///
/// impl TestStruct { /// impl TestStruct {
/// fn new() -> Self { TestStruct { field: 1 } } /// fn new() -> Self { Self { field: 1 } }
/// // Even a getter must start with `&mut self` and not `&self`. /// // Even a getter must start with `&mut self` and not `&self`.
/// fn get_field(&mut self) -> i64 { self.field } /// fn get_field(&mut self) -> i64 { self.field }
/// fn set_field(&mut self, new_val: i64) { self.field = new_val; } /// fn set_field(&mut self, new_val: i64) { self.field = new_val; }
@ -462,7 +461,7 @@ impl Engine {
/// } /// }
/// ///
/// impl TestStruct { /// impl TestStruct {
/// fn new() -> Self { TestStruct { fields: vec![1, 2, 3, 4, 5] } } /// fn new() -> Self { Self { fields: vec![1, 2, 3, 4, 5] } }
/// // Even a getter must start with `&mut self` and not `&self`. /// // Even a getter must start with `&mut self` and not `&self`.
/// fn get_field(&mut self, index: i64) -> i64 { self.fields[index as usize] } /// fn get_field(&mut self, index: i64) -> i64 { self.fields[index as usize] }
/// } /// }
@ -534,7 +533,7 @@ impl Engine {
/// } /// }
/// ///
/// impl TestStruct { /// impl TestStruct {
/// fn new() -> Self { TestStruct { fields: vec![1, 2, 3, 4, 5] } } /// fn new() -> Self { Self { fields: vec![1, 2, 3, 4, 5] } }
/// // Even a getter must start with `&mut self` and not `&self`. /// // Even a getter must start with `&mut self` and not `&self`.
/// fn get_field(&mut self, index: i64) -> Result<Dynamic, Box<EvalAltResult>> { /// fn get_field(&mut self, index: i64) -> Result<Dynamic, Box<EvalAltResult>> {
/// Ok(self.fields[index as usize].into()) /// Ok(self.fields[index as usize].into())
@ -600,7 +599,7 @@ impl Engine {
/// } /// }
/// ///
/// impl TestStruct { /// impl TestStruct {
/// fn new() -> Self { TestStruct { fields: vec![1, 2, 3, 4, 5] } } /// fn new() -> Self { Self { fields: vec![1, 2, 3, 4, 5] } }
/// fn set_field(&mut self, index: i64, value: i64) { self.fields[index as usize] = value; } /// fn set_field(&mut self, index: i64, value: i64) { self.fields[index as usize] = value; }
/// } /// }
/// ///
@ -672,7 +671,7 @@ impl Engine {
/// } /// }
/// ///
/// impl TestStruct { /// impl TestStruct {
/// fn new() -> Self { TestStruct { fields: vec![1, 2, 3, 4, 5] } } /// fn new() -> Self { Self { fields: vec![1, 2, 3, 4, 5] } }
/// fn set_field(&mut self, index: i64, value: i64) -> Result<(), Box<EvalAltResult>> { /// fn set_field(&mut self, index: i64, value: i64) -> Result<(), Box<EvalAltResult>> {
/// self.fields[index as usize] = value; /// self.fields[index as usize] = value;
/// Ok(()) /// Ok(())
@ -724,8 +723,7 @@ impl Engine {
} }
self.register_result_fn(FN_IDX_SET, move |obj: &mut T, index: X, value: U| { self.register_result_fn(FN_IDX_SET, move |obj: &mut T, index: X, value: U| {
callback(obj, index, value)?; callback(obj, index, value).map(Into::into)
Ok(().into())
}) })
} }
@ -745,7 +743,7 @@ impl Engine {
/// } /// }
/// ///
/// impl TestStruct { /// impl TestStruct {
/// fn new() -> Self { TestStruct { fields: vec![1, 2, 3, 4, 5] } } /// fn new() -> Self { Self { fields: vec![1, 2, 3, 4, 5] } }
/// // Even a getter must start with `&mut self` and not `&self`. /// // Even a getter must start with `&mut self` and not `&self`.
/// fn get_field(&mut self, index: i64) -> i64 { self.fields[index as usize] } /// fn get_field(&mut self, index: i64) -> i64 { self.fields[index as usize] }
/// fn set_field(&mut self, index: i64, value: i64) { self.fields[index as usize] = value; } /// fn set_field(&mut self, index: i64, value: i64) { self.fields[index as usize] = value; }
@ -915,7 +913,7 @@ impl Engine {
scripts: &[&str], scripts: &[&str],
optimization_level: OptimizationLevel, optimization_level: OptimizationLevel,
) -> Result<AST, ParseError> { ) -> Result<AST, ParseError> {
let stream = lex(scripts, None, self); let stream = self.lex(scripts, None);
self.parse(&mut stream.peekable(), scope, optimization_level) self.parse(&mut stream.peekable(), scope, optimization_level)
} }
@ -1069,7 +1067,7 @@ impl Engine {
.into()); .into());
}; };
let stream = lex( let stream = self.lex(
&scripts, &scripts,
if has_null { if has_null {
Some(Box::new(|token| match token { Some(Box::new(|token| match token {
@ -1080,7 +1078,6 @@ impl Engine {
} else { } else {
None None
}, },
self,
); );
let ast = let ast =
self.parse_global_expr(&mut stream.peekable(), &scope, OptimizationLevel::None)?; self.parse_global_expr(&mut stream.peekable(), &scope, OptimizationLevel::None)?;
@ -1164,7 +1161,7 @@ impl Engine {
script: &str, script: &str,
) -> Result<AST, ParseError> { ) -> Result<AST, ParseError> {
let scripts = [script]; let scripts = [script];
let stream = lex(&scripts, None, self); let stream = self.lex(&scripts, None);
{ {
let mut peekable = stream.peekable(); let mut peekable = stream.peekable();
self.parse_global_expr(&mut peekable, scope, self.optimization_level) self.parse_global_expr(&mut peekable, scope, self.optimization_level)
@ -1325,7 +1322,7 @@ impl Engine {
script: &str, script: &str,
) -> Result<T, Box<EvalAltResult>> { ) -> Result<T, Box<EvalAltResult>> {
let scripts = [script]; let scripts = [script];
let stream = lex(&scripts, None, self); let stream = self.lex(&scripts, None);
// No need to optimize a lone expression // No need to optimize a lone expression
let ast = self.parse_global_expr(&mut stream.peekable(), scope, OptimizationLevel::None)?; let ast = self.parse_global_expr(&mut stream.peekable(), scope, OptimizationLevel::None)?;
@ -1423,6 +1420,7 @@ impl Engine {
}) })
.or_else(|err| match *err { .or_else(|err| match *err {
EvalAltResult::Return(out, _) => Ok(out), EvalAltResult::Return(out, _) => Ok(out),
EvalAltResult::LoopBreak(_, _) => unreachable!(),
_ => Err(err), _ => Err(err),
}) })
.map(|v| (v, state.operations)) .map(|v| (v, state.operations))
@ -1466,7 +1464,7 @@ impl Engine {
script: &str, script: &str,
) -> Result<(), Box<EvalAltResult>> { ) -> Result<(), Box<EvalAltResult>> {
let scripts = [script]; let scripts = [script];
let stream = lex(&scripts, None, self); let stream = self.lex(&scripts, None);
let ast = self.parse(&mut stream.peekable(), scope, self.optimization_level)?; let ast = self.parse(&mut stream.peekable(), scope, self.optimization_level)?;
self.consume_ast_with_scope(scope, &ast) self.consume_ast_with_scope(scope, &ast)
} }
@ -1497,7 +1495,8 @@ impl Engine {
.map_or_else( .map_or_else(
|err| match *err { |err| match *err {
EvalAltResult::Return(_, _) => Ok(()), EvalAltResult::Return(_, _) => Ok(()),
err => Err(Box::new(err)), EvalAltResult::LoopBreak(_, _) => unreachable!(),
_ => Err(err),
}, },
|_| Ok(()), |_| Ok(()),
) )
@ -1645,7 +1644,7 @@ impl Engine {
lib: &Module, lib: &Module,
name: &str, name: &str,
this_ptr: &mut Option<&mut Dynamic>, this_ptr: &mut Option<&mut Dynamic>,
args: &mut [&mut Dynamic], args: &mut FnCallArgs,
) -> Result<Dynamic, Box<EvalAltResult>> { ) -> Result<Dynamic, Box<EvalAltResult>> {
let fn_def = lib let fn_def = lib
.get_script_fn(name, args.len(), true) .get_script_fn(name, args.len(), true)
@ -1715,7 +1714,7 @@ impl Engine {
/// let mut engine = Engine::new(); /// let mut engine = Engine::new();
/// ///
/// // Register a variable resolver. /// // Register a variable resolver.
/// engine.on_var(|name, _, _, _| { /// engine.on_var(|name, _, _| {
/// match name { /// match name {
/// "MYSTIC_NUMBER" => Ok(Some(42_i64.into())), /// "MYSTIC_NUMBER" => Ok(Some(42_i64.into())),
/// _ => Ok(None) /// _ => Ok(None)
@ -1730,7 +1729,7 @@ impl Engine {
#[inline(always)] #[inline(always)]
pub fn on_var( pub fn on_var(
&mut self, &mut self,
callback: impl Fn(&str, usize, &Scope, &EvalContext) -> Result<Option<Dynamic>, Box<EvalAltResult>> callback: impl Fn(&str, usize, &EvalContext) -> Result<Option<Dynamic>, Box<EvalAltResult>>
+ SendSync + SendSync
+ 'static, + 'static,
) -> &mut Self { ) -> &mut Self {

View File

@ -441,16 +441,17 @@ pub struct Limits {
/// Context of a script evaluation process. /// Context of a script evaluation process.
#[derive(Debug)] #[derive(Debug)]
pub struct EvalContext<'e, 'a, 's, 'm, 't, 'd: 't> { pub struct EvalContext<'e, 'x, 'px: 'x, 'a, 's, 'm, 't, 'pt: 't> {
engine: &'e Engine, engine: &'e Engine,
pub scope: &'x mut Scope<'px>,
pub(crate) mods: &'a mut Imports, pub(crate) mods: &'a mut Imports,
pub(crate) state: &'s mut State, pub(crate) state: &'s mut State,
lib: &'m Module, lib: &'m Module,
pub(crate) this_ptr: &'t mut Option<&'d mut Dynamic>, pub(crate) this_ptr: &'t mut Option<&'pt mut Dynamic>,
level: usize, level: usize,
} }
impl<'e, 'a, 's, 'm, 't, 'd> EvalContext<'e, 'a, 's, 'm, 't, 'd> { impl<'e, 'x, 'px, 'a, 's, 'm, 't, 'pt> EvalContext<'e, 'x, 'px, 'a, 's, 'm, 't, 'pt> {
/// The current `Engine`. /// The current `Engine`.
#[inline(always)] #[inline(always)]
pub fn engine(&self) -> &'e Engine { pub fn engine(&self) -> &'e Engine {
@ -469,6 +470,11 @@ impl<'e, 'a, 's, 'm, 't, 'd> EvalContext<'e, 'a, 's, 'm, 't, 'd> {
pub fn namespace(&self) -> &'m Module { pub fn namespace(&self) -> &'m Module {
self.lib self.lib
} }
/// The current bound `this` pointer, if any.
#[inline(always)]
pub fn this_ptr(&self) -> Option<&Dynamic> {
self.this_ptr.as_ref().map(|v| &**v)
}
/// The current nesting level of function calls. /// The current nesting level of function calls.
#[inline(always)] #[inline(always)]
pub fn call_level(&self) -> usize { pub fn call_level(&self) -> usize {
@ -815,6 +821,7 @@ impl Engine {
if let Some(ref resolve_var) = self.resolve_var { if let Some(ref resolve_var) = self.resolve_var {
let context = EvalContext { let context = EvalContext {
engine: self, engine: self,
scope,
mods, mods,
state, state,
lib, lib,
@ -822,7 +829,7 @@ impl Engine {
level: 0, level: 0,
}; };
if let Some(result) = if let Some(result) =
resolve_var(name, index, scope, &context).map_err(|err| err.fill_position(*pos))? resolve_var(name, index, &context).map_err(|err| err.fill_position(*pos))?
{ {
return Ok((result.into(), name, ScopeEntryType::Constant, *pos)); return Ok((result.into(), name, ScopeEntryType::Constant, *pos));
} }
@ -934,7 +941,9 @@ impl Engine {
level, level,
) )
.map_err(|err| match *err { .map_err(|err| match *err {
EvalAltResult::ErrorFunctionNotFound(_, _) => { EvalAltResult::ErrorFunctionNotFound(fn_sig, _)
if fn_sig.ends_with("]=") =>
{
EvalAltResult::ErrorIndexingType( EvalAltResult::ErrorIndexingType(
self.map_type_name(val_type_name).into(), self.map_type_name(val_type_name).into(),
Position::none(), Position::none(),
@ -1381,9 +1390,12 @@ impl Engine {
) )
.map(|(v, _)| v.into()) .map(|(v, _)| v.into())
.map_err(|err| match *err { .map_err(|err| match *err {
EvalAltResult::ErrorFunctionNotFound(_, _) => Box::new( EvalAltResult::ErrorFunctionNotFound(fn_sig, _) if fn_sig.ends_with(']') => {
EvalAltResult::ErrorIndexingType(type_name.into(), Position::none()), Box::new(EvalAltResult::ErrorIndexingType(
), type_name.into(),
Position::none(),
))
}
_ => err, _ => err,
}) })
} }
@ -1737,17 +1749,22 @@ impl Engine {
Expr::Unit(_) => Ok(().into()), Expr::Unit(_) => Ok(().into()),
Expr::Custom(x) => { Expr::Custom(x) => {
let func = (x.0).1.as_ref(); let func = (x.0).func();
let ep = (x.0).0.iter().map(|e| e.into()).collect::<StaticVec<_>>(); let expressions = (x.0)
.keywords()
.iter()
.map(Into::into)
.collect::<StaticVec<_>>();
let mut context = EvalContext { let mut context = EvalContext {
engine: self, engine: self,
scope,
mods, mods,
state, state,
lib, lib,
this_ptr, this_ptr,
level, level,
}; };
func(scope, &mut context, ep.as_ref()) func(&mut context, &expressions)
} }
_ => unreachable!(), _ => unreachable!(),

View File

@ -435,9 +435,11 @@ impl Engine {
&self, &self,
lib: &Module, lib: &Module,
name: &str, name: &str,
arg_types: &[TypeId], arg_types: impl AsRef<[TypeId]>,
pub_only: bool, pub_only: bool,
) -> bool { ) -> bool {
let arg_types = arg_types.as_ref();
let arg_len = if arg_types.is_empty() { let arg_len = if arg_types.is_empty() {
usize::MAX usize::MAX
} else { } else {
@ -840,7 +842,7 @@ impl Engine {
lib: &Module, lib: &Module,
this_ptr: &mut Option<&mut Dynamic>, this_ptr: &mut Option<&mut Dynamic>,
name: &str, name: &str,
args_expr: &[Expr], args_expr: impl AsRef<[Expr]>,
def_val: &Option<Dynamic>, def_val: &Option<Dynamic>,
mut hash_script: u64, mut hash_script: u64,
native: bool, native: bool,
@ -848,6 +850,8 @@ impl Engine {
capture: bool, capture: bool,
level: usize, level: usize,
) -> Result<Dynamic, Box<EvalAltResult>> { ) -> Result<Dynamic, Box<EvalAltResult>> {
let args_expr = args_expr.as_ref();
// Handle Fn() // Handle Fn()
if name == KEYWORD_FN_PTR && args_expr.len() == 1 { if name == KEYWORD_FN_PTR && args_expr.len() == 1 {
let hash_fn = calc_fn_hash(empty(), name, 1, once(TypeId::of::<ImmutableString>())); let hash_fn = calc_fn_hash(empty(), name, 1, once(TypeId::of::<ImmutableString>()));
@ -1085,11 +1089,13 @@ impl Engine {
this_ptr: &mut Option<&mut Dynamic>, this_ptr: &mut Option<&mut Dynamic>,
modules: &Option<Box<ModuleRef>>, modules: &Option<Box<ModuleRef>>,
name: &str, name: &str,
args_expr: &[Expr], args_expr: impl AsRef<[Expr]>,
def_val: Option<bool>, def_val: Option<bool>,
hash_script: u64, hash_script: u64,
level: usize, level: usize,
) -> Result<Dynamic, Box<EvalAltResult>> { ) -> Result<Dynamic, Box<EvalAltResult>> {
let args_expr = args_expr.as_ref();
let modules = modules.as_ref().unwrap(); let modules = modules.as_ref().unwrap();
let mut arg_values: StaticVec<_>; let mut arg_values: StaticVec<_>;
let mut first_arg_value = None; let mut first_arg_value = None;

View File

@ -6,7 +6,6 @@ use crate::module::Module;
use crate::parser::{FnAccess, ScriptFnDef}; use crate::parser::{FnAccess, ScriptFnDef};
use crate::plugin::PluginFunction; use crate::plugin::PluginFunction;
use crate::result::EvalAltResult; use crate::result::EvalAltResult;
use crate::scope::Scope;
use crate::token::{is_valid_identifier, Position}; use crate::token::{is_valid_identifier, Position};
use crate::utils::ImmutableString; use crate::utils::ImmutableString;
use crate::{calc_fn_hash, StaticVec}; use crate::{calc_fn_hash, StaticVec};
@ -265,14 +264,12 @@ pub type Callback<T, R> = Box<dyn Fn(&T) -> R + Send + Sync + 'static>;
/// A standard callback function. /// A standard callback function.
#[cfg(not(feature = "sync"))] #[cfg(not(feature = "sync"))]
pub type OnVarCallback = Box< pub type OnVarCallback =
dyn Fn(&str, usize, &Scope, &EvalContext) -> Result<Option<Dynamic>, Box<EvalAltResult>> Box<dyn Fn(&str, usize, &EvalContext) -> Result<Option<Dynamic>, Box<EvalAltResult>> + 'static>;
+ 'static,
>;
/// A standard callback function. /// A standard callback function.
#[cfg(feature = "sync")] #[cfg(feature = "sync")]
pub type OnVarCallback = Box< pub type OnVarCallback = Box<
dyn Fn(&str, usize, &Scope, &EvalContext) -> Result<Option<Dynamic>, Box<EvalAltResult>> dyn Fn(&str, usize, &EvalContext) -> Result<Option<Dynamic>, Box<EvalAltResult>>
+ Send + Send
+ Sync + Sync
+ 'static, + 'static,

View File

@ -536,11 +536,11 @@ impl Module {
&mut self, &mut self,
name: impl Into<String>, name: impl Into<String>,
arg_types: &[TypeId], arg_types: &[TypeId],
func: impl Fn(NativeCallContext, &mut [&mut Dynamic]) -> Result<T, Box<EvalAltResult>> func: impl Fn(NativeCallContext, &mut FnCallArgs) -> Result<T, Box<EvalAltResult>>
+ SendSync + SendSync
+ 'static, + 'static,
) -> u64 { ) -> u64 {
let f = move |context: NativeCallContext, args: &mut [&mut Dynamic]| { let f = move |context: NativeCallContext, args: &mut FnCallArgs| {
func(context, args).map(Dynamic::from) func(context, args).map(Dynamic::from)
}; };
self.set_fn( self.set_fn(

View File

@ -7,7 +7,7 @@ use crate::fn_native::{FnPtr, Shared};
use crate::module::{Module, ModuleRef}; use crate::module::{Module, ModuleRef};
use crate::optimize::{optimize_into_ast, OptimizationLevel}; use crate::optimize::{optimize_into_ast, OptimizationLevel};
use crate::scope::{EntryType as ScopeEntryType, Scope}; use crate::scope::{EntryType as ScopeEntryType, Scope};
use crate::syntax::FnCustomSyntaxEval; use crate::syntax::{CustomSyntax, FnCustomSyntaxEval};
use crate::token::{is_keyword_function, is_valid_identifier, Position, Token, TokenStream}; use crate::token::{is_keyword_function, is_valid_identifier, Position, Token, TokenStream};
use crate::utils::StraightHasherBuilder; use crate::utils::StraightHasherBuilder;
use crate::{calc_fn_hash, StaticVec}; use crate::{calc_fn_hash, StaticVec};
@ -919,6 +919,19 @@ impl Hash for CustomExpr {
} }
} }
impl CustomExpr {
/// Get the keywords for this `CustomExpr`.
#[inline(always)]
pub fn keywords(&self) -> &[Expr] {
&self.0
}
/// Get the implementation function for this `CustomExpr`.
#[inline(always)]
pub fn func(&self) -> &FnCustomSyntaxEval {
self.1.as_ref()
}
}
/// _[INTERNALS]_ A type wrapping a floating-point number. /// _[INTERNALS]_ A type wrapping a floating-point number.
/// Exported under the `internals` feature only. /// Exported under the `internals` feature only.
/// ///
@ -2641,40 +2654,27 @@ fn parse_binary_op(
} }
} }
/// Parse an expression. /// Parse a custom syntax.
fn parse_expr( fn parse_custom(
input: &mut TokenStream, input: &mut TokenStream,
state: &mut ParseState, state: &mut ParseState,
lib: &mut FunctionsLib, lib: &mut FunctionsLib,
mut settings: ParseSettings, mut settings: ParseSettings,
key: &str,
syntax: &CustomSyntax,
pos: Position,
) -> Result<Expr, ParseError> { ) -> Result<Expr, ParseError> {
settings.pos = input.peek().unwrap().1;
#[cfg(not(feature = "unchecked"))]
settings.ensure_level_within_max_limit(state.max_expr_depth)?;
// Check if it is a custom syntax.
if let Some(ref custom) = state.engine.custom_syntax {
let (token, pos) = input.peek().unwrap();
let token_pos = *pos;
match token {
Token::Custom(key) if custom.contains_key(key) => {
let custom = custom.get_key_value(key).unwrap();
let (key, syntax) = custom;
input.next().unwrap();
let mut exprs: StaticVec<Expr> = Default::default(); let mut exprs: StaticVec<Expr> = Default::default();
// Adjust the variables stack // Adjust the variables stack
match syntax.scope_delta { match syntax.scope_delta {
delta if delta > 0 => { delta if delta > 0 => {
state.stack.push(("".to_string(), ScopeEntryType::Normal)) state.stack.resize(
} state.stack.len() + delta as usize,
delta if delta < 0 && state.stack.len() <= delta.abs() as usize => { ("".to_string(), ScopeEntryType::Normal),
state.stack.clear() );
} }
delta if delta < 0 && state.stack.len() <= delta.abs() as usize => state.stack.clear(),
delta if delta < 0 => state delta if delta < 0 => state
.stack .stack
.truncate(state.stack.len() - delta.abs() as usize), .truncate(state.stack.len() - delta.abs() as usize),
@ -2716,10 +2716,35 @@ fn parse_expr(
} }
} }
return Ok(Expr::Custom(Box::new(( Ok(Expr::Custom(Box::new((
CustomExpr(exprs, syntax.func.clone()), CustomExpr(exprs, syntax.func.clone()),
token_pos, pos,
)))); ))))
}
/// Parse an expression.
fn parse_expr(
input: &mut TokenStream,
state: &mut ParseState,
lib: &mut FunctionsLib,
mut settings: ParseSettings,
) -> Result<Expr, ParseError> {
settings.pos = input.peek().unwrap().1;
#[cfg(not(feature = "unchecked"))]
settings.ensure_level_within_max_limit(state.max_expr_depth)?;
// Check if it is a custom syntax.
if let Some(ref custom) = state.engine.custom_syntax {
let (token, pos) = input.peek().unwrap();
let token_pos = *pos;
match token {
Token::Custom(key) if custom.contains_key(key) => {
let custom = custom.get_key_value(key).unwrap();
let (key, syntax) = custom;
input.next().unwrap();
return parse_custom(input, state, lib, settings, key, syntax, token_pos);
} }
_ => (), _ => (),
} }

View File

@ -2,7 +2,7 @@
pub use crate::any::Dynamic; pub use crate::any::Dynamic;
pub use crate::engine::Engine; pub use crate::engine::Engine;
pub use crate::fn_native::{CallableFunction, NativeCallContext}; pub use crate::fn_native::{CallableFunction, FnCallArgs, NativeCallContext};
pub use crate::fn_register::{RegisterFn, RegisterResultFn}; pub use crate::fn_register::{RegisterFn, RegisterResultFn};
pub use crate::module::Module; pub use crate::module::Module;
pub use crate::parser::FnAccess; pub use crate::parser::FnAccess;
@ -25,7 +25,7 @@ pub trait PluginFunction {
fn call( fn call(
&self, &self,
context: NativeCallContext, context: NativeCallContext,
args: &mut [&mut Dynamic], args: &mut FnCallArgs,
) -> Result<Dynamic, Box<EvalAltResult>>; ) -> Result<Dynamic, Box<EvalAltResult>>;
/// Is this plugin function a method? /// Is this plugin function a method?

View File

@ -6,7 +6,6 @@ use crate::error::{LexError, ParseError};
use crate::fn_native::{SendSync, Shared}; use crate::fn_native::{SendSync, Shared};
use crate::parser::Expr; use crate::parser::Expr;
use crate::result::EvalAltResult; use crate::result::EvalAltResult;
use crate::scope::Scope;
use crate::token::{is_valid_identifier, Position, Token}; use crate::token::{is_valid_identifier, Position, Token};
use crate::StaticVec; use crate::StaticVec;
@ -19,12 +18,11 @@ use crate::stdlib::{
/// A general expression evaluation trait object. /// A general expression evaluation trait object.
#[cfg(not(feature = "sync"))] #[cfg(not(feature = "sync"))]
pub type FnCustomSyntaxEval = pub type FnCustomSyntaxEval =
dyn Fn(&mut Scope, &mut EvalContext, &[Expression]) -> Result<Dynamic, Box<EvalAltResult>>; dyn Fn(&mut EvalContext, &[Expression]) -> Result<Dynamic, Box<EvalAltResult>>;
/// A general expression evaluation trait object. /// A general expression evaluation trait object.
#[cfg(feature = "sync")] #[cfg(feature = "sync")]
pub type FnCustomSyntaxEval = dyn Fn(&mut Scope, &mut EvalContext, &[Expression]) -> Result<Dynamic, Box<EvalAltResult>> pub type FnCustomSyntaxEval =
+ Send dyn Fn(&mut EvalContext, &[Expression]) -> Result<Dynamic, Box<EvalAltResult>> + Send + Sync;
+ Sync;
/// An expression sub-tree in an AST. /// An expression sub-tree in an AST.
#[derive(Debug, Clone, Hash)] #[derive(Debug, Clone, Hash)]
@ -55,7 +53,7 @@ impl Expression<'_> {
} }
} }
impl EvalContext<'_, '_, '_, '_, '_, '_> { impl EvalContext<'_, '_, '_, '_, '_, '_, '_, '_> {
/// Evaluate an expression tree. /// Evaluate an expression tree.
/// ///
/// ## WARNING - Low Level API /// ## WARNING - Low Level API
@ -64,11 +62,10 @@ impl EvalContext<'_, '_, '_, '_, '_, '_> {
#[inline(always)] #[inline(always)]
pub fn eval_expression_tree( pub fn eval_expression_tree(
&mut self, &mut self,
scope: &mut Scope,
expr: &Expression, expr: &Expression,
) -> Result<Dynamic, Box<EvalAltResult>> { ) -> Result<Dynamic, Box<EvalAltResult>> {
self.engine().eval_expr( self.engine().eval_expr(
scope, self.scope,
self.mods, self.mods,
self.state, self.state,
self.namespace(), self.namespace(),
@ -101,12 +98,14 @@ impl Engine {
/// * `func` is the implementation function. /// * `func` is the implementation function.
pub fn register_custom_syntax<S: AsRef<str> + ToString>( pub fn register_custom_syntax<S: AsRef<str> + ToString>(
&mut self, &mut self,
keywords: &[S], keywords: impl AsRef<[S]>,
new_vars: isize, new_vars: isize,
func: impl Fn(&mut Scope, &mut EvalContext, &[Expression]) -> Result<Dynamic, Box<EvalAltResult>> func: impl Fn(&mut EvalContext, &[Expression]) -> Result<Dynamic, Box<EvalAltResult>>
+ SendSync + SendSync
+ 'static, + 'static,
) -> Result<&mut Self, ParseError> { ) -> Result<&mut Self, ParseError> {
let keywords = keywords.as_ref();
let mut segments: StaticVec<_> = Default::default(); let mut segments: StaticVec<_> = Default::default();
for s in keywords { for s in keywords {

View File

@ -1736,18 +1736,19 @@ impl<'a> Iterator for TokenIterator<'a, '_> {
} }
} }
/// Tokenize an input text stream. impl Engine {
#[inline] /// Tokenize an input text stream.
pub fn lex<'a, 'e>( #[inline]
input: &'a [&'a str], pub fn lex<'a, 'e>(
&'e self,
input: impl IntoIterator<Item = &'a &'a str>,
map: Option<Box<dyn Fn(Token) -> Token>>, map: Option<Box<dyn Fn(Token) -> Token>>,
engine: &'e Engine, ) -> TokenIterator<'a, 'e> {
) -> TokenIterator<'a, 'e> {
TokenIterator { TokenIterator {
engine, engine: self,
state: TokenizeState { state: TokenizeState {
#[cfg(not(feature = "unchecked"))] #[cfg(not(feature = "unchecked"))]
max_string_size: engine.limits_set.max_string_size, max_string_size: self.limits_set.max_string_size,
#[cfg(feature = "unchecked")] #[cfg(feature = "unchecked")]
max_string_size: 0, max_string_size: 0,
non_unary: false, non_unary: false,
@ -1758,9 +1759,10 @@ pub fn lex<'a, 'e>(
pos: Position::new(1, 0), pos: Position::new(1, 0),
stream: MultiInputsStream { stream: MultiInputsStream {
buf: None, buf: None,
streams: input.iter().map(|s| s.chars().peekable()).collect(), streams: input.into_iter().map(|s| s.chars().peekable()).collect(),
index: 0, index: 0,
}, },
map, map,
} }
}
} }

View File

@ -84,7 +84,7 @@ fn test_array_with_structs() -> Result<(), Box<EvalAltResult>> {
} }
fn new() -> Self { fn new() -> Self {
TestStruct { x: 1 } Self { x: 1 }
} }
} }

View File

@ -1,7 +1,5 @@
#![cfg(not(feature = "no_function"))] #![cfg(not(feature = "no_function"))]
use rhai::{ use rhai::{Engine, EvalAltResult, FnPtr, Func, ParseErrorType, RegisterFn, Scope, INT};
Dynamic, Engine, EvalAltResult, FnPtr, Func, Module, ParseErrorType, RegisterFn, Scope, INT,
};
use std::any::TypeId; use std::any::TypeId;
#[test] #[test]

View File

@ -51,7 +51,7 @@ fn test_struct_with_float() -> Result<(), Box<EvalAltResult>> {
} }
fn new() -> Self { fn new() -> Self {
TestStruct { x: 1.0 } Self { x: 1.0 }
} }
} }

View File

@ -25,7 +25,7 @@ fn test_get_set() -> Result<(), Box<EvalAltResult>> {
} }
fn new() -> Self { fn new() -> Self {
TestStruct { Self {
x: 1, x: 1,
y: 0, y: 0,
array: vec![1, 2, 3, 4, 5], array: vec![1, 2, 3, 4, 5],

View File

@ -15,7 +15,7 @@ fn test_method_call() -> Result<(), Box<EvalAltResult>> {
} }
fn new() -> Self { fn new() -> Self {
TestStruct { x: 1 } Self { x: 1 }
} }
} }

View File

@ -20,7 +20,7 @@ fn test_mismatched_op_custom_type() {
impl TestStruct { impl TestStruct {
fn new() -> Self { fn new() -> Self {
TestStruct { x: 1 } Self { x: 1 }
} }
} }

View File

@ -1,4 +1,4 @@
use rhai::{Engine, EvalAltResult, EvalContext, Expression, ParseErrorType, Scope, INT}; use rhai::{Engine, EvalAltResult, EvalContext, Expression, ParseErrorType, INT};
#[test] #[test]
fn test_custom_syntax() -> Result<(), Box<EvalAltResult>> { fn test_custom_syntax() -> Result<(), Box<EvalAltResult>> {
@ -22,18 +22,18 @@ fn test_custom_syntax() -> Result<(), Box<EvalAltResult>> {
"do", "|", "$ident$", "|", "->", "$block$", "while", "$expr$", "do", "|", "$ident$", "|", "->", "$block$", "while", "$expr$",
], ],
1, 1,
|scope: &mut Scope, context: &mut EvalContext, inputs: &[Expression]| { |context: &mut EvalContext, inputs: &[Expression]| {
let var_name = inputs[0].get_variable_name().unwrap().to_string(); let var_name = inputs[0].get_variable_name().unwrap().to_string();
let stmt = inputs.get(1).unwrap(); let stmt = inputs.get(1).unwrap();
let condition = inputs.get(2).unwrap(); let condition = inputs.get(2).unwrap();
scope.push(var_name, 0 as INT); context.scope.push(var_name, 0 as INT);
loop { loop {
context.eval_expression_tree(scope, stmt)?; context.eval_expression_tree(stmt)?;
let stop = !context let stop = !context
.eval_expression_tree(scope, condition)? .eval_expression_tree(condition)?
.as_bool() .as_bool()
.map_err(|err| { .map_err(|err| {
Box::new(EvalAltResult::ErrorMismatchDataType( Box::new(EvalAltResult::ErrorMismatchDataType(
@ -68,7 +68,7 @@ fn test_custom_syntax() -> Result<(), Box<EvalAltResult>> {
// The first symbol must be an identifier // The first symbol must be an identifier
assert_eq!( assert_eq!(
*engine *engine
.register_custom_syntax(&["!"], 0, |_, _, _| Ok(().into())) .register_custom_syntax(&["!"], 0, |_, _| Ok(().into()))
.expect_err("should error") .expect_err("should error")
.0, .0,
ParseErrorType::BadInput("Improper symbol for custom syntax: '!'".to_string()) ParseErrorType::BadInput("Improper symbol for custom syntax: '!'".to_string())

View File

@ -62,7 +62,7 @@ fn test_var_resolver() -> Result<(), Box<EvalAltResult>> {
scope.push("chameleon", 123 as INT); scope.push("chameleon", 123 as INT);
scope.push("DO_NOT_USE", 999 as INT); scope.push("DO_NOT_USE", 999 as INT);
engine.on_var(|name, _, scope, _| { engine.on_var(|name, _, context| {
match name { match name {
"MYSTIC_NUMBER" => Ok(Some((42 as INT).into())), "MYSTIC_NUMBER" => Ok(Some((42 as INT).into())),
// Override a variable - make it not found even if it exists! // Override a variable - make it not found even if it exists!
@ -70,7 +70,11 @@ fn test_var_resolver() -> Result<(), Box<EvalAltResult>> {
Err(EvalAltResult::ErrorVariableNotFound(name.to_string(), Position::none()).into()) Err(EvalAltResult::ErrorVariableNotFound(name.to_string(), Position::none()).into())
} }
// Silently maps 'chameleon' into 'innocent'. // Silently maps 'chameleon' into 'innocent'.
"chameleon" => scope.get_value("innocent").map(Some).ok_or_else(|| { "chameleon" => context
.scope
.get_value("innocent")
.map(Some)
.ok_or_else(|| {
EvalAltResult::ErrorVariableNotFound(name.to_string(), Position::none()).into() EvalAltResult::ErrorVariableNotFound(name.to_string(), Position::none()).into()
}), }),
// Return Ok(None) to continue with the normal variable resolution process. // Return Ok(None) to continue with the normal variable resolution process.