From 4079164bfd2a4a7456f9468a3a398c49de5debe0 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Mon, 3 Aug 2020 12:10:20 +0800 Subject: [PATCH] Implement closures. --- .github/workflows/build.yml | 3 +- Cargo.toml | 5 +- RELEASES.md | 2 + doc/src/appendix/keywords.md | 3 +- doc/src/language/fn-anon.md | 14 +- doc/src/language/fn-closure.md | 22 ++- doc/src/language/keywords.md | 2 +- doc/src/language/oop.md | 19 +- doc/src/links.md | 3 +- doc/src/start/features.md | 5 +- examples/repl.rs | 13 +- src/any.rs | 146 ++++++++------- src/api.rs | 2 +- src/engine.rs | 66 ++++--- src/error.rs | 2 +- src/fn_call.rs | 46 ++--- src/fn_native.rs | 8 +- src/optimize.rs | 2 +- src/parser.rs | 92 +++++++--- src/scope.rs | 69 ++----- src/token.rs | 19 +- tests/closures.rs | 59 +++++- tests/functions.rs | 2 +- tests/shared.rs | 324 --------------------------------- 24 files changed, 340 insertions(+), 588 deletions(-) delete mode 100644 tests/shared.rs diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 276cbf72..11216cc3 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -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] diff --git a/Cargo.toml b/Cargo.toml index c450eecd..7d20a93c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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" diff --git a/RELEASES.md b/RELEASES.md index f163f06f..1679aa0c 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -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 ------------ diff --git a/doc/src/appendix/keywords.md b/doc/src/appendix/keywords.md index a1e20fed..d896739a 100644 --- a/doc/src/appendix/keywords.md +++ b/doc/src/appendix/keywords.md @@ -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 | diff --git a/doc/src/language/fn-anon.md b/doc/src/language/fn-anon.md index e76ba59c..b8a9154d 100644 --- a/doc/src/language/fn-anon.md +++ b/doc/src/language/fn-anon.md @@ -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. diff --git a/doc/src/language/fn-closure.md b/doc/src/language/fn-closure.md index 1701cc4a..9ba66f2e 100644 --- a/doc/src/language/fn-closure.md +++ b/doc/src/language/fn-closure.md @@ -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; ``` diff --git a/doc/src/language/keywords.md b/doc/src/language/keywords.md index 0683386e..4ddaae8f 100644 --- a/doc/src/language/keywords.md +++ b/doc/src/language/keywords.md @@ -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 | | diff --git a/doc/src/language/oop.md b/doc/src/language/oop.md index 09bc7b55..017c4932 100644 --- a/doc/src/language/oop.md +++ b/doc/src/language/oop.md @@ -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 ``` diff --git a/doc/src/links.md b/doc/src/links.md index a22f0038..8667082c 100644 --- a/doc/src/links.md +++ b/doc/src/links.md @@ -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 diff --git a/doc/src/start/features.md b/doc/src/start/features.md index 9f0d42d4..3b8cee04 100644 --- a/doc/src/start/features.md +++ b/doc/src/start/features.md @@ -23,9 +23,8 @@ more control over what a script can (or cannot) do. | `no_object` | Disable support for [custom types] and [object maps]. | | `no_function` | Disable script-defined [functions]. | | `no_module` | Disable loading external [modules]. | -| `no_capture` | Disable [capturing][capture] external variables in [anonymous functions] and [capturing the calling scope]({{rootUrl}}/language/fn-capture.md) in function calls. | -| `no_shared` | Disable sharing of data values. | -| `no_std` | Build for `no-std` (implies `no_shared`). Notice that additional dependencies will be pulled in to replace `std` features. | +| `no_closure` | Disable [capturing][capture] external variables in [anonymous functions] to simulate _closures_, or [capturing the calling scope]({{rootUrl}}/language/fn-capture.md) in function calls. | +| `no_std` | Build for `no-std` (implies `no_closure`). Notice that additional dependencies will be pulled in to replace `std` features. | | `serde` | Enable serialization/deserialization via `serde`. Notice that the [`serde`](https://crates.io/crates/serde) crate will be pulled in together with its dependencies. | | `internals` | Expose internal data structures (e.g. [`AST`] nodes). Beware that Rhai internals are volatile and may change from version to version. | | `unicode-xid-ident` | Allow [Unicode Standard Annex #31](http://www.unicode.org/reports/tr31/) as identifiers. | diff --git a/examples/repl.rs b/examples/repl.rs index b3877dd0..ee73c193 100644 --- a/examples/repl.rs +++ b/examples/repl.rs @@ -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::().unwrap(), + ) + }); continue; } "astu" => { diff --git a/src/any.rs b/src/any.rs index 08ebdeab..78d681be 100644 --- a/src/any.rs +++ b/src/any.rs @@ -4,7 +4,7 @@ use crate::fn_native::{FnPtr, SendSync}; use crate::parser::{ImmutableString, INT}; use crate::r#unsafe::{unsafe_cast_box, unsafe_try_cast}; -#[cfg(not(feature = "no_shared"))] +#[cfg(not(feature = "no_closure"))] use crate::fn_native::SharedMut; #[cfg(not(feature = "no_float"))] @@ -24,14 +24,14 @@ use crate::stdlib::{ string::String, }; -#[cfg(not(feature = "no_shared"))] +#[cfg(not(feature = "no_closure"))] #[cfg(not(feature = "sync"))] use crate::stdlib::{ cell::{Ref, RefCell, RefMut}, rc::Rc, }; -#[cfg(not(feature = "no_shared"))] +#[cfg(not(feature = "no_closure"))] #[cfg(feature = "sync")] use crate::stdlib::sync::{Arc, RwLock, RwLockReadGuard, RwLockWriteGuard}; @@ -159,7 +159,7 @@ pub enum Union { Map(Box), FnPtr(Box), Variant(Box>), - #[cfg(not(feature = "no_shared"))] + #[cfg(not(feature = "no_closure"))] Shared(SharedMut), } @@ -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::(), Union::FnPtr(_) => TypeId::of::(), 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::() => "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::() => write!(f, ""), Union::Variant(value) => write!(f, "{}", (*value).type_name()), - #[cfg(not(feature = "no_shared"))] + #[cfg(not(feature = "no_closure"))] Union::Shared(_) => f.write_str(""), } } @@ -424,7 +424,7 @@ impl fmt::Debug for Dynamic { #[cfg(not(feature = "no_std"))] Union::Variant(value) if value.is::() => write!(f, ""), Union::Variant(value) => write!(f, "{}", (*value).type_name()), - #[cfg(not(feature = "no_shared"))] + #[cfg(not(feature = "no_closure"))] Union::Shared(_) => f.write_str(""), } } @@ -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::(); 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(&self) -> Option { + pub fn clone_inner_data(self) -> Option { 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::().unwrap().clone()); #[cfg(feature = "sync")] return Some(cell.read().unwrap().downcast_ref::().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(&self) -> Option> { 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::() && TypeId::of::() != TypeId::of::() { + 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(&mut self) -> Option> { 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::() && TypeId::of::() != TypeId::of::() { + 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(&self) -> Option<&T> { let type_id = TypeId::of::(); @@ -909,7 +933,7 @@ impl Dynamic { match &self.0 { Union::Variant(value) => value.as_ref().as_ref().as_any().downcast_ref::(), - #[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(&mut self) -> Option<&mut T> { let type_id = TypeId::of::(); @@ -986,7 +1014,7 @@ impl Dynamic { match &mut self.0 { Union::Variant(value) => value.as_mut().as_mut_any().downcast_mut::(), - #[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 { match self.0 { Union::Int(n) => Ok(n), - #[cfg(not(feature = "no_shared"))] - Union::Shared(_) => self - .clone_inner_data::() - .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 { match self.0 { Union::Float(n) => Ok(n), - #[cfg(not(feature = "no_shared"))] - Union::Shared(_) => self - .clone_inner_data::() - .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 { match self.0 { Union::Bool(b) => Ok(b), - #[cfg(not(feature = "no_shared"))] - Union::Shared(_) => self - .clone_inner_data::() - .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 { match self.0 { Union::Char(n) => Ok(n), - #[cfg(not(feature = "no_shared"))] - Union::Shared(_) => self - .clone_inner_data::() - .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"))] { diff --git a/src/api.rs b/src/api.rs index be69f53d..43aaf6d5 100644 --- a/src/api.rs +++ b/src/api.rs @@ -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)?; } diff --git a/src/engine.rs b/src/engine.rs index b0bd5e8b..be915025 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -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(&self) -> bool { match self { Target::Ref(r) => r.is::(), - #[cfg(not(feature = "no_shared"))] + #[cfg(not(feature = "no_closure"))] #[cfg(not(feature = "no_object"))] Target::LockGuard((r, _)) => r.is::(), Target::Value(r) => r.is::(), @@ -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> { 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::().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::().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::().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::().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::().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) diff --git a/src/error.rs b/src/error.rs index 259d6916..1d5ea7f8 100644 --- a/src/error.rs +++ b/src/error.rs @@ -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. /// diff --git a/src/fn_call.rs b/src/fn_call.rs index 6980caca..d56487e2 100644 --- a/src/fn_call.rs +++ b/src/fn_call.rs @@ -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, captured: Scope<'s>, @@ -166,7 +166,7 @@ pub fn ensure_no_data_race( args: &FnCallArgs, is_ref: bool, ) -> Result<(), Box> { - 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> { // 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::().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::().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, diff --git a/src/fn_native.rs b/src/fn_native.rs index f8bb0bf1..a972793b 100644 --- a/src/fn_native.rs +++ b/src/fn_native.rs @@ -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 = Rc; pub type Shared = Arc; /// Mutable reference-counted container (read-write lock) -#[cfg(not(feature = "no_shared"))] +#[cfg(not(feature = "no_closure"))] #[cfg(not(feature = "sync"))] pub type SharedMut = Shared>; /// Mutable reference-counted container (read-write lock) -#[cfg(not(feature = "no_shared"))] +#[cfg(not(feature = "no_closure"))] #[cfg(feature = "sync")] pub type SharedMut = Shared>; diff --git a/src/optimize.rs b/src/optimize.rs index 9072a550..7503c592 100644 --- a/src/optimize.rs +++ b/src/optimize.rs @@ -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, } diff --git a/src/parser.rs b/src/parser.rs index 08869146..7f29bbcd 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -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, /// Access to external variables. - #[cfg(not(feature = "no_capture"))] + #[cfg(not(feature = "no_closure"))] pub externals: HashSet, /// 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, - /// 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, /// 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. - #[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 diff --git a/src/scope.rs b/src/scope.rs index 37f71e99..836108c7 100644 --- a/src/scope.rs +++ b/src/scope.rs @@ -158,32 +158,6 @@ impl<'a> Scope<'a> { self.push_dynamic_value(name, EntryType::Normal, Dynamic::from(value), false) } - /// Add (push) a new shared entry to the Scope. - /// - /// # Examples - /// - /// ``` - /// use rhai::Scope; - /// - /// let mut my_scope = Scope::new(); - /// - /// my_scope.push_shared("x", 42_i64); - /// assert_eq!(my_scope.get_value::("x").unwrap(), 42); - /// ``` - #[cfg(not(feature = "no_shared"))] - pub fn push_shared>, 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::("x").unwrap(), 42); - /// ``` - #[cfg(not(feature = "no_shared"))] - pub fn push_constant_shared>, 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(&self, name: &str) -> Option { self.get_entry(name) - .and_then(|Entry { value, .. }| value.clone_inner_data::()) + .and_then(|Entry { value, .. }| value.clone().clone_inner_data::()) } /// 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::(), 42); + /// assert_eq!(value.cast::(), 42); /// /// let (name, value) = iter.next().unwrap(); /// assert_eq!(name, "foo"); - /// assert_eq!(value.clone().cast::(), "hello"); + /// assert_eq!(value.cast::(), "hello"); /// ``` - pub fn iter(&self) -> impl Iterator { + pub fn iter(&self) -> impl Iterator { + 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 { self.0 .iter() .map(|Entry { name, value, .. }| (name.as_ref(), value)) diff --git a/src/token.rs b/src/token.rs index 920fb33a..4a4d5fc1 100644 --- a/src/token.rs +++ b/src/token.rs @@ -2,7 +2,7 @@ use crate::engine::{ Engine, KEYWORD_DEBUG, KEYWORD_EVAL, KEYWORD_FN_PTR, KEYWORD_FN_PTR_CALL, KEYWORD_FN_PTR_CURRY, - KEYWORD_IS_SHARED, KEYWORD_PRINT, KEYWORD_SHARED, KEYWORD_TAKE, KEYWORD_THIS, KEYWORD_TYPE_OF, + KEYWORD_IS_SHARED, KEYWORD_PRINT, KEYWORD_THIS, KEYWORD_TYPE_OF, }; use crate::error::LexError; @@ -501,14 +501,15 @@ impl Token { "import" | "export" | "as" => Reserved(syntax.into()), "===" | "!==" | "->" | "<-" | "=>" | ":=" | "::<" | "(*" | "*)" | "#" | "public" - | "new" | "use" | "module" | "package" | "var" | "static" | "with" | "do" | "each" - | "then" | "goto" | "exit" | "switch" | "match" | "case" | "try" | "catch" - | "default" | "void" | "null" | "nil" | "spawn" | "go" | "sync" | "async" | "await" - | "yield" => Reserved(syntax.into()), + | "new" | "use" | "module" | "package" | "var" | "static" | "shared" | "with" + | "do" | "each" | "then" | "goto" | "exit" | "switch" | "match" | "case" | "try" + | "catch" | "default" | "void" | "null" | "nil" | "spawn" | "go" | "sync" | "async" + | "await" | "yield" => Reserved(syntax.into()), KEYWORD_PRINT | KEYWORD_DEBUG | KEYWORD_TYPE_OF | KEYWORD_EVAL | KEYWORD_FN_PTR - | KEYWORD_FN_PTR_CALL | KEYWORD_FN_PTR_CURRY | KEYWORD_IS_SHARED | KEYWORD_SHARED - | KEYWORD_TAKE | KEYWORD_THIS => Reserved(syntax.into()), + | KEYWORD_FN_PTR_CALL | KEYWORD_FN_PTR_CURRY | KEYWORD_IS_SHARED | KEYWORD_THIS => { + Reserved(syntax.into()) + } _ => return None, }) @@ -1432,8 +1433,8 @@ fn get_identifier( #[inline(always)] pub fn is_keyword_function(name: &str) -> bool { match name { - #[cfg(not(feature = "no_shared"))] - KEYWORD_SHARED | KEYWORD_TAKE | KEYWORD_IS_SHARED => true, + #[cfg(not(feature = "no_closure"))] + KEYWORD_IS_SHARED => true, KEYWORD_PRINT | KEYWORD_DEBUG | KEYWORD_TYPE_OF | KEYWORD_EVAL | KEYWORD_FN_PTR | KEYWORD_FN_PTR_CALL | KEYWORD_FN_PTR_CURRY => true, _ => false, diff --git a/tests/closures.rs b/tests/closures.rs index c5282870..f0a91ac6 100644 --- a/tests/closures.rs +++ b/tests/closures.rs @@ -35,7 +35,8 @@ fn test_fn_ptr_curry_call() -> Result<(), Box> { } #[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> { let engine = Engine::new(); @@ -56,5 +57,61 @@ fn test_closures() -> Result<(), Box> { 42 ); + assert_eq!( + engine.eval::( + r#" + let a = 41; + let foo = |x| { a += x }; + foo.call(1); + a + "# + )?, + 42 + ); + + assert!(engine.eval::( + 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> { + let engine = Engine::new(); + + assert_eq!( + engine.eval::( + r#" + let a = 1; + let b = 40; + let foo = |x| { this += a + x }; + b.call(foo, 1); + b + "# + )?, + 42 + ); + + assert!(matches!( + *engine + .eval::( + r#" + let a = 20; + let foo = |x| { this += a + x }; + a.call(foo, 1); + a + "# + ) + .expect_err("should error"), + EvalAltResult::ErrorDataRace(_, _) + )); + Ok(()) } diff --git a/tests/functions.rs b/tests/functions.rs index 9336cb6d..58ef8d99 100644 --- a/tests/functions.rs +++ b/tests/functions.rs @@ -122,7 +122,7 @@ fn test_function_pointers() -> Result<(), Box> { } #[test] -#[cfg(not(feature = "no_capture"))] +#[cfg(not(feature = "no_closure"))] fn test_function_captures() -> Result<(), Box> { let engine = Engine::new(); diff --git a/tests/shared.rs b/tests/shared.rs deleted file mode 100644 index b2238fbf..00000000 --- a/tests/shared.rs +++ /dev/null @@ -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> { - let mut engine = Engine::new(); - - assert_eq!(engine.eval::("shared(42)")?, 42); - - assert_eq!(engine.eval::("shared(true)")?, true); - - assert_eq!( - engine.eval::("let x = shared(true); type_of(x)")?, - "bool" - ); - - assert_eq!( - engine.eval::("let x = shared(true); x = (); type_of(x)")?, - "()" - ); - - #[cfg(not(feature = "no_float"))] - assert_eq!(engine.eval::("shared(4.2)")?, 4.2); - - assert_eq!(engine.eval::(r#"shared("test")"#)?, "test"); - - assert_eq!(engine.eval::(r#"shared("test")"#)?, "test"); - - assert_eq!(engine.eval::("shared('x')")?, 'x'); - - assert!(engine.eval::("is_shared(shared(42))")?); - - #[cfg(not(feature = "no_object"))] - assert!(engine.eval::("shared(42).is_shared()")?); - - #[cfg(not(feature = "no_index"))] - { - assert_eq!( - engine.eval::( - r#" - let s = shared("test"); - let i = shared(0); - i = 2; - s[i] = 'S'; - - s - "# - )?, - "teSt" - ); - - assert_eq!( - engine - .eval::( - 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::( - 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::( - 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::( - 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::( - 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::( - 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::( - 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::( - 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::("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::() - .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::(), - TypeId::of::(), - TypeId::of::(), - ], - move |engine: &Engine, lib: &Module, args: &mut [&mut Dynamic]| { - let fp = std::mem::take(args[2]).cast::(); - let mut value = args[1].clone(); - { - let mut lock = value.write_lock::().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::( - 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::( - 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> { - let engine = Engine::new(); - - #[cfg(not(feature = "no_function"))] - #[cfg(not(feature = "no_object"))] - { - assert_eq!( - engine.eval::( - r#" - fn foo(x) { this += x }; - - let a = shared(41); - a.foo(1); - a - "# - )?, - 42 - ); - - assert!(matches!( - *engine - .eval::( - r#" - fn foo(x) { this += x }; - - let a = shared(42); - a.foo(a); - a - "# - ) - .expect_err("should error"), - EvalAltResult::ErrorDataRace(_, _) - )); - } - - Ok(()) -}