From 40aaab60c3d468ee943d2c25d9823b0601b59684 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Tue, 25 Jan 2022 12:24:30 +0800 Subject: [PATCH] Refine debugger. --- src/bin/rhai-dbg.rs | 127 +++++++++++++++++++++------- src/eval/chaining.rs | 105 +++++++++++++++++++---- src/eval/debugger.rs | 194 ++++++++++++++++++++++++++++--------------- src/eval/expr.rs | 26 +++--- src/eval/mod.rs | 2 +- src/eval/stmt.rs | 10 +-- src/func/call.rs | 26 +----- src/lib.rs | 2 +- 8 files changed, 334 insertions(+), 158 deletions(-) diff --git a/src/bin/rhai-dbg.rs b/src/bin/rhai-dbg.rs index cd1f0dd9..18ab359c 100644 --- a/src/bin/rhai-dbg.rs +++ b/src/bin/rhai-dbg.rs @@ -71,16 +71,22 @@ fn print_error(input: &str, mut err: EvalAltResult) { /// Print debug help. fn print_debug_help() { - println!("help => print this help"); - println!("quit, exit => quit"); - println!("scope => print all variables in the scope"); - println!("node => print the current AST node"); - println!("breakpoints => print all break-points"); - println!("clear => delete all break-points"); - println!("break => set a new break-point at the current position"); - println!("step => go to the next expression, diving into functions"); - println!("next => go to the next statement but don't dive into functions"); - println!("continue => continue normal execution"); + println!("help => print this help"); + println!("quit, exit, kill => quit"); + println!("scope => print all variables in the scope"); + println!("node => print the current AST node"); + println!("backtrace => print the current call-stack"); + println!("breakpoints => print all break-points"); + println!("delete => delete a break-point"); + println!("clear => delete all break-points"); + println!("break => set a new break-point at the current position"); + println!("break => set a new break-point for a function call"); + println!( + "break <#args> => set a new break-point for a function call with #args arguments" + ); + println!("step => go to the next expression, diving into functions"); + println!("next => go to the next statement but don't dive into functions"); + println!("continue => continue normal execution"); println!(); } @@ -201,21 +207,30 @@ fn main() { match stdin().read_line(&mut input) { Ok(0) => break DebuggerCommand::Continue, - Ok(_) => match input.as_str().trim_end() { - "help" => print_debug_help(), - "exit" | "quit" => { + Ok(_) => match input.trim_end().split(' ').collect::>().as_slice() { + ["help", ..] => print_debug_help(), + ["exit", ..] | ["quit", ..] | ["kill", ..] => { println!("Script terminated. Bye!"); exit(0); } - "node" => { + ["node", ..] => { println!("{:?} {}@{:?}", node, source.unwrap_or_default(), pos); println!(); } - "continue" => break DebuggerCommand::Continue, - "" | "step" => break DebuggerCommand::StepInto, - "next" => break DebuggerCommand::StepOver, - "scope" => print_scope(context.scope()), - "clear" => { + ["continue", ..] => break DebuggerCommand::Continue, + [""] | ["step", ..] => break DebuggerCommand::StepInto, + ["next", ..] => break DebuggerCommand::StepOver, + ["scope", ..] => print_scope(context.scope()), + ["backtrace", ..] => { + context + .global_runtime_state() + .debugger + .call_stack() + .iter() + .rev() + .for_each(|frame| println!("{}", frame)); + } + ["clear", ..] => { context .global_runtime_state_mut() .debugger @@ -223,30 +238,80 @@ fn main() { .clear(); println!("All break-points cleared."); } - "breakpoints" => context - .global_runtime_state_mut() + ["breakpoints", ..] => context + .global_runtime_state() .debugger - .iter_break_points() + .break_points() + .iter() .enumerate() .for_each(|(i, bp)| match bp { BreakPoint::AtPosition { pos, .. } => { - println!("[{}]", i); + println!("[{}]", i + 1); print_source(&lines, *pos); } - _ => println!("[{}]\n{}", i, bp), + _ => println!("[{}]\n{}", i + 1, bp), }), - "break" => { + ["delete", n, ..] => { + if let Ok(n) = n.parse::() { + let range = 1..=context + .global_runtime_state_mut() + .debugger + .break_points() + .len(); + if range.contains(&n) { + context + .global_runtime_state_mut() + .debugger + .break_points_mut() + .remove(n - 1); + println!("Break-point #{} deleted.", n) + } else { + eprintln!("Invalid break-point: {}", n); + } + } else { + eprintln!("Invalid break-point: '{}'", n); + } + } + ["break", fn_name, args, ..] => { + if let Ok(args) = args.parse::() { + let bp = rhai::debugger::BreakPoint::AtFunctionCall { + name: fn_name.trim().into(), + args, + }; + println!("Break-point added for {}", bp); + context + .global_runtime_state_mut() + .debugger + .break_points_mut() + .push(bp); + } else { + eprintln!("Invalid number of arguments: '{}'", args); + } + } + ["break", fn_name] => { + let bp = rhai::debugger::BreakPoint::AtFunctionName { + name: fn_name.trim().into(), + }; + println!("Break-point added for {}", bp); context .global_runtime_state_mut() .debugger .break_points_mut() - .push(rhai::debugger::BreakPoint::AtPosition { - source: source.unwrap_or("").into(), - pos, - }); - println!("Break-point added at the current position."); + .push(bp); } - cmd => eprintln!("Invalid debugger command: '{}'", cmd), + ["break", ..] => { + let bp = rhai::debugger::BreakPoint::AtPosition { + source: source.unwrap_or("").into(), + pos, + }; + println!("Break-point added {}", bp); + context + .global_runtime_state_mut() + .debugger + .break_points_mut() + .push(bp); + } + cmd => eprintln!("Invalid debugger command: '{}'", cmd[0]), }, Err(err) => panic!("input error: {}", err), } diff --git a/src/eval/chaining.rs b/src/eval/chaining.rs index ee225503..087be80b 100644 --- a/src/eval/chaining.rs +++ b/src/eval/chaining.rs @@ -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, @@ -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::() && 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::() => { + #[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::() => { + 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)) diff --git a/src/eval/debugger.rs b/src/eval/debugger.rs index b8f5f069..1f95aefd 100644 --- a/src/eval/debugger.rs +++ b/src/eval/debugger.rs @@ -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, call_stack: Vec, } @@ -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, args: StaticVec, source: impl Into, 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) { + 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 { &mut self.break_points } - /// Get an iterator over all [`BreakPoint`]'s. - #[inline(always)] - #[must_use] - pub fn iter_break_points(&self) -> impl Iterator { - 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 { - 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>, 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>, + level: usize, + ) -> Option { 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 } } diff --git a/src/eval/expr.rs b/src/eval/expr.rs index d82c7b59..1d37be98 100644 --- a/src/eval/expr.rs +++ b/src/eval/expr.rs @@ -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; } diff --git a/src/eval/mod.rs b/src/eval/mod.rs index 23893682..99cf5938 100644 --- a/src/eval/mod.rs +++ b/src/eval/mod.rs @@ -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; diff --git a/src/eval/stmt.rs b/src/eval/stmt.rs index 92d5d6eb..62bb62c2 100644 --- a/src/eval/stmt.rs +++ b/src/eval/stmt.rs @@ -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; } diff --git a/src/func/call.rs b/src/func/call.rs index 6c934b88..321481b0 100644 --- a/src/func/call.rs +++ b/src/func/call.rs @@ -890,19 +890,11 @@ impl Engine { Ok(( if let Expr::Stack(slot, _) = arg_expr { #[cfg(feature = "debugging")] - let active = - self.run_debugger(scope, global, state, lib, this_ptr, arg_expr.into(), level); - #[cfg(feature = "debugging")] - global.debugger.activate(active); - + self.run_debugger(scope, global, state, lib, this_ptr, arg_expr, level); constants[*slot].clone() } else if let Some(value) = arg_expr.get_literal_value() { #[cfg(feature = "debugging")] - let active = - self.run_debugger(scope, global, state, lib, this_ptr, arg_expr.into(), level); - #[cfg(feature = "debugging")] - global.debugger.activate(active); - + self.run_debugger(scope, global, state, lib, this_ptr, arg_expr, level); value } else { self.eval_expr(scope, global, state, lib, this_ptr, arg_expr, level)? @@ -1148,12 +1140,7 @@ impl Engine { let first_expr = first_arg.unwrap(); #[cfg(feature = "debugging")] - { - let node = first_expr.into(); - let active = - self.run_debugger(scope, global, state, lib, this_ptr, node, level); - global.debugger.activate(active); - } + self.run_debugger(scope, global, state, lib, this_ptr, first_expr, level); // func(x, ...) -> x.func(...) a_expr.iter().try_for_each(|expr| { @@ -1236,12 +1223,7 @@ impl Engine { // &mut first argument and avoid cloning the value if !args_expr.is_empty() && args_expr[0].is_variable_access(true) { #[cfg(feature = "debugging")] - { - let node = (&args_expr[0]).into(); - let active = - self.run_debugger(scope, global, state, lib, this_ptr, node, level); - global.debugger.activate(active); - } + self.run_debugger(scope, global, state, lib, this_ptr, &args_expr[0], level); // func(x, ...) -> x.func(...) arg_values.push(Dynamic::UNIT); diff --git a/src/lib.rs b/src/lib.rs index 3802c1b6..3e5c8acc 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -160,7 +160,7 @@ pub use types::{ /// Exported under the `debugging` feature only. #[cfg(feature = "debugging")] pub mod debugger { - pub use super::eval::{BreakPoint, Debugger, DebuggerCommand}; + pub use super::eval::{BreakPoint, CallStackFrame, Debugger, DebuggerCommand}; } /// An identifier in Rhai. [`SmartString`](https://crates.io/crates/smartstring) is used because most