diff --git a/src/bin/rhai-dbg.rs b/src/bin/rhai-dbg.rs index e44cc073..e25b257c 100644 --- a/src/bin/rhai-dbg.rs +++ b/src/bin/rhai-dbg.rs @@ -77,9 +77,12 @@ fn print_debug_help() { 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!("enable => enable a break-point"); + println!("disable => disable a break-point"); + 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 at a line number"); 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" @@ -252,6 +255,52 @@ fn main() { } _ => println!("[{}]\n{}", i + 1, bp), }), + ["enable", 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() + .get_mut(n - 1) + .unwrap() + .enable(true); + println!("Break-point #{} enabled.", n) + } else { + eprintln!("Invalid break-point: {}", n); + } + } else { + eprintln!("Invalid break-point: '{}'", n); + } + } + ["disable", 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() + .get_mut(n - 1) + .unwrap() + .enable(false); + println!("Break-point #{} disabled.", n) + } else { + eprintln!("Invalid break-point: {}", n); + } + } else { + eprintln!("Invalid break-point: '{}'", n); + } + } ["delete", n, ..] => { if let Ok(n) = n.parse::() { let range = 1..=context @@ -278,6 +327,7 @@ fn main() { let bp = rhai::debugger::BreakPoint::AtFunctionCall { name: fn_name.trim().into(), args, + enabled: true, }; println!("Break-point added for {}", bp); context @@ -289,21 +339,44 @@ fn main() { 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(bp); + ["break", param] => { + if let Ok(n) = param.parse::() { + // Numeric parameter + let range = 1..=lines.len(); + if range.contains(&n) { + let bp = rhai::debugger::BreakPoint::AtPosition { + source: source.unwrap_or("").into(), + pos: Position::new(n as u16, 0), + enabled: true, + }; + println!("Break-point added {}", bp); + context + .global_runtime_state_mut() + .debugger + .break_points_mut() + .push(bp); + } else { + eprintln!("Invalid line number: {}", n); + } + } else { + // Function name parameter + let bp = rhai::debugger::BreakPoint::AtFunctionName { + name: param.trim().into(), + enabled: true, + }; + println!("Break-point added for {}", bp); + context + .global_runtime_state_mut() + .debugger + .break_points_mut() + .push(bp); + } } ["break", ..] => { let bp = rhai::debugger::BreakPoint::AtPosition { source: source.unwrap_or("").into(), pos, + enabled: true, }; println!("Break-point added {}", bp); context diff --git a/src/eval/debugger.rs b/src/eval/debugger.rs index 7a4f52b9..7d839e8e 100644 --- a/src/eval/debugger.rs +++ b/src/eval/debugger.rs @@ -40,36 +40,91 @@ pub enum BreakPoint { /// /// Source is empty if not available. #[cfg(not(feature = "no_position"))] - AtPosition { source: Identifier, pos: Position }, + AtPosition { + source: Identifier, + pos: Position, + enabled: bool, + }, /// Break at a particular function call. - AtFunctionName { name: Identifier }, + AtFunctionName { name: Identifier, enabled: bool }, /// Break at a particular function call with a particular number of arguments. - AtFunctionCall { name: Identifier, args: usize }, + AtFunctionCall { + name: Identifier, + args: usize, + enabled: bool, + }, } impl fmt::Display for BreakPoint { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { - Self::AtPosition { source, pos } => { + Self::AtPosition { + source, + pos, + enabled, + } => { if !source.is_empty() { - write!(f, "{} @ {:?}", source, pos) + write!(f, "{} @ {:?}", source, pos)?; } else { - write!(f, "@ {:?}", pos) + write!(f, "@ {:?}", pos)?; } + if !*enabled { + f.write_str(" (disabled)")?; + } + Ok(()) + } + Self::AtFunctionName { + name: fn_name, + enabled, + } => { + write!(f, "{} (...)", fn_name)?; + if !*enabled { + f.write_str(" (disabled)")?; + } + Ok(()) } - Self::AtFunctionName { name: fn_name } => write!(f, "{} (...)", fn_name), Self::AtFunctionCall { name: fn_name, args, - } => write!( - f, - "{} ({})", - fn_name, - std::iter::repeat("_") - .take(*args) - .collect::>() - .join(", ") - ), + enabled, + } => { + write!( + f, + "{} ({})", + fn_name, + std::iter::repeat("_") + .take(*args) + .collect::>() + .join(", ") + )?; + if !*enabled { + f.write_str(" (disabled)")?; + } + Ok(()) + } + } + } +} + +impl BreakPoint { + /// Is this [`BreakPoint`] enabled? + #[inline(always)] + pub fn is_enabled(&self) -> bool { + match self { + #[cfg(not(feature = "no_position"))] + Self::AtPosition { enabled, .. } => *enabled, + Self::AtFunctionName { enabled, .. } | Self::AtFunctionCall { enabled, .. } => *enabled, + } + } + /// Enable/disable this [`BreakPoint`]. + #[inline(always)] + pub fn enable(&mut self, value: bool) { + match self { + #[cfg(not(feature = "no_position"))] + Self::AtPosition { enabled, .. } => *enabled = value, + Self::AtFunctionName { enabled, .. } | Self::AtFunctionCall { enabled, .. } => { + *enabled = value + } } } } @@ -191,22 +246,33 @@ impl Debugger { } /// Does a particular [`AST` Node][ASTNode] trigger a break-point? pub fn is_break_point(&self, src: &str, node: ASTNode) -> bool { - self.break_points().iter().any(|bp| match bp { - #[cfg(not(feature = "no_position"))] - BreakPoint::AtPosition { source, pos } => node.position() == *pos && src == source, - BreakPoint::AtFunctionName { name } => match node { - ASTNode::Expr(Expr::FnCall(x, _)) | ASTNode::Stmt(Stmt::FnCall(x, _)) => { - x.name == *name + self.break_points() + .iter() + .filter(|&bp| bp.is_enabled()) + .any(|bp| match bp { + #[cfg(not(feature = "no_position"))] + BreakPoint::AtPosition { pos, .. } if pos.is_none() => false, + #[cfg(not(feature = "no_position"))] + BreakPoint::AtPosition { source, pos, .. } if pos.is_beginning_of_line() => { + node.position().line().unwrap_or(0) == pos.line().unwrap() && src == source } - _ => false, - }, - BreakPoint::AtFunctionCall { name, args } => match node { - ASTNode::Expr(Expr::FnCall(x, _)) | ASTNode::Stmt(Stmt::FnCall(x, _)) => { - x.args.len() == *args && x.name == *name + #[cfg(not(feature = "no_position"))] + BreakPoint::AtPosition { source, pos, .. } => { + node.position() == *pos && src == source } - _ => false, - }, - }) + BreakPoint::AtFunctionName { name, .. } => match node { + ASTNode::Expr(Expr::FnCall(x, _)) | ASTNode::Stmt(Stmt::FnCall(x, _)) => { + x.name == *name + } + _ => false, + }, + BreakPoint::AtFunctionCall { name, args, .. } => match node { + ASTNode::Expr(Expr::FnCall(x, _)) | ASTNode::Stmt(Stmt::FnCall(x, _)) => { + x.args.len() == *args && x.name == *name + } + _ => false, + }, + }) } /// Get a slice of all [`BreakPoint`]'s. #[inline(always)] diff --git a/src/tokenizer.rs b/src/tokenizer.rs index ba6fb045..d4202b18 100644 --- a/src/tokenizer.rs +++ b/src/tokenizer.rs @@ -260,7 +260,11 @@ impl fmt::Display for Position { impl fmt::Debug for Position { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { #[cfg(not(feature = "no_position"))] - write!(f, "{}:{}", self.line, self.pos)?; + if self.is_beginning_of_line() { + write!(f, "{}", self.line)?; + } else { + write!(f, "{}:{}", self.line, self.pos)?; + } #[cfg(feature = "no_position")] f.write_str("none")?;