diff --git a/src/api/call_fn.rs b/src/api/call_fn.rs index 32510f60..2057d9b7 100644 --- a/src/api/call_fn.rs +++ b/src/api/call_fn.rs @@ -3,6 +3,7 @@ use crate::eval::{Caches, GlobalRuntimeState}; use crate::types::dynamic::Variant; +use crate::types::RestoreOnDrop; use crate::{ reify, Dynamic, Engine, FuncArgs, Position, RhaiResult, RhaiResultOf, Scope, Shared, StaticVec, AST, ERR, @@ -258,6 +259,10 @@ impl Engine { &mut global.embedded_module_resolver, ast.resolver().cloned(), ); + #[cfg(not(feature = "no_module"))] + let global = &mut *RestoreOnDrop::new(global, move |g| { + g.embedded_module_resolver = orig_embedded_module_resolver + }); let result = if eval_ast && !statements.is_empty() { let r = self.eval_global_statements(global, caches, lib, 0, scope, statements); @@ -293,14 +298,7 @@ impl Engine { } else { Err(ERR::ErrorFunctionNotFound(name.into(), Position::NONE).into()) } - }); - - #[cfg(not(feature = "no_module"))] - { - global.embedded_module_resolver = orig_embedded_module_resolver; - } - - let result = result?; + })?; #[cfg(feature = "debugging")] if self.debugger.is_some() { diff --git a/src/api/eval.rs b/src/api/eval.rs index 02f21831..294bf89a 100644 --- a/src/api/eval.rs +++ b/src/api/eval.rs @@ -3,6 +3,7 @@ use crate::eval::{Caches, GlobalRuntimeState}; use crate::parser::ParseState; use crate::types::dynamic::Variant; +use crate::types::RestoreOnDrop; use crate::{ Dynamic, Engine, OptimizationLevel, Position, RhaiResult, RhaiResultOf, Scope, AST, ERR, }; @@ -225,6 +226,10 @@ impl Engine { &mut global.embedded_module_resolver, ast.resolver().cloned(), ); + #[cfg(not(feature = "no_module"))] + let global = &mut *RestoreOnDrop::new(global, move |g| { + g.embedded_module_resolver = orig_embedded_module_resolver + }); let statements = ast.statements(); @@ -232,23 +237,12 @@ impl Engine { return Ok(Dynamic::UNIT); } - let mut _lib = &[ + let lib = &[ #[cfg(not(feature = "no_function"))] AsRef::>::as_ref(ast).clone(), - ][..]; - #[cfg(not(feature = "no_function"))] - if !ast.has_functions() { - _lib = &[]; - } + ]; - let result = self.eval_global_statements(global, caches, _lib, level, scope, statements); - - #[cfg(not(feature = "no_module"))] - { - global.embedded_module_resolver = orig_embedded_module_resolver; - } - - result + self.eval_global_statements(global, caches, lib, level, scope, statements) } /// _(internals)_ Evaluate a list of statements with no `this` pointer. /// Exported under the `internals` feature only. diff --git a/src/eval/chaining.rs b/src/eval/chaining.rs index cf51a465..7d7560fc 100644 --- a/src/eval/chaining.rs +++ b/src/eval/chaining.rs @@ -4,6 +4,7 @@ use super::{Caches, GlobalRuntimeState, Target}; use crate::ast::{ASTFlags, Expr, OpAssignment}; use crate::types::dynamic::Union; +use crate::types::RestoreOnDrop; use crate::{ Dynamic, Engine, FnArgsVec, Module, Position, RhaiResult, RhaiResultOf, Scope, Shared, ERR, }; @@ -200,29 +201,30 @@ impl Engine { // xxx.fn_name(arg_expr_list) Expr::MethodCall(x, pos) if !x.is_qualified() && new_val.is_none() => { #[cfg(feature = "debugging")] - let reset_debugger = self.run_debugger_with_reset( + let reset = self.run_debugger_with_reset( global, caches, lib, level, scope, this_ptr, rhs, )?; + #[cfg(feature = "debugging")] + let global = &mut *RestoreOnDrop::new(global, move |g| { + g.debugger.reset_status(reset) + }); let crate::ast::FnCallExpr { name, hashes, args, .. } = &**x; + // Truncate the index values upon exit let offset = idx_values.len() - args.len(); + let idx_values = + &mut *RestoreOnDrop::new(idx_values, move |v| v.truncate(offset)); + let call_args = &mut idx_values[offset..]; let pos1 = args.get(0).map_or(Position::NONE, Expr::position); - let result = self.make_method_call( + self.make_method_call( global, caches, lib, level, name, *hashes, target, call_args, pos1, *pos, - ); - - idx_values.truncate(offset); - - #[cfg(feature = "debugging")] - global.debugger.reset_status(reset_debugger); - - result + ) } // xxx.fn_name(...) = ??? Expr::MethodCall(..) if new_val.is_some() => { @@ -378,29 +380,33 @@ impl Engine { // {xxx:map}.fn_name(arg_expr_list)[expr] | {xxx:map}.fn_name(arg_expr_list).expr Expr::MethodCall(ref x, pos) if !x.is_qualified() => { #[cfg(feature = "debugging")] - let reset_debugger = self.run_debugger_with_reset( + let reset = self.run_debugger_with_reset( global, caches, lib, level, scope, this_ptr, _node, )?; + #[cfg(feature = "debugging")] + let global = &mut *RestoreOnDrop::new(global, move |g| { + g.debugger.reset_status(reset) + }); let crate::ast::FnCallExpr { name, hashes, args, .. } = &**x; + // Truncate the index values upon exit let offset = idx_values.len() - args.len(); + let idx_values = &mut *RestoreOnDrop::new(idx_values, move |v| { + v.truncate(offset) + }); + let call_args = &mut idx_values[offset..]; let pos1 = args.get(0).map_or(Position::NONE, Expr::position); - let result = self.make_method_call( + self.make_method_call( global, caches, lib, level, name, *hashes, target, call_args, pos1, pos, - ); - - idx_values.truncate(offset); - - #[cfg(feature = "debugging")] - global.debugger.reset_status(reset_debugger); - - result?.0.into() + )? + .0 + .into() } // {xxx:map}.module::fn_name(...) - syntax error Expr::MethodCall(..) => unreachable!( @@ -504,32 +510,39 @@ impl Engine { } // xxx.fn_name(arg_expr_list)[expr] | xxx.fn_name(arg_expr_list).expr Expr::MethodCall(ref f, pos) if !f.is_qualified() => { - #[cfg(feature = "debugging")] - let reset_debugger = self.run_debugger_with_reset( - global, caches, lib, level, scope, this_ptr, _node, - )?; + let val = { + #[cfg(feature = "debugging")] + let reset = self.run_debugger_with_reset( + global, caches, lib, level, scope, this_ptr, _node, + )?; + #[cfg(feature = "debugging")] + let global = &mut *RestoreOnDrop::new(global, move |g| { + g.debugger.reset_status(reset) + }); - let crate::ast::FnCallExpr { - name, hashes, args, .. - } = &**f; - let rhs_chain = rhs.into(); + let crate::ast::FnCallExpr { + name, hashes, args, .. + } = &**f; - let offset = idx_values.len() - args.len(); - let call_args = &mut idx_values[offset..]; - let pos1 = args.get(0).map_or(Position::NONE, Expr::position); + // Truncate the index values upon exit + let offset = idx_values.len() - args.len(); + let idx_values = + &mut *RestoreOnDrop::new(idx_values, move |v| { + v.truncate(offset) + }); - let result = self.make_method_call( - global, caches, lib, level, name, *hashes, target, call_args, - pos1, pos, - ); + let call_args = &mut idx_values[offset..]; + let pos1 = args.get(0).map_or(Position::NONE, Expr::position); - idx_values.truncate(offset); + self.make_method_call( + global, caches, lib, level, name, *hashes, target, + call_args, pos1, pos, + )? + .0 + }; - #[cfg(feature = "debugging")] - global.debugger.reset_status(reset_debugger); - - let (val, _) = &mut result?; let val = &mut val.into(); + let rhs_chain = rhs.into(); self.eval_dot_index_chain_helper( global, caches, lib, level, this_ptr, val, root, rhs, *options, diff --git a/src/eval/expr.rs b/src/eval/expr.rs index afa47ce4..269ab992 100644 --- a/src/eval/expr.rs +++ b/src/eval/expr.rs @@ -4,6 +4,7 @@ use super::{Caches, EvalContext, GlobalRuntimeState, Target}; use crate::ast::{Expr, OpAssignment}; use crate::engine::{KEYWORD_THIS, OP_CONCAT}; use crate::types::dynamic::AccessMode; +use crate::types::RestoreOnDrop; use crate::{Dynamic, Engine, Module, Position, RhaiResult, RhaiResultOf, Scope, Shared, ERR}; use std::num::NonZeroUsize; #[cfg(feature = "no_std")] @@ -234,18 +235,14 @@ impl Engine { // binary operators are also function calls. if let Expr::FnCall(x, pos) = expr { #[cfg(feature = "debugging")] - let reset_debugger = + let reset = self.run_debugger_with_reset(global, caches, lib, level, scope, this_ptr, expr)?; + #[cfg(feature = "debugging")] + let global = &mut *RestoreOnDrop::new(global, move |g| g.debugger.reset_status(reset)); self.track_operation(global, expr.position())?; - let result = - self.eval_fn_call_expr(global, caches, lib, level, scope, this_ptr, x, *pos); - - #[cfg(feature = "debugging")] - global.debugger.reset_status(reset_debugger); - - return result; + return self.eval_fn_call_expr(global, caches, lib, level, scope, this_ptr, x, *pos); } // Then variable access. @@ -269,12 +266,14 @@ impl Engine { } #[cfg(feature = "debugging")] - let reset_debugger = + let reset = self.run_debugger_with_reset(global, caches, lib, level, scope, this_ptr, expr)?; + #[cfg(feature = "debugging")] + let global = &mut *RestoreOnDrop::new(global, move |g| g.debugger.reset_status(reset)); self.track_operation(global, expr.position())?; - let result = match expr { + match expr { // Constants Expr::DynamicConstant(x, ..) => Ok(x.as_ref().clone()), Expr::IntegerConstant(x, ..) => Ok((*x).into()), @@ -374,60 +373,33 @@ impl Engine { .map(Into::into) } - Expr::And(x, ..) => { - let lhs = self - .eval_expr(global, caches, lib, level, scope, this_ptr, &x.lhs) - .and_then(|v| { - v.as_bool().map_err(|typ| { - self.make_type_mismatch_err::(typ, x.lhs.position()) - }) - }); + Expr::And(x, ..) => Ok((self + .eval_expr(global, caches, lib, level, scope, this_ptr, &x.lhs)? + .as_bool() + .map_err(|typ| self.make_type_mismatch_err::(typ, x.lhs.position()))? + && self + .eval_expr(global, caches, lib, level, scope, this_ptr, &x.rhs)? + .as_bool() + .map_err(|typ| self.make_type_mismatch_err::(typ, x.rhs.position()))?) + .into()), - match lhs { - Ok(true) => self - .eval_expr(global, caches, lib, level, scope, this_ptr, &x.rhs) - .and_then(|v| { - v.as_bool() - .map_err(|typ| { - self.make_type_mismatch_err::(typ, x.rhs.position()) - }) - .map(Into::into) - }), - _ => lhs.map(Into::into), - } - } - - Expr::Or(x, ..) => { - let lhs = self - .eval_expr(global, caches, lib, level, scope, this_ptr, &x.lhs) - .and_then(|v| { - v.as_bool().map_err(|typ| { - self.make_type_mismatch_err::(typ, x.lhs.position()) - }) - }); - - match lhs { - Ok(false) => self - .eval_expr(global, caches, lib, level, scope, this_ptr, &x.rhs) - .and_then(|v| { - v.as_bool() - .map_err(|typ| { - self.make_type_mismatch_err::(typ, x.rhs.position()) - }) - .map(Into::into) - }), - _ => lhs.map(Into::into), - } - } + Expr::Or(x, ..) => Ok((self + .eval_expr(global, caches, lib, level, scope, this_ptr, &x.lhs)? + .as_bool() + .map_err(|typ| self.make_type_mismatch_err::(typ, x.lhs.position()))? + || self + .eval_expr(global, caches, lib, level, scope, this_ptr, &x.rhs)? + .as_bool() + .map_err(|typ| self.make_type_mismatch_err::(typ, x.rhs.position()))?) + .into()), Expr::Coalesce(x, ..) => { - let lhs = self.eval_expr(global, caches, lib, level, scope, this_ptr, &x.lhs); + let value = self.eval_expr(global, caches, lib, level, scope, this_ptr, &x.lhs)?; - match lhs { - Ok(value) if value.is::<()>() => { - self.eval_expr(global, caches, lib, level, scope, this_ptr, &x.rhs) - } - _ => lhs, + if value.is::<()>() { + self.eval_expr(global, caches, lib, level, scope, this_ptr, &x.rhs) + } else { + Ok(value) } } @@ -467,11 +439,6 @@ impl Engine { .eval_dot_index_chain(global, caches, lib, level, scope, this_ptr, expr, &mut None), _ => unreachable!("expression cannot be evaluated: {:?}", expr), - }; - - #[cfg(feature = "debugging")] - global.debugger.reset_status(reset_debugger); - - result + } } } diff --git a/src/eval/stmt.rs b/src/eval/stmt.rs index 9ccde0a0..2841ecba 100644 --- a/src/eval/stmt.rs +++ b/src/eval/stmt.rs @@ -7,6 +7,7 @@ use crate::ast::{ }; use crate::func::{get_builtin_op_assignment_fn, get_hasher}; use crate::types::dynamic::{AccessMode, Union}; +use crate::types::RestoreOnDrop; use crate::{ Dynamic, Engine, ImmutableString, Module, Position, RhaiResult, RhaiResultOf, Scope, Shared, ERR, INT, @@ -39,17 +40,39 @@ impl Engine { return Ok(Dynamic::UNIT); } - let orig_always_search_scope = global.always_search_scope; + // Restore scope at end of block if necessary let orig_scope_len = scope.len(); + let scope = &mut *RestoreOnDrop::new_if(restore_orig_state, scope, move |s| { + s.rewind(orig_scope_len); + }); + + // Restore global state at end of block if necessary + let orig_always_search_scope = global.always_search_scope; #[cfg(not(feature = "no_module"))] let orig_imports_len = global.num_imports(); - let orig_fn_resolution_caches_len = caches.fn_resolution_caches_len(); if restore_orig_state { global.scope_level += 1; } - let result = statements.iter().try_fold(Dynamic::UNIT, |_, stmt| { + let global = &mut *RestoreOnDrop::new_if(restore_orig_state, global, move |g| { + g.scope_level -= 1; + #[cfg(not(feature = "no_module"))] + g.truncate_imports(orig_imports_len); + + // The impact of new local variables goes away at the end of a block + // because any new variables introduced will go out of scope + g.always_search_scope = orig_always_search_scope; + }); + + // Pop new function resolution caches at end of block + let orig_fn_resolution_caches_len = caches.fn_resolution_caches_len(); + let caches = &mut *RestoreOnDrop::new(caches, move |c| { + c.rewind_fn_resolution_caches(orig_fn_resolution_caches_len) + }); + + // Run the statements + statements.iter().try_fold(Dynamic::UNIT, |_, stmt| { #[cfg(not(feature = "no_module"))] let imports_len = global.num_imports(); @@ -89,23 +112,7 @@ impl Engine { } Ok(result) - }); - - // If imports list is modified, pop the functions lookup cache - caches.rewind_fn_resolution_caches(orig_fn_resolution_caches_len); - - if restore_orig_state { - scope.rewind(orig_scope_len); - global.scope_level -= 1; - #[cfg(not(feature = "no_module"))] - global.truncate_imports(orig_imports_len); - - // The impact of new local variables goes away at the end of a block - // because any new variables introduced will go out of scope - global.always_search_scope = orig_always_search_scope; - } - - result + }) } /// Evaluate an op-assignment statement. @@ -205,8 +212,10 @@ impl Engine { rewind_scope: bool, ) -> RhaiResult { #[cfg(feature = "debugging")] - let reset_debugger = + let reset = self.run_debugger_with_reset(global, caches, lib, level, scope, this_ptr, stmt)?; + #[cfg(feature = "debugging")] + let global = &mut *RestoreOnDrop::new(global, move |g| g.debugger.reset_status(reset)); // Coded this way for better branch prediction. // Popular branches are lifted out of the `match` statement into their own branches. @@ -215,13 +224,7 @@ impl Engine { if let Stmt::FnCall(x, pos) = stmt { self.track_operation(global, stmt.position())?; - let result = - self.eval_fn_call_expr(global, caches, lib, level, scope, this_ptr, x, *pos); - - #[cfg(feature = "debugging")] - global.debugger.reset_status(reset_debugger); - - return result; + return self.eval_fn_call_expr(global, caches, lib, level, scope, this_ptr, x, *pos); } // Then assignments. @@ -232,108 +235,85 @@ impl Engine { self.track_operation(global, stmt.position())?; - let result = if let Expr::Variable(x, ..) = lhs { - let rhs_result = self - .eval_expr(global, caches, lib, level, scope, this_ptr, rhs) - .map(Dynamic::flatten); + if let Expr::Variable(x, ..) = lhs { + let rhs_val = self + .eval_expr(global, caches, lib, level, scope, this_ptr, rhs)? + .flatten(); - if let Ok(rhs_val) = rhs_result { - let search_result = - self.search_namespace(global, caches, lib, level, scope, this_ptr, lhs); + let search_val = + self.search_namespace(global, caches, lib, level, scope, this_ptr, lhs)?; - if let Ok(search_val) = search_result { - let (mut lhs_ptr, pos) = search_val; + let (mut lhs_ptr, pos) = search_val; - let var_name = x.3.as_str(); + let var_name = x.3.as_str(); - #[cfg(not(feature = "no_closure"))] - // Also handle case where target is a `Dynamic` shared value - // (returned by a variable resolver, for example) - let is_temp_result = !lhs_ptr.is_ref() && !lhs_ptr.is_shared(); - #[cfg(feature = "no_closure")] - let is_temp_result = !lhs_ptr.is_ref(); + #[cfg(not(feature = "no_closure"))] + // Also handle case where target is a `Dynamic` shared value + // (returned by a variable resolver, for example) + let is_temp_result = !lhs_ptr.is_ref() && !lhs_ptr.is_shared(); + #[cfg(feature = "no_closure")] + let is_temp_result = !lhs_ptr.is_ref(); - // Cannot assign to temp result from expression - if is_temp_result { - return Err( - ERR::ErrorAssignmentToConstant(var_name.to_string(), pos).into() - ); - } - - self.track_operation(global, pos)?; - - let root = (var_name, pos); - let lhs_ptr = &mut lhs_ptr; - - self.eval_op_assignment( - global, caches, lib, level, op_info, lhs_ptr, root, rhs_val, - ) - .map(|_| Dynamic::UNIT) - } else { - search_result.map(|_| Dynamic::UNIT) - } - } else { - rhs_result + // Cannot assign to temp result from expression + if is_temp_result { + return Err(ERR::ErrorAssignmentToConstant(var_name.to_string(), pos).into()); } + + self.track_operation(global, pos)?; + + let root = (var_name, pos); + let lhs_ptr = &mut lhs_ptr; + + return self + .eval_op_assignment(global, caches, lib, level, op_info, lhs_ptr, root, rhs_val) + .map(|_| Dynamic::UNIT); } else { let (op_info, BinaryExpr { lhs, rhs }) = &**x; - let rhs_result = self.eval_expr(global, caches, lib, level, scope, this_ptr, rhs); + let rhs_val = self.eval_expr(global, caches, lib, level, scope, this_ptr, rhs)?; - if let Ok(rhs_val) = rhs_result { - // Check if the result is a string. If so, intern it. - #[cfg(not(feature = "no_closure"))] - let is_string = !rhs_val.is_shared() && rhs_val.is::(); - #[cfg(feature = "no_closure")] - let is_string = rhs_val.is::(); + // Check if the result is a string. If so, intern it. + #[cfg(not(feature = "no_closure"))] + let is_string = !rhs_val.is_shared() && rhs_val.is::(); + #[cfg(feature = "no_closure")] + let is_string = rhs_val.is::(); - let rhs_val = if is_string { - self.get_interned_string( - rhs_val.into_immutable_string().expect("`ImmutableString`"), - ) - .into() - } else { - rhs_val.flatten() - }; - - let _new_val = &mut Some((rhs_val, op_info)); - - // Must be either `var[index] op= val` or `var.prop op= val` - match lhs { - // name op= rhs (handled above) - Expr::Variable(..) => { - unreachable!("Expr::Variable case is already handled") - } - // idx_lhs[idx_expr] op= rhs - #[cfg(not(feature = "no_index"))] - Expr::Index(..) => self - .eval_dot_index_chain( - global, caches, lib, level, scope, this_ptr, lhs, _new_val, - ) - .map(|_| Dynamic::UNIT), - // dot_lhs.dot_rhs op= rhs - #[cfg(not(feature = "no_object"))] - Expr::Dot(..) => self - .eval_dot_index_chain( - global, caches, lib, level, scope, this_ptr, lhs, _new_val, - ) - .map(|_| Dynamic::UNIT), - _ => unreachable!("cannot assign to expression: {:?}", lhs), - } + let rhs_val = if is_string { + self.get_interned_string( + rhs_val.into_immutable_string().expect("`ImmutableString`"), + ) + .into() } else { - rhs_result + rhs_val.flatten() + }; + + let _new_val = &mut Some((rhs_val, op_info)); + + // Must be either `var[index] op= val` or `var.prop op= val` + return match lhs { + // name op= rhs (handled above) + Expr::Variable(..) => { + unreachable!("Expr::Variable case is already handled") + } + // idx_lhs[idx_expr] op= rhs + #[cfg(not(feature = "no_index"))] + Expr::Index(..) => self.eval_dot_index_chain( + global, caches, lib, level, scope, this_ptr, lhs, _new_val, + ), + // dot_lhs.dot_rhs op= rhs + #[cfg(not(feature = "no_object"))] + Expr::Dot(..) => self.eval_dot_index_chain( + global, caches, lib, level, scope, this_ptr, lhs, _new_val, + ), + _ => unreachable!("cannot assign to expression: {:?}", lhs), } - }; - - #[cfg(feature = "debugging")] - global.debugger.reset_status(reset_debugger); - - return result; + .map(|_| Dynamic::UNIT); + } } self.track_operation(global, stmt.position())?; - let result = match stmt { + match stmt { // No-op Stmt::Noop(..) => Ok(Dynamic::UNIT), @@ -385,100 +365,69 @@ impl Engine { }, ) = &**x; - let value_result = - self.eval_expr(global, caches, lib, level, scope, this_ptr, expr); + let mut result = None; - if let Ok(value) = value_result { - let expr_result = if value.is_hashable() { - let hasher = &mut get_hasher(); - value.hash(hasher); - let hash = hasher.finish(); + let value = self.eval_expr(global, caches, lib, level, scope, this_ptr, expr)?; - // First check hashes - if let Some(case_blocks_list) = cases.get(&hash) { - assert!(!case_blocks_list.is_empty()); + if value.is_hashable() { + let hasher = &mut get_hasher(); + value.hash(hasher); + let hash = hasher.finish(); - let mut result = Ok(None); + // First check hashes + if let Some(case_blocks_list) = cases.get(&hash) { + assert!(!case_blocks_list.is_empty()); - for &index in case_blocks_list { - let block = &expressions[index]; + for &index in case_blocks_list { + let block = &expressions[index]; - let cond_result = match block.condition { - Expr::BoolConstant(b, ..) => Ok(b), - ref c => self - .eval_expr(global, caches, lib, level, scope, this_ptr, c) - .and_then(|v| { - v.as_bool().map_err(|typ| { - self.make_type_mismatch_err::( - typ, - c.position(), - ) - }) - }), - }; + let cond_result = match block.condition { + Expr::BoolConstant(b, ..) => b, + ref c => self + .eval_expr(global, caches, lib, level, scope, this_ptr, c)? + .as_bool() + .map_err(|typ| { + self.make_type_mismatch_err::(typ, c.position()) + })?, + }; - match cond_result { - Ok(true) => result = Ok(Some(&block.expr)), - Ok(false) => continue, - _ => result = cond_result.map(|_| None), - } + if cond_result { + result = Some(&block.expr); break; } - - result - } else if value.is::() && !ranges.is_empty() { - // Then check integer ranges - let value = value.as_int().expect("`INT`"); - let mut result = Ok(None); - - for r in ranges.iter().filter(|r| r.contains(value)) { - let block = &expressions[r.index()]; - - let cond_result = match block.condition { - Expr::BoolConstant(b, ..) => Ok(b), - ref c => self - .eval_expr(global, caches, lib, level, scope, this_ptr, c) - .and_then(|v| { - v.as_bool().map_err(|typ| { - self.make_type_mismatch_err::( - typ, - c.position(), - ) - }) - }), - }; - - match cond_result { - Ok(true) => result = Ok(Some(&block.expr)), - Ok(false) => continue, - _ => result = cond_result.map(|_| None), - } - break; - } - - result - } else { - // Nothing matches - Ok(None) } - } else { - // Non-hashable - Ok(None) - }; + } else if value.is::() && !ranges.is_empty() { + // Then check integer ranges + let value = value.as_int().expect("`INT`"); - if let Ok(Some(expr)) = expr_result { - self.eval_expr(global, caches, lib, level, scope, this_ptr, expr) - } else if let Ok(None) = expr_result { - // Default match clause - def_case.as_ref().map_or(Ok(Dynamic::UNIT), |&index| { - let def_expr = &expressions[index].expr; - self.eval_expr(global, caches, lib, level, scope, this_ptr, def_expr) - }) - } else { - expr_result.map(|_| Dynamic::UNIT) + for r in ranges.iter().filter(|r| r.contains(value)) { + let block = &expressions[r.index()]; + + let cond_result = match block.condition { + Expr::BoolConstant(b, ..) => b, + ref c => self + .eval_expr(global, caches, lib, level, scope, this_ptr, c)? + .as_bool() + .map_err(|typ| { + self.make_type_mismatch_err::(typ, c.position()) + })?, + }; + + if cond_result { + result = Some(&block.expr); + break; + } + } } + } + + if let Some(expr) = result { + self.eval_expr(global, caches, lib, level, scope, this_ptr, expr) } else { - value_result + def_case.as_ref().map_or(Ok(Dynamic::UNIT), |&index| { + let def_expr = &expressions[index].expr; + self.eval_expr(global, caches, lib, level, scope, this_ptr, def_expr) + }) } } @@ -512,29 +461,24 @@ impl Engine { loop { let condition = self - .eval_expr(global, caches, lib, level, scope, this_ptr, expr) - .and_then(|v| { - v.as_bool().map_err(|typ| { - self.make_type_mismatch_err::(typ, expr.position()) - }) - }); + .eval_expr(global, caches, lib, level, scope, this_ptr, expr)? + .as_bool() + .map_err(|typ| self.make_type_mismatch_err::(typ, expr.position()))?; - match condition { - Ok(false) => break Ok(Dynamic::UNIT), - Ok(true) if body.is_empty() => (), - Ok(true) => { - match self.eval_stmt_block( - global, caches, lib, level, scope, this_ptr, body, true, - ) { - Ok(_) => (), - Err(err) => match *err { - ERR::LoopBreak(false, ..) => (), - ERR::LoopBreak(true, value, ..) => break Ok(value), - _ => break Err(err), - }, + if !condition { + break Ok(Dynamic::UNIT); + } + + if !body.is_empty() { + if let Err(err) = self.eval_stmt_block( + global, caches, lib, level, scope, this_ptr, body, true, + ) { + match *err { + ERR::LoopBreak(false, ..) => (), + ERR::LoopBreak(true, value, ..) => break Ok(value), + _ => break Err(err), } } - err => break err.map(|_| Dynamic::UNIT), } } } @@ -546,30 +490,24 @@ impl Engine { loop { if !body.is_empty() { - match self.eval_stmt_block( + if let Err(err) = self.eval_stmt_block( global, caches, lib, level, scope, this_ptr, body, true, ) { - Ok(_) => (), - Err(err) => match *err { + match *err { ERR::LoopBreak(false, ..) => continue, ERR::LoopBreak(true, value, ..) => break Ok(value), _ => break Err(err), - }, + } } } let condition = self - .eval_expr(global, caches, lib, level, scope, this_ptr, expr) - .and_then(|v| { - v.as_bool().map_err(|typ| { - self.make_type_mismatch_err::(typ, expr.position()) - }) - }); + .eval_expr(global, caches, lib, level, scope, this_ptr, expr)? + .as_bool() + .map_err(|typ| self.make_type_mismatch_err::(typ, expr.position()))?; - match condition { - Ok(condition) if condition ^ is_while => break Ok(Dynamic::UNIT), - Ok(_) => (), - err => break err.map(|_| Dynamic::UNIT), + if condition ^ is_while { + break Ok(Dynamic::UNIT); } } } @@ -578,102 +516,99 @@ impl Engine { Stmt::For(x, ..) => { let (var_name, counter, expr, statements) = &**x; - let iter_result = self - .eval_expr(global, caches, lib, level, scope, this_ptr, expr) - .map(Dynamic::flatten); + let iter_obj = self + .eval_expr(global, caches, lib, level, scope, this_ptr, expr)? + .flatten(); - if let Ok(iter_obj) = iter_result { - let iter_type = iter_obj.type_id(); + let iter_type = iter_obj.type_id(); - // lib should only contain scripts, so technically they cannot have iterators + // lib should only contain scripts, so technically they cannot have iterators - // Search order: - // 1) Global namespace - functions registered via Engine::register_XXX - // 2) Global modules - packages - // 3) Imported modules - functions marked with global namespace - // 4) Global sub-modules - functions marked with global namespace - let func = self - .global_modules - .iter() - .find_map(|m| m.get_iter(iter_type)); + // Search order: + // 1) Global namespace - functions registered via Engine::register_XXX + // 2) Global modules - packages + // 3) Imported modules - functions marked with global namespace + // 4) Global sub-modules - functions marked with global namespace + let func = self + .global_modules + .iter() + .find_map(|m| m.get_iter(iter_type)); - #[cfg(not(feature = "no_module"))] - let func = func.or_else(|| global.get_iter(iter_type)).or_else(|| { - self.global_sub_modules - .values() - .find_map(|m| m.get_qualified_iter(iter_type)) + #[cfg(not(feature = "no_module"))] + let func = func.or_else(|| global.get_iter(iter_type)).or_else(|| { + self.global_sub_modules + .values() + .find_map(|m| m.get_qualified_iter(iter_type)) + }); + + if let Some(func) = func { + // Restore scope at end of statement + let orig_scope_len = scope.len(); + let scope = &mut *RestoreOnDrop::new(scope, move |s| { + s.rewind(orig_scope_len); }); - if let Some(func) = func { - // Add the loop variables - let orig_scope_len = scope.len(); - let counter_index = if counter.is_empty() { - usize::MAX - } else { - scope.push(counter.name.clone(), 0 as INT); - scope.len() - 1 - }; - - scope.push(var_name.name.clone(), ()); - let index = scope.len() - 1; - - let loop_result = func(iter_obj) - .enumerate() - .try_fold(Dynamic::UNIT, |_, (x, iter_value)| { - // Increment counter - if counter_index < usize::MAX { - // As the variable increments from 0, this should always work - // since any overflow will first be caught below. - let index_value = x as INT; - - #[cfg(not(feature = "unchecked"))] - if index_value > crate::MAX_USIZE_INT { - return Err(ERR::ErrorArithmetic( - format!("for-loop counter overflow: {x}"), - counter.pos, - ) - .into()); - } - - *scope.get_mut_by_index(counter_index).write_lock().unwrap() = - Dynamic::from_int(index_value); - } - - let value = match iter_value { - Ok(v) => v.flatten(), - Err(err) => return Err(err.fill_position(expr.position())), - }; - - *scope.get_mut_by_index(index).write_lock().unwrap() = value; - - self.track_operation(global, statements.position())?; - - if statements.is_empty() { - return Ok(Dynamic::UNIT); - } - - self.eval_stmt_block( - global, caches, lib, level, scope, this_ptr, statements, true, - ) - .map(|_| Dynamic::UNIT) - .or_else(|err| match *err { - ERR::LoopBreak(false, ..) => Ok(Dynamic::UNIT), - _ => Err(err), - }) - }) - .or_else(|err| match *err { - ERR::LoopBreak(true, value, ..) => Ok(value), - _ => Err(err), - }); - - scope.rewind(orig_scope_len); - - loop_result + // Add the loop variables + let counter_index = if counter.is_empty() { + usize::MAX } else { - Err(ERR::ErrorFor(expr.start_position()).into()) - } + scope.push(counter.name.clone(), 0 as INT); + scope.len() - 1 + }; + + scope.push(var_name.name.clone(), ()); + let index = scope.len() - 1; + + func(iter_obj) + .enumerate() + .try_fold(Dynamic::UNIT, |_, (x, iter_value)| { + // Increment counter + if counter_index < usize::MAX { + // As the variable increments from 0, this should always work + // since any overflow will first be caught below. + let index_value = x as INT; + + #[cfg(not(feature = "unchecked"))] + if index_value > crate::MAX_USIZE_INT { + return Err(ERR::ErrorArithmetic( + format!("for-loop counter overflow: {x}"), + counter.pos, + ) + .into()); + } + + *scope.get_mut_by_index(counter_index).write_lock().unwrap() = + Dynamic::from_int(index_value); + } + + let value = match iter_value { + Ok(v) => v.flatten(), + Err(err) => return Err(err.fill_position(expr.position())), + }; + + *scope.get_mut_by_index(index).write_lock().unwrap() = value; + + self.track_operation(global, statements.position())?; + + if statements.is_empty() { + return Ok(Dynamic::UNIT); + } + + self.eval_stmt_block( + global, caches, lib, level, scope, this_ptr, statements, true, + ) + .map(|_| Dynamic::UNIT) + .or_else(|err| match *err { + ERR::LoopBreak(false, ..) => Ok(Dynamic::UNIT), + _ => Err(err), + }) + }) + .or_else(|err| match *err { + ERR::LoopBreak(true, value, ..) => Ok(value), + _ => Err(err), + }) } else { - iter_result + Err(ERR::ErrorFor(expr.start_position()).into()) } } @@ -700,12 +635,12 @@ impl Engine { catch_block, } = &**x; - let result = self + match self .eval_stmt_block(global, caches, lib, level, scope, this_ptr, try_block, true) - .map(|_| Dynamic::UNIT); - - match result { - Ok(_) => result, + { + Ok(_) => self.eval_stmt_block( + global, caches, lib, level, scope, this_ptr, try_block, true, + ), Err(err) if err.is_pseudo_error() => Err(err), Err(err) if !err.is_catchable() => Err(err), Err(mut err) => { @@ -744,13 +679,18 @@ impl Engine { } }; + // Restore scope at end of block let orig_scope_len = scope.len(); + let scope = + &mut *RestoreOnDrop::new_if(!catch_var.is_empty(), scope, move |s| { + s.rewind(orig_scope_len); + }); if !catch_var.is_empty() { scope.push(catch_var.clone(), err_value); } - let result = self.eval_stmt_block( + self.eval_stmt_block( global, caches, lib, @@ -759,21 +699,16 @@ impl Engine { this_ptr, catch_block, true, - ); - - scope.rewind(orig_scope_len); - - match result { - Ok(_) => Ok(Dynamic::UNIT), - Err(result_err) => match *result_err { - // Re-throw exception - ERR::ErrorRuntime(Dynamic(Union::Unit(..)), pos) => { - err.set_position(pos); - Err(err) - } - _ => Err(result_err), - }, - } + ) + .map(|_| Dynamic::UNIT) + .map_err(|result_err| match *result_err { + // Re-throw exception + ERR::ErrorRuntime(Dynamic(Union::Unit(..)), pos) => { + err.set_position(pos); + err + } + _ => result_err, + }) } } } @@ -812,7 +747,7 @@ impl Engine { let export = options.contains(ASTFlags::EXPORTED); // Check variable definition filter - let result = if let Some(ref filter) = self.def_var_filter { + if let Some(ref filter) = self.def_var_filter { let will_shadow = scope.contains(var_name); let nesting_level = global.scope_level; let is_const = access == AccessMode::ReadOnly; @@ -824,74 +759,56 @@ impl Engine { }; let context = EvalContext::new(self, global, None, lib, level, scope, this_ptr); - match filter(true, info, context) { - Ok(true) => None, - Ok(false) => { - Some(Err( - ERR::ErrorForbiddenVariable(var_name.to_string(), *pos).into() - )) - } - err @ Err(_) => Some(err), + if !filter(true, info, context)? { + return Err(ERR::ErrorForbiddenVariable(var_name.to_string(), *pos).into()); } + } + + // Evaluate initial value + let mut value = self + .eval_expr(global, caches, lib, level, scope, this_ptr, expr)? + .flatten(); + + let _alias = if !rewind_scope { + // Put global constants into global module + #[cfg(not(feature = "no_function"))] + #[cfg(not(feature = "no_module"))] + if global.scope_level == 0 + && access == AccessMode::ReadOnly + && lib.iter().any(|m| !m.is_empty()) + { + crate::func::locked_write(global.constants.get_or_insert_with(|| { + crate::Shared::new( + crate::Locked::new(std::collections::BTreeMap::new()), + ) + })) + .insert(var_name.name.clone(), value.clone()); + } + + if export { + Some(var_name) + } else { + None + } + } else if export { + unreachable!("exported variable not on global level"); } else { None }; - if let Some(result) = result { - result.map(|_| Dynamic::UNIT) + if let Some(index) = index { + value.set_access_mode(access); + *scope.get_mut_by_index(scope.len() - index.get()) = value; } else { - // Evaluate initial value - let value_result = self - .eval_expr(global, caches, lib, level, scope, this_ptr, expr) - .map(Dynamic::flatten); - - if let Ok(mut value) = value_result { - let _alias = if !rewind_scope { - // Put global constants into global module - #[cfg(not(feature = "no_function"))] - #[cfg(not(feature = "no_module"))] - if global.scope_level == 0 - && access == AccessMode::ReadOnly - && lib.iter().any(|m| !m.is_empty()) - { - crate::func::locked_write(global.constants.get_or_insert_with( - || { - crate::Shared::new(crate::Locked::new( - std::collections::BTreeMap::new(), - )) - }, - )) - .insert(var_name.name.clone(), value.clone()); - } - - if export { - Some(var_name) - } else { - None - } - } else if export { - unreachable!("exported variable not on global level"); - } else { - None - }; - - if let Some(index) = index { - value.set_access_mode(access); - *scope.get_mut_by_index(scope.len() - index.get()) = value; - } else { - scope.push_entry(var_name.name.clone(), access, value); - } - - #[cfg(not(feature = "no_module"))] - if let Some(alias) = _alias { - scope.add_alias_by_index(scope.len() - 1, alias.name.as_str().into()); - } - - Ok(Dynamic::UNIT) - } else { - value_result - } + scope.push_entry(var_name.name.clone(), access, value); } + + #[cfg(not(feature = "no_module"))] + if let Some(alias) = _alias { + scope.add_alias_by_index(scope.len() - 1, alias.name.as_str().into()); + } + + Ok(Dynamic::UNIT) } // Import statement @@ -904,66 +821,52 @@ impl Engine { return Err(ERR::ErrorTooManyModules(*_pos).into()); } - let path_result = self - .eval_expr(global, caches, lib, level, scope, this_ptr, expr) - .and_then(|v| { - let typ = v.type_name(); - v.try_cast::().ok_or_else(|| { - self.make_type_mismatch_err::( - typ, - expr.position(), - ) - }) - }); + let v = self.eval_expr(global, caches, lib, level, scope, this_ptr, expr)?; + let typ = v.type_name(); + let path = v.try_cast::().ok_or_else(|| { + self.make_type_mismatch_err::(typ, expr.position()) + })?; - if let Ok(path) = path_result { - use crate::ModuleResolver; + use crate::ModuleResolver; - let path_pos = expr.start_position(); + let path_pos = expr.start_position(); - let resolver = global.embedded_module_resolver.clone(); + let resolver = global.embedded_module_resolver.clone(); - let module_result = resolver - .as_ref() - .and_then(|r| match r.resolve_raw(self, global, &path, path_pos) { - Err(err) if matches!(*err, ERR::ErrorModuleNotFound(..)) => None, - result => Some(result), - }) - .or_else(|| { - Some( - self.module_resolver - .resolve_raw(self, global, &path, path_pos), - ) - }) - .unwrap_or_else(|| { - Err(ERR::ErrorModuleNotFound(path.to_string(), path_pos).into()) - }); + let module = resolver + .as_ref() + .and_then(|r| match r.resolve_raw(self, global, &path, path_pos) { + Err(err) if matches!(*err, ERR::ErrorModuleNotFound(..)) => None, + result => Some(result), + }) + .or_else(|| { + Some( + self.module_resolver + .resolve_raw(self, global, &path, path_pos), + ) + }) + .unwrap_or_else(|| { + Err(ERR::ErrorModuleNotFound(path.to_string(), path_pos).into()) + })?; - if let Ok(module) = module_result { - let (export, must_be_indexed) = if !export.is_empty() { - (export.name.clone(), true) - } else { - (self.get_interned_string(""), false) - }; - - if !must_be_indexed || module.is_indexed() { - global.push_import(export, module); - } else { - // Index the module (making a clone copy if necessary) if it is not indexed - let mut m = crate::func::shared_take_or_clone(module); - m.build_index(); - global.push_import(export, m); - } - - global.num_modules_loaded += 1; - - Ok(Dynamic::UNIT) - } else { - module_result.map(|_| Dynamic::UNIT) - } + let (export, must_be_indexed) = if !export.is_empty() { + (export.name.clone(), true) } else { - path_result.map(|_| Dynamic::UNIT) + (self.get_interned_string(""), false) + }; + + if !must_be_indexed || module.is_indexed() { + global.push_import(export, module); + } else { + // Index the module (making a clone copy if necessary) if it is not indexed + let mut m = crate::func::shared_take_or_clone(module); + m.build_index(); + global.push_import(export, m); } + + global.num_modules_loaded += 1; + + Ok(Dynamic::UNIT) } // Export statement @@ -1004,12 +907,7 @@ impl Engine { } _ => unreachable!("statement cannot be evaluated: {:?}", stmt), - }; - - #[cfg(feature = "debugging")] - global.debugger.reset_status(reset_debugger); - - result + } } /// Evaluate a list of statements with no `this` pointer. diff --git a/src/func/call.rs b/src/func/call.rs index ff95e3a4..6c7ddf75 100644 --- a/src/func/call.rs +++ b/src/func/call.rs @@ -9,6 +9,7 @@ use crate::engine::{ }; use crate::eval::{Caches, FnResolutionCacheEntry, GlobalRuntimeState}; use crate::tokenizer::{is_valid_function_name, Token}; +use crate::types::RestoreOnDrop; use crate::{ calc_fn_hash, calc_fn_hash_full, Dynamic, Engine, FnArgsVec, FnPtr, ImmutableString, Module, OptimizationLevel, Position, RhaiError, RhaiResult, RhaiResultOf, Scope, Shared, ERR, @@ -88,9 +89,11 @@ impl<'a> ArgBackup<'a> { /// 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. #[inline(always)] - pub fn restore_first_arg(mut self, args: &mut FnCallArgs<'a>) { + pub fn restore_first_arg(&mut self, args: &mut FnCallArgs<'a>) { if let Some(p) = self.orig_mut.take() { args[0] = p; + } else { + unreachable!("`Some`"); } } } @@ -327,7 +330,7 @@ impl Engine { name: &str, op_token: Option<&Token>, hash: u64, - args: &mut FnCallArgs, + mut args: &mut FnCallArgs, is_ref_mut: bool, pos: Position, ) -> RhaiResultOf<(Dynamic, bool)> { @@ -354,48 +357,45 @@ impl Engine { #[cfg(feature = "debugging")] let orig_call_stack_len = global.debugger.call_stack().len(); - let mut _result = if let Some(FnResolutionCacheEntry { func, source }) = func { - assert!(func.is_native()); + let FnResolutionCacheEntry { func, source } = func.unwrap(); + assert!(func.is_native()); - let mut backup = ArgBackup::new(); + let backup = &mut ArgBackup::new(); - // Calling pure function but the first argument is a reference? - if is_ref_mut && func.is_pure() && !args.is_empty() { - // Clone the first argument - backup.change_first_arg_to_copy(args); - } + // Calling pure function but the first argument is a reference? + let swap = is_ref_mut && func.is_pure() && !args.is_empty(); - #[cfg(feature = "debugging")] - if self.debugger.is_some() { - global.debugger.push_call_stack_frame( - self.get_interned_string(name), - args.iter().map(|v| (*v).clone()).collect(), - source.clone().or_else(|| global.source.clone()), - pos, - ); - } + if swap { + // Clone the first argument + backup.change_first_arg_to_copy(args); + } - // Run external function - let src = source.as_ref().map(|s| s.as_str()); - let context = (self, name, src, &*global, lib, pos, level).into(); + let args = + &mut *RestoreOnDrop::new_if(swap, &mut args, move |a| backup.restore_first_arg(a)); - let result = if func.is_plugin_fn() { - let f = func.get_plugin_fn().unwrap(); - if !f.is_pure() && !args.is_empty() && args[0].is_read_only() { - Err(ERR::ErrorNonPureMethodCallOnConstant(name.to_string(), pos).into()) - } else { - f.call(context, args) - } + #[cfg(feature = "debugging")] + if self.debugger.is_some() { + global.debugger.push_call_stack_frame( + self.get_interned_string(name), + args.iter().map(|v| (*v).clone()).collect(), + source.clone().or_else(|| global.source.clone()), + pos, + ); + } + + // Run external function + let src = source.as_ref().map(|s| s.as_str()); + let context = (self, name, src, &*global, lib, pos, level).into(); + + let mut _result = if func.is_plugin_fn() { + let f = func.get_plugin_fn().unwrap(); + if !f.is_pure() && !args.is_empty() && args[0].is_read_only() { + Err(ERR::ErrorNonPureMethodCallOnConstant(name.to_string(), pos).into()) } else { - func.get_native_fn().unwrap()(context, args) - }; - - // Restore the original reference - backup.restore_first_arg(args); - - result + f.call(context, args) + } } else { - unreachable!("`Some`"); + func.get_native_fn().unwrap()(context, args) }; #[cfg(feature = "debugging")] @@ -413,11 +413,11 @@ impl Engine { Ok(ref r) => crate::eval::DebuggerEvent::FunctionExitWithValue(r), Err(ref err) => crate::eval::DebuggerEvent::FunctionExitWithError(err), }; - match self + + if let Err(err) = self .run_debugger_raw(global, caches, lib, level, scope, &mut None, node, event) { - Ok(_) => (), - Err(err) => _result = Err(err), + _result = Err(err); } } @@ -543,7 +543,7 @@ impl Engine { fn_name: &str, op_token: Option<&Token>, hashes: FnCallHashes, - args: &mut FnCallArgs, + mut args: &mut FnCallArgs, is_ref_mut: bool, _is_method_call: bool, pos: Position, @@ -613,19 +613,11 @@ impl Engine { #[cfg(not(feature = "no_function"))] if !hashes.is_native_only() { // Script-defined function call? + let hash = hashes.script(); let local_entry = &mut None; if let Some(FnResolutionCacheEntry { func, ref source }) = self - .resolve_fn( - global, - caches, - local_entry, - lib, - None, - hashes.script(), - None, - false, - ) + .resolve_fn(global, caches, local_entry, lib, None, hash, None, false) .cloned() { // Script function call @@ -647,8 +639,9 @@ impl Engine { }; let orig_source = mem::replace(&mut global.source, source.clone()); + let global = &mut *RestoreOnDrop::new(global, move |g| g.source = orig_source); - let result = if _is_method_call { + return if _is_method_call { // Method call of script function - map first argument to `this` let (first_arg, rest_args) = args.split_first_mut().unwrap(); @@ -666,27 +659,24 @@ impl Engine { ) } else { // Normal call of script function - let mut backup = ArgBackup::new(); + let backup = &mut ArgBackup::new(); // The first argument is a reference? - if is_ref_mut && !args.is_empty() { + let swap = is_ref_mut && !args.is_empty(); + + if swap { backup.change_first_arg_to_copy(args); } - let result = self.call_script_fn( + let args = &mut *RestoreOnDrop::new_if(swap, &mut args, move |a| { + backup.restore_first_arg(a) + }); + + self.call_script_fn( global, caches, lib, level, scope, &mut None, func, args, true, pos, - ); - - // Restore the original reference - backup.restore_first_arg(args); - - result - }; - - // Restore the original source - global.source = orig_source; - - return result.map(|r| (r, false)); + ) + } + .map(|r| (r, false)); } } @@ -722,17 +712,14 @@ impl Engine { // Do not match function exit for arguments #[cfg(feature = "debugging")] - let reset_debugger = global.debugger.clear_status_if(|status| { + let reset = global.debugger.clear_status_if(|status| { matches!(status, crate::eval::DebuggerStatus::FunctionExit(..)) }); - - let result = self.eval_expr(global, caches, lib, level, scope, this_ptr, arg_expr); - - // Restore function exit status #[cfg(feature = "debugging")] - global.debugger.reset_status(reset_debugger); + let global = &mut *RestoreOnDrop::new(global, move |g| g.debugger.reset_status(reset)); - result.map(|r| (r, arg_expr.start_position())) + self.eval_expr(global, caches, lib, level, scope, this_ptr, arg_expr) + .map(|r| (r, arg_expr.start_position())) } /// Call a dot method. @@ -1389,15 +1376,13 @@ impl Engine { Some(f) if f.is_script() => { let fn_def = f.get_script_fn_def().expect("script-defined function"); let new_scope = &mut Scope::new(); + let orig_source = mem::replace(&mut global.source, module.id_raw().cloned()); + let global = &mut *RestoreOnDrop::new(global, move |g| g.source = orig_source); - let result = self.call_script_fn( + self.call_script_fn( global, caches, lib, level, new_scope, &mut None, fn_def, &mut args, true, pos, - ); - - global.source = orig_source; - - result + ) } Some(f) if f.is_plugin_fn() => { diff --git a/src/types/interner.rs b/src/types/interner.rs index 4b7d387e..43a2d395 100644 --- a/src/types/interner.rs +++ b/src/types/interner.rs @@ -1,3 +1,5 @@ +//! A strings interner type. + use super::BloomFilterU64; use crate::func::{hashing::get_hasher, StraightHashMap}; use crate::ImmutableString; @@ -20,10 +22,8 @@ pub const MAX_INTERNED_STRINGS: usize = 1024; /// Maximum length of strings interned. pub const MAX_STRING_LEN: usize = 24; -/// _(internals)_ A factory of identifiers from text strings. +/// _(internals)_ A cache for interned strings. /// Exported under the `internals` feature only. -/// -/// Normal identifiers, property getters and setters are interned separately. pub struct StringsInterner<'a> { /// Maximum number of strings interned. pub capacity: usize, @@ -103,43 +103,48 @@ impl StringsInterner<'_> { if value.strong_count() > 1 { return value; } - e.insert(value).clone() } }; - // If the interner is over capacity, remove the longest entry that has the lowest count - if self.cache.len() > self.capacity { - // Throttle: leave some buffer to grow when shrinking the cache. - // We leave at least two entries, one for the empty string, and one for the string - // that has just been inserted. - let max = if self.capacity < 5 { - 2 - } else { - self.capacity - 3 - }; - - while self.cache.len() > max { - let (_, _, n) = self - .cache - .iter() - .fold((0, usize::MAX, 0), |(x, c, n), (&k, v)| { - if k != hash - && (v.strong_count() < c || (v.strong_count() == c && v.len() > x)) - { - (v.len(), v.strong_count(), k) - } else { - (x, c, n) - } - }); - - self.cache.remove(&n); - } - } + // Throttle the cache upon exit + self.throttle_cache(hash); result } + /// If the interner is over capacity, remove the longest entry that has the lowest count + fn throttle_cache(&mut self, hash: u64) { + if self.cache.len() <= self.capacity { + return; + } + + // Leave some buffer to grow when shrinking the cache. + // We leave at least two entries, one for the empty string, and one for the string + // that has just been inserted. + let max = if self.capacity < 5 { + 2 + } else { + self.capacity - 3 + }; + + while self.cache.len() > max { + let (_, _, n) = self + .cache + .iter() + .fold((0, usize::MAX, 0), |(x, c, n), (&k, v)| { + if k != hash && (v.strong_count() < c || (v.strong_count() == c && v.len() > x)) + { + (v.len(), v.strong_count(), k) + } else { + (x, c, n) + } + }); + + self.cache.remove(&n); + } + } + /// Number of strings interned. #[inline(always)] #[must_use] diff --git a/src/types/mod.rs b/src/types/mod.rs index b6ca3802..1fbf33d3 100644 --- a/src/types/mod.rs +++ b/src/types/mod.rs @@ -8,6 +8,7 @@ pub mod fn_ptr; pub mod immutable_string; pub mod interner; pub mod parse_error; +pub mod restore; pub mod scope; pub mod variant; @@ -21,5 +22,6 @@ pub use fn_ptr::FnPtr; pub use immutable_string::ImmutableString; pub use interner::StringsInterner; pub use parse_error::{LexError, ParseError, ParseErrorType}; +pub use restore::RestoreOnDrop; pub use scope::Scope; pub use variant::Variant; diff --git a/src/types/restore.rs b/src/types/restore.rs new file mode 100644 index 00000000..d2e6cd87 --- /dev/null +++ b/src/types/restore.rs @@ -0,0 +1,57 @@ +//! Facility to run state restoration logic at the end of scope. + +use std::ops::{Deref, DerefMut}; +#[cfg(feature = "no_std")] +use std::prelude::v1::*; + +/// Run custom restoration logic upon the end of scope. +#[must_use] +pub struct RestoreOnDrop<'a, T, R: FnOnce(&mut T)> { + value: &'a mut T, + restore: Option, +} + +impl<'a, T, R: FnOnce(&mut T)> RestoreOnDrop<'a, T, R> { + /// Create a new [`RestoreOnDrop`] that runs restoration logic at the end of scope only when + /// `need_restore` is `true`. + #[inline(always)] + pub fn new_if(need_restore: bool, value: &'a mut T, restore: R) -> Self { + Self { + value, + restore: if need_restore { Some(restore) } else { None }, + } + } + /// Create a new [`RestoreOnDrop`] that runs restoration logic at the end of scope. + #[inline(always)] + pub fn new(value: &'a mut T, restore: R) -> Self { + Self { + value, + restore: Some(restore), + } + } +} + +impl<'a, T, R: FnOnce(&mut T)> Drop for RestoreOnDrop<'a, T, R> { + #[inline(always)] + fn drop(&mut self) { + if let Some(restore) = self.restore.take() { + restore(self.value); + } + } +} + +impl<'a, T, R: FnOnce(&mut T)> Deref for RestoreOnDrop<'a, T, R> { + type Target = T; + + #[inline(always)] + fn deref(&self) -> &Self::Target { + self.value + } +} + +impl<'a, T, R: FnOnce(&mut T)> DerefMut for RestoreOnDrop<'a, T, R> { + #[inline(always)] + fn deref_mut(&mut self) -> &mut Self::Target { + self.value + } +}