Cleanup useless errors, add help messages for Runtime Errors, tag registers with which register they should be filling

This commit is contained in:
Lumi Kalt 2024-01-22 10:45:23 +00:00
parent f0b5be8c63
commit 2122535f56
6 changed files with 223 additions and 145 deletions

View file

@ -1,6 +1,4 @@
use std::{collections::HashMap, env::Args}; use std::collections::HashMap;
use rayon::iter::{IndexedParallelIterator, IntoParallelIterator, ParallelIterator};
use crate::{ use crate::{
err::RuntimeErr, err::RuntimeErr,
@ -16,12 +14,6 @@ pub enum SymbolValue {
String(String), String(String),
} }
pub enum Value {
Immediate(i64),
Register(u32),
Symbol(String, SymbolValue),
}
#[derive(Debug)] #[derive(Debug)]
pub struct Env { pub struct Env {
pub register_alias: HashMap<String, u32>, pub register_alias: HashMap<String, u32>,
@ -77,7 +69,7 @@ impl Env {
register_alias, register_alias,
labels: HashMap::new(), labels: HashMap::new(),
registers: [0; 32], registers: [0; 32],
stack: Vec::from([0; 1024]), stack: Vec::from([0; 1024]), // 1024 * 64 = 64 KiB stack
instructions: Vec::new(), instructions: Vec::new(),
} }
} }
@ -126,20 +118,25 @@ impl Env {
self.labels.get(label).copied() self.labels.get(label).copied()
} }
pub fn assemble_op(&mut self, op: (Token, Loc)) -> Result<u32, RuntimeErr> { pub fn assemble_op(
&self,
op: (Token, Loc),
) -> Result<u32, (RuntimeErr, Loc, Option<String>)> {
if let (Token::Op(name, args), loc) = op { if let (Token::Op(name, args), loc) = op {
let i = instruction(&name); let i = instruction(&name);
let mut imm = 0u32; let mut imm = 0u32;
let mut regs = vec![0; 4]; let mut regs = vec![0; 4];
if args.len() != i.1.len() { if args.len() != i.1.len() {
return Err(RuntimeErr::InvalidOpArity( return Err((
name, RuntimeErr::InvalidOpArity(name, args.len(), i.1.len()),
args.len(), loc,
i.1.len(), None,
)); ));
} }
let _ = i.1.into_par_iter() let _ =
i.1.clone()
.into_iter()
.enumerate() .enumerate()
.try_for_each(|(k, v)| match v { .try_for_each(|(k, v)| match v {
Arg::Immediate => { Arg::Immediate => {
@ -147,28 +144,53 @@ impl Env {
imm = i as u32; imm = i as u32;
Ok(()) Ok(())
} else { } else {
Err(RuntimeErr::InvalidType("Immediate".to_string(), v.kind())) Err((
RuntimeErr::InvalidType("immediate".to_string(), v.kind()),
args[k].1,
None,
))
} }
} }
Arg::Register => { Arg::Register(id) => {
if let Token::Register(r) = args[k].0 { if let Token::Register(r) = &args[k].0 {
regs[k] = self.reg_to_register(&r).unwrap(); regs[id] = self.reg_to_register(&r).unwrap();
Ok(()) Ok(())
} else { } else {
Err(RuntimeErr::InvalidType("Register".to_string(), v.kind())) Err((
RuntimeErr::InvalidType("register".to_string(), v.kind()),
args[k].1,
None,
))
} }
} }
Arg::Memory => { Arg::Memory => {
if let Token::Memory(i, r) = args[k].0 { if let Token::Memory(i, r) = &args[k].0 {
if r.is_some() { if r.is_some() {
regs[k] = self.reg_to_register(&r.unwrap()).unwrap(); regs[k] = self
.reg_to_register(&if let Token::Register(r) =
*(r.clone().unwrap())
{
r
} else {
unreachable!()
})
.unwrap();
} }
imm = if let Token::Immediate(i) = **i {
i as u32
} else {
unreachable!()
};
Ok(()) Ok(())
} else { } else {
Err(RuntimeErr::InvalidType("Memory".to_string(), v.kind())) Err((
RuntimeErr::InvalidType("memory".to_string(), v.kind()),
args[k].1,
None,
))
} }
} }
_ => unimplemented!() _ => unimplemented!(),
})?; })?;
Ok(u32::from_str_radix(&with(i, imm, regs).0.to_string(), 2).unwrap()) Ok(u32::from_str_radix(&with(i, imm, regs).0.to_string(), 2).unwrap())
} else { } else {

View file

@ -1,8 +1,14 @@
use std::fmt::{self, Display, Formatter}; use std::{
cmp::Ordering,
fmt::{self, Display, Formatter},
};
use itertools::Itertools;
use crate::instructions::instruction;
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub enum SyntaxErr { pub enum SyntaxErr {
TraillingComma,
/// false for '(' true for ')' /// false for '(' true for ')'
UnmatchedParen(bool), UnmatchedParen(bool),
UnexpectedChar, UnexpectedChar,
@ -13,10 +19,9 @@ pub enum SyntaxErr {
impl Display for SyntaxErr { impl Display for SyntaxErr {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
match self { match self {
SyntaxErr::TraillingComma => write!(f, "trailling comma"),
SyntaxErr::UnmatchedParen(_) => write!(f, "unmatched parenthesis"), SyntaxErr::UnmatchedParen(_) => write!(f, "unmatched parenthesis"),
SyntaxErr::UnexpectedChar => write!(f, "unexpected character"), SyntaxErr::UnexpectedChar => write!(f, "unexpected character"),
SyntaxErr::OutsideOp(kind) => write!(f, "{kind} before opcode"), SyntaxErr::OutsideOp(kind) => write!(f, "`{kind}` before opcode"),
SyntaxErr::MemoryInvalidRegister => write!(f, "invalid register"), SyntaxErr::MemoryInvalidRegister => write!(f, "invalid register"),
} }
} }
@ -25,11 +30,10 @@ impl Display for SyntaxErr {
impl SyntaxErr { impl SyntaxErr {
pub fn note(&self) -> String { pub fn note(&self) -> String {
match self { match self {
SyntaxErr::TraillingComma => "remove the final comma".to_string(), SyntaxErr::UnmatchedParen(false) => "add `)` after the register name".to_string(),
SyntaxErr::UnmatchedParen(false) => "add ')' after the register name".to_string(), SyntaxErr::UnmatchedParen(true) => "add `(` before the register name".to_string(),
SyntaxErr::UnmatchedParen(true) => "add '(' before the register name".to_string(),
SyntaxErr::UnexpectedChar => "ensure the input is well-formed".to_string(), SyntaxErr::UnexpectedChar => "ensure the input is well-formed".to_string(),
SyntaxErr::OutsideOp(_) => format!("add arguments after the opcode"), SyntaxErr::OutsideOp(_) => format!("only add arguments after the opcode"),
SyntaxErr::MemoryInvalidRegister => { SyntaxErr::MemoryInvalidRegister => {
"registers are either xN (N < 32 with no leading 0) or the standard aliases" "registers are either xN (N < 32 with no leading 0) or the standard aliases"
.to_string() .to_string()
@ -40,29 +44,53 @@ impl SyntaxErr {
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub enum RuntimeErr { pub enum RuntimeErr {
InvalidRegister(String), InvalidOp,
UnexpectedImmediate, /// op, actual, expected
UnexpectedRegister,
InvalidOp(String),
InvalidOpArity(String, usize, usize), InvalidOpArity(String, usize, usize),
/// actual, expected
InvalidType(String, String), InvalidType(String, String),
} }
impl Display for RuntimeErr { impl Display for RuntimeErr {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
match self { match self {
RuntimeErr::InvalidRegister(reg) => write!(f, "invalid register {}", reg), RuntimeErr::InvalidOp => write!(f, "invalid operation"),
RuntimeErr::UnexpectedImmediate => write!(f, "unexpected immediate"), RuntimeErr::InvalidOpArity(op, actual, expected) => {
RuntimeErr::UnexpectedRegister => write!(f, "unexpected register"), write!(f, "`{}` expected {} args, got {}", op, expected, actual)
RuntimeErr::InvalidOp(op) => write!(f, "invalid operation {}", op), }
RuntimeErr::InvalidOpArity(op, actual, expected) => write!(
f,
"invalid operation arity {} expected {} got {}",
op, expected, actual
),
RuntimeErr::InvalidType(actual, expected) => { RuntimeErr::InvalidType(actual, expected) => {
write!(f, "expected {}, got {}", expected, actual) write!(f, "expected `{}`, got `{}`", expected, actual)
} }
} }
} }
} }
impl RuntimeErr {
pub fn note(&self) -> String {
match self {
RuntimeErr::InvalidOp => "check the ref sheet for the avaliable opcodes".to_string(),
RuntimeErr::InvalidOpArity(op, actual, expected) => {
let args = instruction(op).1;
match actual.cmp(expected) {
Ordering::Equal => unreachable!(),
Ordering::Greater if actual - expected == 1 => {
"remove the extra argument".to_string()
}
Ordering::Greater => "remove the extra arguments".to_string(),
Ordering::Less if expected - actual == 1 => {
format!("add the extra `{}` argument", args.last().unwrap().kind())
}
Ordering::Less => format!(
"add the extra `{}` arguments",
args.get((actual - 1)..)
.unwrap()
.iter()
.map(|arg| arg.kind())
.join("`, `")
),
}
}
RuntimeErr::InvalidType(_, _) => "ensure the operation is valid".to_string(),
}
}
}

View file

@ -222,9 +222,12 @@ pub mod kind {
} }
} }
#[derive(Debug, Clone)]
pub enum Arg { pub enum Arg {
Register, /// rd -> 0, ra -> 1, rb -> 2, rc -> 3
Register(usize),
Immediate, Immediate,
/// always ra
Memory, Memory,
Symbol, Symbol,
} }
@ -232,7 +235,7 @@ pub enum Arg {
impl Arg { impl Arg {
pub fn kind(&self) -> String { pub fn kind(&self) -> String {
match self { match self {
Arg::Register => "register", Arg::Register(_) => "register",
Arg::Immediate => "immediate", Arg::Immediate => "immediate",
Arg::Memory => "memory", Arg::Memory => "memory",
Arg::Symbol => "symbol", Arg::Symbol => "symbol",
@ -250,14 +253,14 @@ pub fn instruction(op: &str) -> (Kind, Vec<Arg>) {
"nop" => (Kind::Pseudo(Pseudo {}), vec![]), "nop" => (Kind::Pseudo(Pseudo {}), vec![]),
// Move // Move
"li" => (Kind::Pseudo(Pseudo {}), vec![Arg::Register, Arg::Immediate]), "li" => (Kind::Pseudo(Pseudo {}), vec![Arg::Register(0), Arg::Immediate]),
"lui" => ( "lui" => (
Kind::U(U { Kind::U(U {
imm: to_bits(0), imm: to_bits(0),
rd: to_bits(0), rd: to_bits(0),
opcode: to_bits(0b0110111), opcode: to_bits(0b0110111),
}), }),
vec![Arg::Register, Arg::Immediate], vec![Arg::Register(0), Arg::Immediate],
), ),
// Memory // Memory
@ -270,7 +273,7 @@ pub fn instruction(op: &str) -> (Kind, Vec<Arg>) {
imm2: to_bits(0), imm2: to_bits(0),
opcode: to_bits(0b0100011), opcode: to_bits(0b0100011),
}), }),
vec![Arg::Register, Arg::Memory], vec![Arg::Register(2), Arg::Memory],
), ),
// Arithmetic, Logic, Shift // Arithmetic, Logic, Shift
@ -283,7 +286,7 @@ pub fn instruction(op: &str) -> (Kind, Vec<Arg>) {
rd: to_bits(0), rd: to_bits(0),
opcode: to_bits(0b0110011), opcode: to_bits(0b0110011),
}), }),
vec![Arg::Register, Arg::Register, Arg::Register], vec![Arg::Register(0), Arg::Register(1), Arg::Register(2)],
), ),
"addi" => ( "addi" => (
Kind::I(I { Kind::I(I {
@ -293,7 +296,7 @@ pub fn instruction(op: &str) -> (Kind, Vec<Arg>) {
rd: to_bits(0), rd: to_bits(0),
opcode: to_bits(0b0010011), opcode: to_bits(0b0010011),
}), }),
vec![Arg::Register, Arg::Register, Arg::Immediate], vec![Arg::Register(0), Arg::Register(1), Arg::Immediate],
), ),
// Multiply, Divide // Multiply, Divide
@ -309,7 +312,7 @@ pub fn instruction(op: &str) -> (Kind, Vec<Arg>) {
imm2: to_bits(0), imm2: to_bits(0),
opcode: to_bits(0b1100011), opcode: to_bits(0b1100011),
}), }),
vec![Arg::Register, Arg::Register, Arg::Immediate], vec![Arg::Register(1), Arg::Register(2), Arg::Immediate],
), ),
_ => unimplemented!(), _ => unimplemented!(),
} }

View file

@ -9,8 +9,7 @@ use codespan_reporting::{
}; };
use riscv_interpreter::{ use riscv_interpreter::{
env::Env, env::Env,
instructions::{instruction, with}, parser::{parse, Token},
parser::parse,
}; };
fn main() -> anyhow::Result<()> { fn main() -> anyhow::Result<()> {
@ -24,10 +23,45 @@ fn main() -> anyhow::Result<()> {
match parse(&env, &input) { match parse(&env, &input) {
Ok(tokens) => { Ok(tokens) => {
println!("{:#?} -> {:#?}", input, tokens); let lines: Vec<&str> = input.lines().collect();
let size = lines.iter().map(|l| l.len()).max().unwrap();
tokens.iter().enumerate().for_each(|(line, token)| {
let token = token.clone();
let mut formatted = format!("{:<1$}", lines[line].to_string() + ":", size + 3);
match token.0 {
Token::Op(..) => match env.assemble_op(token) {
Ok(op) => {
formatted += &format!("{:032b}", op);
println!("{}", formatted);
}
Err(err) => {
let diagnostic = Diagnostic::error()
.with_message("Runtime Error")
.with_labels(vec![Label::primary((), err.1.start..err.1.end)
.with_message(err.0.to_string())])
.with_notes({
let mut notes = Vec::new();
if let Some(note) = &err.2 {
notes.push(note.to_string());
}
notes.push(err.0.note());
notes
});
term::emit(&mut writer.lock(), &config, &file, &diagnostic).unwrap();
}
},
Token::LabelDef(name) => {
println!("{name}:");
}
_ => unreachable!(),
}
});
} }
Err(errs) => { Err(errs) => {
for err in errs { let err = errs.first().unwrap();
let start = err.1.start; let start = err.1.start;
let end = err.1.end + 1; let end = err.1.end + 1;
@ -38,19 +72,18 @@ fn main() -> anyhow::Result<()> {
]) ])
.with_notes({ .with_notes({
let mut notes = Vec::new(); let mut notes = Vec::new();
if let Some(note) = err.3 { if let Some(note) = &err.3 {
notes.push(note); notes.push(note.to_string());
} }
notes.push(err.0.note()); notes.push(err.0.note());
notes notes
}); });
term::emit(&mut writer.lock(), &config, &file, &diagnostic).unwrap(); term::emit(&mut writer.lock(), &config, &file, &diagnostic).unwrap();
}
return Ok(()); return Ok(());
} }
}; };
Ok(()) Ok(())
} }

