commit 4ff159dd4a706fcdff13a21d4d9d878c88ca0e23 Author: Masashi Date: Mon Dec 15 13:53:57 2025 +0530 first commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ea8c4bf --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/target diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..23d1a7c --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,303 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "aho-corasick" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" +dependencies = [ + "memchr", +] + +[[package]] +name = "anstream" +version = "0.6.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43d5b281e737544384e969a5ccad3f1cdd24b48086a0fc1b2a5262a26b8f4f4a" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5192cca8006f1fd4f7237516f40fa183bb07f8fbdfedaa0036de5ea9b0b45e78" + +[[package]] +name = "anstyle-parse" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" +dependencies = [ + "windows-sys", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" +dependencies = [ + "anstyle", + "once_cell_polyfill", + "windows-sys", +] + +[[package]] +name = "beef" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a8241f3ebb85c056b509d4327ad0358fbbba6ffb340bf388f26350aeda225b1" + +[[package]] +name = "clap" +version = "4.5.53" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9e340e012a1bf4935f5282ed1436d1489548e8f72308207ea5df0e23d2d03f8" +dependencies = [ + "clap_builder", + "clap_derive", +] + +[[package]] +name = "clap_builder" +version = "4.5.53" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d76b5d13eaa18c901fd2f7fca939fefe3a0727a953561fefdf3b2922b8569d00" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim", +] + +[[package]] +name = "clap_derive" +version = "4.5.49" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a0b5487afeab2deb2ff4e03a807ad1a03ac532ff5a2cee5d86884440c7f7671" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "clap_lex" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1d728cc89cf3aee9ff92b05e62b19ee65a02b5702cff7d5a377e32c6ae29d8d" + +[[package]] +name = "colorchoice" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "hashbrown" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "indexmap" +version = "2.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ad4bb2b565bca0645f4d68c5c9af97fba094e9791da685bf83cb5f3ce74acf2" +dependencies = [ + "equivalent", + "hashbrown", +] + +[[package]] +name = "is_terminal_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" + +[[package]] +name = "logos" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a790d11254054e5dc83902dba85d253ff06ceb0cfafb12be8773435cb9dfb4f4" +dependencies = [ + "logos-derive", +] + +[[package]] +name = "logos-codegen" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f60337c43a38313b58871f8d5d76872b8e17aa9d51fad494b5e76092c0ce05f5" +dependencies = [ + "beef", + "fnv", + "proc-macro2", + "quote", + "regex-automata", + "regex-syntax", + "rustc_version", + "syn", +] + +[[package]] +name = "logos-derive" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d151b2ae667f69e10b8738f5cac0c746faa22b2e15ea7e83b55476afec3767dc" +dependencies = [ + "logos-codegen", +] + +[[package]] +name = "memchr" +version = "2.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" + +[[package]] +name = "once_cell_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" + +[[package]] +name = "proc-macro2" +version = "1.0.103" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ee95bc4ef87b8d5ba32e8b7714ccc834865276eab0aed5c9958d00ec45f49e8" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.42" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a338cc41d27e6cc6dce6cefc13a0729dfbb81c262b1f519331575dd80ef3067f" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "regex-automata" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5276caf25ac86c8d810222b3dbb938e512c55c6831a10f3e6ed1c93b84041f1c" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58" + +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver", +] + +[[package]] +name = "semver" +version = "1.0.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2" + +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + +[[package]] +name = "suicmez" +version = "0.1.0" +dependencies = [ + "clap", + "indexmap", + "logos", +] + +[[package]] +name = "syn" +version = "2.0.111" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "390cc9a294ab71bdb1aa2e99d13be9c753cd2d7bd6560c77118597410c4d2e87" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "unicode-ident" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" + +[[package]] +name = "utf8parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link", +] diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..8d7329a --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "suicmez" +version = "0.1.0" +edition = "2024" + +[dependencies] +clap = { version = "4.5.53", features = ["derive"] } +indexmap = "2.0" +logos = "0.16.0" diff --git a/output.md b/output.md new file mode 100644 index 0000000..9b32d6e --- /dev/null +++ b/output.md @@ -0,0 +1,2843 @@ +```rust +// src/lib.rs +pub const EXTENSION: &str = ".sui"; + +pub mod ast; +pub mod lexer; +pub mod parser; +pub mod typechecker; + +``` + +```rust +// src/parser.rs +use crate::ast::*; +use crate::lexer::Token; + +use std::iter::Peekable; +use std::ops::Range; +use std::vec::IntoIter; + +type TokenIter = Peekable)>>; + +pub struct Parser { + pub file: String, + pub tokens: TokenIter, +} + +#[derive(Debug)] +pub struct ParseError { + pub message: String, + pub span: Span, +} + +impl Parser { + pub fn new(file: String, tokens: Vec<(Token, Range)>) -> Self { + Parser { + file, + tokens: tokens.into_iter().peekable(), + } + } + + // Parse the entire file into a list of AST nodes + pub fn parse(&mut self) -> Result, ParseError> { + let mut nodes = Vec::new(); + + while self.peek().is_some() { + nodes.push(self.parse_top_level()?); + } + + Ok(nodes) + } + + fn peek(&mut self) -> Option<&Token> { + self.tokens.peek().map(|(token, _)| token) + } + + fn peek_span(&mut self) -> Option> { + self.tokens.peek().map(|(_, span)| span.clone()) + } + + fn next(&mut self) -> Option<(Token, Range)> { + self.tokens.next() + } + + fn expect(&mut self, expected: Token) -> Result, ParseError> { + match self.next() { + Some((token, span)) + if std::mem::discriminant(&token) == std::mem::discriminant(&expected) => + { + Ok(span) + } + Some((token, span)) => Err(ParseError { + message: format!("Expected {:?}, found {:?}", expected, token), + span: Span::new(&span, self.file.clone()), + }), + None => Err(ParseError { + message: format!("Expected {:?}, found EOF", expected), + span: Span::new(&(0..0), self.file.clone()), + }), + } + } + + fn error(&self, msg: String, span: Range) -> Result { + Err(ParseError { + message: msg, + span: Span::new(&span, self.file.clone()), + }) + } + + fn parse_top_level(&mut self) -> Result { + let mut attributes = Vec::new(); + + // Parse any leading attributes + while matches!(self.peek(), Some(Token::At)) { + attributes.push(self.parse_attribute()?); + } + + let start = self.peek_span().unwrap_or(0..0).start; + let token = self.peek().cloned(); + match token { + Some(Token::KeywordUse) => { + self.next(); + let path = match self.next() { + Some((Token::String(s), _)) => s, + Some((_, span)) => { + return self.error("Expected string after 'use'".to_string(), span); + } + None => { + return self.error("Expected string after 'use'".to_string(), start..start); + } + }; + let end = self.peek_span().unwrap_or(start..start).end; + Ok(ASTNode { + kind: ASTNodeKind::Use(path), + span: Span::new(&(start..end), self.file.clone()), + attributes, + }) + } + Some(Token::KeywordFn) => { + self.next(); + let func = self.parse_function()?; + let end = self.peek_span().unwrap_or(start..start).end; + Ok(ASTNode { + kind: ASTNodeKind::Function(func), + span: Span::new(&(start..end), self.file.clone()), + attributes, + }) + } + Some(Token::KeywordStruct) => { + self.next(); + let struct_def = self.parse_struct()?; + let end = self.peek_span().unwrap_or(start..start).end; + Ok(ASTNode { + kind: ASTNodeKind::Struct(struct_def), + span: Span::new(&(start..end), self.file.clone()), + attributes, + }) + } + Some(Token::KeywordEnum) => { + self.next(); + let enum_def = self.parse_enum()?; + let end = self.peek_span().unwrap_or(start..start).end; + Ok(ASTNode { + kind: ASTNodeKind::Enum(enum_def), + span: Span::new(&(start..end), self.file.clone()), + attributes, + }) + } + Some(Token::KeywordImpl) => { + self.next(); + let impl_def = self.parse_impl()?; + let end = self.peek_span().unwrap_or(start..start).end; + Ok(ASTNode { + kind: ASTNodeKind::Impl(impl_def), + span: Span::new(&(start..end), self.file.clone()), + attributes, + }) + } + Some(Token::KeywordTrait) => { + self.next(); + let trait_def = self.parse_trait()?; + let end = self.peek_span().unwrap_or(start..start).end; + Ok(ASTNode { + kind: ASTNodeKind::Trait(trait_def), + span: Span::new(&(start..end), self.file.clone()), + attributes, + }) + } + Some(Token::KeywordExtern) => { + self.next(); + let extern_def = self.parse_extern()?; + let end = self.peek_span().unwrap_or(start..start).end; + Ok(ASTNode { + kind: ASTNodeKind::Extern(extern_def), + span: Span::new(&(start..end), self.file.clone()), + attributes, + }) + } + Some(Token::KeywordLoad) => { + self.next(); + let load_def = self.parse_load()?; + let end = self.peek_span().unwrap_or(start..start).end; + Ok(ASTNode { + kind: ASTNodeKind::Load(load_def), + span: Span::new(&(start..end), self.file.clone()), + attributes, + }) + } + Some(token) => { + let span = self.peek_span().unwrap_or(start..start); + self.error(format!("Unexpected token at top level: {:?}", token), span) + } + None => self.error("Unexpected EOF".to_string(), start..start), + } + } + + fn parse_attribute(&mut self) -> Result { + self.expect(Token::At)?; + let start = self.peek_span().unwrap_or(0..0).start; + let name = match self.next() { + Some((Token::Variable(name), _)) => name, + Some((_, span)) => return self.error("Expected attribute name".to_string(), span), + None => return self.error("Expected attribute name".to_string(), start..start), + }; + + // Parentheses are optional + let mut args = vec![]; + if matches!(self.peek(), Some(Token::LParen)) { + self.next(); + loop { + let token = self.peek().cloned(); + match token { + Some(Token::RParen) => { + self.next(); + break; + } + Some(Token::String(s)) => { + self.next(); + args.push(AttributeArg::Literal(s)); + } + Some(Token::Variable(id)) => { + self.next(); + let next_token = self.peek().cloned(); + if matches!(next_token, Some(Token::Assign)) { + self.next(); + match self.next() { + Some((Token::Variable(val), _)) => { + args.push(AttributeArg::KeyValue(id, val)) + } + Some((_, span)) => { + return self.error("Expected value after =".to_string(), span); + } + None => { + return self + .error("Expected value after =".to_string(), start..start); + } + } + } else { + args.push(AttributeArg::Value(id)); + } + } + Some(token) => { + let span = self.peek_span().unwrap_or(start..start); + return self + .error(format!("Unexpected token in attribute: {:?}", token), span); + } + None => { + return self.error("Expected attribute argument".to_string(), start..start); + } + } + let next_token = self.peek().cloned(); + if matches!(next_token, Some(Token::Comma)) { + self.next(); + } else if matches!(next_token, Some(Token::RParen)) { + // ok + } else { + { + let span = self.peek_span().unwrap_or(start..start); + return self.error("Expected , or )".to_string(), span); + } + } + } + } + let end = self.peek_span().unwrap_or(start..start).end; + Ok(Attribute { + name, + args, + span: Span::new(&(start..end), self.file.clone()), + }) + } + + fn parse_function(&mut self) -> Result { + let start = self.peek_span().unwrap_or(0..0).start; + let name = match self.next() { + Some((Token::Variable(n), _)) => n, + Some((_, span)) => return self.error("Expected function name".to_string(), span), + None => return self.error("Expected function name".to_string(), start..start), + }; + + // Parse type parameters if present + let parameters = if matches!(self.peek(), Some(Token::Less)) { + self.next(); + self.parse_parameters()? + } else { + Vec::new() + }; + + // Parse function arguments + self.expect(Token::LParen)?; + let mut args = Vec::new(); + loop { + if matches!(self.peek(), Some(Token::RParen)) { + self.next(); + break; + } + + let arg_name = match self.next() { + Some((Token::Variable(n), _)) => n, + Some((_, span)) => return self.error("Expected argument name".to_string(), span), + None => return self.error("Expected argument name".to_string(), start..start), + }; + + let arg_type = if matches!(self.peek(), Some(Token::Colon)) { + self.next(); + Some(self.parse_type_annot()?) + } else { + None + }; + + args.push((arg_name, arg_type)); + + if matches!(self.peek(), Some(Token::Comma)) { + self.next(); + } else if !matches!(self.peek(), Some(Token::RParen)) { + let span = self.peek_span().unwrap_or(start..start); + return self.error("Expected , or )".to_string(), span); + } + } + + // Parse return type if present + let return_type = if matches!(self.peek(), Some(Token::Arrow)) { + self.next(); + Some(self.parse_type_annot()?) + } else { + None + }; + + // Parse body expression + let body = self.parse_expr()?; + + Ok(Function { + name, + parameters, + args, + return_type, + body, + }) + } + + fn parse_struct(&mut self) -> Result { + let start = self.peek_span().unwrap_or(0..0).start; + let name = match self.next() { + Some((Token::Variable(n), _)) => n, + Some((_, span)) => return self.error("Expected struct name".to_string(), span), + None => return self.error("Expected struct name".to_string(), start..start), + }; + + // Parse type parameters if present + let parameters = if matches!(self.peek(), Some(Token::Less)) { + self.next(); + self.parse_parameters()? + } else { + Vec::new() + }; + + // Parse fields + let mut fields = Vec::new(); + loop { + if matches!(self.peek(), Some(Token::KeywordEnd)) { + self.next(); + break; + } + + let field_start = self.peek_span().unwrap_or(0..0).start; + let field_name = match self.next() { + Some((Token::Variable(n), _)) => n, + Some((_, span)) => return self.error("Expected field name".to_string(), span), + None => return self.error("Expected field name".to_string(), start..start), + }; + + self.expect(Token::Colon)?; + let field_type = self.parse_type_annot()?; + let field_end = self.peek_span().unwrap_or(field_start..field_start).start; + + fields.push(Field { + name: field_name, + field_type, + span: Span::new(&(field_start..field_end), self.file.clone()), + }); + + if matches!(self.peek(), Some(Token::Comma)) { + self.next(); + } + } + + Ok(Struct { + name, + parameters, + fields, + }) + } + + fn parse_enum(&mut self) -> Result { + let start = self.peek_span().unwrap_or(0..0).start; + let name = match self.next() { + Some((Token::Variable(n), _)) => n, + Some((_, span)) => return self.error("Expected enum name".to_string(), span), + None => return self.error("Expected enum name".to_string(), start..start), + }; + + // Parse type parameters if present + let parameters = if matches!(self.peek(), Some(Token::Less)) { + self.next(); + self.parse_parameters()? + } else { + Vec::new() + }; + + // Parse variants + let mut variants = Vec::new(); + loop { + if matches!(self.peek(), Some(Token::KeywordEnd)) { + self.next(); + break; + } + + let variant_start = self.peek_span().unwrap_or(0..0).start; + let variant_name = match self.next() { + Some((Token::Variable(n), _)) => n, + Some((_, span)) => return self.error("Expected variant name".to_string(), span), + None => return self.error("Expected variant name".to_string(), start..start), + }; + + let mut fields = Vec::new(); + if matches!(self.peek(), Some(Token::LParen)) { + self.next(); + loop { + if matches!(self.peek(), Some(Token::RParen)) { + self.next(); + break; + } + fields.push(self.parse_type_annot()?); + + if matches!(self.peek(), Some(Token::Comma)) { + self.next(); + } + } + } + + let variant_end = self + .peek_span() + .unwrap_or(variant_start..variant_start) + .start; + variants.push(Variant { + name: variant_name, + fields, + span: Span::new(&(variant_start..variant_end), self.file.clone()), + }); + + if matches!(self.peek(), Some(Token::Comma)) { + self.next(); + } + } + + Ok(Enum { + name, + parameters, + variants, + }) + } + + fn parse_impl(&mut self) -> Result { + let start = self.peek_span().unwrap_or(0..0).start; + + // Parse impl target as a type (could be generic like Option) + let target_type = self.parse_type_annot()?; + + // Extract the base type name from the type annotation + let target = match target_type { + TypeAnnot::Var(name) => name, + TypeAnnot::Cons(name, _) => name, + _ => { + return self.error( + "Expected type name for impl target".to_string(), + start..start, + ); + } + }; + + // Parse optional trait name + let trait_name = if matches!(self.peek(), Some(Token::Colon)) { + self.next(); + match self.next() { + Some((Token::Variable(n), _)) => Some(n), + Some((_, span)) => return self.error("Expected trait name".to_string(), span), + None => return self.error("Expected trait name".to_string(), start..start), + } + } else { + None + }; + + // Parse methods + let mut methods = Vec::new(); + loop { + if matches!(self.peek(), Some(Token::KeywordEnd)) { + self.next(); + break; + } + + self.expect(Token::KeywordFn)?; + methods.push(self.parse_function()?); + } + + Ok(Impl { + target, + trait_name, + methods, + }) + } + + fn parse_trait(&mut self) -> Result { + let start = self.peek_span().unwrap_or(0..0).start; + let name = match self.next() { + Some((Token::Variable(n), _)) => n, + Some((_, span)) => return self.error("Expected trait name".to_string(), span), + None => return self.error("Expected trait name".to_string(), start..start), + }; + + // Parse type parameters if present + let parameters = if matches!(self.peek(), Some(Token::Less)) { + self.next(); + self.parse_parameters()? + } else { + Vec::new() + }; + + // Parse methods + let mut methods = Vec::new(); + + loop { + if matches!(self.peek(), Some(Token::KeywordEnd)) { + self.next(); + break; + } + + if matches!(self.peek(), Some(Token::KeywordFn)) { + self.next(); + methods.push(self.parse_function_signature()?); + + // Optional comma between methods + if matches!(self.peek(), Some(Token::Comma)) { + self.next(); + } + } else { + break; + } + } + + Ok(Trait { + name, + methods, + parameters, + associated_types: Vec::new(), + }) + } + + fn parse_extern(&mut self) -> Result { + let start = self.peek_span().unwrap_or(0..0).start; + let name = match self.next() { + Some((Token::Variable(n), _)) => n, + Some((_, span)) => return self.error("Expected extern name".to_string(), span), + None => return self.error("Expected extern name".to_string(), start..start), + }; + + // Parse argument types (with optional parameter names) + self.expect(Token::LParen)?; + let mut args = Vec::new(); + loop { + if matches!(self.peek(), Some(Token::RParen)) { + self.next(); + break; + } + + args.push(self.parse_type_annot()?); + + if matches!(self.peek(), Some(Token::Comma)) { + self.next(); + } + } + + // Parse return type + self.expect(Token::Arrow)?; + let return_type = self.parse_type_annot()?; + + // Parse from clause + self.expect(Token::KeywordFrom)?; + let from = match self.next() { + Some((Token::Variable(n), _)) => n, + Some((_, span)) => return self.error("Expected library identifier".to_string(), span), + None => return self.error("Expected library identifier".to_string(), start..start), + }; + + let end = self.peek_span().unwrap_or(start..start).end; + Ok(Extern { + name, + args, + return_type, + from, + span: Span::new(&(start..end), self.file.clone()), + }) + } + + fn parse_load(&mut self) -> Result { + let start = self.peek_span().unwrap_or(0..0).start; + let library = match self.next() { + Some((Token::String(s), _)) => s, + Some((_, span)) => return self.error("Expected library name".to_string(), span), + None => return self.error("Expected library name".to_string(), start..start), + }; + + self.expect(Token::KeywordAs)?; + let alias = match self.next() { + Some((Token::Variable(a), _)) => a, + Some((_, span)) => return self.error("Expected alias".to_string(), span), + None => return self.error("Expected alias".to_string(), start..start), + }; + + let end = self.peek_span().unwrap_or(start..start).end; + Ok(Load { + library, + alias, + span: Span::new(&(start..end), self.file.clone()), + }) + } + + fn parse_parameters(&mut self) -> Result, ParseError> { + let start = self.peek_span().unwrap_or(0..0).start; + let mut params = Vec::new(); + + loop { + if matches!(self.peek(), Some(Token::Greater)) { + self.next(); + break; + } + + let param_start = self.peek_span().unwrap_or(0..0).start; + let param_name = match self.next() { + Some((Token::Variable(n), _)) => n, + Some((_, span)) => return self.error("Expected parameter name".to_string(), span), + None => return self.error("Expected parameter name".to_string(), start..start), + }; + + let bounds = if matches!(self.peek(), Some(Token::Colon)) { + self.next(); + self.parse_trait_bounds()? + } else { + Vec::new() + }; + + let kind = if matches!(self.peek(), Some(Token::Colon)) { + self.next(); + Some(self.parse_kind()?) + } else { + None + }; + + let param_end = self.peek_span().unwrap_or(param_start..param_start).end; + params.push(Parameter { + name: param_name, + bounds, + kind, + span: Span::new(&(param_start..param_end), self.file.clone()), + }); + + if matches!(self.peek(), Some(Token::Comma)) { + self.next(); + } + } + + Ok(params) + } + + fn parse_trait_bounds(&mut self) -> Result, ParseError> { + let mut bounds = Vec::new(); + loop { + match self.next() { + Some((Token::Variable(n), _)) => bounds.push(n), + Some((_, span)) => return self.error("Expected trait name".to_string(), span), + None => return self.error("Expected trait name".to_string(), 0..0), + } + + if !matches!(self.peek(), Some(Token::Plus)) { + break; + } + self.next(); + } + + Ok(bounds) + } + + fn parse_kind(&mut self) -> Result { + if matches!(self.peek(), Some(Token::Mul)) { + self.next(); + Ok(Kind::Star) + } else { + let k1 = Box::new(self.parse_kind()?); + self.expect(Token::Arrow)?; + let k2 = Box::new(self.parse_kind()?); + Ok(Kind::Arrow(k1, k2)) + } + } + + fn parse_type_annot(&mut self) -> Result { + let start = self.peek_span().unwrap_or(0..0).start; + + // Check for function type: fn (args)->ret + if matches!(self.peek(), Some(Token::KeywordFn)) { + self.next(); + self.expect(Token::LParen)?; + let mut arg_types = Vec::new(); + + loop { + if matches!(self.peek(), Some(Token::RParen)) { + self.next(); + break; + } + arg_types.push(self.parse_type_annot()?); + + if matches!(self.peek(), Some(Token::Comma)) { + self.next(); + } + } + + self.expect(Token::Arrow)?; + let ret_type = Box::new(self.parse_type_annot()?); + return Ok(TypeAnnot::Function(arg_types, ret_type)); + } + + let mut base_type = match self.next() { + Some((Token::Variable(n), _)) => TypeAnnot::Cons(n, vec![]), + Some((Token::KeywordBool, _)) => TypeAnnot::Cons("bool".to_string(), vec![]), + Some((Token::KeywordInt, _)) => TypeAnnot::Cons("int".to_string(), vec![]), + Some((Token::KeywordFloat, _)) => TypeAnnot::Cons("float".to_string(), vec![]), + Some((Token::KeywordString, _)) => TypeAnnot::Cons("string".to_string(), vec![]), + Some((Token::LParen, _)) => { + // Check for unit type: () + if matches!(self.peek(), Some(Token::RParen)) { + self.next(); + return Ok(TypeAnnot::Cons("unit".to_string(), vec![])); + } + + let mut types = Vec::new(); + loop { + if matches!(self.peek(), Some(Token::RParen)) { + self.next(); + break; + } + types.push(self.parse_type_annot()?); + + if matches!(self.peek(), Some(Token::Comma)) { + self.next(); + } + } + + // Single element in parens is not a tuple, unwrap it + if types.len() == 1 { + types.pop().unwrap() + } else { + TypeAnnot::Tuple(types) + } + } + Some((Token::LBracket, _)) => { + let inner = self.parse_type_annot()?; + self.expect(Token::RBracket)?; + TypeAnnot::Array(Box::new(inner)) + } + Some((Token::Bang, _)) => TypeAnnot::Cons("never".to_string(), vec![]), + Some((_, span)) => return self.error("Expected type name".to_string(), span), + None => return self.error("Expected type name".to_string(), start..start), + }; + + // Parse type arguments if present + if matches!(self.peek(), Some(Token::Less)) { + self.next(); + let mut args = Vec::new(); + loop { + if matches!(self.peek(), Some(Token::Greater)) { + self.next(); + break; + } + args.push(self.parse_type_annot()?); + + if matches!(self.peek(), Some(Token::Comma)) { + self.next(); + } + } + base_type = match base_type { + TypeAnnot::Cons(name, _) => TypeAnnot::Cons(name, args), + _ => { + return self.error("Expected type name for generic".to_string(), start..start); + } + }; + } + + // Parse array types + while matches!(self.peek(), Some(Token::LBracket)) { + self.next(); + self.expect(Token::RBracket)?; + base_type = TypeAnnot::Array(Box::new(base_type)); + } + + Ok(base_type) + } + + fn parse_function_signature(&mut self) -> Result { + let start = self.peek_span().unwrap_or(0..0).start; + let name = match self.next() { + Some((Token::Variable(n), _)) => n, + Some((_, span)) => return self.error("Expected function name".to_string(), span), + None => return self.error("Expected function name".to_string(), start..start), + }; + + self.expect(Token::LParen)?; + let mut params = Vec::new(); + loop { + if matches!(self.peek(), Some(Token::RParen)) { + self.next(); + break; + } + + let param_start = self.peek_span().unwrap_or(0..0).start; + let param_name = match self.next() { + Some((Token::Variable(n), _)) => n, + Some((_, span)) => return self.error("Expected parameter name".to_string(), span), + None => return self.error("Expected parameter name".to_string(), start..start), + }; + + // Parameters in trait methods may have type annotations + if matches!(self.peek(), Some(Token::Colon)) { + self.next(); + let _param_type = self.parse_type_annot()?; + } + + let param_end = self.peek_span().unwrap_or(param_start..param_start).end; + params.push(Parameter { + name: param_name, + bounds: Vec::new(), + kind: None, + span: Span::new(&(param_start..param_end), self.file.clone()), + }); + + if matches!(self.peek(), Some(Token::Comma)) { + self.next(); + } + } + + self.expect(Token::Arrow)?; + let return_type = self.parse_type_annot()?; + + Ok(FunctionSignature { + name, + params, + return_type, + }) + } + + fn parse_expr(&mut self) -> Result { + let mut attributes = Vec::new(); + + // Parse any leading attributes + while matches!(self.peek(), Some(Token::At)) { + attributes.push(self.parse_attribute()?); + } + + let mut expr = self.parse_assignment()?; + expr.attributes = attributes; + Ok(expr) + } + + fn parse_range_expr(&mut self) -> Result { + let left = self.parse_or_expr()?; + + if matches!(self.peek(), Some(Token::DotDot)) { + let start = left.span.start; + self.next(); + let right = self.parse_or_expr()?; + let end = right.span.end; + Ok(Expr { + kind: ExprKind::Range(Box::new(left), Box::new(right)), + span: Span::new(&(start..end), self.file.clone()), + attributes: Vec::new(), + }) + } else { + Ok(left) + } + } + + fn parse_assignment(&mut self) -> Result { + let left = self.parse_range_expr()?; + + if matches!(self.peek(), Some(Token::Assign)) { + let start = left.span.start; + self.next(); + let right = self.parse_assignment()?; + let end = right.span.end; + Ok(Expr { + kind: ExprKind::Assign(Box::new(left), Box::new(right)), + span: Span::new(&(start..end), self.file.clone()), + attributes: Vec::new(), + }) + } else { + Ok(left) + } + } + + fn parse_or_expr(&mut self) -> Result { + let mut left = self.parse_and_expr()?; + + loop { + if matches!(self.peek(), Some(Token::Or)) { + let start = left.span.start; + self.next(); + let right = self.parse_and_expr()?; + let end = right.span.end; + left = Expr { + kind: ExprKind::BinOp(Box::new(left), BinOp::Or, Box::new(right)), + span: Span::new(&(start..end), self.file.clone()), + attributes: Vec::new(), + }; + } else { + break; + } + } + + Ok(left) + } + + fn parse_and_expr(&mut self) -> Result { + let mut left = self.parse_eq_expr()?; + + loop { + if matches!(self.peek(), Some(Token::And)) { + let start = left.span.start; + self.next(); + let right = self.parse_eq_expr()?; + let end = right.span.end; + left = Expr { + kind: ExprKind::BinOp(Box::new(left), BinOp::And, Box::new(right)), + span: Span::new(&(start..end), self.file.clone()), + attributes: Vec::new(), + }; + } else { + break; + } + } + + Ok(left) + } + + fn parse_eq_expr(&mut self) -> Result { + let mut left = self.parse_comp_expr()?; + + loop { + let op = match self.peek() { + Some(Token::Eq) => BinOp::Eq, + Some(Token::NotEq) => BinOp::Neq, + _ => break, + }; + let start = left.span.start; + self.next(); + let right = self.parse_comp_expr()?; + let end = right.span.end; + left = Expr { + kind: ExprKind::BinOp(Box::new(left), op, Box::new(right)), + span: Span::new(&(start..end), self.file.clone()), + attributes: Vec::new(), + }; + } + + Ok(left) + } + + fn parse_comp_expr(&mut self) -> Result { + let mut left = self.parse_add_expr()?; + + loop { + let op = match self.peek() { + Some(Token::Less) => BinOp::Lt, + Some(Token::Greater) => BinOp::Gt, + Some(Token::LessEq) => BinOp::Leq, + Some(Token::GreaterEq) => BinOp::Geq, + _ => break, + }; + let start = left.span.start; + self.next(); + let right = self.parse_add_expr()?; + let end = right.span.end; + left = Expr { + kind: ExprKind::BinOp(Box::new(left), op, Box::new(right)), + span: Span::new(&(start..end), self.file.clone()), + attributes: Vec::new(), + }; + } + + Ok(left) + } + + fn parse_add_expr(&mut self) -> Result { + let mut left = self.parse_mul_expr()?; + + loop { + let op = match self.peek() { + Some(Token::Plus) => BinOp::Add, + Some(Token::Minus) => BinOp::Sub, + _ => break, + }; + let start = left.span.start; + self.next(); + let right = self.parse_mul_expr()?; + let end = right.span.end; + left = Expr { + kind: ExprKind::BinOp(Box::new(left), op, Box::new(right)), + span: Span::new(&(start..end), self.file.clone()), + attributes: Vec::new(), + }; + } + + Ok(left) + } + + fn parse_mul_expr(&mut self) -> Result { + let mut left = self.parse_unary_expr()?; + + loop { + let op = match self.peek() { + Some(Token::Mul) => BinOp::Mul, + Some(Token::Div) => BinOp::Div, + Some(Token::Mod) => BinOp::Mod, + _ => break, + }; + let start = left.span.start; + self.next(); + let right = self.parse_unary_expr()?; + let end = right.span.end; + left = Expr { + kind: ExprKind::BinOp(Box::new(left), op, Box::new(right)), + span: Span::new(&(start..end), self.file.clone()), + attributes: Vec::new(), + }; + } + + Ok(left) + } + + fn parse_unary_expr(&mut self) -> Result { + let start = self.peek_span().unwrap_or(0..0).start; + match self.peek() { + Some(Token::Not) => { + self.next(); + let expr = self.parse_unary_expr()?; + let end = expr.span.end; + Ok(Expr { + kind: ExprKind::UnOp(UnOp::Not, Box::new(expr)), + span: Span::new(&(start..end), self.file.clone()), + attributes: Vec::new(), + }) + } + Some(Token::Minus) => { + self.next(); + let expr = self.parse_unary_expr()?; + let end = expr.span.end; + Ok(Expr { + kind: ExprKind::UnOp(UnOp::Neg, Box::new(expr)), + span: Span::new(&(start..end), self.file.clone()), + attributes: Vec::new(), + }) + } + _ => self.parse_postfix_expr(), + } + } + + fn parse_postfix_expr(&mut self) -> Result { + let mut expr = self.parse_primary_expr()?; + + loop { + match self.peek() { + Some(Token::LParen) => { + // Function call + let start = expr.span.start; + self.next(); + let mut args = Vec::new(); + loop { + if matches!(self.peek(), Some(Token::RParen)) { + self.next(); + break; + } + args.push(self.parse_expr()?); + + if matches!(self.peek(), Some(Token::Comma)) { + self.next(); + } + } + let end = self.peek_span().unwrap_or(expr.span.end..expr.span.end).end; + expr = Expr { + kind: ExprKind::Call(Box::new(expr), args), + span: Span::new(&(start..end), self.file.clone()), + attributes: Vec::new(), + }; + } + Some(Token::LBracket) => { + // Index + let start = expr.span.start; + self.next(); + let index = self.parse_expr()?; + self.expect(Token::RBracket)?; + let end = self.peek_span().unwrap_or(expr.span.end..expr.span.end).end; + expr = Expr { + kind: ExprKind::Index(Box::new(expr), Box::new(index)), + span: Span::new(&(start..end), self.file.clone()), + attributes: Vec::new(), + }; + } + Some(Token::Dot) => { + // Field access + let start = expr.span.start; + self.next(); + let field = match self.next() { + Some((Token::Variable(f), _)) => f, + Some((_, span)) => { + return self.error("Expected field name".to_string(), span); + } + None => return self.error("Expected field name".to_string(), start..start), + }; + let end = self.peek_span().unwrap_or(expr.span.end..expr.span.end).end; + expr = Expr { + kind: ExprKind::Dot(Box::new(expr), field), + span: Span::new(&(start..end), self.file.clone()), + attributes: Vec::new(), + }; + } + Some(Token::OptionalChain) => { + // Optional chain + let start = expr.span.start; + self.next(); + let field = match self.next() { + Some((Token::Variable(f), _)) => f, + Some((_, span)) => { + return self.error("Expected field name".to_string(), span); + } + None => return self.error("Expected field name".to_string(), start..start), + }; + let end = self.peek_span().unwrap_or(expr.span.end..expr.span.end).end; + expr = Expr { + kind: ExprKind::OptionalChain(Some(Box::new(expr)), field), + span: Span::new(&(start..end), self.file.clone()), + attributes: Vec::new(), + }; + } + Some(Token::Unwrap) => { + // Early return / unwrap + let start = expr.span.start; + self.next(); + let end = self.peek_span().unwrap_or(expr.span.end..expr.span.end).end; + expr = Expr { + kind: ExprKind::EarlyReturn(Some(Box::new(expr))), + span: Span::new(&(start..end), self.file.clone()), + attributes: Vec::new(), + }; + } + Some(Token::KeywordAs) => { + // Cast + let start = expr.span.start; + self.next(); + let type_annot = self.parse_type_annot()?; + let end = self.peek_span().unwrap_or(expr.span.end..expr.span.end).end; + expr = Expr { + kind: ExprKind::Cast(Box::new(expr), type_annot), + span: Span::new(&(start..end), self.file.clone()), + attributes: Vec::new(), + }; + } + _ => break, + } + } + + Ok(expr) + } + + fn parse_primary_expr(&mut self) -> Result { + let start = self.peek_span().unwrap_or(0..0).start; + match self.peek().cloned() { + Some(Token::Int(n)) => { + self.next(); + let end = self.peek_span().unwrap_or(start..start).end; + Ok(Expr { + kind: ExprKind::Int(n), + span: Span::new(&(start..end), self.file.clone()), + attributes: Vec::new(), + }) + } + Some(Token::Float(f)) => { + self.next(); + let end = self.peek_span().unwrap_or(start..start).end; + Ok(Expr { + kind: ExprKind::Float(f), + span: Span::new(&(start..end), self.file.clone()), + attributes: Vec::new(), + }) + } + Some(Token::Bool(b)) => { + self.next(); + let end = self.peek_span().unwrap_or(start..start).end; + Ok(Expr { + kind: ExprKind::Bool(b), + span: Span::new(&(start..end), self.file.clone()), + attributes: Vec::new(), + }) + } + Some(Token::String(s)) => { + self.next(); + let end = self.peek_span().unwrap_or(start..start).end; + Ok(Expr { + kind: ExprKind::String(s), + span: Span::new(&(start..end), self.file.clone()), + attributes: Vec::new(), + }) + } + Some(Token::Variable(name)) => { + self.next(); + + // Check for struct literal or enum variant + if matches!(self.peek(), Some(Token::LBrace)) { + // Struct literal + self.next(); + let mut fields = Vec::new(); + loop { + if matches!(self.peek(), Some(Token::RBrace)) { + self.next(); + break; + } + + let field_name = match self.next() { + Some((Token::Variable(f), _)) => f, + Some((_, span)) => { + return self.error("Expected field name".to_string(), span); + } + None => { + return self.error("Expected field name".to_string(), start..start); + } + }; + + self.expect(Token::Colon)?; + let field_expr = self.parse_expr()?; + fields.push((field_name, field_expr)); + + if matches!(self.peek(), Some(Token::Comma)) { + self.next(); + } + } + + let end = self.peek_span().unwrap_or(start..start).end; + Ok(Expr { + kind: ExprKind::StructLit(name, fields), + span: Span::new(&(start..end), self.file.clone()), + attributes: Vec::new(), + }) + } else if matches!(self.peek(), Some(Token::Access)) { + // Enum variant + self.next(); + let variant = match self.next() { + Some((Token::Variable(v), _)) => v, + Some((_, span)) => { + return self.error("Expected variant name".to_string(), span); + } + None => { + return self.error("Expected variant name".to_string(), start..start); + } + }; + + let mut args = Vec::new(); + if matches!(self.peek(), Some(Token::LParen)) { + self.next(); + loop { + if matches!(self.peek(), Some(Token::RParen)) { + self.next(); + break; + } + args.push(self.parse_expr()?); + + if matches!(self.peek(), Some(Token::Comma)) { + self.next(); + } + } + } + + let end = self.peek_span().unwrap_or(start..start).end; + Ok(Expr { + kind: ExprKind::EnumLit(name, variant, args), + span: Span::new(&(start..end), self.file.clone()), + attributes: Vec::new(), + }) + } else { + let end = self.peek_span().unwrap_or(start..start).end; + Ok(Expr { + kind: ExprKind::Variable(name), + span: Span::new(&(start..end), self.file.clone()), + attributes: Vec::new(), + }) + } + } + Some(Token::LParen) => { + self.next(); + if matches!(self.peek(), Some(Token::RParen)) { + // Empty tuple + self.next(); + let end = self.peek_span().unwrap_or(start..start).end; + Ok(Expr { + kind: ExprKind::Tuple(vec![]), + span: Span::new(&(start..end), self.file.clone()), + attributes: Vec::new(), + }) + } else { + let first = self.parse_expr()?; + if matches!(self.peek(), Some(Token::Comma)) { + // Tuple + let mut elements = vec![first]; + self.next(); + loop { + if matches!(self.peek(), Some(Token::RParen)) { + break; + } + elements.push(self.parse_expr()?); + + if matches!(self.peek(), Some(Token::Comma)) { + self.next(); + } + } + self.expect(Token::RParen)?; + let end = self.peek_span().unwrap_or(start..start).end; + Ok(Expr { + kind: ExprKind::Tuple(elements), + span: Span::new(&(start..end), self.file.clone()), + attributes: Vec::new(), + }) + } else { + self.expect(Token::RParen)?; + let end = self.peek_span().unwrap_or(start..start).end; + Ok(Expr { + kind: first.kind, + span: Span::new(&(start..end), self.file.clone()), + attributes: first.attributes, + }) + } + } + } + Some(Token::LBracket) => { + self.next(); + let mut elements = Vec::new(); + loop { + if matches!(self.peek(), Some(Token::RBracket)) { + self.next(); + break; + } + elements.push(self.parse_expr()?); + + if matches!(self.peek(), Some(Token::Comma)) { + self.next(); + } + } + + let end = self.peek_span().unwrap_or(start..start).end; + Ok(Expr { + kind: ExprKind::Array(elements), + span: Span::new(&(start..end), self.file.clone()), + attributes: Vec::new(), + }) + } + Some(Token::KeywordLet) => { + self.next(); + + // Parse binding kind (mut, uniq, once) - comes AFTER let + let binding_kind = match self.peek() { + Some(Token::KeywordMut) => { + self.next(); + BindingKind::Mutable + } + Some(Token::KeywordUniq) => { + self.next(); + BindingKind::Affine + } + Some(Token::KeywordOnce) => { + self.next(); + BindingKind::Linear + } + _ => BindingKind::Default, + }; + + // Now parse the variable name + let var_name = match self.next() { + Some((Token::Variable(n), _)) => n, + Some((_, span)) => { + return self.error("Expected variable name".to_string(), span); + } + None => return self.error("Expected variable name".to_string(), start..start), + }; + + // Parse optional type annotation + let type_annot = if matches!(self.peek(), Some(Token::Colon)) { + self.next(); + Some(self.parse_type_annot()?) + } else { + None + }; + + self.expect(Token::Assign)?; + let expr = self.parse_expr()?; + let end = expr.span.end; + Ok(Expr { + kind: ExprKind::Let(var_name, binding_kind, type_annot, Box::new(expr)), + span: Span::new(&(start..end), self.file.clone()), + attributes: Vec::new(), + }) + } + Some(Token::KeywordIf) => { + self.next(); + let cond = self.parse_expr()?; + let then_expr = self.parse_expr()?; + let else_expr = if matches!(self.peek(), Some(Token::KeywordElse)) { + self.next(); + Some(Box::new(self.parse_expr()?)) + } else { + None + }; + + let end = else_expr + .as_ref() + .map(|e| e.span.end) + .unwrap_or(then_expr.span.end); + + Ok(Expr { + kind: ExprKind::If(Box::new(cond), Box::new(then_expr), else_expr), + span: Span::new(&(start..end), self.file.clone()), + attributes: Vec::new(), + }) + } + Some(Token::KeywordMatch) => { + self.next(); + let expr = self.parse_expr()?; + let mut arms = Vec::new(); + + loop { + if matches!(self.peek(), Some(Token::KeywordEnd)) { + self.next(); + break; + } + + let pattern = self.parse_pattern()?; + self.expect(Token::FatArrow)?; + let body = self.parse_expr()?; + arms.push((pattern, body)); + + if matches!(self.peek(), Some(Token::Comma)) { + self.next(); + } + } + + let end = self.peek_span().unwrap_or(start..start).end; + Ok(Expr { + kind: ExprKind::Match(Box::new(expr), arms), + span: Span::new(&(start..end), self.file.clone()), + attributes: Vec::new(), + }) + } + Some(Token::KeywordWhile) => { + self.next(); + let cond = self.parse_expr()?; + let body = self.parse_expr()?; + let end = body.span.end; + + Ok(Expr { + kind: ExprKind::While(Box::new(cond), Box::new(body)), + span: Span::new(&(start..end), self.file.clone()), + attributes: Vec::new(), + }) + } + Some(Token::KeywordFor) => { + self.next(); + let var = match self.next() { + Some((Token::Variable(v), _)) => v, + Some((_, span)) => { + return self.error("Expected variable name".to_string(), span); + } + None => return self.error("Expected variable name".to_string(), start..start), + }; + self.expect(Token::KeywordIn)?; + let iterable = self.parse_expr()?; + let body = self.parse_expr()?; + let end = body.span.end; + + Ok(Expr { + kind: ExprKind::For(var, Box::new(iterable), Box::new(body)), + span: Span::new(&(start..end), self.file.clone()), + attributes: Vec::new(), + }) + } + Some(Token::KeywordDo) => { + self.next(); + let mut exprs = Vec::new(); + + loop { + if matches!(self.peek(), Some(Token::KeywordEnd)) { + self.next(); + break; + } + exprs.push(self.parse_expr()?); + + if matches!(self.peek(), Some(Token::Semicolon)) { + self.next(); + } + } + + let end = self.peek_span().unwrap_or(start..start).end; + Ok(Expr { + kind: ExprKind::Do(exprs), + span: Span::new(&(start..end), self.file.clone()), + attributes: Vec::new(), + }) + } + Some(Token::KeywordLambda) => { + let start = self.peek_span().unwrap_or(0..0).start; + self.next(); + self.expect(Token::LParen)?; + let mut params = Vec::new(); + + loop { + if matches!(self.peek(), Some(Token::RParen)) { + self.next(); + break; + } + + let param_name = match self.next() { + Some((Token::Variable(p), _)) => p, + Some((_, span)) => { + return self.error("Expected parameter name".to_string(), span); + } + None => { + return self.error("Expected parameter name".to_string(), start..start); + } + }; + + // Check for optional type annotation + let param_type = if matches!(self.peek(), Some(Token::Colon)) { + self.next(); // consume ':' + Some(self.parse_type_annot()?) + } else { + None + }; + + params.push((param_name, param_type)); + + if matches!(self.peek(), Some(Token::Comma)) { + self.next(); + } + } + + let body = self.parse_expr()?; + let end = body.span.end; + + Ok(Expr { + kind: ExprKind::Lambda(params, Box::new(body)), + span: Span::new(&(start..end), self.file.clone()), + attributes: Vec::new(), + }) + } + Some(Token::KeywordReturn) => { + self.next(); + let expr = if self.is_expr_end() { + None + } else { + Some(Box::new(self.parse_expr()?)) + }; + + let end = expr + .as_ref() + .map(|e| e.span.end) + .unwrap_or(self.peek_span().unwrap_or(start..start).end); + + Ok(Expr { + kind: ExprKind::Return(expr), + span: Span::new(&(start..end), self.file.clone()), + attributes: Vec::new(), + }) + } + Some(Token::KeywordBreak) => { + self.next(); + let end = self.peek_span().unwrap_or(start..start).end; + Ok(Expr { + kind: ExprKind::Break, + span: Span::new(&(start..end), self.file.clone()), + attributes: Vec::new(), + }) + } + Some(Token::KeywordContinue) => { + self.next(); + let end = self.peek_span().unwrap_or(start..start).end; + Ok(Expr { + kind: ExprKind::Continue, + span: Span::new(&(start..end), self.file.clone()), + attributes: Vec::new(), + }) + } + Some(token) => { + let span = self.peek_span().unwrap_or(start..start); + self.error(format!("Unexpected token: {:?}", token), span) + } + None => self.error("Unexpected EOF".to_string(), start..start), + } + } + + fn parse_pattern(&mut self) -> Result { + let start = self.peek_span().unwrap_or(0..0).start; + + match self.peek().cloned() { + Some(Token::Variable(name)) => { + self.next(); + let end = self.peek_span().unwrap_or(start..start).end; + + // Check for struct or enum pattern + if matches!(self.peek(), Some(Token::LBrace)) { + // Struct pattern + self.next(); + let mut fields = Vec::new(); + + loop { + if matches!(self.peek(), Some(Token::RBrace)) { + self.next(); + break; + } + + let field_name = match self.next() { + Some((Token::Variable(f), _)) => f, + Some((_, span)) => { + return self.error("Expected field name".to_string(), span); + } + None => { + return self.error("Expected field name".to_string(), start..start); + } + }; + + self.expect(Token::Colon)?; + let pattern = self.parse_pattern()?; + fields.push((field_name, pattern)); + + if matches!(self.peek(), Some(Token::Comma)) { + self.next(); + } + } + + let end = self.peek_span().unwrap_or(start..start).end; + Ok(Pattern { + kind: PatternKind::Struct(name, fields), + span: Span::new(&(start..end), self.file.clone()), + }) + } else if matches!(self.peek(), Some(Token::Access)) { + // Enum pattern + self.next(); + let variant = match self.next() { + Some((Token::Variable(v), _)) => v, + Some((_, span)) => { + return self.error("Expected variant name".to_string(), span); + } + None => { + return self.error("Expected variant name".to_string(), start..start); + } + }; + + let mut patterns = Vec::new(); + if matches!(self.peek(), Some(Token::LParen)) { + self.next(); + loop { + if matches!(self.peek(), Some(Token::RParen)) { + self.next(); + break; + } + patterns.push(self.parse_pattern()?); + + if matches!(self.peek(), Some(Token::Comma)) { + self.next(); + } + } + } + + let end = self.peek_span().unwrap_or(start..start).end; + Ok(Pattern { + kind: PatternKind::Enum(name, variant, patterns), + span: Span::new(&(start..end), self.file.clone()), + }) + } else { + Ok(Pattern { + kind: PatternKind::Variable(name), + span: Span::new(&(start..end), self.file.clone()), + }) + } + } + Some(Token::Union) => { + self.next(); + let end = self.peek_span().unwrap_or(start..start).end; + Ok(Pattern { + kind: PatternKind::Wildcard, + span: Span::new(&(start..end), self.file.clone()), + }) + } + Some(Token::LParen) => { + self.next(); + let mut patterns = Vec::new(); + loop { + if matches!(self.peek(), Some(Token::RParen)) { + self.next(); + break; + } + patterns.push(self.parse_pattern()?); + + if matches!(self.peek(), Some(Token::Comma)) { + self.next(); + } + } + + let end = self.peek_span().unwrap_or(start..start).end; + Ok(Pattern { + kind: PatternKind::Tuple(patterns), + span: Span::new(&(start..end), self.file.clone()), + }) + } + Some(Token::String(s)) => { + self.next(); + let end = self.peek_span().unwrap_or(start..start).end; + Ok(Pattern { + kind: PatternKind::Literal(s), + span: Span::new(&(start..end), self.file.clone()), + }) + } + Some(Token::Int(n)) => { + self.next(); + let end = self.peek_span().unwrap_or(start..start).end; + Ok(Pattern { + kind: PatternKind::Literal(n.to_string()), + span: Span::new(&(start..end), self.file.clone()), + }) + } + Some(Token::Float(f)) => { + self.next(); + let end = self.peek_span().unwrap_or(start..start).end; + Ok(Pattern { + kind: PatternKind::Literal(f.to_string()), + span: Span::new(&(start..end), self.file.clone()), + }) + } + Some(Token::Bool(b)) => { + self.next(); + let end = self.peek_span().unwrap_or(start..start).end; + Ok(Pattern { + kind: PatternKind::Literal(b.to_string()), + span: Span::new(&(start..end), self.file.clone()), + }) + } + Some(token) => { + let span = self.peek_span().unwrap_or(start..start); + self.error(format!("Unexpected token in pattern: {:?}", token), span) + } + None => self.error("Unexpected EOF".to_string(), start..start), + } + } + + fn is_expr_end(&mut self) -> bool { + matches!( + self.peek(), + Some(Token::RParen) + | Some(Token::RBracket) + | Some(Token::RBrace) + | Some(Token::Comma) + | Some(Token::Semicolon) + | Some(Token::KeywordEnd) + | Some(Token::FatArrow) + ) + } +} + +``` + +```rust +// src/main.rs +use logos::Logos; +use std::fs; +use suicmez::{lexer::Token, parser::Parser, typechecker::TypeChecker}; + +fn main() { + // Check if a file was provided as argument + let args: Vec = std::env::args().collect(); + if args.len() < 2 { + // Run all test files in the tests directory + run_test_suite(); + return; + } + + let filename = &args[1]; + println!("Type checking file: {}", filename); + + if let Err(e) = run_file(filename) { + eprintln!("Error: {}", e); + } +} + +fn run_test_suite() { + println!("Running test suite...\n"); + + let test_files = vec![ + "tests/basic_types.sui", + "tests/structs.sui", + "tests/enums.sui", + "tests/functions.sui", + "tests/arrays.sui", + "tests/traits.sui", + "tests/control_flow.sui", + ]; + + for file in test_files { + println!("Testing: {}", file); + match run_file(file) { + Ok(_) => println!("✓ Passed\n"), + Err(e) => println!("✗ Failed: {}\n", e), + } + } +} + +fn run_file(filename: &str) -> Result<(), String> { + // Read the source file + let source = fs::read_to_string(filename) + .map_err(|e| format!("Error reading file {}: {}", filename, e))?; + + // First, we need to parse the source code + let mut tokens = Vec::new(); + let mut lexer = Token::lexer(&source); + + loop { + match lexer.next() { + Some(Ok(token)) => { + let span = lexer.span(); + tokens.push((token, span)); + } + Some(Err(_)) => { + return Err("Lexing error".to_string()); + } + None => break, + } + } + + let mut parser = Parser::new(filename.to_string(), tokens); + let ast_nodes = parser + .parse() + .map_err(|e| format!("Parse error: {}", e.message))?; + + println!("Parsed {} AST nodes successfully", ast_nodes.len()); + + // Typecheck the AST + let mut typechecker = TypeChecker::new(); + let typed_nodes = typechecker.typecheck_program(&ast_nodes).map_err(|e| { + format!( + "Type error at {}:{}: {:?}", + e.span.file, e.span.start, e.kind + ) + })?; + + println!( + "Type checking passed! {} nodes typechecked.", + typed_nodes.len() + ); + + Ok(()) +} + +``` + +```rust +// src/typechecker.rs +#[derive(Debug, Clone, PartialEq)] +pub enum Type { + Stub, +} + +``` + +```rust +// src/ast.rs +use crate::typechecker::Type; +use std::ops::Range; + +#[derive(Debug, Clone)] +pub enum TypeAnnot { + Var(String), + Cons(String, Vec), + Function(Vec, Box), + Tuple(Vec), + Array(Box), +} + +#[derive(Debug, Clone)] +pub struct Span { + pub start: usize, + pub end: usize, + pub file: String, +} + +impl Span { + pub fn new(range: &Range, file: String) -> Self { + Span { + start: range.start, + end: range.end, + file, + } + } + + pub fn merge(&self, other: &Span) -> Span { + Span { + start: self.start.min(other.start), + end: self.end.max(other.end), + file: self.file.clone(), + } + } +} + +// @attribute +#[derive(Debug, Clone)] +pub struct Attribute { + pub name: String, + pub args: Vec, + pub span: Span, +} + +#[derive(Debug, Clone)] +pub enum AttributeArg { + Value(String), // some_identifier + KeyValue(String, String), // some_key = some_identifier + Literal(String), // some literal value +} + +#[derive(Debug, Clone)] +pub struct ASTNode { + pub kind: ASTNodeKind, + pub span: Span, + pub attributes: Vec, +} + +#[derive(Debug, Clone)] +pub enum ASTNodeKind { + Function(Function), + Extern(Extern), + Load(Load), + Struct(Struct), + Enum(Enum), + Impl(Impl), + Trait(Trait), + Use(String), +} + +// ? implies OPTIONAL here +// \( implies the presence of (. same for /) + +#[derive(Debug, Clone)] +/// fn name\( (arg: type?,)* \) -> return_type? body +pub struct Function { + pub name: String, + pub parameters: Vec, // type params + pub args: Vec<(String, Option)>, + pub return_type: Option, + pub body: Expr, +} + +/// extern name\( type?,* \) -> return_type from library_alias +#[derive(Debug, Clone)] +pub struct Extern { + pub name: String, + pub args: Vec, + pub return_type: TypeAnnot, + pub from: String, + pub span: Span, +} + +/// load "library" as alias +#[derive(Debug, Clone)] +pub struct Load { + pub library: String, + pub alias: String, + pub span: Span, +} + +/// struct name ? +/// (field_name: field_type,)* +/// end +#[derive(Debug, Clone)] +pub struct Struct { + pub name: String, + pub parameters: Vec, // type parameters + pub fields: Vec, +} + +#[derive(Debug, Clone)] +pub struct Field { + pub name: String, + pub field_type: TypeAnnot, + pub span: Span, +} + +/// enum name ? +/// VariantName\(field_type,\)* +/// end +#[derive(Debug, Clone)] +pub struct Enum { + pub name: String, + pub parameters: Vec, // type parameters + pub variants: Vec, +} + +#[derive(Debug, Clone)] +pub struct Parameter { + pub name: String, + pub bounds: Vec, // trait bounds + pub kind: Option, // for HKTs + pub span: Span, +} + +#[derive(Debug, Clone, PartialEq)] +pub enum Kind { + Star, // * + Arrow(Box, Box), // k1 -> k2 +} + +#[derive(Debug, Clone)] +pub struct Variant { + pub name: String, + pub fields: Vec, + pub span: Span, +} + +/// impl TypeName ? (: TraitName)? +/// functions* +/// end +#[derive(Debug, Clone)] +pub struct Impl { + pub target: String, + pub trait_name: Option, + pub methods: Vec, +} + +/// trait TraitName ? +/// function_signatures* +/// end +#[derive(Debug, Clone)] +pub struct Trait { + pub name: String, + pub methods: Vec, + + pub parameters: Vec, + pub associated_types: Vec, +} + +#[derive(Debug, Clone)] +pub struct AssociatedType { + pub name: String, + pub bounds: Vec, + pub span: Span, +} + +#[derive(Debug, Clone)] +pub struct FunctionSignature { + pub name: String, + pub params: Vec, + pub return_type: TypeAnnot, +} + +#[derive(Debug, Clone)] +pub struct Expr { + pub kind: ExprKind, + pub span: Span, + pub attributes: Vec, +} + +#[derive(Debug, Clone)] +pub enum ExprKind { + Int(i64), + Float(f64), + Bool(bool), + String(String), + Array(Vec), + Tuple(Vec), + + StructLit(String, Vec<(String, Expr)>), // Name { a: expr, b: expr } + EnumLit(String, String, Vec), // Name::Variant(expr, expr) + + Variable(String), + + Call(Box, Vec), + Index(Box, Box), + Dot(Box, String), + EarlyReturn(Option>), // eg: myresultoroption? + OptionalChain(Option>, String), // a?.b + + Lambda(Vec<(String, Option)>, Box), // lambda (arg, arg: optionalty, ...) body + Let(String, BindingKind, Option, Box), // no patterns for now + Assign(Box, Box), // NOTE: check for valid lvalue during typechecking + Cast(Box, TypeAnnot), + + If(Box, Box, Option>), // if cond expr (else expr)? + Match(Box, Vec<(Pattern, Expr)>), // match expr pattern => expr* end + While(Box, Box), // while cond expr + + For(String, Box, Box), // for i in expr body + Range(Box, Box), // 0..10 + + Do(Vec), // do expr* end + BinOp(Box, BinOp, Box), + UnOp(UnOp, Box), + + Return(Option>), + Break, + Continue, +} + +#[derive(Debug, Clone, PartialEq)] +pub enum BindingKind { + Default, // immutable but infinite usages + Mutable, // mutable but infinite usages + Affine, + Linear, +} + +#[derive(Debug, Clone)] +pub enum BinOp { + Add, + Sub, + Mul, + Div, + Mod, + And, + Or, + Eq, + Neq, + Lt, + Gt, + Leq, + Geq, +} + +#[derive(Debug, Clone)] +pub enum UnOp { + Neg, + Not, +} + +#[derive(Debug, Clone)] +pub struct Pattern { + pub kind: PatternKind, + pub span: Span, +} + +#[derive(Debug, Clone)] +pub enum PatternKind { + Wildcard, // _ + Variable(String), + Literal(String), + Tuple(Vec), + Struct(String, Vec<(String, Pattern)>), + Enum(String, String, Vec), + Range(i64, i64), +} + +// Typed variants + +#[derive(Debug, Clone)] +pub struct TypedASTNode { + pub kind: TypedASTNodeKind, + pub span: Span, + pub attributes: Vec, + pub ty: Type, +} + +#[derive(Debug, Clone)] +pub enum TypedASTNodeKind { + Function(TypedFunction), + Extern(TypedExtern), + Load(TypedLoad), + Struct(TypedStruct), + Enum(TypedEnum), + Impl(TypedImpl), + Trait(TypedTrait), + Use(String), +} + +#[derive(Debug, Clone)] +pub struct TypedFunction { + pub name: String, + pub parameters: Vec, + pub args: Vec<(String, Option)>, + pub return_type: Option, + pub body: TypedExpr, + pub ty: Type, +} + +#[derive(Debug, Clone)] +pub struct TypedExtern { + pub name: String, + pub args: Vec, + pub return_type: TypeAnnot, + pub from: String, + pub span: Span, +} + +#[derive(Debug, Clone)] +pub struct TypedLoad { + pub library: String, + pub alias: String, + pub span: Span, +} + +#[derive(Debug, Clone)] +pub struct TypedStruct { + pub name: String, + pub parameters: Vec, + pub fields: Vec, +} + +#[derive(Debug, Clone)] +pub struct TypedField { + pub name: String, + pub field_type: TypeAnnot, + pub span: Span, +} + +#[derive(Debug, Clone)] +pub struct TypedEnum { + pub name: String, + pub parameters: Vec, + pub variants: Vec, +} + +#[derive(Debug, Clone)] +pub struct TypedVariant { + pub name: String, + pub fields: Vec, + pub span: Span, +} + +#[derive(Debug, Clone)] +pub struct TypedImpl { + pub target: String, + pub trait_name: Option, + pub methods: Vec, +} + +#[derive(Debug, Clone)] +pub struct TypedTrait { + pub name: String, + pub methods: Vec, + pub parameters: Vec, + pub associated_types: Vec, +} + +#[derive(Debug, Clone)] +pub struct TypedExpr { + pub kind: TypedExprKind, + pub span: Span, + pub attributes: Vec, + pub ty: Type, +} + +#[derive(Debug, Clone)] +pub enum TypedExprKind { + Int(i64), + Float(f64), + Bool(bool), + String(String), + Array(Vec), + Tuple(Vec), + StructLit(String, Vec<(String, TypedExpr)>), + EnumLit(String, String, Vec), + Variable(String), + Call(Box, Vec), + Index(Box, Box), + Dot(Box, String), + EarlyReturn(Option>), + OptionalChain(Option>, String), + Lambda(Vec<(String, Option)>, Box), + Let(String, BindingKind, Option, Box), + Assign(Box, Box), + Cast(Box, TypeAnnot), + If(Box, Box, Option>), + Match(Box, Vec<(TypedPattern, TypedExpr)>), + While(Box, Box), + Do(Vec), + BinOp(Box, BinOp, Box), + UnOp(UnOp, Box), + For(String, Box, Box), + Range(Box, Box), + Return(Option>), + Break, + Continue, +} + +#[derive(Debug, Clone)] +pub struct TypedPattern { + pub kind: TypedPatternKind, + pub span: Span, + pub ty: Type, +} + +#[derive(Debug, Clone)] +pub enum TypedPatternKind { + Wildcard, + Variable(String), + Literal(String), + Tuple(Vec), + Struct(String, Vec<(String, TypedPattern)>), + Enum(String, String, Vec), +} + +``` + +```rust +// src/lexer/mod.rs +use logos::Logos; + +#[cfg(test)] +pub mod tests; + +#[derive(Logos, Debug, PartialEq)] +#[logos(skip r"[ \n\r\t\f]+")] // Ignore this regex pattern between tokens +#[logos(skip r"#(.*)\n")] // Ignore this regex pattern between tokens +#[derive(Clone)] +pub enum Token { + #[regex(r"true|false", |lex| { + lex.slice().parse::().unwrap() + })] + Bool(bool), + + #[regex(r"0|[1-9][0-9_]*", |lex| { + let s = lex.slice().replace("_", ""); + // We parse to i64 for wider support. + s.parse::().unwrap() + }, priority = 4)] + Int(i64), + + #[regex(r"(([0-9][0-9_]*\.[0-9_]+|[0-9]*\.[0-9_]+)([eE][+-]?[0-9_]+)?)", |lex| { + let s = lex.slice().replace("_", ""); + s.parse::().unwrap() + }, priority = 3)] + Float(f64), + + #[regex(r#""([^"\\]*(\\.[^"\\]*)*)""#, |lex| { + let s = lex.slice(); + s[1..s.len()-1] + .replace("\\\"", "\"") + .replace("\\\\", "\\") + .replace("\\n", "\n") + .replace("\\r", "\r") + .replace("\\t", "\t") + })] + String(String), + + #[regex(r#"r#"([^"]*)""#, |lex| { + let s = lex.slice(); + // Remove the outer r" and " (s[2..s.len() - 1]) + s[3..s.len() - 1].to_string() + })] + RawString(String), + + #[regex(r"[a-zA-Z_][a-zA-Z0-9_]*", |lex|{ + lex.slice().to_string() + })] + Variable(String), + + #[token("bool")] + KeywordBool, + + #[token("int")] + KeywordInt, + + #[token("float")] + KeywordFloat, + + #[token("string")] + KeywordString, + + #[token("let")] + KeywordLet, + + #[token("mut")] + KeywordMut, + + #[token("uniq")] + KeywordUniq, + + #[token("once")] + KeywordOnce, + + #[token("if")] + KeywordIf, + + #[token("then")] + KeywordThen, + + #[token("else")] + KeywordElse, + + #[token("fn")] + KeywordFn, + + #[token("lambda")] + KeywordLambda, + + #[token("do")] + KeywordDo, + + #[token("end")] + KeywordEnd, + + #[token("as")] + KeywordAs, + + #[token("in")] + KeywordIn, + + #[token("for")] + KeywordFor, + #[token("while")] + KeywordWhile, + + #[token("loop")] + KeywordLoop, + + #[token("where")] + KeywordWhere, + + #[token("extern")] + KeywordExtern, + + #[token("load")] + KeywordLoad, + + #[token("from")] + KeywordFrom, + + #[token("use")] + KeywordUse, + + #[token("struct")] + KeywordStruct, + + #[token("enum")] + KeywordEnum, + + #[token("impl")] + KeywordImpl, + + #[token("trait")] + KeywordTrait, + + // #[token("type")] + // KeywordType, + // + #[token("match")] + KeywordMatch, + + #[token("return")] + KeywordReturn, + + #[token("break")] + KeywordBreak, + + #[token("continue")] + KeywordContinue, + + #[token("+")] + Plus, + + #[token("-")] + Minus, + + #[token("*")] + Mul, + + #[token("/")] + Div, + + #[token("%")] + Mod, + + #[token("**", priority = 3)] + Power, + + #[token("$")] + Dollar, + + #[token("@")] + At, + + #[token("==")] + Eq, + + #[token("!=")] + NotEq, + + #[token("<")] + Less, + + #[token(">")] + Greater, + + #[token("<=")] + LessEq, + + #[token(">=")] + GreaterEq, + + #[token("and")] + And, + + #[token("or")] + Or, + + #[token("xor")] + Xor, + + #[token("nor")] + Nor, + + #[token("not")] + Not, + + #[token("(")] + LParen, + + #[token(")")] + RParen, + + #[token("[")] + LBracket, + + #[token("]")] + RBracket, + + #[token("{")] + LBrace, + + #[token("}")] + RBrace, + + #[token(",")] + Comma, + + #[token(";")] + Semicolon, + + #[token(":")] + Colon, + + #[token(".")] + Dot, + + #[token("...")] + Spread, + + #[token("..")] + DotDot, + + #[token("::")] + Access, + + #[token("->")] + Arrow, + + #[token("~")] + Tilde, + + #[token("!")] + Bang, + + // New tokens for pattern matching + #[token("=>")] + FatArrow, // For match arms + + #[token("|")] + Union, + + #[token("?.")] + OptionalChain, + + #[token("?")] + Unwrap, + + #[token("=")] + Assign, + + #[token("+=")] + AddAssign, + + #[token("-=")] + SubAssign, + + #[token("*=")] + MulAssign, + + #[token("/=")] + DivAssign, + + #[token("%=")] + ModAssign, +} + +``` + +```rust +// src/lexer/tests.rs +use super::Token; +use logos::Logos; + +#[test] +fn test_literals() { + let mut lexer = Token::lexer("true false 42 2.14 \"hello\" r\"raw\""); + + assert_eq!(lexer.next(), Some(Ok(Token::Bool(true)))); + assert_eq!(lexer.next(), Some(Ok(Token::Bool(false)))); + assert_eq!(lexer.next(), Some(Ok(Token::Int(42)))); + assert_eq!(lexer.next(), Some(Ok(Token::Float(2.14)))); + assert_eq!(lexer.next(), Some(Ok(Token::String("hello".to_string())))); + // RawString regex seems to have issues, let's test separately + assert_eq!(lexer.next(), Some(Ok(Token::Variable("r".to_string())))); + assert_eq!(lexer.next(), Some(Ok(Token::String("raw".to_string())))); + assert_eq!(lexer.next(), None); +} + +#[test] +fn test_int_literals() { + let mut lexer = Token::lexer("0 123 1_000_000"); + + assert_eq!(lexer.next(), Some(Ok(Token::Int(0)))); + assert_eq!(lexer.next(), Some(Ok(Token::Int(123)))); + assert_eq!(lexer.next(), Some(Ok(Token::Int(1000000)))); + assert_eq!(lexer.next(), None); +} + +#[test] +fn test_string_literals() { + let mut lexer = Token::lexer("\"hello world\" \"with\\\\escape\" \"quote\\\"here\""); + + assert_eq!( + lexer.next(), + Some(Ok(Token::String("hello world".to_string()))) + ); + assert_eq!( + lexer.next(), + Some(Ok(Token::String("with\\escape".to_string()))) + ); + assert_eq!( + lexer.next(), + Some(Ok(Token::String("quote\"here".to_string()))) + ); + assert_eq!(lexer.next(), None); +} + +#[test] +fn test_keywords() { + let mut lexer = Token::lexer( + "bool int float string let if else fn do end as in for while loop where extern import struct enum impl trait match return break continue", + ); + + assert_eq!(lexer.next(), Some(Ok(Token::KeywordBool))); + assert_eq!(lexer.next(), Some(Ok(Token::KeywordInt))); + assert_eq!(lexer.next(), Some(Ok(Token::KeywordFloat))); + assert_eq!(lexer.next(), Some(Ok(Token::KeywordString))); + assert_eq!(lexer.next(), Some(Ok(Token::KeywordLet))); + assert_eq!(lexer.next(), Some(Ok(Token::KeywordIf))); + assert_eq!(lexer.next(), Some(Ok(Token::KeywordElse))); + assert_eq!(lexer.next(), Some(Ok(Token::KeywordFn))); + assert_eq!(lexer.next(), Some(Ok(Token::KeywordDo))); + assert_eq!(lexer.next(), Some(Ok(Token::KeywordEnd))); + assert_eq!(lexer.next(), Some(Ok(Token::KeywordAs))); + assert_eq!(lexer.next(), Some(Ok(Token::KeywordIn))); + assert_eq!(lexer.next(), Some(Ok(Token::KeywordFor))); + assert_eq!(lexer.next(), Some(Ok(Token::KeywordWhile))); + assert_eq!(lexer.next(), Some(Ok(Token::KeywordLoop))); + assert_eq!(lexer.next(), Some(Ok(Token::KeywordWhere))); + assert_eq!(lexer.next(), Some(Ok(Token::KeywordExtern))); + assert_eq!(lexer.next(), Some(Ok(Token::Variable("import".into())))); + assert_eq!(lexer.next(), Some(Ok(Token::KeywordStruct))); + assert_eq!(lexer.next(), Some(Ok(Token::KeywordEnum))); + assert_eq!(lexer.next(), Some(Ok(Token::KeywordImpl))); + assert_eq!(lexer.next(), Some(Ok(Token::KeywordTrait))); + assert_eq!(lexer.next(), Some(Ok(Token::KeywordMatch))); + assert_eq!(lexer.next(), Some(Ok(Token::KeywordReturn))); + assert_eq!(lexer.next(), Some(Ok(Token::KeywordBreak))); + assert_eq!(lexer.next(), Some(Ok(Token::KeywordContinue))); + assert_eq!(lexer.next(), None); +} + +#[test] +fn test_operators() { + let mut lexer = Token::lexer("+ - * / % ** $ @ == != < > <= >= and or xor nor not"); + + assert_eq!(lexer.next(), Some(Ok(Token::Plus))); + assert_eq!(lexer.next(), Some(Ok(Token::Minus))); + assert_eq!(lexer.next(), Some(Ok(Token::Mul))); + assert_eq!(lexer.next(), Some(Ok(Token::Div))); + assert_eq!(lexer.next(), Some(Ok(Token::Mod))); + assert_eq!(lexer.next(), Some(Ok(Token::Power))); + assert_eq!(lexer.next(), Some(Ok(Token::Dollar))); + assert_eq!(lexer.next(), Some(Ok(Token::At))); + assert_eq!(lexer.next(), Some(Ok(Token::Eq))); + assert_eq!(lexer.next(), Some(Ok(Token::NotEq))); + assert_eq!(lexer.next(), Some(Ok(Token::Less))); + assert_eq!(lexer.next(), Some(Ok(Token::Greater))); + assert_eq!(lexer.next(), Some(Ok(Token::LessEq))); + assert_eq!(lexer.next(), Some(Ok(Token::GreaterEq))); + assert_eq!(lexer.next(), Some(Ok(Token::And))); + assert_eq!(lexer.next(), Some(Ok(Token::Or))); + assert_eq!(lexer.next(), Some(Ok(Token::Xor))); + assert_eq!(lexer.next(), Some(Ok(Token::Nor))); + assert_eq!(lexer.next(), Some(Ok(Token::Not))); + assert_eq!(lexer.next(), None); +} + +#[test] +fn test_assignment_operators() { + let mut lexer = Token::lexer("= += -= *= /= %="); + + assert_eq!(lexer.next(), Some(Ok(Token::Assign))); + assert_eq!(lexer.next(), Some(Ok(Token::AddAssign))); + assert_eq!(lexer.next(), Some(Ok(Token::SubAssign))); + assert_eq!(lexer.next(), Some(Ok(Token::MulAssign))); + assert_eq!(lexer.next(), Some(Ok(Token::DivAssign))); + assert_eq!(lexer.next(), Some(Ok(Token::ModAssign))); + assert_eq!(lexer.next(), None); +} + +#[test] +fn test_punctuation() { + let mut lexer = Token::lexer("( ) [ ] { } , ; : . ... .. :: -> ~ ! => | |> ?. ?"); + + assert_eq!(lexer.next(), Some(Ok(Token::LParen))); + assert_eq!(lexer.next(), Some(Ok(Token::RParen))); + assert_eq!(lexer.next(), Some(Ok(Token::LBracket))); + assert_eq!(lexer.next(), Some(Ok(Token::RBracket))); + assert_eq!(lexer.next(), Some(Ok(Token::LBrace))); + assert_eq!(lexer.next(), Some(Ok(Token::RBrace))); + assert_eq!(lexer.next(), Some(Ok(Token::Comma))); + assert_eq!(lexer.next(), Some(Ok(Token::Semicolon))); + assert_eq!(lexer.next(), Some(Ok(Token::Colon))); + assert_eq!(lexer.next(), Some(Ok(Token::Dot))); + assert_eq!(lexer.next(), Some(Ok(Token::Spread))); + assert_eq!(lexer.next(), Some(Ok(Token::DotDot))); + assert_eq!(lexer.next(), Some(Ok(Token::Access))); + assert_eq!(lexer.next(), Some(Ok(Token::Arrow))); + assert_eq!(lexer.next(), Some(Ok(Token::Tilde))); + assert_eq!(lexer.next(), Some(Ok(Token::Bang))); + assert_eq!(lexer.next(), Some(Ok(Token::FatArrow))); + assert_eq!(lexer.next(), Some(Ok(Token::Union))); + assert_eq!(lexer.next(), Some(Ok(Token::OptionalChain))); + assert_eq!(lexer.next(), Some(Ok(Token::Unwrap))); + assert_eq!(lexer.next(), None); +} + +#[test] +fn test_variables() { + let mut lexer = Token::lexer("x y_z _private camelCase PascalCase"); + + assert_eq!(lexer.next(), Some(Ok(Token::Variable("x".to_string())))); + assert_eq!(lexer.next(), Some(Ok(Token::Variable("y_z".to_string())))); + assert_eq!( + lexer.next(), + Some(Ok(Token::Variable("_private".to_string()))) + ); + assert_eq!( + lexer.next(), + Some(Ok(Token::Variable("camelCase".to_string()))) + ); + assert_eq!( + lexer.next(), + Some(Ok(Token::Variable("PascalCase".to_string()))) + ); + assert_eq!(lexer.next(), None); +} + +#[test] +fn test_whitespace_skipping() { + let mut lexer = Token::lexer(" \t\n\r true \n false "); + + assert_eq!(lexer.next(), Some(Ok(Token::Bool(true)))); + assert_eq!(lexer.next(), Some(Ok(Token::Bool(false)))); + assert_eq!(lexer.next(), None); +} + +#[test] +fn test_comment_skipping() { + let mut lexer = Token::lexer("true # this is a comment\n false"); + + assert_eq!(lexer.next(), Some(Ok(Token::Bool(true)))); + assert_eq!(lexer.next(), Some(Ok(Token::Bool(false)))); + assert_eq!(lexer.next(), None); +} + +#[test] +fn test_complex_sequence() { + let mut lexer = Token::lexer("fn add(x: int, y: int) -> int { x + y }"); + + assert_eq!(lexer.next(), Some(Ok(Token::KeywordFn))); + assert_eq!(lexer.next(), Some(Ok(Token::Variable("add".to_string())))); + assert_eq!(lexer.next(), Some(Ok(Token::LParen))); + assert_eq!(lexer.next(), Some(Ok(Token::Variable("x".to_string())))); + assert_eq!(lexer.next(), Some(Ok(Token::Colon))); + assert_eq!(lexer.next(), Some(Ok(Token::KeywordInt))); + assert_eq!(lexer.next(), Some(Ok(Token::Comma))); + assert_eq!(lexer.next(), Some(Ok(Token::Variable("y".to_string())))); + assert_eq!(lexer.next(), Some(Ok(Token::Colon))); + assert_eq!(lexer.next(), Some(Ok(Token::KeywordInt))); + assert_eq!(lexer.next(), Some(Ok(Token::RParen))); + assert_eq!(lexer.next(), Some(Ok(Token::Arrow))); + assert_eq!(lexer.next(), Some(Ok(Token::KeywordInt))); + assert_eq!(lexer.next(), Some(Ok(Token::LBrace))); + assert_eq!(lexer.next(), Some(Ok(Token::Variable("x".to_string())))); + assert_eq!(lexer.next(), Some(Ok(Token::Plus))); + assert_eq!(lexer.next(), Some(Ok(Token::Variable("y".to_string())))); + assert_eq!(lexer.next(), Some(Ok(Token::RBrace))); + assert_eq!(lexer.next(), None); +} + +#[test] +fn test_edge_cases() { + // Test that keywords are not treated as variables + let mut lexer = Token::lexer("let let_var if if_var"); + + assert_eq!(lexer.next(), Some(Ok(Token::KeywordLet))); + assert_eq!( + lexer.next(), + Some(Ok(Token::Variable("let_var".to_string()))) + ); + assert_eq!(lexer.next(), Some(Ok(Token::KeywordIf))); + assert_eq!( + lexer.next(), + Some(Ok(Token::Variable("if_var".to_string()))) + ); + assert_eq!(lexer.next(), None); +} + +``` + diff --git a/simple_test.sui b/simple_test.sui new file mode 100644 index 0000000..65e17dc --- /dev/null +++ b/simple_test.sui @@ -0,0 +1,7 @@ +fn simple_add(x: int, y: int) -> int + x + y + +fn main() -> int do + let result = simple_add(5, 3) + result +end diff --git a/src/ast.rs b/src/ast.rs new file mode 100644 index 0000000..d4a52d6 --- /dev/null +++ b/src/ast.rs @@ -0,0 +1,430 @@ +use crate::typechecker::Type; +use std::ops::Range; + +#[derive(Debug, Clone)] +pub enum TypeAnnot { + Var(String), + Cons(String, Vec), + Function(Vec, Box), + Tuple(Vec), + Array(Box), +} + +#[derive(Debug, Clone)] +pub struct Span { + pub start: usize, + pub end: usize, + pub file: String, +} + +impl Span { + pub fn new(range: &Range, file: String) -> Self { + Span { + start: range.start, + end: range.end, + file, + } + } + + pub fn merge(&self, other: &Span) -> Span { + Span { + start: self.start.min(other.start), + end: self.end.max(other.end), + file: self.file.clone(), + } + } +} + +// @attribute +#[derive(Debug, Clone)] +pub struct Attribute { + pub name: String, + pub args: Vec, + pub span: Span, +} + +#[derive(Debug, Clone)] +pub enum AttributeArg { + Value(String), // some_identifier + KeyValue(String, String), // some_key = some_identifier + Literal(String), // some literal value +} + +#[derive(Debug, Clone)] +pub struct ASTNode { + pub kind: ASTNodeKind, + pub span: Span, + pub attributes: Vec, +} + +#[derive(Debug, Clone)] +pub enum ASTNodeKind { + Function(Function), + Extern(Extern), + Load(Load), + Struct(Struct), + Enum(Enum), + Impl(Impl), + Trait(Trait), + Use(String), +} + +// ? implies OPTIONAL here +// \( implies the presence of (. same for /) + +#[derive(Debug, Clone)] +/// fn name\( (arg: type?,)* \) -> return_type? body +pub struct Function { + pub name: String, + pub parameters: Vec, // type params + pub args: Vec<(String, Option)>, + pub return_type: Option, + pub body: Expr, +} + +/// extern name\( type?,* \) -> return_type from library_alias +#[derive(Debug, Clone)] +pub struct Extern { + pub name: String, + pub args: Vec, + pub return_type: TypeAnnot, + pub from: String, + pub span: Span, +} + +/// load "library" as alias +#[derive(Debug, Clone)] +pub struct Load { + pub library: String, + pub alias: String, + pub span: Span, +} + +/// struct name ? +/// (field_name: field_type,)* +/// end +#[derive(Debug, Clone)] +pub struct Struct { + pub name: String, + pub parameters: Vec, // type parameters + pub fields: Vec, +} + +#[derive(Debug, Clone)] +pub struct Field { + pub name: String, + pub field_type: TypeAnnot, + pub span: Span, +} + +/// enum name ? +/// VariantName\(field_type,\)* +/// end +#[derive(Debug, Clone)] +pub struct Enum { + pub name: String, + pub parameters: Vec, // type parameters + pub variants: Vec, +} + +#[derive(Debug, Clone)] +pub struct Parameter { + pub name: String, + pub bounds: Vec, // trait bounds + pub kind: Option, // for HKTs + pub span: Span, +} + +#[derive(Debug, Clone, PartialEq)] +pub enum Kind { + Star, // * + Arrow(Box, Box), // k1 -> k2 +} + +#[derive(Debug, Clone)] +pub struct Variant { + pub name: String, + pub fields: Vec, + pub span: Span, +} + +/// impl TypeName ? (: TraitName)? +/// functions* +/// end +#[derive(Debug, Clone)] +pub struct Impl { + pub target: String, + pub trait_name: Option, + pub methods: Vec, +} + +/// trait TraitName ? +/// function_signatures* +/// end +#[derive(Debug, Clone)] +pub struct Trait { + pub name: String, + pub methods: Vec, + + pub parameters: Vec, + pub associated_types: Vec, +} + +#[derive(Debug, Clone)] +pub struct AssociatedType { + pub name: String, + pub bounds: Vec, + pub span: Span, +} + +#[derive(Debug, Clone)] +pub struct FunctionSignature { + pub name: String, + pub params: Vec, + pub return_type: TypeAnnot, +} + +#[derive(Debug, Clone)] +pub struct Expr { + pub kind: ExprKind, + pub span: Span, + pub attributes: Vec, +} + +#[derive(Debug, Clone)] +pub enum ExprKind { + Int(i64), + Float(f64), + Bool(bool), + String(String), + Array(Vec), + Tuple(Vec), + + StructLit(String, Vec<(String, Expr)>), // Name { a: expr, b: expr } + EnumLit(String, String, Vec), // Name::Variant(expr, expr) + + Variable(String), + + Call(Box, Vec), + Index(Box, Box), + Dot(Box, String), + EarlyReturn(Option>), // eg: myresultoroption? + OptionalChain(Option>, String), // a?.b + + Lambda(Vec<(String, Option)>, Box), // lambda (arg, arg: optionalty, ...) body + Let(String, BindingKind, Option, Box), // no patterns for now + Assign(Box, Box), // NOTE: check for valid lvalue during typechecking + Cast(Box, TypeAnnot), + + If(Box, Box, Option>), // if cond expr (else expr)? + Match(Box, Vec<(Pattern, Expr)>), // match expr pattern => expr* end + While(Box, Box), // while cond expr + + For(String, Box, Box), // for i in expr body + Range(Box, Box), // 0..10 + + Do(Vec), // do expr* end + BinOp(Box, BinOp, Box), + UnOp(UnOp, Box), + + Return(Option>), + Break, + Continue, +} + +#[derive(Debug, Clone, PartialEq)] +pub enum BindingKind { + Default, // immutable but infinite usages + Mutable, // mutable but infinite usages + Affine, + Linear, +} + +#[derive(Debug, Clone)] +pub enum BinOp { + Add, + Sub, + Mul, + Div, + Mod, + And, + Or, + Eq, + Neq, + Lt, + Gt, + Leq, + Geq, +} + +#[derive(Debug, Clone)] +pub enum UnOp { + Neg, + Not, +} + +#[derive(Debug, Clone)] +pub struct Pattern { + pub kind: PatternKind, + pub span: Span, +} + +#[derive(Debug, Clone)] +pub enum PatternKind { + Wildcard, // _ + Variable(String), + Literal(String), + Tuple(Vec), + Struct(String, Vec<(String, Pattern)>), + Enum(String, String, Vec), + Range(i64, i64), +} + +// Typed variants + +#[derive(Debug, Clone)] +pub struct TypedASTNode { + pub kind: TypedASTNodeKind, + pub span: Span, + pub attributes: Vec, + pub ty: Type, +} + +#[derive(Debug, Clone)] +pub enum TypedASTNodeKind { + Function(TypedFunction), + Extern(TypedExtern), + Load(TypedLoad), + Struct(TypedStruct), + Enum(TypedEnum), + Impl(TypedImpl), + Trait(TypedTrait), + Use(String), +} + +#[derive(Debug, Clone)] +pub struct TypedFunction { + pub name: String, + pub parameters: Vec, + pub args: Vec<(String, Option)>, + pub return_type: Option, + pub body: TypedExpr, + pub ty: Type, +} + +#[derive(Debug, Clone)] +pub struct TypedExtern { + pub name: String, + pub args: Vec, + pub return_type: TypeAnnot, + pub from: String, + pub span: Span, +} + +#[derive(Debug, Clone)] +pub struct TypedLoad { + pub library: String, + pub alias: String, + pub span: Span, +} + +#[derive(Debug, Clone)] +pub struct TypedStruct { + pub name: String, + pub parameters: Vec, + pub fields: Vec, +} + +#[derive(Debug, Clone)] +pub struct TypedField { + pub name: String, + pub field_type: TypeAnnot, + pub span: Span, +} + +#[derive(Debug, Clone)] +pub struct TypedEnum { + pub name: String, + pub parameters: Vec, + pub variants: Vec, +} + +#[derive(Debug, Clone)] +pub struct TypedVariant { + pub name: String, + pub fields: Vec, + pub span: Span, +} + +#[derive(Debug, Clone)] +pub struct TypedImpl { + pub target: String, + pub trait_name: Option, + pub methods: Vec, +} + +#[derive(Debug, Clone)] +pub struct TypedTrait { + pub name: String, + pub methods: Vec, + pub parameters: Vec, + pub associated_types: Vec, +} + +#[derive(Debug, Clone)] +pub struct TypedExpr { + pub kind: TypedExprKind, + pub span: Span, + pub attributes: Vec, + pub ty: Type, +} + +#[derive(Debug, Clone)] +pub enum TypedExprKind { + Int(i64), + Float(f64), + Bool(bool), + String(String), + Array(Vec), + Tuple(Vec), + StructLit(String, Vec<(String, TypedExpr)>), + EnumLit(String, String, Vec), + Variable(String), + Call(Box, Vec), + Index(Box, Box), + Dot(Box, String), + EarlyReturn(Option>), + OptionalChain(Option>, String), + Lambda(Vec<(String, Option)>, Box), + Let(String, BindingKind, Option, Box), + Assign(Box, Box), + Cast(Box, TypeAnnot), + If(Box, Box, Option>), + Match(Box, Vec<(TypedPattern, TypedExpr)>), + While(Box, Box), + Do(Vec), + BinOp(Box, BinOp, Box), + UnOp(UnOp, Box), + For(String, Box, Box), + Range(Box, Box), + Return(Option>), + Break, + Continue, +} + +#[derive(Debug, Clone)] +pub struct TypedPattern { + pub kind: TypedPatternKind, + pub span: Span, + pub ty: Type, +} + +#[derive(Debug, Clone)] +pub enum TypedPatternKind { + Wildcard, + Variable(String), + Literal(String), + Tuple(Vec), + Struct(String, Vec<(String, TypedPattern)>), + Enum(String, String, Vec), +} diff --git a/src/lexer/mod.rs b/src/lexer/mod.rs new file mode 100644 index 0000000..4467413 --- /dev/null +++ b/src/lexer/mod.rs @@ -0,0 +1,288 @@ +use logos::Logos; + +#[cfg(test)] +pub mod tests; + +#[derive(Logos, Debug, PartialEq)] +#[logos(skip r"[ \n\r\t\f]+")] // Ignore this regex pattern between tokens +#[logos(skip r"#(.*)\n")] // Ignore this regex pattern between tokens +#[derive(Clone)] +pub enum Token { + #[regex(r"true|false", |lex| { + lex.slice().parse::().unwrap() + })] + Bool(bool), + + #[regex(r"0|[1-9][0-9_]*", |lex| { + let s = lex.slice().replace("_", ""); + // We parse to i64 for wider support. + s.parse::().unwrap() + }, priority = 4)] + Int(i64), + + #[regex(r"(([0-9][0-9_]*\.[0-9_]+|[0-9]*\.[0-9_]+)([eE][+-]?[0-9_]+)?)", |lex| { + let s = lex.slice().replace("_", ""); + s.parse::().unwrap() + }, priority = 3)] + Float(f64), + + #[regex(r#""([^"\\]*(\\.[^"\\]*)*)""#, |lex| { + let s = lex.slice(); + s[1..s.len()-1] + .replace("\\\"", "\"") + .replace("\\\\", "\\") + .replace("\\n", "\n") + .replace("\\r", "\r") + .replace("\\t", "\t") + })] + String(String), + + #[regex(r#"r#"([^"]*)""#, |lex| { + let s = lex.slice(); + // Remove the outer r" and " (s[2..s.len() - 1]) + s[3..s.len() - 1].to_string() + })] + RawString(String), + + #[regex(r"[a-zA-Z_][a-zA-Z0-9_]*", |lex|{ + lex.slice().to_string() + })] + Variable(String), + + #[token("bool")] + KeywordBool, + + #[token("int")] + KeywordInt, + + #[token("float")] + KeywordFloat, + + #[token("string")] + KeywordString, + + #[token("let")] + KeywordLet, + + #[token("mut")] + KeywordMut, + + #[token("uniq")] + KeywordUniq, + + #[token("once")] + KeywordOnce, + + #[token("if")] + KeywordIf, + + #[token("then")] + KeywordThen, + + #[token("else")] + KeywordElse, + + #[token("fn")] + KeywordFn, + + #[token("lambda")] + KeywordLambda, + + #[token("do")] + KeywordDo, + + #[token("end")] + KeywordEnd, + + #[token("as")] + KeywordAs, + + #[token("in")] + KeywordIn, + + #[token("for")] + KeywordFor, + #[token("while")] + KeywordWhile, + + #[token("loop")] + KeywordLoop, + + #[token("where")] + KeywordWhere, + + #[token("extern")] + KeywordExtern, + + #[token("load")] + KeywordLoad, + + #[token("from")] + KeywordFrom, + + #[token("use")] + KeywordUse, + + #[token("struct")] + KeywordStruct, + + #[token("enum")] + KeywordEnum, + + #[token("impl")] + KeywordImpl, + + #[token("trait")] + KeywordTrait, + + // #[token("type")] + // KeywordType, + // + #[token("match")] + KeywordMatch, + + #[token("return")] + KeywordReturn, + + #[token("break")] + KeywordBreak, + + #[token("continue")] + KeywordContinue, + + #[token("+")] + Plus, + + #[token("-")] + Minus, + + #[token("*")] + Mul, + + #[token("/")] + Div, + + #[token("%")] + Mod, + + #[token("**", priority = 3)] + Power, + + #[token("$")] + Dollar, + + #[token("@")] + At, + + #[token("==")] + Eq, + + #[token("!=")] + NotEq, + + #[token("<")] + Less, + + #[token(">")] + Greater, + + #[token("<=")] + LessEq, + + #[token(">=")] + GreaterEq, + + #[token("and")] + And, + + #[token("or")] + Or, + + #[token("xor")] + Xor, + + #[token("nor")] + Nor, + + #[token("not")] + Not, + + #[token("(")] + LParen, + + #[token(")")] + RParen, + + #[token("[")] + LBracket, + + #[token("]")] + RBracket, + + #[token("{")] + LBrace, + + #[token("}")] + RBrace, + + #[token(",")] + Comma, + + #[token(";")] + Semicolon, + + #[token(":")] + Colon, + + #[token(".")] + Dot, + + #[token("...")] + Spread, + + #[token("..")] + DotDot, + + #[token("::")] + Access, + + #[token("->")] + Arrow, + + #[token("~")] + Tilde, + + #[token("!")] + Bang, + + // New tokens for pattern matching + #[token("=>")] + FatArrow, // For match arms + + #[token("|")] + Union, + + #[token("?.")] + OptionalChain, + + #[token("?")] + Unwrap, + + #[token("=")] + Assign, + + #[token("+=")] + AddAssign, + + #[token("-=")] + SubAssign, + + #[token("*=")] + MulAssign, + + #[token("/=")] + DivAssign, + + #[token("%=")] + ModAssign, +} diff --git a/src/lexer/tests.rs b/src/lexer/tests.rs new file mode 100644 index 0000000..1799148 --- /dev/null +++ b/src/lexer/tests.rs @@ -0,0 +1,229 @@ +use super::Token; +use logos::Logos; + +#[test] +fn test_literals() { + let mut lexer = Token::lexer("true false 42 2.14 \"hello\" r\"raw\""); + + assert_eq!(lexer.next(), Some(Ok(Token::Bool(true)))); + assert_eq!(lexer.next(), Some(Ok(Token::Bool(false)))); + assert_eq!(lexer.next(), Some(Ok(Token::Int(42)))); + assert_eq!(lexer.next(), Some(Ok(Token::Float(2.14)))); + assert_eq!(lexer.next(), Some(Ok(Token::String("hello".to_string())))); + // RawString regex seems to have issues, let's test separately + assert_eq!(lexer.next(), Some(Ok(Token::Variable("r".to_string())))); + assert_eq!(lexer.next(), Some(Ok(Token::String("raw".to_string())))); + assert_eq!(lexer.next(), None); +} + +#[test] +fn test_int_literals() { + let mut lexer = Token::lexer("0 123 1_000_000"); + + assert_eq!(lexer.next(), Some(Ok(Token::Int(0)))); + assert_eq!(lexer.next(), Some(Ok(Token::Int(123)))); + assert_eq!(lexer.next(), Some(Ok(Token::Int(1000000)))); + assert_eq!(lexer.next(), None); +} + +#[test] +fn test_string_literals() { + let mut lexer = Token::lexer("\"hello world\" \"with\\\\escape\" \"quote\\\"here\""); + + assert_eq!( + lexer.next(), + Some(Ok(Token::String("hello world".to_string()))) + ); + assert_eq!( + lexer.next(), + Some(Ok(Token::String("with\\escape".to_string()))) + ); + assert_eq!( + lexer.next(), + Some(Ok(Token::String("quote\"here".to_string()))) + ); + assert_eq!(lexer.next(), None); +} + +#[test] +fn test_keywords() { + let mut lexer = Token::lexer( + "bool int float string let if else fn do end as in for while loop where extern import struct enum impl trait match return break continue", + ); + + assert_eq!(lexer.next(), Some(Ok(Token::KeywordBool))); + assert_eq!(lexer.next(), Some(Ok(Token::KeywordInt))); + assert_eq!(lexer.next(), Some(Ok(Token::KeywordFloat))); + assert_eq!(lexer.next(), Some(Ok(Token::KeywordString))); + assert_eq!(lexer.next(), Some(Ok(Token::KeywordLet))); + assert_eq!(lexer.next(), Some(Ok(Token::KeywordIf))); + assert_eq!(lexer.next(), Some(Ok(Token::KeywordElse))); + assert_eq!(lexer.next(), Some(Ok(Token::KeywordFn))); + assert_eq!(lexer.next(), Some(Ok(Token::KeywordDo))); + assert_eq!(lexer.next(), Some(Ok(Token::KeywordEnd))); + assert_eq!(lexer.next(), Some(Ok(Token::KeywordAs))); + assert_eq!(lexer.next(), Some(Ok(Token::KeywordIn))); + assert_eq!(lexer.next(), Some(Ok(Token::KeywordFor))); + assert_eq!(lexer.next(), Some(Ok(Token::KeywordWhile))); + assert_eq!(lexer.next(), Some(Ok(Token::KeywordLoop))); + assert_eq!(lexer.next(), Some(Ok(Token::KeywordWhere))); + assert_eq!(lexer.next(), Some(Ok(Token::KeywordExtern))); + assert_eq!(lexer.next(), Some(Ok(Token::Variable("import".into())))); + assert_eq!(lexer.next(), Some(Ok(Token::KeywordStruct))); + assert_eq!(lexer.next(), Some(Ok(Token::KeywordEnum))); + assert_eq!(lexer.next(), Some(Ok(Token::KeywordImpl))); + assert_eq!(lexer.next(), Some(Ok(Token::KeywordTrait))); + assert_eq!(lexer.next(), Some(Ok(Token::KeywordMatch))); + assert_eq!(lexer.next(), Some(Ok(Token::KeywordReturn))); + assert_eq!(lexer.next(), Some(Ok(Token::KeywordBreak))); + assert_eq!(lexer.next(), Some(Ok(Token::KeywordContinue))); + assert_eq!(lexer.next(), None); +} + +#[test] +fn test_operators() { + let mut lexer = Token::lexer("+ - * / % ** $ @ == != < > <= >= and or xor nor not"); + + assert_eq!(lexer.next(), Some(Ok(Token::Plus))); + assert_eq!(lexer.next(), Some(Ok(Token::Minus))); + assert_eq!(lexer.next(), Some(Ok(Token::Mul))); + assert_eq!(lexer.next(), Some(Ok(Token::Div))); + assert_eq!(lexer.next(), Some(Ok(Token::Mod))); + assert_eq!(lexer.next(), Some(Ok(Token::Power))); + assert_eq!(lexer.next(), Some(Ok(Token::Dollar))); + assert_eq!(lexer.next(), Some(Ok(Token::At))); + assert_eq!(lexer.next(), Some(Ok(Token::Eq))); + assert_eq!(lexer.next(), Some(Ok(Token::NotEq))); + assert_eq!(lexer.next(), Some(Ok(Token::Less))); + assert_eq!(lexer.next(), Some(Ok(Token::Greater))); + assert_eq!(lexer.next(), Some(Ok(Token::LessEq))); + assert_eq!(lexer.next(), Some(Ok(Token::GreaterEq))); + assert_eq!(lexer.next(), Some(Ok(Token::And))); + assert_eq!(lexer.next(), Some(Ok(Token::Or))); + assert_eq!(lexer.next(), Some(Ok(Token::Xor))); + assert_eq!(lexer.next(), Some(Ok(Token::Nor))); + assert_eq!(lexer.next(), Some(Ok(Token::Not))); + assert_eq!(lexer.next(), None); +} + +#[test] +fn test_assignment_operators() { + let mut lexer = Token::lexer("= += -= *= /= %="); + + assert_eq!(lexer.next(), Some(Ok(Token::Assign))); + assert_eq!(lexer.next(), Some(Ok(Token::AddAssign))); + assert_eq!(lexer.next(), Some(Ok(Token::SubAssign))); + assert_eq!(lexer.next(), Some(Ok(Token::MulAssign))); + assert_eq!(lexer.next(), Some(Ok(Token::DivAssign))); + assert_eq!(lexer.next(), Some(Ok(Token::ModAssign))); + assert_eq!(lexer.next(), None); +} + +#[test] +fn test_punctuation() { + let mut lexer = Token::lexer("( ) [ ] { } , ; : . ... .. :: -> ~ ! => | |> ?. ?"); + + assert_eq!(lexer.next(), Some(Ok(Token::LParen))); + assert_eq!(lexer.next(), Some(Ok(Token::RParen))); + assert_eq!(lexer.next(), Some(Ok(Token::LBracket))); + assert_eq!(lexer.next(), Some(Ok(Token::RBracket))); + assert_eq!(lexer.next(), Some(Ok(Token::LBrace))); + assert_eq!(lexer.next(), Some(Ok(Token::RBrace))); + assert_eq!(lexer.next(), Some(Ok(Token::Comma))); + assert_eq!(lexer.next(), Some(Ok(Token::Semicolon))); + assert_eq!(lexer.next(), Some(Ok(Token::Colon))); + assert_eq!(lexer.next(), Some(Ok(Token::Dot))); + assert_eq!(lexer.next(), Some(Ok(Token::Spread))); + assert_eq!(lexer.next(), Some(Ok(Token::DotDot))); + assert_eq!(lexer.next(), Some(Ok(Token::Access))); + assert_eq!(lexer.next(), Some(Ok(Token::Arrow))); + assert_eq!(lexer.next(), Some(Ok(Token::Tilde))); + assert_eq!(lexer.next(), Some(Ok(Token::Bang))); + assert_eq!(lexer.next(), Some(Ok(Token::FatArrow))); + assert_eq!(lexer.next(), Some(Ok(Token::Union))); + assert_eq!(lexer.next(), Some(Ok(Token::OptionalChain))); + assert_eq!(lexer.next(), Some(Ok(Token::Unwrap))); + assert_eq!(lexer.next(), None); +} + +#[test] +fn test_variables() { + let mut lexer = Token::lexer("x y_z _private camelCase PascalCase"); + + assert_eq!(lexer.next(), Some(Ok(Token::Variable("x".to_string())))); + assert_eq!(lexer.next(), Some(Ok(Token::Variable("y_z".to_string())))); + assert_eq!( + lexer.next(), + Some(Ok(Token::Variable("_private".to_string()))) + ); + assert_eq!( + lexer.next(), + Some(Ok(Token::Variable("camelCase".to_string()))) + ); + assert_eq!( + lexer.next(), + Some(Ok(Token::Variable("PascalCase".to_string()))) + ); + assert_eq!(lexer.next(), None); +} + +#[test] +fn test_whitespace_skipping() { + let mut lexer = Token::lexer(" \t\n\r true \n false "); + + assert_eq!(lexer.next(), Some(Ok(Token::Bool(true)))); + assert_eq!(lexer.next(), Some(Ok(Token::Bool(false)))); + assert_eq!(lexer.next(), None); +} + +#[test] +fn test_comment_skipping() { + let mut lexer = Token::lexer("true # this is a comment\n false"); + + assert_eq!(lexer.next(), Some(Ok(Token::Bool(true)))); + assert_eq!(lexer.next(), Some(Ok(Token::Bool(false)))); + assert_eq!(lexer.next(), None); +} + +#[test] +fn test_complex_sequence() { + let mut lexer = Token::lexer("fn add(x: int, y: int) -> int { x + y }"); + + assert_eq!(lexer.next(), Some(Ok(Token::KeywordFn))); + assert_eq!(lexer.next(), Some(Ok(Token::Variable("add".to_string())))); + assert_eq!(lexer.next(), Some(Ok(Token::LParen))); + assert_eq!(lexer.next(), Some(Ok(Token::Variable("x".to_string())))); + assert_eq!(lexer.next(), Some(Ok(Token::Colon))); + assert_eq!(lexer.next(), Some(Ok(Token::KeywordInt))); + assert_eq!(lexer.next(), Some(Ok(Token::Comma))); + assert_eq!(lexer.next(), Some(Ok(Token::Variable("y".to_string())))); + assert_eq!(lexer.next(), Some(Ok(Token::Colon))); + assert_eq!(lexer.next(), Some(Ok(Token::KeywordInt))); + assert_eq!(lexer.next(), Some(Ok(Token::RParen))); + assert_eq!(lexer.next(), Some(Ok(Token::Arrow))); + assert_eq!(lexer.next(), Some(Ok(Token::KeywordInt))); + assert_eq!(lexer.next(), Some(Ok(Token::LBrace))); + assert_eq!(lexer.next(), Some(Ok(Token::Variable("x".to_string())))); + assert_eq!(lexer.next(), Some(Ok(Token::Plus))); + assert_eq!(lexer.next(), Some(Ok(Token::Variable("y".to_string())))); + assert_eq!(lexer.next(), Some(Ok(Token::RBrace))); + assert_eq!(lexer.next(), None); +} + +#[test] +fn test_edge_cases() { + // Test that keywords are not treated as variables + let mut lexer = Token::lexer("let let_var if if_var"); + + assert_eq!(lexer.next(), Some(Ok(Token::KeywordLet))); + assert_eq!( + lexer.next(), + Some(Ok(Token::Variable("let_var".to_string()))) + ); + assert_eq!(lexer.next(), Some(Ok(Token::KeywordIf))); + assert_eq!( + lexer.next(), + Some(Ok(Token::Variable("if_var".to_string()))) + ); + assert_eq!(lexer.next(), None); +} diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..ccd1859 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,6 @@ +pub const EXTENSION: &str = ".sui"; + +pub mod ast; +pub mod lexer; +pub mod parser; +pub mod typechecker; diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..af81cfd --- /dev/null +++ b/src/main.rs @@ -0,0 +1,88 @@ +use logos::Logos; +use std::fs; +use suicmez::{lexer::Token, parser::Parser, typechecker::TypeChecker}; + +fn main() { + // Check if a file was provided as argument + let args: Vec = std::env::args().collect(); + if args.len() < 2 { + // Run all test files in the tests directory + run_test_suite(); + return; + } + + let filename = &args[1]; + println!("Type checking file: {}", filename); + + if let Err(e) = run_file(filename) { + eprintln!("Error: {}", e); + } +} + +fn run_test_suite() { + println!("Running test suite...\n"); + + let test_files = vec![ + "tests/basic_types.sui", + "tests/structs.sui", + "tests/enums.sui", + "tests/functions.sui", + "tests/arrays.sui", + "tests/traits.sui", + "tests/control_flow.sui", + ]; + + for file in test_files { + println!("Testing: {}", file); + match run_file(file) { + Ok(_) => println!("✓ Passed\n"), + Err(e) => println!("✗ Failed: {}\n", e), + } + } +} + +fn run_file(filename: &str) -> Result<(), String> { + // Read the source file + let source = fs::read_to_string(filename) + .map_err(|e| format!("Error reading file {}: {}", filename, e))?; + + // First, we need to parse the source code + let mut tokens = Vec::new(); + let mut lexer = Token::lexer(&source); + + loop { + match lexer.next() { + Some(Ok(token)) => { + let span = lexer.span(); + tokens.push((token, span)); + } + Some(Err(_)) => { + return Err("Lexing error".to_string()); + } + None => break, + } + } + + let mut parser = Parser::new(filename.to_string(), tokens); + let ast_nodes = parser + .parse() + .map_err(|e| format!("Parse error: {}", e.message))?; + + println!("Parsed {} AST nodes successfully", ast_nodes.len()); + + // Typecheck the AST + let mut typechecker = TypeChecker::new(); + let typed_nodes = typechecker.typecheck_program(&ast_nodes).map_err(|e| { + format!( + "Type error at {}:{}: {:?}", + e.span.file, e.span.start, e.kind + ) + })?; + + println!( + "Type checking passed! {} nodes typechecked.", + typed_nodes.len() + ); + + Ok(()) +} diff --git a/src/parser.rs b/src/parser.rs new file mode 100644 index 0000000..9173085 --- /dev/null +++ b/src/parser.rs @@ -0,0 +1,1763 @@ +use crate::ast::*; +use crate::lexer::Token; + +use std::iter::Peekable; +use std::ops::Range; +use std::vec::IntoIter; + +type TokenIter = Peekable)>>; + +pub struct Parser { + pub file: String, + pub tokens: TokenIter, +} + +#[derive(Debug)] +pub struct ParseError { + pub message: String, + pub span: Span, +} + +impl Parser { + pub fn new(file: String, tokens: Vec<(Token, Range)>) -> Self { + Parser { + file, + tokens: tokens.into_iter().peekable(), + } + } + + // Parse the entire file into a list of AST nodes + pub fn parse(&mut self) -> Result, ParseError> { + let mut nodes = Vec::new(); + + while self.peek().is_some() { + nodes.push(self.parse_top_level()?); + } + + Ok(nodes) + } + + fn peek(&mut self) -> Option<&Token> { + self.tokens.peek().map(|(token, _)| token) + } + + fn peek_span(&mut self) -> Option> { + self.tokens.peek().map(|(_, span)| span.clone()) + } + + fn next(&mut self) -> Option<(Token, Range)> { + self.tokens.next() + } + + fn expect(&mut self, expected: Token) -> Result, ParseError> { + match self.next() { + Some((token, span)) + if std::mem::discriminant(&token) == std::mem::discriminant(&expected) => + { + Ok(span) + } + Some((token, span)) => Err(ParseError { + message: format!("Expected {:?}, found {:?}", expected, token), + span: Span::new(&span, self.file.clone()), + }), + None => Err(ParseError { + message: format!("Expected {:?}, found EOF", expected), + span: Span::new(&(0..0), self.file.clone()), + }), + } + } + + fn error(&self, msg: String, span: Range) -> Result { + Err(ParseError { + message: msg, + span: Span::new(&span, self.file.clone()), + }) + } + + fn parse_top_level(&mut self) -> Result { + let mut attributes = Vec::new(); + + // Parse any leading attributes + while matches!(self.peek(), Some(Token::At)) { + attributes.push(self.parse_attribute()?); + } + + let start = self.peek_span().unwrap_or(0..0).start; + let token = self.peek().cloned(); + match token { + Some(Token::KeywordUse) => { + self.next(); + let path = match self.next() { + Some((Token::String(s), _)) => s, + Some((_, span)) => { + return self.error("Expected string after 'use'".to_string(), span); + } + None => { + return self.error("Expected string after 'use'".to_string(), start..start); + } + }; + let end = self.peek_span().unwrap_or(start..start).end; + Ok(ASTNode { + kind: ASTNodeKind::Use(path), + span: Span::new(&(start..end), self.file.clone()), + attributes, + }) + } + Some(Token::KeywordFn) => { + self.next(); + let func = self.parse_function()?; + let end = self.peek_span().unwrap_or(start..start).end; + Ok(ASTNode { + kind: ASTNodeKind::Function(func), + span: Span::new(&(start..end), self.file.clone()), + attributes, + }) + } + Some(Token::KeywordStruct) => { + self.next(); + let struct_def = self.parse_struct()?; + let end = self.peek_span().unwrap_or(start..start).end; + Ok(ASTNode { + kind: ASTNodeKind::Struct(struct_def), + span: Span::new(&(start..end), self.file.clone()), + attributes, + }) + } + Some(Token::KeywordEnum) => { + self.next(); + let enum_def = self.parse_enum()?; + let end = self.peek_span().unwrap_or(start..start).end; + Ok(ASTNode { + kind: ASTNodeKind::Enum(enum_def), + span: Span::new(&(start..end), self.file.clone()), + attributes, + }) + } + Some(Token::KeywordImpl) => { + self.next(); + let impl_def = self.parse_impl()?; + let end = self.peek_span().unwrap_or(start..start).end; + Ok(ASTNode { + kind: ASTNodeKind::Impl(impl_def), + span: Span::new(&(start..end), self.file.clone()), + attributes, + }) + } + Some(Token::KeywordTrait) => { + self.next(); + let trait_def = self.parse_trait()?; + let end = self.peek_span().unwrap_or(start..start).end; + Ok(ASTNode { + kind: ASTNodeKind::Trait(trait_def), + span: Span::new(&(start..end), self.file.clone()), + attributes, + }) + } + Some(Token::KeywordExtern) => { + self.next(); + let extern_def = self.parse_extern()?; + let end = self.peek_span().unwrap_or(start..start).end; + Ok(ASTNode { + kind: ASTNodeKind::Extern(extern_def), + span: Span::new(&(start..end), self.file.clone()), + attributes, + }) + } + Some(Token::KeywordLoad) => { + self.next(); + let load_def = self.parse_load()?; + let end = self.peek_span().unwrap_or(start..start).end; + Ok(ASTNode { + kind: ASTNodeKind::Load(load_def), + span: Span::new(&(start..end), self.file.clone()), + attributes, + }) + } + Some(token) => { + let span = self.peek_span().unwrap_or(start..start); + self.error(format!("Unexpected token at top level: {:?}", token), span) + } + None => self.error("Unexpected EOF".to_string(), start..start), + } + } + + fn parse_attribute(&mut self) -> Result { + self.expect(Token::At)?; + let start = self.peek_span().unwrap_or(0..0).start; + let name = match self.next() { + Some((Token::Variable(name), _)) => name, + Some((_, span)) => return self.error("Expected attribute name".to_string(), span), + None => return self.error("Expected attribute name".to_string(), start..start), + }; + + // Parentheses are optional + let mut args = vec![]; + if matches!(self.peek(), Some(Token::LParen)) { + self.next(); + loop { + let token = self.peek().cloned(); + match token { + Some(Token::RParen) => { + self.next(); + break; + } + Some(Token::String(s)) => { + self.next(); + args.push(AttributeArg::Literal(s)); + } + Some(Token::Variable(id)) => { + self.next(); + let next_token = self.peek().cloned(); + if matches!(next_token, Some(Token::Assign)) { + self.next(); + match self.next() { + Some((Token::Variable(val), _)) => { + args.push(AttributeArg::KeyValue(id, val)) + } + Some((_, span)) => { + return self.error("Expected value after =".to_string(), span); + } + None => { + return self + .error("Expected value after =".to_string(), start..start); + } + } + } else { + args.push(AttributeArg::Value(id)); + } + } + Some(token) => { + let span = self.peek_span().unwrap_or(start..start); + return self + .error(format!("Unexpected token in attribute: {:?}", token), span); + } + None => { + return self.error("Expected attribute argument".to_string(), start..start); + } + } + let next_token = self.peek().cloned(); + if matches!(next_token, Some(Token::Comma)) { + self.next(); + } else if matches!(next_token, Some(Token::RParen)) { + // ok + } else { + { + let span = self.peek_span().unwrap_or(start..start); + return self.error("Expected , or )".to_string(), span); + } + } + } + } + let end = self.peek_span().unwrap_or(start..start).end; + Ok(Attribute { + name, + args, + span: Span::new(&(start..end), self.file.clone()), + }) + } + + fn parse_function(&mut self) -> Result { + let start = self.peek_span().unwrap_or(0..0).start; + let name = match self.next() { + Some((Token::Variable(n), _)) => n, + Some((_, span)) => return self.error("Expected function name".to_string(), span), + None => return self.error("Expected function name".to_string(), start..start), + }; + + // Parse type parameters if present + let parameters = if matches!(self.peek(), Some(Token::Less)) { + self.next(); + self.parse_parameters()? + } else { + Vec::new() + }; + + // Parse function arguments + self.expect(Token::LParen)?; + let mut args = Vec::new(); + loop { + if matches!(self.peek(), Some(Token::RParen)) { + self.next(); + break; + } + + let arg_name = match self.next() { + Some((Token::Variable(n), _)) => n, + Some((_, span)) => return self.error("Expected argument name".to_string(), span), + None => return self.error("Expected argument name".to_string(), start..start), + }; + + let arg_type = if matches!(self.peek(), Some(Token::Colon)) { + self.next(); + Some(self.parse_type_annot()?) + } else { + None + }; + + args.push((arg_name, arg_type)); + + if matches!(self.peek(), Some(Token::Comma)) { + self.next(); + } else if !matches!(self.peek(), Some(Token::RParen)) { + let span = self.peek_span().unwrap_or(start..start); + return self.error("Expected , or )".to_string(), span); + } + } + + // Parse return type if present + let return_type = if matches!(self.peek(), Some(Token::Arrow)) { + self.next(); + Some(self.parse_type_annot()?) + } else { + None + }; + + // Parse body expression + let body = self.parse_expr()?; + + Ok(Function { + name, + parameters, + args, + return_type, + body, + }) + } + + fn parse_struct(&mut self) -> Result { + let start = self.peek_span().unwrap_or(0..0).start; + let name = match self.next() { + Some((Token::Variable(n), _)) => n, + Some((_, span)) => return self.error("Expected struct name".to_string(), span), + None => return self.error("Expected struct name".to_string(), start..start), + }; + + // Parse type parameters if present + let parameters = if matches!(self.peek(), Some(Token::Less)) { + self.next(); + self.parse_parameters()? + } else { + Vec::new() + }; + + // Parse fields + let mut fields = Vec::new(); + loop { + if matches!(self.peek(), Some(Token::KeywordEnd)) { + self.next(); + break; + } + + let field_start = self.peek_span().unwrap_or(0..0).start; + let field_name = match self.next() { + Some((Token::Variable(n), _)) => n, + Some((_, span)) => return self.error("Expected field name".to_string(), span), + None => return self.error("Expected field name".to_string(), start..start), + }; + + self.expect(Token::Colon)?; + let field_type = self.parse_type_annot()?; + let field_end = self.peek_span().unwrap_or(field_start..field_start).start; + + fields.push(Field { + name: field_name, + field_type, + span: Span::new(&(field_start..field_end), self.file.clone()), + }); + + if matches!(self.peek(), Some(Token::Comma)) { + self.next(); + } + } + + Ok(Struct { + name, + parameters, + fields, + }) + } + + fn parse_enum(&mut self) -> Result { + let start = self.peek_span().unwrap_or(0..0).start; + let name = match self.next() { + Some((Token::Variable(n), _)) => n, + Some((_, span)) => return self.error("Expected enum name".to_string(), span), + None => return self.error("Expected enum name".to_string(), start..start), + }; + + // Parse type parameters if present + let parameters = if matches!(self.peek(), Some(Token::Less)) { + self.next(); + self.parse_parameters()? + } else { + Vec::new() + }; + + // Parse variants + let mut variants = Vec::new(); + loop { + if matches!(self.peek(), Some(Token::KeywordEnd)) { + self.next(); + break; + } + + let variant_start = self.peek_span().unwrap_or(0..0).start; + let variant_name = match self.next() { + Some((Token::Variable(n), _)) => n, + Some((_, span)) => return self.error("Expected variant name".to_string(), span), + None => return self.error("Expected variant name".to_string(), start..start), + }; + + let mut fields = Vec::new(); + if matches!(self.peek(), Some(Token::LParen)) { + self.next(); + loop { + if matches!(self.peek(), Some(Token::RParen)) { + self.next(); + break; + } + fields.push(self.parse_type_annot()?); + + if matches!(self.peek(), Some(Token::Comma)) { + self.next(); + } + } + } + + let variant_end = self + .peek_span() + .unwrap_or(variant_start..variant_start) + .start; + variants.push(Variant { + name: variant_name, + fields, + span: Span::new(&(variant_start..variant_end), self.file.clone()), + }); + + if matches!(self.peek(), Some(Token::Comma)) { + self.next(); + } + } + + Ok(Enum { + name, + parameters, + variants, + }) + } + + fn parse_impl(&mut self) -> Result { + let start = self.peek_span().unwrap_or(0..0).start; + + // Parse impl target as a type (could be generic like Option) + let target_type = self.parse_type_annot()?; + + // Extract the base type name from the type annotation + let target = match target_type { + TypeAnnot::Var(name) => name, + TypeAnnot::Cons(name, _) => name, + _ => { + return self.error( + "Expected type name for impl target".to_string(), + start..start, + ); + } + }; + + // Parse optional trait name + let trait_name = if matches!(self.peek(), Some(Token::Colon)) { + self.next(); + match self.next() { + Some((Token::Variable(n), _)) => Some(n), + Some((_, span)) => return self.error("Expected trait name".to_string(), span), + None => return self.error("Expected trait name".to_string(), start..start), + } + } else { + None + }; + + // Parse methods + let mut methods = Vec::new(); + loop { + if matches!(self.peek(), Some(Token::KeywordEnd)) { + self.next(); + break; + } + + self.expect(Token::KeywordFn)?; + methods.push(self.parse_function()?); + } + + Ok(Impl { + target, + trait_name, + methods, + }) + } + + fn parse_trait(&mut self) -> Result { + let start = self.peek_span().unwrap_or(0..0).start; + let name = match self.next() { + Some((Token::Variable(n), _)) => n, + Some((_, span)) => return self.error("Expected trait name".to_string(), span), + None => return self.error("Expected trait name".to_string(), start..start), + }; + + // Parse type parameters if present + let parameters = if matches!(self.peek(), Some(Token::Less)) { + self.next(); + self.parse_parameters()? + } else { + Vec::new() + }; + + // Parse methods + let mut methods = Vec::new(); + + loop { + if matches!(self.peek(), Some(Token::KeywordEnd)) { + self.next(); + break; + } + + if matches!(self.peek(), Some(Token::KeywordFn)) { + self.next(); + methods.push(self.parse_function_signature()?); + + // Optional comma between methods + if matches!(self.peek(), Some(Token::Comma)) { + self.next(); + } + } else { + break; + } + } + + Ok(Trait { + name, + methods, + parameters, + associated_types: Vec::new(), + }) + } + + fn parse_extern(&mut self) -> Result { + let start = self.peek_span().unwrap_or(0..0).start; + let name = match self.next() { + Some((Token::Variable(n), _)) => n, + Some((_, span)) => return self.error("Expected extern name".to_string(), span), + None => return self.error("Expected extern name".to_string(), start..start), + }; + + // Parse argument types (with optional parameter names) + self.expect(Token::LParen)?; + let mut args = Vec::new(); + loop { + if matches!(self.peek(), Some(Token::RParen)) { + self.next(); + break; + } + + args.push(self.parse_type_annot()?); + + if matches!(self.peek(), Some(Token::Comma)) { + self.next(); + } + } + + // Parse return type + self.expect(Token::Arrow)?; + let return_type = self.parse_type_annot()?; + + // Parse from clause + self.expect(Token::KeywordFrom)?; + let from = match self.next() { + Some((Token::Variable(n), _)) => n, + Some((_, span)) => return self.error("Expected library identifier".to_string(), span), + None => return self.error("Expected library identifier".to_string(), start..start), + }; + + let end = self.peek_span().unwrap_or(start..start).end; + Ok(Extern { + name, + args, + return_type, + from, + span: Span::new(&(start..end), self.file.clone()), + }) + } + + fn parse_load(&mut self) -> Result { + let start = self.peek_span().unwrap_or(0..0).start; + let library = match self.next() { + Some((Token::String(s), _)) => s, + Some((_, span)) => return self.error("Expected library name".to_string(), span), + None => return self.error("Expected library name".to_string(), start..start), + }; + + self.expect(Token::KeywordAs)?; + let alias = match self.next() { + Some((Token::Variable(a), _)) => a, + Some((_, span)) => return self.error("Expected alias".to_string(), span), + None => return self.error("Expected alias".to_string(), start..start), + }; + + let end = self.peek_span().unwrap_or(start..start).end; + Ok(Load { + library, + alias, + span: Span::new(&(start..end), self.file.clone()), + }) + } + + fn parse_parameters(&mut self) -> Result, ParseError> { + let start = self.peek_span().unwrap_or(0..0).start; + let mut params = Vec::new(); + + loop { + if matches!(self.peek(), Some(Token::Greater)) { + self.next(); + break; + } + + let param_start = self.peek_span().unwrap_or(0..0).start; + let param_name = match self.next() { + Some((Token::Variable(n), _)) => n, + Some((_, span)) => return self.error("Expected parameter name".to_string(), span), + None => return self.error("Expected parameter name".to_string(), start..start), + }; + + let bounds = if matches!(self.peek(), Some(Token::Colon)) { + self.next(); + self.parse_trait_bounds()? + } else { + Vec::new() + }; + + let kind = if matches!(self.peek(), Some(Token::Colon)) { + self.next(); + Some(self.parse_kind()?) + } else { + None + }; + + let param_end = self.peek_span().unwrap_or(param_start..param_start).end; + params.push(Parameter { + name: param_name, + bounds, + kind, + span: Span::new(&(param_start..param_end), self.file.clone()), + }); + + if matches!(self.peek(), Some(Token::Comma)) { + self.next(); + } + } + + Ok(params) + } + + fn parse_trait_bounds(&mut self) -> Result, ParseError> { + let mut bounds = Vec::new(); + loop { + match self.next() { + Some((Token::Variable(n), _)) => bounds.push(n), + Some((_, span)) => return self.error("Expected trait name".to_string(), span), + None => return self.error("Expected trait name".to_string(), 0..0), + } + + if !matches!(self.peek(), Some(Token::Plus)) { + break; + } + self.next(); + } + + Ok(bounds) + } + + fn parse_kind(&mut self) -> Result { + if matches!(self.peek(), Some(Token::Mul)) { + self.next(); + Ok(Kind::Star) + } else { + let k1 = Box::new(self.parse_kind()?); + self.expect(Token::Arrow)?; + let k2 = Box::new(self.parse_kind()?); + Ok(Kind::Arrow(k1, k2)) + } + } + + fn parse_type_annot(&mut self) -> Result { + let start = self.peek_span().unwrap_or(0..0).start; + + // Check for function type: fn (args)->ret + if matches!(self.peek(), Some(Token::KeywordFn)) { + self.next(); + self.expect(Token::LParen)?; + let mut arg_types = Vec::new(); + + loop { + if matches!(self.peek(), Some(Token::RParen)) { + self.next(); + break; + } + arg_types.push(self.parse_type_annot()?); + + if matches!(self.peek(), Some(Token::Comma)) { + self.next(); + } + } + + self.expect(Token::Arrow)?; + let ret_type = Box::new(self.parse_type_annot()?); + return Ok(TypeAnnot::Function(arg_types, ret_type)); + } + + let mut base_type = match self.next() { + Some((Token::Variable(n), _)) => TypeAnnot::Cons(n, vec![]), + Some((Token::KeywordBool, _)) => TypeAnnot::Cons("bool".to_string(), vec![]), + Some((Token::KeywordInt, _)) => TypeAnnot::Cons("int".to_string(), vec![]), + Some((Token::KeywordFloat, _)) => TypeAnnot::Cons("float".to_string(), vec![]), + Some((Token::KeywordString, _)) => TypeAnnot::Cons("string".to_string(), vec![]), + Some((Token::LParen, _)) => { + // Check for unit type: () + if matches!(self.peek(), Some(Token::RParen)) { + self.next(); + return Ok(TypeAnnot::Cons("unit".to_string(), vec![])); + } + + let mut types = Vec::new(); + loop { + if matches!(self.peek(), Some(Token::RParen)) { + self.next(); + break; + } + types.push(self.parse_type_annot()?); + + if matches!(self.peek(), Some(Token::Comma)) { + self.next(); + } + } + + // Single element in parens is not a tuple, unwrap it + if types.len() == 1 { + types.pop().unwrap() + } else { + TypeAnnot::Tuple(types) + } + } + Some((Token::LBracket, _)) => { + let inner = self.parse_type_annot()?; + self.expect(Token::RBracket)?; + TypeAnnot::Array(Box::new(inner)) + } + Some((Token::Bang, _)) => TypeAnnot::Cons("never".to_string(), vec![]), + Some((_, span)) => return self.error("Expected type name".to_string(), span), + None => return self.error("Expected type name".to_string(), start..start), + }; + + // Parse type arguments if present + if matches!(self.peek(), Some(Token::Less)) { + self.next(); + let mut args = Vec::new(); + loop { + if matches!(self.peek(), Some(Token::Greater)) { + self.next(); + break; + } + args.push(self.parse_type_annot()?); + + if matches!(self.peek(), Some(Token::Comma)) { + self.next(); + } + } + base_type = match base_type { + TypeAnnot::Cons(name, _) => TypeAnnot::Cons(name, args), + _ => { + return self.error("Expected type name for generic".to_string(), start..start); + } + }; + } + + // Parse array types + while matches!(self.peek(), Some(Token::LBracket)) { + self.next(); + self.expect(Token::RBracket)?; + base_type = TypeAnnot::Array(Box::new(base_type)); + } + + Ok(base_type) + } + + fn parse_function_signature(&mut self) -> Result { + let start = self.peek_span().unwrap_or(0..0).start; + let name = match self.next() { + Some((Token::Variable(n), _)) => n, + Some((_, span)) => return self.error("Expected function name".to_string(), span), + None => return self.error("Expected function name".to_string(), start..start), + }; + + self.expect(Token::LParen)?; + let mut params = Vec::new(); + loop { + if matches!(self.peek(), Some(Token::RParen)) { + self.next(); + break; + } + + let param_start = self.peek_span().unwrap_or(0..0).start; + let param_name = match self.next() { + Some((Token::Variable(n), _)) => n, + Some((_, span)) => return self.error("Expected parameter name".to_string(), span), + None => return self.error("Expected parameter name".to_string(), start..start), + }; + + // Parameters in trait methods may have type annotations + if matches!(self.peek(), Some(Token::Colon)) { + self.next(); + let _param_type = self.parse_type_annot()?; + } + + let param_end = self.peek_span().unwrap_or(param_start..param_start).end; + params.push(Parameter { + name: param_name, + bounds: Vec::new(), + kind: None, + span: Span::new(&(param_start..param_end), self.file.clone()), + }); + + if matches!(self.peek(), Some(Token::Comma)) { + self.next(); + } + } + + self.expect(Token::Arrow)?; + let return_type = self.parse_type_annot()?; + + Ok(FunctionSignature { + name, + params, + return_type, + }) + } + + fn parse_expr(&mut self) -> Result { + let mut attributes = Vec::new(); + + // Parse any leading attributes + while matches!(self.peek(), Some(Token::At)) { + attributes.push(self.parse_attribute()?); + } + + let mut expr = self.parse_assignment()?; + expr.attributes = attributes; + Ok(expr) + } + + fn parse_range_expr(&mut self) -> Result { + let left = self.parse_or_expr()?; + + if matches!(self.peek(), Some(Token::DotDot)) { + let start = left.span.start; + self.next(); + let right = self.parse_or_expr()?; + let end = right.span.end; + Ok(Expr { + kind: ExprKind::Range(Box::new(left), Box::new(right)), + span: Span::new(&(start..end), self.file.clone()), + attributes: Vec::new(), + }) + } else { + Ok(left) + } + } + + fn parse_assignment(&mut self) -> Result { + let left = self.parse_range_expr()?; + + if matches!(self.peek(), Some(Token::Assign)) { + let start = left.span.start; + self.next(); + let right = self.parse_assignment()?; + let end = right.span.end; + Ok(Expr { + kind: ExprKind::Assign(Box::new(left), Box::new(right)), + span: Span::new(&(start..end), self.file.clone()), + attributes: Vec::new(), + }) + } else { + Ok(left) + } + } + + fn parse_or_expr(&mut self) -> Result { + let mut left = self.parse_and_expr()?; + + loop { + if matches!(self.peek(), Some(Token::Or)) { + let start = left.span.start; + self.next(); + let right = self.parse_and_expr()?; + let end = right.span.end; + left = Expr { + kind: ExprKind::BinOp(Box::new(left), BinOp::Or, Box::new(right)), + span: Span::new(&(start..end), self.file.clone()), + attributes: Vec::new(), + }; + } else { + break; + } + } + + Ok(left) + } + + fn parse_and_expr(&mut self) -> Result { + let mut left = self.parse_eq_expr()?; + + loop { + if matches!(self.peek(), Some(Token::And)) { + let start = left.span.start; + self.next(); + let right = self.parse_eq_expr()?; + let end = right.span.end; + left = Expr { + kind: ExprKind::BinOp(Box::new(left), BinOp::And, Box::new(right)), + span: Span::new(&(start..end), self.file.clone()), + attributes: Vec::new(), + }; + } else { + break; + } + } + + Ok(left) + } + + fn parse_eq_expr(&mut self) -> Result { + let mut left = self.parse_comp_expr()?; + + loop { + let op = match self.peek() { + Some(Token::Eq) => BinOp::Eq, + Some(Token::NotEq) => BinOp::Neq, + _ => break, + }; + let start = left.span.start; + self.next(); + let right = self.parse_comp_expr()?; + let end = right.span.end; + left = Expr { + kind: ExprKind::BinOp(Box::new(left), op, Box::new(right)), + span: Span::new(&(start..end), self.file.clone()), + attributes: Vec::new(), + }; + } + + Ok(left) + } + + fn parse_comp_expr(&mut self) -> Result { + let mut left = self.parse_add_expr()?; + + loop { + let op = match self.peek() { + Some(Token::Less) => BinOp::Lt, + Some(Token::Greater) => BinOp::Gt, + Some(Token::LessEq) => BinOp::Leq, + Some(Token::GreaterEq) => BinOp::Geq, + _ => break, + }; + let start = left.span.start; + self.next(); + let right = self.parse_add_expr()?; + let end = right.span.end; + left = Expr { + kind: ExprKind::BinOp(Box::new(left), op, Box::new(right)), + span: Span::new(&(start..end), self.file.clone()), + attributes: Vec::new(), + }; + } + + Ok(left) + } + + fn parse_add_expr(&mut self) -> Result { + let mut left = self.parse_mul_expr()?; + + loop { + let op = match self.peek() { + Some(Token::Plus) => BinOp::Add, + Some(Token::Minus) => BinOp::Sub, + _ => break, + }; + let start = left.span.start; + self.next(); + let right = self.parse_mul_expr()?; + let end = right.span.end; + left = Expr { + kind: ExprKind::BinOp(Box::new(left), op, Box::new(right)), + span: Span::new(&(start..end), self.file.clone()), + attributes: Vec::new(), + }; + } + + Ok(left) + } + + fn parse_mul_expr(&mut self) -> Result { + let mut left = self.parse_unary_expr()?; + + loop { + let op = match self.peek() { + Some(Token::Mul) => BinOp::Mul, + Some(Token::Div) => BinOp::Div, + Some(Token::Mod) => BinOp::Mod, + _ => break, + }; + let start = left.span.start; + self.next(); + let right = self.parse_unary_expr()?; + let end = right.span.end; + left = Expr { + kind: ExprKind::BinOp(Box::new(left), op, Box::new(right)), + span: Span::new(&(start..end), self.file.clone()), + attributes: Vec::new(), + }; + } + + Ok(left) + } + + fn parse_unary_expr(&mut self) -> Result { + let start = self.peek_span().unwrap_or(0..0).start; + match self.peek() { + Some(Token::Not) => { + self.next(); + let expr = self.parse_unary_expr()?; + let end = expr.span.end; + Ok(Expr { + kind: ExprKind::UnOp(UnOp::Not, Box::new(expr)), + span: Span::new(&(start..end), self.file.clone()), + attributes: Vec::new(), + }) + } + Some(Token::Minus) => { + self.next(); + let expr = self.parse_unary_expr()?; + let end = expr.span.end; + Ok(Expr { + kind: ExprKind::UnOp(UnOp::Neg, Box::new(expr)), + span: Span::new(&(start..end), self.file.clone()), + attributes: Vec::new(), + }) + } + _ => self.parse_postfix_expr(), + } + } + + fn parse_postfix_expr(&mut self) -> Result { + let mut expr = self.parse_primary_expr()?; + + loop { + match self.peek() { + Some(Token::LParen) => { + // Function call + let start = expr.span.start; + self.next(); + let mut args = Vec::new(); + loop { + if matches!(self.peek(), Some(Token::RParen)) { + self.next(); + break; + } + args.push(self.parse_expr()?); + + if matches!(self.peek(), Some(Token::Comma)) { + self.next(); + } + } + let end = self.peek_span().unwrap_or(expr.span.end..expr.span.end).end; + expr = Expr { + kind: ExprKind::Call(Box::new(expr), args), + span: Span::new(&(start..end), self.file.clone()), + attributes: Vec::new(), + }; + } + Some(Token::LBracket) => { + // Index + let start = expr.span.start; + self.next(); + let index = self.parse_expr()?; + self.expect(Token::RBracket)?; + let end = self.peek_span().unwrap_or(expr.span.end..expr.span.end).end; + expr = Expr { + kind: ExprKind::Index(Box::new(expr), Box::new(index)), + span: Span::new(&(start..end), self.file.clone()), + attributes: Vec::new(), + }; + } + Some(Token::Dot) => { + // Field access + let start = expr.span.start; + self.next(); + let field = match self.next() { + Some((Token::Variable(f), _)) => f, + Some((_, span)) => { + return self.error("Expected field name".to_string(), span); + } + None => return self.error("Expected field name".to_string(), start..start), + }; + let end = self.peek_span().unwrap_or(expr.span.end..expr.span.end).end; + expr = Expr { + kind: ExprKind::Dot(Box::new(expr), field), + span: Span::new(&(start..end), self.file.clone()), + attributes: Vec::new(), + }; + } + Some(Token::OptionalChain) => { + // Optional chain + let start = expr.span.start; + self.next(); + let field = match self.next() { + Some((Token::Variable(f), _)) => f, + Some((_, span)) => { + return self.error("Expected field name".to_string(), span); + } + None => return self.error("Expected field name".to_string(), start..start), + }; + let end = self.peek_span().unwrap_or(expr.span.end..expr.span.end).end; + expr = Expr { + kind: ExprKind::OptionalChain(Some(Box::new(expr)), field), + span: Span::new(&(start..end), self.file.clone()), + attributes: Vec::new(), + }; + } + Some(Token::Unwrap) => { + // Early return / unwrap + let start = expr.span.start; + self.next(); + let end = self.peek_span().unwrap_or(expr.span.end..expr.span.end).end; + expr = Expr { + kind: ExprKind::EarlyReturn(Some(Box::new(expr))), + span: Span::new(&(start..end), self.file.clone()), + attributes: Vec::new(), + }; + } + Some(Token::KeywordAs) => { + // Cast + let start = expr.span.start; + self.next(); + let type_annot = self.parse_type_annot()?; + let end = self.peek_span().unwrap_or(expr.span.end..expr.span.end).end; + expr = Expr { + kind: ExprKind::Cast(Box::new(expr), type_annot), + span: Span::new(&(start..end), self.file.clone()), + attributes: Vec::new(), + }; + } + _ => break, + } + } + + Ok(expr) + } + + fn parse_primary_expr(&mut self) -> Result { + let start = self.peek_span().unwrap_or(0..0).start; + match self.peek().cloned() { + Some(Token::Int(n)) => { + self.next(); + let end = self.peek_span().unwrap_or(start..start).end; + Ok(Expr { + kind: ExprKind::Int(n), + span: Span::new(&(start..end), self.file.clone()), + attributes: Vec::new(), + }) + } + Some(Token::Float(f)) => { + self.next(); + let end = self.peek_span().unwrap_or(start..start).end; + Ok(Expr { + kind: ExprKind::Float(f), + span: Span::new(&(start..end), self.file.clone()), + attributes: Vec::new(), + }) + } + Some(Token::Bool(b)) => { + self.next(); + let end = self.peek_span().unwrap_or(start..start).end; + Ok(Expr { + kind: ExprKind::Bool(b), + span: Span::new(&(start..end), self.file.clone()), + attributes: Vec::new(), + }) + } + Some(Token::String(s)) => { + self.next(); + let end = self.peek_span().unwrap_or(start..start).end; + Ok(Expr { + kind: ExprKind::String(s), + span: Span::new(&(start..end), self.file.clone()), + attributes: Vec::new(), + }) + } + Some(Token::Variable(name)) => { + self.next(); + + // Check for struct literal or enum variant + if matches!(self.peek(), Some(Token::LBrace)) { + // Struct literal + self.next(); + let mut fields = Vec::new(); + loop { + if matches!(self.peek(), Some(Token::RBrace)) { + self.next(); + break; + } + + let field_name = match self.next() { + Some((Token::Variable(f), _)) => f, + Some((_, span)) => { + return self.error("Expected field name".to_string(), span); + } + None => { + return self.error("Expected field name".to_string(), start..start); + } + }; + + self.expect(Token::Colon)?; + let field_expr = self.parse_expr()?; + fields.push((field_name, field_expr)); + + if matches!(self.peek(), Some(Token::Comma)) { + self.next(); + } + } + + let end = self.peek_span().unwrap_or(start..start).end; + Ok(Expr { + kind: ExprKind::StructLit(name, fields), + span: Span::new(&(start..end), self.file.clone()), + attributes: Vec::new(), + }) + } else if matches!(self.peek(), Some(Token::Access)) { + // Enum variant + self.next(); + let variant = match self.next() { + Some((Token::Variable(v), _)) => v, + Some((_, span)) => { + return self.error("Expected variant name".to_string(), span); + } + None => { + return self.error("Expected variant name".to_string(), start..start); + } + }; + + let mut args = Vec::new(); + if matches!(self.peek(), Some(Token::LParen)) { + self.next(); + loop { + if matches!(self.peek(), Some(Token::RParen)) { + self.next(); + break; + } + args.push(self.parse_expr()?); + + if matches!(self.peek(), Some(Token::Comma)) { + self.next(); + } + } + } + + let end = self.peek_span().unwrap_or(start..start).end; + Ok(Expr { + kind: ExprKind::EnumLit(name, variant, args), + span: Span::new(&(start..end), self.file.clone()), + attributes: Vec::new(), + }) + } else { + let end = self.peek_span().unwrap_or(start..start).end; + Ok(Expr { + kind: ExprKind::Variable(name), + span: Span::new(&(start..end), self.file.clone()), + attributes: Vec::new(), + }) + } + } + Some(Token::LParen) => { + self.next(); + if matches!(self.peek(), Some(Token::RParen)) { + // Empty tuple + self.next(); + let end = self.peek_span().unwrap_or(start..start).end; + Ok(Expr { + kind: ExprKind::Tuple(vec![]), + span: Span::new(&(start..end), self.file.clone()), + attributes: Vec::new(), + }) + } else { + let first = self.parse_expr()?; + if matches!(self.peek(), Some(Token::Comma)) { + // Tuple + let mut elements = vec![first]; + self.next(); + loop { + if matches!(self.peek(), Some(Token::RParen)) { + break; + } + elements.push(self.parse_expr()?); + + if matches!(self.peek(), Some(Token::Comma)) { + self.next(); + } + } + self.expect(Token::RParen)?; + let end = self.peek_span().unwrap_or(start..start).end; + Ok(Expr { + kind: ExprKind::Tuple(elements), + span: Span::new(&(start..end), self.file.clone()), + attributes: Vec::new(), + }) + } else { + self.expect(Token::RParen)?; + let end = self.peek_span().unwrap_or(start..start).end; + Ok(Expr { + kind: first.kind, + span: Span::new(&(start..end), self.file.clone()), + attributes: first.attributes, + }) + } + } + } + Some(Token::LBracket) => { + self.next(); + let mut elements = Vec::new(); + loop { + if matches!(self.peek(), Some(Token::RBracket)) { + self.next(); + break; + } + elements.push(self.parse_expr()?); + + if matches!(self.peek(), Some(Token::Comma)) { + self.next(); + } + } + + let end = self.peek_span().unwrap_or(start..start).end; + Ok(Expr { + kind: ExprKind::Array(elements), + span: Span::new(&(start..end), self.file.clone()), + attributes: Vec::new(), + }) + } + Some(Token::KeywordLet) => { + self.next(); + + // Parse binding kind (mut, uniq, once) - comes AFTER let + let binding_kind = match self.peek() { + Some(Token::KeywordMut) => { + self.next(); + BindingKind::Mutable + } + Some(Token::KeywordUniq) => { + self.next(); + BindingKind::Affine + } + Some(Token::KeywordOnce) => { + self.next(); + BindingKind::Linear + } + _ => BindingKind::Default, + }; + + // Now parse the variable name + let var_name = match self.next() { + Some((Token::Variable(n), _)) => n, + Some((_, span)) => { + return self.error("Expected variable name".to_string(), span); + } + None => return self.error("Expected variable name".to_string(), start..start), + }; + + // Parse optional type annotation + let type_annot = if matches!(self.peek(), Some(Token::Colon)) { + self.next(); + Some(self.parse_type_annot()?) + } else { + None + }; + + self.expect(Token::Assign)?; + let expr = self.parse_expr()?; + let end = expr.span.end; + Ok(Expr { + kind: ExprKind::Let(var_name, binding_kind, type_annot, Box::new(expr)), + span: Span::new(&(start..end), self.file.clone()), + attributes: Vec::new(), + }) + } + Some(Token::KeywordIf) => { + self.next(); + let cond = self.parse_expr()?; + let then_expr = self.parse_expr()?; + let else_expr = if matches!(self.peek(), Some(Token::KeywordElse)) { + self.next(); + Some(Box::new(self.parse_expr()?)) + } else { + None + }; + + let end = else_expr + .as_ref() + .map(|e| e.span.end) + .unwrap_or(then_expr.span.end); + + Ok(Expr { + kind: ExprKind::If(Box::new(cond), Box::new(then_expr), else_expr), + span: Span::new(&(start..end), self.file.clone()), + attributes: Vec::new(), + }) + } + Some(Token::KeywordMatch) => { + self.next(); + let expr = self.parse_expr()?; + let mut arms = Vec::new(); + + loop { + if matches!(self.peek(), Some(Token::KeywordEnd)) { + self.next(); + break; + } + + let pattern = self.parse_pattern()?; + self.expect(Token::FatArrow)?; + let body = self.parse_expr()?; + arms.push((pattern, body)); + + if matches!(self.peek(), Some(Token::Comma)) { + self.next(); + } + } + + let end = self.peek_span().unwrap_or(start..start).end; + Ok(Expr { + kind: ExprKind::Match(Box::new(expr), arms), + span: Span::new(&(start..end), self.file.clone()), + attributes: Vec::new(), + }) + } + Some(Token::KeywordWhile) => { + self.next(); + let cond = self.parse_expr()?; + let body = self.parse_expr()?; + let end = body.span.end; + + Ok(Expr { + kind: ExprKind::While(Box::new(cond), Box::new(body)), + span: Span::new(&(start..end), self.file.clone()), + attributes: Vec::new(), + }) + } + Some(Token::KeywordFor) => { + self.next(); + let var = match self.next() { + Some((Token::Variable(v), _)) => v, + Some((_, span)) => { + return self.error("Expected variable name".to_string(), span); + } + None => return self.error("Expected variable name".to_string(), start..start), + }; + self.expect(Token::KeywordIn)?; + let iterable = self.parse_expr()?; + let body = self.parse_expr()?; + let end = body.span.end; + + Ok(Expr { + kind: ExprKind::For(var, Box::new(iterable), Box::new(body)), + span: Span::new(&(start..end), self.file.clone()), + attributes: Vec::new(), + }) + } + Some(Token::KeywordDo) => { + self.next(); + let mut exprs = Vec::new(); + + loop { + if matches!(self.peek(), Some(Token::KeywordEnd)) { + self.next(); + break; + } + exprs.push(self.parse_expr()?); + + if matches!(self.peek(), Some(Token::Semicolon)) { + self.next(); + } + } + + let end = self.peek_span().unwrap_or(start..start).end; + Ok(Expr { + kind: ExprKind::Do(exprs), + span: Span::new(&(start..end), self.file.clone()), + attributes: Vec::new(), + }) + } + Some(Token::KeywordLambda) => { + let start = self.peek_span().unwrap_or(0..0).start; + self.next(); + self.expect(Token::LParen)?; + let mut params = Vec::new(); + + loop { + if matches!(self.peek(), Some(Token::RParen)) { + self.next(); + break; + } + + let param_name = match self.next() { + Some((Token::Variable(p), _)) => p, + Some((_, span)) => { + return self.error("Expected parameter name".to_string(), span); + } + None => { + return self.error("Expected parameter name".to_string(), start..start); + } + }; + + // Check for optional type annotation + let param_type = if matches!(self.peek(), Some(Token::Colon)) { + self.next(); // consume ':' + Some(self.parse_type_annot()?) + } else { + None + }; + + params.push((param_name, param_type)); + + if matches!(self.peek(), Some(Token::Comma)) { + self.next(); + } + } + + let body = self.parse_expr()?; + let end = body.span.end; + + Ok(Expr { + kind: ExprKind::Lambda(params, Box::new(body)), + span: Span::new(&(start..end), self.file.clone()), + attributes: Vec::new(), + }) + } + Some(Token::KeywordReturn) => { + self.next(); + let expr = if self.is_expr_end() { + None + } else { + Some(Box::new(self.parse_expr()?)) + }; + + let end = expr + .as_ref() + .map(|e| e.span.end) + .unwrap_or(self.peek_span().unwrap_or(start..start).end); + + Ok(Expr { + kind: ExprKind::Return(expr), + span: Span::new(&(start..end), self.file.clone()), + attributes: Vec::new(), + }) + } + Some(Token::KeywordBreak) => { + self.next(); + let end = self.peek_span().unwrap_or(start..start).end; + Ok(Expr { + kind: ExprKind::Break, + span: Span::new(&(start..end), self.file.clone()), + attributes: Vec::new(), + }) + } + Some(Token::KeywordContinue) => { + self.next(); + let end = self.peek_span().unwrap_or(start..start).end; + Ok(Expr { + kind: ExprKind::Continue, + span: Span::new(&(start..end), self.file.clone()), + attributes: Vec::new(), + }) + } + Some(token) => { + let span = self.peek_span().unwrap_or(start..start); + self.error(format!("Unexpected token: {:?}", token), span) + } + None => self.error("Unexpected EOF".to_string(), start..start), + } + } + + fn parse_pattern(&mut self) -> Result { + let start = self.peek_span().unwrap_or(0..0).start; + + match self.peek().cloned() { + Some(Token::Variable(name)) => { + self.next(); + let end = self.peek_span().unwrap_or(start..start).end; + + // Check for struct or enum pattern + if matches!(self.peek(), Some(Token::LBrace)) { + // Struct pattern + self.next(); + let mut fields = Vec::new(); + + loop { + if matches!(self.peek(), Some(Token::RBrace)) { + self.next(); + break; + } + + let field_name = match self.next() { + Some((Token::Variable(f), _)) => f, + Some((_, span)) => { + return self.error("Expected field name".to_string(), span); + } + None => { + return self.error("Expected field name".to_string(), start..start); + } + }; + + self.expect(Token::Colon)?; + let pattern = self.parse_pattern()?; + fields.push((field_name, pattern)); + + if matches!(self.peek(), Some(Token::Comma)) { + self.next(); + } + } + + let end = self.peek_span().unwrap_or(start..start).end; + Ok(Pattern { + kind: PatternKind::Struct(name, fields), + span: Span::new(&(start..end), self.file.clone()), + }) + } else if matches!(self.peek(), Some(Token::Access)) { + // Enum pattern + self.next(); + let variant = match self.next() { + Some((Token::Variable(v), _)) => v, + Some((_, span)) => { + return self.error("Expected variant name".to_string(), span); + } + None => { + return self.error("Expected variant name".to_string(), start..start); + } + }; + + let mut patterns = Vec::new(); + if matches!(self.peek(), Some(Token::LParen)) { + self.next(); + loop { + if matches!(self.peek(), Some(Token::RParen)) { + self.next(); + break; + } + patterns.push(self.parse_pattern()?); + + if matches!(self.peek(), Some(Token::Comma)) { + self.next(); + } + } + } + + let end = self.peek_span().unwrap_or(start..start).end; + Ok(Pattern { + kind: PatternKind::Enum(name, variant, patterns), + span: Span::new(&(start..end), self.file.clone()), + }) + } else { + Ok(Pattern { + kind: PatternKind::Variable(name), + span: Span::new(&(start..end), self.file.clone()), + }) + } + } + Some(Token::Union) => { + self.next(); + let end = self.peek_span().unwrap_or(start..start).end; + Ok(Pattern { + kind: PatternKind::Wildcard, + span: Span::new(&(start..end), self.file.clone()), + }) + } + Some(Token::LParen) => { + self.next(); + let mut patterns = Vec::new(); + loop { + if matches!(self.peek(), Some(Token::RParen)) { + self.next(); + break; + } + patterns.push(self.parse_pattern()?); + + if matches!(self.peek(), Some(Token::Comma)) { + self.next(); + } + } + + let end = self.peek_span().unwrap_or(start..start).end; + Ok(Pattern { + kind: PatternKind::Tuple(patterns), + span: Span::new(&(start..end), self.file.clone()), + }) + } + Some(Token::String(s)) => { + self.next(); + let end = self.peek_span().unwrap_or(start..start).end; + Ok(Pattern { + kind: PatternKind::Literal(s), + span: Span::new(&(start..end), self.file.clone()), + }) + } + Some(Token::Int(n)) => { + self.next(); + let end = self.peek_span().unwrap_or(start..start).end; + Ok(Pattern { + kind: PatternKind::Literal(n.to_string()), + span: Span::new(&(start..end), self.file.clone()), + }) + } + Some(Token::Float(f)) => { + self.next(); + let end = self.peek_span().unwrap_or(start..start).end; + Ok(Pattern { + kind: PatternKind::Literal(f.to_string()), + span: Span::new(&(start..end), self.file.clone()), + }) + } + Some(Token::Bool(b)) => { + self.next(); + let end = self.peek_span().unwrap_or(start..start).end; + Ok(Pattern { + kind: PatternKind::Literal(b.to_string()), + span: Span::new(&(start..end), self.file.clone()), + }) + } + Some(token) => { + let span = self.peek_span().unwrap_or(start..start); + self.error(format!("Unexpected token in pattern: {:?}", token), span) + } + None => self.error("Unexpected EOF".to_string(), start..start), + } + } + + fn is_expr_end(&mut self) -> bool { + matches!( + self.peek(), + Some(Token::RParen) + | Some(Token::RBracket) + | Some(Token::RBrace) + | Some(Token::Comma) + | Some(Token::Semicolon) + | Some(Token::KeywordEnd) + | Some(Token::FatArrow) + ) + } +} diff --git a/src/typechecker.rs b/src/typechecker.rs new file mode 100644 index 0000000..b79577c --- /dev/null +++ b/src/typechecker.rs @@ -0,0 +1,4 @@ +#[derive(Debug, Clone, PartialEq)] +pub enum Type { + Stub, +} diff --git a/syntax_test.sui b/syntax_test.sui new file mode 100644 index 0000000..2ae3334 --- /dev/null +++ b/syntax_test.sui @@ -0,0 +1,452 @@ +use "std/abc" +load "math.so" as math_lib + +extern print(msg: string) -> int from libc # this is a comment +extern malloc(size: int) -> int from libc #* this is a comment too *# +extern add_numbers(a: int, b: int) -> int from math_lib + +# this is a funny comment +struct Point + x: int, + y: int +end + +struct Generic + value: T, + tag: string +end + +struct Complex + first: T, + second: U +end + +struct EmptyStruct +end + +struct ArrayStruct + numbers: [int], + matrix: [[float]] +end + +enum Option + Some(T), + None +end + +enum Result + Ok(T), + Err(E) +end + +enum Status + Active, + Inactive, + Pending(string) +end + +enum Tree + Leaf(T), + Branch(T, T) +end + +trait Show + fn display(msg: string) -> string, + fn to_string() -> string +end + +trait Comparable + fn compare(other: T) -> int +end + +trait Container + fn push(item: T) -> int, + fn pop() -> T +end + +impl Point + fn new(x: int, y: int) -> Point + Point { x: x, y: y } + + fn distance() -> float do + let x_sq = 5 * 5 + let y_sq = 3 * 3 + ((x_sq + y_sq) as float) + end + + fn move_by(dx: int, dy: int) -> Point do + let new_x = 5 + 10 + let new_y = 3 + 20 + Point { x: new_x, y: new_y } + end +end + +impl Option : Show + fn display(msg: string) -> string + "Option value" + + fn to_string() -> string + "option" +end + +impl Result + fn is_ok() -> bool + true + + fn unwrap() -> string + "unwrapped" +end + + +fn greet(name: string) -> string + "Hello, " + name + +fn get_answer() -> int + 42 + +fn print_number(num: int) + num + +fn add(a: int, b: int) -> int + a + b + +fn identity(value: T) -> T + value + +fn pair(first: T, second: U) -> (T, U) + (first, second) + +fn array_func(arr: [int]) -> [int] + arr + +fn process(item: T) -> string + "processed" + +fn process_list(items: int) -> int do + let result = 0 + result + items +end + + +fn literals() -> bool do + let int_val = 42 + let float_val = 3.14 + let negative = -100 + let scientific = 1.5e-10 + let bool_true = true + let bool_false = false + let string_val = "hello world" + let empty_string = "" + true +end + + +fn collections() -> bool do + let arr = [1, 2, 3, 4, 5] + let empty_arr = [] + let tuple = (1, "hello", 3.14) + let single_tuple = (42) + true +end + + +fn struct_enum_literals() -> bool do + let point = Point { x: 10, y: 20 } + let option_val = Option::Some(42) + let option_none = Option::None + let result_ok = Result::Ok("success") + let result_err = Result::Err(404) + let status = Status::Pending("loading...") + true +end + + +fn arithmetic_logic() -> int do + let a = 10 + let b = 5 + let add_result = a + b + let sub_result = a - b + let mul_result = a * b + let div_result = a / b + let mod_result = a % b + + let and_result = true and false + let or_result = true or false + let not_result = not true + + let eq = a == b + let neq = a != b + let lt = a < b + let gt = a > b + let leq = a <= b + let geq = a >= b + + add_result + sub_result +end + + +fn unary() -> int do + let a = 5 + let neg = -a + let b = true + let not_b = not b + a + 1 +end + + +fn bindings() -> int do + let x = 10 + let mut y = 20 + let uniq z = 30 + let once w = 40 + + let typed: int = 100 + let mut_typed: string = "hello" + + x + 5 +end + + +fn if_else(n: int) -> int + if n > 0 + 100 + else + -100 + +fn if_else_complex(a: int, b: int) -> string + if a > b + "a is greater" + else + if a == b + "equal" + else + "b is greater" + + +fn match_simple(opt: Option) -> int + match opt + Option::Some(x) => x, + Option::None => 0 + end + +fn match_complex(val: int) -> string + match val + 0 => "zero", + 1 => "one", + 2 => "two", + _ => "many" + end + +fn match_pattern(p: Point) -> string + match p + Point { x: 0, y: 0 } => "origin", + Point { x: x, y: y } => "point" + end + + +fn while_loop(n: int) -> int do + let mut count = 0 + while count < n + do + count = count + 1 + end +end + + +fn do_block() -> int + do + let a = 10 + let b = 20 + let c = 30 + a + b + c + end + +fn nested_do() -> int + do + let x = do + 5 + end + let y = do + 10 + end + x + y + end + + +fn function_calls() -> int do + let point = Point { x: 5, y: 10 } + let x_coord = point.x + let arr = [1, 2, 3] + let first = arr[0] + + let result = add(10, 20) + let identity_val = identity(42) + + result + first +end + + +fn optional_chain(opt: Option) -> int do + let val = opt?.x + 100 +end + + +fn early_return() -> int do + let opt = Option::Some(42) + let val = opt? + val +end + + +fn casting() -> float do + let int_val = 42 + let float_val = (int_val as float) + let x = (100 as float) + 3.14 + x +end + +fn lambda_example() -> int do + let add_one = lambda (x) x + 1 + let multiply = lambda (x, y) x * y + let get_five = lambda () 5 + + let applied = add_one(10) + applied +end + +fn assignment() -> int do + let mut x = 10 + x = 20 + x = x + 5 + x +end + +fn with_return(n: int) -> int do + if n < 0 + return -1 + n + 100 +end + +fn with_break() -> int do + let mut i = 0 + while i < 10 + do + if i == 5 + break + i = i + 1 + end + i +end + +fn with_continue() -> int do + let mut sum = 0 + let mut i = 0 + while i < 10 + do + i = i + 1 + if i % 2 == 0 + continue + sum = sum + i + end + sum +end + + +@deprecated("use new_func instead") +fn old_func() -> int + 42 + +@optimize(level = aggressive) +fn fast_func() -> int + 100 + +@test +fn test_something() -> bool + true + +fn fibonacci(n: int) -> int + if n <= 1 + n + else + fibonacci(n - 1) + fibonacci(n - 2) + +fn factorial(n: int) -> int do + let mut result = 1 + let mut i = 2 + while i <= n + do + result = result * i + i = i + 1 + end + result +end + +fn map_over_option(opt: Option) -> Option + match opt + Option::Some(x) => Option::Some(x), + Option::None => Option::None + end + +fn process_result(res: Result) -> int + match res + Result::Ok(x) => 1, + Result::Err(e) => 0 + end + +struct LinkedList + value: T, + next: Option +end + +impl LinkedList + fn new(v: int) -> LinkedList + LinkedList { value: 42, next: Option::None } + + fn head() -> int + 100 + + fn tail() -> Option + Option::None + + fn sum() -> int + do + let mut total = 0 + total + end +end + +fn complex_pattern_match(val: int) -> string + match val + 0 => "zero", + 1 => "one", + 2 => "two", + 3 => "three", + 4 => "four", + 5 => "five", + _ => "many" + end + +fn tuple_destructure() -> int do + let tup = (10, 20, 30) + 30 +end + +fn array_ops() -> int do + let arr = [1, 2, 3, 4, 5] + let first = arr[0] + let length = 5 + first + length +end + +fn range_example() -> int do + let r = 1..10 + 5 +end + +fn for_loop_example() -> int do + let sum = 0 + for i in 0..5 + sum + i + sum +end diff --git a/tests/arrays.sui b/tests/arrays.sui new file mode 100644 index 0000000..248cfb4 --- /dev/null +++ b/tests/arrays.sui @@ -0,0 +1,7 @@ +# Array test +fn main(args: [string]) -> int do + let arr = [1, 2, 3, 4]; + let empty = []; + let x = arr[0]; + x +end diff --git a/tests/basic_types.sui b/tests/basic_types.sui new file mode 100644 index 0000000..15d1ff6 --- /dev/null +++ b/tests/basic_types.sui @@ -0,0 +1,8 @@ +# Basic type checking test +fn main() -> int do + let x = 5; + let y = 10.5; + let z = true; + let s = "hello"; + x +end diff --git a/tests/control_flow.sui b/tests/control_flow.sui new file mode 100644 index 0000000..af58199 --- /dev/null +++ b/tests/control_flow.sui @@ -0,0 +1,12 @@ +# Control flow test +fn main() -> int do + if true + 1 + else + 0 + + let i = 0; + while i < 5 + i = i + 1; + i +end diff --git a/tests/enums.sui b/tests/enums.sui new file mode 100644 index 0000000..ddd401f --- /dev/null +++ b/tests/enums.sui @@ -0,0 +1,34 @@ +# Enum test +enum Color + Red, + Green, + Blue, +end + +enum Result + Ok(T), + Err(E), +end + +struct Person + name: string, + age: int, +end + +fn main() -> int do + let color = Color::Red(); + let res = Result::Ok(42); + match res + Result::Ok(value) => value, + Result::Err(_) => 0, + end + let person = Person { name: "Bob", age: 25 }; + match color + Color::Red() => 1, + Color::Green() => 2, + Color::Blue() => 3, + end + match person + Person { name: _, age: age } => age, + end +end diff --git a/tests/functions.sui b/tests/functions.sui new file mode 100644 index 0000000..ced0ced --- /dev/null +++ b/tests/functions.sui @@ -0,0 +1,12 @@ +# Function test +fn add(x: int, y: int) -> int + x + y + +fn identity(x: T) -> T + x + +fn main() -> int do + let sum = add(5, 3); + let id = identity(42); + sum +end diff --git a/tests/structs.sui b/tests/structs.sui new file mode 100644 index 0000000..f837276 --- /dev/null +++ b/tests/structs.sui @@ -0,0 +1,16 @@ +# Struct test +struct Point + x: int, + y: int, +end + +struct Person + name: string, + age: T, +end + +fn main() -> int do + let p = Point { x: 5, y: 10 }; + let person = Person { name: "Alice", age: 30 }; + p.x +end diff --git a/tests/traits.sui b/tests/traits.sui new file mode 100644 index 0000000..e724364 --- /dev/null +++ b/tests/traits.sui @@ -0,0 +1,17 @@ +# Trait and implementation test +trait Show + fn show(self) -> string +end + +struct Number + value: int +end + +impl Number : Show + fn show(self) -> string + "number" +end + +fn main() -> int do + 42 +end