Fix function exit trigger and add function enter trigger.

This commit is contained in:
Stephen Chung 2022-02-02 22:42:33 +08:00
parent db2f1a601c
commit 8322e62c18
9 changed files with 115 additions and 68 deletions

View File

@ -576,7 +576,7 @@ Breaking changes
New features
------------
* Line continuation (via `\`) and multi-line literal strings (wrapped with <code>\`</code>) support are added.
* Line continuation (via `\`) and multi-line literal strings (wrapped with `` ` ``) support are added.
* Rhai scripts can now start with a shebang `#!` which is ignored.
Enhancements

View File

@ -190,8 +190,8 @@ impl Engine {
&mut this_ptr,
fn_def,
&mut args,
Position::NONE,
rewind_scope,
Position::NONE,
0,
);

View File

@ -191,8 +191,8 @@ impl Engine {
let fn_name = crate::engine::FN_IDX_SET;
if let Err(err) = self.exec_fn_call(
global, state, lib, fn_name, hash_set, args, is_ref_mut, true,
root_pos, None, level,
None, global, state, lib, fn_name, hash_set, args, is_ref_mut,
true, root_pos, level,
) {
// Just ignore if there is no index setter
if !matches!(*err, ERR::ErrorFunctionNotFound(_, _)) {
@ -244,8 +244,8 @@ impl Engine {
let fn_name = crate::engine::FN_IDX_SET;
self.exec_fn_call(
global, state, lib, fn_name, hash_set, args, is_ref_mut, true,
root_pos, None, level,
None, global, state, lib, fn_name, hash_set, args, is_ref_mut,
true, root_pos, level,
)?;
}
@ -351,8 +351,8 @@ impl Engine {
let args = &mut [target.as_mut()];
let (mut orig_val, _) = self
.exec_fn_call(
global, state, lib, getter, hash, args, is_ref_mut, true, *pos,
None, level,
None, global, state, lib, getter, hash, args, is_ref_mut, true,
*pos, level,
)
.or_else(|err| match *err {
// Try an indexer if property does not exist
@ -392,7 +392,7 @@ impl Engine {
let hash = crate::ast::FnCallHashes::from_native(*hash_set);
let args = &mut [target.as_mut(), &mut new_val];
self.exec_fn_call(
global, state, lib, setter, hash, args, is_ref_mut, true, *pos, None,
None, global, state, lib, setter, hash, args, is_ref_mut, true, *pos,
level,
)
.or_else(|err| match *err {
@ -405,8 +405,8 @@ impl Engine {
let pos = Position::NONE;
self.exec_fn_call(
global, state, lib, fn_name, hash_set, args, is_ref_mut, true,
pos, None, level,
None, global, state, lib, fn_name, hash_set, args, is_ref_mut,
true, pos, level,
)
.map_err(
|idx_err| match *idx_err {
@ -429,7 +429,7 @@ impl Engine {
let hash = crate::ast::FnCallHashes::from_native(*hash_get);
let args = &mut [target.as_mut()];
self.exec_fn_call(
global, state, lib, getter, hash, args, is_ref_mut, true, *pos, None,
None, global, state, lib, getter, hash, args, is_ref_mut, true, *pos,
level,
)
.map_or_else(
@ -537,8 +537,8 @@ impl Engine {
// Assume getters are always pure
let (mut val, _) = self
.exec_fn_call(
global, state, lib, getter, hash_get, args, is_ref_mut,
true, pos, None, level,
None, global, state, lib, getter, hash_get, args,
is_ref_mut, true, pos, level,
)
.or_else(|err| match *err {
// Try an indexer if property does not exist
@ -585,8 +585,8 @@ impl Engine {
let mut arg_values = [target.as_mut(), val];
let args = &mut arg_values;
self.exec_fn_call(
global, state, lib, setter, hash_set, args, is_ref_mut,
true, pos, None, level,
None, global, state, lib, setter, hash_set, args,
is_ref_mut, true, pos, level,
)
.or_else(
|err| match *err {
@ -600,8 +600,8 @@ impl Engine {
global.hash_idx_set(),
);
self.exec_fn_call(
global, state, lib, fn_name, hash_set, args,
is_ref_mut, true, pos, None, level,
None, global, state, lib, fn_name, hash_set,
args, is_ref_mut, true, pos, level,
)
.or_else(|idx_err| match *idx_err {
ERR::ErrorIndexingType(_, _) => {
@ -767,7 +767,7 @@ impl Engine {
(crate::FnArgsVec::with_capacity(args.len()), Position::NONE),
|(mut values, mut pos), expr| -> RhaiResultOf<_> {
let (value, arg_pos) = self.get_arg_value(
scope, global, state, lib, this_ptr, level, expr, constants,
scope, global, state, lib, this_ptr, expr, constants, level,
)?;
if values.is_empty() {
pos = arg_pos;
@ -813,7 +813,7 @@ impl Engine {
(crate::FnArgsVec::with_capacity(args.len()), Position::NONE),
|(mut values, mut pos), expr| -> RhaiResultOf<_> {
let (value, arg_pos) = self.get_arg_value(
scope, global, state, lib, this_ptr, level, expr, constants,
scope, global, state, lib, this_ptr, expr, constants, level,
)?;
if values.is_empty() {
pos = arg_pos
@ -1079,7 +1079,7 @@ impl Engine {
let pos = Position::NONE;
self.exec_fn_call(
global, state, lib, fn_name, hash_get, args, true, true, pos, None, level,
None, global, state, lib, fn_name, hash_get, args, true, true, pos, level,
)
.map(|(v, _)| v.into())
}

View File

@ -51,6 +51,13 @@ pub enum DebuggerCommand {
FunctionExit,
}
impl Default for DebuggerCommand {
#[inline(always)]
fn default() -> Self {
Self::Continue
}
}
/// The debugger status.
#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
pub enum DebuggerStatus {
@ -60,6 +67,19 @@ pub enum DebuggerStatus {
FunctionExit(usize),
}
impl Default for DebuggerStatus {
#[inline(always)]
fn default() -> Self {
Self::CONTINUE
}
}
impl DebuggerStatus {
pub const CONTINUE: Self = Self::Next(false, false);
pub const STEP: Self = Self::Next(true, true);
pub const NEXT: Self = Self::Next(true, false);
}
/// A event that triggers the debugger.
#[derive(Debug, Clone, Copy)]
pub enum DebuggerEvent<'a> {
@ -231,7 +251,7 @@ impl fmt::Display for CallStackFrame {
#[derive(Debug, Clone, Hash)]
pub struct Debugger {
/// The current status command.
status: DebuggerStatus,
pub(crate) status: DebuggerStatus,
/// The current state.
state: Dynamic,
/// The current set of break-points.
@ -247,9 +267,9 @@ impl Debugger {
pub fn new(engine: &Engine) -> Self {
Self {
status: if engine.debugger.is_some() {
DebuggerStatus::Next(true, true)
DebuggerStatus::STEP
} else {
DebuggerStatus::Next(false, false)
DebuggerStatus::CONTINUE
},
state: if let Some((ref init, _)) = engine.debugger {
init()
@ -299,12 +319,6 @@ impl Debugger {
pos,
});
}
/// Get the current status of this [`Debugger`].
#[inline(always)]
#[must_use]
pub(crate) fn status(&self) -> DebuggerStatus {
self.status
}
/// Set the status of this [`Debugger`].
#[inline(always)]
pub(crate) fn reset_status(&mut self, status: Option<DebuggerStatus>) {
@ -476,19 +490,19 @@ impl Engine {
match command {
DebuggerCommand::Continue => {
global.debugger.status = DebuggerStatus::Next(false, false);
global.debugger.status = DebuggerStatus::CONTINUE;
Ok(None)
}
DebuggerCommand::Next => {
global.debugger.status = DebuggerStatus::Next(false, false);
Ok(Some(DebuggerStatus::Next(true, false)))
global.debugger.status = DebuggerStatus::CONTINUE;
Ok(Some(DebuggerStatus::NEXT))
}
DebuggerCommand::StepOver => {
global.debugger.status = DebuggerStatus::Next(false, false);
Ok(Some(DebuggerStatus::Next(true, true)))
global.debugger.status = DebuggerStatus::CONTINUE;
Ok(Some(DebuggerStatus::STEP))
}
DebuggerCommand::StepInto => {
global.debugger.status = DebuggerStatus::Next(true, true);
global.debugger.status = DebuggerStatus::STEP;
Ok(None)
}
DebuggerCommand::FunctionExit => {
@ -499,6 +513,7 @@ impl Engine {
| ASTNode::Stmt(Stmt::Expr(Expr::FnCall(_, _))) => context.call_level() + 1,
_ => context.call_level(),
};
println!("Set FunctionExit to {}", level);
global.debugger.status = DebuggerStatus::FunctionExit(level);
Ok(None)
}

View File

@ -236,8 +236,8 @@ impl Engine {
);
self.make_function_call(
scope, global, state, lib, this_ptr, name, first_arg, args, constants, *hashes, pos,
*capture, level,
scope, global, state, lib, this_ptr, name, first_arg, args, constants, *hashes,
*capture, pos, level,
)
}

View File

@ -153,6 +153,7 @@ impl Engine {
let hash = hash_op_assign;
let args = &mut [lhs_ptr_inner, &mut new_val];
let level = level + 1;
match self.call_native_fn(
global, state, lib, op_assign, hash, args, true, true, op_pos, level,

View File

@ -427,7 +427,7 @@ impl Engine {
#[cfg(feature = "debugging")]
if self.debugger.is_some() {
match global.debugger.status() {
match global.debugger.status {
crate::eval::DebuggerStatus::FunctionExit(n) if n >= level => {
let scope = &mut &mut Scope::new();
let node = crate::ast::Stmt::Noop(pos);
@ -591,6 +591,7 @@ impl Engine {
/// **DO NOT** reuse the argument values unless for the first `&mut` argument - all others are silently replaced by `()`!
pub(crate) fn exec_fn_call(
&self,
scope: Option<&mut Scope>,
global: &mut GlobalRuntimeState,
state: &mut EvalState,
lib: &[&Module],
@ -600,7 +601,6 @@ impl Engine {
is_ref_mut: bool,
is_method_call: bool,
pos: Position,
scope: Option<&mut Scope>,
level: usize,
) -> RhaiResultOf<(Dynamic, bool)> {
fn no_method_err(name: &str, pos: Position) -> RhaiResultOf<(Dynamic, bool)> {
@ -711,8 +711,8 @@ impl Engine {
&mut Some(*first_arg),
func,
rest_args,
pos,
true,
pos,
level,
);
@ -730,7 +730,7 @@ impl Engine {
}
let result = self.call_script_fn(
scope, global, state, lib, &mut None, func, args, pos, true, level,
scope, global, state, lib, &mut None, func, args, true, pos, level,
);
// Restore the original reference
@ -812,7 +812,7 @@ impl Engine {
// Map it to name(args) in function-call style
self.exec_fn_call(
global, state, lib, fn_name, new_hash, &mut args, false, false, pos, None,
None, global, state, lib, fn_name, new_hash, &mut args, false, false, pos,
level,
)
}
@ -852,7 +852,7 @@ impl Engine {
// Map it to name(args) in function-call style
self.exec_fn_call(
global, state, lib, fn_name, new_hash, &mut args, is_ref_mut, true, pos, None,
None, global, state, lib, fn_name, new_hash, &mut args, is_ref_mut, true, pos,
level,
)
}
@ -924,7 +924,7 @@ impl Engine {
args.extend(call_args.iter_mut());
self.exec_fn_call(
global, state, lib, fn_name, hash, &mut args, is_ref_mut, true, pos, None,
None, global, state, lib, fn_name, hash, &mut args, is_ref_mut, true, pos,
level,
)
}
@ -949,9 +949,9 @@ impl Engine {
state: &mut EvalState,
lib: &[&Module],
this_ptr: &mut Option<&mut Dynamic>,
level: usize,
arg_expr: &Expr,
constants: &[Dynamic],
level: usize,
) -> RhaiResultOf<(Dynamic, Position)> {
Ok((
if let Expr::Stack(slot, _) = arg_expr {
@ -967,7 +967,26 @@ impl Engine {
}
value
} else {
self.eval_expr(scope, global, state, lib, this_ptr, arg_expr, level)?
// Do not match function exit for arguments
#[cfg(feature = "debugging")]
let reset_debugger = match global.debugger.status {
crate::eval::DebuggerStatus::FunctionExit(_) => {
Some(std::mem::take(&mut global.debugger.status))
}
_ => None,
};
let result = self.eval_expr(scope, global, state, lib, this_ptr, arg_expr, level);
// Restore function exit if status is not active
#[cfg(feature = "debugging")]
if self.debugger.is_some()
&& global.debugger.status == crate::eval::DebuggerStatus::CONTINUE
{
global.debugger.reset_status(reset_debugger);
}
result?
},
arg_expr.position(),
))
@ -986,8 +1005,8 @@ impl Engine {
args_expr: &[Expr],
constants: &[Dynamic],
hashes: FnCallHashes,
pos: Position,
capture_scope: bool,
pos: Position,
level: usize,
) -> RhaiResult {
let mut first_arg = first_arg;
@ -1003,7 +1022,7 @@ impl Engine {
KEYWORD_FN_PTR_CALL if total_args >= 1 => {
let arg = first_arg.unwrap();
let (arg_value, arg_pos) =
self.get_arg_value(scope, global, state, lib, this_ptr, level, arg, constants)?;
self.get_arg_value(scope, global, state, lib, this_ptr, arg, constants, level)?;
if !arg_value.is::<FnPtr>() {
return Err(self.make_type_mismatch_err::<FnPtr>(
@ -1038,7 +1057,7 @@ impl Engine {
KEYWORD_FN_PTR if total_args == 1 => {
let arg = first_arg.unwrap();
let (arg_value, arg_pos) =
self.get_arg_value(scope, global, state, lib, this_ptr, level, arg, constants)?;
self.get_arg_value(scope, global, state, lib, this_ptr, arg, constants, level)?;
// Fn - only in function call style
return arg_value
@ -1053,7 +1072,7 @@ impl Engine {
KEYWORD_FN_PTR_CURRY if total_args > 1 => {
let first = first_arg.unwrap();
let (arg_value, arg_pos) = self
.get_arg_value(scope, global, state, lib, this_ptr, level, first, constants)?;
.get_arg_value(scope, global, state, lib, this_ptr, first, constants, level)?;
if !arg_value.is::<FnPtr>() {
return Err(self.make_type_mismatch_err::<FnPtr>(
@ -1070,7 +1089,7 @@ impl Engine {
.iter()
.try_fold(fn_curry, |mut curried, expr| -> RhaiResultOf<_> {
let (value, _) = self.get_arg_value(
scope, global, state, lib, this_ptr, level, expr, constants,
scope, global, state, lib, this_ptr, expr, constants, level,
)?;
curried.push(value);
Ok(curried)
@ -1084,7 +1103,7 @@ impl Engine {
crate::engine::KEYWORD_IS_SHARED if total_args == 1 => {
let arg = first_arg.unwrap();
let (arg_value, _) =
self.get_arg_value(scope, global, state, lib, this_ptr, level, arg, constants)?;
self.get_arg_value(scope, global, state, lib, this_ptr, arg, constants, level)?;
return Ok(arg_value.is_shared().into());
}
@ -1093,14 +1112,14 @@ impl Engine {
crate::engine::KEYWORD_IS_DEF_FN if total_args == 2 => {
let first = first_arg.unwrap();
let (arg_value, arg_pos) = self
.get_arg_value(scope, global, state, lib, this_ptr, level, first, constants)?;
.get_arg_value(scope, global, state, lib, this_ptr, first, constants, level)?;
let fn_name = arg_value
.into_immutable_string()
.map_err(|typ| self.make_type_mismatch_err::<ImmutableString>(typ, arg_pos))?;
let (arg_value, arg_pos) = self.get_arg_value(
scope, global, state, lib, this_ptr, level, &a_expr[0], constants,
scope, global, state, lib, this_ptr, &a_expr[0], constants, level,
)?;
let num_params = arg_value
@ -1120,7 +1139,7 @@ impl Engine {
KEYWORD_IS_DEF_VAR if total_args == 1 => {
let arg = first_arg.unwrap();
let (arg_value, arg_pos) =
self.get_arg_value(scope, global, state, lib, this_ptr, level, arg, constants)?;
self.get_arg_value(scope, global, state, lib, this_ptr, arg, constants, level)?;
let var_name = arg_value
.into_immutable_string()
.map_err(|typ| self.make_type_mismatch_err::<ImmutableString>(typ, arg_pos))?;
@ -1133,7 +1152,7 @@ impl Engine {
let orig_scope_len = scope.len();
let arg = first_arg.unwrap();
let (arg_value, pos) =
self.get_arg_value(scope, global, state, lib, this_ptr, level, arg, constants)?;
self.get_arg_value(scope, global, state, lib, this_ptr, arg, constants, level)?;
let script = &arg_value
.into_immutable_string()
.map_err(|typ| self.make_type_mismatch_err::<ImmutableString>(typ, pos))?;
@ -1182,7 +1201,7 @@ impl Engine {
.map(|&v| v)
.chain(a_expr.iter())
.try_for_each(|expr| {
self.get_arg_value(scope, global, state, lib, this_ptr, level, expr, constants)
self.get_arg_value(scope, global, state, lib, this_ptr, expr, constants, level)
.map(|(value, _)| arg_values.push(value.flatten()))
})?;
args.extend(curry.iter_mut());
@ -1193,7 +1212,7 @@ impl Engine {
return self
.exec_fn_call(
global, state, lib, name, hashes, &mut args, is_ref_mut, false, pos, scope,
scope, global, state, lib, name, hashes, &mut args, is_ref_mut, false, pos,
level,
)
.map(|(v, _)| v);
@ -1216,7 +1235,7 @@ impl Engine {
// func(x, ...) -> x.func(...)
a_expr.iter().try_for_each(|expr| {
self.get_arg_value(scope, global, state, lib, this_ptr, level, expr, constants)
self.get_arg_value(scope, global, state, lib, this_ptr, expr, constants, level)
.map(|(value, _)| arg_values.push(value.flatten()))
})?;
@ -1252,7 +1271,7 @@ impl Engine {
.chain(a_expr.iter())
.try_for_each(|expr| {
self.get_arg_value(
scope, global, state, lib, this_ptr, level, expr, constants,
scope, global, state, lib, this_ptr, expr, constants, level,
)
.map(|(value, _)| arg_values.push(value.flatten()))
})?;
@ -1262,7 +1281,7 @@ impl Engine {
}
self.exec_fn_call(
global, state, lib, name, hashes, &mut args, is_ref_mut, false, pos, None, level,
None, global, state, lib, name, hashes, &mut args, is_ref_mut, false, pos, level,
)
.map(|(v, _)| v)
}
@ -1305,7 +1324,7 @@ impl Engine {
arg_values.push(Dynamic::UNIT);
args_expr.iter().skip(1).try_for_each(|expr| {
self.get_arg_value(scope, global, state, lib, this_ptr, level, expr, constants)
self.get_arg_value(scope, global, state, lib, this_ptr, expr, constants, level)
.map(|(value, _)| arg_values.push(value.flatten()))
})?;
@ -1335,7 +1354,7 @@ impl Engine {
} else {
// func(..., ...) or func(mod::x, ...)
args_expr.iter().try_for_each(|expr| {
self.get_arg_value(scope, global, state, lib, this_ptr, level, expr, constants)
self.get_arg_value(scope, global, state, lib, this_ptr, expr, constants, level)
.map(|(value, _)| arg_values.push(value.flatten()))
})?;
args.extend(arg_values.iter_mut());
@ -1385,7 +1404,7 @@ impl Engine {
mem::swap(&mut global.source, &mut source);
let result = self.call_script_fn(
new_scope, global, state, lib, &mut None, fn_def, &mut args, pos, true,
new_scope, global, state, lib, &mut None, fn_def, &mut args, true, pos,
level,
);

View File

@ -321,6 +321,7 @@ impl<'a> NativeCallContext<'a> {
self.engine()
.exec_fn_call(
None,
&mut global,
&mut state,
self.lib,
@ -330,7 +331,6 @@ impl<'a> NativeCallContext<'a> {
is_ref_mut,
is_method_call,
Position::NONE,
None,
self.level + 1,
)
.map(|(r, _)| r)

View File

@ -31,8 +31,8 @@ impl Engine {
this_ptr: &mut Option<&mut Dynamic>,
fn_def: &ScriptFnDef,
args: &mut FnCallArgs,
pos: Position,
rewind_scope: bool,
pos: Position,
level: usize,
) -> RhaiResult {
#[inline(never)]
@ -73,6 +73,11 @@ impl Engine {
return Err(ERR::ErrorStackOverflow(pos).into());
}
#[cfg(feature = "debugging")]
if self.debugger.is_none() && fn_def.body.is_empty() {
return Ok(Dynamic::UNIT);
}
#[cfg(not(feature = "debugging"))]
if fn_def.body.is_empty() {
return Ok(Dynamic::UNIT);
}
@ -136,6 +141,13 @@ impl Engine {
(lib, None)
};
#[cfg(feature = "debugging")]
if self.debugger.is_some() {
println!("Level = {}", level);
let node = crate::ast::Stmt::Noop(fn_def.body.position());
self.run_debugger(scope, global, state, lib, this_ptr, &node, level)?;
}
// Evaluate the function
let mut _result = self
.eval_stmt_block(
@ -172,7 +184,7 @@ impl Engine {
#[cfg(feature = "debugging")]
if self.debugger.is_some() {
match global.debugger.status() {
match global.debugger.status {
crate::eval::DebuggerStatus::FunctionExit(n) if n >= level => {
let node = crate::ast::Stmt::Noop(pos);
let node = (&node).into();