View file

@ -57,18 +57,14 @@ pub struct Loc {
pub end: usize, pub end: usize,
} }
fn parse_line(env: &Env, input: &str, line: usize) -> Result<Vec<(Token, Loc)>, ParseErr> { fn parse_line(env: &Env, input: &str, line: usize, loc: &mut Loc) -> Result<Vec<(Token, Loc)>, ParseErr> {
let mut loc = Loc {
line,
start: 0,
end: 0,
};
let mut tokens: Vec<(Token, Loc)> = Vec::new(); let mut tokens: Vec<(Token, Loc)> = Vec::new();
let mut chars = input.chars().peekable(); let mut chars = input.chars().peekable();
use Token::*; use Token::*;
loc.line = line;
while let Some(c) = chars.next() { while let Some(c) = chars.next() {
let token = match c { let token = match c {
'\t' | ' ' => Spacing, '\t' | ' ' => Spacing,
@ -87,7 +83,7 @@ fn parse_line(env: &Env, input: &str, line: usize) -> Result<Vec<(Token, Loc)>,
num.push(chars.next().unwrap()); num.push(chars.next().unwrap());
loc.end += 1; loc.end += 1;
} }
if let Some('(') | Some(' ') = chars.peek() { if let Some('(') | Some(' ') | None = chars.peek() {
Immediate(num.parse().unwrap()) Immediate(num.parse().unwrap())
} else { } else {
return Err(( return Err((
@ -170,8 +166,8 @@ fn parse_line(env: &Env, input: &str, line: usize) -> Result<Vec<(Token, Loc)>,
// Opcode or Label definition // Opcode or Label definition
'a'..='z' | 'A'..='Z' | '_' => { 'a'..='z' | 'A'..='Z' | '_' => {
let mut str = c.to_string(); let mut str = c.to_string();
while let Some('a'..='z') | Some('A'..='Z') | Some('_') | Some('0'..='9') | Some('.') = while let Some('a'..='z') | Some('A'..='Z') | Some('_') | Some('0'..='9')
chars.peek() | Some('.') = chars.peek()
{ {
str.push(chars.next().unwrap()); str.push(chars.next().unwrap());
loc.end += 1; loc.end += 1;
@ -180,34 +176,16 @@ fn parse_line(env: &Env, input: &str, line: usize) -> Result<Vec<(Token, Loc)>,
chars.next(); chars.next();
loc.end += 1; loc.end += 1;
LabelDef(str[..str.len()].to_string()) LabelDef(str[..str.len()].to_string())
} else if let Some((Op(_, _), _)) = tokens.get(tokens.len() - 2) { } else {
// These Registers may actually be label references or symbols, but there's ambiguity // These Registers may actually be ops, label references or symbols, but there's ambiguity
// between them and registers, so we'll just assume they're registers for now // between them and registers, so we'll just assume they're registers for now
Register(str.trim().to_owned()) Register(str.trim().to_owned())
} else {
if env.is_valid_register(&str) {
return Err((
SyntaxErr::OutsideOp("register".to_string()),
loc.clone(),
tokens.clone(),
None,
));
}
if str.trim().contains(|c: char| !c.is_alphabetic() && c != '.') {
return Err((
SyntaxErr::UnexpectedChar,
dbg!(loc.clone()),
tokens.clone(),
Some("opcodes must only contain ascii letters".to_string()),
));
}
Op(str, vec![])
} }
} }
_ => return Err((SyntaxErr::UnexpectedChar, loc.clone(), tokens.clone(), None)), _ => return Err((SyntaxErr::UnexpectedChar, loc.clone(), tokens.clone(), None)),
}; };
tokens.push((token, loc.clone())); tokens.push((token, loc.clone()));
loc.end += 1; loc.end += 1; // Newline
loc.start = loc.end; loc.start = loc.end;
} }
@ -215,10 +193,7 @@ fn parse_line(env: &Env, input: &str, line: usize) -> Result<Vec<(Token, Loc)>,
.into_iter() .into_iter()
.filter(|(token, _)| !matches!(token, Token::Spacing)) .filter(|(token, _)| !matches!(token, Token::Spacing))
.group_by(|(token, _)| { .group_by(|(token, _)| {
matches!( matches!(token, Immediate(_) | Register(_) | Memory(_, _) | Symbol(_))
token,
Op(_, _) | Immediate(_) | Register(_) | Memory(_, _) | Symbol(_) | String(_)
)
}) })
.into_iter() .into_iter()
.flat_map(|group| { .flat_map(|group| {
@ -226,8 +201,8 @@ fn parse_line(env: &Env, input: &str, line: usize) -> Result<Vec<(Token, Loc)>,
if is_op { if is_op {
let group = group.collect::<Vec<_>>(); let group = group.collect::<Vec<_>>();
let (op, loc) = group[0].clone(); let (op, loc) = group[0].clone();
let (op, mut args) = match op { let (name, mut args) = match op {
Op(op, args) => (op, args), Register(r) => (r, vec![]),
// because any register/symbol/label def is interpreted as an Op by default, this only // because any register/symbol/label def is interpreted as an Op by default, this only
// partially works. This does trigger on immediate values and memory indexes // partially works. This does trigger on immediate values and memory indexes
_ => { _ => {
@ -242,9 +217,21 @@ fn parse_line(env: &Env, input: &str, line: usize) -> Result<Vec<(Token, Loc)>,
)] )]
} }
}; };
if env.is_valid_register(&name) {
return vec![(
Token::Error((
SyntaxErr::OutsideOp("register".to_string()),
loc.clone(),
group.clone(),
None,
)),
loc.clone(),
)];
}
args.extend_from_slice(&group[1..]); args.extend_from_slice(&group[1..]);
vec![(Op(op, args), loc)] vec![(Op(name, args), loc)]
} else { } else {
group.collect::<Vec<_>>() group.collect::<Vec<_>>()
} }
@ -265,21 +252,26 @@ fn parse_line(env: &Env, input: &str, line: usize) -> Result<Vec<(Token, Loc)>,
/// containing the error, the location of the error, the tokens parsed up to that point, /// containing the error, the location of the error, the tokens parsed up to that point,
/// and an optional message to display to the users for each line with an error /// and an optional message to display to the users for each line with an error
pub fn parse(env: &Env, input: &str) -> Result<Vec<(Token, Loc)>, Vec<ParseErr>> { pub fn parse(env: &Env, input: &str) -> Result<Vec<(Token, Loc)>, Vec<ParseErr>> {
let mut loc = Loc {
line: 0,
start: 0,
end: 0,
};
let parsed_lines = input let parsed_lines = input
.lines() .lines()
.enumerate() .enumerate()
.par_bridge() .map(|(i, line)| parse_line(env, line, i + 1, &mut loc))
.map(|(i, line)| parse_line(env, line, i + 1))
.collect::<Vec<_>>(); .collect::<Vec<_>>();
let (ok, err) = parsed_lines let (ok, err): (Vec<_>, Vec<_>) = parsed_lines.into_iter()
.into_par_iter() .partition(|line| matches!(line, Ok(_)));
.partition::<Vec<Result<_, _>>, Vec<Result<_, _>>, _>(|line| matches!(line, Ok(_)));
dbg!(&err); dbg!(err.clone());
if err.is_empty() { if err.is_empty() {
Ok(ok.into_par_iter().flat_map(|line| line.unwrap()).collect()) Ok(ok.into_iter().flat_map(|line| line.unwrap()).collect())
} else { } else {
Err(err.into_par_iter().map(|line| line.unwrap_err()).collect()) Err(err.into_iter().map(|line| line.unwrap_err()).collect())
} }
} }

4
test.s
View file

@ -1,4 +1,4 @@
a: a:
addi a0 zero 1 a0 addi a0 zero 1
addi a1 zero 2 addi a1 zero 2
add a2 a1 a0 a0 add a2 a1 a0