use crate::error_codes as codes;
use crate::rustlite::{CompileError, Span};
use crate::rustlite::ast::*;
use crate::rustlite::token::{Token, TokenKind};
pub fn parse(tokens: &[Token]) -> Result<Module, CompileError> {
let mut p = Parser::new(tokens);
p.parse_module()
}
const MAX_RECURSION_DEPTH: usize = 96;
struct Parser<'a> {
tokens: &'a [Token],
pos: usize,
depth: usize,
no_struct_literal: bool,
}
impl<'a> Parser<'a> {
fn new(tokens: &'a [Token]) -> Self {
Self { tokens, pos: 0, depth: 0, no_struct_literal: false }
}
fn with_no_struct_literal<T>(
&mut self,
flag: bool,
f: impl FnOnce(&mut Self) -> Result<T, CompileError>,
) -> Result<T, CompileError> {
let prev = self.no_struct_literal;
self.no_struct_literal = flag;
let r = f(self);
self.no_struct_literal = prev;
r
}
fn enter(&mut self) -> Result<(), CompileError> {
self.depth += 1;
if self.depth > MAX_RECURSION_DEPTH {
return Err(CompileError::at_code(
codes::NESTING_TOO_DEEP,
"nesting too deep".to_string(),
self.span(),
));
}
Ok(())
}
fn leave(&mut self) {
self.depth = self.depth.saturating_sub(1);
}
fn peek(&self) -> &TokenKind {
&self.tokens[self.pos].kind
}
fn span(&self) -> Span {
self.tokens[self.pos].span
}
fn at(&self, kind: &TokenKind) -> bool {
std::mem::discriminant(self.peek()) == std::mem::discriminant(kind)
}
fn at_eof(&self) -> bool {
matches!(self.peek(), TokenKind::Eof)
}
fn advance(&mut self) -> &Token {
let tok = &self.tokens[self.pos];
if self.pos + 1 < self.tokens.len() {
self.pos += 1;
}
tok
}
fn expect(&mut self, kind: &TokenKind) -> Result<&Token, CompileError> {
if self.at(kind) {
Ok(self.advance())
} else {
Err(CompileError::at_code(
codes::UNEXPECTED_TOKEN,
format!("expected {kind:?}, got {:?}", self.peek()),
self.span(),
))
}
}
fn expect_ident(&mut self) -> Result<String, CompileError> {
match self.peek().clone() {
TokenKind::Ident(name) => {
let name = name.clone();
self.advance();
Ok(name)
}
_ => Err(CompileError::at_code(
codes::UNEXPECTED_TOKEN,
format!("expected identifier, got {:?}", self.peek()),
self.span(),
)),
}
}
fn parse_module(&mut self) -> Result<Module, CompileError> {
let mut uses = Vec::new();
let mut items = Vec::new();
while !self.at_eof() {
if matches!(self.peek(), TokenKind::Use) {
uses.push(self.parse_use()?);
} else {
items.push(self.parse_item()?);
}
}
Ok(Module { uses, items })
}
fn parse_use(&mut self) -> Result<UseDecl, CompileError> {
let start = self.span();
self.expect(&TokenKind::Use)?;
let path = self.parse_path()?;
self.expect(&TokenKind::Semi)?;
Ok(UseDecl { path, span: Span { start: start.start, end: self.tokens[self.pos - 1].span.end } })
}
fn parse_path(&mut self) -> Result<Vec<String>, CompileError> {
let mut segments = vec![self.expect_ident()?];
while matches!(self.peek(), TokenKind::ColonColon) {
self.advance();
segments.push(self.expect_ident()?);
}
Ok(segments)
}
fn skip_visibility(&mut self) {
if matches!(self.peek(), TokenKind::Ident(n) if n.as_str() == "pub") {
self.advance();
if self.at(&TokenKind::LParen) {
let mut depth = 0usize;
loop {
match self.peek() {
TokenKind::LParen => depth += 1,
TokenKind::RParen => depth -= 1,
TokenKind::Eof => break,
_ => {}
}
self.advance();
if depth == 0 {
break;
}
}
}
}
}
fn parse_item(&mut self) -> Result<Item, CompileError> {
self.skip_visibility();
match self.peek() {
TokenKind::Struct => Ok(Item::Struct(self.parse_struct()?)),
TokenKind::Enum => Ok(Item::Enum(self.parse_enum()?)),
TokenKind::Fn => Ok(Item::Fn(self.parse_fn()?)),
TokenKind::Const => Ok(Item::Const(self.parse_const()?)),
_ => Err(CompileError::at_code(
codes::EXPECTED_ITEM,
format!("expected item (fn/struct/enum/const), got {:?}", self.peek()),
self.span(),
)),
}
}
fn parse_struct(&mut self) -> Result<StructDecl, CompileError> {
let start = self.span();
self.expect(&TokenKind::Struct)?;
let name = self.expect_ident()?;
self.expect(&TokenKind::LBrace)?;
let fields = self.parse_field_list()?;
self.expect(&TokenKind::RBrace)?;
Ok(StructDecl { name, fields, span: Span { start: start.start, end: self.tokens[self.pos - 1].span.end } })
}
fn parse_enum(&mut self) -> Result<EnumDecl, CompileError> {
let start = self.span();
self.expect(&TokenKind::Enum)?;
let name = self.expect_ident()?;
self.expect(&TokenKind::LBrace)?;
let variants = self.parse_variant_list()?;
self.expect(&TokenKind::RBrace)?;
Ok(EnumDecl { name, variants, span: Span { start: start.start, end: self.tokens[self.pos - 1].span.end } })
}
fn parse_fn(&mut self) -> Result<FnDecl, CompileError> {
let start = self.span();
self.expect(&TokenKind::Fn)?;
let name = self.expect_ident()?;
self.expect(&TokenKind::LParen)?;
let params = if !matches!(self.peek(), TokenKind::RParen) {
self.parse_param_list()?
} else {
Vec::new()
};
self.expect(&TokenKind::RParen)?;
let ret_type = if matches!(self.peek(), TokenKind::Arrow) {
self.advance();
Some(self.parse_type()?)
} else {
None
};
let body = self.parse_block()?;
Ok(FnDecl { name, params, ret_type, body, span: Span { start: start.start, end: self.tokens[self.pos - 1].span.end } })
}
fn parse_const(&mut self) -> Result<ConstDecl, CompileError> {
let start = self.span();
self.expect(&TokenKind::Const)?;
let name = self.expect_ident()?;
self.expect(&TokenKind::Colon)?;
let ty = self.parse_type()?;
self.expect(&TokenKind::Eq)?;
let value = self.parse_expr()?;
self.expect(&TokenKind::Semi)?;
Ok(ConstDecl { name, ty, value, span: Span { start: start.start, end: self.tokens[self.pos - 1].span.end } })
}
fn parse_field_list(&mut self) -> Result<Vec<Field>, CompileError> {
let mut fields = Vec::new();
while !matches!(self.peek(), TokenKind::RBrace) {
self.skip_visibility();
let start = self.span();
let name = self.expect_ident()?;
self.expect(&TokenKind::Colon)?;
let ty = self.parse_type()?;
fields.push(Field { name, ty, span: Span { start: start.start, end: self.tokens[self.pos - 1].span.end } });
if !matches!(self.peek(), TokenKind::Comma) { break; }
self.advance();
}
Ok(fields)
}
fn parse_variant_list(&mut self) -> Result<Vec<Variant>, CompileError> {
let mut variants = Vec::new();
while !matches!(self.peek(), TokenKind::RBrace) {
let start = self.span();
let name = self.expect_ident()?;
let payload = match self.peek() {
TokenKind::LParen => {
self.advance();
let mut types = Vec::new();
while !matches!(self.peek(), TokenKind::RParen) {
types.push(self.parse_type()?);
if !matches!(self.peek(), TokenKind::Comma) { break; }
self.advance();
}
self.expect(&TokenKind::RParen)?;
VariantPayload::Tuple(types)
}
TokenKind::LBrace => {
self.advance();
let fields = self.parse_field_list()?;
self.expect(&TokenKind::RBrace)?;
VariantPayload::Struct(fields)
}
_ => VariantPayload::Unit,
};
variants.push(Variant { name, payload, span: Span { start: start.start, end: self.tokens[self.pos - 1].span.end } });
if !matches!(self.peek(), TokenKind::Comma) { break; }
self.advance();
}
Ok(variants)
}
fn parse_param_list(&mut self) -> Result<Vec<Param>, CompileError> {
let mut params = Vec::new();
loop {
let start = self.span();
let name = self.expect_ident()?;
self.expect(&TokenKind::Colon)?;
let ty = self.parse_type()?;
params.push(Param { name, ty, span: Span { start: start.start, end: self.tokens[self.pos - 1].span.end } });
if !matches!(self.peek(), TokenKind::Comma) { break; }
self.advance();
}
Ok(params)
}
fn parse_type(&mut self) -> Result<Ty, CompileError> {
match self.peek() {
TokenKind::I32 => { self.advance(); Ok(Ty::I32) }
TokenKind::I64 => { self.advance(); Ok(Ty::I64) }
TokenKind::F32 => { self.advance(); Ok(Ty::F32) }
TokenKind::F64 => { self.advance(); Ok(Ty::F64) }
TokenKind::Bool => { self.advance(); Ok(Ty::Bool) }
TokenKind::StringType => { self.advance(); Ok(Ty::String) }
TokenKind::Ident(_) => {
let name = self.expect_ident()?;
Ok(Ty::Named(name))
}
TokenKind::LParen => {
self.advance();
let first = self.parse_type()?;
if matches!(self.peek(), TokenKind::Comma) {
let mut types = vec![first];
while matches!(self.peek(), TokenKind::Comma) {
self.advance();
types.push(self.parse_type()?);
}
self.expect(&TokenKind::RParen)?;
Ok(Ty::Tuple(types))
} else {
self.expect(&TokenKind::RParen)?;
Ok(first)
}
}
TokenKind::LBracket => {
self.advance();
let elem = self.parse_type()?;
self.expect(&TokenKind::Semi)?;
let n = match self.peek().clone() {
TokenKind::IntLit(n) if n >= 0 => {
self.advance();
n as usize
}
other => {
return Err(CompileError::at_code(
codes::EXPECTED_TYPE,
format!("array length must be a non-negative integer literal, got {other:?}"),
self.span(),
))
}
};
self.expect(&TokenKind::RBracket)?;
Ok(Ty::Array(Box::new(elem), n))
}
_ => Err(CompileError::at_code(
codes::EXPECTED_TYPE,
format!("expected type, got {:?}", self.peek()),
self.span(),
)),
}
}
fn parse_block(&mut self) -> Result<Block, CompileError> {
self.enter()?;
let r = self.with_no_struct_literal(false, |p| p.parse_block_inner());
self.leave();
r
}
fn parse_block_inner(&mut self) -> Result<Block, CompileError> {
let start = self.span();
self.expect(&TokenKind::LBrace)?;
let mut stmts = Vec::new();
let mut tail: Option<Box<Expr>> = None;
while !matches!(self.peek(), TokenKind::RBrace) {
match self.peek() {
TokenKind::Let => {
stmts.push(self.parse_let_stmt()?);
}
TokenKind::Return => {
stmts.push(self.parse_return_stmt()?);
}
_ => {
let expr = self.parse_expr()?;
let compound = match self.peek() {
TokenKind::PlusEq => Some(BinOp::Add),
TokenKind::MinusEq => Some(BinOp::Sub),
TokenKind::StarEq => Some(BinOp::Mul),
TokenKind::SlashEq => Some(BinOp::Div),
TokenKind::PercentEq => Some(BinOp::Mod),
_ => None,
};
if let Some(op) = compound {
self.advance(); let rhs = self.parse_expr()?;
self.expect(&TokenKind::Semi)?;
let span = expr.span;
let place = expr_to_place(&expr)?;
let value = Expr {
kind: ExprKind::BinOp {
op,
lhs: Box::new(expr),
rhs: Box::new(rhs),
},
span,
};
stmts.push(Stmt::Assign { place, value, span });
continue;
}
if matches!(self.peek(), TokenKind::Eq) && !matches!(self.peek(), TokenKind::EqEq) {
if matches!(self.tokens[self.pos].kind, TokenKind::Eq) {
self.advance(); let value = self.parse_expr()?;
self.expect(&TokenKind::Semi)?;
let place = expr_to_place(&expr)?;
stmts.push(Stmt::Assign {
place,
value,
span: expr.span,
});
continue;
}
}
let is_void_loop =
matches!(expr.kind, ExprKind::While { .. } | ExprKind::Loop { .. });
if is_void_loop {
if matches!(self.peek(), TokenKind::Semi) {
self.advance();
}
let span = expr.span;
stmts.push(Stmt::Expr { expr, span });
} else if matches!(self.peek(), TokenKind::Semi) {
let span = expr.span;
self.advance();
stmts.push(Stmt::Expr { expr, span });
} else if matches!(self.peek(), TokenKind::RBrace) {
tail = Some(Box::new(expr));
} else if is_block_expr(&expr) {
let span = expr.span;
stmts.push(Stmt::Expr { expr, span });
} else {
return Err(CompileError::at_code(
codes::MISSING_SEMICOLON,
format!("expected ';' or '}}' after expression, got {:?}", self.peek()),
self.span(),
));
}
}
}
}
self.expect(&TokenKind::RBrace)?;
Ok(Block { stmts, tail, span: Span { start: start.start, end: self.tokens[self.pos - 1].span.end } })
}
fn parse_let_stmt(&mut self) -> Result<Stmt, CompileError> {
let start = self.span();
self.expect(&TokenKind::Let)?;
let mutable = if matches!(self.peek(), TokenKind::Mut) {
self.advance();
true
} else {
false
};
let name = self.expect_ident()?;
let ty = if matches!(self.peek(), TokenKind::Colon) {
self.advance();
Some(self.parse_type()?)
} else {
None
};
self.expect(&TokenKind::Eq)?;
let init = self.parse_expr()?;
self.expect(&TokenKind::Semi)?;
Ok(Stmt::Let { name, mutable, ty, init, span: Span { start: start.start, end: self.tokens[self.pos - 1].span.end } })
}
fn parse_return_stmt(&mut self) -> Result<Stmt, CompileError> {
let start = self.span();
self.expect(&TokenKind::Return)?;
let value = if matches!(self.peek(), TokenKind::Semi) {
None
} else {
Some(self.parse_expr()?)
};
self.expect(&TokenKind::Semi)?;
Ok(Stmt::Return { value, span: Span { start: start.start, end: self.tokens[self.pos - 1].span.end } })
}
fn parse_expr(&mut self) -> Result<Expr, CompileError> {
self.enter()?;
let r = self.parse_or();
self.leave();
r
}
fn parse_or(&mut self) -> Result<Expr, CompileError> {
let mut lhs = self.parse_and()?;
while matches!(self.peek(), TokenKind::PipePipe) {
self.advance();
let rhs = self.parse_and()?;
let span = Span { start: lhs.span.start, end: rhs.span.end };
lhs = Expr { kind: ExprKind::BinOp { op: BinOp::Or, lhs: Box::new(lhs), rhs: Box::new(rhs) }, span };
}
Ok(lhs)
}
fn parse_and(&mut self) -> Result<Expr, CompileError> {
let mut lhs = self.parse_cmp()?;
while matches!(self.peek(), TokenKind::AmpAmp) {
self.advance();
let rhs = self.parse_cmp()?;
let span = Span { start: lhs.span.start, end: rhs.span.end };
lhs = Expr { kind: ExprKind::BinOp { op: BinOp::And, lhs: Box::new(lhs), rhs: Box::new(rhs) }, span };
}
Ok(lhs)
}
fn parse_cmp(&mut self) -> Result<Expr, CompileError> {
let lhs = self.parse_bitor()?;
let op = match self.peek() {
TokenKind::EqEq => BinOp::Eq,
TokenKind::BangEq => BinOp::Ne,
TokenKind::Lt => BinOp::Lt,
TokenKind::Gt => BinOp::Gt,
TokenKind::LtEq => BinOp::Le,
TokenKind::GtEq => BinOp::Ge,
_ => return Ok(lhs),
};
self.advance();
let rhs = self.parse_bitor()?;
let span = Span { start: lhs.span.start, end: rhs.span.end };
Ok(Expr { kind: ExprKind::BinOp { op, lhs: Box::new(lhs), rhs: Box::new(rhs) }, span })
}
fn parse_bitor(&mut self) -> Result<Expr, CompileError> {
let mut lhs = self.parse_bitxor()?;
while matches!(self.peek(), TokenKind::Pipe) {
self.advance();
let rhs = self.parse_bitxor()?;
let span = Span { start: lhs.span.start, end: rhs.span.end };
lhs = Expr { kind: ExprKind::BinOp { op: BinOp::BitOr, lhs: Box::new(lhs), rhs: Box::new(rhs) }, span };
}
Ok(lhs)
}
fn parse_bitxor(&mut self) -> Result<Expr, CompileError> {
let mut lhs = self.parse_bitand()?;
while matches!(self.peek(), TokenKind::Caret) {
self.advance();
let rhs = self.parse_bitand()?;
let span = Span { start: lhs.span.start, end: rhs.span.end };
lhs = Expr { kind: ExprKind::BinOp { op: BinOp::BitXor, lhs: Box::new(lhs), rhs: Box::new(rhs) }, span };
}
Ok(lhs)
}
fn parse_bitand(&mut self) -> Result<Expr, CompileError> {
let mut lhs = self.parse_shift()?;
while matches!(self.peek(), TokenKind::Amp) {
self.advance();
let rhs = self.parse_shift()?;
let span = Span { start: lhs.span.start, end: rhs.span.end };
lhs = Expr { kind: ExprKind::BinOp { op: BinOp::BitAnd, lhs: Box::new(lhs), rhs: Box::new(rhs) }, span };
}
Ok(lhs)
}
fn parse_shift(&mut self) -> Result<Expr, CompileError> {
let mut lhs = self.parse_sum()?;
while matches!(self.peek(), TokenKind::Shl | TokenKind::Shr) {
let op = if matches!(self.peek(), TokenKind::Shl) { BinOp::Shl } else { BinOp::Shr };
self.advance();
let rhs = self.parse_sum()?;
let span = Span { start: lhs.span.start, end: rhs.span.end };
lhs = Expr { kind: ExprKind::BinOp { op, lhs: Box::new(lhs), rhs: Box::new(rhs) }, span };
}
Ok(lhs)
}
fn parse_sum(&mut self) -> Result<Expr, CompileError> {
let mut lhs = self.parse_term()?;
while matches!(self.peek(), TokenKind::Plus | TokenKind::Minus) {
let op = if matches!(self.peek(), TokenKind::Plus) { BinOp::Add } else { BinOp::Sub };
self.advance();
let rhs = self.parse_term()?;
let span = Span { start: lhs.span.start, end: rhs.span.end };
lhs = Expr { kind: ExprKind::BinOp { op, lhs: Box::new(lhs), rhs: Box::new(rhs) }, span };
}
Ok(lhs)
}
fn parse_term(&mut self) -> Result<Expr, CompileError> {
let mut lhs = self.parse_cast()?;
while matches!(self.peek(), TokenKind::Star | TokenKind::Slash | TokenKind::Percent) {
let op = match self.peek() {
TokenKind::Star => BinOp::Mul,
TokenKind::Slash => BinOp::Div,
_ => BinOp::Mod,
};
self.advance();
let rhs = self.parse_cast()?;
let span = Span { start: lhs.span.start, end: rhs.span.end };
lhs = Expr { kind: ExprKind::BinOp { op, lhs: Box::new(lhs), rhs: Box::new(rhs) }, span };
}
Ok(lhs)
}
fn parse_cast(&mut self) -> Result<Expr, CompileError> {
let mut e = self.parse_unary()?;
while matches!(self.peek(), TokenKind::As) {
self.advance();
let ty = self.parse_type()?;
let span = Span { start: e.span.start, end: self.tokens[self.pos - 1].span.end };
e = Expr { kind: ExprKind::Cast { expr: Box::new(e), ty }, span };
}
Ok(e)
}
fn parse_unary(&mut self) -> Result<Expr, CompileError> {
self.enter()?;
let r = self.parse_unary_inner();
self.leave();
r
}
fn parse_unary_inner(&mut self) -> Result<Expr, CompileError> {
match self.peek() {
TokenKind::Minus => {
let start = self.span();
self.advance();
let operand = self.parse_unary()?;
let span = Span { start: start.start, end: operand.span.end };
Ok(Expr { kind: ExprKind::UnaryOp { op: UnaryOp::Neg, operand: Box::new(operand) }, span })
}
TokenKind::Bang => {
let start = self.span();
self.advance();
let operand = self.parse_unary()?;
let span = Span { start: start.start, end: operand.span.end };
Ok(Expr { kind: ExprKind::UnaryOp { op: UnaryOp::Not, operand: Box::new(operand) }, span })
}
_ => self.parse_postfix(),
}
}
fn parse_postfix(&mut self) -> Result<Expr, CompileError> {
let mut expr = self.parse_atom()?;
loop {
match self.peek() {
TokenKind::Dot => {
self.advance();
let field = self.expect_ident()?;
if matches!(self.peek(), TokenKind::LParen) {
self.advance();
let args = self.parse_arg_list()?;
self.expect(&TokenKind::RParen)?;
let span = Span { start: expr.span.start, end: self.tokens[self.pos - 1].span.end };
expr = Expr { kind: ExprKind::MethodCall { object: Box::new(expr), method: field, args }, span };
} else {
let span = Span { start: expr.span.start, end: self.tokens[self.pos - 1].span.end };
expr = Expr { kind: ExprKind::FieldAccess { object: Box::new(expr), field }, span };
}
}
TokenKind::LParen => {
self.advance();
let args = self.parse_arg_list()?;
self.expect(&TokenKind::RParen)?;
let span = Span { start: expr.span.start, end: self.tokens[self.pos - 1].span.end };
expr = Expr { kind: ExprKind::Call { func: Box::new(expr), args }, span };
}
TokenKind::LBracket => {
self.advance();
let index = self.with_no_struct_literal(false, |p| p.parse_expr())?;
self.expect(&TokenKind::RBracket)?;
let span = Span { start: expr.span.start, end: self.tokens[self.pos - 1].span.end };
expr = Expr { kind: ExprKind::Index { base: Box::new(expr), index: Box::new(index) }, span };
}
_ => break,
}
}
Ok(expr)
}
fn parse_atom(&mut self) -> Result<Expr, CompileError> {
let start = self.span();
match self.peek().clone() {
TokenKind::IntLit(n) => {
self.advance();
Ok(Expr { kind: ExprKind::IntLit(n), span: Span { start: start.start, end: self.tokens[self.pos - 1].span.end } })
}
TokenKind::FloatLit(n) => {
self.advance();
Ok(Expr { kind: ExprKind::FloatLit(n), span: Span { start: start.start, end: self.tokens[self.pos - 1].span.end } })
}
TokenKind::StringLit(s) => {
let s = s.clone();
self.advance();
Ok(Expr { kind: ExprKind::StringLit(s), span: Span { start: start.start, end: self.tokens[self.pos - 1].span.end } })
}
TokenKind::True => {
self.advance();
Ok(Expr { kind: ExprKind::BoolLit(true), span: Span { start: start.start, end: self.tokens[self.pos - 1].span.end } })
}
TokenKind::False => {
self.advance();
Ok(Expr { kind: ExprKind::BoolLit(false), span: Span { start: start.start, end: self.tokens[self.pos - 1].span.end } })
}
TokenKind::LBracket => {
self.advance();
let array = self.with_no_struct_literal(false, |p| {
if matches!(p.peek(), TokenKind::RBracket) {
return Ok(ExprKind::ArrayLit(Vec::new()));
}
let first = p.parse_expr()?;
if matches!(p.peek(), TokenKind::Semi) {
p.advance();
let count = match p.peek().clone() {
TokenKind::IntLit(n) if n >= 0 => {
p.advance();
n as usize
}
other => {
return Err(CompileError::at_code(
codes::EXPECTED_EXPRESSION,
format!("array repeat count must be a non-negative integer literal, got {other:?}"),
p.span(),
))
}
};
return Ok(ExprKind::ArrayRepeat { value: Box::new(first), count });
}
let mut elems = vec![first];
while matches!(p.peek(), TokenKind::Comma) {
p.advance();
if matches!(p.peek(), TokenKind::RBracket) {
break; }
elems.push(p.parse_expr()?);
}
Ok(ExprKind::ArrayLit(elems))
})?;
self.expect(&TokenKind::RBracket)?;
Ok(Expr { kind: array, span: Span { start: start.start, end: self.tokens[self.pos - 1].span.end } })
}
TokenKind::If => self.parse_if_expr(),
TokenKind::Match => self.parse_match_expr(),
TokenKind::While => self.parse_while_expr(),
TokenKind::Loop => self.parse_loop_expr(),
TokenKind::For => self.parse_for_expr(),
TokenKind::Break => {
self.advance();
let value = if !matches!(self.peek(), TokenKind::Semi | TokenKind::RBrace | TokenKind::Comma) {
Some(Box::new(self.parse_expr()?))
} else {
None
};
Ok(Expr { kind: ExprKind::Break { value }, span: Span { start: start.start, end: self.tokens[self.pos - 1].span.end } })
}
TokenKind::Continue => {
self.advance();
Ok(Expr { kind: ExprKind::Continue, span: Span { start: start.start, end: self.tokens[self.pos - 1].span.end } })
}
TokenKind::LParen => {
self.advance();
let (first, exprs) = self.with_no_struct_literal(false, |p| {
let first = p.parse_expr()?;
if matches!(p.peek(), TokenKind::Comma) {
let mut exprs = vec![first];
while matches!(p.peek(), TokenKind::Comma) {
p.advance();
if matches!(p.peek(), TokenKind::RParen) { break; }
exprs.push(p.parse_expr()?);
}
Ok((None, Some(exprs)))
} else {
Ok((Some(first), None))
}
})?;
if let Some(exprs) = exprs {
self.expect(&TokenKind::RParen)?;
Ok(Expr { kind: ExprKind::TupleLit(exprs), span: Span { start: start.start, end: self.tokens[self.pos - 1].span.end } })
} else {
self.expect(&TokenKind::RParen)?;
Ok(first.unwrap())
}
}
TokenKind::Ident(_) => {
let path = self.parse_path()?;
if !self.no_struct_literal
&& matches!(self.peek(), TokenKind::LBrace)
&& self.looks_like_struct_lit()
{
self.advance();
let fields = self.parse_field_init_list()?;
self.expect(&TokenKind::RBrace)?;
Ok(Expr {
kind: ExprKind::StructLit { path, fields },
span: Span { start: start.start, end: self.tokens[self.pos - 1].span.end },
})
} else if path.len() == 1 {
Ok(Expr {
kind: ExprKind::Var(path.into_iter().next().unwrap()),
span: Span { start: start.start, end: self.tokens[self.pos - 1].span.end },
})
} else {
Ok(Expr {
kind: ExprKind::Path(path),
span: Span { start: start.start, end: self.tokens[self.pos - 1].span.end },
})
}
}
_ => Err(CompileError::at_code(
codes::EXPECTED_EXPRESSION,
format!("expected expression, got {:?}", self.peek()),
self.span(),
)),
}
}
fn looks_like_struct_lit(&self) -> bool {
if self.pos + 2 >= self.tokens.len() { return false; }
let after_brace = &self.tokens[self.pos + 1].kind;
let after_that = &self.tokens[self.pos + 2].kind;
match after_brace {
TokenKind::Ident(_) => matches!(after_that, TokenKind::Colon | TokenKind::Comma | TokenKind::RBrace),
TokenKind::RBrace => true, _ => false,
}
}
fn parse_if_expr(&mut self) -> Result<Expr, CompileError> {
let start = self.span();
self.expect(&TokenKind::If)?;
let cond = self.with_no_struct_literal(true, |p| p.parse_expr())?;
let then_block = self.parse_block()?;
let else_block = if matches!(self.peek(), TokenKind::Else) {
self.advance();
if matches!(self.peek(), TokenKind::If) {
Some(ElseBranch::If(Box::new(self.parse_if_expr()?)))
} else {
Some(ElseBranch::Block(self.parse_block()?))
}
} else {
None
};
Ok(Expr {
kind: ExprKind::If { cond: Box::new(cond), then_block, else_block },
span: Span { start: start.start, end: self.tokens[self.pos - 1].span.end },
})
}
fn parse_match_expr(&mut self) -> Result<Expr, CompileError> {
let start = self.span();
self.expect(&TokenKind::Match)?;
let scrutinee = self.with_no_struct_literal(true, |p| p.parse_expr())?;
self.expect(&TokenKind::LBrace)?;
let mut arms = Vec::new();
while !matches!(self.peek(), TokenKind::RBrace) {
arms.push(self.parse_match_arm()?);
}
self.expect(&TokenKind::RBrace)?;
Ok(Expr {
kind: ExprKind::Match { scrutinee: Box::new(scrutinee), arms },
span: Span { start: start.start, end: self.tokens[self.pos - 1].span.end },
})
}
fn parse_match_arm(&mut self) -> Result<MatchArm, CompileError> {
let start = self.span();
let pattern = self.parse_pattern()?;
self.expect(&TokenKind::FatArrow)?;
let body = if matches!(self.peek(), TokenKind::LBrace) {
let block = self.parse_block()?;
if matches!(self.peek(), TokenKind::Comma) { self.advance(); }
Expr { span: block.span, kind: ExprKind::Block(block) }
} else {
let expr = self.parse_expr()?;
if matches!(self.peek(), TokenKind::Comma) { self.advance(); }
expr
};
Ok(MatchArm { pattern, body, span: Span { start: start.start, end: self.tokens[self.pos - 1].span.end } })
}
fn parse_pattern(&mut self) -> Result<Pattern, CompileError> {
let start = self.span();
match self.peek().clone() {
TokenKind::Underscore => {
self.advance();
Ok(Pattern { kind: PatternKind::Wildcard, span: Span { start: start.start, end: self.tokens[self.pos - 1].span.end } })
}
TokenKind::IntLit(n) => {
self.advance();
if matches!(self.peek(), TokenKind::DotDot | TokenKind::DotDotEq) {
let inclusive = matches!(self.peek(), TokenKind::DotDotEq);
self.advance();
let hi = match self.peek().clone() {
TokenKind::IntLit(h) => {
self.advance();
h
}
_ => {
return Err(CompileError::at_code(
codes::EXPECTED_PATTERN,
"range pattern needs an integer upper bound",
self.span(),
))
}
};
return Ok(Pattern {
kind: PatternKind::IntRange { lo: n, hi, inclusive },
span: Span { start: start.start, end: self.tokens[self.pos - 1].span.end },
});
}
Ok(Pattern { kind: PatternKind::Literal(LitPattern::Int(n)), span: Span { start: start.start, end: self.tokens[self.pos - 1].span.end } })
}
TokenKind::FloatLit(n) => {
self.advance();
Ok(Pattern { kind: PatternKind::Literal(LitPattern::Float(n)), span: Span { start: start.start, end: self.tokens[self.pos - 1].span.end } })
}
TokenKind::StringLit(s) => {
let s = s.clone();
self.advance();
Ok(Pattern { kind: PatternKind::Literal(LitPattern::String(s)), span: Span { start: start.start, end: self.tokens[self.pos - 1].span.end } })
}
TokenKind::True => {
self.advance();
Ok(Pattern { kind: PatternKind::Literal(LitPattern::Bool(true)), span: Span { start: start.start, end: self.tokens[self.pos - 1].span.end } })
}
TokenKind::False => {
self.advance();
Ok(Pattern { kind: PatternKind::Literal(LitPattern::Bool(false)), span: Span { start: start.start, end: self.tokens[self.pos - 1].span.end } })
}
TokenKind::Ident(_) => {
let path = self.parse_path()?;
match self.peek() {
TokenKind::LParen => {
self.advance();
let mut fields = Vec::new();
while !matches!(self.peek(), TokenKind::RParen) {
fields.push(self.parse_pattern()?);
if !matches!(self.peek(), TokenKind::Comma) { break; }
self.advance();
}
self.expect(&TokenKind::RParen)?;
Ok(Pattern { kind: PatternKind::TupleVariant { path, fields }, span: Span { start: start.start, end: self.tokens[self.pos - 1].span.end } })
}
TokenKind::LBrace => {
self.advance();
let mut fields = Vec::new();
while !matches!(self.peek(), TokenKind::RBrace) {
let fstart = self.span();
let name = self.expect_ident()?;
let pattern = if matches!(self.peek(), TokenKind::Colon) {
self.advance();
Some(self.parse_pattern()?)
} else {
None
};
fields.push(FieldPattern { name, pattern, span: Span { start: fstart.start, end: self.tokens[self.pos - 1].span.end } });
if !matches!(self.peek(), TokenKind::Comma) { break; }
self.advance();
}
self.expect(&TokenKind::RBrace)?;
Ok(Pattern { kind: PatternKind::StructVariant { path, fields }, span: Span { start: start.start, end: self.tokens[self.pos - 1].span.end } })
}
_ => {
if path.len() == 1 {
Ok(Pattern { kind: PatternKind::Binding(path.into_iter().next().unwrap()), span: Span { start: start.start, end: self.tokens[self.pos - 1].span.end } })
} else {
Ok(Pattern { kind: PatternKind::Path(path), span: Span { start: start.start, end: self.tokens[self.pos - 1].span.end } })
}
}
}
}
_ => Err(CompileError::at_code(
codes::EXPECTED_PATTERN,
format!("expected pattern, got {:?}", self.peek()),
self.span(),
)),
}
}
fn parse_while_expr(&mut self) -> Result<Expr, CompileError> {
let start = self.span();
self.expect(&TokenKind::While)?;
let cond = self.with_no_struct_literal(true, |p| p.parse_expr())?;
let body = self.parse_block()?;
Ok(Expr {
kind: ExprKind::While { cond: Box::new(cond), body },
span: Span { start: start.start, end: self.tokens[self.pos - 1].span.end },
})
}
fn parse_loop_expr(&mut self) -> Result<Expr, CompileError> {
let start = self.span();
self.expect(&TokenKind::Loop)?;
let body = self.parse_block()?;
Ok(Expr {
kind: ExprKind::Loop { body },
span: Span { start: start.start, end: self.tokens[self.pos - 1].span.end },
})
}
fn parse_for_expr(&mut self) -> Result<Expr, CompileError> {
let start = self.span();
self.expect(&TokenKind::For)?;
let var = self.expect_ident()?;
self.expect(&TokenKind::In)?;
let start_expr = self.with_no_struct_literal(true, |p| p.parse_expr())?;
let inclusive = matches!(self.peek(), TokenKind::DotDotEq);
if inclusive {
self.expect(&TokenKind::DotDotEq)?;
} else {
self.expect(&TokenKind::DotDot)?;
}
let end_expr = self.with_no_struct_literal(true, |p| p.parse_expr())?;
let body = self.parse_block()?;
let span = Span { start: start.start, end: self.tokens[self.pos - 1].span.end };
let end_name = format!("__for_end_{}", start.start);
let var_read = |v: &str| Expr { kind: ExprKind::Var(v.to_string()), span };
let int = |n| Expr { kind: ExprKind::IntLit(n), span };
let bin = |op, l, r| Expr {
kind: ExprKind::BinOp { op, lhs: Box::new(l), rhs: Box::new(r) },
span,
};
let exit_op = if inclusive { BinOp::Gt } else { BinOp::Ge };
let mut loop_stmts = vec![
Stmt::Assign {
place: Place { root: var.clone(), fields: Vec::new(), index: None, span },
value: bin(BinOp::Add, var_read(&var), int(1)),
span,
},
Stmt::Expr {
expr: Expr {
kind: ExprKind::If {
cond: Box::new(bin(exit_op, var_read(&var), var_read(&end_name))),
then_block: Block {
stmts: vec![Stmt::Expr {
expr: Expr { kind: ExprKind::Break { value: None }, span },
span,
}],
tail: None,
span,
},
else_block: None,
},
span,
},
span,
},
];
loop_stmts.extend(body.stmts);
if let Some(tail) = body.tail {
loop_stmts.push(Stmt::Expr { expr: *tail, span });
}
let the_loop = Expr {
kind: ExprKind::Loop {
body: Block { stmts: loop_stmts, tail: None, span },
},
span,
};
let outer = Block {
stmts: vec![
Stmt::Let { name: end_name, mutable: false, ty: None, init: end_expr, span },
Stmt::Let {
name: var,
mutable: true,
ty: None,
init: bin(BinOp::Sub, start_expr, int(1)),
span,
},
Stmt::Expr { expr: the_loop, span },
],
tail: None,
span,
};
Ok(Expr { kind: ExprKind::Block(outer), span })
}
fn parse_arg_list(&mut self) -> Result<Vec<Expr>, CompileError> {
self.with_no_struct_literal(false, |p| {
let mut args = Vec::new();
if matches!(p.peek(), TokenKind::RParen) { return Ok(args); }
loop {
args.push(p.parse_expr()?);
if !matches!(p.peek(), TokenKind::Comma) { break; }
p.advance();
}
Ok(args)
})
}
fn parse_field_init_list(&mut self) -> Result<Vec<FieldInit>, CompileError> {
let mut fields = Vec::new();
while !matches!(self.peek(), TokenKind::RBrace) {
let start = self.span();
let name = self.expect_ident()?;
let value = if matches!(self.peek(), TokenKind::Colon) {
self.advance();
Some(self.parse_expr()?)
} else {
None };
fields.push(FieldInit { name, value, span: Span { start: start.start, end: self.tokens[self.pos - 1].span.end } });
if !matches!(self.peek(), TokenKind::Comma) { break; }
self.advance();
}
Ok(fields)
}
}
fn is_block_expr(expr: &Expr) -> bool {
matches!(
expr.kind,
ExprKind::While { .. }
| ExprKind::Loop { .. }
| ExprKind::If { .. }
| ExprKind::Match { .. }
| ExprKind::Block(_)
)
}
fn expr_to_place(expr: &Expr) -> Result<Place, CompileError> {
match &expr.kind {
ExprKind::Var(name) => Ok(Place { root: name.clone(), fields: Vec::new(), index: None, span: expr.span }),
ExprKind::FieldAccess { object, field } => {
let mut place = expr_to_place(object)?;
if place.index.is_some() {
return Err(CompileError::at_code(codes::INVALID_ASSIGN_TARGET, "invalid assignment target", expr.span));
}
place.fields.push(field.clone());
place.span = expr.span;
Ok(place)
}
ExprKind::Index { base, index } => {
let mut place = expr_to_place(base)?;
if place.index.is_some() {
return Err(CompileError::at_code(codes::INVALID_ASSIGN_TARGET, "invalid assignment target", expr.span));
}
place.index = Some(Box::new((**index).clone()));
place.span = expr.span;
Ok(place)
}
_ => Err(CompileError::at_code(codes::INVALID_ASSIGN_TARGET, "invalid assignment target", expr.span)),
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::rustlite::lexer;
fn parse_str(s: &str) -> Module {
let tokens = lexer::lex(s).unwrap();
parse(&tokens).unwrap()
}
#[test]
fn parse_if_statement_then_more() {
let m = parse_str(
"fn f(x: i32) -> i32 { let mut a: i32 = 0; if x > 0 { a = 1; } a }",
);
match &m.items[0] {
Item::Fn(f) => {
assert_eq!(f.body.stmts.len(), 2);
assert!(f.body.tail.is_some());
}
_ => panic!("expected fn"),
}
}
#[test]
fn parse_if_as_tail_value() {
let m = parse_str("fn abs(x: i32) -> i32 { if x > 0 { x } else { 0 - x } }");
match &m.items[0] {
Item::Fn(f) => {
assert!(f.body.stmts.is_empty());
assert!(matches!(
f.body.tail.as_deref().map(|e| &e.kind),
Some(ExprKind::If { .. })
));
}
_ => panic!("expected fn"),
}
}
#[test]
fn parse_compound_assign_desugars() {
let m = parse_str("fn f() { let mut x: i32 = 0; x += 5; x -= 2; }");
let Item::Fn(f) = &m.items[0] else {
panic!("expected fn")
};
let check = |s: &Stmt, add: bool, want_rhs: i64| {
let Stmt::Assign { place, value, .. } = s else {
panic!("expected Assign")
};
assert_eq!(place.root, "x");
let ExprKind::BinOp { op, lhs, rhs } = &value.kind else {
panic!("expected BinOp value")
};
assert!(if add { matches!(op, BinOp::Add) } else { matches!(op, BinOp::Sub) });
assert!(matches!(&lhs.kind, ExprKind::Var(n) if n == "x"));
assert!(matches!(&rhs.kind, ExprKind::IntLit(n) if *n == want_rhs));
};
check(&f.body.stmts[1], true, 5);
check(&f.body.stmts[2], false, 2);
}
#[test]
fn parse_for_desugars_to_loop_with_top_increment() {
let m = parse_str("fn f() { for i in 0..3 { let x: i32 = i; } }");
let Item::Fn(f) = &m.items[0] else {
panic!("expected fn")
};
let for_expr = f
.body
.tail
.as_deref()
.or_else(|| match f.body.stmts.last() {
Some(Stmt::Expr { expr, .. }) => Some(expr),
_ => None,
})
.expect("for expr");
let ExprKind::Block(outer) = &for_expr.kind else {
panic!("for should desugar to a block")
};
assert_eq!(outer.stmts.len(), 3, "let __end, let mut i, loop");
match &outer.stmts[1] {
Stmt::Let { name, mutable, init, .. } => {
assert_eq!(name, "i");
assert!(*mutable);
assert!(matches!(&init.kind, ExprKind::BinOp { op: BinOp::Sub, .. }));
}
_ => panic!("expected `let mut i`"),
}
let Stmt::Expr { expr, .. } = &outer.stmts[2] else {
panic!("expected loop statement")
};
let ExprKind::Loop { body } = &expr.kind else {
panic!("expected a loop")
};
match &body.stmts[0] {
Stmt::Assign { value, .. } => {
assert!(matches!(&value.kind, ExprKind::BinOp { op: BinOp::Add, .. }));
}
_ => panic!("loop body must start with the increment"),
}
assert!(matches!(
&body.stmts[1],
Stmt::Expr { expr, .. } if matches!(&expr.kind, ExprKind::If { .. })
));
}
#[test]
fn parse_bitwise_precedence() {
let m = parse_str("fn f() -> i32 { 1 + 2 << 3 }");
let Item::Fn(f) = &m.items[0] else {
panic!("expected fn")
};
let tail = f.body.tail.as_deref().expect("tail expr");
let ExprKind::BinOp { op: BinOp::Shl, lhs, .. } = &tail.kind else {
panic!("top operator should be Shl")
};
assert!(matches!(&lhs.kind, ExprKind::BinOp { op: BinOp::Add, .. }));
let m = parse_str("fn f() -> i32 { 1 | 2 & 3 }");
let Item::Fn(f) = &m.items[0] else {
panic!("expected fn")
};
let tail = f.body.tail.as_deref().expect("tail expr");
let ExprKind::BinOp { op: BinOp::BitOr, rhs, .. } = &tail.kind else {
panic!("top operator should be BitOr")
};
assert!(matches!(&rhs.kind, ExprKind::BinOp { op: BinOp::BitAnd, .. }));
}
#[test]
fn parse_match_range_pattern() {
let m = parse_str("fn f(x: i32) -> i32 { match x { 0..=5 => 1, _ => 2 } }");
let Item::Fn(f) = &m.items[0] else {
panic!("expected fn")
};
let tail = f.body.tail.as_deref().expect("match tail");
let ExprKind::Match { arms, .. } = &tail.kind else {
panic!("expected match")
};
assert!(matches!(
&arms[0].pattern.kind,
PatternKind::IntRange { lo: 0, hi: 5, inclusive: true }
));
}
#[test]
fn parse_pub_visibility_is_ignored() {
let m = parse_str(
"pub struct P { pub x: i32, y: i32 } pub(crate) fn f() -> i32 { 0 } pub const K: i32 = 1;",
);
assert_eq!(m.items.len(), 3);
match &m.items[0] {
Item::Struct(s) => {
assert_eq!(s.name, "P");
assert_eq!(s.fields.len(), 2);
assert_eq!(s.fields[0].name, "x");
}
_ => panic!("expected struct"),
}
assert!(matches!(&m.items[1], Item::Fn(f) if f.name == "f"));
assert!(matches!(&m.items[2], Item::Const(c) if c.name == "K"));
}
#[test]
fn parse_attributes_are_skipped() {
let m = parse_str(
"#![allow(dead_code)]\n#[no_mangle]\nfn frame(t: i32) -> i32 { #[allow(unused)] let mut a: i32 = t; a }",
);
assert_eq!(m.items.len(), 1);
assert!(matches!(&m.items[0], Item::Fn(f) if f.name == "frame"));
}
#[test]
fn parse_empty_fn() {
let m = parse_str("fn main() {}");
assert_eq!(m.items.len(), 1);
match &m.items[0] {
Item::Fn(f) => {
assert_eq!(f.name, "main");
assert!(f.params.is_empty());
assert!(f.ret_type.is_none());
}
_ => panic!("expected fn"),
}
}
#[test]
fn parse_fn_with_return() {
let m = parse_str("fn add(a: i32, b: i32) -> i32 { a + b }");
match &m.items[0] {
Item::Fn(f) => {
assert_eq!(f.name, "add");
assert_eq!(f.params.len(), 2);
assert!(matches!(f.ret_type, Some(Ty::I32)));
assert!(f.body.tail.is_some());
}
_ => panic!("expected fn"),
}
}
#[test]
fn parse_struct() {
let m = parse_str("struct Point { x: i32, y: i32 }");
match &m.items[0] {
Item::Struct(s) => {
assert_eq!(s.name, "Point");
assert_eq!(s.fields.len(), 2);
}
_ => panic!("expected struct"),
}
}
#[test]
fn parse_enum() {
let m = parse_str("enum Option { None, Some(i32) }");
match &m.items[0] {
Item::Enum(e) => {
assert_eq!(e.name, "Option");
assert_eq!(e.variants.len(), 2);
assert!(matches!(e.variants[0].payload, VariantPayload::Unit));
assert!(matches!(e.variants[1].payload, VariantPayload::Tuple(_)));
}
_ => panic!("expected enum"),
}
}
#[test]
fn parse_match() {
let m = parse_str(r#"
fn check(x: i32) -> i32 {
match x {
0 => 1,
_ => x + 1,
}
}
"#);
match &m.items[0] {
Item::Fn(f) => {
assert!(f.body.tail.is_some());
}
_ => panic!("expected fn"),
}
}
#[test]
fn parse_if_else() {
let m = parse_str("fn f(x: i32) -> i32 { if x > 0 { x } else { 0 - x } }");
match &m.items[0] {
Item::Fn(f) => assert!(f.body.tail.is_some()),
_ => panic!("expected fn"),
}
}
#[test]
fn parse_struct_literal() {
let m = parse_str("fn f() -> Point { Point { x: 1, y: 2 } }");
match &m.items[0] {
Item::Fn(f) => {
let tail = f.body.tail.as_ref().unwrap();
assert!(matches!(tail.kind, ExprKind::StructLit { .. }));
}
_ => panic!("expected fn"),
}
}
#[test]
fn if_cond_ident_lt_ident_is_not_struct_lit() {
let m = parse_str("fn f(a: i32, b: i32) -> i32 { if a < b { 1 } else { 0 } }");
let Item::Fn(f) = &m.items[0] else { panic!("expected fn") };
let tail = f.body.tail.as_deref().expect("if tail");
let ExprKind::If { cond, .. } = &tail.kind else { panic!("expected if") };
let ExprKind::BinOp { op: BinOp::Lt, lhs, rhs } = &cond.kind else {
panic!("condition should be `a < b`")
};
assert!(matches!(&lhs.kind, ExprKind::Var(n) if n == "a"));
assert!(matches!(&rhs.kind, ExprKind::Var(n) if n == "b"), "rhs must be the variable `b`, not a struct literal");
}
#[test]
fn while_cond_ident_bound_is_not_struct_lit() {
let m = parse_str("fn f(n: i32) { let mut i: i32 = 0; while i < n { i = i + 1; } }");
let Item::Fn(f) = &m.items[0] else { panic!("expected fn") };
let Stmt::Expr { expr, .. } = &f.body.stmts[1] else { panic!("expected while stmt") };
let ExprKind::While { cond, .. } = &expr.kind else { panic!("expected while") };
let ExprKind::BinOp { op: BinOp::Lt, rhs, .. } = &cond.kind else {
panic!("condition should be `i < n`")
};
assert!(matches!(&rhs.kind, ExprKind::Var(n) if n == "n"));
}
#[test]
fn for_iterable_ident_bound_compiles() {
assert!(
try_parse("fn f(n: i32) { let mut s: i32 = 0; for k in 0..n { s = s + k; } }").is_ok(),
"`for k in 0..n {{ … }}` (identifier bound) must compile"
);
}
#[test]
fn match_scrutinee_ident_is_not_struct_lit() {
let m = parse_str("fn f(x: i32) -> i32 { match x { 0 => 1, _ => 2 } }");
let Item::Fn(f) = &m.items[0] else { panic!("expected fn") };
let tail = f.body.tail.as_deref().expect("match tail");
let ExprKind::Match { scrutinee, .. } = &tail.kind else { panic!("expected match") };
assert!(matches!(&scrutinee.kind, ExprKind::Var(n) if n == "x"));
}
#[test]
fn struct_literal_still_parses_in_normal_positions() {
let m = parse_str(
"fn g(p: Point) -> Point { let q: Point = Point { x: 1, y: 2 }; g(Point { x: 3, y: 4 }); return Point { x: 5, y: 6 }; }",
);
let Item::Fn(f) = &m.items[0] else { panic!("expected fn") };
let Stmt::Let { init, .. } = &f.body.stmts[0] else { panic!("expected let") };
assert!(matches!(init.kind, ExprKind::StructLit { .. }), "let-init struct literal");
let Stmt::Expr { expr, .. } = &f.body.stmts[1] else { panic!("expected call stmt") };
let ExprKind::Call { args, .. } = &expr.kind else { panic!("expected call") };
assert!(matches!(args[0].kind, ExprKind::StructLit { .. }), "call-arg struct literal");
let Stmt::Return { value: Some(v), .. } = &f.body.stmts[2] else { panic!("expected return") };
assert!(matches!(v.kind, ExprKind::StructLit { .. }), "return struct literal");
}
#[test]
fn struct_literal_in_condition_works_with_parens() {
let m = parse_str("fn f(x: Foo) -> i32 { if x == (Foo { a: 1 }) { 1 } else { 0 } }");
let Item::Fn(f) = &m.items[0] else { panic!("expected fn") };
let tail = f.body.tail.as_deref().expect("if tail");
let ExprKind::If { cond, .. } = &tail.kind else { panic!("expected if") };
let ExprKind::BinOp { op: BinOp::Eq, rhs, .. } = &cond.kind else {
panic!("condition should be `x == (Foo {{ … }})`")
};
assert!(matches!(rhs.kind, ExprKind::StructLit { .. }), "parenthesised struct literal in condition");
}
#[test]
fn condition_body_can_use_struct_literals() {
let m = parse_str("fn f(c: bool) -> Point { if c { Point { x: 1, y: 2 } } else { Point { x: 0, y: 0 } } }");
let Item::Fn(f) = &m.items[0] else { panic!("expected fn") };
let tail = f.body.tail.as_deref().expect("if tail");
let ExprKind::If { then_block, .. } = &tail.kind else { panic!("expected if") };
assert!(
matches!(then_block.tail.as_deref().map(|e| &e.kind), Some(ExprKind::StructLit { .. })),
"struct literal in the then-block body"
);
}
#[test]
fn for_inclusive_range_runs_through_upper_bound() {
let m = parse_str("fn f(n: i32) { for i in 0..=n { let x: i32 = i; } }");
let Item::Fn(f) = &m.items[0] else { panic!("expected fn") };
let for_expr = f.body.tail.as_deref().or_else(|| match f.body.stmts.last() {
Some(Stmt::Expr { expr, .. }) => Some(expr),
_ => None,
}).expect("for expr");
let ExprKind::Block(outer) = &for_expr.kind else { panic!("for should desugar to a block") };
let Stmt::Expr { expr, .. } = &outer.stmts[2] else { panic!("expected loop stmt") };
let ExprKind::Loop { body } = &expr.kind else { panic!("expected loop") };
let Stmt::Expr { expr, .. } = &body.stmts[1] else { panic!("expected exit-check stmt") };
let ExprKind::If { cond, .. } = &expr.kind else { panic!("expected if") };
assert!(
matches!(&cond.kind, ExprKind::BinOp { op: BinOp::Gt, .. }),
"inclusive `..=` must use `>` for the exit test (so the body runs at i == n)"
);
let m = parse_str("fn f(n: i32) { for i in 0..n { let x: i32 = i; } }");
let Item::Fn(f) = &m.items[0] else { panic!("expected fn") };
let for_expr = f.body.tail.as_deref().or_else(|| match f.body.stmts.last() {
Some(Stmt::Expr { expr, .. }) => Some(expr),
_ => None,
}).expect("for expr");
let ExprKind::Block(outer) = &for_expr.kind else { panic!("for should desugar to a block") };
let Stmt::Expr { expr, .. } = &outer.stmts[2] else { panic!("expected loop stmt") };
let ExprKind::Loop { body } = &expr.kind else { panic!("expected loop") };
let Stmt::Expr { expr, .. } = &body.stmts[1] else { panic!("expected exit-check stmt") };
let ExprKind::If { cond, .. } = &expr.kind else { panic!("expected if") };
assert!(
matches!(&cond.kind, ExprKind::BinOp { op: BinOp::Ge, .. }),
"exclusive `..` must use `>=` for the exit test"
);
}
#[test]
fn parse_let_mut_assign() {
let m = parse_str("fn f() { let mut x: i32 = 0; x = 42; }");
match &m.items[0] {
Item::Fn(f) => {
assert_eq!(f.body.stmts.len(), 2);
assert!(matches!(f.body.stmts[0], Stmt::Let { mutable: true, .. }));
assert!(matches!(f.body.stmts[1], Stmt::Assign { .. }));
}
_ => panic!("expected fn"),
}
}
#[test]
fn parse_use_decl() {
let m = parse_str("use host::log; fn f() {}");
assert_eq!(m.uses.len(), 1);
assert_eq!(m.uses[0].path, vec!["host", "log"]);
}
#[test]
fn parse_while_loop() {
let m = parse_str("fn f() { let mut i: i32 = 0; while i < 10 { i = i + 1; } }");
match &m.items[0] {
Item::Fn(f) => assert_eq!(f.body.stmts.len(), 2),
_ => panic!("expected fn"),
}
}
#[test]
fn parse_method_call() {
let m = parse_str("fn f(s: String) -> i32 { s.len() }");
match &m.items[0] {
Item::Fn(f) => {
let tail = f.body.tail.as_ref().unwrap();
assert!(matches!(tail.kind, ExprKind::MethodCall { .. }));
}
_ => panic!("expected fn"),
}
}
fn try_parse(s: &str) -> Result<Module, CompileError> {
let tokens = lexer::lex(s).unwrap();
parse(&tokens)
}
#[test]
fn deeply_nested_parens_error_not_overflow() {
let n = MAX_RECURSION_DEPTH + 5_000;
let src = format!(
"fn f() -> i32 {{ {}1{} }}",
"(".repeat(n),
")".repeat(n)
);
assert!(try_parse(&src).is_err(), "deep paren nesting must error, not overflow");
}
#[test]
fn deeply_nested_unary_error_not_overflow() {
let n = MAX_RECURSION_DEPTH + 5_000;
let src = format!("fn f() -> i32 {{ {}1 }}", "-".repeat(n));
assert!(try_parse(&src).is_err(), "deep unary chain must error, not overflow");
}
#[test]
fn modest_nesting_still_parses() {
let src = "fn f() -> i32 { (((((1))))) }";
assert!(try_parse(src).is_ok(), "modest nesting must still parse");
}
}