Merge branch 'master' into plugins
This commit is contained in:
commit
fbad20eb0d
4
.gitignore
vendored
4
.gitignore
vendored
@ -3,5 +3,5 @@ Cargo.lock
|
||||
.vscode/
|
||||
.cargo/
|
||||
doc/book/
|
||||
before
|
||||
after
|
||||
before*
|
||||
after*
|
||||
|
@ -42,7 +42,7 @@ Standard features
|
||||
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.
|
||||
* 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
|
||||
==============
|
||||
|
||||
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
|
||||
==============
|
||||
|
@ -100,8 +100,12 @@ The Rhai Scripting Language
|
||||
8. [Maximum Call Stack Depth](safety/max-call-stack.md)
|
||||
9. [Maximum Statement Depth](safety/max-stmt-depth.md)
|
||||
7. [Advanced Topics](advanced.md)
|
||||
1. [Capture Scope for Function Call](language/fn-capture.md)
|
||||
2. [Object-Oriented Programming (OOP)](language/oop.md)
|
||||
1. [Advanced Patterns](patterns/index.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)
|
||||
4. [Script Optimization](engine/optimize/index.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).
|
||||
|
||||
* 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
|
||||
------
|
||||
|
@ -5,9 +5,9 @@ Advanced Topics
|
||||
|
||||
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.
|
||||
|
||||
|
@ -3,9 +3,15 @@ Arrays
|
||||
|
||||
{{#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.
|
||||
|
||||
|
@ -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
|
||||
of using the `call` function keyword.
|
||||
|
||||
When a property holding a [function pointer] is called like a method, what happens next depends
|
||||
on whether the target function is a native Rust function or a script-defined function.
|
||||
When a property holding a [function pointer] (which incudes [closures]) is called like a method,
|
||||
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
|
||||
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.
|
||||
|
||||
```rust
|
||||
fn do_action(x) { this.data += x; } // 'this' binds to the object when called
|
||||
|
||||
let obj = #{
|
||||
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
|
||||
|
||||
@ -36,5 +35,7 @@ obj.data == 42;
|
||||
// To achieve the above with normal function pointer call will fail.
|
||||
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
|
||||
```
|
||||
|
@ -19,21 +19,36 @@ Object Map Literals
|
||||
------------------
|
||||
|
||||
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.
|
||||
|
||||
|
||||
Access Properties
|
||||
----------------
|
||||
-----------------
|
||||
|
||||
Property values can be accessed via the _dot_ notation (_object_ `.` _property_)
|
||||
or _index_ notation (_object_ `[` _property_ `]`).
|
||||
### Dot Notation
|
||||
|
||||
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
|
||||
@ -89,7 +104,7 @@ let foo = #{ a:1, b:2, c:3 }["a"];
|
||||
foo == 1;
|
||||
|
||||
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;
|
||||
|
@ -76,6 +76,8 @@
|
||||
|
||||
[function]: {{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 pointers]: {{rootUrl}}/language/fn-ptr.md
|
||||
[currying]: {{rootUrl}}/language/fn-curry.md
|
||||
@ -97,9 +99,10 @@
|
||||
|
||||
[`eval`]: {{rootUrl}}/language/eval.md
|
||||
|
||||
[OOP]: {{rootUrl}}/language/oop.md
|
||||
[OOP]: {{rootUrl}}/patterns/oop.md
|
||||
[DSL]: {{rootUrl}}/engine/dsl.md
|
||||
|
||||
[sand-boxed]: {{rootUrl}}/safety/sandbox.md
|
||||
[maximum statement depth]: {{rootUrl}}/safety/max-stmt-depth.md
|
||||
[maximum call stack depth]: {{rootUrl}}/safety/max-call-stack.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 |
|
||||
|
||||
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
|
||||
dispatched to the actual function with `this` binding to the [object map] itself.
|
||||
a valid [function pointer] (perhaps defined via an [anonymous function] or more commonly as a [closure]),
|
||||
then the call will be dispatched to the actual function with `this` binding to the [object map] itself.
|
||||
|
||||
|
||||
Use Anonymous Functions to Define Methods
|
||||
----------------------------------------
|
||||
|
||||
[Anonymous functions] defined as values for [object map] properties take on a syntactic shape
|
||||
that resembles very closely that of class methods in an OOP language.
|
||||
[Anonymous functions] or [closures] defined as values for [object map] properties take on
|
||||
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
|
||||
can be turned off via the [`no_closure`] feature.
|
||||
|
||||
@ -40,23 +40,22 @@ Examples
|
||||
let factor = 1;
|
||||
|
||||
// Define the object
|
||||
let obj =
|
||||
#{
|
||||
data: 0,
|
||||
increment: |x| this.data += x, // 'this' binds to 'obj'
|
||||
update: |x| this.data = x * factor, // 'this' binds to 'obj', 'factor' is captured
|
||||
action: || print(this.data) // 'this' binds to 'obj'
|
||||
};
|
||||
let obj = #{
|
||||
data: 0, // object field
|
||||
increment: |x| this.data += x, // 'this' binds to 'obj'
|
||||
update: |x| this.data = x * factor, // 'this' binds to 'obj', 'factor' is captured
|
||||
action: || print(this.data) // 'this' binds to 'obj'
|
||||
};
|
||||
|
||||
// Use the object
|
||||
obj.increment(1);
|
||||
obj.action(); // prints 1
|
||||
obj.action(); // prints 1
|
||||
|
||||
obj.update(42);
|
||||
obj.action(); // prints 42
|
||||
obj.action(); // prints 42
|
||||
|
||||
factor = 2;
|
||||
|
||||
obj.update(42);
|
||||
obj.action(); // prints 84
|
||||
obj.action(); // prints 84
|
||||
```
|
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`
|
||||
(in the `RegisterFn` trait) and `Engine::register_result_fn` (in the `RegisterResultFn` trait,
|
||||
see [fallible functions]({{rootUrl}}/rust/fallible.md)).
|
||||
see [fallible functions]).
|
||||
|
||||
```rust
|
||||
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
|
||||
```
|
||||
|
||||
|
||||
Function Overloading
|
||||
--------------------
|
||||
|
||||
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
|
||||
and/or different number.
|
||||
or different number.
|
||||
|
||||
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 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.
|
||||
|
||||
|
@ -91,7 +91,7 @@ struct MyStruct {
|
||||
let engine = Engine::new();
|
||||
|
||||
let result: Dynamic = engine.eval(r#"
|
||||
#{
|
||||
##{
|
||||
a: 42,
|
||||
b: [ "hello", "world" ],
|
||||
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.
|
||||
|
||||
Furthermore, an [`Engine`] created non-`mut` cannot mutate any state outside of itself;
|
||||
so it is highly recommended that [`Engine`]'s are created immutable as much as possible.
|
||||
Furthermore, an [`Engine`] created non-`mut` cannot mutate any state, including itself
|
||||
(and therefore it is also _re-entrant_).
|
||||
|
||||
It is highly recommended that [`Engine`]'s be created immutable as much as possible.
|
||||
|
||||
```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'
|
||||
|
||||
let engine = engine; // shadow the variable so that 'engine' is now immutable
|
||||
// 'engine' is 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
|
||||
.eval(
|
||||
r#"
|
||||
#{
|
||||
##{
|
||||
a: 42,
|
||||
b: [ "hello", "world" ],
|
||||
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};
|
||||
|
||||
#[cfg(not(feature = "no_closure"))]
|
||||
use crate::fn_native::SharedMut;
|
||||
use crate::fn_native::{shared_try_take, Shared};
|
||||
|
||||
#[cfg(not(feature = "no_float"))]
|
||||
use crate::parser::FLOAT;
|
||||
@ -134,6 +134,7 @@ impl<T: Any + Clone + SendSync> Variant for T {
|
||||
|
||||
impl dyn Variant {
|
||||
/// Is this `Variant` a specific type?
|
||||
#[inline(always)]
|
||||
pub fn is<T: Any>(&self) -> bool {
|
||||
TypeId::of::<T>() == self.type_id()
|
||||
}
|
||||
@ -158,9 +159,15 @@ pub enum Union {
|
||||
#[cfg(not(feature = "no_object"))]
|
||||
Map(Box<Map>),
|
||||
FnPtr(Box<FnPtr>),
|
||||
|
||||
Variant(Box<Box<dyn Variant>>),
|
||||
|
||||
#[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`.
|
||||
@ -175,6 +182,7 @@ pub struct DynamicReadLock<'d, T: Variant + Clone>(DynamicReadLockInner<'d, T>);
|
||||
enum DynamicReadLockInner<'d, T: Variant + Clone> {
|
||||
/// A simple reference to a non-shared value.
|
||||
Reference(&'d T),
|
||||
|
||||
/// A read guard to a shared `RefCell`.
|
||||
#[cfg(not(feature = "no_closure"))]
|
||||
#[cfg(not(feature = "sync"))]
|
||||
@ -211,6 +219,7 @@ pub struct DynamicWriteLock<'d, T: Variant + Clone>(DynamicWriteLockInner<'d, T>
|
||||
enum DynamicWriteLockInner<'d, T: Variant + Clone> {
|
||||
/// A simple mutable reference to a non-shared value.
|
||||
Reference(&'d mut T),
|
||||
|
||||
/// A write guard to a shared `RefCell`.
|
||||
#[cfg(not(feature = "no_closure"))]
|
||||
#[cfg(not(feature = "sync"))]
|
||||
@ -250,6 +259,7 @@ impl<'d, T: Variant + Clone> DerefMut for DynamicWriteLock<'d, T> {
|
||||
impl Dynamic {
|
||||
/// Does this `Dynamic` hold a variant data type
|
||||
/// instead of one of the support system primitive types?
|
||||
#[inline(always)]
|
||||
pub fn is_variant(&self) -> bool {
|
||||
match self.0 {
|
||||
Union::Variant(_) => true,
|
||||
@ -259,6 +269,7 @@ impl Dynamic {
|
||||
|
||||
/// Does this `Dynamic` hold a shared data type
|
||||
/// instead of one of the supported system primitive types?
|
||||
#[inline(always)]
|
||||
pub fn is_shared(&self) -> bool {
|
||||
match self.0 {
|
||||
#[cfg(not(feature = "no_closure"))]
|
||||
@ -271,6 +282,7 @@ impl Dynamic {
|
||||
///
|
||||
/// If the `Dynamic` is a Shared variant checking is performed on
|
||||
/// top of it's internal value.
|
||||
#[inline(always)]
|
||||
pub fn is<T: Variant + Clone>(&self) -> bool {
|
||||
let mut target_type_id = TypeId::of::<T>();
|
||||
|
||||
@ -301,7 +313,9 @@ impl Dynamic {
|
||||
#[cfg(not(feature = "no_object"))]
|
||||
Union::Map(_) => TypeId::of::<Map>(),
|
||||
Union::FnPtr(_) => TypeId::of::<FnPtr>(),
|
||||
|
||||
Union::Variant(value) => (***value).type_id(),
|
||||
|
||||
#[cfg(not(feature = "no_closure"))]
|
||||
#[cfg(not(feature = "sync"))]
|
||||
Union::Shared(cell) => (*cell.borrow()).type_id(),
|
||||
@ -335,6 +349,7 @@ impl Dynamic {
|
||||
#[cfg(not(feature = "no_std"))]
|
||||
Union::Variant(value) if value.is::<Instant>() => "timestamp",
|
||||
Union::Variant(value) => (***value).type_name(),
|
||||
|
||||
#[cfg(not(feature = "no_closure"))]
|
||||
#[cfg(not(feature = "sync"))]
|
||||
Union::Shared(cell) => cell
|
||||
@ -399,6 +414,7 @@ impl fmt::Display for Dynamic {
|
||||
#[cfg(not(feature = "no_std"))]
|
||||
Union::Variant(value) if value.is::<Instant>() => f.write_str("<timestamp>"),
|
||||
Union::Variant(value) => f.write_str((*value).type_name()),
|
||||
|
||||
#[cfg(not(feature = "no_closure"))]
|
||||
#[cfg(not(feature = "sync"))]
|
||||
Union::Shared(cell) => {
|
||||
@ -437,6 +453,7 @@ impl fmt::Debug for Dynamic {
|
||||
#[cfg(not(feature = "no_std"))]
|
||||
Union::Variant(value) if value.is::<Instant>() => write!(f, "<timestamp>"),
|
||||
Union::Variant(value) => write!(f, "{}", (*value).type_name()),
|
||||
|
||||
#[cfg(not(feature = "no_closure"))]
|
||||
#[cfg(not(feature = "sync"))]
|
||||
Union::Shared(cell) => {
|
||||
@ -468,7 +485,9 @@ impl Clone for Dynamic {
|
||||
#[cfg(not(feature = "no_object"))]
|
||||
Union::Map(ref value) => Self(Union::Map(value.clone())),
|
||||
Union::FnPtr(ref value) => Self(Union::FnPtr(value.clone())),
|
||||
|
||||
Union::Variant(ref value) => (***value).clone_into_dynamic(),
|
||||
|
||||
#[cfg(not(feature = "no_closure"))]
|
||||
Union::Shared(ref cell) => Self(Union::Shared(cell.clone())),
|
||||
}
|
||||
@ -476,6 +495,7 @@ impl Clone for Dynamic {
|
||||
}
|
||||
|
||||
impl Default for Dynamic {
|
||||
#[inline(always)]
|
||||
fn default() -> Self {
|
||||
Self(Union::Unit(()))
|
||||
}
|
||||
@ -769,25 +789,46 @@ impl Dynamic {
|
||||
self.try_cast::<T>().unwrap()
|
||||
}
|
||||
|
||||
/// Get a copy of the `Dynamic` as a specific type.
|
||||
/// Dereference the `Dynamic`.
|
||||
///
|
||||
/// 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.
|
||||
///
|
||||
/// Returns `None` if the cast fails.
|
||||
/// If the `Dynamic` is a shared value, it a cloned copy of the shared value.
|
||||
#[inline(always)]
|
||||
pub fn clone_inner_data<T: Variant + Clone>(self) -> Option<T> {
|
||||
pub fn get_inner_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 {
|
||||
#[cfg(not(feature = "no_closure"))]
|
||||
Union::Shared(cell) => {
|
||||
#[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")]
|
||||
return Some(cell.read().unwrap().downcast_ref::<T>().unwrap().clone());
|
||||
return shared_try_take(cell)
|
||||
.map_or_else(|c| c.read().unwrap().clone(), RwLock::into_inner);
|
||||
}
|
||||
_ => self.try_cast(),
|
||||
_ => self,
|
||||
}
|
||||
}
|
||||
|
||||
@ -1050,6 +1091,7 @@ impl Dynamic {
|
||||
|
||||
/// Cast the `Dynamic` as the system integer type `INT` and return it.
|
||||
/// Returns the name of the actual type if the cast fails.
|
||||
#[inline(always)]
|
||||
pub fn as_int(&self) -> Result<INT, &'static str> {
|
||||
match self.0 {
|
||||
Union::Int(n) => Ok(n),
|
||||
@ -1062,6 +1104,7 @@ impl Dynamic {
|
||||
/// Cast the `Dynamic` as the system floating-point type `FLOAT` and return it.
|
||||
/// Returns the name of the actual type if the cast fails.
|
||||
#[cfg(not(feature = "no_float"))]
|
||||
#[inline(always)]
|
||||
pub fn as_float(&self) -> Result<FLOAT, &'static str> {
|
||||
match self.0 {
|
||||
Union::Float(n) => Ok(n),
|
||||
@ -1073,6 +1116,7 @@ impl Dynamic {
|
||||
|
||||
/// Cast the `Dynamic` as a `bool` and return it.
|
||||
/// Returns the name of the actual type if the cast fails.
|
||||
#[inline(always)]
|
||||
pub fn as_bool(&self) -> Result<bool, &'static str> {
|
||||
match self.0 {
|
||||
Union::Bool(b) => Ok(b),
|
||||
@ -1084,6 +1128,7 @@ impl Dynamic {
|
||||
|
||||
/// Cast the `Dynamic` as a `char` and return it.
|
||||
/// Returns the name of the actual type if the cast fails.
|
||||
#[inline(always)]
|
||||
pub fn as_char(&self) -> Result<char, &'static str> {
|
||||
match self.0 {
|
||||
Union::Char(n) => Ok(n),
|
||||
@ -1097,6 +1142,7 @@ impl Dynamic {
|
||||
/// Returns the name of the actual type if the cast fails.
|
||||
///
|
||||
/// Cast is failing if `self` is Shared Dynamic
|
||||
#[inline(always)]
|
||||
pub fn as_str(&self) -> Result<&str, &'static str> {
|
||||
match &self.0 {
|
||||
Union::Str(s) => Ok(s),
|
||||
@ -1107,6 +1153,7 @@ impl Dynamic {
|
||||
|
||||
/// Convert the `Dynamic` into `String` and return it.
|
||||
/// Returns the name of the actual type if the cast fails.
|
||||
#[inline(always)]
|
||||
pub fn take_string(self) -> Result<String, &'static str> {
|
||||
self.take_immutable_string()
|
||||
.map(ImmutableString::into_owned)
|
||||
@ -1145,38 +1192,45 @@ impl Dynamic {
|
||||
}
|
||||
|
||||
impl From<()> for Dynamic {
|
||||
#[inline(always)]
|
||||
fn from(value: ()) -> Self {
|
||||
Self(Union::Unit(value))
|
||||
}
|
||||
}
|
||||
impl From<bool> for Dynamic {
|
||||
#[inline(always)]
|
||||
fn from(value: bool) -> Self {
|
||||
Self(Union::Bool(value))
|
||||
}
|
||||
}
|
||||
impl From<INT> for Dynamic {
|
||||
#[inline(always)]
|
||||
fn from(value: INT) -> Self {
|
||||
Self(Union::Int(value))
|
||||
}
|
||||
}
|
||||
#[cfg(not(feature = "no_float"))]
|
||||
impl From<FLOAT> for Dynamic {
|
||||
#[inline(always)]
|
||||
fn from(value: FLOAT) -> Self {
|
||||
Self(Union::Float(value))
|
||||
}
|
||||
}
|
||||
impl From<char> for Dynamic {
|
||||
#[inline(always)]
|
||||
fn from(value: char) -> Self {
|
||||
Self(Union::Char(value))
|
||||
}
|
||||
}
|
||||
impl<S: Into<ImmutableString>> From<S> for Dynamic {
|
||||
#[inline(always)]
|
||||
fn from(value: S) -> Self {
|
||||
Self(Union::Str(value.into()))
|
||||
}
|
||||
}
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
impl<T: Variant + Clone> From<Vec<T>> for Dynamic {
|
||||
#[inline(always)]
|
||||
fn from(value: Vec<T>) -> Self {
|
||||
Self(Union::Array(Box::new(
|
||||
value.into_iter().map(Dynamic::from).collect(),
|
||||
@ -1185,6 +1239,7 @@ impl<T: Variant + Clone> From<Vec<T>> for Dynamic {
|
||||
}
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
impl<T: Variant + Clone> From<&[T]> for Dynamic {
|
||||
#[inline(always)]
|
||||
fn from(value: &[T]) -> Self {
|
||||
Self(Union::Array(Box::new(
|
||||
value.iter().cloned().map(Dynamic::from).collect(),
|
||||
@ -1193,6 +1248,7 @@ impl<T: Variant + Clone> From<&[T]> for Dynamic {
|
||||
}
|
||||
#[cfg(not(feature = "no_object"))]
|
||||
impl<K: Into<ImmutableString>, T: Variant + Clone> From<HashMap<K, T>> for Dynamic {
|
||||
#[inline(always)]
|
||||
fn from(value: HashMap<K, T>) -> Self {
|
||||
Self(Union::Map(Box::new(
|
||||
value
|
||||
@ -1203,11 +1259,13 @@ impl<K: Into<ImmutableString>, T: Variant + Clone> From<HashMap<K, T>> for Dynam
|
||||
}
|
||||
}
|
||||
impl From<FnPtr> for Dynamic {
|
||||
#[inline(always)]
|
||||
fn from(value: FnPtr) -> Self {
|
||||
Self(Union::FnPtr(Box::new(value)))
|
||||
}
|
||||
}
|
||||
impl From<Box<FnPtr>> for Dynamic {
|
||||
#[inline(always)]
|
||||
fn from(value: Box<FnPtr>) -> Self {
|
||||
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"))]
|
||||
use crate::{
|
||||
engine::{make_getter, make_setter, Map},
|
||||
fn_register::RegisterFn,
|
||||
fn_register::{RegisterFn, RegisterResultFn},
|
||||
token::Token,
|
||||
};
|
||||
|
||||
@ -224,6 +224,54 @@ impl Engine {
|
||||
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`.
|
||||
///
|
||||
/// # Example
|
||||
@ -273,6 +321,59 @@ impl Engine {
|
||||
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
|
||||
/// of a registered type with the `Engine`.
|
||||
///
|
||||
@ -375,6 +476,58 @@ impl Engine {
|
||||
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`.
|
||||
///
|
||||
/// # Example
|
||||
@ -424,6 +577,59 @@ impl Engine {
|
||||
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`.
|
||||
///
|
||||
/// # Example
|
||||
|
@ -147,6 +147,7 @@ pub enum Target<'a> {
|
||||
#[cfg(any(not(feature = "no_index"), not(feature = "no_object")))]
|
||||
impl Target<'_> {
|
||||
/// Is the `Target` a reference pointing to other data?
|
||||
#[inline(always)]
|
||||
pub fn is_ref(&self) -> bool {
|
||||
match self {
|
||||
Self::Ref(_) => true,
|
||||
@ -159,6 +160,7 @@ impl Target<'_> {
|
||||
}
|
||||
}
|
||||
/// Is the `Target` an owned value?
|
||||
#[inline(always)]
|
||||
pub fn is_value(&self) -> bool {
|
||||
match self {
|
||||
Self::Ref(_) => false,
|
||||
@ -171,6 +173,7 @@ impl Target<'_> {
|
||||
}
|
||||
}
|
||||
/// Is the `Target` a shared value?
|
||||
#[inline(always)]
|
||||
pub fn is_shared(&self) -> bool {
|
||||
match self {
|
||||
Self::Ref(r) => r.is_shared(),
|
||||
@ -184,6 +187,7 @@ impl Target<'_> {
|
||||
}
|
||||
/// Is the `Target` a specific type?
|
||||
#[allow(dead_code)]
|
||||
#[inline(always)]
|
||||
pub fn is<T: Variant + Clone>(&self) -> bool {
|
||||
match self {
|
||||
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.
|
||||
#[inline(always)]
|
||||
pub fn clone_into_dynamic(self) -> Dynamic {
|
||||
match self {
|
||||
Self::Ref(r) => r.clone(), // Referenced value is cloned
|
||||
@ -208,6 +213,7 @@ impl Target<'_> {
|
||||
}
|
||||
}
|
||||
/// Get a mutable reference from the `Target`.
|
||||
#[inline(always)]
|
||||
pub fn as_mut(&mut self) -> &mut Dynamic {
|
||||
match self {
|
||||
Self::Ref(r) => *r,
|
||||
@ -258,6 +264,7 @@ impl Target<'_> {
|
||||
|
||||
#[cfg(any(not(feature = "no_index"), not(feature = "no_object")))]
|
||||
impl<'a> From<&'a mut Dynamic> for Target<'a> {
|
||||
#[inline(always)]
|
||||
fn from(value: &'a mut Dynamic) -> Self {
|
||||
#[cfg(not(feature = "no_closure"))]
|
||||
#[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")))]
|
||||
impl<T: Into<Dynamic>> From<T> for Target<'_> {
|
||||
#[inline(always)]
|
||||
fn from(value: T) -> Self {
|
||||
Self::Value(value.into())
|
||||
}
|
||||
@ -301,6 +309,7 @@ pub struct State {
|
||||
|
||||
impl State {
|
||||
/// Create a new `State`.
|
||||
#[inline(always)]
|
||||
pub fn new() -> Self {
|
||||
Default::default()
|
||||
}
|
||||
@ -407,6 +416,7 @@ pub struct Engine {
|
||||
}
|
||||
|
||||
impl fmt::Debug for Engine {
|
||||
#[inline(always)]
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self.id.as_ref() {
|
||||
Some(id) => write!(f, "Engine({})", id),
|
||||
@ -689,7 +699,7 @@ impl Engine {
|
||||
idx_values: &mut StaticVec<Dynamic>,
|
||||
chain_type: ChainType,
|
||||
level: usize,
|
||||
mut _new_val: Option<Dynamic>,
|
||||
new_val: Option<Dynamic>,
|
||||
) -> Result<(Dynamic, bool), Box<EvalAltResult>> {
|
||||
if chain_type == ChainType::None {
|
||||
panic!();
|
||||
@ -722,12 +732,12 @@ impl Engine {
|
||||
|
||||
self.eval_dot_index_chain_helper(
|
||||
state, lib, this_ptr, obj_ptr, expr, idx_values, next_chain, level,
|
||||
_new_val,
|
||||
new_val,
|
||||
)
|
||||
.map_err(|err| err.new_position(*pos))
|
||||
}
|
||||
// xxx[rhs] = new_val
|
||||
_ if _new_val.is_some() => {
|
||||
_ if new_val.is_some() => {
|
||||
let mut idx_val2 = idx_val.clone();
|
||||
|
||||
// `call_setter` is introduced to bypass double mutable borrowing of target
|
||||
@ -737,7 +747,7 @@ impl Engine {
|
||||
// Indexed value is a reference - update directly
|
||||
Ok(ref mut obj_ptr) => {
|
||||
obj_ptr
|
||||
.set_value(_new_val.unwrap())
|
||||
.set_value(new_val.unwrap())
|
||||
.map_err(|err| err.new_position(rhs.position()))?;
|
||||
|
||||
None
|
||||
@ -747,7 +757,7 @@ impl Engine {
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
EvalAltResult::ErrorIndexingType(_, _) => {
|
||||
// Raise error if there is no index getter nor setter
|
||||
Some(_new_val.unwrap())
|
||||
Some(new_val.unwrap())
|
||||
}
|
||||
// Any other error - return
|
||||
err => return Err(Box::new(err)),
|
||||
@ -799,13 +809,13 @@ impl Engine {
|
||||
// xxx.module::fn_name(...) - syntax error
|
||||
Expr::FnCall(_) => unreachable!(),
|
||||
// {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 index = prop.clone().into();
|
||||
let mut val = self
|
||||
.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()))?;
|
||||
Ok((Default::default(), true))
|
||||
}
|
||||
@ -820,9 +830,10 @@ impl Engine {
|
||||
Ok((val.clone_into_dynamic(), false))
|
||||
}
|
||||
// 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 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(
|
||||
state, lib, setter, 0, &mut args, is_ref, true, false, None, None,
|
||||
level,
|
||||
@ -872,7 +883,7 @@ impl Engine {
|
||||
|
||||
self.eval_dot_index_chain_helper(
|
||||
state, lib, this_ptr, &mut val, expr, idx_values, next_chain, level,
|
||||
_new_val,
|
||||
new_val,
|
||||
)
|
||||
.map_err(|err| err.new_position(*pos))
|
||||
}
|
||||
@ -905,7 +916,7 @@ impl Engine {
|
||||
idx_values,
|
||||
next_chain,
|
||||
level,
|
||||
_new_val,
|
||||
new_val,
|
||||
)
|
||||
.map_err(|err| err.new_position(*pos))?;
|
||||
|
||||
@ -944,7 +955,7 @@ impl Engine {
|
||||
|
||||
self.eval_dot_index_chain_helper(
|
||||
state, lib, this_ptr, target, expr, idx_values, next_chain,
|
||||
level, _new_val,
|
||||
level, new_val,
|
||||
)
|
||||
.map_err(|err| err.new_position(*pos))
|
||||
}
|
||||
@ -1114,7 +1125,7 @@ impl Engine {
|
||||
state: &mut State,
|
||||
_lib: &Module,
|
||||
target: &'a mut Target,
|
||||
mut _idx: Dynamic,
|
||||
idx: Dynamic,
|
||||
idx_pos: Position,
|
||||
_create: bool,
|
||||
_indexers: bool,
|
||||
@ -1132,7 +1143,7 @@ impl Engine {
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
Dynamic(Union::Array(arr)) => {
|
||||
// val_array[idx]
|
||||
let index = _idx
|
||||
let index = idx
|
||||
.as_int()
|
||||
.map_err(|_| EvalAltResult::ErrorNumericIndexExpr(idx_pos))?;
|
||||
|
||||
@ -1153,13 +1164,13 @@ impl Engine {
|
||||
Dynamic(Union::Map(map)) => {
|
||||
// val_map[idx]
|
||||
Ok(if _create {
|
||||
let index = _idx
|
||||
let index = idx
|
||||
.take_immutable_string()
|
||||
.map_err(|_| EvalAltResult::ErrorStringIndexExpr(idx_pos))?;
|
||||
|
||||
map.entry(index).or_insert(Default::default()).into()
|
||||
} else {
|
||||
let index = _idx
|
||||
let index = idx
|
||||
.read_lock::<ImmutableString>()
|
||||
.ok_or_else(|| EvalAltResult::ErrorStringIndexExpr(idx_pos))?;
|
||||
|
||||
@ -1173,7 +1184,7 @@ impl Engine {
|
||||
Dynamic(Union::Str(s)) => {
|
||||
// val_string[idx]
|
||||
let chars_len = s.chars().count();
|
||||
let index = _idx
|
||||
let index = idx
|
||||
.as_int()
|
||||
.map_err(|_| EvalAltResult::ErrorNumericIndexExpr(idx_pos))?;
|
||||
|
||||
@ -1192,7 +1203,8 @@ impl Engine {
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
_ if _indexers => {
|
||||
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(
|
||||
state, _lib, FN_IDX_GET, 0, args, is_ref, true, false, None, None, _level,
|
||||
)
|
||||
@ -1331,11 +1343,11 @@ impl Engine {
|
||||
)),
|
||||
// Normal assignment
|
||||
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() {
|
||||
*lhs_ptr.write_lock::<Dynamic>().unwrap() = rhs_val;
|
||||
*lhs_ptr.write_lock::<Dynamic>().unwrap() = value;
|
||||
} else {
|
||||
*lhs_ptr = rhs_val;
|
||||
*lhs_ptr = value;
|
||||
}
|
||||
Ok(Default::default())
|
||||
}
|
||||
@ -1378,7 +1390,7 @@ impl Engine {
|
||||
)
|
||||
.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() {
|
||||
*lhs_ptr.write_lock::<Dynamic>().unwrap() = value;
|
||||
} else {
|
||||
@ -1674,14 +1686,14 @@ impl Engine {
|
||||
let index = scope.len() - 1;
|
||||
state.scope_level += 1;
|
||||
|
||||
for loop_var in func(iter_type) {
|
||||
let for_var = scope.get_mut(index).0;
|
||||
let value = loop_var.clone_inner_data().unwrap();
|
||||
for iter_value in func(iter_type) {
|
||||
let (loop_var, _) = scope.get_mut(index);
|
||||
|
||||
if cfg!(not(feature = "no_closure")) && for_var.is_shared() {
|
||||
*for_var.write_lock().unwrap() = value;
|
||||
let value = iter_value.flatten();
|
||||
if cfg!(not(feature = "no_closure")) && loop_var.is_shared() {
|
||||
*loop_var.write_lock().unwrap() = value;
|
||||
} else {
|
||||
*for_var = value;
|
||||
*loop_var = value;
|
||||
}
|
||||
|
||||
self.inc_operations(state)
|
||||
@ -1750,8 +1762,7 @@ impl Engine {
|
||||
let expr = expr.as_ref().unwrap();
|
||||
let val = self
|
||||
.eval_expr(scope, mods, state, lib, this_ptr, expr, level)?
|
||||
.clone_inner_data()
|
||||
.unwrap();
|
||||
.flatten();
|
||||
let var_name = unsafe_cast_var_name_to_lifetime(var_name, &state);
|
||||
scope.push_dynamic_value(var_name, ScopeEntryType::Normal, val, false);
|
||||
Ok(Default::default())
|
||||
@ -1769,8 +1780,7 @@ impl Engine {
|
||||
let ((var_name, _), expr, _) = x.as_ref();
|
||||
let val = self
|
||||
.eval_expr(scope, mods, state, lib, this_ptr, &expr, level)?
|
||||
.clone_inner_data()
|
||||
.unwrap();
|
||||
.flatten();
|
||||
let var_name = unsafe_cast_var_name_to_lifetime(var_name, &state);
|
||||
scope.push_dynamic_value(var_name, ScopeEntryType::Constant, val, true);
|
||||
Ok(Default::default())
|
||||
@ -2000,6 +2010,7 @@ impl Engine {
|
||||
}
|
||||
|
||||
/// 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 {
|
||||
self.type_names
|
||||
.as_ref()
|
||||
|
@ -929,11 +929,11 @@ impl Engine {
|
||||
self.inc_operations(state)
|
||||
.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() {
|
||||
arg_values.insert(0, target.clone().clone_inner_data().unwrap());
|
||||
arg_values.insert(0, target.get_inner_clone());
|
||||
arg_values.iter_mut().collect()
|
||||
} else {
|
||||
// Turn it into a method call only if the object is not shared
|
||||
is_ref = true;
|
||||
once(target).chain(arg_values.iter_mut()).collect()
|
||||
};
|
||||
|
@ -22,13 +22,6 @@ use crate::stdlib::rc::Rc;
|
||||
#[cfg(feature = "sync")]
|
||||
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.
|
||||
#[cfg(feature = "sync")]
|
||||
pub trait SendSync: Send + Sync {}
|
||||
@ -50,15 +43,6 @@ pub type Shared<T> = Rc<T>;
|
||||
#[cfg(feature = "sync")]
|
||||
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.
|
||||
/// 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 {
|
||||
@ -68,16 +52,21 @@ pub fn shared_make_mut<T: Clone>(value: &mut Shared<T>) -> &mut T {
|
||||
return Arc::make_mut(value);
|
||||
}
|
||||
|
||||
/// Consume a `Shared` resource if is unique (i.e. not shared).
|
||||
pub fn shared_try_take<T: Clone>(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).
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics if the resource is shared (i.e. has other outstanding references).
|
||||
pub fn shared_take<T: Clone>(value: Shared<T>) -> T {
|
||||
#[cfg(not(feature = "sync"))]
|
||||
return Rc::try_unwrap(value).map_err(|_| ()).unwrap();
|
||||
#[cfg(feature = "sync")]
|
||||
return Arc::try_unwrap(value).map_err(|_| ()).unwrap();
|
||||
shared_try_take(value).map_err(|_| ()).unwrap()
|
||||
}
|
||||
|
||||
pub type FnCallArgs<'a> = [&'a mut Dynamic];
|
||||
|
@ -338,7 +338,7 @@ impl<'a> Scope<'a> {
|
||||
/// ```
|
||||
pub fn get_value<T: Variant + Clone>(&self, name: &str) -> Option<T> {
|
||||
self.get_entry(name)
|
||||
.and_then(|Entry { value, .. }| value.clone().clone_inner_data::<T>())
|
||||
.and_then(|Entry { value, .. }| value.get_inner_clone().try_cast())
|
||||
}
|
||||
|
||||
/// Update the value of the named entry.
|
||||
@ -441,7 +441,7 @@ impl<'a> Scope<'a> {
|
||||
/// ```
|
||||
pub fn iter(&self) -> impl Iterator<Item = (&str, Dynamic)> {
|
||||
self.iter_raw()
|
||||
.map(|(name, value)| (name, value.clone().clone_inner_data().unwrap()))
|
||||
.map(|(name, value)| (name, value.get_inner_clone()))
|
||||
}
|
||||
|
||||
/// Get an iterator to entries in the Scope.
|
||||
|
@ -1,7 +1,6 @@
|
||||
#![cfg(not(feature = "no_function"))]
|
||||
use rhai::{
|
||||
Dynamic, Engine, EvalAltResult, FnPtr, Func, Module, ParseError, ParseErrorType, RegisterFn,
|
||||
Scope, INT,
|
||||
Dynamic, Engine, EvalAltResult, FnPtr, Func, Module, ParseErrorType, RegisterFn, Scope, INT,
|
||||
};
|
||||
use std::any::TypeId;
|
||||
|
||||
@ -10,12 +9,13 @@ fn test_fn() -> Result<(), Box<EvalAltResult>> {
|
||||
let engine = Engine::new();
|
||||
|
||||
// Expect duplicated parameters error
|
||||
assert!(matches!(
|
||||
engine
|
||||
assert_eq!(
|
||||
*engine
|
||||
.compile("fn hello(x, x) { x }")
|
||||
.expect_err("should be error"),
|
||||
ParseError(x, _) if *x == ParseErrorType::FnDuplicatedParam("hello".to_string(), "x".to_string())
|
||||
));
|
||||
.expect_err("should be error")
|
||||
.0,
|
||||
ParseErrorType::FnDuplicatedParam("hello".to_string(), "x".to_string())
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
#![cfg(not(feature = "unchecked"))]
|
||||
use rhai::{Engine, EvalAltResult, ParseError, ParseErrorType};
|
||||
use rhai::{Engine, EvalAltResult, ParseErrorType};
|
||||
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
use rhai::Array;
|
||||
@ -12,15 +12,21 @@ fn test_max_string_size() -> Result<(), Box<EvalAltResult>> {
|
||||
let mut engine = Engine::new();
|
||||
engine.set_max_string_size(10);
|
||||
|
||||
assert!(matches!(
|
||||
engine.compile(r#"let x = "hello, world!";"#).expect_err("should error"),
|
||||
ParseError(x, _) if *x == ParseErrorType::LiteralTooLarge("Length of string literal".to_string(), 10)
|
||||
));
|
||||
assert_eq!(
|
||||
*engine
|
||||
.compile(r#"let x = "hello, world!";"#)
|
||||
.expect_err("should error")
|
||||
.0,
|
||||
ParseErrorType::LiteralTooLarge("Length of string literal".to_string(), 10)
|
||||
);
|
||||
|
||||
assert!(matches!(
|
||||
engine.compile(r#"let x = "朝に紅顔、暮に白骨";"#).expect_err("should error"),
|
||||
ParseError(x, _) if *x == ParseErrorType::LiteralTooLarge("Length of string literal".to_string(), 10)
|
||||
));
|
||||
assert_eq!(
|
||||
*engine
|
||||
.compile(r#"let x = "朝に紅顔、暮に白骨";"#)
|
||||
.expect_err("should error")
|
||||
.0,
|
||||
ParseErrorType::LiteralTooLarge("Length of string literal".to_string(), 10)
|
||||
);
|
||||
|
||||
assert!(matches!(
|
||||
*engine
|
||||
@ -74,12 +80,13 @@ fn test_max_array_size() -> Result<(), Box<EvalAltResult>> {
|
||||
#[cfg(not(feature = "no_object"))]
|
||||
engine.set_max_map_size(10);
|
||||
|
||||
assert!(matches!(
|
||||
engine
|
||||
assert_eq!(
|
||||
*engine
|
||||
.compile("let x = [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15];")
|
||||
.expect_err("should error"),
|
||||
ParseError(x, _) if *x == ParseErrorType::LiteralTooLarge("Size of array literal".to_string(), 10)
|
||||
));
|
||||
.expect_err("should error")
|
||||
.0,
|
||||
ParseErrorType::LiteralTooLarge("Size of array literal".to_string(), 10)
|
||||
);
|
||||
|
||||
assert!(matches!(
|
||||
*engine
|
||||
@ -186,12 +193,18 @@ fn test_max_map_size() -> Result<(), Box<EvalAltResult>> {
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
engine.set_max_array_size(10);
|
||||
|
||||
assert!(matches!(
|
||||
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};")
|
||||
.expect_err("should error"),
|
||||
ParseError(x, _) if *x == ParseErrorType::LiteralTooLarge("Number of properties in object map literal".to_string(), 10)
|
||||
));
|
||||
assert_eq!(
|
||||
*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};"
|
||||
)
|
||||
.expect_err("should error")
|
||||
.0,
|
||||
ParseErrorType::LiteralTooLarge(
|
||||
"Number of properties in object map literal".to_string(),
|
||||
10
|
||||
)
|
||||
);
|
||||
|
||||
assert!(matches!(
|
||||
*engine
|
||||
|
@ -1,5 +1,6 @@
|
||||
#![cfg(not(feature = "no_function"))]
|
||||
use rhai::{Engine, EvalAltResult, ParseError, ParseErrorType, INT};
|
||||
use rhai::{Engine, EvalAltResult, ParseErrorType, INT};
|
||||
use std::io::Read;
|
||||
|
||||
#[test]
|
||||
fn test_functions() -> Result<(), Box<EvalAltResult>> {
|
||||
@ -155,8 +156,9 @@ fn test_function_captures() -> Result<(), Box<EvalAltResult>> {
|
||||
|
||||
#[cfg(not(feature = "no_object"))]
|
||||
assert!(matches!(
|
||||
engine.compile(
|
||||
r#"
|
||||
*engine
|
||||
.compile(
|
||||
r#"
|
||||
fn foo() { this += x; }
|
||||
|
||||
let x = 41;
|
||||
@ -164,8 +166,10 @@ fn test_function_captures() -> Result<(), Box<EvalAltResult>> {
|
||||
|
||||
y.foo!();
|
||||
"#
|
||||
).expect_err("should error"),
|
||||
ParseError(err, _) if matches!(*err, ParseErrorType::MalformedCapture(_))
|
||||
)
|
||||
.expect_err("should error")
|
||||
.0,
|
||||
ParseErrorType::MalformedCapture(_)
|
||||
));
|
||||
|
||||
Ok(())
|
||||
|
@ -1,4 +1,4 @@
|
||||
use rhai::{Engine, EvalAltResult, ParseError, ParseErrorType, INT};
|
||||
use rhai::{Engine, EvalAltResult, ParseErrorType, INT};
|
||||
|
||||
#[test]
|
||||
fn test_loop() -> Result<(), Box<EvalAltResult>> {
|
||||
@ -26,15 +26,21 @@ fn test_loop() -> Result<(), Box<EvalAltResult>> {
|
||||
21
|
||||
);
|
||||
|
||||
assert!(matches!(
|
||||
engine.compile("let x = 0; break;").expect_err("should error"),
|
||||
ParseError(x, _) if *x == ParseErrorType::LoopBreak
|
||||
));
|
||||
assert_eq!(
|
||||
*engine
|
||||
.compile("let x = 0; break;")
|
||||
.expect_err("should error")
|
||||
.0,
|
||||
ParseErrorType::LoopBreak
|
||||
);
|
||||
|
||||
assert!(matches!(
|
||||
engine.compile("let x = 0; if x > 0 { continue; }").expect_err("should error"),
|
||||
ParseError(x, _) if *x == ParseErrorType::LoopBreak
|
||||
));
|
||||
assert_eq!(
|
||||
*engine
|
||||
.compile("let x = 0; if x > 0 { continue; }")
|
||||
.expect_err("should error")
|
||||
.0,
|
||||
ParseErrorType::LoopBreak
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
File diff suppressed because one or more lines are too long
Loading…
Reference in New Issue
Block a user