1
0
Fork 0
mirror of https://github.com/imjasonh/nescript synced 2026-07-08 08:55:38 +00:00

Implement NEScript compiler Milestone 1 ("Hello Sprite")

Complete implementation of the NEScript compiler pipeline for M1:
- Lexer: full tokenization with hex/binary/decimal literals, all keywords, operators
- Parser: recursive descent with Pratt expression parsing (M1 subset)
- Analyzer: symbol resolution, type checking, memory allocation
- 6502 Assembler: full opcode encoding table (~150 valid combinations)
- Code Generator: AST → 6502 instructions (direct, no IR for M1)
- Runtime: NES hardware init, NMI handler, controller read, OAM DMA
- Linker: NROM layout, vector table, palette loading, CHR data
- ROM Builder: iNES header generation, PRG/CHR padding
- CLI: `build` and `check` subcommands via clap

143 tests across all modules:
- 22 lexer tests (literals, keywords, operators, error recovery)
- 18 parser tests (expressions, statements, game structure, errors)
- 7 analyzer tests (symbol resolution, memory allocation, transitions)
- 30 assembler tests (every addressing mode, label resolution)
- 7 codegen tests (var init, arithmetic, buttons, draw, comparisons)
- 11 runtime tests (init sequence, NMI handler, controller read)
- 10 ROM builder tests (iNES format, mirroring, banking, validation)
- 5 linker tests (vector table, CHR data, palette loading)
- 7 integration tests (end-to-end compilation, error detection)

CI: GitHub Actions for check, fmt, clippy, test
Pre-commit: script for local fmt + clippy + test validation

https://claude.ai/code/session_01W6eQFStA66EuMKHUFo2rx3
This commit is contained in:
Claude 2026-04-11 22:07:56 +00:00
parent 1fca6864ac
commit 39ca246151
No known key found for this signature in database
32 changed files with 6306 additions and 0 deletions

933
src/parser/mod.rs Normal file
View file

@ -0,0 +1,933 @@
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 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 states = 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::KwVar => {
globals.push(self.parse_var_decl()?);
}
TokenKind::KwConst => {
constants.push(self.parse_const_decl()?);
}
TokenKind::KwState => {
states.push(self.parse_state_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: Vec::new(),
states,
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,
_ => {
return Err(Diagnostic::error(
ErrorCode::E0201,
format!("unknown mapper '{val}'"),
self.current_span(),
)
.with_help("supported mappers for M1: NROM"));
}
};
}
"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();
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: Placement::Auto,
span: Span::new(start.file_id, start.start, self.current_span().start),
})
}
// ── 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().start),
})
}
// ── 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::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().start),
})
}
// ── 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()
}
// ── 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().start),
})
}
// ── Statements ──
fn parse_statement(&mut self) -> Result<Statement, Diagnostic> {
match self.peek().clone() {
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::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
while let TokenKind::Ident(_) = self.peek() {
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::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 {
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)
}
_ => 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> {
match self.peek().clone() {
TokenKind::KwU8 => {
self.advance();
Ok(NesType::U8)
}
TokenKind::KwI8 => {
self.advance();
Ok(NesType::I8)
}
TokenKind::KwU16 => {
self.advance();
Ok(NesType::U16)
}
TokenKind::KwBool => {
self.advance();
Ok(NesType::Bool)
}
_ => Err(Diagnostic::error(
ErrorCode::E0201,
format!("expected type, found '{}'", self.peek()),
self.current_span(),
)),
}
}
// ── 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> {
match self.peek().clone() {
TokenKind::Minus => {
let span = self.current_span();
self.advance();
let expr = self.parse_unary()?;
Ok(Expr::UnaryOp(UnaryOp::Negate, Box::new(expr), span))
}
TokenKind::KwNot => {
let span = self.current_span();
self.advance();
let expr = self.parse_unary()?;
Ok(Expr::UnaryOp(UnaryOp::Not, Box::new(expr), span))
}
TokenKind::Tilde => {
let span = self.current_span();
self.advance();
let expr = self.parse_unary()?;
Ok(Expr::UnaryOp(UnaryOp::BitNot, Box::new(expr), span))
}
_ => self.parse_primary(),
}
}
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 {
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::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)
}