Implement closures.

This commit is contained in:
Stephen Chung 2020-08-03 12:10:20 +08:00
parent 747c0345f2
commit 4079164bfd
24 changed files with 340 additions and 588 deletions

View File

@ -30,8 +30,7 @@ jobs:
- "--features no_object" - "--features no_object"
- "--features no_function" - "--features no_function"
- "--features no_module" - "--features no_module"
- "--features no_capture" - "--features no_closure"
- "--features no_shared"
- "--features unicode-xid-ident" - "--features unicode-xid-ident"
toolchain: [stable] toolchain: [stable]
experimental: [false] experimental: [false]

View File

@ -33,14 +33,13 @@ only_i64 = [] # set INT=i64 (default) and disable support for all other in
no_index = [] # no arrays and indexing no_index = [] # no arrays and indexing
no_object = [] # no custom objects no_object = [] # no custom objects
no_function = [] # no script-defined functions 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_closure = [] # no automatic sharing and capture of anonymous functions to external variables
no_shared = [] # no shared values
no_module = [] # no modules no_module = [] # no modules
internals = [] # expose internal data structures internals = [] # expose internal data structures
unicode-xid-ident = ["unicode-xid"] # allow Unicode Standard Annex #31 for identifiers. unicode-xid-ident = ["unicode-xid"] # allow Unicode Standard Annex #31 for identifiers.
# compiling for no-std # 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] [profile.release]
lto = "fat" lto = "fat"

View File

@ -25,6 +25,7 @@ New features
* Capturing of the calling scope for function call via the `func!(...)` syntax. * 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`. * `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. * 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 Breaking changes
---------------- ----------------
@ -33,6 +34,7 @@ Breaking changes
* Function signature for defining custom syntax is simplified. * Function signature for defining custom syntax is simplified.
* `Engine::register_raw_fn_XXX` API shortcuts are removed. * `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. * `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 Housekeeping
------------ ------------

View File

@ -10,8 +10,6 @@ Keywords List
| `let` | Variable declaration | | No | | `let` | Variable declaration | | No |
| `const` | Constant declaration | | No | | `const` | Constant declaration | | No |
| `is_shared` | Is a value shared? | | 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 | | `if` | If statement | | No |
| `else` | else block of if statement | | No | | `else` | else block of if statement | | No |
| `while` | While loop | | No | | `while` | While loop | | No |
@ -44,6 +42,7 @@ Reserved Keywords
| --------- | --------------------- | | --------- | --------------------- |
| `var` | Variable declaration | | `var` | Variable declaration |
| `static` | Variable declaration | | `static` | Variable declaration |
| `shared` | Share value |
| `do` | Looping | | `do` | Looping |
| `each` | Looping | | `each` | Looping |
| `then` | Control flow | | `then` | Control flow |

View File

@ -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 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 ```rust
let obj = #{ 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 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 **not** real closures.
Rust's function pointers. In particular, they capture their execution environment via [automatic currying][capture],
unless the [`no_closure`] feature is turned on.
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].

View File

@ -15,7 +15,7 @@ is created.
Variables that are accessible during the time the [anonymous function] is created can be captured, 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. 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 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. 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 Examples
-------- --------
```rust ```rust
let x = 40; let x = 1;
let f = |y| x + y; // current value of variable 'x' is auto-curried let f = |y| x + y; // variable 'x' is auto-curried (captured) into 'f'
// the value 40 is curried 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: // The above de-sugars into this:
fn anon$1001(x, y) { x + y } // parameter 'x' is inserted 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; f.call(2) == 42;
``` ```

View File

@ -9,7 +9,7 @@ The following are reserved keywords in Rhai:
| ------------------------------------------------- | ------------------------------------------------ | --------------------- | :--------------------: | | ------------------------------------------------- | ------------------------------------------------ | --------------------- | :--------------------: |
| `true`, `false` | | Boolean constants | | | `true`, `false` | | Boolean constants | |
| `let`, `const` | `var`, `static` | Variable declarations | | | `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 | | | `if`, `else` | `then`, `goto`, `exit` | Control flow | |
| | `switch`, `match`, `case` | Matching | | | | `switch`, `match`, `case` | Matching | |
| `while`, `loop`, `for`, `in`, `continue`, `break` | `do`, `each` | Looping | | | `while`, `loop`, `for`, `in`, `continue`, `break` | `do`, `each` | Looping | |

View File

@ -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 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 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 Examples
-------- --------
```rust ```rust
let factor = 1;
// Define the object // Define the object
let obj = let obj =
#{ #{
data: 0, data: 0,
increment: |x| this.data += x, // when called, 'this' binds to 'obj' increment: |x| this.data += x, // 'this' binds to 'obj'
update: |x| this.data = x, // when called, 'this' binds to 'obj' update: |x| this.data = x * factor, // 'this' binds to 'obj', 'factor' is captured
action: || print(this.data) // when called, 'this' binds to 'obj' action: || print(this.data) // 'this' binds to 'obj'
}; };
// Use the object // Use the object
@ -52,4 +54,9 @@ obj.action(); // prints 1
obj.update(42); obj.update(42);
obj.action(); // prints 42 obj.action(); // prints 42
factor = 2;
obj.update(42);
obj.action(); // prints 84
``` ```

View File

@ -9,8 +9,7 @@
[`no_object`]: {{rootUrl}}/start/features.md [`no_object`]: {{rootUrl}}/start/features.md
[`no_function`]: {{rootUrl}}/start/features.md [`no_function`]: {{rootUrl}}/start/features.md
[`no_module`]: {{rootUrl}}/start/features.md [`no_module`]: {{rootUrl}}/start/features.md
[`no_capture`]: {{rootUrl}}/start/features.md [`no_closure`]: {{rootUrl}}/start/features.md
[`no_shared`]: {{rootUrl}}/start/features.md
[`no_std`]: {{rootUrl}}/start/features.md [`no_std`]: {{rootUrl}}/start/features.md
[`no-std`]: {{rootUrl}}/start/features.md [`no-std`]: {{rootUrl}}/start/features.md
[`internals`]: {{rootUrl}}/start/features.md [`internals`]: {{rootUrl}}/start/features.md

View File

