Archived
1494 lines
57 KiB
Rust
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);
|
|
}
|
|
}
|