From 691e04292fe8b8a29f15c5b2281c535ebf99e594 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Thu, 30 Jul 2020 13:28:06 +0800 Subject: [PATCH 01/40] Put externals in ScriptFnDef. --- src/optimize.rs | 30 +++++++++++++-------- src/parser.rs | 71 ++++++++++++++++++++++++++++++------------------- 2 files changed, 63 insertions(+), 38 deletions(-) diff --git a/src/optimize.rs b/src/optimize.rs index b46b7488..e26d1908 100644 --- a/src/optimize.rs +++ b/src/optimize.rs @@ -5,10 +5,10 @@ use crate::calc_fn_hash; use crate::engine::{ Engine, Imports, KEYWORD_DEBUG, KEYWORD_EVAL, KEYWORD_FN_PTR, KEYWORD_PRINT, KEYWORD_TYPE_OF, }; +use crate::fn_native::FnPtr; use crate::module::Module; use crate::parser::{map_dynamic_to_expr, Expr, ScriptFnDef, Stmt, AST}; use crate::scope::{Entry as ScopeEntry, EntryType as ScopeEntryType, Scope}; -use crate::token::is_valid_identifier; use crate::utils::StaticVec; #[cfg(not(feature = "no_function"))] @@ -19,6 +19,7 @@ use crate::parser::CustomExpr; use crate::stdlib::{ boxed::Box, + convert::TryFrom, iter::empty, string::{String, ToString}, vec, @@ -418,7 +419,7 @@ fn optimize_expr(expr: Expr, state: &mut State) -> Expr { // All other items can be thrown away. state.set_dirty(); let pos = m.1; - m.0.into_iter().find(|((name, _), _)| name.as_str() == prop.as_str()) + m.0.into_iter().find(|((name, _), _)| name == prop) .map(|(_, mut expr)| { expr.set_position(pos); expr }) .unwrap_or_else(|| Expr::Unit(pos)) } @@ -495,7 +496,7 @@ fn optimize_expr(expr: Expr, state: &mut State) -> Expr { state.set_dirty(); let ch = a.0.to_string(); - if b.0.iter().find(|((name, _), _)| name.as_str() == ch.as_str()).is_some() { + if b.0.iter().find(|((name, _), _)| name == &ch).is_some() { Expr::True(a.1) } else { Expr::False(a.1) @@ -553,14 +554,19 @@ fn optimize_expr(expr: Expr, state: &mut State) -> Expr { // Fn("...") Expr::FnCall(x) - if x.1.is_none() - && (x.0).0 == KEYWORD_FN_PTR - && x.3.len() == 1 - && matches!(x.3[0], Expr::StringConstant(_)) + if x.1.is_none() + && (x.0).0 == KEYWORD_FN_PTR + && x.3.len() == 1 + && matches!(x.3[0], Expr::StringConstant(_)) => { - match &x.3[0] { - Expr::StringConstant(s) if is_valid_identifier(s.0.chars()) => Expr::FnPointer(s.clone()), - _ => Expr::FnCall(x) + if let Expr::StringConstant(s) = &x.3[0] { + if let Ok(fn_ptr) = FnPtr::try_from(s.0.as_str()) { + Expr::FnPointer(Box::new((fn_ptr.take_data().0, s.1))) + } else { + Expr::FnCall(x) + } + } else { + unreachable!() } } @@ -578,7 +584,7 @@ fn optimize_expr(expr: Expr, state: &mut State) -> Expr { let _has_script_fn = state.lib.iter_fn().find(|(_, _, _, f)| { if !f.is_script() { return false; } let fn_def = f.get_fn_def(); - fn_def.name.as_str() == name && (args.len()..=args.len() + 1).contains(&fn_def.params.len()) + fn_def.name == name && (args.len()..=args.len() + 1).contains(&fn_def.params.len()) }).is_some(); #[cfg(feature = "no_function")] @@ -765,6 +771,8 @@ pub fn optimize_into_ast( access: fn_def.access, body: Default::default(), params: fn_def.params.clone(), + #[cfg(not(feature = "no_capture"))] + externals: fn_def.externals.clone(), pos: fn_def.pos, } .into() diff --git a/src/parser.rs b/src/parser.rs index 3068bad0..abe3c7d8 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -363,6 +363,9 @@ pub struct ScriptFnDef { pub access: FnAccess, /// Names of function parameters. pub params: StaticVec, + /// Access to external variables. + #[cfg(not(feature = "no_capture"))] + pub externals: StaticVec, /// Function body. pub body: Stmt, /// Position of the function definition. @@ -448,7 +451,7 @@ impl<'e> ParseState<'e> { /// The return value is the offset to be deducted from `Stack::len`, /// i.e. the top element of the `ParseState` is offset 1. /// Return `None` when the variable name is not found in the `stack`. - fn access_var(&mut self, name: &str, pos: Position) -> Option { + fn access_var(&mut self, name: &str, _pos: Position) -> Option { let index = self .stack .iter() @@ -459,7 +462,7 @@ impl<'e> ParseState<'e> { #[cfg(not(feature = "no_capture"))] if index.is_none() && !self.externals.contains_key(name) { - self.externals.insert(name.to_string(), pos); + self.externals.insert(name.to_string(), _pos); } index @@ -2848,7 +2851,7 @@ fn parse_stmt( match input.next().unwrap() { (Token::Fn, pos) => { - let mut state = ParseState::new( + let mut new_state = ParseState::new( state.engine, #[cfg(not(feature = "unchecked"))] state.max_function_expr_depth, @@ -2867,7 +2870,7 @@ fn parse_stmt( pos: pos, }; - let func = parse_fn(input, &mut state, lib, access, settings)?; + let func = parse_fn(input, &mut new_state, lib, access, settings)?; // Qualifiers (none) + function name + number of arguments. let hash = calc_fn_hash(empty(), &func.name, func.params.len(), empty()); @@ -3039,12 +3042,23 @@ fn parse_fn( (_, pos) => return Err(PERR::FnMissingBody(name).into_err(*pos)), }; - let params = params.into_iter().map(|(p, _)| p).collect(); + let params: StaticVec<_> = params.into_iter().map(|(p, _)| p).collect(); + + #[cfg(not(feature = "no_capture"))] + let externals = state + .externals + .iter() + .map(|(name, _)| name) + .filter(|name| !params.contains(name)) + .cloned() + .collect(); Ok(ScriptFnDef { name: name.into(), access, params, + #[cfg(not(feature = "no_capture"))] + externals, body, pos: settings.pos, }) @@ -3054,40 +3068,31 @@ fn parse_fn( #[cfg(not(feature = "no_capture"))] fn make_curry_from_externals( fn_expr: Expr, - state: &mut ParseState, - settings: &ParseSettings, + externals: StaticVec<(String, Position)>, + pos: Position, ) -> Expr { - if state.externals.is_empty() { + if externals.is_empty() { return fn_expr; } + let num_externals = externals.len(); let mut args: StaticVec<_> = Default::default(); - state.externals.iter().for_each(|(var_name, pos)| { - args.push(Expr::Variable(Box::new(( - (var_name.clone(), *pos), - None, - 0, - None, - )))); + externals.into_iter().for_each(|(var_name, pos)| { + args.push(Expr::Variable(Box::new(((var_name, pos), None, 0, None)))); }); - let hash = calc_fn_hash( - empty(), - KEYWORD_FN_PTR_CURRY, - state.externals.len(), - empty(), - ); + let hash = calc_fn_hash(empty(), KEYWORD_FN_PTR_CURRY, num_externals, empty()); let fn_call = Expr::FnCall(Box::new(( - (KEYWORD_FN_PTR_CURRY.into(), false, settings.pos), + (KEYWORD_FN_PTR_CURRY.into(), false, pos), None, hash, args, None, ))); - Expr::Dot(Box::new((fn_expr, fn_call, settings.pos))) + Expr::Dot(Box::new((fn_expr, fn_call, pos))) } /// Parse an anonymous function definition. @@ -3160,11 +3165,20 @@ fn parse_anon_fn( #[cfg(feature = "no_capture")] let params: StaticVec<_> = params.into_iter().map(|(v, _)| v).collect(); + // External variables may need to be processed in a consistent order, + // so extract them into a list. + #[cfg(not(feature = "no_capture"))] + let externals: StaticVec<_> = state + .externals + .iter() + .map(|(k, &v)| (k.clone(), v)) + .collect(); + // Add parameters that are auto-curried #[cfg(not(feature = "no_capture"))] - let params: StaticVec<_> = state - .externals - .keys() + let params: StaticVec<_> = externals + .iter() + .map(|(k, _)| k) .cloned() .chain(params.into_iter().map(|(v, _)| v)) .collect(); @@ -3183,10 +3197,13 @@ fn parse_anon_fn( // Create unique function name let fn_name: ImmutableString = format!("{}{:16x}", FN_ANONYMOUS, hash).into(); + // Define the function let script = ScriptFnDef { name: fn_name.clone(), access: FnAccess::Public, params, + #[cfg(not(feature = "no_capture"))] + externals: Default::default(), body, pos: settings.pos, }; @@ -3194,7 +3211,7 @@ fn parse_anon_fn( let expr = Expr::FnPointer(Box::new((fn_name, settings.pos))); #[cfg(not(feature = "no_capture"))] - let expr = make_curry_from_externals(expr, state, &settings); + let expr = make_curry_from_externals(expr, externals, settings.pos); Ok((expr, script)) } From e505a068397a52f44174b27443c3d64e68276982 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Thu, 30 Jul 2020 13:28:25 +0800 Subject: [PATCH 02/40] Add comparison operators to ImmutableString. --- src/engine.rs | 8 +++----- src/fn_call.rs | 3 ++- src/packages/map_basic.rs | 6 +++--- src/utils.rs | 43 +++++++++++++++++++++++++++++++++++++++ 4 files changed, 51 insertions(+), 9 deletions(-) diff --git a/src/engine.rs b/src/engine.rs index 7806382f..2ad03365 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -1105,7 +1105,7 @@ impl Engine { .downcast_ref::() .ok_or_else(|| EvalAltResult::ErrorStringIndexExpr(idx_pos))?; - map.get_mut(index.as_str()) + map.get_mut(index) .map(Target::from) .unwrap_or_else(|| Target::from(())) }) @@ -1208,10 +1208,8 @@ impl Engine { #[cfg(not(feature = "no_object"))] Dynamic(Union::Map(rhs_value)) => match lhs_value { // Only allows String or char - Dynamic(Union::Str(s)) => Ok(rhs_value.contains_key(s.as_str()).into()), - Dynamic(Union::Char(c)) => { - Ok(rhs_value.contains_key(c.to_string().as_str()).into()) - } + Dynamic(Union::Str(s)) => Ok(rhs_value.contains_key(&s).into()), + Dynamic(Union::Char(c)) => Ok(rhs_value.contains_key(&c.to_string()).into()), _ => Err(Box::new(EvalAltResult::ErrorInExpr(lhs.position()))), }, Dynamic(Union::Str(rhs_value)) => match lhs_value { diff --git a/src/fn_call.rs b/src/fn_call.rs index 162410f3..861f490b 100644 --- a/src/fn_call.rs +++ b/src/fn_call.rs @@ -665,7 +665,8 @@ impl Engine { )) }) .and_then(|s| FnPtr::try_from(s)) - .map(Into::::into); + .map(Into::::into) + .map_err(|err| err.new_position(expr.position())); } } diff --git a/src/packages/map_basic.rs b/src/packages/map_basic.rs index cc1aa000..9437bc67 100644 --- a/src/packages/map_basic.rs +++ b/src/packages/map_basic.rs @@ -23,7 +23,7 @@ fn map_get_values(map: &mut Map) -> FuncReturn> { def_package!(crate:BasicMapPackage:"Basic object map utilities.", lib, { lib.set_fn_2_mut( "has", - |map: &mut Map, prop: ImmutableString| Ok(map.contains_key(prop.as_str())), + |map: &mut Map, prop: ImmutableString| Ok(map.contains_key(&prop)), ); lib.set_fn_1_mut("len", |map: &mut Map| Ok(map.len() as INT)); lib.set_fn_1_mut("clear", |map: &mut Map| { @@ -32,7 +32,7 @@ def_package!(crate:BasicMapPackage:"Basic object map utilities.", lib, { }); lib.set_fn_2_mut( "remove", - |x: &mut Map, name: ImmutableString| Ok(x.remove(name.as_str()).unwrap_or_else(|| ().into())), + |x: &mut Map, name: ImmutableString| Ok(x.remove(&name).unwrap_or_else(|| ().into())), ); lib.set_fn_2_mut( "mixin", @@ -47,7 +47,7 @@ def_package!(crate:BasicMapPackage:"Basic object map utilities.", lib, { "fill_with", |map1: &mut Map, map2: Map| { map2.into_iter().for_each(|(key, value)| { - if !map1.contains_key(key.as_str()) { + if !map1.contains_key(&key) { map1.insert(key, value); } }); diff --git a/src/utils.rs b/src/utils.rs index 13e11704..6668a7a9 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -6,6 +6,7 @@ use crate::stdlib::{ any::TypeId, borrow::Borrow, boxed::Box, + cmp::Ordering, fmt, hash::{BuildHasher, Hash, Hasher}, iter::FromIterator, @@ -141,6 +142,12 @@ impl AsRef for ImmutableString { } } +impl Borrow for ImmutableString { + fn borrow(&self) -> &String { + &self.0 + } +} + impl Borrow for ImmutableString { fn borrow(&self) -> &str { self.0.as_str() @@ -348,6 +355,42 @@ impl AddAssign for ImmutableString { } } +impl> PartialEq for ImmutableString { + fn eq(&self, other: &S) -> bool { + self.as_str().eq(other.as_ref()) + } +} + +impl PartialEq for str { + fn eq(&self, other: &ImmutableString) -> bool { + self.eq(other.as_str()) + } +} + +impl PartialEq for String { + fn eq(&self, other: &ImmutableString) -> bool { + self.eq(other.as_str()) + } +} + +impl> PartialOrd for ImmutableString { + fn partial_cmp(&self, other: &S) -> Option { + self.as_str().partial_cmp(other.as_ref()) + } +} + +impl PartialOrd for str { + fn partial_cmp(&self, other: &ImmutableString) -> Option { + self.partial_cmp(other.as_str()) + } +} + +impl PartialOrd for String { + fn partial_cmp(&self, other: &ImmutableString) -> Option { + self.as_str().partial_cmp(other.as_str()) + } +} + impl ImmutableString { /// Consume the `ImmutableString` and convert it into a `String`. /// If there are other references to the same string, a cloned copy is returned. From 98b294c699a163a74551eda7a3a88dfb1e16e9ae Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Thu, 30 Jul 2020 18:18:28 +0800 Subject: [PATCH 03/40] Implement capturing. --- RELEASES.md | 4 + doc/src/SUMMARY.md | 13 +- doc/src/advanced.md | 2 + doc/src/language/fn-anon.md | 2 +- doc/src/language/fn-capture.md | 62 +++++++ doc/src/language/fn-closure.md | 2 + doc/src/language/fn-curry.md | 4 +- doc/src/start/features.md | 2 +- src/engine.rs | 49 +++--- src/error.rs | 11 +- src/fn_call.rs | 288 ++++++++++++++++++++------------- src/fn_native.rs | 2 +- src/optimize.rs | 4 +- src/parser.rs | 66 +++++--- src/scope.rs | 13 +- src/token.rs | 14 +- tests/functions.rs | 52 +++++- 17 files changed, 410 insertions(+), 180 deletions(-) create mode 100644 doc/src/language/fn-capture.md diff --git a/RELEASES.md b/RELEASES.md index 7f59cddf..f163f06f 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -9,6 +9,8 @@ This version adds: * Binding the `this` pointer in a function pointer `call`. * Anonymous functions (in Rust closure syntax). Simplifies creation of single-use ad-hoc functions. * Currying of function pointers. +* Auto-currying of anonymous functions. +* Capturing call scope via `func!(...)` syntax. New features ------------ @@ -19,6 +21,8 @@ New features * Anonymous functions are supported in the syntax of a Rust closure, e.g. `|x, y, z| x + y - z`. * Custom syntax now works even without the `internals` feature. * Currying of function pointers is supported via the new `curry` keyword. +* Automatic currying of anonymous functions to capture environment variables. +* 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. diff --git a/doc/src/SUMMARY.md b/doc/src/SUMMARY.md index 78ea9882..6b966de1 100644 --- a/doc/src/SUMMARY.md +++ b/doc/src/SUMMARY.md @@ -100,21 +100,22 @@ The Rhai Scripting Language 8. [Maximum Call Stack Depth](safety/max-call-stack.md) 9. [Maximum Statement Depth](safety/max-stmt-depth.md) 7. [Advanced Topics](advanced.md) - 1. [Object-Oriented Programming (OOP)](language/oop.md) - 2. [Serialization/Deserialization of `Dynamic` with `serde`](rust/serde.md) - 3. [Script Optimization](engine/optimize/index.md) + 1. [Capture Scope for Function Call](language/fn-capture.md) + 2. [Object-Oriented Programming (OOP)](language/oop.md) + 3. [Serialization/Deserialization of `Dynamic` with `serde`](rust/serde.md) + 4. [Script Optimization](engine/optimize/index.md) 1. [Optimization Levels](engine/optimize/optimize-levels.md) 2. [Re-Optimize an AST](engine/optimize/reoptimize.md) 3. [Eager Function Evaluation](engine/optimize/eager.md) 4. [Side-Effect Considerations](engine/optimize/side-effects.md) 5. [Volatility Considerations](engine/optimize/volatility.md) 6. [Subtle Semantic Changes](engine/optimize/semantics.md) - 4. [Low-Level API](rust/register-raw.md) - 5. [Use as DSL](engine/dsl.md) + 5. [Low-Level API](rust/register-raw.md) + 6. [Use as DSL](engine/dsl.md) 1. [Disable Keywords and/or Operators](engine/disable.md) 2. [Custom Operators](engine/custom-op.md) 3. [Extending with Custom Syntax](engine/custom-syntax.md) - 6. [Eval Statement](language/eval.md) + 7. [Eval Statement](language/eval.md) 8. [Appendix](appendix/index.md) 1. [Keywords](appendix/keywords.md) 2. [Operators and Symbols](appendix/operators.md) diff --git a/doc/src/advanced.md b/doc/src/advanced.md index cd958c73..6b79fe70 100644 --- a/doc/src/advanced.md +++ b/doc/src/advanced.md @@ -5,6 +5,8 @@ Advanced Topics This section covers advanced features such as: +* [Capture the calling scope]({{rootUrl}}/language/fn-capture.md) in a function call. + * Simulated [Object Oriented Programming (OOP)][OOP]. * [`serde`] integration. diff --git a/doc/src/language/fn-anon.md b/doc/src/language/fn-anon.md index a4cb0fee..e76ba59c 100644 --- a/doc/src/language/fn-anon.md +++ b/doc/src/language/fn-anon.md @@ -54,7 +54,7 @@ WARNING - NOT Closures ---------------------- Remember: anonymous functions, though having the same syntax as Rust _closures_, are themselves -**not** closures. In particular, they do not capture their running environment. They are more like +**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`] diff --git a/doc/src/language/fn-capture.md b/doc/src/language/fn-capture.md new file mode 100644 index 00000000..042675e9 --- /dev/null +++ b/doc/src/language/fn-capture.md @@ -0,0 +1,62 @@ +Capture The Calling Scope for Function Call +========================================== + +{{#include ../links.md}} + + +Peeking Out of The Pure Box +--------------------------- + +Rhai functions are _pure_, meaning that they depend on on their arguments and have no +access to the calling environment. + +When a function accesses a variable that is not defined within that function's scope, +it raises an evaluation error. + +It is possible, through a special syntax, to capture the calling scope - i.e. the scope +that makes the function call - and access variables defined there. + +```rust +fn foo(y) { // function accesses 'x' and 'y', but 'x' is not defined + x += y; // 'x' is modified in this function + x +} + +let x = 1; + +foo(41); // error: variable 'x' not found + +// Calling a function with a '!' causes it to capture the calling scope + +foo!(41) == 42; // the function can access the value of 'x', but cannot change it + +x == 1; // 'x' is still the original value + +x.method!(); // <- syntax error: capturing is not allowed in method-call style + +// Capturing also works for function pointers + +let f = Fn("foo"); + +call!(f, 41) == 42; // must use function-call style + +f.call!(41); // <- syntax error: capturing is not allowed in method-call style +``` + + +No Mutations +------------ + +Variables in the calling scope are accessed as copies. +Changes to them do not reflect back to the calling scope. + +Rhai functions remain _pure_ in the sense that they can never mutate their environment. + + +Caveat Emptor +------------- + +Functions relying on the calling scope is a _Very Bad Idea™_ because it makes code almost impossible +to reason and maintain, as their behaviors are volatile and unpredictable. + +This usage should be at the last resort. diff --git a/doc/src/language/fn-closure.md b/doc/src/language/fn-closure.md index d8749601..1701cc4a 100644 --- a/doc/src/language/fn-closure.md +++ b/doc/src/language/fn-closure.md @@ -1,6 +1,8 @@ Capture External Variables via Automatic Currying ================================================ +{{#include ../links.md}} + Poor Man's Closures ------------------- diff --git a/doc/src/language/fn-curry.md b/doc/src/language/fn-curry.md index 71c8933a..83705175 100644 --- a/doc/src/language/fn-curry.md +++ b/doc/src/language/fn-curry.md @@ -33,7 +33,7 @@ curried.call(2) == 42; // <- de-sugars to 'func.call(21, 2)' Automatic Currying ------------------ -[Anonymous functions] defined via a closure syntax _capture_ external variables that are not shadowed inside -the function's scope. +[Anonymous functions] defined via a closure syntax _capture_ the _values_ of external variables +that are not shadowed inside the function's scope. This is accomplished via [automatic currying]. diff --git a/doc/src/start/features.md b/doc/src/start/features.md index 7a06618a..efeb39c5 100644 --- a/doc/src/start/features.md +++ b/doc/src/start/features.md @@ -23,7 +23,7 @@ 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 external variables in [anonymous functions]. | +| `no_capture` | Disable [capturing][capture] external variables in [anonymous functions] and [capturing the calling scope]({{rootUrl}}/language/fn-capture.md) in function calls. | | `no_std` | Build for `no-std`. 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. | diff --git a/src/engine.rs b/src/engine.rs index 2ad03365..ac264af4 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -686,7 +686,7 @@ impl Engine { let args = &mut [target.as_mut(), &mut idx_val2, &mut new_val]; self.exec_fn_call( - state, lib, FN_IDX_SET, true, 0, args, is_ref, true, false, + state, lib, FN_IDX_SET, 0, args, is_ref, true, false, None, None, level, ) .or_else(|err| match *err { @@ -710,7 +710,7 @@ impl Engine { let args = &mut [target.as_mut(), &mut idx_val2, &mut new_val]; self.exec_fn_call( - state, lib, FN_IDX_SET, true, 0, args, is_ref, true, false, + state, lib, FN_IDX_SET, 0, args, is_ref, true, false, None, None, level, )?; } @@ -732,7 +732,7 @@ impl Engine { match rhs { // xxx.fn_name(arg_expr_list) Expr::FnCall(x) if x.1.is_none() => { - let ((name, native, pos), _, hash, _, def_val) = x.as_ref(); + let ((name, native, _, pos), _, hash, _, def_val) = x.as_ref(); self.make_method_call( state, lib, name, *hash, target, idx_val, *def_val, *native, false, level, @@ -766,7 +766,7 @@ impl Engine { let ((_, _, setter), pos) = x.as_ref(); let mut args = [target.as_mut(), _new_val.as_mut().unwrap()]; self.exec_fn_call( - state, lib, setter, true, 0, &mut args, is_ref, true, false, None, + state, lib, setter, 0, &mut args, is_ref, true, false, None, None, level, ) .map(|(v, _)| (v, true)) @@ -777,7 +777,7 @@ impl Engine { let ((_, getter, _), pos) = x.as_ref(); let mut args = [target.as_mut()]; self.exec_fn_call( - state, lib, getter, true, 0, &mut args, is_ref, true, false, None, + state, lib, getter, 0, &mut args, is_ref, true, false, None, None, level, ) .map(|(v, _)| (v, false)) @@ -795,7 +795,7 @@ impl Engine { } // {xxx:map}.fn_name(arg_expr_list)[expr] | {xxx:map}.fn_name(arg_expr_list).expr Expr::FnCall(x) if x.1.is_none() => { - let ((name, native, pos), _, hash, _, def_val) = x.as_ref(); + let ((name, native, _, pos), _, hash, _, def_val) = x.as_ref(); let (val, _) = self .make_method_call( state, lib, name, *hash, target, idx_val, *def_val, @@ -828,7 +828,7 @@ impl Engine { let args = &mut arg_values[..1]; let (mut val, updated) = self .exec_fn_call( - state, lib, getter, true, 0, args, is_ref, true, false, + state, lib, getter, 0, args, is_ref, true, false, None, None, level, ) .map_err(|err| err.new_position(*pos))?; @@ -848,8 +848,8 @@ impl Engine { // Re-use args because the first &mut parameter will not be consumed arg_values[1] = val; self.exec_fn_call( - state, lib, setter, true, 0, arg_values, is_ref, true, - false, None, level, + state, lib, setter, 0, arg_values, is_ref, true, false, + None, None, level, ) .or_else( |err| match *err { @@ -866,7 +866,7 @@ impl Engine { } // xxx.fn_name(arg_expr_list)[expr] | xxx.fn_name(arg_expr_list).expr Expr::FnCall(x) if x.1.is_none() => { - let ((name, native, pos), _, hash, _, def_val) = x.as_ref(); + let ((name, native, _, pos), _, hash, _, def_val) = x.as_ref(); let (mut val, _) = self .make_method_call( state, lib, name, *hash, target, idx_val, *def_val, @@ -1138,7 +1138,7 @@ impl Engine { let type_name = val.type_name(); let args = &mut [val, &mut _idx]; self.exec_fn_call( - state, _lib, FN_IDX_GET, true, 0, args, is_ref, true, false, None, _level, + state, _lib, FN_IDX_GET, 0, args, is_ref, true, false, None, None, _level, ) .map(|(v, _)| v.into()) .map_err(|err| match *err { @@ -1186,15 +1186,13 @@ impl Engine { let def_value = Some(false); let args = &mut [&mut lhs_value.clone(), value]; - let hashes = ( - // Qualifiers (none) + function name + number of arguments + argument `TypeId`'s. - calc_fn_hash(empty(), op, args.len(), args.iter().map(|a| a.type_id())), - 0, - ); + // Qualifiers (none) + function name + number of arguments + argument `TypeId`'s. + let hash = + calc_fn_hash(empty(), op, args.len(), args.iter().map(|a| a.type_id())); let (r, _) = self .call_fn_raw( - &mut scope, mods, state, lib, op, hashes, args, false, false, false, + &mut scope, mods, state, lib, op, hash, args, false, false, false, def_value, level, ) .map_err(|err| err.new_position(rhs.position()))?; @@ -1301,14 +1299,14 @@ impl Engine { } else if run_builtin_op_assignment(op, lhs_ptr, &rhs_val)?.is_none() { // Not built in, map to `var = var op rhs` let op = &op[..op.len() - 1]; // extract operator without = - let hash = calc_fn_hash(empty(), op, 2, empty()); + // Clone the LHS value let args = &mut [&mut lhs_ptr.clone(), &mut rhs_val]; + // Run function let (value, _) = self .exec_fn_call( - state, lib, op, true, hash, args, false, false, false, None, - level, + state, lib, op, 0, args, false, false, false, None, None, level, ) .map_err(|err| err.new_position(*op_pos))?; // Set value to LHS @@ -1331,13 +1329,12 @@ impl Engine { } else { // Op-assignment - always map to `lhs = lhs op rhs` let op = &op[..op.len() - 1]; // extract operator without = - let hash = calc_fn_hash(empty(), op, 2, empty()); let args = &mut [ &mut self.eval_expr(scope, mods, state, lib, this_ptr, lhs_expr, level)?, &mut rhs_val, ]; self.exec_fn_call( - state, lib, op, true, hash, args, false, false, false, None, level, + state, lib, op, 0, args, false, false, false, None, None, level, ) .map(|(v, _)| v) .map_err(|err| err.new_position(*op_pos))? @@ -1407,20 +1404,20 @@ impl Engine { // Normal function call Expr::FnCall(x) if x.1.is_none() => { - let ((name, native, pos), _, hash, args_expr, def_val) = x.as_ref(); + let ((name, native, capture, pos), _, hash, args_expr, def_val) = x.as_ref(); self.make_function_call( scope, mods, state, lib, this_ptr, name, args_expr, *def_val, *hash, *native, - false, level, + false, *capture, level, ) .map_err(|err| err.new_position(*pos)) } // Module-qualified function call Expr::FnCall(x) if x.1.is_some() => { - let ((name, _, pos), modules, hash, args_expr, def_val) = x.as_ref(); + let ((name, _, capture, pos), modules, hash, args_expr, def_val) = x.as_ref(); self.make_qualified_function_call( scope, mods, state, lib, this_ptr, modules, name, args_expr, *def_val, *hash, - level, + *capture, level, ) .map_err(|err| err.new_position(*pos)) } diff --git a/src/error.rs b/src/error.rs index fc92e5ea..259d6916 100644 --- a/src/error.rs +++ b/src/error.rs @@ -91,6 +91,10 @@ pub enum ParseErrorType { /// /// Never appears under the `no_object` and `no_index` features combination. MalformedInExpr(String), + /// A capturing has syntax error. Wrapped value is the error description (if any). + /// + /// Never appears under the `no_capture` feature. + MalformedCapture(String), /// A map definition has duplicated property names. Wrapped value is the property name. /// /// Never appears under the `no_object` feature. @@ -166,6 +170,7 @@ impl ParseErrorType { Self::MalformedCallExpr(_) => "Invalid expression in function call arguments", Self::MalformedIndexExpr(_) => "Invalid index in indexing expression", Self::MalformedInExpr(_) => "Invalid 'in' expression", + Self::MalformedCapture(_) => "Invalid capturing", Self::DuplicatedProperty(_) => "Duplicated property in object map literal", Self::ForbiddenConstantExpr(_) => "Expecting a constant", Self::PropertyExpected => "Expecting name of a property", @@ -199,9 +204,9 @@ impl fmt::Display for ParseErrorType { } Self::UnknownOperator(s) => write!(f, "{}: '{}'", self.desc(), s), - Self::MalformedIndexExpr(s) => f.write_str(if s.is_empty() { self.desc() } else { s }), - - Self::MalformedInExpr(s) => f.write_str(if s.is_empty() { self.desc() } else { s }), + Self::MalformedIndexExpr(s) | Self::MalformedInExpr(s) | Self::MalformedCapture(s) => { + f.write_str(if s.is_empty() { self.desc() } else { s }) + } Self::DuplicatedProperty(s) => { write!(f, "Duplicated property '{}' for object map literal", s) diff --git a/src/fn_call.rs b/src/fn_call.rs index 861f490b..ca1aef51 100644 --- a/src/fn_call.rs +++ b/src/fn_call.rs @@ -19,8 +19,9 @@ use crate::utils::StaticVec; #[cfg(not(feature = "no_function"))] use crate::{ - parser::ScriptFnDef, r#unsafe::unsafe_cast_var_name_to_lifetime, - scope::EntryType as ScopeEntryType, + parser::ScriptFnDef, + r#unsafe::unsafe_cast_var_name_to_lifetime, + scope::{Entry as ScopeEntry, EntryType as ScopeEntryType}, }; #[cfg(not(feature = "no_float"))] @@ -105,8 +106,32 @@ fn restore_first_arg<'a>(old_this_ptr: Option<&'a mut Dynamic>, args: &mut FnCal } } +// Add captured variables into scope +#[cfg(not(feature = "no_capture"))] +fn add_captured_variables_into_scope<'s>( + externals: &[String], + captured: &'s Scope<'s>, + scope: &mut Scope<'s>, +) { + externals + .iter() + .map(|var_name| captured.get_entry(var_name)) + .filter(Option::is_some) + .map(Option::unwrap) + .for_each( + |ScopeEntry { + name, typ, value, .. + }| { + match typ { + ScopeEntryType::Normal => scope.push(name.clone(), value.clone()), + ScopeEntryType::Constant => scope.push_constant(name.clone(), value.clone()), + }; + }, + ); +} + impl Engine { - /// Universal method for calling functions either registered with the `Engine` or written in Rhai. + /// Call a native Rust function registered with the `Engine`. /// Position in `EvalAltResult` is `None` and must be set afterwards. /// /// ## WARNING @@ -121,7 +146,7 @@ impl Engine { state: &mut State, lib: &Module, fn_name: &str, - (hash_fn, hash_script): (u64, u64), + hash_fn: u64, args: &mut FnCallArgs, is_ref: bool, _is_method: bool, @@ -131,8 +156,6 @@ impl Engine { ) -> Result<(Dynamic, bool), Box> { self.inc_operations(state)?; - let native_only = hash_script == 0; - // Check for stack overflow #[cfg(not(feature = "no_function"))] #[cfg(not(feature = "unchecked"))] @@ -142,23 +165,13 @@ impl Engine { )); } - let mut this_copy: Dynamic = Default::default(); - let mut old_this_ptr: Option<&mut Dynamic> = None; - // Search for the function - // First search in script-defined functions (can override built-in) - // Then search registered native functions (can override packages) + // First search registered native functions (can override packages) // Then search packages - // NOTE: We skip script functions for global_module and packages, and native functions for lib - let func = if !native_only { - lib.get_fn(hash_script, pub_only) //.or_else(|| lib.get_fn(hash_fn, pub_only)) - } else { - None - } - //.or_else(|| self.global_module.get_fn(hash_script, pub_only)) - .or_else(|| self.global_module.get_fn(hash_fn, pub_only)) - //.or_else(|| self.packages.get_fn(hash_script, pub_only)) - .or_else(|| self.packages.get_fn(hash_fn, pub_only)); + let func = self + .global_module + .get_fn(hash_fn, pub_only) + .or_else(|| self.packages.get_fn(hash_fn, pub_only)); if let Some(func) = func { #[cfg(not(feature = "no_function"))] @@ -166,43 +179,12 @@ impl Engine { #[cfg(feature = "no_function")] let need_normalize = is_ref && func.is_pure(); + let mut this_copy: Dynamic = Default::default(); + let mut old_this_ptr: Option<&mut Dynamic> = None; + // Calling pure function but the first argument is a reference? normalize_first_arg(need_normalize, &mut this_copy, &mut old_this_ptr, args); - #[cfg(not(feature = "no_function"))] - if func.is_script() { - // Run scripted function - let fn_def = func.get_fn_def(); - - // Method call of script function - map first argument to `this` - return if _is_method { - let (first, rest) = args.split_at_mut(1); - Ok(( - self.call_script_fn( - _scope, - _mods, - state, - lib, - &mut Some(first[0]), - fn_name, - fn_def, - rest, - _level, - )?, - false, - )) - } else { - let result = self.call_script_fn( - _scope, _mods, state, lib, &mut None, fn_name, fn_def, args, _level, - )?; - - // Restore the original reference - restore_first_arg(old_this_ptr, args); - - Ok((result, false)) - }; - } - // Run external function let result = func.get_native_fn()(self, lib, args)?; @@ -414,24 +396,23 @@ impl Engine { state: &mut State, lib: &Module, fn_name: &str, - native_only: bool, hash_script: u64, args: &mut FnCallArgs, is_ref: bool, is_method: bool, pub_only: bool, + capture: Option, def_val: Option, level: usize, ) -> Result<(Dynamic, bool), Box> { // Qualifiers (none) + function name + number of arguments + argument `TypeId`'s. let arg_types = args.iter().map(|a| a.type_id()); let hash_fn = calc_fn_hash(empty(), fn_name, args.len(), arg_types); - let hashes = (hash_fn, if native_only { 0 } else { hash_script }); match fn_name { // type_of KEYWORD_TYPE_OF - if args.len() == 1 && !self.has_override(lib, hashes.0, hashes.1, pub_only) => + if args.len() == 1 && !self.has_override(lib, hash_fn, hash_script, pub_only) => { Ok(( self.map_type_name(args[0].type_name()).to_string().into(), @@ -441,7 +422,7 @@ impl Engine { // Fn KEYWORD_FN_PTR - if args.len() == 1 && !self.has_override(lib, hashes.0, hashes.1, pub_only) => + if args.len() == 1 && !self.has_override(lib, hash_fn, hash_script, pub_only) => { Err(Box::new(EvalAltResult::ErrorRuntime( "'Fn' should not be called in method style. Try Fn(...);".into(), @@ -451,7 +432,7 @@ impl Engine { // eval - reaching this point it must be a method-style call KEYWORD_EVAL - if args.len() == 1 && !self.has_override(lib, hashes.0, hashes.1, pub_only) => + if args.len() == 1 && !self.has_override(lib, hash_fn, hash_script, pub_only) => { Err(Box::new(EvalAltResult::ErrorRuntime( "'eval' should not be called in method style. Try eval(...);".into(), @@ -459,12 +440,61 @@ impl Engine { ))) } - // Normal function call + // Normal script function call + #[cfg(not(feature = "no_function"))] + _ if hash_script > 0 && lib.contains_fn(hash_script, pub_only) => { + // Get scripted function + let func = lib.get_fn(hash_script, pub_only).unwrap().get_fn_def(); + + let scope = &mut Scope::new(); + let mods = &mut Imports::new(); + + // Add captured variables into scope + #[cfg(not(feature = "no_capture"))] + if let Some(captured) = &capture { + add_captured_variables_into_scope(&func.externals, captured, scope); + } + + let result = if is_method { + // Method call of script function - map first argument to `this` + let (first, rest) = args.split_at_mut(1); + self.call_script_fn( + scope, + mods, + state, + lib, + &mut Some(first[0]), + fn_name, + func, + rest, + level, + )? + } else { + // Normal call of script function - map first argument to `this` + let mut first_copy: Dynamic = Default::default(); + let mut old_first: Option<&mut Dynamic> = None; + + // The first argument is a reference? + normalize_first_arg(is_ref, &mut first_copy, &mut old_first, args); + + let result = self.call_script_fn( + scope, mods, state, lib, &mut None, fn_name, func, args, level, + )?; + + // Restore the original reference + restore_first_arg(old_first, args); + + result + }; + + Ok((result, false)) + } + // Normal native function call _ => { let mut scope = Scope::new(); let mut mods = Imports::new(); self.call_fn_raw( - &mut scope, &mut mods, state, lib, fn_name, hashes, args, is_ref, is_method, + &mut scope, &mut mods, state, lib, fn_name, hash_fn, args, is_ref, is_method, pub_only, def_val, level, ) } @@ -522,7 +552,7 @@ impl Engine { state: &mut State, lib: &Module, name: &str, - hash: u64, + hash_script: u64, target: &mut Target, idx_val: Dynamic, def_val: Option, @@ -545,7 +575,11 @@ impl Engine { // Redirect function name let fn_name = fn_ptr.fn_name(); // Recalculate hash - let hash = calc_fn_hash(empty(), fn_name, curry.len() + idx.len(), empty()); + let hash = if native { + 0 + } else { + calc_fn_hash(empty(), fn_name, curry.len() + idx.len(), empty()) + }; // Arguments are passed as-is, adding the curried arguments let mut arg_values = curry .iter_mut() @@ -555,7 +589,7 @@ impl Engine { // Map it to name(args) in function-call style self.exec_fn_call( - state, lib, fn_name, native, hash, args, false, false, pub_only, def_val, level, + state, lib, fn_name, hash, args, false, false, pub_only, None, def_val, level, ) } else if _fn_name == KEYWORD_FN_PTR_CALL && idx.len() > 0 && idx[0].is::() { // FnPtr call on object @@ -564,7 +598,11 @@ impl Engine { // Redirect function name let fn_name = fn_ptr.get_fn_name().clone(); // Recalculate hash - let hash = calc_fn_hash(empty(), &fn_name, curry.len() + idx.len(), empty()); + let hash = if native { + 0 + } else { + calc_fn_hash(empty(), &fn_name, curry.len() + idx.len(), empty()) + }; // Replace the first argument with the object pointer, adding the curried arguments let mut arg_values = once(obj) .chain(curry.iter_mut()) @@ -574,7 +612,7 @@ impl Engine { // Map it to name(args) in function-call style self.exec_fn_call( - state, lib, &fn_name, native, hash, args, is_ref, true, pub_only, def_val, level, + state, lib, &fn_name, hash, args, is_ref, true, pub_only, None, def_val, level, ) } else if _fn_name == KEYWORD_FN_PTR_CURRY && obj.is::() { // Curry call @@ -595,7 +633,7 @@ impl Engine { } else { #[cfg(not(feature = "no_object"))] let redirected; - let mut _hash = hash; + let mut _hash = hash_script; // Check if it is a map method call in OOP style #[cfg(not(feature = "no_object"))] @@ -611,12 +649,16 @@ impl Engine { } }; + if native { + _hash = 0; + } + // Attached object pointer in front of the arguments let mut arg_values = once(obj).chain(idx.iter_mut()).collect::>(); let args = arg_values.as_mut(); self.exec_fn_call( - state, lib, _fn_name, native, _hash, args, is_ref, true, pub_only, def_val, level, + state, lib, _fn_name, _hash, args, is_ref, true, pub_only, None, def_val, level, ) }?; @@ -641,16 +683,17 @@ impl Engine { name: &str, args_expr: &[Expr], def_val: Option, - mut hash: u64, + mut hash_script: u64, native: bool, pub_only: bool, + capture: bool, level: usize, ) -> Result> { // Handle Fn() if name == KEYWORD_FN_PTR && args_expr.len() == 1 { let hash_fn = calc_fn_hash(empty(), name, 1, once(TypeId::of::())); - if !self.has_override(lib, hash_fn, hash, pub_only) { + if !self.has_override(lib, hash_fn, hash_script, pub_only) { // Fn - only in function call style let expr = args_expr.get(0).unwrap(); let arg_value = self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)?; @@ -698,11 +741,43 @@ impl Engine { .into()); } + // Handle call() - Redirect function call + let redirected; + let mut args_expr = args_expr.as_ref(); + let mut curry: StaticVec<_> = Default::default(); + let mut name = name; + + if name == KEYWORD_FN_PTR_CALL + && args_expr.len() >= 1 + && !self.has_override(lib, 0, hash_script, pub_only) + { + let expr = args_expr.get(0).unwrap(); + let fn_name = self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)?; + + if fn_name.is::() { + let fn_ptr = fn_name.cast::(); + curry = fn_ptr.curry().iter().cloned().collect(); + // Redirect function name + redirected = fn_ptr.take_data().0; + name = &redirected; + // Skip the first argument + args_expr = &args_expr.as_ref()[1..]; + // Recalculate hash + hash_script = calc_fn_hash(empty(), name, curry.len() + args_expr.len(), empty()); + } else { + return Err(Box::new(EvalAltResult::ErrorMismatchOutputType( + self.map_type_name(type_name::()).into(), + fn_name.type_name().into(), + expr.position(), + ))); + } + } + // Handle eval() if name == KEYWORD_EVAL && args_expr.len() == 1 { let hash_fn = calc_fn_hash(empty(), name, 1, once(TypeId::of::())); - if !self.has_override(lib, hash_fn, hash, pub_only) { + if !self.has_override(lib, hash_fn, hash_script, pub_only) { // eval - only in function call style let prev_len = scope.len(); let expr = args_expr.get(0).unwrap(); @@ -721,42 +796,15 @@ impl Engine { } } - // Handle call() - Redirect function call - let redirected; - let mut args_expr = args_expr.as_ref(); - let mut curry: StaticVec<_> = Default::default(); - let mut name = name; - - if name == KEYWORD_FN_PTR_CALL - && args_expr.len() >= 1 - && !self.has_override(lib, 0, hash, pub_only) - { - let expr = args_expr.get(0).unwrap(); - let fn_name = self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)?; - - if fn_name.is::() { - let fn_ptr = fn_name.cast::(); - curry = fn_ptr.curry().iter().cloned().collect(); - // Redirect function name - redirected = fn_ptr.take_data().0; - name = &redirected; - // Skip the first argument - args_expr = &args_expr.as_ref()[1..]; - // Recalculate hash - hash = calc_fn_hash(empty(), name, curry.len() + args_expr.len(), empty()); - } else { - return Err(Box::new(EvalAltResult::ErrorMismatchOutputType( - self.map_type_name(type_name::()).into(), - fn_name.type_name().into(), - expr.position(), - ))); - } - } - - // Normal function call - except for Fn and eval (handled above) + // Normal function call - except for Fn, curry, call and eval (handled above) let mut arg_values: StaticVec<_>; let mut args: StaticVec<_>; let mut is_ref = false; + let capture = if capture && !scope.is_empty() { + Some(scope.clone()) + } else { + None + }; if args_expr.is_empty() && curry.is_empty() { // No arguments @@ -797,9 +845,11 @@ impl Engine { } } + let hash = if native { 0 } else { hash_script }; let args = args.as_mut(); + self.exec_fn_call( - state, lib, name, native, hash, args, is_ref, false, pub_only, def_val, level, + state, lib, name, hash, args, is_ref, false, pub_only, capture, def_val, level, ) .map(|(v, _)| v) } @@ -818,10 +868,18 @@ impl Engine { args_expr: &[Expr], def_val: Option, hash_script: u64, + capture: bool, level: usize, ) -> Result> { let modules = modules.as_ref().unwrap(); + #[cfg(not(feature = "no_capture"))] + let capture = if capture && !scope.is_empty() { + Some(scope.clone()) + } else { + None + }; + let mut arg_values: StaticVec<_>; let mut args: StaticVec<_>; @@ -887,12 +945,18 @@ impl Engine { #[cfg(not(feature = "no_function"))] Some(f) if f.is_script() => { let args = args.as_mut(); - let fn_def = f.get_fn_def(); - let mut scope = Scope::new(); - let mut mods = Imports::new(); - self.call_script_fn( - &mut scope, &mut mods, state, lib, &mut None, name, fn_def, args, level, - ) + let func = f.get_fn_def(); + + let scope = &mut Scope::new(); + let mods = &mut Imports::new(); + + // Add captured variables into scope + #[cfg(not(feature = "no_capture"))] + if let Some(captured) = &capture { + add_captured_variables_into_scope(&func.externals, captured, scope); + } + + self.call_script_fn(scope, mods, state, lib, &mut None, name, func, args, level) } Some(f) => f.get_native_fn()(self, lib, args.as_mut()), None if def_val.is_some() => Ok(def_val.unwrap().into()), diff --git a/src/fn_native.rs b/src/fn_native.rs index 5751351c..f6bb962e 100644 --- a/src/fn_native.rs +++ b/src/fn_native.rs @@ -130,13 +130,13 @@ impl FnPtr { &mut Default::default(), lib.as_ref(), fn_name, - false, hash_script, args.as_mut(), has_this, has_this, true, None, + None, 0, ) .map(|(v, _)| v) diff --git a/src/optimize.rs b/src/optimize.rs index e26d1908..7c71aced 100644 --- a/src/optimize.rs +++ b/src/optimize.rs @@ -138,7 +138,7 @@ fn call_fn_with_constant_arguments( &mut Default::default(), state.lib, fn_name, - (hash_fn, 0), + hash_fn, arg_values.iter_mut().collect::>().as_mut(), false, false, @@ -576,7 +576,7 @@ fn optimize_expr(expr: Expr, state: &mut State) -> Expr { && state.optimization_level == OptimizationLevel::Full // full optimizations && x.3.iter().all(|expr| expr.is_constant()) // all arguments are constants => { - let ((name, _, pos), _, _, args, def_value) = x.as_mut(); + let ((name, _, _, pos), _, _, args, def_value) = x.as_mut(); // First search in functions lib (can override built-in) // Cater for both normal function call style and method call style (one additional arguments) diff --git a/src/parser.rs b/src/parser.rs index abe3c7d8..c8f4e314 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -748,12 +748,12 @@ pub enum Expr { Stmt(Box<(Stmt, Position)>), /// Wrapped expression - should not be optimized away. Expr(Box), - /// func(expr, ... ) - ((function name, native_only, position), optional modules, hash, arguments, optional default value) + /// func(expr, ... ) - ((function name, native_only, capture, position), optional modules, hash, arguments, optional default value) /// Use `Cow<'static, str>` because a lot of operators (e.g. `==`, `>=`) are implemented as function calls /// and the function names are predictable, so no need to allocate a new `String`. FnCall( Box<( - (Cow<'static, str>, bool, Position), + (Cow<'static, str>, bool, bool, Position), Option>, u64, StaticVec, @@ -871,7 +871,7 @@ impl Expr { Self::Property(x) => x.1, Self::Stmt(x) => x.1, Self::Variable(x) => (x.0).1, - Self::FnCall(x) => (x.0).2, + Self::FnCall(x) => (x.0).3, Self::Assignment(x) => x.0.position(), Self::And(x) | Self::Or(x) | Self::In(x) => x.2, @@ -903,7 +903,7 @@ impl Expr { Self::Variable(x) => (x.0).1 = new_pos, Self::Property(x) => x.1 = new_pos, Self::Stmt(x) => x.1 = new_pos, - Self::FnCall(x) => (x.0).2 = new_pos, + Self::FnCall(x) => (x.0).3 = new_pos, Self::And(x) => x.2 = new_pos, Self::Or(x) => x.2 = new_pos, Self::In(x) => x.2 = new_pos, @@ -1009,6 +1009,7 @@ impl Expr { #[cfg(not(feature = "no_index"))] Token::LeftBracket => true, Token::LeftParen => true, + Token::Bang => true, Token::DoubleColon => true, _ => false, }, @@ -1101,6 +1102,7 @@ fn parse_fn_call( state: &mut ParseState, lib: &mut FunctionsLib, id: String, + capture: bool, mut modules: Option>, settings: ParseSettings, ) -> Result { @@ -1143,7 +1145,7 @@ fn parse_fn_call( }; return Ok(Expr::FnCall(Box::new(( - (id.into(), false, settings.pos), + (id.into(), false, capture, settings.pos), modules, hash_script, args, @@ -1185,7 +1187,7 @@ fn parse_fn_call( }; return Ok(Expr::FnCall(Box::new(( - (id.into(), false, settings.pos), + (id.into(), false, capture, settings.pos), modules, hash_script, args, @@ -1594,6 +1596,8 @@ fn parse_primary( _ => input.next().unwrap(), }; + let (next_token, _) = input.peek().unwrap(); + let mut root_expr = match token { Token::IntegerConstant(x) => Expr::IntegerConstant(Box::new((x, settings.pos))), #[cfg(not(feature = "no_float"))] @@ -1605,7 +1609,7 @@ fn parse_primary( Expr::Variable(Box::new(((s, settings.pos), None, 0, index))) } // Function call is allowed to have reserved keyword - Token::Reserved(s) if input.peek().unwrap().0 == Token::LeftParen => { + Token::Reserved(s) if *next_token == Token::LeftParen || *next_token == Token::Bang => { if is_keyword_function(&s) { Expr::Variable(Box::new(((s, settings.pos), None, 0, None))) } else { @@ -1613,7 +1617,7 @@ fn parse_primary( } } // Access to `this` as a variable is OK - Token::Reserved(s) if s == KEYWORD_THIS && input.peek().unwrap().0 != Token::LeftParen => { + Token::Reserved(s) if s == KEYWORD_THIS && *next_token != Token::LeftParen => { if !settings.is_function_scope { return Err( PERR::BadInput(format!("'{}' can only be used in functions", s)) @@ -1653,11 +1657,26 @@ fn parse_primary( settings.pos = token_pos; root_expr = match (root_expr, token) { + // Function call + #[cfg(not(feature = "no_capture"))] + (Expr::Variable(x), Token::Bang) => { + if !match_token(input, Token::LeftParen)? { + return Err(PERR::MissingToken( + Token::LeftParen.syntax().into(), + "to start arguments list of function call".into(), + ) + .into_err(input.peek().unwrap().1)); + } + + let ((name, pos), modules, _, _) = *x; + settings.pos = pos; + parse_fn_call(input, state, lib, name, true, modules, settings.level_up())? + } // Function call (Expr::Variable(x), Token::LeftParen) => { let ((name, pos), modules, _, _) = *x; settings.pos = pos; - parse_fn_call(input, state, lib, name, modules, settings.level_up())? + parse_fn_call(input, state, lib, name, false, modules, settings.level_up())? } (Expr::Property(_), _) => unreachable!(), // module access @@ -1767,7 +1786,7 @@ fn parse_unary( args.push(expr); Ok(Expr::FnCall(Box::new(( - (op.into(), true, pos), + (op.into(), true, false, pos), None, hash, args, @@ -1792,7 +1811,7 @@ fn parse_unary( let hash = calc_fn_hash(empty(), op, 2, empty()); Ok(Expr::FnCall(Box::new(( - (op.into(), true, pos), + (op.into(), true, false, pos), None, hash, args, @@ -1987,7 +2006,14 @@ fn make_dot_expr(lhs: Expr, rhs: Expr, op_pos: Position) -> Result { + return Err(PERR::MalformedCapture( + "method-call style does not support capturing".into(), + ) + .into_err((x.0).3)) + } + // lhs.func(...) (lhs, func @ Expr::FnCall(_)) => Expr::Dot(Box::new((lhs, func, op_pos))), // lhs.rhs (_, rhs) => return Err(PERR::PropertyExpected.into_err(rhs.position())), @@ -2196,7 +2222,7 @@ fn parse_binary_op( let cmp_def = Some(false); let op = op_token.syntax(); let hash = calc_fn_hash(empty(), &op, 2, empty()); - let op = (op, true, pos); + let op = (op, true, false, pos); let mut args = StaticVec::new(); args.push(root); @@ -2257,7 +2283,7 @@ fn parse_binary_op( .unwrap_or(false) => { // Accept non-native functions for custom operators - let op = (op.0, false, op.2); + let op = (op.0, false, op.2, op.3); Expr::FnCall(Box::new((op, None, hash, args, None))) } @@ -2975,10 +3001,12 @@ fn parse_fn( let (token, pos) = input.next().unwrap(); - let name = token.into_function_name().map_err(|t| match t { - Token::Reserved(s) => PERR::Reserved(s).into_err(pos), - _ => PERR::FnMissingName.into_err(pos), - })?; + let name = token + .into_function_name_for_override() + .map_err(|t| match t { + Token::Reserved(s) => PERR::Reserved(s).into_err(pos), + _ => PERR::FnMissingName.into_err(pos), + })?; match input.peek().unwrap() { (Token::LeftParen, _) => eat_token(input, Token::LeftParen), @@ -3085,7 +3113,7 @@ fn make_curry_from_externals( let hash = calc_fn_hash(empty(), KEYWORD_FN_PTR_CURRY, num_externals, empty()); let fn_call = Expr::FnCall(Box::new(( - (KEYWORD_FN_PTR_CURRY.into(), false, pos), + (KEYWORD_FN_PTR_CURRY.into(), false, false, pos), None, hash, args, diff --git a/src/scope.rs b/src/scope.rs index 5693554c..3e43b850 100644 --- a/src/scope.rs +++ b/src/scope.rs @@ -316,6 +316,14 @@ impl<'a> Scope<'a> { }) } + /// Get an entry in the Scope, starting from the last. + pub(crate) fn get_entry(&self, name: &str) -> Option<&Entry> { + self.0 + .iter() + .rev() + .find(|Entry { name: key, .. }| name == key) + } + /// Get the value of an entry in the Scope, starting from the last. /// /// # Examples @@ -329,10 +337,7 @@ impl<'a> Scope<'a> { /// assert_eq!(my_scope.get_value::("x").unwrap(), 42); /// ``` pub fn get_value(&self, name: &str) -> Option { - self.0 - .iter() - .rev() - .find(|Entry { name: key, .. }| name == key) + self.get_entry(name) .and_then(|Entry { value, .. }| value.downcast_ref::().cloned()) } diff --git a/src/token.rs b/src/token.rs index 7d1a07ba..a35ff4f6 100644 --- a/src/token.rs +++ b/src/token.rs @@ -678,9 +678,9 @@ impl Token { } /// Convert a token into a function name, if possible. - pub(crate) fn into_function_name(self) -> Result { + pub(crate) fn into_function_name_for_override(self) -> Result { match self { - Self::Reserved(s) if is_keyword_function(&s) => Ok(s), + Self::Reserved(s) if can_override_keyword(&s) => Ok(s), Self::Custom(s) | Self::Identifier(s) if is_valid_identifier(s.chars()) => Ok(s), _ => Err(self), } @@ -1439,6 +1439,16 @@ pub fn is_keyword_function(name: &str) -> bool { || name == KEYWORD_FN_PTR_CURRY } +/// Can this keyword be overridden as a function? +#[inline(always)] +pub fn can_override_keyword(name: &str) -> bool { + name == KEYWORD_PRINT + || name == KEYWORD_DEBUG + || name == KEYWORD_TYPE_OF + || name == KEYWORD_EVAL + || name == KEYWORD_FN_PTR +} + pub fn is_valid_identifier(name: impl Iterator) -> bool { let mut first_alphabetic = false; diff --git a/tests/functions.rs b/tests/functions.rs index 78d20fa3..9336cb6d 100644 --- a/tests/functions.rs +++ b/tests/functions.rs @@ -1,5 +1,5 @@ #![cfg(not(feature = "no_function"))] -use rhai::{Engine, EvalAltResult, INT}; +use rhai::{Engine, EvalAltResult, ParseError, ParseErrorType, INT}; #[test] fn test_functions() -> Result<(), Box> { @@ -120,3 +120,53 @@ fn test_function_pointers() -> Result<(), Box> { Ok(()) } + +#[test] +#[cfg(not(feature = "no_capture"))] +fn test_function_captures() -> Result<(), Box> { + let engine = Engine::new(); + + assert_eq!( + engine.eval::( + r#" + fn foo(y) { x += y; x } + + let x = 41; + let y = 999; + + foo!(1) + x + "# + )?, + 83 + ); + + assert!(engine + .eval::( + r#" + fn foo(y) { x += y; x } + + let x = 41; + let y = 999; + + foo(1) + x + "# + ) + .is_err()); + + #[cfg(not(feature = "no_object"))] + assert!(matches!( + engine.compile( + r#" + fn foo() { this += x; } + + let x = 41; + let y = 999; + + y.foo!(); + "# + ).expect_err("should error"), + ParseError(err, _) if matches!(*err, ParseErrorType::MalformedCapture(_)) + )); + + Ok(()) +} From 7d4620d0d9cf4f8ff1349dc08bd5b476121f80c8 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Thu, 30 Jul 2020 23:29:11 +0800 Subject: [PATCH 04/40] Unbounded -> unbound. --- doc/src/language/functions.md | 2 +- src/engine.rs | 4 ++-- src/result.rs | 12 ++++++------ tests/fn_ptr.rs | 2 +- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/doc/src/language/functions.md b/doc/src/language/functions.md index d8f67502..3bd15cd0 100644 --- a/doc/src/language/functions.md +++ b/doc/src/language/functions.md @@ -127,5 +127,5 @@ x.change(); // call 'change' in method-call style, 'this' binds to 'x' x == 42; // 'x' is changed! -change(); // <- error: `this` is unbounded +change(); // <- error: `this` is unbound ``` diff --git a/src/engine.rs b/src/engine.rs index ac264af4..6aa1f9c2 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -556,7 +556,7 @@ pub fn search_scope_only<'s, 'a>( if let Some(val) = this_ptr { return Ok(((*val).into(), KEYWORD_THIS, ScopeEntryType::Normal, *pos)); } else { - return Err(Box::new(EvalAltResult::ErrorUnboundedThis(*pos))); + return Err(Box::new(EvalAltResult::ErrorUnboundThis(*pos))); } } @@ -1247,7 +1247,7 @@ impl Engine { if let Some(val) = this_ptr { Ok(val.clone()) } else { - Err(Box::new(EvalAltResult::ErrorUnboundedThis((x.0).1))) + Err(Box::new(EvalAltResult::ErrorUnboundThis((x.0).1))) } } Expr::Variable(_) => { diff --git a/src/result.rs b/src/result.rs index 550dcc37..c0cc620d 100644 --- a/src/result.rs +++ b/src/result.rs @@ -39,8 +39,8 @@ pub enum EvalAltResult { /// An error has occurred inside a called function. /// Wrapped values are the name of the function and the interior error. ErrorInFunctionCall(String, Box, Position), - /// Access to `this` that is not bounded. - ErrorUnboundedThis(Position), + /// Access to `this` that is not bound. + ErrorUnboundThis(Position), /// Non-boolean operand encountered for boolean operator. Wrapped value is the operator. ErrorBooleanArgMismatch(String, Position), /// Non-character value encountered where a character is required. @@ -112,7 +112,7 @@ impl EvalAltResult { Self::ErrorParsing(p, _) => p.desc(), Self::ErrorInFunctionCall(_, _, _) => "Error in called function", Self::ErrorFunctionNotFound(_, _) => "Function not found", - Self::ErrorUnboundedThis(_) => "'this' is not bounded", + Self::ErrorUnboundThis(_) => "'this' is not bound", Self::ErrorBooleanArgMismatch(_, _) => "Boolean operator expects boolean operands", Self::ErrorCharMismatch(_) => "Character expected", Self::ErrorNumericIndexExpr(_) => { @@ -187,7 +187,7 @@ impl fmt::Display for EvalAltResult { Self::ErrorIndexingType(_, _) | Self::ErrorNumericIndexExpr(_) | Self::ErrorStringIndexExpr(_) - | Self::ErrorUnboundedThis(_) + | Self::ErrorUnboundThis(_) | Self::ErrorImportExpr(_) | Self::ErrorLogicGuard(_) | Self::ErrorFor(_) @@ -276,7 +276,7 @@ impl EvalAltResult { Self::ErrorParsing(_, pos) | Self::ErrorFunctionNotFound(_, pos) | Self::ErrorInFunctionCall(_, _, pos) - | Self::ErrorUnboundedThis(pos) + | Self::ErrorUnboundThis(pos) | Self::ErrorBooleanArgMismatch(_, pos) | Self::ErrorCharMismatch(pos) | Self::ErrorArrayBounds(_, _, pos) @@ -316,7 +316,7 @@ impl EvalAltResult { Self::ErrorParsing(_, pos) | Self::ErrorFunctionNotFound(_, pos) | Self::ErrorInFunctionCall(_, _, pos) - | Self::ErrorUnboundedThis(pos) + | Self::ErrorUnboundThis(pos) | Self::ErrorBooleanArgMismatch(_, pos) | Self::ErrorCharMismatch(pos) | Self::ErrorArrayBounds(_, _, pos) diff --git a/tests/fn_ptr.rs b/tests/fn_ptr.rs index 833bb20c..c6658f17 100644 --- a/tests/fn_ptr.rs +++ b/tests/fn_ptr.rs @@ -73,7 +73,7 @@ fn test_fn_ptr() -> Result<(), Box> { "# ) .expect_err("should error"), - EvalAltResult::ErrorInFunctionCall(fn_name, err, _) if fn_name == "foo" && matches!(*err, EvalAltResult::ErrorUnboundedThis(_)) + EvalAltResult::ErrorInFunctionCall(fn_name, err, _) if fn_name == "foo" && matches!(*err, EvalAltResult::ErrorUnboundThis(_)) )); Ok(()) From a7ff20763659e055568e54d70a8a9ca4440c936d Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Thu, 30 Jul 2020 23:29:30 +0800 Subject: [PATCH 05/40] Use Scope::flatten_clone for capturing. --- src/fn_call.rs | 25 ++++++++++++------------- src/parser.rs | 6 +++--- src/scope.rs | 21 +++++++++++++++++++-- 3 files changed, 34 insertions(+), 18 deletions(-) diff --git a/src/fn_call.rs b/src/fn_call.rs index ca1aef51..6672d746 100644 --- a/src/fn_call.rs +++ b/src/fn_call.rs @@ -36,6 +36,7 @@ use crate::engine::{Map, Target, FN_GET, FN_SET}; use crate::stdlib::{ any::{type_name, TypeId}, boxed::Box, + collections::HashSet, convert::TryFrom, format, iter::{empty, once}, @@ -109,22 +110,20 @@ fn restore_first_arg<'a>(old_this_ptr: Option<&'a mut Dynamic>, args: &mut FnCal // Add captured variables into scope #[cfg(not(feature = "no_capture"))] fn add_captured_variables_into_scope<'s>( - externals: &[String], - captured: &'s Scope<'s>, + externals: &HashSet, + captured: Scope<'s>, scope: &mut Scope<'s>, ) { - externals - .iter() - .map(|var_name| captured.get_entry(var_name)) - .filter(Option::is_some) - .map(Option::unwrap) + captured + .into_iter() + .filter(|ScopeEntry { name, .. }| externals.contains(name.as_ref())) .for_each( |ScopeEntry { name, typ, value, .. }| { match typ { - ScopeEntryType::Normal => scope.push(name.clone(), value.clone()), - ScopeEntryType::Constant => scope.push_constant(name.clone(), value.clone()), + ScopeEntryType::Normal => scope.push(name, value), + ScopeEntryType::Constant => scope.push_constant(name, value), }; }, ); @@ -451,7 +450,7 @@ impl Engine { // Add captured variables into scope #[cfg(not(feature = "no_capture"))] - if let Some(captured) = &capture { + if let Some(captured) = capture { add_captured_variables_into_scope(&func.externals, captured, scope); } @@ -801,7 +800,7 @@ impl Engine { let mut args: StaticVec<_>; let mut is_ref = false; let capture = if capture && !scope.is_empty() { - Some(scope.clone()) + Some(scope.flatten_clone()) } else { None }; @@ -875,7 +874,7 @@ impl Engine { #[cfg(not(feature = "no_capture"))] let capture = if capture && !scope.is_empty() { - Some(scope.clone()) + Some(scope.flatten_clone()) } else { None }; @@ -952,7 +951,7 @@ impl Engine { // Add captured variables into scope #[cfg(not(feature = "no_capture"))] - if let Some(captured) = &capture { + if let Some(captured) = capture { add_captured_variables_into_scope(&func.externals, captured, scope); } diff --git a/src/parser.rs b/src/parser.rs index c8f4e314..3fd6365b 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -25,7 +25,7 @@ use crate::stdlib::{ borrow::Cow, boxed::Box, char, - collections::HashMap, + collections::{HashMap, HashSet}, fmt, format, hash::{Hash, Hasher}, iter::empty, @@ -355,7 +355,7 @@ impl fmt::Display for FnAccess { /// ## WARNING /// /// This type is volatile and may change. -#[derive(Debug, Clone, Hash)] +#[derive(Debug, Clone)] pub struct ScriptFnDef { /// Function name. pub name: ImmutableString, @@ -365,7 +365,7 @@ pub struct ScriptFnDef { pub params: StaticVec, /// Access to external variables. #[cfg(not(feature = "no_capture"))] - pub externals: StaticVec, + pub externals: HashSet, /// Function body. pub body: Stmt, /// Position of the function definition. diff --git a/src/scope.rs b/src/scope.rs index 3e43b850..b2024564 100644 --- a/src/scope.rs +++ b/src/scope.rs @@ -4,7 +4,9 @@ use crate::any::{Dynamic, Variant}; use crate::parser::{map_dynamic_to_expr, Expr}; use crate::token::Position; -use crate::stdlib::{borrow::Cow, boxed::Box, iter, string::String, vec::Vec}; +use crate::stdlib::{ + borrow::Cow, boxed::Box, collections::HashMap, iter, string::String, vec::Vec, +}; /// Type of an entry in the Scope. #[derive(Debug, Eq, PartialEq, Hash, Copy, Clone)] @@ -389,13 +391,28 @@ impl<'a> Scope<'a> { self } + /// Clone the Scope, keeping only the last instances of each variable name. + /// Shadowed variables are omitted in the copy. + #[cfg(not(feature = "no_capture"))] + pub(crate) fn flatten_clone(&self) -> Self { + let mut entries: HashMap<&str, Entry> = Default::default(); + + self.0.iter().rev().for_each(|entry| { + entries + .entry(entry.name.as_ref()) + .or_insert_with(|| entry.clone()); + }); + + Self(entries.into_iter().map(|(_, v)| v).collect()) + } + /// Get an iterator to entries in the Scope. #[cfg(not(feature = "no_module"))] pub(crate) fn into_iter(self) -> impl Iterator> { self.0.into_iter() } - /// Get an iterator to entries in the Scope. + /// Get an iterator to entries in the Scope in reverse order. pub(crate) fn to_iter(&self) -> impl Iterator { self.0.iter().rev() // Always search a Scope in reverse order } From e5fe222de3a61146ae34c4b85b6be3c91cbcf717 Mon Sep 17 00:00:00 2001 From: Ilya Lakhin Date: Mon, 27 Jul 2020 11:30:09 +0700 Subject: [PATCH 06/40] Shared variant of Dynamic type; All read/write access operations in Dynamic backed by Read/Write lock guards; new shared() script function --- Cargo.toml | 1 + src/any.rs | 287 ++++++++++++++++++++++++++++++++++-- src/engine.rs | 39 ++++- src/fn_call.rs | 36 +++-- src/fn_native.rs | 13 +- src/fn_register.rs | 46 +++--- src/module.rs | 20 +-- src/packages/array_basic.rs | 4 +- src/packages/string_more.rs | 4 +- src/scope.rs | 2 +- src/token.rs | 4 +- 11 files changed, 383 insertions(+), 73 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index ec00a10c..85b57974 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -34,6 +34,7 @@ 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 explicit shared variables in the script code no_module = [] # no modules internals = [] # expose internal data structures unicode-xid-ident = ["unicode-xid"] # allow Unicode Standard Annex #31 for identifiers. diff --git a/src/any.rs b/src/any.rs index 9d28c768..a7983030 100644 --- a/src/any.rs +++ b/src/any.rs @@ -1,6 +1,6 @@ //! Helper module which defines the `Any` trait to to allow dynamic value handling. -use crate::fn_native::{FnPtr, SendSync}; +use crate::fn_native::{FnPtr, SendSync, SharedMut}; use crate::parser::{ImmutableString, INT}; use crate::r#unsafe::{unsafe_cast_box, unsafe_try_cast}; @@ -18,8 +18,15 @@ use crate::stdlib::{ boxed::Box, fmt, string::String, + ops::{Deref, DerefMut} }; +#[cfg(not(feature = "sync"))] +use crate::stdlib::{rc::Rc, cell::{RefCell, Ref, RefMut}}; + +#[cfg(feature = "sync")] +use crate::stdlib::sync::{Arc, RwLock, RwLockReadGuard, RwLockWriteGuard}; + #[cfg(not(feature = "no_object"))] use crate::stdlib::collections::HashMap; @@ -144,6 +151,88 @@ pub enum Union { Map(Box), FnPtr(Box), Variant(Box>), + Shared(Box), +} + +/// Internal Shared Dynamic representation. +/// +/// Created with `Dynamic::into_shared()`. +#[derive(Clone)] +pub struct SharedCell { + value_type_id: TypeId, + value_type_name: &'static str, + container: SharedMut +} + +/// Dynamic's underlying `Variant` read guard that supports `Deref` for Variant's +/// reference reading. +/// +/// This data structure provides transparent interoperability between normal +/// `Dynamic` types and Shared Dynamic references. +#[derive(Debug)] +pub struct DynamicReadLock<'d, T: Variant + Clone>(DynamicReadLockInner<'d, T>); + +#[derive(Debug)] +enum DynamicReadLockInner<'d, T: Variant + Clone> { + Reference(&'d T), + #[cfg(not(feature = "sync"))] + Guard(Ref<'d, Dynamic>), + #[cfg(feature = "sync")] + Guard(RwLockReadGuard<'d, Dynamic>), +} + +impl<'d, T: Variant + Clone> Deref for DynamicReadLock<'d, T> { + type Target = T; + + #[inline(always)] + fn deref(&self) -> &Self::Target { + match &self.0 { + DynamicReadLockInner::Reference(reference) => reference.deref(), + // unwrapping is safe because all checks already done in it's constructor + DynamicReadLockInner::Guard(guard) => guard.downcast_ref().unwrap(), + } + } +} + +/// Dynamic's underlying `Variant` write guard that supports `Deref` and `DerefMut` +/// for Variant's reference reading/writing. +/// +/// This data structure provides transparent interoperability between normal +/// `Dynamic` types and Shared Dynamic references. +#[derive(Debug)] +pub struct DynamicWriteLock<'d, T: Variant + Clone>(DynamicWriteLockInner<'d, T>); + +#[derive(Debug)] +enum DynamicWriteLockInner<'d, T: Variant + Clone> { + Reference(&'d mut T), + #[cfg(not(feature = "sync"))] + Guard(RefMut<'d, Dynamic>), + #[cfg(feature = "sync")] + Guard(RwLockWriteGuard<'d, Dynamic>), +} + +impl<'d, T: Variant + Clone> Deref for DynamicWriteLock<'d, T> { + type Target = T; + + #[inline(always)] + fn deref(&self) -> &Self::Target { + match &self.0 { + DynamicWriteLockInner::Reference(reference) => reference.deref(), + // unwrapping is safe because all checks already done in it's constructor + DynamicWriteLockInner::Guard(guard) => guard.downcast_ref().unwrap(), + } + } +} + +impl<'d, T: Variant + Clone> DerefMut for DynamicWriteLock<'d, T> { + #[inline(always)] + fn deref_mut(&mut self) -> &mut Self::Target { + match &mut self.0 { + DynamicWriteLockInner::Reference(reference) => reference.deref_mut(), + // unwrapping is safe because all checks already done in it's constructor + DynamicWriteLockInner::Guard(guard) => guard.downcast_mut().unwrap(), + } + } } impl Dynamic { @@ -156,7 +245,19 @@ impl Dynamic { } } + /// Does this `Dynamic` hold a shared data type + /// instead of one of the support system primitive types? + pub fn is_shared(&self) -> bool { + match self.0 { + Union::Shared(_) => true, + _ => false, + } + } + /// Is the value held by this `Dynamic` a particular type? + /// + /// If the `Dynamic` is a Shared variant checking is performed on + /// top of it's internal value. pub fn is(&self) -> bool { self.type_id() == TypeId::of::() || match self.0 { @@ -181,6 +282,7 @@ impl Dynamic { Union::Map(_) => TypeId::of::(), Union::FnPtr(_) => TypeId::of::(), Union::Variant(value) => (***value).type_id(), + Union::Shared(cell) => (**cell).value_type_id, } } @@ -203,6 +305,7 @@ impl Dynamic { #[cfg(not(feature = "no_std"))] Union::Variant(value) if value.is::() => "timestamp", Union::Variant(value) => (***value).type_name(), + Union::Shared(cell) => (**cell).value_type_name, } } } @@ -258,6 +361,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()), + Union::Shared(cell) => write!(f, "{}", (**cell).value_type_name), } } } @@ -284,6 +388,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()), + Union::Shared(cell) => write!(f, "{}", (**cell).value_type_name), } } } @@ -304,6 +409,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(), + Union::Shared(ref cell) => Self(Union::Shared(Box::new((**cell).clone()))) } } } @@ -415,10 +521,53 @@ impl Dynamic { Self(Union::Variant(Box::new(boxed))) } + /// Turns Dynamic into Shared Dynamic variant backed by runtime + /// mutable reference-counter container(`Arc>` or + /// `Rc` depending on `sync` feature). + /// + /// Instances of Shared Dynamic are relatively cheap to clone. All clones of + /// Shared Dynamic references the same chunk of memory. + /// + /// The Dynamic is capable to work transparently with the it's inner + /// data, seamlessly casting between ordinary instances of Dynamic and + /// Shared Dynamic instances. + /// + /// If original value already a Shared variant returns identity. + pub fn into_shared(self) -> Self { + match self.0 { + Union::Shared(..) => self, + _ => { + let cell = SharedCell { + value_type_id: self.type_id(), + value_type_name: self.type_name(), + #[cfg(not(feature = "sync"))] + container: Rc::new(RefCell::new(self)), + #[cfg(feature = "sync")] + container: Arc::new(RwLock::new(self)), + }; + + Self(Union::Shared(Box::new(cell))) + }, + } + } + /// Get a copy of the `Dynamic` value as a specific type. /// Casting to a `Dynamic` just returns as is. /// - /// Returns an error with the name of the value's actual type when the cast fails. + /// Returns None if types mismatched. + /// + /// # Shared Dynamic + /// + /// When accessing Shared Dynamic in sync mode(`sync` feature enabled) + /// can block current thread while the underlined data is being written. + /// + /// When accessing Shared Dynamic in NON-sync mode can **panic** if the data + /// is currently borrowed for write. + /// + /// ## Safety + /// + /// Both situations normally shouldn't happen since all operations in Rhai + /// use pass-by-value data and the script executed in a single thread. /// /// # Example /// @@ -433,6 +582,24 @@ impl Dynamic { pub fn try_cast(self) -> Option { let type_id = TypeId::of::(); + if type_id == TypeId::of::() { + return unsafe_cast_box::<_, T>(Box::new(self)).ok().map(|v| *v); + } + + #[cfg(not(feature = "sync"))] + if let Union::Shared(cell) = self.0 { + let reference = cell.container.borrow(); + + return (*reference).clone().try_cast() + } + + #[cfg(feature = "sync")] + if let Union::Shared(cell) = self.0 { + let read_lock = cell.container.read().unwrap(); + + return (*read_lock).clone().try_cast() + } + if type_id == TypeId::of::() { return match self.0 { Union::Int(value) => unsafe_try_cast(value), @@ -496,9 +663,6 @@ impl Dynamic { _ => None, }; } - if type_id == TypeId::of::() { - return unsafe_cast_box::<_, T>(Box::new(self)).ok().map(|v| *v); - } match self.0 { Union::Variant(value) => (*value).as_box_any().downcast().map(|x| *x).ok(), @@ -511,7 +675,14 @@ impl Dynamic { /// /// # Panics /// - /// Panics if the cast fails (e.g. the type of the actual value is not the same as the specified type). + /// Panics if the cast fails (e.g. the type of the actual value is not the + /// same as the specified type), or if the data held by Shared Dynamic is + /// currently borrowed(when the `sync` feature disabled). + /// + /// # Notes + /// + /// If the `sync` feature enabled Shared Dynamic can block current thread + /// while the data being written. /// /// # Example /// @@ -531,7 +702,104 @@ impl Dynamic { /// Casting to `Dynamic` just returns a reference to it. /// Returns `None` if the cast fails. #[inline(always)] - pub fn downcast_ref(&self) -> Option<&T> { + pub fn read_lock(&self) -> Option> { + match self.0 { + Union::Shared(ref cell) => { + let type_id = TypeId::of::(); + + if type_id != TypeId::of::() && cell.value_type_id != type_id { + return None + } + + #[cfg(not(feature = "sync"))] + return Some(DynamicReadLock(DynamicReadLockInner::Guard( + cell.container.borrow() + ))); + + #[cfg(feature = "sync")] + return Some(DynamicReadLock(DynamicReadLockInner::Guard( + cell.container.read().unwrap() + ))); + }, + _ => { + self.downcast_ref().map(|reference| { + DynamicReadLock(DynamicReadLockInner::Reference(reference)) + }) + } + } + } + + /// Get a copy of a specific type to the `Dynamic`. + /// Casting to `Dynamic` just returns a reference to it. + /// Returns `None` if the cast fails. + #[inline(always)] + pub fn read(&self) -> Option { + match self.0 { + Union::Shared(ref cell) => { + let type_id = TypeId::of::(); + + if type_id != TypeId::of::() && cell.value_type_id != type_id { + return None + } + + #[cfg(not(feature = "sync"))] + return Some(cell + .container + .borrow() + .deref() + .downcast_ref::() + .unwrap() + .clone()); + + #[cfg(feature = "sync")] + return Some(cell + .container + .read() + .unwrap() + .deref() + .downcast_ref::() + .unwrap() + .clone()); + }, + _ => { + self.downcast_ref().cloned() + } + } + } + + /// Get a mutable reference of a specific type to the `Dynamic`. + /// Casting to `Dynamic` just returns a mutable reference to it. + /// Returns `None` if the cast fails. + #[inline(always)] + pub fn write_lock(&mut self) -> Option> { + match self.0 { + Union::Shared(ref cell) => { + let type_id = TypeId::of::(); + + if type_id != TypeId::of::() && cell.value_type_id != type_id { + return None + } + + #[cfg(not(feature = "sync"))] + return Some(DynamicWriteLock(DynamicWriteLockInner::Guard( + cell.container.borrow_mut() + ))); + + #[cfg(feature = "sync")] + return Some(DynamicWriteLock(DynamicWriteLockInner::Guard( + cell.container.write().unwrap() + ))); + }, + _ => { + self.downcast_mut().map(|reference| { + DynamicWriteLock(DynamicWriteLockInner::Reference(reference)) + }) + } + } + } + + #[inline(always)] + fn downcast_ref(&self) -> Option<&T> { let type_id = TypeId::of::(); if type_id == TypeId::of::() { @@ -607,11 +875,8 @@ impl Dynamic { } } - /// Get a mutable reference of a specific type to the `Dynamic`. - /// Casting to `Dynamic` just returns a mutable reference to it. - /// Returns `None` if the cast fails. #[inline(always)] - pub fn downcast_mut(&mut self) -> Option<&mut T> { + fn downcast_mut(&mut self) -> Option<&mut T> { let type_id = TypeId::of::(); if type_id == TypeId::of::() { diff --git a/src/engine.rs b/src/engine.rs index 7806382f..06ff8d7f 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -91,6 +91,7 @@ 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_THIS: &str = "this"; pub const FN_TO_STRING: &str = "to_string"; #[cfg(not(feature = "no_object"))] @@ -1068,6 +1069,18 @@ impl Engine { let val = target.as_mut(); + // if val.is_shared() { + // return self.get_indexed_mut( + // state, + // lib, + // &mut Target::Value(val.read::().unwrap()), + // idx, + // idx_pos, + // create, + // level, + // ); + // } + match val { #[cfg(not(feature = "no_index"))] Dynamic(Union::Array(arr)) => { @@ -1102,7 +1115,7 @@ impl Engine { map.entry(index).or_insert(Default::default()).into() } else { let index = _idx - .downcast_ref::() + .read_lock::() .ok_or_else(|| EvalAltResult::ErrorStringIndexExpr(idx_pos))?; map.get_mut(index.as_str()) @@ -1280,7 +1293,11 @@ impl Engine { )), // Normal assignment ScopeEntryType::Normal if op.is_empty() => { - *lhs_ptr = rhs_val; + if lhs_ptr.is_shared() { + *lhs_ptr.write_lock::().unwrap() = rhs_val; + } else { + *lhs_ptr = rhs_val; + } Ok(Default::default()) } // Op-assignment - in order of precedence: @@ -1298,8 +1315,13 @@ impl Engine { .get_fn(hash_fn, false) .or_else(|| self.packages.get_fn(hash_fn, false)) { - // Overriding exact implementation - func(self, lib, &mut [lhs_ptr, &mut rhs_val])?; + if lhs_ptr.is_shared() { + // Overriding exact implementation + func(self, lib, &mut [&mut lhs_ptr.write_lock::().unwrap(), &mut rhs_val])?; + } else { + // Overriding exact implementation + func(self, lib, &mut [lhs_ptr, &mut rhs_val])?; + } } else if run_builtin_op_assignment(op, lhs_ptr, &rhs_val)?.is_none() { // Not built in, map to `var = var op rhs` let op = &op[..op.len() - 1]; // extract operator without = @@ -1313,8 +1335,13 @@ impl Engine { level, ) .map_err(|err| err.new_position(*op_pos))?; - // Set value to LHS - *lhs_ptr = value; + if lhs_ptr.is_shared() { + // Set value to LHS + *lhs_ptr.write_lock::().unwrap() = value; + } else { + // Set value to LHS + *lhs_ptr = value; + } } Ok(Default::default()) } diff --git a/src/fn_call.rs b/src/fn_call.rs index 162410f3..6ca46046 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_PRINT, - KEYWORD_TYPE_OF, + KEYWORD_TYPE_OF, KEYWORD_SHARED }; use crate::error::ParseErrorType; use crate::fn_native::{FnCallArgs, FnPtr}; @@ -16,6 +16,7 @@ use crate::result::EvalAltResult; use crate::scope::Scope; use crate::token::Position; use crate::utils::StaticVec; +use crate::stdlib::ops::Deref; #[cfg(not(feature = "no_function"))] use crate::{ @@ -540,7 +541,7 @@ impl Engine { let (result, updated) = if _fn_name == KEYWORD_FN_PTR_CALL && obj.is::() { // FnPtr call - let fn_ptr = obj.downcast_ref::().unwrap(); + let fn_ptr = obj.read_lock::().unwrap(); let mut curry = fn_ptr.curry().iter().cloned().collect::>(); // Redirect function name let fn_name = fn_ptr.fn_name(); @@ -578,7 +579,7 @@ impl Engine { ) } else if _fn_name == KEYWORD_FN_PTR_CURRY && obj.is::() { // Curry call - let fn_ptr = obj.downcast_ref::().unwrap(); + let fn_ptr = obj.read_lock::().unwrap(); Ok(( FnPtr::new_unchecked( fn_ptr.get_fn_name().clone(), @@ -599,9 +600,9 @@ impl Engine { // Check if it is a map method call in OOP style #[cfg(not(feature = "no_object"))] - if let Some(map) = obj.downcast_ref::() { + if let Some(map) = obj.read_lock::() { if let Some(val) = map.get(_fn_name) { - if let Some(f) = val.downcast_ref::() { + if let Some(f) = val.read_lock::() { // Remap the function name redirected = f.get_fn_name().clone(); _fn_name = &redirected; @@ -697,6 +698,15 @@ impl Engine { .into()); } + // Handle shared() + #[cfg(not(feature = "no_shared"))] + if 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 eval() if name == KEYWORD_EVAL && args_expr.len() == 1 { let hash_fn = calc_fn_hash(empty(), name, 1, once(TypeId::of::())); @@ -984,8 +994,8 @@ pub fn run_builtin_binary_op( _ => (), } } else if args_type == TypeId::of::() { - let x = x.downcast_ref::().unwrap(); - let y = y.downcast_ref::().unwrap(); + let x = &*x.read_lock::().unwrap(); + let y = &*y.read_lock::().unwrap(); match op { "+" => return Ok(Some((x + y).into())), @@ -1058,7 +1068,7 @@ pub fn run_builtin_op_assignment( } if args_type == TypeId::of::() { - let x = x.downcast_mut::().unwrap(); + let mut x = x.write_lock::().unwrap(); let y = y.clone().cast::(); #[cfg(not(feature = "unchecked"))] @@ -1094,7 +1104,7 @@ pub fn run_builtin_op_assignment( _ => (), } } else if args_type == TypeId::of::() { - let x = x.downcast_mut::().unwrap(); + let mut x = x.write_lock::().unwrap(); let y = y.clone().cast::(); match op { @@ -1103,18 +1113,18 @@ pub fn run_builtin_op_assignment( _ => (), } } else if args_type == TypeId::of::() { - let x = x.downcast_mut::().unwrap(); - let y = y.downcast_ref::().unwrap(); + let mut x = x.write_lock::().unwrap(); + let y = y.read_lock::().unwrap(); match op { - "+=" => return Ok(Some(*x += y)), + "+=" => return Ok(Some(*x += y.deref())), _ => (), } } #[cfg(not(feature = "no_float"))] if args_type == TypeId::of::() { - let x = x.downcast_mut::().unwrap(); + let mut x = x.write_lock::().unwrap(); let y = y.clone().cast::(); match op { diff --git a/src/fn_native.rs b/src/fn_native.rs index 5751351c..e51bf71b 100644 --- a/src/fn_native.rs +++ b/src/fn_native.rs @@ -1,6 +1,6 @@ //! Module defining interfaces to native-Rust functions. -use crate::any::Dynamic; +use crate::any::{Dynamic, Variant}; use crate::calc_fn_hash; use crate::engine::Engine; use crate::module::Module; @@ -18,9 +18,9 @@ use crate::stdlib::{boxed::Box, convert::TryFrom, fmt, iter::empty, string::Stri use crate::stdlib::mem; #[cfg(not(feature = "sync"))] -use crate::stdlib::rc::Rc; +use crate::stdlib::{rc::Rc, cell::{RefCell, Ref, RefMut}}; #[cfg(feature = "sync")] -use crate::stdlib::sync::Arc; +use crate::stdlib::sync::{Arc, RwLock}; /// Trait that maps to `Send + Sync` only under the `sync` feature. #[cfg(feature = "sync")] @@ -34,11 +34,18 @@ pub trait SendSync {} #[cfg(not(feature = "sync"))] impl SendSync for T {} +/// Immutable reference counting container #[cfg(not(feature = "sync"))] pub type Shared = Rc; #[cfg(feature = "sync")] pub type Shared = Arc; +/// Mutable reference counting container(read-write lock) +#[cfg(not(feature = "sync"))] +pub type SharedMut = Rc>; +#[cfg(feature = "sync")] +pub type SharedMut = Arc>; + /// Consume a `Shared` resource and return a mutable reference to the wrapped value. /// If the resource is shared (i.e. has other outstanding references), a cloned copy is used. pub fn shared_make_mut(value: &mut Shared) -> &mut T { diff --git a/src/fn_register.rs b/src/fn_register.rs index c2eb5534..ddcddff3 100644 --- a/src/fn_register.rs +++ b/src/fn_register.rs @@ -2,7 +2,7 @@ #![allow(non_snake_case)] -use crate::any::{Dynamic, Variant}; +use crate::any::{Dynamic, Variant, DynamicWriteLock}; use crate::engine::Engine; use crate::fn_native::{CallableFunction, FnAny, FnCallArgs, SendSync}; use crate::module::Module; @@ -97,11 +97,11 @@ pub trait RegisterResultFn { pub struct Mut(T); //pub struct Ref(T); -/// Dereference into &mut. +/// Dereference into DynamicWriteLock #[inline(always)] -pub fn by_ref(data: &mut Dynamic) -> &mut T { - // Directly cast the &mut Dynamic into &mut T to access the underlying data. - data.downcast_mut::().unwrap() +pub fn by_ref(data: &mut Dynamic) -> DynamicWriteLock { + // Directly cast the &mut Dynamic into DynamicWriteLock to access the underlying data. + data.write_lock::().unwrap() } /// Dereference into value. @@ -124,24 +124,23 @@ pub fn by_value(data: &mut Dynamic) -> T { /// This macro creates a closure wrapping a registered function. macro_rules! make_func { - ($fn:ident : $map:expr ; $($par:ident => $convert:expr),*) => { + ($fn:ident : $map:expr ; $($par:ident => $let:stmt => $convert:expr => $arg:expr),*) => { // ^ function pointer // ^ result mapping function // ^ function parameter generic type name (A, B, C etc.) -// ^ dereferencing function +// ^ argument let statement(e.g. let mut A ...) +// ^ dereferencing function +// ^ argument reference expression(like A, *B, &mut C etc) Box::new(move |_: &Engine, _: &Module, args: &mut FnCallArgs| { // The arguments are assumed to be of the correct number and types! let mut _drain = args.iter_mut(); - $( - // Downcast every element, panic in case of a type mismatch (which shouldn't happen). - // Call the user-supplied function using ($convert) to access it either by value or by reference. - let $par = ($convert)(_drain.next().unwrap()); - )* + $($let)* + $($par = ($convert)(_drain.next().unwrap()); )* // Call the function with each parameter value - let r = $fn($($par),*); + let r = $fn($($arg),*); // Map the result $map(r) @@ -181,12 +180,13 @@ macro_rules! def_register { () => { def_register!(imp from_pure :); }; - (imp $abi:ident : $($par:ident => $mark:ty => $param:ty => $clone:expr),*) => { + (imp $abi:ident : $($par:ident => $arg:expr => $mark:ty => $param:ty => $let:stmt => $clone:expr),*) => { // ^ function ABI type // ^ function parameter generic type name (A, B, C etc.) - // ^ function parameter marker type (T, Ref or Mut) - // ^ function parameter actual type (T, &T or &mut T) - // ^ dereferencing function +// ^ call argument(like A, *B, &mut C etc) + // ^ function parameter marker type (T, Ref or Mut) + // ^ function parameter actual type (T, &T or &mut T) + // ^ argument let statement impl< $($par: Variant + Clone,)* FN: Fn($($param),*) -> RET + SendSync + 'static, @@ -196,7 +196,7 @@ macro_rules! def_register { fn register_fn(&mut self, name: &str, f: FN) -> &mut Self { self.global_module.set_fn(name, FnAccess::Public, &[$(map_type_id::<$par>()),*], - CallableFunction::$abi(make_func!(f : map_dynamic ; $($par => $clone),*)) + CallableFunction::$abi(make_func!(f : map_dynamic ; $($par => $let => $clone => $arg),*)) ); self } @@ -210,7 +210,7 @@ macro_rules! def_register { fn register_result_fn(&mut self, name: &str, f: FN) -> &mut Self { self.global_module.set_fn(name, FnAccess::Public, &[$(map_type_id::<$par>()),*], - CallableFunction::$abi(make_func!(f : map_result ; $($par => $clone),*)) + CallableFunction::$abi(make_func!(f : map_result ; $($par => $let => $clone => $arg),*)) ); self } @@ -219,11 +219,11 @@ macro_rules! def_register { //def_register!(imp_pop $($par => $mark => $param),*); }; ($p0:ident $(, $p:ident)*) => { - def_register!(imp from_pure : $p0 => $p0 => $p0 => by_value $(, $p => $p => $p => by_value)*); - def_register!(imp from_method : $p0 => Mut<$p0> => &mut $p0 => by_ref $(, $p => $p => $p => by_value)*); + def_register!(imp from_pure : $p0 => $p0 => $p0 => $p0 => let $p0 => by_value $(, $p => $p => $p => $p => let $p => by_value)*); + def_register!(imp from_method : $p0 => &mut $p0 => Mut<$p0> => &mut $p0 => let mut $p0 => by_ref $(, $p => $p => $p => $p => let $p => by_value)*); // ^ CallableFunction - // handle the first parameter ^ first parameter passed through - // ^ others passed by value (by_value) + // handle the first parameter ^ first parameter passed through + // ^ others passed by value (by_value) // Currently does not support first argument which is a reference, as there will be // conflicting implementations since &T: Any and T: Any cannot be distinguished diff --git a/src/module.rs b/src/module.rs index 8eacde7b..fa23abd6 100644 --- a/src/module.rs +++ b/src/module.rs @@ -442,7 +442,7 @@ impl Module { /// // Since it is a primary type, it can also be cheaply copied /// let double = args[1].clone().cast::(); /// // Get a mutable reference to the first argument. - /// let x = args[0].downcast_mut::().unwrap(); + /// let mut x = args[0].write_lock::().unwrap(); /// /// let orig = *x; /// @@ -534,7 +534,7 @@ impl Module { func: impl Fn(&mut A) -> FuncReturn + SendSync + 'static, ) -> u64 { let f = move |_: &Engine, _: &Module, args: &mut FnCallArgs| { - func(args[0].downcast_mut::().unwrap()).map(Dynamic::from) + func(&mut args[0].write_lock::().unwrap()).map(Dynamic::from) }; let arg_types = [TypeId::of::()]; self.set_fn(name, Public, &arg_types, Func::from_method(Box::new(f))) @@ -615,9 +615,9 @@ impl Module { ) -> u64 { let f = move |_: &Engine, _: &Module, args: &mut FnCallArgs| { let b = mem::take(args[1]).cast::(); - let a = args[0].downcast_mut::().unwrap(); + let mut a = args[0].write_lock::().unwrap(); - func(a, b).map(Dynamic::from) + func(&mut a, b).map(Dynamic::from) }; let arg_types = [TypeId::of::(), TypeId::of::()]; self.set_fn(name, Public, &arg_types, Func::from_method(Box::new(f))) @@ -739,9 +739,9 @@ impl Module { let f = move |_: &Engine, _: &Module, args: &mut FnCallArgs| { let b = mem::take(args[1]).cast::(); let c = mem::take(args[2]).cast::(); - let a = args[0].downcast_mut::().unwrap(); + let mut a = args[0].write_lock::().unwrap(); - func(a, b, c).map(Dynamic::from) + func(&mut a, b, c).map(Dynamic::from) }; let arg_types = [TypeId::of::(), TypeId::of::(), TypeId::of::()]; self.set_fn(name, Public, &arg_types, Func::from_method(Box::new(f))) @@ -773,9 +773,9 @@ impl Module { let f = move |_: &Engine, _: &Module, args: &mut FnCallArgs| { let b = mem::take(args[1]).cast::(); let c = mem::take(args[2]).cast::(); - let a = args[0].downcast_mut::().unwrap(); + let mut a = args[0].write_lock::().unwrap(); - func(a, b, c).map(Dynamic::from) + func(&mut a, b, c).map(Dynamic::from) }; let arg_types = [TypeId::of::(), TypeId::of::(), TypeId::of::()]; self.set_fn( @@ -896,9 +896,9 @@ impl Module { let b = mem::take(args[1]).cast::(); let c = mem::take(args[2]).cast::(); let d = mem::take(args[3]).cast::(); - let a = args[0].downcast_mut::().unwrap(); + let mut a = args[0].write_lock::().unwrap(); - func(a, b, c, d).map(Dynamic::from) + func(&mut a, b, c, d).map(Dynamic::from) }; let arg_types = [ TypeId::of::(), diff --git a/src/packages/array_basic.rs b/src/packages/array_basic.rs index c0bea7ee..5ca3b1c7 100644 --- a/src/packages/array_basic.rs +++ b/src/packages/array_basic.rs @@ -34,7 +34,7 @@ fn pad( _: &Module, args: &mut [&mut Dynamic], ) -> FuncReturn<()> { - let len = *args[1].downcast_ref::().unwrap(); + let len = *args[1].read_lock::().unwrap(); // Check if array will be over max size limit #[cfg(not(feature = "unchecked"))] @@ -52,7 +52,7 @@ fn pad( if len > 0 { let item = args[2].clone(); - let list = args[0].downcast_mut::().unwrap(); + let mut list = args[0].write_lock::().unwrap(); if len as usize > list.len() { list.resize(len as usize, item); diff --git a/src/packages/string_more.rs b/src/packages/string_more.rs index 6ddc2b0c..b50d6915 100644 --- a/src/packages/string_more.rs +++ b/src/packages/string_more.rs @@ -228,7 +228,7 @@ def_package!(crate:MoreStringPackage:"Additional string utilities, including str "pad", &[TypeId::of::(), TypeId::of::(), TypeId::of::()], |_engine: &Engine, _: &Module, args: &mut [&mut Dynamic]| { - let len = *args[1].downcast_ref::< INT>().unwrap(); + let len = *args[1].read_lock::< INT>().unwrap(); // Check if string will be over max size limit #[cfg(not(feature = "unchecked"))] @@ -243,7 +243,7 @@ def_package!(crate:MoreStringPackage:"Additional string utilities, including str if len > 0 { let ch = mem::take(args[2]).cast::(); - let s = args[0].downcast_mut::().unwrap(); + let mut s = args[0].write_lock::().unwrap(); let orig_len = s.chars().count(); diff --git a/src/scope.rs b/src/scope.rs index 5693554c..b095f782 100644 --- a/src/scope.rs +++ b/src/scope.rs @@ -333,7 +333,7 @@ impl<'a> Scope<'a> { .iter() .rev() .find(|Entry { name: key, .. }| name == key) - .and_then(|Entry { value, .. }| value.downcast_ref::().cloned()) + .and_then(|Entry { value, .. }| value.read::()) } /// Update the value of the named entry. diff --git a/src/token.rs b/src/token.rs index 7d1a07ba..bd496afb 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_PRINT, KEYWORD_THIS, KEYWORD_TYPE_OF, + KEYWORD_SHARED, KEYWORD_PRINT, KEYWORD_THIS, KEYWORD_TYPE_OF, }; use crate::error::LexError; @@ -507,7 +507,7 @@ impl Token { | "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_THIS => Reserved(syntax.into()), + | KEYWORD_FN_PTR_CALL | KEYWORD_FN_PTR_CURRY | KEYWORD_SHARED | KEYWORD_THIS => Reserved(syntax.into()), _ => return None, }) From aa87a7f5efee45f712e20e4601e5954c26ec2e2f Mon Sep 17 00:00:00 2001 From: Ilya Lakhin Date: Fri, 31 Jul 2020 05:34:20 +0700 Subject: [PATCH 07/40] Fixes in Engine to properly interpret Shared Dynamic --- src/any.rs | 43 +++++++++++-- src/engine.rs | 105 +++++++++++++++++++------------ src/fn_call.rs | 1 - src/fn_native.rs | 2 +- src/fn_register.rs | 2 +- src/parser.rs | 28 +++++++-- src/token.rs | 11 +++- tests/closures.rs | 151 ++++++++++++++++++++++++++++++++++++++++++++- 8 files changed, 288 insertions(+), 55 deletions(-) diff --git a/src/any.rs b/src/any.rs index a7983030..7990ff10 100644 --- a/src/any.rs +++ b/src/any.rs @@ -18,7 +18,7 @@ use crate::stdlib::{ boxed::Box, fmt, string::String, - ops::{Deref, DerefMut} + ops::{DerefMut, Deref} }; #[cfg(not(feature = "sync"))] @@ -259,11 +259,13 @@ impl Dynamic { /// If the `Dynamic` is a Shared variant checking is performed on /// top of it's internal value. pub fn is(&self) -> bool { - self.type_id() == TypeId::of::() - || match self.0 { - Union::Str(_) => TypeId::of::() == TypeId::of::(), - _ => false, - } + let mut target_type_id = TypeId::of::(); + + if target_type_id == TypeId::of::() { + target_type_id = TypeId::of::(); + } + + self.type_id() == target_type_id } /// Get the TypeId of the value held by this `Dynamic`. @@ -606,6 +608,7 @@ impl Dynamic { _ => None, }; } + #[cfg(not(feature = "no_float"))] if type_id == TypeId::of::() { return match self.0 { @@ -613,30 +616,35 @@ impl Dynamic { _ => None, }; } + if type_id == TypeId::of::() { return match self.0 { Union::Bool(value) => unsafe_try_cast(value), _ => None, }; } + if type_id == TypeId::of::() { return match self.0 { Union::Str(value) => unsafe_try_cast(value), _ => None, }; } + if type_id == TypeId::of::() { return match self.0 { Union::Str(value) => unsafe_try_cast(value.into_owned()), _ => None, }; } + if type_id == TypeId::of::() { return match self.0 { Union::Char(value) => unsafe_try_cast(value), _ => None, }; } + #[cfg(not(feature = "no_index"))] if type_id == TypeId::of::() { return match self.0 { @@ -644,6 +652,7 @@ impl Dynamic { _ => None, }; } + #[cfg(not(feature = "no_object"))] if type_id == TypeId::of::() { return match self.0 { @@ -651,12 +660,14 @@ impl Dynamic { _ => None, }; } + if type_id == TypeId::of::() { return match self.0 { Union::FnPtr(value) => unsafe_cast_box::<_, T>(value).ok().map(|v| *v), _ => None, }; } + if type_id == TypeId::of::<()>() { return match self.0 { Union::Unit(value) => unsafe_try_cast(value), @@ -951,6 +962,7 @@ impl Dynamic { pub fn as_int(&self) -> Result { match self.0 { Union::Int(n) => Ok(n), + Union::Shared(_) => self.read::().ok_or_else(|| self.type_name()), _ => Err(self.type_name()), } } @@ -961,6 +973,7 @@ impl Dynamic { pub fn as_float(&self) -> Result { match self.0 { Union::Float(n) => Ok(n), + Union::Shared(_) => self.read::().ok_or_else(|| self.type_name()), _ => Err(self.type_name()), } } @@ -970,6 +983,7 @@ impl Dynamic { pub fn as_bool(&self) -> Result { match self.0 { Union::Bool(b) => Ok(b), + Union::Shared(_) => self.read::().ok_or_else(|| self.type_name()), _ => Err(self.type_name()), } } @@ -979,12 +993,15 @@ impl Dynamic { pub fn as_char(&self) -> Result { match self.0 { Union::Char(n) => Ok(n), + Union::Shared(_) => self.read::().ok_or_else(|| self.type_name()), _ => Err(self.type_name()), } } /// Cast the `Dynamic` as a string and return the string slice. /// Returns the name of the actual type if the cast fails. + /// + /// Cast is failing if `self` is Shared Dynamic pub fn as_str(&self) -> Result<&str, &'static str> { match &self.0 { Union::Str(s) => Ok(s), @@ -1006,6 +1023,20 @@ impl Dynamic { match self.0 { Union::Str(s) => Ok(s), Union::FnPtr(f) => Ok(f.take_data().0), + Union::Shared(cell) => { + #[cfg(not(feature = "sync"))] + match &cell.container.borrow().deref().0 { + Union::Str(s) => Ok(s.clone()), + Union::FnPtr(f) => Ok(f.clone().take_data().0), + _ => Err(cell.value_type_name), + } + #[cfg(feature = "sync")] + match &cell.container.read().deref().0 { + Union::Str(s) => Ok(s.clone()), + Union::FnPtr(f) => Ok(f.clone().take_data().0), + _ => Err(cell.value_type_name), + } + } _ => Err(self.type_name()), } } diff --git a/src/engine.rs b/src/engine.rs index 06ff8d7f..8f727211 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -1,6 +1,6 @@ //! Main module defining the script evaluation `Engine`. -use crate::any::{map_std_type_name, Dynamic, Union}; +use crate::any::{map_std_type_name, Dynamic, Union, DynamicWriteLock}; use crate::calc_fn_hash; use crate::fn_call::run_builtin_op_assignment; use crate::fn_native::{CallableFunction, Callback, FnPtr}; @@ -39,6 +39,7 @@ use crate::stdlib::{ iter::{empty, once}, string::{String, ToString}, vec::Vec, + ops::DerefMut, }; #[cfg(not(feature = "no_index"))] @@ -123,6 +124,9 @@ pub enum ChainType { pub enum Target<'a> { /// The target is a mutable reference to a `Dynamic` value somewhere. Ref(&'a mut Dynamic), + /// The target is a mutable reference to a Shared `Dynamic` value. + /// It holds the access guard and the original container both for cloning purposes + LockGuard((DynamicWriteLock<'a, Dynamic>, Dynamic)), /// The target is a temporary `Dynamic` value (i.e. the mutation can cause no side effects). Value(Dynamic), /// The target is a character inside a String. @@ -137,6 +141,7 @@ impl Target<'_> { pub fn is_ref(&self) -> bool { match self { Self::Ref(_) => true, + Self::LockGuard(_) => true, Self::Value(_) => false, #[cfg(not(feature = "no_index"))] Self::StringChar(_, _, _) => false, @@ -146,6 +151,7 @@ impl Target<'_> { pub fn is_value(&self) -> bool { match self { Self::Ref(_) => false, + Self::LockGuard(_) => false, Self::Value(_) => true, #[cfg(not(feature = "no_index"))] Self::StringChar(_, _, _) => false, @@ -156,6 +162,7 @@ impl Target<'_> { pub fn is(&self) -> bool { match self { Target::Ref(r) => r.is::(), + Target::LockGuard((r, _)) => r.is::(), Target::Value(r) => r.is::(), #[cfg(not(feature = "no_index"))] Target::StringChar(_, _, _) => TypeId::of::() == TypeId::of::(), @@ -165,6 +172,7 @@ impl Target<'_> { pub fn clone_into_dynamic(self) -> Dynamic { match self { Self::Ref(r) => r.clone(), // Referenced value is cloned + Self::LockGuard((_, orig)) => orig, // Return original container of the Shared Dynamic Self::Value(v) => v, // Owned value is simply taken #[cfg(not(feature = "no_index"))] Self::StringChar(_, _, ch) => ch, // Character is taken @@ -174,6 +182,7 @@ impl Target<'_> { pub fn as_mut(&mut self) -> &mut Dynamic { match self { Self::Ref(r) => *r, + Self::LockGuard((r, _)) => r.deref_mut(), Self::Value(ref mut r) => r, #[cfg(not(feature = "no_index"))] Self::StringChar(_, _, ref mut r) => r, @@ -184,13 +193,16 @@ impl Target<'_> { pub fn set_value(&mut self, new_val: Dynamic) -> Result<(), Box> { match self { Self::Ref(r) => **r = new_val, + Self::LockGuard((r, _)) => **r = new_val, Self::Value(_) => { return Err(Box::new(EvalAltResult::ErrorAssignmentToUnknownLHS( Position::none(), ))) } #[cfg(not(feature = "no_index"))] - Self::StringChar(Dynamic(Union::Str(ref mut s)), index, _) => { + Self::StringChar(string, index, _) if string.is::() => { + let mut s = string.write_lock::().unwrap(); + // Replace the character at the specified index position let new_ch = new_val .as_char() @@ -216,7 +228,13 @@ 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 { - Self::Ref(value) + if value.is_shared() { + // clone is cheap since it holds Arc/Rw under the hood + let container = value.clone(); + Self::LockGuard((value.write_lock::().unwrap(), container)) + } else { + Self::Ref(value) + } } } #[cfg(any(not(feature = "no_index"), not(feature = "no_object")))] @@ -676,15 +694,46 @@ impl Engine { } // xxx[rhs] = new_val _ if _new_val.is_some() => { - let mut new_val = _new_val.unwrap(); let mut idx_val2 = idx_val.clone(); + // `next` is introduced to bypass double mutable borrowing of target + #[cfg(not(feature = "no_index"))] + let mut next: Option<(u8, Dynamic)>; + match self.get_indexed_mut(state, lib, target, idx_val, pos, true, level) { // Indexed value is an owned value - the only possibility is an indexer // Try to call an index setter #[cfg(not(feature = "no_index"))] Ok(obj_ptr) if obj_ptr.is_value() => { - let args = &mut [target.as_mut(), &mut idx_val2, &mut new_val]; + next = Some((1, _new_val.unwrap())); + } + // Indexed value is a reference - update directly + Ok(ref mut obj_ptr) => { + obj_ptr + .set_value(_new_val.unwrap()) + .map_err(|err| err.new_position(rhs.position()))?; + + #[cfg(not(feature = "no_index"))] + { + next = None; + } + } + Err(err) => match *err { + // No index getter - try to call an index setter + #[cfg(not(feature = "no_index"))] + EvalAltResult::ErrorIndexingType(_, _) => { + next = Some((2, _new_val.unwrap())); + } + // Error + err => return Err(Box::new(err)), + }, + }; + + #[cfg(not(feature = "no_index"))] + match &mut next { + // next step is custom index setter call + Some((1, _new_val)) => { + let args = &mut [target.as_mut(), &mut idx_val2, _new_val]; self.exec_fn_call( state, lib, FN_IDX_SET, true, 0, args, is_ref, true, false, @@ -698,27 +747,20 @@ impl Engine { _ => Err(err), })?; } - // Indexed value is a reference - update directly - Ok(ref mut obj_ptr) => { - obj_ptr - .set_value(new_val) - .map_err(|err| err.new_position(rhs.position()))?; - } - Err(err) => match *err { - // No index getter - try to call an index setter - #[cfg(not(feature = "no_index"))] - EvalAltResult::ErrorIndexingType(_, _) => { - let args = &mut [target.as_mut(), &mut idx_val2, &mut new_val]; - self.exec_fn_call( - state, lib, FN_IDX_SET, true, 0, args, is_ref, true, false, - None, level, - )?; - } - // Error - err => return Err(Box::new(err)), - }, + // next step is custom index setter call in case of error + Some((2, _new_val)) => { + let args = &mut [target.as_mut(), &mut idx_val2, _new_val]; + + self.exec_fn_call( + state, lib, FN_IDX_SET, true, 0, args, is_ref, true, false, + None, level, + )?; + } + None => (), + _ => unreachable!() } + Ok(Default::default()) } // xxx[rhs] @@ -835,11 +877,10 @@ impl Engine { .map_err(|err| err.new_position(*pos))?; let val = &mut val; - let target = &mut val.into(); let (result, may_be_changed) = self .eval_dot_index_chain_helper( - state, lib, this_ptr, target, expr, idx_values, next_chain, + state, lib, this_ptr, &mut val.into(), expr, idx_values, next_chain, level, _new_val, ) .map_err(|err| err.new_position(*pos))?; @@ -1069,18 +1110,6 @@ impl Engine { let val = target.as_mut(); - // if val.is_shared() { - // return self.get_indexed_mut( - // state, - // lib, - // &mut Target::Value(val.read::().unwrap()), - // idx, - // idx_pos, - // create, - // level, - // ); - // } - match val { #[cfg(not(feature = "no_index"))] Dynamic(Union::Array(arr)) => { diff --git a/src/fn_call.rs b/src/fn_call.rs index 6ca46046..19f3fd33 100644 --- a/src/fn_call.rs +++ b/src/fn_call.rs @@ -699,7 +699,6 @@ impl Engine { } // Handle shared() - #[cfg(not(feature = "no_shared"))] if 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)?; diff --git a/src/fn_native.rs b/src/fn_native.rs index e51bf71b..54f3da08 100644 --- a/src/fn_native.rs +++ b/src/fn_native.rs @@ -18,7 +18,7 @@ use crate::stdlib::{boxed::Box, convert::TryFrom, fmt, iter::empty, string::Stri use crate::stdlib::mem; #[cfg(not(feature = "sync"))] -use crate::stdlib::{rc::Rc, cell::{RefCell, Ref, RefMut}}; +use crate::stdlib::{rc::Rc, cell::RefCell}; #[cfg(feature = "sync")] use crate::stdlib::sync::{Arc, RwLock}; diff --git a/src/fn_register.rs b/src/fn_register.rs index ddcddff3..eef4e66b 100644 --- a/src/fn_register.rs +++ b/src/fn_register.rs @@ -114,7 +114,7 @@ pub fn by_value(data: &mut Dynamic) -> T { ref_T.clone() } else if TypeId::of::() == TypeId::of::() { // If T is String, data must be ImmutableString, so map directly to it - *unsafe_cast_box(Box::new(data.as_str().unwrap().to_string())).unwrap() + *unsafe_cast_box(Box::new(data.clone().take_string().unwrap())).unwrap() } else { // We consume the argument and then replace it with () - the argument is not supposed to be used again. // This way, we avoid having to clone the argument again, because it is already a clone when passed here. diff --git a/src/parser.rs b/src/parser.rs index 3068bad0..2128f38c 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -411,6 +411,11 @@ struct ParseState<'e> { /// Tracks a list of external variables (variables that are not explicitly declared in the scope). #[cfg(not(feature = "no_capture"))] 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. + /// All consequent calls to `access_var` will not be affected + #[cfg(not(feature = "no_capture"))] + capture: bool, /// Encapsulates a local stack with variable names to simulate an actual runtime scope. modules: Vec, /// Maximum levels of expression nesting. @@ -436,6 +441,8 @@ impl<'e> ParseState<'e> { max_function_expr_depth, #[cfg(not(feature = "no_capture"))] externals: Default::default(), + #[cfg(not(feature = "no_capture"))] + capture: true, stack: Default::default(), modules: Default::default(), } @@ -458,8 +465,12 @@ impl<'e> ParseState<'e> { .and_then(|(i, _)| NonZeroUsize::new(i + 1)); #[cfg(not(feature = "no_capture"))] - if index.is_none() && !self.externals.contains_key(name) { - self.externals.insert(name.to_string(), pos); + if self.capture { + if index.is_none() && !self.externals.contains_key(name) { + self.externals.insert(name.to_string(), pos); + } + } else { + self.capture = true } index @@ -2171,9 +2182,18 @@ fn parse_binary_op( let (op_token, pos) = input.next().unwrap(); - let rhs = parse_unary(input, state, lib, settings)?; + let next = input.peek().unwrap(); + let next_precedence = next.0.precedence(custom); - let next_precedence = input.peek().unwrap().0.precedence(custom); + #[cfg(any(not(feature = "no_object"), not(feature = "no_capture")))] + if op_token == Token::Period { + if let (Token::Identifier(_), _) = next { + // prevents capturing of the object properties as vars: xxx. + state.capture = false; + } + } + + let rhs = parse_unary(input, state, lib, settings)?; // Bind to right if the next operator has higher precedence // If same precedence, then check if the operator binds right diff --git a/src/token.rs b/src/token.rs index bd496afb..71ec09a6 100644 --- a/src/token.rs +++ b/src/token.rs @@ -1430,13 +1430,20 @@ fn get_identifier( /// Is this keyword allowed as a function? #[inline(always)] pub fn is_keyword_function(name: &str) -> bool { - name == KEYWORD_PRINT + let mut result = name == KEYWORD_PRINT || name == KEYWORD_DEBUG || name == KEYWORD_TYPE_OF || name == KEYWORD_EVAL || name == KEYWORD_FN_PTR || name == KEYWORD_FN_PTR_CALL - || name == KEYWORD_FN_PTR_CURRY + || name == KEYWORD_FN_PTR_CURRY; + + #[cfg(not(feature = "no-shared"))] + { + result = result || name == KEYWORD_SHARED; + } + + result } pub fn is_valid_identifier(name: impl Iterator) -> bool { diff --git a/tests/closures.rs b/tests/closures.rs index 3aec4865..ae0c6535 100644 --- a/tests/closures.rs +++ b/tests/closures.rs @@ -1,6 +1,6 @@ #![cfg(not(feature = "no_function"))] -use rhai::{Dynamic, Engine, EvalAltResult, FnPtr, Module, INT}; -use std::any::TypeId; +use rhai::{Dynamic, Engine, EvalAltResult, FnPtr, Module, INT, Array}; +use std::any::{TypeId, Any}; #[test] fn test_fn_ptr_curry_call() -> Result<(), Box> { @@ -58,3 +58,150 @@ fn test_closures() -> Result<(), Box> { Ok(()) } + +#[test] +#[cfg(not(feature = "no_shared"))] +fn test_shared() -> Result<(), Box> { + let engine = Engine::new(); + + // assert_eq!( + // engine.eval::( + // r#" + // shared(42) + // "# + // )?, + // 42 + // ); + // + // assert_eq!( + // engine.eval::( + // r#" + // shared(true) + // "# + // )?, + // true + // ); + // + // #[cfg(not(feature = "no_float"))] + // assert_eq!( + // engine.eval::( + // r#" + // shared(4.2) + // "# + // )?, + // 4.2 + // ); + // + // assert_eq!( + // engine.eval::( + // r#" + // shared("test") + // "# + // )?, + // "test" + // ); + // + // #[cfg(not(feature = "no_index"))] + // { + // assert_eq!( + // engine.eval::( + // r#" + // let x = shared([1, 2, 3]); + // let y = shared([4, 5]); + // x + y + // "# + // )?.len(), + // 5 + // ); + // + // 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_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" still holds Dynamic Shared + // y.c + // "#)?, + // 5 + // ); + // + // #[cfg(not(feature = "no_capture"))] + // assert_eq!( + // engine.eval::(r#" + // let x = shared(1); + // (|| x = x + 41).call(); + // x + // "#)?, + // 42 + // ); + + #[cfg(all(not(feature = "no_object"), 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; + // b = 20; + // + // let f = |a| { + // x.c = x.a + x.b;// + a; + // }; + // + // f.call(20); + // + // x.c + + let x = #{a: 1, b: 2}; + + x.a + x.b + "#)?, + 42 + ); + + Ok(()) +} From 060dd33046136d2f36b0380442116a433c47bf0d Mon Sep 17 00:00:00 2001 From: Ilya Lakhin Date: Fri, 31 Jul 2020 10:44:57 +0700 Subject: [PATCH 08/40] Shared Dynamic tests and fixes in Engine; Also fixed a bug in Parser variable capturing --- src/any.rs | 2 +- src/parser.rs | 7 +- tests/closures.rs | 359 +++++++++++++++++++++++++++++----------------- 3 files changed, 231 insertions(+), 137 deletions(-) diff --git a/src/any.rs b/src/any.rs index 7990ff10..db2ada1b 100644 --- a/src/any.rs +++ b/src/any.rs @@ -1031,7 +1031,7 @@ impl Dynamic { _ => Err(cell.value_type_name), } #[cfg(feature = "sync")] - match &cell.container.read().deref().0 { + match &cell.container.read().unwrap().deref().0 { Union::Str(s) => Ok(s.clone()), Union::FnPtr(f) => Ok(f.clone().take_data().0), _ => Err(cell.value_type_name), diff --git a/src/parser.rs b/src/parser.rs index 2128f38c..1d9f26dd 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -2182,12 +2182,9 @@ fn parse_binary_op( let (op_token, pos) = input.next().unwrap(); - let next = input.peek().unwrap(); - let next_precedence = next.0.precedence(custom); - #[cfg(any(not(feature = "no_object"), not(feature = "no_capture")))] if op_token == Token::Period { - if let (Token::Identifier(_), _) = next { + if let (Token::Identifier(_), _) = input.peek().unwrap() { // prevents capturing of the object properties as vars: xxx. state.capture = false; } @@ -2195,6 +2192,8 @@ fn parse_binary_op( let rhs = parse_unary(input, state, lib, settings)?; + let next_precedence = input.peek().unwrap().0.precedence(custom); + // Bind to right if the next operator has higher precedence // If same precedence, then check if the operator binds right let rhs = if (precedence == next_precedence && bind_right) || precedence < next_precedence { diff --git a/tests/closures.rs b/tests/closures.rs index ae0c6535..25166ee1 100644 --- a/tests/closures.rs +++ b/tests/closures.rs @@ -1,5 +1,5 @@ #![cfg(not(feature = "no_function"))] -use rhai::{Dynamic, Engine, EvalAltResult, FnPtr, Module, INT, Array}; +use rhai::{Dynamic, Engine, EvalAltResult, RegisterFn, FnPtr, Module, INT, Array}; use std::any::{TypeId, Any}; #[test] @@ -62,146 +62,241 @@ fn test_closures() -> Result<(), Box> { #[test] #[cfg(not(feature = "no_shared"))] fn test_shared() -> Result<(), Box> { - let engine = Engine::new(); + let mut engine = Engine::new(); - // assert_eq!( - // engine.eval::( - // r#" - // shared(42) - // "# - // )?, - // 42 - // ); - // - // assert_eq!( - // engine.eval::( - // r#" - // shared(true) - // "# - // )?, - // true - // ); - // - // #[cfg(not(feature = "no_float"))] - // assert_eq!( - // engine.eval::( - // r#" - // shared(4.2) - // "# - // )?, - // 4.2 - // ); - // - // assert_eq!( - // engine.eval::( - // r#" - // shared("test") - // "# - // )?, - // "test" - // ); - // - // #[cfg(not(feature = "no_index"))] - // { - // assert_eq!( - // engine.eval::( - // r#" - // let x = shared([1, 2, 3]); - // let y = shared([4, 5]); - // x + y - // "# - // )?.len(), - // 5 - // ); - // - // 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_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" still holds Dynamic Shared - // y.c - // "#)?, - // 5 - // ); - // - // #[cfg(not(feature = "no_capture"))] - // assert_eq!( - // engine.eval::(r#" - // let x = shared(1); - // (|| x = x + 41).call(); - // x - // "#)?, - // 42 - // ); + assert_eq!( + engine.eval::( + r#" + shared(42) + "# + )?, + 42 + ); - #[cfg(all(not(feature = "no_object"), not(feature = "no_capture")))] + assert_eq!( + engine.eval::( + r#" + shared(true) + "# + )?, + true + ); + + #[cfg(not(feature = "no_float"))] + assert_eq!( + engine.eval::( + r#" + shared(4.2) + "# + )?, + 4.2 + ); + + assert_eq!( + engine.eval::( + r#" + shared("test") + "# + )?, + "test" + ); + + assert_eq!( + engine.eval::( + r#" + shared('x') + "# + )?, + 'x' + ); + + assert_eq!( + engine.eval::( + r#" + let s = shared("test"); + let i = shared(0); + i = 2; + + s[i] = 'S'; + s + "# + )?, + "teSt" + ); + + #[cfg(not(feature = "no_index"))] + { + assert_eq!( + engine.eval::( + r#" + let x = shared([1, 2, 3]); + let y = shared([4, 5]); + x + y + "# + )?.len(), + 5 + ); + + 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 + ); + + 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 x = shared(#{a: 1, b: shared(2), c: 3}); - // let a = x.a; - // let b = x.b; - // a = 100; - // b = 20; - // - // let f = |a| { - // x.c = x.a + x.b;// + a; - // }; - // - // f.call(20); - // - // x.c + let y = shared(#{a: 1, b: 2, c: 3}); + y.c = shared(5); + y.c + "#)?, + 5 + ); - let x = #{a: 1, b: 2}; + #[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 + ); - x.a + x.b + #[cfg(not(feature = "no_capture"))] + assert_eq!( + engine.eval::(r#" + let x = shared(1); + (|| x = x + 41).call(); + x "#)?, 42 ); + #[cfg(all(not(feature = "no_object"), 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::(r#" + custom_addition(shared(20), shared(22)) + "#)?, + 42 + ); + + #[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::(); + + engine.register_get_set("x", TestStruct::get_x, TestStruct::set_x); + engine.register_fn("update", TestStruct::update); + engine.register_fn("merge", TestStruct::merge); + engine.register_fn("new_ts", TestStruct::new); + + assert_eq!( + engine.eval::( + r" + let a = shared(new_ts()); + + a.x = 100; + a.update(); + // a.merge(a); + a.x + " + )?, + 1100 + ); + Ok(()) } From cb005506e2f3984dc3e8f5c0f225f3d0ec25ab0b Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Fri, 31 Jul 2020 12:11:16 +0800 Subject: [PATCH 09/40] Simplify function calling. --- src/engine.rs | 15 ++-- src/fn_call.rs | 192 +++++++++++++++++++++++++++-------------------- src/fn_native.rs | 10 +++ src/optimize.rs | 12 +-- 4 files changed, 130 insertions(+), 99 deletions(-) diff --git a/src/engine.rs b/src/engine.rs index 6aa1f9c2..dc916876 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -1179,7 +1179,6 @@ impl Engine { #[cfg(not(feature = "no_index"))] Dynamic(Union::Array(mut rhs_value)) => { let op = "=="; - let mut scope = Scope::new(); // Call the `==` operator to compare each value for value in rhs_value.iter_mut() { @@ -1190,13 +1189,13 @@ impl Engine { let hash = calc_fn_hash(empty(), op, args.len(), args.iter().map(|a| a.type_id())); - let (r, _) = self - .call_fn_raw( - &mut scope, mods, state, lib, op, hash, args, false, false, false, - def_value, level, - ) - .map_err(|err| err.new_position(rhs.position()))?; - if r.as_bool().unwrap_or(false) { + if self + .call_native_fn(state, lib, op, hash, args, false, false, def_value) + .map_err(|err| err.new_position(rhs.position()))? + .0 + .as_bool() + .unwrap_or(false) + { return Ok(true.into()); } } diff --git a/src/fn_call.rs b/src/fn_call.rs index 6672d746..91c8e67b 100644 --- a/src/fn_call.rs +++ b/src/fn_call.rs @@ -41,7 +41,7 @@ use crate::stdlib::{ format, iter::{empty, once}, mem, - string::ToString, + string::{String, ToString}, vec::Vec, }; @@ -67,43 +67,69 @@ fn extract_prop_from_setter(_fn_name: &str) -> Option<&str> { None } -/// This function replaces the first argument of a method call with a clone copy. -/// This is to prevent a pure function unintentionally consuming the first argument. -fn normalize_first_arg<'a>( - normalize: bool, - this_copy: &mut Dynamic, - old_this_ptr: &mut Option<&'a mut Dynamic>, - args: &mut FnCallArgs<'a>, -) { - // Only do it for method calls with arguments. - if !normalize || args.is_empty() { - return; - } - - // Clone the original value. - *this_copy = args[0].clone(); - - // Replace the first reference with a reference to the clone, force-casting the lifetime. - // Keep the original reference. Must remember to restore it later with `restore_first_arg_of_method_call`. - // - // # Safety - // - // Blindly casting a a reference to another lifetime saves on allocations and string cloning, - // but must be used with the utmost care. - // - // We can do this here because, at the end of this scope, we'd restore the original reference - // with `restore_first_arg_of_method_call`. Therefore this shorter lifetime does not get "out". - let this_pointer = mem::replace(args.get_mut(0).unwrap(), unsafe { - mem::transmute(this_copy) - }); - - *old_this_ptr = Some(this_pointer); +/// A type that temporarily stores a mutable reference to a `Dynamic`, +/// replacing it with a cloned copy. +#[derive(Debug, Default)] +struct ArgBackup<'a> { + orig_mut: Option<&'a mut Dynamic>, + value_copy: Dynamic, } -/// This function restores the first argument that was replaced by `normalize_first_arg_of_method_call`. -fn restore_first_arg<'a>(old_this_ptr: Option<&'a mut Dynamic>, args: &mut FnCallArgs<'a>) { - if let Some(this_pointer) = old_this_ptr { - args[0] = this_pointer; +impl<'a> ArgBackup<'a> { + /// This function replaces the first argument of a method call with a clone copy. + /// This is to prevent a pure function unintentionally consuming the first argument. + /// + /// `restore_first_arg` must be called before the end of the scope to prevent the shorter lifetime from leaking. + /// + /// # Safety + /// + /// This method blindly casts a reference to another lifetime, which saves allocation and string cloning. + /// + /// If `restore_first_arg` is called before the end of the scope, the shorter lifetime will not leak. + fn change_first_arg_to_copy(&mut self, normalize: bool, args: &mut FnCallArgs<'a>) { + // Only do it for method calls with arguments. + if !normalize || args.is_empty() { + return; + } + + // Clone the original value. + self.value_copy = args[0].clone(); + + // Replace the first reference with a reference to the clone, force-casting the lifetime. + // Must remember to restore it later with `restore_first_arg`. + // + // # Safety + // + // Blindly casting a reference to another lifetime saves allocation and string cloning, + // but must be used with the utmost care. + // + // We can do this here because, before the end of this scope, we'd restore the original reference + // via `restore_first_arg`. Therefore this shorter lifetime does not leak. + self.orig_mut = Some(mem::replace(args.get_mut(0).unwrap(), unsafe { + mem::transmute(&mut self.value_copy) + })); + } + + /// This function restores the first argument that was replaced by `change_first_arg_to_copy`. + /// + /// # Safety + /// + /// If `change_first_arg_to_copy` has been called, this function **MUST** be called _BEFORE_ exiting + /// the current scope. Otherwise it is undefined behavior as the shorter lifetime will leak. + fn restore_first_arg(&mut self, args: &mut FnCallArgs<'a>) { + if let Some(this_pointer) = self.orig_mut.take() { + args[0] = this_pointer; + } + } +} + +impl Drop for ArgBackup<'_> { + fn drop(&mut self) { + // Panic if the shorter lifetime leaks. + assert!( + self.orig_mut.is_none(), + "MutBackup::restore has not been called prior to existing this scope" + ); } } @@ -138,34 +164,21 @@ impl Engine { /// Function call arguments be _consumed_ when the function requires them to be passed by value. /// All function arguments not in the first position are always passed by value and thus consumed. /// **DO NOT** reuse the argument values unless for the first `&mut` argument - all others are silently replaced by `()`! - pub(crate) fn call_fn_raw( + pub(crate) fn call_native_fn( &self, - _scope: &mut Scope, - _mods: &mut Imports, state: &mut State, lib: &Module, fn_name: &str, hash_fn: u64, args: &mut FnCallArgs, is_ref: bool, - _is_method: bool, pub_only: bool, def_val: Option, - _level: usize, ) -> Result<(Dynamic, bool), Box> { self.inc_operations(state)?; - // Check for stack overflow - #[cfg(not(feature = "no_function"))] - #[cfg(not(feature = "unchecked"))] - if _level > self.limits.max_call_stack_depth { - return Err(Box::new( - EvalAltResult::ErrorStackOverflow(Position::none()), - )); - } - - // Search for the function - // First search registered native functions (can override packages) + // Search for the native function + // First search registered functions (can override packages) // Then search packages let func = self .global_module @@ -173,22 +186,19 @@ impl Engine { .or_else(|| self.packages.get_fn(hash_fn, pub_only)); if let Some(func) = func { - #[cfg(not(feature = "no_function"))] - let need_normalize = is_ref && (func.is_pure() || (func.is_script() && !_is_method)); - #[cfg(feature = "no_function")] - let need_normalize = is_ref && func.is_pure(); - - let mut this_copy: Dynamic = Default::default(); - let mut old_this_ptr: Option<&mut Dynamic> = None; + assert!(func.is_native()); // Calling pure function but the first argument is a reference? - normalize_first_arg(need_normalize, &mut this_copy, &mut old_this_ptr, args); + let mut backup: ArgBackup = Default::default(); + backup.change_first_arg_to_copy(is_ref && func.is_pure(), args); // Run external function - let result = func.get_native_fn()(self, lib, args)?; + let result = func.get_native_fn()(self, lib, args); // Restore the original reference - restore_first_arg(old_this_ptr, args); + backup.restore_first_arg(args); + + let result = result?; // See if the function match print/debug (which requires special processing) return Ok(match fn_name { @@ -320,6 +330,17 @@ impl Engine { args: &mut FnCallArgs, level: usize, ) -> Result> { + self.inc_operations(state)?; + + // Check for stack overflow + #[cfg(not(feature = "no_function"))] + #[cfg(not(feature = "unchecked"))] + if level > self.limits.max_call_stack_depth { + return Err(Box::new( + EvalAltResult::ErrorStackOverflow(Position::none()), + )); + } + let orig_scope_level = state.scope_level; state.scope_level += 1; @@ -382,7 +403,7 @@ impl Engine { || self.packages.contains_fn(hash_fn, pub_only) } - /// Perform an actual function call, taking care of special functions + /// Perform an actual function call, native Rust or scripted, taking care of special functions. /// Position in `EvalAltResult` is `None` and must be set afterwards. /// /// ## WARNING @@ -470,33 +491,26 @@ impl Engine { )? } else { // Normal call of script function - map first argument to `this` - let mut first_copy: Dynamic = Default::default(); - let mut old_first: Option<&mut Dynamic> = None; - // The first argument is a reference? - normalize_first_arg(is_ref, &mut first_copy, &mut old_first, args); + let mut backup: ArgBackup = Default::default(); + backup.change_first_arg_to_copy(is_ref, args); let result = self.call_script_fn( scope, mods, state, lib, &mut None, fn_name, func, args, level, - )?; + ); // Restore the original reference - restore_first_arg(old_first, args); + backup.restore_first_arg(args); - result + result? }; Ok((result, false)) } // Normal native function call - _ => { - let mut scope = Scope::new(); - let mut mods = Imports::new(); - self.call_fn_raw( - &mut scope, &mut mods, state, lib, fn_name, hash_fn, args, is_ref, is_method, - pub_only, def_val, level, - ) - } + _ => self.call_native_fn( + state, lib, fn_name, hash_fn, args, is_ref, pub_only, def_val, + ), } } @@ -508,9 +522,21 @@ impl Engine { mods: &mut Imports, state: &mut State, lib: &Module, - script: &Dynamic, + script_expr: &Dynamic, + level: usize, ) -> Result> { - let script = script.as_str().map_err(|typ| { + self.inc_operations(state)?; + + // Check for stack overflow + #[cfg(not(feature = "no_function"))] + #[cfg(not(feature = "unchecked"))] + if level > self.limits.max_call_stack_depth { + return Err(Box::new( + EvalAltResult::ErrorStackOverflow(Position::none()), + )); + } + + let script = script_expr.as_str().map_err(|typ| { EvalAltResult::ErrorMismatchOutputType( self.map_type_name(type_name::()).into(), typ.into(), @@ -782,12 +808,12 @@ impl Engine { let expr = args_expr.get(0).unwrap(); let script = self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)?; let result = self - .eval_script_expr(scope, mods, state, lib, &script) + .eval_script_expr(scope, mods, state, lib, &script, level + 1) .map_err(|err| err.new_position(expr.position())); + // IMPORTANT! If the eval defines new variables in the current scope, + // all variable offsets from this point on will be mis-aligned. if scope.len() != prev_len { - // IMPORTANT! If the eval defines new variables in the current scope, - // all variable offsets from this point on will be mis-aligned. state.always_search = true; } diff --git a/src/fn_native.rs b/src/fn_native.rs index f6bb962e..064b82fa 100644 --- a/src/fn_native.rs +++ b/src/fn_native.rs @@ -280,6 +280,16 @@ impl CallableFunction { Self::Pure(_) | Self::Method(_) | Self::Iterator(_) => false, } } + /// Is this a native Rust function? + pub fn is_native(&self) -> bool { + match self { + Self::Pure(_) | Self::Method(_) => true, + Self::Iterator(_) => true, + + #[cfg(not(feature = "no_function"))] + Self::Script(_) => false, + } + } /// Get the access mode. pub fn access(&self) -> FnAccess { match self { diff --git a/src/optimize.rs b/src/optimize.rs index 7c71aced..3244540b 100644 --- a/src/optimize.rs +++ b/src/optimize.rs @@ -3,7 +3,7 @@ use crate::any::Dynamic; use crate::calc_fn_hash; use crate::engine::{ - Engine, Imports, KEYWORD_DEBUG, KEYWORD_EVAL, KEYWORD_FN_PTR, KEYWORD_PRINT, KEYWORD_TYPE_OF, + Engine, KEYWORD_DEBUG, KEYWORD_EVAL, KEYWORD_FN_PTR, KEYWORD_PRINT, KEYWORD_TYPE_OF, }; use crate::fn_native::FnPtr; use crate::module::Module; @@ -132,22 +132,18 @@ fn call_fn_with_constant_arguments( state .engine - .call_fn_raw( - &mut Scope::new(), - &mut Imports::new(), + .call_native_fn( &mut Default::default(), state.lib, fn_name, hash_fn, arg_values.iter_mut().collect::>().as_mut(), false, - false, true, None, - 0, ) - .map(|(v, _)| Some(v)) - .unwrap_or_else(|_| None) + .ok() + .map(|(v, _)| v) } /// Optimize a statement. From 49392d57d7d3dfe1cbb105c0c8ac0f2b8eee71c0 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Fri, 31 Jul 2020 12:40:16 +0800 Subject: [PATCH 10/40] Fix no_std feature. --- src/fn_call.rs | 4 ++-- src/scope.rs | 1 - 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/fn_call.rs b/src/fn_call.rs index 91c8e67b..da029c78 100644 --- a/src/fn_call.rs +++ b/src/fn_call.rs @@ -523,14 +523,14 @@ impl Engine { state: &mut State, lib: &Module, script_expr: &Dynamic, - level: usize, + _level: usize, ) -> Result> { self.inc_operations(state)?; // Check for stack overflow #[cfg(not(feature = "no_function"))] #[cfg(not(feature = "unchecked"))] - if level > self.limits.max_call_stack_depth { + if _level > self.limits.max_call_stack_depth { return Err(Box::new( EvalAltResult::ErrorStackOverflow(Position::none()), )); diff --git a/src/scope.rs b/src/scope.rs index b2024564..a167a216 100644 --- a/src/scope.rs +++ b/src/scope.rs @@ -407,7 +407,6 @@ impl<'a> Scope<'a> { } /// Get an iterator to entries in the Scope. - #[cfg(not(feature = "no_module"))] pub(crate) fn into_iter(self) -> impl Iterator> { self.0.into_iter() } From ca64668e583ec7b34377a1db5a11294c5c2570ff Mon Sep 17 00:00:00 2001 From: Ilya Lakhin Date: Fri, 31 Jul 2020 11:41:22 +0700 Subject: [PATCH 11/40] take() keyword; shared test with registered functions with callbacks --- Cargo.toml | 2 +- src/any.rs | 6 ++++-- src/engine.rs | 1 + src/fn_call.rs | 5 ++++- src/token.rs | 9 +++++---- tests/closures.rs | 48 ++++++++++++++++++++++++++++++++++++++++++++--- 6 files changed, 60 insertions(+), 11 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 85b57974..0cc0e20d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -34,7 +34,7 @@ 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 explicit shared variables in the script code +no_shared = [] # no explicit shared() and take() functions in the script code no_module = [] # no modules internals = [] # expose internal data structures unicode-xid-ident = ["unicode-xid"] # allow Unicode Standard Annex #31 for identifiers. diff --git a/src/any.rs b/src/any.rs index db2ada1b..1a82d237 100644 --- a/src/any.rs +++ b/src/any.rs @@ -568,7 +568,7 @@ impl Dynamic { /// /// ## Safety /// - /// Both situations normally shouldn't happen since all operations in Rhai + /// Both situations normally shouldn't happen since most operations in Rhai /// use pass-by-value data and the script executed in a single thread. /// /// # Example @@ -741,7 +741,9 @@ impl Dynamic { } /// Get a copy of a specific type to the `Dynamic`. - /// Casting to `Dynamic` just returns a reference to it. + /// Casting to `Dynamic` returns a clone of the value in case of NON-shared + /// Dynamic. In case of Shared Dynamic returns a clone of the inner data of + /// Shared Dynamic. /// Returns `None` if the cast fails. #[inline(always)] pub fn read(&self) -> Option { diff --git a/src/engine.rs b/src/engine.rs index 8f727211..4cc3b266 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -93,6 +93,7 @@ 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_THIS: &str = "this"; pub const FN_TO_STRING: &str = "to_string"; #[cfg(not(feature = "no_object"))] diff --git a/src/fn_call.rs b/src/fn_call.rs index 19f3fd33..6f82df42 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_PRINT, - KEYWORD_TYPE_OF, KEYWORD_SHARED + KEYWORD_TYPE_OF, KEYWORD_SHARED, KEYWORD_TAKE }; use crate::error::ParseErrorType; use crate::fn_native::{FnCallArgs, FnPtr}; @@ -593,6 +593,9 @@ impl Engine { .into(), false, )) + } else if _fn_name == KEYWORD_TAKE { + // take call + return Ok((obj.read::().unwrap(), false)); } else { #[cfg(not(feature = "no_object"))] let redirected; diff --git a/src/token.rs b/src/token.rs index 71ec09a6..b0c77e38 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_SHARED, KEYWORD_PRINT, KEYWORD_THIS, KEYWORD_TYPE_OF, + KEYWORD_SHARED, KEYWORD_TAKE, KEYWORD_PRINT, KEYWORD_THIS, KEYWORD_TYPE_OF, }; use crate::error::LexError; @@ -503,11 +503,12 @@ impl Token { "===" | "!==" | "->" | "<-" | "=>" | ":=" | "::<" | "(*" | "*)" | "#" | "public" | "new" | "use" | "module" | "package" | "var" | "static" | "with" | "do" | "each" | "then" | "goto" | "exit" | "switch" | "match" | "case" | "try" | "catch" - | "default" | "void" | "null" | "nil" | "spawn" | "go" | "shared" | "sync" + | "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_SHARED | KEYWORD_THIS => Reserved(syntax.into()), + | KEYWORD_FN_PTR_CALL | KEYWORD_FN_PTR_CURRY | KEYWORD_SHARED + | KEYWORD_TAKE |KEYWORD_THIS => Reserved(syntax.into()), _ => return None, }) @@ -1440,7 +1441,7 @@ pub fn is_keyword_function(name: &str) -> bool { #[cfg(not(feature = "no-shared"))] { - result = result || name == KEYWORD_SHARED; + result = result || name == KEYWORD_SHARED || name == KEYWORD_TAKE; } result diff --git a/tests/closures.rs b/tests/closures.rs index 25166ee1..6198bc26 100644 --- a/tests/closures.rs +++ b/tests/closures.rs @@ -116,8 +116,8 @@ fn test_shared() -> Result<(), Box> { let s = shared("test"); let i = shared(0); i = 2; - s[i] = 'S'; + s "# )?, @@ -283,6 +283,26 @@ fn test_shared() -> Result<(), Box> { engine.register_fn("update", TestStruct::update); engine.register_fn("merge", TestStruct::merge); engine.register_fn("new_ts", TestStruct::new); + engine. + 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::( @@ -291,11 +311,33 @@ fn test_shared() -> Result<(), Box> { a.x = 100; a.update(); - // a.merge(a); + a.merge(a.take()); // take is important to prevent a deadlock + a.x " )?, - 1100 + 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(()) From 4f771d904ab5dc944abba237500c90bacd2208b4 Mon Sep 17 00:00:00 2001 From: Ilya Lakhin Date: Fri, 31 Jul 2020 12:08:14 +0700 Subject: [PATCH 12/40] Code cleanup --- src/fn_call.rs | 4 +- src/fn_native.rs | 2 +- src/fn_register.rs | 2 +- tests/closures.rs | 184 ++++++++++++++++++++++++--------------------- 4 files changed, 101 insertions(+), 91 deletions(-) diff --git a/src/fn_call.rs b/src/fn_call.rs index 6f82df42..dd56b88d 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_PRINT, - KEYWORD_TYPE_OF, KEYWORD_SHARED, KEYWORD_TAKE + KEYWORD_TYPE_OF, KEYWORD_SHARED, }; use crate::error::ParseErrorType; use crate::fn_native::{FnCallArgs, FnPtr}; @@ -31,7 +31,7 @@ use crate::parser::FLOAT; use crate::engine::{FN_IDX_GET, FN_IDX_SET}; #[cfg(not(feature = "no_object"))] -use crate::engine::{Map, Target, FN_GET, FN_SET}; +use crate::engine::{Map, Target, FN_GET, FN_SET, KEYWORD_TAKE}; use crate::stdlib::{ any::{type_name, TypeId}, diff --git a/src/fn_native.rs b/src/fn_native.rs index 54f3da08..de926a47 100644 --- a/src/fn_native.rs +++ b/src/fn_native.rs @@ -1,6 +1,6 @@ //! Module defining interfaces to native-Rust functions. -use crate::any::{Dynamic, Variant}; +use crate::any::Dynamic; use crate::calc_fn_hash; use crate::engine::Engine; use crate::module::Module; diff --git a/src/fn_register.rs b/src/fn_register.rs index eef4e66b..e01f44a1 100644 --- a/src/fn_register.rs +++ b/src/fn_register.rs @@ -15,7 +15,7 @@ use crate::stdlib::{ any::TypeId, boxed::Box, mem, - string::{String, ToString}, + string::String, }; /// Trait to register custom functions with the `Engine`. diff --git a/tests/closures.rs b/tests/closures.rs index 6198bc26..4f8c16dd 100644 --- a/tests/closures.rs +++ b/tests/closures.rs @@ -1,6 +1,12 @@ #![cfg(not(feature = "no_function"))] -use rhai::{Dynamic, Engine, EvalAltResult, RegisterFn, FnPtr, Module, INT, Array}; -use std::any::{TypeId, Any}; +use rhai::{Dynamic, Engine, EvalAltResult, FnPtr, Module, INT}; +use std::any::TypeId; + +#[cfg(not(feature = "no_shared"))] +use rhai::RegisterFn; + +#[cfg(not(feature = "no_index"))] +use rhai::Array; #[test] fn test_fn_ptr_curry_call() -> Result<(), Box> { @@ -35,7 +41,7 @@ fn test_fn_ptr_curry_call() -> Result<(), Box> { } #[test] -#[cfg(not(feature = "no_capture"))] +#[cfg(all(not(feature = "no_capture"), not(feature = "no_object")))] fn test_closures() -> Result<(), Box> { let engine = Engine::new(); @@ -110,9 +116,11 @@ fn test_shared() -> Result<(), Box> { 'x' ); - assert_eq!( - engine.eval::( - r#" + #[cfg(not(feature = "no_index"))] + { + assert_eq!( + engine.eval::( + r#" let s = shared("test"); let i = shared(0); i = 2; @@ -120,12 +128,10 @@ fn test_shared() -> Result<(), Box> { s "# - )?, - "teSt" - ); + )?, + "teSt" + ); - #[cfg(not(feature = "no_index"))] - { assert_eq!( engine.eval::( r#" @@ -137,6 +143,7 @@ fn test_shared() -> Result<(), Box> { 5 ); + #[cfg(not(feature = "no_object"))] assert_eq!( engine.eval::( r" @@ -170,6 +177,7 @@ fn test_shared() -> Result<(), Box> { true ); + #[cfg(not(feature = "no_object"))] assert_eq!( engine.eval::( r#" @@ -250,95 +258,97 @@ fn test_shared() -> Result<(), Box> { 42 ); - #[derive(Clone)] - struct TestStruct { - x: INT, - } - - impl TestStruct { - fn update(&mut self) { - self.x += 1000; + #[cfg(not(feature = "no_object"))] + { + #[derive(Clone)] + struct TestStruct { + x: INT, } - fn merge(&mut self, other: Self) { - self.x += other.x; + 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 } + } } - fn get_x(&mut self) -> INT { - self.x - } + 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(); - fn set_x(&mut self, new_x: INT) { - self.x = new_x; - } + fp.call_dynamic(engine, lib, Some(this_ptr), [value]) + }, + ); - fn new() -> Self { - TestStruct { x: 1 } - } - } + assert_eq!( + engine.eval::( + r" + let a = shared(new_ts()); - engine.register_type::(); + a.x = 100; + a.update(); + a.merge(a.take()); // take is important to prevent a deadlock - engine.register_get_set("x", TestStruct::get_x, TestStruct::set_x); - engine.register_fn("update", TestStruct::update); - engine.register_fn("merge", TestStruct::merge); - engine.register_fn("new_ts", TestStruct::new); - engine. - 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]) - }, + a.x + " + )?, + 2200 ); - assert_eq!( - engine.eval::( - r" - let a = shared(new_ts()); + assert_eq!( + engine.eval::( + r" + let a = shared(new_ts()); + let b = shared(100); - a.x = 100; - a.update(); - a.merge(a.take()); // take is important to prevent a deadlock + a.mutate_with_cb(b, |param| { + this.x = param; + param = 50; + this.update(); + }); - a.x - " - )?, - 2200 - ); + a.update(); + a.x += b; - 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 - ); + a.x + " + )?, + 2151 + ); + } Ok(()) } From 5d1f5cc2b41286b03fae08ae2cf11c8fef3527c8 Mon Sep 17 00:00:00 2001 From: Ilya Lakhin Date: Fri, 31 Jul 2020 13:10:05 +0700 Subject: [PATCH 13/40] Dynamic::read renamed to Dynamic::clone_inner_data --- src/any.rs | 88 +++++++++++++++++++++++++------------------------- src/fn_call.rs | 2 +- src/scope.rs | 2 +- 3 files changed, 46 insertions(+), 46 deletions(-) diff --git a/src/any.rs b/src/any.rs index 1a82d237..cbb3e444 100644 --- a/src/any.rs +++ b/src/any.rs @@ -709,6 +709,46 @@ impl Dynamic { self.try_cast::().unwrap() } + /// Get a copy of a specific type to the `Dynamic`. + /// Casting to `Dynamic` returns a clone of the value in case of NON-shared + /// Dynamic. In case of Shared Dynamic returns a clone of the inner data of + /// Shared Dynamic. + /// Returns `None` if the cast fails. + #[inline(always)] + pub fn clone_inner_data(&self) -> Option { + match self.0 { + Union::Shared(ref cell) => { + let type_id = TypeId::of::(); + + if type_id != TypeId::of::() && cell.value_type_id != type_id { + return None + } + + #[cfg(not(feature = "sync"))] + return Some(cell + .container + .borrow() + .deref() + .downcast_ref::() + .unwrap() + .clone()); + + #[cfg(feature = "sync")] + return Some(cell + .container + .read() + .unwrap() + .deref() + .downcast_ref::() + .unwrap() + .clone()); + }, + _ => { + self.downcast_ref().cloned() + } + } + } + /// Get a reference of a specific type to the `Dynamic`. /// Casting to `Dynamic` just returns a reference to it. /// Returns `None` if the cast fails. @@ -740,46 +780,6 @@ impl Dynamic { } } - /// Get a copy of a specific type to the `Dynamic`. - /// Casting to `Dynamic` returns a clone of the value in case of NON-shared - /// Dynamic. In case of Shared Dynamic returns a clone of the inner data of - /// Shared Dynamic. - /// Returns `None` if the cast fails. - #[inline(always)] - pub fn read(&self) -> Option { - match self.0 { - Union::Shared(ref cell) => { - let type_id = TypeId::of::(); - - if type_id != TypeId::of::() && cell.value_type_id != type_id { - return None - } - - #[cfg(not(feature = "sync"))] - return Some(cell - .container - .borrow() - .deref() - .downcast_ref::() - .unwrap() - .clone()); - - #[cfg(feature = "sync")] - return Some(cell - .container - .read() - .unwrap() - .deref() - .downcast_ref::() - .unwrap() - .clone()); - }, - _ => { - self.downcast_ref().cloned() - } - } - } - /// Get a mutable reference of a specific type to the `Dynamic`. /// Casting to `Dynamic` just returns a mutable reference to it. /// Returns `None` if the cast fails. @@ -964,7 +964,7 @@ impl Dynamic { pub fn as_int(&self) -> Result { match self.0 { Union::Int(n) => Ok(n), - Union::Shared(_) => self.read::().ok_or_else(|| self.type_name()), + Union::Shared(_) => self.clone_inner_data::().ok_or_else(|| self.type_name()), _ => Err(self.type_name()), } } @@ -975,7 +975,7 @@ impl Dynamic { pub fn as_float(&self) -> Result { match self.0 { Union::Float(n) => Ok(n), - Union::Shared(_) => self.read::().ok_or_else(|| self.type_name()), + Union::Shared(_) => self.clone_inner_data::().ok_or_else(|| self.type_name()), _ => Err(self.type_name()), } } @@ -985,7 +985,7 @@ impl Dynamic { pub fn as_bool(&self) -> Result { match self.0 { Union::Bool(b) => Ok(b), - Union::Shared(_) => self.read::().ok_or_else(|| self.type_name()), + Union::Shared(_) => self.clone_inner_data::().ok_or_else(|| self.type_name()), _ => Err(self.type_name()), } } @@ -995,7 +995,7 @@ impl Dynamic { pub fn as_char(&self) -> Result { match self.0 { Union::Char(n) => Ok(n), - Union::Shared(_) => self.read::().ok_or_else(|| self.type_name()), + Union::Shared(_) => self.clone_inner_data::().ok_or_else(|| self.type_name()), _ => Err(self.type_name()), } } diff --git a/src/fn_call.rs b/src/fn_call.rs index e0dd1184..bed49230 100644 --- a/src/fn_call.rs +++ b/src/fn_call.rs @@ -658,7 +658,7 @@ impl Engine { )) } else if _fn_name == KEYWORD_TAKE { // take call - return Ok((obj.read::().unwrap(), false)); + return Ok((obj.clone_inner_data::().unwrap(), false)); } else { #[cfg(not(feature = "no_object"))] let redirected; diff --git a/src/scope.rs b/src/scope.rs index 2fb3df21..b1b9a3b3 100644 --- a/src/scope.rs +++ b/src/scope.rs @@ -340,7 +340,7 @@ impl<'a> Scope<'a> { /// ``` pub fn get_value(&self, name: &str) -> Option { self.get_entry(name) - .and_then(|Entry { value, .. }| value.read::()) + .and_then(|Entry { value, .. }| value.clone_inner_data::()) } /// Update the value of the named entry. From 871fcb38be48153c3970d8968e2726560ddcb599 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Fri, 31 Jul 2020 16:03:08 +0800 Subject: [PATCH 14/40] Minor style changes and make sure no_shared works on all. --- .github/workflows/build.yml | 2 + Cargo.toml | 6 +- src/any.rs | 276 +++++++++++++++++++++--------------- src/engine.rs | 85 ++++++----- src/fn_call.rs | 8 +- src/fn_native.rs | 25 +++- src/token.rs | 12 +- 7 files changed, 246 insertions(+), 168 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 1370e03e..cd10a5a6 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -29,6 +29,8 @@ jobs: - "--features no_object" - "--features no_function" - "--features no_module" + - "--features no_capture" + - "--features no_shared" - "--features unicode-xid-ident" toolchain: [stable] experimental: [false] diff --git a/Cargo.toml b/Cargo.toml index 0cc0e20d..13ef119f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,7 +22,7 @@ smallvec = { version = "1.4.1", default-features = false } [features] #default = ["unchecked", "sync", "no_optimize", "no_float", "only_i32", "no_index", "no_object", "no_function", "no_module"] -default = [] +default = [ "no_shared"] plugins = [] unchecked = [] # unchecked arithmetic sync = [] # restrict to only types that implement Send + Sync @@ -34,13 +34,13 @@ 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 explicit shared() and take() functions in the script code +no_shared = [] # no shared values 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 = [ "num-traits/libm", "hashbrown", "core-error", "libm", "ahash" ] +no_std = [ "no_shared", "num-traits/libm", "hashbrown", "core-error", "libm", "ahash" ] [profile.release] lto = "fat" diff --git a/src/any.rs b/src/any.rs index cbb3e444..470d86c7 100644 --- a/src/any.rs +++ b/src/any.rs @@ -1,9 +1,12 @@ //! Helper module which defines the `Any` trait to to allow dynamic value handling. -use crate::fn_native::{FnPtr, SendSync, SharedMut}; +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"))] +use crate::fn_native::SharedMut; + #[cfg(not(feature = "no_float"))] use crate::parser::FLOAT; @@ -17,13 +20,18 @@ use crate::stdlib::{ any::{type_name, Any, TypeId}, boxed::Box, fmt, + ops::{Deref, DerefMut}, string::String, - ops::{DerefMut, Deref} }; +#[cfg(not(feature = "no_shared"))] #[cfg(not(feature = "sync"))] -use crate::stdlib::{rc::Rc, cell::{RefCell, Ref, RefMut}}; +use crate::stdlib::{ + cell::{Ref, RefCell, RefMut}, + rc::Rc, +}; +#[cfg(not(feature = "no_shared"))] #[cfg(feature = "sync")] use crate::stdlib::sync::{Arc, RwLock, RwLockReadGuard, RwLockWriteGuard}; @@ -151,32 +159,39 @@ pub enum Union { Map(Box), FnPtr(Box), Variant(Box>), + #[cfg(not(feature = "no_shared"))] Shared(Box), } /// Internal Shared Dynamic representation. /// /// Created with `Dynamic::into_shared()`. +#[cfg(not(feature = "no_shared"))] #[derive(Clone)] pub struct SharedCell { value_type_id: TypeId, value_type_name: &'static str, - container: SharedMut + container: SharedMut, } -/// Dynamic's underlying `Variant` read guard that supports `Deref` for Variant's -/// reference reading. +/// Underlying `Variant` read guard for `Dynamic`. /// -/// This data structure provides transparent interoperability between normal -/// `Dynamic` types and Shared Dynamic references. +/// This data structure provides transparent interoperability between +/// normal `Dynamic` and shared Dynamic values. #[derive(Debug)] pub struct DynamicReadLock<'d, T: Variant + Clone>(DynamicReadLockInner<'d, T>); +/// Different types of read guards for `DynamicReadLock`. #[derive(Debug)] 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 = "sync"))] Guard(Ref<'d, Dynamic>), + /// A read guard to a shared `RwLock`. + #[cfg(not(feature = "no_shared"))] #[cfg(feature = "sync")] Guard(RwLockReadGuard<'d, Dynamic>), } @@ -188,25 +203,31 @@ impl<'d, T: Variant + Clone> Deref for DynamicReadLock<'d, T> { fn deref(&self) -> &Self::Target { match &self.0 { DynamicReadLockInner::Reference(reference) => reference.deref(), - // unwrapping is safe because all checks already done in it's constructor + // Unwrapping is safe because all checking is already done in its constructor + #[cfg(not(feature = "no_shared"))] DynamicReadLockInner::Guard(guard) => guard.downcast_ref().unwrap(), } } } -/// Dynamic's underlying `Variant` write guard that supports `Deref` and `DerefMut` -/// for Variant's reference reading/writing. +/// Underlying `Variant` write guard for `Dynamic`. /// -/// This data structure provides transparent interoperability between normal -/// `Dynamic` types and Shared Dynamic references. +/// This data structure provides transparent interoperability between +/// normal `Dynamic` and shared Dynamic values. #[derive(Debug)] pub struct DynamicWriteLock<'d, T: Variant + Clone>(DynamicWriteLockInner<'d, T>); +/// Different types of write guards for `DynamicReadLock`. #[derive(Debug)] 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 = "sync"))] Guard(RefMut<'d, Dynamic>), + /// A write guard to a shared `RwLock`. + #[cfg(not(feature = "no_shared"))] #[cfg(feature = "sync")] Guard(RwLockWriteGuard<'d, Dynamic>), } @@ -218,7 +239,8 @@ impl<'d, T: Variant + Clone> Deref for DynamicWriteLock<'d, T> { fn deref(&self) -> &Self::Target { match &self.0 { DynamicWriteLockInner::Reference(reference) => reference.deref(), - // unwrapping is safe because all checks already done in it's constructor + // Unwrapping is safe because all checking is already done in its constructor + #[cfg(not(feature = "no_shared"))] DynamicWriteLockInner::Guard(guard) => guard.downcast_ref().unwrap(), } } @@ -229,7 +251,8 @@ impl<'d, T: Variant + Clone> DerefMut for DynamicWriteLock<'d, T> { fn deref_mut(&mut self) -> &mut Self::Target { match &mut self.0 { DynamicWriteLockInner::Reference(reference) => reference.deref_mut(), - // unwrapping is safe because all checks already done in it's constructor + // Unwrapping is safe because all checking is already done in its constructor + #[cfg(not(feature = "no_shared"))] DynamicWriteLockInner::Guard(guard) => guard.downcast_mut().unwrap(), } } @@ -247,6 +270,7 @@ impl Dynamic { /// Does this `Dynamic` hold a shared data type /// instead of one of the support system primitive types? + #[cfg(not(feature = "no_shared"))] pub fn is_shared(&self) -> bool { match self.0 { Union::Shared(_) => true, @@ -284,6 +308,7 @@ impl Dynamic { Union::Map(_) => TypeId::of::(), Union::FnPtr(_) => TypeId::of::(), Union::Variant(value) => (***value).type_id(), + #[cfg(not(feature = "no_shared"))] Union::Shared(cell) => (**cell).value_type_id, } } @@ -307,6 +332,7 @@ 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"))] Union::Shared(cell) => (**cell).value_type_name, } } @@ -363,7 +389,8 @@ 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()), - Union::Shared(cell) => write!(f, "{}", (**cell).value_type_name), + #[cfg(not(feature = "no_shared"))] + Union::Shared(cell) => write!(f, "", (**cell).value_type_name), } } } @@ -390,7 +417,8 @@ 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()), - Union::Shared(cell) => write!(f, "{}", (**cell).value_type_name), + #[cfg(not(feature = "no_shared"))] + Union::Shared(cell) => write!(f, "", (**cell).value_type_name), } } } @@ -411,7 +439,8 @@ 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(), - Union::Shared(ref cell) => Self(Union::Shared(Box::new((**cell).clone()))) + #[cfg(not(feature = "no_shared"))] + Union::Shared(ref cell) => Self(Union::Shared(Box::new((**cell).clone()))), } } } @@ -523,53 +552,42 @@ impl Dynamic { Self(Union::Variant(Box::new(boxed))) } - /// Turns Dynamic into Shared Dynamic variant backed by runtime - /// mutable reference-counter container(`Arc>` or - /// `Rc` depending on `sync` feature). + /// Turn the `Dynamic` value into a shared `Dynamic` value backed by an `Rc>` + /// or `Arc>` depending on the `sync` feature. /// - /// Instances of Shared Dynamic are relatively cheap to clone. All clones of - /// Shared Dynamic references the same chunk of memory. + /// Shared `Dynamic` values are relatively cheap to clone as they simply increment the + /// reference counts. /// - /// The Dynamic is capable to work transparently with the it's inner - /// data, seamlessly casting between ordinary instances of Dynamic and - /// Shared Dynamic instances. + /// Shared `Dynamic` values can be converted seamlessly to and from ordinary `Dynamic` values. /// - /// If original value already a Shared variant returns identity. + /// If the `Dynamic` value is already shared, this method returns itself. + #[cfg(not(feature = "no_shared"))] pub fn into_shared(self) -> Self { match self.0 { Union::Shared(..) => self, - _ => { - let cell = SharedCell { - value_type_id: self.type_id(), - value_type_name: self.type_name(), - #[cfg(not(feature = "sync"))] - container: Rc::new(RefCell::new(self)), - #[cfg(feature = "sync")] - container: Arc::new(RwLock::new(self)), - }; + _ => Self(Union::Shared(Box::new(SharedCell { + value_type_id: self.type_id(), + value_type_name: self.type_name(), - Self(Union::Shared(Box::new(cell))) - }, + #[cfg(not(feature = "sync"))] + container: Rc::new(RefCell::new(self)), + #[cfg(feature = "sync")] + container: Arc::new(RwLock::new(self)), + }))), } } - /// Get a copy of the `Dynamic` value as a specific type. + /// Convert the `Dynamic` value into specific type. /// Casting to a `Dynamic` just returns as is. /// - /// Returns None if types mismatched. + /// Returns `None` if types mismatched. /// - /// # Shared Dynamic + /// # Panics and Deadlocks /// - /// When accessing Shared Dynamic in sync mode(`sync` feature enabled) - /// can block current thread while the underlined data is being written. + /// Under the `sync` feature, this call may deadlock. + /// Otherwise, this call panics if the data is currently borrowed for write. /// - /// When accessing Shared Dynamic in NON-sync mode can **panic** if the data - /// is currently borrowed for write. - /// - /// ## Safety - /// - /// Both situations normally shouldn't happen since most operations in Rhai - /// use pass-by-value data and the script executed in a single thread. + /// These normally shouldn't occur since most operations in Rhai is single-threaded. /// /// # Example /// @@ -588,20 +606,6 @@ impl Dynamic { return unsafe_cast_box::<_, T>(Box::new(self)).ok().map(|v| *v); } - #[cfg(not(feature = "sync"))] - if let Union::Shared(cell) = self.0 { - let reference = cell.container.borrow(); - - return (*reference).clone().try_cast() - } - - #[cfg(feature = "sync")] - if let Union::Shared(cell) = self.0 { - let read_lock = cell.container.read().unwrap(); - - return (*read_lock).clone().try_cast() - } - if type_id == TypeId::of::() { return match self.0 { Union::Int(value) => unsafe_try_cast(value), @@ -677,23 +681,35 @@ 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 = "sync"))] + Union::Shared(cell) => return cell.container.borrow().deref().clone().try_cast(), + + #[cfg(not(feature = "no_shared"))] + #[cfg(feature = "sync")] + Union::Shared(cell) => { + return cell.container.read().unwrap().deref().clone().try_cast() + } + _ => None, } } - /// Get a copy of the `Dynamic` value as a specific type. + /// Convert the `Dynamic` value into a specific type. /// Casting to a `Dynamic` just returns as is. /// - /// # Panics + /// Returns `None` if types mismatched. + /// + /// # Panics and Deadlocks /// /// Panics if the cast fails (e.g. the type of the actual value is not the - /// same as the specified type), or if the data held by Shared Dynamic is - /// currently borrowed(when the `sync` feature disabled). + /// same as the specified type). /// - /// # Notes + /// Under the `sync` feature, this call may deadlock. + /// Otherwise, this call panics if the data is currently borrowed for write. /// - /// If the `sync` feature enabled Shared Dynamic can block current thread - /// while the data being written. + /// These normally shouldn't occur since most operations in Rhai is single-threaded. /// /// # Example /// @@ -709,108 +725,115 @@ impl Dynamic { self.try_cast::().unwrap() } - /// Get a copy of a specific type to the `Dynamic`. - /// Casting to `Dynamic` returns a clone of the value in case of NON-shared - /// Dynamic. In case of Shared Dynamic returns a clone of the inner data of - /// Shared Dynamic. + /// Get a copy of the `Dynamic` as a specific type. + /// + /// If the `Dynamic` is not a shared value, it returns a cloned copy of the value. + /// + /// If the `Dynamic` is a shared value, it returns a cloned copy of the shared value. + /// /// Returns `None` if the cast fails. #[inline(always)] pub fn clone_inner_data(&self) -> Option { match self.0 { + #[cfg(not(feature = "no_shared"))] Union::Shared(ref cell) => { let type_id = TypeId::of::(); - if type_id != TypeId::of::() && cell.value_type_id != type_id { - return None + if type_id != TypeId::of::() && type_id != cell.value_type_id { + return None; } #[cfg(not(feature = "sync"))] - return Some(cell - .container - .borrow() - .deref() - .downcast_ref::() - .unwrap() - .clone()); + return Some( + cell.container + .borrow() + .deref() + .downcast_ref::() + .unwrap() + .clone(), + ); #[cfg(feature = "sync")] - return Some(cell - .container - .read() - .unwrap() - .deref() - .downcast_ref::() - .unwrap() - .clone()); - }, - _ => { - self.downcast_ref().cloned() + return Some( + cell.container + .read() + .unwrap() + .deref() + .downcast_ref::() + .unwrap() + .clone(), + ); } + _ => self.downcast_ref().cloned(), } } /// Get a reference of a specific type to the `Dynamic`. /// Casting to `Dynamic` just returns a reference to it. + /// /// Returns `None` if the cast fails. #[inline(always)] pub fn read_lock(&self) -> Option> { match self.0 { + #[cfg(not(feature = "no_shared"))] Union::Shared(ref cell) => { let type_id = TypeId::of::(); - if type_id != TypeId::of::() && cell.value_type_id != type_id { - return None + if type_id != TypeId::of::() && type_id != cell.value_type_id { + return None; } #[cfg(not(feature = "sync"))] return Some(DynamicReadLock(DynamicReadLockInner::Guard( - cell.container.borrow() + cell.container.borrow(), ))); #[cfg(feature = "sync")] return Some(DynamicReadLock(DynamicReadLockInner::Guard( - cell.container.read().unwrap() + cell.container.read().unwrap(), ))); - }, - _ => { - self.downcast_ref().map(|reference| { - DynamicReadLock(DynamicReadLockInner::Reference(reference)) - }) } + _ => self + .downcast_ref() + .map(|reference| DynamicReadLock(DynamicReadLockInner::Reference(reference))), } } /// Get a mutable reference of a specific type to the `Dynamic`. /// Casting to `Dynamic` just returns a mutable reference to it. + /// /// Returns `None` if the cast fails. #[inline(always)] pub fn write_lock(&mut self) -> Option> { match self.0 { + #[cfg(not(feature = "no_shared"))] Union::Shared(ref cell) => { let type_id = TypeId::of::(); if type_id != TypeId::of::() && cell.value_type_id != type_id { - return None + return None; } #[cfg(not(feature = "sync"))] return Some(DynamicWriteLock(DynamicWriteLockInner::Guard( - cell.container.borrow_mut() + cell.container.borrow_mut(), ))); #[cfg(feature = "sync")] return Some(DynamicWriteLock(DynamicWriteLockInner::Guard( - cell.container.write().unwrap() + cell.container.write().unwrap(), ))); - }, - _ => { - self.downcast_mut().map(|reference| { - DynamicWriteLock(DynamicWriteLockInner::Reference(reference)) - }) } + _ => self + .downcast_mut() + .map(|reference| DynamicWriteLock(DynamicWriteLockInner::Reference(reference))), } } + /// Get a reference of a specific type to the `Dynamic`. + /// Casting to `Dynamic` just returns a reference to it. + /// + /// Returns `None` if the cast fails. #[inline(always)] fn downcast_ref(&self) -> Option<&T> { let type_id = TypeId::of::(); @@ -884,10 +907,16 @@ impl Dynamic { match &self.0 { Union::Variant(value) => value.as_ref().as_ref().as_any().downcast_ref::(), + #[cfg(not(feature = "no_shared"))] + Union::Shared(_) => unreachable!(), _ => None, } } + /// Get a mutable reference of a specific type to the `Dynamic`. + /// Casting to `Dynamic` just returns a mutable reference to it. + /// + /// Returns `None` if the cast fails. #[inline(always)] fn downcast_mut(&mut self) -> Option<&mut T> { let type_id = TypeId::of::(); @@ -955,6 +984,8 @@ impl Dynamic { match &mut self.0 { Union::Variant(value) => value.as_mut().as_mut_any().downcast_mut::(), + #[cfg(not(feature = "no_shared"))] + Union::Shared(_) => unreachable!(), _ => None, } } @@ -964,7 +995,10 @@ impl Dynamic { pub fn as_int(&self) -> Result { match self.0 { Union::Int(n) => Ok(n), - Union::Shared(_) => self.clone_inner_data::().ok_or_else(|| self.type_name()), + #[cfg(not(feature = "no_shared"))] + Union::Shared(_) => self + .clone_inner_data::() + .ok_or_else(|| self.type_name()), _ => Err(self.type_name()), } } @@ -975,7 +1009,10 @@ impl Dynamic { pub fn as_float(&self) -> Result { match self.0 { Union::Float(n) => Ok(n), - Union::Shared(_) => self.clone_inner_data::().ok_or_else(|| self.type_name()), + #[cfg(not(feature = "no_shared"))] + Union::Shared(_) => self + .clone_inner_data::() + .ok_or_else(|| self.type_name()), _ => Err(self.type_name()), } } @@ -985,7 +1022,10 @@ impl Dynamic { pub fn as_bool(&self) -> Result { match self.0 { Union::Bool(b) => Ok(b), - Union::Shared(_) => self.clone_inner_data::().ok_or_else(|| self.type_name()), + #[cfg(not(feature = "no_shared"))] + Union::Shared(_) => self + .clone_inner_data::() + .ok_or_else(|| self.type_name()), _ => Err(self.type_name()), } } @@ -995,7 +1035,10 @@ impl Dynamic { pub fn as_char(&self) -> Result { match self.0 { Union::Char(n) => Ok(n), - Union::Shared(_) => self.clone_inner_data::().ok_or_else(|| self.type_name()), + #[cfg(not(feature = "no_shared"))] + Union::Shared(_) => self + .clone_inner_data::() + .ok_or_else(|| self.type_name()), _ => Err(self.type_name()), } } @@ -1025,6 +1068,7 @@ impl Dynamic { match self.0 { Union::Str(s) => Ok(s), Union::FnPtr(f) => Ok(f.take_data().0), + #[cfg(not(feature = "no_shared"))] Union::Shared(cell) => { #[cfg(not(feature = "sync"))] match &cell.container.borrow().deref().0 { diff --git a/src/engine.rs b/src/engine.rs index e083f84d..e3f5dad8 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -1,6 +1,6 @@ //! Main module defining the script evaluation `Engine`. -use crate::any::{map_std_type_name, Dynamic, Union, DynamicWriteLock}; +use crate::any::{map_std_type_name, Dynamic, DynamicWriteLock, Union}; use crate::calc_fn_hash; use crate::fn_call::run_builtin_op_assignment; use crate::fn_native::{CallableFunction, Callback, FnPtr}; @@ -37,9 +37,9 @@ use crate::stdlib::{ collections::{HashMap, HashSet}, fmt, format, iter::{empty, once}, + ops::DerefMut, string::{String, ToString}, vec::Vec, - ops::DerefMut, }; #[cfg(not(feature = "no_index"))] @@ -172,9 +172,9 @@ impl Target<'_> { /// Get the value of the `Target` as a `Dynamic`, cloning a referenced value if necessary. pub fn clone_into_dynamic(self) -> Dynamic { match self { - Self::Ref(r) => r.clone(), // Referenced value is cloned + Self::Ref(r) => r.clone(), // Referenced value is cloned Self::LockGuard((_, orig)) => orig, // Return original container of the Shared Dynamic - Self::Value(v) => v, // Owned value is simply taken + Self::Value(v) => v, // Owned value is simply taken #[cfg(not(feature = "no_index"))] Self::StringChar(_, _, ch) => ch, // Character is taken } @@ -229,15 +229,17 @@ 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"))] if value.is_shared() { - // clone is cheap since it holds Arc/Rw under the hood + // cloning is cheap since it holds Arc/Rw under the hood let container = value.clone(); - Self::LockGuard((value.write_lock::().unwrap(), container)) - } else { - Self::Ref(value) + return Self::LockGuard((value.write_lock::().unwrap(), container)); } + + Self::Ref(value) } } + #[cfg(any(not(feature = "no_index"), not(feature = "no_object")))] impl> From for Target<'_> { fn from(value: T) -> Self { @@ -706,7 +708,7 @@ impl Engine { // Try to call an index setter #[cfg(not(feature = "no_index"))] Ok(obj_ptr) if obj_ptr.is_value() => { - next = Some((1, _new_val.unwrap())); + next = Some((1, _new_val.unwrap())); } // Indexed value is a reference - update directly Ok(ref mut obj_ptr) => { @@ -740,13 +742,13 @@ impl Engine { state, lib, FN_IDX_SET, 0, args, is_ref, true, false, None, None, level, ) - .or_else(|err| match *err { - // If there is no index setter, no need to set it back because the indexer is read-only - EvalAltResult::ErrorFunctionNotFound(_, _) => { - Ok(Default::default()) - } - _ => Err(err), - })?; + .or_else(|err| match *err { + // If there is no index setter, no need to set it back because the indexer is read-only + EvalAltResult::ErrorFunctionNotFound(_, _) => { + Ok(Default::default()) + } + _ => Err(err), + })?; } // next step is custom index setter call in case of error @@ -759,7 +761,7 @@ impl Engine { )?; } None => (), - _ => unreachable!() + _ => unreachable!(), } Ok(Default::default()) @@ -881,8 +883,15 @@ impl Engine { let (result, may_be_changed) = self .eval_dot_index_chain_helper( - state, lib, this_ptr, &mut val.into(), expr, idx_values, next_chain, - level, _new_val, + state, + lib, + this_ptr, + &mut val.into(), + expr, + idx_values, + next_chain, + level, + _new_val, ) .map_err(|err| err.new_position(*pos))?; @@ -1318,11 +1327,13 @@ impl Engine { )), // Normal assignment ScopeEntryType::Normal if op.is_empty() => { - if lhs_ptr.is_shared() { - *lhs_ptr.write_lock::().unwrap() = rhs_val; + #[cfg(not(feature = "no_shared"))] + let lhs_ptr = if lhs_ptr.is_shared() { + lhs_ptr.write_lock::().unwrap(); } else { - *lhs_ptr = rhs_val; - } + lhs_ptr + }; + *lhs_ptr = rhs_val; Ok(Default::default()) } // Op-assignment - in order of precedence: @@ -1340,13 +1351,15 @@ impl Engine { .get_fn(hash_fn, false) .or_else(|| self.packages.get_fn(hash_fn, false)) { - if lhs_ptr.is_shared() { - // Overriding exact implementation - func(self, lib, &mut [&mut lhs_ptr.write_lock::().unwrap(), &mut rhs_val])?; + // Overriding exact implementation + #[cfg(not(feature = "no_shared"))] + let lhs_ptr = if lhs_ptr.is_shared() { + &mut lhs_ptr.write_lock::().unwrap() } else { - // Overriding exact implementation - func(self, lib, &mut [lhs_ptr, &mut rhs_val])?; - } + lhs_ptr + }; + + func(self, lib, &mut [lhs_ptr, &mut rhs_val])?; } else if run_builtin_op_assignment(op, lhs_ptr, &rhs_val)?.is_none() { // Not built in, map to `var = var op rhs` let op = &op[..op.len() - 1]; // extract operator without = @@ -1360,13 +1373,15 @@ impl Engine { state, lib, op, 0, args, false, false, false, None, None, level, ) .map_err(|err| err.new_position(*op_pos))?; - if lhs_ptr.is_shared() { - // Set value to LHS - *lhs_ptr.write_lock::().unwrap() = value; + + #[cfg(not(feature = "no_shared"))] + let lhs_ptr = if lhs_ptr.is_shared() { + lhs_ptr.write_lock::().unwrap() } else { - // Set value to LHS - *lhs_ptr = value; - } + lhs_ptr + }; + + *lhs_ptr = value; } Ok(Default::default()) } diff --git a/src/fn_call.rs b/src/fn_call.rs index bed49230..d093c165 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_PRINT, - KEYWORD_TYPE_OF, KEYWORD_SHARED, + KEYWORD_TYPE_OF, }; use crate::error::ParseErrorType; use crate::fn_native::{FnCallArgs, FnPtr}; @@ -14,9 +14,9 @@ use crate::optimize::OptimizationLevel; use crate::parser::{Expr, ImmutableString, AST, INT}; use crate::result::EvalAltResult; use crate::scope::Scope; +use crate::stdlib::ops::Deref; use crate::token::Position; use crate::utils::StaticVec; -use crate::stdlib::ops::Deref; #[cfg(not(feature = "no_function"))] use crate::{ @@ -34,6 +34,9 @@ use crate::engine::{FN_IDX_GET, FN_IDX_SET}; #[cfg(not(feature = "no_object"))] use crate::engine::{Map, Target, FN_GET, FN_SET, KEYWORD_TAKE}; +#[cfg(not(feature = "no_shared"))] +use crate::engine::KEYWORD_SHARED; + use crate::stdlib::{ any::{type_name, TypeId}, boxed::Box, @@ -771,6 +774,7 @@ impl Engine { } // Handle shared() + #[cfg(not(feature = "no_shared"))] if 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)?; diff --git a/src/fn_native.rs b/src/fn_native.rs index f1e3a63c..f8bb0bf1 100644 --- a/src/fn_native.rs +++ b/src/fn_native.rs @@ -18,33 +18,46 @@ use crate::stdlib::{boxed::Box, convert::TryFrom, fmt, iter::empty, string::Stri use crate::stdlib::mem; #[cfg(not(feature = "sync"))] -use crate::stdlib::{rc::Rc, cell::RefCell}; +use crate::stdlib::rc::Rc; #[cfg(feature = "sync")] -use crate::stdlib::sync::{Arc, RwLock}; +use crate::stdlib::sync::Arc; + +#[cfg(not(feature = "no_shared"))] +#[cfg(not(feature = "sync"))] +use crate::stdlib::cell::RefCell; +#[cfg(not(feature = "no_shared"))] +#[cfg(feature = "sync")] +use crate::stdlib::sync::RwLock; /// Trait that maps to `Send + Sync` only under the `sync` feature. #[cfg(feature = "sync")] pub trait SendSync: Send + Sync {} +/// Trait that maps to `Send + Sync` only under the `sync` feature. #[cfg(feature = "sync")] impl SendSync for T {} /// Trait that maps to `Send + Sync` only under the `sync` feature. #[cfg(not(feature = "sync"))] pub trait SendSync {} +/// Trait that maps to `Send + Sync` only under the `sync` feature. #[cfg(not(feature = "sync"))] impl SendSync for T {} -/// Immutable reference counting container +/// Immutable reference-counted container #[cfg(not(feature = "sync"))] pub type Shared = Rc; +/// Immutable reference-counted container #[cfg(feature = "sync")] pub type Shared = Arc; -/// Mutable reference counting container(read-write lock) +/// Mutable reference-counted container (read-write lock) +#[cfg(not(feature = "no_shared"))] #[cfg(not(feature = "sync"))] -pub type SharedMut = Rc>; +pub type SharedMut = Shared>; +/// Mutable reference-counted container (read-write lock) +#[cfg(not(feature = "no_shared"))] #[cfg(feature = "sync")] -pub type SharedMut = Arc>; +pub type SharedMut = Shared>; /// Consume a `Shared` resource and return a mutable reference to the wrapped value. /// If the resource is shared (i.e. has other outstanding references), a cloned copy is used. diff --git a/src/token.rs b/src/token.rs index 367a1dcc..f5a160e3 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_SHARED, KEYWORD_TAKE, KEYWORD_PRINT, KEYWORD_THIS, KEYWORD_TYPE_OF, + KEYWORD_PRINT, KEYWORD_SHARED, KEYWORD_TAKE, KEYWORD_THIS, KEYWORD_TYPE_OF, }; use crate::error::LexError; @@ -503,12 +503,12 @@ impl Token { "===" | "!==" | "->" | "<-" | "=>" | ":=" | "::<" | "(*" | "*)" | "#" | "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()), + | "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_SHARED - | KEYWORD_TAKE |KEYWORD_THIS => Reserved(syntax.into()), + | KEYWORD_FN_PTR_CALL | KEYWORD_FN_PTR_CURRY | KEYWORD_SHARED | KEYWORD_TAKE + | KEYWORD_THIS => Reserved(syntax.into()), _ => return None, }) @@ -1439,7 +1439,7 @@ pub fn is_keyword_function(name: &str) -> bool { || name == KEYWORD_FN_PTR_CALL || name == KEYWORD_FN_PTR_CURRY; - #[cfg(not(feature = "no-shared"))] + #[cfg(not(feature = "no_shared"))] { result = result || name == KEYWORD_SHARED || name == KEYWORD_TAKE; } From e70e0ff4e2ef97a9258977a18e15f8aafa7cfd2c Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Fri, 31 Jul 2020 16:39:38 +0800 Subject: [PATCH 15/40] Fixup. --- Cargo.toml | 2 +- src/any.rs | 33 ++++-- src/engine.rs | 34 ++++-- tests/closures.rs | 294 ---------------------------------------------- tests/shared.rs | 268 ++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 310 insertions(+), 321 deletions(-) create mode 100644 tests/shared.rs diff --git a/Cargo.toml b/Cargo.toml index 13ef119f..c450eecd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,7 +22,7 @@ smallvec = { version = "1.4.1", default-features = false } [features] #default = ["unchecked", "sync", "no_optimize", "no_float", "only_i32", "no_index", "no_object", "no_function", "no_module"] -default = [ "no_shared"] +default = [] plugins = [] unchecked = [] # unchecked arithmetic sync = [] # restrict to only types that implement Send + Sync diff --git a/src/any.rs b/src/any.rs index 470d86c7..1f1a6df8 100644 --- a/src/any.rs +++ b/src/any.rs @@ -578,7 +578,9 @@ impl Dynamic { } /// Convert the `Dynamic` value into specific type. - /// Casting to a `Dynamic` just returns as is. + /// + /// Casting to a `Dynamic` just returns as is, but if it contains a shared value, + /// it is cloned into a `Dynamic` with a normal value. /// /// Returns `None` if types mismatched. /// @@ -602,6 +604,19 @@ impl Dynamic { pub fn try_cast(self) -> Option { let type_id = TypeId::of::(); + match self.0 { + #[cfg(not(feature = "no_shared"))] + #[cfg(not(feature = "sync"))] + Union::Shared(cell) => return cell.container.borrow().deref().clone().try_cast(), + + #[cfg(not(feature = "no_shared"))] + #[cfg(feature = "sync")] + Union::Shared(cell) => { + return cell.container.read().unwrap().deref().clone().try_cast() + } + _ => (), + } + if type_id == TypeId::of::() { return unsafe_cast_box::<_, T>(Box::new(self)).ok().map(|v| *v); } @@ -681,23 +696,15 @@ 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 = "sync"))] - Union::Shared(cell) => return cell.container.borrow().deref().clone().try_cast(), - - #[cfg(not(feature = "no_shared"))] - #[cfg(feature = "sync")] - Union::Shared(cell) => { - return cell.container.read().unwrap().deref().clone().try_cast() - } - + Union::Shared(_) => unreachable!(), _ => None, } } /// Convert the `Dynamic` value into a specific type. - /// Casting to a `Dynamic` just returns as is. + /// + /// Casting to a `Dynamic` just returns as is, but if it contains a shared value, + /// it is cloned into a `Dynamic` with a normal value. /// /// Returns `None` if types mismatched. /// diff --git a/src/engine.rs b/src/engine.rs index e3f5dad8..b3585804 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -1328,12 +1328,15 @@ impl Engine { // Normal assignment ScopeEntryType::Normal if op.is_empty() => { #[cfg(not(feature = "no_shared"))] - let lhs_ptr = if lhs_ptr.is_shared() { - lhs_ptr.write_lock::().unwrap(); + if lhs_ptr.is_shared() { + *lhs_ptr.write_lock::().unwrap() = rhs_val; } else { - lhs_ptr - }; - *lhs_ptr = rhs_val; + *lhs_ptr = rhs_val; + } + #[cfg(feature = "no_shared")] + { + *lhs_ptr = rhs_val; + } Ok(Default::default()) } // Op-assignment - in order of precedence: @@ -1351,14 +1354,17 @@ impl Engine { .get_fn(hash_fn, false) .or_else(|| self.packages.get_fn(hash_fn, false)) { - // Overriding exact implementation + let mut lock_guard; + #[cfg(not(feature = "no_shared"))] let lhs_ptr = if lhs_ptr.is_shared() { - &mut lhs_ptr.write_lock::().unwrap() + lock_guard = Some(lhs_ptr.write_lock::().unwrap()); + lock_guard.as_deref_mut().unwrap() } else { lhs_ptr }; + // Overriding exact implementation func(self, lib, &mut [lhs_ptr, &mut rhs_val])?; } else if run_builtin_op_assignment(op, lhs_ptr, &rhs_val)?.is_none() { // Not built in, map to `var = var op rhs` @@ -1375,13 +1381,15 @@ impl Engine { .map_err(|err| err.new_position(*op_pos))?; #[cfg(not(feature = "no_shared"))] - let lhs_ptr = if lhs_ptr.is_shared() { - lhs_ptr.write_lock::().unwrap() + if lhs_ptr.is_shared() { + *lhs_ptr.write_lock::().unwrap() = value; } else { - lhs_ptr - }; - - *lhs_ptr = value; + *lhs_ptr = value; + } + #[cfg(feature = "no_shared")] + { + *lhs_ptr = value; + } } Ok(Default::default()) } diff --git a/tests/closures.rs b/tests/closures.rs index 4f8c16dd..c5282870 100644 --- a/tests/closures.rs +++ b/tests/closures.rs @@ -2,12 +2,6 @@ use rhai::{Dynamic, Engine, EvalAltResult, FnPtr, Module, INT}; use std::any::TypeId; -#[cfg(not(feature = "no_shared"))] -use rhai::RegisterFn; - -#[cfg(not(feature = "no_index"))] -use rhai::Array; - #[test] fn test_fn_ptr_curry_call() -> Result<(), Box> { let mut module = Module::new(); @@ -64,291 +58,3 @@ fn test_closures() -> Result<(), Box> { Ok(()) } - -#[test] -#[cfg(not(feature = "no_shared"))] -fn test_shared() -> Result<(), Box> { - let mut engine = Engine::new(); - - assert_eq!( - engine.eval::( - r#" - shared(42) - "# - )?, - 42 - ); - - assert_eq!( - engine.eval::( - r#" - shared(true) - "# - )?, - true - ); - - #[cfg(not(feature = "no_float"))] - assert_eq!( - engine.eval::( - r#" - shared(4.2) - "# - )?, - 4.2 - ); - - assert_eq!( - engine.eval::( - r#" - shared("test") - "# - )?, - "test" - ); - - assert_eq!( - engine.eval::( - r#" - shared('x') - "# - )?, - 'x' - ); - - #[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_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_capture"))] - assert_eq!( - engine.eval::(r#" - let x = shared(1); - (|| x = x + 41).call(); - x - "#)?, - 42 - ); - - #[cfg(all(not(feature = "no_object"), 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::(r#" - 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(()) -} diff --git a/tests/shared.rs b/tests/shared.rs new file mode 100644 index 00000000..e35de3d0 --- /dev/null +++ b/tests/shared.rs @@ -0,0 +1,268 @@ +#![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); + + #[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::("shared('x')")?, 'x'); + + #[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(()) +} From df8587ac9136149735f6fc4625e33814c70705fc Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Fri, 31 Jul 2020 17:06:40 +0800 Subject: [PATCH 16/40] Make sure both shared and take can be called in both styles. --- src/fn_call.rs | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/src/fn_call.rs b/src/fn_call.rs index d093c165..3d99d571 100644 --- a/src/fn_call.rs +++ b/src/fn_call.rs @@ -32,10 +32,10 @@ use crate::parser::FLOAT; use crate::engine::{FN_IDX_GET, FN_IDX_SET}; #[cfg(not(feature = "no_object"))] -use crate::engine::{Map, Target, FN_GET, FN_SET, KEYWORD_TAKE}; +use crate::engine::{Map, Target, FN_GET, FN_SET}; #[cfg(not(feature = "no_shared"))] -use crate::engine::KEYWORD_SHARED; +use crate::engine::{KEYWORD_SHARED, KEYWORD_TAKE}; use crate::stdlib::{ any::{type_name, TypeId}, @@ -659,9 +659,12 @@ impl Engine { .into(), false, )) - } else if _fn_name == KEYWORD_TAKE { + } else if _fn_name == KEYWORD_SHARED && idx.is_empty() { // take call - return Ok((obj.clone_inner_data::().unwrap(), false)); + Ok((obj.clone().into_shared(), false)) + } else if _fn_name == KEYWORD_TAKE && idx.is_empty() { + // take call + Ok((obj.clone_inner_data::().unwrap(), false)) } else { #[cfg(not(feature = "no_object"))] let redirected; @@ -782,6 +785,15 @@ impl Engine { return Ok(value.into_shared()); } + // Handle take() + #[cfg(not(feature = "no_shared"))] + if 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(); From a35155b3e9ba9788d3e7fbc700ff751d2862be86 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Fri, 31 Jul 2020 17:07:09 +0800 Subject: [PATCH 17/40] Add shared and take to keywords list. --- doc/src/appendix/keywords.md | 61 ++++++++++++++-------------- doc/src/language/keywords.md | 31 +++++++------- doc/src/language/values-and-types.md | 3 +- doc/src/links.md | 1 + doc/src/start/features.md | 3 +- 5 files changed, 52 insertions(+), 47 deletions(-) diff --git a/doc/src/appendix/keywords.md b/doc/src/appendix/keywords.md index 5877f1a0..8aee03b1 100644 --- a/doc/src/appendix/keywords.md +++ b/doc/src/appendix/keywords.md @@ -3,35 +3,37 @@ Keywords List {{#include ../links.md}} -| Keyword | Description | Inactive under | -| :-------------------: | ---------------------------------------- | :-------------: | -| `true` | Boolean true literal | | -| `false` | Boolean false literal | | -| `let` | Variable declaration | | -| `const` | Constant declaration | | -| `if` | If statement | | -| `else` | else block of if statement | | -| `while` | While loop | | -| `loop` | Infinite loop | | -| `for` | For loop | | -| `in` | Containment test, part of for loop | | -| `continue` | Continue a loop at the next iteration | | -| `break` | Loop breaking | | -| `return` | Return value | | -| `throw` | Throw exception | | -| `import` | Import module | [`no_module`] | -| `export` | Export variable | [`no_module`] | -| `as` | Alias for variable export | [`no_module`] | -| `private` | Mark function private | [`no_function`] | -| `fn` (lower-case `f`) | Function definition | [`no_function`] | -| `Fn` (capital `F`) | Function to create a [function pointer] | | -| `call` | Call a [function pointer] | | -| `curry` | Curry a [function pointer] | | -| `this` | Reference to base object for method call | [`no_function`] | -| `type_of` | Get type name of value | | -| `print` | Print value | | -| `debug` | Print value in debug format | | -| `eval` | Evaluate script | | +| Keyword | Description | Inactive under | Overloadable | +| :-------------------: | ---------------------------------------- | :-------------: | :----------: | +| `true` | Boolean true literal | | No | +| `false` | Boolean false literal | | No | +| `let` | Variable declaration | | No | +| `const` | Constant declaration | | 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 | +| `loop` | Infinite loop | | No | +| `for` | For loop | | No | +| `in` | Containment test, part of for loop | | No | +| `continue` | Continue a loop at the next iteration | | No | +| `break` | Loop breaking | | No | +| `return` | Return value | | No | +| `throw` | Throw exception | | No | +| `import` | Import module | [`no_module`] | No | +| `export` | Export variable | [`no_module`] | No | +| `as` | Alias for variable export | [`no_module`] | No | +| `private` | Mark function private | [`no_function`] | No | +| `fn` (lower-case `f`) | Function definition | [`no_function`] | No | +| `Fn` (capital `F`) | Function to create a [function pointer] | | Yes | +| `call` | Call a [function pointer] | | No | +| `curry` | Curry a [function pointer] | | No | +| `this` | Reference to base object for method call | [`no_function`] | No | +| `type_of` | Get type name of value | | Yes | +| `print` | Print value | | Yes | +| `debug` | Print value in debug format | | Yes | +| `eval` | Evaluate script | | Yes | Reserved Keywords @@ -59,7 +61,6 @@ Reserved Keywords | `package` | Package | | `spawn` | Threading | | `go` | Threading | -| `shared` | Threading | | `await` | Async | | `async` | Async | | `sync` | Async | diff --git a/doc/src/language/keywords.md b/doc/src/language/keywords.md index 509823db..a5142007 100644 --- a/doc/src/language/keywords.md +++ b/doc/src/language/keywords.md @@ -5,21 +5,22 @@ Keywords The following are reserved keywords in Rhai: -| Active keywords | Reserved keywords | Usage | Inactive under feature | -| ------------------------------------------------- | ---------------------------------------------------------- | --------------------- | :--------------------: | -| `true`, `false` | | Boolean constants | | -| `let`, `const` | `var`, `static` | Variable declarations | | -| `if`, `else` | `then`, `goto`, `exit` | Control flow | | -| | `switch`, `match`, `case` | Matching | | -| `while`, `loop`, `for`, `in`, `continue`, `break` | `do`, `each` | Looping | | -| `fn`, `private` | `public`, `new` | Functions | [`no_function`] | -| `return` | | Return values | | -| `throw` | `try`, `catch` | Throw exceptions | | -| `import`, `export`, `as` | `use`, `with`, `module`, `package` | Modules/packages | [`no_module`] | -| `Fn`, `call`, `curry` | | Function pointers | | -| | `spawn`, `go`, `shared`, `sync`, `async`, `await`, `yield` | Threading/async | | -| `type_of`, `print`, `debug`, `eval` | | Special functions | | -| | `default`, `void`, `null`, `nil` | Special values | | +| Active keywords | Reserved keywords | Usage | Inactive under feature | +| ------------------------------------------------- | ------------------------------------------------ | --------------------- | :--------------------: | +| `true`, `false` | | Boolean constants | | +| `let`, `const` | `var`, `static` | Variable declarations | | +| `shared`, `take` | | Shared values | [`no_shared`] | +| `if`, `else` | `then`, `goto`, `exit` | Control flow | | +| | `switch`, `match`, `case` | Matching | | +| `while`, `loop`, `for`, `in`, `continue`, `break` | `do`, `each` | Looping | | +| `fn`, `private` | `public`, `new` | Functions | [`no_function`] | +| `return` | | Return values | | +| `throw` | `try`, `catch` | Throw exceptions | | +| `import`, `export`, `as` | `use`, `with`, `module`, `package` | Modules/packages | [`no_module`] | +| `Fn`, `call`, `curry` | | Function pointers | | +| | `spawn`, `go`, `sync`, `async`, `await`, `yield` | Threading/async | | +| `type_of`, `print`, `debug`, `eval` | | Special functions | | +| | `default`, `void`, `null`, `nil` | Special values | | Keywords cannot become the name of a [function] or [variable], even when they are disabled. diff --git a/doc/src/language/values-and-types.md b/doc/src/language/values-and-types.md index 11426e84..833eead5 100644 --- a/doc/src/language/values-and-types.md +++ b/doc/src/language/values-and-types.md @@ -14,9 +14,10 @@ The following primitive types are supported natively: | **Immutable Unicode [string]** | `rhai::ImmutableString` (implemented as `Rc` or `Arc`) | `"string"` | `"hello"` etc. | | **[`Array`]** (disabled with [`no_index`]) | `rhai::Array` | `"array"` | `"[ ?, ?, ? ]"` | | **[Object map]** (disabled with [`no_object`]) | `rhai::Map` | `"map"` | `"#{ "a": 1, "b": 2 }"` | -| **[Timestamp]** (implemented in the [`BasicTimePackage`][packages], disabled with [`no_std`]) | `std::time::Instant` ([`instant::Instant`] if [WASM] build) | `"timestamp"` | _not supported_ | +| **[Timestamp]** (implemented in the [`BasicTimePackage`][packages], disabled with [`no_std`]) | `std::time::Instant` ([`instant::Instant`] if [WASM] build) | `"timestamp"` | `""` | | **[Function pointer]** | `rhai::FnPtr` | `Fn` | `"Fn(foo)"` | | **[`Dynamic`] value** (i.e. can be anything) | `rhai::Dynamic` | _the actual type_ | _actual value_ | +| **Shared value** (a reference-counted, shared [`Dynamic`] value) | | _the actual type_ | _actual value_ | | **System integer** (current configuration) | `rhai::INT` (`i32` or `i64`) | `"i32"` or `"i64"` | `"42"`, `"123"` etc. | | **System floating-point** (current configuration, disabled with [`no_float`]) | `rhai::FLOAT` (`f32` or `f64`) | `"f32"` or `"f64"` | `"123.456"` etc. | | **Nothing/void/nil/null/Unit** (or whatever it is called) | `()` | `"()"` | `""` _(empty string)_ | diff --git a/doc/src/links.md b/doc/src/links.md index 19fdf0e9..a22f0038 100644 --- a/doc/src/links.md +++ b/doc/src/links.md @@ -10,6 +10,7 @@ [`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_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 efeb39c5..9f0d42d4 100644 --- a/doc/src/start/features.md +++ b/doc/src/start/features.md @@ -24,7 +24,8 @@ more control over what a script can (or cannot) do. | `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_std` | Build for `no-std`. Notice that additional dependencies will be pulled in to replace `std` features. | +| `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. | | `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. | From c8a60875ec5ee460b65203ae2f555541f3982835 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Fri, 31 Jul 2020 17:13:22 +0800 Subject: [PATCH 18/40] CI also in closures branch. --- .github/workflows/build.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index cd10a5a6..276cbf72 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -4,6 +4,7 @@ on: push: branches: - master + - closures pull_request: {} jobs: From d563b878aa8bb757f36ff003d6b865764f1ede45 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Fri, 31 Jul 2020 17:26:49 +0800 Subject: [PATCH 19/40] Fix no_std builds. --- src/any.rs | 1 + src/engine.rs | 1 + src/fn_call.rs | 19 +++++++++++++------ 3 files changed, 15 insertions(+), 6 deletions(-) diff --git a/src/any.rs b/src/any.rs index 1f1a6df8..520bad0d 100644 --- a/src/any.rs +++ b/src/any.rs @@ -696,6 +696,7 @@ impl Dynamic { match self.0 { Union::Variant(value) => (*value).as_box_any().downcast().map(|x| *x).ok(), + #[cfg(not(feature = "no_shared"))] Union::Shared(_) => unreachable!(), _ => None, } diff --git a/src/engine.rs b/src/engine.rs index b3585804..5e8d88cf 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -1354,6 +1354,7 @@ impl Engine { .get_fn(hash_fn, false) .or_else(|| self.packages.get_fn(hash_fn, false)) { + #[cfg(not(feature = "no_shared"))] let mut lock_guard; #[cfg(not(feature = "no_shared"))] diff --git a/src/fn_call.rs b/src/fn_call.rs index 3d99d571..6015d59f 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_PRINT, - KEYWORD_TYPE_OF, + KEYWORD_SHARED, KEYWORD_TAKE, KEYWORD_TYPE_OF, }; use crate::error::ParseErrorType; use crate::fn_native::{FnCallArgs, FnPtr}; @@ -34,9 +34,6 @@ 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_shared"))] -use crate::engine::{KEYWORD_SHARED, KEYWORD_TAKE}; - use crate::stdlib::{ any::{type_name, TypeId}, boxed::Box, @@ -661,10 +658,20 @@ impl Engine { )) } else if _fn_name == KEYWORD_SHARED && idx.is_empty() { // take call - Ok((obj.clone().into_shared(), false)) + #[cfg(not(feature = "no_shared"))] + { + Ok((obj.clone().into_shared(), false)) + } + #[cfg(feature = "no_shared")] + unreachable!() } else if _fn_name == KEYWORD_TAKE && idx.is_empty() { // take call - Ok((obj.clone_inner_data::().unwrap(), false)) + #[cfg(not(feature = "no_shared"))] + { + Ok((obj.clone_inner_data::().unwrap(), false)) + } + #[cfg(feature = "no_shared")] + unreachable!() } else { #[cfg(not(feature = "no_object"))] let redirected; From 0295e109ad56ea6b78c8d0c30674bdb968868207 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Fri, 31 Jul 2020 18:06:01 +0800 Subject: [PATCH 20/40] Fix features --- src/engine.rs | 22 +++++++++++++++++----- src/token.rs | 16 +++++++--------- 2 files changed, 24 insertions(+), 14 deletions(-) diff --git a/src/engine.rs b/src/engine.rs index 5e8d88cf..f9017d96 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -1,6 +1,6 @@ //! Main module defining the script evaluation `Engine`. -use crate::any::{map_std_type_name, Dynamic, DynamicWriteLock, Union}; +use crate::any::{map_std_type_name, Dynamic, Union}; use crate::calc_fn_hash; use crate::fn_call::run_builtin_op_assignment; use crate::fn_native::{CallableFunction, Callback, FnPtr}; @@ -31,13 +31,15 @@ use crate::module::resolvers; #[cfg(any(not(feature = "no_object"), not(feature = "no_module")))] use crate::utils::ImmutableString; +#[cfg(not(feature = "no_shared"))] +use crate::any::DynamicWriteLock; + use crate::stdlib::{ borrow::Cow, boxed::Box, collections::{HashMap, HashSet}, fmt, format, iter::{empty, once}, - ops::DerefMut, string::{String, ToString}, vec::Vec, }; @@ -45,6 +47,9 @@ use crate::stdlib::{ #[cfg(not(feature = "no_index"))] use crate::stdlib::any::TypeId; +#[cfg(not(feature = "no_shared"))] +use crate::stdlib::ops::DerefMut; + /// Variable-sized array of `Dynamic` values. /// /// Not available under the `no_index` feature. @@ -126,7 +131,8 @@ pub enum Target<'a> { /// The target is a mutable reference to a `Dynamic` value somewhere. Ref(&'a mut Dynamic), /// The target is a mutable reference to a Shared `Dynamic` value. - /// It holds the access guard and the original container both for cloning purposes + /// It holds both the access guard and the original shared value. + #[cfg(not(feature = "no_shared"))] LockGuard((DynamicWriteLock<'a, Dynamic>, Dynamic)), /// The target is a temporary `Dynamic` value (i.e. the mutation can cause no side effects). Value(Dynamic), @@ -142,6 +148,7 @@ impl Target<'_> { pub fn is_ref(&self) -> bool { match self { Self::Ref(_) => true, + #[cfg(not(feature = "no_shared"))] Self::LockGuard(_) => true, Self::Value(_) => false, #[cfg(not(feature = "no_index"))] @@ -152,6 +159,7 @@ impl Target<'_> { pub fn is_value(&self) -> bool { match self { Self::Ref(_) => false, + #[cfg(not(feature = "no_shared"))] Self::LockGuard(_) => false, Self::Value(_) => true, #[cfg(not(feature = "no_index"))] @@ -163,6 +171,7 @@ impl Target<'_> { pub fn is(&self) -> bool { match self { Target::Ref(r) => r.is::(), + #[cfg(not(feature = "no_shared"))] Target::LockGuard((r, _)) => r.is::(), Target::Value(r) => r.is::(), #[cfg(not(feature = "no_index"))] @@ -173,7 +182,8 @@ impl Target<'_> { pub fn clone_into_dynamic(self) -> Dynamic { match self { Self::Ref(r) => r.clone(), // Referenced value is cloned - Self::LockGuard((_, orig)) => orig, // Return original container of the Shared Dynamic + #[cfg(not(feature = "no_shared"))] + Self::LockGuard((_, orig)) => orig, // Original value is simply taken Self::Value(v) => v, // Owned value is simply taken #[cfg(not(feature = "no_index"))] Self::StringChar(_, _, ch) => ch, // Character is taken @@ -183,6 +193,7 @@ impl Target<'_> { pub fn as_mut(&mut self) -> &mut Dynamic { match self { Self::Ref(r) => *r, + #[cfg(not(feature = "no_shared"))] Self::LockGuard((r, _)) => r.deref_mut(), Self::Value(ref mut r) => r, #[cfg(not(feature = "no_index"))] @@ -194,6 +205,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"))] Self::LockGuard((r, _)) => **r = new_val, Self::Value(_) => { return Err(Box::new(EvalAltResult::ErrorAssignmentToUnknownLHS( @@ -231,7 +243,7 @@ impl<'a> From<&'a mut Dynamic> for Target<'a> { fn from(value: &'a mut Dynamic) -> Self { #[cfg(not(feature = "no_shared"))] if value.is_shared() { - // cloning is cheap since it holds Arc/Rw under the hood + // Cloning is cheap for a shared value let container = value.clone(); return Self::LockGuard((value.write_lock::().unwrap(), container)); } diff --git a/src/token.rs b/src/token.rs index f5a160e3..571ddf10 100644 --- a/src/token.rs +++ b/src/token.rs @@ -1431,20 +1431,18 @@ fn get_identifier( /// Is this keyword allowed as a function? #[inline(always)] pub fn is_keyword_function(name: &str) -> bool { - let mut result = name == KEYWORD_PRINT + #[cfg(not(feature = "no_shared"))] + if name == KEYWORD_SHARED || name == KEYWORD_TAKE { + return true; + } + + name == KEYWORD_PRINT || name == KEYWORD_DEBUG || name == KEYWORD_TYPE_OF || name == KEYWORD_EVAL || name == KEYWORD_FN_PTR || name == KEYWORD_FN_PTR_CALL - || name == KEYWORD_FN_PTR_CURRY; - - #[cfg(not(feature = "no_shared"))] - { - result = result || name == KEYWORD_SHARED || name == KEYWORD_TAKE; - } - - result + || name == KEYWORD_FN_PTR_CURRY } /// Can this keyword be overridden as a function? From 8d0623d07f8b100f6e26d7489fbb7be9bbabf40c Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Fri, 31 Jul 2020 18:43:34 +0800 Subject: [PATCH 21/40] Add is_shared function. --- doc/src/appendix/keywords.md | 1 + doc/src/language/keywords.md | 3 +-- src/engine.rs | 5 +++-- src/fn_call.rs | 19 +++++++++++++++++-- src/token.rs | 28 +++++++++++----------------- tests/shared.rs | 5 +++++ 6 files changed, 38 insertions(+), 23 deletions(-) diff --git a/doc/src/appendix/keywords.md b/doc/src/appendix/keywords.md index 8aee03b1..a1e20fed 100644 --- a/doc/src/appendix/keywords.md +++ b/doc/src/appendix/keywords.md @@ -9,6 +9,7 @@ Keywords List | `false` | Boolean false literal | | No | | `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 | diff --git a/doc/src/language/keywords.md b/doc/src/language/keywords.md index a5142007..0683386e 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` | | Shared values | [`no_shared`] | +| `shared`, `take`, `is_shared` | | Shared values | [`no_shared`] | | `if`, `else` | `then`, `goto`, `exit` | Control flow | | | | `switch`, `match`, `case` | Matching | | | `while`, `loop`, `for`, `in`, `continue`, `break` | `do`, `each` | Looping | | @@ -23,4 +23,3 @@ The following are reserved keywords in Rhai: | | `default`, `void`, `null`, `nil` | Special values | | Keywords cannot become the name of a [function] or [variable], even when they are disabled. - diff --git a/src/engine.rs b/src/engine.rs index f9017d96..bd778e4e 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -99,6 +99,7 @@ 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"; #[cfg(not(feature = "no_object"))] @@ -181,10 +182,10 @@ impl Target<'_> { /// Get the value of the `Target` as a `Dynamic`, cloning a referenced value if necessary. pub fn clone_into_dynamic(self) -> Dynamic { match self { - Self::Ref(r) => r.clone(), // Referenced value is cloned + Self::Ref(r) => r.clone(), // Referenced value is cloned #[cfg(not(feature = "no_shared"))] Self::LockGuard((_, orig)) => orig, // Original value is simply taken - Self::Value(v) => v, // Owned value is simply taken + Self::Value(v) => v, // Owned value is simply taken #[cfg(not(feature = "no_index"))] Self::StringChar(_, _, ch) => ch, // Character is taken } diff --git a/src/fn_call.rs b/src/fn_call.rs index 6015d59f..d680800a 100644 --- a/src/fn_call.rs +++ b/src/fn_call.rs @@ -4,8 +4,8 @@ use crate::any::Dynamic; 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_PRINT, - KEYWORD_SHARED, KEYWORD_TAKE, KEYWORD_TYPE_OF, + 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, }; use crate::error::ParseErrorType; use crate::fn_native::{FnCallArgs, FnPtr}; @@ -451,6 +451,12 @@ impl Engine { ))) } + // Fn + KEYWORD_IS_SHARED if args.len() == 1 => Err(Box::new(EvalAltResult::ErrorRuntime( + "'is_shared' should not be called in method style. Try is_shared(...);".into(), + Position::none(), + ))), + // eval - reaching this point it must be a method-style call KEYWORD_EVAL if args.len() == 1 && !self.has_override(lib, hash_fn, hash_script, pub_only) => @@ -783,6 +789,15 @@ impl Engine { .into()); } + // Handle is_shared() + #[cfg(not(feature = "no_shared"))] + if 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() #[cfg(not(feature = "no_shared"))] if name == KEYWORD_SHARED && args_expr.len() == 1 { diff --git a/src/token.rs b/src/token.rs index 571ddf10..dfd50c27 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_PRINT, KEYWORD_SHARED, KEYWORD_TAKE, KEYWORD_THIS, KEYWORD_TYPE_OF, + KEYWORD_IS_SHARED, KEYWORD_PRINT, KEYWORD_SHARED, KEYWORD_TAKE, KEYWORD_THIS, KEYWORD_TYPE_OF, }; use crate::error::LexError; @@ -1431,28 +1431,22 @@ fn get_identifier( /// Is this keyword allowed as a function? #[inline(always)] pub fn is_keyword_function(name: &str) -> bool { - #[cfg(not(feature = "no_shared"))] - if name == KEYWORD_SHARED || name == KEYWORD_TAKE { - return true; + match name { + #[cfg(not(feature = "no_shared"))] + KEYWORD_SHARED | KEYWORD_TAKE | 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, } - - name == KEYWORD_PRINT - || name == KEYWORD_DEBUG - || name == KEYWORD_TYPE_OF - || name == KEYWORD_EVAL - || name == KEYWORD_FN_PTR - || name == KEYWORD_FN_PTR_CALL - || name == KEYWORD_FN_PTR_CURRY } /// Can this keyword be overridden as a function? #[inline(always)] pub fn can_override_keyword(name: &str) -> bool { - name == KEYWORD_PRINT - || name == KEYWORD_DEBUG - || name == KEYWORD_TYPE_OF - || name == KEYWORD_EVAL - || name == KEYWORD_FN_PTR + match name { + KEYWORD_PRINT | KEYWORD_DEBUG | KEYWORD_TYPE_OF | KEYWORD_EVAL | KEYWORD_FN_PTR => true, + _ => false, + } } pub fn is_valid_identifier(name: impl Iterator) -> bool { diff --git a/tests/shared.rs b/tests/shared.rs index e35de3d0..7cd7af86 100644 --- a/tests/shared.rs +++ b/tests/shared.rs @@ -18,6 +18,11 @@ fn test_shared() -> Result<(), Box> { 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!( From 60891e694f480e7c62f7a6d06749958503ceaa3b Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Fri, 31 Jul 2020 22:30:23 +0800 Subject: [PATCH 22/40] Streamline code and feature gates. --- src/any.rs | 17 ++- src/api.rs | 19 ++-- src/engine.rs | 69 +++++++----- src/fn_call.rs | 125 ++++++++++----------- src/optimize.rs | 23 ++-- src/packages/arithmetic.rs | 210 +++++++++++++---------------------- src/packages/array_basic.rs | 8 +- src/packages/iter_basic.rs | 18 ++- src/packages/logic.rs | 7 +- src/packages/map_basic.rs | 19 ++-- src/packages/math_basic.rs | 35 +++--- src/packages/string_basic.rs | 7 +- src/packages/string_more.rs | 7 +- src/parser.rs | 67 ++++++----- src/scope.rs | 55 ++++++++- src/token.rs | 4 +- 16 files changed, 337 insertions(+), 353 deletions(-) diff --git a/src/any.rs b/src/any.rs index 520bad0d..a8ee1e66 100644 --- a/src/any.rs +++ b/src/any.rs @@ -270,9 +270,9 @@ impl Dynamic { /// Does this `Dynamic` hold a shared data type /// instead of one of the support system primitive types? - #[cfg(not(feature = "no_shared"))] pub fn is_shared(&self) -> bool { match self.0 { + #[cfg(not(feature = "no_shared"))] Union::Shared(_) => true, _ => false, } @@ -440,7 +440,7 @@ impl Clone for Dynamic { Union::FnPtr(ref value) => Self(Union::FnPtr(value.clone())), Union::Variant(ref value) => (***value).clone_into_dynamic(), #[cfg(not(feature = "no_shared"))] - Union::Shared(ref cell) => Self(Union::Shared(Box::new((**cell).clone()))), + Union::Shared(ref cell) => Self(Union::Shared(cell.clone())), } } } @@ -561,9 +561,13 @@ impl Dynamic { /// Shared `Dynamic` values can be converted seamlessly to and from ordinary `Dynamic` values. /// /// If the `Dynamic` value is already shared, this method returns itself. - #[cfg(not(feature = "no_shared"))] + /// + /// # Panics + /// + /// Panics under the `no_shared` feature. pub fn into_shared(self) -> Self { - match self.0 { + #[cfg(not(feature = "no_shared"))] + return match self.0 { Union::Shared(..) => self, _ => Self(Union::Shared(Box::new(SharedCell { value_type_id: self.type_id(), @@ -574,7 +578,10 @@ impl Dynamic { #[cfg(feature = "sync")] container: Arc::new(RwLock::new(self)), }))), - } + }; + + #[cfg(feature = "no_shared")] + unimplemented!() } /// Convert the `Dynamic` value into specific type. diff --git a/src/api.rs b/src/api.rs index 42ff7037..e6e9ea7a 100644 --- a/src/api.rs +++ b/src/api.rs @@ -1305,16 +1305,15 @@ impl Engine { mut ast: AST, optimization_level: OptimizationLevel, ) -> AST { - #[cfg(not(feature = "no_function"))] - let lib = ast - .lib() - .iter_fn() - .filter(|(_, _, _, f)| f.is_script()) - .map(|(_, _, _, f)| f.get_fn_def().clone()) - .collect(); - - #[cfg(feature = "no_function")] - let lib = Default::default(); + let lib = if cfg!(not(feature = "no_function")) { + ast.lib() + .iter_fn() + .filter(|(_, _, _, f)| f.is_script()) + .map(|(_, _, _, f)| f.get_fn_def().clone()) + .collect() + } else { + Default::default() + }; let stmt = mem::take(ast.statements_mut()); optimize_into_ast(self, scope, stmt, lib, optimization_level) diff --git a/src/engine.rs b/src/engine.rs index bd778e4e..cdd9c040 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -32,6 +32,7 @@ use crate::module::resolvers; use crate::utils::ImmutableString; #[cfg(not(feature = "no_shared"))] +#[cfg(not(feature = "no_object"))] use crate::any::DynamicWriteLock; use crate::stdlib::{ @@ -44,12 +45,13 @@ use crate::stdlib::{ vec::Vec, }; +#[cfg(not(feature = "no_shared"))] +#[cfg(not(feature = "no_object"))] +use crate::stdlib::ops::DerefMut; + #[cfg(not(feature = "no_index"))] use crate::stdlib::any::TypeId; -#[cfg(not(feature = "no_shared"))] -use crate::stdlib::ops::DerefMut; - /// Variable-sized array of `Dynamic` values. /// /// Not available under the `no_index` feature. @@ -134,6 +136,7 @@ pub enum Target<'a> { /// 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_object"))] LockGuard((DynamicWriteLock<'a, Dynamic>, Dynamic)), /// The target is a temporary `Dynamic` value (i.e. the mutation can cause no side effects). Value(Dynamic), @@ -150,6 +153,7 @@ impl Target<'_> { match self { Self::Ref(_) => true, #[cfg(not(feature = "no_shared"))] + #[cfg(not(feature = "no_object"))] Self::LockGuard(_) => true, Self::Value(_) => false, #[cfg(not(feature = "no_index"))] @@ -161,18 +165,32 @@ impl Target<'_> { match self { Self::Ref(_) => false, #[cfg(not(feature = "no_shared"))] + #[cfg(not(feature = "no_object"))] Self::LockGuard(_) => false, Self::Value(_) => true, #[cfg(not(feature = "no_index"))] Self::StringChar(_, _, _) => false, } } + /// Is the `Target` a shared value? + pub fn is_shared(&self) -> bool { + match self { + Self::Ref(r) => r.is_shared(), + #[cfg(not(feature = "no_shared"))] + #[cfg(not(feature = "no_object"))] + Self::LockGuard(_) => true, + Self::Value(r) => r.is_shared(), + #[cfg(not(feature = "no_index"))] + Self::StringChar(_, _, _) => false, + } + } /// Is the `Target` a specific type? #[allow(dead_code)] pub fn is(&self) -> bool { match self { Target::Ref(r) => r.is::(), #[cfg(not(feature = "no_shared"))] + #[cfg(not(feature = "no_object"))] Target::LockGuard((r, _)) => r.is::(), Target::Value(r) => r.is::(), #[cfg(not(feature = "no_index"))] @@ -184,6 +202,7 @@ impl Target<'_> { match self { Self::Ref(r) => r.clone(), // Referenced value is cloned #[cfg(not(feature = "no_shared"))] + #[cfg(not(feature = "no_object"))] Self::LockGuard((_, orig)) => orig, // Original value is simply taken Self::Value(v) => v, // Owned value is simply taken #[cfg(not(feature = "no_index"))] @@ -195,6 +214,7 @@ impl Target<'_> { match self { Self::Ref(r) => *r, #[cfg(not(feature = "no_shared"))] + #[cfg(not(feature = "no_object"))] Self::LockGuard((r, _)) => r.deref_mut(), Self::Value(ref mut r) => r, #[cfg(not(feature = "no_index"))] @@ -207,6 +227,7 @@ impl Target<'_> { match self { Self::Ref(r) => **r = new_val, #[cfg(not(feature = "no_shared"))] + #[cfg(not(feature = "no_object"))] Self::LockGuard((r, _)) => **r = new_val, Self::Value(_) => { return Err(Box::new(EvalAltResult::ErrorAssignmentToUnknownLHS( @@ -243,6 +264,7 @@ impl Target<'_> { 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_object"))] if value.is_shared() { // Cloning is cheap for a shared value let container = value.clone(); @@ -427,11 +449,11 @@ impl Default for Engine { progress: None, // optimization level - #[cfg(feature = "no_optimize")] - optimization_level: OptimizationLevel::None, - - #[cfg(not(feature = "no_optimize"))] - optimization_level: OptimizationLevel::Simple, + optimization_level: if cfg!(feature = "no_optimize") { + OptimizationLevel::None + } else { + OptimizationLevel::Simple + }, #[cfg(not(feature = "unchecked"))] limits: Limits { @@ -639,11 +661,11 @@ impl Engine { debug: Box::new(|_| {}), progress: None, - #[cfg(feature = "no_optimize")] - optimization_level: OptimizationLevel::None, - - #[cfg(not(feature = "no_optimize"))] - optimization_level: OptimizationLevel::Simple, + optimization_level: if cfg!(feature = "no_optimize") { + OptimizationLevel::None + } else { + OptimizationLevel::Simple + }, #[cfg(not(feature = "unchecked"))] limits: Limits { @@ -1340,16 +1362,11 @@ impl Engine { )), // Normal assignment ScopeEntryType::Normal if op.is_empty() => { - #[cfg(not(feature = "no_shared"))] - if lhs_ptr.is_shared() { + if cfg!(not(feature = "no_shared")) && lhs_ptr.is_shared() { *lhs_ptr.write_lock::().unwrap() = rhs_val; } else { *lhs_ptr = rhs_val; } - #[cfg(feature = "no_shared")] - { - *lhs_ptr = rhs_val; - } Ok(Default::default()) } // Op-assignment - in order of precedence: @@ -1394,16 +1411,11 @@ impl Engine { ) .map_err(|err| err.new_position(*op_pos))?; - #[cfg(not(feature = "no_shared"))] - if lhs_ptr.is_shared() { + if cfg!(not(feature = "no_shared")) && lhs_ptr.is_shared() { *lhs_ptr.write_lock::().unwrap() = value; } else { *lhs_ptr = value; } - #[cfg(feature = "no_shared")] - { - *lhs_ptr = value; - } } Ok(Default::default()) } @@ -1845,13 +1857,15 @@ impl Engine { .map_err(|err| err.new_position(stmt.position())) } + /// Check a result to ensure that the data size is within allowable limit. + /// Position in `EvalAltResult` may be None and should be set afterwards. #[cfg(feature = "unchecked")] #[inline(always)] fn check_data_size( &self, result: Result>, ) -> Result> { - return result; + result } /// Check a result to ensure that the data size is within allowable limit. @@ -1861,9 +1875,6 @@ impl Engine { &self, result: Result>, ) -> Result> { - #[cfg(feature = "unchecked")] - return result; - // If no data size limits, just return if self.limits.max_string_size + self.limits.max_array_size + self.limits.max_map_size == 0 { diff --git a/src/fn_call.rs b/src/fn_call.rs index d680800a..cbddcff3 100644 --- a/src/fn_call.rs +++ b/src/fn_call.rs @@ -20,9 +20,8 @@ use crate::utils::StaticVec; #[cfg(not(feature = "no_function"))] use crate::{ - parser::ScriptFnDef, - r#unsafe::unsafe_cast_var_name_to_lifetime, - scope::{Entry as ScopeEntry, EntryType as ScopeEntryType}, + parser::ScriptFnDef, r#unsafe::unsafe_cast_var_name_to_lifetime, + scope::EntryType as ScopeEntryType, }; #[cfg(not(feature = "no_float"))] @@ -34,18 +33,23 @@ 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"))] +use crate::scope::Entry as ScopeEntry; + use crate::stdlib::{ any::{type_name, TypeId}, boxed::Box, - collections::HashSet, convert::TryFrom, format, iter::{empty, once}, mem, - string::{String, ToString}, + string::ToString, vec::Vec, }; +#[cfg(not(feature = "no_capture"))] +use crate::stdlib::{collections::HashSet, string::String}; + /// Extract the property name from a getter function name. #[inline(always)] fn extract_prop_from_getter(_fn_name: &str) -> Option<&str> { @@ -422,7 +426,7 @@ impl Engine { is_ref: bool, is_method: bool, pub_only: bool, - capture: Option, + _capture: Option, def_val: Option, level: usize, ) -> Result<(Dynamic, bool), Box> { @@ -451,12 +455,6 @@ impl Engine { ))) } - // Fn - KEYWORD_IS_SHARED if args.len() == 1 => Err(Box::new(EvalAltResult::ErrorRuntime( - "'is_shared' should not be called in method style. Try is_shared(...);".into(), - Position::none(), - ))), - // eval - reaching this point it must be a method-style call KEYWORD_EVAL if args.len() == 1 && !self.has_override(lib, hash_fn, hash_script, pub_only) => @@ -478,7 +476,7 @@ impl Engine { // Add captured variables into scope #[cfg(not(feature = "no_capture"))] - if let Some(captured) = capture { + if let Some(captured) = _capture { add_captured_variables_into_scope(&func.externals, captured, scope); } @@ -662,22 +660,18 @@ impl Engine { .into(), false, )) - } else if _fn_name == KEYWORD_SHARED && idx.is_empty() { + } else if cfg!(not(feature = "no_shared")) + && _fn_name == KEYWORD_IS_SHARED + && idx.is_empty() + { // take call - #[cfg(not(feature = "no_shared"))] - { - Ok((obj.clone().into_shared(), false)) - } - #[cfg(feature = "no_shared")] - unreachable!() - } else if _fn_name == KEYWORD_TAKE && idx.is_empty() { + Ok((target.is_shared().into(), false)) + } else if cfg!(not(feature = "no_shared")) && _fn_name == KEYWORD_SHARED && idx.is_empty() { // take call - #[cfg(not(feature = "no_shared"))] - { - Ok((obj.clone_inner_data::().unwrap(), false)) - } - #[cfg(feature = "no_shared")] - unreachable!() + 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; @@ -790,8 +784,7 @@ impl Engine { } // Handle is_shared() - #[cfg(not(feature = "no_shared"))] - if name == KEYWORD_IS_SHARED && args_expr.len() == 1 { + if cfg!(not(feature = "no_shared")) && 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)?; @@ -799,8 +792,7 @@ impl Engine { } // Handle shared() - #[cfg(not(feature = "no_shared"))] - if name == KEYWORD_SHARED && args_expr.len() == 1 { + 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)?; @@ -808,8 +800,7 @@ impl Engine { } // Handle take() - #[cfg(not(feature = "no_shared"))] - if name == KEYWORD_TAKE && args_expr.len() == 1 { + 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)?; @@ -875,7 +866,7 @@ impl Engine { let mut arg_values: StaticVec<_>; let mut args: StaticVec<_>; let mut is_ref = false; - let capture = if capture && !scope.is_empty() { + let capture = if cfg!(not(feature = "no_capture")) && capture && !scope.is_empty() { Some(scope.flatten_clone()) } else { None @@ -943,18 +934,10 @@ impl Engine { args_expr: &[Expr], def_val: Option, hash_script: u64, - capture: bool, + _capture: bool, level: usize, ) -> Result> { let modules = modules.as_ref().unwrap(); - - #[cfg(not(feature = "no_capture"))] - let capture = if capture && !scope.is_empty() { - Some(scope.flatten_clone()) - } else { - None - }; - let mut arg_values: StaticVec<_>; let mut args: StaticVec<_>; @@ -1027,8 +1010,12 @@ impl Engine { // Add captured variables into scope #[cfg(not(feature = "no_capture"))] - if let Some(captured) = capture { - add_captured_variables_into_scope(&func.externals, captured, scope); + if _capture && !scope.is_empty() { + add_captured_variables_into_scope( + &func.externals, + scope.flatten_clone(), + scope, + ); } self.call_script_fn(scope, mods, state, lib, &mut None, name, func, args, level) @@ -1073,30 +1060,30 @@ pub fn run_builtin_binary_op( let x = x.clone().cast::(); let y = y.clone().cast::(); - #[cfg(not(feature = "unchecked"))] - match op { - "+" => return add(x, y).map(Into::into).map(Some), - "-" => return sub(x, y).map(Into::into).map(Some), - "*" => return mul(x, y).map(Into::into).map(Some), - "/" => return div(x, y).map(Into::into).map(Some), - "%" => return modulo(x, y).map(Into::into).map(Some), - "~" => return pow_i_i(x, y).map(Into::into).map(Some), - ">>" => return shr(x, y).map(Into::into).map(Some), - "<<" => return shl(x, y).map(Into::into).map(Some), - _ => (), - } - - #[cfg(feature = "unchecked")] - match op { - "+" => return Ok(Some((x + y).into())), - "-" => return Ok(Some((x - y).into())), - "*" => return Ok(Some((x * y).into())), - "/" => return Ok(Some((x / y).into())), - "%" => return Ok(Some((x % y).into())), - "~" => return pow_i_i_u(x, y).map(Into::into).map(Some), - ">>" => return shr_u(x, y).map(Into::into).map(Some), - "<<" => return shl_u(x, y).map(Into::into).map(Some), - _ => (), + if cfg!(not(feature = "unchecked")) { + match op { + "+" => return add(x, y).map(Into::into).map(Some), + "-" => return sub(x, y).map(Into::into).map(Some), + "*" => return mul(x, y).map(Into::into).map(Some), + "/" => return div(x, y).map(Into::into).map(Some), + "%" => return modulo(x, y).map(Into::into).map(Some), + "~" => return pow_i_i(x, y).map(Into::into).map(Some), + ">>" => return shr(x, y).map(Into::into).map(Some), + "<<" => return shl(x, y).map(Into::into).map(Some), + _ => (), + } + } else { + match op { + "+" => return Ok(Some((x + y).into())), + "-" => return Ok(Some((x - y).into())), + "*" => return Ok(Some((x * y).into())), + "/" => return Ok(Some((x / y).into())), + "%" => return Ok(Some((x % y).into())), + "~" => return pow_i_i_u(x, y).map(Into::into).map(Some), + ">>" => return shr_u(x, y).map(Into::into).map(Some), + "<<" => return shl_u(x, y).map(Into::into).map(Some), + _ => (), + } } match op { diff --git a/src/optimize.rs b/src/optimize.rs index 3244540b..9072a550 100644 --- a/src/optimize.rs +++ b/src/optimize.rs @@ -576,17 +576,13 @@ fn optimize_expr(expr: Expr, state: &mut State) -> Expr { // First search in functions lib (can override built-in) // Cater for both normal function call style and method call style (one additional arguments) - #[cfg(not(feature = "no_function"))] - let _has_script_fn = state.lib.iter_fn().find(|(_, _, _, f)| { + let has_script_fn = cfg!(not(feature = "no_function")) && state.lib.iter_fn().find(|(_, _, _, f)| { if !f.is_script() { return false; } let fn_def = f.get_fn_def(); fn_def.name == name && (args.len()..=args.len() + 1).contains(&fn_def.params.len()) }).is_some(); - #[cfg(feature = "no_function")] - let _has_script_fn: bool = false; - - if _has_script_fn { + if has_script_fn { // A script-defined function overrides the built-in function - do not make the call x.3 = x.3.into_iter().map(|a| optimize_expr(a, state)).collect(); return Expr::FnCall(x); @@ -748,11 +744,13 @@ pub fn optimize_into_ast( _functions: Vec, level: OptimizationLevel, ) -> AST { - #[cfg(feature = "no_optimize")] - const level: OptimizationLevel = OptimizationLevel::None; + let level = if cfg!(feature = "no_optimize") { + OptimizationLevel::None + } else { + level + }; - #[cfg(not(feature = "no_function"))] - let lib = { + let lib = if cfg!(not(feature = "no_function")) { let mut module = Module::new(); if !level.is_none() { @@ -814,11 +812,10 @@ pub fn optimize_into_ast( } module + } else { + Default::default() }; - #[cfg(feature = "no_function")] - let lib = Default::default(); - AST::new( match level { OptimizationLevel::None => statements, diff --git a/src/packages/arithmetic.rs b/src/packages/arithmetic.rs index fd95e675..d8e256cc 100644 --- a/src/packages/arithmetic.rs +++ b/src/packages/arithmetic.rs @@ -2,38 +2,29 @@ use crate::def_package; use crate::module::FuncReturn; use crate::parser::INT; -#[cfg(not(feature = "unchecked"))] use crate::{result::EvalAltResult, token::Position}; #[cfg(not(feature = "no_float"))] use crate::parser::FLOAT; -#[cfg(not(feature = "no_float"))] -#[cfg(feature = "no_std")] -use num_traits::*; - -#[cfg(not(feature = "unchecked"))] use num_traits::{ identities::Zero, CheckedAdd, CheckedDiv, CheckedMul, CheckedNeg, CheckedRem, CheckedShl, CheckedShr, CheckedSub, }; -#[cfg(not(feature = "only_i32"))] -#[cfg(not(feature = "only_i64"))] -use crate::stdlib::ops::{BitAnd, BitOr, BitXor}; +#[cfg(feature = "no_std")] +#[cfg(not(feature = "no_float"))] +use num_traits::float::Float; -#[cfg(any(feature = "unchecked", not(feature = "no_float")))] -use crate::stdlib::ops::{Add, Div, Mul, Neg, Rem, Sub}; - -#[cfg(feature = "unchecked")] -use crate::stdlib::ops::{Shl, Shr}; - -#[cfg(not(feature = "unchecked"))] -use crate::stdlib::{boxed::Box, fmt::Display, format}; +use crate::stdlib::{ + boxed::Box, + fmt::Display, + format, + ops::{Add, BitAnd, BitOr, BitXor, Div, Mul, Neg, Rem, Shl, Shr, Sub}, +}; // Checked add -#[cfg(not(feature = "unchecked"))] -pub(crate) fn add(x: T, y: T) -> FuncReturn { +pub fn add(x: T, y: T) -> FuncReturn { x.checked_add(&y).ok_or_else(|| { Box::new(EvalAltResult::ErrorArithmetic( format!("Addition overflow: {} + {}", x, y), @@ -42,8 +33,7 @@ pub(crate) fn add(x: T, y: T) -> FuncReturn { }) } // Checked subtract -#[cfg(not(feature = "unchecked"))] -pub(crate) fn sub(x: T, y: T) -> FuncReturn { +pub fn sub(x: T, y: T) -> FuncReturn { x.checked_sub(&y).ok_or_else(|| { Box::new(EvalAltResult::ErrorArithmetic( format!("Subtraction underflow: {} - {}", x, y), @@ -52,8 +42,7 @@ pub(crate) fn sub(x: T, y: T) -> FuncReturn { }) } // Checked multiply -#[cfg(not(feature = "unchecked"))] -pub(crate) fn mul(x: T, y: T) -> FuncReturn { +pub fn mul(x: T, y: T) -> FuncReturn { x.checked_mul(&y).ok_or_else(|| { Box::new(EvalAltResult::ErrorArithmetic( format!("Multiplication overflow: {} * {}", x, y), @@ -62,8 +51,7 @@ pub(crate) fn mul(x: T, y: T) -> FuncReturn { }) } // Checked divide -#[cfg(not(feature = "unchecked"))] -pub(crate) fn div(x: T, y: T) -> FuncReturn +pub fn div(x: T, y: T) -> FuncReturn where T: Display + CheckedDiv + PartialEq + Zero, { @@ -83,8 +71,7 @@ where }) } // Checked negative - e.g. -(i32::MIN) will overflow i32::MAX -#[cfg(not(feature = "unchecked"))] -pub(crate) fn neg(x: T) -> FuncReturn { +pub fn neg(x: T) -> FuncReturn { x.checked_neg().ok_or_else(|| { Box::new(EvalAltResult::ErrorArithmetic( format!("Negation overflow: -{}", x), @@ -93,8 +80,7 @@ pub(crate) fn neg(x: T) -> FuncReturn { }) } // Checked absolute -#[cfg(not(feature = "unchecked"))] -pub(crate) fn abs(x: T) -> FuncReturn { +pub fn abs(x: T) -> FuncReturn { // FIX - We don't use Signed::abs() here because, contrary to documentation, it panics // when the number is ::MIN instead of returning ::MIN itself. if x >= ::zero() { @@ -109,32 +95,26 @@ pub(crate) fn abs(x: T) -> FuncRetu } } // Unchecked add - may panic on overflow -#[cfg(any(feature = "unchecked", not(feature = "no_float")))] fn add_u(x: T, y: T) -> FuncReturn<::Output> { Ok(x + y) } // Unchecked subtract - may panic on underflow -#[cfg(any(feature = "unchecked", not(feature = "no_float")))] fn sub_u(x: T, y: T) -> FuncReturn<::Output> { Ok(x - y) } // Unchecked multiply - may panic on overflow -#[cfg(any(feature = "unchecked", not(feature = "no_float")))] fn mul_u(x: T, y: T) -> FuncReturn<::Output> { Ok(x * y) } // Unchecked divide - may panic when dividing by zero -#[cfg(any(feature = "unchecked", not(feature = "no_float")))] fn div_u(x: T, y: T) -> FuncReturn<::Output> { Ok(x / y) } // Unchecked negative - may panic on overflow -#[cfg(any(feature = "unchecked", not(feature = "no_float")))] fn neg_u(x: T) -> FuncReturn<::Output> { Ok(-x) } // Unchecked absolute - may panic on overflow -#[cfg(any(feature = "unchecked", not(feature = "no_float")))] fn abs_u(x: T) -> FuncReturn<::Output> where T: Neg + PartialOrd + Default + Into<::Output>, @@ -147,24 +127,17 @@ where } } // Bit operators -#[cfg(not(feature = "only_i32"))] -#[cfg(not(feature = "only_i64"))] fn binary_and(x: T, y: T) -> FuncReturn<::Output> { Ok(x & y) } -#[cfg(not(feature = "only_i32"))] -#[cfg(not(feature = "only_i64"))] fn binary_or(x: T, y: T) -> FuncReturn<::Output> { Ok(x | y) } -#[cfg(not(feature = "only_i32"))] -#[cfg(not(feature = "only_i64"))] fn binary_xor(x: T, y: T) -> FuncReturn<::Output> { Ok(x ^ y) } // Checked left-shift -#[cfg(not(feature = "unchecked"))] -pub(crate) fn shl(x: T, y: INT) -> FuncReturn { +pub fn shl(x: T, y: INT) -> FuncReturn { // Cannot shift by a negative number of bits if y < 0 { return Err(Box::new(EvalAltResult::ErrorArithmetic( @@ -181,8 +154,7 @@ pub(crate) fn shl(x: T, y: INT) -> FuncReturn { }) } // Checked right-shift -#[cfg(not(feature = "unchecked"))] -pub(crate) fn shr(x: T, y: INT) -> FuncReturn { +pub fn shr(x: T, y: INT) -> FuncReturn { // Cannot shift by a negative number of bits if y < 0 { return Err(Box::new(EvalAltResult::ErrorArithmetic( @@ -199,18 +171,15 @@ pub(crate) fn shr(x: T, y: INT) -> FuncReturn { }) } // Unchecked left-shift - may panic if shifting by a negative number of bits -#[cfg(feature = "unchecked")] -pub(crate) fn shl_u>(x: T, y: T) -> FuncReturn<>::Output> { +pub fn shl_u>(x: T, y: T) -> FuncReturn<>::Output> { Ok(x.shl(y)) } // Unchecked right-shift - may panic if shifting by a negative number of bits -#[cfg(feature = "unchecked")] -pub(crate) fn shr_u>(x: T, y: T) -> FuncReturn<>::Output> { +pub fn shr_u>(x: T, y: T) -> FuncReturn<>::Output> { Ok(x.shr(y)) } // Checked modulo -#[cfg(not(feature = "unchecked"))] -pub(crate) fn modulo(x: T, y: T) -> FuncReturn { +pub fn modulo(x: T, y: T) -> FuncReturn { x.checked_rem(&y).ok_or_else(|| { Box::new(EvalAltResult::ErrorArithmetic( format!("Modulo division by zero or overflow: {} % {}", x, y), @@ -219,62 +188,58 @@ pub(crate) fn modulo(x: T, y: T) -> FuncReturn { }) } // Unchecked modulo - may panic if dividing by zero -#[cfg(any(feature = "unchecked", not(feature = "no_float")))] fn modulo_u(x: T, y: T) -> FuncReturn<::Output> { Ok(x % y) } // Checked power -#[cfg(not(feature = "unchecked"))] -pub(crate) fn pow_i_i(x: INT, y: INT) -> FuncReturn { - #[cfg(not(feature = "only_i32"))] - if y > (u32::MAX as INT) { - Err(Box::new(EvalAltResult::ErrorArithmetic( - format!("Integer raised to too large an index: {} ~ {}", x, y), - Position::none(), - ))) - } else if y < 0 { - Err(Box::new(EvalAltResult::ErrorArithmetic( - format!("Integer raised to a negative index: {} ~ {}", x, y), - Position::none(), - ))) - } else { - x.checked_pow(y as u32).ok_or_else(|| { - Box::new(EvalAltResult::ErrorArithmetic( - format!("Power overflow: {} ~ {}", x, y), +pub fn pow_i_i(x: INT, y: INT) -> FuncReturn { + if cfg!(not(feature = "only_i32")) { + if y > (u32::MAX as INT) { + Err(Box::new(EvalAltResult::ErrorArithmetic( + format!("Integer raised to too large an index: {} ~ {}", x, y), Position::none(), - )) - }) - } - - #[cfg(feature = "only_i32")] - if y < 0 { - Err(Box::new(EvalAltResult::ErrorArithmetic( - format!("Integer raised to a negative index: {} ~ {}", x, y), - Position::none(), - ))) - } else { - x.checked_pow(y as u32).ok_or_else(|| { - Box::new(EvalAltResult::ErrorArithmetic( - format!("Power overflow: {} ~ {}", x, y), + ))) + } else if y < 0 { + Err(Box::new(EvalAltResult::ErrorArithmetic( + format!("Integer raised to a negative index: {} ~ {}", x, y), Position::none(), - )) - }) + ))) + } else { + x.checked_pow(y as u32).ok_or_else(|| { + Box::new(EvalAltResult::ErrorArithmetic( + format!("Power overflow: {} ~ {}", x, y), + Position::none(), + )) + }) + } + } else { + if y < 0 { + Err(Box::new(EvalAltResult::ErrorArithmetic( + format!("Integer raised to a negative index: {} ~ {}", x, y), + Position::none(), + ))) + } else { + x.checked_pow(y as u32).ok_or_else(|| { + Box::new(EvalAltResult::ErrorArithmetic( + format!("Power overflow: {} ~ {}", x, y), + Position::none(), + )) + }) + } } } // Unchecked integer power - may panic on overflow or if the power index is too high (> u32::MAX) -#[cfg(feature = "unchecked")] -pub(crate) fn pow_i_i_u(x: INT, y: INT) -> FuncReturn { +pub fn pow_i_i_u(x: INT, y: INT) -> FuncReturn { Ok(x.pow(y as u32)) } // Floating-point power - always well-defined #[cfg(not(feature = "no_float"))] -pub(crate) fn pow_f_f(x: FLOAT, y: FLOAT) -> FuncReturn { +pub fn pow_f_f(x: FLOAT, y: FLOAT) -> FuncReturn { Ok(x.powf(y)) } // Checked power #[cfg(not(feature = "no_float"))] -#[cfg(not(feature = "unchecked"))] -pub(crate) fn pow_f_i(x: FLOAT, y: INT) -> FuncReturn { +pub fn pow_f_i(x: FLOAT, y: INT) -> FuncReturn { // Raise to power that is larger than an i32 if y > (i32::MAX as INT) { return Err(Box::new(EvalAltResult::ErrorArithmetic( @@ -286,9 +251,8 @@ pub(crate) fn pow_f_i(x: FLOAT, y: INT) -> FuncReturn { Ok(x.powi(y as i32)) } // Unchecked power - may be incorrect if the power index is too high (> i32::MAX) -#[cfg(feature = "unchecked")] #[cfg(not(feature = "no_float"))] -pub(crate) fn pow_f_i_u(x: FLOAT, y: INT) -> FuncReturn { +pub fn pow_f_i_u(x: FLOAT, y: INT) -> FuncReturn { Ok(x.powi(y as i32)) } @@ -317,11 +281,8 @@ macro_rules! reg_sign { } def_package!(crate:ArithmeticPackage:"Basic arithmetic", lib, { - #[cfg(not(feature = "only_i32"))] - #[cfg(not(feature = "only_i64"))] - { - #[cfg(not(feature = "unchecked"))] - { + if cfg!(not(feature = "only_i32")) && cfg!(not(feature = "only_i64")) { + if cfg!(not(feature = "unchecked")) { // Checked basic arithmetic reg_op!(lib, "+", add, i8, u8, i16, u16, i32, u32, u64); reg_op!(lib, "-", sub, i8, u8, i16, u16, i32, u32, u64); @@ -332,8 +293,7 @@ def_package!(crate:ArithmeticPackage:"Basic arithmetic", lib, { reg_op!(lib, ">>", shr, i8, u8, i16, u16, i32, u32, u64); reg_op!(lib, "%", modulo, i8, u8, i16, u16, i32, u32, u64); - #[cfg(not(target_arch = "wasm32"))] - { + if cfg!(not(target_arch = "wasm32")) { reg_op!(lib, "+", add, i128, u128); reg_op!(lib, "-", sub, i128, u128); reg_op!(lib, "*", mul, i128, u128); @@ -345,8 +305,7 @@ def_package!(crate:ArithmeticPackage:"Basic arithmetic", lib, { } } - #[cfg(feature = "unchecked")] - { + if cfg!(feature = "unchecked") { // Unchecked basic arithmetic reg_op!(lib, "+", add_u, i8, u8, i16, u16, i32, u32, u64); reg_op!(lib, "-", sub_u, i8, u8, i16, u16, i32, u32, u64); @@ -357,8 +316,7 @@ def_package!(crate:ArithmeticPackage:"Basic arithmetic", lib, { reg_op!(lib, ">>", shr_u, i64, i8, u8, i16, u16, i32, u32, u64); reg_op!(lib, "%", modulo_u, i8, u8, i16, u16, i32, u32, u64); - #[cfg(not(target_arch = "wasm32"))] - { + if cfg!(not(target_arch = "wasm32")) { reg_op!(lib, "+", add_u, i128, u128); reg_op!(lib, "-", sub_u, i128, u128); reg_op!(lib, "*", mul_u, i128, u128); @@ -372,13 +330,13 @@ def_package!(crate:ArithmeticPackage:"Basic arithmetic", lib, { reg_sign!(lib, "sign", INT, i8, i16, i32, i64); - #[cfg(not(target_arch = "wasm32"))] - reg_sign!(lib, "sign", INT, i128); + if cfg!(not(target_arch = "wasm32")) { + reg_sign!(lib, "sign", INT, i128); + } } // Basic arithmetic for floating-point - no need to check - #[cfg(not(feature = "no_float"))] - { + if cfg!(not(feature = "no_float")) { reg_op!(lib, "+", add_u, f32); reg_op!(lib, "-", sub_u, f32); reg_op!(lib, "*", mul_u, f32); @@ -387,15 +345,12 @@ def_package!(crate:ArithmeticPackage:"Basic arithmetic", lib, { reg_sign!(lib, "sign", f64, f64); } - #[cfg(not(feature = "only_i32"))] - #[cfg(not(feature = "only_i64"))] - { + if cfg!(not(feature = "only_i32")) && cfg!(not(feature = "only_i64")) { reg_op!(lib, "|", binary_or, i8, u8, i16, u16, i32, u32, u64); reg_op!(lib, "&", binary_and, i8, u8, i16, u16, i32, u32, u64); reg_op!(lib, "^", binary_xor, i8, u8, i16, u16, i32, u32, u64); - #[cfg(not(target_arch = "wasm32"))] - { + if cfg!(not(target_arch = "wasm32")) { reg_op!(lib, "|", binary_or, i128, u128); reg_op!(lib, "&", binary_and, i128, u128); reg_op!(lib, "^", binary_xor, i128, u128); @@ -405,12 +360,11 @@ def_package!(crate:ArithmeticPackage:"Basic arithmetic", lib, { #[cfg(not(feature = "no_float"))] { // Checked power - #[cfg(not(feature = "unchecked"))] - lib.set_fn_2("~", pow_f_i); - - // Unchecked power - #[cfg(feature = "unchecked")] - lib.set_fn_2("~", pow_f_i_u); + if cfg!(not(feature = "unchecked")) { + lib.set_fn_2("~", pow_f_i); + } else { + lib.set_fn_2("~", pow_f_i_u); + } // Floating-point modulo and power reg_op!(lib, "%", modulo_u, f32); @@ -421,19 +375,15 @@ def_package!(crate:ArithmeticPackage:"Basic arithmetic", lib, { } // Checked unary - #[cfg(not(feature = "unchecked"))] - { + if cfg!(not(feature = "unchecked")) { reg_unary!(lib, "-", neg, INT); reg_unary!(lib, "abs", abs, INT); - #[cfg(not(feature = "only_i32"))] - #[cfg(not(feature = "only_i64"))] - { + if cfg!(not(feature = "only_i32")) && cfg!(not(feature = "only_i64")) { reg_unary!(lib, "-", neg, i8, i16, i32, i64); reg_unary!(lib, "abs", abs, i8, i16, i32, i64); - #[cfg(not(target_arch = "wasm32"))] - { + if cfg!(not(target_arch = "wasm32")) { reg_unary!(lib, "-", neg, i128); reg_unary!(lib, "abs", abs, i128); } @@ -441,19 +391,15 @@ def_package!(crate:ArithmeticPackage:"Basic arithmetic", lib, { } // Unchecked unary - #[cfg(feature = "unchecked")] - { + if cfg!(feature = "unchecked") { reg_unary!(lib, "-", neg_u, INT); reg_unary!(lib, "abs", abs_u, INT); - #[cfg(not(feature = "only_i32"))] - #[cfg(not(feature = "only_i64"))] - { + if cfg!(not(feature = "only_i32")) && cfg!(not(feature = "only_i64")) { reg_unary!(lib, "-", neg_u, i8, i16, i32, i64); reg_unary!(lib, "abs", abs_u, i8, i16, i32, i64); - #[cfg(not(target_arch = "wasm32"))] - { + if cfg!(not(target_arch = "wasm32")) { reg_unary!(lib, "-", neg_u, i128); reg_unary!(lib, "abs", abs_u, i128); } diff --git a/src/packages/array_basic.rs b/src/packages/array_basic.rs index 5ca3b1c7..c246f1d5 100644 --- a/src/packages/array_basic.rs +++ b/src/packages/array_basic.rs @@ -82,7 +82,6 @@ macro_rules! reg_pad { }; } -#[cfg(not(feature = "no_index"))] def_package!(crate:BasicArrayPackage:"Basic array utilities.", lib, { reg_op!(lib, "push", push, INT, bool, char, ImmutableString, Array, ()); reg_pad!(lib, "pad", pad, INT, bool, char, ImmutableString, Array, ()); @@ -104,15 +103,12 @@ def_package!(crate:BasicArrayPackage:"Basic array utilities.", lib, { }, ); - #[cfg(not(feature = "only_i32"))] - #[cfg(not(feature = "only_i64"))] - { + if cfg!(not(feature = "only_i32")) && cfg!(not(feature = "only_i64")) { reg_op!(lib, "push", push, i8, u8, i16, u16, i32, i64, u32, u64); reg_pad!(lib, "pad", pad, i8, u8, i16, u16, i32, u32, i64, u64); reg_tri!(lib, "insert", ins, i8, u8, i16, u16, i32, i64, u32, u64); - #[cfg(not(target_arch = "wasm32"))] - { + if cfg!(not(target_arch = "wasm32")) { reg_op!(lib, "push", push, i128, u128); reg_pad!(lib, "pad", pad, i128, u128); reg_tri!(lib, "insert", ins, i128, u128); diff --git a/src/packages/iter_basic.rs b/src/packages/iter_basic.rs index cccf4b6c..8914c6a5 100644 --- a/src/packages/iter_basic.rs +++ b/src/packages/iter_basic.rs @@ -73,9 +73,7 @@ def_package!(crate:BasicIteratorPackage:"Basic range iterators.", lib, { reg_range::(lib); lib.set_fn_2("range", get_range::); - #[cfg(not(feature = "only_i32"))] - #[cfg(not(feature = "only_i64"))] - { + if cfg!(not(feature = "only_i32")) && cfg!(not(feature = "only_i64")) { macro_rules! reg_range { ($lib:expr, $x:expr, $( $y:ty ),*) => ( $( @@ -87,16 +85,15 @@ def_package!(crate:BasicIteratorPackage:"Basic range iterators.", lib, { reg_range!(lib, "range", i8, u8, i16, u16, i32, i64, u32, u64); - #[cfg(not(target_arch = "wasm32"))] - reg_range!(lib, "range", i128, u128); + if cfg!(not(target_arch = "wasm32")) { + reg_range!(lib, "range", i128, u128); + } } reg_step::(lib); lib.set_fn_3("range", get_step_range::); - #[cfg(not(feature = "only_i32"))] - #[cfg(not(feature = "only_i64"))] - { + if cfg!(not(feature = "only_i32")) && cfg!(not(feature = "only_i64")) { macro_rules! reg_step { ($lib:expr, $x:expr, $( $y:ty ),*) => ( $( @@ -108,7 +105,8 @@ def_package!(crate:BasicIteratorPackage:"Basic range iterators.", lib, { reg_step!(lib, "range", i8, u8, i16, u16, i32, i64, u32, u64); - #[cfg(not(target_arch = "wasm32"))] - reg_step!(lib, "range", i128, u128); + if cfg!(not(target_arch = "wasm32")) { + reg_step!(lib, "range", i128, u128); + } } }); diff --git a/src/packages/logic.rs b/src/packages/logic.rs index 1cb4b437..6c70a38c 100644 --- a/src/packages/logic.rs +++ b/src/packages/logic.rs @@ -33,9 +33,7 @@ macro_rules! reg_op { } def_package!(crate:LogicPackage:"Logical operators.", lib, { - #[cfg(not(feature = "only_i32"))] - #[cfg(not(feature = "only_i64"))] - { + if cfg!(not(feature = "only_i32")) && cfg!(not(feature = "only_i64")) { reg_op!(lib, "<", lt, i8, u8, i16, u16, i32, u32, u64); reg_op!(lib, "<=", lte, i8, u8, i16, u16, i32, u32, u64); reg_op!(lib, ">", gt, i8, u8, i16, u16, i32, u32, u64); @@ -43,8 +41,7 @@ def_package!(crate:LogicPackage:"Logical operators.", lib, { reg_op!(lib, "==", eq, i8, u8, i16, u16, i32, u32, u64); reg_op!(lib, "!=", ne, i8, u8, i16, u16, i32, u32, u64); - #[cfg(not(target_arch = "wasm32"))] - { + if cfg!(not(target_arch = "wasm32")) { reg_op!(lib, "<", lt, i128, u128); reg_op!(lib, "<=", lte, i128, u128); reg_op!(lib, ">", gt, i128, u128); diff --git a/src/packages/map_basic.rs b/src/packages/map_basic.rs index 9437bc67..4e2e58ad 100644 --- a/src/packages/map_basic.rs +++ b/src/packages/map_basic.rs @@ -1,25 +1,20 @@ #![cfg(not(feature = "no_object"))] +use crate::any::Dynamic; use crate::def_package; use crate::engine::Map; +use crate::module::FuncReturn; use crate::parser::{ImmutableString, INT}; -#[cfg(not(feature = "no_index"))] -use crate::{any::Dynamic, module::FuncReturn}; - -#[cfg(not(feature = "no_index"))] use crate::stdlib::vec::Vec; -#[cfg(not(feature = "no_index"))] fn map_get_keys(map: &mut Map) -> FuncReturn> { Ok(map.iter().map(|(k, _)| k.clone().into()).collect()) } -#[cfg(not(feature = "no_index"))] fn map_get_values(map: &mut Map) -> FuncReturn> { Ok(map.iter().map(|(_, v)| v.clone()).collect()) } -#[cfg(not(feature = "no_object"))] def_package!(crate:BasicMapPackage:"Basic object map utilities.", lib, { lib.set_fn_2_mut( "has", @@ -74,9 +69,11 @@ def_package!(crate:BasicMapPackage:"Basic object map utilities.", lib, { ); // Register map access functions - #[cfg(not(feature = "no_index"))] - lib.set_fn_1_mut("keys", map_get_keys); + if cfg!(not(feature = "no_index")) { + lib.set_fn_1_mut("keys", map_get_keys); + } - #[cfg(not(feature = "no_index"))] - lib.set_fn_1_mut("values", map_get_values); + if cfg!(not(feature = "no_index")) { + lib.set_fn_1_mut("values", map_get_values); + } }); diff --git a/src/packages/math_basic.rs b/src/packages/math_basic.rs index 55bb68a3..f8fd2d33 100644 --- a/src/packages/math_basic.rs +++ b/src/packages/math_basic.rs @@ -5,22 +5,20 @@ use crate::parser::INT; use crate::parser::FLOAT; #[cfg(not(feature = "no_float"))] -#[cfg(not(feature = "unchecked"))] use crate::{result::EvalAltResult, token::Position}; -#[cfg(not(feature = "no_float"))] #[cfg(feature = "no_std")] -use num_traits::*; +#[cfg(not(feature = "no_float"))] +use num_traits::float::Float; #[cfg(not(feature = "no_float"))] -#[cfg(not(feature = "unchecked"))] use crate::stdlib::{boxed::Box, format}; +#[allow(dead_code)] #[cfg(feature = "only_i32")] -#[cfg(not(feature = "unchecked"))] pub const MAX_INT: INT = i32::MAX; +#[allow(dead_code)] #[cfg(not(feature = "only_i32"))] -#[cfg(not(feature = "unchecked"))] pub const MAX_INT: INT = i64::MAX; def_package!(crate:BasicMathPackage:"Basic mathematic functions.", lib, { @@ -69,9 +67,7 @@ def_package!(crate:BasicMathPackage:"Basic mathematic functions.", lib, { lib.set_fn_1("to_float", |x: INT| Ok(x as FLOAT)); lib.set_fn_1("to_float", |x: f32| Ok(x as FLOAT)); - #[cfg(not(feature = "only_i32"))] - #[cfg(not(feature = "only_i64"))] - { + if cfg!(not(feature = "only_i32")) && cfg!(not(feature = "only_i64")) { lib.set_fn_1("to_float", |x: i8| Ok(x as FLOAT)); lib.set_fn_1("to_float", |x: u8| Ok(x as FLOAT)); lib.set_fn_1("to_float", |x: i16| Ok(x as FLOAT)); @@ -81,8 +77,7 @@ def_package!(crate:BasicMathPackage:"Basic mathematic functions.", lib, { lib.set_fn_1("to_float", |x: i64| Ok(x as FLOAT)); lib.set_fn_1("to_float", |x: u64| Ok(x as FLOAT)); - #[cfg(not(target_arch = "wasm32"))] - { + if cfg!(not(target_arch = "wasm32")) { lib.set_fn_1("to_float", |x: i128| Ok(x as FLOAT)); lib.set_fn_1("to_float", |x: u128| Ok(x as FLOAT)); } @@ -91,28 +86,25 @@ def_package!(crate:BasicMathPackage:"Basic mathematic functions.", lib, { lib.set_fn_1("to_int", |ch: char| Ok(ch as INT)); - #[cfg(not(feature = "only_i32"))] - #[cfg(not(feature = "only_i64"))] - { + if cfg!(not(feature = "only_i32")) && cfg!(not(feature = "only_i64")) { lib.set_fn_1("to_int", |x: i8| Ok(x as INT)); lib.set_fn_1("to_int", |x: u8| Ok(x as INT)); lib.set_fn_1("to_int", |x: i16| Ok(x as INT)); lib.set_fn_1("to_int", |x: u16| Ok(x as INT)); } - #[cfg(not(feature = "only_i32"))] - { + if cfg!(not(feature = "only_i32")) { lib.set_fn_1("to_int", |x: i32| Ok(x as INT)); lib.set_fn_1("to_int", |x: u64| Ok(x as INT)); - #[cfg(feature = "only_i64")] - lib.set_fn_1("to_int", |x: u32| Ok(x as INT)); + if cfg!(feature = "only_i64") { + lib.set_fn_1("to_int", |x: u32| Ok(x as INT)); + } } #[cfg(not(feature = "no_float"))] { - #[cfg(not(feature = "unchecked"))] - { + if cfg!(not(feature = "unchecked")) { lib.set_fn_1( "to_int", |x: f32| { @@ -141,8 +133,7 @@ def_package!(crate:BasicMathPackage:"Basic mathematic functions.", lib, { ); } - #[cfg(feature = "unchecked")] - { + if cfg!(feature = "unchecked") { lib.set_fn_1("to_int", |x: f32| Ok(x as INT)); lib.set_fn_1("to_int", |x: f64| Ok(x as INT)); } diff --git a/src/packages/string_basic.rs b/src/packages/string_basic.rs index 2e405004..36f4f333 100644 --- a/src/packages/string_basic.rs +++ b/src/packages/string_basic.rs @@ -48,9 +48,7 @@ def_package!(crate:BasicStringPackage:"Basic string utilities, including printin reg_op!(lib, KEYWORD_DEBUG, to_debug, INT, bool, (), char, ImmutableString); - #[cfg(not(feature = "only_i32"))] - #[cfg(not(feature = "only_i64"))] - { + if cfg!(not(feature = "only_i32")) && cfg!(not(feature = "only_i64")) { reg_op!(lib, KEYWORD_PRINT, to_string, i8, u8, i16, u16, i32, u32); reg_op!(lib, FN_TO_STRING, to_string, i8, u8, i16, u16, i32, u32); reg_op!(lib, KEYWORD_DEBUG, to_debug, i8, u8, i16, u16, i32, u32); @@ -58,8 +56,7 @@ def_package!(crate:BasicStringPackage:"Basic string utilities, including printin reg_op!(lib, FN_TO_STRING, to_string, i64, u64); reg_op!(lib, KEYWORD_DEBUG, to_debug, i64, u64); - #[cfg(not(target_arch = "wasm32"))] - { + if cfg!(not(target_arch = "wasm32")) { reg_op!(lib, KEYWORD_PRINT, to_string, i128, u128); reg_op!(lib, FN_TO_STRING, to_string, i128, u128); reg_op!(lib, KEYWORD_DEBUG, to_debug, i128, u128); diff --git a/src/packages/string_more.rs b/src/packages/string_more.rs index b50d6915..130af8b8 100644 --- a/src/packages/string_more.rs +++ b/src/packages/string_more.rs @@ -94,14 +94,11 @@ def_package!(crate:MoreStringPackage:"Additional string utilities, including str reg_op!(lib, "+", prepend, INT, bool, char); lib.set_fn_2("+", |_: (), y: ImmutableString| Ok(y)); - #[cfg(not(feature = "only_i32"))] - #[cfg(not(feature = "only_i64"))] - { + if cfg!(not(feature = "only_i32")) && cfg!(not(feature = "only_i64")) { reg_op!(lib, "+", append, i8, u8, i16, u16, i32, i64, u32, u64); reg_op!(lib, "+", prepend, i8, u8, i16, u16, i32, i64, u32, u64); - #[cfg(not(target_arch = "wasm32"))] - { + if cfg!(not(target_arch = "wasm32")) { reg_op!(lib, "+", append, i128, u128); reg_op!(lib, "+", prepend, i128, u128); } diff --git a/src/parser.rs b/src/parser.rs index 2f832a75..08869146 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -2,7 +2,9 @@ use crate::any::{Dynamic, Union}; use crate::calc_fn_hash; -use crate::engine::{Engine, KEYWORD_THIS, MARKER_BLOCK, MARKER_EXPR, MARKER_IDENT}; +use crate::engine::{ + Engine, KEYWORD_FN_PTR_CURRY, KEYWORD_THIS, MARKER_BLOCK, MARKER_EXPR, MARKER_IDENT, +}; use crate::error::{LexError, ParseError, ParseErrorType}; use crate::fn_native::Shared; use crate::module::{Module, ModuleRef}; @@ -15,9 +17,6 @@ use crate::utils::{StaticVec, StraightHasherBuilder}; #[cfg(not(feature = "no_function"))] use crate::engine::FN_ANONYMOUS; -#[cfg(not(feature = "no_capture"))] -use crate::engine::KEYWORD_FN_PTR_CURRY; - #[cfg(not(feature = "no_object"))] use crate::engine::{make_getter, make_setter}; @@ -25,7 +24,7 @@ use crate::stdlib::{ borrow::Cow, boxed::Box, char, - collections::{HashMap, HashSet}, + collections::HashMap, fmt, format, hash::{Hash, Hasher}, iter::empty, @@ -40,6 +39,9 @@ use crate::stdlib::{ #[cfg(not(feature = "no_function"))] use crate::stdlib::collections::hash_map::DefaultHasher; +#[cfg(not(feature = "no_capture"))] +use crate::stdlib::collections::HashSet; + #[cfg(feature = "no_std")] #[cfg(not(feature = "no_function"))] use ahash::AHasher; @@ -2211,11 +2213,13 @@ fn parse_binary_op( let (op_token, pos) = input.next().unwrap(); - #[cfg(any(not(feature = "no_object"), not(feature = "no_capture")))] - if op_token == Token::Period { + 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. - state.capture = false; + #[cfg(not(feature = "no_capture"))] + { + state.capture = false; + } } } @@ -3112,7 +3116,6 @@ fn parse_fn( } /// Creates a curried expression from a list of external variables -#[cfg(not(feature = "no_capture"))] fn make_curry_from_externals( fn_expr: Expr, externals: StaticVec<(String, Position)>, @@ -3209,26 +3212,31 @@ fn parse_anon_fn( let body = parse_stmt(input, state, lib, settings.level_up()) .map(|stmt| stmt.unwrap_or_else(|| Stmt::Noop(pos)))?; - #[cfg(feature = "no_capture")] - let params: StaticVec<_> = params.into_iter().map(|(v, _)| v).collect(); - // External variables may need to be processed in a consistent order, // so extract them into a list. - #[cfg(not(feature = "no_capture"))] - let externals: StaticVec<_> = state - .externals - .iter() - .map(|(k, &v)| (k.clone(), v)) - .collect(); + let externals: StaticVec<_> = { + #[cfg(not(feature = "no_capture"))] + { + state + .externals + .iter() + .map(|(k, &v)| (k.clone(), v)) + .collect() + } + #[cfg(feature = "no_capture")] + Default::default() + }; - // Add parameters that are auto-curried - #[cfg(not(feature = "no_capture"))] - let params: StaticVec<_> = externals - .iter() - .map(|(k, _)| k) - .cloned() - .chain(params.into_iter().map(|(v, _)| v)) - .collect(); + let params: StaticVec<_> = if cfg!(not(feature = "no_capture")) { + externals + .iter() + .map(|(k, _)| k) + .cloned() + .chain(params.into_iter().map(|(v, _)| v)) + .collect() + } else { + params.into_iter().map(|(v, _)| v).collect() + }; // Calculate hash #[cfg(feature = "no_std")] @@ -3257,8 +3265,11 @@ fn parse_anon_fn( let expr = Expr::FnPointer(Box::new((fn_name, settings.pos))); - #[cfg(not(feature = "no_capture"))] - let expr = make_curry_from_externals(expr, externals, settings.pos); + let expr = if cfg!(not(feature = "no_capture")) { + make_curry_from_externals(expr, externals, settings.pos) + } else { + expr + }; Ok((expr, script)) } diff --git a/src/scope.rs b/src/scope.rs index b1b9a3b3..37f71e99 100644 --- a/src/scope.rs +++ b/src/scope.rs @@ -158,6 +158,32 @@ 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 @@ -200,6 +226,34 @@ 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. @@ -393,7 +447,6 @@ impl<'a> Scope<'a> { /// Clone the Scope, keeping only the last instances of each variable name. /// Shadowed variables are omitted in the copy. - #[cfg(not(feature = "no_capture"))] pub(crate) fn flatten_clone(&self) -> Self { let mut entries: HashMap<&str, Entry> = Default::default(); diff --git a/src/token.rs b/src/token.rs index dfd50c27..920fb33a 100644 --- a/src/token.rs +++ b/src/token.rs @@ -507,8 +507,8 @@ impl Token { | "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_SHARED | KEYWORD_TAKE - | KEYWORD_THIS => Reserved(syntax.into()), + | KEYWORD_FN_PTR_CALL | KEYWORD_FN_PTR_CURRY | KEYWORD_IS_SHARED | KEYWORD_SHARED + | KEYWORD_TAKE | KEYWORD_THIS => Reserved(syntax.into()), _ => return None, }) From 5873bccd506756216d28b265ed4759f672f68619 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Fri, 31 Jul 2020 23:37:30 +0800 Subject: [PATCH 23/40] Fix no_index and no_object. --- src/engine.rs | 68 ++++++++++++++++++--------------------------------- 1 file changed, 24 insertions(+), 44 deletions(-) diff --git a/src/engine.rs b/src/engine.rs index cdd9c040..a011bf67 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -734,69 +734,49 @@ impl Engine { _ if _new_val.is_some() => { let mut idx_val2 = idx_val.clone(); - // `next` is introduced to bypass double mutable borrowing of target - #[cfg(not(feature = "no_index"))] - let mut next: Option<(u8, Dynamic)>; - - match self.get_indexed_mut(state, lib, target, idx_val, pos, true, level) { - // Indexed value is an owned value - the only possibility is an indexer - // Try to call an index setter + // `call_setter` is introduced to bypass double mutable borrowing of target + let mut call_setter = match self + .get_indexed_mut(state, lib, target, idx_val, pos, true, level) + { + // Indexed value is an owned value - the only possibility is a value from an indexer. + // Try to call an index setter to update it back, but no need to raise an error + // if the indexer is read-only. #[cfg(not(feature = "no_index"))] - Ok(obj_ptr) if obj_ptr.is_value() => { - next = Some((1, _new_val.unwrap())); - } + Ok(obj_ptr) if obj_ptr.is_value() => Some((false, _new_val.unwrap())), // Indexed value is a reference - update directly Ok(ref mut obj_ptr) => { obj_ptr .set_value(_new_val.unwrap()) .map_err(|err| err.new_position(rhs.position()))?; - #[cfg(not(feature = "no_index"))] - { - next = None; - } + None } Err(err) => match *err { // No index getter - try to call an index setter - #[cfg(not(feature = "no_index"))] EvalAltResult::ErrorIndexingType(_, _) => { - next = Some((2, _new_val.unwrap())); + // Raise error if there is no index getter nor setter + Some((true, _new_val.unwrap())) } - // Error + // Any other error - return err => return Err(Box::new(err)), }, }; - #[cfg(not(feature = "no_index"))] - match &mut next { - // next step is custom index setter call - Some((1, _new_val)) => { - let args = &mut [target.as_mut(), &mut idx_val2, _new_val]; + if let Some((must_have_setter, ref mut new_val)) = call_setter { + let args = &mut [target.as_mut(), &mut idx_val2, new_val]; - self.exec_fn_call( - state, lib, FN_IDX_SET, 0, args, is_ref, true, false, None, - None, level, - ) - .or_else(|err| match *err { + match self.exec_fn_call( + state, lib, FN_IDX_SET, 0, args, is_ref, true, false, None, None, + level, + ) { + Ok(_) => (), + Err(err) if !must_have_setter => match *err { // If there is no index setter, no need to set it back because the indexer is read-only - EvalAltResult::ErrorFunctionNotFound(_, _) => { - Ok(Default::default()) - } - _ => Err(err), - })?; + EvalAltResult::ErrorFunctionNotFound(_, _) => (), + _ => return Err(err), + }, + err => return err, } - - // next step is custom index setter call in case of error - Some((2, _new_val)) => { - let args = &mut [target.as_mut(), &mut idx_val2, _new_val]; - - self.exec_fn_call( - state, lib, FN_IDX_SET, 0, args, is_ref, true, false, None, - None, level, - )?; - } - None => (), - _ => unreachable!(), } Ok(Default::default()) From a2858e0fd31894a5a07f40288aaa6cc55cd3f1ac Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Sat, 1 Aug 2020 12:19:27 +0800 Subject: [PATCH 24/40] Refine feature gates --- src/fn_call.rs | 96 ++++++++-------- src/packages/arithmetic.rs | 210 +++++++++++++---------------------- src/packages/array_basic.rs | 8 +- src/packages/iter_basic.rs | 18 ++- src/packages/logic.rs | 7 +- src/packages/map_basic.rs | 19 ++-- src/packages/math_basic.rs | 35 +++--- src/packages/string_basic.rs | 7 +- src/packages/string_more.rs | 9 +- 9 files changed, 164 insertions(+), 245 deletions(-) diff --git a/src/fn_call.rs b/src/fn_call.rs index da029c78..4b40df64 100644 --- a/src/fn_call.rs +++ b/src/fn_call.rs @@ -1023,30 +1023,30 @@ pub fn run_builtin_binary_op( let x = x.clone().cast::(); let y = y.clone().cast::(); - #[cfg(not(feature = "unchecked"))] - match op { - "+" => return add(x, y).map(Into::into).map(Some), - "-" => return sub(x, y).map(Into::into).map(Some), - "*" => return mul(x, y).map(Into::into).map(Some), - "/" => return div(x, y).map(Into::into).map(Some), - "%" => return modulo(x, y).map(Into::into).map(Some), - "~" => return pow_i_i(x, y).map(Into::into).map(Some), - ">>" => return shr(x, y).map(Into::into).map(Some), - "<<" => return shl(x, y).map(Into::into).map(Some), - _ => (), - } - - #[cfg(feature = "unchecked")] - match op { - "+" => return Ok(Some((x + y).into())), - "-" => return Ok(Some((x - y).into())), - "*" => return Ok(Some((x * y).into())), - "/" => return Ok(Some((x / y).into())), - "%" => return Ok(Some((x % y).into())), - "~" => return pow_i_i_u(x, y).map(Into::into).map(Some), - ">>" => return shr_u(x, y).map(Into::into).map(Some), - "<<" => return shl_u(x, y).map(Into::into).map(Some), - _ => (), + if cfg!(not(feature = "unchecked")) { + match op { + "+" => return add(x, y).map(Into::into).map(Some), + "-" => return sub(x, y).map(Into::into).map(Some), + "*" => return mul(x, y).map(Into::into).map(Some), + "/" => return div(x, y).map(Into::into).map(Some), + "%" => return modulo(x, y).map(Into::into).map(Some), + "~" => return pow_i_i(x, y).map(Into::into).map(Some), + ">>" => return shr(x, y).map(Into::into).map(Some), + "<<" => return shl(x, y).map(Into::into).map(Some), + _ => (), + } + } else { + match op { + "+" => return Ok(Some((x + y).into())), + "-" => return Ok(Some((x - y).into())), + "*" => return Ok(Some((x * y).into())), + "/" => return Ok(Some((x / y).into())), + "%" => return Ok(Some((x % y).into())), + "~" => return pow_i_i_u(x, y).map(Into::into).map(Some), + ">>" => return shr_u(x, y).map(Into::into).map(Some), + "<<" => return shl_u(x, y).map(Into::into).map(Some), + _ => (), + } } match op { @@ -1151,30 +1151,30 @@ pub fn run_builtin_op_assignment( let x = x.downcast_mut::().unwrap(); let y = y.clone().cast::(); - #[cfg(not(feature = "unchecked"))] - match op { - "+=" => return Ok(Some(*x = add(*x, y)?)), - "-=" => return Ok(Some(*x = sub(*x, y)?)), - "*=" => return Ok(Some(*x = mul(*x, y)?)), - "/=" => return Ok(Some(*x = div(*x, y)?)), - "%=" => return Ok(Some(*x = modulo(*x, y)?)), - "~=" => return Ok(Some(*x = pow_i_i(*x, y)?)), - ">>=" => return Ok(Some(*x = shr(*x, y)?)), - "<<=" => return Ok(Some(*x = shl(*x, y)?)), - _ => (), - } - - #[cfg(feature = "unchecked")] - match op { - "+=" => return Ok(Some(*x += y)), - "-=" => return Ok(Some(*x -= y)), - "*=" => return Ok(Some(*x *= y)), - "/=" => return Ok(Some(*x /= y)), - "%=" => return Ok(Some(*x %= y)), - "~=" => return Ok(Some(*x = pow_i_i_u(*x, y)?)), - ">>=" => return Ok(Some(*x = shr_u(*x, y)?)), - "<<=" => return Ok(Some(*x = shl_u(*x, y)?)), - _ => (), + if cfg!(not(feature = "unchecked")) { + match op { + "+=" => return Ok(Some(*x = add(*x, y)?)), + "-=" => return Ok(Some(*x = sub(*x, y)?)), + "*=" => return Ok(Some(*x = mul(*x, y)?)), + "/=" => return Ok(Some(*x = div(*x, y)?)), + "%=" => return Ok(Some(*x = modulo(*x, y)?)), + "~=" => return Ok(Some(*x = pow_i_i(*x, y)?)), + ">>=" => return Ok(Some(*x = shr(*x, y)?)), + "<<=" => return Ok(Some(*x = shl(*x, y)?)), + _ => (), + } + } else { + match op { + "+=" => return Ok(Some(*x += y)), + "-=" => return Ok(Some(*x -= y)), + "*=" => return Ok(Some(*x *= y)), + "/=" => return Ok(Some(*x /= y)), + "%=" => return Ok(Some(*x %= y)), + "~=" => return Ok(Some(*x = pow_i_i_u(*x, y)?)), + ">>=" => return Ok(Some(*x = shr_u(*x, y)?)), + "<<=" => return Ok(Some(*x = shl_u(*x, y)?)), + _ => (), + } } match op { diff --git a/src/packages/arithmetic.rs b/src/packages/arithmetic.rs index fd95e675..d8e256cc 100644 --- a/src/packages/arithmetic.rs +++ b/src/packages/arithmetic.rs @@ -2,38 +2,29 @@ use crate::def_package; use crate::module::FuncReturn; use crate::parser::INT; -#[cfg(not(feature = "unchecked"))] use crate::{result::EvalAltResult, token::Position}; #[cfg(not(feature = "no_float"))] use crate::parser::FLOAT; -#[cfg(not(feature = "no_float"))] -#[cfg(feature = "no_std")] -use num_traits::*; - -#[cfg(not(feature = "unchecked"))] use num_traits::{ identities::Zero, CheckedAdd, CheckedDiv, CheckedMul, CheckedNeg, CheckedRem, CheckedShl, CheckedShr, CheckedSub, }; -#[cfg(not(feature = "only_i32"))] -#[cfg(not(feature = "only_i64"))] -use crate::stdlib::ops::{BitAnd, BitOr, BitXor}; +#[cfg(feature = "no_std")] +#[cfg(not(feature = "no_float"))] +use num_traits::float::Float; -#[cfg(any(feature = "unchecked", not(feature = "no_float")))] -use crate::stdlib::ops::{Add, Div, Mul, Neg, Rem, Sub}; - -#[cfg(feature = "unchecked")] -use crate::stdlib::ops::{Shl, Shr}; - -#[cfg(not(feature = "unchecked"))] -use crate::stdlib::{boxed::Box, fmt::Display, format}; +use crate::stdlib::{ + boxed::Box, + fmt::Display, + format, + ops::{Add, BitAnd, BitOr, BitXor, Div, Mul, Neg, Rem, Shl, Shr, Sub}, +}; // Checked add -#[cfg(not(feature = "unchecked"))] -pub(crate) fn add(x: T, y: T) -> FuncReturn { +pub fn add(x: T, y: T) -> FuncReturn { x.checked_add(&y).ok_or_else(|| { Box::new(EvalAltResult::ErrorArithmetic( format!("Addition overflow: {} + {}", x, y), @@ -42,8 +33,7 @@ pub(crate) fn add(x: T, y: T) -> FuncReturn { }) } // Checked subtract -#[cfg(not(feature = "unchecked"))] -pub(crate) fn sub(x: T, y: T) -> FuncReturn { +pub fn sub(x: T, y: T) -> FuncReturn { x.checked_sub(&y).ok_or_else(|| { Box::new(EvalAltResult::ErrorArithmetic( format!("Subtraction underflow: {} - {}", x, y), @@ -52,8 +42,7 @@ pub(crate) fn sub(x: T, y: T) -> FuncReturn { }) } // Checked multiply -#[cfg(not(feature = "unchecked"))] -pub(crate) fn mul(x: T, y: T) -> FuncReturn { +pub fn mul(x: T, y: T) -> FuncReturn { x.checked_mul(&y).ok_or_else(|| { Box::new(EvalAltResult::ErrorArithmetic( format!("Multiplication overflow: {} * {}", x, y), @@ -62,8 +51,7 @@ pub(crate) fn mul(x: T, y: T) -> FuncReturn { }) } // Checked divide -#[cfg(not(feature = "unchecked"))] -pub(crate) fn div(x: T, y: T) -> FuncReturn +pub fn div(x: T, y: T) -> FuncReturn where T: Display + CheckedDiv + PartialEq + Zero, { @@ -83,8 +71,7 @@ where }) } // Checked negative - e.g. -(i32::MIN) will overflow i32::MAX -#[cfg(not(feature = "unchecked"))] -pub(crate) fn neg(x: T) -> FuncReturn { +pub fn neg(x: T) -> FuncReturn { x.checked_neg().ok_or_else(|| { Box::new(EvalAltResult::ErrorArithmetic( format!("Negation overflow: -{}", x), @@ -93,8 +80,7 @@ pub(crate) fn neg(x: T) -> FuncReturn { }) } // Checked absolute -#[cfg(not(feature = "unchecked"))] -pub(crate) fn abs(x: T) -> FuncReturn { +pub fn abs(x: T) -> FuncReturn { // FIX - We don't use Signed::abs() here because, contrary to documentation, it panics // when the number is ::MIN instead of returning ::MIN itself. if x >= ::zero() { @@ -109,32 +95,26 @@ pub(crate) fn abs(x: T) -> FuncRetu } } // Unchecked add - may panic on overflow -#[cfg(any(feature = "unchecked", not(feature = "no_float")))] fn add_u(x: T, y: T) -> FuncReturn<::Output> { Ok(x + y) } // Unchecked subtract - may panic on underflow -#[cfg(any(feature = "unchecked", not(feature = "no_float")))] fn sub_u(x: T, y: T) -> FuncReturn<::Output> { Ok(x - y) } // Unchecked multiply - may panic on overflow -#[cfg(any(feature = "unchecked", not(feature = "no_float")))] fn mul_u(x: T, y: T) -> FuncReturn<::Output> { Ok(x * y) } // Unchecked divide - may panic when dividing by zero -#[cfg(any(feature = "unchecked", not(feature = "no_float")))] fn div_u(x: T, y: T) -> FuncReturn<::Output> { Ok(x / y) } // Unchecked negative - may panic on overflow -#[cfg(any(feature = "unchecked", not(feature = "no_float")))] fn neg_u(x: T) -> FuncReturn<::Output> { Ok(-x) } // Unchecked absolute - may panic on overflow -#[cfg(any(feature = "unchecked", not(feature = "no_float")))] fn abs_u(x: T) -> FuncReturn<::Output> where T: Neg + PartialOrd + Default + Into<::Output>, @@ -147,24 +127,17 @@ where } } // Bit operators -#[cfg(not(feature = "only_i32"))] -#[cfg(not(feature = "only_i64"))] fn binary_and(x: T, y: T) -> FuncReturn<::Output> { Ok(x & y) } -#[cfg(not(feature = "only_i32"))] -#[cfg(not(feature = "only_i64"))] fn binary_or(x: T, y: T) -> FuncReturn<::Output> { Ok(x | y) } -#[cfg(not(feature = "only_i32"))] -#[cfg(not(feature = "only_i64"))] fn binary_xor(x: T, y: T) -> FuncReturn<::Output> { Ok(x ^ y) } // Checked left-shift -#[cfg(not(feature = "unchecked"))] -pub(crate) fn shl(x: T, y: INT) -> FuncReturn { +pub fn shl(x: T, y: INT) -> FuncReturn { // Cannot shift by a negative number of bits if y < 0 { return Err(Box::new(EvalAltResult::ErrorArithmetic( @@ -181,8 +154,7 @@ pub(crate) fn shl(x: T, y: INT) -> FuncReturn { }) } // Checked right-shift -#[cfg(not(feature = "unchecked"))] -pub(crate) fn shr(x: T, y: INT) -> FuncReturn { +pub fn shr(x: T, y: INT) -> FuncReturn { // Cannot shift by a negative number of bits if y < 0 { return Err(Box::new(EvalAltResult::ErrorArithmetic( @@ -199,18 +171,15 @@ pub(crate) fn shr(x: T, y: INT) -> FuncReturn { }) } // Unchecked left-shift - may panic if shifting by a negative number of bits -#[cfg(feature = "unchecked")] -pub(crate) fn shl_u>(x: T, y: T) -> FuncReturn<>::Output> { +pub fn shl_u>(x: T, y: T) -> FuncReturn<>::Output> { Ok(x.shl(y)) } // Unchecked right-shift - may panic if shifting by a negative number of bits -#[cfg(feature = "unchecked")] -pub(crate) fn shr_u>(x: T, y: T) -> FuncReturn<>::Output> { +pub fn shr_u>(x: T, y: T) -> FuncReturn<>::Output> { Ok(x.shr(y)) } // Checked modulo -#[cfg(not(feature = "unchecked"))] -pub(crate) fn modulo(x: T, y: T) -> FuncReturn { +pub fn modulo(x: T, y: T) -> FuncReturn { x.checked_rem(&y).ok_or_else(|| { Box::new(EvalAltResult::ErrorArithmetic( format!("Modulo division by zero or overflow: {} % {}", x, y), @@ -219,62 +188,58 @@ pub(crate) fn modulo(x: T, y: T) -> FuncReturn { }) } // Unchecked modulo - may panic if dividing by zero -#[cfg(any(feature = "unchecked", not(feature = "no_float")))] fn modulo_u(x: T, y: T) -> FuncReturn<::Output> { Ok(x % y) } // Checked power -#[cfg(not(feature = "unchecked"))] -pub(crate) fn pow_i_i(x: INT, y: INT) -> FuncReturn { - #[cfg(not(feature = "only_i32"))] - if y > (u32::MAX as INT) { - Err(Box::new(EvalAltResult::ErrorArithmetic( - format!("Integer raised to too large an index: {} ~ {}", x, y), - Position::none(), - ))) - } else if y < 0 { - Err(Box::new(EvalAltResult::ErrorArithmetic( - format!("Integer raised to a negative index: {} ~ {}", x, y), - Position::none(), - ))) - } else { - x.checked_pow(y as u32).ok_or_else(|| { - Box::new(EvalAltResult::ErrorArithmetic( - format!("Power overflow: {} ~ {}", x, y), +pub fn pow_i_i(x: INT, y: INT) -> FuncReturn { + if cfg!(not(feature = "only_i32")) { + if y > (u32::MAX as INT) { + Err(Box::new(EvalAltResult::ErrorArithmetic( + format!("Integer raised to too large an index: {} ~ {}", x, y), Position::none(), - )) - }) - } - - #[cfg(feature = "only_i32")] - if y < 0 { - Err(Box::new(EvalAltResult::ErrorArithmetic( - format!("Integer raised to a negative index: {} ~ {}", x, y), - Position::none(), - ))) - } else { - x.checked_pow(y as u32).ok_or_else(|| { - Box::new(EvalAltResult::ErrorArithmetic( - format!("Power overflow: {} ~ {}", x, y), + ))) + } else if y < 0 { + Err(Box::new(EvalAltResult::ErrorArithmetic( + format!("Integer raised to a negative index: {} ~ {}", x, y), Position::none(), - )) - }) + ))) + } else { + x.checked_pow(y as u32).ok_or_else(|| { + Box::new(EvalAltResult::ErrorArithmetic( + format!("Power overflow: {} ~ {}", x, y), + Position::none(), + )) + }) + } + } else { + if y < 0 { + Err(Box::new(EvalAltResult::ErrorArithmetic( + format!("Integer raised to a negative index: {} ~ {}", x, y), + Position::none(), + ))) + } else { + x.checked_pow(y as u32).ok_or_else(|| { + Box::new(EvalAltResult::ErrorArithmetic( + format!("Power overflow: {} ~ {}", x, y), + Position::none(), + )) + }) + } } } // Unchecked integer power - may panic on overflow or if the power index is too high (> u32::MAX) -#[cfg(feature = "unchecked")] -pub(crate) fn pow_i_i_u(x: INT, y: INT) -> FuncReturn { +pub fn pow_i_i_u(x: INT, y: INT) -> FuncReturn { Ok(x.pow(y as u32)) } // Floating-point power - always well-defined #[cfg(not(feature = "no_float"))] -pub(crate) fn pow_f_f(x: FLOAT, y: FLOAT) -> FuncReturn { +pub fn pow_f_f(x: FLOAT, y: FLOAT) -> FuncReturn { Ok(x.powf(y)) } // Checked power #[cfg(not(feature = "no_float"))] -#[cfg(not(feature = "unchecked"))] -pub(crate) fn pow_f_i(x: FLOAT, y: INT) -> FuncReturn { +pub fn pow_f_i(x: FLOAT, y: INT) -> FuncReturn { // Raise to power that is larger than an i32 if y > (i32::MAX as INT) { return Err(Box::new(EvalAltResult::ErrorArithmetic( @@ -286,9 +251,8 @@ pub(crate) fn pow_f_i(x: FLOAT, y: INT) -> FuncReturn { Ok(x.powi(y as i32)) } // Unchecked power - may be incorrect if the power index is too high (> i32::MAX) -#[cfg(feature = "unchecked")] #[cfg(not(feature = "no_float"))] -pub(crate) fn pow_f_i_u(x: FLOAT, y: INT) -> FuncReturn { +pub fn pow_f_i_u(x: FLOAT, y: INT) -> FuncReturn { Ok(x.powi(y as i32)) } @@ -317,11 +281,8 @@ macro_rules! reg_sign { } def_package!(crate:ArithmeticPackage:"Basic arithmetic", lib, { - #[cfg(not(feature = "only_i32"))] - #[cfg(not(feature = "only_i64"))] - { - #[cfg(not(feature = "unchecked"))] - { + if cfg!(not(feature = "only_i32")) && cfg!(not(feature = "only_i64")) { + if cfg!(not(feature = "unchecked")) { // Checked basic arithmetic reg_op!(lib, "+", add, i8, u8, i16, u16, i32, u32, u64); reg_op!(lib, "-", sub, i8, u8, i16, u16, i32, u32, u64); @@ -332,8 +293,7 @@ def_package!(crate:ArithmeticPackage:"Basic arithmetic", lib, { reg_op!(lib, ">>", shr, i8, u8, i16, u16, i32, u32, u64); reg_op!(lib, "%", modulo, i8, u8, i16, u16, i32, u32, u64); - #[cfg(not(target_arch = "wasm32"))] - { + if cfg!(not(target_arch = "wasm32")) { reg_op!(lib, "+", add, i128, u128); reg_op!(lib, "-", sub, i128, u128); reg_op!(lib, "*", mul, i128, u128); @@ -345,8 +305,7 @@ def_package!(crate:ArithmeticPackage:"Basic arithmetic", lib, { } } - #[cfg(feature = "unchecked")] - { + if cfg!(feature = "unchecked") { // Unchecked basic arithmetic reg_op!(lib, "+", add_u, i8, u8, i16, u16, i32, u32, u64); reg_op!(lib, "-", sub_u, i8, u8, i16, u16, i32, u32, u64); @@ -357,8 +316,7 @@ def_package!(crate:ArithmeticPackage:"Basic arithmetic", lib, { reg_op!(lib, ">>", shr_u, i64, i8, u8, i16, u16, i32, u32, u64); reg_op!(lib, "%", modulo_u, i8, u8, i16, u16, i32, u32, u64); - #[cfg(not(target_arch = "wasm32"))] - { + if cfg!(not(target_arch = "wasm32")) { reg_op!(lib, "+", add_u, i128, u128); reg_op!(lib, "-", sub_u, i128, u128); reg_op!(lib, "*", mul_u, i128, u128); @@ -372,13 +330,13 @@ def_package!(crate:ArithmeticPackage:"Basic arithmetic", lib, { reg_sign!(lib, "sign", INT, i8, i16, i32, i64); - #[cfg(not(target_arch = "wasm32"))] - reg_sign!(lib, "sign", INT, i128); + if cfg!(not(target_arch = "wasm32")) { + reg_sign!(lib, "sign", INT, i128); + } } // Basic arithmetic for floating-point - no need to check - #[cfg(not(feature = "no_float"))] - { + if cfg!(not(feature = "no_float")) { reg_op!(lib, "+", add_u, f32); reg_op!(lib, "-", sub_u, f32); reg_op!(lib, "*", mul_u, f32); @@ -387,15 +345,12 @@ def_package!(crate:ArithmeticPackage:"Basic arithmetic", lib, { reg_sign!(lib, "sign", f64, f64); } - #[cfg(not(feature = "only_i32"))] - #[cfg(not(feature = "only_i64"))] - { + if cfg!(not(feature = "only_i32")) && cfg!(not(feature = "only_i64")) { reg_op!(lib, "|", binary_or, i8, u8, i16, u16, i32, u32, u64); reg_op!(lib, "&", binary_and, i8, u8, i16, u16, i32, u32, u64); reg_op!(lib, "^", binary_xor, i8, u8, i16, u16, i32, u32, u64); - #[cfg(not(target_arch = "wasm32"))] - { + if cfg!(not(target_arch = "wasm32")) { reg_op!(lib, "|", binary_or, i128, u128); reg_op!(lib, "&", binary_and, i128, u128); reg_op!(lib, "^", binary_xor, i128, u128); @@ -405,12 +360,11 @@ def_package!(crate:ArithmeticPackage:"Basic arithmetic", lib, { #[cfg(not(feature = "no_float"))] { // Checked power - #[cfg(not(feature = "unchecked"))] - lib.set_fn_2("~", pow_f_i); - - // Unchecked power - #[cfg(feature = "unchecked")] - lib.set_fn_2("~", pow_f_i_u); + if cfg!(not(feature = "unchecked")) { + lib.set_fn_2("~", pow_f_i); + } else { + lib.set_fn_2("~", pow_f_i_u); + } // Floating-point modulo and power reg_op!(lib, "%", modulo_u, f32); @@ -421,19 +375,15 @@ def_package!(crate:ArithmeticPackage:"Basic arithmetic", lib, { } // Checked unary - #[cfg(not(feature = "unchecked"))] - { + if cfg!(not(feature = "unchecked")) { reg_unary!(lib, "-", neg, INT); reg_unary!(lib, "abs", abs, INT); - #[cfg(not(feature = "only_i32"))] - #[cfg(not(feature = "only_i64"))] - { + if cfg!(not(feature = "only_i32")) && cfg!(not(feature = "only_i64")) { reg_unary!(lib, "-", neg, i8, i16, i32, i64); reg_unary!(lib, "abs", abs, i8, i16, i32, i64); - #[cfg(not(target_arch = "wasm32"))] - { + if cfg!(not(target_arch = "wasm32")) { reg_unary!(lib, "-", neg, i128); reg_unary!(lib, "abs", abs, i128); } @@ -441,19 +391,15 @@ def_package!(crate:ArithmeticPackage:"Basic arithmetic", lib, { } // Unchecked unary - #[cfg(feature = "unchecked")] - { + if cfg!(feature = "unchecked") { reg_unary!(lib, "-", neg_u, INT); reg_unary!(lib, "abs", abs_u, INT); - #[cfg(not(feature = "only_i32"))] - #[cfg(not(feature = "only_i64"))] - { + if cfg!(not(feature = "only_i32")) && cfg!(not(feature = "only_i64")) { reg_unary!(lib, "-", neg_u, i8, i16, i32, i64); reg_unary!(lib, "abs", abs_u, i8, i16, i32, i64); - #[cfg(not(target_arch = "wasm32"))] - { + if cfg!(not(target_arch = "wasm32")) { reg_unary!(lib, "-", neg_u, i128); reg_unary!(lib, "abs", abs_u, i128); } diff --git a/src/packages/array_basic.rs b/src/packages/array_basic.rs index c0bea7ee..5db525c5 100644 --- a/src/packages/array_basic.rs +++ b/src/packages/array_basic.rs @@ -82,7 +82,6 @@ macro_rules! reg_pad { }; } -#[cfg(not(feature = "no_index"))] def_package!(crate:BasicArrayPackage:"Basic array utilities.", lib, { reg_op!(lib, "push", push, INT, bool, char, ImmutableString, Array, ()); reg_pad!(lib, "pad", pad, INT, bool, char, ImmutableString, Array, ()); @@ -104,15 +103,12 @@ def_package!(crate:BasicArrayPackage:"Basic array utilities.", lib, { }, ); - #[cfg(not(feature = "only_i32"))] - #[cfg(not(feature = "only_i64"))] - { + if cfg!(not(feature = "only_i32")) && cfg!(not(feature = "only_i64")) { reg_op!(lib, "push", push, i8, u8, i16, u16, i32, i64, u32, u64); reg_pad!(lib, "pad", pad, i8, u8, i16, u16, i32, u32, i64, u64); reg_tri!(lib, "insert", ins, i8, u8, i16, u16, i32, i64, u32, u64); - #[cfg(not(target_arch = "wasm32"))] - { + if cfg!(not(target_arch = "wasm32")) { reg_op!(lib, "push", push, i128, u128); reg_pad!(lib, "pad", pad, i128, u128); reg_tri!(lib, "insert", ins, i128, u128); diff --git a/src/packages/iter_basic.rs b/src/packages/iter_basic.rs index cccf4b6c..8914c6a5 100644 --- a/src/packages/iter_basic.rs +++ b/src/packages/iter_basic.rs @@ -73,9 +73,7 @@ def_package!(crate:BasicIteratorPackage:"Basic range iterators.", lib, { reg_range::(lib); lib.set_fn_2("range", get_range::); - #[cfg(not(feature = "only_i32"))] - #[cfg(not(feature = "only_i64"))] - { + if cfg!(not(feature = "only_i32")) && cfg!(not(feature = "only_i64")) { macro_rules! reg_range { ($lib:expr, $x:expr, $( $y:ty ),*) => ( $( @@ -87,16 +85,15 @@ def_package!(crate:BasicIteratorPackage:"Basic range iterators.", lib, { reg_range!(lib, "range", i8, u8, i16, u16, i32, i64, u32, u64); - #[cfg(not(target_arch = "wasm32"))] - reg_range!(lib, "range", i128, u128); + if cfg!(not(target_arch = "wasm32")) { + reg_range!(lib, "range", i128, u128); + } } reg_step::(lib); lib.set_fn_3("range", get_step_range::); - #[cfg(not(feature = "only_i32"))] - #[cfg(not(feature = "only_i64"))] - { + if cfg!(not(feature = "only_i32")) && cfg!(not(feature = "only_i64")) { macro_rules! reg_step { ($lib:expr, $x:expr, $( $y:ty ),*) => ( $( @@ -108,7 +105,8 @@ def_package!(crate:BasicIteratorPackage:"Basic range iterators.", lib, { reg_step!(lib, "range", i8, u8, i16, u16, i32, i64, u32, u64); - #[cfg(not(target_arch = "wasm32"))] - reg_step!(lib, "range", i128, u128); + if cfg!(not(target_arch = "wasm32")) { + reg_step!(lib, "range", i128, u128); + } } }); diff --git a/src/packages/logic.rs b/src/packages/logic.rs index 1cb4b437..6c70a38c 100644 --- a/src/packages/logic.rs +++ b/src/packages/logic.rs @@ -33,9 +33,7 @@ macro_rules! reg_op { } def_package!(crate:LogicPackage:"Logical operators.", lib, { - #[cfg(not(feature = "only_i32"))] - #[cfg(not(feature = "only_i64"))] - { + if cfg!(not(feature = "only_i32")) && cfg!(not(feature = "only_i64")) { reg_op!(lib, "<", lt, i8, u8, i16, u16, i32, u32, u64); reg_op!(lib, "<=", lte, i8, u8, i16, u16, i32, u32, u64); reg_op!(lib, ">", gt, i8, u8, i16, u16, i32, u32, u64); @@ -43,8 +41,7 @@ def_package!(crate:LogicPackage:"Logical operators.", lib, { reg_op!(lib, "==", eq, i8, u8, i16, u16, i32, u32, u64); reg_op!(lib, "!=", ne, i8, u8, i16, u16, i32, u32, u64); - #[cfg(not(target_arch = "wasm32"))] - { + if cfg!(not(target_arch = "wasm32")) { reg_op!(lib, "<", lt, i128, u128); reg_op!(lib, "<=", lte, i128, u128); reg_op!(lib, ">", gt, i128, u128); diff --git a/src/packages/map_basic.rs b/src/packages/map_basic.rs index 9437bc67..4e2e58ad 100644 --- a/src/packages/map_basic.rs +++ b/src/packages/map_basic.rs @@ -1,25 +1,20 @@ #![cfg(not(feature = "no_object"))] +use crate::any::Dynamic; use crate::def_package; use crate::engine::Map; +use crate::module::FuncReturn; use crate::parser::{ImmutableString, INT}; -#[cfg(not(feature = "no_index"))] -use crate::{any::Dynamic, module::FuncReturn}; - -#[cfg(not(feature = "no_index"))] use crate::stdlib::vec::Vec; -#[cfg(not(feature = "no_index"))] fn map_get_keys(map: &mut Map) -> FuncReturn> { Ok(map.iter().map(|(k, _)| k.clone().into()).collect()) } -#[cfg(not(feature = "no_index"))] fn map_get_values(map: &mut Map) -> FuncReturn> { Ok(map.iter().map(|(_, v)| v.clone()).collect()) } -#[cfg(not(feature = "no_object"))] def_package!(crate:BasicMapPackage:"Basic object map utilities.", lib, { lib.set_fn_2_mut( "has", @@ -74,9 +69,11 @@ def_package!(crate:BasicMapPackage:"Basic object map utilities.", lib, { ); // Register map access functions - #[cfg(not(feature = "no_index"))] - lib.set_fn_1_mut("keys", map_get_keys); + if cfg!(not(feature = "no_index")) { + lib.set_fn_1_mut("keys", map_get_keys); + } - #[cfg(not(feature = "no_index"))] - lib.set_fn_1_mut("values", map_get_values); + if cfg!(not(feature = "no_index")) { + lib.set_fn_1_mut("values", map_get_values); + } }); diff --git a/src/packages/math_basic.rs b/src/packages/math_basic.rs index 55bb68a3..f8fd2d33 100644 --- a/src/packages/math_basic.rs +++ b/src/packages/math_basic.rs @@ -5,22 +5,20 @@ use crate::parser::INT; use crate::parser::FLOAT; #[cfg(not(feature = "no_float"))] -#[cfg(not(feature = "unchecked"))] use crate::{result::EvalAltResult, token::Position}; -#[cfg(not(feature = "no_float"))] #[cfg(feature = "no_std")] -use num_traits::*; +#[cfg(not(feature = "no_float"))] +use num_traits::float::Float; #[cfg(not(feature = "no_float"))] -#[cfg(not(feature = "unchecked"))] use crate::stdlib::{boxed::Box, format}; +#[allow(dead_code)] #[cfg(feature = "only_i32")] -#[cfg(not(feature = "unchecked"))] pub const MAX_INT: INT = i32::MAX; +#[allow(dead_code)] #[cfg(not(feature = "only_i32"))] -#[cfg(not(feature = "unchecked"))] pub const MAX_INT: INT = i64::MAX; def_package!(crate:BasicMathPackage:"Basic mathematic functions.", lib, { @@ -69,9 +67,7 @@ def_package!(crate:BasicMathPackage:"Basic mathematic functions.", lib, { lib.set_fn_1("to_float", |x: INT| Ok(x as FLOAT)); lib.set_fn_1("to_float", |x: f32| Ok(x as FLOAT)); - #[cfg(not(feature = "only_i32"))] - #[cfg(not(feature = "only_i64"))] - { + if cfg!(not(feature = "only_i32")) && cfg!(not(feature = "only_i64")) { lib.set_fn_1("to_float", |x: i8| Ok(x as FLOAT)); lib.set_fn_1("to_float", |x: u8| Ok(x as FLOAT)); lib.set_fn_1("to_float", |x: i16| Ok(x as FLOAT)); @@ -81,8 +77,7 @@ def_package!(crate:BasicMathPackage:"Basic mathematic functions.", lib, { lib.set_fn_1("to_float", |x: i64| Ok(x as FLOAT)); lib.set_fn_1("to_float", |x: u64| Ok(x as FLOAT)); - #[cfg(not(target_arch = "wasm32"))] - { + if cfg!(not(target_arch = "wasm32")) { lib.set_fn_1("to_float", |x: i128| Ok(x as FLOAT)); lib.set_fn_1("to_float", |x: u128| Ok(x as FLOAT)); } @@ -91,28 +86,25 @@ def_package!(crate:BasicMathPackage:"Basic mathematic functions.", lib, { lib.set_fn_1("to_int", |ch: char| Ok(ch as INT)); - #[cfg(not(feature = "only_i32"))] - #[cfg(not(feature = "only_i64"))] - { + if cfg!(not(feature = "only_i32")) && cfg!(not(feature = "only_i64")) { lib.set_fn_1("to_int", |x: i8| Ok(x as INT)); lib.set_fn_1("to_int", |x: u8| Ok(x as INT)); lib.set_fn_1("to_int", |x: i16| Ok(x as INT)); lib.set_fn_1("to_int", |x: u16| Ok(x as INT)); } - #[cfg(not(feature = "only_i32"))] - { + if cfg!(not(feature = "only_i32")) { lib.set_fn_1("to_int", |x: i32| Ok(x as INT)); lib.set_fn_1("to_int", |x: u64| Ok(x as INT)); - #[cfg(feature = "only_i64")] - lib.set_fn_1("to_int", |x: u32| Ok(x as INT)); + if cfg!(feature = "only_i64") { + lib.set_fn_1("to_int", |x: u32| Ok(x as INT)); + } } #[cfg(not(feature = "no_float"))] { - #[cfg(not(feature = "unchecked"))] - { + if cfg!(not(feature = "unchecked")) { lib.set_fn_1( "to_int", |x: f32| { @@ -141,8 +133,7 @@ def_package!(crate:BasicMathPackage:"Basic mathematic functions.", lib, { ); } - #[cfg(feature = "unchecked")] - { + if cfg!(feature = "unchecked") { lib.set_fn_1("to_int", |x: f32| Ok(x as INT)); lib.set_fn_1("to_int", |x: f64| Ok(x as INT)); } diff --git a/src/packages/string_basic.rs b/src/packages/string_basic.rs index 2e405004..36f4f333 100644 --- a/src/packages/string_basic.rs +++ b/src/packages/string_basic.rs @@ -48,9 +48,7 @@ def_package!(crate:BasicStringPackage:"Basic string utilities, including printin reg_op!(lib, KEYWORD_DEBUG, to_debug, INT, bool, (), char, ImmutableString); - #[cfg(not(feature = "only_i32"))] - #[cfg(not(feature = "only_i64"))] - { + if cfg!(not(feature = "only_i32")) && cfg!(not(feature = "only_i64")) { reg_op!(lib, KEYWORD_PRINT, to_string, i8, u8, i16, u16, i32, u32); reg_op!(lib, FN_TO_STRING, to_string, i8, u8, i16, u16, i32, u32); reg_op!(lib, KEYWORD_DEBUG, to_debug, i8, u8, i16, u16, i32, u32); @@ -58,8 +56,7 @@ def_package!(crate:BasicStringPackage:"Basic string utilities, including printin reg_op!(lib, FN_TO_STRING, to_string, i64, u64); reg_op!(lib, KEYWORD_DEBUG, to_debug, i64, u64); - #[cfg(not(target_arch = "wasm32"))] - { + if cfg!(not(target_arch = "wasm32")) { reg_op!(lib, KEYWORD_PRINT, to_string, i128, u128); reg_op!(lib, FN_TO_STRING, to_string, i128, u128); reg_op!(lib, KEYWORD_DEBUG, to_debug, i128, u128); diff --git a/src/packages/string_more.rs b/src/packages/string_more.rs index 6ddc2b0c..35dcab00 100644 --- a/src/packages/string_more.rs +++ b/src/packages/string_more.rs @@ -94,14 +94,11 @@ def_package!(crate:MoreStringPackage:"Additional string utilities, including str reg_op!(lib, "+", prepend, INT, bool, char); lib.set_fn_2("+", |_: (), y: ImmutableString| Ok(y)); - #[cfg(not(feature = "only_i32"))] - #[cfg(not(feature = "only_i64"))] - { + if cfg!(not(feature = "only_i32")) && cfg!(not(feature = "only_i64")) { reg_op!(lib, "+", append, i8, u8, i16, u16, i32, i64, u32, u64); reg_op!(lib, "+", prepend, i8, u8, i16, u16, i32, i64, u32, u64); - #[cfg(not(target_arch = "wasm32"))] - { + if cfg!(not(target_arch = "wasm32")) { reg_op!(lib, "+", append, i128, u128); reg_op!(lib, "+", prepend, i128, u128); } @@ -228,7 +225,7 @@ def_package!(crate:MoreStringPackage:"Additional string utilities, including str "pad", &[TypeId::of::(), TypeId::of::(), TypeId::of::()], |_engine: &Engine, _: &Module, args: &mut [&mut Dynamic]| { - let len = *args[1].downcast_ref::< INT>().unwrap(); + let len = *args[1].downcast_ref::().unwrap(); // Check if string will be over max size limit #[cfg(not(feature = "unchecked"))] From af2f8acb5dc42a9c63ad88f946a86637c746af94 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Sat, 1 Aug 2020 12:21:15 +0800 Subject: [PATCH 25/40] Refine indexer set. --- src/engine.rs | 64 +++++++++++++++++++++++++++----------------------- src/fn_call.rs | 48 ++++++++++++++++++------------------- 2 files changed, 58 insertions(+), 54 deletions(-) diff --git a/src/engine.rs b/src/engine.rs index a011bf67..cb56c28b 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -721,8 +721,9 @@ impl Engine { Expr::Dot(x) | Expr::Index(x) => { let (idx, expr, pos) = x.as_ref(); let idx_pos = idx.position(); - let obj_ptr = &mut self - .get_indexed_mut(state, lib, target, idx_val, idx_pos, false, level)?; + let obj_ptr = &mut self.get_indexed_mut( + state, lib, target, idx_val, idx_pos, false, true, level, + )?; self.eval_dot_index_chain_helper( state, lib, this_ptr, obj_ptr, expr, idx_values, next_chain, level, @@ -735,14 +736,9 @@ impl Engine { let mut idx_val2 = idx_val.clone(); // `call_setter` is introduced to bypass double mutable borrowing of target - let mut call_setter = match self - .get_indexed_mut(state, lib, target, idx_val, pos, true, level) + let _call_setter = match self + .get_indexed_mut(state, lib, target, idx_val, pos, true, false, level) { - // Indexed value is an owned value - the only possibility is a value from an indexer. - // Try to call an index setter to update it back, but no need to raise an error - // if the indexer is read-only. - #[cfg(not(feature = "no_index"))] - Ok(obj_ptr) if obj_ptr.is_value() => Some((false, _new_val.unwrap())), // Indexed value is a reference - update directly Ok(ref mut obj_ptr) => { obj_ptr @@ -753,37 +749,42 @@ impl Engine { } Err(err) => match *err { // No index getter - try to call an index setter + #[cfg(not(feature = "no_index"))] EvalAltResult::ErrorIndexingType(_, _) => { // Raise error if there is no index getter nor setter - Some((true, _new_val.unwrap())) + Some(_new_val.unwrap()) } // Any other error - return err => return Err(Box::new(err)), }, }; - if let Some((must_have_setter, ref mut new_val)) = call_setter { - let args = &mut [target.as_mut(), &mut idx_val2, new_val]; + #[cfg(not(feature = "no_index"))] + if let Some(mut new_val) = _call_setter { + let val = target.as_mut(); + let val_type_name = val.type_name(); + let args = &mut [val, &mut idx_val2, &mut new_val]; - match self.exec_fn_call( + self.exec_fn_call( state, lib, FN_IDX_SET, 0, args, is_ref, true, false, None, None, level, - ) { - Ok(_) => (), - Err(err) if !must_have_setter => match *err { - // If there is no index setter, no need to set it back because the indexer is read-only - EvalAltResult::ErrorFunctionNotFound(_, _) => (), - _ => return Err(err), - }, - err => return err, - } + ) + .map_err(|err| match *err { + EvalAltResult::ErrorFunctionNotFound(_, _) => { + EvalAltResult::ErrorIndexingType( + self.map_type_name(val_type_name).into(), + Position::none(), + ) + } + err => err, + })?; } Ok(Default::default()) } // xxx[rhs] _ => self - .get_indexed_mut(state, lib, target, idx_val, pos, false, level) + .get_indexed_mut(state, lib, target, idx_val, pos, false, true, level) .map(|v| (v.clone_into_dynamic(), false)), } } @@ -806,8 +807,8 @@ impl Engine { Expr::Property(x) if target.is::() && _new_val.is_some() => { let ((prop, _, _), pos) = x.as_ref(); let index = prop.clone().into(); - let mut val = - self.get_indexed_mut(state, lib, target, index, *pos, true, level)?; + let mut val = self + .get_indexed_mut(state, lib, target, index, *pos, true, false, level)?; val.set_value(_new_val.unwrap()) .map_err(|err| err.new_position(rhs.position()))?; @@ -817,8 +818,9 @@ impl Engine { Expr::Property(x) if target.is::() => { let ((prop, _, _), pos) = x.as_ref(); let index = prop.clone().into(); - let val = - self.get_indexed_mut(state, lib, target, index, *pos, false, level)?; + let val = self.get_indexed_mut( + state, lib, target, index, *pos, false, false, level, + )?; Ok((val.clone_into_dynamic(), false)) } @@ -852,7 +854,9 @@ impl Engine { Expr::Property(p) => { let ((prop, _, _), pos) = p.as_ref(); let index = prop.clone().into(); - self.get_indexed_mut(state, lib, target, index, *pos, false, level)? + self.get_indexed_mut( + state, lib, target, index, *pos, false, true, level, + )? } // {xxx:map}.fn_name(arg_expr_list)[expr] | {xxx:map}.fn_name(arg_expr_list).expr Expr::FnCall(x) if x.1.is_none() => { @@ -1125,6 +1129,7 @@ impl Engine { mut _idx: Dynamic, idx_pos: Position, _create: bool, + _indexers: bool, _level: usize, ) -> Result, Box> { self.inc_operations(state)?; @@ -1201,7 +1206,7 @@ impl Engine { #[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_index"))] - _ => { + _ if _indexers => { let type_name = val.type_name(); let args = &mut [val, &mut _idx]; self.exec_fn_call( @@ -1216,7 +1221,6 @@ impl Engine { }) } - #[cfg(any(feature = "no_index", feature = "no_object"))] _ => Err(Box::new(EvalAltResult::ErrorIndexingType( self.map_type_name(val.type_name()).into(), Position::none(), diff --git a/src/fn_call.rs b/src/fn_call.rs index cbddcff3..05b65e3a 100644 --- a/src/fn_call.rs +++ b/src/fn_call.rs @@ -1188,30 +1188,30 @@ pub fn run_builtin_op_assignment( let mut x = x.write_lock::().unwrap(); let y = y.clone().cast::(); - #[cfg(not(feature = "unchecked"))] - match op { - "+=" => return Ok(Some(*x = add(*x, y)?)), - "-=" => return Ok(Some(*x = sub(*x, y)?)), - "*=" => return Ok(Some(*x = mul(*x, y)?)), - "/=" => return Ok(Some(*x = div(*x, y)?)), - "%=" => return Ok(Some(*x = modulo(*x, y)?)), - "~=" => return Ok(Some(*x = pow_i_i(*x, y)?)), - ">>=" => return Ok(Some(*x = shr(*x, y)?)), - "<<=" => return Ok(Some(*x = shl(*x, y)?)), - _ => (), - } - - #[cfg(feature = "unchecked")] - match op { - "+=" => return Ok(Some(*x += y)), - "-=" => return Ok(Some(*x -= y)), - "*=" => return Ok(Some(*x *= y)), - "/=" => return Ok(Some(*x /= y)), - "%=" => return Ok(Some(*x %= y)), - "~=" => return Ok(Some(*x = pow_i_i_u(*x, y)?)), - ">>=" => return Ok(Some(*x = shr_u(*x, y)?)), - "<<=" => return Ok(Some(*x = shl_u(*x, y)?)), - _ => (), + if cfg!(not(feature = "unchecked")) { + match op { + "+=" => return Ok(Some(*x = add(*x, y)?)), + "-=" => return Ok(Some(*x = sub(*x, y)?)), + "*=" => return Ok(Some(*x = mul(*x, y)?)), + "/=" => return Ok(Some(*x = div(*x, y)?)), + "%=" => return Ok(Some(*x = modulo(*x, y)?)), + "~=" => return Ok(Some(*x = pow_i_i(*x, y)?)), + ">>=" => return Ok(Some(*x = shr(*x, y)?)), + "<<=" => return Ok(Some(*x = shl(*x, y)?)), + _ => (), + } + } else { + match op { + "+=" => return Ok(Some(*x += y)), + "-=" => return Ok(Some(*x -= y)), + "*=" => return Ok(Some(*x *= y)), + "/=" => return Ok(Some(*x /= y)), + "%=" => return Ok(Some(*x %= y)), + "~=" => return Ok(Some(*x = pow_i_i_u(*x, y)?)), + ">>=" => return Ok(Some(*x = shr_u(*x, y)?)), + "<<=" => return Ok(Some(*x = shl_u(*x, y)?)), + _ => (), + } } match op { From cc53b2173111c644b22397776a148b801c82307a Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Sat, 1 Aug 2020 22:28:13 +0800 Subject: [PATCH 26/40] Avoid hard-coding variable type for shared. --- src/any.rs | 123 +++++++++++++++++------------------------------- src/fn_call.rs | 10 ++-- src/utils.rs | 12 +++++ tests/shared.rs | 12 +++++ 4 files changed, 71 insertions(+), 86 deletions(-) diff --git a/src/any.rs b/src/any.rs index a8ee1e66..6280e426 100644 --- a/src/any.rs +++ b/src/any.rs @@ -160,18 +160,7 @@ pub enum Union { FnPtr(Box), Variant(Box>), #[cfg(not(feature = "no_shared"))] - Shared(Box), -} - -/// Internal Shared Dynamic representation. -/// -/// Created with `Dynamic::into_shared()`. -#[cfg(not(feature = "no_shared"))] -#[derive(Clone)] -pub struct SharedCell { - value_type_id: TypeId, - value_type_name: &'static str, - container: SharedMut, + Shared(SharedMut), } /// Underlying `Variant` read guard for `Dynamic`. @@ -202,7 +191,7 @@ impl<'d, T: Variant + Clone> Deref for DynamicReadLock<'d, T> { #[inline(always)] fn deref(&self) -> &Self::Target { match &self.0 { - DynamicReadLockInner::Reference(reference) => reference.deref(), + DynamicReadLockInner::Reference(reference) => *reference, // Unwrapping is safe because all checking is already done in its constructor #[cfg(not(feature = "no_shared"))] DynamicReadLockInner::Guard(guard) => guard.downcast_ref().unwrap(), @@ -238,7 +227,7 @@ impl<'d, T: Variant + Clone> Deref for DynamicWriteLock<'d, T> { #[inline(always)] fn deref(&self) -> &Self::Target { match &self.0 { - DynamicWriteLockInner::Reference(reference) => reference.deref(), + DynamicWriteLockInner::Reference(reference) => *reference, // Unwrapping is safe because all checking is already done in its constructor #[cfg(not(feature = "no_shared"))] DynamicWriteLockInner::Guard(guard) => guard.downcast_ref().unwrap(), @@ -250,7 +239,7 @@ impl<'d, T: Variant + Clone> DerefMut for DynamicWriteLock<'d, T> { #[inline(always)] fn deref_mut(&mut self) -> &mut Self::Target { match &mut self.0 { - DynamicWriteLockInner::Reference(reference) => reference.deref_mut(), + DynamicWriteLockInner::Reference(reference) => *reference, // Unwrapping is safe because all checking is already done in its constructor #[cfg(not(feature = "no_shared"))] DynamicWriteLockInner::Guard(guard) => guard.downcast_mut().unwrap(), @@ -309,7 +298,11 @@ impl Dynamic { Union::FnPtr(_) => TypeId::of::(), Union::Variant(value) => (***value).type_id(), #[cfg(not(feature = "no_shared"))] - Union::Shared(cell) => (**cell).value_type_id, + #[cfg(not(feature = "sync"))] + Union::Shared(cell) => (*cell.borrow()).type_id(), + #[cfg(not(feature = "no_shared"))] + #[cfg(feature = "sync")] + Union::Shared(cell) => (*cell.read().unwrap()).type_id(), } } @@ -333,7 +326,11 @@ impl Dynamic { Union::Variant(value) if value.is::() => "timestamp", Union::Variant(value) => (***value).type_name(), #[cfg(not(feature = "no_shared"))] - Union::Shared(cell) => (**cell).value_type_name, + #[cfg(not(feature = "sync"))] + Union::Shared(cell) => (*cell.borrow()).type_name(), + #[cfg(not(feature = "no_shared"))] + #[cfg(feature = "sync")] + Union::Shared(cell) => (*cell.read().unwrap()).type_name(), } } } @@ -390,7 +387,7 @@ impl fmt::Display for Dynamic { Union::Variant(value) if value.is::() => write!(f, ""), Union::Variant(value) => write!(f, "{}", (*value).type_name()), #[cfg(not(feature = "no_shared"))] - Union::Shared(cell) => write!(f, "", (**cell).value_type_name), + Union::Shared(_) => f.write_str(""), } } } @@ -418,7 +415,7 @@ impl fmt::Debug for Dynamic { Union::Variant(value) if value.is::() => write!(f, ""), Union::Variant(value) => write!(f, "{}", (*value).type_name()), #[cfg(not(feature = "no_shared"))] - Union::Shared(cell) => write!(f, "", (**cell).value_type_name), + Union::Shared(_) => f.write_str(""), } } } @@ -569,15 +566,10 @@ impl Dynamic { #[cfg(not(feature = "no_shared"))] return match self.0 { Union::Shared(..) => self, - _ => Self(Union::Shared(Box::new(SharedCell { - value_type_id: self.type_id(), - value_type_name: self.type_name(), - - #[cfg(not(feature = "sync"))] - container: Rc::new(RefCell::new(self)), - #[cfg(feature = "sync")] - container: Arc::new(RwLock::new(self)), - }))), + #[cfg(not(feature = "sync"))] + _ => Self(Union::Shared(Rc::new(RefCell::new(self)))), + #[cfg(feature = "sync")] + _ => Self(Union::Shared(Arc::new(RwLock::new(self)))), }; #[cfg(feature = "no_shared")] @@ -614,13 +606,11 @@ impl Dynamic { match self.0 { #[cfg(not(feature = "no_shared"))] #[cfg(not(feature = "sync"))] - Union::Shared(cell) => return cell.container.borrow().deref().clone().try_cast(), + Union::Shared(cell) => return cell.borrow().clone().try_cast(), #[cfg(not(feature = "no_shared"))] #[cfg(feature = "sync")] - Union::Shared(cell) => { - return cell.container.read().unwrap().deref().clone().try_cast() - } + Union::Shared(cell) => return cell.read().unwrap().clone().try_cast(), _ => (), } @@ -752,32 +742,11 @@ impl Dynamic { match self.0 { #[cfg(not(feature = "no_shared"))] Union::Shared(ref cell) => { - let type_id = TypeId::of::(); - - if type_id != TypeId::of::() && type_id != cell.value_type_id { - return None; - } - #[cfg(not(feature = "sync"))] - return Some( - cell.container - .borrow() - .deref() - .downcast_ref::() - .unwrap() - .clone(), - ); + return Some(cell.borrow().downcast_ref::().unwrap().clone()); #[cfg(feature = "sync")] - return Some( - cell.container - .read() - .unwrap() - .deref() - .downcast_ref::() - .unwrap() - .clone(), - ); + return Some(cell.read().unwrap().downcast_ref::().unwrap().clone()); } _ => self.downcast_ref().cloned(), } @@ -792,20 +761,12 @@ impl Dynamic { match self.0 { #[cfg(not(feature = "no_shared"))] Union::Shared(ref cell) => { - let type_id = TypeId::of::(); - - if type_id != TypeId::of::() && type_id != cell.value_type_id { - return None; - } - #[cfg(not(feature = "sync"))] - return Some(DynamicReadLock(DynamicReadLockInner::Guard( - cell.container.borrow(), - ))); + return Some(DynamicReadLock(DynamicReadLockInner::Guard(cell.borrow()))); #[cfg(feature = "sync")] return Some(DynamicReadLock(DynamicReadLockInner::Guard( - cell.container.read().unwrap(), + cell.read().unwrap(), ))); } _ => self @@ -823,20 +784,14 @@ impl Dynamic { match self.0 { #[cfg(not(feature = "no_shared"))] Union::Shared(ref cell) => { - let type_id = TypeId::of::(); - - if type_id != TypeId::of::() && cell.value_type_id != type_id { - return None; - } - #[cfg(not(feature = "sync"))] return Some(DynamicWriteLock(DynamicWriteLockInner::Guard( - cell.container.borrow_mut(), + cell.borrow_mut(), ))); #[cfg(feature = "sync")] return Some(DynamicWriteLock(DynamicWriteLockInner::Guard( - cell.container.write().unwrap(), + cell.write().unwrap(), ))); } _ => self @@ -1086,16 +1041,22 @@ impl Dynamic { #[cfg(not(feature = "no_shared"))] Union::Shared(cell) => { #[cfg(not(feature = "sync"))] - match &cell.container.borrow().deref().0 { - Union::Str(s) => Ok(s.clone()), - Union::FnPtr(f) => Ok(f.clone().take_data().0), - _ => Err(cell.value_type_name), + { + let inner = cell.borrow(); + match &inner.0 { + Union::Str(s) => Ok(s.clone()), + Union::FnPtr(f) => Ok(f.clone().take_data().0), + _ => Err((*inner).type_name()), + } } #[cfg(feature = "sync")] - match &cell.container.read().unwrap().deref().0 { - Union::Str(s) => Ok(s.clone()), - Union::FnPtr(f) => Ok(f.clone().take_data().0), - _ => Err(cell.value_type_name), + { + let inner = cell.read().unwrap(); + match &inner.0 { + Union::Str(s) => Ok(s.clone()), + Union::FnPtr(f) => Ok(f.clone().take_data().0), + _ => Err((*inner).type_name()), + } } } _ => Err(self.type_name()), diff --git a/src/fn_call.rs b/src/fn_call.rs index 05b65e3a..ace08128 100644 --- a/src/fn_call.rs +++ b/src/fn_call.rs @@ -1185,8 +1185,8 @@ pub fn run_builtin_op_assignment( } if args_type == TypeId::of::() { - let mut x = x.write_lock::().unwrap(); let y = y.clone().cast::(); + let mut x = x.write_lock::().unwrap(); if cfg!(not(feature = "unchecked")) { match op { @@ -1221,8 +1221,8 @@ pub fn run_builtin_op_assignment( _ => (), } } else if args_type == TypeId::of::() { - let mut x = x.write_lock::().unwrap(); let y = y.clone().cast::(); + let mut x = x.write_lock::().unwrap(); match op { "&=" => return Ok(Some(*x = *x && y)), @@ -1230,19 +1230,19 @@ pub fn run_builtin_op_assignment( _ => (), } } else if args_type == TypeId::of::() { + let y = y.read_lock::().unwrap().deref().clone(); let mut x = x.write_lock::().unwrap(); - let y = y.read_lock::().unwrap(); match op { - "+=" => return Ok(Some(*x += y.deref())), + "+=" => return Ok(Some(*x += y)), _ => (), } } #[cfg(not(feature = "no_float"))] if args_type == TypeId::of::() { - let mut x = x.write_lock::().unwrap(); let y = y.clone().cast::(); + let mut x = x.write_lock::().unwrap(); match op { "+=" => return Ok(Some(*x += y)), diff --git a/src/utils.rs b/src/utils.rs index 6668a7a9..ee448260 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -264,6 +264,18 @@ impl AddAssign<&ImmutableString> for ImmutableString { } } +impl AddAssign for ImmutableString { + fn add_assign(&mut self, rhs: ImmutableString) { + if !rhs.is_empty() { + if self.is_empty() { + self.0 = rhs.0; + } else { + self.make_mut().push_str(rhs.0.as_str()); + } + } + } +} + impl Add<&str> for ImmutableString { type Output = Self; diff --git a/tests/shared.rs b/tests/shared.rs index 7cd7af86..8223c876 100644 --- a/tests/shared.rs +++ b/tests/shared.rs @@ -11,11 +11,23 @@ fn test_shared() -> Result<(), Box> { 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))")?); From 1daf91df30038bb2a0b00132b27ca4e4ff06db7b Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Sat, 1 Aug 2020 22:28:17 +0800 Subject: [PATCH 27/40] Avoid storing shared value inside shared value. --- src/engine.rs | 31 ++++++++++++++++++------------- 1 file changed, 18 insertions(+), 13 deletions(-) diff --git a/src/engine.rs b/src/engine.rs index cb56c28b..24a1b20b 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -1347,7 +1347,11 @@ 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() = rhs_val; + *lhs_ptr.write_lock::().unwrap() = if rhs_val.is_shared() { + rhs_val.clone_inner_data().unwrap() + } else { + rhs_val + }; } else { *lhs_ptr = rhs_val; } @@ -1368,19 +1372,16 @@ impl Engine { .get_fn(hash_fn, false) .or_else(|| self.packages.get_fn(hash_fn, false)) { - #[cfg(not(feature = "no_shared"))] - let mut lock_guard; + if cfg!(not(feature = "no_shared")) && lhs_ptr.is_shared() { + let mut lock_guard = lhs_ptr.write_lock::().unwrap(); + let lhs_ptr_inner = lock_guard.deref_mut(); - #[cfg(not(feature = "no_shared"))] - let lhs_ptr = if lhs_ptr.is_shared() { - lock_guard = Some(lhs_ptr.write_lock::().unwrap()); - lock_guard.as_deref_mut().unwrap() + // Overriding exact implementation + func(self, lib, &mut [lhs_ptr_inner, &mut rhs_val])?; } else { - lhs_ptr - }; - - // Overriding exact implementation - func(self, lib, &mut [lhs_ptr, &mut rhs_val])?; + // Overriding exact implementation + func(self, lib, &mut [lhs_ptr, &mut rhs_val])?; + } } else if run_builtin_op_assignment(op, lhs_ptr, &rhs_val)?.is_none() { // Not built in, map to `var = var op rhs` let op = &op[..op.len() - 1]; // extract operator without = @@ -1396,7 +1397,11 @@ 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() = value; + *lhs_ptr.write_lock::().unwrap() = if value.is_shared() { + value.clone_inner_data().unwrap() + } else { + value + }; } else { *lhs_ptr = value; } From b86c87253bc0137a0d8e9a03799bc811e6710e03 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Sun, 2 Aug 2020 13:33:51 +0800 Subject: [PATCH 28/40] Prevent data races. --- src/any.rs | 32 ++++++++++++++++++++++++++++++++ src/api.rs | 6 ++++++ src/engine.rs | 15 ++++++++++----- src/fn_call.rs | 30 ++++++++++++++++++++++++++++++ src/result.rs | 8 +++++++- tests/shared.rs | 39 +++++++++++++++++++++++++++++++++++++++ 6 files changed, 124 insertions(+), 6 deletions(-) diff --git a/src/any.rs b/src/any.rs index 6280e426..e0281efe 100644 --- a/src/any.rs +++ b/src/any.rs @@ -282,6 +282,11 @@ impl Dynamic { } /// Get the TypeId of the value held by this `Dynamic`. + /// + /// # 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. pub fn type_id(&self) -> TypeId { match &self.0 { Union::Unit(_) => TypeId::of::<()>(), @@ -307,6 +312,11 @@ impl Dynamic { } /// Get the name of the type of the value held by this `Dynamic`. + /// + /// # 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. pub fn type_name(&self) -> &'static str { match &self.0 { Union::Unit(_) => "()", @@ -752,6 +762,28 @@ impl Dynamic { } } + /// Is the `Dynamic` a shared value that is locked? + /// + /// ## Note + /// + /// Under the `sync` feature, shared values use `RwLock` and they are never locked. + /// Access just waits until the `RwLock` is released. + /// So this method always returns `false` under `sync`. + #[inline(always)] + pub fn is_locked(&self) -> bool { + match self.0 { + #[cfg(not(feature = "no_shared"))] + Union::Shared(ref _cell) => { + #[cfg(not(feature = "sync"))] + return _cell.try_borrow().is_err(); + + #[cfg(feature = "sync")] + return false; + } + _ => false, + } + } + /// Get a reference of a specific type to the `Dynamic`. /// Casting to `Dynamic` just returns a reference to it. /// diff --git a/src/api.rs b/src/api.rs index e6e9ea7a..be69f53d 100644 --- a/src/api.rs +++ b/src/api.rs @@ -3,6 +3,7 @@ use crate::any::{Dynamic, Variant}; use crate::engine::{Engine, Imports, State}; use crate::error::ParseError; +use crate::fn_call::ensure_no_data_race; use crate::fn_native::{IteratorFn, SendSync}; use crate::module::{FuncReturn, Module}; use crate::optimize::{optimize_into_ast, OptimizationLevel}; @@ -1282,6 +1283,11 @@ impl Engine { let mut mods = Imports::new(); let args = args.as_mut(); + // Check for data race. + if cfg!(not(feature = "no_shared")) { + ensure_no_data_race(name, args, false)?; + } + self.call_script_fn( scope, &mut mods, &mut state, lib, this_ptr, name, fn_def, args, 0, ) diff --git a/src/engine.rs b/src/engine.rs index 24a1b20b..b0bd5e8b 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -41,14 +41,11 @@ use crate::stdlib::{ collections::{HashMap, HashSet}, fmt, format, iter::{empty, once}, + ops::DerefMut, string::{String, ToString}, vec::Vec, }; -#[cfg(not(feature = "no_shared"))] -#[cfg(not(feature = "no_object"))] -use crate::stdlib::ops::DerefMut; - #[cfg(not(feature = "no_index"))] use crate::stdlib::any::TypeId; @@ -561,7 +558,8 @@ pub fn search_imports_mut<'s>( }) } -/// Search for a variable within the scope and imports +/// Search for a variable within the scope or within imports, +/// depending on whether the variable name is qualified. pub fn search_namespace<'s, 'a>( scope: &'s mut Scope, mods: &'s mut Imports, @@ -631,6 +629,13 @@ pub fn search_scope_only<'s, 'a>( }; let (val, typ) = scope.get_mut(index); + + // 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() { + // return Err(Box::new(EvalAltResult::ErrorDataRace(name.into(), *pos))); + // } + Ok((val, name, typ, *pos)) } diff --git a/src/fn_call.rs b/src/fn_call.rs index ace08128..2b4ffb65 100644 --- a/src/fn_call.rs +++ b/src/fn_call.rs @@ -160,6 +160,31 @@ fn add_captured_variables_into_scope<'s>( ); } +#[inline(always)] +pub fn ensure_no_data_race( + fn_name: &str, + args: &FnCallArgs, + is_ref: bool, +) -> Result<(), Box> { + if cfg!(not(feature = "no_shared")) { + let skip = if is_ref { 1 } else { 0 }; + + if let Some((n, _)) = args + .iter() + .skip(skip) + .enumerate() + .find(|(_, a)| a.is_locked()) + { + return Err(Box::new(EvalAltResult::ErrorDataRace( + format!("argument #{} of function '{}'", n + 1 + skip, fn_name), + Position::none(), + ))); + } + } + + Ok(()) +} + impl Engine { /// Call a native Rust function registered with the `Engine`. /// Position in `EvalAltResult` is `None` and must be set afterwards. @@ -430,6 +455,11 @@ impl Engine { def_val: Option, level: usize, ) -> Result<(Dynamic, bool), Box> { + // Check for data race. + if cfg!(not(feature = "no_shared")) { + ensure_no_data_race(fn_name, args, is_ref)?; + } + // Qualifiers (none) + function name + number of arguments + argument `TypeId`'s. let arg_types = args.iter().map(|a| a.type_id()); let hash_fn = calc_fn_hash(empty(), fn_name, args.len(), arg_types); diff --git a/src/result.rs b/src/result.rs index c0cc620d..cecd14bd 100644 --- a/src/result.rs +++ b/src/result.rs @@ -69,6 +69,8 @@ pub enum EvalAltResult { ErrorVariableNotFound(String, Position), /// Usage of an unknown module. Wrapped value is the name of the module. ErrorModuleNotFound(String, Position), + /// Data race detected when accessing a variable. Wrapped value is the name of the variable. + ErrorDataRace(String, Position), /// Assignment to an inappropriate LHS (left-hand-side) expression. ErrorAssignmentToUnknownLHS(Position), /// Assignment to a constant variable. @@ -136,7 +138,8 @@ impl EvalAltResult { Self::ErrorLogicGuard(_) => "Boolean value expected", Self::ErrorFor(_) => "For loop expects an array, object map, or range", Self::ErrorVariableNotFound(_, _) => "Variable not found", - Self::ErrorModuleNotFound(_, _) => "module not found", + Self::ErrorModuleNotFound(_, _) => "Module not found", + Self::ErrorDataRace(_, _) => "Data race detected when accessing variable", Self::ErrorAssignmentToUnknownLHS(_) => { "Assignment to an unsupported left-hand side expression" } @@ -180,6 +183,7 @@ impl fmt::Display for EvalAltResult { Self::ErrorFunctionNotFound(s, _) | Self::ErrorVariableNotFound(s, _) + | Self::ErrorDataRace(s, _) | Self::ErrorModuleNotFound(s, _) => write!(f, "{}: '{}'", desc, s)?, Self::ErrorDotExpr(s, _) if !s.is_empty() => write!(f, "{}", s)?, @@ -289,6 +293,7 @@ impl EvalAltResult { | Self::ErrorFor(pos) | Self::ErrorVariableNotFound(_, pos) | Self::ErrorModuleNotFound(_, pos) + | Self::ErrorDataRace(_, pos) | Self::ErrorAssignmentToUnknownLHS(pos) | Self::ErrorAssignmentToConstant(_, pos) | Self::ErrorMismatchOutputType(_, _, pos) @@ -329,6 +334,7 @@ impl EvalAltResult { | Self::ErrorFor(pos) | Self::ErrorVariableNotFound(_, pos) | Self::ErrorModuleNotFound(_, pos) + | Self::ErrorDataRace(_, pos) | Self::ErrorAssignmentToUnknownLHS(pos) | Self::ErrorAssignmentToConstant(_, pos) | Self::ErrorMismatchOutputType(_, _, pos) diff --git a/tests/shared.rs b/tests/shared.rs index 8223c876..b2238fbf 100644 --- a/tests/shared.rs +++ b/tests/shared.rs @@ -283,3 +283,42 @@ fn test_shared() -> Result<(), Box> { 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(()) +} From 747c0345f2ccff00a41bbcea3488b2689f11941d Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Sun, 2 Aug 2020 13:51:07 +0800 Subject: [PATCH 29/40] Do not convert a function call into a method call if the object is shared. --- src/any.rs | 2 +- src/fn_call.rs | 18 ++++++++++-------- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/src/any.rs b/src/any.rs index e0281efe..08ebdeab 100644 --- a/src/any.rs +++ b/src/any.rs @@ -258,7 +258,7 @@ impl Dynamic { } /// Does this `Dynamic` hold a shared data type - /// instead of one of the support system primitive types? + /// instead of one of the supported system primitive types? pub fn is_shared(&self) -> bool { match self.0 { #[cfg(not(feature = "no_shared"))] diff --git a/src/fn_call.rs b/src/fn_call.rs index 2b4ffb65..6980caca 100644 --- a/src/fn_call.rs +++ b/src/fn_call.rs @@ -906,11 +906,11 @@ impl Engine { // No arguments args = Default::default(); } else { - // See if the first argument is a variable, if so, convert to method-call style + // If the first argument is a variable, and there is no curried arguments, convert to method-call style // in order to leverage potential &mut first argument and avoid cloning the value match args_expr.get(0).unwrap() { // func(x, ...) -> x.func(...) - lhs @ Expr::Variable(_) => { + lhs @ Expr::Variable(_) if curry.is_empty() => { arg_values = args_expr .iter() .skip(1) @@ -922,12 +922,14 @@ impl Engine { self.inc_operations(state) .map_err(|err| err.new_position(pos))?; - args = once(target) - .chain(curry.iter_mut()) - .chain(arg_values.iter_mut()) - .collect(); - - is_ref = true; + // 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.iter_mut().collect() + } else { + is_ref = true; + once(target).chain(arg_values.iter_mut()).collect() + }; } // func(..., ...) _ => { From 096eb4493e058988cc503d4e4fcbd2b769ac688f Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Sun, 2 Aug 2020 18:55:22 +0800 Subject: [PATCH 30/40] Engine::load_package API change. --- RELEASES.md | 1 + src/settings.rs | 4 ++-- tests/closures.rs | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/RELEASES.md b/RELEASES.md index f163f06f..d727a20e 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -33,6 +33,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. +* `Engine::load_package` takes any type that is `Into`. Housekeeping ------------ diff --git a/src/settings.rs b/src/settings.rs index d638499c..d4949a41 100644 --- a/src/settings.rs +++ b/src/settings.rs @@ -18,9 +18,9 @@ impl Engine { /// /// When searching for functions, packages loaded later are preferred. /// In other words, loaded packages are searched in reverse order. - pub fn load_package(&mut self, package: PackageLibrary) -> &mut Self { + pub fn load_package(&mut self, package: impl Into) -> &mut Self { // Push the package to the top - packages are searched in reverse order - self.packages.push(package); + self.packages.push(package.into()); self } diff --git a/tests/closures.rs b/tests/closures.rs index 3aec4865..27ecc5e6 100644 --- a/tests/closures.rs +++ b/tests/closures.rs @@ -16,7 +16,7 @@ fn test_fn_ptr_curry_call() -> Result<(), Box> { ); let mut engine = Engine::new(); - engine.load_package(module.into()); + engine.load_package(module); #[cfg(not(feature = "no_object"))] assert_eq!( From dd0177f588e4f940aa3b57f1771db27f926153d9 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Mon, 3 Aug 2020 10:07:52 +0800 Subject: [PATCH 31/40] Allow deprecated API. --- tests/call_fn.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/call_fn.rs b/tests/call_fn.rs index 0f9087d2..a22f7b6d 100644 --- a/tests/call_fn.rs +++ b/tests/call_fn.rs @@ -88,6 +88,7 @@ fn test_call_fn_private() -> Result<(), Box> { fn test_fn_ptr_raw() -> Result<(), Box> { let mut engine = Engine::new(); + #[allow(deprecated)] engine .register_fn("mul", |x: &mut INT, y: INT| *x *= y) .register_raw_fn( From 4079164bfd2a4a7456f9468a3a398c49de5debe0 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Mon, 3 Aug 2020 12:10:20 +0800 Subject: [PATCH 32/40] 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(()) -} From dddd8133df659086431d0d6df28c0cf2dbad2305 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Mon, 3 Aug 2020 12:38:56 +0800 Subject: [PATCH 33/40] Set unshared value in let/const statement. --- src/engine.rs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/engine.rs b/src/engine.rs index be915025..7b99dd3d 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -1764,7 +1764,10 @@ impl Engine { Stmt::Let(x) if x.1.is_some() => { let ((var_name, _), expr, _) = x.as_ref(); let expr = expr.as_ref().unwrap(); - let val = self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)?; + let val = self + .eval_expr(scope, mods, state, lib, this_ptr, expr, level)? + .clone_inner_data() + .unwrap(); let var_name = unsafe_cast_var_name_to_lifetime(var_name, &state); scope.push_dynamic_value(var_name, ScopeEntryType::Normal, val, false); Ok(Default::default()) @@ -1780,7 +1783,10 @@ impl Engine { // Const statement Stmt::Const(x) if x.1.is_constant() => { let ((var_name, _), expr, _) = x.as_ref(); - let val = self.eval_expr(scope, mods, state, lib, this_ptr, &expr, level)?; + let val = self + .eval_expr(scope, mods, state, lib, this_ptr, &expr, level)? + .clone_inner_data() + .unwrap(); let var_name = unsafe_cast_var_name_to_lifetime(var_name, &state); scope.push_dynamic_value(var_name, ScopeEntryType::Constant, val, true); Ok(Default::default()) From e1a70fe9585bb143545dd511602127f47d438886 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Mon, 3 Aug 2020 23:11:08 +0800 Subject: [PATCH 34/40] Add FnPtr to standard packages. --- src/packages/array_basic.rs | 7 ++++--- src/packages/string_basic.rs | 2 +- src/packages/string_more.rs | 5 +++-- 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/src/packages/array_basic.rs b/src/packages/array_basic.rs index c246f1d5..5574a4f5 100644 --- a/src/packages/array_basic.rs +++ b/src/packages/array_basic.rs @@ -3,6 +3,7 @@ use crate::any::{Dynamic, Variant}; use crate::def_package; use crate::engine::{Array, Engine}; +use crate::fn_native::FnPtr; use crate::module::{FuncReturn, Module}; use crate::parser::{ImmutableString, INT}; @@ -83,9 +84,9 @@ macro_rules! reg_pad { } def_package!(crate:BasicArrayPackage:"Basic array utilities.", lib, { - reg_op!(lib, "push", push, INT, bool, char, ImmutableString, Array, ()); - reg_pad!(lib, "pad", pad, INT, bool, char, ImmutableString, Array, ()); - reg_tri!(lib, "insert", ins, INT, bool, char, ImmutableString, Array, ()); + reg_op!(lib, "push", push, INT, bool, char, ImmutableString, FnPtr, Array, ()); + reg_pad!(lib, "pad", pad, INT, bool, char, ImmutableString, FnPtr, Array, ()); + reg_tri!(lib, "insert", ins, INT, bool, char, ImmutableString, FnPtr, Array, ()); lib.set_fn_2_mut("append", |x: &mut Array, y: Array| { x.extend(y); diff --git a/src/packages/string_basic.rs b/src/packages/string_basic.rs index 36f4f333..08778150 100644 --- a/src/packages/string_basic.rs +++ b/src/packages/string_basic.rs @@ -21,7 +21,7 @@ fn to_debug(x: &mut T) -> FuncReturn { Ok(format!("{:?}", x).into()) } fn to_string(x: &mut T) -> FuncReturn { - Ok(format!("{}", x).into()) + Ok(x.to_string().into()) } #[cfg(not(feature = "no_object"))] fn format_map(x: &mut Map) -> FuncReturn { diff --git a/src/packages/string_more.rs b/src/packages/string_more.rs index 130af8b8..149823c4 100644 --- a/src/packages/string_more.rs +++ b/src/packages/string_more.rs @@ -1,6 +1,7 @@ use crate::any::Dynamic; use crate::def_package; use crate::engine::Engine; +use crate::fn_native::FnPtr; use crate::module::{FuncReturn, Module}; use crate::parser::{ImmutableString, INT}; use crate::utils::StaticVec; @@ -88,10 +89,10 @@ macro_rules! reg_op { } def_package!(crate:MoreStringPackage:"Additional string utilities, including string building.", lib, { - reg_op!(lib, "+", append, INT, bool, char); + reg_op!(lib, "+", append, INT, bool, char, FnPtr); lib.set_fn_2( "+", |x: ImmutableString, _: ()| Ok(x)); - reg_op!(lib, "+", prepend, INT, bool, char); + reg_op!(lib, "+", prepend, INT, bool, char, FnPtr); lib.set_fn_2("+", |_: (), y: ImmutableString| Ok(y)); if cfg!(not(feature = "only_i32")) && cfg!(not(feature = "only_i64")) { From abe74e7f44e8722f7578dec2efcf73190220a095 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Mon, 3 Aug 2020 23:11:24 +0800 Subject: [PATCH 35/40] Handle Dynamic::from(FnPtr). --- src/any.rs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/any.rs b/src/any.rs index 78d681be..f9c8a093 100644 --- a/src/any.rs +++ b/src/any.rs @@ -551,6 +551,11 @@ impl Dynamic { } } + boxed = match unsafe_cast_box::<_, FnPtr>(boxed) { + Ok(fn_ptr) => return (*fn_ptr).into(), + Err(val) => val, + }; + boxed = match unsafe_cast_box::<_, Dynamic>(boxed) { Ok(d) => return *d, Err(val) => val, @@ -805,6 +810,7 @@ impl Dynamic { let data = cell.read().unwrap(); let type_id = (*data).type_id(); + println!("Type = {}", (*data).type_name()); if type_id != TypeId::of::() && TypeId::of::() != TypeId::of::() { None @@ -1176,7 +1182,7 @@ impl, T: Variant + Clone> From> for Dynam } impl From for Dynamic { fn from(value: FnPtr) -> Self { - Box::new(value).into() + Self(Union::FnPtr(Box::new(value))) } } impl From> for Dynamic { From 3b6d35368f228d0b79656220c94de1196b96ced1 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Mon, 3 Aug 2020 23:11:38 +0800 Subject: [PATCH 36/40] Handle shared for loop variable. --- src/engine.rs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/engine.rs b/src/engine.rs index 7b99dd3d..65228a8f 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -1701,7 +1701,15 @@ impl Engine { state.scope_level += 1; for loop_var in func(iter_type) { - *scope.get_mut(index).0 = loop_var; + let for_var = scope.get_mut(index).0; + let value = loop_var.clone_inner_data().unwrap(); + + if cfg!(not(feature = "no_closure")) && for_var.is_shared() { + *for_var.write_lock().unwrap() = value; + } else { + *for_var = value; + } + self.inc_operations(state) .map_err(|err| err.new_position(stmt.position()))?; From 2aa08c0dd95d8d340fdd6fe09396c53c42ae67c7 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Mon, 3 Aug 2020 23:12:42 +0800 Subject: [PATCH 37/40] get_constant_value for FnPointer. --- src/parser.rs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/parser.rs b/src/parser.rs index 7f29bbcd..272215ac 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -6,7 +6,7 @@ use crate::engine::{ Engine, KEYWORD_FN_PTR_CURRY, KEYWORD_THIS, MARKER_BLOCK, MARKER_EXPR, MARKER_IDENT, }; use crate::error::{LexError, ParseError, ParseErrorType}; -use crate::fn_native::Shared; +use crate::fn_native::{FnPtr, Shared}; use crate::module::{Module, ModuleRef}; use crate::optimize::{optimize_into_ast, OptimizationLevel}; use crate::scope::{EntryType as ScopeEntryType, Scope}; @@ -835,6 +835,10 @@ impl Expr { Self::FloatConstant(x) => x.0.into(), Self::CharConstant(x) => x.0.into(), Self::StringConstant(x) => x.0.clone().into(), + Self::FnPointer(x) => Dynamic(Union::FnPtr(Box::new(FnPtr::new_unchecked( + x.0.clone(), + Default::default(), + )))), Self::True(_) => true.into(), Self::False(_) => false.into(), Self::Unit(_) => ().into(), @@ -3292,7 +3296,7 @@ fn parse_anon_fn( let hash = s.finish(); // Create unique function name - let fn_name: ImmutableString = format!("{}{:16x}", FN_ANONYMOUS, hash).into(); + let fn_name: ImmutableString = format!("{}{:016x}", FN_ANONYMOUS, hash).into(); // Define the function let script = ScriptFnDef { From 4a7bf893e643ee0f2e4069ffac144c6ddc6db6b4 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Mon, 3 Aug 2020 23:13:09 +0800 Subject: [PATCH 38/40] Refine flatten clone for scope. --- src/scope.rs | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/src/scope.rs b/src/scope.rs index 836108c7..61f6fa17 100644 --- a/src/scope.rs +++ b/src/scope.rs @@ -4,9 +4,7 @@ use crate::any::{Dynamic, Variant}; use crate::parser::{map_dynamic_to_expr, Expr}; use crate::token::Position; -use crate::stdlib::{ - borrow::Cow, boxed::Box, collections::HashMap, iter, string::String, vec::Vec, -}; +use crate::stdlib::{borrow::Cow, boxed::Box, iter, string::String, vec::Vec}; /// Type of an entry in the Scope. #[derive(Debug, Eq, PartialEq, Hash, Copy, Clone)] @@ -394,15 +392,19 @@ impl<'a> Scope<'a> { /// Clone the Scope, keeping only the last instances of each variable name. /// Shadowed variables are omitted in the copy. pub(crate) fn flatten_clone(&self) -> Self { - let mut entries: HashMap<&str, Entry> = Default::default(); + let mut entries: Vec = Default::default(); self.0.iter().rev().for_each(|entry| { - entries - .entry(entry.name.as_ref()) - .or_insert_with(|| entry.clone()); + if entries + .iter() + .find(|Entry { name, .. }| &entry.name == name) + .is_none() + { + entries.push(entry.clone()); + } }); - Self(entries.into_iter().map(|(_, v)| v).collect()) + Self(entries) } /// Get an iterator to entries in the Scope. From 3d6c83c6d81e62bd746af1d979fc594a4fc1cacf Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Tue, 4 Aug 2020 09:47:48 +0800 Subject: [PATCH 39/40] Fix serde builds. --- src/any.rs | 53 +++++++++++++++++++++++++++++++------------------ src/serde/de.rs | 3 +++ 2 files changed, 37 insertions(+), 19 deletions(-) diff --git a/src/any.rs b/src/any.rs index f9c8a093..bdde78f3 100644 --- a/src/any.rs +++ b/src/any.rs @@ -337,7 +337,10 @@ impl Dynamic { Union::Variant(value) => (***value).type_name(), #[cfg(not(feature = "no_closure"))] #[cfg(not(feature = "sync"))] - Union::Shared(cell) => (*cell.borrow()).type_name(), + Union::Shared(cell) => cell + .try_borrow() + .map(|v| (*v).type_name()) + .unwrap_or(""), #[cfg(not(feature = "no_closure"))] #[cfg(feature = "sync")] Union::Shared(cell) => (*cell.read().unwrap()).type_name(), @@ -394,10 +397,20 @@ impl fmt::Display for Dynamic { Union::FnPtr(value) => fmt::Display::fmt(value, f), #[cfg(not(feature = "no_std"))] - Union::Variant(value) if value.is::() => write!(f, ""), - Union::Variant(value) => write!(f, "{}", (*value).type_name()), + Union::Variant(value) if value.is::() => f.write_str(""), + Union::Variant(value) => f.write_str((*value).type_name()), #[cfg(not(feature = "no_closure"))] - Union::Shared(_) => f.write_str(""), + #[cfg(not(feature = "sync"))] + Union::Shared(cell) => { + if let Ok(v) = cell.try_borrow() { + fmt::Display::fmt(&*v, f) + } else { + f.write_str("") + } + } + #[cfg(not(feature = "no_closure"))] + #[cfg(feature = "sync")] + Union::Shared(cell) => fmt::Display::fmt(*cell.read_lock().unwrap(), f), } } } @@ -425,7 +438,17 @@ impl fmt::Debug for Dynamic { Union::Variant(value) if value.is::() => write!(f, ""), Union::Variant(value) => write!(f, "{}", (*value).type_name()), #[cfg(not(feature = "no_closure"))] - Union::Shared(_) => f.write_str(""), + #[cfg(not(feature = "sync"))] + Union::Shared(cell) => { + if let Ok(v) = cell.try_borrow() { + write!(f, "{:?} (shared)", *v) + } else { + f.write_str("") + } + } + #[cfg(not(feature = "no_closure"))] + #[cfg(feature = "sync")] + Union::Shared(cell) => fmt::Display::fmt(*cell.read_lock().unwrap(), f), } } } @@ -861,13 +884,9 @@ impl Dynamic { /// Get a reference of a specific type to the `Dynamic`. /// Casting to `Dynamic` just returns a reference to it. /// - /// Returns `None` if the cast fails. - /// - /// # Panics - /// - /// Panics if the value is shared. + /// Returns `None` if the cast fails, or if the value is shared. #[inline(always)] - fn downcast_ref(&self) -> Option<&T> { + pub(crate) fn downcast_ref(&self) -> Option<&T> { let type_id = TypeId::of::(); if type_id == TypeId::of::() { @@ -940,7 +959,7 @@ impl Dynamic { match &self.0 { Union::Variant(value) => value.as_ref().as_ref().as_any().downcast_ref::(), #[cfg(not(feature = "no_closure"))] - Union::Shared(_) => unreachable!(), + Union::Shared(_) => None, _ => None, } } @@ -948,13 +967,9 @@ impl Dynamic { /// Get a mutable reference of a specific type to the `Dynamic`. /// Casting to `Dynamic` just returns a mutable reference to it. /// - /// Returns `None` if the cast fails. - /// - /// # Panics - /// - /// Panics if the value is shared. + /// Returns `None` if the cast fails, or if the value is shared. #[inline(always)] - fn downcast_mut(&mut self) -> Option<&mut T> { + pub(crate) fn downcast_mut(&mut self) -> Option<&mut T> { let type_id = TypeId::of::(); if type_id == TypeId::of::() { @@ -1021,7 +1036,7 @@ impl Dynamic { match &mut self.0 { Union::Variant(value) => value.as_mut().as_mut_any().downcast_mut::(), #[cfg(not(feature = "no_closure"))] - Union::Shared(_) => unreachable!(), + Union::Shared(_) => None, _ => None, } } diff --git a/src/serde/de.rs b/src/serde/de.rs index 8a60604c..f6d328db 100644 --- a/src/serde/de.rs +++ b/src/serde/de.rs @@ -177,6 +177,9 @@ impl<'de> Deserializer<'de> for &mut DynamicDeserializer<'de> { Union::Variant(value) if value.is::() => self.deserialize_u128(visitor), Union::Variant(_) => self.type_error(), + + #[cfg(not(feature = "no_closure"))] + Union::Shared(_) => self.type_error(), } } From 4878a695030e91e4454e0fb67449c6ba91fb01cf Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Tue, 4 Aug 2020 16:27:55 +0800 Subject: [PATCH 40/40] Add docs for closures. --- Cargo.toml | 2 +- RELEASES.md | 4 +- doc/src/SUMMARY.md | 2 +- doc/src/about/features.md | 2 + doc/src/language/fn-anon.md | 5 +- doc/src/language/fn-capture.md | 13 ++- doc/src/language/fn-closure.md | 154 ++++++++++++++++++++++++--- doc/src/language/fn-curry.md | 2 +- doc/src/language/values-and-types.md | 32 +++--- doc/src/links.md | 4 +- doc/src/rust/register-raw.md | 18 ++-- doc/src/start/features.md | 2 +- src/any.rs | 24 ++--- 13 files changed, 200 insertions(+), 64 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 7d20a93c..f6ffdea4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,7 +23,7 @@ smallvec = { version = "1.4.1", default-features = false } [features] #default = ["unchecked", "sync", "no_optimize", "no_float", "only_i32", "no_index", "no_object", "no_function", "no_module"] default = [] -plugins = [] +plugins = [] # custom plugins support unchecked = [] # unchecked arithmetic sync = [] # restrict to only types that implement Send + Sync no_optimize = [] # no script optimizer diff --git a/RELEASES.md b/RELEASES.md index 79f298ec..1d2a842b 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -9,7 +9,7 @@ This version adds: * Binding the `this` pointer in a function pointer `call`. * Anonymous functions (in Rust closure syntax). Simplifies creation of single-use ad-hoc functions. * Currying of function pointers. -* Auto-currying of anonymous functions. +* Closures - auto-currying of anonymous functions to capture shared variables from the external scope. * Capturing call scope via `func!(...)` syntax. New features @@ -21,7 +21,7 @@ New features * Anonymous functions are supported in the syntax of a Rust closure, e.g. `|x, y, z| x + y - z`. * Custom syntax now works even without the `internals` feature. * Currying of function pointers is supported via the new `curry` keyword. -* Automatic currying of anonymous functions to capture environment variables. +* Automatic currying of anonymous functions to capture shared variables from the external scope. * 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. diff --git a/doc/src/SUMMARY.md b/doc/src/SUMMARY.md index 6b966de1..3b23b321 100644 --- a/doc/src/SUMMARY.md +++ b/doc/src/SUMMARY.md @@ -79,7 +79,7 @@ The Rhai Scripting Language 4. [Function Pointers](language/fn-ptr.md) 5. [Anonymous Functions](language/fn-anon.md) 6. [Currying](language/fn-curry.md) - 7. [Capturing External Variables](language/fn-closure.md) + 7. [Closures](language/fn-closure.md) 16. [Print and Debug](language/print-debug.md) 17. [Modules](language/modules/index.md) 1. [Export Variables, Functions and Sub-Modules](language/modules/export.md) diff --git a/doc/src/about/features.md b/doc/src/about/features.md index 245668b2..6c2e3965 100644 --- a/doc/src/about/features.md +++ b/doc/src/about/features.md @@ -37,6 +37,8 @@ Dynamic * Dynamic dispatch via [function pointers] with additional support for [currying]. +* Closures via [automatic currying] with capturing shared variables from the external scope. + * Some support for [object-oriented programming (OOP)][OOP]. Safe diff --git a/doc/src/language/fn-anon.md b/doc/src/language/fn-anon.md index b8a9154d..8b514509 100644 --- a/doc/src/language/fn-anon.md +++ b/doc/src/language/fn-anon.md @@ -55,5 +55,6 @@ WARNING - NOT Real Closures Remember: anonymous functions, though having the same syntax as Rust _closures_, are themselves **not** real closures. -In particular, they capture their execution environment via [automatic currying][capture], -unless the [`no_closure`] feature is turned on. + +In particular, they capture their execution environment via [automatic currying] +(disabled via [`no_closure`]). diff --git a/doc/src/language/fn-capture.md b/doc/src/language/fn-capture.md index 042675e9..08052826 100644 --- a/doc/src/language/fn-capture.md +++ b/doc/src/language/fn-capture.md @@ -16,6 +16,8 @@ it raises an evaluation error. It is possible, through a special syntax, to capture the calling scope - i.e. the scope that makes the function call - and access variables defined there. +Capturing can be disabled via the [`no_closure`] feature. + ```rust fn foo(y) { // function accesses 'x' and 'y', but 'x' is not defined x += y; // 'x' is modified in this function @@ -47,7 +49,7 @@ f.call!(41); // <- syntax error: capturing is not allowed in method-c No Mutations ------------ -Variables in the calling scope are accessed as copies. +Variables in the calling scope are captured as copies. Changes to them do not reflect back to the calling scope. Rhai functions remain _pure_ in the sense that they can never mutate their environment. @@ -56,7 +58,10 @@ Rhai functions remain _pure_ in the sense that they can never mutate their envir Caveat Emptor ------------- -Functions relying on the calling scope is a _Very Bad Idea™_ because it makes code almost impossible -to reason and maintain, as their behaviors are volatile and unpredictable. +Functions relying on the calling scope is often a _Very Bad Idea™_ because it makes code +almost impossible to reason and maintain, as their behaviors are volatile and unpredictable. -This usage should be at the last resort. +They behave more like macros that are expanded inline than actual function calls, thus the +syntax is also similar to Rust's macro invocations. + +This usage should be at the last resort. YOU HAVE BEEN WARNED. diff --git a/doc/src/language/fn-closure.md b/doc/src/language/fn-closure.md index 9ba66f2e..2f6ce2c5 100644 --- a/doc/src/language/fn-closure.md +++ b/doc/src/language/fn-closure.md @@ -1,10 +1,10 @@ -Capture External Variables via Automatic Currying -================================================ +Simulating Closures +=================== {{#include ../links.md}} -Poor Man's Closures -------------------- +Capture External Variables via Automatic Currying +------------------------------------------------ Since [anonymous functions] de-sugar to standard function definitions, they retain all the behaviors of Rhai functions, including being _pure_, having no access to external variables. @@ -15,13 +15,23 @@ 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 captured variables are automatically converted into reference-counted shared values. + +The captured variables are automatically converted into **reference-counted shared values** +(`Rc>` in normal builds, `Arc>` in [`sync`] builds). + +Therefore, similar to closures in many languages, these captured shared values persist through +reference counting, and may be read or modified even after the variables that hold them +go out of scope and no longer exist. + +Use the `is_shared` function to check whether a particular value is a shared value. + +Automatic currying can be turned off via the [`no_closure`] feature. -New Parameters For Captured Variables ------------------------------------- +Actual Implementation +--------------------- -In actual implementation, this de-sugars to: +The actual implementation de-sugars to: 1. Keeping track of what variables are accessed inside the anonymous function, @@ -29,32 +39,148 @@ 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 variable is then turned into a reference-counted shared value. +4. The variable is then converted into a **reference-counted shared value**. + + An [anonymous function] which captures an external variable is the only way to create a reference-counted shared value in Rhai. 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. + This process is called _Automatic Currying_, and is the mechanism through which Rhai simulates normal closures. Examples -------- ```rust -let x = 1; +let x = 1; // a normal variable let f = |y| x + y; // variable 'x' is auto-curried (captured) into 'f' - // 'x' is converted into a shared value -x = 40; // 'x' can be changed +x.is_shared() == true; // 'x' is now a shared value! + +x = 40; // changing 'x'... 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 -make_shared(x); // convert 'x' into a shared value +make_shared(x); // convert variable 'x' into a shared value let f = Fn("anon$1001").curry(x); // shared 'x' is curried f.call(2) == 42; ``` + + +Beware: Captured Variables are Truly Shared +------------------------------------------ + +The example below is a typical tutorial sample for many languages to illustrate the traps +that may accompany capturing external scope variables in closures. + +It prints `9`, `9`, `9`, ... `9`, `9`, not `0`, `1`, `2`, ... `8`, `9`, because there is +ever only one captured variable, and all ten closures capture the _same_ variable. + +```rust +let funcs = []; + +for i in range(0, 10) { + funcs.push(|| print(i)); // the for loop variable 'i' is captured +} + +funcs.len() == 10; // 10 closures stored in the array + +funcs[0].type_of() == "Fn"; // make sure these are closures + +for f in funcs { + f.call(); // all the references to 'i' are the same variable! +} +``` + + +Therefore - Be Careful to Prevent Data Races +------------------------------------------- + +Rust does not have data races, but that doesn't mean Rhai doesn't. + +Avoid performing a method call on a captured shared variable (which essentially takes a +mutable reference to the shared object) while using that same variable as a parameter +in the method call - this is a sure-fire way to generate a data race error. + +If a shared value is used as the `this` pointer in a method call to a closure function, +then the same shared value _must not_ be captured inside that function, or a data race +will occur and the script will terminate with an error. + +```rust +let x = 20; + +let f = |a| this += x + a; // 'x' is captured in this closure + +x.is_shared() == true; // now 'x' is shared + +x.call(f, 2); // <- error: data race detected on 'x' +``` + + +Data Races in `sync` Builds Can Become Deadlocks +----------------------------------------------- + +Under the [`sync`] feature, shared values are guarded with a `RwLock`, meaning that data race +conditions no longer raise an error. + +Instead, they wait endlessly for the `RwLock` to be freed, and thus can become deadlocks. + +On the other hand, since the same thread (i.e. the [`Engine`] thread) that is holding the lock +is attempting to read it again, this may also [panic](https://doc.rust-lang.org/std/sync/struct.RwLock.html#panics-1) +depending on the O/S. + +```rust +let x = 20; + +let f = |a| this += x + a; // 'x' is captured in this closure + +// Under `sync`, the following may wait forever, or may panic, +// because 'x' is locked as the `this` pointer but also accessed +// via a captured shared value. +x.call(f, 2); +``` + + +TL;DR +----- + +### Q: Why are closures implemented as automatic currying? + +In concept, a closure _closes_ over captured variables from the outer scope - that's why +they are called _closures_. When this happen, a typical language implementation hoists +those variables that are captured away from the stack frame and into heap-allocated storage. +This is because those variables may be needed after the stack frame goes away. + +These heap-allocated captured variables only go away when all the closures that need them +are finished with them. A garbage collector makes this trivial to implement - they are +automatically collected as soon as all closures needing them are destroyed. + +In Rust, this can be done by reference counting instead, with the potential pitfall of creating +reference loops that will prevent those variables from being deallocated forever. +Rhai avoids this by clone-copying most data values, so reference loops are hard to create. + +Rhai does the hoisting of captured variables into the heap by converting those values +into reference-counted locked values, also allocated on the heap. The process is identical. + +Closures are usually implemented as a data structure containing two items: + +1) A function pointer to the function body of the closure, +2) A data structure containing references to the captured shared variables on the heap. + +Usually a language implementation passes the structure containing references to captured +shared variables into the function pointer, the function body taking this data structure +as an additional parameter. + +This is essentially what Rhai does, except that Rhai passes each variable individually +as separate parameters to the function, instead of creating a structure and passing that +structure as a single parameter. This is the only difference. + +Therefore, in most languages, essentially all closures are implemented as automatic currying of +shared variables hoisted into the heap, automatically passing those variables as parameters into +the function. Rhai just brings this directly up to the front. diff --git a/doc/src/language/fn-curry.md b/doc/src/language/fn-curry.md index 83705175..c223d8cd 100644 --- a/doc/src/language/fn-curry.md +++ b/doc/src/language/fn-curry.md @@ -33,7 +33,7 @@ curried.call(2) == 42; // <- de-sugars to 'func.call(21, 2)' Automatic Currying ------------------ -[Anonymous functions] defined via a closure syntax _capture_ the _values_ of external variables +[Anonymous functions] defined via a closure syntax _capture_ external variables that are not shadowed inside the function's scope. This is accomplished via [automatic currying]. diff --git a/doc/src/language/values-and-types.md b/doc/src/language/values-and-types.md index 833eead5..f7446913 100644 --- a/doc/src/language/values-and-types.md +++ b/doc/src/language/values-and-types.md @@ -5,22 +5,22 @@ Values and Types The following primitive types are supported natively: -| Category | Equivalent Rust types | [`type_of()`] | `to_string()` | -| --------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------- | --------------------- | ----------------------- | -| **Integer number** | `u8`, `i8`, `u16`, `i16`,
`u32`, `i32` (default for [`only_i32`]),
`u64`, `i64` _(default)_ | `"i32"`, `"u64"` etc. | `"42"`, `"123"` etc. | -| **Floating-point number** (disabled with [`no_float`]) | `f32`, `f64` _(default)_ | `"f32"` or `"f64"` | `"123.4567"` etc. | -| **Boolean value** | `bool` | `"bool"` | `"true"` or `"false"` | -| **Unicode character** | `char` | `"char"` | `"A"`, `"x"` etc. | -| **Immutable Unicode [string]** | `rhai::ImmutableString` (implemented as `Rc` or `Arc`) | `"string"` | `"hello"` etc. | -| **[`Array`]** (disabled with [`no_index`]) | `rhai::Array` | `"array"` | `"[ ?, ?, ? ]"` | -| **[Object map]** (disabled with [`no_object`]) | `rhai::Map` | `"map"` | `"#{ "a": 1, "b": 2 }"` | -| **[Timestamp]** (implemented in the [`BasicTimePackage`][packages], disabled with [`no_std`]) | `std::time::Instant` ([`instant::Instant`] if [WASM] build) | `"timestamp"` | `""` | -| **[Function pointer]** | `rhai::FnPtr` | `Fn` | `"Fn(foo)"` | -| **[`Dynamic`] value** (i.e. can be anything) | `rhai::Dynamic` | _the actual type_ | _actual value_ | -| **Shared value** (a reference-counted, shared [`Dynamic`] value) | | _the actual type_ | _actual value_ | -| **System integer** (current configuration) | `rhai::INT` (`i32` or `i64`) | `"i32"` or `"i64"` | `"42"`, `"123"` etc. | -| **System floating-point** (current configuration, disabled with [`no_float`]) | `rhai::FLOAT` (`f32` or `f64`) | `"f32"` or `"f64"` | `"123.456"` etc. | -| **Nothing/void/nil/null/Unit** (or whatever it is called) | `()` | `"()"` | `""` _(empty string)_ | +| Category | Equivalent Rust types | [`type_of()`] | `to_string()` | +| -------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------- | --------------------- | ----------------------- | +| **Integer number** | `u8`, `i8`, `u16`, `i16`,
`u32`, `i32` (default for [`only_i32`]),
`u64`, `i64` _(default)_ | `"i32"`, `"u64"` etc. | `"42"`, `"123"` etc. | +| **Floating-point number** (disabled with [`no_float`]) | `f32`, `f64` _(default)_ | `"f32"` or `"f64"` | `"123.4567"` etc. | +| **Boolean value** | `bool` | `"bool"` | `"true"` or `"false"` | +| **Unicode character** | `char` | `"char"` | `"A"`, `"x"` etc. | +| **Immutable Unicode [string]** | `rhai::ImmutableString` (implemented as `Rc` or `Arc`) | `"string"` | `"hello"` etc. | +| **[`Array`]** (disabled with [`no_index`]) | `rhai::Array` | `"array"` | `"[ ?, ?, ? ]"` | +| **[Object map]** (disabled with [`no_object`]) | `rhai::Map` | `"map"` | `"#{ "a": 1, "b": 2 }"` | +| **[Timestamp]** (implemented in the [`BasicTimePackage`][packages], disabled with [`no_std`]) | `std::time::Instant` ([`instant::Instant`] if [WASM] build) | `"timestamp"` | `""` | +| **[Function pointer]** | `rhai::FnPtr` | `Fn` | `"Fn(foo)"` | +| **[`Dynamic`] value** (i.e. can be anything) | `rhai::Dynamic` | _the actual type_ | _actual value_ | +| **Shared value** (a reference-counted, shared [`Dynamic`] value, created via [automatic currying], disabled with [`no_closure`]) | | _the actual type_ | _actual value_ | +| **System integer** (current configuration) | `rhai::INT` (`i32` or `i64`) | `"i32"` or `"i64"` | `"42"`, `"123"` etc. | +| **System floating-point** (current configuration, disabled with [`no_float`]) | `rhai::FLOAT` (`f32` or `f64`) | `"f32"` or `"f64"` | `"123.456"` etc. | +| **Nothing/void/nil/null/Unit** (or whatever it is called) | `()` | `"()"` | `""` _(empty string)_ | All types are treated strictly separate by Rhai, meaning that `i32` and `i64` and `u32` are completely different - they even cannot be added together. This is very similar to Rust. diff --git a/doc/src/links.md b/doc/src/links.md index 8667082c..11b16796 100644 --- a/doc/src/links.md +++ b/doc/src/links.md @@ -79,8 +79,10 @@ [function pointer]: {{rootUrl}}/language/fn-ptr.md [function pointers]: {{rootUrl}}/language/fn-ptr.md [currying]: {{rootUrl}}/language/fn-curry.md -[capture]: {{rootUrl}}/language/fn-closure.md +[capture]: {{rootUrl}}/language/fn-capture.md [automatic currying]: {{rootUrl}}/language/fn-closure.md +[closure]: {{rootUrl}}/language/fn-closure.md +[closures]: {{rootUrl}}/language/fn-closure.md [function namespace]: {{rootUrl}}/language/fn-namespaces.md [function namespaces]: {{rootUrl}}/language/fn-namespaces.md [anonymous function]: {{rootUrl}}/language/fn-anon.md diff --git a/doc/src/rust/register-raw.md b/doc/src/rust/register-raw.md index cf997286..b666d588 100644 --- a/doc/src/rust/register-raw.md +++ b/doc/src/rust/register-raw.md @@ -35,12 +35,12 @@ engine.register_raw_fn( // Therefore, get a '&mut' reference to the first argument _last_. // Alternatively, use `args.split_at_mut(1)` etc. to split the slice first. - let y: i64 = *args[1].downcast_ref::() // get a reference to the second argument + let y: i64 = *args[1].read_lock::() // get a reference to the second argument .unwrap(); // then copying it because it is a primary type let y: i64 = std::mem::take(args[1]).cast::(); // alternatively, directly 'consume' it - let x: &mut i64 = args[0].downcast_mut::() // get a '&mut' reference to the + let x: &mut i64 = args[0].write_lock::() // get a '&mut' reference to the .unwrap(); // first argument *x += y; // perform the action @@ -84,12 +84,12 @@ Extract Arguments To extract an argument from the `args` parameter (`&mut [&mut Dynamic]`), use the following: -| Argument type | Access (`n` = argument position) | Result | -| ------------------------------ | -------------------------------------- | ---------------------------------------------------------- | -| [Primary type][standard types] | `args[n].clone().cast::()` | Copy of value. | -| Custom type | `args[n].downcast_ref::().unwrap()` | Immutable reference to value. | -| Custom type (consumed) | `std::mem::take(args[n]).cast::()` | The _consumed_ value.
The original value becomes `()`. | -| `this` object | `args[0].downcast_mut::().unwrap()` | Mutable reference to value. | +| Argument type | Access (`n` = argument position) | Result | +| ------------------------------ | ------------------------------------- | ---------------------------------------------------------- | +| [Primary type][standard types] | `args[n].clone().cast::()` | Copy of value. | +| Custom type | `args[n].read_lock::().unwrap()` | Immutable reference to value. | +| Custom type (consumed) | `std::mem::take(args[n]).cast::()` | The _consumed_ value.
The original value becomes `()`. | +| `this` object | `args[0].write_lock::().unwrap()` | Mutable reference to value. | When there is a mutable reference to the `this` object (i.e. the first argument), there can be no other immutable references to `args`, otherwise the Rust borrow checker will complain. @@ -156,5 +156,5 @@ let this_ptr = first[0].downcast_mut::
().unwrap(); // Immutable reference to the second value parameter // This can be mutable but there is no point because the parameter is passed by value -let value = rest[0].downcast_ref::().unwrap(); +let value_ref = rest[0].read_lock::().unwrap(); ``` diff --git a/doc/src/start/features.md b/doc/src/start/features.md index 3b8cee04..3201aa07 100644 --- a/doc/src/start/features.md +++ b/doc/src/start/features.md @@ -23,7 +23,7 @@ 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_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_closure` | Disable [capturing][automatic currying] 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. | diff --git a/src/any.rs b/src/any.rs index bdde78f3..5efcad16 100644 --- a/src/any.rs +++ b/src/any.rs @@ -283,9 +283,9 @@ impl Dynamic { /// Get the TypeId of the value held by this `Dynamic`. /// - /// # Panics and Deadlocks When Value is Shared + /// # Panics or Deadlocks When Value is Shared /// - /// Under the `sync` feature, this call may deadlock. + /// Under the `sync` feature, this call may deadlock, or [panic](https://doc.rust-lang.org/std/sync/struct.RwLock.html#panics-1). /// Otherwise, this call panics if the data is currently borrowed for write. pub fn type_id(&self) -> TypeId { match &self.0 { @@ -313,9 +313,9 @@ impl Dynamic { /// Get the name of the type of the value held by this `Dynamic`. /// - /// # Panics and Deadlocks When Value is Shared + /// # Panics or Deadlocks When Value is Shared /// - /// Under the `sync` feature, this call may deadlock. + /// Under the `sync` feature, this call may deadlock, or [panic](https://doc.rust-lang.org/std/sync/struct.RwLock.html#panics-1). /// Otherwise, this call panics if the data is currently borrowed for write. pub fn type_name(&self) -> &'static str { match &self.0 { @@ -621,9 +621,9 @@ impl Dynamic { /// /// Returns `None` if types mismatched. /// - /// # Panics and Deadlocks + /// # Panics or Deadlocks /// - /// Under the `sync` feature, this call may deadlock. + /// Under the `sync` feature, this call may deadlock, or [panic](https://doc.rust-lang.org/std/sync/struct.RwLock.html#panics-1). /// Otherwise, this call panics if the data is currently borrowed for write. /// /// These normally shouldn't occur since most operations in Rhai is single-threaded. @@ -744,12 +744,12 @@ impl Dynamic { /// /// Returns `None` if types mismatched. /// - /// # Panics and Deadlocks + /// # Panics or Deadlocks /// /// Panics if the cast fails (e.g. the type of the actual value is not the /// same as the specified type). /// - /// Under the `sync` feature, this call may deadlock. + /// Under the `sync` feature, this call may deadlock, or [panic](https://doc.rust-lang.org/std/sync/struct.RwLock.html#panics-1). /// Otherwise, this call panics if the data is currently borrowed for write. /// /// These normally shouldn't occur since most operations in Rhai is single-threaded. @@ -817,9 +817,9 @@ impl Dynamic { /// /// Returns `None` if the cast fails. /// - /// # Panics and Deadlocks When Value is Shared + /// # Panics or Deadlocks When Value is Shared /// - /// Under the `sync` feature, this call may deadlock. + /// Under the `sync` feature, this call may deadlock, or [panic](https://doc.rust-lang.org/std/sync/struct.RwLock.html#panics-1). /// Otherwise, this call panics if the data is currently borrowed for write. #[inline(always)] pub fn read_lock(&self) -> Option> { @@ -852,9 +852,9 @@ impl Dynamic { /// /// Returns `None` if the cast fails. /// - /// # Panics and Deadlocks When Value is Shared + /// # Panics or Deadlocks When Value is Shared /// - /// Under the `sync` feature, this call may deadlock. + /// Under the `sync` feature, this call may deadlock, or [panic](https://doc.rust-lang.org/std/sync/struct.RwLock.html#panics-1). /// Otherwise, this call panics if the data is currently borrowed for write. #[inline(always)] pub fn write_lock(&mut self) -> Option> {