`=` was missing from the Pratt table, causing `a = b;` to fail with "expected `;`, found `=`". Assignment is now BinaryOp::Assign with binding power (2, 2) — lowest precedence, right-associative — so `a = b = c` parses as `a = (b = c)`.
1540 lines
48 KiB
Rust
1540 lines
48 KiB
Rust
use std::fmt;
|
|
|
|
use crate::{
|
|
ast::{
|
|
BinaryOp, Block, ElseBranch, Expr, ExprKind, FieldDef, FuncDef, Param, Program, Stmt,
|
|
StmtKind, StructDef, StructField, TopLevelDef, TopLevelDefKind, Type, UnaryOp,
|
|
},
|
|
lexer::Lexer,
|
|
token::{Span, Token, TokenKind},
|
|
};
|
|
|
|
// ── Parse error ───────────────────────────────────────────────────────────────
|
|
|
|
#[derive(Debug, Clone)]
|
|
pub struct ParseError {
|
|
pub span: Span,
|
|
pub message: String,
|
|
}
|
|
|
|
impl fmt::Display for ParseError {
|
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
write!(f, "error at {}: {}", self.span, self.message)
|
|
}
|
|
}
|
|
|
|
// ── 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: lowest precedence, right-associative (left_bp == right_bp).
|
|
// `a = b = c` → `a = (b = c)`.
|
|
TokenKind::Eq => (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::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_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::Eq => BinaryOp::Assign,
|
|
_ => unreachable!("not a binary op: {:?}", kind),
|
|
}
|
|
}
|
|
|
|
// ── Parser ─────────────────────────────────────────────────────────────────────
|
|
|
|
pub struct Parser<'src> {
|
|
tokens: Vec<Token<'src>>,
|
|
pos: usize,
|
|
pub errors: Vec<ParseError>,
|
|
}
|
|
|
|
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 {
|
|
let span = Span::new(tok.span.start, tok.span.start);
|
|
self.errors.push(ParseError {
|
|
span,
|
|
message: format!("expected {}, found {}", kind, tok.kind),
|
|
});
|
|
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(ParseError {
|
|
span: tok.span,
|
|
message: format!("expected type, found {}", tok.kind),
|
|
});
|
|
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(ParseError {
|
|
span: tok.span,
|
|
message: format!("unexpected {} in statement position", tok.kind),
|
|
});
|
|
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(ParseError {
|
|
span: tok.span,
|
|
message: format!("unexpected token {} in expression", tok.kind),
|
|
});
|
|
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,
|
|
)
|
|
}
|
|
|
|
// 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(ParseError {
|
|
span: tok.span,
|
|
message: format!("expected `fn` or `struct`, found {}", tok.kind),
|
|
});
|
|
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(_)));
|
|
}
|
|
}
|