Use RestoreOnDrop.

This commit is contained in:
Stephen Chung 2022-11-08 11:52:46 +08:00
parent cba914db95
commit f4e2901353
9 changed files with 618 additions and 699 deletions

View File

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

View File

@ -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::<crate::Shared<_>>::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.

View File

@ -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() => {
let val = {
#[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, ..
} = &**f;
let rhs_chain = rhs.into();
// 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(
global, caches, lib, level, name, *hashes, target, call_args,
pos1, pos,
);
self.make_method_call(
global, caches, lib, level, name, *hashes, target,
call_args, pos1, pos,
)?
.0
};
idx_values.truncate(offset);
#[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,

View File

@ -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::<bool>(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::<bool>(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::<bool>(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::<bool>(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::<bool>(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::<bool>(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::<bool>(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::<bool>(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::<()>() => {
if value.is::<()>() {
self.eval_expr(global, caches, lib, level, scope, this_ptr, &x.rhs)
}
_ => lhs,
} 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
}
}
}

View File

@ -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,16 +235,14 @@ 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 var_name = x.3.as_str();
@ -255,9 +256,7 @@ impl Engine {
// Cannot assign to temp result from expression
if is_temp_result {
return Err(
ERR::ErrorAssignmentToConstant(var_name.to_string(), pos).into()
);
return Err(ERR::ErrorAssignmentToConstant(var_name.to_string(), pos).into());
}
self.track_operation(global, pos)?;
@ -265,22 +264,14 @@ impl Engine {
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
}
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::<ImmutableString>();
@ -299,41 +290,30 @@ impl Engine {
let _new_val = &mut Some((rhs_val, op_info));
// Must be either `var[index] op= val` or `var.prop op= val`
match lhs {
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(
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(
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),
}
} else {
rhs_result
.map(|_| Dynamic::UNIT);
}
};
#[cfg(feature = "debugging")]
global.debugger.reset_status(reset_debugger);
return result;
}
self.track_operation(global, stmt.position())?;
let result = match stmt {
match stmt {
// No-op
Stmt::Noop(..) => Ok(Dynamic::UNIT),
@ -385,11 +365,11 @@ 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 value = self.eval_expr(global, caches, lib, level, scope, this_ptr, expr)?;
if value.is_hashable() {
let hasher = &mut get_hasher();
value.hash(hasher);
let hash = hasher.finish();
@ -398,87 +378,56 @@ impl Engine {
if let Some(case_blocks_list) = cases.get(&hash) {
assert!(!case_blocks_list.is_empty());
let mut result = Ok(None);
for &index in case_blocks_list {
let block = &expressions[index];
let cond_result = match block.condition {
Expr::BoolConstant(b, ..) => Ok(b),
Expr::BoolConstant(b, ..) => 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::<bool>(
typ,
c.position(),
)
})
}),
.eval_expr(global, caches, lib, level, scope, this_ptr, c)?
.as_bool()
.map_err(|typ| {
self.make_type_mismatch_err::<bool>(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::<INT>() && !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),
Expr::BoolConstant(b, ..) => 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::<bool>(
typ,
c.position(),
)
})
}),
.eval_expr(global, caches, lib, level, scope, this_ptr, c)?
.as_bool()
.map_err(|typ| {
self.make_type_mismatch_err::<bool>(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 {
// Nothing matches
Ok(None)
}
} else {
// Non-hashable
Ok(None)
};
}
}
if let Ok(Some(expr)) = expr_result {
if let Some(expr) = result {
self.eval_expr(global, caches, lib, level, scope, this_ptr, expr)
} else if let Ok(None) = expr_result {
// Default match clause
} else {
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)
}
} else {
value_result
}
}
@ -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::<bool>(typ, expr.position())
})
});
.eval_expr(global, caches, lib, level, scope, this_ptr, expr)?
.as_bool()
.map_err(|typ| self.make_type_mismatch_err::<bool>(typ, expr.position()))?;
match condition {
Ok(false) => break Ok(Dynamic::UNIT),
Ok(true) if body.is_empty() => (),
Ok(true) => {
match self.eval_stmt_block(
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,
) {
Ok(_) => (),
Err(err) => match *err {
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::<bool>(typ, expr.position())
})
});
.eval_expr(global, caches, lib, level, scope, this_ptr, expr)?
.as_bool()
.map_err(|typ| self.make_type_mismatch_err::<bool>(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,11 +516,10 @@ 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();
// lib should only contain scripts, so technically they cannot have iterators
@ -605,8 +542,13 @@ impl Engine {
});
if let Some(func) = func {
// Add the loop variables
// 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);
});
// Add the loop variables
let counter_index = if counter.is_empty() {
usize::MAX
} else {
@ -617,7 +559,7 @@ impl Engine {
scope.push(var_name.name.clone(), ());
let index = scope.len() - 1;
let loop_result = func(iter_obj)
func(iter_obj)
.enumerate()
.try_fold(Dynamic::UNIT, |_, (x, iter_value)| {
// Increment counter
@ -664,17 +606,10 @@ impl Engine {
.or_else(|err| match *err {
ERR::LoopBreak(true, value, ..) => Ok(value),
_ => Err(err),
});
scope.rewind(orig_scope_len);
loop_result
})
} else {
Err(ERR::ErrorFor(expr.start_position()).into())
}
} else {
iter_result
}
}
// Continue/Break statement
@ -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 {
)
.map(|_| Dynamic::UNIT)
.map_err(|result_err| match *result_err {
// Re-throw exception
ERR::ErrorRuntime(Dynamic(Union::Unit(..)), pos) => {
err.set_position(pos);
Err(err)
}
_ => Err(result_err),
},
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,28 +759,16 @@ 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()
))
if !filter(true, info, context)? {
return Err(ERR::ErrorForbiddenVariable(var_name.to_string(), *pos).into());
}
err @ Err(_) => Some(err),
}
} else {
None
};
if let Some(result) = result {
result.map(|_| Dynamic::UNIT)
} else {
// Evaluate initial value
let value_result = self
.eval_expr(global, caches, lib, level, scope, this_ptr, expr)
.map(Dynamic::flatten);
let mut value = self
.eval_expr(global, caches, lib, level, scope, this_ptr, expr)?
.flatten();
if let Ok(mut value) = value_result {
let _alias = if !rewind_scope {
// Put global constants into global module
#[cfg(not(feature = "no_function"))]
@ -854,13 +777,11 @@ impl Engine {
&& 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(),
))
},
))
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());
}
@ -888,10 +809,6 @@ impl Engine {
}
Ok(Dynamic::UNIT)
} else {
value_result
}
}
}
// Import statement
@ -904,26 +821,19 @@ 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 v = self.eval_expr(global, caches, lib, level, scope, this_ptr, expr)?;
let typ = v.type_name();
v.try_cast::<crate::ImmutableString>().ok_or_else(|| {
self.make_type_mismatch_err::<crate::ImmutableString>(
typ,
expr.position(),
)
})
});
let path = v.try_cast::<crate::ImmutableString>().ok_or_else(|| {
self.make_type_mismatch_err::<crate::ImmutableString>(typ, expr.position())
})?;
if let Ok(path) = path_result {
use crate::ModuleResolver;
let path_pos = expr.start_position();
let resolver = global.embedded_module_resolver.clone();
let module_result = resolver
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,
@ -937,9 +847,8 @@ impl Engine {
})
.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 {
@ -958,12 +867,6 @@ impl Engine {
global.num_modules_loaded += 1;
Ok(Dynamic::UNIT)
} else {
module_result.map(|_| Dynamic::UNIT)
}
} else {
path_result.map(|_| 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.

View File

@ -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,17 +357,22 @@ 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 {
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() {
let swap = is_ref_mut && func.is_pure() && !args.is_empty();
if swap {
// Clone the first argument
backup.change_first_arg_to_copy(args);
}
let args =
&mut *RestoreOnDrop::new_if(swap, &mut args, move |a| backup.restore_first_arg(a));
#[cfg(feature = "debugging")]
if self.debugger.is_some() {
global.debugger.push_call_stack_frame(
@ -379,7 +387,7 @@ impl Engine {
let src = source.as_ref().map(|s| s.as_str());
let context = (self, name, src, &*global, lib, pos, level).into();
let result = if func.is_plugin_fn() {
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())
@ -390,14 +398,6 @@ impl Engine {
func.get_native_fn().unwrap()(context, args)
};
// Restore the original reference
backup.restore_first_arg(args);
result
} else {
unreachable!("`Some`");
};
#[cfg(feature = "debugging")]
{
let trigger = match global.debugger.status {
@ -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() => {

View File

@ -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,14 +103,23 @@ 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.
// 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 {
@ -124,8 +133,7 @@ impl StringsInterner<'_> {
.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))
if k != hash && (v.strong_count() < c || (v.strong_count() == c && v.len() > x))
{
(v.len(), v.strong_count(), k)
} else {
@ -137,9 +145,6 @@ impl StringsInterner<'_> {
}
}
result
}
/// Number of strings interned.
#[inline(always)]
#[must_use]

View File

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

57
src/types/restore.rs Normal file
View File

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