use anyhow::Result;
use std::io::{Write as _, stdin, stdout};
use crate::{cpu::State::FetchOpcode, system::System};
const BANNER: &str = "RMON v1.0 (C) 1977 Solid State Technologies, Inc.";
const USAGE: &str = "Commands:
G [<address>] = Go (run till halted)
H = Help
M [<address>] = Memory dump
S [<address>] = Single step
Q = Quit
Enter = Repeat last command";
#[non_exhaustive]
#[derive(Copy, Clone)]
pub enum Command {
Go,
Help,
Memory,
Quit,
Step,
}
use Command::*;
#[non_exhaustive]
pub struct Monitor {
pub last_addr: Option<u16>,
pub last_cmd: Option<Command>,
pub step: bool,
pub sys: System,
}
impl Default for Monitor {
#[inline]
fn default() -> Self {
let mut sys = System::default();
sys.reset();
Self {
last_addr: None,
last_cmd: None,
step: false,
sys,
}
}
}
impl Monitor {
fn get_command(&mut self) -> Result<(Command, Option<u16>)> {
let mut input = String::new();
print!("> ");
stdout().flush()?;
_ = stdin().read_line(&mut input)?;
let (cmd, arg) = match input.to_ascii_uppercase().trim() {
"" => (self.last_cmd.unwrap_or(Step), None),
cmd if cmd.starts_with('G') => (Go, parse_arg(cmd)),
cmd if cmd.starts_with('M') => (Memory, parse_arg(cmd)),
cmd if cmd.starts_with('S') => (Step, parse_arg(cmd)),
cmd if cmd.starts_with('Q') => (Quit, None),
_ => (Help, None),
};
self.last_cmd = Some(cmd);
Ok((cmd, arg))
}
#[inline]
pub fn go(&mut self, addr: Option<u16>) {
self.step = false;
self.run(addr);
}
#[inline]
pub fn help(&self) {
println!("{USAGE}");
}
#[inline]
pub fn interact(&mut self) -> Result<()> {
println!("{BANNER}");
self.step = true;
self.last_cmd = Some(Step);
self.sys.debug_print();
loop {
match self.get_command()? {
(Go, addr) => self.go(addr),
(Help, _) => self.help(),
(Memory, addr) => self.memory(addr),
(Step, addr) => self.step(addr),
(Quit, _) => break,
}
}
Ok(())
}
#[inline]
pub fn memory(&mut self, addr: Option<u16>) {
let mut base = addr.unwrap_or(self.last_addr.unwrap_or(self.sys.cpu.pc));
for _ in 0..8_u8 {
print!("{base:04X}:");
let mut offset = 0;
for _ in 0..16_u8 {
let byte = self.sys.peek_mem(base.wrapping_add(offset));
print!(" {byte:02X}");
offset = offset.wrapping_add(1);
}
println!();
base = base.wrapping_add(16);
}
self.last_addr = Some(base);
}
#[inline]
pub fn run(&mut self, addr: Option<u16>) {
self.sys.cpu.halt = false;
if let Some(addr) = addr {
self.sys.cpu.pc = addr;
}
loop {
self.sys.tick();
if self.sys.cpu.state == FetchOpcode {
if self.sys.cpu.halt {
println!("halted");
break;
}
if self.step {
break;
}
}
}
self.last_addr = Some(self.sys.cpu.pc);
self.sys.debug_print();
}
#[inline]
pub fn step(&mut self, addr: Option<u16>) {
self.step = true;
self.run(addr);
}
}
#[expect(clippy::print_stderr, reason = "error")]
fn parse_arg(cmd: &str) -> Option<u16> {
match cmd.split_once(' ') {
Some((_, arg)) => u16::from_str_radix(arg, 16)
.map_err(|err| eprintln!("parsing '{arg}' as hex literal: {err}"))
.ok(),
None => None,
}
}