mirror of
https://github.com/kaesaecracker/echse.git
synced 2025-03-31 16:58:47 +02:00
225 lines
6.5 KiB
Rust
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();
|
|
}
|