From 9bdfa4f522681570e4da5e423c80b2e099ce4cc4 Mon Sep 17 00:00:00 2001 From: Nilstrieb Date: Sat, 18 Sep 2021 22:51:33 +0200 Subject: [PATCH] more types to fix bugs confusing line numbers and spans --- src/db.rs | 44 ++++++++++++++++---- src/stmt.rs | 116 ++++++++++++++++++++++++++++++++++++++++++++-------- test.m8 | 25 ++++++++--- 3 files changed, 153 insertions(+), 32 deletions(-) diff --git a/src/db.rs b/src/db.rs index b1fb509..222f0ae 100644 --- a/src/db.rs +++ b/src/db.rs @@ -1,10 +1,10 @@ -use crate::stmt::{Code, Stmt}; +use crate::stmt::{Code, LineNumber, Span, Stmt}; use std::io::Write; #[derive(Debug, Clone)] struct Vm<'a> { stmts: Vec, - span: Vec, + span: Vec, code_lines: Vec<&'a str>, pc: usize, registers: Vec, @@ -53,6 +53,10 @@ impl Vm<'_> { } } } + + fn statement_at_span(&self, search_span: Span) -> Option { + self.span.iter().position(|span| *span >= search_span) + } } #[derive(Debug, Copy, Clone)] @@ -122,8 +126,22 @@ fn debug_input(vm: &Vm) -> VmInstruction { "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), + 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!( + "Line number '{}' out of bounds for length {}.", + line_number, + vm.code_lines.len() + ); + continue; + } + }; + return VmInstruction::Break(stmt_pos); + } Err(_) => println!("Invalid argument provided"), }, None => print_breakpoints(vm), @@ -175,17 +193,27 @@ fn print_registers(vm: &Vm) { fn print_program(vm: &Vm) { use std::cmp::min; - println!("Program: (pc = {}, line = {})", vm.pc, vm.span[vm.pc]); + // todo rewrite all of this, it's terrible! + + 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_code = vm.span[lower_stmt]; - let higher_code = vm.span[higher_stmt - 1]; + 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[vm.pc] { + 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); diff --git a/src/stmt.rs b/src/stmt.rs index c81c616..2648d32 100644 --- a/src/stmt.rs +++ b/src/stmt.rs @@ -2,6 +2,26 @@ 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), @@ -11,18 +31,21 @@ pub enum Stmt { 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 span: Vec, pub code_lines: Vec<&'a str>, } enum IrStmt<'a> { Inc(usize), Dec(usize), - IsZero(usize, &'a str), - Jump(&'a str), + IsZeroLabel(usize, &'a str), + IsZeroLine(usize, LineNumber), + JumpLabel(&'a str), + JumpLine(LineNumber), Label(&'a str), Stop, } @@ -35,7 +58,7 @@ pub fn parse(text: &str) -> Result { let code_lines = text.lines().collect::>(); - for (line_number, line) in code_lines.iter().enumerate() { + for (line_index, line) in code_lines.iter().enumerate() { if line.split_whitespace().next().is_none() { continue; } @@ -46,33 +69,82 @@ pub fn parse(text: &str) -> Result { } Ok(stmt) => { statement_number += 1; - statements.push((stmt, line_number)); + statements.push((stmt, Span(line_index))); } - Err(msg) => return Err(format!("error on line {}: {}", line_number, msg)), + Err(msg) => return Err(format!("error on line '{}': {}", line_index - 1, msg)), } } - let result: Result, String> = statements + 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::IsZero(r, label) => Ok(( + IrStmt::IsZeroLine(r, line_number) => Ok(( Stmt::IsZero( r, - match labels.get(label) { - Some(line) => *line, + match statements + .iter() + .position(|(_, stmt_span)| stmt_span.line_number() == line_number.0) + { + Some(stmt_number) => stmt_number, None => { - return Err(format!("Label '{}' not found on line {}", label, span)) + return Err(format!( + "Referencing line '{}' on line '{}': {}, out of bounds", + line_number.0, + span.line_number(), + code_lines[span.0] + )) } }, ), *span, )), - IrStmt::Jump(label) => Ok(( + 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)), + None => { + return Err(format!( + "Label '{}' not found on line {}", + label, + span.line_number() + )) + } }), *span, )), @@ -93,7 +165,7 @@ pub fn parse(text: &str) -> Result { fn parse_line(line: &str) -> Result { let no_register = || "No register provided".to_string(); - let no_label = || "No label 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(); @@ -122,12 +194,20 @@ fn parse_line(line: &str) -> Result { .ok_or_else(no_register)? .parse() .map_err(display_err)?; - let label = iter.next().ok_or_else(no_label)?; - IrStmt::IsZero(register, label) + 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 label = iter.next().ok_or_else(no_label)?; - IrStmt::Jump(label) + 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 => { diff --git a/test.m8 b/test.m8 index 73183f0..2cc19c3 100644 --- a/test.m8 +++ b/test.m8 @@ -1,7 +1,20 @@ +.test INC 1 -.start -DEC 1 -IS_ZERO 1 end -JUMP start -.end -STOP \ No newline at end of file +.test +INC 2 +.test +INC 3 +.test +INC 4 +.test +INC 5 +.test +INC 6 +.test +INC 7 +.test +INC 8 +.test +INC 9 +.test +INC 10