cool errors

This commit is contained in:
nora 2022-06-13 12:32:39 +02:00
parent 29219405ff
commit e6ea89b754
7 changed files with 254 additions and 33 deletions

16
Cargo.lock generated
View file

@ -17,10 +17,20 @@ dependencies = [
"memchr",
]
[[package]]
name = "ariadne"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f1cb2a2046bea8ce5e875551f5772024882de0b540c7f93dfc5d6cf1ca8b030c"
dependencies = [
"yansi",
]
[[package]]
name = "asm-thing"
version = "0.1.0"
dependencies = [
"ariadne",
"dbg-pls",
"insta",
"logos",
@ -571,3 +581,9 @@ checksum = "56c1936c4cc7a1c9ab21a1ebb602eb942ba868cbd44a99cb7cdc5892335e1c85"
dependencies = [
"linked-hash-map",
]
[[package]]
name = "yansi"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09041cd90cf85f7f8b2df60c646f853b7f535ce68f85244eb6731cf89fa498ec"

View file

@ -6,6 +6,7 @@ edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
ariadne = "0.1.5"
dbg-pls = { version = "0.3.2", features = ["colors", "derive"] }
logos = "0.12.1"

View file

@ -1,3 +1,4 @@
use ariadne::{Color, Label, Report, ReportKind, Source};
use dbg_pls::DebugPls;
use logos::Span;
@ -5,6 +6,31 @@ use logos::Span;
pub struct CompilerError {
pub msg: String,
pub span: Span,
pub notes: Vec<(String, Span)>,
pub help: Option<String>,
}
pub type Result<T> = std::result::Result<T, CompilerError>;
pub fn report(error: CompilerError, filename: &str, src: &str) {
let mut report = Report::build(ReportKind::Error, filename, 12)
.with_message(&error.msg)
.with_label(
Label::new((filename, error.span))
.with_message(error.msg)
.with_color(Color::Red),
);
for (note, span) in error.notes {
report = report.with_label(Label::new((filename, span)).with_message(note));
}
if let Some(help) = error.help {
report = report.with_help(help);
}
report
.finish()
.print((filename, Source::from(src)))
.expect("failed to print");
}

View file

@ -6,6 +6,13 @@ mod parser;
fn main() -> Result<(), io::Error> {
let file = std::fs::read_to_string("./test.at")?;
let result = parser::parse(&file);
let _ = dbg_pls::color!(result);
match result {
Ok(ast) => {
dbg_pls::color!(ast);
}
Err(error) => error::report(error, "test.at", &file),
}
Ok(())
}

View file

@ -61,7 +61,7 @@ pub enum StmtKind {
Div { to: Expr, value: Expr },
Jmp { to: Expr },
Je { to: Expr },
Cmp { rhs: Expr, lhs: Expr },
Cmp { lhs: Expr, rhs: Expr },
Label { name: String },
}
@ -87,21 +87,36 @@ where
}
impl CompilerError {
fn new(msg: String, span: Span) -> Self {
Self { span, msg }
fn new(msg: String, span: Span, notes: Vec<(String, Span)>, help: Option<String>) -> Self {
Self {
msg,
span,
notes,
help,
}
}
fn simple(msg: String, span: Span) -> Self {
Self::new_notes(msg, span, Vec::new())
}
fn new_notes(msg: String, span: Span, notes: Vec<(String, Span)>) -> Self {
Self::new(msg, span, notes, None)
}
fn not_allowed(span: Span, token: &str) -> Self {
Self::new(format!("`{token}` is not allowed here"), span)
Self::simple(format!("`{token}` is not allowed here"), span)
}
fn invalid_token(span: Span) -> Self {
Self::new("Invalid token".to_string(), span)
Self::simple("Invalid token".to_string(), span)
}
fn eof() -> Self {
Self {
span: Default::default(),
span: 0..0,
msg: "Unexpected end of file".to_string(),
notes: Vec::new(),
help: None,
}
}
}
@ -112,13 +127,13 @@ macro_rules! expect {
if let $token = next {
span
} else {
return Err(CompilerError {
msg: format!(
return Err(CompilerError::simple(
format!(
concat!("Expected ", stringify!($token), ", found {:?}"),
next,
),
span,
});
));
}
}};
}
@ -137,7 +152,7 @@ where
}
fn stmt(&mut self) -> Result<Stmt> {
let stmt = |kind, span| Stmt { kind, span };
let stmt = |span, kind| Stmt { kind, span };
let (token, span) = self.next()?;
Ok(match token {
@ -145,60 +160,65 @@ where
let to = self.expr()?;
expect!(self, Token::Comma);
let from = self.expr()?;
stmt(StmtKind::Mov { to, from }, Default::default())
stmt(span.start..from.span.end, StmtKind::Mov { to, from })
}
Token::Jmp => {
let to = self.expr()?;
Stmt {
kind: StmtKind::Jmp { to },
span: Default::default(),
}
stmt(span.start..to.span.end, StmtKind::Jmp { to })
}
Token::Je => {
let to = self.expr()?;
stmt(StmtKind::Je { to }, Default::default())
stmt(span.start..to.span.end, StmtKind::Je { to })
}
Token::Cmp => {
let lhs = self.expr()?;
expect!(self, Token::Comma);
let rhs = self.expr()?;
stmt(StmtKind::Cmp { lhs, rhs }, Default::default())
stmt(span.start..rhs.span.end, StmtKind::Cmp { lhs, rhs })
}
Token::Add => {
let to = self.expr()?;
expect!(self, Token::Comma);
let value = self.expr()?;
stmt(StmtKind::Add { to, value }, Default::default())
stmt(span.start..value.span.end, StmtKind::Add { to, value })
}
Token::Sub => {
let to = self.expr()?;
expect!(self, Token::Comma);
let value = self.expr()?;
stmt(StmtKind::Sub { to, value }, Default::default())
stmt(span.start..value.span.end, StmtKind::Sub { to, value })
}
Token::Mul => {
let to = self.expr()?;
expect!(self, Token::Comma);
let value = self.expr()?;
stmt(StmtKind::Mul { to, value }, Default::default())
stmt(span.start..value.span.end, StmtKind::Mul { to, value })
}
Token::Div => {
let to = self.expr()?;
expect!(self, Token::Comma);
let value = self.expr()?;
stmt(StmtKind::Div { to, value }, Default::default())
stmt(span.start..value.span.end, StmtKind::Div { to, value })
}
Token::Label(name) => {
let name = name
.strip_suffix(":")
.expect("lexer produced invalid label")
.to_owned();
stmt(span, StmtKind::Label { name })
}
Token::Label(name) => stmt(
StmtKind::Label {
name: name.to_owned(),
},
Default::default(),
),
Token::BracketOpen => return Err(CompilerError::not_allowed(span, "[")),
Token::BracketClose => return Err(CompilerError::not_allowed(span, "]")),
Token::Comma => return Err(CompilerError::not_allowed(span, ",")),
Token::Number(_) => return Err(CompilerError::not_allowed(span, "{number}")),
Token::Word(_) => return Err(CompilerError::not_allowed(span, "{word}")),
Token::Word(word) => {
return Err(CompilerError::new(
"{word}".to_string(),
span.clone(),
vec![],
Some(format!("Consider using a label instead: `{}:`", word)),
))
}
Token::Error => return Err(CompilerError::invalid_token(span)),
})
}
@ -219,8 +239,10 @@ where
if let Ok(n) = r_number.parse::<u8>() {
if n > 15 {
return Err(CompilerError::new(
format!("Only registers from 0..15 are available. Invalid register: {n}"),
span,
format!("Invalid register number: {n}"),
span.clone(),
vec![("Registers available: r0..r15".to_owned(), span)],
None,
));
}
return Ok(expr(ExprKind::Register(n), span));
@ -261,4 +283,27 @@ pub fn parse(src: &str) -> Result<Vec<Stmt>> {
}
#[cfg(test)]
mod tests {}
mod tests {
#[test]
fn program() {
let result = super::parse(
"
mov r0, 3
cmp r0, 8
je true
jmp false
true:
jmp exit
// loop
false:
mov r1, [8]
jmp false
exit:
",
);
insta::assert_debug_snapshot!(result);
}
}

View file

@ -0,0 +1,126 @@
---
source: src/parser.rs
expression: result
---
Ok(
[
Stmt {
kind: Mov {
to: Expr {
kind: Register(
0,
),
span: 5..7,
},
from: Expr {
kind: Number(
3,
),
span: 9..10,
},
},
span: 1..10,
},
Stmt {
kind: Cmp {
lhs: Expr {
kind: Register(
0,
),
span: 15..17,
},
rhs: Expr {
kind: Number(
8,
),
span: 19..20,
},
},
span: 11..20,
},
Stmt {
kind: Je {
to: Expr {
kind: Name(
"true",
),
span: 24..28,
},
},
span: 21..28,
},
Stmt {
kind: Jmp {
to: Expr {
kind: Name(
"false",
),
span: 33..38,
},
},
span: 29..38,
},
Stmt {
kind: Label {
name: "true",
},
span: 39..44,
},
Stmt {
kind: Jmp {
to: Expr {
kind: Name(
"exit",
),
span: 49..53,
},
},
span: 45..53,
},
Stmt {
kind: Label {
name: "false",
},
span: 63..69,
},
Stmt {
kind: Mov {
to: Expr {
kind: Register(
1,
),
span: 74..76,
},
from: Expr {
kind: Addr(
Expr {
kind: Number(
8,
),
span: 79..80,
},
),
span: 78..81,
},
},
span: 70..81,
},
Stmt {
kind: Jmp {
to: Expr {
kind: Name(
"false",
),
span: 86..91,
},
},
span: 82..91,
},
Stmt {
kind: Label {
name: "exit",
},
span: 94..99,
},
],
)

View file

@ -7,7 +7,7 @@ jmp exit
// loop
false:
mov r1, [8]
mov r5, [8]
jmp false