Implement closures.
This commit is contained in:
parent
747c0345f2
commit
4079164bfd
3
.github/workflows/build.yml
vendored
3
.github/workflows/build.yml
vendored
@ -30,8 +30,7 @@ jobs:
|
||||
- "--features no_object"
|
||||
- "--features no_function"
|
||||
- "--features no_module"
|
||||
- "--features no_capture"
|
||||
- "--features no_shared"
|
||||
- "--features no_closure"
|
||||
- "--features unicode-xid-ident"
|
||||
toolchain: [stable]
|
||||
experimental: [false]
|
||||
|
@ -33,14 +33,13 @@ only_i64 = [] # set INT=i64 (default) and disable support for all other in
|
||||
no_index = [] # no arrays and indexing
|
||||
no_object = [] # no custom objects
|
||||
no_function = [] # no script-defined functions
|
||||
no_capture = [] # no automatic read/write binding of anonymous function's local variables to it's external context
|
||||
no_shared = [] # no shared values
|
||||
no_closure = [] # no automatic sharing and capture of anonymous functions to external variables
|
||||
no_module = [] # no modules
|
||||
internals = [] # expose internal data structures
|
||||
unicode-xid-ident = ["unicode-xid"] # allow Unicode Standard Annex #31 for identifiers.
|
||||
|
||||
# compiling for no-std
|
||||
no_std = [ "no_shared", "num-traits/libm", "hashbrown", "core-error", "libm", "ahash" ]
|
||||
no_std = [ "no_closure", "num-traits/libm", "hashbrown", "core-error", "libm", "ahash" ]
|
||||
|
||||
[profile.release]
|
||||
lto = "fat"
|
||||
|
@ -25,6 +25,7 @@ New features
|
||||
* Capturing of the calling scope for function call via the `func!(...)` syntax.
|
||||
* `Module::set_indexer_get_set_fn` is added as a shorthand of both `Module::set_indexer_get_fn` and `Module::set_indexer_set_fn`.
|
||||
* New `unicode-xid-ident` feature to allow [Unicode Standard Annex #31](http://www.unicode.org/reports/tr31/) for identifiers.
|
||||
* `Scope::iter_raw` returns an iterator with a reference to the underlying `Dynamic` value (which may be shared).
|
||||
|
||||
Breaking changes
|
||||
----------------
|
||||
@ -33,6 +34,7 @@ Breaking changes
|
||||
* Function signature for defining custom syntax is simplified.
|
||||
* `Engine::register_raw_fn_XXX` API shortcuts are removed.
|
||||
* `PackagesCollection::get_fn`, `PackagesCollection::contains_fn`, `Module::get_fn` and `Module::contains_fn` now take an additional `public_only` parameter indicating whether only public functions are accepted.
|
||||
* The iterator returned by `Scope::iter` now contains a clone of the `Dynamic` value (unshared).
|
||||
|
||||
Housekeeping
|
||||
------------
|
||||
|
@ -10,8 +10,6 @@ Keywords List
|
||||
| `let` | Variable declaration | | No |
|
||||
| `const` | Constant declaration | | No |
|
||||
| `is_shared` | Is a value shared? | | No |
|
||||
| `shared` | Share value | [`no_shared`] | No |
|
||||
| `take` | Un-share value | [`no_shared`] | No |
|
||||
| `if` | If statement | | No |
|
||||
| `else` | else block of if statement | | No |
|
||||
| `while` | While loop | | No |
|
||||
@ -44,6 +42,7 @@ Reserved Keywords
|
||||
| --------- | --------------------- |
|
||||
| `var` | Variable declaration |
|
||||
| `static` | Variable declaration |
|
||||
| `shared` | Share value |
|
||||
| `do` | Looping |
|
||||
| `each` | Looping |
|
||||
| `then` | Control flow |
|
||||
|
@ -22,7 +22,7 @@ fn print_obj() { print(this.data); }
|
||||
```
|
||||
|
||||
The above can be replaced by using _anonymous functions_ which have the same syntax as Rust's closures
|
||||
(but they are **NOT** closures, merely syntactic sugar):
|
||||
(but they are **NOT** real closures, merely syntactic sugar):
|
||||
|
||||
```rust
|
||||
let obj = #{
|
||||
@ -50,12 +50,10 @@ fn anon_fn_1002() { print this.data; }
|
||||
```
|
||||
|
||||
|
||||
WARNING - NOT Closures
|
||||
----------------------
|
||||
WARNING - NOT Real Closures
|
||||
--------------------------
|
||||
|
||||
Remember: anonymous functions, though having the same syntax as Rust _closures_, are themselves
|
||||
**not** closures. In particular, they do not capture their execution environment. They are more like
|
||||
Rust's function pointers.
|
||||
|
||||
They do, however, _capture_ variable _values_ from their execution environment, unless the [`no_capture`]
|
||||
feature is turned on. This is accomplished via [automatic currying][capture].
|
||||
**not** real closures.
|
||||
In particular, they capture their execution environment via [automatic currying][capture],
|
||||
unless the [`no_closure`] feature is turned on.
|
||||
|
@ -15,7 +15,7 @@ is created.
|
||||
|
||||
Variables that are accessible during the time the [anonymous function] is created can be captured,
|
||||
as long as they are not shadowed by local variables defined within the function's scope.
|
||||
The values captured are the values of those variables at the time of the [anonymous function]'s creation.
|
||||
The captured variables are automatically converted into reference-counted shared values.
|
||||
|
||||
|
||||
New Parameters For Captured Variables
|
||||
@ -29,28 +29,32 @@ In actual implementation, this de-sugars to:
|
||||
|
||||
3. The variable is added to the parameters list of the anonymous function, at the front.
|
||||
|
||||
4. The current value of the variable is then [curried][currying] into the [function pointer] itself, essentially carrying that value and inserting it into future calls of the function.
|
||||
4. The variable is then turned into a reference-counted shared value.
|
||||
|
||||
Automatic currying can be turned off via the [`no_capture`] feature.
|
||||
5. The shared value is then [curried][currying] into the [function pointer] itself, essentially carrying a reference to that shared value and inserting it into future calls of the function.
|
||||
|
||||
Automatic currying can be turned off via the [`no_closure`] feature.
|
||||
|
||||
|
||||
Examples
|
||||
--------
|
||||
|
||||
```rust
|
||||
let x = 40;
|
||||
let x = 1;
|
||||
|
||||
let f = |y| x + y; // current value of variable 'x' is auto-curried
|
||||
// the value 40 is curried into 'f'
|
||||
let f = |y| x + y; // variable 'x' is auto-curried (captured) into 'f'
|
||||
// 'x' is converted into a shared value
|
||||
|
||||
x = 1; // 'x' can be changed but the curried value is not
|
||||
x = 40; // 'x' can be changed
|
||||
|
||||
f.call(2) == 42; // the value of 'x' is still 40
|
||||
f.call(2) == 42; // the value of 'x' is 40 because 'x' is shared
|
||||
|
||||
// The above de-sugars into this:
|
||||
fn anon$1001(x, y) { x + y } // parameter 'x' is inserted
|
||||
|
||||
let f = Fn("anon$1001").curry(x); // current value of 'x' is curried
|
||||
make_shared(x); // convert 'x' into a shared value
|
||||
|
||||
let f = Fn("anon$1001").curry(x); // shared 'x' is curried
|
||||
|
||||
f.call(2) == 42;
|
||||
```
|
||||
|
@ -9,7 +9,7 @@ The following are reserved keywords in Rhai:
|
||||
| ------------------------------------------------- | ------------------------------------------------ | --------------------- | :--------------------: |
|
||||
| `true`, `false` | | Boolean constants | |
|
||||
| `let`, `const` | `var`, `static` | Variable declarations | |
|
||||
| `shared`, `take`, `is_shared` | | Shared values | [`no_shared`] |
|
||||
| `is_shared` | | Shared values | [`no_closure`] |
|
||||
| `if`, `else` | `then`, `goto`, `exit` | Control flow | |
|
||||
| | `switch`, `match`, `case` | Matching | |
|
||||
| `while`, `loop`, `for`, `in`, `continue`, `break` | `do`, `each` | Looping | |
|
||||
|
@ -30,20 +30,22 @@ 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
|
||||
common OOP pattern. Capturing is accomplished via a feature called _[automatic currying]_ and
|
||||
can be turned off via the [`no_capture`] feature.
|
||||
can be turned off via the [`no_closure`] feature.
|
||||
|
||||
|
||||
Examples
|
||||
--------
|
||||
|
||||
```rust
|
||||
let factor = 1;
|
||||
|
||||
// Define the object
|
||||
let obj =
|
||||
#{
|
||||
data: 0,
|
||||
increment: |x| this.data += x, // when called, 'this' binds to 'obj'
|
||||
update: |x| this.data = x, // when called, 'this' binds to 'obj'
|
||||
action: || print(this.data) // when called, '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
|
||||
action: || print(this.data) // 'this' binds to 'obj'
|
||||
};
|
||||
|
||||
// Use the object
|
||||
@ -52,4 +54,9 @@ obj.action(); // prints 1
|
||||
|
||||
obj.update(42);
|
||||
obj.action(); // prints 42
|
||||
|
||||
factor = 2;
|
||||
|
||||
obj.update(42);
|
||||
obj.action(); // prints 84
|
||||
```
|
||||
|
@ -9,8 +9,7 @@
|
||||
[`no_object`]: {{rootUrl}}/start/features.md
|
||||
[`no_function`]: {{rootUrl}}/start/features.md
|
||||
[`no_module`]: {{rootUrl}}/start/features.md
|
||||
[`no_capture`]: {{rootUrl}}/start/features.md
|
||||
[`no_shared`]: {{rootUrl}}/start/features.md
|
||||
[`no_closure`]: {{rootUrl}}/start/features.md
|
||||
[`no_std`]: {{rootUrl}}/start/features.md
|
||||
[`no-std`]: {{rootUrl}}/start/features.md
|
||||
[`internals`]: {{rootUrl}}/start/features.md
|
||||
|
@ -23,9 +23,8 @@ more control over what a script can (or cannot) do.
|
||||
| `no_object` | Disable support for [custom types] and [object maps]. |
|
||||
| `no_function` | Disable script-defined [functions]. |
|
||||
| `no_module` | Disable loading external [modules]. |
|
||||
| `no_capture` | Disable [capturing][capture] external variables in [anonymous functions] and [capturing the calling scope]({{rootUrl}}/language/fn-capture.md) in function calls. |
|
||||
| `no_shared` | Disable sharing of data values. |
|
||||
| `no_std` | Build for `no-std` (implies `no_shared`). Notice that additional dependencies will be pulled in to replace `std` features. |
|
||||
| `no_closure` | Disable [capturing][capture] external variables in [anonymous functions] to simulate _closures_, or [capturing the calling scope]({{rootUrl}}/language/fn-capture.md) in function calls. |
|
||||
| `no_std` | Build for `no-std` (implies `no_closure`). Notice that additional dependencies will be pulled in to replace `std` features. |
|
||||
| `serde` | Enable serialization/deserialization via `serde`. Notice that the [`serde`](https://crates.io/crates/serde) crate will be pulled in together with its dependencies. |
|
||||
| `internals` | Expose internal data structures (e.g. [`AST`] nodes). Beware that Rhai internals are volatile and may change from version to version. |
|
||||
| `unicode-xid-ident` | Allow [Unicode Standard Annex #31](http://www.unicode.org/reports/tr31/) as identifiers. |
|
||||
|
@ -114,10 +114,15 @@ fn main() {
|
||||
}
|
||||
"exit" | "quit" => break, // quit
|
||||
"scope" => {
|
||||
scope
|
||||
.iter()
|
||||
.enumerate()
|
||||
.for_each(|(i, (name, value))| println!("[{}] {} = {:?}", i + 1, name, value));
|
||||
scope.iter_raw().enumerate().for_each(|(i, (name, value))| {
|
||||
println!(
|
||||
"[{}] {}{} = {:?}",
|
||||
i + 1,
|
||||
name,
|
||||
if value.is_shared() { " (shared)" } else { "" },
|
||||
*value.read_lock::<Dynamic>().unwrap(),
|
||||
)
|
||||
});
|
||||
continue;
|
||||
}
|
||||
"astu" => {
|
||||
|
146
src/any.rs
146
src/any.rs
@ -4,7 +4,7 @@ use crate::fn_native::{FnPtr, SendSync};
|
||||
use crate::parser::{ImmutableString, INT};
|
||||
use crate::r#unsafe::{unsafe_cast_box, unsafe_try_cast};
|
||||
|
||||
#[cfg(not(feature = "no_shared"))]
|
||||
#[cfg(not(feature = "no_closure"))]
|
||||
use crate::fn_native::SharedMut;
|
||||
|
||||
#[cfg(not(feature = "no_float"))]
|
||||
@ -24,14 +24,14 @@ use crate::stdlib::{
|
||||
string::String,
|
||||
};
|
||||
|
||||
#[cfg(not(feature = "no_shared"))]
|
||||
#[cfg(not(feature = "no_closure"))]
|
||||
#[cfg(not(feature = "sync"))]
|
||||
use crate::stdlib::{
|
||||
cell::{Ref, RefCell, RefMut},
|
||||
rc::Rc,
|
||||
};
|
||||
|
||||
#[cfg(not(feature = "no_shared"))]
|
||||
#[cfg(not(feature = "no_closure"))]
|
||||
#[cfg(feature = "sync")]
|
||||
use crate::stdlib::sync::{Arc, RwLock, RwLockReadGuard, RwLockWriteGuard};
|
||||
|
||||
@ -159,7 +159,7 @@ pub enum Union {
|
||||
Map(Box<Map>),
|
||||
FnPtr(Box<FnPtr>),
|
||||
Variant(Box<Box<dyn Variant>>),
|
||||
#[cfg(not(feature = "no_shared"))]
|
||||
#[cfg(not(feature = "no_closure"))]
|
||||
Shared(SharedMut<Dynamic>),
|
||||
}
|
||||
|
||||
@ -176,11 +176,11 @@ 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_shared"))]
|
||||
#[cfg(not(feature = "no_closure"))]
|
||||
#[cfg(not(feature = "sync"))]
|
||||
Guard(Ref<'d, Dynamic>),
|
||||
/// A read guard to a shared `RwLock`.
|
||||
#[cfg(not(feature = "no_shared"))]
|
||||
#[cfg(not(feature = "no_closure"))]
|
||||
#[cfg(feature = "sync")]
|
||||
Guard(RwLockReadGuard<'d, Dynamic>),
|
||||
}
|
||||
@ -193,7 +193,7 @@ impl<'d, T: Variant + Clone> Deref for DynamicReadLock<'d, T> {
|
||||
match &self.0 {
|
||||
DynamicReadLockInner::Reference(reference) => *reference,
|
||||
// Unwrapping is safe because all checking is already done in its constructor
|
||||
#[cfg(not(feature = "no_shared"))]
|
||||
#[cfg(not(feature = "no_closure"))]
|
||||
DynamicReadLockInner::Guard(guard) => guard.downcast_ref().unwrap(),
|
||||
}
|
||||
}
|
||||
@ -212,11 +212,11 @@ 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_shared"))]
|
||||
#[cfg(not(feature = "no_closure"))]
|
||||
#[cfg(not(feature = "sync"))]
|
||||
Guard(RefMut<'d, Dynamic>),
|
||||
/// A write guard to a shared `RwLock`.
|
||||
#[cfg(not(feature = "no_shared"))]
|
||||
#[cfg(not(feature = "no_closure"))]
|
||||
#[cfg(feature = "sync")]
|
||||
Guard(RwLockWriteGuard<'d, Dynamic>),
|
||||
}
|
||||
@ -229,7 +229,7 @@ impl<'d, T: Variant + Clone> Deref for DynamicWriteLock<'d, T> {
|
||||
match &self.0 {
|
||||
DynamicWriteLockInner::Reference(reference) => *reference,
|
||||
// Unwrapping is safe because all checking is already done in its constructor
|
||||
#[cfg(not(feature = "no_shared"))]
|
||||
#[cfg(not(feature = "no_closure"))]
|
||||
DynamicWriteLockInner::Guard(guard) => guard.downcast_ref().unwrap(),
|
||||
}
|
||||
}
|
||||
@ -241,7 +241,7 @@ impl<'d, T: Variant + Clone> DerefMut for DynamicWriteLock<'d, T> {
|
||||
match &mut self.0 {
|
||||
DynamicWriteLockInner::Reference(reference) => *reference,
|
||||
// Unwrapping is safe because all checking is already done in its constructor
|
||||
#[cfg(not(feature = "no_shared"))]
|
||||
#[cfg(not(feature = "no_closure"))]
|
||||
DynamicWriteLockInner::Guard(guard) => guard.downcast_mut().unwrap(),
|
||||
}
|
||||
}
|
||||
@ -261,7 +261,7 @@ impl Dynamic {
|
||||
/// instead of one of the supported system primitive types?
|
||||
pub fn is_shared(&self) -> bool {
|
||||
match self.0 {
|
||||
#[cfg(not(feature = "no_shared"))]
|
||||
#[cfg(not(feature = "no_closure"))]
|
||||
Union::Shared(_) => true,
|
||||
_ => false,
|
||||
}
|
||||
@ -302,10 +302,10 @@ impl Dynamic {
|
||||
Union::Map(_) => TypeId::of::<Map>(),
|
||||
Union::FnPtr(_) => TypeId::of::<FnPtr>(),
|
||||
Union::Variant(value) => (***value).type_id(),
|
||||
#[cfg(not(feature = "no_shared"))]
|
||||
#[cfg(not(feature = "no_closure"))]
|
||||
#[cfg(not(feature = "sync"))]
|
||||
Union::Shared(cell) => (*cell.borrow()).type_id(),
|
||||
#[cfg(not(feature = "no_shared"))]
|
||||
#[cfg(not(feature = "no_closure"))]
|
||||
#[cfg(feature = "sync")]
|
||||
Union::Shared(cell) => (*cell.read().unwrap()).type_id(),
|
||||
}
|
||||
@ -335,10 +335,10 @@ 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_shared"))]
|
||||
#[cfg(not(feature = "no_closure"))]
|
||||
#[cfg(not(feature = "sync"))]
|
||||
Union::Shared(cell) => (*cell.borrow()).type_name(),
|
||||
#[cfg(not(feature = "no_shared"))]
|
||||
#[cfg(not(feature = "no_closure"))]
|
||||
#[cfg(feature = "sync")]
|
||||
Union::Shared(cell) => (*cell.read().unwrap()).type_name(),
|
||||
}
|
||||
@ -396,7 +396,7 @@ impl fmt::Display 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_shared"))]
|
||||
#[cfg(not(feature = "no_closure"))]
|
||||
Union::Shared(_) => f.write_str("<shared>"),
|
||||
}
|
||||
}
|
||||
@ -424,7 +424,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_shared"))]
|
||||
#[cfg(not(feature = "no_closure"))]
|
||||
Union::Shared(_) => f.write_str("<shared>"),
|
||||
}
|
||||
}
|
||||
@ -446,7 +446,7 @@ impl Clone for Dynamic {
|
||||
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_shared"))]
|
||||
#[cfg(not(feature = "no_closure"))]
|
||||
Union::Shared(ref cell) => Self(Union::Shared(cell.clone())),
|
||||
}
|
||||
}
|
||||
@ -571,9 +571,9 @@ impl Dynamic {
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics under the `no_shared` feature.
|
||||
/// Panics under the `no_closure` feature.
|
||||
pub fn into_shared(self) -> Self {
|
||||
#[cfg(not(feature = "no_shared"))]
|
||||
#[cfg(not(feature = "no_closure"))]
|
||||
return match self.0 {
|
||||
Union::Shared(..) => self,
|
||||
#[cfg(not(feature = "sync"))]
|
||||
@ -582,7 +582,7 @@ impl Dynamic {
|
||||
_ => Self(Union::Shared(Arc::new(RwLock::new(self)))),
|
||||
};
|
||||
|
||||
#[cfg(feature = "no_shared")]
|
||||
#[cfg(feature = "no_closure")]
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
@ -614,11 +614,11 @@ impl Dynamic {
|
||||
let type_id = TypeId::of::<T>();
|
||||
|
||||
match self.0 {
|
||||
#[cfg(not(feature = "no_shared"))]
|
||||
#[cfg(not(feature = "no_closure"))]
|
||||
#[cfg(not(feature = "sync"))]
|
||||
Union::Shared(cell) => return cell.borrow().clone().try_cast(),
|
||||
|
||||
#[cfg(not(feature = "no_shared"))]
|
||||
#[cfg(not(feature = "no_closure"))]
|
||||
#[cfg(feature = "sync")]
|
||||
Union::Shared(cell) => return cell.read().unwrap().clone().try_cast(),
|
||||
_ => (),
|
||||
@ -703,7 +703,7 @@ impl Dynamic {
|
||||
|
||||
match self.0 {
|
||||
Union::Variant(value) => (*value).as_box_any().downcast().map(|x| *x).ok(),
|
||||
#[cfg(not(feature = "no_shared"))]
|
||||
#[cfg(not(feature = "no_closure"))]
|
||||
Union::Shared(_) => unreachable!(),
|
||||
_ => None,
|
||||
}
|
||||
@ -748,17 +748,17 @@ impl Dynamic {
|
||||
///
|
||||
/// Returns `None` if the cast fails.
|
||||
#[inline(always)]
|
||||
pub fn clone_inner_data<T: Variant + Clone>(&self) -> Option<T> {
|
||||
pub fn clone_inner_data<T: Variant + Clone>(self) -> Option<T> {
|
||||
match self.0 {
|
||||
#[cfg(not(feature = "no_shared"))]
|
||||
Union::Shared(ref cell) => {
|
||||
#[cfg(not(feature = "no_closure"))]
|
||||
Union::Shared(cell) => {
|
||||
#[cfg(not(feature = "sync"))]
|
||||
return Some(cell.borrow().downcast_ref::<T>().unwrap().clone());
|
||||
|
||||
#[cfg(feature = "sync")]
|
||||
return Some(cell.read().unwrap().downcast_ref::<T>().unwrap().clone());
|
||||
}
|
||||
_ => self.downcast_ref().cloned(),
|
||||
_ => self.try_cast(),
|
||||
}
|
||||
}
|
||||
|
||||
@ -772,7 +772,7 @@ impl Dynamic {
|
||||
#[inline(always)]
|
||||
pub fn is_locked(&self) -> bool {
|
||||
match self.0 {
|
||||
#[cfg(not(feature = "no_shared"))]
|
||||
#[cfg(not(feature = "no_closure"))]
|
||||
Union::Shared(ref _cell) => {
|
||||
#[cfg(not(feature = "sync"))]
|
||||
return _cell.try_borrow().is_err();
|
||||
@ -788,22 +788,33 @@ impl Dynamic {
|
||||
/// Casting to `Dynamic` just returns a reference to it.
|
||||
///
|
||||
/// Returns `None` if the cast fails.
|
||||
///
|
||||
/// # Panics and Deadlocks When Value is Shared
|
||||
///
|
||||
/// Under the `sync` feature, this call may deadlock.
|
||||
/// Otherwise, this call panics if the data is currently borrowed for write.
|
||||
#[inline(always)]
|
||||
pub fn read_lock<T: Variant + Clone>(&self) -> Option<DynamicReadLock<T>> {
|
||||
match self.0 {
|
||||
#[cfg(not(feature = "no_shared"))]
|
||||
#[cfg(not(feature = "no_closure"))]
|
||||
Union::Shared(ref cell) => {
|
||||
#[cfg(not(feature = "sync"))]
|
||||
return Some(DynamicReadLock(DynamicReadLockInner::Guard(cell.borrow())));
|
||||
let data = cell.borrow();
|
||||
|
||||
#[cfg(feature = "sync")]
|
||||
return Some(DynamicReadLock(DynamicReadLockInner::Guard(
|
||||
cell.read().unwrap(),
|
||||
)));
|
||||
let data = cell.read().unwrap();
|
||||
|
||||
let type_id = (*data).type_id();
|
||||
|
||||
if type_id != TypeId::of::<T>() && TypeId::of::<Dynamic>() != TypeId::of::<T>() {
|
||||
None
|
||||
} else {
|
||||
Some(DynamicReadLock(DynamicReadLockInner::Guard(data)))
|
||||
}
|
||||
}
|
||||
_ => self
|
||||
.downcast_ref()
|
||||
.map(|reference| DynamicReadLock(DynamicReadLockInner::Reference(reference))),
|
||||
.map(|r| DynamicReadLock(DynamicReadLockInner::Reference(r))),
|
||||
}
|
||||
}
|
||||
|
||||
@ -811,24 +822,33 @@ impl Dynamic {
|
||||
/// Casting to `Dynamic` just returns a mutable reference to it.
|
||||
///
|
||||
/// Returns `None` if the cast fails.
|
||||
///
|
||||
/// # Panics and Deadlocks When Value is Shared
|
||||
///
|
||||
/// Under the `sync` feature, this call may deadlock.
|
||||
/// Otherwise, this call panics if the data is currently borrowed for write.
|
||||
#[inline(always)]
|
||||
pub fn write_lock<T: Variant + Clone>(&mut self) -> Option<DynamicWriteLock<T>> {
|
||||
match self.0 {
|
||||
#[cfg(not(feature = "no_shared"))]
|
||||
#[cfg(not(feature = "no_closure"))]
|
||||
Union::Shared(ref cell) => {
|
||||
#[cfg(not(feature = "sync"))]
|
||||
return Some(DynamicWriteLock(DynamicWriteLockInner::Guard(
|
||||
cell.borrow_mut(),
|
||||
)));
|
||||
let data = cell.borrow_mut();
|
||||
|
||||
#[cfg(feature = "sync")]
|
||||
return Some(DynamicWriteLock(DynamicWriteLockInner::Guard(
|
||||
cell.write().unwrap(),
|
||||
)));
|
||||
let data = cell.write().unwrap();
|
||||
|
||||
let type_id = (*data).type_id();
|
||||
|
||||
if type_id != TypeId::of::<T>() && TypeId::of::<Dynamic>() != TypeId::of::<T>() {
|
||||
None
|
||||
} else {
|
||||
Some(DynamicWriteLock(DynamicWriteLockInner::Guard(data)))
|
||||
}
|
||||
}
|
||||
_ => self
|
||||
.downcast_mut()
|
||||
.map(|reference| DynamicWriteLock(DynamicWriteLockInner::Reference(reference))),
|
||||
.map(|r| DynamicWriteLock(DynamicWriteLockInner::Reference(r))),
|
||||
}
|
||||
}
|
||||
|
||||
@ -836,6 +856,10 @@ impl Dynamic {
|
||||
/// Casting to `Dynamic` just returns a reference to it.
|
||||
///
|
||||
/// Returns `None` if the cast fails.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics if the value is shared.
|
||||
#[inline(always)]
|
||||
fn downcast_ref<T: Variant + Clone>(&self) -> Option<&T> {
|
||||
let type_id = TypeId::of::<T>();
|
||||
@ -909,7 +933,7 @@ impl Dynamic {
|
||||
|
||||
match &self.0 {
|
||||
Union::Variant(value) => value.as_ref().as_ref().as_any().downcast_ref::<T>(),
|
||||
#[cfg(not(feature = "no_shared"))]
|
||||
#[cfg(not(feature = "no_closure"))]
|
||||
Union::Shared(_) => unreachable!(),
|
||||
_ => None,
|
||||
}
|
||||
@ -919,6 +943,10 @@ impl Dynamic {
|
||||
/// Casting to `Dynamic` just returns a mutable reference to it.
|
||||
///
|
||||
/// Returns `None` if the cast fails.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics if the value is shared.
|
||||
#[inline(always)]
|
||||
fn downcast_mut<T: Variant + Clone>(&mut self) -> Option<&mut T> {
|
||||
let type_id = TypeId::of::<T>();
|
||||
@ -986,7 +1014,7 @@ impl Dynamic {
|
||||
|
||||
match &mut self.0 {
|
||||
Union::Variant(value) => value.as_mut().as_mut_any().downcast_mut::<T>(),
|
||||
#[cfg(not(feature = "no_shared"))]
|
||||
#[cfg(not(feature = "no_closure"))]
|
||||
Union::Shared(_) => unreachable!(),
|
||||
_ => None,
|
||||
}
|
||||
@ -997,10 +1025,8 @@ impl Dynamic {
|
||||
pub fn as_int(&self) -> Result<INT, &'static str> {
|
||||
match self.0 {
|
||||
Union::Int(n) => Ok(n),
|
||||
#[cfg(not(feature = "no_shared"))]
|
||||
Union::Shared(_) => self
|
||||
.clone_inner_data::<INT>()
|
||||
.ok_or_else(|| self.type_name()),
|
||||
#[cfg(not(feature = "no_closure"))]
|
||||
Union::Shared(_) => self.read_lock().map(|v| *v).ok_or_else(|| self.type_name()),
|
||||
_ => Err(self.type_name()),
|
||||
}
|
||||
}
|
||||
@ -1011,10 +1037,8 @@ impl Dynamic {
|
||||
pub fn as_float(&self) -> Result<FLOAT, &'static str> {
|
||||
match self.0 {
|
||||
Union::Float(n) => Ok(n),
|
||||
#[cfg(not(feature = "no_shared"))]
|
||||
Union::Shared(_) => self
|
||||
.clone_inner_data::<FLOAT>()
|
||||
.ok_or_else(|| self.type_name()),
|
||||
#[cfg(not(feature = "no_closure"))]
|
||||
Union::Shared(_) => self.read_lock().map(|v| *v).ok_or_else(|| self.type_name()),
|
||||
_ => Err(self.type_name()),
|
||||
}
|
||||
}
|
||||
@ -1024,10 +1048,8 @@ impl Dynamic {
|
||||
pub fn as_bool(&self) -> Result<bool, &'static str> {
|
||||
match self.0 {
|
||||
Union::Bool(b) => Ok(b),
|
||||
#[cfg(not(feature = "no_shared"))]
|
||||
Union::Shared(_) => self
|
||||
.clone_inner_data::<bool>()
|
||||
.ok_or_else(|| self.type_name()),
|
||||
#[cfg(not(feature = "no_closure"))]
|
||||
Union::Shared(_) => self.read_lock().map(|v| *v).ok_or_else(|| self.type_name()),
|
||||
_ => Err(self.type_name()),
|
||||
}
|
||||
}
|
||||
@ -1037,10 +1059,8 @@ impl Dynamic {
|
||||
pub fn as_char(&self) -> Result<char, &'static str> {
|
||||
match self.0 {
|
||||
Union::Char(n) => Ok(n),
|
||||
#[cfg(not(feature = "no_shared"))]
|
||||
Union::Shared(_) => self
|
||||
.clone_inner_data::<char>()
|
||||
.ok_or_else(|| self.type_name()),
|
||||
#[cfg(not(feature = "no_closure"))]
|
||||
Union::Shared(_) => self.read_lock().map(|v| *v).ok_or_else(|| self.type_name()),
|
||||
_ => Err(self.type_name()),
|
||||
}
|
||||
}
|
||||
@ -1070,7 +1090,7 @@ impl Dynamic {
|
||||
match self.0 {
|
||||
Union::Str(s) => Ok(s),
|
||||
Union::FnPtr(f) => Ok(f.take_data().0),
|
||||
#[cfg(not(feature = "no_shared"))]
|
||||
#[cfg(not(feature = "no_closure"))]
|
||||
Union::Shared(cell) => {
|
||||
#[cfg(not(feature = "sync"))]
|
||||
{
|
||||
|
@ -1284,7 +1284,7 @@ impl Engine {
|
||||
let args = args.as_mut();
|
||||
|
||||
// Check for data race.
|
||||
if cfg!(not(feature = "no_shared")) {
|
||||
if cfg!(not(feature = "no_closure")) {
|
||||
ensure_no_data_race(name, args, false)?;
|
||||
}
|
||||
|
||||
|
@ -31,7 +31,7 @@ use crate::module::resolvers;
|
||||
#[cfg(any(not(feature = "no_object"), not(feature = "no_module")))]
|
||||
use crate::utils::ImmutableString;
|
||||
|
||||
#[cfg(not(feature = "no_shared"))]
|
||||
#[cfg(not(feature = "no_closure"))]
|
||||
#[cfg(not(feature = "no_object"))]
|
||||
use crate::any::DynamicWriteLock;
|
||||
|
||||
@ -49,6 +49,9 @@ use crate::stdlib::{
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
use crate::stdlib::any::TypeId;
|
||||
|
||||
#[cfg(not(feature = "no_closure"))]
|
||||
use crate::stdlib::mem;
|
||||
|
||||
/// Variable-sized array of `Dynamic` values.
|
||||
///
|
||||
/// Not available under the `no_index` feature.
|
||||
@ -96,8 +99,6 @@ pub const KEYWORD_EVAL: &str = "eval";
|
||||
pub const KEYWORD_FN_PTR: &str = "Fn";
|
||||
pub const KEYWORD_FN_PTR_CALL: &str = "call";
|
||||
pub const KEYWORD_FN_PTR_CURRY: &str = "curry";
|
||||
pub const KEYWORD_SHARED: &str = "shared";
|
||||
pub const KEYWORD_TAKE: &str = "take";
|
||||
pub const KEYWORD_IS_SHARED: &str = "is_shared";
|
||||
pub const KEYWORD_THIS: &str = "this";
|
||||
pub const FN_TO_STRING: &str = "to_string";
|
||||
@ -132,7 +133,7 @@ pub enum Target<'a> {
|
||||
Ref(&'a mut Dynamic),
|
||||
/// The target is a mutable reference to a Shared `Dynamic` value.
|
||||
/// It holds both the access guard and the original shared value.
|
||||
#[cfg(not(feature = "no_shared"))]
|
||||
#[cfg(not(feature = "no_closure"))]
|
||||
#[cfg(not(feature = "no_object"))]
|
||||
LockGuard((DynamicWriteLock<'a, Dynamic>, Dynamic)),
|
||||
/// The target is a temporary `Dynamic` value (i.e. the mutation can cause no side effects).
|
||||
@ -149,7 +150,7 @@ impl Target<'_> {
|
||||
pub fn is_ref(&self) -> bool {
|
||||
match self {
|
||||
Self::Ref(_) => true,
|
||||
#[cfg(not(feature = "no_shared"))]
|
||||
#[cfg(not(feature = "no_closure"))]
|
||||
#[cfg(not(feature = "no_object"))]
|
||||
Self::LockGuard(_) => true,
|
||||
Self::Value(_) => false,
|
||||
@ -161,7 +162,7 @@ impl Target<'_> {
|
||||
pub fn is_value(&self) -> bool {
|
||||
match self {
|
||||
Self::Ref(_) => false,
|
||||
#[cfg(not(feature = "no_shared"))]
|
||||
#[cfg(not(feature = "no_closure"))]
|
||||
#[cfg(not(feature = "no_object"))]
|
||||
Self::LockGuard(_) => false,
|
||||
Self::Value(_) => true,
|
||||
@ -173,7 +174,7 @@ impl Target<'_> {
|
||||
pub fn is_shared(&self) -> bool {
|
||||
match self {
|
||||
Self::Ref(r) => r.is_shared(),
|
||||
#[cfg(not(feature = "no_shared"))]
|
||||
#[cfg(not(feature = "no_closure"))]
|
||||
#[cfg(not(feature = "no_object"))]
|
||||
Self::LockGuard(_) => true,
|
||||
Self::Value(r) => r.is_shared(),
|
||||
@ -186,7 +187,7 @@ impl Target<'_> {
|
||||
pub fn is<T: Variant + Clone>(&self) -> bool {
|
||||
match self {
|
||||
Target::Ref(r) => r.is::<T>(),
|
||||
#[cfg(not(feature = "no_shared"))]
|
||||
#[cfg(not(feature = "no_closure"))]
|
||||
#[cfg(not(feature = "no_object"))]
|
||||
Target::LockGuard((r, _)) => r.is::<T>(),
|
||||
Target::Value(r) => r.is::<T>(),
|
||||
@ -198,7 +199,7 @@ impl Target<'_> {
|
||||
pub fn clone_into_dynamic(self) -> Dynamic {
|
||||
match self {
|
||||
Self::Ref(r) => r.clone(), // Referenced value is cloned
|
||||
#[cfg(not(feature = "no_shared"))]
|
||||
#[cfg(not(feature = "no_closure"))]
|
||||
#[cfg(not(feature = "no_object"))]
|
||||
Self::LockGuard((_, orig)) => orig, // Original value is simply taken
|
||||
Self::Value(v) => v, // Owned value is simply taken
|
||||
@ -210,7 +211,7 @@ impl Target<'_> {
|
||||
pub fn as_mut(&mut self) -> &mut Dynamic {
|
||||
match self {
|
||||
Self::Ref(r) => *r,
|
||||
#[cfg(not(feature = "no_shared"))]
|
||||
#[cfg(not(feature = "no_closure"))]
|
||||
#[cfg(not(feature = "no_object"))]
|
||||
Self::LockGuard((r, _)) => r.deref_mut(),
|
||||
Self::Value(ref mut r) => r,
|
||||
@ -223,7 +224,7 @@ impl Target<'_> {
|
||||
pub fn set_value(&mut self, new_val: Dynamic) -> Result<(), Box<EvalAltResult>> {
|
||||
match self {
|
||||
Self::Ref(r) => **r = new_val,
|
||||
#[cfg(not(feature = "no_shared"))]
|
||||
#[cfg(not(feature = "no_closure"))]
|
||||
#[cfg(not(feature = "no_object"))]
|
||||
Self::LockGuard((r, _)) => **r = new_val,
|
||||
Self::Value(_) => {
|
||||
@ -260,7 +261,7 @@ impl Target<'_> {
|
||||
#[cfg(any(not(feature = "no_index"), not(feature = "no_object")))]
|
||||
impl<'a> From<&'a mut Dynamic> for Target<'a> {
|
||||
fn from(value: &'a mut Dynamic) -> Self {
|
||||
#[cfg(not(feature = "no_shared"))]
|
||||
#[cfg(not(feature = "no_closure"))]
|
||||
#[cfg(not(feature = "no_object"))]
|
||||
if value.is_shared() {
|
||||
// Cloning is cheap for a shared value
|
||||
@ -632,7 +633,7 @@ pub fn search_scope_only<'s, 'a>(
|
||||
|
||||
// Check for data race - probably not necessary because the only place it should conflict is in a method call
|
||||
// when the object variable is also used as a parameter.
|
||||
// if cfg!(not(feature = "no_shared")) && val.is_locked() {
|
||||
// if cfg!(not(feature = "no_closure")) && val.is_locked() {
|
||||
// return Err(Box::new(EvalAltResult::ErrorDataRace(name.into(), *pos)));
|
||||
// }
|
||||
|
||||
@ -1351,12 +1352,9 @@ impl Engine {
|
||||
)),
|
||||
// Normal assignment
|
||||
ScopeEntryType::Normal if op.is_empty() => {
|
||||
if cfg!(not(feature = "no_shared")) && lhs_ptr.is_shared() {
|
||||
*lhs_ptr.write_lock::<Dynamic>().unwrap() = if rhs_val.is_shared() {
|
||||
rhs_val.clone_inner_data().unwrap()
|
||||
} else {
|
||||
rhs_val
|
||||
};
|
||||
let rhs_val = rhs_val.clone_inner_data().unwrap();
|
||||
if cfg!(not(feature = "no_closure")) && lhs_ptr.is_shared() {
|
||||
*lhs_ptr.write_lock::<Dynamic>().unwrap() = rhs_val;
|
||||
} else {
|
||||
*lhs_ptr = rhs_val;
|
||||
}
|
||||
@ -1377,7 +1375,7 @@ impl Engine {
|
||||
.get_fn(hash_fn, false)
|
||||
.or_else(|| self.packages.get_fn(hash_fn, false))
|
||||
{
|
||||
if cfg!(not(feature = "no_shared")) && lhs_ptr.is_shared() {
|
||||
if cfg!(not(feature = "no_closure")) && lhs_ptr.is_shared() {
|
||||
let mut lock_guard = lhs_ptr.write_lock::<Dynamic>().unwrap();
|
||||
let lhs_ptr_inner = lock_guard.deref_mut();
|
||||
|
||||
@ -1401,12 +1399,9 @@ impl Engine {
|
||||
)
|
||||
.map_err(|err| err.new_position(*op_pos))?;
|
||||
|
||||
if cfg!(not(feature = "no_shared")) && lhs_ptr.is_shared() {
|
||||
*lhs_ptr.write_lock::<Dynamic>().unwrap() = if value.is_shared() {
|
||||
value.clone_inner_data().unwrap()
|
||||
} else {
|
||||
value
|
||||
};
|
||||
let value = value.clone_inner_data().unwrap();
|
||||
if cfg!(not(feature = "no_closure")) && lhs_ptr.is_shared() {
|
||||
*lhs_ptr.write_lock::<Dynamic>().unwrap() = value;
|
||||
} else {
|
||||
*lhs_ptr = value;
|
||||
}
|
||||
@ -1845,6 +1840,25 @@ impl Engine {
|
||||
}
|
||||
Ok(Default::default())
|
||||
}
|
||||
|
||||
// Share statement
|
||||
#[cfg(not(feature = "no_closure"))]
|
||||
Stmt::Share(x) => {
|
||||
let (var_name, _) = x.as_ref();
|
||||
|
||||
match scope.get_index(var_name) {
|
||||
Some((index, ScopeEntryType::Normal)) => {
|
||||
let (val, _) = scope.get_mut(index);
|
||||
|
||||
if !val.is_shared() {
|
||||
// Replace the variable with a shared value.
|
||||
*val = mem::take(val).into_shared();
|
||||
}
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
Ok(Default::default())
|
||||
}
|
||||
};
|
||||
|
||||
self.check_data_size(result)
|
||||
|
@ -93,7 +93,7 @@ pub enum ParseErrorType {
|
||||
MalformedInExpr(String),
|
||||
/// A capturing has syntax error. Wrapped value is the error description (if any).
|
||||
///
|
||||
/// Never appears under the `no_capture` feature.
|
||||
/// Never appears under the `no_closure` feature.
|
||||
MalformedCapture(String),
|
||||
/// A map definition has duplicated property names. Wrapped value is the property name.
|
||||
///
|
||||
|
@ -5,7 +5,7 @@ use crate::calc_fn_hash;
|
||||
use crate::engine::{
|
||||
search_imports, search_namespace, search_scope_only, Engine, Imports, State, KEYWORD_DEBUG,
|
||||
KEYWORD_EVAL, KEYWORD_FN_PTR, KEYWORD_FN_PTR_CALL, KEYWORD_FN_PTR_CURRY, KEYWORD_IS_SHARED,
|
||||
KEYWORD_PRINT, KEYWORD_SHARED, KEYWORD_TAKE, KEYWORD_TYPE_OF,
|
||||
KEYWORD_PRINT, KEYWORD_TYPE_OF,
|
||||
};
|
||||
use crate::error::ParseErrorType;
|
||||
use crate::fn_native::{FnCallArgs, FnPtr};
|
||||
@ -33,7 +33,7 @@ use crate::engine::{FN_IDX_GET, FN_IDX_SET};
|
||||
#[cfg(not(feature = "no_object"))]
|
||||
use crate::engine::{Map, Target, FN_GET, FN_SET};
|
||||
|
||||
#[cfg(not(feature = "no_capture"))]
|
||||
#[cfg(not(feature = "no_closure"))]
|
||||
use crate::scope::Entry as ScopeEntry;
|
||||
|
||||
use crate::stdlib::{
|
||||
@ -47,7 +47,7 @@ use crate::stdlib::{
|
||||
vec::Vec,
|
||||
};
|
||||
|
||||
#[cfg(not(feature = "no_capture"))]
|
||||
#[cfg(not(feature = "no_closure"))]
|
||||
use crate::stdlib::{collections::HashSet, string::String};
|
||||
|
||||
/// Extract the property name from a getter function name.
|
||||
@ -139,7 +139,7 @@ impl Drop for ArgBackup<'_> {
|
||||
}
|
||||
|
||||
// Add captured variables into scope
|
||||
#[cfg(not(feature = "no_capture"))]
|
||||
#[cfg(not(feature = "no_closure"))]
|
||||
fn add_captured_variables_into_scope<'s>(
|
||||
externals: &HashSet<String>,
|
||||
captured: Scope<'s>,
|
||||
@ -166,7 +166,7 @@ pub fn ensure_no_data_race(
|
||||
args: &FnCallArgs,
|
||||
is_ref: bool,
|
||||
) -> Result<(), Box<EvalAltResult>> {
|
||||
if cfg!(not(feature = "no_shared")) {
|
||||
if cfg!(not(feature = "no_closure")) {
|
||||
let skip = if is_ref { 1 } else { 0 };
|
||||
|
||||
if let Some((n, _)) = args
|
||||
@ -456,7 +456,7 @@ impl Engine {
|
||||
level: usize,
|
||||
) -> Result<(Dynamic, bool), Box<EvalAltResult>> {
|
||||
// Check for data race.
|
||||
if cfg!(not(feature = "no_shared")) {
|
||||
if cfg!(not(feature = "no_closure")) {
|
||||
ensure_no_data_race(fn_name, args, is_ref)?;
|
||||
}
|
||||
|
||||
@ -505,7 +505,7 @@ impl Engine {
|
||||
let mods = &mut Imports::new();
|
||||
|
||||
// Add captured variables into scope
|
||||
#[cfg(not(feature = "no_capture"))]
|
||||
#[cfg(not(feature = "no_closure"))]
|
||||
if let Some(captured) = _capture {
|
||||
add_captured_variables_into_scope(&func.externals, captured, scope);
|
||||
}
|
||||
@ -690,18 +690,12 @@ impl Engine {
|
||||
.into(),
|
||||
false,
|
||||
))
|
||||
} else if cfg!(not(feature = "no_shared"))
|
||||
} else if cfg!(not(feature = "no_closure"))
|
||||
&& _fn_name == KEYWORD_IS_SHARED
|
||||
&& idx.is_empty()
|
||||
{
|
||||
// take call
|
||||
Ok((target.is_shared().into(), false))
|
||||
} else if cfg!(not(feature = "no_shared")) && _fn_name == KEYWORD_SHARED && idx.is_empty() {
|
||||
// take call
|
||||
Ok((obj.clone().into_shared(), false))
|
||||
} else if cfg!(not(feature = "no_shared")) && _fn_name == KEYWORD_TAKE && idx.is_empty() {
|
||||
// take call
|
||||
Ok((obj.clone_inner_data::<Dynamic>().unwrap(), false))
|
||||
} else {
|
||||
#[cfg(not(feature = "no_object"))]
|
||||
let redirected;
|
||||
@ -814,29 +808,13 @@ impl Engine {
|
||||
}
|
||||
|
||||
// Handle is_shared()
|
||||
if cfg!(not(feature = "no_shared")) && name == KEYWORD_IS_SHARED && args_expr.len() == 1 {
|
||||
if cfg!(not(feature = "no_closure")) && name == KEYWORD_IS_SHARED && args_expr.len() == 1 {
|
||||
let expr = args_expr.get(0).unwrap();
|
||||
let value = self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)?;
|
||||
|
||||
return Ok(value.is_shared().into());
|
||||
}
|
||||
|
||||
// Handle shared()
|
||||
if cfg!(not(feature = "no_shared")) && name == KEYWORD_SHARED && args_expr.len() == 1 {
|
||||
let expr = args_expr.get(0).unwrap();
|
||||
let value = self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)?;
|
||||
|
||||
return Ok(value.into_shared());
|
||||
}
|
||||
|
||||
// Handle take()
|
||||
if cfg!(not(feature = "no_shared")) && name == KEYWORD_TAKE && args_expr.len() == 1 {
|
||||
let expr = args_expr.get(0).unwrap();
|
||||
let value = self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)?;
|
||||
|
||||
return Ok(value.clone_inner_data::<Dynamic>().unwrap());
|
||||
}
|
||||
|
||||
// Handle call() - Redirect function call
|
||||
let redirected;
|
||||
let mut args_expr = args_expr.as_ref();
|
||||
@ -896,7 +874,7 @@ impl Engine {
|
||||
let mut arg_values: StaticVec<_>;
|
||||
let mut args: StaticVec<_>;
|
||||
let mut is_ref = false;
|
||||
let capture = if cfg!(not(feature = "no_capture")) && capture && !scope.is_empty() {
|
||||
let capture = if cfg!(not(feature = "no_closure")) && capture && !scope.is_empty() {
|
||||
Some(scope.flatten_clone())
|
||||
} else {
|
||||
None
|
||||
@ -924,7 +902,7 @@ impl Engine {
|
||||
|
||||
// Turn it into a method call only if the object is not shared
|
||||
args = if target.is_shared() {
|
||||
arg_values.insert(0, target.clone_inner_data().unwrap());
|
||||
arg_values.insert(0, target.clone().clone_inner_data().unwrap());
|
||||
arg_values.iter_mut().collect()
|
||||
} else {
|
||||
is_ref = true;
|
||||
@ -1041,7 +1019,7 @@ impl Engine {
|
||||
let mods = &mut Imports::new();
|
||||
|
||||
// Add captured variables into scope
|
||||
#[cfg(not(feature = "no_capture"))]
|
||||
#[cfg(not(feature = "no_closure"))]
|
||||
if _capture && !scope.is_empty() {
|
||||
add_captured_variables_into_scope(
|
||||
&func.externals,
|
||||
|
@ -22,10 +22,10 @@ use crate::stdlib::rc::Rc;
|
||||
#[cfg(feature = "sync")]
|
||||
use crate::stdlib::sync::Arc;
|
||||
|
||||
#[cfg(not(feature = "no_shared"))]
|
||||
#[cfg(not(feature = "no_closure"))]
|
||||
#[cfg(not(feature = "sync"))]
|
||||
use crate::stdlib::cell::RefCell;
|
||||
#[cfg(not(feature = "no_shared"))]
|
||||
#[cfg(not(feature = "no_closure"))]
|
||||
#[cfg(feature = "sync")]
|
||||
use crate::stdlib::sync::RwLock;
|
||||
|
||||
@ -51,11 +51,11 @@ pub type Shared<T> = Rc<T>;
|
||||
pub type Shared<T> = Arc<T>;
|
||||
|
||||
/// Mutable reference-counted container (read-write lock)
|
||||
#[cfg(not(feature = "no_shared"))]
|
||||
#[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_shared"))]
|
||||
#[cfg(not(feature = "no_closure"))]
|
||||
#[cfg(feature = "sync")]
|
||||
pub type SharedMut<T> = Shared<RwLock<T>>;
|
||||
|
||||
|
@ -765,7 +765,7 @@ pub fn optimize_into_ast(
|
||||
access: fn_def.access,
|
||||
body: Default::default(),
|
||||
params: fn_def.params.clone(),
|
||||
#[cfg(not(feature = "no_capture"))]
|
||||
#[cfg(not(feature = "no_closure"))]
|
||||
externals: fn_def.externals.clone(),
|
||||
pos: fn_def.pos,
|
||||
}
|
||||
|
@ -39,7 +39,7 @@ use crate::stdlib::{
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
use crate::stdlib::collections::hash_map::DefaultHasher;
|
||||
|
||||
#[cfg(not(feature = "no_capture"))]
|
||||
#[cfg(not(feature = "no_closure"))]
|
||||
use crate::stdlib::collections::HashSet;
|
||||
|
||||
#[cfg(feature = "no_std")]
|
||||
@ -366,7 +366,7 @@ pub struct ScriptFnDef {
|
||||
/// Names of function parameters.
|
||||
pub params: StaticVec<String>,
|
||||
/// Access to external variables.
|
||||
#[cfg(not(feature = "no_capture"))]
|
||||
#[cfg(not(feature = "no_closure"))]
|
||||
pub externals: HashSet<String>,
|
||||
/// Function body.
|
||||
pub body: Stmt,
|
||||
@ -414,13 +414,13 @@ struct ParseState<'e> {
|
||||
/// Encapsulates a local stack with variable names to simulate an actual runtime scope.
|
||||
stack: Vec<(String, ScopeEntryType)>,
|
||||
/// Tracks a list of external variables (variables that are not explicitly declared in the scope).
|
||||
#[cfg(not(feature = "no_capture"))]
|
||||
#[cfg(not(feature = "no_closure"))]
|
||||
externals: HashMap<String, Position>,
|
||||
/// An indicator that prevents variables capturing into externals one time.
|
||||
/// If set to true the next call of `access_var` will not capture the variable.
|
||||
/// An indicator that disables variable capturing into externals one single time.
|
||||
/// If set to false the next call to `access_var` will not capture the variable.
|
||||
/// All consequent calls to `access_var` will not be affected
|
||||
#[cfg(not(feature = "no_capture"))]
|
||||
capture: bool,
|
||||
#[cfg(not(feature = "no_closure"))]
|
||||
allow_capture: bool,
|
||||
/// Encapsulates a local stack with variable names to simulate an actual runtime scope.
|
||||
modules: Vec<String>,
|
||||
/// Maximum levels of expression nesting.
|
||||
@ -444,10 +444,10 @@ impl<'e> ParseState<'e> {
|
||||
max_expr_depth,
|
||||
#[cfg(not(feature = "unchecked"))]
|
||||
max_function_expr_depth,
|
||||
#[cfg(not(feature = "no_capture"))]
|
||||
#[cfg(not(feature = "no_closure"))]
|
||||
externals: Default::default(),
|
||||
#[cfg(not(feature = "no_capture"))]
|
||||
capture: true,
|
||||
#[cfg(not(feature = "no_closure"))]
|
||||
allow_capture: true,
|
||||
stack: Default::default(),
|
||||
modules: Default::default(),
|
||||
}
|
||||
@ -469,13 +469,13 @@ impl<'e> ParseState<'e> {
|
||||
.find(|(_, (n, _))| *n == name)
|
||||
.and_then(|(i, _)| NonZeroUsize::new(i + 1));
|
||||
|
||||
#[cfg(not(feature = "no_capture"))]
|
||||
if self.capture {
|
||||
#[cfg(not(feature = "no_closure"))]
|
||||
if self.allow_capture {
|
||||
if index.is_none() && !self.externals.contains_key(name) {
|
||||
self.externals.insert(name.to_string(), _pos);
|
||||
}
|
||||
} else {
|
||||
self.capture = true
|
||||
self.allow_capture = true
|
||||
}
|
||||
|
||||
index
|
||||
@ -579,6 +579,9 @@ pub enum Stmt {
|
||||
Position,
|
||||
)>,
|
||||
),
|
||||
/// Convert a variable to shared.
|
||||
#[cfg(not(feature = "no_closure"))]
|
||||
Share(Box<(String, Position)>),
|
||||
}
|
||||
|
||||
impl Default for Stmt {
|
||||
@ -606,6 +609,9 @@ impl Stmt {
|
||||
Stmt::Import(x) => x.2,
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
Stmt::Export(x) => x.1,
|
||||
|
||||
#[cfg(not(feature = "no_closure"))]
|
||||
Stmt::Share(x) => x.1,
|
||||
}
|
||||
}
|
||||
|
||||
@ -629,6 +635,9 @@ impl Stmt {
|
||||
Stmt::Import(x) => x.2 = new_pos,
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
Stmt::Export(x) => x.1 = new_pos,
|
||||
|
||||
#[cfg(not(feature = "no_closure"))]
|
||||
Stmt::Share(x) => x.1 = new_pos,
|
||||
}
|
||||
|
||||
self
|
||||
@ -655,6 +664,9 @@ impl Stmt {
|
||||
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
Stmt::Import(_) | Stmt::Export(_) => false,
|
||||
|
||||
#[cfg(not(feature = "no_closure"))]
|
||||
Stmt::Share(_) => false,
|
||||
}
|
||||
}
|
||||
|
||||
@ -678,6 +690,9 @@ impl Stmt {
|
||||
Stmt::Import(_) => false,
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
Stmt::Export(_) => false,
|
||||
|
||||
#[cfg(not(feature = "no_closure"))]
|
||||
Stmt::Share(_) => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1671,7 +1686,7 @@ fn parse_primary(
|
||||
|
||||
root_expr = match (root_expr, token) {
|
||||
// Function call
|
||||
#[cfg(not(feature = "no_capture"))]
|
||||
#[cfg(not(feature = "no_closure"))]
|
||||
(Expr::Variable(x), Token::Bang) => {
|
||||
if !match_token(input, Token::LeftParen)? {
|
||||
return Err(PERR::MissingToken(
|
||||
@ -1855,7 +1870,7 @@ fn parse_unary(
|
||||
|
||||
let (expr, func) = parse_anon_fn(input, &mut new_state, lib, settings)?;
|
||||
|
||||
#[cfg(not(feature = "no_capture"))]
|
||||
#[cfg(not(feature = "no_closure"))]
|
||||
new_state.externals.iter().for_each(|(closure, pos)| {
|
||||
state.access_var(closure, *pos);
|
||||
});
|
||||
@ -2216,9 +2231,9 @@ fn parse_binary_op(
|
||||
if cfg!(not(feature = "no_object")) && op_token == Token::Period {
|
||||
if let (Token::Identifier(_), _) = input.peek().unwrap() {
|
||||
// prevents capturing of the object properties as vars: xxx.<var>
|
||||
#[cfg(not(feature = "no_capture"))]
|
||||
#[cfg(not(feature = "no_closure"))]
|
||||
{
|
||||
state.capture = false;
|
||||
state.allow_capture = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -3095,7 +3110,7 @@ fn parse_fn(
|
||||
|
||||
let params: StaticVec<_> = params.into_iter().map(|(p, _)| p).collect();
|
||||
|
||||
#[cfg(not(feature = "no_capture"))]
|
||||
#[cfg(not(feature = "no_closure"))]
|
||||
let externals = state
|
||||
.externals
|
||||
.iter()
|
||||
@ -3108,7 +3123,7 @@ fn parse_fn(
|
||||
name: name.into(),
|
||||
access,
|
||||
params,
|
||||
#[cfg(not(feature = "no_capture"))]
|
||||
#[cfg(not(feature = "no_closure"))]
|
||||
externals,
|
||||
body,
|
||||
pos: settings.pos,
|
||||
@ -3128,6 +3143,17 @@ fn make_curry_from_externals(
|
||||
let num_externals = externals.len();
|
||||
let mut args: StaticVec<_> = Default::default();
|
||||
|
||||
#[cfg(not(feature = "no_closure"))]
|
||||
externals.iter().for_each(|(var_name, pos)| {
|
||||
args.push(Expr::Variable(Box::new((
|
||||
(var_name.into(), *pos),
|
||||
None,
|
||||
0,
|
||||
None,
|
||||
))));
|
||||
});
|
||||
|
||||
#[cfg(feature = "no_closure")]
|
||||
externals.into_iter().for_each(|(var_name, pos)| {
|
||||
args.push(Expr::Variable(Box::new(((var_name, pos), None, 0, None))));
|
||||
});
|
||||
@ -3142,7 +3168,23 @@ fn make_curry_from_externals(
|
||||
None,
|
||||
)));
|
||||
|
||||
Expr::Dot(Box::new((fn_expr, fn_call, pos)))
|
||||
let expr = Expr::Dot(Box::new((fn_expr, fn_call, pos)));
|
||||
|
||||
// If there are captured variables, convert the entire expression into a statement block,
|
||||
// then insert the relevant `Share` statements.
|
||||
#[cfg(not(feature = "no_closure"))]
|
||||
{
|
||||
// Statement block
|
||||
let mut statements: StaticVec<_> = Default::default();
|
||||
// Insert `Share` statements
|
||||
statements.extend(externals.into_iter().map(|x| Stmt::Share(Box::new(x))));
|
||||
// Final expression
|
||||
statements.push(Stmt::Expr(Box::new(expr)));
|
||||
Expr::Stmt(Box::new((Stmt::Block(Box::new((statements, pos))), pos)))
|
||||
}
|
||||
|
||||
#[cfg(feature = "no_closure")]
|
||||
return expr;
|
||||
}
|
||||
|
||||
/// Parse an anonymous function definition.
|
||||
@ -3215,7 +3257,7 @@ fn parse_anon_fn(
|
||||
// External variables may need to be processed in a consistent order,
|
||||
// so extract them into a list.
|
||||
let externals: StaticVec<_> = {
|
||||
#[cfg(not(feature = "no_capture"))]
|
||||
#[cfg(not(feature = "no_closure"))]
|
||||
{
|
||||
state
|
||||
.externals
|
||||
@ -3223,11 +3265,11 @@ fn parse_anon_fn(
|
||||
.map(|(k, &v)| (k.clone(), v))
|
||||
.collect()
|
||||
}
|
||||
#[cfg(feature = "no_capture")]
|
||||
#[cfg(feature = "no_closure")]
|
||||
Default::default()
|
||||
};
|
||||
|
||||
let params: StaticVec<_> = if cfg!(not(feature = "no_capture")) {
|
||||
let params: StaticVec<_> = if cfg!(not(feature = "no_closure")) {
|
||||
externals
|
||||
.iter()
|
||||
.map(|(k, _)| k)
|
||||
@ -3257,7 +3299,7 @@ fn parse_anon_fn(
|
||||
name: fn_name.clone(),
|
||||
access: FnAccess::Public,
|
||||
params,
|
||||
#[cfg(not(feature = "no_capture"))]
|
||||
#[cfg(not(feature = "no_closure"))]
|
||||
externals: Default::default(),
|
||||
body,
|
||||
pos: settings.pos,
|
||||
@ -3265,7 +3307,7 @@ fn parse_anon_fn(
|
||||
|
||||
let expr = Expr::FnPointer(Box::new((fn_name, settings.pos)));
|
||||
|
||||
let expr = if cfg!(not(feature = "no_capture")) {
|
||||
let expr = if cfg!(not(feature = "no_closure")) {
|
||||
make_curry_from_externals(expr, externals, settings.pos)
|
||||
} else {
|
||||
expr
|
||||
|
69
src/scope.rs
69
src/scope.rs
@ -158,32 +158,6 @@ impl<'a> Scope<'a> {
|
||||
self.push_dynamic_value(name, EntryType::Normal, Dynamic::from(value), false)
|
||||
}
|
||||
|
||||
/// Add (push) a new shared entry to the Scope.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use rhai::Scope;
|
||||
///
|
||||
/// let mut my_scope = Scope::new();
|
||||
///
|
||||
/// my_scope.push_shared("x", 42_i64);
|
||||
/// assert_eq!(my_scope.get_value::<i64>("x").unwrap(), 42);
|
||||
/// ```
|
||||
#[cfg(not(feature = "no_shared"))]
|
||||
pub fn push_shared<K: Into<Cow<'a, str>>, T: Variant + Clone>(
|
||||
&mut self,
|
||||
name: K,
|
||||
value: T,
|
||||
) -> &mut Self {
|
||||
self.push_dynamic_value(
|
||||
name,
|
||||
EntryType::Normal,
|
||||
Dynamic::from(value).into_shared(),
|
||||
false,
|
||||
)
|
||||
}
|
||||
|
||||
/// Add (push) a new `Dynamic` entry to the Scope.
|
||||
///
|
||||
/// # Examples
|
||||
@ -226,34 +200,6 @@ impl<'a> Scope<'a> {
|
||||
self.push_dynamic_value(name, EntryType::Constant, Dynamic::from(value), true)
|
||||
}
|
||||
|
||||
/// Add (push) a new shared constant to the Scope.
|
||||
///
|
||||
/// Shared constants are immutable and cannot be assigned to, but their shared values can change.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use rhai::Scope;
|
||||
///
|
||||
/// let mut my_scope = Scope::new();
|
||||
///
|
||||
/// my_scope.push_constant_shared("x", 42_i64);
|
||||
/// assert_eq!(my_scope.get_value::<i64>("x").unwrap(), 42);
|
||||
/// ```
|
||||
#[cfg(not(feature = "no_shared"))]
|
||||
pub fn push_constant_shared<K: Into<Cow<'a, str>>, T: Variant + Clone>(
|
||||
&mut self,
|
||||
name: K,
|
||||
value: T,
|
||||
) -> &mut Self {
|
||||
self.push_dynamic_value(
|
||||
name,
|
||||
EntryType::Constant,
|
||||
Dynamic::from(value).into_shared(),
|
||||
true,
|
||||
)
|
||||
}
|
||||
|
||||
/// Add (push) a new constant with a `Dynamic` value to the Scope.
|
||||
///
|
||||
/// Constants are immutable and cannot be assigned to. Their values never change.
|
||||
@ -394,7 +340,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_inner_data::<T>())
|
||||
.and_then(|Entry { value, .. }| value.clone().clone_inner_data::<T>())
|
||||
}
|
||||
|
||||
/// Update the value of the named entry.
|
||||
@ -485,13 +431,20 @@ impl<'a> Scope<'a> {
|
||||
///
|
||||
/// let (name, value) = iter.next().unwrap();
|
||||
/// assert_eq!(name, "x");
|
||||
/// assert_eq!(value.clone().cast::<i64>(), 42);
|
||||
/// assert_eq!(value.cast::<i64>(), 42);
|
||||
///
|
||||
/// let (name, value) = iter.next().unwrap();
|
||||
/// assert_eq!(name, "foo");
|
||||
/// assert_eq!(value.clone().cast::<String>(), "hello");
|
||||
/// assert_eq!(value.cast::<String>(), "hello");
|
||||
/// ```
|
||||
pub fn iter(&self) -> impl Iterator<Item = (&str, &Dynamic)> {
|
||||
pub fn iter(&self) -> impl Iterator<Item = (&str, Dynamic)> {
|
||||
self.iter_raw()
|
||||
.map(|(name, value)| (name, value.clone().clone_inner_data().unwrap()))
|
||||
}
|
||||
|
||||
/// Get an iterator to entries in the Scope.
|
||||
/// Shared values are not expanded.
|
||||
pub fn iter_raw(&self) -> impl Iterator<Item = (&str, &Dynamic)> {
|
||||
self.0
|
||||
.iter()
|
||||
.map(|Entry { name, value, .. }| (name.as_ref(), value))
|
||||
|
19
src/token.rs
19
src/token.rs
@ -2,7 +2,7 @@
|
||||
|
||||
use crate::engine::{
|
||||
Engine, KEYWORD_DEBUG, KEYWORD_EVAL, KEYWORD_FN_PTR, KEYWORD_FN_PTR_CALL, KEYWORD_FN_PTR_CURRY,
|
||||
KEYWORD_IS_SHARED, KEYWORD_PRINT, KEYWORD_SHARED, KEYWORD_TAKE, KEYWORD_THIS, KEYWORD_TYPE_OF,
|
||||
KEYWORD_IS_SHARED, KEYWORD_PRINT, KEYWORD_THIS, KEYWORD_TYPE_OF,
|
||||
};
|
||||
|
||||
use crate::error::LexError;
|
||||
@ -501,14 +501,15 @@ impl Token {
|
||||
"import" | "export" | "as" => Reserved(syntax.into()),
|
||||
|
||||
"===" | "!==" | "->" | "<-" | "=>" | ":=" | "::<" | "(*" | "*)" | "#" | "public"
|
||||
| "new" | "use" | "module" | "package" | "var" | "static" | "with" | "do" | "each"
|
||||
| "then" | "goto" | "exit" | "switch" | "match" | "case" | "try" | "catch"
|
||||
| "default" | "void" | "null" | "nil" | "spawn" | "go" | "sync" | "async" | "await"
|
||||
| "yield" => Reserved(syntax.into()),
|
||||
| "new" | "use" | "module" | "package" | "var" | "static" | "shared" | "with"
|
||||
| "do" | "each" | "then" | "goto" | "exit" | "switch" | "match" | "case" | "try"
|
||||
| "catch" | "default" | "void" | "null" | "nil" | "spawn" | "go" | "sync" | "async"
|
||||
| "await" | "yield" => Reserved(syntax.into()),
|
||||
|
||||
KEYWORD_PRINT | KEYWORD_DEBUG | KEYWORD_TYPE_OF | KEYWORD_EVAL | KEYWORD_FN_PTR
|
||||
| KEYWORD_FN_PTR_CALL | KEYWORD_FN_PTR_CURRY | KEYWORD_IS_SHARED | KEYWORD_SHARED
|
||||
| KEYWORD_TAKE | KEYWORD_THIS => Reserved(syntax.into()),
|
||||
| KEYWORD_FN_PTR_CALL | KEYWORD_FN_PTR_CURRY | KEYWORD_IS_SHARED | KEYWORD_THIS => {
|
||||
Reserved(syntax.into())
|
||||
}
|
||||
|
||||
_ => return None,
|
||||
})
|
||||
@ -1432,8 +1433,8 @@ fn get_identifier(
|
||||
#[inline(always)]
|
||||
pub fn is_keyword_function(name: &str) -> bool {
|
||||
match name {
|
||||
#[cfg(not(feature = "no_shared"))]
|
||||
KEYWORD_SHARED | KEYWORD_TAKE | KEYWORD_IS_SHARED => true,
|
||||
#[cfg(not(feature = "no_closure"))]
|
||||
KEYWORD_IS_SHARED => true,
|
||||
KEYWORD_PRINT | KEYWORD_DEBUG | KEYWORD_TYPE_OF | KEYWORD_EVAL | KEYWORD_FN_PTR
|
||||
| KEYWORD_FN_PTR_CALL | KEYWORD_FN_PTR_CURRY => true,
|
||||
_ => false,
|
||||
|
@ -35,7 +35,8 @@ fn test_fn_ptr_curry_call() -> Result<(), Box<EvalAltResult>> {
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(all(not(feature = "no_capture"), not(feature = "no_object")))]
|
||||
#[cfg(not(feature = "no_closure"))]
|
||||
#[cfg(not(feature = "no_object"))]
|
||||
fn test_closures() -> Result<(), Box<EvalAltResult>> {
|
||||
let engine = Engine::new();
|
||||
|
||||
@ -56,5 +57,61 @@ fn test_closures() -> Result<(), Box<EvalAltResult>> {
|
||||
42
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
engine.eval::<INT>(
|
||||
r#"
|
||||
let a = 41;
|
||||
let foo = |x| { a += x };
|
||||
foo.call(1);
|
||||
a
|
||||
"#
|
||||
)?,
|
||||
42
|
||||
);
|
||||
|
||||
assert!(engine.eval::<bool>(
|
||||
r#"
|
||||
let a = 41;
|
||||
let foo = |x| { a += x };
|
||||
a.is_shared()
|
||||
"#
|
||||
)?);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(not(feature = "no_closure"))]
|
||||
#[cfg(not(feature = "no_object"))]
|
||||
fn test_closures_data_race() -> Result<(), Box<EvalAltResult>> {
|
||||
let engine = Engine::new();
|
||||
|
||||
assert_eq!(
|
||||
engine.eval::<INT>(
|
||||
r#"
|
||||
let a = 1;
|
||||
let b = 40;
|
||||
let foo = |x| { this += a + x };
|
||||
b.call(foo, 1);
|
||||
b
|
||||
"#
|
||||
)?,
|
||||
42
|
||||
);
|
||||
|
||||
assert!(matches!(
|
||||
*engine
|
||||
.eval::<INT>(
|
||||
r#"
|
||||
let a = 20;
|
||||
let foo = |x| { this += a + x };
|
||||
a.call(foo, 1);
|
||||
a
|
||||
"#
|
||||
)
|
||||
.expect_err("should error"),
|
||||
EvalAltResult::ErrorDataRace(_, _)
|
||||
));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
@ -122,7 +122,7 @@ fn test_function_pointers() -> Result<(), Box<EvalAltResult>> {
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(not(feature = "no_capture"))]
|
||||
#[cfg(not(feature = "no_closure"))]
|
||||
fn test_function_captures() -> Result<(), Box<EvalAltResult>> {
|
||||
let engine = Engine::new();
|
||||
|
||||
|
324
tests/shared.rs
324
tests/shared.rs
@ -1,324 +0,0 @@
|
||||
#![cfg(not(feature = "no_shared"))]
|
||||
|
||||
use rhai::{Array, Dynamic, Engine, EvalAltResult, FnPtr, Module, RegisterFn, INT};
|
||||
use std::any::TypeId;
|
||||
|
||||
#[test]
|
||||
fn test_shared() -> Result<(), Box<EvalAltResult>> {
|
||||
let mut engine = Engine::new();
|
||||
|
||||
assert_eq!(engine.eval::<INT>("shared(42)")?, 42);
|
||||
|
||||
assert_eq!(engine.eval::<bool>("shared(true)")?, true);
|
||||
|
||||
assert_eq!(
|
||||
engine.eval::<String>("let x = shared(true); type_of(x)")?,
|
||||
"bool"
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
engine.eval::<String>("let x = shared(true); x = (); type_of(x)")?,
|
||||
"()"
|
||||
);
|
||||
|
||||
#[cfg(not(feature = "no_float"))]
|
||||
assert_eq!(engine.eval::<f64>("shared(4.2)")?, 4.2);
|
||||
|
||||
assert_eq!(engine.eval::<String>(r#"shared("test")"#)?, "test");
|
||||
|
||||
assert_eq!(engine.eval::<String>(r#"shared("test")"#)?, "test");
|
||||
|
||||
assert_eq!(engine.eval::<char>("shared('x')")?, 'x');
|
||||
|
||||
assert!(engine.eval::<bool>("is_shared(shared(42))")?);
|
||||
|
||||
#[cfg(not(feature = "no_object"))]
|
||||
assert!(engine.eval::<bool>("shared(42).is_shared()")?);
|
||||
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
{
|
||||
assert_eq!(
|
||||
engine.eval::<String>(
|
||||
r#"
|
||||
let s = shared("test");
|
||||
let i = shared(0);
|
||||
i = 2;
|
||||
s[i] = 'S';
|
||||
|
||||
s
|
||||
"#
|
||||
)?,
|
||||
"teSt"
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
engine
|
||||
.eval::<Array>(
|
||||
r#"
|
||||
let x = shared([1, 2, 3]);
|
||||
let y = shared([4, 5]);
|
||||
x + y
|
||||
"#
|
||||
)?
|
||||
.len(),
|
||||
5
|
||||
);
|
||||
|
||||
#[cfg(not(feature = "no_object"))]
|
||||
assert_eq!(
|
||||
engine.eval::<INT>(
|
||||
r"
|
||||
let x = shared([2, 9]);
|
||||
x.insert(-1, 1);
|
||||
x.insert(999, 3);
|
||||
|
||||
let r = x.remove(2);
|
||||
|
||||
let y = shared([4, 5]);
|
||||
x.append(y);
|
||||
|
||||
x.len + r
|
||||
"
|
||||
)?,
|
||||
14
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
engine.eval::<bool>(
|
||||
r#"
|
||||
let x = shared([1, 2, 3]);
|
||||
|
||||
if x[0] + x[2] == 4 {
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
"#
|
||||
)?,
|
||||
true
|
||||
);
|
||||
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
#[cfg(not(feature = "no_object"))]
|
||||
assert_eq!(
|
||||
engine.eval::<INT>(
|
||||
r#"
|
||||
let x = shared([1, 2, 3]);
|
||||
let y = shared(());
|
||||
|
||||
(|| {
|
||||
for i in x {
|
||||
y = i * 10;
|
||||
}
|
||||
}).call();
|
||||
|
||||
y
|
||||
"#
|
||||
)?,
|
||||
30
|
||||
);
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "no_object"))]
|
||||
assert_eq!(
|
||||
engine.eval::<INT>(
|
||||
r#"
|
||||
let y = shared(#{a: 1, b: 2, c: 3});
|
||||
y.c = shared(5);
|
||||
y.c
|
||||
"#
|
||||
)?,
|
||||
5
|
||||
);
|
||||
|
||||
#[cfg(not(feature = "no_object"))]
|
||||
assert_eq!(
|
||||
engine.eval::<INT>(
|
||||
r#"
|
||||
let y = shared(#{a: 1, b: 2, c: shared(3)});
|
||||
let c = y.c;
|
||||
c = 5;// "c" holds Dynamic Shared
|
||||
y.c
|
||||
"#
|
||||
)?,
|
||||
5
|
||||
);
|
||||
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
#[cfg(not(feature = "no_capture"))]
|
||||
assert_eq!(
|
||||
engine.eval::<INT>(
|
||||
r#"
|
||||
let x = shared(1);
|
||||
(|| x = x + 41).call();
|
||||
x
|
||||
"#
|
||||
)?,
|
||||
42
|
||||
);
|
||||
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
#[cfg(not(feature = "no_object"))]
|
||||
#[cfg(not(feature = "no_capture"))]
|
||||
assert_eq!(
|
||||
engine.eval::<INT>(
|
||||
r#"
|
||||
let x = shared(#{a: 1, b: shared(2), c: 3});
|
||||
let a = x.a;
|
||||
let b = x.b;
|
||||
a = 100; // does not hold reference to x.a
|
||||
b = 20; // does hold reference to x.b
|
||||
|
||||
let f = |a| {
|
||||
x.c = x.a + x.b + a;
|
||||
};
|
||||
|
||||
f.call(21);
|
||||
|
||||
x.c
|
||||
"#
|
||||
)?,
|
||||
42
|
||||
);
|
||||
|
||||
// Register a binary function named `foo`
|
||||
engine.register_fn("custom_addition", |x: INT, y: INT| x + y);
|
||||
|
||||
assert_eq!(
|
||||
engine.eval::<INT>("custom_addition(shared(20), shared(22))")?,
|
||||
42
|
||||
);
|
||||
|
||||
#[cfg(not(feature = "no_object"))]
|
||||
{
|
||||
#[derive(Clone)]
|
||||
struct TestStruct {
|
||||
x: INT,
|
||||
}
|
||||
|
||||
impl TestStruct {
|
||||
fn update(&mut self) {
|
||||
self.x += 1000;
|
||||
}
|
||||
|
||||
fn merge(&mut self, other: Self) {
|
||||
self.x += other.x;
|
||||
}
|
||||
|
||||
fn get_x(&mut self) -> INT {
|
||||
self.x
|
||||
}
|
||||
|
||||
fn set_x(&mut self, new_x: INT) {
|
||||
self.x = new_x;
|
||||
}
|
||||
|
||||
fn new() -> Self {
|
||||
TestStruct { x: 1 }
|
||||
}
|
||||
}
|
||||
|
||||
engine
|
||||
.register_type::<TestStruct>()
|
||||
.register_get_set("x", TestStruct::get_x, TestStruct::set_x)
|
||||
.register_fn("update", TestStruct::update)
|
||||
.register_fn("merge", TestStruct::merge)
|
||||
.register_fn("new_ts", TestStruct::new)
|
||||
.register_raw_fn(
|
||||
"mutate_with_cb",
|
||||
&[
|
||||
TypeId::of::<TestStruct>(),
|
||||
TypeId::of::<INT>(),
|
||||
TypeId::of::<FnPtr>(),
|
||||
],
|
||||
move |engine: &Engine, lib: &Module, args: &mut [&mut Dynamic]| {
|
||||
let fp = std::mem::take(args[2]).cast::<FnPtr>();
|
||||
let mut value = args[1].clone();
|
||||
{
|
||||
let mut lock = value.write_lock::<INT>().unwrap();
|
||||
*lock = *lock + 1;
|
||||
}
|
||||
let this_ptr = args.get_mut(0).unwrap();
|
||||
|
||||
fp.call_dynamic(engine, lib, Some(this_ptr), [value])
|
||||
},
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
engine.eval::<INT>(
|
||||
r"
|
||||
let a = shared(new_ts());
|
||||
|
||||
a.x = 100;
|
||||
a.update();
|
||||
a.merge(a.take()); // take is important to prevent a deadlock
|
||||
|
||||
a.x
|
||||
"
|
||||
)?,
|
||||
2200
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
engine.eval::<INT>(
|
||||
r"
|
||||
let a = shared(new_ts());
|
||||
let b = shared(100);
|
||||
|
||||
a.mutate_with_cb(b, |param| {
|
||||
this.x = param;
|
||||
param = 50;
|
||||
this.update();
|
||||
});
|
||||
|
||||
a.update();
|
||||
a.x += b;
|
||||
|
||||
a.x
|
||||
"
|
||||
)?,
|
||||
2151
|
||||
);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_shared_data_race() -> Result<(), Box<EvalAltResult>> {
|
||||
let engine = Engine::new();
|
||||
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
#[cfg(not(feature = "no_object"))]
|
||||
{
|
||||
assert_eq!(
|
||||
engine.eval::<INT>(
|
||||
r#"
|
||||
fn foo(x) { this += x };
|
||||
|
||||
let a = shared(41);
|
||||
a.foo(1);
|
||||
a
|
||||
"#
|
||||
)?,
|
||||
42
|
||||
);
|
||||
|
||||
assert!(matches!(
|
||||
*engine
|
||||
.eval::<INT>(
|
||||
r#"
|
||||
fn foo(x) { this += x };
|
||||
|
||||
let a = shared(42);
|
||||
a.foo(a);
|
||||
a
|
||||
"#
|
||||
)
|
||||
.expect_err("should error"),
|
||||
EvalAltResult::ErrorDataRace(_, _)
|
||||
));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
Loading…
Reference in New Issue
Block a user