use echse::instructions::ExecuteInstructionError; use echse::machine::{Machine, MachineBuilder}; use echse::parser::EiParser; use std::collections::HashSet; use std::io::{BufRead, StdinLock, Write}; #[derive(clap::Parser)] #[command(version, about, long_about = None)] struct Cli { ei_file: String, #[arg(short, long, default_value_t = 2)] registers: usize, } #[derive(clap::Parser)] #[command( disable_help_flag = true, disable_version_flag = true, no_binary_name = true, bin_name = "" )] enum DebugCommand { #[command(visible_alias = "q")] /// exit the debugger Quit, #[command(visible_alias = "p")] /// prints the current state Print { #[arg(value_enum, default_value_t = PrintMode::All)] mode: PrintMode, }, #[command(visible_alias = "s")] /// executes a single instruction Step, #[command(visible_alias = "j")] /// set the instruction pointer Jump { ip: usize }, #[command(visible_alias = "b")] /// toggle breakpoints Break { ip: Option }, #[command(visible_alias = "bl")] BreakList, /// continue running instructions #[command(visible_alias = "c")] Continue, /// Write a value into the specified register #[command(visible_alias = "w")] Write { register: usize, #[arg(allow_negative_numbers = true)] value: isize, }, } #[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, clap::ValueEnum, Default, Debug)] enum PrintMode { #[default] #[clap(alias = "a")] All, #[clap(alias = "r")] Registers, #[clap(alias = "i")] Instructions, } struct App { machine: Machine, breakpoints: HashSet, } impl App { pub(crate) fn exec(&mut self) { println!("Welcome to the debugger. Run 'help' to see available commands."); let mut stdin = std::io::stdin().lock(); let mut input_buf = String::new(); loop { Self::read_command_line(&mut stdin, &mut input_buf); let command = shellwords::split(input_buf.trim()).map(clap::Parser::try_parse_from); match command { Ok(Ok(command)) => { if !self.run_command(command) { break; } } Err(e) => { println!("{e}"); } Ok(Err(e)) => println!("{e}"), }; } } fn read_command_line(stdin: &mut StdinLock, input_buf: &mut String) { input_buf.clear(); let mut stdout = std::io::stdout().lock(); stdout.write_fmt(format_args!("> ")).unwrap(); stdout.flush().unwrap(); drop(stdout); stdin.read_line(input_buf).unwrap(); } fn run_command(&mut self, command: DebugCommand) -> bool { match command { DebugCommand::Quit => return false, DebugCommand::Print { mode } => match mode { PrintMode::All => { println!("{}", self.machine); } PrintMode::Registers => { println!("{}", self.machine.registers); } PrintMode::Instructions => { println!("next instruction is {}", self.machine.ip); println!("{}", self.machine.instructions); } }, DebugCommand::Step => { if self.machine.instructions.len() > self.machine.ip { println!( "{:03}: {}\n", self.machine.ip, self.machine.instructions[self.machine.ip] ); match self.machine.step() { Ok(_) => {} Err(e) => Self::print_exec_err(e), } } else { println!("reached end of program\n"); } } DebugCommand::Jump { ip } => { self.machine.ip = ip; println!("ip now at {ip}\n"); } DebugCommand::Break { ip } => { let ip = ip.unwrap_or(self.machine.ip); if self.breakpoints.contains(&ip) { self.breakpoints.remove(&ip); println!("removed breakpoint at {ip}\n"); } else { self.breakpoints.insert(ip); println!("added breakpoint at {ip}\n"); } } DebugCommand::BreakList => { println!("current breakpoints: {:?}\n", self.breakpoints); } DebugCommand::Continue => { let mut count = 0; loop { match self.machine.step() { Ok(_) => {} Err(e) => eprintln!("{e}"), } count += 1; if self.breakpoints.contains(&self.machine.ip) { println!( "breakpoint hit before executing {:03}: {}", self.machine.ip, self.machine.instructions[self.machine.ip] ); break; } } println!( "stepped {count} instructions, ip now at {}\n", self.machine.ip ); } DebugCommand::Write { value, register } => { if self.machine.registers.len() <= register { eprintln!( "Cannot write to register r{register}, as this machine only has {}\n", self.machine.registers.len() ); } else { self.machine.registers[register].write(value); println!("r{register}: {value}\n"); } } } true } } fn main() { let args: Cli = clap::Parser::parse(); let parser = EiParser::from_file(&args.ei_file).expect("failed to parse input file"); let instructions = match parser.parse() { Ok(i) => i, Err(e) => { parser .write_error_report(e, &mut std::io::stderr()) .expect("failed to generate error report"); return; } }; let machine = MachineBuilder::new() .with_registers(args.registers) .with_instructions(instructions) .build() .unwrap(); let mut app = App { machine, breakpoints: HashSet::new(), }; app.exec(); }