Files
flux/fluxc/src/parser.rs
Jooris Hadeler 5bf4a494cb Feat: add structured diagnostics with yansi colors
Introduces fluxc/src/diagnostics.rs with Level (Critical, Error,
Warning, Note), Label (Primary/Secondary with optional message), and
Diagnostic types. Diagnostic::render(src, filename) produces
rustc-style output: colored header, --> file:line:col pointer, source
line with gutter, and ^ / - underlines aligned to the offending span.

Replaces the flat ParseError struct in the parser; all five error
sites now emit Diagnostic values with source-pointing labels.
2026-03-10 18:44:29 +01:00

1573 lines
50 KiB
Rust

use crate::{
ast::{
BinaryOp, Block, CompoundAssignOp, ElseBranch, Expr, ExprKind, FieldDef, FuncDef, Param,
Program, Stmt, StmtKind, StructDef, StructField, TopLevelDef, TopLevelDefKind, Type,
UnaryOp,
},
diagnostics::{Diagnostic, Label},
lexer::Lexer,
token::{Span, Token, TokenKind},
};
// ── Binding powers ─────────────────────────────────────────────────────────────
//
// Returns `(left_bp, right_bp)` for infix operators.
// left_bp < right_bp → left-associative
// left_bp > right_bp → right-associative (none here)
//
// NOTE: comparison operators (==, !=, <, >, <=, >=) are not listed in the
// GRAMMAR.ebnf precedence table but appear in examples; placed between
// bitwise-AND (50) and additive (60) at 55.
fn infix_bp(kind: TokenKind) -> Option<(u8, u8)> {
let bp = match kind {
// Assignment and compound assignment: lowest precedence, right-associative.
TokenKind::Eq
| TokenKind::PlusEq
| TokenKind::MinusEq
| TokenKind::StarEq
| TokenKind::SlashEq
| TokenKind::PercentEq
| TokenKind::AmpEq
| TokenKind::PipeEq
| TokenKind::CaretEq
| TokenKind::ShlEq
| TokenKind::ShrEq => (2, 2),
TokenKind::Or => (10, 11),
TokenKind::And => (20, 21),
TokenKind::Pipe => (30, 31),
TokenKind::Caret => (40, 41),
TokenKind::Amp => (50, 51),
TokenKind::EqEq
| TokenKind::BangEq
| TokenKind::Lt
| TokenKind::Gt
| TokenKind::LtEq
| TokenKind::GtEq => (55, 56),
TokenKind::Plus | TokenKind::Minus => (60, 61),
TokenKind::Shl | TokenKind::Shr => (65, 66),
TokenKind::Star | TokenKind::Slash | TokenKind::Percent => (70, 71),
// Postfix: `.`, `[`, `(` — handled separately in parse_led, bp listed
// here only so callers can detect them as infix/postfix operators.
TokenKind::Dot | TokenKind::LBracket | TokenKind::LParen => (90, 91),
_ => return None,
};
Some(bp)
}
// Returns the right binding power for prefix operators.
fn prefix_bp(kind: TokenKind) -> Option<u8> {
match kind {
TokenKind::Bang
| TokenKind::Tilde
| TokenKind::Minus
| TokenKind::Star
| TokenKind::Amp => Some(80),
_ => None,
}
}
fn token_to_unary_op(kind: TokenKind) -> UnaryOp {
match kind {
TokenKind::Minus => UnaryOp::Neg,
TokenKind::Bang => UnaryOp::Not,
TokenKind::Tilde => UnaryOp::BitNot,
TokenKind::Star => UnaryOp::Deref,
TokenKind::Amp => UnaryOp::AddrOf,
_ => unreachable!("not a unary op: {:?}", kind),
}
}
fn token_to_compound_assign_op(kind: TokenKind) -> Option<CompoundAssignOp> {
match kind {
TokenKind::PlusEq => Some(CompoundAssignOp::Add),
TokenKind::MinusEq => Some(CompoundAssignOp::Sub),
TokenKind::StarEq => Some(CompoundAssignOp::Mul),
TokenKind::SlashEq => Some(CompoundAssignOp::Div),
TokenKind::PercentEq => Some(CompoundAssignOp::Rem),
TokenKind::AmpEq => Some(CompoundAssignOp::BitAnd),
TokenKind::PipeEq => Some(CompoundAssignOp::BitOr),
TokenKind::CaretEq => Some(CompoundAssignOp::BitXor),
TokenKind::ShlEq => Some(CompoundAssignOp::Shl),
TokenKind::ShrEq => Some(CompoundAssignOp::Shr),
_ => None,
}
}
fn token_to_binary_op(kind: TokenKind) -> BinaryOp {
match kind {
TokenKind::Or => BinaryOp::Or,
TokenKind::And => BinaryOp::And,
TokenKind::Pipe => BinaryOp::BitOr,
TokenKind::Caret => BinaryOp::BitXor,
TokenKind::Amp => BinaryOp::BitAnd,
TokenKind::EqEq => BinaryOp::Eq,
TokenKind::BangEq => BinaryOp::Ne,
TokenKind::Lt => BinaryOp::Lt,
TokenKind::Gt => BinaryOp::Gt,
TokenKind::LtEq => BinaryOp::Le,
TokenKind::GtEq => BinaryOp::Ge,
TokenKind::Plus => BinaryOp::Add,
TokenKind::Minus => BinaryOp::Sub,
TokenKind::Star => BinaryOp::Mul,
TokenKind::Slash => BinaryOp::Div,
TokenKind::Percent => BinaryOp::Rem,
TokenKind::Shl => BinaryOp::Shl,
TokenKind::Shr => BinaryOp::Shr,
TokenKind::Eq => BinaryOp::Assign,
_ => unreachable!("not a binary op: {:?}", kind),
}
}
// ── Parser ─────────────────────────────────────────────────────────────────────
pub struct Parser<'src> {
tokens: Vec<Token<'src>>,
pos: usize,
pub errors: Vec<Diagnostic>,
}
impl<'src> Parser<'src> {
pub fn new(src: &'src str) -> Self {
let tokens = Lexer::new(src).tokenize();
Self {
tokens,
pos: 0,
errors: Vec::new(),
}
}
// ── Token access ──────────────────────────────────────────────────────────
fn current(&self) -> Token<'src> {
self.tokens[self.pos]
}
/// Advance past the current token and return it.
fn advance(&mut self) -> Token<'src> {
let tok = self.current();
if tok.kind != TokenKind::Eof {
self.pos += 1;
}
tok
}
/// Consume the current token if it matches `kind`; otherwise record an
/// error and return a zero-width dummy token at the current position
/// so that parsing can continue (missing-token insertion).
fn expect(&mut self, kind: TokenKind) -> Token<'src> {
let tok = self.current();
if tok.kind == kind {
self.advance()
} else {
self.errors.push(
Diagnostic::error(format!("expected {kind}, found {}", tok.kind)).with_label(
Label::primary(tok.span).with_message(format!("expected {kind} here")),
),
);
let span = Span::new(tok.span.start, tok.span.start);
Token::new(kind, span, "")
}
}
/// Skip tokens until we reach a natural statement boundary, so that
/// subsequent statements can still be parsed cleanly.
///
/// Stops *before* statement-starting keywords and `}` (so the caller can
/// handle them), and stops *after* consuming a `;`.
fn synchronize(&mut self) {
loop {
match self.current().kind {
// Stop before these — they begin the next statement or close a block.
TokenKind::Eof
| TokenKind::RCurly
| TokenKind::Let
| TokenKind::Return
| TokenKind::If
| TokenKind::While
| TokenKind::Loop
| TokenKind::Break
| TokenKind::Continue => break,
// Consume the `;` and stop — it terminates the current statement.
TokenKind::Semicolon => {
self.advance();
break;
}
_ => {
self.advance();
}
}
}
}
// ── Public API ────────────────────────────────────────────────────────────
/// Parse a type annotation.
pub fn parse_type(&mut self) -> Type {
let tok = self.advance();
match tok.kind {
// Primitive types
TokenKind::U8 => Type::U8,
TokenKind::U16 => Type::U16,
TokenKind::U32 => Type::U32,
TokenKind::U64 => Type::U64,
TokenKind::I8 => Type::I8,
TokenKind::I16 => Type::I16,
TokenKind::I32 => Type::I32,
TokenKind::I64 => Type::I64,
TokenKind::F32 => Type::F32,
TokenKind::F64 => Type::F64,
TokenKind::Bool => Type::Bool,
TokenKind::Char => Type::Char,
// Named type (user-defined struct, etc.)
TokenKind::Ident => Type::Named(tok.text.to_owned(), tok.span),
// Pointer: `*opaque` or `*<type>`
TokenKind::Star => {
if self.current().kind == TokenKind::Opaque {
self.advance();
Type::OpaquePointer
} else {
Type::Pointer(Box::new(self.parse_type()))
}
}
// Array: `[type; INT_LIT]`
TokenKind::LBracket => {
let elem = self.parse_type();
self.expect(TokenKind::Semicolon);
let size_tok = self.expect(TokenKind::IntLit);
self.expect(TokenKind::RBracket);
Type::Array {
elem: Box::new(elem),
size: size_tok.text.to_owned(),
}
}
// Error — insert recovery placeholder
_ => {
self.errors.push(
Diagnostic::error(format!("expected type, found {}", tok.kind))
.with_label(Label::primary(tok.span).with_message("expected a type here")),
);
Type::Error
}
}
}
/// Parse a block: `{ stmt* }`.
pub fn parse_block(&mut self) -> Block {
let open = self.expect(TokenKind::LCurly);
let mut stmts = Vec::new();
loop {
if matches!(self.current().kind, TokenKind::RCurly | TokenKind::Eof) {
break;
}
stmts.push(self.parse_stmt());
}
let close = self.expect(TokenKind::RCurly);
Block {
stmts,
span: open.span.cover(close.span),
}
}
/// Parse a single statement.
///
/// **Recovery policy**
/// - *Missing-token insertion*: `expect()` handles single missing tokens
/// (e.g. a forgotten `;`) by inserting a zero-width dummy — no tokens
/// are skipped and no error cascades.
/// - *Synchronization*: tokens that can never start a statement or
/// expression trigger `synchronize()`, which skips forward until the
/// next statement boundary to prevent cascading errors.
pub fn parse_stmt(&mut self) -> Stmt {
let tok = self.current();
match tok.kind {
TokenKind::Let => self.parse_let_stmt(),
TokenKind::Return => self.parse_return_stmt(),
TokenKind::If => self.parse_if_stmt(),
TokenKind::While => self.parse_while_stmt(),
TokenKind::Loop => self.parse_loop_stmt(),
TokenKind::Break => {
let kw = self.advance();
let semi = self.expect(TokenKind::Semicolon);
Stmt {
kind: StmtKind::Break,
span: kw.span.cover(semi.span),
}
}
TokenKind::Continue => {
let kw = self.advance();
let semi = self.expect(TokenKind::Semicolon);
Stmt {
kind: StmtKind::Continue,
span: kw.span.cover(semi.span),
}
}
TokenKind::LCurly => {
let block = self.parse_block();
let span = block.span;
Stmt {
kind: StmtKind::Block(block),
span,
}
}
// Tokens that cannot start any statement or expression.
// Synchronize to prevent cascading errors.
TokenKind::RCurly
| TokenKind::RParen
| TokenKind::RBracket
| TokenKind::Else
| TokenKind::Comma
| TokenKind::Arrow
| TokenKind::Fn
| TokenKind::Struct
| TokenKind::Eof => {
self.errors.push(
Diagnostic::error(format!("unexpected {} in statement position", tok.kind))
.with_label(Label::primary(tok.span)),
);
self.synchronize();
Stmt {
kind: StmtKind::Error,
span: tok.span,
}
}
// Anything else is an expression statement.
_ => self.parse_expr_stmt(),
}
}
/// Parse a single expression.
///
/// `allow_struct_literals` controls whether a bare `Ident { … }` is
/// parsed as a struct literal. Pass `false` in `if`/`while` conditions
/// so that `{` is not consumed as a struct body.
pub fn parse_expr(&mut self, allow_struct_literals: bool) -> Expr {
self.pratt(0, allow_struct_literals)
}
// ── Statement helpers ─────────────────────────────────────────────────────
fn parse_let_stmt(&mut self) -> Stmt {
let start = self.advance(); // consume `let`
let mutable = if self.current().kind == TokenKind::Mut {
self.advance();
true
} else {
false
};
let name_tok = self.expect(TokenKind::Ident);
let ty = if self.current().kind == TokenKind::Colon {
self.advance();
Some(self.parse_type())
} else {
None
};
let init = if self.current().kind == TokenKind::Eq {
self.advance();
Some(self.parse_expr(true))
} else {
None
};
let semi = self.expect(TokenKind::Semicolon);
Stmt {
kind: StmtKind::Let {
mutable,
name: name_tok.text.to_owned(),
name_span: name_tok.span,
ty,
init,
},
span: start.span.cover(semi.span),
}
}
fn parse_return_stmt(&mut self) -> Stmt {
let kw = self.advance(); // consume `return`
// LL(1): `;` → unit return; anything else → parse expression
let value = if self.current().kind != TokenKind::Semicolon {
Some(self.parse_expr(true))
} else {
None
};
let semi = self.expect(TokenKind::Semicolon);
Stmt {
kind: StmtKind::Return(value),
span: kw.span.cover(semi.span),
}
}
fn parse_if_stmt(&mut self) -> Stmt {
let kw = self.advance(); // consume `if`
// Condition: expr_ns (no struct literals at outermost level)
let cond = self.parse_expr(false);
let then_block = self.parse_block();
let else_branch = if self.current().kind == TokenKind::Else {
self.advance(); // consume `else`
if self.current().kind == TokenKind::If {
let nested = self.parse_if_stmt();
Some(ElseBranch::If(Box::new(nested)))
} else {
Some(ElseBranch::Block(self.parse_block()))
}
} else {
None
};
let end_span = match &else_branch {
Some(ElseBranch::If(s)) => s.span,
Some(ElseBranch::Block(b)) => b.span,
None => then_block.span,
};
Stmt {
kind: StmtKind::If {
cond,
then_block,
else_branch,
},
span: kw.span.cover(end_span),
}
}
fn parse_while_stmt(&mut self) -> Stmt {
let kw = self.advance(); // consume `while`
let cond = self.parse_expr(false); // no struct literals in condition
let body = self.parse_block();
let span = kw.span.cover(body.span);
Stmt {
kind: StmtKind::While { cond, body },
span,
}
}
fn parse_loop_stmt(&mut self) -> Stmt {
let kw = self.advance(); // consume `loop`
let body = self.parse_block();
let span = kw.span.cover(body.span);
Stmt {
kind: StmtKind::Loop { body },
span,
}
}
fn parse_expr_stmt(&mut self) -> Stmt {
let expr = self.parse_expr(true);
let semi = self.expect(TokenKind::Semicolon);
let span = expr.span.cover(semi.span);
Stmt {
kind: StmtKind::Expr(expr),
span,
}
}
// ── Pratt core ────────────────────────────────────────────────────────────
fn pratt(&mut self, min_bp: u8, allow_struct_lit: bool) -> Expr {
let mut lhs = self.parse_nud(allow_struct_lit);
loop {
let op_tok = self.current();
// Struct literal: `Ident {` — only when the flag is set, and only
// when the lhs is a bare identifier.
if allow_struct_lit
&& op_tok.kind == TokenKind::LCurly
&& matches!(lhs.kind, ExprKind::Ident(_))
&& min_bp == 0
{
lhs = self.parse_struct_lit(lhs);
continue;
}
let (l_bp, r_bp) = match infix_bp(op_tok.kind) {
Some(bp) => bp,
None => break,
};
if l_bp < min_bp {
break;
}
lhs = self.parse_led(lhs, op_tok, r_bp, allow_struct_lit);
}
lhs
}
// ── Null denotation (prefix / primary) ───────────────────────────────────
fn parse_nud(&mut self, allow_struct_lit: bool) -> Expr {
let tok = self.advance();
match tok.kind {
// Literals
TokenKind::IntLit => Expr::new(ExprKind::IntLit(tok.text.to_owned()), tok.span),
TokenKind::FloatLit => Expr::new(ExprKind::FloatLit(tok.text.to_owned()), tok.span),
TokenKind::StringLit => Expr::new(ExprKind::StringLit(tok.text.to_owned()), tok.span),
TokenKind::CharLit => Expr::new(ExprKind::CharLit(tok.text.to_owned()), tok.span),
TokenKind::True => Expr::new(ExprKind::Bool(true), tok.span),
TokenKind::False => Expr::new(ExprKind::Bool(false), tok.span),
// Identifier
TokenKind::Ident => Expr::new(ExprKind::Ident(tok.text.to_owned()), tok.span),
// Prefix unary
kind if prefix_bp(kind).is_some() => {
let r_bp = prefix_bp(kind).unwrap();
let op = token_to_unary_op(kind);
let operand = self.pratt(r_bp, allow_struct_lit);
let span = tok.span.cover(operand.span);
Expr::new(
ExprKind::Unary {
op,
op_span: tok.span,
expr: Box::new(operand),
},
span,
)
}
// Grouped expression
TokenKind::LParen => {
// Inside parentheses struct literals are always allowed.
let inner = self.pratt(0, true);
let close = self.expect(TokenKind::RParen);
let span = tok.span.cover(close.span);
Expr::new(ExprKind::Group(Box::new(inner)), span)
}
// Error recovery
_ => {
self.errors.push(
Diagnostic::error(format!("unexpected token {} in expression", tok.kind))
.with_label(Label::primary(tok.span)),
);
Expr::new(ExprKind::Error, tok.span)
}
}
}
// ── Left denotation (infix / postfix) ────────────────────────────────────
fn parse_led(
&mut self,
lhs: Expr,
op_tok: Token<'src>,
r_bp: u8,
allow_struct_lit: bool,
) -> Expr {
// Consume the operator token.
self.advance();
match op_tok.kind {
// Field access: `expr.field`
TokenKind::Dot => {
let field_tok = self.expect(TokenKind::Ident);
let span = lhs.span.cover(field_tok.span);
Expr::new(
ExprKind::Field {
expr: Box::new(lhs),
field: field_tok.text.to_owned(),
field_span: field_tok.span,
},
span,
)
}
// Index: `expr[index]`
TokenKind::LBracket => {
// Inside brackets struct literals are always allowed.
let index = self.pratt(0, true);
let close = self.expect(TokenKind::RBracket);
let span = lhs.span.cover(close.span);
Expr::new(
ExprKind::Index {
expr: Box::new(lhs),
index: Box::new(index),
},
span,
)
}
// Call: `expr(args…)`
TokenKind::LParen => {
let (args, close_span) = self.parse_arg_list();
let span = lhs.span.cover(close_span);
Expr::new(
ExprKind::Call {
callee: Box::new(lhs),
args,
},
span,
)
}
// Compound assignment: `lhs op= rhs`
kind if token_to_compound_assign_op(kind).is_some() => {
let op = token_to_compound_assign_op(kind).unwrap();
let rhs = self.pratt(r_bp, allow_struct_lit);
let span = lhs.span.cover(rhs.span);
Expr::new(
ExprKind::CompoundAssign {
op,
op_span: op_tok.span,
lhs: Box::new(lhs),
rhs: Box::new(rhs),
},
span,
)
}
// Binary operator
kind => {
let op = token_to_binary_op(kind);
let rhs = self.pratt(r_bp, allow_struct_lit);
let span = lhs.span.cover(rhs.span);
Expr::new(
ExprKind::Binary {
op,
op_span: op_tok.span,
lhs: Box::new(lhs),
rhs: Box::new(rhs),
},
span,
)
}
}
}
// ── Struct literal ────────────────────────────────────────────────────────
/// Called after we have already parsed the leading `Ident` as `lhs` and
/// the current token is `{`.
fn parse_struct_lit(&mut self, name_expr: Expr) -> Expr {
let (name, name_span) = match name_expr.kind {
ExprKind::Ident(ref s) => (s.clone(), name_expr.span),
_ => unreachable!(),
};
self.advance(); // consume `{`
let fields = self.parse_struct_field_list();
let close = self.expect(TokenKind::RCurly);
let span = name_span.cover(close.span);
Expr::new(
ExprKind::StructLit {
name,
name_span,
fields,
},
span,
)
}
fn parse_struct_field_list(&mut self) -> Vec<StructField> {
let mut fields = Vec::new();
loop {
if matches!(self.current().kind, TokenKind::RCurly | TokenKind::Eof) {
break;
}
fields.push(self.parse_struct_field());
if self.current().kind == TokenKind::Comma {
self.advance();
} else {
break;
}
}
fields
}
fn parse_struct_field(&mut self) -> StructField {
let name_tok = self.expect(TokenKind::Ident);
self.expect(TokenKind::Colon);
// Struct literals allowed inside field values.
let value = self.pratt(0, true);
StructField {
name: name_tok.text.to_owned(),
name_span: name_tok.span,
value,
}
}
// ── Argument list ─────────────────────────────────────────────────────────
// ── Top-level definitions ─────────────────────────────────────────────────
/// Parse an entire source file as a `Program`.
pub fn parse_program(&mut self) -> Program {
let start = self.current().span;
let mut defs = Vec::new();
loop {
if self.current().kind == TokenKind::Eof {
break;
}
defs.push(self.parse_top_level_def());
}
let span = start.cover(self.current().span);
Program { defs, span }
}
/// Parse one top-level definition (`fn` or `struct`).
pub fn parse_top_level_def(&mut self) -> TopLevelDef {
let tok = self.current();
match tok.kind {
TokenKind::Fn => self.parse_func_def(),
TokenKind::Struct => self.parse_struct_def(),
_ => {
self.errors.push(
Diagnostic::error(format!("expected `fn` or `struct`, found {}", tok.kind))
.with_label(
Label::primary(tok.span).with_message("expected `fn` or `struct`"),
),
);
self.synchronize_top_level();
TopLevelDef {
kind: TopLevelDefKind::Error,
span: tok.span,
}
}
}
}
/// Skip tokens until the next top-level boundary (`fn`, `struct`, or EOF).
fn synchronize_top_level(&mut self) {
loop {
match self.current().kind {
TokenKind::Eof | TokenKind::Fn | TokenKind::Struct => break,
_ => {
self.advance();
}
}
}
}
fn parse_func_def(&mut self) -> TopLevelDef {
let kw = self.advance(); // consume `fn`
let name_tok = self.expect(TokenKind::Ident);
self.expect(TokenKind::LParen);
let params = self.parse_param_list();
self.expect(TokenKind::RParen);
let ret_ty = if self.current().kind == TokenKind::Arrow {
self.advance();
Some(self.parse_type())
} else {
None
};
let body = self.parse_block();
let span = kw.span.cover(body.span);
TopLevelDef {
kind: TopLevelDefKind::Func(FuncDef {
name: name_tok.text.to_owned(),
name_span: name_tok.span,
params,
ret_ty,
body,
}),
span,
}
}
fn parse_param_list(&mut self) -> Vec<Param> {
let mut params = Vec::new();
loop {
if matches!(self.current().kind, TokenKind::RParen | TokenKind::Eof) {
break;
}
params.push(self.parse_param());
if self.current().kind == TokenKind::Comma {
self.advance();
} else {
break;
}
}
params
}
fn parse_param(&mut self) -> Param {
let mutable = if self.current().kind == TokenKind::Mut {
self.advance();
true
} else {
false
};
let name_tok = self.expect(TokenKind::Ident);
self.expect(TokenKind::Colon);
let ty = self.parse_type();
Param {
mutable,
name: name_tok.text.to_owned(),
name_span: name_tok.span,
ty,
}
}
fn parse_struct_def(&mut self) -> TopLevelDef {
let kw = self.advance(); // consume `struct`
let name_tok = self.expect(TokenKind::Ident);
self.expect(TokenKind::LCurly);
let fields = self.parse_field_def_list();
let close = self.expect(TokenKind::RCurly);
let span = kw.span.cover(close.span);
TopLevelDef {
kind: TopLevelDefKind::Struct(StructDef {
name: name_tok.text.to_owned(),
name_span: name_tok.span,
fields,
}),
span,
}
}
fn parse_field_def_list(&mut self) -> Vec<FieldDef> {
let mut fields = Vec::new();
loop {
if matches!(self.current().kind, TokenKind::RCurly | TokenKind::Eof) {
break;
}
fields.push(self.parse_field_def());
if self.current().kind == TokenKind::Comma {
self.advance();
} else {
break;
}
}
fields
}
fn parse_field_def(&mut self) -> FieldDef {
let name_tok = self.expect(TokenKind::Ident);
self.expect(TokenKind::Colon);
let ty = self.parse_type();
FieldDef {
name: name_tok.text.to_owned(),
name_span: name_tok.span,
ty,
}
}
/// Parse `arg, arg, …` up to `)`. The opening `(` has already been
/// consumed by `parse_led`. Returns `(args, close_span)`.
fn parse_arg_list(&mut self) -> (Vec<Expr>, Span) {
let mut args = Vec::new();
loop {
if matches!(self.current().kind, TokenKind::RParen | TokenKind::Eof) {
break;
}
// Struct literals allowed inside argument lists.
args.push(self.pratt(0, true));
if self.current().kind == TokenKind::Comma {
self.advance();
} else {
break;
}
}
let close = self.expect(TokenKind::RParen);
(args, close.span)
}
}
// ── Tests ──────────────────────────────────────────────────────────────────────
#[cfg(test)]
mod tests {
use super::*;
use crate::ast::{ElseBranch, ExprKind, StmtKind, TopLevelDefKind, Type};
// ── Expression test helpers ───────────────────────────────────────────────
fn parse(src: &str) -> Expr {
Parser::new(src).parse_expr(true)
}
fn parse_no_struct(src: &str) -> Expr {
Parser::new(src).parse_expr(false)
}
// ── Statement test helpers ────────────────────────────────────────────────
fn stmt(src: &str) -> Stmt {
Parser::new(src).parse_stmt()
}
fn parse_type_str(src: &str) -> Type {
Parser::new(src).parse_type()
}
// ── Expression tests ──────────────────────────────────────────────────────
#[test]
fn int_literal() {
let expr = parse("42");
assert!(matches!(expr.kind, ExprKind::IntLit(ref s) if s == "42"));
}
#[test]
fn float_literal() {
let expr = parse("3.14");
assert!(matches!(expr.kind, ExprKind::FloatLit(ref s) if s == "3.14"));
}
#[test]
fn bool_literals() {
assert!(matches!(parse("true").kind, ExprKind::Bool(true)));
assert!(matches!(parse("false").kind, ExprKind::Bool(false)));
}
#[test]
fn ident() {
let expr = parse("foo");
assert!(matches!(expr.kind, ExprKind::Ident(ref s) if s == "foo"));
}
#[test]
fn unary_neg() {
let expr = parse("-42");
assert!(matches!(
expr.kind,
ExprKind::Unary {
op: UnaryOp::Neg,
..
}
));
}
#[test]
fn unary_not() {
let expr = parse("!x");
assert!(matches!(
expr.kind,
ExprKind::Unary {
op: UnaryOp::Not,
..
}
));
}
#[test]
fn binary_add() {
let expr = parse("a + b");
assert!(matches!(
expr.kind,
ExprKind::Binary {
op: BinaryOp::Add,
..
}
));
}
#[test]
fn binary_precedence() {
// `a + b * c` should parse as `a + (b * c)`
let expr = parse("a + b * c");
match &expr.kind {
ExprKind::Binary {
op: BinaryOp::Add,
lhs,
rhs,
..
} => {
assert!(matches!(lhs.kind, ExprKind::Ident(ref s) if s == "a"));
assert!(matches!(
rhs.kind,
ExprKind::Binary {
op: BinaryOp::Mul,
..
}
));
}
_ => panic!("expected binary add, got {:?}", expr.kind),
}
}
#[test]
fn comparison() {
let expr = parse("a == b");
assert!(matches!(
expr.kind,
ExprKind::Binary {
op: BinaryOp::Eq,
..
}
));
}
#[test]
fn logical_and_or() {
// `a or b and c` → `a or (b and c)` (and binds tighter)
let expr = parse("a or b and c");
match &expr.kind {
ExprKind::Binary {
op: BinaryOp::Or,
rhs,
..
} => {
assert!(matches!(
rhs.kind,
ExprKind::Binary {
op: BinaryOp::And,
..
}
));
}
_ => panic!("expected or at top level"),
}
}
#[test]
fn grouped_expr() {
let expr = parse("(a + b)");
assert!(matches!(expr.kind, ExprKind::Group(_)));
}
#[test]
fn field_access() {
let expr = parse("foo.bar");
assert!(matches!(expr.kind, ExprKind::Field { ref field, .. } if field == "bar"));
}
#[test]
fn index_expr() {
let expr = parse("arr[0]");
assert!(matches!(expr.kind, ExprKind::Index { .. }));
}
#[test]
fn call_no_args() {
let expr = parse("foo()");
match &expr.kind {
ExprKind::Call { args, .. } => assert!(args.is_empty()),
_ => panic!("expected call"),
}
}
#[test]
fn call_with_args() {
let expr = parse("foo(1, 2, 3)");
match &expr.kind {
ExprKind::Call { args, .. } => assert_eq!(args.len(), 3),
_ => panic!("expected call"),
}
}
#[test]
fn struct_literal() {
let expr = parse("Foo { x: 1, y: 2 }");
match &expr.kind {
ExprKind::StructLit { name, fields, .. } => {
assert_eq!(name, "Foo");
assert_eq!(fields.len(), 2);
}
_ => panic!("expected struct literal, got {:?}", expr.kind),
}
}
#[test]
fn struct_literal_disabled() {
// With allow_struct_literals=false, `Foo { ... }` should NOT be a
// struct literal — the Ident is parsed alone and `{` is left unconsumed.
let expr = parse_no_struct("Foo { x: 1 }");
assert!(matches!(expr.kind, ExprKind::Ident(ref s) if s == "Foo"));
}
#[test]
fn chained_field_access() {
let expr = parse("a.b.c");
match &expr.kind {
ExprKind::Field {
expr: inner, field, ..
} => {
assert_eq!(field, "c");
assert!(matches!(inner.kind, ExprKind::Field { ref field, .. } if field == "b"));
}
_ => panic!("expected field access"),
}
}
#[test]
fn assignment_expr() {
let expr = parse("a = b");
assert!(matches!(
expr.kind,
ExprKind::Binary {
op: BinaryOp::Assign,
..
}
));
}
#[test]
fn assignment_right_associative() {
// `a = b = c` → `a = (b = c)`
let expr = parse("a = b = c");
match &expr.kind {
ExprKind::Binary {
op: BinaryOp::Assign,
rhs,
..
} => {
assert!(matches!(
rhs.kind,
ExprKind::Binary {
op: BinaryOp::Assign,
..
}
));
}
_ => panic!("expected assignment"),
}
}
#[test]
fn assignment_stmt() {
let s = stmt("a = b + 1;");
match &s.kind {
StmtKind::Expr(e) => assert!(matches!(
e.kind,
ExprKind::Binary {
op: BinaryOp::Assign,
..
}
)),
_ => panic!("expected expr stmt with assignment"),
}
}
#[test]
fn deref_and_addrof() {
assert!(matches!(
parse("*p").kind,
ExprKind::Unary {
op: UnaryOp::Deref,
..
}
));
assert!(matches!(
parse("&x").kind,
ExprKind::Unary {
op: UnaryOp::AddrOf,
..
}
));
}
// ── Type tests ────────────────────────────────────────────────────────────
#[test]
fn type_primitives() {
assert!(matches!(parse_type_str("u8"), Type::U8));
assert!(matches!(parse_type_str("u16"), Type::U16));
assert!(matches!(parse_type_str("u32"), Type::U32));
assert!(matches!(parse_type_str("u64"), Type::U64));
assert!(matches!(parse_type_str("i8"), Type::I8));
assert!(matches!(parse_type_str("i16"), Type::I16));
assert!(matches!(parse_type_str("i32"), Type::I32));
assert!(matches!(parse_type_str("i64"), Type::I64));
assert!(matches!(parse_type_str("f32"), Type::F32));
assert!(matches!(parse_type_str("f64"), Type::F64));
assert!(matches!(parse_type_str("bool"), Type::Bool));
assert!(matches!(parse_type_str("char"), Type::Char));
}
#[test]
fn type_named() {
assert!(matches!(parse_type_str("Foo"), Type::Named(ref s, _) if s == "Foo"));
}
#[test]
fn type_pointer() {
assert!(matches!(parse_type_str("*i32"), Type::Pointer(_)));
}
#[test]
fn type_opaque_pointer() {
assert!(matches!(parse_type_str("*opaque"), Type::OpaquePointer));
}
#[test]
fn type_array() {
assert!(
matches!(parse_type_str("[i32; 10]"), Type::Array { ref size, .. } if size == "10")
);
}
#[test]
fn type_nested_pointer() {
// `**i32` → Pointer(Pointer(I32))
assert!(matches!(parse_type_str("**i32"), Type::Pointer(_)));
}
// ── Statement tests ───────────────────────────────────────────────────────
#[test]
fn let_basic() {
let s = stmt("let x = 5;");
match &s.kind {
StmtKind::Let {
mutable,
name,
ty,
init,
..
} => {
assert!(!mutable);
assert_eq!(name, "x");
assert!(ty.is_none());
assert!(matches!(
init.as_ref().unwrap().kind,
ExprKind::IntLit(ref v) if v == "5"
));
}
_ => panic!("expected let"),
}
}
#[test]
fn let_mut() {
assert!(matches!(
stmt("let mut x = 5;").kind,
StmtKind::Let { mutable: true, .. }
));
}
#[test]
fn let_with_type() {
let s = stmt("let x: i32 = 0;");
assert!(matches!(
s.kind,
StmtKind::Let {
ty: Some(Type::I32),
..
}
));
}
#[test]
fn let_type_only() {
let s = stmt("let x: bool;");
assert!(matches!(
s.kind,
StmtKind::Let {
ty: Some(Type::Bool),
init: None,
..
}
));
}
#[test]
fn return_unit() {
assert!(matches!(stmt("return;").kind, StmtKind::Return(None)));
}
#[test]
fn return_value() {
assert!(matches!(
stmt("return x + 1;").kind,
StmtKind::Return(Some(_))
));
}
#[test]
fn if_no_else() {
assert!(matches!(
stmt("if x < 10 { foo(); }").kind,
StmtKind::If {
else_branch: None,
..
}
));
}
#[test]
fn if_else() {
let s = stmt("if x { a(); } else { b(); }");
assert!(matches!(
s.kind,
StmtKind::If {
else_branch: Some(ElseBranch::Block(_)),
..
}
));
}
#[test]
fn if_else_if() {
let s = stmt("if a { } else if b { }");
assert!(matches!(
s.kind,
StmtKind::If {
else_branch: Some(ElseBranch::If(_)),
..
}
));
}
#[test]
fn while_stmt() {
assert!(matches!(
stmt("while n > 0 { n = n - 1; }").kind,
StmtKind::While { .. }
));
}
#[test]
fn loop_stmt() {
assert!(matches!(
stmt("loop { break; }").kind,
StmtKind::Loop { .. }
));
}
#[test]
fn break_stmt() {
assert!(matches!(stmt("break;").kind, StmtKind::Break));
}
#[test]
fn continue_stmt() {
assert!(matches!(stmt("continue;").kind, StmtKind::Continue));
}
#[test]
fn block_stmt() {
assert!(matches!(stmt("{ let x = 1; }").kind, StmtKind::Block(_)));
}
#[test]
fn expr_stmt_call() {
let s = stmt("foo(1, 2);");
match &s.kind {
StmtKind::Expr(e) => assert!(matches!(e.kind, ExprKind::Call { .. })),
_ => panic!("expected expr stmt"),
}
}
#[test]
fn nested_blocks() {
// Blocks containing other blocks parse without panic
let s = stmt("{ { let x = 1; } }");
match &s.kind {
StmtKind::Block(outer) => {
assert_eq!(outer.stmts.len(), 1);
assert!(matches!(outer.stmts[0].kind, StmtKind::Block(_)));
}
_ => panic!("expected block"),
}
}
// ── Recovery tests ────────────────────────────────────────────────────────
#[test]
fn missing_semicolon_records_error() {
// `let x = 5` with no `;` should record exactly one error
let mut p = Parser::new("let x = 5");
p.parse_stmt();
assert!(!p.errors.is_empty(), "expected at least one error");
}
#[test]
fn stray_token_synchronizes_to_next_stmt() {
// `,` cannot start a statement; parser should synchronize so that
// the following `let` still parses correctly.
let mut p = Parser::new(", let x = 1;");
let s1 = p.parse_stmt();
let s2 = p.parse_stmt();
assert!(
matches!(s1.kind, StmtKind::Error),
"first stmt should be Error"
);
assert!(
matches!(s2.kind, StmtKind::Let { .. }),
"second stmt should be Let"
);
}
#[test]
fn missing_let_name_inserts_dummy() {
// `let = 5;` — missing name, but a dummy is inserted and parsing
// continues; we expect errors but no panic.
let mut p = Parser::new("let = 5;");
let s = p.parse_stmt();
assert!(!p.errors.is_empty());
// Even with the error, we should still get a Let node back.
assert!(matches!(s.kind, StmtKind::Let { .. }));
}
#[test]
fn if_condition_no_struct_literal() {
// `if Foo { x: 1 } { }` — `Foo` is the condition (no struct literal
// allowed), `{ x: 1 }` is an unexpected block; `{ }` is the body.
// The important thing is that this doesn't panic.
let mut p = Parser::new("if Foo { }");
let s = p.parse_stmt();
assert!(matches!(s.kind, StmtKind::If { .. }));
}
// ── Function definition tests ─────────────────────────────────────────────
fn top(src: &str) -> TopLevelDef {
Parser::new(src).parse_top_level_def()
}
#[test]
fn func_def_empty() {
let d = top("fn foo() { }");
match &d.kind {
TopLevelDefKind::Func(f) => {
assert_eq!(f.name, "foo");
assert!(f.params.is_empty());
assert!(f.ret_ty.is_none());
}
_ => panic!("expected func def"),
}
}
#[test]
fn func_def_with_return_type() {
let d = top("fn answer() -> i32 { return 42; }");
match &d.kind {
TopLevelDefKind::Func(f) => {
assert!(matches!(f.ret_ty, Some(Type::I32)));
}
_ => panic!("expected func def"),
}
}
#[test]
fn func_def_params() {
let d = top("fn add(a: i32, b: i32) -> i32 { return a + b; }");
match &d.kind {
TopLevelDefKind::Func(f) => {
assert_eq!(f.params.len(), 2);
assert_eq!(f.params[0].name, "a");
assert!(!f.params[0].mutable);
assert!(matches!(f.params[0].ty, Type::I32));
assert_eq!(f.params[1].name, "b");
}
_ => panic!("expected func def"),
}
}
#[test]
fn func_def_mut_param() {
let d = top("fn inc(mut n: i32) -> i32 { n = n + 1; return n; }");
match &d.kind {
TopLevelDefKind::Func(f) => {
assert!(f.params[0].mutable);
}
_ => panic!("expected func def"),
}
}
#[test]
fn func_def_pointer_param() {
let d = top("fn foo(p: *i32) { }");
match &d.kind {
TopLevelDefKind::Func(f) => {
assert!(matches!(f.params[0].ty, Type::Pointer(_)));
}
_ => panic!("expected func def"),
}
}
// ── Struct definition tests ───────────────────────────────────────────────
#[test]
fn struct_def_empty() {
let d = top("struct Empty { }");
match &d.kind {
TopLevelDefKind::Struct(s) => {
assert_eq!(s.name, "Empty");
assert!(s.fields.is_empty());
}
_ => panic!("expected struct def"),
}
}
#[test]
fn struct_def_with_fields() {
let d = top("struct Point { x: f64, y: f64 }");
match &d.kind {
TopLevelDefKind::Struct(s) => {
assert_eq!(s.name, "Point");
assert_eq!(s.fields.len(), 2);
assert_eq!(s.fields[0].name, "x");
assert!(matches!(s.fields[0].ty, Type::F64));
assert_eq!(s.fields[1].name, "y");
}
_ => panic!("expected struct def"),
}
}
#[test]
fn struct_def_named_field_type() {
let d = top("struct Node { value: i32, next: *Node }");
match &d.kind {
TopLevelDefKind::Struct(s) => {
assert!(matches!(s.fields[1].ty, Type::Pointer(_)));
}
_ => panic!("expected struct def"),
}
}
// ── Program tests ─────────────────────────────────────────────────────────
fn program(src: &str) -> Program {
Parser::new(src).parse_program()
}
#[test]
fn program_empty() {
let p = program("");
assert!(p.defs.is_empty());
}
#[test]
fn program_single_func() {
let p = program("fn main() { }");
assert_eq!(p.defs.len(), 1);
assert!(matches!(p.defs[0].kind, TopLevelDefKind::Func(_)));
}
#[test]
fn program_struct_and_func() {
let src = "struct Point { x: f64, y: f64 } fn main() { }";
let p = program(src);
assert_eq!(p.defs.len(), 2);
assert!(matches!(p.defs[0].kind, TopLevelDefKind::Struct(_)));
assert!(matches!(p.defs[1].kind, TopLevelDefKind::Func(_)));
}
#[test]
fn program_multiple_funcs() {
let src = "fn foo() { } fn bar() -> i32 { return 1; } fn baz(x: bool) { }";
let p = program(src);
assert_eq!(p.defs.len(), 3);
}
// ── Top-level recovery tests ──────────────────────────────────────────────
#[test]
fn top_level_stray_token_synchronizes() {
// A stray token should produce Error and then the next definition
// should still parse correctly.
let mut p = Parser::new("42 fn foo() { }");
let d1 = p.parse_top_level_def();
let d2 = p.parse_top_level_def();
assert!(matches!(d1.kind, TopLevelDefKind::Error));
assert!(matches!(d2.kind, TopLevelDefKind::Func(_)));
assert!(!p.errors.is_empty());
}
#[test]
fn top_level_missing_func_name_inserts_dummy() {
// `fn () { }` — missing name; expect() inserts a dummy, no panic.
let mut p = Parser::new("fn () { }");
let d = p.parse_top_level_def();
assert!(!p.errors.is_empty());
assert!(matches!(d.kind, TopLevelDefKind::Func(_)));
}
}