commit
126dc58ea5
1
.github/workflows/build.yml
vendored
1
.github/workflows/build.yml
vendored
@ -38,6 +38,7 @@ jobs:
|
|||||||
- {toolchain: stable, os: macos-latest, experimental: false, flags: ""}
|
- {toolchain: stable, os: macos-latest, experimental: false, flags: ""}
|
||||||
- {toolchain: beta, os: ubuntu-latest, experimental: false, flags: ""}
|
- {toolchain: beta, os: ubuntu-latest, experimental: false, flags: ""}
|
||||||
- {toolchain: nightly, os: ubuntu-latest, experimental: true, flags: ""}
|
- {toolchain: nightly, os: ubuntu-latest, experimental: true, flags: ""}
|
||||||
|
fail-fast: false
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v2
|
||||||
|
4
.gitignore
vendored
4
.gitignore
vendored
@ -3,5 +3,5 @@ Cargo.lock
|
|||||||
.vscode/
|
.vscode/
|
||||||
.cargo/
|
.cargo/
|
||||||
doc/book/
|
doc/book/
|
||||||
before
|
before*
|
||||||
after
|
after*
|
||||||
|
@ -41,7 +41,7 @@ Standard features
|
|||||||
Protection against attacks
|
Protection against attacks
|
||||||
--------------------------
|
--------------------------
|
||||||
|
|
||||||
* Sand-boxed - the scripting engine, if declared immutable, cannot mutate the containing environment unless explicitly permitted (e.g. via a `RefCell`).
|
* 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.
|
||||||
|
|
||||||
|
@ -4,6 +4,11 @@ Rhai Release Notes
|
|||||||
Version 0.19.0
|
Version 0.19.0
|
||||||
==============
|
==============
|
||||||
|
|
||||||
|
New features
|
||||||
|
------------
|
||||||
|
|
||||||
|
* Adds `Engine::register_get_result`, `Engine::register_set_result`, `Engine::register_indexer_get_result`, `Engine::register_indexer_set_result` API.
|
||||||
|
|
||||||
|
|
||||||
Version 0.18.1
|
Version 0.18.1
|
||||||
==============
|
==============
|
||||||
|
@ -100,8 +100,12 @@ The Rhai Scripting Language
|
|||||||
8. [Maximum Call Stack Depth](safety/max-call-stack.md)
|
8. [Maximum Call Stack Depth](safety/max-call-stack.md)
|
||||||
9. [Maximum Statement Depth](safety/max-stmt-depth.md)
|
9. [Maximum Statement Depth](safety/max-stmt-depth.md)
|
||||||
7. [Advanced Topics](advanced.md)
|
7. [Advanced Topics](advanced.md)
|
||||||
1. [Capture Scope for Function Call](language/fn-capture.md)
|
1. [Advanced Patterns](patterns/index.md)
|
||||||
2. [Object-Oriented Programming (OOP)](language/oop.md)
|
1. [Object-Oriented Programming (OOP)](patterns/oop.md)
|
||||||
|
2. [Loadable Configuration](patterns/config.md)
|
||||||
|
3. [Control Layer](patterns/control.md)
|
||||||
|
4. [Singleton Command](patterns/singleton.md)
|
||||||
|
2. [Capture Scope for Function Call](language/fn-capture.md)
|
||||||
3. [Serialization/Deserialization of `Dynamic` with `serde`](rust/serde.md)
|
3. [Serialization/Deserialization of `Dynamic` with `serde`](rust/serde.md)
|
||||||
4. [Script Optimization](engine/optimize/index.md)
|
4. [Script Optimization](engine/optimize/index.md)
|
||||||
1. [Optimization Levels](engine/optimize/optimize-levels.md)
|
1. [Optimization Levels](engine/optimize/optimize-levels.md)
|
||||||
|
@ -46,7 +46,7 @@ Safe
|
|||||||
|
|
||||||
* Relatively little `unsafe` code (yes there are some for performance reasons).
|
* Relatively little `unsafe` code (yes there are some for performance reasons).
|
||||||
|
|
||||||
* Sand-boxed - the scripting [`Engine`], if declared immutable, cannot mutate the containing environment unless explicitly permitted (e.g. via a `RefCell`).
|
* Sand-boxed - the scripting [`Engine`], if declared immutable, cannot mutate the containing environment unless [explicitly permitted]({{rootUrl}}/patterns/control.md).
|
||||||
|
|
||||||
Rugged
|
Rugged
|
||||||
------
|
------
|
||||||
|
@ -5,9 +5,9 @@ Advanced Topics
|
|||||||
|
|
||||||
This section covers advanced features such as:
|
This section covers advanced features such as:
|
||||||
|
|
||||||
* [Capture the calling scope]({{rootUrl}}/language/fn-capture.md) in a function call.
|
* [Advanced patterns]({{rootUrl}}/patterns/index.md) in using Rhai.
|
||||||
|
|
||||||
* Simulated [Object Oriented Programming (OOP)][OOP].
|
* [Capture the calling scope]({{rootUrl}}/language/fn-capture.md) in a function call.
|
||||||
|
|
||||||
* [`serde`] integration.
|
* [`serde`] integration.
|
||||||
|
|
||||||
|
@ -3,9 +3,15 @@ Arrays
|
|||||||
|
|
||||||
{{#include ../links.md}}
|
{{#include ../links.md}}
|
||||||
|
|
||||||
Arrays are first-class citizens in Rhai. Like C, arrays are accessed with zero-based, non-negative integer indices.
|
Arrays are first-class citizens in Rhai. Like C, arrays are accessed with zero-based, non-negative integer indices:
|
||||||
|
|
||||||
Array literals are built within square brackets '`[`' ... '`]`' and separated by commas '`,`'.
|
> _array_ `[` _index_ `]`
|
||||||
|
|
||||||
|
Array literals are built within square brackets '`[`' ... '`]`' and separated by commas '`,`':
|
||||||
|
|
||||||
|
> `[` _value_ `,` _value_ `,` `...` `,` _value_ `]`
|
||||||
|
>
|
||||||
|
> `[` _value_ `,` _value_ `,` `...` `,` _value_ `,` `]` `// trailing comma is OK`
|
||||||
|
|
||||||
All elements stored in an array are [`Dynamic`], and the array can freely grow or shrink with elements added or removed.
|
All elements stored in an array are [`Dynamic`], and the array can freely grow or shrink with elements added or removed.
|
||||||
|
|
||||||
|
@ -10,24 +10,23 @@ If an [object map]'s property holds a [function pointer], the property can simpl
|
|||||||
a normal method in method-call syntax. This is a _short-hand_ to avoid the more verbose syntax
|
a normal method in method-call syntax. This is a _short-hand_ to avoid the more verbose syntax
|
||||||
of using the `call` function keyword.
|
of using the `call` function keyword.
|
||||||
|
|
||||||
When a property holding a [function pointer] is called like a method, what happens next depends
|
When a property holding a [function pointer] (which incudes [closures]) is called like a method,
|
||||||
on whether the target function is a native Rust function or a script-defined function.
|
what happens next depends on whether the target function is a native Rust function or
|
||||||
|
a script-defined function.
|
||||||
|
|
||||||
If it is a registered native Rust method function, then it is called directly.
|
If it is a registered native Rust method function, it is called directly.
|
||||||
|
|
||||||
If it is a script-defined function, the `this` variable within the function body is bound
|
If it is a script-defined function, the `this` variable within the function body is bound
|
||||||
to the [object map] before the function is called. There is no way to simulate this behavior
|
to the [object map] before the function is called. There is no way to simulate this behavior
|
||||||
via a normal function-call syntax because all scripted function arguments are passed by value.
|
via a normal function-call syntax because all scripted function arguments are passed by value.
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
fn do_action(x) { this.data += x; } // 'this' binds to the object when called
|
|
||||||
|
|
||||||
let obj = #{
|
let obj = #{
|
||||||
data: 40,
|
data: 40,
|
||||||
action: Fn("do_action") // 'action' holds a function pointer to 'do_action'
|
action: || this.data += x // 'action' holds a function pointer which is a closure
|
||||||
};
|
};
|
||||||
|
|
||||||
obj.action(2); // Calls 'do_action' with `this` bound to 'obj'
|
obj.action(2); // Calls the function pointer with `this` bound to 'obj'
|
||||||
|
|
||||||
obj.call(obj.action, 2); // The above de-sugars to this
|
obj.call(obj.action, 2); // The above de-sugars to this
|
||||||
|
|
||||||
@ -36,5 +35,7 @@ obj.data == 42;
|
|||||||
// To achieve the above with normal function pointer call will fail.
|
// To achieve the above with normal function pointer call will fail.
|
||||||
fn do_action(map, x) { map.data += x; } // 'map' is a copy
|
fn do_action(map, x) { map.data += x; } // 'map' is a copy
|
||||||
|
|
||||||
|
obj.action = Fn("do_action");
|
||||||
|
|
||||||
obj.action.call(obj, 2); // 'obj' is passed as a copy by value
|
obj.action.call(obj, 2); // 'obj' is passed as a copy by value
|
||||||
```
|
```
|
||||||
|
@ -19,21 +19,36 @@ Object Map Literals
|
|||||||
------------------
|
------------------
|
||||||
|
|
||||||
Object map literals are built within braces '`#{`' ... '`}`' (_name_ `:` _value_ syntax similar to Rust)
|
Object map literals are built within braces '`#{`' ... '`}`' (_name_ `:` _value_ syntax similar to Rust)
|
||||||
and separated by commas '`,`'. The property _name_ can be a simple variable name following the same
|
and separated by commas '`,`':
|
||||||
|
|
||||||
|
> `#{` _property_ `:` _value_ `,` `...` `,` _property_ `:` _value_ `}`
|
||||||
|
>
|
||||||
|
> `#{` _property_ `:` _value_ `,` `...` `,` _property_ `:` _value_ `,` `}` `// trailing comma is OK`
|
||||||
|
|
||||||
|
The property _name_ can be a simple variable name following the same
|
||||||
naming rules as [variables], or an arbitrary [string] literal.
|
naming rules as [variables], or an arbitrary [string] literal.
|
||||||
|
|
||||||
|
|
||||||
Access Properties
|
Access Properties
|
||||||
----------------
|
-----------------
|
||||||
|
|
||||||
Property values can be accessed via the _dot_ notation (_object_ `.` _property_)
|
### Dot Notation
|
||||||
or _index_ notation (_object_ `[` _property_ `]`).
|
|
||||||
|
|
||||||
The dot notation allows only property names that follow the same naming rules as [variables].
|
The _dot notation_ allows only property names that follow the same naming rules as [variables].
|
||||||
|
|
||||||
The index notation allows setting/getting properties of arbitrary names (even the empty [string]).
|
> _object_ `.` _property_
|
||||||
|
|
||||||
**Important:** Trying to read a non-existent property returns [`()`] instead of causing an error.
|
### Index Notation
|
||||||
|
|
||||||
|
The _index notation_ allows setting/getting properties of arbitrary names (even the empty [string]).
|
||||||
|
|
||||||
|
> _object_ `[` _property_ `]`
|
||||||
|
|
||||||
|
### Non-Existence
|
||||||
|
|
||||||
|
Trying to read a non-existing property returns [`()`] instead of causing an error.
|
||||||
|
|
||||||
|
This is similar to JavaScript where accessing a non-existing property returns `undefined`.
|
||||||
|
|
||||||
|
|
||||||
Built-in Functions
|
Built-in Functions
|
||||||
@ -89,7 +104,7 @@ let foo = #{ a:1, b:2, c:3 }["a"];
|
|||||||
foo == 1;
|
foo == 1;
|
||||||
|
|
||||||
fn abc() {
|
fn abc() {
|
||||||
#{ a:1, b:2, c:3 } // a function returning an object map
|
##{ a:1, b:2, c:3 } // a function returning an object map
|
||||||
}
|
}
|
||||||
|
|
||||||
let foo = abc().b;
|
let foo = abc().b;
|
||||||
|
@ -76,6 +76,8 @@
|
|||||||
|
|
||||||
[function]: {{rootUrl}}/language/functions.md
|
[function]: {{rootUrl}}/language/functions.md
|
||||||
[functions]: {{rootUrl}}/language/functions.md
|
[functions]: {{rootUrl}}/language/functions.md
|
||||||
|
[function overloading]: {{rootUrl}}/rust/functions.md#function-overloading
|
||||||
|
[fallible functions]: {{rootUrl}}/rust/fallible.md
|
||||||
[function pointer]: {{rootUrl}}/language/fn-ptr.md
|
[function pointer]: {{rootUrl}}/language/fn-ptr.md
|
||||||
[function pointers]: {{rootUrl}}/language/fn-ptr.md
|
[function pointers]: {{rootUrl}}/language/fn-ptr.md
|
||||||
[currying]: {{rootUrl}}/language/fn-curry.md
|
[currying]: {{rootUrl}}/language/fn-curry.md
|
||||||
@ -97,9 +99,10 @@
|
|||||||
|
|
||||||
[`eval`]: {{rootUrl}}/language/eval.md
|
[`eval`]: {{rootUrl}}/language/eval.md
|
||||||
|
|
||||||
[OOP]: {{rootUrl}}/language/oop.md
|
[OOP]: {{rootUrl}}/patterns/oop.md
|
||||||
[DSL]: {{rootUrl}}/engine/dsl.md
|
[DSL]: {{rootUrl}}/engine/dsl.md
|
||||||
|
|
||||||
|
[sand-boxed]: {{rootUrl}}/safety/sandbox.md
|
||||||
[maximum statement depth]: {{rootUrl}}/safety/max-stmt-depth.md
|
[maximum statement depth]: {{rootUrl}}/safety/max-stmt-depth.md
|
||||||
[maximum call stack depth]: {{rootUrl}}/safety/max-call-stack.md
|
[maximum call stack depth]: {{rootUrl}}/safety/max-call-stack.md
|
||||||
[maximum number of operations]: {{rootUrl}}/safety/max-operations.md
|
[maximum number of operations]: {{rootUrl}}/safety/max-operations.md
|
||||||
|
105
doc/src/patterns/config.md
Normal file
105
doc/src/patterns/config.md
Normal file
@ -0,0 +1,105 @@
|
|||||||
|
Loadable Configuration
|
||||||
|
======================
|
||||||
|
|
||||||
|
{{#include ../links.md}}
|
||||||
|
|
||||||
|
|
||||||
|
Usage Scenario
|
||||||
|
--------------
|
||||||
|
|
||||||
|
* A system where settings and configurations are complex and logic-driven.
|
||||||
|
|
||||||
|
* Where said system is too complex to configure via standard configuration file formats such as `JSON`, `TOML` or `YAML`.
|
||||||
|
|
||||||
|
* The system is complex enough to require a full programming language to configure. Essentially _configuration by code_.
|
||||||
|
|
||||||
|
* Yet the configuration must be flexible, late-bound and dynamically loadable, just like a configuration file.
|
||||||
|
|
||||||
|
|
||||||
|
Key Concepts
|
||||||
|
------------
|
||||||
|
|
||||||
|
* Leverage the loadable [modules] of Rhai. The [`no_module`] feature must not be on.
|
||||||
|
|
||||||
|
* Expose the configuration API. Use separate scripts to configure that API. Dynamically load scripts via the `import` statement.
|
||||||
|
|
||||||
|
* Leverage [function overloading] to simplify the API design.
|
||||||
|
|
||||||
|
* Since Rhai is _sand-boxed_, it cannot mutate the environment. To modify the external configuration object via an API, it must be wrapped in a `RefCell` (or `RwLock`/`Mutex` for [`sync`]) and shared to the [`Engine`].
|
||||||
|
|
||||||
|
|
||||||
|
Implementation
|
||||||
|
--------------
|
||||||
|
|
||||||
|
### Configuration Type
|
||||||
|
|
||||||
|
```rust
|
||||||
|
#[derive(Debug, Clone, Default)]
|
||||||
|
struct Config {
|
||||||
|
pub id: String;
|
||||||
|
pub some_field: i64;
|
||||||
|
pub some_list: Vec<String>;
|
||||||
|
pub some_map: HashMap<String, bool>;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Make Shared Object
|
||||||
|
|
||||||
|
```rust
|
||||||
|
let config: Rc<RefCell<Config>> = Rc::new(RefCell::(Default::default()));
|
||||||
|
```
|
||||||
|
|
||||||
|
### Register Config API
|
||||||
|
|
||||||
|
```rust
|
||||||
|
// Notice 'move' is used to move the shared configuration object into the closure.
|
||||||
|
let cfg = config.clone();
|
||||||
|
engine.register_fn("config_set_id", move |id: String| *cfg.borrow_mut().id = id);
|
||||||
|
|
||||||
|
let cfg = config.clone();
|
||||||
|
engine.register_fn("config_get_id", move || cfg.borrow().id.clone());
|
||||||
|
|
||||||
|
let cfg = config.clone();
|
||||||
|
engine.register_fn("config_set", move |value: i64| *cfg.borrow_mut().some_field = value);
|
||||||
|
|
||||||
|
// Remember Rhai functions can be overloaded when designing the API.
|
||||||
|
|
||||||
|
let cfg = config.clone();
|
||||||
|
engine.register_fn("config_add", move |value: String|
|
||||||
|
cfg.borrow_mut().some_list.push(value)
|
||||||
|
);
|
||||||
|
|
||||||
|
let cfg = config.clone();
|
||||||
|
engine.register_fn("config_add", move |values: &mut Array|
|
||||||
|
cfg.borrow_mut().some_list.extend(values.into_iter().map(|v| v.to_string()))
|
||||||
|
);
|
||||||
|
|
||||||
|
let cfg = config.clone();
|
||||||
|
engine.register_fn("config_add", move |key: String, value: bool|
|
||||||
|
cfg.borrow_mut().som_map.insert(key, value)
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
### Configuration Script
|
||||||
|
|
||||||
|
```rust
|
||||||
|
------------------
|
||||||
|
| my_config.rhai |
|
||||||
|
------------------
|
||||||
|
|
||||||
|
config_set_id("hello");
|
||||||
|
|
||||||
|
config_add("foo"); // add to list
|
||||||
|
config_add("bar", true); // add to map
|
||||||
|
config_add("baz", false); // add to map
|
||||||
|
```
|
||||||
|
|
||||||
|
### Load the Configuration
|
||||||
|
|
||||||
|
```rust
|
||||||
|
import "my_config"; // run configuration script without creating a module
|
||||||
|
|
||||||
|
let id = config_get_id();
|
||||||
|
|
||||||
|
id == "hello";
|
||||||
|
```
|
131
doc/src/patterns/control.md
Normal file
131
doc/src/patterns/control.md
Normal file
@ -0,0 +1,131 @@
|
|||||||
|
Scriptable Control Layer
|
||||||
|
========================
|
||||||
|
|
||||||
|
{{#include ../links.md}}
|
||||||
|
|
||||||
|
|
||||||
|
Usage Scenario
|
||||||
|
--------------
|
||||||
|
|
||||||
|
* A system provides core functionalities, but no driving logic.
|
||||||
|
|
||||||
|
* The driving logic must be dynamic and hot-loadable.
|
||||||
|
|
||||||
|
* A script is used to drive the system and provide control intelligence.
|
||||||
|
|
||||||
|
|
||||||
|
Key Concepts
|
||||||
|
------------
|
||||||
|
|
||||||
|
* Expose a Control API.
|
||||||
|
|
||||||
|
* Leverage [function overloading] to simplify the API design.
|
||||||
|
|
||||||
|
* Since Rhai is _[sand-boxed]_, it cannot mutate the environment. To perform external actions via an API, the actual system must be wrapped in a `RefCell` (or `RwLock`/`Mutex` for [`sync`]) and shared to the [`Engine`].
|
||||||
|
|
||||||
|
|
||||||
|
Implementation
|
||||||
|
--------------
|
||||||
|
|
||||||
|
There are two broad ways for Rhai to control an external system, both of which involve
|
||||||
|
wrapping the system in a shared, interior-mutated object.
|
||||||
|
|
||||||
|
This is one way which does not involve exposing the data structures of the external system,
|
||||||
|
but only through exposing an abstract API primarily made up of functions.
|
||||||
|
|
||||||
|
Use this when the API is relatively simple and clean, and the number of functions is small enough.
|
||||||
|
|
||||||
|
For a complex API involving lots of functions, or an API that is object-based,
|
||||||
|
use the [Singleton Command Object]({{rootUrl}}/patterns/singleton.md) pattern instead.
|
||||||
|
|
||||||
|
|
||||||
|
### Functional API
|
||||||
|
|
||||||
|
Assume that a system provides the following functional API:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
struct EnergizerBunny;
|
||||||
|
|
||||||
|
impl EnergizerBunny {
|
||||||
|
pub fn new () -> Self { ... }
|
||||||
|
pub fn go (&mut self) { ... }
|
||||||
|
pub fn stop (&mut self) { ... }
|
||||||
|
pub fn is_going (&self) { ... }
|
||||||
|
pub fn get_speed (&self) -> i64 { ... }
|
||||||
|
pub fn set_speed (&mut self, speed: i64) { ... }
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Wrap API in Shared Object
|
||||||
|
|
||||||
|
```rust
|
||||||
|
let bunny: Rc<RefCell<EnergizerBunny>> = Rc::new(RefCell::(EnergizerBunny::new()));
|
||||||
|
```
|
||||||
|
|
||||||
|
### Register Control API
|
||||||
|
|
||||||
|
```rust
|
||||||
|
// Notice 'move' is used to move the shared API object into the closure.
|
||||||
|
let b = bunny.clone();
|
||||||
|
engine.register_fn("bunny_power", move |on: bool| {
|
||||||
|
if on {
|
||||||
|
if b.borrow().is_going() {
|
||||||
|
println!("Still going...");
|
||||||
|
} else {
|
||||||
|
b.borrow_mut().go();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if b.borrow().is_going() {
|
||||||
|
b.borrow_mut().stop();
|
||||||
|
} else {
|
||||||
|
println!("Already out of battery!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let b = bunny.clone();
|
||||||
|
engine.register_fn("bunny_is_going", move || b.borrow().is_going());
|
||||||
|
|
||||||
|
let b = bunny.clone();
|
||||||
|
engine.register_fn("bunny_get_speed", move ||
|
||||||
|
if b.borrow().is_going() { b.borrow().get_speed() } else { 0 }
|
||||||
|
);
|
||||||
|
|
||||||
|
let b = bunny.clone();
|
||||||
|
engine.register_result_fn("bunny_set_speed", move |speed: i64|
|
||||||
|
if speed <= 0 {
|
||||||
|
return Err("Speed must be positive!".into());
|
||||||
|
} else if speed > 100 {
|
||||||
|
return Err("Bunny will be going too fast!".into());
|
||||||
|
}
|
||||||
|
|
||||||
|
if b.borrow().is_going() {
|
||||||
|
b.borrow_mut().set_speed(speed)
|
||||||
|
} else {
|
||||||
|
return Err("Bunny is not yet going!".into());
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(().into())
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
### Use the API
|
||||||
|
|
||||||
|
```rust
|
||||||
|
if !bunny_is_going() { bunny_power(true); }
|
||||||
|
|
||||||
|
if bunny_get_speed() > 50 { bunny_set_speed(50); }
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
Caveat
|
||||||
|
------
|
||||||
|
|
||||||
|
Although this usage pattern appears a perfect fit for _game_ logic, avoid writing the
|
||||||
|
_entire game_ in Rhai. Performance will not be acceptable.
|
||||||
|
|
||||||
|
Implement as much functionalities of the game engine in Rust as possible.
|
||||||
|
Rhai integrates well with Rust so this is usually not a hinderance.
|
||||||
|
|
||||||
|
Lift as much out of Rhai as possible.
|
||||||
|
Use Rhai only for the logic that _must_ be dynamic or hot-loadable.
|
7
doc/src/patterns/index.md
Normal file
7
doc/src/patterns/index.md
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
Advanced Patterns
|
||||||
|
=================
|
||||||
|
|
||||||
|
{{#include ../links.md}}
|
||||||
|
|
||||||
|
|
||||||
|
Leverage the full power and flexibility of Rhai in advanced scenarios.
|
@ -18,17 +18,17 @@ Rhai's [object maps] has [special support for OOP]({{rootUrl}}/language/object-m
|
|||||||
| [Object map] properties that hold [function pointers] | methods |
|
| [Object map] properties that hold [function pointers] | methods |
|
||||||
|
|
||||||
When a property of an [object map] is called like a method function, and if it happens to hold
|
When a property of an [object map] is called like a method function, and if it happens to hold
|
||||||
a valid [function pointer] (perhaps defined via an [anonymous function]), then the call will be
|
a valid [function pointer] (perhaps defined via an [anonymous function] or more commonly as a [closure]),
|
||||||
dispatched to the actual function with `this` binding to the [object map] itself.
|
then the call will be dispatched to the actual function with `this` binding to the [object map] itself.
|
||||||
|
|
||||||
|
|
||||||
Use Anonymous Functions to Define Methods
|
Use Anonymous Functions to Define Methods
|
||||||
----------------------------------------
|
----------------------------------------
|
||||||
|
|
||||||
[Anonymous functions] defined as values for [object map] properties take on a syntactic shape
|
[Anonymous functions] or [closures] defined as values for [object map] properties take on
|
||||||
that resembles very closely that of class methods in an OOP language.
|
a syntactic shape that resembles very closely that of class methods in an OOP language.
|
||||||
|
|
||||||
Anonymous functions can also _capture_ variables from the defining environment, which is a very
|
Closures also _[capture][automatic currying]_ variables from the defining environment, which is a very
|
||||||
common OOP pattern. Capturing is accomplished via a feature called _[automatic currying]_ and
|
common OOP pattern. Capturing is accomplished via a feature called _[automatic currying]_ and
|
||||||
can be turned off via the [`no_closure`] feature.
|
can be turned off via the [`no_closure`] feature.
|
||||||
|
|
||||||
@ -40,9 +40,8 @@ Examples
|
|||||||
let factor = 1;
|
let factor = 1;
|
||||||
|
|
||||||
// Define the object
|
// Define the object
|
||||||
let obj =
|
let obj = #{
|
||||||
#{
|
data: 0, // object field
|
||||||
data: 0,
|
|
||||||
increment: |x| this.data += x, // 'this' binds to 'obj'
|
increment: |x| this.data += x, // 'this' binds to 'obj'
|
||||||
update: |x| this.data = x * factor, // 'this' binds to 'obj', 'factor' is captured
|
update: |x| this.data = x * factor, // 'this' binds to 'obj', 'factor' is captured
|
||||||
action: || print(this.data) // 'this' binds to 'obj'
|
action: || print(this.data) // 'this' binds to 'obj'
|
150
doc/src/patterns/singleton.md
Normal file
150
doc/src/patterns/singleton.md
Normal file
@ -0,0 +1,150 @@
|
|||||||
|
Singleton Command Object
|
||||||
|
=======================
|
||||||
|
|
||||||
|
{{#include ../links.md}}
|
||||||
|
|
||||||
|
|
||||||
|
Usage Scenario
|
||||||
|
--------------
|
||||||
|
|
||||||
|
* A system provides core functionalities, but no driving logic.
|
||||||
|
|
||||||
|
* The driving logic must be dynamic and hot-loadable.
|
||||||
|
|
||||||
|
* A script is used to drive the system and provide control intelligence.
|
||||||
|
|
||||||
|
* The API is multiplexed, meaning that it can act on multiple system-provided entities, or
|
||||||
|
|
||||||
|
* The API lends itself readily to an object-oriented (OO) representation.
|
||||||
|
|
||||||
|
|
||||||
|
Key Concepts
|
||||||
|
------------
|
||||||
|
|
||||||
|
* Expose a Command type with an API. The [`no_object`] feature must not be on.
|
||||||
|
|
||||||
|
* Leverage [function overloading] to simplify the API design.
|
||||||
|
|
||||||
|
* Since Rhai is _[sand-boxed]_, it cannot mutate the environment. To perform external actions via an API, the command object type must be wrapped in a `RefCell` (or `RwLock`/`Mutex` for [`sync`]) and shared to the [`Engine`].
|
||||||
|
|
||||||
|
* Load each command object into a custom [`Scope`] as constant variables.
|
||||||
|
|
||||||
|
* Control each command object in script via the constants.
|
||||||
|
|
||||||
|
|
||||||
|
Implementation
|
||||||
|
--------------
|
||||||
|
|
||||||
|
There are two broad ways for Rhai to control an external system, both of which involve
|
||||||
|
wrapping the system in a shared, interior-mutated object.
|
||||||
|
|
||||||
|
This is the other way which involves directly exposing the data structures of the external system
|
||||||
|
as a name singleton object in the scripting space.
|
||||||
|
|
||||||
|
Use this when the API is complex and clearly object-based.
|
||||||
|
|
||||||
|
For a relatively simple API that is action-based and not object-based,
|
||||||
|
use the [Control Layer]({{rootUrl}}/patterns/control.md) pattern instead.
|
||||||
|
|
||||||
|
|
||||||
|
### Functional API
|
||||||
|
|
||||||
|
Assume the following command object type:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
struct EnergizerBunny { ... }
|
||||||
|
|
||||||
|
impl EnergizerBunny {
|
||||||
|
pub fn new () -> Self { ... }
|
||||||
|
pub fn go (&mut self) { ... }
|
||||||
|
pub fn stop (&mut self) { ... }
|
||||||
|
pub fn is_going (&self) { ... }
|
||||||
|
pub fn get_speed (&self) -> i64 { ... }
|
||||||
|
pub fn set_speed (&mut self, speed: i64) { ... }
|
||||||
|
pub fn turn (&mut self, left_turn: bool) { ... }
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Wrap Command Object Type as Shared
|
||||||
|
|
||||||
|
```rust
|
||||||
|
let SharedBunnyType = Rc<RefCell<EnergizerBunny>>;
|
||||||
|
```
|
||||||
|
|
||||||
|
### Register the Custom Type
|
||||||
|
|
||||||
|
```rust
|
||||||
|
engine.register_type_with_name::<SharedBunnyType>("EnergizerBunny");
|
||||||
|
```
|
||||||
|
|
||||||
|
### Register Methods and Getters/Setters
|
||||||
|
|
||||||
|
```rust
|
||||||
|
engine
|
||||||
|
.register_get_set("power",
|
||||||
|
|bunny: &mut SharedBunnyType| bunny.borrow().is_going(),
|
||||||
|
|bunny: &mut SharedBunnyType, on: bool| {
|
||||||
|
if on {
|
||||||
|
if bunny.borrow().is_going() {
|
||||||
|
println!("Still going...");
|
||||||
|
} else {
|
||||||
|
bunny.borrow_mut().go();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if bunny.borrow().is_going() {
|
||||||
|
bunny.borrow_mut().stop();
|
||||||
|
} else {
|
||||||
|
println!("Already out of battery!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
).register_get("speed", |bunny: &mut SharedBunnyType| {
|
||||||
|
if bunny.borrow().is_going() {
|
||||||
|
bunny.borrow().get_speed()
|
||||||
|
} else {
|
||||||
|
0
|
||||||
|
}
|
||||||
|
}).register_set_result("speed", |bunny: &mut SharedBunnyType, speed: i64| {
|
||||||
|
if speed <= 0 {
|
||||||
|
Err("Speed must be positive!".into())
|
||||||
|
} else if speed > 100 {
|
||||||
|
Err("Bunny will be going too fast!".into())
|
||||||
|
} else if !bunny.borrow().is_going() {
|
||||||
|
Err("Bunny is not yet going!".into())
|
||||||
|
} else {
|
||||||
|
b.borrow_mut().set_speed(speed);
|
||||||
|
Ok(().into())
|
||||||
|
}
|
||||||
|
}).register_fn("turn_left", |bunny: &mut SharedBunnyType| {
|
||||||
|
if bunny.borrow().is_going() {
|
||||||
|
bunny.borrow_mut().turn(true);
|
||||||
|
}
|
||||||
|
}).register_fn("turn_right", |bunny: &mut SharedBunnyType| {
|
||||||
|
if bunny.borrow().is_going() {
|
||||||
|
bunny.borrow_mut().turn(false);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
### Push Constant Command Object into Custom Scope
|
||||||
|
|
||||||
|
```rust
|
||||||
|
let bunny: SharedBunnyType = Rc::new(RefCell::(EnergizerBunny::new()));
|
||||||
|
|
||||||
|
let mut scope = Scope::new();
|
||||||
|
scope.push_constant("BUNNY", bunny.clone());
|
||||||
|
|
||||||
|
engine.consume_with_scope(&mut scope, script)?;
|
||||||
|
```
|
||||||
|
|
||||||
|
### Use the Command API in Script
|
||||||
|
|
||||||
|
```rust
|
||||||
|
// Access the command object via constant variable 'BUNNY'.
|
||||||
|
|
||||||
|
if !BUNNY.power { BUNNY.power = true; }
|
||||||
|
|
||||||
|
if BUNNY.speed > 50 { BUNNY.speed = 50; }
|
||||||
|
|
||||||
|
BUNNY.turn_left();
|
||||||
|
```
|
@ -7,7 +7,7 @@ Rhai's scripting engine is very lightweight. It gets most of its abilities from
|
|||||||
|
|
||||||
To call these functions, they need to be _registered_ with the [`Engine`] using `Engine::register_fn`
|
To call these functions, they need to be _registered_ with the [`Engine`] using `Engine::register_fn`
|
||||||
(in the `RegisterFn` trait) and `Engine::register_result_fn` (in the `RegisterResultFn` trait,
|
(in the `RegisterFn` trait) and `Engine::register_result_fn` (in the `RegisterResultFn` trait,
|
||||||
see [fallible functions]({{rootUrl}}/rust/fallible.md)).
|
see [fallible functions]).
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
use rhai::{Dynamic, Engine, EvalAltResult, ImmutableString};
|
use rhai::{Dynamic, Engine, EvalAltResult, ImmutableString};
|
||||||
@ -62,8 +62,12 @@ let x = (42_i64).into(); // 'into()' works for standard t
|
|||||||
let y = Dynamic::from("hello!".to_string()); // remember &str is not supported by Rhai
|
let y = Dynamic::from("hello!".to_string()); // remember &str is not supported by Rhai
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
|
Function Overloading
|
||||||
|
--------------------
|
||||||
|
|
||||||
Functions registered with the [`Engine`] can be _overloaded_ as long as the _signature_ is unique,
|
Functions registered with the [`Engine`] can be _overloaded_ as long as the _signature_ is unique,
|
||||||
i.e. different functions can have the same name as long as their parameters are of different types
|
i.e. different functions can have the same name as long as their parameters are of different types
|
||||||
and/or different number.
|
or different number.
|
||||||
|
|
||||||
New definitions _overwrite_ previous definitions of the same name and same number/types of parameters.
|
New definitions _overwrite_ previous definitions of the same name and same number/types of parameters.
|
||||||
|
@ -5,7 +5,9 @@ Custom Type Indexers
|
|||||||
|
|
||||||
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_ `]`
|
||||||
|
|
||||||
Like getters and setters, indexers take a `&mut` reference to the first parameter.
|
Like getters and setters, indexers take a `&mut` reference to the first parameter.
|
||||||
|
|
||||||
|
@ -91,7 +91,7 @@ struct MyStruct {
|
|||||||
let engine = Engine::new();
|
let engine = Engine::new();
|
||||||
|
|
||||||
let result: Dynamic = engine.eval(r#"
|
let result: Dynamic = engine.eval(r#"
|
||||||
#{
|
##{
|
||||||
a: 42,
|
a: 42,
|
||||||
b: [ "hello", "world" ],
|
b: [ "hello", "world" ],
|
||||||
c: true,
|
c: true,
|
||||||
|
@ -5,13 +5,31 @@ Sand-Boxing - Block Access to External Data
|
|||||||
|
|
||||||
Rhai is _sand-boxed_ so a script can never read from outside its own environment.
|
Rhai is _sand-boxed_ so a script can never read from outside its own environment.
|
||||||
|
|
||||||
Furthermore, an [`Engine`] created non-`mut` cannot mutate any state outside of itself;
|
Furthermore, an [`Engine`] created non-`mut` cannot mutate any state, including itself
|
||||||
so it is highly recommended that [`Engine`]'s are created immutable as much as possible.
|
(and therefore it is also _re-entrant_).
|
||||||
|
|
||||||
|
It is highly recommended that [`Engine`]'s be created immutable as much as possible.
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
let mut engine = Engine::new(); // create mutable 'Engine'
|
// Use the fluent API to configure an 'Engine' and then keep an immutable instance.
|
||||||
|
let engine = Engine::new()
|
||||||
|
.register_get("field", get_field)
|
||||||
|
.register_set("field", set_field)
|
||||||
|
.register_fn("do_work", action);
|
||||||
|
|
||||||
engine.register_get("add", add); // configure 'engine'
|
// 'engine' is immutable...
|
||||||
|
|
||||||
let engine = engine; // shadow the variable so that 'engine' is now immutable
|
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
|
Using Rhai to Control External Environment
|
||||||
|
-----------------------------------------
|
||||||
|
|
||||||
|
How does a _sand-boxed_, immutable [`Engine`] control the external environment?
|
||||||
|
This is necessary in order to use Rhai as a _dynamic control layer_ over a Rust core system.
|
||||||
|
|
||||||
|
There are two general patterns, both involving wrapping the external system
|
||||||
|
in a shared, interior-mutated object (e.g. `Rc<RefCell<T>>`):
|
||||||
|
|
||||||
|
* [Control Layer]({{rootUrl}}/patterns/control.md) pattern.
|
||||||
|
|
||||||
|
* [Singleton Command Object]({{rootUrl}}/patterns/singleton.md) pattern.
|
||||||
|
@ -56,7 +56,7 @@ mod example {
|
|||||||
let result: Dynamic = engine
|
let result: Dynamic = engine
|
||||||
.eval(
|
.eval(
|
||||||
r#"
|
r#"
|
||||||
#{
|
##{
|
||||||
a: 42,
|
a: 42,
|
||||||
b: [ "hello", "world" ],
|
b: [ "hello", "world" ],
|
||||||
c: true,
|
c: true,
|
||||||
|
80
src/any.rs
80
src/any.rs
@ -5,7 +5,7 @@ use crate::parser::{ImmutableString, INT};
|
|||||||
use crate::r#unsafe::{unsafe_cast_box, unsafe_try_cast};
|
use crate::r#unsafe::{unsafe_cast_box, unsafe_try_cast};
|
||||||
|
|
||||||
#[cfg(not(feature = "no_closure"))]
|
#[cfg(not(feature = "no_closure"))]
|
||||||
use crate::fn_native::SharedMut;
|
use crate::fn_native::{shared_try_take, Shared};
|
||||||
|
|
||||||
#[cfg(not(feature = "no_float"))]
|
#[cfg(not(feature = "no_float"))]
|
||||||
use crate::parser::FLOAT;
|
use crate::parser::FLOAT;
|
||||||
@ -134,6 +134,7 @@ impl<T: Any + Clone + SendSync> Variant for T {
|
|||||||
|
|
||||||
impl dyn Variant {
|
impl dyn Variant {
|
||||||
/// Is this `Variant` a specific type?
|
/// Is this `Variant` a specific type?
|
||||||
|
#[inline(always)]
|
||||||
pub fn is<T: Any>(&self) -> bool {
|
pub fn is<T: Any>(&self) -> bool {
|
||||||
TypeId::of::<T>() == self.type_id()
|
TypeId::of::<T>() == self.type_id()
|
||||||
}
|
}
|
||||||
@ -158,9 +159,15 @@ pub enum Union {
|
|||||||
#[cfg(not(feature = "no_object"))]
|
#[cfg(not(feature = "no_object"))]
|
||||||
Map(Box<Map>),
|
Map(Box<Map>),
|
||||||
FnPtr(Box<FnPtr>),
|
FnPtr(Box<FnPtr>),
|
||||||
|
|
||||||
Variant(Box<Box<dyn Variant>>),
|
Variant(Box<Box<dyn Variant>>),
|
||||||
|
|
||||||
#[cfg(not(feature = "no_closure"))]
|
#[cfg(not(feature = "no_closure"))]
|
||||||
Shared(SharedMut<Dynamic>),
|
#[cfg(not(feature = "sync"))]
|
||||||
|
Shared(Shared<RefCell<Dynamic>>),
|
||||||
|
#[cfg(not(feature = "no_closure"))]
|
||||||
|
#[cfg(feature = "sync")]
|
||||||
|
Shared(Shared<RwLock<Dynamic>>),
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Underlying `Variant` read guard for `Dynamic`.
|
/// Underlying `Variant` read guard for `Dynamic`.
|
||||||
@ -175,6 +182,7 @@ pub struct DynamicReadLock<'d, T: Variant + Clone>(DynamicReadLockInner<'d, T>);
|
|||||||
enum DynamicReadLockInner<'d, T: Variant + Clone> {
|
enum DynamicReadLockInner<'d, T: Variant + Clone> {
|
||||||
/// A simple reference to a non-shared value.
|
/// A simple reference to a non-shared value.
|
||||||
Reference(&'d T),
|
Reference(&'d T),
|
||||||
|
|
||||||
/// A read guard to a shared `RefCell`.
|
/// A read guard to a shared `RefCell`.
|
||||||
#[cfg(not(feature = "no_closure"))]
|
#[cfg(not(feature = "no_closure"))]
|
||||||
#[cfg(not(feature = "sync"))]
|
#[cfg(not(feature = "sync"))]
|
||||||
@ -211,6 +219,7 @@ pub struct DynamicWriteLock<'d, T: Variant + Clone>(DynamicWriteLockInner<'d, T>
|
|||||||
enum DynamicWriteLockInner<'d, T: Variant + Clone> {
|
enum DynamicWriteLockInner<'d, T: Variant + Clone> {
|
||||||
/// A simple mutable reference to a non-shared value.
|
/// A simple mutable reference to a non-shared value.
|
||||||
Reference(&'d mut T),
|
Reference(&'d mut T),
|
||||||
|
|
||||||
/// A write guard to a shared `RefCell`.
|
/// A write guard to a shared `RefCell`.
|
||||||
#[cfg(not(feature = "no_closure"))]
|
#[cfg(not(feature = "no_closure"))]
|
||||||
#[cfg(not(feature = "sync"))]
|
#[cfg(not(feature = "sync"))]
|
||||||
@ -250,6 +259,7 @@ impl<'d, T: Variant + Clone> DerefMut for DynamicWriteLock<'d, T> {
|
|||||||
impl Dynamic {
|
impl Dynamic {
|
||||||
/// Does this `Dynamic` hold a variant data type
|
/// Does this `Dynamic` hold a variant data type
|
||||||
/// instead of one of the support system primitive types?
|
/// instead of one of the support system primitive types?
|
||||||
|
#[inline(always)]
|
||||||
pub fn is_variant(&self) -> bool {
|
pub fn is_variant(&self) -> bool {
|
||||||
match self.0 {
|
match self.0 {
|
||||||
Union::Variant(_) => true,
|
Union::Variant(_) => true,
|
||||||
@ -259,6 +269,7 @@ impl Dynamic {
|
|||||||
|
|
||||||
/// Does this `Dynamic` hold a shared data type
|
/// Does this `Dynamic` hold a shared data type
|
||||||
/// instead of one of the supported system primitive types?
|
/// instead of one of the supported system primitive types?
|
||||||
|
#[inline(always)]
|
||||||
pub fn is_shared(&self) -> bool {
|
pub fn is_shared(&self) -> bool {
|
||||||
match self.0 {
|
match self.0 {
|
||||||
#[cfg(not(feature = "no_closure"))]
|
#[cfg(not(feature = "no_closure"))]
|
||||||
@ -271,6 +282,7 @@ impl Dynamic {
|
|||||||
///
|
///
|
||||||
/// If the `Dynamic` is a Shared variant checking is performed on
|
/// If the `Dynamic` is a Shared variant checking is performed on
|
||||||
/// top of it's internal value.
|
/// top of it's internal value.
|
||||||
|
#[inline(always)]
|
||||||
pub fn is<T: Variant + Clone>(&self) -> bool {
|
pub fn is<T: Variant + Clone>(&self) -> bool {
|
||||||
let mut target_type_id = TypeId::of::<T>();
|
let mut target_type_id = TypeId::of::<T>();
|
||||||
|
|
||||||
@ -301,7 +313,9 @@ impl Dynamic {
|
|||||||
#[cfg(not(feature = "no_object"))]
|
#[cfg(not(feature = "no_object"))]
|
||||||
Union::Map(_) => TypeId::of::<Map>(),
|
Union::Map(_) => TypeId::of::<Map>(),
|
||||||
Union::FnPtr(_) => TypeId::of::<FnPtr>(),
|
Union::FnPtr(_) => TypeId::of::<FnPtr>(),
|
||||||
|
|
||||||
Union::Variant(value) => (***value).type_id(),
|
Union::Variant(value) => (***value).type_id(),
|
||||||
|
|
||||||
#[cfg(not(feature = "no_closure"))]
|
#[cfg(not(feature = "no_closure"))]
|
||||||
#[cfg(not(feature = "sync"))]
|
#[cfg(not(feature = "sync"))]
|
||||||
Union::Shared(cell) => (*cell.borrow()).type_id(),
|
Union::Shared(cell) => (*cell.borrow()).type_id(),
|
||||||
@ -335,6 +349,7 @@ impl Dynamic {
|
|||||||
#[cfg(not(feature = "no_std"))]
|
#[cfg(not(feature = "no_std"))]
|
||||||
Union::Variant(value) if value.is::<Instant>() => "timestamp",
|
Union::Variant(value) if value.is::<Instant>() => "timestamp",
|
||||||
Union::Variant(value) => (***value).type_name(),
|
Union::Variant(value) => (***value).type_name(),
|
||||||
|
|
||||||
#[cfg(not(feature = "no_closure"))]
|
#[cfg(not(feature = "no_closure"))]
|
||||||
#[cfg(not(feature = "sync"))]
|
#[cfg(not(feature = "sync"))]
|
||||||
Union::Shared(cell) => cell
|
Union::Shared(cell) => cell
|
||||||
@ -399,6 +414,7 @@ impl fmt::Display for Dynamic {
|
|||||||
#[cfg(not(feature = "no_std"))]
|
#[cfg(not(feature = "no_std"))]
|
||||||
Union::Variant(value) if value.is::<Instant>() => f.write_str("<timestamp>"),
|
Union::Variant(value) if value.is::<Instant>() => f.write_str("<timestamp>"),
|
||||||
Union::Variant(value) => f.write_str((*value).type_name()),
|
Union::Variant(value) => f.write_str((*value).type_name()),
|
||||||
|
|
||||||
#[cfg(not(feature = "no_closure"))]
|
#[cfg(not(feature = "no_closure"))]
|
||||||
#[cfg(not(feature = "sync"))]
|
#[cfg(not(feature = "sync"))]
|
||||||
Union::Shared(cell) => {
|
Union::Shared(cell) => {
|
||||||
@ -437,6 +453,7 @@ impl fmt::Debug for Dynamic {
|
|||||||
#[cfg(not(feature = "no_std"))]
|
#[cfg(not(feature = "no_std"))]
|
||||||
Union::Variant(value) if value.is::<Instant>() => write!(f, "<timestamp>"),
|
Union::Variant(value) if value.is::<Instant>() => write!(f, "<timestamp>"),
|
||||||
Union::Variant(value) => write!(f, "{}", (*value).type_name()),
|
Union::Variant(value) => write!(f, "{}", (*value).type_name()),
|
||||||
|
|
||||||
#[cfg(not(feature = "no_closure"))]
|
#[cfg(not(feature = "no_closure"))]
|
||||||
#[cfg(not(feature = "sync"))]
|
#[cfg(not(feature = "sync"))]
|
||||||
Union::Shared(cell) => {
|
Union::Shared(cell) => {
|
||||||
@ -468,7 +485,9 @@ impl Clone for Dynamic {
|
|||||||
#[cfg(not(feature = "no_object"))]
|
#[cfg(not(feature = "no_object"))]
|
||||||
Union::Map(ref value) => Self(Union::Map(value.clone())),
|
Union::Map(ref value) => Self(Union::Map(value.clone())),
|
||||||
Union::FnPtr(ref value) => Self(Union::FnPtr(value.clone())),
|
Union::FnPtr(ref value) => Self(Union::FnPtr(value.clone())),
|
||||||
|
|
||||||
Union::Variant(ref value) => (***value).clone_into_dynamic(),
|
Union::Variant(ref value) => (***value).clone_into_dynamic(),
|
||||||
|
|
||||||
#[cfg(not(feature = "no_closure"))]
|
#[cfg(not(feature = "no_closure"))]
|
||||||
Union::Shared(ref cell) => Self(Union::Shared(cell.clone())),
|
Union::Shared(ref cell) => Self(Union::Shared(cell.clone())),
|
||||||
}
|
}
|
||||||
@ -476,6 +495,7 @@ impl Clone for Dynamic {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Default for Dynamic {
|
impl Default for Dynamic {
|
||||||
|
#[inline(always)]
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self(Union::Unit(()))
|
Self(Union::Unit(()))
|
||||||
}
|
}
|
||||||
@ -769,25 +789,46 @@ impl Dynamic {
|
|||||||
self.try_cast::<T>().unwrap()
|
self.try_cast::<T>().unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get a copy of the `Dynamic` as a specific type.
|
/// Flatten the `Dynamic` and clone it.
|
||||||
///
|
///
|
||||||
/// If the `Dynamic` is not a shared value, it returns a cloned copy of the value.
|
/// If the `Dynamic` is not a shared value, it returns a cloned copy.
|
||||||
///
|
///
|
||||||
/// If the `Dynamic` is a shared value, it returns a cloned copy of the shared value.
|
/// If the `Dynamic` is a shared value, it a cloned copy of the shared value.
|
||||||
///
|
|
||||||
/// Returns `None` if the cast fails.
|
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn clone_inner_data<T: Variant + Clone>(self) -> Option<T> {
|
pub fn flatten_clone(&self) -> Self {
|
||||||
|
match &self.0 {
|
||||||
|
#[cfg(not(feature = "no_closure"))]
|
||||||
|
Union::Shared(cell) => {
|
||||||
|
#[cfg(not(feature = "sync"))]
|
||||||
|
return cell.borrow().clone();
|
||||||
|
|
||||||
|
#[cfg(feature = "sync")]
|
||||||
|
return cell.read().unwrap().clone();
|
||||||
|
}
|
||||||
|
_ => self.clone(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Flatten the `Dynamic`.
|
||||||
|
///
|
||||||
|
/// If the `Dynamic` is not a shared value, it returns itself.
|
||||||
|
///
|
||||||
|
/// If the `Dynamic` is a shared value, it returns the shared value if there are
|
||||||
|
/// no outstanding references, or a cloned copy.
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn flatten(self) -> Self {
|
||||||
match self.0 {
|
match self.0 {
|
||||||
#[cfg(not(feature = "no_closure"))]
|
#[cfg(not(feature = "no_closure"))]
|
||||||
Union::Shared(cell) => {
|
Union::Shared(cell) => {
|
||||||
#[cfg(not(feature = "sync"))]
|
#[cfg(not(feature = "sync"))]
|
||||||
return Some(cell.borrow().downcast_ref::<T>().unwrap().clone());
|
return shared_try_take(cell)
|
||||||
|
.map_or_else(|c| c.borrow().clone(), RefCell::into_inner);
|
||||||
|
|
||||||
#[cfg(feature = "sync")]
|
#[cfg(feature = "sync")]
|
||||||
return Some(cell.read().unwrap().downcast_ref::<T>().unwrap().clone());
|
return shared_try_take(cell)
|
||||||
|
.map_or_else(|c| c.read().unwrap().clone(), |v| v.into_inner().unwrap());
|
||||||
}
|
}
|
||||||
_ => self.try_cast(),
|
_ => self,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1043,6 +1084,7 @@ impl Dynamic {
|
|||||||
|
|
||||||
/// Cast the `Dynamic` as the system integer type `INT` and return it.
|
/// Cast the `Dynamic` as the system integer type `INT` and return it.
|
||||||
/// Returns the name of the actual type if the cast fails.
|
/// Returns the name of the actual type if the cast fails.
|
||||||
|
#[inline(always)]
|
||||||
pub fn as_int(&self) -> Result<INT, &'static str> {
|
pub fn as_int(&self) -> Result<INT, &'static str> {
|
||||||
match self.0 {
|
match self.0 {
|
||||||
Union::Int(n) => Ok(n),
|
Union::Int(n) => Ok(n),
|
||||||
@ -1055,6 +1097,7 @@ impl Dynamic {
|
|||||||
/// Cast the `Dynamic` as the system floating-point type `FLOAT` and return it.
|
/// Cast the `Dynamic` as the system floating-point type `FLOAT` and return it.
|
||||||
/// Returns the name of the actual type if the cast fails.
|
/// Returns the name of the actual type if the cast fails.
|
||||||
#[cfg(not(feature = "no_float"))]
|
#[cfg(not(feature = "no_float"))]
|
||||||
|
#[inline(always)]
|
||||||
pub fn as_float(&self) -> Result<FLOAT, &'static str> {
|
pub fn as_float(&self) -> Result<FLOAT, &'static str> {
|
||||||
match self.0 {
|
match self.0 {
|
||||||
Union::Float(n) => Ok(n),
|
Union::Float(n) => Ok(n),
|
||||||
@ -1066,6 +1109,7 @@ impl Dynamic {
|
|||||||
|
|
||||||
/// Cast the `Dynamic` as a `bool` and return it.
|
/// Cast the `Dynamic` as a `bool` and return it.
|
||||||
/// Returns the name of the actual type if the cast fails.
|
/// Returns the name of the actual type if the cast fails.
|
||||||
|
#[inline(always)]
|
||||||
pub fn as_bool(&self) -> Result<bool, &'static str> {
|
pub fn as_bool(&self) -> Result<bool, &'static str> {
|
||||||
match self.0 {
|
match self.0 {
|
||||||
Union::Bool(b) => Ok(b),
|
Union::Bool(b) => Ok(b),
|
||||||
@ -1077,6 +1121,7 @@ impl Dynamic {
|
|||||||
|
|
||||||
/// Cast the `Dynamic` as a `char` and return it.
|
/// Cast the `Dynamic` as a `char` and return it.
|
||||||
/// Returns the name of the actual type if the cast fails.
|
/// Returns the name of the actual type if the cast fails.
|
||||||
|
#[inline(always)]
|
||||||
pub fn as_char(&self) -> Result<char, &'static str> {
|
pub fn as_char(&self) -> Result<char, &'static str> {
|
||||||
match self.0 {
|
match self.0 {
|
||||||
Union::Char(n) => Ok(n),
|
Union::Char(n) => Ok(n),
|
||||||
@ -1090,6 +1135,7 @@ impl Dynamic {
|
|||||||
/// Returns the name of the actual type if the cast fails.
|
/// Returns the name of the actual type if the cast fails.
|
||||||
///
|
///
|
||||||
/// Cast is failing if `self` is Shared Dynamic
|
/// Cast is failing if `self` is Shared Dynamic
|
||||||
|
#[inline(always)]
|
||||||
pub fn as_str(&self) -> Result<&str, &'static str> {
|
pub fn as_str(&self) -> Result<&str, &'static str> {
|
||||||
match &self.0 {
|
match &self.0 {
|
||||||
Union::Str(s) => Ok(s),
|
Union::Str(s) => Ok(s),
|
||||||
@ -1100,6 +1146,7 @@ impl Dynamic {
|
|||||||
|
|
||||||
/// Convert the `Dynamic` into `String` and return it.
|
/// Convert the `Dynamic` into `String` and return it.
|
||||||
/// Returns the name of the actual type if the cast fails.
|
/// Returns the name of the actual type if the cast fails.
|
||||||
|
#[inline(always)]
|
||||||
pub fn take_string(self) -> Result<String, &'static str> {
|
pub fn take_string(self) -> Result<String, &'static str> {
|
||||||
self.take_immutable_string()
|
self.take_immutable_string()
|
||||||
.map(ImmutableString::into_owned)
|
.map(ImmutableString::into_owned)
|
||||||
@ -1138,38 +1185,45 @@ impl Dynamic {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl From<()> for Dynamic {
|
impl From<()> for Dynamic {
|
||||||
|
#[inline(always)]
|
||||||
fn from(value: ()) -> Self {
|
fn from(value: ()) -> Self {
|
||||||
Self(Union::Unit(value))
|
Self(Union::Unit(value))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl From<bool> for Dynamic {
|
impl From<bool> for Dynamic {
|
||||||
|
#[inline(always)]
|
||||||
fn from(value: bool) -> Self {
|
fn from(value: bool) -> Self {
|
||||||
Self(Union::Bool(value))
|
Self(Union::Bool(value))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl From<INT> for Dynamic {
|
impl From<INT> for Dynamic {
|
||||||
|
#[inline(always)]
|
||||||
fn from(value: INT) -> Self {
|
fn from(value: INT) -> Self {
|
||||||
Self(Union::Int(value))
|
Self(Union::Int(value))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#[cfg(not(feature = "no_float"))]
|
#[cfg(not(feature = "no_float"))]
|
||||||
impl From<FLOAT> for Dynamic {
|
impl From<FLOAT> for Dynamic {
|
||||||
|
#[inline(always)]
|
||||||
fn from(value: FLOAT) -> Self {
|
fn from(value: FLOAT) -> Self {
|
||||||
Self(Union::Float(value))
|
Self(Union::Float(value))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl From<char> for Dynamic {
|
impl From<char> for Dynamic {
|
||||||
|
#[inline(always)]
|
||||||
fn from(value: char) -> Self {
|
fn from(value: char) -> Self {
|
||||||
Self(Union::Char(value))
|
Self(Union::Char(value))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl<S: Into<ImmutableString>> From<S> for Dynamic {
|
impl<S: Into<ImmutableString>> From<S> for Dynamic {
|
||||||
|
#[inline(always)]
|
||||||
fn from(value: S) -> Self {
|
fn from(value: S) -> Self {
|
||||||
Self(Union::Str(value.into()))
|
Self(Union::Str(value.into()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#[cfg(not(feature = "no_index"))]
|
#[cfg(not(feature = "no_index"))]
|
||||||
impl<T: Variant + Clone> From<Vec<T>> for Dynamic {
|
impl<T: Variant + Clone> From<Vec<T>> for Dynamic {
|
||||||
|
#[inline(always)]
|
||||||
fn from(value: Vec<T>) -> Self {
|
fn from(value: Vec<T>) -> Self {
|
||||||
Self(Union::Array(Box::new(
|
Self(Union::Array(Box::new(
|
||||||
value.into_iter().map(Dynamic::from).collect(),
|
value.into_iter().map(Dynamic::from).collect(),
|
||||||
@ -1178,6 +1232,7 @@ impl<T: Variant + Clone> From<Vec<T>> for Dynamic {
|
|||||||
}
|
}
|
||||||
#[cfg(not(feature = "no_index"))]
|
#[cfg(not(feature = "no_index"))]
|
||||||
impl<T: Variant + Clone> From<&[T]> for Dynamic {
|
impl<T: Variant + Clone> From<&[T]> for Dynamic {
|
||||||
|
#[inline(always)]
|
||||||
fn from(value: &[T]) -> Self {
|
fn from(value: &[T]) -> Self {
|
||||||
Self(Union::Array(Box::new(
|
Self(Union::Array(Box::new(
|
||||||
value.iter().cloned().map(Dynamic::from).collect(),
|
value.iter().cloned().map(Dynamic::from).collect(),
|
||||||
@ -1186,6 +1241,7 @@ impl<T: Variant + Clone> From<&[T]> for Dynamic {
|
|||||||
}
|
}
|
||||||
#[cfg(not(feature = "no_object"))]
|
#[cfg(not(feature = "no_object"))]
|
||||||
impl<K: Into<ImmutableString>, T: Variant + Clone> From<HashMap<K, T>> for Dynamic {
|
impl<K: Into<ImmutableString>, T: Variant + Clone> From<HashMap<K, T>> for Dynamic {
|
||||||
|
#[inline(always)]
|
||||||
fn from(value: HashMap<K, T>) -> Self {
|
fn from(value: HashMap<K, T>) -> Self {
|
||||||
Self(Union::Map(Box::new(
|
Self(Union::Map(Box::new(
|
||||||
value
|
value
|
||||||
@ -1196,11 +1252,13 @@ impl<K: Into<ImmutableString>, T: Variant + Clone> From<HashMap<K, T>> for Dynam
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl From<FnPtr> for Dynamic {
|
impl From<FnPtr> for Dynamic {
|
||||||
|
#[inline(always)]
|
||||||
fn from(value: FnPtr) -> Self {
|
fn from(value: FnPtr) -> Self {
|
||||||
Self(Union::FnPtr(Box::new(value)))
|
Self(Union::FnPtr(Box::new(value)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl From<Box<FnPtr>> for Dynamic {
|
impl From<Box<FnPtr>> for Dynamic {
|
||||||
|
#[inline(always)]
|
||||||
fn from(value: Box<FnPtr>) -> Self {
|
fn from(value: Box<FnPtr>) -> Self {
|
||||||
Self(Union::FnPtr(value))
|
Self(Union::FnPtr(value))
|
||||||
}
|
}
|
||||||
|
208
src/api.rs
208
src/api.rs
@ -18,7 +18,7 @@ use crate::engine::{FN_IDX_GET, FN_IDX_SET};
|
|||||||
#[cfg(not(feature = "no_object"))]
|
#[cfg(not(feature = "no_object"))]
|
||||||
use crate::{
|
use crate::{
|
||||||
engine::{make_getter, make_setter, Map},
|
engine::{make_getter, make_setter, Map},
|
||||||
fn_register::RegisterFn,
|
fn_register::{RegisterFn, RegisterResultFn},
|
||||||
token::Token,
|
token::Token,
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -224,6 +224,54 @@ impl Engine {
|
|||||||
self.register_fn(&make_getter(name), callback)
|
self.register_fn(&make_getter(name), callback)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Register a getter function for a member of a registered type with the `Engine`.
|
||||||
|
/// Returns `Result<Dynamic, Box<EvalAltResult>>`.
|
||||||
|
///
|
||||||
|
/// The function signature must start with `&mut self` and not `&self`.
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// use rhai::{Engine, Dynamic, EvalAltResult, RegisterFn};
|
||||||
|
///
|
||||||
|
/// #[derive(Clone)]
|
||||||
|
/// struct TestStruct {
|
||||||
|
/// field: i64
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// impl TestStruct {
|
||||||
|
/// fn new() -> Self { TestStruct { field: 1 } }
|
||||||
|
///
|
||||||
|
/// // Even a getter must start with `&mut self` and not `&self`.
|
||||||
|
/// fn get_field(&mut self) -> Result<Dynamic, Box<EvalAltResult>> {
|
||||||
|
/// Ok(self.field.into())
|
||||||
|
/// }
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// # fn main() -> Result<(), Box<rhai::EvalAltResult>> {
|
||||||
|
/// let mut engine = Engine::new();
|
||||||
|
///
|
||||||
|
/// // Register the custom type.
|
||||||
|
/// engine.register_type::<TestStruct>();
|
||||||
|
///
|
||||||
|
/// engine.register_fn("new_ts", TestStruct::new);
|
||||||
|
///
|
||||||
|
/// // Register a getter on a property (notice it doesn't have to be the same name).
|
||||||
|
/// engine.register_get_result("xyz", TestStruct::get_field);
|
||||||
|
///
|
||||||
|
/// assert_eq!(engine.eval::<i64>("let a = new_ts(); a.xyz")?, 1);
|
||||||
|
/// # Ok(())
|
||||||
|
/// # }
|
||||||
|
/// ```
|
||||||
|
#[cfg(not(feature = "no_object"))]
|
||||||
|
pub fn register_get_result<T: Variant + Clone>(
|
||||||
|
&mut self,
|
||||||
|
name: &str,
|
||||||
|
callback: impl Fn(&mut T) -> Result<Dynamic, Box<EvalAltResult>> + SendSync + 'static,
|
||||||
|
) -> &mut Self {
|
||||||
|
self.register_result_fn(&make_getter(name), callback)
|
||||||
|
}
|
||||||
|
|
||||||
/// Register a setter function for a member of a registered type with the `Engine`.
|
/// Register a setter function for a member of a registered type with the `Engine`.
|
||||||
///
|
///
|
||||||
/// # Example
|
/// # Example
|
||||||
@ -273,6 +321,59 @@ impl Engine {
|
|||||||
self.register_fn(&make_setter(name), callback)
|
self.register_fn(&make_setter(name), callback)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Register a setter function for a member of a registered type with the `Engine`.
|
||||||
|
/// Returns `Result<Dynamic, Box<EvalAltResult>>`.
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// use rhai::{Engine, Dynamic, EvalAltResult, RegisterFn};
|
||||||
|
///
|
||||||
|
/// #[derive(Debug, Clone, Eq, PartialEq)]
|
||||||
|
/// struct TestStruct {
|
||||||
|
/// field: i64
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// impl TestStruct {
|
||||||
|
/// fn new() -> Self { TestStruct { field: 1 } }
|
||||||
|
/// fn set_field(&mut self, new_val: i64) -> Result<Dynamic, Box<EvalAltResult>> {
|
||||||
|
/// self.field = new_val;
|
||||||
|
/// Ok(().into())
|
||||||
|
/// }
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// # fn main() -> Result<(), Box<rhai::EvalAltResult>> {
|
||||||
|
/// let mut engine = Engine::new();
|
||||||
|
///
|
||||||
|
/// // Register the custom type.
|
||||||
|
/// engine.register_type::<TestStruct>();
|
||||||
|
///
|
||||||
|
/// engine.register_fn("new_ts", TestStruct::new);
|
||||||
|
///
|
||||||
|
/// // Register a setter on a property (notice it doesn't have to be the same name)
|
||||||
|
/// engine.register_set_result("xyz", TestStruct::set_field);
|
||||||
|
///
|
||||||
|
/// // Notice that, with a getter, there is no way to get the property value
|
||||||
|
/// assert_eq!(
|
||||||
|
/// engine.eval::<TestStruct>("let a = new_ts(); a.xyz = 42; a")?,
|
||||||
|
/// TestStruct { field: 42 }
|
||||||
|
/// );
|
||||||
|
/// # Ok(())
|
||||||
|
/// # }
|
||||||
|
/// ```
|
||||||
|
#[cfg(not(feature = "no_object"))]
|
||||||
|
pub fn register_set_result<T, U>(
|
||||||
|
&mut self,
|
||||||
|
name: &str,
|
||||||
|
callback: impl Fn(&mut T, U) -> Result<Dynamic, Box<EvalAltResult>> + SendSync + 'static,
|
||||||
|
) -> &mut Self
|
||||||
|
where
|
||||||
|
T: Variant + Clone,
|
||||||
|
U: Variant + Clone,
|
||||||
|
{
|
||||||
|
self.register_result_fn(&make_setter(name), callback)
|
||||||
|
}
|
||||||
|
|
||||||
/// Shorthand for registering both getter and setter functions
|
/// Shorthand for registering both getter and setter functions
|
||||||
/// of a registered type with the `Engine`.
|
/// of a registered type with the `Engine`.
|
||||||
///
|
///
|
||||||
@ -375,6 +476,58 @@ impl Engine {
|
|||||||
self.register_fn(FN_IDX_GET, callback)
|
self.register_fn(FN_IDX_GET, callback)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Register an index getter for a registered type with the `Engine`.
|
||||||
|
/// Returns `Result<Dynamic, Box<EvalAltResult>>`.
|
||||||
|
///
|
||||||
|
/// The function signature must start with `&mut self` and not `&self`.
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// use rhai::{Engine, Dynamic, EvalAltResult, RegisterFn};
|
||||||
|
///
|
||||||
|
/// #[derive(Clone)]
|
||||||
|
/// struct TestStruct {
|
||||||
|
/// fields: Vec<i64>
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// impl TestStruct {
|
||||||
|
/// fn new() -> Self { TestStruct { fields: vec![1, 2, 3, 4, 5] } }
|
||||||
|
///
|
||||||
|
/// // Even a getter must start with `&mut self` and not `&self`.
|
||||||
|
/// fn get_field(&mut self, index: i64) -> Result<Dynamic, Box<EvalAltResult>> {
|
||||||
|
/// Ok(self.fields[index as usize].into())
|
||||||
|
/// }
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// # fn main() -> Result<(), Box<rhai::EvalAltResult>> {
|
||||||
|
/// let mut engine = Engine::new();
|
||||||
|
///
|
||||||
|
/// // Register the custom type.
|
||||||
|
/// engine.register_type::<TestStruct>();
|
||||||
|
///
|
||||||
|
/// engine.register_fn("new_ts", TestStruct::new);
|
||||||
|
///
|
||||||
|
/// // Register an indexer.
|
||||||
|
/// engine.register_indexer_get_result(TestStruct::get_field);
|
||||||
|
///
|
||||||
|
/// assert_eq!(engine.eval::<i64>("let a = new_ts(); a[2]")?, 3);
|
||||||
|
/// # Ok(())
|
||||||
|
/// # }
|
||||||
|
/// ```
|
||||||
|
#[cfg(not(feature = "no_object"))]
|
||||||
|
#[cfg(not(feature = "no_index"))]
|
||||||
|
pub fn register_indexer_get_result<T, X>(
|
||||||
|
&mut self,
|
||||||
|
callback: impl Fn(&mut T, X) -> Result<Dynamic, Box<EvalAltResult>> + SendSync + 'static,
|
||||||
|
) -> &mut Self
|
||||||
|
where
|
||||||
|
T: Variant + Clone,
|
||||||
|
X: Variant + Clone,
|
||||||
|
{
|
||||||
|
self.register_result_fn(FN_IDX_GET, callback)
|
||||||
|
}
|
||||||
|
|
||||||
/// Register an index setter for a registered type with the `Engine`.
|
/// Register an index setter for a registered type with the `Engine`.
|
||||||
///
|
///
|
||||||
/// # Example
|
/// # Example
|
||||||
@ -424,6 +577,59 @@ impl Engine {
|
|||||||
self.register_fn(FN_IDX_SET, callback)
|
self.register_fn(FN_IDX_SET, callback)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Register an index setter for a registered type with the `Engine`.
|
||||||
|
/// Returns `Result<Dynamic, Box<EvalAltResult>>`.
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// use rhai::{Engine, Dynamic, EvalAltResult, RegisterFn};
|
||||||
|
///
|
||||||
|
/// #[derive(Clone)]
|
||||||
|
/// struct TestStruct {
|
||||||
|
/// fields: Vec<i64>
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// impl TestStruct {
|
||||||
|
/// fn new() -> Self { TestStruct { fields: vec![1, 2, 3, 4, 5] } }
|
||||||
|
/// fn set_field(&mut self, index: i64, value: i64) -> Result<Dynamic, Box<EvalAltResult>> {
|
||||||
|
/// self.fields[index as usize] = value;
|
||||||
|
/// Ok(().into())
|
||||||
|
/// }
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// # fn main() -> Result<(), Box<rhai::EvalAltResult>> {
|
||||||
|
/// let mut engine = Engine::new();
|
||||||
|
///
|
||||||
|
/// // Register the custom type.
|
||||||
|
/// engine.register_type::<TestStruct>();
|
||||||
|
///
|
||||||
|
/// engine.register_fn("new_ts", TestStruct::new);
|
||||||
|
///
|
||||||
|
/// // Register an indexer.
|
||||||
|
/// engine.register_indexer_set_result(TestStruct::set_field);
|
||||||
|
///
|
||||||
|
/// assert_eq!(
|
||||||
|
/// engine.eval::<TestStruct>("let a = new_ts(); a[2] = 42; a")?.fields[2],
|
||||||
|
/// 42
|
||||||
|
/// );
|
||||||
|
/// # Ok(())
|
||||||
|
/// # }
|
||||||
|
/// ```
|
||||||
|
#[cfg(not(feature = "no_object"))]
|
||||||
|
#[cfg(not(feature = "no_index"))]
|
||||||
|
pub fn register_indexer_set_result<T, X, U>(
|
||||||
|
&mut self,
|
||||||
|
callback: impl Fn(&mut T, X, U) -> Result<Dynamic, Box<EvalAltResult>> + SendSync + 'static,
|
||||||
|
) -> &mut Self
|
||||||
|
where
|
||||||
|
T: Variant + Clone,
|
||||||
|
U: Variant + Clone,
|
||||||
|
X: Variant + Clone,
|
||||||
|
{
|
||||||
|
self.register_result_fn(FN_IDX_SET, callback)
|
||||||
|
}
|
||||||
|
|
||||||
/// Shorthand for register both index getter and setter functions for a registered type with the `Engine`.
|
/// Shorthand for register both index getter and setter functions for a registered type with the `Engine`.
|
||||||
///
|
///
|
||||||
/// # Example
|
/// # Example
|
||||||
|
@ -147,6 +147,7 @@ pub enum Target<'a> {
|
|||||||
#[cfg(any(not(feature = "no_index"), not(feature = "no_object")))]
|
#[cfg(any(not(feature = "no_index"), not(feature = "no_object")))]
|
||||||
impl Target<'_> {
|
impl Target<'_> {
|
||||||
/// Is the `Target` a reference pointing to other data?
|
/// Is the `Target` a reference pointing to other data?
|
||||||
|
#[inline(always)]
|
||||||
pub fn is_ref(&self) -> bool {
|
pub fn is_ref(&self) -> bool {
|
||||||
match self {
|
match self {
|
||||||
Self::Ref(_) => true,
|
Self::Ref(_) => true,
|
||||||
@ -159,6 +160,7 @@ impl Target<'_> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
/// Is the `Target` an owned value?
|
/// Is the `Target` an owned value?
|
||||||
|
#[inline(always)]
|
||||||
pub fn is_value(&self) -> bool {
|
pub fn is_value(&self) -> bool {
|
||||||
match self {
|
match self {
|
||||||
Self::Ref(_) => false,
|
Self::Ref(_) => false,
|
||||||
@ -171,6 +173,7 @@ impl Target<'_> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
/// Is the `Target` a shared value?
|
/// Is the `Target` a shared value?
|
||||||
|
#[inline(always)]
|
||||||
pub fn is_shared(&self) -> bool {
|
pub fn is_shared(&self) -> bool {
|
||||||
match self {
|
match self {
|
||||||
Self::Ref(r) => r.is_shared(),
|
Self::Ref(r) => r.is_shared(),
|
||||||
@ -184,6 +187,7 @@ impl Target<'_> {
|
|||||||
}
|
}
|
||||||
/// Is the `Target` a specific type?
|
/// Is the `Target` a specific type?
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
|
#[inline(always)]
|
||||||
pub fn is<T: Variant + Clone>(&self) -> bool {
|
pub fn is<T: Variant + Clone>(&self) -> bool {
|
||||||
match self {
|
match self {
|
||||||
Target::Ref(r) => r.is::<T>(),
|
Target::Ref(r) => r.is::<T>(),
|
||||||
@ -196,6 +200,7 @@ impl Target<'_> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
/// Get the value of the `Target` as a `Dynamic`, cloning a referenced value if necessary.
|
/// Get the value of the `Target` as a `Dynamic`, cloning a referenced value if necessary.
|
||||||
|
#[inline(always)]
|
||||||
pub fn clone_into_dynamic(self) -> Dynamic {
|
pub fn clone_into_dynamic(self) -> Dynamic {
|
||||||
match self {
|
match self {
|
||||||
Self::Ref(r) => r.clone(), // Referenced value is cloned
|
Self::Ref(r) => r.clone(), // Referenced value is cloned
|
||||||
@ -208,6 +213,7 @@ impl Target<'_> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
/// Get a mutable reference from the `Target`.
|
/// Get a mutable reference from the `Target`.
|
||||||
|
#[inline(always)]
|
||||||
pub fn as_mut(&mut self) -> &mut Dynamic {
|
pub fn as_mut(&mut self) -> &mut Dynamic {
|
||||||
match self {
|
match self {
|
||||||
Self::Ref(r) => *r,
|
Self::Ref(r) => *r,
|
||||||
@ -258,6 +264,7 @@ impl Target<'_> {
|
|||||||
|
|
||||||
#[cfg(any(not(feature = "no_index"), not(feature = "no_object")))]
|
#[cfg(any(not(feature = "no_index"), not(feature = "no_object")))]
|
||||||
impl<'a> From<&'a mut Dynamic> for Target<'a> {
|
impl<'a> From<&'a mut Dynamic> for Target<'a> {
|
||||||
|
#[inline(always)]
|
||||||
fn from(value: &'a mut Dynamic) -> Self {
|
fn from(value: &'a mut Dynamic) -> Self {
|
||||||
#[cfg(not(feature = "no_closure"))]
|
#[cfg(not(feature = "no_closure"))]
|
||||||
#[cfg(not(feature = "no_object"))]
|
#[cfg(not(feature = "no_object"))]
|
||||||
@ -273,6 +280,7 @@ impl<'a> From<&'a mut Dynamic> for Target<'a> {
|
|||||||
|
|
||||||
#[cfg(any(not(feature = "no_index"), not(feature = "no_object")))]
|
#[cfg(any(not(feature = "no_index"), not(feature = "no_object")))]
|
||||||
impl<T: Into<Dynamic>> From<T> for Target<'_> {
|
impl<T: Into<Dynamic>> From<T> for Target<'_> {
|
||||||
|
#[inline(always)]
|
||||||
fn from(value: T) -> Self {
|
fn from(value: T) -> Self {
|
||||||
Self::Value(value.into())
|
Self::Value(value.into())
|
||||||
}
|
}
|
||||||
@ -301,6 +309,7 @@ pub struct State {
|
|||||||
|
|
||||||
impl State {
|
impl State {
|
||||||
/// Create a new `State`.
|
/// Create a new `State`.
|
||||||
|
#[inline(always)]
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Default::default()
|
Default::default()
|
||||||
}
|
}
|
||||||
@ -407,6 +416,7 @@ pub struct Engine {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Debug for Engine {
|
impl fmt::Debug for Engine {
|
||||||
|
#[inline(always)]
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
match self.id.as_ref() {
|
match self.id.as_ref() {
|
||||||
Some(id) => write!(f, "Engine({})", id),
|
Some(id) => write!(f, "Engine({})", id),
|
||||||
@ -689,7 +699,7 @@ impl Engine {
|
|||||||
idx_values: &mut StaticVec<Dynamic>,
|
idx_values: &mut StaticVec<Dynamic>,
|
||||||
chain_type: ChainType,
|
chain_type: ChainType,
|
||||||
level: usize,
|
level: usize,
|
||||||
mut _new_val: Option<Dynamic>,
|
new_val: Option<Dynamic>,
|
||||||
) -> Result<(Dynamic, bool), Box<EvalAltResult>> {
|
) -> Result<(Dynamic, bool), Box<EvalAltResult>> {
|
||||||
if chain_type == ChainType::None {
|
if chain_type == ChainType::None {
|
||||||
panic!();
|
panic!();
|
||||||
@ -722,12 +732,12 @@ impl Engine {
|
|||||||
|
|
||||||
self.eval_dot_index_chain_helper(
|
self.eval_dot_index_chain_helper(
|
||||||
state, lib, this_ptr, obj_ptr, expr, idx_values, next_chain, level,
|
state, lib, this_ptr, obj_ptr, expr, idx_values, next_chain, level,
|
||||||
_new_val,
|
new_val,
|
||||||
)
|
)
|
||||||
.map_err(|err| err.new_position(*pos))
|
.map_err(|err| err.new_position(*pos))
|
||||||
}
|
}
|
||||||
// xxx[rhs] = new_val
|
// xxx[rhs] = new_val
|
||||||
_ if _new_val.is_some() => {
|
_ if new_val.is_some() => {
|
||||||
let mut idx_val2 = idx_val.clone();
|
let mut idx_val2 = idx_val.clone();
|
||||||
|
|
||||||
// `call_setter` is introduced to bypass double mutable borrowing of target
|
// `call_setter` is introduced to bypass double mutable borrowing of target
|
||||||
@ -737,7 +747,7 @@ impl Engine {
|
|||||||
// Indexed value is a reference - update directly
|
// Indexed value is a reference - update directly
|
||||||
Ok(ref mut obj_ptr) => {
|
Ok(ref mut obj_ptr) => {
|
||||||
obj_ptr
|
obj_ptr
|
||||||
.set_value(_new_val.unwrap())
|
.set_value(new_val.unwrap())
|
||||||
.map_err(|err| err.new_position(rhs.position()))?;
|
.map_err(|err| err.new_position(rhs.position()))?;
|
||||||
|
|
||||||
None
|
None
|
||||||
@ -747,7 +757,7 @@ impl Engine {
|
|||||||
#[cfg(not(feature = "no_index"))]
|
#[cfg(not(feature = "no_index"))]
|
||||||
EvalAltResult::ErrorIndexingType(_, _) => {
|
EvalAltResult::ErrorIndexingType(_, _) => {
|
||||||
// Raise error if there is no index getter nor setter
|
// Raise error if there is no index getter nor setter
|
||||||
Some(_new_val.unwrap())
|
Some(new_val.unwrap())
|
||||||
}
|
}
|
||||||
// Any other error - return
|
// Any other error - return
|
||||||
err => return Err(Box::new(err)),
|
err => return Err(Box::new(err)),
|
||||||
@ -799,13 +809,13 @@ impl Engine {
|
|||||||
// xxx.module::fn_name(...) - syntax error
|
// xxx.module::fn_name(...) - syntax error
|
||||||
Expr::FnCall(_) => unreachable!(),
|
Expr::FnCall(_) => unreachable!(),
|
||||||
// {xxx:map}.id = ???
|
// {xxx:map}.id = ???
|
||||||
Expr::Property(x) if target.is::<Map>() && _new_val.is_some() => {
|
Expr::Property(x) if target.is::<Map>() && new_val.is_some() => {
|
||||||
let ((prop, _, _), pos) = x.as_ref();
|
let ((prop, _, _), pos) = x.as_ref();
|
||||||
let index = prop.clone().into();
|
let index = prop.clone().into();
|
||||||
let mut val = self
|
let mut val = self
|
||||||
.get_indexed_mut(state, lib, target, index, *pos, true, false, level)?;
|
.get_indexed_mut(state, lib, target, index, *pos, true, false, level)?;
|
||||||
|
|
||||||
val.set_value(_new_val.unwrap())
|
val.set_value(new_val.unwrap())
|
||||||
.map_err(|err| err.new_position(rhs.position()))?;
|
.map_err(|err| err.new_position(rhs.position()))?;
|
||||||
Ok((Default::default(), true))
|
Ok((Default::default(), true))
|
||||||
}
|
}
|
||||||
@ -820,9 +830,10 @@ impl Engine {
|
|||||||
Ok((val.clone_into_dynamic(), false))
|
Ok((val.clone_into_dynamic(), false))
|
||||||
}
|
}
|
||||||
// xxx.id = ???
|
// xxx.id = ???
|
||||||
Expr::Property(x) if _new_val.is_some() => {
|
Expr::Property(x) if new_val.is_some() => {
|
||||||
let ((_, _, setter), pos) = x.as_ref();
|
let ((_, _, setter), pos) = x.as_ref();
|
||||||
let mut args = [target.as_mut(), _new_val.as_mut().unwrap()];
|
let mut new_val = new_val;
|
||||||
|
let mut args = [target.as_mut(), new_val.as_mut().unwrap()];
|
||||||
self.exec_fn_call(
|
self.exec_fn_call(
|
||||||
state, lib, setter, 0, &mut args, is_ref, true, false, None, None,
|
state, lib, setter, 0, &mut args, is_ref, true, false, None, None,
|
||||||
level,
|
level,
|
||||||
@ -872,7 +883,7 @@ impl Engine {
|
|||||||
|
|
||||||
self.eval_dot_index_chain_helper(
|
self.eval_dot_index_chain_helper(
|
||||||
state, lib, this_ptr, &mut val, expr, idx_values, next_chain, level,
|
state, lib, this_ptr, &mut val, expr, idx_values, next_chain, level,
|
||||||
_new_val,
|
new_val,
|
||||||
)
|
)
|
||||||
.map_err(|err| err.new_position(*pos))
|
.map_err(|err| err.new_position(*pos))
|
||||||
}
|
}
|
||||||
@ -905,7 +916,7 @@ impl Engine {
|
|||||||
idx_values,
|
idx_values,
|
||||||
next_chain,
|
next_chain,
|
||||||
level,
|
level,
|
||||||
_new_val,
|
new_val,
|
||||||
)
|
)
|
||||||
.map_err(|err| err.new_position(*pos))?;
|
.map_err(|err| err.new_position(*pos))?;
|
||||||
|
|
||||||
@ -944,7 +955,7 @@ impl Engine {
|
|||||||
|
|
||||||
self.eval_dot_index_chain_helper(
|
self.eval_dot_index_chain_helper(
|
||||||
state, lib, this_ptr, target, expr, idx_values, next_chain,
|
state, lib, this_ptr, target, expr, idx_values, next_chain,
|
||||||
level, _new_val,
|
level, new_val,
|
||||||
)
|
)
|
||||||
.map_err(|err| err.new_position(*pos))
|
.map_err(|err| err.new_position(*pos))
|
||||||
}
|
}
|
||||||
@ -1114,7 +1125,7 @@ impl Engine {
|
|||||||
state: &mut State,
|
state: &mut State,
|
||||||
_lib: &Module,
|
_lib: &Module,
|
||||||
target: &'a mut Target,
|
target: &'a mut Target,
|
||||||
mut _idx: Dynamic,
|
idx: Dynamic,
|
||||||
idx_pos: Position,
|
idx_pos: Position,
|
||||||
_create: bool,
|
_create: bool,
|
||||||
_indexers: bool,
|
_indexers: bool,
|
||||||
@ -1132,7 +1143,7 @@ impl Engine {
|
|||||||
#[cfg(not(feature = "no_index"))]
|
#[cfg(not(feature = "no_index"))]
|
||||||
Dynamic(Union::Array(arr)) => {
|
Dynamic(Union::Array(arr)) => {
|
||||||
// val_array[idx]
|
// val_array[idx]
|
||||||
let index = _idx
|
let index = idx
|
||||||
.as_int()
|
.as_int()
|
||||||
.map_err(|_| EvalAltResult::ErrorNumericIndexExpr(idx_pos))?;
|
.map_err(|_| EvalAltResult::ErrorNumericIndexExpr(idx_pos))?;
|
||||||
|
|
||||||
@ -1153,13 +1164,13 @@ impl Engine {
|
|||||||
Dynamic(Union::Map(map)) => {
|
Dynamic(Union::Map(map)) => {
|
||||||
// val_map[idx]
|
// val_map[idx]
|
||||||
Ok(if _create {
|
Ok(if _create {
|
||||||
let index = _idx
|
let index = idx
|
||||||
.take_immutable_string()
|
.take_immutable_string()
|
||||||
.map_err(|_| EvalAltResult::ErrorStringIndexExpr(idx_pos))?;
|
.map_err(|_| EvalAltResult::ErrorStringIndexExpr(idx_pos))?;
|
||||||
|
|
||||||
map.entry(index).or_insert(Default::default()).into()
|
map.entry(index).or_insert(Default::default()).into()
|
||||||
} else {
|
} else {
|
||||||
let index = _idx
|
let index = idx
|
||||||
.read_lock::<ImmutableString>()
|
.read_lock::<ImmutableString>()
|
||||||
.ok_or_else(|| EvalAltResult::ErrorStringIndexExpr(idx_pos))?;
|
.ok_or_else(|| EvalAltResult::ErrorStringIndexExpr(idx_pos))?;
|
||||||
|
|
||||||
@ -1173,7 +1184,7 @@ impl Engine {
|
|||||||
Dynamic(Union::Str(s)) => {
|
Dynamic(Union::Str(s)) => {
|
||||||
// val_string[idx]
|
// val_string[idx]
|
||||||
let chars_len = s.chars().count();
|
let chars_len = s.chars().count();
|
||||||
let index = _idx
|
let index = idx
|
||||||
.as_int()
|
.as_int()
|
||||||
.map_err(|_| EvalAltResult::ErrorNumericIndexExpr(idx_pos))?;
|
.map_err(|_| EvalAltResult::ErrorNumericIndexExpr(idx_pos))?;
|
||||||
|
|
||||||
@ -1192,7 +1203,8 @@ impl Engine {
|
|||||||
#[cfg(not(feature = "no_index"))]
|
#[cfg(not(feature = "no_index"))]
|
||||||
_ if _indexers => {
|
_ if _indexers => {
|
||||||
let type_name = val.type_name();
|
let type_name = val.type_name();
|
||||||
let args = &mut [val, &mut _idx];
|
let mut idx = idx;
|
||||||
|
let args = &mut [val, &mut idx];
|
||||||
self.exec_fn_call(
|
self.exec_fn_call(
|
||||||
state, _lib, FN_IDX_GET, 0, args, is_ref, true, false, None, None, _level,
|
state, _lib, FN_IDX_GET, 0, args, is_ref, true, false, None, None, _level,
|
||||||
)
|
)
|
||||||
@ -1331,11 +1343,11 @@ impl Engine {
|
|||||||
)),
|
)),
|
||||||
// Normal assignment
|
// Normal assignment
|
||||||
ScopeEntryType::Normal if op.is_empty() => {
|
ScopeEntryType::Normal if op.is_empty() => {
|
||||||
let rhs_val = rhs_val.clone_inner_data().unwrap();
|
let value = rhs_val.flatten();
|
||||||
if cfg!(not(feature = "no_closure")) && lhs_ptr.is_shared() {
|
if cfg!(not(feature = "no_closure")) && lhs_ptr.is_shared() {
|
||||||
*lhs_ptr.write_lock::<Dynamic>().unwrap() = rhs_val;
|
*lhs_ptr.write_lock::<Dynamic>().unwrap() = value;
|
||||||
} else {
|
} else {
|
||||||
*lhs_ptr = rhs_val;
|
*lhs_ptr = value;
|
||||||
}
|
}
|
||||||
Ok(Default::default())
|
Ok(Default::default())
|
||||||
}
|
}
|
||||||
@ -1378,7 +1390,7 @@ impl Engine {
|
|||||||
)
|
)
|
||||||
.map_err(|err| err.new_position(*op_pos))?;
|
.map_err(|err| err.new_position(*op_pos))?;
|
||||||
|
|
||||||
let value = value.clone_inner_data().unwrap();
|
let value = value.flatten();
|
||||||
if cfg!(not(feature = "no_closure")) && lhs_ptr.is_shared() {
|
if cfg!(not(feature = "no_closure")) && lhs_ptr.is_shared() {
|
||||||
*lhs_ptr.write_lock::<Dynamic>().unwrap() = value;
|
*lhs_ptr.write_lock::<Dynamic>().unwrap() = value;
|
||||||
} else {
|
} else {
|
||||||
@ -1674,14 +1686,14 @@ impl Engine {
|
|||||||
let index = scope.len() - 1;
|
let index = scope.len() - 1;
|
||||||
state.scope_level += 1;
|
state.scope_level += 1;
|
||||||
|
|
||||||
for loop_var in func(iter_type) {
|
for iter_value in func(iter_type) {
|
||||||
let for_var = scope.get_mut(index).0;
|
let (loop_var, _) = scope.get_mut(index);
|
||||||
let value = loop_var.clone_inner_data().unwrap();
|
|
||||||
|
|
||||||
if cfg!(not(feature = "no_closure")) && for_var.is_shared() {
|
let value = iter_value.flatten();
|
||||||
*for_var.write_lock().unwrap() = value;
|
if cfg!(not(feature = "no_closure")) && loop_var.is_shared() {
|
||||||
|
*loop_var.write_lock().unwrap() = value;
|
||||||
} else {
|
} else {
|
||||||
*for_var = value;
|
*loop_var = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
self.inc_operations(state)
|
self.inc_operations(state)
|
||||||
@ -1750,8 +1762,7 @@ impl Engine {
|
|||||||
let expr = expr.as_ref().unwrap();
|
let expr = expr.as_ref().unwrap();
|
||||||
let val = self
|
let val = self
|
||||||
.eval_expr(scope, mods, state, lib, this_ptr, expr, level)?
|
.eval_expr(scope, mods, state, lib, this_ptr, expr, level)?
|
||||||
.clone_inner_data()
|
.flatten();
|
||||||
.unwrap();
|
|
||||||
let var_name = unsafe_cast_var_name_to_lifetime(var_name, &state);
|
let var_name = unsafe_cast_var_name_to_lifetime(var_name, &state);
|
||||||
scope.push_dynamic_value(var_name, ScopeEntryType::Normal, val, false);
|
scope.push_dynamic_value(var_name, ScopeEntryType::Normal, val, false);
|
||||||
Ok(Default::default())
|
Ok(Default::default())
|
||||||
@ -1769,8 +1780,7 @@ impl Engine {
|
|||||||
let ((var_name, _), expr, _) = x.as_ref();
|
let ((var_name, _), expr, _) = x.as_ref();
|
||||||
let val = self
|
let val = self
|
||||||
.eval_expr(scope, mods, state, lib, this_ptr, &expr, level)?
|
.eval_expr(scope, mods, state, lib, this_ptr, &expr, level)?
|
||||||
.clone_inner_data()
|
.flatten();
|
||||||
.unwrap();
|
|
||||||
let var_name = unsafe_cast_var_name_to_lifetime(var_name, &state);
|
let var_name = unsafe_cast_var_name_to_lifetime(var_name, &state);
|
||||||
scope.push_dynamic_value(var_name, ScopeEntryType::Constant, val, true);
|
scope.push_dynamic_value(var_name, ScopeEntryType::Constant, val, true);
|
||||||
Ok(Default::default())
|
Ok(Default::default())
|
||||||
@ -2000,6 +2010,7 @@ impl Engine {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Map a type_name into a pretty-print name
|
/// Map a type_name into a pretty-print name
|
||||||
|
#[inline(always)]
|
||||||
pub(crate) fn map_type_name<'a>(&'a self, name: &'a str) -> &'a str {
|
pub(crate) fn map_type_name<'a>(&'a self, name: &'a str) -> &'a str {
|
||||||
self.type_names
|
self.type_names
|
||||||
.as_ref()
|
.as_ref()
|
||||||
|
@ -925,11 +925,11 @@ impl Engine {
|
|||||||
self.inc_operations(state)
|
self.inc_operations(state)
|
||||||
.map_err(|err| err.new_position(pos))?;
|
.map_err(|err| err.new_position(pos))?;
|
||||||
|
|
||||||
// Turn it into a method call only if the object is not shared
|
|
||||||
args = if target.is_shared() {
|
args = if target.is_shared() {
|
||||||
arg_values.insert(0, target.clone().clone_inner_data().unwrap());
|
arg_values.insert(0, target.flatten_clone());
|
||||||
arg_values.iter_mut().collect()
|
arg_values.iter_mut().collect()
|
||||||
} else {
|
} else {
|
||||||
|
// Turn it into a method call only if the object is not shared
|
||||||
is_ref = true;
|
is_ref = true;
|
||||||
once(target).chain(arg_values.iter_mut()).collect()
|
once(target).chain(arg_values.iter_mut()).collect()
|
||||||
};
|
};
|
||||||
|
@ -21,13 +21,6 @@ use crate::stdlib::rc::Rc;
|
|||||||
#[cfg(feature = "sync")]
|
#[cfg(feature = "sync")]
|
||||||
use crate::stdlib::sync::Arc;
|
use crate::stdlib::sync::Arc;
|
||||||
|
|
||||||
#[cfg(not(feature = "no_closure"))]
|
|
||||||
#[cfg(not(feature = "sync"))]
|
|
||||||
use crate::stdlib::cell::RefCell;
|
|
||||||
#[cfg(not(feature = "no_closure"))]
|
|
||||||
#[cfg(feature = "sync")]
|
|
||||||
use crate::stdlib::sync::RwLock;
|
|
||||||
|
|
||||||
/// Trait that maps to `Send + Sync` only under the `sync` feature.
|
/// Trait that maps to `Send + Sync` only under the `sync` feature.
|
||||||
#[cfg(feature = "sync")]
|
#[cfg(feature = "sync")]
|
||||||
pub trait SendSync: Send + Sync {}
|
pub trait SendSync: Send + Sync {}
|
||||||
@ -49,15 +42,6 @@ pub type Shared<T> = Rc<T>;
|
|||||||
#[cfg(feature = "sync")]
|
#[cfg(feature = "sync")]
|
||||||
pub type Shared<T> = Arc<T>;
|
pub type Shared<T> = Arc<T>;
|
||||||
|
|
||||||
/// Mutable reference-counted container (read-write lock)
|
|
||||||
#[cfg(not(feature = "no_closure"))]
|
|
||||||
#[cfg(not(feature = "sync"))]
|
|
||||||
pub type SharedMut<T> = Shared<RefCell<T>>;
|
|
||||||
/// Mutable reference-counted container (read-write lock)
|
|
||||||
#[cfg(not(feature = "no_closure"))]
|
|
||||||
#[cfg(feature = "sync")]
|
|
||||||
pub type SharedMut<T> = Shared<RwLock<T>>;
|
|
||||||
|
|
||||||
/// Consume a `Shared` resource and return a mutable reference to the wrapped value.
|
/// Consume a `Shared` resource and return a mutable reference to the wrapped value.
|
||||||
/// If the resource is shared (i.e. has other outstanding references), a cloned copy is used.
|
/// If the resource is shared (i.e. has other outstanding references), a cloned copy is used.
|
||||||
pub fn shared_make_mut<T: Clone>(value: &mut Shared<T>) -> &mut T {
|
pub fn shared_make_mut<T: Clone>(value: &mut Shared<T>) -> &mut T {
|
||||||
@ -67,16 +51,21 @@ pub fn shared_make_mut<T: Clone>(value: &mut Shared<T>) -> &mut T {
|
|||||||
return Arc::make_mut(value);
|
return Arc::make_mut(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Consume a `Shared` resource if is unique (i.e. not shared).
|
||||||
|
pub fn shared_try_take<T>(value: Shared<T>) -> Result<T, Shared<T>> {
|
||||||
|
#[cfg(not(feature = "sync"))]
|
||||||
|
return Rc::try_unwrap(value);
|
||||||
|
#[cfg(feature = "sync")]
|
||||||
|
return Arc::try_unwrap(value);
|
||||||
|
}
|
||||||
|
|
||||||
/// Consume a `Shared` resource, assuming that it is unique (i.e. not shared).
|
/// Consume a `Shared` resource, assuming that it is unique (i.e. not shared).
|
||||||
///
|
///
|
||||||
/// # Panics
|
/// # Panics
|
||||||
///
|
///
|
||||||
/// Panics if the resource is shared (i.e. has other outstanding references).
|
/// Panics if the resource is shared (i.e. has other outstanding references).
|
||||||
pub fn shared_take<T: Clone>(value: Shared<T>) -> T {
|
pub fn shared_take<T>(value: Shared<T>) -> T {
|
||||||
#[cfg(not(feature = "sync"))]
|
shared_try_take(value).map_err(|_| ()).unwrap()
|
||||||
return Rc::try_unwrap(value).map_err(|_| ()).unwrap();
|
|
||||||
#[cfg(feature = "sync")]
|
|
||||||
return Arc::try_unwrap(value).map_err(|_| ()).unwrap();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub type FnCallArgs<'a> = [&'a mut Dynamic];
|
pub type FnCallArgs<'a> = [&'a mut Dynamic];
|
||||||
|
@ -1634,6 +1634,12 @@ fn parse_primary(
|
|||||||
Token::FloatConstant(x) => Expr::FloatConstant(Box::new(FloatWrapper(x, settings.pos))),
|
Token::FloatConstant(x) => Expr::FloatConstant(Box::new(FloatWrapper(x, settings.pos))),
|
||||||
Token::CharConstant(c) => Expr::CharConstant(Box::new((c, settings.pos))),
|
Token::CharConstant(c) => Expr::CharConstant(Box::new((c, settings.pos))),
|
||||||
Token::StringConstant(s) => Expr::StringConstant(Box::new((s.into(), settings.pos))),
|
Token::StringConstant(s) => Expr::StringConstant(Box::new((s.into(), settings.pos))),
|
||||||
|
|
||||||
|
// Function call
|
||||||
|
Token::Identifier(s) if *next_token == Token::LeftParen || *next_token == Token::Bang => {
|
||||||
|
Expr::Variable(Box::new(((s, settings.pos), None, 0, None)))
|
||||||
|
}
|
||||||
|
// Normal variable access
|
||||||
Token::Identifier(s) => {
|
Token::Identifier(s) => {
|
||||||
let index = state.access_var(&s, settings.pos);
|
let index = state.access_var(&s, settings.pos);
|
||||||
Expr::Variable(Box::new(((s, settings.pos), None, 0, index)))
|
Expr::Variable(Box::new(((s, settings.pos), None, 0, index)))
|
||||||
|
@ -338,7 +338,7 @@ impl<'a> Scope<'a> {
|
|||||||
/// ```
|
/// ```
|
||||||
pub fn get_value<T: Variant + Clone>(&self, name: &str) -> Option<T> {
|
pub fn get_value<T: Variant + Clone>(&self, name: &str) -> Option<T> {
|
||||||
self.get_entry(name)
|
self.get_entry(name)
|
||||||
.and_then(|Entry { value, .. }| value.clone().clone_inner_data::<T>())
|
.and_then(|Entry { value, .. }| value.flatten_clone().try_cast())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Update the value of the named entry.
|
/// Update the value of the named entry.
|
||||||
@ -441,7 +441,7 @@ impl<'a> Scope<'a> {
|
|||||||
/// ```
|
/// ```
|
||||||
pub fn iter(&self) -> impl Iterator<Item = (&str, Dynamic)> {
|
pub fn iter(&self) -> impl Iterator<Item = (&str, Dynamic)> {
|
||||||
self.iter_raw()
|
self.iter_raw()
|
||||||
.map(|(name, value)| (name, value.clone().clone_inner_data().unwrap()))
|
.map(|(name, value)| (name, value.flatten_clone()))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get an iterator to entries in the Scope.
|
/// Get an iterator to entries in the Scope.
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
#![cfg(not(feature = "no_function"))]
|
#![cfg(not(feature = "no_function"))]
|
||||||
use rhai::{
|
use rhai::{
|
||||||
Dynamic, Engine, EvalAltResult, FnPtr, Func, Module, ParseError, ParseErrorType, RegisterFn,
|
Dynamic, Engine, EvalAltResult, FnPtr, Func, Module, ParseErrorType, RegisterFn, Scope, INT,
|
||||||
Scope, INT,
|
|
||||||
};
|
};
|
||||||
use std::any::TypeId;
|
use std::any::TypeId;
|
||||||
|
|
||||||
@ -10,12 +9,13 @@ fn test_fn() -> Result<(), Box<EvalAltResult>> {
|
|||||||
let engine = Engine::new();
|
let engine = Engine::new();
|
||||||
|
|
||||||
// Expect duplicated parameters error
|
// Expect duplicated parameters error
|
||||||
assert!(matches!(
|
assert_eq!(
|
||||||
engine
|
*engine
|
||||||
.compile("fn hello(x, x) { x }")
|
.compile("fn hello(x, x) { x }")
|
||||||
.expect_err("should be error"),
|
.expect_err("should be error")
|
||||||
ParseError(x, _) if *x == ParseErrorType::FnDuplicatedParam("hello".to_string(), "x".to_string())
|
.0,
|
||||||
));
|
ParseErrorType::FnDuplicatedParam("hello".to_string(), "x".to_string())
|
||||||
|
);
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -1,12 +1,13 @@
|
|||||||
#![cfg(not(feature = "no_function"))]
|
#![cfg(not(feature = "no_function"))]
|
||||||
use rhai::{Dynamic, Engine, EvalAltResult, FnPtr, Module, INT};
|
use rhai::{Dynamic, Engine, EvalAltResult, FnPtr, Module, RegisterFn, INT};
|
||||||
use std::any::TypeId;
|
use std::any::TypeId;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_fn_ptr_curry_call() -> Result<(), Box<EvalAltResult>> {
|
fn test_fn_ptr_curry_call() -> Result<(), Box<EvalAltResult>> {
|
||||||
let mut module = Module::new();
|
let mut engine = Engine::new();
|
||||||
|
|
||||||
module.set_raw_fn(
|
#[allow(deprecated)]
|
||||||
|
engine.register_raw_fn(
|
||||||
"call_with_arg",
|
"call_with_arg",
|
||||||
&[TypeId::of::<FnPtr>(), TypeId::of::<INT>()],
|
&[TypeId::of::<FnPtr>(), TypeId::of::<INT>()],
|
||||||
|engine: &Engine, lib: &Module, args: &mut [&mut Dynamic]| {
|
|engine: &Engine, lib: &Module, args: &mut [&mut Dynamic]| {
|
||||||
@ -15,9 +16,6 @@ fn test_fn_ptr_curry_call() -> Result<(), Box<EvalAltResult>> {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
let mut engine = Engine::new();
|
|
||||||
engine.load_package(module);
|
|
||||||
|
|
||||||
#[cfg(not(feature = "no_object"))]
|
#[cfg(not(feature = "no_object"))]
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
engine.eval::<INT>(
|
engine.eval::<INT>(
|
||||||
@ -38,7 +36,7 @@ fn test_fn_ptr_curry_call() -> Result<(), Box<EvalAltResult>> {
|
|||||||
#[cfg(not(feature = "no_closure"))]
|
#[cfg(not(feature = "no_closure"))]
|
||||||
#[cfg(not(feature = "no_object"))]
|
#[cfg(not(feature = "no_object"))]
|
||||||
fn test_closures() -> Result<(), Box<EvalAltResult>> {
|
fn test_closures() -> Result<(), Box<EvalAltResult>> {
|
||||||
let engine = Engine::new();
|
let mut engine = Engine::new();
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
engine.eval::<INT>(
|
engine.eval::<INT>(
|
||||||
@ -77,6 +75,19 @@ fn test_closures() -> Result<(), Box<EvalAltResult>> {
|
|||||||
"#
|
"#
|
||||||
)?);
|
)?);
|
||||||
|
|
||||||
|
engine.register_fn("plus_one", |x: INT| x + 1);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
engine.eval::<INT>(
|
||||||
|
r#"
|
||||||
|
let a = 41;
|
||||||
|
let f = || plus_one(a);
|
||||||
|
f.call()
|
||||||
|
"#
|
||||||
|
)?,
|
||||||
|
42
|
||||||
|
);
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
#![cfg(not(feature = "unchecked"))]
|
#![cfg(not(feature = "unchecked"))]
|
||||||
use rhai::{Engine, EvalAltResult, ParseError, ParseErrorType};
|
use rhai::{Engine, EvalAltResult, ParseErrorType};
|
||||||
|
|
||||||
#[cfg(not(feature = "no_index"))]
|
#[cfg(not(feature = "no_index"))]
|
||||||
use rhai::Array;
|
use rhai::Array;
|
||||||
@ -12,15 +12,21 @@ fn test_max_string_size() -> Result<(), Box<EvalAltResult>> {
|
|||||||
let mut engine = Engine::new();
|
let mut engine = Engine::new();
|
||||||
engine.set_max_string_size(10);
|
engine.set_max_string_size(10);
|
||||||
|
|
||||||
assert!(matches!(
|
assert_eq!(
|
||||||
engine.compile(r#"let x = "hello, world!";"#).expect_err("should error"),
|
*engine
|
||||||
ParseError(x, _) if *x == ParseErrorType::LiteralTooLarge("Length of string literal".to_string(), 10)
|
.compile(r#"let x = "hello, world!";"#)
|
||||||
));
|
.expect_err("should error")
|
||||||
|
.0,
|
||||||
|
ParseErrorType::LiteralTooLarge("Length of string literal".to_string(), 10)
|
||||||
|
);
|
||||||
|
|
||||||
assert!(matches!(
|
assert_eq!(
|
||||||
engine.compile(r#"let x = "朝に紅顔、暮に白骨";"#).expect_err("should error"),
|
*engine
|
||||||
ParseError(x, _) if *x == ParseErrorType::LiteralTooLarge("Length of string literal".to_string(), 10)
|
.compile(r#"let x = "朝に紅顔、暮に白骨";"#)
|
||||||
));
|
.expect_err("should error")
|
||||||
|
.0,
|
||||||
|
ParseErrorType::LiteralTooLarge("Length of string literal".to_string(), 10)
|
||||||
|
);
|
||||||
|
|
||||||
assert!(matches!(
|
assert!(matches!(
|
||||||
*engine
|
*engine
|
||||||
@ -74,12 +80,13 @@ fn test_max_array_size() -> Result<(), Box<EvalAltResult>> {
|
|||||||
#[cfg(not(feature = "no_object"))]
|
#[cfg(not(feature = "no_object"))]
|
||||||
engine.set_max_map_size(10);
|
engine.set_max_map_size(10);
|
||||||
|
|
||||||
assert!(matches!(
|
assert_eq!(
|
||||||
engine
|
*engine
|
||||||
.compile("let x = [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15];")
|
.compile("let x = [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15];")
|
||||||
.expect_err("should error"),
|
.expect_err("should error")
|
||||||
ParseError(x, _) if *x == ParseErrorType::LiteralTooLarge("Size of array literal".to_string(), 10)
|
.0,
|
||||||
));
|
ParseErrorType::LiteralTooLarge("Size of array literal".to_string(), 10)
|
||||||
|
);
|
||||||
|
|
||||||
assert!(matches!(
|
assert!(matches!(
|
||||||
*engine
|
*engine
|
||||||
@ -186,12 +193,18 @@ fn test_max_map_size() -> Result<(), Box<EvalAltResult>> {
|
|||||||
#[cfg(not(feature = "no_index"))]
|
#[cfg(not(feature = "no_index"))]
|
||||||
engine.set_max_array_size(10);
|
engine.set_max_array_size(10);
|
||||||
|
|
||||||
assert!(matches!(
|
assert_eq!(
|
||||||
engine
|
*engine
|
||||||
.compile("let x = #{a:1,b:2,c:3,d:4,e:5,f:6,g:7,h:8,i:9,j:10,k:11,l:12,m:13,n:14,o:15};")
|
.compile(
|
||||||
.expect_err("should error"),
|
"let x = #{a:1,b:2,c:3,d:4,e:5,f:6,g:7,h:8,i:9,j:10,k:11,l:12,m:13,n:14,o:15};"
|
||||||
ParseError(x, _) if *x == ParseErrorType::LiteralTooLarge("Number of properties in object map literal".to_string(), 10)
|
)
|
||||||
));
|
.expect_err("should error")
|
||||||
|
.0,
|
||||||
|
ParseErrorType::LiteralTooLarge(
|
||||||
|
"Number of properties in object map literal".to_string(),
|
||||||
|
10
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
assert!(matches!(
|
assert!(matches!(
|
||||||
*engine
|
*engine
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
#![cfg(not(feature = "no_function"))]
|
#![cfg(not(feature = "no_function"))]
|
||||||
use rhai::{Engine, EvalAltResult, ParseError, ParseErrorType, INT};
|
use rhai::{Engine, EvalAltResult, ParseErrorType, INT};
|
||||||
|
use std::io::Read;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_functions() -> Result<(), Box<EvalAltResult>> {
|
fn test_functions() -> Result<(), Box<EvalAltResult>> {
|
||||||
@ -155,7 +156,8 @@ fn test_function_captures() -> Result<(), Box<EvalAltResult>> {
|
|||||||
|
|
||||||
#[cfg(not(feature = "no_object"))]
|
#[cfg(not(feature = "no_object"))]
|
||||||
assert!(matches!(
|
assert!(matches!(
|
||||||
engine.compile(
|
*engine
|
||||||
|
.compile(
|
||||||
r#"
|
r#"
|
||||||
fn foo() { this += x; }
|
fn foo() { this += x; }
|
||||||
|
|
||||||
@ -164,8 +166,10 @@ fn test_function_captures() -> Result<(), Box<EvalAltResult>> {
|
|||||||
|
|
||||||
y.foo!();
|
y.foo!();
|
||||||
"#
|
"#
|
||||||
).expect_err("should error"),
|
)
|
||||||
ParseError(err, _) if matches!(*err, ParseErrorType::MalformedCapture(_))
|
.expect_err("should error")
|
||||||
|
.0,
|
||||||
|
ParseErrorType::MalformedCapture(_)
|
||||||
));
|
));
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
use rhai::{Engine, EvalAltResult, ParseError, ParseErrorType, INT};
|
use rhai::{Engine, EvalAltResult, ParseErrorType, INT};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_loop() -> Result<(), Box<EvalAltResult>> {
|
fn test_loop() -> Result<(), Box<EvalAltResult>> {
|
||||||
@ -26,15 +26,21 @@ fn test_loop() -> Result<(), Box<EvalAltResult>> {
|
|||||||
21
|
21
|
||||||
);
|
);
|
||||||
|
|
||||||
assert!(matches!(
|
assert_eq!(
|
||||||
engine.compile("let x = 0; break;").expect_err("should error"),
|
*engine
|
||||||
ParseError(x, _) if *x == ParseErrorType::LoopBreak
|
.compile("let x = 0; break;")
|
||||||
));
|
.expect_err("should error")
|
||||||
|
.0,
|
||||||
|
ParseErrorType::LoopBreak
|
||||||
|
);
|
||||||
|
|
||||||
assert!(matches!(
|
assert_eq!(
|
||||||
engine.compile("let x = 0; if x > 0 { continue; }").expect_err("should error"),
|
*engine
|
||||||
ParseError(x, _) if *x == ParseErrorType::LoopBreak
|
.compile("let x = 0; if x > 0 { continue; }")
|
||||||
));
|
.expect_err("should error")
|
||||||
|
.0,
|
||||||
|
ParseErrorType::LoopBreak
|
||||||
|
);
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
File diff suppressed because one or more lines are too long
Loading…
Reference in New Issue
Block a user