diff --git a/Cargo.lock b/Cargo.lock index 35b6b12..43a24f5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -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" diff --git a/Cargo.toml b/Cargo.toml index 2092773..601ac24 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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" diff --git a/src/error.rs b/src/error.rs index c9b4239..251f1a2 100644 --- a/src/error.rs +++ b/src/error.rs @@ -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, } pub type Result = std::result::Result; + +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"); +} diff --git a/src/main.rs b/src/main.rs index 0bcee6f..12740bf 100644 --- a/src/main.rs +++ b/src/main.rs @@ -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(()) } diff --git a/src/parser.rs b/src/parser.rs index 42f0487..0793532 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -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) -> 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 { - 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::() { 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> { } #[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); + } +} diff --git a/src/snapshots/asm_thing__parser__tests__program.snap b/src/snapshots/asm_thing__parser__tests__program.snap new file mode 100644 index 0000000..058576b --- /dev/null +++ b/src/snapshots/asm_thing__parser__tests__program.snap @@ -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, + }, + ], +) diff --git a/test.at b/test.at index 0fd78b1..d9116a9 100644 --- a/test.at +++ b/test.at @@ -7,7 +7,7 @@ jmp exit // loop false: -mov r1, [8] +mov r5, [8] jmp false