diff --git a/README.md b/README.md index 699a2a2..6058cee 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,5 @@ 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/run.rs b/src/db.rs similarity index 86% rename from src/run.rs rename to src/db.rs index ecca985..ba7b864 100644 --- a/src/run.rs +++ b/src/db.rs @@ -1,5 +1,5 @@ -use crate::parse; -use crate::parse::{Code, LineNumber, Register, Span, Stmt, StmtIdx}; +use crate::stmt; +use crate::stmt::{Code, LineNumber, Span, Stmt}; 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: StmtIdx, + pc: usize, 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.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); + 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)) => { + if self.registers[r] == 0 { + self.pc = line - 1; } } - Some(Stmt::Jump(index)) => self.pc = StmtIdx(index.0 - 1), + Some(Stmt::Jump(line)) => self.pc = line - 1, Some(Stmt::Stop) => return VmState::Stop, None => return VmState::OutOfBounds, } - self.pc.0 += 1; + self.pc += 1; if self.breakpoints.contains(&self.pc) { VmState::Break } else { @@ -57,11 +57,8 @@ impl Vm<'_> { } } - fn statement_at_span(&self, search_span: Span) -> Option { - self.span - .iter() - .position(|span| *span >= search_span) - .map(|idx| StmtIdx(idx)) + fn statement_at_span(&self, search_span: Span) -> Option { + self.span.iter().position(|span| *span >= search_span) } } @@ -75,8 +72,8 @@ enum VmRunKind { enum VmInstruction { Step, Run(VmRunKind), - Break(StmtIdx), - Set(Register, usize), + Break(usize), + Set(usize, usize), Stop, } @@ -93,13 +90,13 @@ pub fn start(program_path: Option) { } } -fn read_and_run(path: &str) { +fn read_and_run<'a>(path: &str) { let path = Path::new(path); match std::fs::read_to_string(path) { - Ok(content) => match parse::parse(&content, filename(path)) { + Ok(content) => match stmt::parse(&content, filename(&path)) { Ok(stmts) => run(stmts), - Err(why) => eprintln!("{}", why), + Err(why) => eprintln!("error while parsing: {}.", why), }, Err(why) => eprintln!("error while reading file: {}.", why), }; @@ -129,10 +126,6 @@ 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); @@ -141,7 +134,7 @@ fn run(code: Code) { span: code.span, code_lines: code.code_lines, file_name: code.file_name, - pc: StmtIdx(0), + pc: 0, registers: vec![0; max_register_index + 1], breakpoints: vec![], }; @@ -181,7 +174,7 @@ fn run(code: Code) { } } } - VmInstruction::Set(r, value) => vm.registers[r.0] = value, + VmInstruction::Set(r, value) => vm.registers[r] = value, } } println!("Execution finished."); @@ -235,19 +228,19 @@ fn debug_input(vm: &Vm) -> VmInstruction { } } -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 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.0, - Stmt::Dec(r) => r.0, - Stmt::IsZero(r, _) => r.0, + Stmt::Inc(r) => *r, + Stmt::Dec(r) => *r, + Stmt::IsZero(r, _) => *r, Stmt::Jump(_) => 0, Stmt::Stop => 0, }) @@ -265,7 +258,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.0) { + if let Some(span_pc) = vm.span.get(vm.pc) { println!("Program:"); let lower = span_pc.0.saturating_sub(5); @@ -291,7 +284,7 @@ fn print_breakpoints(vm: &Vm) { ", vm.breakpoints .iter() - .map(|p| p.0.to_string()) + .map(|p| p.to_string()) .collect::>() .join(", ") ); @@ -334,3 +327,7 @@ 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/main.rs b/src/main.rs index 1ac60d2..107993d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,5 +1,5 @@ -mod parse; -mod run; +mod db; +mod stmt; fn main() { println!( @@ -9,5 +9,5 @@ Type 'help' for help " ); - run::start(std::env::args().nth(1)); + db::start(std::env::args().nth(1)); } 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/stmt.rs b/src/stmt.rs new file mode 100644 index 0000000..f3b9d41 --- /dev/null +++ b/src/stmt.rs @@ -0,0 +1,236 @@ +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) + } +} + +#[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, +} + +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() { + 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(stmt) => { + statement_number += 1; + statements.push((stmt, Span(line_index))); + } + Err(msg) => return Err(format!("error on line '{}': {}", line_index - 1, msg)), + } + } + + let result: Result, String> = statements + .iter() + .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!(), + }) + .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().expect("Empty lines filtered out"); + + 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 stmt.starts_with('.') { + IrStmt::Label(&stmt[1..]) + } else { + 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..2cc19c3 100644 --- a/test.m8 +++ b/test.m8 @@ -1,7 +1,20 @@ +.test INC 1 -JUMP 7 +.test INC 2 -STOP +.test INC 3 - -JUMP 4 \ No newline at end of file +.test +INC 4 +.test +INC 5 +.test +INC 6 +.test +INC 7 +.test +INC 8 +.test +INC 9 +.test +INC 10