Merge pull request #213 from schungx/master

Fix closure bug.
This commit is contained in:
Stephen Chung 2020-08-08 23:22:56 +08:00 committed by GitHub
commit 126dc58ea5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
34 changed files with 940 additions and 180 deletions

View File

@ -38,6 +38,7 @@ jobs:
- {toolchain: stable, os: macos-latest, experimental: false, flags: ""}
- {toolchain: beta, os: ubuntu-latest, experimental: false, flags: ""}
- {toolchain: nightly, os: ubuntu-latest, experimental: true, flags: ""}
fail-fast: false
steps:
- name: Checkout
uses: actions/checkout@v2

4
.gitignore vendored
View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

View File

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

View File

@ -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
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 |
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,9 +40,8 @@ Examples
let factor = 1;
// Define the object
let obj =
#{
data: 0,
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'

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`
(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.

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

View File

@ -91,7 +91,7 @@ struct MyStruct {
let engine = Engine::new();
let result: Dynamic = engine.eval(r#"
#{
##{
a: 42,
b: [ "hello", "world" ],
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.
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.

View File

@ -56,7 +56,7 @@ mod example {
let result: Dynamic = engine
.eval(
r#"
#{
##{
a: 42,
b: [ "hello", "world" ],
c: true,

View File

@ -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.
/// Flatten the `Dynamic` and clone it.
///
/// If the `Dynamic` is not a shared value, it returns a cloned copy of the value.
/// If the `Dynamic` is not a shared value, it returns a cloned copy.
///
/// If the `Dynamic` is a shared value, it returns a cloned copy of the shared value.
///
/// 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 flatten_clone(&self) -> Self {
match &self.0 {
#[cfg(not(feature = "no_closure"))]
Union::Shared(cell) => {
#[cfg(not(feature = "sync"))]
return cell.borrow().clone();
#[cfg(feature = "sync")]
return cell.read().unwrap().clone();
}
_ => self.clone(),
}
}
/// Flatten the `Dynamic`.
///
/// If the `Dynamic` is not a shared value, it returns itself.
///
/// If the `Dynamic` is a shared value, it returns the shared value if there are
/// no outstanding references, or a cloned copy.
#[inline(always)]
pub fn flatten(self) -> Self {
match self.0 {
#[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(), |v| v.into_inner().unwrap());
}
_ => self.try_cast(),
_ => self,
}
}
@ -1043,6 +1084,7 @@ impl Dynamic {
/// Cast the `Dynamic` as the system integer type `INT` and return it.
/// 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),
@ -1055,6 +1097,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),
@ -1066,6 +1109,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),
@ -1077,6 +1121,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),
@ -1090,6 +1135,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),
@ -1100,6 +1146,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)
@ -1138,38 +1185,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(),
@ -1178,6 +1232,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(),
@ -1186,6 +1241,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
@ -1196,11 +1252,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))
}

View File

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

View File

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

View File

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

View File

@ -21,13 +21,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 {}
@ -49,15 +42,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 {
@ -67,16 +51,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>(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();
pub fn shared_take<T>(value: Shared<T>) -> T {
shared_try_take(value).map_err(|_| ()).unwrap()
}
pub type FnCallArgs<'a> = [&'a mut Dynamic];

View File

@ -1634,6 +1634,12 @@ fn parse_primary(
Token::FloatConstant(x) => Expr::FloatConstant(Box::new(FloatWrapper(x, settings.pos))),
Token::CharConstant(c) => Expr::CharConstant(Box::new((c, settings.pos))),
Token::StringConstant(s) => Expr::StringConstant(Box::new((s.into(), settings.pos))),
// Function call
Token::Identifier(s) if *next_token == Token::LeftParen || *next_token == Token::Bang => {
Expr::Variable(Box::new(((s, settings.pos), None, 0, None)))
}
// Normal variable access
Token::Identifier(s) => {
let index = state.access_var(&s, settings.pos);
Expr::Variable(Box::new(((s, settings.pos), None, 0, index)))

View File

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

View File

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

View File

@ -1,12 +1,13 @@
#![cfg(not(feature = "no_function"))]
use rhai::{Dynamic, Engine, EvalAltResult, FnPtr, Module, INT};
use rhai::{Dynamic, Engine, EvalAltResult, FnPtr, Module, RegisterFn, INT};
use std::any::TypeId;
#[test]
fn test_fn_ptr_curry_call() -> Result<(), Box<EvalAltResult>> {
let mut module = Module::new();
let mut engine = Engine::new();
module.set_raw_fn(
#[allow(deprecated)]
engine.register_raw_fn(
"call_with_arg",
&[TypeId::of::<FnPtr>(), TypeId::of::<INT>()],
|engine: &Engine, lib: &Module, args: &mut [&mut Dynamic]| {
@ -15,9 +16,6 @@ fn test_fn_ptr_curry_call() -> Result<(), Box<EvalAltResult>> {
},
);
let mut engine = Engine::new();
engine.load_package(module);
#[cfg(not(feature = "no_object"))]
assert_eq!(
engine.eval::<INT>(
@ -38,7 +36,7 @@ fn test_fn_ptr_curry_call() -> Result<(), Box<EvalAltResult>> {
#[cfg(not(feature = "no_closure"))]
#[cfg(not(feature = "no_object"))]
fn test_closures() -> Result<(), Box<EvalAltResult>> {
let engine = Engine::new();
let mut engine = Engine::new();
assert_eq!(
engine.eval::<INT>(
@ -77,6 +75,19 @@ fn test_closures() -> Result<(), Box<EvalAltResult>> {
"#
)?);
engine.register_fn("plus_one", |x: INT| x + 1);
assert_eq!(
engine.eval::<INT>(
r#"
let a = 41;
let f = || plus_one(a);
f.call()
"#
)?,
42
);
Ok(())
}

View File

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

View File

@ -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,7 +156,8 @@ fn test_function_captures() -> Result<(), Box<EvalAltResult>> {
#[cfg(not(feature = "no_object"))]
assert!(matches!(
engine.compile(
*engine
.compile(
r#"
fn foo() { this += x; }
@ -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(())

View File

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