Use RestoreOnDrop.
This commit is contained in:
parent
cba914db95
commit
f4e2901353
@ -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() {
|
||||
|
@ -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.
|
||||
|
@ -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,
|
||||
|
@ -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::<()>() => {
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
816
src/eval/stmt.rs
816
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::<ImmutableString>();
|
||||
#[cfg(feature = "no_closure")]
|
||||
let is_string = rhs_val.is::<ImmutableString>();
|
||||
// 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>();
|
||||
#[cfg(feature = "no_closure")]
|
||||
let is_string = rhs_val.is::<ImmutableString>();
|
||||
|
||||
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::<bool>(
|
||||
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::<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),
|
||||
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(),
|
||||
)
|
||||
})
|
||||
}),
|
||||
};
|
||||
|
||||
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::<INT>() && !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::<bool>(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::<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(
|
||||
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::<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,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::<crate::ImmutableString>().ok_or_else(|| {
|
||||
self.make_type_mismatch_err::<crate::ImmutableString>(
|
||||
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::<crate::ImmutableString>().ok_or_else(|| {
|
||||
self.make_type_mismatch_err::<crate::ImmutableString>(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.
|
||||
|
145
src/func/call.rs
145
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() => {
|
||||
|
@ -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]
|
||||
|
@ -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
57
src/types/restore.rs
Normal 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
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user