From ebae9c0dde143cde8b71ba28e20f0a203ea97f4a Mon Sep 17 00:00:00 2001 From: nils <48135649+Nilstrieb@users.noreply.github.com> Date: Sat, 18 Sep 2021 22:54:31 +0200 Subject: [PATCH 01/13] Update README.md --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 4b60fcc..16d73a9 100644 --- a/README.md +++ b/README.md @@ -11,9 +11,11 @@ Usage: `$ ./m8db ` * `INC r` * `DEC r` * `JUMP label` +* `JUMP line` * `STOP` * `IS_ZERO r label` +* `IS_ZERO r line` * `.labelname` -Where `r` is a register number and `label` is a label name. +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 From 27f013358ec8b728a416f8b7487779f7da905574 Mon Sep 17 00:00:00 2001 From: Nilstrieb Date: Sat, 18 Sep 2021 23:16:44 +0200 Subject: [PATCH 02/13] better program display and fixed stepping out of bounds --- src/db.rs | 54 +++++++++++++++++++++++++++--------------------------- 1 file changed, 27 insertions(+), 27 deletions(-) diff --git a/src/db.rs b/src/db.rs index 222f0ae..1e1d63e 100644 --- a/src/db.rs +++ b/src/db.rs @@ -94,14 +94,19 @@ pub fn run(code: Code) { eprintln!("error: Program ran out of bounds."); return; } - VmState::Run => unreachable!(), + VmState::Run => unreachable!("Program still running after returning from run"), _ => {} }, - VmInstruction::Step => { - if let VmState::Stop = vm.step() { - break; + 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 { @@ -193,31 +198,26 @@ fn print_registers(vm: &Vm) { fn print_program(vm: &Vm) { use std::cmp::min; - // todo rewrite all of this, it's terrible! + if let Some(span_pc) = vm.span.get(vm.pc) { + println!( + "Program: (pc = {}, line = {})", + vm.pc, + span_pc.line_number() + ); - println!( - "Program: (pc = {}, line = {})", - vm.pc, - vm.span - .get(vm.pc) - .cloned() - .unwrap_or(Span(vm.span.len())) - .line_number() - ); - let lower_stmt = if vm.pc > 5 { vm.pc - 5 } else { 0 }; - let len = vm.stmts.len(); - let higher_stmt = if len < 5 { len } else { min(vm.pc + 5, len) }; + let lower = span_pc.0.saturating_sub(5); + let higher = min(vm.code_lines.len(), span_pc.0 + 6); - let lower_code = vm.span[lower_stmt].0; - let higher_code = vm.span[higher_stmt - 1].0; - - for line_index in lower_code..higher_code { - let code_line = vm.code_lines[line_index]; - if line_index == vm.span.get(vm.pc).cloned().unwrap_or(Span(vm.span.len())).0 { - println!("> {} {}", line_index + 1, code_line) - } else { - println!("{} {}", line_index + 1, code_line); + 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."); } } From e48bf1ee094c0b07a349202e6578a6f857bd71db Mon Sep 17 00:00:00 2001 From: Nilstrieb Date: Sat, 18 Sep 2021 23:25:32 +0200 Subject: [PATCH 03/13] updated version in Cargo.toml --- Cargo.lock | 2 +- Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d54abbb..d67bc72 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4,4 +4,4 @@ version = 3 [[package]] name = "m8db" -version = "0.1.0" +version = "1.2.1" diff --git a/Cargo.toml b/Cargo.toml index f9dd6c3..1eb7108 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "m8db" -version = "0.1.0" +version = "1.2.1" edition = "2018" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html From 5d31ce90d66cd2c1a018dc655d934b041caf4780 Mon Sep 17 00:00:00 2001 From: Nilstrieb Date: Sun, 19 Sep 2021 00:00:41 +0200 Subject: [PATCH 04/13] added file load mode --- src/db.rs | 91 ++++++++++++++++++++++++++++++++++++++++++++++++----- src/main.rs | 19 +---------- src/stmt.rs | 4 ++- 3 files changed, 88 insertions(+), 26 deletions(-) diff --git a/src/db.rs b/src/db.rs index 1e1d63e..1682934 100644 --- a/src/db.rs +++ b/src/db.rs @@ -1,5 +1,7 @@ +use crate::stmt; use crate::stmt::{Code, LineNumber, Span, Stmt}; use std::io::Write; +use std::path::Path; #[derive(Debug, Clone)] struct Vm<'a> { @@ -9,6 +11,7 @@ struct Vm<'a> { pc: usize, registers: Vec, breakpoints: Vec, + file_name: String, } #[derive(Debug, Copy, Clone)] @@ -71,14 +74,66 @@ enum VmInstruction { Run(VmRunKind), Break(usize), Set(usize, usize), + Stop, } -pub fn run(code: Code) { +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<'a>(path: &str) { + let path = Path::new(path); + + match std::fs::read_to_string(path) { + Ok(content) => match stmt::parse(&content, filename(&path)) { + Ok(stmts) => run(stmts), + Err(why) => eprintln!("parse error: {}", why), + }, + Err(why) => eprintln!("file error: {}", 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!("No file path provided to load from"), + }, + "h" | "help" => print_load_help(), + "q" | "quit" => return LoadInstruction::Quit, + _ => {} + } + } + } +} + +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: 0, registers: vec![0; max_register_index + 1], breakpoints: vec![], @@ -86,6 +141,7 @@ pub fn run(code: Code) { loop { match debug_input(&vm) { + VmInstruction::Stop => break, VmInstruction::Run(time_kind) => match vm.run(time_kind) { VmState::Stop => break, VmState::OutOfBounds => { @@ -119,17 +175,18 @@ pub fn run(code: Code) { VmInstruction::Set(r, value) => vm.registers[r] = value, } } + println!("Execution finished."); } fn debug_input(vm: &Vm) -> VmInstruction { loop { - let input = get_input(); - let mut iter = input.split_ascii_whitespace(); + 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_help(), + "h" | "?" | "help" => print_debug_help(), "b" | "break" => match iter.next() { Some(line_number) => match line_number.parse::() { Ok(line_number) => { @@ -162,6 +219,7 @@ fn debug_input(vm: &Vm) -> VmInstruction { return VmInstruction::Run(VmRunKind::WithoutTime); } "s" | "step" => return VmInstruction::Step, + "q" | "quit" => return VmInstruction::Stop, _ => {} } } @@ -234,7 +292,18 @@ fn print_breakpoints(vm: &Vm) { ); } -fn print_help() { +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: @@ -244,15 +313,23 @@ fn print_help() { 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() -> String { +fn get_input(prompt: Option<&str>) -> String { let mut input_buf = String::new(); - print!("(m8db) "); + 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() } + +fn filename(path: &Path) -> String { + path.file_stem().unwrap().to_str().unwrap().to_owned() +} diff --git a/src/main.rs b/src/main.rs index c2e6c72..107993d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -2,14 +2,6 @@ mod db; mod stmt; fn main() { - let filename = match std::env::args().nth(1) { - Some(name) => name, - None => { - eprintln!("error: no file provided.\nUsage: "); - return; - } - }; - println!( "m8db - M8 Debugger (C) Nilstrieb (https://github.com/Nilstrieb/m8db) @@ -17,14 +9,5 @@ Type 'help' for help " ); - 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."); + db::start(std::env::args().nth(1)); } diff --git a/src/stmt.rs b/src/stmt.rs index 2648d32..f3b9d41 100644 --- a/src/stmt.rs +++ b/src/stmt.rs @@ -37,6 +37,7 @@ pub struct Code<'a> { /// 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, } enum IrStmt<'a> { @@ -50,7 +51,7 @@ enum IrStmt<'a> { Stop, } -pub fn parse(text: &str) -> Result { +pub fn parse(text: &str, file_name: String) -> Result { let mut labels = HashMap::new(); let mut statements = Vec::new(); @@ -159,6 +160,7 @@ pub fn parse(text: &str) -> Result { stmts, span, code_lines, + file_name, } }) } From c38a932d23cc944f82f0efed0a080182d54df424 Mon Sep 17 00:00:00 2001 From: Nilstrieb Date: Sun, 19 Sep 2021 00:04:40 +0200 Subject: [PATCH 05/13] show better errors --- README.md | 2 +- src/db.rs | 26 ++++++++++++-------------- 2 files changed, 13 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index 16d73a9..6058cee 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ Debugger and interpreter for the M8 pseudo-assembly language. Inspired by `gdb` More infos: https://github.com/ah1m1/M8NI -Usage: `$ ./m8db ` +Usage: `$ ./m8db (filename)` # Instructions: diff --git a/src/db.rs b/src/db.rs index 1682934..ba7b864 100644 --- a/src/db.rs +++ b/src/db.rs @@ -96,9 +96,9 @@ fn read_and_run<'a>(path: &str) { match std::fs::read_to_string(path) { Ok(content) => match stmt::parse(&content, filename(&path)) { Ok(stmts) => run(stmts), - Err(why) => eprintln!("parse error: {}", why), + Err(why) => eprintln!("error while parsing: {}.", why), }, - Err(why) => eprintln!("file error: {}", why), + Err(why) => eprintln!("error while reading file: {}.", why), }; } @@ -116,11 +116,11 @@ fn loading_input() -> LoadInstruction { match str { "l" | "load" => match iter.next() { Some(path) => return LoadInstruction::Load(path.to_owned()), - None => println!("No file path provided to load from"), + 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), } } } @@ -150,7 +150,9 @@ fn run(code: Code) { eprintln!("error: Program ran out of bounds."); return; } - VmState::Run => unreachable!("Program still running after returning from run"), + VmState::Run => { + unreachable!("internal error: Program still running after returning from run.") + } _ => {} }, VmInstruction::Step => match vm.step() { @@ -195,7 +197,7 @@ fn debug_input(vm: &Vm) -> VmInstruction { Some(pos) => pos, None => { println!( - "Line number '{}' out of bounds for length {}.", + "error: Line number '{}' out of bounds for length {}.", line_number, vm.code_lines.len() ); @@ -204,13 +206,13 @@ fn debug_input(vm: &Vm) -> VmInstruction { }; return VmInstruction::Break(stmt_pos); } - Err(_) => println!("Invalid argument provided"), + 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!("Invalid arguments provided"), + None => println!("error: Invalid arguments provided."), }, "c" | "continue" => { if let Some("time") = iter.next() { @@ -220,7 +222,7 @@ fn debug_input(vm: &Vm) -> VmInstruction { } "s" | "step" => return VmInstruction::Step, "q" | "quit" => return VmInstruction::Stop, - _ => {} + cmd => println!("error: Unknown command: {}.", cmd), } } } @@ -257,11 +259,7 @@ fn print_program(vm: &Vm) { use std::cmp::min; if let Some(span_pc) = vm.span.get(vm.pc) { - println!( - "Program: (pc = {}, line = {})", - vm.pc, - span_pc.line_number() - ); + println!("Program:"); let lower = span_pc.0.saturating_sub(5); let higher = min(vm.code_lines.len(), span_pc.0 + 6); From 8acef7bbff3c8497c106b2a82c4ca76304ca8082 Mon Sep 17 00:00:00 2001 From: Nilstrieb Date: Sun, 19 Sep 2021 00:05:48 +0200 Subject: [PATCH 06/13] version 1.3.0 --- Cargo.lock | 2 +- Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d67bc72..e36bfa7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4,4 +4,4 @@ version = 3 [[package]] name = "m8db" -version = "1.2.1" +version = "1.3.0" diff --git a/Cargo.toml b/Cargo.toml index 1eb7108..ebbb657 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "m8db" -version = "1.2.1" +version = "1.3.0" edition = "2018" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html From 27908bf079c87ba8c5cbbe0df30983bf167650ef Mon Sep 17 00:00:00 2001 From: nils <48135649+Nilstrieb@users.noreply.github.com> Date: Sun, 19 Sep 2021 00:11:02 +0200 Subject: [PATCH 07/13] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 16d73a9..912c475 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ Debugger and interpreter for the M8 pseudo-assembly language. Inspired by `gdb` More infos: https://github.com/ah1m1/M8NI -Usage: `$ ./m8db ` +Usage: `$ ./m8db ` or `./m8db` # Instructions: From a24b93fd23dec55a017ba46d7b818468703d2381 Mon Sep 17 00:00:00 2001 From: Nilstrieb Date: Sun, 19 Sep 2021 14:18:02 +0200 Subject: [PATCH 08/13] fixed the last span/line number bug --- src/db.rs | 6 +++--- src/stmt.rs | 8 +++++++- test.m8 | 22 +++++----------------- 3 files changed, 15 insertions(+), 21 deletions(-) diff --git a/src/db.rs b/src/db.rs index ba7b864..67b4a2d 100644 --- a/src/db.rs +++ b/src/db.rs @@ -28,12 +28,12 @@ impl Vm<'_> { match self.stmts.get(pc).cloned() { Some(Stmt::Inc(r)) => self.registers[r] += 1, Some(Stmt::Dec(r)) => self.registers[r] -= 1, - Some(Stmt::IsZero(r, line)) => { + Some(Stmt::IsZero(r, index)) => { if self.registers[r] == 0 { - self.pc = line - 1; + self.pc = index - 1; } } - Some(Stmt::Jump(line)) => self.pc = line - 1, + Some(Stmt::Jump(index)) => self.pc = index - 1, Some(Stmt::Stop) => return VmState::Stop, None => return VmState::OutOfBounds, } diff --git a/src/stmt.rs b/src/stmt.rs index f3b9d41..d58a78d 100644 --- a/src/stmt.rs +++ b/src/stmt.rs @@ -72,7 +72,13 @@ pub fn parse(text: &str, file_name: String) -> Result { statement_number += 1; statements.push((stmt, Span(line_index))); } - Err(msg) => return Err(format!("error on line '{}': {}", line_index - 1, msg)), + Err(msg) => { + return Err(format!( + "error on line '{}': {}", + Span(line_index).line_number(), + msg + )) + } } } diff --git a/test.m8 b/test.m8 index 2cc19c3..1ba61ca 100644 --- a/test.m8 +++ b/test.m8 @@ -1,20 +1,8 @@ -.test +# hi INC 1 -.test +JUMP 8 INC 2 -.test +STOP INC 3 -.test -INC 4 -.test -INC 5 -.test -INC 6 -.test -INC 7 -.test -INC 8 -.test -INC 9 -.test -INC 10 +#another comment +JUMP 4 \ No newline at end of file From fc974548abd961016bd1fc17a7392f592a0d8754 Mon Sep 17 00:00:00 2001 From: Nilstrieb Date: Sun, 19 Sep 2021 14:23:28 +0200 Subject: [PATCH 09/13] added comment support --- README.md | 2 ++ src/stmt.rs | 15 +++++++++++---- test.m8 | 2 +- 3 files changed, 14 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 6058cee..699a2a2 100644 --- a/README.md +++ b/README.md @@ -17,5 +17,7 @@ Usage: `$ ./m8db (filename)` * `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 diff --git a/src/stmt.rs b/src/stmt.rs index d58a78d..a44106d 100644 --- a/src/stmt.rs +++ b/src/stmt.rs @@ -49,6 +49,7 @@ enum IrStmt<'a> { JumpLine(LineNumber), Label(&'a str), Stop, + None, } pub fn parse(text: &str, file_name: String) -> Result { @@ -60,14 +61,12 @@ pub fn parse(text: &str, file_name: String) -> Result { let code_lines = text.lines().collect::>(); for (line_index, line) in code_lines.iter().enumerate() { - if line.split_whitespace().next().is_none() { - continue; - } let result = parse_line(line); match result { Ok(IrStmt::Label(name)) => { labels.insert(name, statement_number); } + Ok(IrStmt::None) => {} Ok(stmt) => { statement_number += 1; statements.push((stmt, Span(line_index))); @@ -84,6 +83,7 @@ pub fn parse(text: &str, file_name: String) -> Result { let result: Result, String> = 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)), @@ -157,6 +157,7 @@ pub fn parse(text: &str, file_name: String) -> Result { )), IrStmt::Stop => Ok((Stmt::Stop, *span)), IrStmt::Label(_) => unreachable!(), + IrStmt::None => unreachable!(), }) .collect(); @@ -177,7 +178,11 @@ fn parse_line(line: &str) -> Result { let display_err = |parse_err: ParseIntError| parse_err.to_string(); let mut iter = line.split_whitespace(); - let first = iter.next().expect("Empty lines filtered out"); + let first = iter.next(); + let first = match first { + Some(first) => first, + None => return Ok(IrStmt::None), + }; Ok(match first { "INC" => { @@ -221,6 +226,8 @@ fn parse_line(line: &str) -> Result { stmt => { if stmt.starts_with('.') { IrStmt::Label(&stmt[1..]) + } else if stmt.starts_with('#') { + IrStmt::None } else { return Err(format!("Illegal instruction: '{}'", stmt)); } diff --git a/test.m8 b/test.m8 index 1ba61ca..87a696c 100644 --- a/test.m8 +++ b/test.m8 @@ -4,5 +4,5 @@ JUMP 8 INC 2 STOP INC 3 -#another comment + JUMP 4 \ No newline at end of file From 0c201410b757c7b0622473675e28c31ae5b2202a Mon Sep 17 00:00:00 2001 From: Nilstrieb Date: Sun, 19 Sep 2021 14:24:36 +0200 Subject: [PATCH 10/13] remove impl std::fmt::Display for Stmt --- src/stmt.rs | 16 +--------------- 1 file changed, 1 insertion(+), 15 deletions(-) diff --git a/src/stmt.rs b/src/stmt.rs index a44106d..782cc71 100644 --- a/src/stmt.rs +++ b/src/stmt.rs @@ -1,6 +1,5 @@ use std::collections::HashMap; -use std::fmt::Formatter; -use std::num::ParseIntError; +ouse 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)] @@ -234,16 +233,3 @@ fn parse_line(line: &str) -> Result { } }) } - -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(()) - } -} From 3f159f644d14bce4636f14a230dc077862c9ebf2 Mon Sep 17 00:00:00 2001 From: Nilstrieb Date: Sun, 19 Sep 2021 14:24:48 +0200 Subject: [PATCH 11/13] remove impl std::fmt::Display for Stmt --- src/stmt.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/stmt.rs b/src/stmt.rs index 782cc71..49d5738 100644 --- a/src/stmt.rs +++ b/src/stmt.rs @@ -1,5 +1,5 @@ use std::collections::HashMap; -ouse std::num::ParseIntError; +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)] From 8a32cbf057190fae01592dcd0e4a4f24826a1b7f Mon Sep 17 00:00:00 2001 From: Nilstrieb Date: Sun, 19 Sep 2021 14:26:34 +0200 Subject: [PATCH 12/13] clippy lints --- src/db.rs | 4 ++-- src/stmt.rs | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/db.rs b/src/db.rs index 67b4a2d..fda1a3d 100644 --- a/src/db.rs +++ b/src/db.rs @@ -90,11 +90,11 @@ pub fn start(program_path: Option) { } } -fn read_and_run<'a>(path: &str) { +fn read_and_run(path: &str) { let path = Path::new(path); match std::fs::read_to_string(path) { - Ok(content) => match stmt::parse(&content, filename(&path)) { + Ok(content) => match stmt::parse(&content, filename(path)) { Ok(stmts) => run(stmts), Err(why) => eprintln!("error while parsing: {}.", why), }, diff --git a/src/stmt.rs b/src/stmt.rs index 49d5738..f545b7d 100644 --- a/src/stmt.rs +++ b/src/stmt.rs @@ -223,8 +223,8 @@ fn parse_line(line: &str) -> Result { } "STOP" => IrStmt::Stop, stmt => { - if stmt.starts_with('.') { - IrStmt::Label(&stmt[1..]) + if let Some(stripped) = stmt.strip_prefix('.') { + IrStmt::Label(stripped) } else if stmt.starts_with('#') { IrStmt::None } else { From fc4fe7235a2472cbcb72f5a7cab0123712aa4034 Mon Sep 17 00:00:00 2001 From: Nilstrieb Date: Sun, 19 Sep 2021 15:37:13 +0200 Subject: [PATCH 13/13] refactoring --- src/main.rs | 6 +- src/parse.rs | 257 ++++++++++++++++++++++++++++++++++++++++++ src/{db.rs => run.rs} | 67 +++++------ src/stmt.rs | 235 -------------------------------------- test.m8 | 3 +- 5 files changed, 296 insertions(+), 272 deletions(-) create mode 100644 src/parse.rs rename src/{db.rs => run.rs} (87%) delete mode 100644 src/stmt.rs diff --git a/src/main.rs b/src/main.rs index 107993d..1ac60d2 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,5 +1,5 @@ -mod db; -mod stmt; +mod parse; +mod run; fn main() { println!( @@ -9,5 +9,5 @@ Type 'help' for help " ); - db::start(std::env::args().nth(1)); + run::start(std::env::args().nth(1)); } diff --git a/src/parse.rs b/src/parse.rs new file mode 100644 index 0000000..41d0531 --- /dev/null +++ b/src/parse.rs @@ -0,0 +1,257 @@ +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/db.rs b/src/run.rs similarity index 87% rename from src/db.rs rename to src/run.rs index fda1a3d..ecca985 100644 --- a/src/db.rs +++ b/src/run.rs @@ -1,5 +1,5 @@ -use crate::stmt; -use crate::stmt::{Code, LineNumber, Span, Stmt}; +use crate::parse; +use crate::parse::{Code, LineNumber, Register, Span, Stmt, StmtIdx}; use std::io::Write; use std::path::Path; @@ -8,9 +8,9 @@ struct Vm<'a> { stmts: Vec, span: Vec, code_lines: Vec<&'a str>, - pc: usize, + pc: StmtIdx, registers: Vec, - breakpoints: Vec, + breakpoints: Vec, file_name: String, } @@ -25,19 +25,19 @@ enum VmState { impl Vm<'_> { fn step(&mut self) -> VmState { let pc = self.pc; - match self.stmts.get(pc).cloned() { - Some(Stmt::Inc(r)) => self.registers[r] += 1, - Some(Stmt::Dec(r)) => self.registers[r] -= 1, + 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 { - self.pc = index - 1; + if self.registers[r.0] == 0 { + self.pc = StmtIdx(index.0 - 1); } } - Some(Stmt::Jump(index)) => self.pc = index - 1, + Some(Stmt::Jump(index)) => self.pc = StmtIdx(index.0 - 1), Some(Stmt::Stop) => return VmState::Stop, None => return VmState::OutOfBounds, } - self.pc += 1; + self.pc.0 += 1; if self.breakpoints.contains(&self.pc) { VmState::Break } else { @@ -57,8 +57,11 @@ impl Vm<'_> { } } - fn statement_at_span(&self, search_span: Span) -> Option { - self.span.iter().position(|span| *span >= search_span) + fn statement_at_span(&self, search_span: Span) -> Option { + self.span + .iter() + .position(|span| *span >= search_span) + .map(|idx| StmtIdx(idx)) } } @@ -72,8 +75,8 @@ enum VmRunKind { enum VmInstruction { Step, Run(VmRunKind), - Break(usize), - Set(usize, usize), + Break(StmtIdx), + Set(Register, usize), Stop, } @@ -94,9 +97,9 @@ fn read_and_run(path: &str) { let path = Path::new(path); match std::fs::read_to_string(path) { - Ok(content) => match stmt::parse(&content, filename(path)) { + Ok(content) => match parse::parse(&content, filename(path)) { Ok(stmts) => run(stmts), - Err(why) => eprintln!("error while parsing: {}.", why), + Err(why) => eprintln!("{}", why), }, Err(why) => eprintln!("error while reading file: {}.", why), }; @@ -126,6 +129,10 @@ fn loading_input() -> LoadInstruction { } } +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); @@ -134,7 +141,7 @@ fn run(code: Code) { span: code.span, code_lines: code.code_lines, file_name: code.file_name, - pc: 0, + pc: StmtIdx(0), registers: vec![0; max_register_index + 1], breakpoints: vec![], }; @@ -174,7 +181,7 @@ fn run(code: Code) { } } } - VmInstruction::Set(r, value) => vm.registers[r] = value, + VmInstruction::Set(r, value) => vm.registers[r.0] = value, } } println!("Execution finished."); @@ -228,19 +235,19 @@ fn debug_input(vm: &Vm) -> VmInstruction { } } -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 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, - Stmt::Dec(r) => *r, - Stmt::IsZero(r, _) => *r, + Stmt::Inc(r) => r.0, + Stmt::Dec(r) => r.0, + Stmt::IsZero(r, _) => r.0, Stmt::Jump(_) => 0, Stmt::Stop => 0, }) @@ -258,7 +265,7 @@ fn print_registers(vm: &Vm) { fn print_program(vm: &Vm) { use std::cmp::min; - if let Some(span_pc) = vm.span.get(vm.pc) { + if let Some(span_pc) = vm.span.get(vm.pc.0) { println!("Program:"); let lower = span_pc.0.saturating_sub(5); @@ -284,7 +291,7 @@ fn print_breakpoints(vm: &Vm) { ", vm.breakpoints .iter() - .map(|p| p.to_string()) + .map(|p| p.0.to_string()) .collect::>() .join(", ") ); @@ -327,7 +334,3 @@ fn get_input(prompt: Option<&str>) -> String { std::io::stdin().read_line(&mut input_buf).unwrap(); input_buf.trim().to_owned() } - -fn filename(path: &Path) -> String { - path.file_stem().unwrap().to_str().unwrap().to_owned() -} diff --git a/src/stmt.rs b/src/stmt.rs deleted file mode 100644 index f545b7d..0000000 --- a/src/stmt.rs +++ /dev/null @@ -1,235 +0,0 @@ -use std::collections::HashMap; -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) - } -} - -#[derive(Debug, Copy, Clone)] -pub enum Stmt { - Inc(usize), - Dec(usize), - IsZero(usize, usize), - Jump(usize), - 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, -} - -enum IrStmt<'a> { - Inc(usize), - Dec(usize), - IsZeroLabel(usize, &'a str), - IsZeroLine(usize, LineNumber), - JumpLabel(&'a str), - JumpLine(LineNumber), - Label(&'a str), - Stop, - None, -} - -pub fn parse(text: &str, file_name: String) -> Result { - let mut labels = HashMap::new(); - - let mut statements = Vec::new(); - let mut statement_number = 0; - - let code_lines = text.lines().collect::>(); - - for (line_index, line) in code_lines.iter().enumerate() { - let result = parse_line(line); - match result { - Ok(IrStmt::Label(name)) => { - labels.insert(name, statement_number); - } - Ok(IrStmt::None) => {} - Ok(stmt) => { - statement_number += 1; - statements.push((stmt, Span(line_index))); - } - Err(msg) => { - return Err(format!( - "error on line '{}': {}", - Span(line_index).line_number(), - msg - )) - } - } - } - - let result: Result, String> = 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, - match statements - .iter() - .position(|(_, stmt_span)| stmt_span.line_number() == line_number.0) - { - Some(stmt_number) => stmt_number, - None => { - return Err(format!( - "Referencing line '{}' on line '{}': {}, out of bounds", - line_number.0, - span.line_number(), - code_lines[span.0] - )) - } - }, - ), - *span, - )), - IrStmt::JumpLine(line_number) => Ok(( - Stmt::Jump( - match statements - .iter() - .position(|(_, stmt_span)| stmt_span.line_number() == line_number.0) - { - Some(stmt_number) => stmt_number, - None => { - return Err(format!( - "Referencing line '{}' on line '{}': {}, out of bounds", - line_number.0, - span.line_number(), - code_lines[span.0] - )) - } - }, - ), - *span, - )), - IrStmt::IsZeroLabel(r, label) => Ok(( - Stmt::IsZero( - r, - match labels.get(label) { - Some(line) => *line, - None => { - return Err(format!( - "Label '{}' not found on line '{}'", - label, - span.line_number() - )) - } - }, - ), - *span, - )), - IrStmt::JumpLabel(label) => Ok(( - Stmt::Jump(match labels.get(label) { - Some(line) => *line, - None => { - return Err(format!( - "Label '{}' not found on line {}", - label, - span.line_number() - )) - } - }), - *span, - )), - IrStmt::Stop => Ok((Stmt::Stop, *span)), - IrStmt::Label(_) => unreachable!(), - IrStmt::None => unreachable!(), - }) - .collect(); - - result.map(|vec| { - let (stmts, span) = vec.iter().cloned().unzip(); - Code { - stmts, - span, - code_lines, - file_name, - } - }) -} - -fn parse_line(line: &str) -> Result { - let no_register = || "No register provided".to_string(); - let no_label_or_line_number = || "No label or line number provided".to_string(); - let display_err = |parse_err: ParseIntError| parse_err.to_string(); - - 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 = iter - .next() - .ok_or_else(no_register)? - .parse() - .map_err(display_err)?; - IrStmt::Inc(register) - } - "DEC" => { - let register = iter - .next() - .ok_or_else(no_register)? - .parse() - .map_err(display_err)?; - IrStmt::Dec(register) - } - "IS_ZERO" => { - let register = iter - .next() - .ok_or_else(no_register)? - .parse() - .map_err(display_err)?; - 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(format!("Illegal instruction: '{}'", stmt)); - } - } - }) -} diff --git a/test.m8 b/test.m8 index 87a696c..85a45bc 100644 --- a/test.m8 +++ b/test.m8 @@ -1,6 +1,5 @@ -# hi INC 1 -JUMP 8 +JUMP 7 INC 2 STOP INC 3