more types to fix bugs confusing line numbers and spans

This commit is contained in:
nora 2021-09-18 22:51:33 +02:00
parent 1321903483
commit 9bdfa4f522
3 changed files with 153 additions and 32 deletions

View file

@ -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<Stmt>,
span: Vec<usize>,
span: Vec<Span>,
code_lines: Vec<&'a str>,
pc: usize,
registers: Vec<usize>,
@ -53,6 +53,10 @@ impl Vm<'_> {
}
}
}
fn statement_at_span(&self, search_span: Span) -> Option<usize> {
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::<usize>() {
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);

View file

@ -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<Stmt>,
/// Has the same length as `stmts`, points to line numbers where the instructions come from
pub span: Vec<usize>,
pub span: Vec<Span>,
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<Code, String> {
let code_lines = text.lines().collect::<Vec<_>>();
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<Code, String> {
}
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<Vec<(Stmt, usize)>, String> = statements
let result: Result<Vec<(Stmt, Span)>, 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<Code, String> {
fn parse_line(line: &str) -> Result<IrStmt, String> {
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<IrStmt, String> {
.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::<usize>() {
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::<usize>() {
IrStmt::JumpLine(LineNumber(line_number))
} else {
IrStmt::JumpLabel(jump_target)
}
}
"STOP" => IrStmt::Stop,
stmt => {

25
test.m8
View file

@ -1,7 +1,20 @@
.test
INC 1
.start
DEC 1
IS_ZERO 1 end
JUMP start
.end
STOP
.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