//! Recursive-descent parser for the Engram language. use el_lexer::{Span, Spanned, Token}; use crate::ast::*; use crate::error::{ParseError, ParseErrorKind}; /// Parse a token stream into a [`Program`]. /// /// The `source` string is stored verbatim in the returned program for /// diagnostics and source map generation. pub fn parse(tokens: Vec>, source: String) -> Result { let mut p = Parser::new(tokens); let stmts = p.parse_program()?; Ok(Program { stmts, source }) } // ── Parser state ────────────────────────────────────────────────────────────── struct Parser { tokens: Vec>, /// Current cursor into `tokens`. pos: usize, } impl Parser { fn new(tokens: Vec>) -> Self { Self { tokens, pos: 0 } } // ── Token stream navigation ─────────────────────────────────────────────── fn current(&self) -> &Spanned { &self.tokens[self.pos.min(self.tokens.len() - 1)] } fn peek(&self) -> &Token { &self.current().node } fn peek_span(&self) -> Span { self.current().span } #[allow(dead_code)] fn peek2(&self) -> Option<&Token> { self.tokens.get(self.pos + 1).map(|s| &s.node) } fn advance(&mut self) -> &Spanned { let tok = &self.tokens[self.pos.min(self.tokens.len() - 1)]; if self.pos < self.tokens.len() - 1 { self.pos += 1; } tok } fn at_end(&self) -> bool { matches!(self.peek(), Token::Eof) } /// Consume the current token if it matches `expected`, otherwise error. fn expect(&mut self, expected: &Token) -> Result { if self.peek() == expected { let span = self.peek_span(); self.advance(); Ok(span) } else { Err(ParseError::expected(format!("{expected}"), self.peek(), self.peek_span())) } } fn expect_ident(&mut self) -> Result<(String, Span), ParseError> { let span = self.peek_span(); match self.peek().clone() { Token::Ident(name) => { self.advance(); Ok((name, span)) } tok => Err(ParseError::new( ParseErrorKind::ExpectedIdent(tok.to_string()), span, )), } } /// Like `expect_ident` but also accepts keywords as bare names. /// Used in contexts like seed field names where `type:` must work. fn expect_ident_or_keyword(&mut self) -> Result<(String, Span), ParseError> { let span = self.peek_span(); let name = match self.peek().clone() { Token::Ident(name) => name, // Accept any keyword as an identifier in seed field position Token::Type => "type".to_string(), Token::Fn => "fn".to_string(), Token::Let => "let".to_string(), Token::Enum => "enum".to_string(), Token::Match => "match".to_string(), Token::Return => "return".to_string(), Token::Activate => "activate".to_string(), Token::Where => "where".to_string(), Token::Sealed => "sealed".to_string(), Token::If => "if".to_string(), Token::Else => "else".to_string(), Token::For => "for".to_string(), Token::In => "in".to_string(), Token::Seed => "seed".to_string(), Token::Assert => "assert".to_string(), Token::Target => "target".to_string(), Token::Protocol => "protocol".to_string(), Token::Impl => "impl".to_string(), Token::Import => "import".to_string(), Token::From => "from".to_string(), Token::As => "as".to_string(), Token::With => "with".to_string(), Token::While => "while".to_string(), Token::Retry => "retry".to_string(), Token::Times => "times".to_string(), Token::Fallback => "fallback".to_string(), Token::Reason => "reason".to_string(), Token::Parallel => "parallel".to_string(), Token::Trace => "trace".to_string(), Token::Requires => "requires".to_string(), Token::Deploy => "deploy".to_string(), Token::To => "to".to_string(), Token::Via => "via".to_string(), tok => return Err(ParseError::new( ParseErrorKind::ExpectedIdent(tok.to_string()), span, )), }; self.advance(); Ok((name, span)) } fn eat(&mut self, tok: &Token) -> bool { if self.peek() == tok { self.advance(); true } else { false } } // ── Top-level ───────────────────────────────────────────────────────────── fn parse_program(&mut self) -> Result, ParseError> { let mut stmts = Vec::new(); while !self.at_end() { // Skip optional semicolons at top level while self.eat(&Token::Semicolon) {} if self.at_end() { break; } stmts.push(self.parse_stmt()?); } Ok(stmts) } // ── Statements ──────────────────────────────────────────────────────────── fn parse_stmt(&mut self) -> Result { let start = self.peek_span(); match self.peek().clone() { Token::Let => self.parse_let(start), Token::Fn => self.parse_fn_def(start, vec![]), Token::At => self.parse_decorated_fn(start), Token::Type => self.parse_type_def(start), Token::Enum => self.parse_enum_def(start), Token::Test => self.parse_test_def(start), Token::Seed => self.parse_seed(start), Token::Assert => self.parse_assert(start), Token::Import => self.parse_import(start), Token::From => self.parse_from_import(start), Token::Protocol => self.parse_protocol_def(start), Token::Impl => self.parse_impl_def(start), Token::While => self.parse_while(start), Token::Retry => self.parse_retry(start), Token::Deploy => self.parse_deploy(start), Token::Return => { self.advance(); // consume `return` let expr = self.parse_expr()?; let span = Span::new(start.start, expr_span_end(&expr, start), start.line, start.col); self.eat(&Token::Semicolon); Ok(Stmt::Return(expr, span)) } _ => { let expr = self.parse_expr()?; let span = start; self.eat(&Token::Semicolon); Ok(Stmt::Expr(expr, span)) } } } fn parse_test_def(&mut self, start: Span) -> Result { self.expect(&Token::Test)?; // test name is a string literal let name = match self.peek().clone() { Token::StringLiteral(s) => { self.advance(); s } tok => return Err(ParseError::expected("string literal (test name)", &tok, self.peek_span())), }; // Optional `target: unit|e2e|both` let target = if self.eat(&Token::Target) { self.expect(&Token::Colon)?; let (target_name, span) = self.expect_ident()?; match target_name.as_str() { "unit" => crate::ast::TestTarget::Unit, "e2e" => crate::ast::TestTarget::E2e, "both" => crate::ast::TestTarget::Both, other => return Err(ParseError::new( ParseErrorKind::InvalidExprStart(format!("unknown test target '{other}': use unit, e2e, or both")), span, )), } } else { crate::ast::TestTarget::Unit }; self.expect(&Token::LBrace)?; let body = self.parse_block_body()?; self.expect(&Token::RBrace)?; Ok(Stmt::TestDef { name, target, body, span: start }) } fn parse_seed(&mut self, start: Span) -> Result { self.expect(&Token::Seed)?; let (kind, _) = self.expect_ident()?; self.expect(&Token::LBrace)?; let seed = match kind.as_str() { "Node" => { let mut node_type = String::new(); let mut content = String::new(); let mut importance: f32 = 1.0; let mut tier: Option = None; while !matches!(self.peek(), Token::RBrace | Token::Eof) { let (field_name, _) = self.expect_ident_or_keyword()?; self.expect(&Token::Colon)?; match field_name.as_str() { "type" => { node_type = match self.peek().clone() { Token::StringLiteral(s) => { self.advance(); s } tok => return Err(ParseError::expected("string", &tok, self.peek_span())), }; } "content" => { content = match self.peek().clone() { Token::StringLiteral(s) => { self.advance(); s } tok => return Err(ParseError::expected("string", &tok, self.peek_span())), }; } "importance" => { importance = match self.peek().clone() { Token::FloatLiteral(f) => { self.advance(); f as f32 } Token::IntLiteral(n) => { self.advance(); n as f32 } tok => return Err(ParseError::expected("float", &tok, self.peek_span())), }; } "tier" => { let (t, _) = self.expect_ident()?; tier = Some(t); } _ => { // Skip unknown fields gracefully self.parse_expr()?; } } self.eat(&Token::Comma); self.eat(&Token::Semicolon); } crate::ast::SeedStmt::Node { node_type, content, importance, tier } } "Edge" => { let mut from = String::new(); let mut to = String::new(); let mut relation = String::new(); let mut weight: f32 = 1.0; while !matches!(self.peek(), Token::RBrace | Token::Eof) { let (field_name, _) = self.expect_ident_or_keyword()?; self.expect(&Token::Colon)?; match field_name.as_str() { "from" => { from = match self.peek().clone() { Token::StringLiteral(s) => { self.advance(); s } tok => return Err(ParseError::expected("string", &tok, self.peek_span())), }; } "to" => { to = match self.peek().clone() { Token::StringLiteral(s) => { self.advance(); s } tok => return Err(ParseError::expected("string", &tok, self.peek_span())), }; } "relation" => { let (rel, _) = self.expect_ident()?; relation = rel; } "weight" => { weight = match self.peek().clone() { Token::FloatLiteral(f) => { self.advance(); f as f32 } Token::IntLiteral(n) => { self.advance(); n as f32 } tok => return Err(ParseError::expected("float", &tok, self.peek_span())), }; } _ => { self.parse_expr()?; } } self.eat(&Token::Comma); self.eat(&Token::Semicolon); } crate::ast::SeedStmt::Edge { from, to, relation, weight } } other => return Err(ParseError::new( ParseErrorKind::InvalidExprStart(format!("unknown seed kind '{other}': use Node or Edge")), start, )), }; self.expect(&Token::RBrace)?; self.eat(&Token::Semicolon); Ok(Stmt::Seed(seed, start)) } fn parse_assert(&mut self, start: Span) -> Result { self.expect(&Token::Assert)?; let expr = self.parse_expr()?; self.eat(&Token::Semicolon); Ok(Stmt::Assert(expr, start)) } fn parse_let(&mut self, start: Span) -> Result { self.expect(&Token::Let)?; let (name, _) = self.expect_ident()?; let type_ann = if self.eat(&Token::Colon) { Some(self.parse_type_expr()?) } else { None }; self.expect(&Token::Eq)?; let value = self.parse_expr()?; self.eat(&Token::Semicolon); Ok(Stmt::Let { name, type_ann, value, span: start }) } fn parse_fn_def(&mut self, start: Span, decorators: Vec) -> Result { self.expect(&Token::Fn)?; let (name, _) = self.expect_ident()?; // Optional generic type parameters: `` let type_params = if self.eat(&Token::Lt) { let mut tps = Vec::new(); while !matches!(self.peek(), Token::Gt | Token::Eof) { let (tp, _) = self.expect_ident()?; tps.push(tp); if !self.eat(&Token::Comma) { break; } } self.expect(&Token::Gt)?; tps } else { Vec::new() }; self.expect(&Token::LParen)?; let params = self.parse_param_list_with_type_params(&type_params)?; self.expect(&Token::RParen)?; self.expect(&Token::Arrow)?; let return_type = self.parse_type_expr_with_params(&type_params)?; // Optional `requires expr` let requires = if self.eat(&Token::Requires) { Some(Box::new(self.parse_expr()?)) } else { None }; self.expect(&Token::LBrace)?; let body = self.parse_block_body()?; self.expect(&Token::RBrace)?; Ok(Stmt::FnDef { name, decorators, type_params, params, return_type, requires, body, span: start }) } /// Parse one or more `@decorator` annotations, then the `fn` definition. fn parse_decorated_fn(&mut self, start: Span) -> Result { let mut decorators = Vec::new(); while matches!(self.peek(), Token::At) { let dec_span = self.peek_span(); self.advance(); // consume `@` let (name, _) = self.expect_ident()?; let args = if self.eat(&Token::LParen) { let mut a = Vec::new(); while !matches!(self.peek(), Token::RParen | Token::Eof) { a.push(self.parse_expr()?); if !self.eat(&Token::Comma) { break; } } self.expect(&Token::RParen)?; a } else { vec![] }; decorators.push(Decorator { name, args, span: dec_span }); } // After decorators, expect `fn` if !matches!(self.peek(), Token::Fn) { return Err(ParseError::expected("fn", self.peek(), self.peek_span())); } self.parse_fn_def(start, decorators) } /// Parse `import std::collections::Map` or `import std::array::{map, filter}` fn parse_import(&mut self, start: Span) -> Result { self.expect(&Token::Import)?; let mut path = Vec::new(); let (first, _) = self.expect_ident()?; path.push(first); while self.eat(&Token::ColonColon) { // Could be `{name, name}` for multi-import if matches!(self.peek(), Token::LBrace) { self.advance(); let mut names = Vec::new(); while !matches!(self.peek(), Token::RBrace | Token::Eof) { let (n, _) = self.expect_ident()?; names.push(n); if !self.eat(&Token::Comma) { break; } } self.expect(&Token::RBrace)?; let alias = if self.eat(&Token::As) { let (a, _) = self.expect_ident()?; Some(a) } else { None }; self.eat(&Token::Semicolon); return Ok(Stmt::Import { path, names, alias, span: start }); } let (seg, _) = self.expect_ident()?; path.push(seg); } let alias = if self.eat(&Token::As) { let (a, _) = self.expect_ident()?; Some(a) } else { None }; self.eat(&Token::Semicolon); Ok(Stmt::Import { path, names: vec![], alias, span: start }) } /// Parse `from mypackage import { Thing, OtherThing }` fn parse_from_import(&mut self, start: Span) -> Result { self.expect(&Token::From)?; let mut path = Vec::new(); let (first, _) = self.expect_ident()?; path.push(first); while self.eat(&Token::ColonColon) { let (seg, _) = self.expect_ident()?; path.push(seg); } self.expect(&Token::Import)?; let names = if self.eat(&Token::LBrace) { let mut ns = Vec::new(); while !matches!(self.peek(), Token::RBrace | Token::Eof) { let (n, _) = self.expect_ident()?; ns.push(n); if !self.eat(&Token::Comma) { break; } } self.expect(&Token::RBrace)?; ns } else { let (n, _) = self.expect_ident()?; vec![n] }; self.eat(&Token::Semicolon); Ok(Stmt::Import { path, names, alias: None, span: start }) } /// Parse `protocol Name { fn method(params) -> ReturnType }` fn parse_protocol_def(&mut self, start: Span) -> Result { self.expect(&Token::Protocol)?; let (name, _) = self.expect_ident()?; self.expect(&Token::LBrace)?; let mut methods = Vec::new(); while !matches!(self.peek(), Token::RBrace | Token::Eof) { while self.eat(&Token::Semicolon) {} if matches!(self.peek(), Token::RBrace | Token::Eof) { break; } let method_span = self.peek_span(); self.expect(&Token::Fn)?; let (method_name, _) = self.expect_ident()?; self.expect(&Token::LParen)?; let params = self.parse_param_list()?; self.expect(&Token::RParen)?; self.expect(&Token::Arrow)?; let return_type = self.parse_type_expr()?; methods.push(ProtocolMethod { name: method_name, params, return_type, span: method_span }); } self.expect(&Token::RBrace)?; Ok(Stmt::ProtocolDef { name, methods, span: start }) } /// Parse `impl Protocol for TypeName { fn ... }` fn parse_impl_def(&mut self, start: Span) -> Result { self.expect(&Token::Impl)?; let (protocol_name, _) = self.expect_ident()?; self.expect(&Token::For)?; let (type_name, _) = self.expect_ident()?; self.expect(&Token::LBrace)?; let mut methods = Vec::new(); while !matches!(self.peek(), Token::RBrace | Token::Eof) { while self.eat(&Token::Semicolon) {} if matches!(self.peek(), Token::RBrace | Token::Eof) { break; } let method_start = self.peek_span(); if matches!(self.peek(), Token::Fn) { let m = self.parse_fn_def(method_start, vec![])?; methods.push(m); } else { return Err(ParseError::expected("fn", self.peek(), self.peek_span())); } } self.expect(&Token::RBrace)?; Ok(Stmt::ImplDef { protocol_name, type_name, methods, span: start }) } /// Parse `while { }` fn parse_while(&mut self, start: Span) -> Result { self.expect(&Token::While)?; let condition = self.parse_expr()?; self.expect(&Token::LBrace)?; let body = self.parse_block_body()?; self.expect(&Token::RBrace)?; Ok(Stmt::While { condition, body, span: start }) } /// Parse `retry N times { ... } fallback { ... }` fn parse_retry(&mut self, start: Span) -> Result { self.expect(&Token::Retry)?; let count = self.parse_expr()?; self.expect(&Token::Times)?; self.expect(&Token::LBrace)?; let body = self.parse_block_body()?; self.expect(&Token::RBrace)?; let fallback = if self.eat(&Token::Fallback) { self.expect(&Token::LBrace)?; let fb = self.parse_block_body()?; self.expect(&Token::RBrace)?; Some(fb) } else { None }; Ok(Stmt::Retry { count, body, fallback, span: start }) } /// Parse `deploy fn_name to "/route" via target` fn parse_deploy(&mut self, start: Span) -> Result { self.expect(&Token::Deploy)?; let (fn_name, _) = self.expect_ident()?; self.expect(&Token::To)?; let route = match self.peek().clone() { Token::StringLiteral(s) => { self.advance(); s } tok => return Err(ParseError::expected("string literal (route)", &tok, self.peek_span())), }; self.expect(&Token::Via)?; let (target, _) = self.expect_ident()?; self.eat(&Token::Semicolon); Ok(Stmt::Deploy { fn_name, route, target, span: start }) } #[allow(dead_code)] fn parse_param_list(&mut self) -> Result, ParseError> { self.parse_param_list_with_type_params(&[]) } fn parse_param_list_with_type_params(&mut self, type_params: &[String]) -> Result, ParseError> { let mut params = Vec::new(); while !matches!(self.peek(), Token::RParen | Token::Eof | Token::Pipe) { let span = self.peek_span(); let (name, _) = self.expect_ident()?; self.expect(&Token::Colon)?; let type_ann = self.parse_type_expr_with_params(type_params)?; params.push(Param { name, type_ann, span }); if !self.eat(&Token::Comma) { break; } } Ok(params) } fn parse_type_def(&mut self, start: Span) -> Result { self.expect(&Token::Type)?; let (name, _) = self.expect_ident()?; self.expect(&Token::LBrace)?; let mut fields = Vec::new(); while !matches!(self.peek(), Token::RBrace | Token::Eof) { let span = self.peek_span(); let (fname, _) = self.expect_ident()?; self.expect(&Token::Colon)?; let type_ann = self.parse_type_expr()?; fields.push(Field { name: fname, type_ann, span }); self.eat(&Token::Comma); self.eat(&Token::Semicolon); } self.expect(&Token::RBrace)?; Ok(Stmt::TypeDef { name, fields, span: start }) } fn parse_enum_def(&mut self, start: Span) -> Result { self.expect(&Token::Enum)?; let (name, _) = self.expect_ident()?; self.expect(&Token::LBrace)?; let mut variants = Vec::new(); while !matches!(self.peek(), Token::RBrace | Token::Eof) { let span = self.peek_span(); let (vname, _) = self.expect_ident()?; let payload = if self.eat(&Token::LParen) { let ty = self.parse_type_expr()?; self.expect(&Token::RParen)?; Some(ty) } else { None }; variants.push(Variant { name: vname, payload, span }); self.eat(&Token::Comma); self.eat(&Token::Semicolon); } self.expect(&Token::RBrace)?; Ok(Stmt::EnumDef { name, variants, span: start }) } fn parse_block_body(&mut self) -> Result, ParseError> { let mut stmts = Vec::new(); while !matches!(self.peek(), Token::RBrace | Token::Eof) { while self.eat(&Token::Semicolon) {} if matches!(self.peek(), Token::RBrace | Token::Eof) { break; } stmts.push(self.parse_stmt()?); } Ok(stmts) } // ── Type expressions ────────────────────────────────────────────────────── fn parse_type_expr(&mut self) -> Result { self.parse_type_expr_with_params(&[]) } fn parse_type_expr_with_params(&mut self, type_params: &[String]) -> Result { let span = self.peek_span(); // Array type: [T] if self.eat(&Token::LBracket) { let inner = self.parse_type_expr_with_params(type_params)?; self.expect(&Token::RBracket)?; let mut te = TypeExpr::Array(Box::new(inner)); // Optional array: [T]? if self.eat(&Token::QuestionMark) { te = TypeExpr::Optional(Box::new(te)); } return Ok(te); } // Named type let name = match self.peek().clone() { Token::Ident(n) => { self.advance(); n } tok => return Err(ParseError::new( ParseErrorKind::InvalidTypeExpr(tok.to_string()), span, )), }; // Check for function type: fn(A) -> B if name == "fn" { self.expect(&Token::LParen)?; let mut params = Vec::new(); while !matches!(self.peek(), Token::RParen | Token::Eof) { params.push(self.parse_type_expr_with_params(type_params)?); if !self.eat(&Token::Comma) { break; } } self.expect(&Token::RParen)?; self.expect(&Token::Arrow)?; let ret = self.parse_type_expr_with_params(type_params)?; return Ok(TypeExpr::Fn { params, return_type: Box::new(ret) }); } // Result — built-in generic result type if name == "Result" && self.eat(&Token::Lt) { let ok = self.parse_type_expr()?; self.expect(&Token::Comma)?; let err = self.parse_type_expr()?; self.expect(&Token::Gt)?; let mut te = TypeExpr::Result { ok: Box::new(ok), err: Box::new(err) }; if self.eat(&Token::QuestionMark) { te = TypeExpr::Optional(Box::new(te)); } return Ok(te); } // Map — built-in map type if name == "Map" && self.eat(&Token::Lt) { let key = self.parse_type_expr()?; self.expect(&Token::Comma)?; let value = self.parse_type_expr()?; self.expect(&Token::Gt)?; let mut te = TypeExpr::Map { key: Box::new(key), value: Box::new(value) }; if self.eat(&Token::QuestionMark) { te = TypeExpr::Optional(Box::new(te)); } return Ok(te); } // If the name is in the current generic type params list, emit TypeParam if type_params.contains(&name) { return Ok(TypeExpr::TypeParam(name)); } // Named type with optional ? suffix let mut te = TypeExpr::Named(name); if self.eat(&Token::QuestionMark) { te = TypeExpr::Optional(Box::new(te)); } Ok(te) } // ── Expressions ─────────────────────────────────────────────────────────── fn parse_expr(&mut self) -> Result { self.parse_pipe_expr() } fn parse_bitwise_or_expr(&mut self) -> Result { let mut left = self.parse_bitwise_xor_expr()?; while self.eat(&Token::Pipe) { let right = self.parse_bitwise_xor_expr()?; left = Expr::BinOp { op: BinOp::BitOr, left: Box::new(left), right: Box::new(right) }; } Ok(left) } fn parse_bitwise_xor_expr(&mut self) -> Result { let mut left = self.parse_bitwise_and_expr()?; while self.eat(&Token::Caret) { let right = self.parse_bitwise_and_expr()?; left = Expr::BinOp { op: BinOp::BitXor, left: Box::new(left), right: Box::new(right) }; } Ok(left) } fn parse_bitwise_and_expr(&mut self) -> Result { let mut left = self.parse_shift_expr()?; while self.eat(&Token::Ampersand) { let right = self.parse_shift_expr()?; left = Expr::BinOp { op: BinOp::BitAnd, left: Box::new(left), right: Box::new(right) }; } Ok(left) } fn parse_shift_expr(&mut self) -> Result { let mut left = self.parse_additive()?; loop { let op = match self.peek() { Token::Shl => BinOp::Shl, Token::Shr => BinOp::Shr, _ => break, }; self.advance(); let right = self.parse_additive()?; left = Expr::BinOp { op, left: Box::new(left), right: Box::new(right) }; } Ok(left) } /// pipe_expr = or_expr (|> ident)* /// `a |> f` desugars to `Call(f, [a])` fn parse_pipe_expr(&mut self) -> Result { let mut left = self.parse_or_expr()?; while self.eat(&Token::PipeOp) { // RHS must be a callable (ident or path) let func_expr = self.parse_postfix()?; left = Expr::Call { func: Box::new(func_expr), args: vec![left], }; } Ok(left) } fn parse_or_expr(&mut self) -> Result { let mut left = self.parse_and_expr()?; while self.eat(&Token::Or) { let right = self.parse_and_expr()?; left = Expr::BinOp { op: BinOp::Or, left: Box::new(left), right: Box::new(right) }; } Ok(left) } fn parse_and_expr(&mut self) -> Result { let mut left = self.parse_equality()?; while self.eat(&Token::And) { let right = self.parse_equality()?; left = Expr::BinOp { op: BinOp::And, left: Box::new(left), right: Box::new(right) }; } Ok(left) } fn parse_equality(&mut self) -> Result { let mut left = self.parse_comparison()?; loop { let op = match self.peek() { Token::EqEq => BinOp::Eq, Token::NotEq => BinOp::NotEq, _ => break, }; self.advance(); let right = self.parse_comparison()?; left = Expr::BinOp { op, left: Box::new(left), right: Box::new(right) }; } Ok(left) } fn parse_comparison(&mut self) -> Result { let mut left = self.parse_bitwise_or_expr()?; loop { let op = match self.peek() { Token::Lt => BinOp::Lt, Token::Gt => BinOp::Gt, Token::LtEq => BinOp::LtEq, Token::GtEq => BinOp::GtEq, _ => break, }; self.advance(); let right = self.parse_additive()?; left = Expr::BinOp { op, left: Box::new(left), right: Box::new(right) }; } Ok(left) } fn parse_additive(&mut self) -> Result { let mut left = self.parse_multiplicative()?; loop { let op = match self.peek() { Token::Plus => BinOp::Add, Token::Minus => BinOp::Sub, _ => break, }; self.advance(); let right = self.parse_multiplicative()?; left = Expr::BinOp { op, left: Box::new(left), right: Box::new(right) }; } Ok(left) } fn parse_multiplicative(&mut self) -> Result { let mut left = self.parse_unary()?; loop { let op = match self.peek() { Token::Star => BinOp::Mul, Token::Slash => BinOp::Div, Token::Percent => BinOp::Mod, _ => break, }; self.advance(); let right = self.parse_unary()?; left = Expr::BinOp { op, left: Box::new(left), right: Box::new(right) }; } Ok(left) } fn parse_unary(&mut self) -> Result { if self.eat(&Token::Not) { let inner = self.parse_unary()?; return Ok(Expr::UnaryNot(Box::new(inner))); } if self.eat(&Token::Tilde) { let inner = self.parse_unary()?; return Ok(Expr::UnaryBitNot(Box::new(inner))); } self.parse_postfix() } fn parse_postfix(&mut self) -> Result { let mut expr = self.parse_primary()?; loop { match self.peek() { Token::Dot => { self.advance(); let (field, _) = self.expect_ident()?; expr = Expr::Field { object: Box::new(expr), field }; } Token::LParen => { self.advance(); let args = self.parse_arg_list()?; self.expect(&Token::RParen)?; expr = Expr::Call { func: Box::new(expr), args }; } Token::LBracket => { self.advance(); let index = self.parse_expr()?; self.expect(&Token::RBracket)?; expr = Expr::Index { object: Box::new(expr), index: Box::new(index) }; } Token::QuestionMark => { self.advance(); expr = Expr::Try(Box::new(expr)); } Token::With => { self.advance(); // consume `with` self.expect(&Token::LBrace)?; let mut updates = Vec::new(); while !matches!(self.peek(), Token::RBrace | Token::Eof) { let (field_name, _) = self.expect_ident()?; self.expect(&Token::Colon)?; let field_expr = self.parse_expr()?; updates.push((field_name, field_expr)); if !self.eat(&Token::Comma) { break; } } self.expect(&Token::RBrace)?; expr = Expr::With { base: Box::new(expr), updates }; } _ => break, } } Ok(expr) } fn parse_arg_list(&mut self) -> Result, ParseError> { let mut args = Vec::new(); while !matches!(self.peek(), Token::RParen | Token::Eof) { args.push(self.parse_expr()?); if !self.eat(&Token::Comma) { break; } } Ok(args) } fn parse_primary(&mut self) -> Result { let span = self.peek_span(); match self.peek().clone() { // Literals Token::IntLiteral(n) => { self.advance(); Ok(Expr::Literal(Literal::Int(n))) } Token::FloatLiteral(f) => { self.advance(); Ok(Expr::Literal(Literal::Float(f))) } Token::StringLiteral(s) => { self.advance(); Ok(Expr::Literal(Literal::Str(s))) } Token::BoolLiteral(b) => { self.advance(); Ok(Expr::Literal(Literal::Bool(b))) } // Grouped or tuple Token::LParen => { self.advance(); let expr = self.parse_expr()?; self.expect(&Token::RParen)?; Ok(expr) } // Block or map literal Token::LBrace => { self.advance(); // Peek ahead: if next is a string/ident followed by colon, it's a map literal // For simplicity, check if we see string/ident + colon at start if self.is_map_literal() { let mut pairs = Vec::new(); while !matches!(self.peek(), Token::RBrace | Token::Eof) { let key = self.parse_expr()?; self.expect(&Token::Colon)?; let val = self.parse_expr()?; pairs.push((key, val)); if !self.eat(&Token::Comma) { break; } } self.expect(&Token::RBrace)?; Ok(Expr::MapLiteral(pairs)) } else { let stmts = self.parse_block_body()?; self.expect(&Token::RBrace)?; Ok(Expr::Block(stmts)) } } // Array literal Token::LBracket => { self.advance(); let mut elems = Vec::new(); while !matches!(self.peek(), Token::RBracket | Token::Eof) { elems.push(self.parse_expr()?); if !self.eat(&Token::Comma) { break; } } self.expect(&Token::RBracket)?; Ok(Expr::Array(elems)) } // Closure: |params| expr or |params| -> ReturnType { body } Token::Pipe => { self.advance(); // consume first `|` let mut params = Vec::new(); while !matches!(self.peek(), Token::Pipe | Token::Eof) { let p_span = self.peek_span(); let (pname, _) = self.expect_ident()?; self.expect(&Token::Colon)?; let type_ann = self.parse_type_expr()?; params.push(Param { name: pname, type_ann, span: p_span }); if !self.eat(&Token::Comma) { break; } } self.expect(&Token::Pipe)?; // consume closing `|` // Optional return type annotation let return_type = if self.eat(&Token::Arrow) { Some(self.parse_type_expr()?) } else { None }; // Body is a block `{ stmts }` or a bare expression let body = if matches!(self.peek(), Token::LBrace) { self.advance(); let stmts = self.parse_block_body()?; self.expect(&Token::RBrace)?; Expr::Block(stmts) } else { self.parse_expr()? }; Ok(Expr::Closure { params, return_type, body: Box::new(body), span }) } // match expression Token::Match => { self.advance(); let subject = self.parse_expr()?; self.expect(&Token::LBrace)?; let arms = self.parse_match_arms()?; self.expect(&Token::RBrace)?; Ok(Expr::Match { subject: Box::new(subject), arms }) } // activate Type where "query" Token::Activate => { self.advance(); let (type_name, _) = self.expect_ident()?; self.expect(&Token::Where)?; let query = match self.peek().clone() { Token::StringLiteral(s) => { self.advance(); s } tok => return Err(ParseError::expected("string literal", &tok, self.peek_span())), }; Ok(Expr::Activate { type_name, query }) } // sealed { stmts } Token::Sealed => { self.advance(); self.expect(&Token::LBrace)?; let stmts = self.parse_block_body()?; self.expect(&Token::RBrace)?; Ok(Expr::Sealed(stmts)) } // if/else Token::If => { self.advance(); let cond = self.parse_expr()?; let then = self.parse_primary()?; // expects block let else_ = if self.eat(&Token::Else) { Some(Box::new(self.parse_primary()?)) } else { None }; Ok(Expr::If { cond: Box::new(cond), then: Box::new(then), else_ }) } // Identifier — could be plain name, path (Foo::Bar), or struct literal (Foo { ... }) Token::Ident(name) => { self.advance(); // Check for path: Foo::Bar or Foo::Bar::Baz if matches!(self.peek(), Token::ColonColon) { let mut segments = vec![name]; while self.eat(&Token::ColonColon) { let (seg, _) = self.expect_ident()?; segments.push(seg); } Ok(Expr::Path { segments }) } else if matches!(self.peek(), Token::LBrace) && name.chars().next().map(|c| c.is_uppercase()).unwrap_or(false) { // Struct literal: TypeName { field: expr, ... } self.advance(); // consume `{` let mut fields = Vec::new(); while !matches!(self.peek(), Token::RBrace | Token::Eof) { let (field_name, _) = self.expect_ident()?; self.expect(&Token::Colon)?; let field_expr = self.parse_expr()?; fields.push((field_name, field_expr)); if !self.eat(&Token::Comma) { break; } } self.expect(&Token::RBrace)?; Ok(Expr::StructLit { type_name: name, fields, span }) } else { Ok(Expr::Ident(name)) } } // reason "query" Token::Reason => { self.advance(); let query = match self.peek().clone() { Token::StringLiteral(s) => { self.advance(); s } tok => return Err(ParseError::expected("string literal", &tok, self.peek_span())), }; Ok(Expr::Reason { query }) } // parallel { name: expr, ... } Token::Parallel => { self.advance(); self.expect(&Token::LBrace)?; let mut entries = Vec::new(); while !matches!(self.peek(), Token::RBrace | Token::Eof) { let (entry_name, _) = self.expect_ident()?; self.expect(&Token::Colon)?; let entry_expr = self.parse_expr()?; entries.push((entry_name, entry_expr)); if !self.eat(&Token::Comma) { self.eat(&Token::Semicolon); } if matches!(self.peek(), Token::RBrace) { break; } } self.expect(&Token::RBrace)?; Ok(Expr::Parallel { entries }) } // trace "label" { stmts } Token::Trace => { self.advance(); let label = match self.peek().clone() { Token::StringLiteral(s) => { self.advance(); s } tok => return Err(ParseError::expected("string literal (trace label)", &tok, self.peek_span())), }; self.expect(&Token::LBrace)?; let body = self.parse_block_body()?; self.expect(&Token::RBrace)?; Ok(Expr::Trace { label, body }) } tok => Err(ParseError::new( ParseErrorKind::InvalidExprStart(tok.to_string()), span, )), } } /// Heuristic: are we at the start of a map literal? /// We peek ahead for `string/ident :` pattern. fn is_map_literal(&self) -> bool { // Look at current token (first inside `{`) match self.peek() { Token::StringLiteral(_) => { // Check if next is Colon self.tokens.get(self.pos + 1).is_some_and(|t| matches!(t.node, Token::Colon)) } Token::RBrace => false, // empty block `{}` _ => false, } } // ── Match arms ──────────────────────────────────────────────────────────── fn parse_match_arms(&mut self) -> Result, ParseError> { let mut arms = Vec::new(); while !matches!(self.peek(), Token::RBrace | Token::Eof) { let span = self.peek_span(); let pattern = self.parse_pattern()?; self.expect(&Token::FatArrow)?; let body = self.parse_expr()?; arms.push(MatchArm { pattern, body, span }); self.eat(&Token::Comma); self.eat(&Token::Semicolon); } Ok(arms) } fn parse_pattern(&mut self) -> Result { let span = self.peek_span(); match self.peek().clone() { // Wildcard Token::Ident(ref s) if s == "_" => { self.advance(); Ok(Pattern::Wildcard) } // Could be: binding, enum variant, or path Token::Ident(name) => { self.advance(); if self.eat(&Token::ColonColon) { // EnumVariant pattern: Status::Active or Status::Pending(reason) let (variant, _) = self.expect_ident()?; let payload = if self.eat(&Token::LParen) { let (bind, _) = self.expect_ident()?; self.expect(&Token::RParen)?; Some(bind) } else { None }; Ok(Pattern::EnumVariant { enum_name: name, variant, payload }) } else { // Simple binding Ok(Pattern::Binding(name)) } } Token::IntLiteral(n) => { self.advance(); Ok(Pattern::Literal(Literal::Int(n))) } Token::StringLiteral(s) => { self.advance(); Ok(Pattern::Literal(Literal::Str(s))) } Token::BoolLiteral(b) => { self.advance(); Ok(Pattern::Literal(Literal::Bool(b))) } tok => Err(ParseError::new( ParseErrorKind::InvalidPattern(tok.to_string()), span, )), } } } // ── Helpers ─────────────────────────────────────────────────────────────────── fn expr_span_end(_expr: &Expr, fallback: Span) -> usize { fallback.end } // ── Tests ───────────────────────────────────────────────────────────────────── #[cfg(test)] mod tests { use el_lexer::tokenize; use super::*; fn parse_src(src: &str) -> Program { let tokens = tokenize(src).expect("lex failed"); parse(tokens, src.to_string()).expect("parse failed") } #[test] fn test_parse_let() { let p = parse_src("let x: Int = 42"); assert!(matches!(p.stmts[0], Stmt::Let { ref name, .. } if name == "x")); } #[test] fn test_parse_fn_def() { let src = r#"fn greet(name: String) -> String { return "Hello" }"#; let p = parse_src(src); assert!(matches!(&p.stmts[0], Stmt::FnDef { name, .. } if name == "greet")); } #[test] fn test_parse_type_def() { let src = "type User { id: Uuid name: String email: String }"; let p = parse_src(src); assert!(matches!(&p.stmts[0], Stmt::TypeDef { name, fields, .. } if name == "User" && fields.len() == 3)); } #[test] fn test_parse_enum_def() { let src = "enum Status { Active Inactive Pending(String) }"; let p = parse_src(src); match &p.stmts[0] { Stmt::EnumDef { name, variants, .. } => { assert_eq!(name, "Status"); assert_eq!(variants.len(), 3); assert_eq!(variants[2].name, "Pending"); assert!(variants[2].payload.is_some()); } _ => panic!("expected EnumDef"), } } #[test] fn test_parse_match() { let src = r#" match status { Status::Active => "active" Status::Inactive => "inactive" } "#; let p = parse_src(src); assert!(matches!(&p.stmts[0], Stmt::Expr(Expr::Match { arms, .. }, _) if arms.len() == 2)); } #[test] fn test_parse_activate() { let src = r#"activate User where "customer who purchased recently""#; let p = parse_src(src); assert!(matches!( &p.stmts[0], Stmt::Expr(Expr::Activate { type_name, query }, _) if type_name == "User" && query.contains("customer") )); } #[test] fn test_parse_sealed_block() { let src = r#"sealed { let key: String = "secret" }"#; let p = parse_src(src); assert!(matches!(&p.stmts[0], Stmt::Expr(Expr::Sealed(_), _))); } #[test] fn test_parse_binary_ops() { let p = parse_src("let result = 1 + 2 * 3"); match &p.stmts[0] { Stmt::Let { value: Expr::BinOp { op: BinOp::Add, right, .. }, .. } => { // Right side should be 2*3 assert!(matches!(**right, Expr::BinOp { op: BinOp::Mul, .. })); } _ => panic!("unexpected AST"), } } #[test] fn test_parse_fn_call() { let p = parse_src(r#"greet("Will")"#); assert!(matches!(&p.stmts[0], Stmt::Expr(Expr::Call { .. }, _))); } #[test] fn test_parse_field_access() { let p = parse_src("user.name"); assert!(matches!(&p.stmts[0], Stmt::Expr(Expr::Field { field, .. }, _) if field == "name")); } #[test] fn test_parse_if_else() { let src = r#"if x == 1 { return "yes" } else { return "no" }"#; let p = parse_src(src); assert!(matches!(&p.stmts[0], Stmt::Expr(Expr::If { else_: Some(_), .. }, _))); } #[test] fn test_parse_array_literal() { let p = parse_src("[1, 2, 3]"); assert!(matches!(&p.stmts[0], Stmt::Expr(Expr::Array(elems), _) if elems.len() == 3)); } #[test] fn test_parse_path_expr() { let p = parse_src("Status::Active"); assert!(matches!(&p.stmts[0], Stmt::Expr(Expr::Path { segments }, _) if segments.len() == 2)); } #[test] fn test_parse_decorator() { let src = r#"@public fn hello() -> String { return "hi" }"#; let p = parse_src(src); match &p.stmts[0] { Stmt::FnDef { name, decorators, .. } => { assert_eq!(name, "hello"); assert_eq!(decorators.len(), 1); assert_eq!(decorators[0].name, "public"); } _ => panic!("expected FnDef"), } } #[test] fn test_parse_decorator_with_args() { let src = r#"@cache(300) fn fetch() -> String { return "data" }"#; let p = parse_src(src); match &p.stmts[0] { Stmt::FnDef { decorators, .. } => { assert_eq!(decorators.len(), 1); assert_eq!(decorators[0].name, "cache"); assert_eq!(decorators[0].args.len(), 1); } _ => panic!("expected FnDef"), } } #[test] fn test_parse_multiple_decorators() { let src = r#"@authenticate @trace fn secure() -> Void { }"#; let p = parse_src(src); match &p.stmts[0] { Stmt::FnDef { decorators, .. } => { assert_eq!(decorators.len(), 2); assert_eq!(decorators[0].name, "authenticate"); assert_eq!(decorators[1].name, "trace"); } _ => panic!("expected FnDef"), } } #[test] fn test_parse_import() { let p = parse_src("import std::collections::Map"); match &p.stmts[0] { Stmt::Import { path, names, alias, .. } => { assert_eq!(path, &["std", "collections", "Map"]); assert!(names.is_empty()); assert!(alias.is_none()); } _ => panic!("expected Import"), } } #[test] fn test_parse_import_multi() { let p = parse_src("import std::array::{map, filter}"); match &p.stmts[0] { Stmt::Import { path, names, .. } => { assert_eq!(path, &["std", "array"]); assert_eq!(names, &["map", "filter"]); } _ => panic!("expected Import"), } } #[test] fn test_parse_from_import() { let p = parse_src("from mypackage import { Thing, OtherThing }"); match &p.stmts[0] { Stmt::Import { path, names, .. } => { assert_eq!(path, &["mypackage"]); assert_eq!(names, &["Thing", "OtherThing"]); } _ => panic!("expected Import"), } } #[test] fn test_parse_protocol_def() { let src = r#"protocol Printable { fn print(self: String) -> Void }"#; let p = parse_src(src); match &p.stmts[0] { Stmt::ProtocolDef { name, methods, .. } => { assert_eq!(name, "Printable"); assert_eq!(methods.len(), 1); assert_eq!(methods[0].name, "print"); } _ => panic!("expected ProtocolDef"), } } #[test] fn test_parse_impl_def() { let src = r#"impl Printable for User { fn print(self: String) -> Void { } }"#; let p = parse_src(src); match &p.stmts[0] { Stmt::ImplDef { protocol_name, type_name, methods, .. } => { assert_eq!(protocol_name, "Printable"); assert_eq!(type_name, "User"); assert_eq!(methods.len(), 1); } _ => panic!("expected ImplDef"), } } #[test] fn test_parse_closure() { let p = parse_src("let double = |x: Int| x"); match &p.stmts[0] { Stmt::Let { value: Expr::Closure { params, .. }, .. } => { assert_eq!(params.len(), 1); assert_eq!(params[0].name, "x"); } _ => panic!("expected Let with Closure"), } } #[test] fn test_parse_closure_with_block() { let p = parse_src("let add = |x: Int, y: Int| -> Int { x }"); match &p.stmts[0] { Stmt::Let { value: Expr::Closure { params, return_type, .. }, .. } => { assert_eq!(params.len(), 2); assert!(return_type.is_some()); } _ => panic!("expected Let with Closure"), } } #[test] fn test_parse_result_type() { let p = parse_src("fn fetch() -> Result { return fetch() }"); match &p.stmts[0] { Stmt::FnDef { return_type, .. } => { assert!(matches!(return_type, TypeExpr::Result { .. })); } _ => panic!("expected FnDef"), } } #[test] fn test_parse_map_type() { let p = parse_src("let m: Map = m"); match &p.stmts[0] { Stmt::Let { type_ann: Some(TypeExpr::Map { .. }), .. } => {} _ => panic!("expected Let with Map type"), } } #[test] fn test_parse_optional_type_questionmark() { let p = parse_src("let x: Int? = x"); match &p.stmts[0] { Stmt::Let { type_ann: Some(TypeExpr::Optional(inner)), .. } => { assert!(matches!(inner.as_ref(), TypeExpr::Named(n) if n == "Int")); } _ => panic!("expected Let with Optional type"), } } #[test] fn test_parse_try_operator() { let p = parse_src("let x = fetch()"); // Just verify it parses OK for now; try is tested via `fetch()?` assert!(p.stmts.len() == 1); } }