diff --git a/print3.wat b/print3.wat new file mode 100644 index 0000000..bf9ca07 --- /dev/null +++ b/print3.wat @@ -0,0 +1,36 @@ +;; testing offsets and such + +(module + ;; import from wasi + ;; fn fd_write(fd, *iovs, iovs_len, nwritten) -> bytes_written + (import "wasi_snapshot_preview1" "fd_write" (func $fd_write (param i32 i32 i32 i32) (result i32))) + + ;; create memory (size = 1 page = 64KiB) + (memory $foobar 1) + + ;; export memory - it's required, but we don't use it so the size is set to 0 + (export "memory" (memory 0)) + + ;; write string to memory (offset = 8 bytes) + (data (i32.const 20) "Hello, world!\n") + + (func $main (export "_start") + ;; iov.iov_base - pointer to string (offset = 0 bytes) + ;; the string's offset is 8 bytes in memory + (i32.store (i32.const 12) (i32.const 20)) + + ;; iov.iov_len - length of the hello world string (offset = 4 bytes) + ;; the string's length is 14 bytes + (i32.store (i32.const 16) (i32.const 24)) + + (i32.const 1) + + (call $fd_write + ;; (i32.const 1) ;; fd: stdout = 1 + (i32.const 12) ;; data: pointer to memory - this is the first memory we create (index 0) + (i32.const 1) ;; data_len: there's 1 string + (i32.const 2468) ;; nwritten: i don't care about this, write it wherever + ) + drop ;; drop number of bytes written + ) +) diff --git a/src/compiler.rs b/src/compiler.rs index 1c09499..1d2fc8f 100644 --- a/src/compiler.rs +++ b/src/compiler.rs @@ -17,7 +17,7 @@ use crate::Error; /// pipeline to convert parsed tree -> wat/wasm pub struct Compiler<'a> { - output: Box<&'a mut dyn std::io::Write>, + output: &'a mut dyn std::io::Write, } struct Allocator { @@ -44,23 +44,26 @@ struct Allocator { // "#; impl<'a> Compiler<'a> { - pub fn new(output: Box<&mut dyn Write>, _output_format: OutputFormat) -> Compiler { - Compiler { - output, - } + pub fn new(output: &mut dyn Write, _output_format: OutputFormat) -> Compiler { + Compiler { output } } pub fn write_module(&mut self, stmts: &[Statement]) -> Result<(), Error> { writeln!(self.output, "(module")?; - + let mut ctx = Context::new(); // ctx.funcs.insert("print".to_string(), (vec![("message".to_string(), Type::String)], Type::empty())); - + // writeln!(self.output, "{}", PRELUDE)?; - + let mut gctx = GenContext::new(); let mut alloc = Allocator::new(); - get_locals(&Expr::Block(crate::data::Block(stmts.to_vec())), &mut alloc, &mut ctx, &mut gctx); + get_locals( + &Expr::Block(crate::data::Block(stmts.to_vec())), + &mut alloc, + &mut ctx, + &mut gctx, + ); // get_strings(&Expr::Block(crate::data::Block(stmts.to_vec())), &mut gctx); // if !gctx.strings.is_empty() { // writeln!(self.output, "(memory 1)")?; @@ -68,11 +71,11 @@ impl<'a> Compiler<'a> { // writeln!(self.output, "(data $strings (i32.const {}) \"{}\")", offset, s.replace("\n", "\\n").replace("\"", "\\\""))?; // } // } - + for stmt in stmts { match stmt { Statement::Func(func) => self.write_func(&gctx, &ctx, func)?, - Statement::Let(..) | Statement::TailExpr(..) | Statement::Expr (..) => { + Statement::Let(..) | Statement::TailExpr(..) | Statement::Expr(..) => { return Err(Error::OtherError("incorrect top level statement".into())) } }; @@ -81,7 +84,12 @@ impl<'a> Compiler<'a> { Ok(()) } - fn write_func(&mut self, parent_gctx: &GenContext, ctx: &Context, func: &Func) -> Result<(), Error> { + fn write_func( + &mut self, + parent_gctx: &GenContext, + ctx: &Context, + func: &Func, + ) -> Result<(), Error> { write!(self.output, "(func ${}", func.name)?; if func.name == "main" { @@ -101,10 +109,10 @@ impl<'a> Compiler<'a> { if !func.ret.is_empty() { write!(self.output, " (result {})", func.ret.string())?; } - + writeln!(self.output)?; writeln!(self.output, "(local $match i32)")?; - + let inferred = func.block.infer(&ctx)?; if func.ret != inferred { return Err(Error::TypeErrorOld(format!( @@ -139,12 +147,19 @@ impl<'a> Compiler<'a> { Ok(()) } - fn gen_expr(&mut self, expr: &Expr, parent_gctx: &GenContext, ctx: &Context) -> Result<(), Error> { + fn gen_expr( + &mut self, + expr: &Expr, + parent_gctx: &GenContext, + ctx: &Context, + ) -> Result<(), Error> { match expr { Expr::Literal(lit) => match lit { Literal::Integer(int) => writeln!(self.output, "i32.const {int}")?, Literal::Float(f) => writeln!(self.output, "f64.const {f}")?, - Literal::Boolean(b) => writeln!(self.output, "i32.const {}", if *b { 1 } else { 0 })?, + Literal::Boolean(b) => { + writeln!(self.output, "i32.const {}", if *b { 1 } else { 0 })? + } // Literal::String(s) => writeln!(self.output, "i32.const {}", parent_gctx.strings.iter().find_map(|(s2, off)| (s == s2).then_some(off)).unwrap())?, _ => todo!(), }, @@ -210,8 +225,12 @@ impl<'a> Compiler<'a> { match pat { Pattern::Literal(lit) => { match lit { - Literal::Integer(int) => writeln!(self.output, "i32.const {}", int)?, - Literal::Boolean(b) => writeln!(self.output, "i32.const {}", if *b { 1 } else { 0 })?, + Literal::Integer(int) => { + writeln!(self.output, "i32.const {}", int)? + } + Literal::Boolean(b) => { + writeln!(self.output, "i32.const {}", if *b { 1 } else { 0 })? + } _ => todo!(), } writeln!(self.output, "local.get $match")?; @@ -245,7 +264,7 @@ impl<'a> Compiler<'a> { self.gen_expr(expr, parent_gctx, &ctx)?; if !expr.infer(&ctx).unwrap().is_empty() { writeln!(self.output, "drop")?; - } + } } Statement::Let(name, expr) => { let ty = expr.infer(&ctx).unwrap(); @@ -271,9 +290,7 @@ impl<'a> Compiler<'a> { // FIXME: not sure what i was thinking impl Allocator { fn new() -> Allocator { - Allocator { - count: 0, - } + Allocator { count: 0 } } fn alloc_ident(&mut self, name: &str) -> String { @@ -306,15 +323,18 @@ fn get_locals(expr: &Expr, alloc: &mut Allocator, ctx: &mut Context, gctx: &mut Statement::Let(name, expr) => { // FIXME: block scoped variables (name collisions) get_locals(expr, alloc, ctx, gctx); - + let ty = expr.infer(ctx).unwrap(); ctx.locals.insert(name.clone(), ty); gctx.exprs.push((alloc.alloc_ident(name), expr.clone())); } Statement::TailExpr(expr) => get_locals(expr, alloc, ctx, gctx), Statement::Expr(expr) => get_locals(expr, alloc, ctx, gctx), - Statement::Func(Func { name, ret, params, .. }) => { - ctx.funcs.insert(name.clone(), (params.clone(), ret.clone())); + Statement::Func(Func { + name, ret, params, .. + }) => { + ctx.funcs + .insert(name.clone(), (params.clone(), ret.clone())); } } } @@ -325,7 +345,11 @@ fn get_locals(expr: &Expr, alloc: &mut Allocator, ctx: &mut Context, gctx: &mut get_locals(b, alloc, ctx, gctx); } Expr::Literal(Literal::String(s)) => { - let offset = gctx.strings.last().map(|(s, off)| off + s.len()).unwrap_or(8); + let offset = gctx + .strings + .last() + .map(|(s, off)| off + s.len()) + .unwrap_or(8); gctx.strings.push((s.clone(), offset)); } Expr::Match(pat, arms) => { diff --git a/src/data.rs b/src/data.rs index 69ada1c..96a0c53 100644 --- a/src/data.rs +++ b/src/data.rs @@ -1,6 +1,6 @@ // TODO: TypedStatement and TypedExpression? -use crate::lexer::{TokenContent, Symbol}; +use crate::lexer::{Symbol, TokenContent}; #[rustfmt::skip] #[derive(Debug, Clone)] @@ -175,7 +175,7 @@ impl Type { Self::Tuple(v) => { let s: Vec<_> = v.iter().map(Type::string).collect(); format!("({})", s.join(", ")) - }, + } _ => unimplemented!(), } } diff --git a/src/error.rs b/src/error.rs index b72239e..14ea60c 100644 --- a/src/error.rs +++ b/src/error.rs @@ -1,4 +1,4 @@ -use miette::{Diagnostic, SourceSpan, NamedSource}; +use miette::{Diagnostic, NamedSource, SourceSpan}; use thiserror::Error; #[derive(Debug, Error, Diagnostic)] @@ -17,8 +17,8 @@ pub enum Error { #[diagnostic()] pub struct SyntaxWrapper { #[source_code] - pub src: NamedSource, - + pub src: NamedSource, + #[related] pub syn: Vec, } @@ -40,10 +40,9 @@ pub struct TypeWrapper { pub struct SyntaxError { // #[source_code] // pub src: NamedSource, - #[help] pub help: String, - + #[label("i do not know what this is")] pub pos: SourceSpan, } @@ -55,27 +54,27 @@ pub enum TypeError { Unary { #[help] help: String, - + #[label("i do not know what this is")] expr: SourceSpan, - + #[label("i do not know what this is")] operator: SourceSpan, }, Binary { #[help] help: String, - + #[label("i do not know what this is")] expr: SourceSpan, - + #[label("i do not know what this is")] operator: SourceSpan, }, UnknownType { #[help] help: String, - + #[label("i do not know what this type is")] unknown: SourceSpan, }, @@ -88,7 +87,7 @@ impl Error { pos: pos.into(), }) } - + pub fn ty(what: &'static str) -> Error { Error::TypeErrorOld(what.to_string()) } diff --git a/src/generator.rs b/src/generator.rs index c59dbd3..f9dd16f 100644 --- a/src/generator.rs +++ b/src/generator.rs @@ -3,7 +3,7 @@ use crate::Error; /// helper pipeline to convert wat/wasm -> actual text/binary pub struct Generator<'a> { - output: Box<&'a mut dyn std::io::Write>, + output: &'a mut dyn std::io::Write, format: OutputFormat, } @@ -24,11 +24,8 @@ impl OutputFormat { } impl Generator<'_> { - pub fn new(output: Box<&mut dyn std::io::Write>, format: OutputFormat) -> Generator { - Generator { - output, - format, - } + pub fn new(output: &mut dyn std::io::Write, format: OutputFormat) -> Generator { + Generator { output, format } } pub fn write_module(&mut self) -> Result<(), Error> { @@ -52,7 +49,7 @@ impl Generator<'_> { } Ok(()) } - + pub fn write_raw(&mut self, text: &str) -> Result<(), Error> { write!(self.output, "{}", text)?; Ok(()) diff --git a/src/lexer.rs b/src/lexer.rs index 017e6f2..249a56c 100644 --- a/src/lexer.rs +++ b/src/lexer.rs @@ -4,8 +4,6 @@ use crate::Error; pub struct Lexer { input: Vec, pos: usize, - name: String, - src: String, } #[rustfmt::skip] @@ -51,12 +49,10 @@ pub enum Symbol { } impl Lexer { - pub fn new(input: &str, name: &str) -> Lexer { + pub fn new(input: &str) -> Lexer { Lexer { input: input.chars().collect(), pos: 0, - src: input.to_string(), - name: name.to_string(), } } @@ -73,7 +69,10 @@ impl Lexer { .get(self.pos) .is_some_and(|c| c.is_ascii_alphanumeric()) { - return Err(Error::syn((self.pos, 1), "there shouldn't be extra characters after the number")); + return Err(Error::syn( + (self.pos, 1), + "there shouldn't be extra characters after the number", + )); } token } @@ -81,7 +80,10 @@ impl Lexer { self.pos += 1; let ch = self.lex_char()?; if !self.input.get(self.pos).is_some_and(|c| *c == '\'') { - return Err(Error::syn((start, self.pos - start), "close your character with a '")); + return Err(Error::syn( + (start, self.pos - start), + "close your character with a '", + )); } self.pos += 1; TokenContent::Char(ch) @@ -111,7 +113,10 @@ impl Lexer { } _ => self.lex_op()?, }; - Ok(Some(Token { token: tok, span: (start, self.pos - start) })) + Ok(Some(Token { + token: tok, + span: (start, self.pos - start), + })) } fn lex_number(&mut self) -> Result { @@ -144,7 +149,10 @@ impl Lexer { buffer.push(ch); self.pos += 1; } else if ch.is_ascii_digit() { - return Err(Error::syn((self.pos, 1), "you seem to have used the wrong base for this number")); + return Err(Error::syn( + (self.pos, 1), + "you seem to have used the wrong base for this number", + )); } else { break; } @@ -182,10 +190,12 @@ impl Lexer { let ch = match self.input.get(self.pos) { Some('\\') => { self.pos += 1; - let ch = self - .input - .get(self.pos) - .ok_or_else(|| Error::syn((self.pos, 1), "there should be an character here (which will be escaped)"))?; + let ch = self.input.get(self.pos).ok_or_else(|| { + Error::syn( + (self.pos, 1), + "there should be an character here (which will be escaped)", + ) + })?; match ch { 'n' => '\n', 't' => '\t', @@ -193,11 +203,23 @@ impl Lexer { '"' => '\"', // 'x' => '\x', // 'u' => '\u', - _ => return Err(Error::syn((self.pos - 1, 2), &format!("i only know how to escape \\n, \\t, \\\\, or \\\", not \\{ch}"))), + _ => { + return Err(Error::syn( + (self.pos - 1, 2), + format!( + "i only know how to escape \\n, \\t, \\\\, or \\\", not \\{ch}" + ), + )) + } } } Some(ch) => *ch, - None => return Err(Error::syn((self.pos, 1), "there should be a character here, not an eof")), + None => { + return Err(Error::syn( + (self.pos, 1), + "there should be a character here, not an eof", + )) + } }; self.pos += 1; Ok(ch) @@ -360,7 +382,12 @@ impl Lexer { ',' => Symbol::Comma, ';' => Symbol::Semicolon, '?' => Symbol::Question, - _ => return Err(Error::syn((self.pos, self.pos), "unexpected character here")), + _ => { + return Err(Error::syn( + (self.pos, self.pos), + "unexpected character here", + )) + } }; self.pos += 1; Ok(TokenContent::Symbol(symbol)) diff --git a/src/lib.rs b/src/lib.rs index 9cddc8f..4ef1e3e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,8 +1,8 @@ #![allow(dead_code, clippy::single_match, clippy::only_used_in_recursion)] +pub mod compiler; pub mod data; pub mod error; -pub mod compiler; pub mod generator; pub mod lexer; pub mod parser; diff --git a/src/main.rs b/src/main.rs index da6da89..cf08cb7 100644 --- a/src/main.rs +++ b/src/main.rs @@ -4,10 +4,15 @@ a second time when generating (so the types are known), there should be a better way */ -use std::{path::PathBuf, fs::OpenOptions}; +use std::{fs::OpenOptions, path::PathBuf}; use clap::Parser; -use lang::{compiler::Compiler, generator::OutputFormat, lexer, parser, error::{SyntaxWrapper, TypeWrapper}}; +use lang::{ + compiler::Compiler, + error::{SyntaxWrapper, TypeWrapper}, + generator::OutputFormat, + lexer, parser, +}; use miette::NamedSource; #[derive(Debug, clap::Parser)] @@ -17,41 +22,40 @@ enum Cli { /// The file containing the code you want to run file: PathBuf, }, - + /// Compile code into webassembly binary or text Compile { #[arg(short, long)] /// What format to output as format: Option, - + #[arg(short, long)] /// Where to output to output: Option, - + /// The file containing the code you want to compile file: PathBuf, }, - // future work: // // /// Start a read-print-eval loop // Repl, - // + // // /// Check/lint the source code without running it // Check, - // + // // /// Run tests and/or benchmarks // Test, - // + // // /// Generate documentation // Doc, - // + // // /// Create a new project // Create, - // + // // /// Format source code nicely // Fmt, - // + // // /// Start a language server // // NOTE: this is VERY HARD to implement and require an overhaul of the lexer/parser/checker // Lsp, @@ -59,7 +63,8 @@ enum Cli { #[derive(Debug, Clone, clap::ValueEnum)] enum OutputType { - Wat, Wasm, + Wat, + Wasm, } fn main() { @@ -92,11 +97,11 @@ fn main() { ..Default::default() }; let reporter = miette::GraphicalReportHandler::new().with_theme(theme); - - match args { - Cli::Run { file } => { - let source = std::fs::read_to_string(&file).expect("no source!"); - let lexer = lexer::Lexer::new(&source, file.to_str().unwrap()); + + match &args { + Cli::Run { file } | Cli::Compile { file, .. } => { + let source = std::fs::read_to_string(file).expect("no source!"); + let lexer = lexer::Lexer::new(&source); let tokens = match lexer.collect::, _>>() { Ok(tokens) => tokens, @@ -105,7 +110,7 @@ fn main() { lang::Error::SyntaxError(syn) => { let mut s = String::new(); let syn = SyntaxWrapper { - src: NamedSource::new(file.to_string_lossy().to_string(), source), + src: NamedSource::new(file.to_string_lossy(), source), syn: vec![syn], }; @@ -118,10 +123,10 @@ fn main() { } }; - let mut parser = parser::Parser::new(tokens, source.clone(), file.to_string_lossy().to_string()); + let mut parser = parser::Parser::new(tokens); let mut statements = vec![]; loop { - match parser.next() { + match parser.parse_statement_option() { Ok(None) => break, Ok(Some(tree)) => statements.push(tree), Err(error) => { @@ -129,7 +134,7 @@ fn main() { lang::Error::SyntaxError(syn) => { let mut s = String::new(); let syn = SyntaxWrapper { - src: NamedSource::new(file.to_string_lossy().to_string(), source), + src: NamedSource::new(file.to_string_lossy(), source), syn: vec![syn], }; reporter.render_report(&mut s, &syn).unwrap(); @@ -138,7 +143,7 @@ fn main() { lang::Error::TypeError(ty) => { let mut s = String::new(); let ty = TypeWrapper { - src: NamedSource::new(file.to_string_lossy().to_string(), source), + src: NamedSource::new(file.to_string_lossy(), source), ty: vec![ty], }; reporter.render_report(&mut s, &ty).unwrap(); @@ -150,74 +155,66 @@ fn main() { } } } - - let mut wat = vec![]; - let mut gen = Compiler::new(Box::new(&mut wat), OutputFormat::WatVerbose); - - if let Err(err) = gen.write_module(&statements) { - panic!("{:?}", err); - } - let wat = String::from_utf8(wat).unwrap(); - let engine = wasmtime::Engine::default(); - let module = wasmtime::Module::new(&engine, wat).unwrap(); - let linker = wasmtime::Linker::new(&engine); - let mut store = wasmtime::Store::new(&engine, 4); - let instance = linker.instantiate(&mut store, &module).unwrap(); - let start = instance.get_typed_func::<(), i32>(&mut store, "_start").unwrap(); - dbg!(start.call(&mut store, ()).unwrap()); - } - Cli::Compile { file, format, output } => { - let source = std::fs::read_to_string(&file).expect("no source!"); - let lexer = lexer::Lexer::new(&source, file.to_str().unwrap()); + match args { + Cli::Run { .. } => { + let mut wat = vec![]; + let mut gen = Compiler::new(&mut wat, OutputFormat::WatVerbose); - let tokens = match lexer.collect::, _>>() { - Ok(tokens) => tokens, - Err(error) => { - eprintln!("error: {:?}", error); - return - } - }; - - let mut parser = parser::Parser::new(tokens, source.clone(), file.to_string_lossy().to_string()); - let mut statements = vec![]; - loop { - match parser.next() { - Ok(None) => break, - Ok(Some(tree)) => statements.push(tree), - Err(error) => { - eprintln!("error: {:?}", error); - return; - } - } - } - - let format = format.unwrap_or_else(|| { - if file.extension().is_some_and(|ext| ext == "wasm") { - OutputType::Wasm - } else { - OutputType::Wat - } - }); - - let mut output: Box = match output { - Some(path) => Box::new(OpenOptions::new().create(true).write(true).open(path).expect("failed to open file")), - None => Box::new(std::io::stdout()), - }; - - match format { - OutputType::Wat => { - let mut gen = Compiler::new(Box::new(&mut output), OutputFormat::WatVerbose); - if let Err(err) = gen.write_module(&statements) { panic!("{:?}", err); } + + let wat = String::from_utf8(wat).unwrap(); + let engine = wasmtime::Engine::default(); + let module = wasmtime::Module::new(&engine, wat).unwrap(); + let linker = wasmtime::Linker::new(&engine); + let mut store = wasmtime::Store::new(&engine, 4); + let instance = linker.instantiate(&mut store, &module).unwrap(); + let start = instance + .get_typed_func::<(), i32>(&mut store, "_start") + .unwrap(); + dbg!(start.call(&mut store, ()).unwrap()); } - OutputType::Wasm => { - let mut gen = Compiler::new(Box::new(&mut output), OutputFormat::Wasm); - - if let Err(err) = gen.write_module(&statements) { - panic!("{:?}", err); + Cli::Compile { + format, + output, + file, + } => { + let format = format.unwrap_or_else(|| { + if file.extension().is_some_and(|ext| ext == "wasm") { + OutputType::Wasm + } else { + OutputType::Wat + } + }); + + let mut output: Box = match output { + Some(path) => Box::new( + OpenOptions::new() + .create(true) + .write(true) + .open(path) + .expect("failed to open file"), + ), + None => Box::new(std::io::stdout()), + }; + + match format { + OutputType::Wat => { + let mut gen = Compiler::new(&mut output, OutputFormat::WatVerbose); + + if let Err(err) = gen.write_module(&statements) { + panic!("{:?}", err); + } + } + OutputType::Wasm => { + let mut gen = Compiler::new(&mut output, OutputFormat::Wasm); + + if let Err(err) = gen.write_module(&statements) { + panic!("{:?}", err); + } + } } } } diff --git a/src/parser.rs b/src/parser.rs index 7e0c117..c3b95c2 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -1,14 +1,12 @@ use std::collections::HashMap; -use crate::data::{BinaryOp, Block, Expr, Literal, Pattern, PrefixOp, Statement, Type, Func}; -use crate::lexer::{TokenContent, Symbol, Token}; +use crate::data::{BinaryOp, Block, Expr, Func, Literal, Pattern, PrefixOp, Statement, Type}; +use crate::lexer::{Symbol, Token, TokenContent}; use crate::Error; pub struct Parser { tokens: Vec, pos: usize, - src: String, - name: String, } #[derive(Debug, Clone)] @@ -18,13 +16,8 @@ pub struct Context { } impl Parser { - pub fn new(tokens: Vec, src: String, name: String) -> Parser { - Parser { - src, - name, - tokens, - pos: 0, - } + pub fn new(tokens: Vec) -> Parser { + Parser { tokens, pos: 0 } } fn err_syn(&self, pos: (usize, usize), help: &str) -> Error { @@ -32,7 +25,10 @@ impl Parser { } fn err_eof(&self, wanted: &str) -> Error { - self.err_syn(self.tokens.last().map(|tok| tok.span).unwrap_or((0, 0)), &format!("wanted {}, got eof", wanted)) + self.err_syn( + self.tokens.last().map(|tok| tok.span).unwrap_or((0, 0)), + &format!("wanted {}, got eof", wanted), + ) } fn err_ty_unknown(&self, pos: (usize, usize), help: &str) -> Error { @@ -65,7 +61,7 @@ impl Parser { result } - pub fn next(&mut self) -> Result, Error> { + pub fn parse_statement_option(&mut self) -> Result, Error> { match self.peek_tok() { Some(_) => Some(self.parse_statement()).transpose(), None => Ok(None), @@ -80,7 +76,7 @@ impl Parser { TokenContent::Let => { self.eat(TokenContent::Let)?; let name = self.parse_ident()?; - if let Ok(_) = self.eat(TokenContent::Symbol(Symbol::Colon)) { + if self.eat(TokenContent::Symbol(Symbol::Colon)).is_ok() { // TODO: types in variables self.parse_type()?; } @@ -101,17 +97,20 @@ impl Parser { let name = self.parse_ident()?; self.eat(TokenContent::OpenParan)?; let mut params = vec![]; - while !self.peek_tok().is_some_and(|tok| tok.token == TokenContent::CloseParan) { + while !self + .peek_tok() + .is_some_and(|tok| tok.token == TokenContent::CloseParan) + { let name = self.parse_ident()?; self.eat(TokenContent::Symbol(Symbol::Colon))?; let ty = self.parse_type()?; params.push((name, ty)); - + if self.eat(TokenContent::Symbol(Symbol::Comma)).is_err() { break; } } - + self.eat(TokenContent::CloseParan)?; let ret = if self.eat(TokenContent::Symbol(Symbol::ThinArrow)).is_ok() { self.parse_type()? @@ -121,11 +120,20 @@ impl Parser { self.eat(TokenContent::OpenBrace)?; let block = self.parse_block()?; self.eat(TokenContent::CloseBrace)?; - Statement::Func(Func { name, params, ret, block, public }) + Statement::Func(Func { + name, + params, + ret, + block, + public, + }) } _ => { let expr = self.parse_expr(0)?; - if self.peek_tok().is_some_and(|tk| tk.token == TokenContent::Symbol(Symbol::Semicolon)) { + if self + .peek_tok() + .is_some_and(|tk| tk.token == TokenContent::Symbol(Symbol::Semicolon)) + { self.eat(TokenContent::Symbol(Symbol::Semicolon))?; Statement::Expr(expr) } else { @@ -145,20 +153,28 @@ impl Parser { "i32" => Type::Integer, "f64" => Type::Float, "bool" => Type::Boolean, - _ => return Err(self.err_ty_unknown(tok.span, "use a type that i know (currently only i32 and bool)")), + _ => { + return Err(self.err_ty_unknown( + tok.span, + "use a type that i know (currently only i32 and bool)", + )) + } }, TokenContent::OpenParan => { let mut tys = vec![]; - while !self.peek_tok().is_some_and(|tok| tok.token == TokenContent::CloseParan) { + while !self + .peek_tok() + .is_some_and(|tok| tok.token == TokenContent::CloseParan) + { tys.push(self.parse_type()?); - + if self.eat(TokenContent::Symbol(Symbol::Comma)).is_err() { break; } } self.eat(TokenContent::CloseParan)?; Type::Tuple(tys) - }, + } _ => return Err(self.err_syn(tok.span, "this should be a pattern")), }; Ok(ty) @@ -166,8 +182,13 @@ impl Parser { fn parse_ident(&mut self) -> Result { let result = match self.peek_tok() { - Some(Token { token: TokenContent::Ident(ident), .. }) => Ok(ident.to_string()), - Some(t) => return Err(self.err_syn(t.span, &format!("expected ident, got {:?}", t.token))), + Some(Token { + token: TokenContent::Ident(ident), + .. + }) => Ok(ident.to_string()), + Some(t) => { + return Err(self.err_syn(t.span, &format!("expected ident, got {:?}", t.token))) + } None => return Err(self.err_eof("ident")), }; self.pos += 1; @@ -177,7 +198,10 @@ impl Parser { fn parse_block(&mut self) -> Result { let mut statements = vec![]; loop { - if self.peek_tok().is_some_and(|tok| tok.token == TokenContent::CloseBrace) { + if self + .peek_tok() + .is_some_and(|tok| tok.token == TokenContent::CloseBrace) + { break; } match self.parse_statement()? { @@ -188,7 +212,10 @@ impl Parser { stmt => statements.push(stmt), } match self.peek_tok() { - Some(Token { token: TokenContent::CloseBrace, .. }) => break, + Some(Token { + token: TokenContent::CloseBrace, + .. + }) => break, _ => (), }; } @@ -212,7 +239,10 @@ impl Parser { let ident = ident.clone(); if self.eat(TokenContent::OpenParan).is_ok() { let mut params = vec![]; - while !self.peek_tok().is_some_and(|tok| tok.token == TokenContent::CloseParan) { + while !self + .peek_tok() + .is_some_and(|tok| tok.token == TokenContent::CloseParan) + { params.push(self.parse_expr(0)?); if self.eat(TokenContent::Symbol(Symbol::Comma)).is_err() { break; @@ -233,17 +263,30 @@ impl Parser { self.eat(TokenContent::OpenBrace)?; let block = self.parse_block()?; self.eat(TokenContent::CloseBrace)?; - let otherwise = if self.peek_tok().is_some_and(|t| t.token == TokenContent::Else) { + let otherwise = if self + .peek_tok() + .is_some_and(|t| t.token == TokenContent::Else) + { self.next_tok(); match self.peek_tok() { - Some(Token { token: TokenContent::OpenBrace, .. }) => { + Some(Token { + token: TokenContent::OpenBrace, + .. + }) => { self.eat(TokenContent::OpenBrace)?; let b = Some(self.parse_block()?); self.eat(TokenContent::CloseBrace)?; b } - Some(Token { token: TokenContent::If, .. }) => Some(Block(vec![Statement::TailExpr(self.parse_expr(0)?)])), - Some(tk) => return Err(self.err_syn(tk.span, "this should be followed by an if or block")), + Some(Token { + token: TokenContent::If, + .. + }) => Some(Block(vec![Statement::TailExpr(self.parse_expr(0)?)])), + Some(tk) => { + return Err( + self.err_syn(tk.span, "this should be followed by an if or block") + ) + } None => return Err(self.err_eof("if or block")), } } else { @@ -260,7 +303,10 @@ impl Parser { } TokenContent::Symbol(_) => { let Some(op) = PrefixOp::from_token(&tok.token) else { - return Err(self.err_syn(tok.span, "this should be changed into a valid operator or removed outright")); + return Err(self.err_syn( + tok.span, + "this should be changed into a valid operator or removed outright", + )); }; let expr = self.parse_expr(1)?; Expr::Unary(op, Box::new(expr)) @@ -279,7 +325,11 @@ impl Parser { break; } - if self.peek_tok().is_none() || self.peek_tok().is_some_and(|t| t.token == TokenContent::CloseBrace) { + if self.peek_tok().is_none() + || self + .peek_tok() + .is_some_and(|t| t.token == TokenContent::CloseBrace) + { break; } } diff --git a/src/types.rs b/src/types.rs index d381b4a..7a38c25 100644 --- a/src/types.rs +++ b/src/types.rs @@ -48,7 +48,7 @@ impl Expr { if args.len() < params.len() { return Err(Error::ty("missing parameters")); } - + if args.len() > params.len() { return Err(Error::ty("too many parameters")); } @@ -59,7 +59,7 @@ impl Expr { return Err(Error::TypeErrorOld(format!("wrong type for {name}: it should've been {ty:?} but you put a {got:?}", ))); } } - + Ok(ret.clone()) } None => Err(Error::ReferenceError(format!( diff --git a/test/bad b/test/bad index dc9ee8a..f095d61 100644 --- a/test/bad +++ b/test/bad @@ -1,3 +1,3 @@ fn main() { - let a = 10 * true; + let a = 10 + } \ No newline at end of file diff --git a/tests/lexer.rs b/tests/lexer.rs index 78b0af3..262fa81 100644 --- a/tests/lexer.rs +++ b/tests/lexer.rs @@ -1,14 +1,44 @@ -use lang::{lexer::{Lexer, TokenContent, Symbol}, Error}; +use lang::{ + lexer::{Lexer, Symbol, Token, TokenContent}, + Error, +}; #[test] fn test_foo() { let tokens: Result, Error> = Lexer::new("1 * 5 / 3").collect(); let tokens = tokens.expect("should parse"); - assert_eq!(tokens, vec![ - TokenContent::Number { radix: 10, text: "1".into() }, - TokenContent::Symbol(Symbol::Star), - TokenContent::Number { radix: 10, text: "5".into() }, - TokenContent::Symbol(Symbol::Slash), - TokenContent::Number { radix: 10, text: "3".into() }, - ]) + assert_eq!( + tokens, + vec![ + Token { + span: (0, 1), + token: TokenContent::Number { + radix: 10, + text: "1".into() + }, + }, + Token { + span: (1, 1), + token: TokenContent::Symbol(Symbol::Star), + }, + Token { + span: (2, 1), + token: TokenContent::Number { + radix: 10, + text: "5".into() + }, + }, + Token { + span: (3, 1), + token: TokenContent::Symbol(Symbol::Slash), + }, + Token { + span: (4, 1), + token: TokenContent::Number { + radix: 10, + text: "3".into() + }, + }, + ] + ) }