Refine debugger.

This commit is contained in:
Stephen Chung
2022-01-25 12:24:30 +08:00
parent fc87dec128
commit 40aaab60c3
8 changed files with 334 additions and 158 deletions

View File

@@ -125,6 +125,7 @@ impl Engine {
this_ptr: &mut Option<&mut Dynamic>,
target: &mut Target,
root: (&str, Position),
parent: &Expr,
rhs: &Expr,
terminate_chaining: bool,
idx_values: &mut StaticVec<super::ChainArgument>,
@@ -138,6 +139,9 @@ impl Engine {
// Pop the last index value
let idx_val = idx_values.pop().unwrap();
#[cfg(feature = "debugging")]
let scope = &mut Scope::new();
match chain_type {
#[cfg(not(feature = "no_index"))]
ChainType::Indexing => {
@@ -150,6 +154,9 @@ impl Engine {
Expr::Dot(x, term, x_pos) | Expr::Index(x, term, x_pos)
if !_terminate_chaining =>
{
#[cfg(feature = "debugging")]
self.run_debugger(scope, global, state, lib, this_ptr, parent, level);
let mut idx_val_for_setter = idx_val.clone();
let idx_pos = x.lhs.position();
let rhs_chain = rhs.into();
@@ -162,7 +169,7 @@ impl Engine {
let obj_ptr = &mut obj;
match self.eval_dot_index_chain_helper(
global, state, lib, this_ptr, obj_ptr, root, &x.rhs, *term,
global, state, lib, this_ptr, obj_ptr, root, rhs, &x.rhs, *term,
idx_values, rhs_chain, level, new_val,
) {
Ok((result, true)) if is_obj_temp_val => {
@@ -195,6 +202,9 @@ impl Engine {
}
// xxx[rhs] op= new_val
_ if new_val.is_some() => {
#[cfg(feature = "debugging")]
self.run_debugger(scope, global, state, lib, this_ptr, parent, level);
let ((new_val, new_pos), (op_info, op_pos)) = new_val.expect("`Some`");
let mut idx_val_for_setter = idx_val.clone();
@@ -236,11 +246,15 @@ impl Engine {
Ok((Dynamic::UNIT, true))
}
// xxx[rhs]
_ => self
.get_indexed_mut(
_ => {
#[cfg(feature = "debugging")]
self.run_debugger(scope, global, state, lib, this_ptr, parent, level);
self.get_indexed_mut(
global, state, lib, target, idx_val, pos, false, true, level,
)
.map(|v| (v.take_or_clone(), false)),
.map(|v| (v.take_or_clone(), false))
}
}
}
@@ -251,9 +265,20 @@ impl Engine {
Expr::FnCall(x, pos) if !x.is_qualified() && new_val.is_none() => {
let crate::ast::FnCallExpr { name, hashes, .. } = x.as_ref();
let call_args = &mut idx_val.into_fn_call_args();
self.make_method_call(
#[cfg(feature = "debugging")]
let reset_debugger = self.run_debugger_with_reset(
scope, global, state, lib, this_ptr, rhs, level,
);
let result = self.make_method_call(
global, state, lib, name, *hashes, target, call_args, *pos, level,
)
);
#[cfg(feature = "debugging")]
global.debugger.reset_status(reset_debugger);
result
}
// xxx.fn_name(...) = ???
Expr::FnCall(_, _) if new_val.is_some() => {
@@ -265,6 +290,9 @@ impl Engine {
}
// {xxx:map}.id op= ???
Expr::Property(x) if target.is::<crate::Map>() && new_val.is_some() => {
#[cfg(feature = "debugging")]
self.run_debugger(scope, global, state, lib, this_ptr, rhs, level);
let (name, pos) = &x.2;
let ((new_val, new_pos), (op_info, op_pos)) = new_val.expect("`Some`");
let index = name.into();
@@ -283,6 +311,9 @@ impl Engine {
}
// {xxx:map}.id
Expr::Property(x) if target.is::<crate::Map>() => {
#[cfg(feature = "debugging")]
self.run_debugger(scope, global, state, lib, this_ptr, rhs, level);
let (name, pos) = &x.2;
let index = name.into();
let val = self.get_indexed_mut(
@@ -292,6 +323,9 @@ impl Engine {
}
// xxx.id op= ???
Expr::Property(x) if new_val.is_some() => {
#[cfg(feature = "debugging")]
self.run_debugger(scope, global, state, lib, this_ptr, rhs, level);
let ((getter, hash_get), (setter, hash_set), (name, pos)) = x.as_ref();
let ((mut new_val, new_pos), (op_info, op_pos)) = new_val.expect("`Some`");
@@ -368,6 +402,9 @@ impl Engine {
}
// xxx.id
Expr::Property(x) => {
#[cfg(feature = "debugging")]
self.run_debugger(scope, global, state, lib, this_ptr, rhs, level);
let ((getter, hash_get), _, (name, pos)) = x.as_ref();
let hash = crate::ast::FnCallHashes::from_native(*hash_get);
let args = &mut [target.as_mut()];
@@ -401,8 +438,13 @@ impl Engine {
Expr::Index(x, term, x_pos) | Expr::Dot(x, term, x_pos)
if target.is::<crate::Map>() =>
{
let node = &x.lhs;
let val_target = &mut match x.lhs {
Expr::Property(ref p) => {
#[cfg(feature = "debugging")]
self.run_debugger(scope, global, state, lib, this_ptr, node, level);
let (name, pos) = &p.2;
let index = name.into();
self.get_indexed_mut(
@@ -413,11 +455,21 @@ impl Engine {
Expr::FnCall(ref x, pos) if !x.is_qualified() => {
let crate::ast::FnCallExpr { name, hashes, .. } = x.as_ref();
let call_args = &mut idx_val.into_fn_call_args();
let (val, _) = self.make_method_call(
#[cfg(feature = "debugging")]
let reset_debugger = self.run_debugger_with_reset(
scope, global, state, lib, this_ptr, node, level,
);
let result = self.make_method_call(
global, state, lib, name, *hashes, target, call_args, pos,
level,
)?;
val.into()
);
#[cfg(feature = "debugging")]
global.debugger.reset_status(reset_debugger);
result?.0.into()
}
// {xxx:map}.module::fn_name(...) - syntax error
Expr::FnCall(_, _) => unreachable!(
@@ -429,16 +481,21 @@ impl Engine {
let rhs_chain = rhs.into();
self.eval_dot_index_chain_helper(
global, state, lib, this_ptr, val_target, root, &x.rhs, *term,
global, state, lib, this_ptr, val_target, root, rhs, &x.rhs, *term,
idx_values, rhs_chain, level, new_val,
)
.map_err(|err| err.fill_position(*x_pos))
}
// xxx.sub_lhs[expr] | xxx.sub_lhs.expr
Expr::Index(x, term, x_pos) | Expr::Dot(x, term, x_pos) => {
let node = &x.lhs;
match x.lhs {
// xxx.prop[expr] | xxx.prop.expr
Expr::Property(ref p) => {
#[cfg(feature = "debugging")]
self.run_debugger(scope, global, state, lib, this_ptr, node, level);
let ((getter, hash_get), (setter, hash_set), (name, pos)) =
p.as_ref();
let rhs_chain = rhs.into();
@@ -482,6 +539,7 @@ impl Engine {
this_ptr,
&mut val.into(),
root,
rhs,
&x.rhs,
*term,
idx_values,
@@ -536,14 +594,24 @@ impl Engine {
let crate::ast::FnCallExpr { name, hashes, .. } = f.as_ref();
let rhs_chain = rhs.into();
let args = &mut idx_val.into_fn_call_args();
let (mut val, _) = self.make_method_call(
#[cfg(feature = "debugging")]
let reset_debugger = self.run_debugger_with_reset(
scope, global, state, lib, this_ptr, node, level,
);
let result = self.make_method_call(
global, state, lib, name, *hashes, target, args, pos, level,
)?;
let val = &mut val;
);
#[cfg(feature = "debugging")]
global.debugger.reset_status(reset_debugger);
let val = &mut result?.0;
let target = &mut val.into();
self.eval_dot_index_chain_helper(
global, state, lib, this_ptr, target, root, &x.rhs, *term,
global, state, lib, this_ptr, target, root, rhs, &x.rhs, *term,
idx_values, rhs_chain, level, new_val,
)
.map_err(|err| err.fill_position(pos))
@@ -594,6 +662,9 @@ impl Engine {
match lhs {
// id.??? or id[???]
Expr::Variable(_, var_pos, x) => {
#[cfg(feature = "debugging")]
self.run_debugger(scope, global, state, lib, this_ptr, lhs, level);
#[cfg(not(feature = "unchecked"))]
self.inc_operations(&mut global.num_operations, *var_pos)?;
@@ -604,7 +675,7 @@ impl Engine {
let root = (x.2.as_str(), *var_pos);
self.eval_dot_index_chain_helper(
global, state, lib, &mut None, obj_ptr, root, rhs, term, idx_values,
global, state, lib, &mut None, obj_ptr, root, expr, rhs, term, idx_values,
chain_type, level, new_val,
)
.map(|(v, _)| v)
@@ -618,8 +689,8 @@ impl Engine {
let obj_ptr = &mut value.into();
let root = ("", expr.position());
self.eval_dot_index_chain_helper(
global, state, lib, this_ptr, obj_ptr, root, rhs, term, idx_values, chain_type,
level, new_val,
global, state, lib, this_ptr, obj_ptr, root, expr, rhs, term, idx_values,
chain_type, level, new_val,
)
.map(|(v, _)| if is_assignment { Dynamic::UNIT } else { v })
.map_err(|err| err.fill_position(op_pos))

View File

@@ -42,9 +42,9 @@ pub enum BreakPoint {
#[cfg(not(feature = "no_position"))]
AtPosition { source: Identifier, pos: Position },
/// Break at a particular function call.
AtFunctionName { fn_name: Identifier },
AtFunctionName { name: Identifier },
/// Break at a particular function call with a particular number of arguments.
AtFunctionCall { fn_name: Identifier, args: usize },
AtFunctionCall { name: Identifier, args: usize },
}
impl fmt::Display for BreakPoint {
@@ -57,8 +57,11 @@ impl fmt::Display for BreakPoint {
write!(f, "@ {:?}", pos)
}
}
Self::AtFunctionName { fn_name } => write!(f, "{} (...)", fn_name),
Self::AtFunctionCall { fn_name, args } => write!(
Self::AtFunctionName { name: fn_name } => write!(f, "{} (...)", fn_name),
Self::AtFunctionCall {
name: fn_name,
args,
} => write!(
f,
"{} ({})",
fn_name,
@@ -71,6 +74,7 @@ impl fmt::Display for BreakPoint {
}
}
/// A function call.
#[derive(Debug, Clone, Hash)]
pub struct CallStackFrame {
pub fn_name: Identifier,
@@ -104,7 +108,7 @@ impl fmt::Display for CallStackFrame {
/// A type providing debugging facilities.
#[derive(Debug, Clone, Hash)]
pub struct Debugger {
active: bool,
status: DebuggerCommand,
break_points: Vec<BreakPoint>,
call_stack: Vec<CallStackFrame>,
}
@@ -113,7 +117,7 @@ impl Debugger {
/// Create a new [`Debugger`].
pub const fn new() -> Self {
Self {
active: false,
status: DebuggerCommand::Continue,
break_points: Vec::new(),
call_stack: Vec::new(),
}
@@ -123,54 +127,74 @@ impl Debugger {
pub fn call_stack_len(&self) -> usize {
self.call_stack.len()
}
/// Get the current call stack.
#[inline(always)]
pub fn call_stack(&self) -> &[CallStackFrame] {
&self.call_stack
}
/// Rewind the function call stack to a particular depth.
#[inline(always)]
pub fn rewind_call_stack(&mut self, len: usize) {
pub(crate) fn rewind_call_stack(&mut self, len: usize) {
self.call_stack.truncate(len);
}
/// Add a new frame to the function call stack.
#[inline(always)]
pub fn push_call_stack_frame(
pub(crate) fn push_call_stack_frame(
&mut self,
fn_name: impl Into<Identifier>,
args: StaticVec<Dynamic>,
source: impl Into<Identifier>,
pos: Position,
) {
let fp = CallStackFrame {
self.call_stack.push(CallStackFrame {
fn_name: fn_name.into(),
args,
source: source.into(),
pos,
};
println!("{}", fp);
self.call_stack.push(fp);
});
}
/// Is this [`Debugger`] currently active?
/// Get the current status of this [`Debugger`].
#[inline(always)]
#[must_use]
pub fn is_active(&self) -> bool {
self.active
pub fn status(&self) -> DebuggerCommand {
self.status
}
/// Activate or deactivate this [`Debugger`].
/// Set the status of this [`Debugger`].
#[inline(always)]
pub fn set_status(&mut self, status: DebuggerCommand) {
self.status = status;
}
/// Set the status of this [`Debugger`].
#[inline(always)]
pub fn reset_status(&mut self, status: Option<DebuggerCommand>) {
if let Some(cmd) = status {
self.status = cmd;
}
}
/// Activate: set the status of this [`Debugger`] to [`DebuggerCommand::StepInto`].
/// Deactivate: set the status of this [`Debugger`] to [`DebuggerCommand::Continue`].
#[inline(always)]
pub fn activate(&mut self, active: bool) {
self.active = active;
if active {
self.set_status(DebuggerCommand::StepInto);
} else {
self.set_status(DebuggerCommand::Continue);
}
}
/// Does a particular [`AST` Node][ASTNode] trigger a break-point?
pub fn is_break_point(&self, src: &str, node: ASTNode) -> bool {
self.iter_break_points().any(|bp| match bp {
self.break_points().iter().any(|bp| match bp {
#[cfg(not(feature = "no_position"))]
BreakPoint::AtPosition { source, pos } => node.position() == *pos && src == source,
BreakPoint::AtFunctionName { fn_name } => match node {
BreakPoint::AtFunctionName { name } => match node {
ASTNode::Expr(Expr::FnCall(x, _)) | ASTNode::Stmt(Stmt::FnCall(x, _)) => {
x.name == *fn_name
x.name == *name
}
_ => false,
},
BreakPoint::AtFunctionCall { fn_name, args } => match node {
BreakPoint::AtFunctionCall { name, args } => match node {
ASTNode::Expr(Expr::FnCall(x, _)) | ASTNode::Stmt(Stmt::FnCall(x, _)) => {
x.args.len() == *args && x.name == *fn_name
x.args.len() == *args && x.name == *name
}
_ => false,
},
@@ -179,7 +203,7 @@ impl Debugger {
/// Get a slice of all [`BreakPoint`]'s.
#[inline(always)]
#[must_use]
pub fn break_points(&mut self) -> &[BreakPoint] {
pub fn break_points(&self) -> &[BreakPoint] {
&self.break_points
}
/// Get the underlying [`Vec`] holding all [`BreakPoint`]'s.
@@ -188,66 +212,98 @@ impl Debugger {
pub fn break_points_mut(&mut self) -> &mut Vec<BreakPoint> {
&mut self.break_points
}
/// Get an iterator over all [`BreakPoint`]'s.
#[inline(always)]
#[must_use]
pub fn iter_break_points(&self) -> impl Iterator<Item = &BreakPoint> {
self.break_points.iter()
}
/// Get a mutable iterator over all [`BreakPoint`]'s.
#[inline(always)]
#[must_use]
pub fn iter_break_points_mut(&mut self) -> impl Iterator<Item = &mut BreakPoint> {
self.break_points.iter_mut()
}
}
impl Engine {
pub(crate) fn run_debugger(
/// Run the debugger callback.
#[inline(always)]
pub(crate) fn run_debugger<'a>(
&self,
scope: &mut Scope,
global: &mut GlobalRuntimeState,
state: &mut EvalState,
lib: &[&Module],
this_ptr: &mut Option<&mut Dynamic>,
node: ASTNode,
node: impl Into<ASTNode<'a>>,
level: usize,
) -> bool {
) {
if let Some(cmd) =
self.run_debugger_with_reset(scope, global, state, lib, this_ptr, node, level)
{
global.debugger.set_status(cmd);
}
}
/// Run the debugger callback.
///
/// Returns `true` if the debugger needs to be reactivated at the end of the block, statement or
/// function call.
///
/// # Note
///
/// When the debugger callback return [`DebuggerCommand::StepOver`], the debugger if temporarily
/// disabled and `true` is returned.
///
/// It is up to the [`Engine`] to reactivate the debugger.
#[inline]
#[must_use]
pub(crate) fn run_debugger_with_reset<'a>(
&self,
scope: &mut Scope,
global: &mut GlobalRuntimeState,
state: &mut EvalState,
lib: &[&Module],
this_ptr: &mut Option<&mut Dynamic>,
node: impl Into<ASTNode<'a>>,
level: usize,
) -> Option<DebuggerCommand> {
if let Some(ref on_debugger) = self.debugger {
if global.debugger.active || global.debugger.is_break_point(&global.source, node) {
let source = global.source.clone();
let source = if source.is_empty() {
None
} else {
Some(source.as_str())
};
let mut context = crate::EvalContext {
engine: self,
scope,
global,
state,
lib,
this_ptr,
level,
};
let node = node.into();
match on_debugger(&mut context, node, source, node.position()) {
DebuggerCommand::Continue => {
global.debugger.activate(false);
return false;
}
DebuggerCommand::StepInto => {
global.debugger.activate(true);
return true;
}
DebuggerCommand::StepOver => {
global.debugger.activate(false);
return true;
}
let stop = match global.debugger.status {
DebuggerCommand::Continue => false,
DebuggerCommand::StepOver => matches!(node, ASTNode::Stmt(_)),
DebuggerCommand::StepInto => true,
};
if !stop && !global.debugger.is_break_point(&global.source, node) {
return None;
}
let source = global.source.clone();
let source = if source.is_empty() {
None
} else {
Some(source.as_str())
};
let mut context = crate::EvalContext {
engine: self,
scope,
global,
state,
lib,
this_ptr,
level,
};
let command = on_debugger(&mut context, node, source, node.position());
match command {
DebuggerCommand::Continue => {
global.debugger.set_status(DebuggerCommand::Continue);
None
}
DebuggerCommand::StepInto => {
global.debugger.set_status(DebuggerCommand::StepInto);
None
}
DebuggerCommand::StepOver => {
global.debugger.set_status(DebuggerCommand::Continue);
Some(DebuggerCommand::StepOver)
}
}
} else {
None
}
false
}
}

View File

@@ -259,16 +259,16 @@ impl Engine {
expr: &Expr,
level: usize,
) -> RhaiResult {
#[cfg(feature = "debugging")]
let reset_debugger_command =
self.run_debugger(scope, global, state, lib, this_ptr, expr.into(), level);
// Coded this way for better branch prediction.
// Popular branches are lifted out of the `match` statement into their own branches.
// Function calls should account for a relatively larger portion of expressions because
// binary operators are also function calls.
if let Expr::FnCall(x, pos) = expr {
#[cfg(feature = "debugging")]
let reset_debugger =
self.run_debugger_with_reset(scope, global, state, lib, this_ptr, expr, level);
#[cfg(not(feature = "unchecked"))]
self.inc_operations(&mut global.num_operations, expr.position())?;
@@ -276,7 +276,7 @@ impl Engine {
self.eval_fn_call_expr(scope, global, state, lib, this_ptr, x, *pos, level);
#[cfg(feature = "debugging")]
global.debugger.activate(reset_debugger_command);
global.debugger.reset_status(reset_debugger);
return result;
}
@@ -285,10 +285,13 @@ impl Engine {
// We shouldn't do this for too many variants because, soon or later, the added comparisons
// will cost more than the mis-predicted `match` branch.
if let Expr::Variable(index, var_pos, x) = expr {
#[cfg(feature = "debugging")]
self.run_debugger(scope, global, state, lib, this_ptr, expr, level);
#[cfg(not(feature = "unchecked"))]
self.inc_operations(&mut global.num_operations, expr.position())?;
let result = if index.is_none() && x.0.is_none() && x.2 == KEYWORD_THIS {
return if index.is_none() && x.0.is_none() && x.2 == KEYWORD_THIS {
this_ptr
.as_deref()
.cloned()
@@ -297,13 +300,12 @@ impl Engine {
self.search_namespace(scope, global, state, lib, this_ptr, expr)
.map(|(val, _)| val.take_or_clone())
};
#[cfg(feature = "debugging")]
global.debugger.activate(reset_debugger_command);
return result;
}
#[cfg(feature = "debugging")]
let reset_debugger =
self.run_debugger_with_reset(scope, global, state, lib, this_ptr, expr, level);
#[cfg(not(feature = "unchecked"))]
self.inc_operations(&mut global.num_operations, expr.position())?;
@@ -523,7 +525,7 @@ impl Engine {
};
#[cfg(feature = "debugging")]
global.debugger.activate(reset_debugger_command);
global.debugger.reset_status(reset_debugger);
return result;
}

View File

@@ -11,7 +11,7 @@ mod target;
#[cfg(any(not(feature = "no_index"), not(feature = "no_object")))]
pub use chaining::{ChainArgument, ChainType};
#[cfg(feature = "debugging")]
pub use debugger::{BreakPoint, Debugger, DebuggerCommand, OnDebuggerCallback};
pub use debugger::{BreakPoint, CallStackFrame, Debugger, DebuggerCommand, OnDebuggerCallback};
pub use eval_context::EvalContext;
pub use eval_state::EvalState;
pub use global_state::GlobalRuntimeState;

View File

@@ -195,8 +195,8 @@ impl Engine {
level: usize,
) -> RhaiResult {
#[cfg(feature = "debugging")]
let reset_debugger_command =
self.run_debugger(scope, global, state, lib, this_ptr, stmt.into(), level);
let reset_debugger =
self.run_debugger_with_reset(scope, global, state, lib, this_ptr, stmt, level);
// Coded this way for better branch prediction.
// Popular branches are lifted out of the `match` statement into their own branches.
@@ -210,7 +210,7 @@ impl Engine {
self.eval_fn_call_expr(scope, global, state, lib, this_ptr, x, *pos, level);
#[cfg(feature = "debugging")]
global.debugger.activate(reset_debugger_command);
global.debugger.reset_status(reset_debugger);
return result;
}
@@ -301,7 +301,7 @@ impl Engine {
};
#[cfg(feature = "debugging")]
global.debugger.activate(reset_debugger_command);
global.debugger.reset_status(reset_debugger);
return result;
}
@@ -931,7 +931,7 @@ impl Engine {
};
#[cfg(feature = "debugging")]
global.debugger.activate(reset_debugger_command);
global.debugger.reset_status(reset_debugger);
return result;
}