Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 37 additions & 0 deletions debugger/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,7 @@ pub enum DebuggerEvent {
pub struct DebuggerContext {
handle: Option<JoinHandle<()>>,
is_done: Arc<AtomicBool>,
step_once: Arc<AtomicBool>,
grammar: Option<Vec<OptimizedRule>>,
input: Option<String>,
breakpoints: Arc<Mutex<HashSet<String>>>,
Expand Down Expand Up @@ -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 || {
Expand All @@ -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)
Expand All @@ -265,6 +276,7 @@ impl DebuggerContext {

thread::park();
}

false
}),
);
Expand Down Expand Up @@ -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<Position<'_>, DebuggerError> {
match self.input {
Expand All @@ -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())),
Expand Down Expand Up @@ -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");

Expand Down
59 changes: 37 additions & 22 deletions debugger/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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<Receiver<DebuggerEvent>>,
last_command: Cow<'a, str>,
}

impl Cli {
impl<'a> Cli<'a> {
fn grammar(&mut self, path: PathBuf) -> Result<(), DebuggerError> {
self.context.load_grammar(&path)
}
Expand All @@ -53,29 +55,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)) => {
Expand Down Expand Up @@ -115,6 +110,7 @@ impl Cli {
da - delete all breakpoints\n\
r(run) <rule> - run a rule\n\
c(continue) - continue\n\
n(next) - step to next rule\n\
l(list) - list breakpoints\n\
h(help) - help\n\
"
Expand All @@ -130,12 +126,31 @@ 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()?,
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) => {
Expand Down Expand Up @@ -319,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);
Expand Down