diff --git a/Cargo.lock b/Cargo.lock index 93bedae..d54abbb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3,5 +3,5 @@ version = 3 [[package]] -name = "m8ni-rs" +name = "m8db" version = "0.1.0" diff --git a/Cargo.toml b/Cargo.toml index f25889d..f9dd6c3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "m8ni-rs" +name = "m8db" version = "0.1.0" edition = "2018" diff --git a/src/db.rs b/src/db.rs new file mode 100644 index 0000000..a4f4664 --- /dev/null +++ b/src/db.rs @@ -0,0 +1,179 @@ +use crate::stmt::Stmt; +use std::io::Write; + +#[derive(Debug, Clone)] +struct Vm { + stmts: Vec, + pc: usize, + registers: Vec, + breakpoints: Vec, +} + +#[derive(Debug, Copy, Clone)] +enum VmState { + Run, + Break, + Stop, +} + +impl Vm { + fn step(&mut self) -> VmState { + let pc = self.pc; + self.pc += 1; + match self.stmts[pc] { + Stmt::Inc(r) => self.registers[r] += 1, + Stmt::Dec(r) => self.registers[r] -= 1, + Stmt::IsZero(r, line) => { + if self.registers[r] == 0 { + self.pc = line; + } + } + Stmt::Jump(line) => self.pc = line, + Stmt::Stop => return VmState::Stop, + } + if self.breakpoints.contains(&self.pc) { + VmState::Break + } else { + VmState::Run + } + } + + fn run(&mut self) -> VmState { + while let VmState::Run = self.step() {} + VmState::Break + } +} + +#[derive(Debug, Copy, Clone)] +enum VmInstruction { + Step, + Run, + Break(usize), +} + +pub fn run(stmts: Vec) { + let max_register = max_register(&stmts); + let mut vm = Vm { + stmts, + pc: 0, + registers: vec![0; max_register], + breakpoints: vec![], + }; + + loop { + match debug_input(&mut vm) { + VmInstruction::Run => match vm.run() { + VmState::Stop => break, + VmState::Run => unreachable!(), + _ => {} + }, + VmInstruction::Step => match vm.step() { + VmState::Stop => break, + _ => {} + }, + VmInstruction::Break(line) => { + let position = vm.breakpoints.iter().position(|point| *point == line); + match position { + None => vm.breakpoints.push(line), + Some(pos) => { + vm.breakpoints.remove(pos); + } + } + } + } + } +} + +fn debug_input(vm: &Vm) -> VmInstruction { + loop { + let mut input_buf = String::new(); + print!("(m8db) "); + std::io::stdout().flush().unwrap(); + std::io::stdin().read_line(&mut input_buf).unwrap(); + let input = input_buf.trim(); + let mut iter = input.split_ascii_whitespace(); + match iter.next() { + Some(str) => match str { + "r" | "register" => print_registers(vm), + "p" | "program" => print_program(vm), + "h" | "?" | "help" => print_help(), + "b" | "break" => match iter.next() { + Some(num) => match num.parse() { + Ok(num) => return VmInstruction::Break(num), + Err(_) => println!("Invalid argument provided"), + }, + None => print_breakpoints(vm), + }, + "c" | "continue" => return VmInstruction::Run, + "s" | "step" => return VmInstruction::Step, + _ => {} + }, + None => {} + } + } +} + +fn max_register(stmts: &[Stmt]) -> usize { + stmts + .iter() + .map(|stmt| match stmt { + Stmt::Inc(r) => *r, + Stmt::Dec(r) => *r, + Stmt::IsZero(r, _) => *r, + Stmt::Jump(_) => 0, + Stmt::Stop => 0, + }) + .max() + .unwrap_or(0) +} + +fn print_registers(vm: &Vm) { + println!("Registers:"); + for (i, r) in vm.registers.iter().enumerate() { + println!("{: >4} : {}", i, r); + } +} + +fn print_program(vm: &Vm) { + use std::cmp::{max, min}; + + println!("Program:"); + let lower = max(vm.pc, 5) - 5; + let higher = min(vm.pc, vm.stmts.len() - 6) + 5; + + for i in lower..higher { + let stmt = vm.stmts[i]; + if i == vm.pc { + println!("> {} {}", i, stmt) + } else { + println!("{} {}", i, stmt); + } + } +} + +fn print_breakpoints(vm: &Vm) { + println!( + "Breakpoints: + {} + ", + vm.breakpoints + .iter() + .map(|p| p.to_string()) + .collect::>() + .join(", ") + ); +} + +fn print_help() { + println!( + "List of commands and their aliases: + + step (s) -- Steps the program forward by one step + break (b) -- Set a breakpoint to a line, use again to toggle + continue (c) -- Run the program until the next breakpoint + register (r) -- Shows the contents of the registers + program (p) -- Shows where the program currently is + help (h, ?) -- Shows this help page + " + ); +} diff --git a/src/main.rs b/src/main.rs index b5d0721..deb302e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,54 +1,22 @@ -enum Stmt { - Inc(usize), - Dec(usize), - IsZero(usize, usize), - Jump(usize), - Stop -} +mod db; +mod stmt; fn main() { let filename = match std::env::args().skip(1).next() { Some(name) => name, - None => eprintln!("error: no file provided.\nUsage: "), + None => { + eprintln!("error: no file provided.\nUsage: "); + return; + } }; let program = std::fs::read_to_string(filename).unwrap(); - let statements = parse(&program); -} - -fn parse(text: &str) -> Result, String> { - text.lines().map(parse_line).collect() -} - - -fn parse_line(line: &str) -> Result { - const NO_REGISTER: fn() -> String = || "No register".to_string(); - const NO_LINE_NUMBER: fn() -> String = || "No line number".to_string(); - const EMPTY_LINE: fn() -> String = || "Empty line not allowed".to_string(); - - - let mut iter = line.split_ascii_whitespace(); - let first = iter.next().ok_or_else(EMPTY_LINE)?; - - Ok(match first { - "INC" => { - let register = iter.next().ok_or_else(NO_REGISTER)?.parse()?; - Stmt::Inc(register) - } - "DEC" => { - let register = iter.next().ok_or_else(NO_REGISTER)?.parse()?; - Stmt::Dec(register) - } - "IS_ZERO" => { - let register = iter.next().ok_or_else(NO_REGISTER)?.parse()?; - let line_number = iter.next().ok_or_else(NO_LINE_NUMBER)?.parse()?; - Stmt::IsZero(register, line_number) - } - "JUMP" => { - let line_number = iter.next().ok_or_else(NO_LINE_NUMBER)?.parse()?; - Stmt::Jump(line_number) - } - "STOP" => Stmt::Stop, - stmt => return Err(format!("Illegal instruction: '{}'", stmt)), - }) + let statements = match stmt::parse(&program) { + Ok(stmts) => stmts, + Err(str) => { + eprintln!("{}", str); + return; + } + }; + db::run(statements); } diff --git a/src/stmt.rs b/src/stmt.rs new file mode 100644 index 0000000..12992cd --- /dev/null +++ b/src/stmt.rs @@ -0,0 +1,80 @@ +use std::fmt::Formatter; +use std::num::ParseIntError; + +#[derive(Debug, Copy, Clone)] +pub enum Stmt { + Inc(usize), + Dec(usize), + IsZero(usize, usize), + Jump(usize), + Stop, +} + +pub fn parse(text: &str) -> Result, String> { + text.lines().map(parse_line).collect() +} + +fn parse_line(line: &str) -> Result { + const NO_REGISTER: fn() -> String = || "No register".to_string(); + const NO_LINE_NUMBER: fn() -> String = || "No line number".to_string(); + const EMPTY_LINE: fn() -> String = || "Empty line not allowed".to_string(); + const DISPLAY_ERR: fn(ParseIntError) -> String = |parse_err| parse_err.to_string(); + + let mut iter = line.split_ascii_whitespace(); + let first = iter.next().ok_or_else(EMPTY_LINE)?; + + Ok(match first { + "INC" => { + let register = iter + .next() + .ok_or_else(NO_REGISTER)? + .parse() + .map_err(DISPLAY_ERR)?; + Stmt::Inc(register) + } + "DEC" => { + let register = iter + .next() + .ok_or_else(NO_REGISTER)? + .parse() + .map_err(DISPLAY_ERR)?; + Stmt::Dec(register) + } + "IS_ZERO" => { + let register = iter + .next() + .ok_or_else(NO_REGISTER)? + .parse() + .map_err(DISPLAY_ERR)?; + let line_number = iter + .next() + .ok_or_else(NO_LINE_NUMBER)? + .parse() + .map_err(DISPLAY_ERR)?; + Stmt::IsZero(register, line_number) + } + "JUMP" => { + let line_number = iter + .next() + .ok_or_else(NO_LINE_NUMBER)? + .parse() + .map_err(DISPLAY_ERR)?; + Stmt::Jump(line_number) + } + "STOP" => Stmt::Stop, + stmt => return Err(format!("Illegal instruction: '{}'", stmt)), + }) +} + +impl std::fmt::Display for Stmt { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + match self { + Stmt::Inc(r) => write!(f, "INC {}", r)?, + Stmt::Dec(r) => write!(f, "DEC {}", r)?, + Stmt::IsZero(r, line) => write!(f, "IS_ZERO {} {}", r, line)?, + Stmt::Jump(r) => write!(f, "JUMP {}", r)?, + Stmt::Stop => write!(f, "STOP")?, + } + Ok(()) + } +}