pub mod ast; pub mod preprocess; #[cfg(test)] mod tests; pub use preprocess::preprocess as preprocess_source; use crate::errors::{Diagnostic, ErrorCode}; use crate::lexer::{Span, Token, TokenKind}; use ast::*; pub struct Parser { tokens: Vec, pos: usize, diagnostics: Vec, /// When true, `parse_primary` refuses to consume an `Ident {` /// pattern as a struct literal — the `{` is reserved for the /// following `if` / `while` / `for` block. Struct literals in /// conditions must be parenthesized: `if x == (Foo { a: 1 })`. restrict_struct_literals: bool, } impl Parser { pub fn new(tokens: Vec) -> Self { Self { tokens, pos: 0, diagnostics: Vec::new(), restrict_struct_literals: false, } } pub fn parse(mut self) -> (Option, Vec) { match self.parse_program() { Ok(program) => (Some(program), self.diagnostics), Err(diag) => { self.diagnostics.push(diag); (None, self.diagnostics) } } } // ── Token helpers ── fn peek(&self) -> &TokenKind { self.tokens .get(self.pos) .map_or(&TokenKind::Eof, |t| &t.kind) } fn peek_at_offset(&self, offset: usize) -> Option<&TokenKind> { self.tokens.get(self.pos + offset).map(|t| &t.kind) } fn current_span(&self) -> Span { self.tokens.get(self.pos).map_or(Span::dummy(), |t| t.span) } fn advance(&mut self) -> &Token { let tok = &self.tokens[self.pos]; if self.pos < self.tokens.len() - 1 { self.pos += 1; } tok } fn expect(&mut self, expected: &TokenKind) -> Result { if self.peek() == expected { let span = self.current_span(); self.advance(); Ok(span) } else { Err(Diagnostic::error( ErrorCode::E0201, format!("expected '{expected}', found '{}'", self.peek()), self.current_span(), )) } } fn expect_ident(&mut self) -> Result<(String, Span), Diagnostic> { if let TokenKind::Ident(name) = self.peek().clone() { let span = self.current_span(); self.advance(); Ok((name, span)) } else { Err(Diagnostic::error( ErrorCode::E0201, format!("expected identifier, found '{}'", self.peek()), self.current_span(), )) } } /// Accept an identifier or a keyword that can be used as a name /// (e.g., button names like "start", "select"). fn expect_name(&mut self) -> Result<(String, Span), Diagnostic> { let span = self.current_span(); let name = match self.peek() { TokenKind::Ident(n) => n.clone(), // Keywords that double as button/property names TokenKind::KwStart => "start".to_string(), TokenKind::KwState => "state".to_string(), TokenKind::KwBreak => "break".to_string(), TokenKind::KwContinue => "continue".to_string(), TokenKind::KwReturn => "return".to_string(), _ => { return Err(Diagnostic::error( ErrorCode::E0201, format!("expected name, found '{}'", self.peek()), span, )); } }; self.advance(); Ok((name, span)) } // ── Program ── fn parse_program(&mut self) -> Result { let mut game = None; let mut globals = Vec::new(); let mut constants = Vec::new(); let mut enums: Vec = Vec::new(); let mut structs: Vec = Vec::new(); let mut functions = Vec::new(); let mut states = Vec::new(); let mut sprites = Vec::new(); let mut palettes = Vec::new(); let mut backgrounds = Vec::new(); let mut banks = Vec::new(); let mut start_state = None; let mut on_frame = None; let span = self.current_span(); while *self.peek() != TokenKind::Eof { match self.peek().clone() { TokenKind::KwGame => { game = Some(self.parse_game_decl()?); } TokenKind::KwFast | TokenKind::KwSlow => { globals.push(self.parse_var_decl()?); } TokenKind::KwVar => { globals.push(self.parse_var_decl()?); } TokenKind::KwFun | TokenKind::KwInline => { functions.push(self.parse_fun_decl()?); } TokenKind::KwConst => { constants.push(self.parse_const_decl()?); } TokenKind::KwEnum => { enums.push(self.parse_enum_decl()?); } TokenKind::KwStruct => { structs.push(self.parse_struct_decl()?); } TokenKind::KwState => { states.push(self.parse_state_decl()?); } TokenKind::KwSprite => { sprites.push(self.parse_sprite_decl()?); } TokenKind::KwPalette => { palettes.push(self.parse_palette_decl()?); } TokenKind::KwBackground => { backgrounds.push(self.parse_background_decl()?); } TokenKind::KwBank => { banks.push(self.parse_bank_decl()?); } TokenKind::KwOn => { // Top-level `on frame` — implicit single state for M1 on_frame = Some(self.parse_on_frame()?); } TokenKind::KwStart => { let kw_span = self.current_span(); self.advance(); let (name, _) = self.expect_ident()?; if start_state.is_some() { return Err(Diagnostic::error( ErrorCode::E0505, "multiple 'start' declarations", kw_span, )); } start_state = Some(name); } _ => { return Err(Diagnostic::error( ErrorCode::E0201, format!("unexpected token '{}' at top level", self.peek()), self.current_span(), )); } } } let game = game.ok_or_else(|| { Diagnostic::error(ErrorCode::E0504, "missing 'game' declaration", span) })?; // If there's a top-level `on frame` but no explicit states, // wrap it in an implicit "Main" state if !states.is_empty() || on_frame.is_none() { // Multi-state or no frame handler — use states as-is } else if let Some(frame_block) = on_frame { states.push(StateDecl { name: "Main".to_string(), locals: Vec::new(), on_enter: None, on_exit: None, on_frame: Some(frame_block), on_scanline: Vec::new(), span, }); if start_state.is_none() { start_state = Some("Main".to_string()); } } let start_state = start_state.ok_or_else(|| { Diagnostic::error(ErrorCode::E0504, "missing 'start' declaration", span) })?; Ok(Program { game, globals, constants, enums, structs, functions, states, sprites, palettes, backgrounds, banks, start_state, span, }) } fn parse_struct_decl(&mut self) -> Result { let start = self.current_span(); self.expect(&TokenKind::KwStruct)?; let (name, _) = self.expect_ident()?; self.expect(&TokenKind::LBrace)?; let mut fields = Vec::new(); while *self.peek() != TokenKind::RBrace && *self.peek() != TokenKind::Eof { let field_span = self.current_span(); let (field_name, _) = self.expect_ident()?; self.expect(&TokenKind::Colon)?; let field_type = self.parse_type()?; fields.push(StructField { name: field_name, field_type, span: field_span, }); if *self.peek() == TokenKind::Comma { self.advance(); } else if *self.peek() != TokenKind::RBrace { return Err(Diagnostic::error( ErrorCode::E0201, "expected ',' or '}' in struct body", self.current_span(), )); } } self.expect(&TokenKind::RBrace)?; Ok(StructDecl { name, fields, span: Span::new(start.file_id, start.start, self.current_span().end), }) } fn parse_enum_decl(&mut self) -> Result { let start = self.current_span(); self.expect(&TokenKind::KwEnum)?; let (name, _) = self.expect_ident()?; self.expect(&TokenKind::LBrace)?; let mut variants = Vec::new(); while *self.peek() != TokenKind::RBrace && *self.peek() != TokenKind::Eof { let span = self.current_span(); let (vname, _) = self.expect_ident()?; variants.push((vname, span)); if *self.peek() == TokenKind::Comma { self.advance(); } else if *self.peek() != TokenKind::RBrace { return Err(Diagnostic::error( ErrorCode::E0201, "expected ',' or '}' in enum body", self.current_span(), )); } } self.expect(&TokenKind::RBrace)?; if variants.len() > 256 { return Err(Diagnostic::error( ErrorCode::E0201, "enum has more than 256 variants (u8 overflow)", start, )); } Ok(EnumDecl { name, variants, span: Span::new(start.file_id, start.start, self.current_span().end), }) } // ── Game declaration ── fn parse_game_decl(&mut self) -> Result { let start_span = self.current_span(); self.expect(&TokenKind::KwGame)?; let name = if let TokenKind::StringLiteral(s) = self.peek().clone() { self.advance(); s } else { return Err(Diagnostic::error( ErrorCode::E0201, "expected game name string", self.current_span(), )); }; self.expect(&TokenKind::LBrace)?; let mut mapper = Mapper::NROM; let mut mirroring = Mirroring::Horizontal; while *self.peek() != TokenKind::RBrace && *self.peek() != TokenKind::Eof { let (key, _) = self.expect_ident()?; self.expect(&TokenKind::Colon)?; match key.as_str() { "mapper" => { let (val, _) = self.expect_ident()?; mapper = match val.as_str() { "NROM" => Mapper::NROM, "MMC1" => Mapper::MMC1, "UxROM" => Mapper::UxROM, "MMC3" => Mapper::MMC3, _ => { return Err(Diagnostic::error( ErrorCode::E0201, format!("unknown mapper '{val}'"), self.current_span(), ) .with_help("supported mappers: NROM, MMC1, UxROM, MMC3")); } }; } "mirroring" => { let (val, _) = self.expect_ident()?; mirroring = match val.as_str() { "horizontal" => Mirroring::Horizontal, "vertical" => Mirroring::Vertical, _ => { return Err(Diagnostic::error( ErrorCode::E0201, format!("unknown mirroring '{val}'"), self.current_span(), )); } }; } _ => { return Err(Diagnostic::error( ErrorCode::E0201, format!("unknown game property '{key}'"), self.current_span(), )); } } } self.expect(&TokenKind::RBrace)?; Ok(GameDecl { name, mapper, mirroring, span: Span::new( start_span.file_id, start_span.start, self.current_span().end, ), }) } // ── Variable declaration ── fn parse_var_decl(&mut self) -> Result { let start = self.current_span(); let placement = match self.peek() { TokenKind::KwFast => { self.advance(); Placement::Fast } TokenKind::KwSlow => { self.advance(); Placement::Slow } _ => Placement::Auto, }; self.expect(&TokenKind::KwVar)?; let (name, _) = self.expect_ident()?; self.expect(&TokenKind::Colon)?; let var_type = self.parse_type()?; let init = if *self.peek() == TokenKind::Assign { self.advance(); Some(self.parse_expr()?) } else { None }; Ok(VarDecl { name, var_type, init, placement, span: Span::new(start.file_id, start.start, self.current_span().end), }) } // ── Const declaration ── fn parse_const_decl(&mut self) -> Result { let start = self.current_span(); self.expect(&TokenKind::KwConst)?; let (name, _) = self.expect_ident()?; self.expect(&TokenKind::Colon)?; let const_type = self.parse_type()?; self.expect(&TokenKind::Assign)?; let value = self.parse_expr()?; Ok(ConstDecl { name, const_type, value, span: Span::new(start.file_id, start.start, self.current_span().end), }) } // ── Function declaration ── fn parse_fun_decl(&mut self) -> Result { let start = self.current_span(); let is_inline = if *self.peek() == TokenKind::KwInline { self.advance(); true } else { false }; self.expect(&TokenKind::KwFun)?; let (name, _) = self.expect_ident()?; self.expect(&TokenKind::LParen)?; let mut params = Vec::new(); while *self.peek() != TokenKind::RParen && *self.peek() != TokenKind::Eof { let (pname, _) = self.expect_ident()?; self.expect(&TokenKind::Colon)?; let ptype = self.parse_type()?; params.push(Param { name: pname, param_type: ptype, }); if *self.peek() == TokenKind::Comma { self.advance(); } } self.expect(&TokenKind::RParen)?; let return_type = if *self.peek() == TokenKind::Arrow { self.advance(); Some(self.parse_type()?) } else { None }; let body = self.parse_block()?; Ok(FunDecl { name, params, return_type, body, is_inline, span: Span::new(start.file_id, start.start, self.current_span().end), }) } // ── State declaration ── fn parse_state_decl(&mut self) -> Result { let start = self.current_span(); self.expect(&TokenKind::KwState)?; let (name, _) = self.expect_ident()?; self.expect(&TokenKind::LBrace)?; let mut locals = Vec::new(); let mut on_enter = None; let mut on_exit = None; let mut on_frame = None; let mut on_scanline: Vec<(u8, Block)> = Vec::new(); while *self.peek() != TokenKind::RBrace && *self.peek() != TokenKind::Eof { match self.peek().clone() { TokenKind::KwFast | TokenKind::KwSlow | TokenKind::KwVar => { locals.push(self.parse_var_decl()?); } TokenKind::KwOn => { self.advance(); let (event, event_span) = self.expect_ident()?; match event.as_str() { "enter" => { on_enter = Some(self.parse_block()?); } "exit" => { on_exit = Some(self.parse_block()?); } "frame" => { on_frame = Some(self.parse_block()?); } "scanline" => { // Syntax: `on scanline(N) { ... }` self.expect(&TokenKind::LParen)?; let line = if let TokenKind::IntLiteral(v) = self.peek().clone() { self.advance(); if v > 239 { return Err(Diagnostic::error( ErrorCode::E0201, format!("scanline value {v} out of range (0-239)"), self.current_span(), )); } v as u8 } else { return Err(Diagnostic::error( ErrorCode::E0201, "expected integer scanline number", self.current_span(), )); }; self.expect(&TokenKind::RParen)?; let body = self.parse_block()?; on_scanline.push((line, body)); } _ => { return Err(Diagnostic::error( ErrorCode::E0201, format!("unknown event handler 'on {event}'"), event_span, )); } } } _ => { return Err(Diagnostic::error( ErrorCode::E0201, format!("unexpected token '{}' in state body", self.peek()), self.current_span(), )); } } } self.expect(&TokenKind::RBrace)?; Ok(StateDecl { name, locals, on_enter, on_exit, on_frame, on_scanline, span: Span::new(start.file_id, start.start, self.current_span().end), }) } // ── Bank declaration ── fn parse_bank_decl(&mut self) -> Result { let start = self.current_span(); self.expect(&TokenKind::KwBank)?; let (name, _) = self.expect_ident()?; self.expect(&TokenKind::Colon)?; let (type_str, _) = self.expect_ident()?; let bank_type = match type_str.as_str() { "prg" => BankType::Prg, "chr" => BankType::Chr, _ => { return Err(Diagnostic::error( ErrorCode::E0201, format!("expected 'prg' or 'chr', found '{type_str}'"), self.current_span(), )); } }; Ok(BankDecl { name, bank_type, span: Span::new(start.file_id, start.start, self.current_span().end), }) } // ── Top-level on frame ── fn parse_on_frame(&mut self) -> Result { self.expect(&TokenKind::KwOn)?; let (event, _) = self.expect_ident()?; if event != "frame" { return Err(Diagnostic::error( ErrorCode::E0201, format!("expected 'frame' after 'on', found '{event}'"), self.current_span(), )); } self.parse_block() } // ── Sprite / Palette / Background declarations ── fn parse_sprite_decl(&mut self) -> Result { let start = self.current_span(); self.expect(&TokenKind::KwSprite)?; let (name, _) = self.expect_ident()?; self.expect(&TokenKind::LBrace)?; let mut chr_source = None; while *self.peek() != TokenKind::RBrace && *self.peek() != TokenKind::Eof { let (key, _) = self.expect_ident()?; self.expect(&TokenKind::Colon)?; match key.as_str() { "chr" => { chr_source = Some(self.parse_asset_source()?); } _ => { return Err(Diagnostic::error( ErrorCode::E0201, format!("unknown sprite property '{key}'"), self.current_span(), )); } } } self.expect(&TokenKind::RBrace)?; let chr_source = chr_source.ok_or_else(|| { Diagnostic::error(ErrorCode::E0201, "sprite requires 'chr' property", start) })?; Ok(SpriteDecl { name, chr_source, span: Span::new(start.file_id, start.start, self.current_span().end), }) } fn parse_palette_decl(&mut self) -> Result { let start = self.current_span(); self.expect(&TokenKind::KwPalette)?; let (name, _) = self.expect_ident()?; self.expect(&TokenKind::LBrace)?; let mut colors = None; while *self.peek() != TokenKind::RBrace && *self.peek() != TokenKind::Eof { let (key, _) = self.expect_ident()?; self.expect(&TokenKind::Colon)?; match key.as_str() { "colors" => { self.expect(&TokenKind::LBracket)?; let mut vals = Vec::new(); while *self.peek() != TokenKind::RBracket && *self.peek() != TokenKind::Eof { if let TokenKind::IntLiteral(v) = self.peek().clone() { self.advance(); vals.push(v as u8); } else { return Err(Diagnostic::error( ErrorCode::E0201, format!("expected color index, found '{}'", self.peek()), self.current_span(), )); } if *self.peek() == TokenKind::Comma { self.advance(); } } self.expect(&TokenKind::RBracket)?; colors = Some(vals); } _ => { return Err(Diagnostic::error( ErrorCode::E0201, format!("unknown palette property '{key}'"), self.current_span(), )); } } } self.expect(&TokenKind::RBrace)?; let colors = colors.ok_or_else(|| { Diagnostic::error( ErrorCode::E0201, "palette requires 'colors' property", start, ) })?; Ok(PaletteDecl { name, colors, span: Span::new(start.file_id, start.start, self.current_span().end), }) } fn parse_background_decl(&mut self) -> Result { let start = self.current_span(); self.expect(&TokenKind::KwBackground)?; let (name, _) = self.expect_ident()?; self.expect(&TokenKind::LBrace)?; let mut chr_source = None; while *self.peek() != TokenKind::RBrace && *self.peek() != TokenKind::Eof { let (key, _) = self.expect_ident()?; self.expect(&TokenKind::Colon)?; match key.as_str() { "chr" => { chr_source = Some(self.parse_asset_source()?); } _ => { return Err(Diagnostic::error( ErrorCode::E0201, format!("unknown background property '{key}'"), self.current_span(), )); } } } self.expect(&TokenKind::RBrace)?; let chr_source = chr_source.ok_or_else(|| { Diagnostic::error( ErrorCode::E0201, "background requires 'chr' property", start, ) })?; Ok(BackgroundDecl { name, chr_source, span: Span::new(start.file_id, start.start, self.current_span().end), }) } fn parse_asset_source(&mut self) -> Result { match self.peek() { TokenKind::At => { self.advance(); // consume '@' let (kind, _) = self.expect_ident()?; self.expect(&TokenKind::LParen)?; let path = if let TokenKind::StringLiteral(s) = self.peek().clone() { self.advance(); s } else { return Err(Diagnostic::error( ErrorCode::E0201, format!("expected string path, found '{}'", self.peek()), self.current_span(), )); }; self.expect(&TokenKind::RParen)?; match kind.as_str() { "chr" => Ok(AssetSource::Chr(path)), "binary" => Ok(AssetSource::Binary(path)), _ => Err(Diagnostic::error( ErrorCode::E0201, format!("unknown asset source kind '@{kind}'"), self.current_span(), )), } } TokenKind::LBracket => { self.advance(); let mut bytes = Vec::new(); while *self.peek() != TokenKind::RBracket && *self.peek() != TokenKind::Eof { if let TokenKind::IntLiteral(v) = self.peek().clone() { self.advance(); bytes.push(v as u8); } else { return Err(Diagnostic::error( ErrorCode::E0201, format!("expected byte value, found '{}'", self.peek()), self.current_span(), )); } if *self.peek() == TokenKind::Comma { self.advance(); } } self.expect(&TokenKind::RBracket)?; Ok(AssetSource::Inline(bytes)) } _ => Err(Diagnostic::error( ErrorCode::E0201, format!( "expected asset source (@chr, @binary, or [...]), found '{}'", self.peek() ), self.current_span(), )), } } // ── Block ── fn parse_block(&mut self) -> Result { let start = self.current_span(); self.expect(&TokenKind::LBrace)?; let mut statements = Vec::new(); while *self.peek() != TokenKind::RBrace && *self.peek() != TokenKind::Eof { statements.push(self.parse_statement()?); } self.expect(&TokenKind::RBrace)?; Ok(Block { statements, span: Span::new(start.file_id, start.start, self.current_span().end), }) } // ── Statements ── fn parse_statement(&mut self) -> Result { match self.peek().clone() { TokenKind::KwFast | TokenKind::KwSlow | TokenKind::KwVar => { let decl = self.parse_var_decl()?; Ok(Statement::VarDecl(decl)) } TokenKind::KwIf => self.parse_if(), TokenKind::KwWhile => self.parse_while(), TokenKind::KwFor => self.parse_for(), TokenKind::KwMatch => self.parse_match(), TokenKind::KwLoop => self.parse_loop(), TokenKind::KwBreak => { let span = self.current_span(); self.advance(); Ok(Statement::Break(span)) } TokenKind::KwContinue => { let span = self.current_span(); self.advance(); Ok(Statement::Continue(span)) } TokenKind::KwReturn => { let span = self.current_span(); self.advance(); let value = if *self.peek() == TokenKind::RBrace { None } else { Some(self.parse_expr()?) }; Ok(Statement::Return(value, span)) } TokenKind::KwDraw => self.parse_draw(), TokenKind::KwTransition => { let span = self.current_span(); self.advance(); let (name, _) = self.expect_ident()?; Ok(Statement::Transition(name, span)) } TokenKind::KwWaitFrame => { let span = self.current_span(); self.advance(); Ok(Statement::WaitFrame(span)) } TokenKind::KwLoadBackground => { let span = self.current_span(); self.advance(); let (name, _) = self.expect_ident()?; Ok(Statement::LoadBackground(name, span)) } TokenKind::KwSetPalette => { let span = self.current_span(); self.advance(); let (name, _) = self.expect_ident()?; Ok(Statement::SetPalette(name, span)) } TokenKind::KwScroll => { let span = self.current_span(); self.advance(); self.expect(&TokenKind::LParen)?; let x = self.parse_expr()?; self.expect(&TokenKind::Comma)?; let y = self.parse_expr()?; self.expect(&TokenKind::RParen)?; Ok(Statement::Scroll(x, y, span)) } TokenKind::KwDebug => self.parse_debug_statement(), TokenKind::KwPlay => { let span = self.current_span(); self.advance(); let (name, _) = self.expect_ident()?; Ok(Statement::Play(name, span)) } TokenKind::KwStartMusic => { let span = self.current_span(); self.advance(); let (name, _) = self.expect_ident()?; Ok(Statement::StartMusic(name, span)) } TokenKind::KwStopMusic => { let span = self.current_span(); self.advance(); Ok(Statement::StopMusic(span)) } TokenKind::KwAsm => { let span = self.current_span(); self.advance(); // KwAsm // The lexer emits an AsmBody token after `asm` when it // sees the opening brace. Consume it here. if let TokenKind::AsmBody(body) = self.peek().clone() { self.advance(); Ok(Statement::InlineAsm(body, span)) } else { Err(Diagnostic::error( ErrorCode::E0201, "expected `{` after `asm`", self.current_span(), )) } } TokenKind::Ident(_) => self.parse_assign_or_call(), _ => Err(Diagnostic::error( ErrorCode::E0201, format!("unexpected token '{}' in statement position", self.peek()), self.current_span(), )), } } fn parse_if(&mut self) -> Result { let start = self.current_span(); self.expect(&TokenKind::KwIf)?; let saved = self.restrict_struct_literals; self.restrict_struct_literals = true; let condition = self.parse_expr()?; self.restrict_struct_literals = saved; let then_block = self.parse_block()?; let mut else_ifs = Vec::new(); let mut else_block = None; while *self.peek() == TokenKind::KwElse { self.advance(); if *self.peek() == TokenKind::KwIf { self.advance(); self.restrict_struct_literals = true; let cond = self.parse_expr()?; self.restrict_struct_literals = saved; let block = self.parse_block()?; else_ifs.push((cond, block)); } else { else_block = Some(self.parse_block()?); break; } } Ok(Statement::If( condition, then_block, else_ifs, else_block, start, )) } fn parse_while(&mut self) -> Result { let start = self.current_span(); self.expect(&TokenKind::KwWhile)?; let saved = self.restrict_struct_literals; self.restrict_struct_literals = true; let condition = self.parse_expr()?; self.restrict_struct_literals = saved; let body = self.parse_block()?; Ok(Statement::While(condition, body, start)) } fn parse_loop(&mut self) -> Result { let start = self.current_span(); self.expect(&TokenKind::KwLoop)?; let body = self.parse_block()?; Ok(Statement::Loop(body, start)) } /// Parse `match expr { pat => { body }, pat => { body }, _ => { body } }`. /// Desugars to a chain of `if expr == pat { body } else if ...` /// at parse time — no dedicated AST variant needed. fn parse_match(&mut self) -> Result { let start = self.current_span(); self.expect(&TokenKind::KwMatch)?; let saved = self.restrict_struct_literals; self.restrict_struct_literals = true; let scrutinee = self.parse_expr()?; self.restrict_struct_literals = saved; self.expect(&TokenKind::LBrace)?; let mut arms: Vec<(Expr, Block)> = Vec::new(); let mut default: Option = None; while *self.peek() != TokenKind::RBrace && *self.peek() != TokenKind::Eof { // A default arm is `_ => { ... }`. if let TokenKind::Ident(name) = self.peek().clone() { if name == "_" { self.advance(); self.expect(&TokenKind::FatArrow)?; let body = self.parse_block()?; default = Some(body); if *self.peek() == TokenKind::Comma { self.advance(); } continue; } } let pat_span = self.current_span(); let pat = self.parse_expr()?; self.expect(&TokenKind::FatArrow)?; let body = self.parse_block()?; // Build `scrutinee == pat` as the branch condition. let cond = Expr::BinaryOp( Box::new(scrutinee.clone()), BinOp::Eq, Box::new(pat), pat_span, ); arms.push((cond, body)); if *self.peek() == TokenKind::Comma { self.advance(); } } self.expect(&TokenKind::RBrace)?; if arms.is_empty() { // `match x { _ => body }` or empty match — emit the // default block directly, or an empty no-op. if let Some(body) = default { return Ok(Statement::If( Expr::BoolLiteral(true, start), body, Vec::new(), None, start, )); } return Ok(Statement::If( Expr::BoolLiteral(false, start), Block { statements: Vec::new(), span: start, }, Vec::new(), None, start, )); } // Build an if/else-if chain. The first arm becomes the // `then` block; subsequent arms become `else if` entries; // the default arm (if any) becomes the final `else`. let (first_cond, first_body) = arms.remove(0); Ok(Statement::If(first_cond, first_body, arms, default, start)) } fn parse_for(&mut self) -> Result { let start = self.current_span(); self.expect(&TokenKind::KwFor)?; let (var, _) = self.expect_ident()?; self.expect(&TokenKind::KwIn)?; let saved = self.restrict_struct_literals; self.restrict_struct_literals = true; let start_expr = self.parse_expr()?; self.expect(&TokenKind::DotDot)?; let end_expr = self.parse_expr()?; self.restrict_struct_literals = saved; let body = self.parse_block()?; Ok(Statement::For { var, start: start_expr, end: end_expr, body, span: start, }) } fn parse_draw(&mut self) -> Result { let start = self.current_span(); self.expect(&TokenKind::KwDraw)?; let (sprite_name, _) = self.expect_ident()?; let mut x = None; let mut y = None; let mut frame = None; // Parse keyword arguments: at: (x, y), frame: n // Only consume an ident if it's followed by ':', indicating a keyword arg. while matches!(self.peek(), TokenKind::Ident(_)) && self.peek_at_offset(1) == Some(&TokenKind::Colon) { let (key, _) = self.expect_ident()?; self.expect(&TokenKind::Colon)?; match key.as_str() { "at" => { self.expect(&TokenKind::LParen)?; x = Some(self.parse_expr()?); self.expect(&TokenKind::Comma)?; y = Some(self.parse_expr()?); self.expect(&TokenKind::RParen)?; } "frame" => { frame = Some(self.parse_expr()?); } _ => { return Err(Diagnostic::error( ErrorCode::E0201, format!("unknown draw property '{key}'"), self.current_span(), )); } } } let x = x.ok_or_else(|| { Diagnostic::error(ErrorCode::E0201, "draw requires 'at: (x, y)'", start) })?; let y = y.ok_or_else(|| { Diagnostic::error(ErrorCode::E0201, "draw requires 'at: (x, y)'", start) })?; Ok(Statement::Draw(DrawStmt { sprite_name, x, y, frame, span: start, })) } /// Parse debug.log(...) or debug.assert(...) fn parse_debug_statement(&mut self) -> Result { let start = self.current_span(); self.expect(&TokenKind::KwDebug)?; self.expect(&TokenKind::Dot)?; let (method, _) = self.expect_ident()?; self.expect(&TokenKind::LParen)?; match method.as_str() { "log" => { let mut args = Vec::new(); while *self.peek() != TokenKind::RParen && *self.peek() != TokenKind::Eof { args.push(self.parse_expr()?); if *self.peek() == TokenKind::Comma { self.advance(); } } self.expect(&TokenKind::RParen)?; Ok(Statement::DebugLog(args, start)) } "assert" => { let cond = self.parse_expr()?; self.expect(&TokenKind::RParen)?; Ok(Statement::DebugAssert(cond, start)) } _ => Err(Diagnostic::error( ErrorCode::E0201, format!("unknown debug method '{method}' (expected 'log' or 'assert')"), start, )), } } fn parse_assign_or_call(&mut self) -> Result { let start = self.current_span(); let (name, _) = self.expect_ident()?; // Check for button.X pattern if name == "button" && *self.peek() == TokenKind::Dot { // This shouldn't be a statement on its own return Err(Diagnostic::error( ErrorCode::E0201, "button read is an expression, not a statement", start, )); } match self.peek().clone() { TokenKind::Assign => { self.advance(); let value = self.parse_expr()?; Ok(Statement::Assign( LValue::Var(name), AssignOp::Assign, value, start, )) } TokenKind::PlusAssign => { self.advance(); let value = self.parse_expr()?; Ok(Statement::Assign( LValue::Var(name), AssignOp::PlusAssign, value, start, )) } TokenKind::MinusAssign => { self.advance(); let value = self.parse_expr()?; Ok(Statement::Assign( LValue::Var(name), AssignOp::MinusAssign, value, start, )) } TokenKind::AmpAssign => { self.advance(); let value = self.parse_expr()?; Ok(Statement::Assign( LValue::Var(name), AssignOp::AmpAssign, value, start, )) } TokenKind::PipeAssign => { self.advance(); let value = self.parse_expr()?; Ok(Statement::Assign( LValue::Var(name), AssignOp::PipeAssign, value, start, )) } TokenKind::CaretAssign => { self.advance(); let value = self.parse_expr()?; Ok(Statement::Assign( LValue::Var(name), AssignOp::CaretAssign, value, start, )) } TokenKind::ShiftLeftAssign => { self.advance(); let value = self.parse_expr()?; Ok(Statement::Assign( LValue::Var(name), AssignOp::ShiftLeftAssign, value, start, )) } TokenKind::ShiftRightAssign => { self.advance(); let value = self.parse_expr()?; Ok(Statement::Assign( LValue::Var(name), AssignOp::ShiftRightAssign, value, start, )) } TokenKind::LBracket => { // Array index assignment: name[index] = value self.advance(); let index = self.parse_expr()?; self.expect(&TokenKind::RBracket)?; let op = self.parse_assign_op()?; let value = self.parse_expr()?; Ok(Statement::Assign( LValue::ArrayIndex(name, Box::new(index)), op, value, start, )) } TokenKind::Dot => { // Field assignment: name.field = value self.advance(); let (field, _) = self.expect_ident()?; let op = self.parse_assign_op()?; let value = self.parse_expr()?; Ok(Statement::Assign( LValue::Field(name, field), op, value, start, )) } TokenKind::LParen => { // Function call self.advance(); let mut args = Vec::new(); while *self.peek() != TokenKind::RParen && *self.peek() != TokenKind::Eof { args.push(self.parse_expr()?); if *self.peek() == TokenKind::Comma { self.advance(); } } self.expect(&TokenKind::RParen)?; Ok(Statement::Call(name, args, start)) } _ => Err(Diagnostic::error( ErrorCode::E0201, format!( "expected assignment operator or '(' after identifier, found '{}'", self.peek() ), self.current_span(), )), } } fn parse_assign_op(&mut self) -> Result { match self.peek() { TokenKind::Assign => { self.advance(); Ok(AssignOp::Assign) } TokenKind::PlusAssign => { self.advance(); Ok(AssignOp::PlusAssign) } TokenKind::MinusAssign => { self.advance(); Ok(AssignOp::MinusAssign) } TokenKind::AmpAssign => { self.advance(); Ok(AssignOp::AmpAssign) } TokenKind::PipeAssign => { self.advance(); Ok(AssignOp::PipeAssign) } TokenKind::CaretAssign => { self.advance(); Ok(AssignOp::CaretAssign) } TokenKind::ShiftLeftAssign => { self.advance(); Ok(AssignOp::ShiftLeftAssign) } TokenKind::ShiftRightAssign => { self.advance(); Ok(AssignOp::ShiftRightAssign) } _ => Err(Diagnostic::error( ErrorCode::E0201, format!("expected assignment operator, found '{}'", self.peek()), self.current_span(), )), } } // ── Type parsing ── fn parse_type(&mut self) -> Result { let base = match self.peek().clone() { TokenKind::KwU8 => { self.advance(); NesType::U8 } TokenKind::KwI8 => { self.advance(); NesType::I8 } TokenKind::KwU16 => { self.advance(); NesType::U16 } TokenKind::KwBool => { self.advance(); NesType::Bool } TokenKind::Ident(name) => { // User-declared struct types are referenced by name. // The analyzer validates that the name exists. self.advance(); NesType::Struct(name) } _ => { return Err(Diagnostic::error( ErrorCode::E0201, format!("expected type, found '{}'", self.peek()), self.current_span(), )); } }; // Check for array suffix [N] if *self.peek() == TokenKind::LBracket { self.advance(); if let TokenKind::IntLiteral(size) = self.peek().clone() { self.advance(); self.expect(&TokenKind::RBracket)?; Ok(NesType::Array(Box::new(base), size)) } else { Err(Diagnostic::error( ErrorCode::E0201, "expected array size", self.current_span(), )) } } else { Ok(base) } } // ── Expression parsing (Pratt / precedence climbing) ── fn parse_expr(&mut self) -> Result { self.parse_or_expr() } fn parse_or_expr(&mut self) -> Result { let mut left = self.parse_and_expr()?; while *self.peek() == TokenKind::KwOr { let span = self.current_span(); self.advance(); let right = self.parse_and_expr()?; left = Expr::BinaryOp(Box::new(left), BinOp::Or, Box::new(right), span); } Ok(left) } fn parse_and_expr(&mut self) -> Result { let mut left = self.parse_comparison()?; while *self.peek() == TokenKind::KwAnd { let span = self.current_span(); self.advance(); let right = self.parse_comparison()?; left = Expr::BinaryOp(Box::new(left), BinOp::And, Box::new(right), span); } Ok(left) } fn parse_comparison(&mut self) -> Result { let mut left = self.parse_bitwise_or()?; loop { let (op, span) = match self.peek() { TokenKind::Eq => (BinOp::Eq, self.current_span()), TokenKind::NotEq => (BinOp::NotEq, self.current_span()), TokenKind::Lt => (BinOp::Lt, self.current_span()), TokenKind::Gt => (BinOp::Gt, self.current_span()), TokenKind::LtEq => (BinOp::LtEq, self.current_span()), TokenKind::GtEq => (BinOp::GtEq, self.current_span()), _ => break, }; self.advance(); let right = self.parse_bitwise_or()?; left = Expr::BinaryOp(Box::new(left), op, Box::new(right), span); } Ok(left) } fn parse_bitwise_or(&mut self) -> Result { let mut left = self.parse_bitwise_xor()?; while *self.peek() == TokenKind::Pipe { let span = self.current_span(); self.advance(); let right = self.parse_bitwise_xor()?; left = Expr::BinaryOp(Box::new(left), BinOp::BitwiseOr, Box::new(right), span); } Ok(left) } fn parse_bitwise_xor(&mut self) -> Result { let mut left = self.parse_bitwise_and()?; while *self.peek() == TokenKind::Caret { let span = self.current_span(); self.advance(); let right = self.parse_bitwise_and()?; left = Expr::BinaryOp(Box::new(left), BinOp::BitwiseXor, Box::new(right), span); } Ok(left) } fn parse_bitwise_and(&mut self) -> Result { let mut left = self.parse_shift()?; while *self.peek() == TokenKind::Amp { let span = self.current_span(); self.advance(); let right = self.parse_shift()?; left = Expr::BinaryOp(Box::new(left), BinOp::BitwiseAnd, Box::new(right), span); } Ok(left) } fn parse_shift(&mut self) -> Result { let mut left = self.parse_additive()?; loop { let (op, span) = match self.peek() { TokenKind::ShiftLeft => (BinOp::ShiftLeft, self.current_span()), TokenKind::ShiftRight => (BinOp::ShiftRight, self.current_span()), _ => break, }; self.advance(); let right = self.parse_additive()?; left = Expr::BinaryOp(Box::new(left), op, Box::new(right), span); } Ok(left) } fn parse_additive(&mut self) -> Result { let mut left = self.parse_multiplicative()?; loop { let (op, span) = match self.peek() { TokenKind::Plus => (BinOp::Add, self.current_span()), TokenKind::Minus => (BinOp::Sub, self.current_span()), _ => break, }; self.advance(); let right = self.parse_multiplicative()?; left = Expr::BinaryOp(Box::new(left), op, Box::new(right), span); } Ok(left) } fn parse_multiplicative(&mut self) -> Result { let mut left = self.parse_unary()?; loop { let (op, span) = match self.peek() { TokenKind::Star => (BinOp::Mul, self.current_span()), TokenKind::Slash => (BinOp::Div, self.current_span()), TokenKind::Percent => (BinOp::Mod, self.current_span()), _ => break, }; self.advance(); let right = self.parse_unary()?; left = Expr::BinaryOp(Box::new(left), op, Box::new(right), span); } Ok(left) } fn parse_unary(&mut self) -> Result { let expr = match self.peek().clone() { TokenKind::Minus => { let span = self.current_span(); self.advance(); let expr = self.parse_unary()?; Expr::UnaryOp(UnaryOp::Negate, Box::new(expr), span) } TokenKind::KwNot => { let span = self.current_span(); self.advance(); let expr = self.parse_unary()?; Expr::UnaryOp(UnaryOp::Not, Box::new(expr), span) } TokenKind::Tilde => { let span = self.current_span(); self.advance(); let expr = self.parse_unary()?; Expr::UnaryOp(UnaryOp::BitNot, Box::new(expr), span) } _ => self.parse_primary()?, }; self.parse_cast_suffix(expr) } fn parse_cast_suffix(&mut self, mut expr: Expr) -> Result { while *self.peek() == TokenKind::KwAs { let span = self.current_span(); self.advance(); let target_type = self.parse_type()?; expr = Expr::Cast(Box::new(expr), target_type, span); } Ok(expr) } fn parse_primary(&mut self) -> Result { match self.peek().clone() { TokenKind::IntLiteral(v) => { let span = self.current_span(); self.advance(); Ok(Expr::IntLiteral(v, span)) } TokenKind::BoolLiteral(v) => { let span = self.current_span(); self.advance(); Ok(Expr::BoolLiteral(v, span)) } TokenKind::Ident(name) => { let span = self.current_span(); self.advance(); // Check for button.X (player 1 default) if name == "button" && *self.peek() == TokenKind::Dot { self.advance(); let (button, _) = self.expect_name()?; return Ok(Expr::ButtonRead(None, button, span)); } // Check for p1.button.X / p2.button.X if (name == "p1" || name == "p2") && *self.peek() == TokenKind::Dot { self.advance(); // Expect 'button' if let TokenKind::Ident(kw) = self.peek().clone() { if kw == "button" { self.advance(); self.expect(&TokenKind::Dot)?; let (button, _) = self.expect_name()?; let player = if name == "p1" { Some(Player::P1) } else { Some(Player::P2) }; return Ok(Expr::ButtonRead(player, button, span)); } } return Err(Diagnostic::error( ErrorCode::E0201, "expected 'button' after 'p1.' or 'p2.'", self.current_span(), )); } // Check for array index if *self.peek() == TokenKind::LBracket { self.advance(); let index = self.parse_expr()?; self.expect(&TokenKind::RBracket)?; return Ok(Expr::ArrayIndex(name, Box::new(index), span)); } // Check for function call if *self.peek() == TokenKind::LParen { self.advance(); let mut args = Vec::new(); while *self.peek() != TokenKind::RParen && *self.peek() != TokenKind::Eof { args.push(self.parse_expr()?); if *self.peek() == TokenKind::Comma { self.advance(); } } self.expect(&TokenKind::RParen)?; return Ok(Expr::Call(name, args, span)); } // Check for field access: `name.field` if *self.peek() == TokenKind::Dot { self.advance(); let (field, _) = self.expect_ident()?; return Ok(Expr::FieldAccess(name, field, span)); } // Check for struct literal: `Name { field: expr, ... }`. // Disabled in condition contexts to keep parsing // unambiguous for `if`/`while`/`for`. if !self.restrict_struct_literals && *self.peek() == TokenKind::LBrace { self.advance(); let mut fields = Vec::new(); while *self.peek() != TokenKind::RBrace && *self.peek() != TokenKind::Eof { let (field_name, _) = self.expect_ident()?; self.expect(&TokenKind::Colon)?; // Struct literal field values can contain // their own nested struct literals, so we // temporarily allow them regardless of the // outer restriction. let saved = self.restrict_struct_literals; self.restrict_struct_literals = false; let value = self.parse_expr()?; self.restrict_struct_literals = saved; fields.push((field_name, value)); if *self.peek() == TokenKind::Comma { self.advance(); } else if *self.peek() != TokenKind::RBrace { return Err(Diagnostic::error( ErrorCode::E0201, "expected ',' or '}' in struct literal", self.current_span(), )); } } self.expect(&TokenKind::RBrace)?; return Ok(Expr::StructLiteral(name, fields, span)); } Ok(Expr::Ident(name, span)) } TokenKind::LBracket => { let span = self.current_span(); self.advance(); let mut elements = Vec::new(); while *self.peek() != TokenKind::RBracket && *self.peek() != TokenKind::Eof { elements.push(self.parse_expr()?); if *self.peek() == TokenKind::Comma { self.advance(); } } self.expect(&TokenKind::RBracket)?; Ok(Expr::ArrayLiteral(elements, span)) } TokenKind::LParen => { self.advance(); let expr = self.parse_expr()?; self.expect(&TokenKind::RParen)?; Ok(expr) } _ => Err(Diagnostic::error( ErrorCode::E0201, format!("expected expression, found '{}'", self.peek()), self.current_span(), )), } } } pub fn parse(source: &str) -> (Option, Vec) { let (tokens, lex_diags) = crate::lexer::lex(source); if lex_diags.iter().any(Diagnostic::is_error) { return (None, lex_diags); } let (program, mut parse_diags) = Parser::new(tokens).parse(); let mut all_diags = lex_diags; all_diags.append(&mut parse_diags); (program, all_diags) }