1
0
Fork 0
mirror of https://github.com/imjasonh/nescript synced 2026-07-08 08:55:38 +00:00
nescript/src/parser/mod.rs
Claude 6430c3a935
Sprite resolution, asset wiring, shift-assign, unreachable state warning
Sprite/asset pipeline:
- Linker::link_with_assets() places sprite CHR data in ROM at correct tile
- assets::resolve_sprites() walks Program for inline sprite bytes
- CodeGen::with_sprites() maps sprite names to tile indices
- gen_draw() uses correct tile index from sprite declarations
- main.rs wires the full resolution pipeline

Shift-assign operators (<<= and >>=):
- AssignOp::ShiftLeftAssign and ShiftRightAssign variants
- Parser handles in both statement and array index contexts
- Codegen emits ASL A / LSR A
- IR lowering maps to ShiftLeft/ShiftRight ops

Unreachable state warning (W0104):
- BFS from start state finds reachable states via transitions
- States not reached produce W0104 warning

Error polish helpers:
- suggest_var_name() for "did you mean" suggestions
- emit_undefined_var() for E0502 with typo hints
- Used by analyzer for better diagnostics

242 tests pass, clippy clean.

https://claude.ai/code/session_01W6eQFStA66EuMKHUFo2rx3
2026-04-12 10:01:44 +00:00

1358 lines
46 KiB
Rust

pub mod ast;
#[cfg(test)]
mod tests;
use crate::errors::{Diagnostic, ErrorCode};
use crate::lexer::{Span, Token, TokenKind};
use ast::*;
pub struct Parser {
tokens: Vec<Token>,
pos: usize,
diagnostics: Vec<Diagnostic>,
}
impl Parser {
pub fn new(tokens: Vec<Token>) -> Self {
Self {
tokens,
pos: 0,
diagnostics: Vec::new(),
}
}
pub fn parse(mut self) -> (Option<Program>, Vec<Diagnostic>) {
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<Span, Diagnostic> {
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<Program, Diagnostic> {
let mut game = None;
let mut globals = Vec::new();
let mut constants = 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::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 => {
self.advance();
let (name, _) = self.expect_ident()?;
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,
functions,
states,
sprites,
palettes,
backgrounds,
banks,
start_state,
span,
})
}
// ── Game declaration ──
fn parse_game_decl(&mut self) -> Result<GameDecl, Diagnostic> {
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<VarDecl, Diagnostic> {
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<ConstDecl, Diagnostic> {
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<FunDecl, Diagnostic> {
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<StateDecl, Diagnostic> {
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 on_scanline = 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, _) = 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()?);
}
_ => {
return Err(Diagnostic::error(
ErrorCode::E0201,
format!("unknown event handler 'on {event}'"),
self.current_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<BankDecl, Diagnostic> {
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<Block, Diagnostic> {
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<SpriteDecl, Diagnostic> {
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<PaletteDecl, Diagnostic> {
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<BackgroundDecl, Diagnostic> {
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<AssetSource, Diagnostic> {
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<Block, Diagnostic> {
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<Statement, Diagnostic> {
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::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::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<Statement, Diagnostic> {
let start = self.current_span();
self.expect(&TokenKind::KwIf)?;
let condition = self.parse_expr()?;
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();
let cond = self.parse_expr()?;
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<Statement, Diagnostic> {
let start = self.current_span();
self.expect(&TokenKind::KwWhile)?;
let condition = self.parse_expr()?;
let body = self.parse_block()?;
Ok(Statement::While(condition, body, start))
}
fn parse_loop(&mut self) -> Result<Statement, Diagnostic> {
let start = self.current_span();
self.expect(&TokenKind::KwLoop)?;
let body = self.parse_block()?;
Ok(Statement::Loop(body, start))
}
fn parse_draw(&mut self) -> Result<Statement, Diagnostic> {
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,
}))
}
fn parse_assign_or_call(&mut self) -> Result<Statement, Diagnostic> {
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::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<AssignOp, Diagnostic> {
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<NesType, Diagnostic> {
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
}
_ => {
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<Expr, Diagnostic> {
self.parse_or_expr()
}
fn parse_or_expr(&mut self) -> Result<Expr, Diagnostic> {
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<Expr, Diagnostic> {
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<Expr, Diagnostic> {
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<Expr, Diagnostic> {
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<Expr, Diagnostic> {
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<Expr, Diagnostic> {
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<Expr, Diagnostic> {
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<Expr, Diagnostic> {
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<Expr, Diagnostic> {
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<Expr, Diagnostic> {
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<Expr, Diagnostic> {
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<Expr, Diagnostic> {
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
if name == "button" && *self.peek() == TokenKind::Dot {
self.advance();
let (button, _) = self.expect_name()?;
return Ok(Expr::ButtonRead(None, button, 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));
}
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<Program>, Vec<Diagnostic>) {
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)
}