mirror of
https://github.com/Noratrieb/m8db.git
synced 2026-01-14 23:35:03 +01:00
235 lines
7.2 KiB
Rust
235 lines
7.2 KiB
Rust
use std::collections::HashMap;
|
|
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)]
|
|
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<Stmt>,
|
|
/// Has the same length as `stmts`, points to line numbers where the instructions come from
|
|
pub span: Vec<Span>,
|
|
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<Code, String> {
|
|
let mut labels = HashMap::new();
|
|
|
|
let mut statements = Vec::new();
|
|
let mut statement_number = 0;
|
|
|
|
let code_lines = text.lines().collect::<Vec<_>>();
|
|
|
|
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<Vec<(Stmt, Span)>, 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<IrStmt, String> {
|
|
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::<usize>() {
|
|
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::<usize>() {
|
|
IrStmt::JumpLine(LineNumber(line_number))
|
|
} else {
|
|
IrStmt::JumpLabel(jump_target)
|
|
}
|
|
}
|
|
"STOP" => IrStmt::Stop,
|
|
stmt => {
|
|
if stmt.starts_with('.') {
|
|
IrStmt::Label(&stmt[1..])
|
|
} else if stmt.starts_with('#') {
|
|
IrStmt::None
|
|
} else {
|
|
return Err(format!("Illegal instruction: '{}'", stmt));
|
|
}
|
|
}
|
|
})
|
|
}
|