echse/krabbeltier/src/main.rs
2024-12-15 11:28:25 +01:00

225 lines
6.5 KiB
Rust

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<usize> },
#[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<usize>,
}
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();
}