diff --git a/Cargo.lock b/Cargo.lock index e36bfa7..d54abbb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4,4 +4,4 @@ version = 3 [[package]] name = "m8db" -version = "1.3.0" +version = "0.1.0" diff --git a/Cargo.toml b/Cargo.toml index ebbb657..f9dd6c3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "m8db" -version = "1.3.0" +version = "0.1.0" edition = "2018" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/README.md b/README.md index 699a2a2..f3cd631 100644 --- a/README.md +++ b/README.md @@ -1,23 +1,16 @@ # m8db -Debugger and interpreter for the M8 pseudo-assembly language. Inspired by `gdb` or `lldb` +Debugger and interpreter for the M8 pseudo-assembly language More infos: https://github.com/ah1m1/M8NI -Usage: `$ ./m8db (filename)` - # Instructions: * `INC r` * `DEC r` -* `JUMP label` * `JUMP line` * `STOP` -* `IS_ZERO r label` * `IS_ZERO r line` -* `.labelname` -`# anything` is a comment - -Where `r` is a register number, `line` is a line number and `label` is a label name. -`IS_ZERO` jumps to `label` if `r` is zero +Where `r` is a register number and `line` is a line number. +`IS_ZERO` jumps to `line` if `r` is zero diff --git a/src/db.rs b/src/db.rs new file mode 100644 index 0000000..5f267db --- /dev/null +++ b/src/db.rs @@ -0,0 +1,197 @@ +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 - 1; + } + } + Stmt::Jump(line) => self.pc = line - 1, + Stmt::Stop => return VmState::Stop, + } + if self.breakpoints.contains(&self.pc) { + VmState::Break + } else { + VmState::Run + } + } + + fn run(&mut self) -> VmState { + loop { + match self.step() { + state @ (VmState::Break | VmState::Stop) => return state, + _ => {} + } + } + } +} + +#[derive(Debug, Copy, Clone)] +enum VmInstruction { + Step, + Run, + Break(usize), + Set(usize, usize), +} + +pub fn run(stmts: Vec) { + let max_register_index = max_register(&stmts); + let mut vm = Vm { + stmts, + pc: 0, + registers: vec![0; max_register_index + 1], + 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); + } + } + } + VmInstruction::Set(r, value) => vm.registers[r] = value, + } + } +} + +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), + }, + "set" => match parse_set_command(&mut iter) { + Some((reg, value)) => return VmInstruction::Set(reg, value), + None => println!("Invalid arguments provided"), + }, + "c" | "continue" => return VmInstruction::Run, + "s" | "step" => return VmInstruction::Step, + _ => {} + }, + None => {} + } + } +} + +fn parse_set_command<'a>(iter: &mut impl Iterator) -> Option<(usize, usize)> { + let reg: usize = iter.next().and_then(|reg| reg.parse().ok())?; + let value: usize = iter.next().and_then(|value| value.parse().ok())?; + Some((reg, value)) +} + +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::min; + + println!("Program:"); + let lower = if vm.pc > 5 { vm.pc - 5 } else { 0 }; + let len = vm.stmts.len(); + let higher = if len < 5 { len } else { min(vm.pc + 5, len) }; + + 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 + set -- Sets a register to a value + 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 1ac60d2..bfdc7b0 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,7 +1,15 @@ -mod parse; -mod run; +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: "); + return; + } + }; + println!( "m8db - M8 Debugger (C) Nilstrieb (https://github.com/Nilstrieb/m8db) @@ -9,5 +17,14 @@ Type 'help' for help " ); - run::start(std::env::args().nth(1)); + let program = std::fs::read_to_string(filename).unwrap(); + let statements = match stmt::parse(&program) { + Ok(stmts) => stmts, + Err(str) => { + eprintln!("{}", str); + return; + } + }; + db::run(statements); + println!("Execution finished."); } diff --git a/src/parse.rs b/src/parse.rs deleted file mode 100644 index 41d0531..0000000 --- a/src/parse.rs +++ /dev/null @@ -1,257 +0,0 @@ -use std::collections::HashMap; -use std::fmt::Formatter; -use std::num::ParseIntError; - -/// A span referencing the line where a statement came from. Starts at 0 -#[derive(Debug, Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash)] -pub struct Span(pub usize); - -impl Span { - pub fn line_number(&self) -> usize { - self.0 + 1 - } -} - -/// A line number, starts at 1 -#[derive(Debug, Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash)] -pub struct LineNumber(pub usize); - -impl LineNumber { - pub fn span(&self) -> Span { - Span(self.0 - 1) - } -} - -/// An index into a `Vm` `Stmt` -#[derive(Debug, Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash)] -pub struct StmtIdx(pub usize); - -/// A register index -#[derive(Debug, Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash)] -pub struct Register(pub usize); - -#[derive(Debug, Copy, Clone)] -pub enum Stmt { - Inc(Register), - Dec(Register), - IsZero(Register, StmtIdx), - Jump(StmtIdx), - Stop, -} - -#[derive(Debug, Clone)] -pub struct Code<'a> { - pub stmts: Vec, - /// Has the same length as `stmts`, points to line numbers where the instructions come from - pub span: Vec, - pub code_lines: Vec<&'a str>, - pub file_name: String, -} - -#[derive(Debug, Clone)] -enum IrStmt<'a> { - Inc(Register), - Dec(Register), - IsZeroLabel(Register, &'a str), - IsZeroLine(Register, LineNumber), - JumpLabel(&'a str), - JumpLine(LineNumber), - Label(&'a str), - Stop, - None, -} - -#[derive(Debug)] -struct ParseErr { - span: Span, - inner: ParseErrInner, -} - -impl ParseErr { - fn new(span: Span, inner: ParseErrInner) -> Self { - Self { span, inner } - } -} - -#[derive(Debug)] -pub enum ParseErrInner { - OutOfBoundsLineRef(LineNumber), - LabelNotFound(String), - ParseIntErr(ParseIntError), - NoRegister, - NoLabelOrLine, - IllegalStmt(String), -} - -type StdResult = std::result::Result; -type Result = StdResult; - -impl std::fmt::Display for ParseErr { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - write!(f, "error on line '{}': ", self.span.line_number())?; - match &self.inner { - ParseErrInner::OutOfBoundsLineRef(referenced) => { - write!(f, "Referencing line '{}': out of bounds", referenced.0,) - } - ParseErrInner::LabelNotFound(label) => write!(f, "Label '{}' not found", label,), - ParseErrInner::ParseIntErr(err) => write!(f, "{}", err), - ParseErrInner::NoRegister => write!(f, "No register provided"), - ParseErrInner::NoLabelOrLine => write!(f, "No label or line provided"), - ParseErrInner::IllegalStmt(stmt) => write!(f, "Illegal statement: '{}'", stmt), - }?; - write!(f, ".") - } -} - -fn resolve_line_number( - stmts: &[(IrStmt, Span)], - number: LineNumber, - span: Span, -) -> Result { - match stmts - .iter() - .position(|(_, stmt_span)| stmt_span.line_number() == number.0) - { - Some(stmt_number) => Ok(StmtIdx(stmt_number)), - None => Err(ParseErr::new( - span, - ParseErrInner::OutOfBoundsLineRef(number), - )), - } -} - -fn resolve_label(labels: &HashMap<&str, StmtIdx>, span: Span, label: &str) -> Result { - match labels.get(label) { - Some(line) => Ok(*line), - None => Err(ParseErr::new( - span, - ParseErrInner::LabelNotFound(label.to_owned()), - )), - } -} - -pub fn parse(text: &str, file_name: String) -> StdResult { - let mut labels = HashMap::new(); - - let mut ir_statements = Vec::new(); - let mut statement_number = StmtIdx(0); - - let code_lines = text.lines().collect::>(); - - for (line_index, line) in code_lines.iter().enumerate() { - let span = Span(line_index); - let result = parse_line(span, line); - match result { - Ok(IrStmt::Label(name)) => { - labels.insert(name, statement_number); - } - Ok(IrStmt::None) => {} - Ok(stmt) => { - statement_number.0 += 1; - ir_statements.push((stmt, span)); - } - Err(err) => return Err(err.to_string()), - } - } - - let statements: Result> = ir_statements - .iter() - .filter(|stmt| !matches!(stmt, (IrStmt::None, _))) - .map(|(stmt, span)| match *stmt { - IrStmt::Inc(r) => Ok((Stmt::Inc(r), *span)), - IrStmt::Dec(r) => Ok((Stmt::Dec(r), *span)), - IrStmt::IsZeroLine(r, line_number) => Ok(( - Stmt::IsZero(r, resolve_line_number(&ir_statements, line_number, *span)?), - *span, - )), - IrStmt::JumpLine(line_number) => Ok(( - Stmt::Jump(resolve_line_number(&ir_statements, line_number, *span)?), - *span, - )), - IrStmt::IsZeroLabel(r, label) => Ok(( - Stmt::IsZero(r, resolve_label(&labels, *span, label)?), - *span, - )), - IrStmt::JumpLabel(label) => { - Ok((Stmt::Jump(resolve_label(&labels, *span, label)?), *span)) - } - IrStmt::Stop => Ok((Stmt::Stop, *span)), - IrStmt::Label(_) => unreachable!(), - IrStmt::None => unreachable!(), - }) - .collect(); - - statements - .map(|vec| { - let (stmts, span) = vec.iter().cloned().unzip(); - Code { - stmts, - span, - code_lines, - file_name, - } - }) - .map_err(|err| err.to_string()) -} - -fn parse_line(span: Span, line: &str) -> Result { - let no_label_or_line_number = || ParseErr::new(span, ParseErrInner::NoLabelOrLine); - - let mut iter = line.split_whitespace(); - let first = iter.next(); - let first = match first { - Some(first) => first, - None => return Ok(IrStmt::None), - }; - - Ok(match first { - "INC" => { - let register = next_register(&mut iter, span)?; - IrStmt::Inc(register) - } - "DEC" => { - let register = next_register(&mut iter, span)?; - IrStmt::Dec(register) - } - "IS_ZERO" => { - let register = next_register(&mut iter, span)?; - let jump_target = iter.next().ok_or_else(no_label_or_line_number)?; - if let Ok(line_number) = jump_target.parse::() { - IrStmt::IsZeroLine(register, LineNumber(line_number)) - } else { - IrStmt::IsZeroLabel(register, jump_target) - } - } - "JUMP" => { - let jump_target = iter.next().ok_or_else(no_label_or_line_number)?; - if let Ok(line_number) = jump_target.parse::() { - IrStmt::JumpLine(LineNumber(line_number)) - } else { - IrStmt::JumpLabel(jump_target) - } - } - "STOP" => IrStmt::Stop, - stmt => { - if let Some(stripped) = stmt.strip_prefix('.') { - IrStmt::Label(stripped) - } else if stmt.starts_with('#') { - IrStmt::None - } else { - return Err(ParseErr::new( - span, - ParseErrInner::IllegalStmt(stmt.to_owned()), - )); - } - } - }) -} - -fn next_register<'a>(iter: &mut impl Iterator, span: Span) -> Result { - iter.next() - .ok_or_else(|| ParseErr::new(span, ParseErrInner::NoRegister))? - .parse() - .map(|num| Register(num)) - .map_err(|parse_err: ParseIntError| { - ParseErr::new(span, ParseErrInner::ParseIntErr(parse_err)) - }) -} diff --git a/src/run.rs b/src/run.rs deleted file mode 100644 index ecca985..0000000 --- a/src/run.rs +++ /dev/null @@ -1,336 +0,0 @@ -use crate::parse; -use crate::parse::{Code, LineNumber, Register, Span, Stmt, StmtIdx}; -use std::io::Write; -use std::path::Path; - -#[derive(Debug, Clone)] -struct Vm<'a> { - stmts: Vec, - span: Vec, - code_lines: Vec<&'a str>, - pc: StmtIdx, - registers: Vec, - breakpoints: Vec, - file_name: String, -} - -#[derive(Debug, Copy, Clone)] -enum VmState { - Run, - Break, - Stop, - OutOfBounds, -} - -impl Vm<'_> { - fn step(&mut self) -> VmState { - let pc = self.pc; - match self.stmts.get(pc.0).cloned() { - Some(Stmt::Inc(r)) => self.registers[r.0] += 1, - Some(Stmt::Dec(r)) => self.registers[r.0] -= 1, - Some(Stmt::IsZero(r, index)) => { - if self.registers[r.0] == 0 { - self.pc = StmtIdx(index.0 - 1); - } - } - Some(Stmt::Jump(index)) => self.pc = StmtIdx(index.0 - 1), - Some(Stmt::Stop) => return VmState::Stop, - None => return VmState::OutOfBounds, - } - self.pc.0 += 1; - if self.breakpoints.contains(&self.pc) { - VmState::Break - } else { - VmState::Run - } - } - - fn run(&mut self, time_kind: VmRunKind) -> VmState { - let now = std::time::Instant::now(); - loop { - if let state @ (VmState::Break | VmState::Stop | VmState::OutOfBounds) = self.step() { - if let VmRunKind::WithTime = time_kind { - println!("Vm ran for {}ms.", now.elapsed().as_millis()); - } - return state; - } - } - } - - fn statement_at_span(&self, search_span: Span) -> Option { - self.span - .iter() - .position(|span| *span >= search_span) - .map(|idx| StmtIdx(idx)) - } -} - -#[derive(Debug, Copy, Clone)] -enum VmRunKind { - WithTime, - WithoutTime, -} - -#[derive(Debug, Copy, Clone)] -enum VmInstruction { - Step, - Run(VmRunKind), - Break(StmtIdx), - Set(Register, usize), - Stop, -} - -pub fn start(program_path: Option) { - if let Some(path) = program_path { - read_and_run(&path); - } - - loop { - match loading_input() { - LoadInstruction::Quit => return, - LoadInstruction::Load(path) => read_and_run(&path), - } - } -} - -fn read_and_run(path: &str) { - let path = Path::new(path); - - match std::fs::read_to_string(path) { - Ok(content) => match parse::parse(&content, filename(path)) { - Ok(stmts) => run(stmts), - Err(why) => eprintln!("{}", why), - }, - Err(why) => eprintln!("error while reading file: {}.", why), - }; -} - -#[derive(Debug, Clone)] -enum LoadInstruction { - Quit, - Load(String), -} - -fn loading_input() -> LoadInstruction { - loop { - let input = get_input(None); - let mut iter = input.split_whitespace(); - if let Some(str) = iter.next() { - match str { - "l" | "load" => match iter.next() { - Some(path) => return LoadInstruction::Load(path.to_owned()), - None => println!("error: No file path provided to load from."), - }, - "h" | "help" => print_load_help(), - "q" | "quit" => return LoadInstruction::Quit, - cmd => println!("error: Unknown command: {}.", cmd), - } - } - } -} - -fn filename(path: &Path) -> String { - path.file_stem().unwrap().to_str().unwrap().to_owned() -} - -fn run(code: Code) { - println!("Loaded {}.", code.file_name); - let max_register_index = max_register(&code.stmts); - let mut vm = Vm { - stmts: code.stmts, - span: code.span, - code_lines: code.code_lines, - file_name: code.file_name, - pc: StmtIdx(0), - registers: vec![0; max_register_index + 1], - breakpoints: vec![], - }; - - loop { - match debug_input(&vm) { - VmInstruction::Stop => break, - VmInstruction::Run(time_kind) => match vm.run(time_kind) { - VmState::Stop => break, - VmState::OutOfBounds => { - print_program(&vm); - print_registers(&vm); - eprintln!("error: Program ran out of bounds."); - return; - } - VmState::Run => { - unreachable!("internal error: Program still running after returning from run.") - } - _ => {} - }, - VmInstruction::Step => match vm.step() { - VmState::Stop => break, - VmState::OutOfBounds => { - print_program(&vm); - print_registers(&vm); - eprintln!("error: Program ran out of bounds."); - return; - } - _ => {} - }, - 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); - } - } - } - VmInstruction::Set(r, value) => vm.registers[r.0] = value, - } - } - println!("Execution finished."); -} - -fn debug_input(vm: &Vm) -> VmInstruction { - loop { - let input = get_input(Some(&vm.file_name)); - let mut iter = input.split_whitespace(); - if let Some(str) = iter.next() { - match str { - "r" | "register" => print_registers(vm), - "p" | "program" => print_program(vm), - "h" | "?" | "help" => print_debug_help(), - "b" | "break" => match iter.next() { - Some(line_number) => match line_number.parse::() { - Ok(line_number) => { - let stmt_pos = - match vm.statement_at_span(LineNumber(line_number).span()) { - Some(pos) => pos, - None => { - println!( - "error: Line number '{}' out of bounds for length {}.", - line_number, - vm.code_lines.len() - ); - continue; - } - }; - return VmInstruction::Break(stmt_pos); - } - Err(_) => println!("error: Invalid argument provided."), - }, - None => print_breakpoints(vm), - }, - "set" => match parse_set_command(&mut iter) { - Some((reg, value)) => return VmInstruction::Set(reg, value), - None => println!("error: Invalid arguments provided."), - }, - "c" | "continue" => { - if let Some("time") = iter.next() { - return VmInstruction::Run(VmRunKind::WithTime); - } - return VmInstruction::Run(VmRunKind::WithoutTime); - } - "s" | "step" => return VmInstruction::Step, - "q" | "quit" => return VmInstruction::Stop, - cmd => println!("error: Unknown command: {}.", cmd), - } - } - } -} - -fn parse_set_command<'a>(iter: &mut impl Iterator) -> Option<(Register, usize)> { - let reg = iter.next().and_then(|reg| reg.parse().ok())?; - let value = iter.next().and_then(|value| value.parse().ok())?; - Some((Register(reg), value)) -} - -fn max_register(stmts: &[Stmt]) -> usize { - stmts - .iter() - .map(|stmt| match stmt { - Stmt::Inc(r) => r.0, - Stmt::Dec(r) => r.0, - Stmt::IsZero(r, _) => r.0, - 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::min; - - if let Some(span_pc) = vm.span.get(vm.pc.0) { - println!("Program:"); - - let lower = span_pc.0.saturating_sub(5); - let higher = min(vm.code_lines.len(), span_pc.0 + 6); - - for line_index in lower..higher { - let code_line = vm.code_lines[line_index]; - if line_index == span_pc.0 { - println!("> {} {}", Span(line_index).line_number(), code_line); - } else { - println!("{} {}", Span(line_index).line_number(), code_line); - } - } - } else { - println!("Reached the end of the program."); - } -} - -fn print_breakpoints(vm: &Vm) { - println!( - "Breakpoints: - {} - ", - vm.breakpoints - .iter() - .map(|p| p.0.to_string()) - .collect::>() - .join(", ") - ); -} - -fn print_load_help() { - println!( - "List of commands and their aliases: - - load (l) -- Load and run a program - quit (q) -- Quits the program - help (h, ?) -- Shows this help page - " - ); -} - -fn print_debug_help() { - println!( - "List of commands and their aliases: - - step (s) -- Steps the program forward by one step - set -- Sets a register to a value - break (b) -- Set a breakpoint to a line, use again to toggle - continue (c) (time) -- Run the program until the next breakpoint, add 'time' to display execution time - register (r) -- Shows the contents of the registers - program (p) -- Shows where the program currently is - quit (q) -- Stop execution of the current program - help (h, ?) -- Shows this help page - " - ); -} - -fn get_input(prompt: Option<&str>) -> String { - let mut input_buf = String::new(); - match prompt { - None => print!("(m8db) "), - Some(text) => print!("(m8db - {}) ", text), - } - std::io::stdout().flush().unwrap(); - std::io::stdin().read_line(&mut input_buf).unwrap(); - input_buf.trim().to_owned() -} 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(()) + } +} diff --git a/test.m8 b/test.m8 index 85a45bc..de343cd 100644 --- a/test.m8 +++ b/test.m8 @@ -1,7 +1,5 @@ -INC 1 -JUMP 7 -INC 2 -STOP +IS_ZERO 1 5 INC 3 - -JUMP 4 \ No newline at end of file +DEC 1 +JUMP 1 +STOP \ No newline at end of file