@ -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_object` | Disable support for [custom types] and [object maps]. |
| `no_function` | Disable script-defined [functions]. | | `no_function` | Disable script-defined [functions]. |
| `no_module` | Disable loading external [modules]. | | `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_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_shared` | Disable sharing of data values. | | `no_std` | Build for `no-std` (implies `no_closure`). Notice that additional dependencies will be pulled in to replace `std` features. |
| `no_std` | Build for `no-std` (implies `no_shared`). 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. | | `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. | | `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. | | `unicode-xid-ident` | Allow [Unicode Standard Annex #31](http://www.unicode.org/reports/tr31/) as identifiers. |

View File

@ -114,10 +114,15 @@ fn main() {
} }
"exit" | "quit" => break, // quit "exit" | "quit" => break, // quit
"scope" => { "scope" => {
scope scope.iter_raw().enumerate().for_each(|(i, (name, value))| {
.iter() println!(
.enumerate() "[{}] {}{} = {:?}",
.for_each(|(i, (name, value))| println!("[{}] {} = {:?}", i + 1, name, value)); i + 1,
name,
if value.is_shared() { " (shared)" } else { "" },
*value.read_lock::<Dynamic>().unwrap(),
)
});
continue; continue;
} }
"astu" => { "astu" => {

View File

@ -4,7 +4,7 @@ use crate::fn_native::{FnPtr, SendSync};
use crate::parser::{ImmutableString, INT}; use crate::parser::{ImmutableString, INT};
use crate::r#unsafe::{unsafe_cast_box, unsafe_try_cast}; use crate::r#unsafe::{unsafe_cast_box, unsafe_try_cast};
#[cfg(not(feature = "no_shared"))] #[cfg(not(feature = "no_closure"))]
use crate::fn_native::SharedMut; use crate::fn_native::SharedMut;
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
@ -24,14 +24,14 @@ use crate::stdlib::{
string::String, string::String,
}; };
#[cfg(not(feature = "no_shared"))] #[cfg(not(feature = "no_closure"))]
#[cfg(not(feature = "sync"))] #[cfg(not(feature = "sync"))]
use crate::stdlib::{ use crate::stdlib::{
cell::{Ref, RefCell, RefMut}, cell::{Ref, RefCell, RefMut},
rc::Rc, rc::Rc,
}; };
#[cfg(not(feature = "no_shared"))] #[cfg(not(feature = "no_closure"))]
#[cfg(feature = "sync")] #[cfg(feature = "sync")]
use crate::stdlib::sync::{Arc, RwLock, RwLockReadGuard, RwLockWriteGuard}; use crate::stdlib::sync::{Arc, RwLock, RwLockReadGuard, RwLockWriteGuard};
@ -159,7 +159,7 @@ pub enum Union {
Map(Box<Map>), Map(Box<Map>),
FnPtr(Box<FnPtr>), FnPtr(Box<FnPtr>),
Variant(Box<Box<dyn Variant>>), Variant(Box<Box<dyn Variant>>),
#[cfg(not(feature = "no_shared"))] #[cfg(not(feature = "no_closure"))]
Shared(SharedMut<Dynamic>), Shared(SharedMut<Dynamic>),
} }
@ -176,11 +176,11 @@ enum DynamicReadLockInner<'d, T: Variant + Clone> {
/// A simple reference to a non-shared value. /// A simple reference to a non-shared value.
Reference(&'d T), Reference(&'d T),
/// A read guard to a shared `RefCell`. /// A read guard to a shared `RefCell`.
#[cfg(not(feature = "no_shared"))] #[cfg(not(feature = "no_closure"))]
#[cfg(not(feature = "sync"))] #[cfg(not(feature = "sync"))]
Guard(Ref<'d, Dynamic>), Guard(Ref<'d, Dynamic>),
/// A read guard to a shared `RwLock`. /// A read guard to a shared `RwLock`.
#[cfg(not(feature = "no_shared"))] #[cfg(not(feature = "no_closure"))]
#[cfg(feature = "sync")] #[cfg(feature = "sync")]
Guard(RwLockReadGuard<'d, Dynamic>), Guard(RwLockReadGuard<'d, Dynamic>),
} }
@ -193,7 +193,7 @@ impl<'d, T: Variant + Clone> Deref for DynamicReadLock<'d, T> {
match &self.0 { match &self.0 {
DynamicReadLockInner::Reference(reference) => *reference, DynamicReadLockInner::Reference(reference) => *reference,
// Unwrapping is safe because all checking is already done in its constructor // 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(), 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. /// A simple mutable reference to a non-shared value.
Reference(&'d mut T), Reference(&'d mut T),
/// A write guard to a shared `RefCell`. /// A write guard to a shared `RefCell`.
#[cfg(not(feature = "no_shared"))] #[cfg(not(feature = "no_closure"))]
#[cfg(not(feature = "sync"))] #[cfg(not(feature = "sync"))]
Guard(RefMut<'d, Dynamic>), Guard(RefMut<'d, Dynamic>),
/// A write guard to a shared `RwLock`. /// A write guard to a shared `RwLock`.
#[cfg(not(feature = "no_shared"))] #[cfg(not(feature = "no_closure"))]
#[cfg(feature = "sync")] #[cfg(feature = "sync")]
Guard(RwLockWriteGuard<'d, Dynamic>), Guard(RwLockWriteGuard<'d, Dynamic>),
} }
@ -229,7 +229,7 @@ impl<'d, T: Variant + Clone> Deref for DynamicWriteLock<'d, T> {
match &self.0 { match &self.0 {
DynamicWriteLockInner::Reference(reference) => *reference, DynamicWriteLockInner::Reference(reference) => *reference,
// Unwrapping is safe because all checking is already done in its constructor // 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(), 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 { match &mut self.0 {
DynamicWriteLockInner::Reference(reference) => *reference, DynamicWriteLockInner::Reference(reference) => *reference,
// Unwrapping is safe because all checking is already done in its constructor // 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(), DynamicWriteLockInner::Guard(guard) => guard.downcast_mut().unwrap(),
} }
} }
@ -261,7 +261,7 @@ impl Dynamic {
/// instead of one of the supported system primitive types? /// instead of one of the supported system primitive types?
pub fn is_shared(&self) -> bool { pub fn is_shared(&self) -> bool {
match self.0 { match self.0 {
#[cfg(not(feature = "no_shared"))] #[cfg(not(feature = "no_closure"))]
Union::Shared(_) => true, Union::Shared(_) => true,
_ => false, _ => false,
} }
@ -302,10 +302,10 @@ impl Dynamic {
Union::Map(_) => TypeId::of::<Map>(), Union::Map(_) => TypeId::of::<Map>(),
Union::FnPtr(_) => TypeId::of::<FnPtr>(), Union::FnPtr(_) => TypeId::of::<FnPtr>(),
Union::Variant(value) => (***value).type_id(), Union::Variant(value) => (***value).type_id(),
#[cfg(not(feature = "no_shared"))] #[cfg(not(feature = "no_closure"))]
#[cfg(not(feature = "sync"))] #[cfg(not(feature = "sync"))]
Union::Shared(cell) => (*cell.borrow()).type_id(), Union::Shared(cell) => (*cell.borrow()).type_id(),
#[cfg(not(feature = "no_shared"))] #[cfg(not(feature = "no_closure"))]
#[cfg(feature = "sync")] #[cfg(feature = "sync")]
Union::Shared(cell) => (*cell.read().unwrap()).type_id(), Union::Shared(cell) => (*cell.read().unwrap()).type_id(),
} }
@ -335,10 +335,10 @@ impl Dynamic {
#[cfg(not(feature = "no_std"))] #[cfg(not(feature = "no_std"))]
Union::Variant(value) if value.is::<Instant>() => "timestamp", Union::Variant(value) if value.is::<Instant>() => "timestamp",
Union::Variant(value) => (***value).type_name(), Union::Variant(value) => (***value).type_name(),
#[cfg(not(feature = "no_shared"))] #[cfg(not(feature = "no_closure"))]
#[cfg(not(feature = "sync"))] #[cfg(not(feature = "sync"))]
Union::Shared(cell) => (*cell.borrow()).type_name(), Union::Shared(cell) => (*cell.borrow()).type_name(),
#[cfg(not(feature = "no_shared"))] #[cfg(not(feature = "no_closure"))]
#[cfg(feature = "sync")] #[cfg(feature = "sync")]
Union::Shared(cell) => (*cell.read().unwrap()).type_name(), Union::Shared(cell) => (*cell.read().unwrap()).type_name(),
} }
@ -396,7 +396,7 @@ impl fmt::Display for Dynamic {
#[cfg(not(feature = "no_std"))] #[cfg(not(feature = "no_std"))]
Union::Variant(value) if value.is::<Instant>() => write!(f, "<timestamp>"), Union::Variant(value) if value.is::<Instant>() => write!(f, "<timestamp>"),
Union::Variant(value) => write!(f, "{}", (*value).type_name()), Union::Variant(value) => write!(f, "{}", (*value).type_name()),
#[cfg(not(feature = "no_shared"))] #[cfg(not(feature = "no_closure"))]
Union::Shared(_) => f.write_str("<shared>"), Union::Shared(_) => f.write_str("<shared>"),
} }
} }
@ -424,7 +424,7 @@ impl fmt::Debug for Dynamic {
#[cfg(not(feature = "no_std"))] #[cfg(not(feature = "no_std"))]
Union::Variant(value) if value.is::<Instant>() => write!(f, "<timestamp>"), Union::Variant(value) if value.is::<Instant>() => write!(f, "<timestamp>"),
Union::Variant(value) => write!(f, "{}", (*value).type_name()), Union::Variant(value) => write!(f, "{}", (*value).type_name()),
#[cfg(not(feature = "no_shared"))] #[cfg(not(feature = "no_closure"))]
Union::Shared(_) => f.write_str("<shared>"), Union::Shared(_) => f.write_str("<shared>"),
} }
} }
@ -446,7 +446,7 @@ impl Clone for Dynamic {
Union::Map(ref value) => Self(Union::Map(value.clone())), Union::Map(ref value) => Self(Union::Map(value.clone())),
Union::FnPtr(ref value) => Self(Union::FnPtr(value.clone())), Union::FnPtr(ref value) => Self(Union::FnPtr(value.clone())),
Union::Variant(ref value) => (***value).clone_into_dynamic(), Union::Variant(ref value) => (***value).clone_into_dynamic(),
#[cfg(not(feature = "no_shared"))] #[cfg(not(feature = "no_closure"))]
Union::Shared(ref cell) => Self(Union::Shared(cell.clone())), Union::Shared(ref cell) => Self(Union::Shared(cell.clone())),
} }
} }
@ -571,9 +571,9 @@ impl Dynamic {
/// ///
/// # Panics /// # Panics
/// ///
/// Panics under the `no_shared` feature. /// Panics under the `no_closure` feature.
pub fn into_shared(self) -> Self { pub fn into_shared(self) -> Self {
#[cfg(not(feature = "no_shared"))] #[cfg(not(feature = "no_closure"))]
return match self.0 { return match self.0 {
Union::Shared(..) => self, Union::Shared(..) => self,
#[cfg(not(feature = "sync"))] #[cfg(not(feature = "sync"))]
@ -582,7 +582,7 @@ impl Dynamic {
_ => Self(Union::Shared(Arc::new(RwLock::new(self)))), _ => Self(Union::Shared(Arc::new(RwLock::new(self)))),
}; };
#[cfg(feature = "no_shared")] #[cfg(feature = "no_closure")]
unimplemented!() unimplemented!()
} }
@ -614,11 +614,11 @@ impl Dynamic {
let type_id = TypeId::of::<T>(); let type_id = TypeId::of::<T>();
match self.0 { match self.0 {
#[cfg(not(feature = "no_shared"))] #[cfg(not(feature = "no_closure"))]
#[cfg(not(feature = "sync"))] #[cfg(not(feature = "sync"))]
Union::Shared(cell) => return cell.borrow().clone().try_cast(), Union::Shared(cell) => return cell.borrow().clone().try_cast(),
#[cfg(not(feature = "no_shared"))] #[cfg(not(feature = "no_closure"))]
#[cfg(feature = "sync")] #[cfg(feature = "sync")]
Union::Shared(cell) => return cell.read().unwrap().clone().try_cast(), Union::Shared(cell) => return cell.read().unwrap().clone().try_cast(),
_ => (), _ => (),
@ -703,7 +703,7 @@ impl Dynamic {
match self.0 { match self.0 {
Union::Variant(value) => (*value).as_box_any().downcast().map(|x| *x).ok(), 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!(), Union::Shared(_) => unreachable!(),
_ => None, _ => None,
} }
@ -748,17 +748,17 @@ impl Dynamic {
/// ///
/// Returns `None` if the cast fails. /// Returns `None` if the cast fails.
#[inline(always)] #[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 { match self.0 {
#[cfg(not(feature = "no_shared"))] #[cfg(not(feature = "no_closure"))]
Union::Shared(ref cell) => { Union::Shared(cell) => {
#[cfg(not(feature = "sync"))] #[cfg(not(feature = "sync"))]
return Some(cell.borrow().downcast_ref::<T>().unwrap().clone()); return Some(cell.borrow().downcast_ref::<T>().unwrap().clone());
#[cfg(feature = "sync")] #[cfg(feature = "sync")]
return Some(cell.read().unwrap().downcast_ref::<T>().unwrap().clone()); 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)] #[inline(always)]
pub fn is_locked(&self) -> bool { pub fn is_locked(&self) -> bool {
match self.0 { match self.0 {
#[cfg(not(feature = "no_shared"))] #[cfg(not(feature = "no_closure"))]
Union::Shared(ref _cell) => { Union::Shared(ref _cell) => {
#[cfg(not(feature = "sync"))] #[cfg(not(feature = "sync"))]
return _cell.try_borrow().is_err(); return _cell.try_borrow().is_err();
@ -788,22 +788,33 @@ impl Dynamic {
/// Casting to `Dynamic` just returns a reference to it. /// Casting to `Dynamic` just returns a reference to it.
/// ///
/// Returns `None` if the cast fails. /// 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)] #[inline(always)]
pub fn read_lock<T: Variant + Clone>(&self) -> Option<DynamicReadLock<T>> { pub fn read_lock<T: Variant + Clone>(&self) -> Option<DynamicReadLock<T>> {
match self.0 { match self.0 {
#[cfg(not(feature = "no_shared"))] #[cfg(not(feature = "no_closure"))]
Union::Shared(ref cell) => { Union::Shared(ref cell) => {
#[cfg(not(feature = "sync"))] #[cfg(not(feature = "sync"))]
return Some(DynamicReadLock(DynamicReadLockInner::Guard(cell.borrow()))); let data = cell.borrow();
#[cfg(feature = "sync")] #[cfg(feature = "sync")]
return Some(DynamicReadLock(DynamicReadLockInner::Guard( let data = cell.read().unwrap();
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 _ => self
.downcast_ref() .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. /// Casting to `Dynamic` just returns a mutable reference to it.
/// ///
/// Returns `None` if the cast fails. /// 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)] #[inline(always)]
pub fn write_lock<T: Variant + Clone>(&mut self) -> Option<DynamicWriteLock<T>> { pub fn write_lock<T: Variant + Clone>(&mut self) -> Option<DynamicWriteLock<T>> {
match self.0 { match self.0 {
#[cfg(not(feature = "no_shared"))] #[cfg(not(feature = "no_closure"))]
Union::Shared(ref cell) => { Union::Shared(ref cell) => {
#[cfg(not(feature = "sync"))] #[cfg(not(feature = "sync"))]
return Some(DynamicWriteLock(DynamicWriteLockInner::Guard( let data = cell.borrow_mut();
cell.borrow_mut(),
)));
#[cfg(feature = "sync")] #[cfg(feature = "sync")]
return Some(DynamicWriteLock(DynamicWriteLockInner::Guard( let data = cell.write().unwrap();
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 _ => self
.downcast_mut() .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. /// Casting to `Dynamic` just returns a reference to it.
/// ///
/// Returns `None` if the cast fails. /// Returns `None` if the cast fails.
///
/// # Panics
///
/// Panics if the value is shared.
#[inline(always)] #[inline(always)]
fn downcast_ref<T: Variant + Clone>(&self) -> Option<&T> { fn downcast_ref<T: Variant + Clone>(&self) -> Option<&T> {
let type_id = TypeId::of::<T>(); let type_id = TypeId::of::<T>();
@ -909,7 +933,7 @@ impl Dynamic {
match &self.0 { match &self.0 {
Union::Variant(value) => value.as_ref().as_ref().as_any().downcast_ref::<T>(), 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!(), Union::Shared(_) => unreachable!(),
_ => None, _ => None,
} }
@ -919,6 +943,10 @@ impl Dynamic {
/// Casting to `Dynamic` just returns a mutable reference to it. /// Casting to `Dynamic` just returns a mutable reference to it.
/// ///
/// Returns `None` if the cast fails. /// Returns `None` if the cast fails.
///
/// # Panics
///
/// Panics if the value is shared.
#[inline(always)] #[inline(always)]
fn downcast_mut<T: Variant + Clone>(&mut self) -> Option<&mut T> { fn downcast_mut<T: Variant + Clone>(&mut self) -> Option<&mut T> {
let type_id = TypeId::of::<T>(); let type_id = TypeId::of::<T>();
@ -986,7 +1014,7 @@ impl Dynamic {
match &mut self.0 { match &mut self.0 {
Union::Variant(value) => value.as_mut().as_mut_any().downcast_mut::<T>(), 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!(), Union::Shared(_) => unreachable!(),
_ => None, _ => None,
} }
@ -997,10 +1025,8 @@ impl Dynamic {
pub fn as_int(&self) -> Result<INT, &'static str> { pub fn as_int(&self) -> Result<INT, &'static str> {
match self.0 { match self.0 {
Union::Int(n) => Ok(n), Union::Int(n) => Ok(n),
#[cfg(not(feature = "no_shared"))] #[cfg(not(feature = "no_closure"))]
Union::Shared(_) => self Union::Shared(_) => self.read_lock().map(|v| *v).ok_or_else(|| self.type_name()),
.clone_inner_data::<INT>()
.ok_or_else(|| self.type_name()),
_ => Err(self.type_name()), _ => Err(self.type_name()),
} }
} }
@ -1011,10 +1037,8 @@ impl Dynamic {
pub fn as_float(&self) -> Result<FLOAT, &'static str> { pub fn as_float(&self) -> Result<FLOAT, &'static str> {
match self.0 { match self.0 {
Union::Float(n) => Ok(n), Union::Float(n) => Ok(n),
#[cfg(not(feature = "no_shared"))] #[cfg(not(feature = "no_closure"))]
Union::Shared(_) => self Union::Shared(_) => self.read_lock().map(|v| *v).ok_or_else(|| self.type_name()),
.clone_inner_data::<FLOAT>()
.ok_or_else(|| self.type_name()),
_ => Err(self.type_name()), _ => Err(self.type_name()),
} }
} }
@ -1024,10 +1048,8 @@ impl Dynamic {
pub fn as_bool(&self) -> Result<bool, &'static str> { pub fn as_bool(&self) -> Result<bool, &'static str> {
match self.0 { match self.0 {
Union::Bool(b) => Ok(b), Union::Bool(b) => Ok(b),
#[cfg(not(feature = "no_shared"))] #[cfg(not(feature = "no_closure"))]
Union::Shared(_) => self Union::Shared(_) => self.read_lock().map(|v| *v).ok_or_else(|| self.type_name()),
.clone_inner_data::<bool>()
.ok_or_else(|| self.type_name()),
_ => Err(self.type_name()), _ => Err(self.type_name()),
} }
} }
@ -1037,10 +1059,8 @@ impl Dynamic {
pub fn as_char(&self) -> Result<char, &'static str> { pub fn as_char(&self) -> Result<char, &'static str> {
match self.0 { match self.0 {
Union::Char(n) => Ok(n), Union::Char(n) => Ok(n),
#[cfg(not(feature = "no_shared"))] #[cfg(not(feature = "no_closure"))]
Union::Shared(_) => self Union::Shared(_) => self.read_lock().map(|v| *v).ok_or_else(|| self.type_name()),
.clone_inner_data::<char>()
.ok_or_else(|| self.type_name()),
_ => Err(self.type_name()), _ => Err(self.type_name()),
} }
} }
@ -1070,7 +1090,7 @@ impl Dynamic {
match self.0 { match self.0 {
Union::Str(s) => Ok(s), Union::Str(s) => Ok(s),
Union::FnPtr(f) => Ok(f.take_data().0), Union::FnPtr(f) => Ok(f.take_data().0),
#[cfg(not(feature = "no_shared"))] #[cfg(not(feature = "no_closure"))]
Union::Shared(cell) => { Union::Shared(cell) => {
#[cfg(not(feature = "sync"))] #[cfg(not(feature = "sync"))]
{ {

View File

@ -1284,7 +1284,7 @@ impl Engine {
let args = args.as_mut(); let args = args.as_mut();
// Check for data race. // Check for data race.
if cfg!(not(feature = "no_shared")) { if cfg!(not(feature = "no_closure")) {
ensure_no_data_race(name, args, false)?; ensure_no_data_race(name, args, false)?;
} }

View File

@ -31,7 +31,7 @@ use crate::module::resolvers;
#[cfg(any(not(feature = "no_object"), not(feature = "no_module")))] #[cfg(any(not(feature = "no_object"), not(feature = "no_module")))]
use crate::utils::ImmutableString; use crate::utils::ImmutableString;
#[cfg(not(feature = "no_shared"))] #[cfg(not(feature = "no_closure"))]
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
use crate::any::DynamicWriteLock; use crate::any::DynamicWriteLock;
@ -49,6 +49,9 @@ use crate::stdlib::{
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
use crate::stdlib::any::TypeId; use crate::stdlib::any::TypeId;
#[cfg(not(feature = "no_closure"))]
use crate::stdlib::mem;
/// Variable-sized array of `Dynamic` values. /// Variable-sized array of `Dynamic` values.
/// ///
/// Not available under the `no_index` feature. /// 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: &str = "Fn";
pub const KEYWORD_FN_PTR_CALL: &str = "call"; pub const KEYWORD_FN_PTR_CALL: &str = "call";
pub const KEYWORD_FN_PTR_CURRY: &str = "curry"; 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_IS_SHARED: &str = "is_shared";
pub const KEYWORD_THIS: &str = "this"; pub const KEYWORD_THIS: &str = "this";
pub const FN_TO_STRING: &str = "to_string"; pub const FN_TO_STRING: &str = "to_string";
@ -132,7 +133,7 @@ pub enum Target<'a> {
Ref(&'a mut Dynamic), Ref(&'a mut Dynamic),
/// The target is a mutable reference to a Shared `Dynamic` value. /// The target is a mutable reference to a Shared `Dynamic` value.
/// It holds both the access guard and the original shared 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"))] #[cfg(not(feature = "no_object"))]
LockGuard((DynamicWriteLock<'a, Dynamic>, Dynamic)), LockGuard((DynamicWriteLock<'a, Dynamic>, Dynamic)),
/// The target is a temporary `Dynamic` value (i.e. the mutation can cause no side effects). /// 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 { pub fn is_ref(&self) -> bool {
match self { match self {
Self::Ref(_) => true, Self::Ref(_) => true,
#[cfg(not(feature = "no_shared"))] #[cfg(not(feature = "no_closure"))]
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
Self::LockGuard(_) => true, Self::LockGuard(_) => true,
Self::Value(_) => false, Self::Value(_) => false,
@ -161,7 +162,7 @@ impl Target<'_> {
pub fn is_value(&self) -> bool { pub fn is_value(&self) -> bool {
match self { match self {
Self::Ref(_) => false, Self::Ref(_) => false,
#[cfg(not(feature = "no_shared"))] #[cfg(not(feature = "no_closure"))]
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
Self::LockGuard(_) => false, Self::LockGuard(_) => false,
Self::Value(_) => true, Self::Value(_) => true,
@ -173,7 +174,7 @@ impl Target<'_> {
pub fn is_shared(&self) -> bool { pub fn is_shared(&self) -> bool {
match self { match self {
Self::Ref(r) => r.is_shared(), Self::Ref(r) => r.is_shared(),
#[cfg(not(feature = "no_shared"))] #[cfg(not(feature = "no_closure"))]
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
Self::LockGuard(_) => true, Self::LockGuard(_) => true,
Self::Value(r) => r.is_shared(), Self::Value(r) => r.is_shared(),
@ -186,7 +187,7 @@ impl Target<'_> {
pub fn is<T: Variant + Clone>(&self) -> bool { pub fn is<T: Variant + Clone>(&self) -> bool {
match self { match self {
Target::Ref(r) => r.is::<T>(), Target::Ref(r) => r.is::<T>(),
#[cfg(not(feature = "no_shared"))] #[cfg(not(feature = "no_closure"))]
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
Target::LockGuard((r, _)) => r.is::<T>(), Target::LockGuard((r, _)) => r.is::<T>(),
Target::Value(r) => r.is::<T>(), Target::Value(r) => r.is::<T>(),
@ -198,7 +199,7 @@ impl Target<'_> {
pub fn clone_into_dynamic(self) -> Dynamic { pub fn clone_into_dynamic(self) -> Dynamic {
match self { match self {
Self::Ref(r) => r.clone(), // Referenced value is cloned Self::Ref(r) => r.clone(), // Referenced value is cloned
#[cfg(not(feature = "no_shared"))] #[cfg(not(feature = "no_closure"))]
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
Self::LockGuard((_, orig)) => orig, // Original value is simply taken Self::LockGuard((_, orig)) => orig, // Original value is simply taken
Self::Value(v) => v, // Owned 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 { pub fn as_mut(&mut self) -> &mut Dynamic {
match self { match self {
Self::Ref(r) => *r, Self::Ref(r) => *r,
#[cfg(not(feature = "no_shared"))] #[cfg(not(feature = "no_closure"))]
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
Self::LockGuard((r, _)) => r.deref_mut(), Self::LockGuard((r, _)) => r.deref_mut(),
Self::Value(ref mut r) => r, Self::Value(ref mut r) => r,
@ -223,7 +224,7 @@ impl Target<'_> {
pub fn set_value(&mut self, new_val: Dynamic) -> Result<(), Box<EvalAltResult>> { pub fn set_value(&mut self, new_val: Dynamic) -> Result<(), Box<EvalAltResult>> {
match self { match self {
Self::Ref(r) => **r = new_val, Self::Ref(r) => **r = new_val,
#[cfg(not(feature = "no_shared"))] #[cfg(not(feature = "no_closure"))]
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
Self::LockGuard((r, _)) => **r = new_val, Self::LockGuard((r, _)) => **r = new_val,
Self::Value(_) => { Self::Value(_) => {
@ -260,7 +261,7 @@ impl Target<'_> {
#[cfg(any(not(feature = "no_index"), not(feature = "no_object")))] #[cfg(any(not(feature = "no_index"), not(feature = "no_object")))]
impl<'a> From<&'a mut Dynamic> for Target<'a> { impl<'a> From<&'a mut Dynamic> for Target<'a> {
fn from(value: &'a mut Dynamic) -> Self { fn from(value: &'a mut Dynamic) -> Self {
#[cfg(not(feature = "no_shared"))] #[cfg(not(feature = "no_closure"))]
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
if value.is_shared() { if value.is_shared() {
// Cloning is cheap for a shared value // 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 // 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. // 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))); // return Err(Box::new(EvalAltResult::ErrorDataRace(name.into(), *pos)));
// } // }
@ -1351,12 +1352,9 @@ impl Engine {
)), )),
// Normal assignment // Normal assignment
ScopeEntryType::Normal if op.is_empty() => { ScopeEntryType::Normal if op.is_empty() => {
if cfg!(not(feature = "no_shared")) && lhs_ptr.is_shared() { let rhs_val = rhs_val.clone_inner_data().unwrap();
*lhs_ptr.write_lock::<Dynamic>().unwrap() = if rhs_val.is_shared() { if cfg!(not(feature = "no_closure")) && lhs_ptr.is_shared() {
rhs_val.clone_inner_data().unwrap() *lhs_ptr.write_lock::<Dynamic>().unwrap() = rhs_val;
} else {
rhs_val
};
} else { } else {
*lhs_ptr = rhs_val; *lhs_ptr = rhs_val;
} }
@ -1377,7 +1375,7 @@ impl Engine {
.get_fn(hash_fn, false) .get_fn(hash_fn, false)
.or_else(|| self.packages.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 mut lock_guard = lhs_ptr.write_lock::<Dynamic>().unwrap();
let lhs_ptr_inner = lock_guard.deref_mut(); let lhs_ptr_inner = lock_guard.deref_mut();
@ -1401,12 +1399,9 @@ impl Engine {
) )
.map_err(|err| err.new_position(*op_pos))?; .map_err(|err| err.new_position(*op_pos))?;
if cfg!(not(feature = "no_shared")) && lhs_ptr.is_shared() { let value = value.clone_inner_data().unwrap();
*lhs_ptr.write_lock::<Dynamic>().unwrap() = if value.is_shared() { if cfg!(not(feature = "no_closure")) && lhs_ptr.is_shared() {
value.clone_inner_data().unwrap() *lhs_ptr.write_lock::<Dynamic>().unwrap() = value;
} else {
value
};
} else { } else {
*lhs_ptr = value; *lhs_ptr = value;
} }
@ -1845,6 +1840,25 @@ impl Engine {
} }
Ok(Default::default()) 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) self.check_data_size(result)

View File

@ -93,7 +93,7 @@ pub enum ParseErrorType {
MalformedInExpr(String), MalformedInExpr(String),
/// A capturing has syntax error. Wrapped value is the error description (if any). /// 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), MalformedCapture(String),
/// A map definition has duplicated property names. Wrapped value is the property name. /// A map definition has duplicated property names. Wrapped value is the property name.
/// ///

View File

@ -5,7 +5,7 @@ use crate::calc_fn_hash;
use crate::engine::{ use crate::engine::{
search_imports, search_namespace, search_scope_only, Engine, Imports, State, KEYWORD_DEBUG, 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_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::error::ParseErrorType;
use crate::fn_native::{FnCallArgs, FnPtr}; use crate::fn_native::{FnCallArgs, FnPtr};
@ -33,7 +33,7 @@ use crate::engine::{FN_IDX_GET, FN_IDX_SET};
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
use crate::engine::{Map, Target, FN_GET, FN_SET}; 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::scope::Entry as ScopeEntry;
use crate::stdlib::{ use crate::stdlib::{
@ -47,7 +47,7 @@ use crate::stdlib::{
vec::Vec, vec::Vec,
}; };
#[cfg(not(feature = "no_capture"))] #[cfg(not(feature = "no_closure"))]
use crate::stdlib::{collections::HashSet, string::String}; use crate::stdlib::{collections::HashSet, string::String};
/// Extract the property name from a getter function name. /// Extract the property name from a getter function name.
@ -139,7 +139,7 @@ impl Drop for ArgBackup<'_> {
} }
// Add captured variables into scope // Add captured variables into scope
#[cfg(not(feature = "no_capture"))] #[cfg(not(feature = "no_closure"))]
fn add_captured_variables_into_scope<'s>( fn add_captured_variables_into_scope<'s>(
externals: &HashSet<String>, externals: &HashSet<String>,
captured: Scope<'s>, captured: Scope<'s>,
@ -166,7 +166,7 @@ pub fn ensure_no_data_race(
args: &FnCallArgs, args: &FnCallArgs,
is_ref: bool, is_ref: bool,
) -> Result<(), Box<EvalAltResult>> { ) -> Result<(), Box<EvalAltResult>> {
if cfg!(not(feature = "no_shared")) { if cfg!(not(feature = "no_closure")) {
let skip = if is_ref { 1 } else { 0 }; let skip = if is_ref { 1 } else { 0 };
if let Some((n, _)) = args if let Some((n, _)) = args
@ -456,7 +456,7 @@ impl Engine {
level: usize, level: usize,
) -> Result<(Dynamic, bool), Box<EvalAltResult>> { ) -> Result<(Dynamic, bool), Box<EvalAltResult>> {
// Check for data race. // 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)?; ensure_no_data_race(fn_name, args, is_ref)?;
} }
@ -505,7 +505,7 @@ impl Engine {
let mods = &mut Imports::new(); let mods = &mut Imports::new();
// Add captured variables into scope // Add captured variables into scope
#[cfg(not(feature = "no_capture"))] #[cfg(not(feature = "no_closure"))]
if let Some(captured) = _capture { if let Some(captured) = _capture {
add_captured_variables_into_scope(&func.externals, captured, scope); add_captured_variables_into_scope(&func.externals, captured, scope);
} }
@ -690,18 +690,12 @@ impl Engine {
.into(), .into(),
false, false,
)) ))
} else if cfg!(not(feature = "no_shared")) } else if cfg!(not(feature = "no_closure"))
&& _fn_name == KEYWORD_IS_SHARED && _fn_name == KEYWORD_IS_SHARED
&& idx.is_empty() && idx.is_empty()
{ {
// take call // take call
Ok((target.is_shared().into(), false)) 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 { } else {
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
let redirected; let redirected;
@ -814,29 +808,13 @@ impl Engine {
} }
// Handle is_shared() // 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 expr = args_expr.get(0).unwrap();
let value = self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)?; let value = self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)?;
return Ok(value.is_shared().into()); 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 // Handle call() - Redirect function call
let redirected; let redirected;
let mut args_expr = args_expr.as_ref(); let mut args_expr = args_expr.as_ref();
@ -896,7 +874,7 @@ impl Engine {
let mut arg_values: StaticVec<_>; let mut arg_values: StaticVec<_>;
let mut args: StaticVec<_>; let mut args: StaticVec<_>;
let mut is_ref = false; 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()) Some(scope.flatten_clone())
} else { } else {
None None
@ -924,7 +902,7 @@ impl Engine {
// Turn it into a method call only if the object is not shared // Turn it into a method call only if the object is not shared
args = if target.is_shared() { args = if target.is_shared() {
arg_values.insert(0, target.clone_inner_data().unwrap()); arg_values.insert(0, target.clone().clone_inner_data().unwrap());
arg_values.iter_mut().collect() arg_values.iter_mut().collect()
} else { } else {
is_ref = true; is_ref = true;
@ -1041,7 +1019,7 @@ impl Engine {
let mods = &mut Imports::new(); let mods = &mut Imports::new();
// Add captured variables into scope // Add captured variables into scope
#[cfg(not(feature = "no_capture"))] #[cfg(not(feature = "no_closure"))]
if _capture && !scope.is_empty() { if _capture && !scope.is_empty() {
add_captured_variables_into_scope( add_captured_variables_into_scope(
&func.externals, &func.externals,

View File

@ -22,10 +22,10 @@ use crate::stdlib::rc::Rc;
#[cfg(feature = "sync")] #[cfg(feature = "sync")]
use crate::stdlib::sync::Arc; use crate::stdlib::sync::Arc;
#[cfg(not(feature = "no_shared"))] #[cfg(not(feature = "no_closure"))]
#[cfg(not(feature = "sync"))] #[cfg(not(feature = "sync"))]
use crate::stdlib::cell::RefCell; use crate::stdlib::cell::RefCell;
#[cfg(not(feature = "no_shared"))] #[cfg(not(feature = "no_closure"))]
#[cfg(feature = "sync")] #[cfg(feature = "sync")]
use crate::stdlib::sync::RwLock; use crate::stdlib::sync::RwLock;
@ -51,11 +51,11 @@ pub type Shared<T> = Rc<T>;
pub type Shared<T> = Arc<T>; pub type Shared<T> = Arc<T>;
/// Mutable reference-counted container (read-write lock) /// Mutable reference-counted container (read-write lock)
#[cfg(not(feature = "no_shared"))] #[cfg(not(feature = "no_closure"))]
#[cfg(not(feature = "sync"))] #[cfg(not(feature = "sync"))]
pub type SharedMut<T> = Shared<RefCell<T>>; pub type SharedMut<T> = Shared<RefCell<T>>;
/// Mutable reference-counted container (read-write lock) /// Mutable reference-counted container (read-write lock)
#[cfg(not(feature = "no_shared"))] #[cfg(not(feature = "no_closure"))]
#[cfg(feature = "sync")] #[cfg(feature = "sync")]
pub type SharedMut<T> = Shared<RwLock<T>>; pub type SharedMut<T> = Shared<RwLock<T>>;

View File

@ -765,7 +765,7 @@ pub fn optimize_into_ast(
access: fn_def.access, access: fn_def.access,
body: Default::default(), body: Default::default(),
params: fn_def.params.clone(), params: fn_def.params.clone(),
#[cfg(not(feature = "no_capture"))] #[cfg(not(feature = "no_closure"))]
externals: fn_def.externals.clone(), externals: fn_def.externals.clone(),
pos: fn_def.pos, pos: fn_def.pos,
} }

View File

@ -39,7 +39,7 @@ use crate::stdlib::{
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]
use crate::stdlib::collections::hash_map::DefaultHasher; use crate::stdlib::collections::hash_map::DefaultHasher;
#[cfg(not(feature = "no_capture"))] #[cfg(not(feature = "no_closure"))]
use crate::stdlib::collections::HashSet; use crate::stdlib::collections::HashSet;
#[cfg(feature = "no_std")] #[cfg(feature = "no_std")]
@ -366,7 +366,7 @@ pub struct ScriptFnDef {
/// Names of function parameters. /// Names of function parameters.
pub params: StaticVec<String>, pub params: StaticVec<String>,
/// Access to external variables. /// Access to external variables.
#[cfg(not(feature = "no_capture"))] #[cfg(not(feature = "no_closure"))]
pub externals: HashSet<String>, pub externals: HashSet<String>,
/// Function body. /// Function body.
pub body: Stmt, pub body: Stmt,
@ -414,13 +414,13 @@ struct ParseState<'e> {
/// Encapsulates a local stack with variable names to simulate an actual runtime scope. /// Encapsulates a local stack with variable names to simulate an actual runtime scope.
stack: Vec<(String, ScopeEntryType)>, stack: Vec<(String, ScopeEntryType)>,
/// Tracks a list of external variables (variables that are not explicitly declared in the scope). /// 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>, externals: HashMap<String, Position>,
/// An indicator that prevents variables capturing into externals one time. /// An indicator that disables variable capturing into externals one single time.
/// If set to true the next call of `access_var` will not capture the variable. /// 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 /// All consequent calls to `access_var` will not be affected
#[cfg(not(feature = "no_capture"))] #[cfg(not(feature = "no_closure"))]
capture: bool, allow_capture: bool,
/// Encapsulates a local stack with variable names to simulate an actual runtime scope. /// Encapsulates a local stack with variable names to simulate an actual runtime scope.
modules: Vec<String>, modules: Vec<String>,
/// Maximum levels of expression nesting. /// Maximum levels of expression nesting.
@ -444,10 +444,10 @@ impl<'e> ParseState<'e> {
max_expr_depth, max_expr_depth,
#[cfg(not(feature = "unchecked"))] #[cfg(not(feature = "unchecked"))]
max_function_expr_depth, max_function_expr_depth,
#[cfg(not(feature = "no_capture"))] #[cfg(not(feature = "no_closure"))]
externals: Default::default(), externals: Default::default(),
#[cfg(not(feature = "no_capture"))] #[cfg(not(feature = "no_closure"))]
capture: true, allow_capture: true,
stack: Default::default(), stack: Default::default(),
modules: Default::default(), modules: Default::default(),
} }
@ -469,13 +469,13 @@ impl<'e> ParseState<'e> {
.find(|(_, (n, _))| *n == name) .find(|(_, (n, _))| *n == name)
.and_then(|(i, _)| NonZeroUsize::new(i + 1)); .and_then(|(i, _)| NonZeroUsize::new(i + 1));
#[cfg(not(feature = "no_capture"))] #[cfg(not(feature = "no_closure"))]
if self.capture { if self.allow_capture {
if index.is_none() && !self.externals.contains_key(name) { if index.is_none() && !self.externals.contains_key(name) {
self.externals.insert(name.to_string(), _pos); self.externals.insert(name.to_string(), _pos);
} }
} else { } else {
self.capture = true self.allow_capture = true
} }
index index
@ -579,6 +579,9 @@ pub enum Stmt {
Position, Position,
)>, )>,
), ),
/// Convert a variable to shared.
#[cfg(not(feature = "no_closure"))]
Share(Box<(String, Position)>),
} }
impl Default for Stmt { impl Default for Stmt {
@ -606,6 +609,9 @@ impl Stmt {
Stmt::Import(x) => x.2, Stmt::Import(x) => x.2,
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
Stmt::Export(x) => x.1, 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, Stmt::Import(x) => x.2 = new_pos,
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
Stmt::Export(x) => x.1 = new_pos, Stmt::Export(x) => x.1 = new_pos,
#[cfg(not(feature = "no_closure"))]
Stmt::Share(x) => x.1 = new_pos,
} }
self self
@ -655,6 +664,9 @@ impl Stmt {
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
Stmt::Import(_) | Stmt::Export(_) => false, Stmt::Import(_) | Stmt::Export(_) => false,
#[cfg(not(feature = "no_closure"))]
Stmt::Share(_) => false,
} }
} }
@ -678,6 +690,9 @@ impl Stmt {
Stmt::Import(_) => false, Stmt::Import(_) => false,
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
Stmt::Export(_) => false, Stmt::Export(_) => false,
#[cfg(not(feature = "no_closure"))]
Stmt::Share(_) => false,
} }
} }
} }
@ -1671,7 +1686,7 @@ fn parse_primary(
root_expr = match (root_expr, token) { root_expr = match (root_expr, token) {
// Function call // Function call
#[cfg(not(feature = "no_capture"))] #[cfg(not(feature = "no_closure"))]
(Expr::Variable(x), Token::Bang) => { (Expr::Variable(x), Token::Bang) => {
if !match_token(input, Token::LeftParen)? { if !match_token(input, Token::LeftParen)? {
return Err(PERR::MissingToken( return Err(PERR::MissingToken(
@ -1855,7 +1870,7 @@ fn parse_unary(
let (expr, func) = parse_anon_fn(input, &mut new_state, lib, settings)?; 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)| { new_state.externals.iter().for_each(|(closure, pos)| {
state.access_var(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 cfg!(not(feature = "no_object")) && op_token == Token::Period {
if let (Token::Identifier(_), _) = input.peek().unwrap() { if let (Token::Identifier(_), _) = input.peek().unwrap() {
// prevents capturing of the object properties as vars: xxx.<var> // 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(); let params: StaticVec<_> = params.into_iter().map(|(p, _)| p).collect();
#[cfg(not(feature = "no_capture"))] #[cfg(not(feature = "no_closure"))]
let externals = state let externals = state
.externals .externals
.iter() .iter()
@ -3108,7 +3123,7 @@ fn parse_fn(
name: name.into(), name: name.into(),
access, access,
params, params,
#[cfg(not(feature = "no_capture"))] #[cfg(not(feature = "no_closure"))]
externals, externals,
body, body,
pos: settings.pos, pos: settings.pos,
@ -3128,6 +3143,17 @@ fn make_curry_from_externals(
let num_externals = externals.len(); let num_externals = externals.len();
let mut args: StaticVec<_> = Default::default(); 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)| { externals.into_iter().for_each(|(var_name, pos)| {
args.push(Expr::Variable(Box::new(((var_name, pos), None, 0, None)))); args.push(Expr::Variable(Box::new(((var_name, pos), None, 0, None))));
}); });
@ -3142,7 +3168,23 @@ fn make_curry_from_externals(
None, 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. /// Parse an anonymous function definition.
@ -3215,7 +3257,7 @@ fn parse_anon_fn(
// External variables may need to be processed in a consistent order, // External variables may need to be processed in a consistent order,
// so extract them into a list. // so extract them into a list.
let externals: StaticVec<_> = { let externals: StaticVec<_> = {
#[cfg(not(feature = "no_capture"))] #[cfg(not(feature = "no_closure"))]
{ {
state state
.externals .externals
@ -3223,11 +3265,11 @@ fn parse_anon_fn(
.map(|(k, &v)| (k.clone(), v)) .map(|(k, &v)| (k.clone(), v))
.collect() .collect()
} }
#[cfg(feature = "no_capture")] #[cfg(feature = "no_closure")]
Default::default() Default::default()
}; };
let params: StaticVec<_> = if cfg!(not(feature = "no_capture")) { let params: StaticVec<_> = if cfg!(not(feature = "no_closure")) {
externals externals
.iter() .iter()
.map(|(k, _)| k) .map(|(k, _)| k)
@ -3257,7 +3299,7 @@ fn parse_anon_fn(
name: fn_name.clone(), name: fn_name.clone(),
access: FnAccess::Public, access: FnAccess::Public,
params, params,
#[cfg(not(feature = "no_capture"))] #[cfg(not(feature = "no_closure"))]
externals: Default::default(), externals: Default::default(),
body, body,
pos: settings.pos, pos: settings.pos,
@ -3265,7 +3307,7 @@ fn parse_anon_fn(
let expr = Expr::FnPointer(Box::new((fn_name, settings.pos))); 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) make_curry_from_externals(expr, externals, settings.pos)
} else { } else {
expr expr

View File

@ -158,32 +158,6 @@ impl<'a> Scope<'a> {
self.push_dynamic_value(name, EntryType::Normal, Dynamic::from(value), false) 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. /// Add (push) a new `Dynamic` entry to the Scope.
/// ///
/// # Examples /// # Examples
@ -226,34 +200,6 @@ impl<'a> Scope<'a> {
self.push_dynamic_value(name, EntryType::Constant, Dynamic::from(value), true) 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. /// Add (push) a new constant with a `Dynamic` value to the Scope.
/// ///
/// Constants are immutable and cannot be assigned to. Their values never change. /// 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> { pub fn get_value<T: Variant + Clone>(&self, name: &str) -> Option<T> {
self.get_entry(name) self.get_entry(name)
.and_then(|Entry { value, .. }| value.clone_inner_data::<T>()) .and_then(|Entry { value, .. }| value.clone().clone_inner_data::<T>())
} }
/// Update the value of the named entry. /// Update the value of the named entry.
@ -485,13 +431,20 @@ impl<'a> Scope<'a> {
/// ///
/// let (name, value) = iter.next().unwrap(); /// let (name, value) = iter.next().unwrap();
/// assert_eq!(name, "x"); /// assert_eq!(name, "x");
/// assert_eq!(value.clone().cast::<i64>(), 42); /// assert_eq!(value.cast::<i64>(), 42);
/// ///
/// let (name, value) = iter.next().unwrap(); /// let (name, value) = iter.next().unwrap();
/// assert_eq!(name, "foo"); /// 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 self.0
.iter() .iter()
.map(|Entry { name, value, .. }| (name.as_ref(), value)) .map(|Entry { name, value, .. }| (name.as_ref(), value))

View File

@ -2,7 +2,7 @@
use crate::engine::{ use crate::engine::{
Engine, KEYWORD_DEBUG, KEYWORD_EVAL, KEYWORD_FN_PTR, KEYWORD_FN_PTR_CALL, KEYWORD_FN_PTR_CURRY, 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; use crate::error::LexError;
@ -501,14 +501,15 @@ impl Token {
"import" | "export" | "as" => Reserved(syntax.into()), "import" | "export" | "as" => Reserved(syntax.into()),
"===" | "!==" | "->" | "<-" | "=>" | ":=" | "::<" | "(*" | "*)" | "#" | "public" "===" | "!==" | "->" | "<-" | "=>" | ":=" | "::<" | "(*" | "*)" | "#" | "public"
| "new" | "use" | "module" | "package" | "var" | "static" | "with" | "do" | "each" | "new" | "use" | "module" | "package" | "var" | "static" | "shared" | "with"
| "then" | "goto" | "exit" | "switch" | "match" | "case" | "try" | "catch" | "do" | "each" | "then" | "goto" | "exit" | "switch" | "match" | "case" | "try"
| "default" | "void" | "null" | "nil" | "spawn" | "go" | "sync" | "async" | "await" | "catch" | "default" | "void" | "null" | "nil" | "spawn" | "go" | "sync" | "async"
| "yield" => Reserved(syntax.into()), | "await" | "yield" => Reserved(syntax.into()),
KEYWORD_PRINT | KEYWORD_DEBUG | KEYWORD_TYPE_OF | KEYWORD_EVAL | KEYWORD_FN_PTR 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_FN_PTR_CALL | KEYWORD_FN_PTR_CURRY | KEYWORD_IS_SHARED | KEYWORD_THIS => {
| KEYWORD_TAKE | KEYWORD_THIS => Reserved(syntax.into()), Reserved(syntax.into())
}
_ => return None, _ => return None,
}) })
@ -1432,8 +1433,8 @@ fn get_identifier(
#[inline(always)] #[inline(always)]
pub fn is_keyword_function(name: &str) -> bool { pub fn is_keyword_function(name: &str) -> bool {
match name { match name {
#[cfg(not(feature = "no_shared"))] #[cfg(not(feature = "no_closure"))]
KEYWORD_SHARED | KEYWORD_TAKE | KEYWORD_IS_SHARED => true, KEYWORD_IS_SHARED => true,
KEYWORD_PRINT | KEYWORD_DEBUG | KEYWORD_TYPE_OF | KEYWORD_EVAL | KEYWORD_FN_PTR KEYWORD_PRINT | KEYWORD_DEBUG | KEYWORD_TYPE_OF | KEYWORD_EVAL | KEYWORD_FN_PTR
| KEYWORD_FN_PTR_CALL | KEYWORD_FN_PTR_CURRY => true, | KEYWORD_FN_PTR_CALL | KEYWORD_FN_PTR_CURRY => true,
_ => false, _ => false,

View File

@ -35,7 +35,8 @@ fn test_fn_ptr_curry_call() -> Result<(), Box<EvalAltResult>> {
} }
#[test] #[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>> { fn test_closures() -> Result<(), Box<EvalAltResult>> {
let engine = Engine::new(); let engine = Engine::new();
@ -56,5 +57,61 @@ fn test_closures() -> Result<(), Box<EvalAltResult>> {
42 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(()) Ok(())
} }

View File

@ -122,7 +122,7 @@ fn test_function_pointers() -> Result<(), Box<EvalAltResult>> {
} }
#[test] #[test]
#[cfg(not(feature = "no_capture"))] #[cfg(not(feature = "no_closure"))]
fn test_function_captures() -> Result<(), Box<EvalAltResult>> { fn test_function_captures() -> Result<(), Box<EvalAltResult>> {
let engine = Engine::new(); let engine = Engine::new();

View File

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