diff --git a/.rustfmt.toml b/.rustfmt.toml new file mode 100644 index 0000000..4d7dd9e --- /dev/null +++ b/.rustfmt.toml @@ -0,0 +1,3 @@ +imports_granularity = "Crate" +newline_style = "Unix" +group_imports = "StdExternalCrate" \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index dacbc9a..35b6b12 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -22,6 +22,7 @@ name = "asm-thing" version = "0.1.0" dependencies = [ "dbg-pls", + "insta", "logos", ] @@ -70,6 +71,19 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "console" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a28b32d32ca44b70c3e4acd7db1babf555fa026e385fb95f18028f88848b3c31" +dependencies = [ + "encode_unicode", + "libc", + "once_cell", + "terminal_size", + "winapi", +] + [[package]] name = "crc32fast" version = "1.3.2" @@ -108,6 +122,12 @@ dependencies = [ "syn", ] +[[package]] +name = "encode_unicode" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" + [[package]] name = "flate2" version = "1.0.24" @@ -140,6 +160,20 @@ dependencies = [ "hashbrown", ] +[[package]] +name = "insta" +version = "1.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bcc3e639bcba360d9237acabd22014c16f3df772db463b7446cd81b070714767" +dependencies = [ + "console", + "once_cell", + "serde", + "serde_json", + "serde_yaml", + "similar", +] + [[package]] name = "itoa" version = "1.0.2" @@ -345,6 +379,9 @@ name = "serde" version = "1.0.137" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "61ea8d54c77f8315140a05f4c7237403bf38b72704d031543aa1d16abbf517d1" +dependencies = [ + "serde_derive", +] [[package]] name = "serde_derive" @@ -368,6 +405,24 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_yaml" +version = "0.8.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "707d15895415db6628332b737c838b88c598522e4dc70647e59b72312924aebc" +dependencies = [ + "indexmap", + "ryu", + "serde", + "yaml-rust", +] + +[[package]] +name = "similar" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e24979f63a11545f5f2c60141afe249d4f19f84581ea2138065e400941d83d3" + [[package]] name = "smawk" version = "0.3.1" @@ -407,6 +462,16 @@ dependencies = [ "yaml-rust", ] +[[package]] +name = "terminal_size" +version = "0.1.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "633c1a546cee861a1a6d0dc69ebeca693bf4296661ba7852b9d21d159e0506df" +dependencies = [ + "libc", + "winapi", +] + [[package]] name = "textwrap" version = "0.15.0" diff --git a/Cargo.toml b/Cargo.toml index d1590d2..2092773 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,3 +8,6 @@ edition = "2021" [dependencies] dbg-pls = { version = "0.3.2", features = ["colors", "derive"] } logos = "0.12.1" + +[dev-dependencies] +insta = "1.14.1" diff --git a/src/error.rs b/src/error.rs new file mode 100644 index 0000000..c9b4239 --- /dev/null +++ b/src/error.rs @@ -0,0 +1,10 @@ +use dbg_pls::DebugPls; +use logos::Span; + +#[derive(Debug, DebugPls)] +pub struct CompilerError { + pub msg: String, + pub span: Span, +} + +pub type Result = std::result::Result; diff --git a/src/main.rs b/src/main.rs index 6bc99cd..0bcee6f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,9 +1,11 @@ use std::io; +mod error; mod parser; fn main() -> Result<(), io::Error> { - let file = std::fs::read_to_string("../test.at")?; - parser::run(&file); + let file = std::fs::read_to_string("./test.at")?; + let result = parser::parse(&file); + let _ = dbg_pls::color!(result); Ok(()) } diff --git a/src/parser.rs b/src/parser.rs index 20bd085..42f0487 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -1,6 +1,9 @@ +use std::{fmt::Debug, iter::Peekable}; + use dbg_pls::DebugPls; use logos::{Lexer, Logos, Span}; -use std::iter::Peekable; + +use crate::error::{CompilerError, Result}; #[derive(Debug, Clone, PartialEq, Eq, Logos, DebugPls)] pub enum Token<'a> { @@ -43,18 +46,13 @@ pub fn lex(src: &str) -> Lexer<'_, Token<'_>> { ::lexer(src) } -pub fn run(src: &str) { - let tokens = lex(src).collect::>(); - dbg_pls::color!(tokens); -} - -#[derive(Debug, PartialEq, Eq)] +#[derive(Debug, PartialEq, Eq, DebugPls)] pub struct Stmt { pub kind: StmtKind, pub span: Span, } -#[derive(Debug, PartialEq, Eq)] +#[derive(Debug, PartialEq, Eq, DebugPls)] pub enum StmtKind { Mov { to: Expr, from: Expr }, Add { to: Expr, value: Expr }, @@ -64,54 +62,85 @@ pub enum StmtKind { Jmp { to: Expr }, Je { to: Expr }, Cmp { rhs: Expr, lhs: Expr }, + Label { name: String }, } -#[derive(Debug, PartialEq, Eq)] +#[derive(Debug, PartialEq, Eq, DebugPls)] pub struct Expr { pub kind: ExprKind, pub span: Span, } -#[derive(Debug, PartialEq, Eq)] +#[derive(Debug, PartialEq, Eq, DebugPls)] pub enum ExprKind { - Register, - Number, + Register(u8), + Number(u64), Addr(Box), + Name(String), } struct Parser<'a, I> where - I: Iterator>, + I: Iterator, Span)>, { iter: Peekable, } -struct CompilerError; +impl CompilerError { + fn new(msg: String, span: Span) -> Self { + Self { span, msg } + } -type Result = std::result::Result; + fn not_allowed(span: Span, token: &str) -> Self { + Self::new(format!("`{token}` is not allowed here"), span) + } -macro_rules! expect { - ($self:ident, $token:pat) => { - if let $token = $self.next()? { - return Err(CompilerError); + fn invalid_token(span: Span) -> Self { + Self::new("Invalid token".to_string(), span) + } + fn eof() -> Self { + Self { + span: Default::default(), + msg: "Unexpected end of file".to_string(), } - }; + } } -fn stmt(kind: StmtKind, span: Span) -> Stmt { - Stmt { kind, span } +macro_rules! expect { + ($self:ident, $token:pat) => {{ + let (next, span) = $self.next()?; + if let $token = next { + span + } else { + return Err(CompilerError { + msg: format!( + concat!("Expected ", stringify!($token), ", found {:?}"), + next, + ), + span, + }); + } + }}; } impl<'a, I> Parser<'a, I> where - I: Iterator>, + I: Iterator, Span)>, { fn program(&mut self) -> Result> { - todo!() + let mut stmts = Vec::new(); + while let Ok(_) = self.peek() { + let stmt = self.stmt()?; + stmts.push(stmt); + } + Ok(stmts) } fn stmt(&mut self) -> Result { - Ok(match self.next()? { + let stmt = |kind, span| Stmt { kind, span }; + + let (token, span) = self.next()?; + Ok(match token { Token::Mov => { let to = self.expr()?; expect!(self, Token::Comma); @@ -135,29 +164,101 @@ where let rhs = self.expr()?; stmt(StmtKind::Cmp { lhs, rhs }, Default::default()) } - Token::Add => {} - Token::Sub => {} - Token::Mul => {} - Token::Div => {} - Token::BracketOpen => {} - Token::BracketClose => {} - Token::Comma => {} - Token::Label(_) => {} - Token::Number(_) => {} - Token::Word(_) => {} - Token::Error => {} + Token::Add => { + let to = self.expr()?; + expect!(self, Token::Comma); + let value = self.expr()?; + stmt(StmtKind::Add { to, value }, Default::default()) + } + Token::Sub => { + let to = self.expr()?; + expect!(self, Token::Comma); + let value = self.expr()?; + stmt(StmtKind::Sub { to, value }, Default::default()) + } + Token::Mul => { + let to = self.expr()?; + expect!(self, Token::Comma); + let value = self.expr()?; + stmt(StmtKind::Mul { to, value }, Default::default()) + } + Token::Div => { + let to = self.expr()?; + expect!(self, Token::Comma); + let value = self.expr()?; + stmt(StmtKind::Div { to, value }, Default::default()) + } + 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::Error => return Err(CompilerError::invalid_token(span)), }) } fn expr(&mut self) -> Result { - todo!() + let expr = |kind, span| Expr { kind, span }; + + let (token, span) = self.next()?; + Ok(match token { + Token::BracketOpen => { + let inner = self.expr()?; + let bclose_span = expect!(self, Token::BracketClose); + expr(ExprKind::Addr(Box::new(inner)), span.start..bclose_span.end) + } + Token::Number(n) => expr(ExprKind::Number(n), span), + Token::Word(name) => { + if let Some(r_number) = name.strip_prefix("r") { + 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, + )); + } + return Ok(expr(ExprKind::Register(n), span)); + } + } + expr(ExprKind::Name(name.to_owned()), span) + } + Token::Mov => return Err(CompilerError::not_allowed(span, "mov")), + Token::Jmp => return Err(CompilerError::not_allowed(span, "jmp")), + Token::Je => return Err(CompilerError::not_allowed(span, "je")), + Token::Cmp => return Err(CompilerError::not_allowed(span, "cmp")), + Token::Add => return Err(CompilerError::not_allowed(span, "add")), + Token::Sub => return Err(CompilerError::not_allowed(span, "sub")), + Token::Mul => return Err(CompilerError::not_allowed(span, "mul")), + Token::Div => return Err(CompilerError::not_allowed(span, "div")), + Token::BracketClose => return Err(CompilerError::not_allowed(span, "]")), + Token::Comma => return Err(CompilerError::not_allowed(span, ",")), + Token::Label(_) => return Err(CompilerError::not_allowed(span, "{label}")), + Token::Error => return Err(CompilerError::invalid_token(span)), + }) } - fn peek(&mut self) -> Result<&Token<'a>> { - self.iter.peek().ok_or(CompilerError) + fn peek(&mut self) -> Result<&(Token<'a>, Span)> { + self.iter.peek().ok_or(CompilerError::eof()) } - fn next(&mut self) -> Result> { - self.iter.next().ok_or(CompilerError) + fn next(&mut self) -> Result<(Token<'a>, Span)> { + self.iter.next().ok_or(CompilerError::eof()) } } + +pub fn parse(src: &str) -> Result> { + let lexer = lex(src).spanned(); + let mut parser = Parser { + iter: lexer.peekable(), + }; + parser.program() +} + +#[cfg(test)] +mod tests {}