commit
1f9fb34e75
23
README.md
23
README.md
@ -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
|
||||||
-------
|
-------
|
||||||
|
|
||||||
|
@ -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
|
||||||
------------
|
------------
|
||||||
|
@ -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)
|
||||||
|
@ -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++.
|
||||||
|
|
||||||
|
@ -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(),
|
||||||
|
@ -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
BIN
doc/src/images/rhai.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 5.0 KiB |
@ -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::*;
|
||||||
|
@ -1,124 +1,118 @@
|
|||||||
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.
|
||||||
|
|
||||||
```rust
|
|
||||||
use rhai::{Engine, EvalAltResult};
|
|
||||||
use rhai::RegisterFn; // remember 'RegisterFn' is needed
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
Register a Custom Type and its Methods
|
||||||
struct TestStruct {
|
-------------------------------------
|
||||||
field: i64
|
|
||||||
}
|
|
||||||
|
|
||||||
impl TestStruct {
|
Any custom type must implement the `Clone` trait as this allows the [`Engine`] to pass by value.
|
||||||
fn update(&mut self) {
|
|
||||||
self.field += 41;
|
|
||||||
}
|
|
||||||
|
|
||||||
fn new() -> Self {
|
If the [`sync`] feature is used, it must also be `Send + Sync`.
|
||||||
TestStruct { field: 1 }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut engine = Engine::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
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
Register a Custom Type
|
|
||||||
---------------------
|
|
||||||
|
|
||||||
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`
|
Notice that the custom type needs to be _registered_ using `Engine::register_type`
|
||||||
or `Engine::register_type_with_name`.
|
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::RegisterFn; // remember 'RegisterFn' is needed
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
struct TestStruct {
|
struct TestStruct {
|
||||||
field: i64
|
field: i64
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TestStruct {
|
impl TestStruct {
|
||||||
fn update(&mut self) { // methods take &mut as first parameter
|
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();
|
||||||
|
|
||||||
engine.register_type::<TestStruct>();
|
// Most Engine API's can be chained up.
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
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
|
engine
|
||||||
.register_fn("update", TestStruct::update) // registers 'update(&mut TestStruct)'
|
.register_type::<TestStruct>() // register custom type
|
||||||
.register_fn("new_ts", TestStruct::new); // registers 'new()'
|
.register_fn("new_ts", TestStruct::new)
|
||||||
|
.register_fn("update", TestStruct::update);
|
||||||
|
|
||||||
|
// 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
|
||||||
```
|
```
|
||||||
|
|
||||||
***Note**: Rhai follows the convention that methods of custom types take a `&mut` first parameter
|
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).*
|
to that type, so that invoking methods can always update it.
|
||||||
|
|
||||||
|
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'
|
||||||
|
@ -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" }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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.**
|
|
||||||
|
@ -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:
|
||||||
|
|
||||||
|
@ -11,7 +11,7 @@ impl TestStruct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn new() -> Self {
|
fn new() -> Self {
|
||||||
TestStruct { x: 1 }
|
Self { x: 1 }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -11,7 +11,7 @@ impl TestStruct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn new() -> Self {
|
fn new() -> Self {
|
||||||
TestStruct { x: 1 }
|
Self { x: 1 }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
57
src/api.rs
57
src/api.rs
@ -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 {
|
||||||
|
@ -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!(),
|
||||||
|
@ -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;
|
||||||
|
@ -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,
|
||||||
|
@ -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(
|
||||||
|
141
src/parser.rs
141
src/parser.rs
@ -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,6 +2654,74 @@ fn parse_binary_op(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Parse a custom syntax.
|
||||||
|
fn parse_custom(
|
||||||
|
input: &mut TokenStream,
|
||||||
|
state: &mut ParseState,
|
||||||
|
lib: &mut FunctionsLib,
|
||||||
|
mut settings: ParseSettings,
|
||||||
|
key: &str,
|
||||||
|
syntax: &CustomSyntax,
|
||||||
|
pos: Position,
|
||||||
|
) -> Result<Expr, ParseError> {
|
||||||
|
let mut exprs: StaticVec<Expr> = Default::default();
|
||||||
|
|
||||||
|
// Adjust the variables stack
|
||||||
|
match syntax.scope_delta {
|
||||||
|
delta if delta > 0 => {
|
||||||
|
state.stack.resize(
|
||||||
|
state.stack.len() + delta as usize,
|
||||||
|
("".to_string(), ScopeEntryType::Normal),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
delta if delta < 0 && state.stack.len() <= delta.abs() as usize => state.stack.clear(),
|
||||||
|
delta if delta < 0 => state
|
||||||
|
.stack
|
||||||
|
.truncate(state.stack.len() - delta.abs() as usize),
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
|
||||||
|
for segment in syntax.segments.iter() {
|
||||||
|
settings.pos = input.peek().unwrap().1;
|
||||||
|
let settings = settings.level_up();
|
||||||
|
|
||||||
|
match segment.as_str() {
|
||||||
|
MARKER_IDENT => match input.next().unwrap() {
|
||||||
|
(Token::Identifier(s), pos) => {
|
||||||
|
exprs.push(Expr::Variable(Box::new(((s, pos), None, 0, None))));
|
||||||
|
}
|
||||||
|
(Token::Reserved(s), pos) if is_valid_identifier(s.chars()) => {
|
||||||
|
return Err(PERR::Reserved(s).into_err(pos));
|
||||||
|
}
|
||||||
|
(_, pos) => return Err(PERR::VariableExpected.into_err(pos)),
|
||||||
|
},
|
||||||
|
MARKER_EXPR => exprs.push(parse_expr(input, state, lib, settings)?),
|
||||||
|
MARKER_BLOCK => {
|
||||||
|
let stmt = parse_block(input, state, lib, settings)?;
|
||||||
|
let pos = stmt.position();
|
||||||
|
exprs.push(Expr::Stmt(Box::new((stmt, pos))))
|
||||||
|
}
|
||||||
|
s => match input.peek().unwrap() {
|
||||||
|
(t, _) if t.syntax().as_ref() == s => {
|
||||||
|
input.next().unwrap();
|
||||||
|
}
|
||||||
|
(_, pos) => {
|
||||||
|
return Err(PERR::MissingToken(
|
||||||
|
s.to_string(),
|
||||||
|
format!("for '{}' expression", key),
|
||||||
|
)
|
||||||
|
.into_err(*pos))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(Expr::Custom(Box::new((
|
||||||
|
CustomExpr(exprs, syntax.func.clone()),
|
||||||
|
pos,
|
||||||
|
))))
|
||||||
|
}
|
||||||
|
|
||||||
/// Parse an expression.
|
/// Parse an expression.
|
||||||
fn parse_expr(
|
fn parse_expr(
|
||||||
input: &mut TokenStream,
|
input: &mut TokenStream,
|
||||||
@ -2662,64 +2743,8 @@ fn parse_expr(
|
|||||||
Token::Custom(key) if custom.contains_key(key) => {
|
Token::Custom(key) if custom.contains_key(key) => {
|
||||||
let custom = custom.get_key_value(key).unwrap();
|
let custom = custom.get_key_value(key).unwrap();
|
||||||
let (key, syntax) = custom;
|
let (key, syntax) = custom;
|
||||||
|
|
||||||
input.next().unwrap();
|
input.next().unwrap();
|
||||||
|
return parse_custom(input, state, lib, settings, key, syntax, token_pos);
|
||||||
let mut exprs: StaticVec<Expr> = Default::default();
|
|
||||||
|
|
||||||
// Adjust the variables stack
|
|
||||||
match syntax.scope_delta {
|
|
||||||
delta if delta > 0 => {
|
|
||||||
state.stack.push(("".to_string(), ScopeEntryType::Normal))
|
|
||||||
}
|
|
||||||
delta if delta < 0 && state.stack.len() <= delta.abs() as usize => {
|
|
||||||
state.stack.clear()
|
|
||||||
}
|
|
||||||
delta if delta < 0 => state
|
|
||||||
.stack
|
|
||||||
.truncate(state.stack.len() - delta.abs() as usize),
|
|
||||||
_ => (),
|
|
||||||
}
|
|
||||||
|
|
||||||
for segment in syntax.segments.iter() {
|
|
||||||
settings.pos = input.peek().unwrap().1;
|
|
||||||
let settings = settings.level_up();
|
|
||||||
|
|
||||||
match segment.as_str() {
|
|
||||||
MARKER_IDENT => match input.next().unwrap() {
|
|
||||||
(Token::Identifier(s), pos) => {
|
|
||||||
exprs.push(Expr::Variable(Box::new(((s, pos), None, 0, None))));
|
|
||||||
}
|
|
||||||
(Token::Reserved(s), pos) if is_valid_identifier(s.chars()) => {
|
|
||||||
return Err(PERR::Reserved(s).into_err(pos));
|
|
||||||
}
|
|
||||||
(_, pos) => return Err(PERR::VariableExpected.into_err(pos)),
|
|
||||||
},
|
|
||||||
MARKER_EXPR => exprs.push(parse_expr(input, state, lib, settings)?),
|
|
||||||
MARKER_BLOCK => {
|
|
||||||
let stmt = parse_block(input, state, lib, settings)?;
|
|
||||||
let pos = stmt.position();
|
|
||||||
exprs.push(Expr::Stmt(Box::new((stmt, pos))))
|
|
||||||
}
|
|
||||||
s => match input.peek().unwrap() {
|
|
||||||
(t, _) if t.syntax().as_ref() == s => {
|
|
||||||
input.next().unwrap();
|
|
||||||
}
|
|
||||||
(_, pos) => {
|
|
||||||
return Err(PERR::MissingToken(
|
|
||||||
s.to_string(),
|
|
||||||
format!("for '{}' expression", key),
|
|
||||||
)
|
|
||||||
.into_err(*pos))
|
|
||||||
}
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return Ok(Expr::Custom(Box::new((
|
|
||||||
CustomExpr(exprs, syntax.func.clone()),
|
|
||||||
token_pos,
|
|
||||||
))));
|
|
||||||
}
|
}
|
||||||
_ => (),
|
_ => (),
|
||||||
}
|
}
|
||||||
|
@ -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?
|
||||||
|
@ -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 {
|
||||||
|
54
src/token.rs
54
src/token.rs
@ -1736,31 +1736,33 @@ 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>(
|
||||||
map: Option<Box<dyn Fn(Token) -> Token>>,
|
&'e self,
|
||||||
engine: &'e Engine,
|
input: impl IntoIterator<Item = &'a &'a str>,
|
||||||
) -> TokenIterator<'a, 'e> {
|
map: Option<Box<dyn Fn(Token) -> Token>>,
|
||||||
TokenIterator {
|
) -> TokenIterator<'a, 'e> {
|
||||||
engine,
|
TokenIterator {
|
||||||
state: TokenizeState {
|
engine: self,
|
||||||
#[cfg(not(feature = "unchecked"))]
|
state: TokenizeState {
|
||||||
max_string_size: engine.limits_set.max_string_size,
|
#[cfg(not(feature = "unchecked"))]
|
||||||
#[cfg(feature = "unchecked")]
|
max_string_size: self.limits_set.max_string_size,
|
||||||
max_string_size: 0,
|
#[cfg(feature = "unchecked")]
|
||||||
non_unary: false,
|
max_string_size: 0,
|
||||||
comment_level: 0,
|
non_unary: false,
|
||||||
end_with_none: false,
|
comment_level: 0,
|
||||||
include_comments: false,
|
end_with_none: false,
|
||||||
},
|
include_comments: false,
|
||||||
pos: Position::new(1, 0),
|
},
|
||||||
stream: MultiInputsStream {
|
pos: Position::new(1, 0),
|
||||||
buf: None,
|
stream: MultiInputsStream {
|
||||||
streams: input.iter().map(|s| s.chars().peekable()).collect(),
|
buf: None,
|
||||||
index: 0,
|
streams: input.into_iter().map(|s| s.chars().peekable()).collect(),
|
||||||
},
|
index: 0,
|
||||||
map,
|
},
|
||||||
|
map,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -84,7 +84,7 @@ fn test_array_with_structs() -> Result<(), Box<EvalAltResult>> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn new() -> Self {
|
fn new() -> Self {
|
||||||
TestStruct { x: 1 }
|
Self { x: 1 }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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]
|
||||||
|
@ -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 }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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],
|
||||||
|
@ -15,7 +15,7 @@ fn test_method_call() -> Result<(), Box<EvalAltResult>> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn new() -> Self {
|
fn new() -> Self {
|
||||||
TestStruct { x: 1 }
|
Self { x: 1 }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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 }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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())
|
||||||
|
@ -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,9 +70,13 @@ 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
|
||||||
EvalAltResult::ErrorVariableNotFound(name.to_string(), Position::none()).into()
|
.scope
|
||||||
}),
|
.get_value("innocent")
|
||||||
|
.map(Some)
|
||||||
|
.ok_or_else(|| {
|
||||||
|
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.
|
||||||
_ => Ok(None),
|
_ => Ok(None),
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user