Use RestoreOnDrop.
This commit is contained in:
parent
cba914db95
commit
f4e2901353
@ -3,6 +3,7 @@
|
|||||||
|
|
||||||
use crate::eval::{Caches, GlobalRuntimeState};
|
use crate::eval::{Caches, GlobalRuntimeState};
|
||||||
use crate::types::dynamic::Variant;
|
use crate::types::dynamic::Variant;
|
||||||
|
use crate::types::RestoreOnDrop;
|
||||||
use crate::{
|
use crate::{
|
||||||
reify, Dynamic, Engine, FuncArgs, Position, RhaiResult, RhaiResultOf, Scope, Shared, StaticVec,
|
reify, Dynamic, Engine, FuncArgs, Position, RhaiResult, RhaiResultOf, Scope, Shared, StaticVec,
|
||||||
AST, ERR,
|
AST, ERR,
|
||||||
@ -258,6 +259,10 @@ impl Engine {
|
|||||||
&mut global.embedded_module_resolver,
|
&mut global.embedded_module_resolver,
|
||||||
ast.resolver().cloned(),
|
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 result = if eval_ast && !statements.is_empty() {
|
||||||
let r = self.eval_global_statements(global, caches, lib, 0, scope, statements);
|
let r = self.eval_global_statements(global, caches, lib, 0, scope, statements);
|
||||||
@ -293,14 +298,7 @@ impl Engine {
|
|||||||
} else {
|
} else {
|
||||||
Err(ERR::ErrorFunctionNotFound(name.into(), Position::NONE).into())
|
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")]
|
#[cfg(feature = "debugging")]
|
||||||
if self.debugger.is_some() {
|
if self.debugger.is_some() {
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
use crate::eval::{Caches, GlobalRuntimeState};
|
use crate::eval::{Caches, GlobalRuntimeState};
|
||||||
use crate::parser::ParseState;
|
use crate::parser::ParseState;
|
||||||
use crate::types::dynamic::Variant;
|
use crate::types::dynamic::Variant;
|
||||||
|
use crate::types::RestoreOnDrop;
|
||||||
use crate::{
|
use crate::{
|
||||||
Dynamic, Engine, OptimizationLevel, Position, RhaiResult, RhaiResultOf, Scope, AST, ERR,
|
Dynamic, Engine, OptimizationLevel, Position, RhaiResult, RhaiResultOf, Scope, AST, ERR,
|
||||||
};
|
};
|
||||||
@ -225,6 +226,10 @@ impl Engine {
|
|||||||
&mut global.embedded_module_resolver,
|
&mut global.embedded_module_resolver,
|
||||||
ast.resolver().cloned(),
|
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();
|
let statements = ast.statements();
|
||||||
|
|
||||||
@ -232,23 +237,12 @@ impl Engine {
|
|||||||
return Ok(Dynamic::UNIT);
|
return Ok(Dynamic::UNIT);
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut _lib = &[
|
let lib = &[
|
||||||
#[cfg(not(feature = "no_function"))]
|
#[cfg(not(feature = "no_function"))]
|
||||||
AsRef::<crate::Shared<_>>::as_ref(ast).clone(),
|
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);
|
self.eval_global_statements(global, caches, lib, level, scope, statements)
|
||||||
|
|
||||||
#[cfg(not(feature = "no_module"))]
|
|
||||||
{
|
|
||||||
global.embedded_module_resolver = orig_embedded_module_resolver;
|
|
||||||
}
|
|
||||||
|
|
||||||
result
|
|
||||||
}
|
}
|
||||||
/// _(internals)_ Evaluate a list of statements with no `this` pointer.
|
/// _(internals)_ Evaluate a list of statements with no `this` pointer.
|
||||||
/// Exported under the `internals` feature only.
|
/// Exported under the `internals` feature only.
|
||||||
|
@ -4,6 +4,7 @@
|
|||||||
use super::{Caches, GlobalRuntimeState, Target};
|
use super::{Caches, GlobalRuntimeState, Target};
|
||||||
use crate::ast::{ASTFlags, Expr, OpAssignment};
|
use crate::ast::{ASTFlags, Expr, OpAssignment};
|
||||||
use crate::types::dynamic::Union;
|
use crate::types::dynamic::Union;
|
||||||
|
use crate::types::RestoreOnDrop;
|
||||||
use crate::{
|
use crate::{
|
||||||
Dynamic, Engine, FnArgsVec, Module, Position, RhaiResult, RhaiResultOf, Scope, Shared, ERR,
|
Dynamic, Engine, FnArgsVec, Module, Position, RhaiResult, RhaiResultOf, Scope, Shared, ERR,
|
||||||
};
|
};
|
||||||
@ -200,29 +201,30 @@ impl Engine {
|
|||||||
// xxx.fn_name(arg_expr_list)
|
// xxx.fn_name(arg_expr_list)
|
||||||
Expr::MethodCall(x, pos) if !x.is_qualified() && new_val.is_none() => {
|
Expr::MethodCall(x, pos) if !x.is_qualified() && new_val.is_none() => {
|
||||||
#[cfg(feature = "debugging")]
|
#[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,
|
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 {
|
let crate::ast::FnCallExpr {
|
||||||
name, hashes, args, ..
|
name, hashes, args, ..
|
||||||
} = &**x;
|
} = &**x;
|
||||||
|
|
||||||
|
// Truncate the index values upon exit
|
||||||
let offset = idx_values.len() - args.len();
|
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 call_args = &mut idx_values[offset..];
|
||||||
let pos1 = args.get(0).map_or(Position::NONE, Expr::position);
|
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,
|
global, caches, lib, level, name, *hashes, target, call_args, pos1,
|
||||||
*pos,
|
*pos,
|
||||||
);
|
)
|
||||||
|
|
||||||
idx_values.truncate(offset);
|
|
||||||
|
|
||||||
#[cfg(feature = "debugging")]
|
|
||||||
global.debugger.reset_status(reset_debugger);
|
|
||||||
|
|
||||||
result
|
|
||||||
}
|
}
|
||||||
// xxx.fn_name(...) = ???
|
// xxx.fn_name(...) = ???
|
||||||
Expr::MethodCall(..) if new_val.is_some() => {
|
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
|
// {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() => {
|
Expr::MethodCall(ref x, pos) if !x.is_qualified() => {
|
||||||
#[cfg(feature = "debugging")]
|
#[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,
|
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 {
|
let crate::ast::FnCallExpr {
|
||||||
name, hashes, args, ..
|
name, hashes, args, ..
|
||||||
} = &**x;
|
} = &**x;
|
||||||
|
|
||||||
|
// Truncate the index values upon exit
|
||||||
let offset = idx_values.len() - args.len();
|
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 call_args = &mut idx_values[offset..];
|
||||||
let pos1 = args.get(0).map_or(Position::NONE, Expr::position);
|
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,
|
global, caches, lib, level, name, *hashes, target, call_args,
|
||||||
pos1, pos,
|
pos1, pos,
|
||||||
);
|
)?
|
||||||
|
.0
|
||||||
idx_values.truncate(offset);
|
.into()
|
||||||
|
|
||||||
#[cfg(feature = "debugging")]
|
|
||||||
global.debugger.reset_status(reset_debugger);
|
|
||||||
|
|
||||||
result?.0.into()
|
|
||||||
}
|
}
|
||||||
// {xxx:map}.module::fn_name(...) - syntax error
|
// {xxx:map}.module::fn_name(...) - syntax error
|
||||||
Expr::MethodCall(..) => unreachable!(
|
Expr::MethodCall(..) => unreachable!(
|
||||||
@ -504,32 +510,39 @@ impl Engine {
|
|||||||
}
|
}
|
||||||
// xxx.fn_name(arg_expr_list)[expr] | xxx.fn_name(arg_expr_list).expr
|
// xxx.fn_name(arg_expr_list)[expr] | xxx.fn_name(arg_expr_list).expr
|
||||||
Expr::MethodCall(ref f, pos) if !f.is_qualified() => {
|
Expr::MethodCall(ref f, pos) if !f.is_qualified() => {
|
||||||
#[cfg(feature = "debugging")]
|
let val = {
|
||||||
let reset_debugger = self.run_debugger_with_reset(
|
#[cfg(feature = "debugging")]
|
||||||
global, caches, lib, level, scope, this_ptr, _node,
|
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 {
|
let crate::ast::FnCallExpr {
|
||||||
name, hashes, args, ..
|
name, hashes, args, ..
|
||||||
} = &**f;
|
} = &**f;
|
||||||
let rhs_chain = rhs.into();
|
|
||||||
|
|
||||||
let offset = idx_values.len() - args.len();
|
// Truncate the index values upon exit
|
||||||
let call_args = &mut idx_values[offset..];
|
let offset = idx_values.len() - args.len();
|
||||||
let pos1 = args.get(0).map_or(Position::NONE, Expr::position);
|
let idx_values =
|
||||||
|
&mut *RestoreOnDrop::new(idx_values, move |v| {
|
||||||
|
v.truncate(offset)
|
||||||
|
});
|
||||||
|
|
||||||
let result = self.make_method_call(
|
let call_args = &mut idx_values[offset..];
|
||||||
global, caches, lib, level, name, *hashes, target, call_args,
|
let pos1 = args.get(0).map_or(Position::NONE, Expr::position);
|
||||||
pos1, pos,
|
|
||||||
);
|
|
||||||
|
|
||||||
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 val = &mut val.into();
|
||||||
|
let rhs_chain = rhs.into();
|
||||||
|
|
||||||
self.eval_dot_index_chain_helper(
|
self.eval_dot_index_chain_helper(
|
||||||
global, caches, lib, level, this_ptr, val, root, rhs, *options,
|
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::ast::{Expr, OpAssignment};
|
||||||
use crate::engine::{KEYWORD_THIS, OP_CONCAT};
|
use crate::engine::{KEYWORD_THIS, OP_CONCAT};
|
||||||
use crate::types::dynamic::AccessMode;
|
use crate::types::dynamic::AccessMode;
|
||||||
|
use crate::types::RestoreOnDrop;
|
||||||
use crate::{Dynamic, Engine, Module, Position, RhaiResult, RhaiResultOf, Scope, Shared, ERR};
|
use crate::{Dynamic, Engine, Module, Position, RhaiResult, RhaiResultOf, Scope, Shared, ERR};
|
||||||
use std::num::NonZeroUsize;
|
use std::num::NonZeroUsize;
|
||||||
#[cfg(feature = "no_std")]
|
#[cfg(feature = "no_std")]
|
||||||
@ -234,18 +235,14 @@ impl Engine {
|
|||||||
// binary operators are also function calls.
|
// binary operators are also function calls.
|
||||||
if let Expr::FnCall(x, pos) = expr {
|
if let Expr::FnCall(x, pos) = expr {
|
||||||
#[cfg(feature = "debugging")]
|
#[cfg(feature = "debugging")]
|
||||||
let reset_debugger =
|
let reset =
|
||||||
self.run_debugger_with_reset(global, caches, lib, level, scope, this_ptr, expr)?;
|
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())?;
|
self.track_operation(global, expr.position())?;
|
||||||
|
|
||||||
let result =
|
return self.eval_fn_call_expr(global, caches, lib, level, scope, this_ptr, x, *pos);
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Then variable access.
|
// Then variable access.
|
||||||
@ -269,12 +266,14 @@ impl Engine {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "debugging")]
|
#[cfg(feature = "debugging")]
|
||||||
let reset_debugger =
|
let reset =
|
||||||
self.run_debugger_with_reset(global, caches, lib, level, scope, this_ptr, expr)?;
|
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())?;
|
self.track_operation(global, expr.position())?;
|
||||||
|
|
||||||
let result = match expr {
|
match expr {
|
||||||
// Constants
|
// Constants
|
||||||
Expr::DynamicConstant(x, ..) => Ok(x.as_ref().clone()),
|
Expr::DynamicConstant(x, ..) => Ok(x.as_ref().clone()),
|
||||||
Expr::IntegerConstant(x, ..) => Ok((*x).into()),
|
Expr::IntegerConstant(x, ..) => Ok((*x).into()),
|
||||||
@ -374,60 +373,33 @@ impl Engine {
|
|||||||
.map(Into::into)
|
.map(Into::into)
|
||||||
}
|
}
|
||||||
|
|
||||||
Expr::And(x, ..) => {
|
Expr::And(x, ..) => Ok((self
|
||||||
let lhs = self
|
.eval_expr(global, caches, lib, level, scope, this_ptr, &x.lhs)?
|
||||||
.eval_expr(global, caches, lib, level, scope, this_ptr, &x.lhs)
|
.as_bool()
|
||||||
.and_then(|v| {
|
.map_err(|typ| self.make_type_mismatch_err::<bool>(typ, x.lhs.position()))?
|
||||||
v.as_bool().map_err(|typ| {
|
&& self
|
||||||
self.make_type_mismatch_err::<bool>(typ, x.lhs.position())
|
.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 {
|
Expr::Or(x, ..) => Ok((self
|
||||||
Ok(true) => self
|
.eval_expr(global, caches, lib, level, scope, this_ptr, &x.lhs)?
|
||||||
.eval_expr(global, caches, lib, level, scope, this_ptr, &x.rhs)
|
.as_bool()
|
||||||
.and_then(|v| {
|
.map_err(|typ| self.make_type_mismatch_err::<bool>(typ, x.lhs.position()))?
|
||||||
v.as_bool()
|
|| self
|
||||||
.map_err(|typ| {
|
.eval_expr(global, caches, lib, level, scope, this_ptr, &x.rhs)?
|
||||||
self.make_type_mismatch_err::<bool>(typ, x.rhs.position())
|
.as_bool()
|
||||||
})
|
.map_err(|typ| self.make_type_mismatch_err::<bool>(typ, x.rhs.position()))?)
|
||||||
.map(Into::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::Coalesce(x, ..) => {
|
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 {
|
if value.is::<()>() {
|
||||||
Ok(value) if value.is::<()>() => {
|
self.eval_expr(global, caches, lib, level, scope, this_ptr, &x.rhs)
|
||||||
self.eval_expr(global, caches, lib, level, scope, this_ptr, &x.rhs)
|
} else {
|
||||||
}
|
Ok(value)
|
||||||
_ => lhs,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -467,11 +439,6 @@ impl Engine {
|
|||||||
.eval_dot_index_chain(global, caches, lib, level, scope, this_ptr, expr, &mut None),
|
.eval_dot_index_chain(global, caches, lib, level, scope, this_ptr, expr, &mut None),
|
||||||
|
|
||||||
_ => unreachable!("expression cannot be evaluated: {:?}", expr),
|
_ => 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::func::{get_builtin_op_assignment_fn, get_hasher};
|
||||||
use crate::types::dynamic::{AccessMode, Union};
|
use crate::types::dynamic::{AccessMode, Union};
|
||||||
|
use crate::types::RestoreOnDrop;
|
||||||
use crate::{
|
use crate::{
|
||||||
Dynamic, Engine, ImmutableString, Module, Position, RhaiResult, RhaiResultOf, Scope, Shared,
|
Dynamic, Engine, ImmutableString, Module, Position, RhaiResult, RhaiResultOf, Scope, Shared,
|
||||||
ERR, INT,
|
ERR, INT,
|
||||||
@ -39,17 +40,39 @@ impl Engine {
|
|||||||
return Ok(Dynamic::UNIT);
|
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 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"))]
|
#[cfg(not(feature = "no_module"))]
|
||||||
let orig_imports_len = global.num_imports();
|
let orig_imports_len = global.num_imports();
|
||||||
let orig_fn_resolution_caches_len = caches.fn_resolution_caches_len();
|
|
||||||
|
|
||||||
if restore_orig_state {
|
if restore_orig_state {
|
||||||
global.scope_level += 1;
|
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"))]
|
#[cfg(not(feature = "no_module"))]
|
||||||
let imports_len = global.num_imports();
|
let imports_len = global.num_imports();
|
||||||
|
|
||||||
@ -89,23 +112,7 @@ impl Engine {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Ok(result)
|
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.
|
/// Evaluate an op-assignment statement.
|
||||||
@ -205,8 +212,10 @@ impl Engine {
|
|||||||
rewind_scope: bool,
|
rewind_scope: bool,
|
||||||
) -> RhaiResult {
|
) -> RhaiResult {
|
||||||
#[cfg(feature = "debugging")]
|
#[cfg(feature = "debugging")]
|
||||||
let reset_debugger =
|
let reset =
|
||||||
self.run_debugger_with_reset(global, caches, lib, level, scope, this_ptr, stmt)?;
|
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.
|
// Coded this way for better branch prediction.
|
||||||
// Popular branches are lifted out of the `match` statement into their own branches.
|
// 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 {
|
if let Stmt::FnCall(x, pos) = stmt {
|
||||||
self.track_operation(global, stmt.position())?;
|
self.track_operation(global, stmt.position())?;
|
||||||
|
|
||||||
let result =
|
return self.eval_fn_call_expr(global, caches, lib, level, scope, this_ptr, x, *pos);
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Then assignments.
|
// Then assignments.
|
||||||
@ -232,108 +235,85 @@ impl Engine {
|
|||||||
|
|
||||||
self.track_operation(global, stmt.position())?;
|
self.track_operation(global, stmt.position())?;
|
||||||
|
|
||||||
let result = if let Expr::Variable(x, ..) = lhs {
|
if let Expr::Variable(x, ..) = lhs {
|
||||||
let rhs_result = self
|
let rhs_val = self
|
||||||
.eval_expr(global, caches, lib, level, scope, this_ptr, rhs)
|
.eval_expr(global, caches, lib, level, scope, this_ptr, rhs)?
|
||||||
.map(Dynamic::flatten);
|
.flatten();
|
||||||
|
|
||||||
if let Ok(rhs_val) = rhs_result {
|
let search_val =
|
||||||
let search_result =
|
self.search_namespace(global, caches, lib, level, scope, this_ptr, lhs)?;
|
||||||
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"))]
|
#[cfg(not(feature = "no_closure"))]
|
||||||
// Also handle case where target is a `Dynamic` shared value
|
// Also handle case where target is a `Dynamic` shared value
|
||||||
// (returned by a variable resolver, for example)
|
// (returned by a variable resolver, for example)
|
||||||
let is_temp_result = !lhs_ptr.is_ref() && !lhs_ptr.is_shared();
|
let is_temp_result = !lhs_ptr.is_ref() && !lhs_ptr.is_shared();
|
||||||
#[cfg(feature = "no_closure")]
|
#[cfg(feature = "no_closure")]
|
||||||
let is_temp_result = !lhs_ptr.is_ref();
|
let is_temp_result = !lhs_ptr.is_ref();
|
||||||
|
|
||||||
// Cannot assign to temp result from expression
|
// Cannot assign to temp result from expression
|
||||||
if is_temp_result {
|
if is_temp_result {
|
||||||
return Err(
|
return Err(ERR::ErrorAssignmentToConstant(var_name.to_string(), pos).into());
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 {
|
} else {
|
||||||
let (op_info, BinaryExpr { lhs, rhs }) = &**x;
|
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.
|
||||||
// Check if the result is a string. If so, intern it.
|
#[cfg(not(feature = "no_closure"))]
|
||||||
#[cfg(not(feature = "no_closure"))]
|
let is_string = !rhs_val.is_shared() && rhs_val.is::<ImmutableString>();
|
||||||
let is_string = !rhs_val.is_shared() && rhs_val.is::<ImmutableString>();
|
#[cfg(feature = "no_closure")]
|
||||||
#[cfg(feature = "no_closure")]
|
let is_string = rhs_val.is::<ImmutableString>();
|
||||||
let is_string = rhs_val.is::<ImmutableString>();
|
|
||||||
|
|
||||||
let rhs_val = if is_string {
|
let rhs_val = if is_string {
|
||||||
self.get_interned_string(
|
self.get_interned_string(
|
||||||
rhs_val.into_immutable_string().expect("`ImmutableString`"),
|
rhs_val.into_immutable_string().expect("`ImmutableString`"),
|
||||||
)
|
)
|
||||||
.into()
|
.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),
|
|
||||||
}
|
|
||||||
} else {
|
} 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),
|
||||||
}
|
}
|
||||||
};
|
.map(|_| Dynamic::UNIT);
|
||||||
|
}
|
||||||
#[cfg(feature = "debugging")]
|
|
||||||
global.debugger.reset_status(reset_debugger);
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
self.track_operation(global, stmt.position())?;
|
self.track_operation(global, stmt.position())?;
|
||||||
|
|
||||||
let result = match stmt {
|
match stmt {
|
||||||
// No-op
|
// No-op
|
||||||
Stmt::Noop(..) => Ok(Dynamic::UNIT),
|
Stmt::Noop(..) => Ok(Dynamic::UNIT),
|
||||||
|
|
||||||
@ -385,100 +365,69 @@ impl Engine {
|
|||||||
},
|
},
|
||||||
) = &**x;
|
) = &**x;
|
||||||
|
|
||||||
let value_result =
|
let mut result = None;
|
||||||
self.eval_expr(global, caches, lib, level, scope, this_ptr, expr);
|
|
||||||
|
|
||||||
if let Ok(value) = value_result {
|
let value = self.eval_expr(global, caches, lib, level, scope, this_ptr, expr)?;
|
||||||
let expr_result = if value.is_hashable() {
|
|
||||||
let hasher = &mut get_hasher();
|
|
||||||
value.hash(hasher);
|
|
||||||
let hash = hasher.finish();
|
|
||||||
|
|
||||||
// First check hashes
|
if value.is_hashable() {
|
||||||
if let Some(case_blocks_list) = cases.get(&hash) {
|
let hasher = &mut get_hasher();
|
||||||
assert!(!case_blocks_list.is_empty());
|
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 {
|
for &index in case_blocks_list {
|
||||||
let block = &expressions[index];
|
let block = &expressions[index];
|
||||||
|
|
||||||
let cond_result = match block.condition {
|
let cond_result = match block.condition {
|
||||||
Expr::BoolConstant(b, ..) => Ok(b),
|
Expr::BoolConstant(b, ..) => b,
|
||||||
ref c => self
|
ref c => self
|
||||||
.eval_expr(global, caches, lib, level, scope, this_ptr, c)
|
.eval_expr(global, caches, lib, level, scope, this_ptr, c)?
|
||||||
.and_then(|v| {
|
.as_bool()
|
||||||
v.as_bool().map_err(|typ| {
|
.map_err(|typ| {
|
||||||
self.make_type_mismatch_err::<bool>(
|
self.make_type_mismatch_err::<bool>(typ, c.position())
|
||||||
typ,
|
})?,
|
||||||
c.position(),
|
};
|
||||||
)
|
|
||||||
})
|
|
||||||
}),
|
|
||||||
};
|
|
||||||
|
|
||||||
match cond_result {
|
if cond_result {
|
||||||
Ok(true) => result = Ok(Some(&block.expr)),
|
result = Some(&block.expr);
|
||||||
Ok(false) => continue,
|
|
||||||
_ => result = cond_result.map(|_| None),
|
|
||||||
}
|
|
||||||
break;
|
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 {
|
} else if value.is::<INT>() && !ranges.is_empty() {
|
||||||
// Non-hashable
|
// Then check integer ranges
|
||||||
Ok(None)
|
let value = value.as_int().expect("`INT`");
|
||||||
};
|
|
||||||
|
|
||||||
if let Ok(Some(expr)) = expr_result {
|
for r in ranges.iter().filter(|r| r.contains(value)) {
|
||||||
self.eval_expr(global, caches, lib, level, scope, this_ptr, expr)
|
let block = &expressions[r.index()];
|
||||||
} else if let Ok(None) = expr_result {
|
|
||||||
// Default match clause
|
let cond_result = match block.condition {
|
||||||
def_case.as_ref().map_or(Ok(Dynamic::UNIT), |&index| {
|
Expr::BoolConstant(b, ..) => b,
|
||||||
let def_expr = &expressions[index].expr;
|
ref c => self
|
||||||
self.eval_expr(global, caches, lib, level, scope, this_ptr, def_expr)
|
.eval_expr(global, caches, lib, level, scope, this_ptr, c)?
|
||||||
})
|
.as_bool()
|
||||||
} else {
|
.map_err(|typ| {
|
||||||
expr_result.map(|_| Dynamic::UNIT)
|
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 {
|
} 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 {
|
loop {
|
||||||
let condition = self
|
let condition = self
|
||||||
.eval_expr(global, caches, lib, level, scope, this_ptr, expr)
|
.eval_expr(global, caches, lib, level, scope, this_ptr, expr)?
|
||||||
.and_then(|v| {
|
.as_bool()
|
||||||
v.as_bool().map_err(|typ| {
|
.map_err(|typ| self.make_type_mismatch_err::<bool>(typ, expr.position()))?;
|
||||||
self.make_type_mismatch_err::<bool>(typ, expr.position())
|
|
||||||
})
|
|
||||||
});
|
|
||||||
|
|
||||||
match condition {
|
if !condition {
|
||||||
Ok(false) => break Ok(Dynamic::UNIT),
|
break Ok(Dynamic::UNIT);
|
||||||
Ok(true) if body.is_empty() => (),
|
}
|
||||||
Ok(true) => {
|
|
||||||
match self.eval_stmt_block(
|
if !body.is_empty() {
|
||||||
global, caches, lib, level, scope, this_ptr, body, true,
|
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(false, ..) => (),
|
||||||
ERR::LoopBreak(true, value, ..) => break Ok(value),
|
ERR::LoopBreak(true, value, ..) => break Ok(value),
|
||||||
_ => break Err(err),
|
_ => break Err(err),
|
||||||
},
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
err => break err.map(|_| Dynamic::UNIT),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -546,30 +490,24 @@ impl Engine {
|
|||||||
|
|
||||||
loop {
|
loop {
|
||||||
if !body.is_empty() {
|
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,
|
global, caches, lib, level, scope, this_ptr, body, true,
|
||||||
) {
|
) {
|
||||||
Ok(_) => (),
|
match *err {
|
||||||
Err(err) => match *err {
|
|
||||||
ERR::LoopBreak(false, ..) => continue,
|
ERR::LoopBreak(false, ..) => continue,
|
||||||
ERR::LoopBreak(true, value, ..) => break Ok(value),
|
ERR::LoopBreak(true, value, ..) => break Ok(value),
|
||||||
_ => break Err(err),
|
_ => break Err(err),
|
||||||
},
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let condition = self
|
let condition = self
|
||||||
.eval_expr(global, caches, lib, level, scope, this_ptr, expr)
|
.eval_expr(global, caches, lib, level, scope, this_ptr, expr)?
|
||||||
.and_then(|v| {
|
.as_bool()
|
||||||
v.as_bool().map_err(|typ| {
|
.map_err(|typ| self.make_type_mismatch_err::<bool>(typ, expr.position()))?;
|
||||||
self.make_type_mismatch_err::<bool>(typ, expr.position())
|
|
||||||
})
|
|
||||||
});
|
|
||||||
|
|
||||||
match condition {
|
if condition ^ is_while {
|
||||||
Ok(condition) if condition ^ is_while => break Ok(Dynamic::UNIT),
|
break Ok(Dynamic::UNIT);
|
||||||
Ok(_) => (),
|
|
||||||
err => break err.map(|_| Dynamic::UNIT),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -578,102 +516,99 @@ impl Engine {
|
|||||||
Stmt::For(x, ..) => {
|
Stmt::For(x, ..) => {
|
||||||
let (var_name, counter, expr, statements) = &**x;
|
let (var_name, counter, expr, statements) = &**x;
|
||||||
|
|
||||||
let iter_result = self
|
let iter_obj = self
|
||||||
.eval_expr(global, caches, lib, level, scope, this_ptr, expr)
|
.eval_expr(global, caches, lib, level, scope, this_ptr, expr)?
|
||||||
.map(Dynamic::flatten);
|
.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:
|
// Search order:
|
||||||
// 1) Global namespace - functions registered via Engine::register_XXX
|
// 1) Global namespace - functions registered via Engine::register_XXX
|
||||||
// 2) Global modules - packages
|
// 2) Global modules - packages
|
||||||
// 3) Imported modules - functions marked with global namespace
|
// 3) Imported modules - functions marked with global namespace
|
||||||
// 4) Global sub-modules - functions marked with global namespace
|
// 4) Global sub-modules - functions marked with global namespace
|
||||||
let func = self
|
let func = self
|
||||||
.global_modules
|
.global_modules
|
||||||
.iter()
|
.iter()
|
||||||
.find_map(|m| m.get_iter(iter_type));
|
.find_map(|m| m.get_iter(iter_type));
|
||||||
|
|
||||||
#[cfg(not(feature = "no_module"))]
|
#[cfg(not(feature = "no_module"))]
|
||||||
let func = func.or_else(|| global.get_iter(iter_type)).or_else(|| {
|
let func = func.or_else(|| global.get_iter(iter_type)).or_else(|| {
|
||||||
self.global_sub_modules
|
self.global_sub_modules
|
||||||
.values()
|
.values()
|
||||||
.find_map(|m| m.get_qualified_iter(iter_type))
|
.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
|
||||||
// Add the loop variables
|
let counter_index = if counter.is_empty() {
|
||||||
let orig_scope_len = scope.len();
|
usize::MAX
|
||||||
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
|
|
||||||
} else {
|
} 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 {
|
} else {
|
||||||
iter_result
|
Err(ERR::ErrorFor(expr.start_position()).into())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -700,12 +635,12 @@ impl Engine {
|
|||||||
catch_block,
|
catch_block,
|
||||||
} = &**x;
|
} = &**x;
|
||||||
|
|
||||||
let result = self
|
match self
|
||||||
.eval_stmt_block(global, caches, lib, level, scope, this_ptr, try_block, true)
|
.eval_stmt_block(global, caches, lib, level, scope, this_ptr, try_block, true)
|
||||||
.map(|_| Dynamic::UNIT);
|
{
|
||||||
|
Ok(_) => self.eval_stmt_block(
|
||||||
match result {
|
global, caches, lib, level, scope, this_ptr, try_block, true,
|
||||||
Ok(_) => result,
|
),
|
||||||
Err(err) if err.is_pseudo_error() => Err(err),
|
Err(err) if err.is_pseudo_error() => Err(err),
|
||||||
Err(err) if !err.is_catchable() => Err(err),
|
Err(err) if !err.is_catchable() => Err(err),
|
||||||
Err(mut err) => {
|
Err(mut err) => {
|
||||||
@ -744,13 +679,18 @@ impl Engine {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Restore scope at end of block
|
||||||
let orig_scope_len = scope.len();
|
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() {
|
if !catch_var.is_empty() {
|
||||||
scope.push(catch_var.clone(), err_value);
|
scope.push(catch_var.clone(), err_value);
|
||||||
}
|
}
|
||||||
|
|
||||||
let result = self.eval_stmt_block(
|
self.eval_stmt_block(
|
||||||
global,
|
global,
|
||||||
caches,
|
caches,
|
||||||
lib,
|
lib,
|
||||||
@ -759,21 +699,16 @@ impl Engine {
|
|||||||
this_ptr,
|
this_ptr,
|
||||||
catch_block,
|
catch_block,
|
||||||
true,
|
true,
|
||||||
);
|
)
|
||||||
|
.map(|_| Dynamic::UNIT)
|
||||||
scope.rewind(orig_scope_len);
|
.map_err(|result_err| match *result_err {
|
||||||
|
// Re-throw exception
|
||||||
match result {
|
ERR::ErrorRuntime(Dynamic(Union::Unit(..)), pos) => {
|
||||||
Ok(_) => Ok(Dynamic::UNIT),
|
err.set_position(pos);
|
||||||
Err(result_err) => match *result_err {
|
err
|
||||||
// Re-throw exception
|
}
|
||||||
ERR::ErrorRuntime(Dynamic(Union::Unit(..)), pos) => {
|
_ => result_err,
|
||||||
err.set_position(pos);
|
})
|
||||||
Err(err)
|
|
||||||
}
|
|
||||||
_ => Err(result_err),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -812,7 +747,7 @@ impl Engine {
|
|||||||
let export = options.contains(ASTFlags::EXPORTED);
|
let export = options.contains(ASTFlags::EXPORTED);
|
||||||
|
|
||||||
// Check variable definition filter
|
// 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 will_shadow = scope.contains(var_name);
|
||||||
let nesting_level = global.scope_level;
|
let nesting_level = global.scope_level;
|
||||||
let is_const = access == AccessMode::ReadOnly;
|
let is_const = access == AccessMode::ReadOnly;
|
||||||
@ -824,74 +759,56 @@ impl Engine {
|
|||||||
};
|
};
|
||||||
let context = EvalContext::new(self, global, None, lib, level, scope, this_ptr);
|
let context = EvalContext::new(self, global, None, lib, level, scope, this_ptr);
|
||||||
|
|
||||||
match filter(true, info, context) {
|
if !filter(true, info, context)? {
|
||||||
Ok(true) => None,
|
return Err(ERR::ErrorForbiddenVariable(var_name.to_string(), *pos).into());
|
||||||
Ok(false) => {
|
|
||||||
Some(Err(
|
|
||||||
ERR::ErrorForbiddenVariable(var_name.to_string(), *pos).into()
|
|
||||||
))
|
|
||||||
}
|
|
||||||
err @ Err(_) => Some(err),
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 {
|
} else {
|
||||||
None
|
None
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Some(result) = result {
|
if let Some(index) = index {
|
||||||
result.map(|_| Dynamic::UNIT)
|
value.set_access_mode(access);
|
||||||
|
*scope.get_mut_by_index(scope.len() - index.get()) = value;
|
||||||
} else {
|
} else {
|
||||||
// Evaluate initial value
|
scope.push_entry(var_name.name.clone(), access, 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
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[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
|
// Import statement
|
||||||
@ -904,66 +821,52 @@ impl Engine {
|
|||||||
return Err(ERR::ErrorTooManyModules(*_pos).into());
|
return Err(ERR::ErrorTooManyModules(*_pos).into());
|
||||||
}
|
}
|
||||||
|
|
||||||
let path_result = self
|
let v = self.eval_expr(global, caches, lib, level, scope, this_ptr, expr)?;
|
||||||
.eval_expr(global, caches, lib, level, scope, this_ptr, expr)
|
let typ = v.type_name();
|
||||||
.and_then(|v| {
|
let path = v.try_cast::<crate::ImmutableString>().ok_or_else(|| {
|
||||||
let typ = v.type_name();
|
self.make_type_mismatch_err::<crate::ImmutableString>(typ, expr.position())
|
||||||
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
|
let module = resolver
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.and_then(|r| match r.resolve_raw(self, global, &path, path_pos) {
|
.and_then(|r| match r.resolve_raw(self, global, &path, path_pos) {
|
||||||
Err(err) if matches!(*err, ERR::ErrorModuleNotFound(..)) => None,
|
Err(err) if matches!(*err, ERR::ErrorModuleNotFound(..)) => None,
|
||||||
result => Some(result),
|
result => Some(result),
|
||||||
})
|
})
|
||||||
.or_else(|| {
|
.or_else(|| {
|
||||||
Some(
|
Some(
|
||||||
self.module_resolver
|
self.module_resolver
|
||||||
.resolve_raw(self, global, &path, path_pos),
|
.resolve_raw(self, global, &path, path_pos),
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
.unwrap_or_else(|| {
|
.unwrap_or_else(|| {
|
||||||
Err(ERR::ErrorModuleNotFound(path.to_string(), path_pos).into())
|
Err(ERR::ErrorModuleNotFound(path.to_string(), path_pos).into())
|
||||||
});
|
})?;
|
||||||
|
|
||||||
if let Ok(module) = module_result {
|
let (export, must_be_indexed) = if !export.is_empty() {
|
||||||
let (export, must_be_indexed) = if !export.is_empty() {
|
(export.name.clone(), true)
|
||||||
(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)
|
|
||||||
}
|
|
||||||
} else {
|
} 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
|
// Export statement
|
||||||
@ -1004,12 +907,7 @@ impl Engine {
|
|||||||
}
|
}
|
||||||
|
|
||||||
_ => unreachable!("statement cannot be evaluated: {:?}", stmt),
|
_ => 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.
|
/// 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::eval::{Caches, FnResolutionCacheEntry, GlobalRuntimeState};
|
||||||
use crate::tokenizer::{is_valid_function_name, Token};
|
use crate::tokenizer::{is_valid_function_name, Token};
|
||||||
|
use crate::types::RestoreOnDrop;
|
||||||
use crate::{
|
use crate::{
|
||||||
calc_fn_hash, calc_fn_hash_full, Dynamic, Engine, FnArgsVec, FnPtr, ImmutableString, Module,
|
calc_fn_hash, calc_fn_hash_full, Dynamic, Engine, FnArgsVec, FnPtr, ImmutableString, Module,
|
||||||
OptimizationLevel, Position, RhaiError, RhaiResult, RhaiResultOf, Scope, Shared, ERR,
|
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_
|
/// 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.
|
/// exiting the current scope. Otherwise it is undefined behavior as the shorter lifetime will leak.
|
||||||
#[inline(always)]
|
#[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() {
|
if let Some(p) = self.orig_mut.take() {
|
||||||
args[0] = p;
|
args[0] = p;
|
||||||
|
} else {
|
||||||
|
unreachable!("`Some`");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -327,7 +330,7 @@ impl Engine {
|
|||||||
name: &str,
|
name: &str,
|
||||||
op_token: Option<&Token>,
|
op_token: Option<&Token>,
|
||||||
hash: u64,
|
hash: u64,
|
||||||
args: &mut FnCallArgs,
|
mut args: &mut FnCallArgs,
|
||||||
is_ref_mut: bool,
|
is_ref_mut: bool,
|
||||||
pos: Position,
|
pos: Position,
|
||||||
) -> RhaiResultOf<(Dynamic, bool)> {
|
) -> RhaiResultOf<(Dynamic, bool)> {
|
||||||
@ -354,48 +357,45 @@ impl Engine {
|
|||||||
#[cfg(feature = "debugging")]
|
#[cfg(feature = "debugging")]
|
||||||
let orig_call_stack_len = global.debugger.call_stack().len();
|
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());
|
assert!(func.is_native());
|
||||||
|
|
||||||
let mut backup = ArgBackup::new();
|
let backup = &mut ArgBackup::new();
|
||||||
|
|
||||||
// Calling pure function but the first argument is a reference?
|
// 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();
|
||||||
// Clone the first argument
|
|
||||||
backup.change_first_arg_to_copy(args);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "debugging")]
|
if swap {
|
||||||
if self.debugger.is_some() {
|
// Clone the first argument
|
||||||
global.debugger.push_call_stack_frame(
|
backup.change_first_arg_to_copy(args);
|
||||||
self.get_interned_string(name),
|
}
|
||||||
args.iter().map(|v| (*v).clone()).collect(),
|
|
||||||
source.clone().or_else(|| global.source.clone()),
|
|
||||||
pos,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Run external function
|
let args =
|
||||||
let src = source.as_ref().map(|s| s.as_str());
|
&mut *RestoreOnDrop::new_if(swap, &mut args, move |a| backup.restore_first_arg(a));
|
||||||
let context = (self, name, src, &*global, lib, pos, level).into();
|
|
||||||
|
|
||||||
let result = if func.is_plugin_fn() {
|
#[cfg(feature = "debugging")]
|
||||||
let f = func.get_plugin_fn().unwrap();
|
if self.debugger.is_some() {
|
||||||
if !f.is_pure() && !args.is_empty() && args[0].is_read_only() {
|
global.debugger.push_call_stack_frame(
|
||||||
Err(ERR::ErrorNonPureMethodCallOnConstant(name.to_string(), pos).into())
|
self.get_interned_string(name),
|
||||||
} else {
|
args.iter().map(|v| (*v).clone()).collect(),
|
||||||
f.call(context, args)
|
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 {
|
} else {
|
||||||
func.get_native_fn().unwrap()(context, args)
|
f.call(context, args)
|
||||||
};
|
}
|
||||||
|
|
||||||
// Restore the original reference
|
|
||||||
backup.restore_first_arg(args);
|
|
||||||
|
|
||||||
result
|
|
||||||
} else {
|
} else {
|
||||||
unreachable!("`Some`");
|
func.get_native_fn().unwrap()(context, args)
|
||||||
};
|
};
|
||||||
|
|
||||||
#[cfg(feature = "debugging")]
|
#[cfg(feature = "debugging")]
|
||||||
@ -413,11 +413,11 @@ impl Engine {
|
|||||||
Ok(ref r) => crate::eval::DebuggerEvent::FunctionExitWithValue(r),
|
Ok(ref r) => crate::eval::DebuggerEvent::FunctionExitWithValue(r),
|
||||||
Err(ref err) => crate::eval::DebuggerEvent::FunctionExitWithError(err),
|
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)
|
.run_debugger_raw(global, caches, lib, level, scope, &mut None, node, event)
|
||||||
{
|
{
|
||||||
Ok(_) => (),
|
_result = Err(err);
|
||||||
Err(err) => _result = Err(err),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -543,7 +543,7 @@ impl Engine {
|
|||||||
fn_name: &str,
|
fn_name: &str,
|
||||||
op_token: Option<&Token>,
|
op_token: Option<&Token>,
|
||||||
hashes: FnCallHashes,
|
hashes: FnCallHashes,
|
||||||
args: &mut FnCallArgs,
|
mut args: &mut FnCallArgs,
|
||||||
is_ref_mut: bool,
|
is_ref_mut: bool,
|
||||||
_is_method_call: bool,
|
_is_method_call: bool,
|
||||||
pos: Position,
|
pos: Position,
|
||||||
@ -613,19 +613,11 @@ impl Engine {
|
|||||||
#[cfg(not(feature = "no_function"))]
|
#[cfg(not(feature = "no_function"))]
|
||||||
if !hashes.is_native_only() {
|
if !hashes.is_native_only() {
|
||||||
// Script-defined function call?
|
// Script-defined function call?
|
||||||
|
let hash = hashes.script();
|
||||||
let local_entry = &mut None;
|
let local_entry = &mut None;
|
||||||
|
|
||||||
if let Some(FnResolutionCacheEntry { func, ref source }) = self
|
if let Some(FnResolutionCacheEntry { func, ref source }) = self
|
||||||
.resolve_fn(
|
.resolve_fn(global, caches, local_entry, lib, None, hash, None, false)
|
||||||
global,
|
|
||||||
caches,
|
|
||||||
local_entry,
|
|
||||||
lib,
|
|
||||||
None,
|
|
||||||
hashes.script(),
|
|
||||||
None,
|
|
||||||
false,
|
|
||||||
)
|
|
||||||
.cloned()
|
.cloned()
|
||||||
{
|
{
|
||||||
// Script function call
|
// Script function call
|
||||||
@ -647,8 +639,9 @@ impl Engine {
|
|||||||
};
|
};
|
||||||
|
|
||||||
let orig_source = mem::replace(&mut global.source, source.clone());
|
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`
|
// Method call of script function - map first argument to `this`
|
||||||
let (first_arg, rest_args) = args.split_first_mut().unwrap();
|
let (first_arg, rest_args) = args.split_first_mut().unwrap();
|
||||||
|
|
||||||
@ -666,27 +659,24 @@ impl Engine {
|
|||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
// Normal call of script function
|
// Normal call of script function
|
||||||
let mut backup = ArgBackup::new();
|
let backup = &mut ArgBackup::new();
|
||||||
|
|
||||||
// The first argument is a reference?
|
// 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);
|
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,
|
global, caches, lib, level, scope, &mut None, func, args, true, pos,
|
||||||
);
|
)
|
||||||
|
}
|
||||||
// Restore the original reference
|
.map(|r| (r, false));
|
||||||
backup.restore_first_arg(args);
|
|
||||||
|
|
||||||
result
|
|
||||||
};
|
|
||||||
|
|
||||||
// Restore the original source
|
|
||||||
global.source = orig_source;
|
|
||||||
|
|
||||||
return result.map(|r| (r, false));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -722,17 +712,14 @@ impl Engine {
|
|||||||
|
|
||||||
// Do not match function exit for arguments
|
// Do not match function exit for arguments
|
||||||
#[cfg(feature = "debugging")]
|
#[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(..))
|
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")]
|
#[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.
|
/// Call a dot method.
|
||||||
@ -1389,15 +1376,13 @@ impl Engine {
|
|||||||
Some(f) if f.is_script() => {
|
Some(f) if f.is_script() => {
|
||||||
let fn_def = f.get_script_fn_def().expect("script-defined function");
|
let fn_def = f.get_script_fn_def().expect("script-defined function");
|
||||||
let new_scope = &mut Scope::new();
|
let new_scope = &mut Scope::new();
|
||||||
|
|
||||||
let orig_source = mem::replace(&mut global.source, module.id_raw().cloned());
|
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, 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() => {
|
Some(f) if f.is_plugin_fn() => {
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
//! A strings interner type.
|
||||||
|
|
||||||
use super::BloomFilterU64;
|
use super::BloomFilterU64;
|
||||||
use crate::func::{hashing::get_hasher, StraightHashMap};
|
use crate::func::{hashing::get_hasher, StraightHashMap};
|
||||||
use crate::ImmutableString;
|
use crate::ImmutableString;
|
||||||
@ -20,10 +22,8 @@ pub const MAX_INTERNED_STRINGS: usize = 1024;
|
|||||||
/// Maximum length of strings interned.
|
/// Maximum length of strings interned.
|
||||||
pub const MAX_STRING_LEN: usize = 24;
|
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.
|
/// Exported under the `internals` feature only.
|
||||||
///
|
|
||||||
/// Normal identifiers, property getters and setters are interned separately.
|
|
||||||
pub struct StringsInterner<'a> {
|
pub struct StringsInterner<'a> {
|
||||||
/// Maximum number of strings interned.
|
/// Maximum number of strings interned.
|
||||||
pub capacity: usize,
|
pub capacity: usize,
|
||||||
@ -103,43 +103,48 @@ impl StringsInterner<'_> {
|
|||||||
if value.strong_count() > 1 {
|
if value.strong_count() > 1 {
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
e.insert(value).clone()
|
e.insert(value).clone()
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// If the interner is over capacity, remove the longest entry that has the lowest count
|
// Throttle the cache upon exit
|
||||||
if self.cache.len() > self.capacity {
|
self.throttle_cache(hash);
|
||||||
// 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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
result
|
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.
|
/// Number of strings interned.
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
#[must_use]
|
#[must_use]
|
||||||
|
@ -8,6 +8,7 @@ pub mod fn_ptr;
|
|||||||
pub mod immutable_string;
|
pub mod immutable_string;
|
||||||
pub mod interner;
|
pub mod interner;
|
||||||
pub mod parse_error;
|
pub mod parse_error;
|
||||||
|
pub mod restore;
|
||||||
pub mod scope;
|
pub mod scope;
|
||||||
pub mod variant;
|
pub mod variant;
|
||||||
|
|
||||||
@ -21,5 +22,6 @@ pub use fn_ptr::FnPtr;
|
|||||||
pub use immutable_string::ImmutableString;
|
pub use immutable_string::ImmutableString;
|
||||||
pub use interner::StringsInterner;
|
pub use interner::StringsInterner;
|
||||||
pub use parse_error::{LexError, ParseError, ParseErrorType};
|
pub use parse_error::{LexError, ParseError, ParseErrorType};
|
||||||
|
pub use restore::RestoreOnDrop;
|
||||||
pub use scope::Scope;
|
pub use scope::Scope;
|
||||||
pub use variant::Variant;
|
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…
x
Reference in New Issue
Block a user