Merge branch 'master' into plugins

This commit is contained in:
Stephen Chung 2020-08-08 16:26:52 +08:00
commit fbad20eb0d
31 changed files with 914 additions and 172 deletions

4
.gitignore vendored
View File

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

View File

@ -42,7 +42,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.

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,7 @@
Advanced Patterns
=================
{{#include ../links.md}}
Leverage the full power and flexibility of Rhai in advanced scenarios.

View File

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

View 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();
```

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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. /// 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. /// 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 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 { 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(), 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. /// 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),
@ -1062,6 +1104,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),
@ -1073,6 +1116,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),
@ -1084,6 +1128,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),
@ -1097,6 +1142,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),
@ -1107,6 +1153,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)
@ -1145,38 +1192,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(),
@ -1185,6 +1239,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(),
@ -1193,6 +1248,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
@ -1203,11 +1259,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))
} }

View File

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

View File

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

View File

@ -929,11 +929,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.get_inner_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()
}; };

View File

@ -22,13 +22,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 {}
@ -50,15 +43,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 {
@ -68,16 +52,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: 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). /// 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: Clone>(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];

View File

@ -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.get_inner_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.get_inner_clone()))
} }
/// Get an iterator to entries in the Scope. /// Get an iterator to entries in the Scope.

View File

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

View File

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

View File

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

View File

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