diff --git a/src/engine.rs b/src/engine.rs index 5c47a3cf..9990388c 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -37,6 +37,7 @@ use crate::stdlib::{ collections::{HashMap, HashSet}, fmt, format, iter::{empty, once}, + num::NonZeroUsize, ops::DerefMut, string::{String, ToString}, vec::Vec, @@ -250,8 +251,8 @@ impl<'a> Target<'a> { Self::LockGuard(_) => (), #[cfg(not(feature = "no_index"))] Self::StringChar(_, _, ch) => { - let new_val = ch.clone(); - self.set_value((new_val, Position::none()), Position::none()) + let char_value = ch.clone(); + self.set_value((char_value, Position::none()), Position::none()) .unwrap(); } } @@ -284,10 +285,9 @@ impl<'a> Target<'a> { })?; let mut chars = s.chars().collect::>(); - let ch = chars[*index]; // See if changed - if so, update the String - if ch != new_ch { + if chars[*index] != new_ch { chars[*index] = new_ch; *s = chars.iter().collect::().into(); } @@ -520,13 +520,13 @@ pub fn search_imports<'s>( // Qualified - check if the root module is directly indexed let index = if state.always_search { - None + 0 } else { - modules.index() + modules.index().map_or(0, NonZeroUsize::get) }; - Ok(if let Some(index) = index { - let offset = mods.len() - index.get(); + Ok(if index > 0 { + let offset = mods.len() - index; &mods.get(offset).unwrap().1 } else { mods.iter() @@ -548,13 +548,13 @@ pub fn search_imports_mut<'s>( // Qualified - check if the root module is directly indexed let index = if state.always_search { - None + 0 } else { - modules.index() + modules.index().map_or(0, NonZeroUsize::get) }; - Ok(if let Some(index) = index { - let offset = mods.len() - index.get(); + Ok(if index > 0 { + let offset = mods.len() - index; &mut mods.get_mut(offset).unwrap().1 } else { mods.iter_mut() @@ -684,19 +684,15 @@ impl Engine { // Qualified variable ((name, pos), Some(modules), hash_var, _) => { let module = search_imports_mut(mods, state, modules)?; - let target = - module - .get_qualified_var_mut(*hash_var) - .map_err(|err| match *err { - EvalAltResult::ErrorVariableNotFound(_, _) => { - EvalAltResult::ErrorVariableNotFound( - format!("{}{}", modules, name), - *pos, - ) - .into() - } - _ => err.fill_position(*pos), - })?; + let target = module.get_qualified_var_mut(*hash_var).map_err(|mut err| { + match *err { + EvalAltResult::ErrorVariableNotFound(ref mut err_name, _) => { + *err_name = format!("{}{}", modules, name); + } + _ => (), + } + err.fill_position(*pos) + })?; // Module variables are constant Ok((target.into(), name, ScopeEntryType::Constant, *pos)) @@ -733,7 +729,11 @@ impl Engine { } // Check if it is directly indexed - let index = if state.always_search { None } else { *index }; + let index = if state.always_search { + 0 + } else { + index.map_or(0, NonZeroUsize::get) + }; // Check the variable resolver, if any if let Some(ref resolve_var) = self.resolve_var { @@ -745,15 +745,15 @@ impl Engine { this_ptr, level: 0, }; - if let Some(result) = resolve_var(name, index.map_or(0, |v| v.get()), scope, &context) - .map_err(|err| err.fill_position(*pos))? + if let Some(result) = + resolve_var(name, index, scope, &context).map_err(|err| err.fill_position(*pos))? { return Ok((result.into(), name, ScopeEntryType::Constant, *pos)); } } - let index = if let Some(index) = index { - scope.len() - index.get() + let index = if index > 0 { + scope.len() - index } else { // Find the variable in the scope scope diff --git a/src/fn_call.rs b/src/fn_call.rs index aa35b527..62f90c3b 100644 --- a/src/fn_call.rs +++ b/src/fn_call.rs @@ -50,10 +50,6 @@ use crate::stdlib::{ vec::Vec, }; -#[cfg(not(feature = "no_closure"))] -#[cfg(not(feature = "no_function"))] -use crate::stdlib::{collections::HashSet, string::String}; - #[cfg(feature = "no_std")] #[cfg(not(feature = "no_float"))] use num_traits::float::Float; @@ -146,29 +142,6 @@ impl Drop for ArgBackup<'_> { } } -// Add captured variables into scope -#[cfg(not(feature = "no_closure"))] -#[cfg(not(feature = "no_function"))] -fn add_captured_variables_into_scope<'s>( - externals: &HashSet, - captured: Scope<'s>, - scope: &mut Scope<'s>, -) { - captured - .into_iter() - .filter(|ScopeEntry { name, .. }| externals.contains(name.as_ref())) - .for_each( - |ScopeEntry { - name, typ, value, .. - }| { - match typ { - ScopeEntryType::Normal => scope.push(name, value), - ScopeEntryType::Constant => scope.push_constant(name, value), - }; - }, - ); -} - #[inline(always)] pub fn ensure_no_data_race( fn_name: &str, @@ -483,6 +456,8 @@ impl Engine { /// Perform an actual function call, native Rust or scripted, taking care of special functions. /// Position in `EvalAltResult` is `None` and must be set afterwards. /// + /// Capture `Scope` is consumed by this function. + /// /// ## WARNING /// /// Function call arguments may be _consumed_ when the function requires them to be passed by value. @@ -498,7 +473,7 @@ impl Engine { is_ref: bool, _is_method: bool, pub_only: bool, - _capture: Option, + _capture: Option<&mut Scope>, // `Scope` is consumed. def_val: &Option, _level: usize, ) -> Result<(Dynamic, bool), Box> { @@ -574,10 +549,29 @@ impl Engine { let scope = &mut Scope::new(); let mods = &mut Imports::new(); - // Add captured variables into scope + // Move captured variables into scope #[cfg(not(feature = "no_closure"))] if let Some(captured) = _capture { - add_captured_variables_into_scope(&func.externals, captured, scope); + captured + .iter_mut() + .filter(|ScopeEntry { name, .. }| { + func.externals.contains(name.as_ref()) + }) + .for_each( + |ScopeEntry { + name, typ, value, .. + }| { + // Consume the scope values. + match typ { + ScopeEntryType::Normal => { + scope.push(name.clone(), mem::take(value)) + } + ScopeEntryType::Constant => { + scope.push_constant(name.clone(), mem::take(value)) + } + }; + }, + ); } let result = if _is_method { @@ -1019,11 +1013,13 @@ impl Engine { let mut arg_values: StaticVec<_>; let mut args: StaticVec<_>; let mut is_ref = false; - let capture = if cfg!(not(feature = "no_closure")) && capture && !scope.is_empty() { - Some(scope.flatten_clone()) + let mut capture_scope = if cfg!(not(feature = "no_closure")) && capture && !scope.is_empty() + { + Some(scope.clone_visible()) } else { None }; + let capture = capture_scope.as_mut(); if args_expr.is_empty() && curry.is_empty() { // No arguments @@ -1189,20 +1185,12 @@ impl Engine { let args = args.as_mut(); let func = f.get_fn_def(); - let scope = &mut Scope::new(); + let new_scope = &mut Scope::new(); let mods = &mut Imports::new(); - // Add captured variables into scope - #[cfg(not(feature = "no_closure"))] - 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) + self.call_script_fn( + new_scope, mods, state, lib, &mut None, name, func, args, level, + ) } Some(f) if f.is_plugin_fn() => f.get_plugin_fn().call(args.as_mut()), Some(f) if f.is_native() => { diff --git a/src/scope.rs b/src/scope.rs index ad021f7e..43ebdc65 100644 --- a/src/scope.rs +++ b/src/scope.rs @@ -420,7 +420,7 @@ impl<'a> Scope<'a> { /// Clone the Scope, keeping only the last instances of each variable name. /// Shadowed variables are omitted in the copy. #[inline] - pub(crate) fn flatten_clone(&self) -> Self { + pub(crate) fn clone_visible(&self) -> Self { let mut entries: Vec = Default::default(); self.0.iter().rev().for_each(|entry| { @@ -488,6 +488,12 @@ impl<'a> Scope<'a> { }| { (name.as_ref(), typ.is_constant(), value) }, ) } + + /// Get a mutable iterator to entries in the Scope. + #[inline(always)] + pub(crate) fn iter_mut(&mut self) -> impl Iterator> { + self.0.iter_mut() + } } impl<'a, K: Into>> iter::Extend<(K, EntryType, Dynamic)> for Scope<'a> {