This repository has been archived on 2026-08-20. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
el-retired/engrams/el-parser/src/parser.rs
T

1494 lines
57 KiB
Rust

//! 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<Spanned<Token>>, source: String) -> Result<Program, ParseError> {
let mut p = Parser::new(tokens);
let stmts = p.parse_program()?;
Ok(Program { stmts, source })
}
// ── Parser state ──────────────────────────────────────────────────────────────
struct Parser {
tokens: Vec<Spanned<Token>>,
/// Current cursor into `tokens`.
pos: usize,
}
impl Parser {
fn new(tokens: Vec<Spanned<Token>>) -> Self {
Self { tokens, pos: 0 }
}
// ── Token stream navigation ───────────────────────────────────────────────
fn current(&self) -> &Spanned<Token> {
&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<Token> {
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<Span, ParseError> {
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<Vec<Stmt>, 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<Stmt, ParseError> {
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<Stmt, ParseError> {
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<Stmt, ParseError> {
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<String> = 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<Stmt, ParseError> {
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<Stmt, ParseError> {
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<Decorator>) -> Result<Stmt, ParseError> {
self.expect(&Token::Fn)?;
let (name, _) = self.expect_ident()?;
// Optional generic type parameters: `<T, E>`
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<Stmt, ParseError> {
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<Stmt, ParseError> {
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<Stmt, ParseError> {
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<Stmt, ParseError> {
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<Stmt, ParseError> {
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 <condition> { <body> }`
fn parse_while(&mut self, start: Span) -> Result<Stmt, ParseError> {
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<Stmt, ParseError> {
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<Stmt, ParseError> {
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<Vec<Param>, ParseError> {
self.parse_param_list_with_type_params(&[])
}
fn parse_param_list_with_type_params(&mut self, type_params: &[String]) -> Result<Vec<Param>, 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<Stmt, ParseError> {
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<Stmt, ParseError> {
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<Vec<Stmt>, 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<TypeExpr, ParseError> {
self.parse_type_expr_with_params(&[])
}
fn parse_type_expr_with_params(&mut self, type_params: &[String]) -> Result<TypeExpr, ParseError> {
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<T, E> — 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<K, V> — 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<Expr, ParseError> {
self.parse_pipe_expr()
}
fn parse_bitwise_or_expr(&mut self) -> Result<Expr, ParseError> {
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<Expr, ParseError> {
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<Expr, ParseError> {
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<Expr, ParseError> {
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<Expr, ParseError> {
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<Expr, ParseError> {
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<Expr, ParseError> {
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<Expr, ParseError> {
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<Expr, ParseError> {
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<Expr, ParseError> {
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<Expr, ParseError> {
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<Expr, ParseError> {
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<Expr, ParseError> {
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<Vec<Expr>, 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<Expr, ParseError> {
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<Vec<MatchArm>, 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<Pattern, ParseError> {
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<String, String> { 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<String, Int> = 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);
}
}