From 5eacaed1547f8e9870c7824e47d2800ae1344fce Mon Sep 17 00:00:00 2001 From: Steven vanZyl Date: Sun, 6 Apr 2025 09:36:16 -0400 Subject: [PATCH 1/2] Debugger single step next Adds a new comand n(next) which will step the debugger one rule and break. --- debugger/src/lib.rs | 37 +++++++++++++++++++++++++++++++++++++ debugger/src/main.rs | 29 ++++++++++++----------------- 2 files changed, 49 insertions(+), 17 deletions(-) diff --git a/debugger/src/lib.rs b/debugger/src/lib.rs index 09013980..7899c32f 100644 --- a/debugger/src/lib.rs +++ b/debugger/src/lib.rs @@ -130,6 +130,7 @@ pub enum DebuggerEvent { pub struct DebuggerContext { handle: Option>, is_done: Arc, + step_once: Arc, grammar: Option>, input: Option, breakpoints: Arc>>, @@ -243,6 +244,7 @@ impl DebuggerContext { let breakpoints = Arc::clone(&self.breakpoints); let is_done = Arc::clone(&self.is_done); let is_done_signal = Arc::clone(&self.is_done); + let step_once_signal = Arc::clone(&self.step_once); let rsender = sender.clone(); thread::spawn(move || { @@ -253,6 +255,15 @@ impl DebuggerContext { return true; } + if step_once_signal.load(Ordering::SeqCst) { + step_once_signal.store(false, Ordering::SeqCst); + rsender + .send(DebuggerEvent::Breakpoint(rule, pos.pos())) + .expect(CHANNEL_CLOSED_PANIC); + + return false; + } + let contains_rule = { let lock = breakpoints.lock().expect(POISONED_LOCK_PANIC); lock.contains(&rule) @@ -265,6 +276,7 @@ impl DebuggerContext { thread::park(); } + false }), ); @@ -347,6 +359,22 @@ impl DebuggerContext { } } + /// Continue the debugger, breaking on the next rule. + pub fn next(&mut self) -> Result<(), DebuggerError> { + if self.is_done.load(Ordering::SeqCst) { + return Err(DebuggerError::EofReached); + } + + match self.handle { + Some(ref handle) => { + self.step_once.store(true, Ordering::SeqCst); + handle.thread().unpark(); + Ok(()) + } + None => Err(DebuggerError::RunRuleFirst), + } + } + /// Returns a `Position` from the loaded input. pub fn get_position(&self, pos: usize) -> Result, DebuggerError> { match self.input { @@ -361,6 +389,7 @@ impl Default for DebuggerContext { Self { handle: None, is_done: Arc::new(AtomicBool::new(false)), + step_once: Arc::new(AtomicBool::new(false)), grammar: None, input: None, breakpoints: Arc::new(Mutex::new(HashSet::new())), @@ -411,6 +440,14 @@ mod test { let event = receiver.recv().expect("Error: failed to receive event"); assert_eq!(event, DebuggerEvent::Breakpoint("ident".to_owned(), 5)); + + context.delete_all_breakpoints(); + + context.next().expect("Error: failed to next"); + + let event = receiver.recv().expect("Error: failed to receive event"); + assert_eq!(event, DebuggerEvent::Breakpoint("digit".to_owned(), 5)); + context.cont().expect("Error: failed to continue"); let event = receiver.recv().expect("Error: failed to receive event"); diff --git a/debugger/src/main.rs b/debugger/src/main.rs index 0e482f24..b5256c62 100644 --- a/debugger/src/main.rs +++ b/debugger/src/main.rs @@ -53,29 +53,22 @@ impl Cli { fn run(&mut self, rule: &str) -> Result<(), DebuggerError> { let (sender, receiver) = mpsc::sync_channel(1); - let rec = &receiver; - self.context.run(rule, sender)?; - match rec.recv_timeout(Duration::from_secs(5)) { - Ok(DebuggerEvent::Breakpoint(rule, pos)) => { - let error: Error<()> = Error::new_from_pos( - ErrorVariant::CustomError { - message: format!("parsing {}", rule), - }, - self.context.get_position(pos)?, - ); - println!("{}", error); - } - Ok(DebuggerEvent::Eof) => println!("end-of-input reached"), - Ok(DebuggerEvent::Error(error)) => println!("{}", error), - Err(_) => eprintln!("parsing timed out"), - } self.receiver = Some(receiver); - Ok(()) + self.context.run(rule, sender)?; + self.receive() } fn cont(&mut self) -> Result<(), DebuggerError> { self.context.cont()?; + self.receive() + } + + fn next(&mut self) -> Result<(), DebuggerError> { + self.context.next()?; + self.receive() + } + fn receive(&mut self) -> Result<(), DebuggerError> { match self.receiver { Some(ref rec) => match rec.recv_timeout(Duration::from_secs(5)) { Ok(DebuggerEvent::Breakpoint(rule, pos)) => { @@ -115,6 +108,7 @@ impl Cli { da - delete all breakpoints\n\ r(run) - run a rule\n\ c(continue) - continue\n\ + n(next) - step to next rule\n\ l(list) - list breakpoints\n\ h(help) - help\n\ " @@ -136,6 +130,7 @@ impl Cli { help if "help".starts_with(help) => Cli::help(), list if "list".starts_with(list) => self.list(), cont if "continue".starts_with(cont) => self.cont()?, + next if "next".starts_with(next) => self.next()?, "ba" => self.context.add_all_rules_breakpoints()?, "da" => self.context.delete_all_breakpoints(), grammar if "grammar".starts_with(grammar) => { From e1a1a855bf4f0d9e2227fea5a451ba951200cd45 Mon Sep 17 00:00:00 2001 From: Steven vanZyl Date: Sun, 6 Apr 2025 10:51:21 -0400 Subject: [PATCH 2/2] Debugger empty command repeat previous If an empty command is submitted to the debugger then the previous command, if there is one, is repeated with a message. Particularly useful with the next and continue commands. This feature is taken from GDB. --- debugger/src/main.rs | 30 +++++++++++++++++++++++++----- 1 file changed, 25 insertions(+), 5 deletions(-) diff --git a/debugger/src/main.rs b/debugger/src/main.rs index b5256c62..d8eeb17f 100644 --- a/debugger/src/main.rs +++ b/debugger/src/main.rs @@ -15,6 +15,7 @@ html_favicon_url = "https://raw.githubusercontent.com/pest-parser/pest/master/pest-logo.svg" )] #![warn(missing_docs, rust_2018_idioms, unused_qualifications)] +use std::borrow::Cow; use std::path::PathBuf; use std::sync::mpsc::{self, Receiver}; use std::time::Duration; @@ -33,12 +34,13 @@ use rustyline::{Editor, Helper}; const VERSION: &str = env!("CARGO_PKG_VERSION"); #[derive(Default)] -struct Cli { +struct Cli<'a> { context: DebuggerContext, receiver: Option>, + last_command: Cow<'a, str>, } -impl Cli { +impl<'a> Cli<'a> { fn grammar(&mut self, path: PathBuf) -> Result<(), DebuggerError> { self.context.load_grammar(&path) } @@ -124,9 +126,27 @@ impl Cli { } fn execute_command(&mut self, command: &str) -> Result<(), DebuggerError> { - let verb = command.split(&[' ', '\t']).next().unwrap().trim(); + let command = if command.is_empty() { + // last_command is a Cow to avoid cloning the string when it's not needed + // while making the borrow checker happy about the references + let last = self.last_command.clone(); + if !last.is_empty() { + println!("Repeating last command: {}", last); + } + &last.clone() + } else { + self.last_command = Cow::Owned(command.to_owned()); + command + }; + + let verb = command + .split(&[' ', '\t']) + .next() + .unwrap_or_default() + .trim(); + match verb { - "" => (), + "" => (), // Empty command handled above help if "help".starts_with(help) => Cli::help(), list if "list".starts_with(list) => self.list(), cont if "continue".starts_with(cont) => self.cont()?, @@ -314,7 +334,7 @@ impl Default for CliArgs { } impl CliArgs { - fn init(self, context: &mut Cli) { + fn init(self, context: &mut Cli<'_>) { if let Some(grammar_file) = self.grammar_file { if let Err(e) = context.grammar(grammar_file) { eprintln!("Error: {}", e);