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_function"
- "--features no_module"
- "--features no_capture"
- "--features no_shared"
- "--features no_closure"
- "--features unicode-xid-ident"
toolchain: [stable]
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_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"

View File

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

View File

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

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

View File

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

View File

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

View File

@ -30,26 +30,33 @@ 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
obj.increment(1);
obj.action(); // prints 1
obj.action(); // prints 1
obj.update(42);
obj.action(); // prints 42
obj.action(); // prints 42
factor = 2;
obj.update(42);
obj.action(); // prints 84
```

View File

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

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

View File

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

View File

@ -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"))]
{

View File

@ -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)?;
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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