diff --git a/crates/js/src/ast.rs b/crates/js/src/ast.rs new file mode 100644 index 0000000..5d420cf --- /dev/null +++ b/crates/js/src/ast.rs @@ -0,0 +1,504 @@ +//! Abstract Syntax Tree node types for ECMAScript 2024. + +use crate::lexer::Span; + +/// A complete JavaScript program. +#[derive(Debug, Clone, PartialEq)] +pub struct Program { + pub body: Vec, + pub source_type: SourceType, + pub span: Span, +} + +/// Whether the program is a script or module. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum SourceType { + Script, + Module, +} + +// ── Statements ────────────────────────────────────────────── + +/// A statement or declaration. +#[derive(Debug, Clone, PartialEq)] +pub struct Stmt { + pub kind: StmtKind, + pub span: Span, +} + +#[derive(Debug, Clone, PartialEq)] +pub enum StmtKind { + /// Expression followed by semicolon. + Expr(Expr), + /// `{ ... }` + Block(Vec), + /// `var`/`let`/`const` declaration. + VarDecl { + kind: VarKind, + declarators: Vec, + }, + /// `function name(...) { ... }` + FunctionDecl(FunctionDef), + /// `class Name { ... }` + ClassDecl(ClassDef), + /// `if (test) consequent else alternate` + If { + test: Expr, + consequent: Box, + alternate: Option>, + }, + /// `for (init; test; update) body` + For { + init: Option, + test: Option, + update: Option, + body: Box, + }, + /// `for (left in right) body` + ForIn { + left: ForInOfLeft, + right: Expr, + body: Box, + }, + /// `for (left of right) body` + ForOf { + left: ForInOfLeft, + right: Expr, + body: Box, + is_await: bool, + }, + /// `while (test) body` + While { test: Expr, body: Box }, + /// `do body while (test)` + DoWhile { body: Box, test: Expr }, + /// `switch (discriminant) { cases }` + Switch { + discriminant: Expr, + cases: Vec, + }, + /// `try { block } catch (param) { handler } finally { finalizer }` + Try { + block: Vec, + handler: Option, + finalizer: Option>, + }, + /// `return expr;` + Return(Option), + /// `throw expr;` + Throw(Expr), + /// `break label;` + Break(Option), + /// `continue label;` + Continue(Option), + /// `label: stmt` + Labeled { label: String, body: Box }, + /// `with (object) body` + With { object: Expr, body: Box }, + /// `debugger;` + Debugger, + /// `;` + Empty, + /// `import` declaration. + Import { + specifiers: Vec, + source: String, + }, + /// `export` declaration. + Export(ExportDecl), +} + +// ── Variable declarations ─────────────────────────────────── + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum VarKind { + Var, + Let, + Const, +} + +#[derive(Debug, Clone, PartialEq)] +pub struct VarDeclarator { + pub pattern: Pattern, + pub init: Option, + pub span: Span, +} + +// ── For-statement helpers ─────────────────────────────────── + +#[derive(Debug, Clone, PartialEq)] +pub enum ForInit { + VarDecl { + kind: VarKind, + declarators: Vec, + }, + Expr(Expr), +} + +#[derive(Debug, Clone, PartialEq)] +pub enum ForInOfLeft { + VarDecl { kind: VarKind, pattern: Pattern }, + Pattern(Pattern), +} + +// ── Switch / Try helpers ──────────────────────────────────── + +#[derive(Debug, Clone, PartialEq)] +pub struct SwitchCase { + /// `None` for `default:`. + pub test: Option, + pub consequent: Vec, + pub span: Span, +} + +#[derive(Debug, Clone, PartialEq)] +pub struct CatchClause { + pub param: Option, + pub body: Vec, + pub span: Span, +} + +// ── Import / Export ───────────────────────────────────────── + +#[derive(Debug, Clone, PartialEq)] +pub enum ImportSpecifier { + /// `import defaultExport from "mod"` + Default(String), + /// `import { foo } from "mod"` or `import { foo as bar } from "mod"` + Named { imported: String, local: String }, + /// `import * as ns from "mod"` + Namespace(String), +} + +#[derive(Debug, Clone, PartialEq)] +pub enum ExportDecl { + /// `export default expr` + Default(Expr), + /// `export function/class/var ...` + Declaration(Box), + /// `export { a, b as c }` + Named { + specifiers: Vec, + source: Option, + }, + /// `export * from "mod"` + AllFrom(String), +} + +#[derive(Debug, Clone, PartialEq)] +pub struct ExportSpecifier { + pub local: String, + pub exported: String, +} + +// ── Expressions ───────────────────────────────────────────── + +#[derive(Debug, Clone, PartialEq)] +pub struct Expr { + pub kind: ExprKind, + pub span: Span, +} + +#[derive(Debug, Clone, PartialEq)] +pub enum ExprKind { + /// Numeric literal. + Number(f64), + /// String literal. + String(String), + /// `true` / `false`. + Bool(bool), + /// `null`. + Null, + /// An identifier reference. + Identifier(String), + /// `this`. + This, + /// `[a, b, c]` + Array(Vec>), + /// `{ key: value, ... }` + Object(Vec), + /// `function name(...) { ... }` expression. + Function(FunctionDef), + /// `(...) => body` + Arrow { + params: Vec, + body: ArrowBody, + is_async: bool, + }, + /// `class Name { ... }` expression. + Class(ClassDef), + /// Prefix unary (`!x`, `-x`, `~x`, `typeof x`, `void x`, `delete x`). + Unary { op: UnaryOp, argument: Box }, + /// `++x`, `--x`, `x++`, `x--`. + Update { + op: UpdateOp, + argument: Box, + prefix: bool, + }, + /// `a + b`, `a * b`, etc. + Binary { + op: BinaryOp, + left: Box, + right: Box, + }, + /// `a && b`, `a || b`, `a ?? b`. + Logical { + op: LogicalOp, + left: Box, + right: Box, + }, + /// `a = b`, `a += b`, etc. + Assignment { + op: AssignOp, + left: Box, + right: Box, + }, + /// `test ? consequent : alternate` + Conditional { + test: Box, + consequent: Box, + alternate: Box, + }, + /// `callee(arguments)` + Call { + callee: Box, + arguments: Vec, + }, + /// `new callee(arguments)` + New { + callee: Box, + arguments: Vec, + }, + /// `object.property` or `object[property]` + Member { + object: Box, + property: Box, + computed: bool, + }, + /// `obj?.prop`, `obj?.[expr]`, `fn?.(args)` + OptionalChain { base: Box }, + /// Template literal: `` `hello ${name}` `` + TemplateLiteral { + quasis: Vec, + expressions: Vec, + }, + /// Tagged template: `` tag`string` `` + TaggedTemplate { tag: Box, quasi: Box }, + /// `a, b, c` + Sequence(Vec), + /// `...expr` + Spread(Box), + /// `yield expr` / `yield* expr` + Yield { + argument: Option>, + delegate: bool, + }, + /// `await expr` + Await(Box), + /// `/pattern/flags` + RegExp { pattern: String, flags: String }, +} + +// ── Array element ─────────────────────────────────────────── + +#[derive(Debug, Clone, PartialEq)] +pub enum ArrayElement { + Expr(Expr), + Spread(Expr), +} + +// ── Object literal ────────────────────────────────────────── + +#[derive(Debug, Clone, PartialEq)] +pub struct Property { + pub key: PropertyKey, + pub value: Option, + pub kind: PropertyKind, + pub computed: bool, + pub shorthand: bool, + pub method: bool, + pub span: Span, +} + +#[derive(Debug, Clone, PartialEq)] +pub enum PropertyKey { + Identifier(String), + String(String), + Number(f64), + Computed(Expr), +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum PropertyKind { + Init, + Get, + Set, +} + +// ── Arrow function body ───────────────────────────────────── + +#[derive(Debug, Clone, PartialEq)] +pub enum ArrowBody { + Expr(Box), + Block(Vec), +} + +// ── Function definition ───────────────────────────────────── + +#[derive(Debug, Clone, PartialEq)] +pub struct FunctionDef { + pub id: Option, + pub params: Vec, + pub body: Vec, + pub is_async: bool, + pub is_generator: bool, +} + +// ── Class definition ──────────────────────────────────────── + +#[derive(Debug, Clone, PartialEq)] +pub struct ClassDef { + pub id: Option, + pub super_class: Option>, + pub body: Vec, +} + +#[derive(Debug, Clone, PartialEq)] +pub struct ClassMember { + pub kind: ClassMemberKind, + pub span: Span, +} + +#[derive(Debug, Clone, PartialEq)] +pub enum ClassMemberKind { + Method { + key: PropertyKey, + value: FunctionDef, + kind: MethodKind, + is_static: bool, + computed: bool, + }, + Property { + key: PropertyKey, + value: Option, + is_static: bool, + computed: bool, + }, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum MethodKind { + Method, + Get, + Set, + Constructor, +} + +// ── Patterns (destructuring) ──────────────────────────────── + +#[derive(Debug, Clone, PartialEq)] +pub struct Pattern { + pub kind: PatternKind, + pub span: Span, +} + +#[derive(Debug, Clone, PartialEq)] +pub enum PatternKind { + /// Simple binding: `x` + Identifier(String), + /// Array destructuring: `[a, b, ...rest]` + Array { + elements: Vec>, + rest: Option>, + }, + /// Object destructuring: `{ a, b: c, ...rest }` + Object { + properties: Vec, + rest: Option>, + }, + /// Default value: `x = defaultValue` + Assign { + left: Box, + right: Box, + }, +} + +#[derive(Debug, Clone, PartialEq)] +pub struct ObjectPatternProp { + pub key: PropertyKey, + pub value: Pattern, + pub computed: bool, + pub shorthand: bool, + pub span: Span, +} + +// ── Operators ─────────────────────────────────────────────── + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum UnaryOp { + Minus, + Plus, + Not, + BitwiseNot, + Typeof, + Void, + Delete, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum UpdateOp { + Increment, + Decrement, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum BinaryOp { + Add, + Sub, + Mul, + Div, + Rem, + Exp, + Eq, + Ne, + StrictEq, + StrictNe, + Lt, + Le, + Gt, + Ge, + Shl, + Shr, + Ushr, + BitAnd, + BitOr, + BitXor, + In, + Instanceof, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum LogicalOp { + And, + Or, + Nullish, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum AssignOp { + Assign, + AddAssign, + SubAssign, + MulAssign, + DivAssign, + RemAssign, + ExpAssign, + ShlAssign, + ShrAssign, + UshrAssign, + BitAndAssign, + BitOrAssign, + BitXorAssign, + AndAssign, + OrAssign, + NullishAssign, +} diff --git a/crates/js/src/lib.rs b/crates/js/src/lib.rs index b4b1e13..3aae7bd 100644 --- a/crates/js/src/lib.rs +++ b/crates/js/src/lib.rs @@ -1,6 +1,8 @@ //! JavaScript engine — lexer, parser, bytecode, register VM, GC, JIT (AArch64). +pub mod ast; pub mod lexer; +pub mod parser; use std::fmt; diff --git a/crates/js/src/parser.rs b/crates/js/src/parser.rs new file mode 100644 index 0000000..7d950fe --- /dev/null +++ b/crates/js/src/parser.rs @@ -0,0 +1,3564 @@ +//! Recursive-descent JavaScript parser with Pratt expression parsing. +//! +//! Transforms a token stream from the [`crate::lexer::Lexer`] into an +//! [`crate::ast::Program`] AST. + +use crate::ast::*; +use crate::lexer::{LexError, Lexer, SourcePos, Span, Token, TokenKind}; +use std::fmt; + +// ── Error ─────────────────────────────────────────────────── + +/// An error produced during parsing. +#[derive(Debug, Clone, PartialEq)] +pub struct ParseError { + pub message: String, + pub pos: SourcePos, +} + +impl fmt::Display for ParseError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!( + f, + "ParseError at {}:{}: {}", + self.pos.line, self.pos.col, self.message + ) + } +} + +impl From for ParseError { + fn from(e: LexError) -> Self { + ParseError { + message: e.message, + pos: e.pos, + } + } +} + +// ── Parser ────────────────────────────────────────────────── + +pub struct Parser { + tokens: Vec, + pos: usize, + /// When false, `in` is not treated as a binary operator (for-init disambiguation). + allow_in: bool, +} + +impl Parser { + /// Parse source as a script. + pub fn parse(source: &str) -> Result { + let tokens = Lexer::tokenize(source)?; + let mut parser = Parser { + tokens, + pos: 0, + allow_in: true, + }; + parser.parse_program(SourceType::Script) + } + + /// Parse source as a module. + pub fn parse_module(source: &str) -> Result { + let tokens = Lexer::tokenize(source)?; + let mut parser = Parser { + tokens, + pos: 0, + allow_in: true, + }; + parser.parse_program(SourceType::Module) + } + + // ── Token navigation ──────────────────────────────────── + + fn peek(&self) -> &Token { + &self.tokens[self.pos.min(self.tokens.len() - 1)] + } + + fn peek_kind(&self) -> &TokenKind { + &self.peek().kind + } + + fn peek_ahead(&self, offset: usize) -> &TokenKind { + let idx = (self.pos + offset).min(self.tokens.len() - 1); + &self.tokens[idx].kind + } + + fn at(&self, kind: &TokenKind) -> bool { + std::mem::discriminant(self.peek_kind()) == std::mem::discriminant(kind) + } + + fn at_eof(&self) -> bool { + matches!(self.peek_kind(), TokenKind::Eof) + } + + fn advance(&mut self) -> Token { + let tok = self.tokens[self.pos.min(self.tokens.len() - 1)].clone(); + if self.pos < self.tokens.len() - 1 { + self.pos += 1; + } + tok + } + + fn eat(&mut self, kind: &TokenKind) -> bool { + if self.at(kind) { + self.advance(); + true + } else { + false + } + } + + fn expect(&mut self, kind: &TokenKind) -> Result { + if self.at(kind) { + Ok(self.advance()) + } else { + Err(self.error(format!("expected {:?}, found {:?}", kind, self.peek_kind()))) + } + } + + fn expect_semicolon(&mut self) -> Result<(), ParseError> { + if self.eat(&TokenKind::Semicolon) { + return Ok(()); + } + // ASI: insert semicolon before `}`, at EOF, or after newline + if matches!(self.peek_kind(), TokenKind::RBrace | TokenKind::Eof) + || self.peek().preceded_by_newline + { + return Ok(()); + } + Err(self.error(format!("expected `;`, found {:?}", self.peek_kind()))) + } + + fn expect_identifier(&mut self) -> Result { + match self.peek_kind().clone() { + TokenKind::Identifier(name) => { + self.advance(); + Ok(name) + } + _ => Err(self.error(format!("expected identifier, found {:?}", self.peek_kind()))), + } + } + + fn start_span(&self) -> SourcePos { + self.peek().span.start + } + + fn end_span(&self) -> SourcePos { + if self.pos > 0 { + self.tokens[self.pos - 1].span.end + } else { + self.peek().span.start + } + } + + fn span_from(&self, start: SourcePos) -> Span { + Span { + start, + end: self.end_span(), + } + } + + fn error(&self, message: String) -> ParseError { + ParseError { + message, + pos: self.peek().span.start, + } + } + + // ── Program ───────────────────────────────────────────── + + fn parse_program(&mut self, source_type: SourceType) -> Result { + let start = self.start_span(); + let mut body = Vec::new(); + while !self.at_eof() { + body.push(self.parse_statement_list_item()?); + } + Ok(Program { + body, + source_type, + span: self.span_from(start), + }) + } + + fn parse_statement_list_item(&mut self) -> Result { + match self.peek_kind() { + TokenKind::Function => self.parse_function_declaration(), + TokenKind::Class => self.parse_class_declaration(), + TokenKind::Var | TokenKind::Let | TokenKind::Const => { + self.parse_variable_declaration_stmt() + } + TokenKind::Async + if matches!(self.peek_ahead(1), TokenKind::Function) + && !self.tokens[self.pos + 1].preceded_by_newline => + { + self.parse_async_function_declaration() + } + TokenKind::Import + if !matches!(self.peek_ahead(1), TokenKind::LParen | TokenKind::Dot) => + { + self.parse_import_declaration() + } + TokenKind::Export => self.parse_export_declaration(), + _ => self.parse_statement(), + } + } + + // ── Statements ────────────────────────────────────────── + + fn parse_statement(&mut self) -> Result { + match self.peek_kind() { + TokenKind::LBrace => self.parse_block_stmt(), + TokenKind::Semicolon => { + let start = self.start_span(); + self.advance(); + Ok(Stmt { + kind: StmtKind::Empty, + span: self.span_from(start), + }) + } + TokenKind::If => self.parse_if(), + TokenKind::For => self.parse_for(), + TokenKind::While => self.parse_while(), + TokenKind::Do => self.parse_do_while(), + TokenKind::Switch => self.parse_switch(), + TokenKind::Try => self.parse_try(), + TokenKind::Return => self.parse_return(), + TokenKind::Throw => self.parse_throw(), + TokenKind::Break => self.parse_break(), + TokenKind::Continue => self.parse_continue(), + TokenKind::With => self.parse_with(), + TokenKind::Debugger => { + let start = self.start_span(); + self.advance(); + self.expect_semicolon()?; + Ok(Stmt { + kind: StmtKind::Debugger, + span: self.span_from(start), + }) + } + // Labeled statement: `ident: stmt` + TokenKind::Identifier(_) if matches!(self.peek_ahead(1), TokenKind::Colon) => { + self.parse_labeled() + } + // Variable declarations can also appear as statements + TokenKind::Var | TokenKind::Let | TokenKind::Const => { + self.parse_variable_declaration_stmt() + } + // Function/class declarations in statement position + TokenKind::Function => self.parse_function_declaration(), + TokenKind::Class => self.parse_class_declaration(), + TokenKind::Async + if matches!(self.peek_ahead(1), TokenKind::Function) + && !self.tokens[self.pos + 1].preceded_by_newline => + { + self.parse_async_function_declaration() + } + _ => self.parse_expression_statement(), + } + } + + fn parse_block_stmt(&mut self) -> Result { + let start = self.start_span(); + let body = self.parse_block_body()?; + Ok(Stmt { + kind: StmtKind::Block(body), + span: self.span_from(start), + }) + } + + fn parse_block_body(&mut self) -> Result, ParseError> { + self.expect(&TokenKind::LBrace)?; + let mut body = Vec::new(); + while !self.at(&TokenKind::RBrace) && !self.at_eof() { + body.push(self.parse_statement_list_item()?); + } + self.expect(&TokenKind::RBrace)?; + Ok(body) + } + + fn parse_expression_statement(&mut self) -> Result { + let start = self.start_span(); + let expr = self.parse_expression()?; + self.expect_semicolon()?; + Ok(Stmt { + kind: StmtKind::Expr(expr), + span: self.span_from(start), + }) + } + + fn parse_if(&mut self) -> Result { + let start = self.start_span(); + self.expect(&TokenKind::If)?; + self.expect(&TokenKind::LParen)?; + let test = self.parse_expression()?; + self.expect(&TokenKind::RParen)?; + let consequent = Box::new(self.parse_statement()?); + let alternate = if self.eat(&TokenKind::Else) { + Some(Box::new(self.parse_statement()?)) + } else { + None + }; + Ok(Stmt { + kind: StmtKind::If { + test, + consequent, + alternate, + }, + span: self.span_from(start), + }) + } + + fn parse_while(&mut self) -> Result { + let start = self.start_span(); + self.expect(&TokenKind::While)?; + self.expect(&TokenKind::LParen)?; + let test = self.parse_expression()?; + self.expect(&TokenKind::RParen)?; + let body = Box::new(self.parse_statement()?); + Ok(Stmt { + kind: StmtKind::While { test, body }, + span: self.span_from(start), + }) + } + + fn parse_do_while(&mut self) -> Result { + let start = self.start_span(); + self.expect(&TokenKind::Do)?; + let body = Box::new(self.parse_statement()?); + self.expect(&TokenKind::While)?; + self.expect(&TokenKind::LParen)?; + let test = self.parse_expression()?; + self.expect(&TokenKind::RParen)?; + self.expect_semicolon()?; + Ok(Stmt { + kind: StmtKind::DoWhile { body, test }, + span: self.span_from(start), + }) + } + + fn parse_for(&mut self) -> Result { + let start = self.start_span(); + self.expect(&TokenKind::For)?; + self.expect(&TokenKind::LParen)?; + + // Check for for-in/for-of with var/let/const + if matches!( + self.peek_kind(), + TokenKind::Var | TokenKind::Let | TokenKind::Const + ) { + let var_kind = match self.peek_kind() { + TokenKind::Var => VarKind::Var, + TokenKind::Let => VarKind::Let, + TokenKind::Const => VarKind::Const, + _ => unreachable!(), + }; + self.advance(); + let pattern = self.parse_binding_pattern()?; + + if self.eat(&TokenKind::In) { + let right = self.parse_expression()?; + self.expect(&TokenKind::RParen)?; + let body = Box::new(self.parse_statement()?); + return Ok(Stmt { + kind: StmtKind::ForIn { + left: ForInOfLeft::VarDecl { + kind: var_kind, + pattern, + }, + right, + body, + }, + span: self.span_from(start), + }); + } + if self.eat(&TokenKind::Of) { + let right = self.parse_assignment_expression()?; + self.expect(&TokenKind::RParen)?; + let body = Box::new(self.parse_statement()?); + return Ok(Stmt { + kind: StmtKind::ForOf { + left: ForInOfLeft::VarDecl { + kind: var_kind, + pattern, + }, + right, + body, + is_await: false, + }, + span: self.span_from(start), + }); + } + + // Regular for: parse remaining declarators + let mut declarators = vec![VarDeclarator { + span: pattern.span, + init: if self.eat(&TokenKind::Assign) { + Some(self.parse_assignment_expression_no_in()?) + } else { + None + }, + pattern, + }]; + while self.eat(&TokenKind::Comma) { + declarators.push(self.parse_var_declarator_no_in()?); + } + self.expect(&TokenKind::Semicolon)?; + let test = if self.at(&TokenKind::Semicolon) { + None + } else { + Some(self.parse_expression()?) + }; + self.expect(&TokenKind::Semicolon)?; + let update = if self.at(&TokenKind::RParen) { + None + } else { + Some(self.parse_expression()?) + }; + self.expect(&TokenKind::RParen)?; + let body = Box::new(self.parse_statement()?); + return Ok(Stmt { + kind: StmtKind::For { + init: Some(ForInit::VarDecl { + kind: var_kind, + declarators, + }), + test, + update, + body, + }, + span: self.span_from(start), + }); + } + + // No var/let/const + if self.eat(&TokenKind::Semicolon) { + // for(; ...) + let test = if self.at(&TokenKind::Semicolon) { + None + } else { + Some(self.parse_expression()?) + }; + self.expect(&TokenKind::Semicolon)?; + let update = if self.at(&TokenKind::RParen) { + None + } else { + Some(self.parse_expression()?) + }; + self.expect(&TokenKind::RParen)?; + let body = Box::new(self.parse_statement()?); + return Ok(Stmt { + kind: StmtKind::For { + init: None, + test, + update, + body, + }, + span: self.span_from(start), + }); + } + + // Expression init — might be for-in/for-of + let old_allow_in = self.allow_in; + self.allow_in = false; + let init_expr = self.parse_expression()?; + self.allow_in = old_allow_in; + + if self.eat(&TokenKind::In) { + let pattern = self.expr_to_pattern(&init_expr)?; + let right = self.parse_expression()?; + self.expect(&TokenKind::RParen)?; + let body = Box::new(self.parse_statement()?); + return Ok(Stmt { + kind: StmtKind::ForIn { + left: ForInOfLeft::Pattern(pattern), + right, + body, + }, + span: self.span_from(start), + }); + } + if self.eat(&TokenKind::Of) { + let pattern = self.expr_to_pattern(&init_expr)?; + let right = self.parse_assignment_expression()?; + self.expect(&TokenKind::RParen)?; + let body = Box::new(self.parse_statement()?); + return Ok(Stmt { + kind: StmtKind::ForOf { + left: ForInOfLeft::Pattern(pattern), + right, + body, + is_await: false, + }, + span: self.span_from(start), + }); + } + + self.expect(&TokenKind::Semicolon)?; + let test = if self.at(&TokenKind::Semicolon) { + None + } else { + Some(self.parse_expression()?) + }; + self.expect(&TokenKind::Semicolon)?; + let update = if self.at(&TokenKind::RParen) { + None + } else { + Some(self.parse_expression()?) + }; + self.expect(&TokenKind::RParen)?; + let body = Box::new(self.parse_statement()?); + Ok(Stmt { + kind: StmtKind::For { + init: Some(ForInit::Expr(init_expr)), + test, + update, + body, + }, + span: self.span_from(start), + }) + } + + fn parse_switch(&mut self) -> Result { + let start = self.start_span(); + self.expect(&TokenKind::Switch)?; + self.expect(&TokenKind::LParen)?; + let discriminant = self.parse_expression()?; + self.expect(&TokenKind::RParen)?; + self.expect(&TokenKind::LBrace)?; + let mut cases = Vec::new(); + while !self.at(&TokenKind::RBrace) && !self.at_eof() { + let case_start = self.start_span(); + let test = if self.eat(&TokenKind::Case) { + Some(self.parse_expression()?) + } else { + self.expect(&TokenKind::Default)?; + None + }; + self.expect(&TokenKind::Colon)?; + let mut consequent = Vec::new(); + while !matches!( + self.peek_kind(), + TokenKind::Case | TokenKind::Default | TokenKind::RBrace + ) && !self.at_eof() + { + consequent.push(self.parse_statement_list_item()?); + } + cases.push(SwitchCase { + test, + consequent, + span: self.span_from(case_start), + }); + } + self.expect(&TokenKind::RBrace)?; + Ok(Stmt { + kind: StmtKind::Switch { + discriminant, + cases, + }, + span: self.span_from(start), + }) + } + + fn parse_try(&mut self) -> Result { + let start = self.start_span(); + self.expect(&TokenKind::Try)?; + let block = self.parse_block_body()?; + + let handler = if self.eat(&TokenKind::Catch) { + let catch_start = self.start_span(); + let param = if self.eat(&TokenKind::LParen) { + let pat = self.parse_binding_pattern()?; + self.expect(&TokenKind::RParen)?; + Some(pat) + } else { + None + }; + let body = self.parse_block_body()?; + Some(CatchClause { + param, + body, + span: self.span_from(catch_start), + }) + } else { + None + }; + + let finalizer = if self.eat(&TokenKind::Finally) { + Some(self.parse_block_body()?) + } else { + None + }; + + if handler.is_none() && finalizer.is_none() { + return Err(self.error("try requires catch or finally".into())); + } + + Ok(Stmt { + kind: StmtKind::Try { + block, + handler, + finalizer, + }, + span: self.span_from(start), + }) + } + + fn parse_return(&mut self) -> Result { + let start = self.start_span(); + self.expect(&TokenKind::Return)?; + // Restricted production: newline after return inserts semicolon + let argument = if matches!( + self.peek_kind(), + TokenKind::Semicolon | TokenKind::RBrace | TokenKind::Eof + ) || self.peek().preceded_by_newline + { + None + } else { + Some(self.parse_expression()?) + }; + self.expect_semicolon()?; + Ok(Stmt { + kind: StmtKind::Return(argument), + span: self.span_from(start), + }) + } + + fn parse_throw(&mut self) -> Result { + let start = self.start_span(); + self.expect(&TokenKind::Throw)?; + if self.peek().preceded_by_newline { + return Err(ParseError { + message: "newline not allowed after throw".into(), + pos: self.peek().span.start, + }); + } + let argument = self.parse_expression()?; + self.expect_semicolon()?; + Ok(Stmt { + kind: StmtKind::Throw(argument), + span: self.span_from(start), + }) + } + + fn parse_break(&mut self) -> Result { + let start = self.start_span(); + self.expect(&TokenKind::Break)?; + let label = if !self.peek().preceded_by_newline { + if let TokenKind::Identifier(name) = self.peek_kind().clone() { + self.advance(); + Some(name) + } else { + None + } + } else { + None + }; + self.expect_semicolon()?; + Ok(Stmt { + kind: StmtKind::Break(label), + span: self.span_from(start), + }) + } + + fn parse_continue(&mut self) -> Result { + let start = self.start_span(); + self.expect(&TokenKind::Continue)?; + let label = if !self.peek().preceded_by_newline { + if let TokenKind::Identifier(name) = self.peek_kind().clone() { + self.advance(); + Some(name) + } else { + None + } + } else { + None + }; + self.expect_semicolon()?; + Ok(Stmt { + kind: StmtKind::Continue(label), + span: self.span_from(start), + }) + } + + fn parse_labeled(&mut self) -> Result { + let start = self.start_span(); + let label = self.expect_identifier()?; + self.expect(&TokenKind::Colon)?; + let body = Box::new(self.parse_statement()?); + Ok(Stmt { + kind: StmtKind::Labeled { label, body }, + span: self.span_from(start), + }) + } + + fn parse_with(&mut self) -> Result { + let start = self.start_span(); + self.expect(&TokenKind::With)?; + self.expect(&TokenKind::LParen)?; + let object = self.parse_expression()?; + self.expect(&TokenKind::RParen)?; + let body = Box::new(self.parse_statement()?); + Ok(Stmt { + kind: StmtKind::With { object, body }, + span: self.span_from(start), + }) + } + + // ── Variable declarations ─────────────────────────────── + + fn parse_variable_declaration_stmt(&mut self) -> Result { + let start = self.start_span(); + let kind = match self.peek_kind() { + TokenKind::Var => VarKind::Var, + TokenKind::Let => VarKind::Let, + TokenKind::Const => VarKind::Const, + _ => return Err(self.error("expected var, let, or const".into())), + }; + self.advance(); + let mut declarators = vec![self.parse_var_declarator()?]; + while self.eat(&TokenKind::Comma) { + declarators.push(self.parse_var_declarator()?); + } + self.expect_semicolon()?; + Ok(Stmt { + kind: StmtKind::VarDecl { kind, declarators }, + span: self.span_from(start), + }) + } + + fn parse_var_declarator(&mut self) -> Result { + let start = self.start_span(); + let pattern = self.parse_binding_pattern()?; + let init = if self.eat(&TokenKind::Assign) { + Some(self.parse_assignment_expression()?) + } else { + None + }; + Ok(VarDeclarator { + pattern, + init, + span: self.span_from(start), + }) + } + + fn parse_var_declarator_no_in(&mut self) -> Result { + let start = self.start_span(); + let pattern = self.parse_binding_pattern()?; + let init = if self.eat(&TokenKind::Assign) { + Some(self.parse_assignment_expression_no_in()?) + } else { + None + }; + Ok(VarDeclarator { + pattern, + init, + span: self.span_from(start), + }) + } + + // ── Binding patterns (destructuring) ──────────────────── + + fn parse_binding_pattern(&mut self) -> Result { + match self.peek_kind() { + TokenKind::LBracket => self.parse_array_pattern(), + TokenKind::LBrace => self.parse_object_pattern(), + _ => { + let start = self.start_span(); + let name = self.expect_identifier()?; + Ok(Pattern { + kind: PatternKind::Identifier(name), + span: self.span_from(start), + }) + } + } + } + + fn parse_array_pattern(&mut self) -> Result { + let start = self.start_span(); + self.expect(&TokenKind::LBracket)?; + let mut elements = Vec::new(); + let mut rest = None; + while !self.at(&TokenKind::RBracket) && !self.at_eof() { + if self.eat(&TokenKind::Comma) { + elements.push(None); + continue; + } + if self.at(&TokenKind::Ellipsis) { + self.advance(); + rest = Some(Box::new(self.parse_binding_pattern()?)); + self.eat(&TokenKind::Comma); // trailing comma after rest + break; + } + let mut pat = self.parse_binding_pattern()?; + if self.eat(&TokenKind::Assign) { + let right = self.parse_assignment_expression()?; + let span = Span { + start: pat.span.start, + end: right.span.end, + }; + pat = Pattern { + kind: PatternKind::Assign { + left: Box::new(pat), + right: Box::new(right), + }, + span, + }; + } + elements.push(Some(pat)); + if !self.eat(&TokenKind::Comma) { + break; + } + } + self.expect(&TokenKind::RBracket)?; + Ok(Pattern { + kind: PatternKind::Array { elements, rest }, + span: self.span_from(start), + }) + } + + fn parse_object_pattern(&mut self) -> Result { + let start = self.start_span(); + self.expect(&TokenKind::LBrace)?; + let mut properties = Vec::new(); + let mut rest = None; + while !self.at(&TokenKind::RBrace) && !self.at_eof() { + if self.at(&TokenKind::Ellipsis) { + self.advance(); + rest = Some(Box::new(self.parse_binding_pattern()?)); + self.eat(&TokenKind::Comma); + break; + } + let prop_start = self.start_span(); + let (key, computed) = self.parse_property_name()?; + if self.eat(&TokenKind::Colon) { + // { key: pattern } + let mut value = self.parse_binding_pattern()?; + if self.eat(&TokenKind::Assign) { + let right = self.parse_assignment_expression()?; + let span = Span { + start: value.span.start, + end: right.span.end, + }; + value = Pattern { + kind: PatternKind::Assign { + left: Box::new(value), + right: Box::new(right), + }, + span, + }; + } + properties.push(ObjectPatternProp { + key, + value, + computed, + shorthand: false, + span: self.span_from(prop_start), + }); + } else { + // Shorthand: { name } or { name = default } + let name = match &key { + PropertyKey::Identifier(n) => n.clone(), + _ => return Err(self.error("shorthand properties require identifier".into())), + }; + let ident_span = self.span_from(prop_start); + let mut value = Pattern { + kind: PatternKind::Identifier(name.clone()), + span: ident_span, + }; + if self.eat(&TokenKind::Assign) { + let right = self.parse_assignment_expression()?; + let span = Span { + start: value.span.start, + end: right.span.end, + }; + value = Pattern { + kind: PatternKind::Assign { + left: Box::new(value), + right: Box::new(right), + }, + span, + }; + } + properties.push(ObjectPatternProp { + key, + value, + computed, + shorthand: true, + span: self.span_from(prop_start), + }); + } + if !self.eat(&TokenKind::Comma) { + break; + } + } + self.expect(&TokenKind::RBrace)?; + Ok(Pattern { + kind: PatternKind::Object { properties, rest }, + span: self.span_from(start), + }) + } + + // ── Function declarations ─────────────────────────────── + + fn parse_function_declaration(&mut self) -> Result { + let start = self.start_span(); + self.expect(&TokenKind::Function)?; + let is_generator = self.eat(&TokenKind::Star); + let id = Some(self.expect_identifier()?); + let (params, body) = self.parse_function_params_and_body()?; + Ok(Stmt { + kind: StmtKind::FunctionDecl(FunctionDef { + id, + params, + body, + is_async: false, + is_generator, + }), + span: self.span_from(start), + }) + } + + fn parse_async_function_declaration(&mut self) -> Result { + let start = self.start_span(); + self.expect(&TokenKind::Async)?; + self.expect(&TokenKind::Function)?; + let is_generator = self.eat(&TokenKind::Star); + let id = Some(self.expect_identifier()?); + let (params, body) = self.parse_function_params_and_body()?; + Ok(Stmt { + kind: StmtKind::FunctionDecl(FunctionDef { + id, + params, + body, + is_async: true, + is_generator, + }), + span: self.span_from(start), + }) + } + + fn parse_function_params_and_body(&mut self) -> Result<(Vec, Vec), ParseError> { + self.expect(&TokenKind::LParen)?; + let params = self.parse_formal_parameters()?; + self.expect(&TokenKind::RParen)?; + let body = self.parse_block_body()?; + Ok((params, body)) + } + + fn parse_formal_parameters(&mut self) -> Result, ParseError> { + let mut params = Vec::new(); + while !self.at(&TokenKind::RParen) && !self.at_eof() { + if self.at(&TokenKind::Ellipsis) { + self.advance(); + params.push(self.parse_binding_pattern()?); + self.eat(&TokenKind::Comma); + break; + } + let mut pat = self.parse_binding_pattern()?; + if self.eat(&TokenKind::Assign) { + let right = self.parse_assignment_expression()?; + let span = Span { + start: pat.span.start, + end: right.span.end, + }; + pat = Pattern { + kind: PatternKind::Assign { + left: Box::new(pat), + right: Box::new(right), + }, + span, + }; + } + params.push(pat); + if !self.eat(&TokenKind::Comma) { + break; + } + } + Ok(params) + } + + // ── Class declarations ────────────────────────────────── + + fn parse_class_declaration(&mut self) -> Result { + let start = self.start_span(); + let class_def = self.parse_class(true)?; + Ok(Stmt { + kind: StmtKind::ClassDecl(class_def), + span: self.span_from(start), + }) + } + + fn parse_class(&mut self, require_name: bool) -> Result { + self.expect(&TokenKind::Class)?; + let id = if require_name { + Some(self.expect_identifier()?) + } else if let TokenKind::Identifier(_) = self.peek_kind() { + Some(self.expect_identifier()?) + } else { + None + }; + let super_class = if self.eat(&TokenKind::Extends) { + Some(Box::new(self.parse_left_hand_side_expression()?)) + } else { + None + }; + let body = self.parse_class_body()?; + Ok(ClassDef { + id, + super_class, + body, + }) + } + + fn parse_class_body(&mut self) -> Result, ParseError> { + self.expect(&TokenKind::LBrace)?; + let mut members = Vec::new(); + while !self.at(&TokenKind::RBrace) && !self.at_eof() { + if self.eat(&TokenKind::Semicolon) { + continue; + } + members.push(self.parse_class_member()?); + } + self.expect(&TokenKind::RBrace)?; + Ok(members) + } + + fn parse_class_member(&mut self) -> Result { + let start = self.start_span(); + let is_static = self.eat(&TokenKind::Static); + + // Check for getter/setter + if let TokenKind::Identifier(ref name) = self.peek_kind().clone() { + if (name == "get" || name == "set") + && !matches!( + self.peek_ahead(1), + TokenKind::LParen | TokenKind::Assign | TokenKind::Semicolon + ) + { + let kind = if name == "get" { + MethodKind::Get + } else { + MethodKind::Set + }; + self.advance(); + let (key, computed) = self.parse_property_name()?; + let (params, body) = self.parse_function_params_and_body()?; + return Ok(ClassMember { + kind: ClassMemberKind::Method { + key, + value: FunctionDef { + id: None, + params, + body, + is_async: false, + is_generator: false, + }, + kind, + is_static, + computed, + }, + span: self.span_from(start), + }); + } + } + + // Generator method + let is_generator = self.eat(&TokenKind::Star); + + // Async method + let is_async = if !is_generator + && matches!(self.peek_kind(), TokenKind::Async) + && !matches!( + self.peek_ahead(1), + TokenKind::LParen | TokenKind::Assign | TokenKind::Semicolon + ) { + self.advance(); + true + } else { + false + }; + + let is_generator = is_generator || (is_async && self.eat(&TokenKind::Star)); + + let (key, computed) = self.parse_property_name()?; + + // Check for constructor + let method_kind = + if !is_static && !computed && key == PropertyKey::Identifier("constructor".into()) { + MethodKind::Constructor + } else { + MethodKind::Method + }; + + if self.at(&TokenKind::LParen) { + let (params, body) = self.parse_function_params_and_body()?; + Ok(ClassMember { + kind: ClassMemberKind::Method { + key, + value: FunctionDef { + id: None, + params, + body, + is_async, + is_generator, + }, + kind: method_kind, + is_static, + computed, + }, + span: self.span_from(start), + }) + } else { + // Class field + let value = if self.eat(&TokenKind::Assign) { + Some(self.parse_assignment_expression()?) + } else { + None + }; + self.expect_semicolon()?; + Ok(ClassMember { + kind: ClassMemberKind::Property { + key, + value, + is_static, + computed, + }, + span: self.span_from(start), + }) + } + } + + // ── Import / Export ───────────────────────────────────── + + fn parse_import_declaration(&mut self) -> Result { + let start = self.start_span(); + self.expect(&TokenKind::Import)?; + + // `import "source"` — side-effect import + if let TokenKind::String(source) = self.peek_kind().clone() { + self.advance(); + self.expect_semicolon()?; + return Ok(Stmt { + kind: StmtKind::Import { + specifiers: vec![], + source, + }, + span: self.span_from(start), + }); + } + + let mut specifiers = Vec::new(); + + // `import * as ns from "source"` + if self.eat(&TokenKind::Star) { + self.expect_identifier_matching("as")?; + let local = self.expect_identifier()?; + specifiers.push(ImportSpecifier::Namespace(local)); + } + // `import { a, b as c } from "source"` + else if self.at(&TokenKind::LBrace) { + self.advance(); + while !self.at(&TokenKind::RBrace) && !self.at_eof() { + let imported = self.expect_identifier()?; + let local = if self.eat_identifier_matching("as") { + self.expect_identifier()? + } else { + imported.clone() + }; + specifiers.push(ImportSpecifier::Named { imported, local }); + if !self.eat(&TokenKind::Comma) { + break; + } + } + self.expect(&TokenKind::RBrace)?; + } + // `import defaultExport from "source"` or `import defaultExport, { ... } from "source"` + else { + let default_name = self.expect_identifier()?; + specifiers.push(ImportSpecifier::Default(default_name)); + + if self.eat(&TokenKind::Comma) { + if self.eat(&TokenKind::Star) { + self.expect_identifier_matching("as")?; + let local = self.expect_identifier()?; + specifiers.push(ImportSpecifier::Namespace(local)); + } else { + self.expect(&TokenKind::LBrace)?; + while !self.at(&TokenKind::RBrace) && !self.at_eof() { + let imported = self.expect_identifier()?; + let local = if self.eat_identifier_matching("as") { + self.expect_identifier()? + } else { + imported.clone() + }; + specifiers.push(ImportSpecifier::Named { imported, local }); + if !self.eat(&TokenKind::Comma) { + break; + } + } + self.expect(&TokenKind::RBrace)?; + } + } + } + + self.expect_identifier_matching("from")?; + let source = match self.peek_kind().clone() { + TokenKind::String(s) => { + self.advance(); + s + } + _ => return Err(self.error("expected module source string".into())), + }; + self.expect_semicolon()?; + Ok(Stmt { + kind: StmtKind::Import { specifiers, source }, + span: self.span_from(start), + }) + } + + fn parse_export_declaration(&mut self) -> Result { + let start = self.start_span(); + self.expect(&TokenKind::Export)?; + + // `export default ...` + if self.eat(&TokenKind::Default) { + if matches!(self.peek_kind(), TokenKind::Function) { + let decl = self.parse_function_declaration()?; + return Ok(Stmt { + kind: StmtKind::Export(ExportDecl::Declaration(Box::new(decl))), + span: self.span_from(start), + }); + } + if matches!(self.peek_kind(), TokenKind::Class) { + let decl = self.parse_class_declaration()?; + return Ok(Stmt { + kind: StmtKind::Export(ExportDecl::Declaration(Box::new(decl))), + span: self.span_from(start), + }); + } + let expr = self.parse_assignment_expression()?; + self.expect_semicolon()?; + return Ok(Stmt { + kind: StmtKind::Export(ExportDecl::Default(expr)), + span: self.span_from(start), + }); + } + + // `export * from "source"` + if self.eat(&TokenKind::Star) { + self.expect_identifier_matching("from")?; + let source = match self.peek_kind().clone() { + TokenKind::String(s) => { + self.advance(); + s + } + _ => return Err(self.error("expected module source string".into())), + }; + self.expect_semicolon()?; + return Ok(Stmt { + kind: StmtKind::Export(ExportDecl::AllFrom(source)), + span: self.span_from(start), + }); + } + + // `export { a, b as c } [from "source"]` + if self.at(&TokenKind::LBrace) { + self.advance(); + let mut specifiers = Vec::new(); + while !self.at(&TokenKind::RBrace) && !self.at_eof() { + let local = self.expect_identifier()?; + let exported = if self.eat_identifier_matching("as") { + self.expect_identifier()? + } else { + local.clone() + }; + specifiers.push(ExportSpecifier { local, exported }); + if !self.eat(&TokenKind::Comma) { + break; + } + } + self.expect(&TokenKind::RBrace)?; + let source = if self.eat_identifier_matching("from") { + match self.peek_kind().clone() { + TokenKind::String(s) => { + self.advance(); + Some(s) + } + _ => return Err(self.error("expected module source string".into())), + } + } else { + None + }; + self.expect_semicolon()?; + return Ok(Stmt { + kind: StmtKind::Export(ExportDecl::Named { specifiers, source }), + span: self.span_from(start), + }); + } + + // `export var/let/const/function/class ...` + let decl = self.parse_statement_list_item()?; + Ok(Stmt { + kind: StmtKind::Export(ExportDecl::Declaration(Box::new(decl))), + span: self.span_from(start), + }) + } + + // ── Helper for contextual keywords ────────────────────── + + fn expect_identifier_matching(&mut self, expected: &str) -> Result<(), ParseError> { + match self.peek_kind().clone() { + TokenKind::Identifier(ref name) if name == expected => { + self.advance(); + Ok(()) + } + _ => Err(self.error(format!("expected '{}'", expected))), + } + } + + fn eat_identifier_matching(&mut self, expected: &str) -> bool { + if let TokenKind::Identifier(ref name) = self.peek_kind() { + if name == expected { + self.advance(); + return true; + } + } + false + } + + // ── Expressions ───────────────────────────────────────── + + fn parse_expression(&mut self) -> Result { + let start = self.start_span(); + let expr = self.parse_assignment_expression()?; + if !self.at(&TokenKind::Comma) { + return Ok(expr); + } + let mut exprs = vec![expr]; + while self.eat(&TokenKind::Comma) { + exprs.push(self.parse_assignment_expression()?); + } + Ok(Expr { + kind: ExprKind::Sequence(exprs), + span: self.span_from(start), + }) + } + + fn parse_assignment_expression(&mut self) -> Result { + // Check for arrow function: `x => ...` or `async x => ...` + if let TokenKind::Identifier(ref name) = self.peek_kind().clone() { + if matches!(self.peek_ahead(1), TokenKind::Arrow) && !self.peek().preceded_by_newline { + let param_start = self.start_span(); + let name = name.clone(); + self.advance(); // identifier + self.advance(); // => + let params = vec![Pattern { + kind: PatternKind::Identifier(name), + span: self.span_from(param_start), + }]; + return self.parse_arrow_body(param_start, params, false); + } + } + + // Check for `async x => ...` or `async (...)` arrow + if matches!(self.peek_kind(), TokenKind::Async) && !self.peek().preceded_by_newline { + if let TokenKind::Identifier(_) = self.peek_ahead(1) { + if matches!(self.peek_ahead(2), TokenKind::Arrow) { + let start = self.start_span(); + self.advance(); // async + let param_start = self.start_span(); + let name = self.expect_identifier()?; + self.advance(); // => + let params = vec![Pattern { + kind: PatternKind::Identifier(name), + span: self.span_from(param_start), + }]; + return self.parse_arrow_body(start, params, true); + } + } + } + + let start = self.start_span(); + let left = self.parse_conditional_expression()?; + + // Check for arrow function after parenthesized expr: `(a, b) => ...` + if matches!(self.peek_kind(), TokenKind::Arrow) && !self.peek().preceded_by_newline { + self.advance(); // => + let params = self.expr_to_params(&left)?; + return self.parse_arrow_body(start, params, false); + } + + // Assignment + if let Some(op) = self.peek_assign_op() { + self.advance(); + let right = self.parse_assignment_expression()?; + return Ok(Expr { + span: self.span_from(start), + kind: ExprKind::Assignment { + op, + left: Box::new(left), + right: Box::new(right), + }, + }); + } + + Ok(left) + } + + fn parse_assignment_expression_no_in(&mut self) -> Result { + let old = self.allow_in; + self.allow_in = false; + let result = self.parse_assignment_expression(); + self.allow_in = old; + result + } + + fn parse_arrow_body( + &mut self, + start: SourcePos, + params: Vec, + is_async: bool, + ) -> Result { + let body = if self.at(&TokenKind::LBrace) { + ArrowBody::Block(self.parse_block_body()?) + } else { + ArrowBody::Expr(Box::new(self.parse_assignment_expression()?)) + }; + Ok(Expr { + kind: ExprKind::Arrow { + params, + body, + is_async, + }, + span: self.span_from(start), + }) + } + + fn parse_conditional_expression(&mut self) -> Result { + let start = self.start_span(); + let expr = self.parse_binary_expression(0)?; + if !self.eat(&TokenKind::Question) { + return Ok(expr); + } + let consequent = self.parse_assignment_expression()?; + self.expect(&TokenKind::Colon)?; + let alternate = self.parse_assignment_expression()?; + Ok(Expr { + kind: ExprKind::Conditional { + test: Box::new(expr), + consequent: Box::new(consequent), + alternate: Box::new(alternate), + }, + span: self.span_from(start), + }) + } + + // ── Pratt expression parsing ──────────────────────────── + + fn parse_binary_expression(&mut self, min_bp: u8) -> Result { + let start = self.start_span(); + let mut left = self.parse_unary_expression()?; + + loop { + let Some((op, left_bp, right_bp)) = self.peek_binary_op() else { + break; + }; + if left_bp < min_bp { + break; + } + self.advance(); + + let right = self.parse_binary_expression(right_bp)?; + let span = self.span_from(start); + + left = match op { + BinOrLogical::Binary(op) => Expr { + kind: ExprKind::Binary { + op, + left: Box::new(left), + right: Box::new(right), + }, + span, + }, + BinOrLogical::Logical(op) => Expr { + kind: ExprKind::Logical { + op, + left: Box::new(left), + right: Box::new(right), + }, + span, + }, + }; + } + + Ok(left) + } + + fn parse_unary_expression(&mut self) -> Result { + let start = self.start_span(); + match self.peek_kind() { + TokenKind::Minus => { + self.advance(); + let arg = self.parse_unary_expression()?; + Ok(Expr { + kind: ExprKind::Unary { + op: UnaryOp::Minus, + argument: Box::new(arg), + }, + span: self.span_from(start), + }) + } + TokenKind::Plus => { + self.advance(); + let arg = self.parse_unary_expression()?; + Ok(Expr { + kind: ExprKind::Unary { + op: UnaryOp::Plus, + argument: Box::new(arg), + }, + span: self.span_from(start), + }) + } + TokenKind::Not => { + self.advance(); + let arg = self.parse_unary_expression()?; + Ok(Expr { + kind: ExprKind::Unary { + op: UnaryOp::Not, + argument: Box::new(arg), + }, + span: self.span_from(start), + }) + } + TokenKind::Tilde => { + self.advance(); + let arg = self.parse_unary_expression()?; + Ok(Expr { + kind: ExprKind::Unary { + op: UnaryOp::BitwiseNot, + argument: Box::new(arg), + }, + span: self.span_from(start), + }) + } + TokenKind::Typeof => { + self.advance(); + let arg = self.parse_unary_expression()?; + Ok(Expr { + kind: ExprKind::Unary { + op: UnaryOp::Typeof, + argument: Box::new(arg), + }, + span: self.span_from(start), + }) + } + TokenKind::Void => { + self.advance(); + let arg = self.parse_unary_expression()?; + Ok(Expr { + kind: ExprKind::Unary { + op: UnaryOp::Void, + argument: Box::new(arg), + }, + span: self.span_from(start), + }) + } + TokenKind::Delete => { + self.advance(); + let arg = self.parse_unary_expression()?; + Ok(Expr { + kind: ExprKind::Unary { + op: UnaryOp::Delete, + argument: Box::new(arg), + }, + span: self.span_from(start), + }) + } + TokenKind::PlusPlus => { + self.advance(); + let arg = self.parse_unary_expression()?; + Ok(Expr { + kind: ExprKind::Update { + op: UpdateOp::Increment, + argument: Box::new(arg), + prefix: true, + }, + span: self.span_from(start), + }) + } + TokenKind::MinusMinus => { + self.advance(); + let arg = self.parse_unary_expression()?; + Ok(Expr { + kind: ExprKind::Update { + op: UpdateOp::Decrement, + argument: Box::new(arg), + prefix: true, + }, + span: self.span_from(start), + }) + } + TokenKind::Await => { + self.advance(); + let arg = self.parse_unary_expression()?; + Ok(Expr { + kind: ExprKind::Await(Box::new(arg)), + span: self.span_from(start), + }) + } + _ => self.parse_postfix_expression(), + } + } + + fn parse_postfix_expression(&mut self) -> Result { + let start = self.start_span(); + let mut expr = self.parse_call_expression()?; + + if !self.peek().preceded_by_newline { + if matches!(self.peek_kind(), TokenKind::PlusPlus) { + self.advance(); + expr = Expr { + kind: ExprKind::Update { + op: UpdateOp::Increment, + argument: Box::new(expr), + prefix: false, + }, + span: self.span_from(start), + }; + } else if matches!(self.peek_kind(), TokenKind::MinusMinus) { + self.advance(); + expr = Expr { + kind: ExprKind::Update { + op: UpdateOp::Decrement, + argument: Box::new(expr), + prefix: false, + }, + span: self.span_from(start), + }; + } + } + + Ok(expr) + } + + fn parse_call_expression(&mut self) -> Result { + let start = self.start_span(); + + if matches!(self.peek_kind(), TokenKind::New) { + return self.parse_new_expression(); + } + + let mut expr = self.parse_primary_expression()?; + + loop { + match self.peek_kind() { + TokenKind::Dot => { + self.advance(); + let prop_start = self.start_span(); + let name = self.expect_identifier()?; + expr = Expr { + kind: ExprKind::Member { + object: Box::new(expr), + property: Box::new(Expr { + kind: ExprKind::Identifier(name), + span: self.span_from(prop_start), + }), + computed: false, + }, + span: self.span_from(start), + }; + } + TokenKind::LBracket => { + self.advance(); + let prop = self.parse_expression()?; + self.expect(&TokenKind::RBracket)?; + expr = Expr { + kind: ExprKind::Member { + object: Box::new(expr), + property: Box::new(prop), + computed: true, + }, + span: self.span_from(start), + }; + } + TokenKind::LParen => { + let arguments = self.parse_arguments()?; + expr = Expr { + kind: ExprKind::Call { + callee: Box::new(expr), + arguments, + }, + span: self.span_from(start), + }; + } + TokenKind::QuestionDot => { + self.advance(); + // ?. can be followed by identifier, [expr], or (args) + match self.peek_kind() { + TokenKind::LBracket => { + self.advance(); + let prop = self.parse_expression()?; + self.expect(&TokenKind::RBracket)?; + expr = Expr { + kind: ExprKind::Member { + object: Box::new(Expr { + kind: ExprKind::OptionalChain { + base: Box::new(expr.clone()), + }, + span: self.span_from(start), + }), + property: Box::new(prop), + computed: true, + }, + span: self.span_from(start), + }; + } + TokenKind::LParen => { + let arguments = self.parse_arguments()?; + expr = Expr { + kind: ExprKind::Call { + callee: Box::new(Expr { + kind: ExprKind::OptionalChain { + base: Box::new(expr.clone()), + }, + span: self.span_from(start), + }), + arguments, + }, + span: self.span_from(start), + }; + } + _ => { + let prop_start = self.start_span(); + let name = self.expect_identifier()?; + expr = Expr { + kind: ExprKind::Member { + object: Box::new(Expr { + kind: ExprKind::OptionalChain { + base: Box::new(expr.clone()), + }, + span: self.span_from(start), + }), + property: Box::new(Expr { + kind: ExprKind::Identifier(name), + span: self.span_from(prop_start), + }), + computed: false, + }, + span: self.span_from(start), + }; + } + } + } + TokenKind::TemplateFull(_) | TokenKind::TemplateHead(_) => { + let quasi = self.parse_template_literal()?; + expr = Expr { + kind: ExprKind::TaggedTemplate { + tag: Box::new(expr), + quasi: Box::new(quasi), + }, + span: self.span_from(start), + }; + } + _ => break, + } + } + + Ok(expr) + } + + fn parse_new_expression(&mut self) -> Result { + let start = self.start_span(); + self.expect(&TokenKind::New)?; + + // new new Foo() → new (new Foo()) + if matches!(self.peek_kind(), TokenKind::New) { + let callee = self.parse_new_expression()?; + return Ok(Expr { + kind: ExprKind::New { + callee: Box::new(callee), + arguments: vec![], + }, + span: self.span_from(start), + }); + } + + let callee = self.parse_primary_expression()?; + // Allow member access on the callee + let callee = self.parse_member_chain(start, callee)?; + let arguments = if self.at(&TokenKind::LParen) { + self.parse_arguments()? + } else { + vec![] + }; + Ok(Expr { + kind: ExprKind::New { + callee: Box::new(callee), + arguments, + }, + span: self.span_from(start), + }) + } + + fn parse_member_chain(&mut self, start: SourcePos, mut expr: Expr) -> Result { + loop { + match self.peek_kind() { + TokenKind::Dot => { + self.advance(); + let prop_start = self.start_span(); + let name = self.expect_identifier()?; + expr = Expr { + kind: ExprKind::Member { + object: Box::new(expr), + property: Box::new(Expr { + kind: ExprKind::Identifier(name), + span: self.span_from(prop_start), + }), + computed: false, + }, + span: self.span_from(start), + }; + } + TokenKind::LBracket => { + self.advance(); + let prop = self.parse_expression()?; + self.expect(&TokenKind::RBracket)?; + expr = Expr { + kind: ExprKind::Member { + object: Box::new(expr), + property: Box::new(prop), + computed: true, + }, + span: self.span_from(start), + }; + } + _ => break, + } + } + Ok(expr) + } + + fn parse_left_hand_side_expression(&mut self) -> Result { + self.parse_call_expression() + } + + fn parse_arguments(&mut self) -> Result, ParseError> { + self.expect(&TokenKind::LParen)?; + let mut args = Vec::new(); + while !self.at(&TokenKind::RParen) && !self.at_eof() { + if self.at(&TokenKind::Ellipsis) { + let start = self.start_span(); + self.advance(); + let arg = self.parse_assignment_expression()?; + args.push(Expr { + kind: ExprKind::Spread(Box::new(arg)), + span: self.span_from(start), + }); + } else { + args.push(self.parse_assignment_expression()?); + } + if !self.eat(&TokenKind::Comma) { + break; + } + } + self.expect(&TokenKind::RParen)?; + Ok(args) + } + + // ── Primary expressions ───────────────────────────────── + + fn parse_primary_expression(&mut self) -> Result { + let start = self.start_span(); + match self.peek_kind().clone() { + TokenKind::Number(n) => { + self.advance(); + Ok(Expr { + kind: ExprKind::Number(n), + span: self.span_from(start), + }) + } + TokenKind::String(s) => { + self.advance(); + Ok(Expr { + kind: ExprKind::String(s), + span: self.span_from(start), + }) + } + TokenKind::True => { + self.advance(); + Ok(Expr { + kind: ExprKind::Bool(true), + span: self.span_from(start), + }) + } + TokenKind::False => { + self.advance(); + Ok(Expr { + kind: ExprKind::Bool(false), + span: self.span_from(start), + }) + } + TokenKind::Null => { + self.advance(); + Ok(Expr { + kind: ExprKind::Null, + span: self.span_from(start), + }) + } + TokenKind::This => { + self.advance(); + Ok(Expr { + kind: ExprKind::This, + span: self.span_from(start), + }) + } + TokenKind::Identifier(name) => { + self.advance(); + Ok(Expr { + kind: ExprKind::Identifier(name), + span: self.span_from(start), + }) + } + TokenKind::LParen => self.parse_parenthesized_expression(), + TokenKind::LBracket => self.parse_array_expression(), + TokenKind::LBrace => self.parse_object_expression(), + TokenKind::Function => self.parse_function_expression(), + TokenKind::Class => self.parse_class_expression(), + TokenKind::Async => { + // async function expression + if matches!(self.peek_ahead(1), TokenKind::Function) + && !self.tokens[self.pos + 1].preceded_by_newline + { + return self.parse_async_function_expression(); + } + // Otherwise treat 'async' as an identifier + self.advance(); + Ok(Expr { + kind: ExprKind::Identifier("async".into()), + span: self.span_from(start), + }) + } + TokenKind::TemplateFull(_) | TokenKind::TemplateHead(_) => { + self.parse_template_literal() + } + TokenKind::RegExp { pattern, flags } => { + self.advance(); + Ok(Expr { + kind: ExprKind::RegExp { pattern, flags }, + span: self.span_from(start), + }) + } + TokenKind::Yield => { + self.advance(); + let delegate = self.eat(&TokenKind::Star); + let argument = if !self.peek().preceded_by_newline + && !matches!( + self.peek_kind(), + TokenKind::Semicolon + | TokenKind::RBrace + | TokenKind::RParen + | TokenKind::RBracket + | TokenKind::Colon + | TokenKind::Comma + | TokenKind::Eof + ) { + Some(Box::new(self.parse_assignment_expression()?)) + } else { + None + }; + Ok(Expr { + kind: ExprKind::Yield { argument, delegate }, + span: self.span_from(start), + }) + } + TokenKind::Super => { + self.advance(); + Ok(Expr { + kind: ExprKind::Identifier("super".into()), + span: self.span_from(start), + }) + } + TokenKind::Import => { + // import() call expression + self.advance(); + Ok(Expr { + kind: ExprKind::Identifier("import".into()), + span: self.span_from(start), + }) + } + _ => Err(self.error(format!("unexpected token {:?}", self.peek_kind()))), + } + } + + fn parse_parenthesized_expression(&mut self) -> Result { + let start = self.start_span(); + self.expect(&TokenKind::LParen)?; + + // Empty parens: `() => ...` + if self.at(&TokenKind::RParen) { + self.advance(); + // Must be arrow + if !matches!(self.peek_kind(), TokenKind::Arrow) { + return Err(self.error("unexpected empty parentheses".into())); + } + // Arrow will be handled by caller + return Ok(Expr { + kind: ExprKind::Sequence(vec![]), + span: self.span_from(start), + }); + } + + // `(...rest) => ...` + if self.at(&TokenKind::Ellipsis) { + // This must be an arrow parameter list with rest + let rest_start = self.start_span(); + self.advance(); + let rest_pat = self.parse_binding_pattern()?; + self.expect(&TokenKind::RParen)?; + // Must be followed by => + if !matches!(self.peek_kind(), TokenKind::Arrow) { + return Err(self.error("expected '=>'".into())); + } + let _ = rest_pat; // Will be handled as pattern + // Return a marker that the arrow handler will convert + return Ok(Expr { + kind: ExprKind::Spread(Box::new(Expr { + kind: match rest_pat.kind { + PatternKind::Identifier(name) => ExprKind::Identifier(name), + _ => { + return Err(ParseError { + message: "complex rest pattern in arrow".into(), + pos: rest_start, + }) + } + }, + span: rest_pat.span, + })), + span: self.span_from(start), + }); + } + + let expr = self.parse_expression()?; + self.expect(&TokenKind::RParen)?; + Ok(expr) + } + + fn parse_array_expression(&mut self) -> Result { + let start = self.start_span(); + self.expect(&TokenKind::LBracket)?; + let mut elements = Vec::new(); + while !self.at(&TokenKind::RBracket) && !self.at_eof() { + if self.eat(&TokenKind::Comma) { + elements.push(None); + continue; + } + if self.at(&TokenKind::Ellipsis) { + let spread_start = self.start_span(); + self.advance(); + let arg = self.parse_assignment_expression()?; + elements.push(Some(ArrayElement::Spread(Expr { + kind: ExprKind::Spread(Box::new(arg)), + span: self.span_from(spread_start), + }))); + } else { + let elem = self.parse_assignment_expression()?; + elements.push(Some(ArrayElement::Expr(elem))); + } + if !self.eat(&TokenKind::Comma) { + break; + } + } + self.expect(&TokenKind::RBracket)?; + Ok(Expr { + kind: ExprKind::Array(elements), + span: self.span_from(start), + }) + } + + fn parse_object_expression(&mut self) -> Result { + let start = self.start_span(); + self.expect(&TokenKind::LBrace)?; + let mut properties = Vec::new(); + while !self.at(&TokenKind::RBrace) && !self.at_eof() { + let prop = self.parse_object_property()?; + properties.push(prop); + if !self.eat(&TokenKind::Comma) { + break; + } + } + self.expect(&TokenKind::RBrace)?; + Ok(Expr { + kind: ExprKind::Object(properties), + span: self.span_from(start), + }) + } + + fn parse_object_property(&mut self) -> Result { + let prop_start = self.start_span(); + + // Spread: `...expr` + if self.at(&TokenKind::Ellipsis) { + self.advance(); + let arg = self.parse_assignment_expression()?; + return Ok(Property { + key: PropertyKey::Identifier("".into()), + value: Some(arg), + kind: PropertyKind::Init, + computed: false, + shorthand: false, + method: false, + span: self.span_from(prop_start), + }); + } + + // Getter/setter: `get name() {}` or `set name(v) {}` + if let TokenKind::Identifier(ref name) = self.peek_kind().clone() { + if (name == "get" || name == "set") + && !matches!( + self.peek_ahead(1), + TokenKind::Colon + | TokenKind::Comma + | TokenKind::RBrace + | TokenKind::LParen + | TokenKind::Assign + ) + { + let kind = if name == "get" { + PropertyKind::Get + } else { + PropertyKind::Set + }; + self.advance(); + let (key, computed) = self.parse_property_name()?; + let (params, body) = self.parse_function_params_and_body()?; + return Ok(Property { + key, + value: Some(Expr { + kind: ExprKind::Function(FunctionDef { + id: None, + params, + body, + is_async: false, + is_generator: false, + }), + span: self.span_from(prop_start), + }), + kind, + computed, + shorthand: false, + method: true, + span: self.span_from(prop_start), + }); + } + } + + // Generator method: `*name() {}` + if self.eat(&TokenKind::Star) { + let (key, computed) = self.parse_property_name()?; + let (params, body) = self.parse_function_params_and_body()?; + return Ok(Property { + key, + value: Some(Expr { + kind: ExprKind::Function(FunctionDef { + id: None, + params, + body, + is_async: false, + is_generator: true, + }), + span: self.span_from(prop_start), + }), + kind: PropertyKind::Init, + computed, + shorthand: false, + method: true, + span: self.span_from(prop_start), + }); + } + + // Async method: `async name() {}` + let is_async = if matches!(self.peek_kind(), TokenKind::Async) + && !matches!( + self.peek_ahead(1), + TokenKind::Colon | TokenKind::Comma | TokenKind::RBrace | TokenKind::Assign + ) + && !self + .tokens + .get(self.pos + 1) + .is_none_or(|t| t.preceded_by_newline) + { + self.advance(); + true + } else { + false + }; + + let is_generator = is_async && self.eat(&TokenKind::Star); + + let (key, computed) = self.parse_property_name()?; + + // Method shorthand: `name() {}` or `name(params) {}` + if self.at(&TokenKind::LParen) { + let (params, body) = self.parse_function_params_and_body()?; + return Ok(Property { + key, + value: Some(Expr { + kind: ExprKind::Function(FunctionDef { + id: None, + params, + body, + is_async, + is_generator, + }), + span: self.span_from(prop_start), + }), + kind: PropertyKind::Init, + computed, + shorthand: false, + method: true, + span: self.span_from(prop_start), + }); + } + + // Shorthand: `{ name }` or `{ name = default }` + if !computed + && matches!( + self.peek_kind(), + TokenKind::Comma | TokenKind::RBrace | TokenKind::Assign + ) + { + let name = match &key { + PropertyKey::Identifier(n) => n.clone(), + _ => return Err(self.error("expected identifier for shorthand property".into())), + }; + let value = if self.eat(&TokenKind::Assign) { + let default = self.parse_assignment_expression()?; + Some(Expr { + kind: ExprKind::Assignment { + op: AssignOp::Assign, + left: Box::new(Expr { + kind: ExprKind::Identifier(name.clone()), + span: self.span_from(prop_start), + }), + right: Box::new(default), + }, + span: self.span_from(prop_start), + }) + } else { + Some(Expr { + kind: ExprKind::Identifier(name), + span: self.span_from(prop_start), + }) + }; + return Ok(Property { + key, + value, + kind: PropertyKind::Init, + computed, + shorthand: true, + method: false, + span: self.span_from(prop_start), + }); + } + + // Regular: `key: value` + self.expect(&TokenKind::Colon)?; + let value = self.parse_assignment_expression()?; + Ok(Property { + key, + value: Some(value), + kind: PropertyKind::Init, + computed, + shorthand: false, + method: false, + span: self.span_from(prop_start), + }) + } + + fn parse_property_name(&mut self) -> Result<(PropertyKey, bool), ParseError> { + match self.peek_kind().clone() { + TokenKind::Identifier(name) => { + self.advance(); + Ok((PropertyKey::Identifier(name), false)) + } + TokenKind::String(s) => { + self.advance(); + Ok((PropertyKey::String(s), false)) + } + TokenKind::Number(n) => { + self.advance(); + Ok((PropertyKey::Number(n), false)) + } + TokenKind::LBracket => { + self.advance(); + let expr = self.parse_assignment_expression()?; + self.expect(&TokenKind::RBracket)?; + Ok((PropertyKey::Computed(expr), true)) + } + // Keywords can also be property names + _ if is_keyword(self.peek_kind()) => { + let name = format!("{}", self.peek_kind()); + self.advance(); + Ok((PropertyKey::Identifier(name), false)) + } + _ => Err(self.error(format!( + "expected property name, found {:?}", + self.peek_kind() + ))), + } + } + + fn parse_function_expression(&mut self) -> Result { + let start = self.start_span(); + self.expect(&TokenKind::Function)?; + let is_generator = self.eat(&TokenKind::Star); + let id = if let TokenKind::Identifier(_) = self.peek_kind() { + Some(self.expect_identifier()?) + } else { + None + }; + let (params, body) = self.parse_function_params_and_body()?; + Ok(Expr { + kind: ExprKind::Function(FunctionDef { + id, + params, + body, + is_async: false, + is_generator, + }), + span: self.span_from(start), + }) + } + + fn parse_async_function_expression(&mut self) -> Result { + let start = self.start_span(); + self.expect(&TokenKind::Async)?; + self.expect(&TokenKind::Function)?; + let is_generator = self.eat(&TokenKind::Star); + let id = if let TokenKind::Identifier(_) = self.peek_kind() { + Some(self.expect_identifier()?) + } else { + None + }; + let (params, body) = self.parse_function_params_and_body()?; + Ok(Expr { + kind: ExprKind::Function(FunctionDef { + id, + params, + body, + is_async: true, + is_generator, + }), + span: self.span_from(start), + }) + } + + fn parse_class_expression(&mut self) -> Result { + let start = self.start_span(); + let class_def = self.parse_class(false)?; + Ok(Expr { + kind: ExprKind::Class(class_def), + span: self.span_from(start), + }) + } + + fn parse_template_literal(&mut self) -> Result { + let start = self.start_span(); + match self.peek_kind().clone() { + TokenKind::TemplateFull(s) => { + self.advance(); + Ok(Expr { + kind: ExprKind::TemplateLiteral { + quasis: vec![s], + expressions: vec![], + }, + span: self.span_from(start), + }) + } + TokenKind::TemplateHead(s) => { + self.advance(); + let mut quasis = vec![s]; + let mut expressions = Vec::new(); + loop { + expressions.push(self.parse_expression()?); + match self.peek_kind().clone() { + TokenKind::TemplateMiddle(s) => { + self.advance(); + quasis.push(s); + } + TokenKind::TemplateTail(s) => { + self.advance(); + quasis.push(s); + break; + } + _ => { + return Err(self.error("expected template continuation or tail".into())); + } + } + } + Ok(Expr { + kind: ExprKind::TemplateLiteral { + quasis, + expressions, + }, + span: self.span_from(start), + }) + } + _ => Err(self.error("expected template literal".into())), + } + } + + // ── Operator tables ───────────────────────────────────── + + fn peek_binary_op(&self) -> Option<(BinOrLogical, u8, u8)> { + let kind = self.peek_kind(); + let (op, left_bp, right_bp) = match kind { + TokenKind::Or => (BinOrLogical::Logical(LogicalOp::Or), 2, 3), + TokenKind::And => (BinOrLogical::Logical(LogicalOp::And), 4, 5), + TokenKind::Nullish => (BinOrLogical::Logical(LogicalOp::Nullish), 6, 7), + TokenKind::Pipe => (BinOrLogical::Binary(BinaryOp::BitOr), 8, 9), + TokenKind::Caret => (BinOrLogical::Binary(BinaryOp::BitXor), 10, 11), + TokenKind::Amp => (BinOrLogical::Binary(BinaryOp::BitAnd), 12, 13), + TokenKind::Eq => (BinOrLogical::Binary(BinaryOp::Eq), 14, 15), + TokenKind::Ne => (BinOrLogical::Binary(BinaryOp::Ne), 14, 15), + TokenKind::StrictEq => (BinOrLogical::Binary(BinaryOp::StrictEq), 14, 15), + TokenKind::StrictNe => (BinOrLogical::Binary(BinaryOp::StrictNe), 14, 15), + TokenKind::Lt => (BinOrLogical::Binary(BinaryOp::Lt), 16, 17), + TokenKind::Gt => (BinOrLogical::Binary(BinaryOp::Gt), 16, 17), + TokenKind::Le => (BinOrLogical::Binary(BinaryOp::Le), 16, 17), + TokenKind::Ge => (BinOrLogical::Binary(BinaryOp::Ge), 16, 17), + TokenKind::Instanceof => (BinOrLogical::Binary(BinaryOp::Instanceof), 16, 17), + TokenKind::In if self.allow_in => (BinOrLogical::Binary(BinaryOp::In), 16, 17), + TokenKind::Shl => (BinOrLogical::Binary(BinaryOp::Shl), 18, 19), + TokenKind::Shr => (BinOrLogical::Binary(BinaryOp::Shr), 18, 19), + TokenKind::Ushr => (BinOrLogical::Binary(BinaryOp::Ushr), 18, 19), + TokenKind::Plus => (BinOrLogical::Binary(BinaryOp::Add), 20, 21), + TokenKind::Minus => (BinOrLogical::Binary(BinaryOp::Sub), 20, 21), + TokenKind::Star => (BinOrLogical::Binary(BinaryOp::Mul), 22, 23), + TokenKind::Slash => (BinOrLogical::Binary(BinaryOp::Div), 22, 23), + TokenKind::Percent => (BinOrLogical::Binary(BinaryOp::Rem), 22, 23), + TokenKind::Exp => (BinOrLogical::Binary(BinaryOp::Exp), 25, 24), // right-assoc + _ => return None, + }; + Some((op, left_bp, right_bp)) + } + + fn peek_assign_op(&self) -> Option { + match self.peek_kind() { + TokenKind::Assign => Some(AssignOp::Assign), + TokenKind::PlusAssign => Some(AssignOp::AddAssign), + TokenKind::MinusAssign => Some(AssignOp::SubAssign), + TokenKind::StarAssign => Some(AssignOp::MulAssign), + TokenKind::SlashAssign => Some(AssignOp::DivAssign), + TokenKind::PercentAssign => Some(AssignOp::RemAssign), + TokenKind::ExpAssign => Some(AssignOp::ExpAssign), + TokenKind::ShlAssign => Some(AssignOp::ShlAssign), + TokenKind::ShrAssign => Some(AssignOp::ShrAssign), + TokenKind::UshrAssign => Some(AssignOp::UshrAssign), + TokenKind::AmpAssign => Some(AssignOp::BitAndAssign), + TokenKind::PipeAssign => Some(AssignOp::BitOrAssign), + TokenKind::CaretAssign => Some(AssignOp::BitXorAssign), + TokenKind::AndAssign => Some(AssignOp::AndAssign), + TokenKind::OrAssign => Some(AssignOp::OrAssign), + TokenKind::NullishAssign => Some(AssignOp::NullishAssign), + _ => None, + } + } + + // ── Expression ↔ Pattern conversion ───────────────────── + + fn expr_to_pattern(&self, expr: &Expr) -> Result { + let span = expr.span; + match &expr.kind { + ExprKind::Identifier(name) => Ok(Pattern { + kind: PatternKind::Identifier(name.clone()), + span, + }), + ExprKind::Array(elements) => { + let mut pats = Vec::new(); + let mut rest = None; + for elem in elements { + match elem { + None => pats.push(None), + Some(ArrayElement::Spread(e)) => { + if let ExprKind::Spread(inner) = &e.kind { + rest = Some(Box::new(self.expr_to_pattern(inner)?)); + } else { + rest = Some(Box::new(self.expr_to_pattern(e)?)); + } + } + Some(ArrayElement::Expr(e)) => { + pats.push(Some(self.expr_to_pattern(e)?)); + } + } + } + Ok(Pattern { + kind: PatternKind::Array { + elements: pats, + rest, + }, + span, + }) + } + ExprKind::Object(properties) => { + let mut pats = Vec::new(); + let mut rest = None; + for prop in properties { + if prop.key == PropertyKey::Identifier("".into()) { + if let Some(val) = &prop.value { + // Spread property + rest = Some(Box::new(self.expr_to_pattern(val)?)); + } + continue; + } + let value = if let Some(val) = &prop.value { + self.expr_to_pattern(val)? + } else { + continue; + }; + pats.push(ObjectPatternProp { + key: prop.key.clone(), + value, + computed: prop.computed, + shorthand: prop.shorthand, + span: prop.span, + }); + } + Ok(Pattern { + kind: PatternKind::Object { + properties: pats, + rest, + }, + span, + }) + } + ExprKind::Assignment { + op: AssignOp::Assign, + left, + right, + } => { + let left_pat = self.expr_to_pattern(left)?; + Ok(Pattern { + kind: PatternKind::Assign { + left: Box::new(left_pat), + right: right.clone(), + }, + span, + }) + } + ExprKind::Spread(inner) => self.expr_to_pattern(inner), + _ => Err(ParseError { + message: "invalid destructuring pattern".into(), + pos: span.start, + }), + } + } + + fn expr_to_params(&self, expr: &Expr) -> Result, ParseError> { + match &expr.kind { + ExprKind::Sequence(exprs) if exprs.is_empty() => Ok(vec![]), + ExprKind::Sequence(exprs) => { + let mut params = Vec::new(); + for e in exprs { + params.push(self.expr_to_pattern(e)?); + } + Ok(params) + } + ExprKind::Spread(inner) => { + let pat = self.expr_to_pattern(inner)?; + Ok(vec![pat]) + } + _ => { + // Single expression as single parameter + Ok(vec![self.expr_to_pattern(expr)?]) + } + } + } +} + +// ── Helpers ───────────────────────────────────────────────── + +/// Distinguishes binary operators from logical operators in Pratt parsing. +enum BinOrLogical { + Binary(BinaryOp), + Logical(LogicalOp), +} + +/// Returns true if the token kind is a keyword (used for property name parsing). +fn is_keyword(kind: &TokenKind) -> bool { + matches!( + kind, + TokenKind::Await + | TokenKind::Break + | TokenKind::Case + | TokenKind::Catch + | TokenKind::Class + | TokenKind::Const + | TokenKind::Continue + | TokenKind::Debugger + | TokenKind::Default + | TokenKind::Delete + | TokenKind::Do + | TokenKind::Else + | TokenKind::Export + | TokenKind::Extends + | TokenKind::Finally + | TokenKind::For + | TokenKind::Function + | TokenKind::If + | TokenKind::Import + | TokenKind::In + | TokenKind::Instanceof + | TokenKind::Let + | TokenKind::New + | TokenKind::Of + | TokenKind::Return + | TokenKind::Static + | TokenKind::Super + | TokenKind::Switch + | TokenKind::This + | TokenKind::Throw + | TokenKind::Try + | TokenKind::Typeof + | TokenKind::Var + | TokenKind::Void + | TokenKind::While + | TokenKind::With + | TokenKind::Yield + | TokenKind::Async + | TokenKind::True + | TokenKind::False + | TokenKind::Null + ) +} + +// ── Tests ─────────────────────────────────────────────────── + +#[cfg(test)] +mod tests { + use super::*; + + fn parse(src: &str) -> Program { + Parser::parse(src).unwrap() + } + + fn parse_expr(src: &str) -> Expr { + let prog = parse(src); + match &prog.body[0].kind { + StmtKind::Expr(e) => e.clone(), + _ => panic!("expected expression statement, got {:?}", prog.body[0].kind), + } + } + + fn parse_stmt(src: &str) -> Stmt { + let prog = parse(src); + prog.body[0].clone() + } + + // ── Literals ──────────────────────────────────────── + + #[test] + fn test_number_literal() { + let expr = parse_expr("42"); + assert_eq!(expr.kind, ExprKind::Number(42.0)); + } + + #[test] + fn test_string_literal() { + let expr = parse_expr("\"hello\""); + assert_eq!(expr.kind, ExprKind::String("hello".into())); + } + + #[test] + fn test_boolean_literals() { + assert_eq!(parse_expr("true").kind, ExprKind::Bool(true)); + assert_eq!(parse_expr("false").kind, ExprKind::Bool(false)); + } + + #[test] + fn test_null_literal() { + assert_eq!(parse_expr("null").kind, ExprKind::Null); + } + + #[test] + fn test_this() { + assert_eq!(parse_expr("this").kind, ExprKind::This); + } + + #[test] + fn test_identifier() { + assert_eq!(parse_expr("foo").kind, ExprKind::Identifier("foo".into())); + } + + #[test] + fn test_regexp() { + let expr = parse_expr("x = /test/gi"); + match &expr.kind { + ExprKind::Assignment { right, .. } => { + assert_eq!( + right.kind, + ExprKind::RegExp { + pattern: "test".into(), + flags: "gi".into() + } + ); + } + _ => panic!("expected assignment"), + } + } + + // ── Binary expressions ────────────────────────────── + + #[test] + fn test_addition() { + let expr = parse_expr("1 + 2"); + match expr.kind { + ExprKind::Binary { op, left, right } => { + assert_eq!(op, BinaryOp::Add); + assert_eq!(left.kind, ExprKind::Number(1.0)); + assert_eq!(right.kind, ExprKind::Number(2.0)); + } + _ => panic!("expected binary"), + } + } + + #[test] + fn test_precedence() { + // 1 + 2 * 3 should be 1 + (2 * 3) + let expr = parse_expr("1 + 2 * 3"); + match expr.kind { + ExprKind::Binary { + op: BinaryOp::Add, + left, + right, + } => { + assert_eq!(left.kind, ExprKind::Number(1.0)); + match right.kind { + ExprKind::Binary { + op: BinaryOp::Mul, .. + } => {} + _ => panic!("expected multiply on right"), + } + } + _ => panic!("expected binary add"), + } + } + + #[test] + fn test_exponentiation_right_assoc() { + // 2 ** 3 ** 2 should be 2 ** (3 ** 2) + let expr = parse_expr("2 ** 3 ** 2"); + match expr.kind { + ExprKind::Binary { + op: BinaryOp::Exp, + left, + right, + } => { + assert_eq!(left.kind, ExprKind::Number(2.0)); + match right.kind { + ExprKind::Binary { + op: BinaryOp::Exp, .. + } => {} + _ => panic!("expected exp on right"), + } + } + _ => panic!("expected binary exp"), + } + } + + #[test] + fn test_comparison() { + let expr = parse_expr("a === b"); + match expr.kind { + ExprKind::Binary { + op: BinaryOp::StrictEq, + .. + } => {} + _ => panic!("expected strict eq"), + } + } + + #[test] + fn test_logical_operators() { + let expr = parse_expr("a && b || c"); + // Should be (a && b) || c because && has higher precedence + match expr.kind { + ExprKind::Logical { + op: LogicalOp::Or, .. + } => {} + _ => panic!("expected logical or"), + } + } + + // ── Unary expressions ─────────────────────────────── + + #[test] + fn test_unary_minus() { + let expr = parse_expr("-x"); + match expr.kind { + ExprKind::Unary { + op: UnaryOp::Minus, + argument, + } => { + assert_eq!(argument.kind, ExprKind::Identifier("x".into())); + } + _ => panic!("expected unary minus"), + } + } + + #[test] + fn test_typeof() { + let expr = parse_expr("typeof x"); + match expr.kind { + ExprKind::Unary { + op: UnaryOp::Typeof, + .. + } => {} + _ => panic!("expected typeof"), + } + } + + #[test] + fn test_prefix_increment() { + let expr = parse_expr("++x"); + match expr.kind { + ExprKind::Update { + op: UpdateOp::Increment, + prefix: true, + .. + } => {} + _ => panic!("expected prefix increment"), + } + } + + #[test] + fn test_postfix_decrement() { + let expr = parse_expr("x--"); + match expr.kind { + ExprKind::Update { + op: UpdateOp::Decrement, + prefix: false, + .. + } => {} + _ => panic!("expected postfix decrement"), + } + } + + // ── Conditional expression ────────────────────────── + + #[test] + fn test_ternary() { + let expr = parse_expr("a ? b : c"); + match expr.kind { + ExprKind::Conditional { + test, + consequent, + alternate, + } => { + assert_eq!(test.kind, ExprKind::Identifier("a".into())); + assert_eq!(consequent.kind, ExprKind::Identifier("b".into())); + assert_eq!(alternate.kind, ExprKind::Identifier("c".into())); + } + _ => panic!("expected conditional"), + } + } + + // ── Assignment ────────────────────────────────────── + + #[test] + fn test_assignment() { + let expr = parse_expr("x = 42"); + match expr.kind { + ExprKind::Assignment { + op: AssignOp::Assign, + left, + right, + } => { + assert_eq!(left.kind, ExprKind::Identifier("x".into())); + assert_eq!(right.kind, ExprKind::Number(42.0)); + } + _ => panic!("expected assignment"), + } + } + + #[test] + fn test_compound_assignment() { + let expr = parse_expr("x += 1"); + match expr.kind { + ExprKind::Assignment { + op: AssignOp::AddAssign, + .. + } => {} + _ => panic!("expected add-assign"), + } + } + + // ── Member expressions ────────────────────────────── + + #[test] + fn test_dot_access() { + let expr = parse_expr("a.b"); + match expr.kind { + ExprKind::Member { + computed: false, .. + } => {} + _ => panic!("expected member access"), + } + } + + #[test] + fn test_bracket_access() { + let expr = parse_expr("a[0]"); + match expr.kind { + ExprKind::Member { computed: true, .. } => {} + _ => panic!("expected computed member"), + } + } + + // ── Call expressions ──────────────────────────────── + + #[test] + fn test_function_call() { + let expr = parse_expr("foo(1, 2)"); + match expr.kind { + ExprKind::Call { + callee, arguments, .. + } => { + assert_eq!(callee.kind, ExprKind::Identifier("foo".into())); + assert_eq!(arguments.len(), 2); + } + _ => panic!("expected call"), + } + } + + #[test] + fn test_method_call() { + let expr = parse_expr("obj.method()"); + match expr.kind { + ExprKind::Call { callee, .. } => match callee.kind { + ExprKind::Member { .. } => {} + _ => panic!("expected member callee"), + }, + _ => panic!("expected call"), + } + } + + #[test] + fn test_new_expression() { + let expr = parse_expr("new Foo(1)"); + match expr.kind { + ExprKind::New { + callee, arguments, .. + } => { + assert_eq!(callee.kind, ExprKind::Identifier("Foo".into())); + assert_eq!(arguments.len(), 1); + } + _ => panic!("expected new"), + } + } + + // ── Array and object literals ─────────────────────── + + #[test] + fn test_array_literal() { + let expr = parse_expr("[1, 2, 3]"); + match expr.kind { + ExprKind::Array(elements) => { + assert_eq!(elements.len(), 3); + } + _ => panic!("expected array"), + } + } + + #[test] + fn test_object_literal() { + let expr = parse_expr("({a: 1, b: 2})"); + match expr.kind { + ExprKind::Object(props) => { + assert_eq!(props.len(), 2); + assert_eq!(props[0].key, PropertyKey::Identifier("a".into())); + } + _ => panic!("expected object"), + } + } + + #[test] + fn test_shorthand_property() { + let expr = parse_expr("({x, y})"); + match expr.kind { + ExprKind::Object(props) => { + assert!(props[0].shorthand); + assert!(props[1].shorthand); + } + _ => panic!("expected object"), + } + } + + #[test] + fn test_computed_property() { + let expr = parse_expr("({[key]: value})"); + match expr.kind { + ExprKind::Object(props) => { + assert!(props[0].computed); + } + _ => panic!("expected object"), + } + } + + // ── Arrow functions ───────────────────────────────── + + #[test] + fn test_arrow_single_param() { + let expr = parse_expr("x => x + 1"); + match expr.kind { + ExprKind::Arrow { + params, + body: ArrowBody::Expr(_), + is_async: false, + } => { + assert_eq!(params.len(), 1); + } + _ => panic!("expected arrow"), + } + } + + #[test] + fn test_arrow_multi_param() { + let expr = parse_expr("(a, b) => a + b"); + match expr.kind { + ExprKind::Arrow { params, .. } => { + assert_eq!(params.len(), 2); + } + _ => panic!("expected arrow"), + } + } + + #[test] + fn test_arrow_body_block() { + let expr = parse_expr("() => { return 1; }"); + match expr.kind { + ExprKind::Arrow { + body: ArrowBody::Block(stmts), + .. + } => { + assert_eq!(stmts.len(), 1); + } + _ => panic!("expected arrow with block body"), + } + } + + // ── Template literals ─────────────────────────────── + + #[test] + fn test_template_no_subst() { + let expr = parse_expr("`hello`"); + match expr.kind { + ExprKind::TemplateLiteral { + quasis, + expressions, + } => { + assert_eq!(quasis, vec!["hello"]); + assert!(expressions.is_empty()); + } + _ => panic!("expected template literal"), + } + } + + #[test] + fn test_template_with_subst() { + let expr = parse_expr("`hello ${name}!`"); + match expr.kind { + ExprKind::TemplateLiteral { + quasis, + expressions, + } => { + assert_eq!(quasis, vec!["hello ", "!"]); + assert_eq!(expressions.len(), 1); + } + _ => panic!("expected template literal"), + } + } + + // ── Spread ────────────────────────────────────────── + + #[test] + fn test_spread_in_call() { + let expr = parse_expr("foo(...args)"); + match expr.kind { + ExprKind::Call { arguments, .. } => { + assert_eq!(arguments.len(), 1); + assert!(matches!(arguments[0].kind, ExprKind::Spread(_))); + } + _ => panic!("expected call"), + } + } + + // ── Variable declarations ─────────────────────────── + + #[test] + fn test_var_decl() { + let stmt = parse_stmt("var x = 42;"); + match stmt.kind { + StmtKind::VarDecl { kind, declarators } => { + assert_eq!(kind, VarKind::Var); + assert_eq!(declarators.len(), 1); + assert!(matches!( + declarators[0].pattern.kind, + PatternKind::Identifier(ref n) if n == "x" + )); + assert!(declarators[0].init.is_some()); + } + _ => panic!("expected var decl"), + } + } + + #[test] + fn test_const_decl() { + let stmt = parse_stmt("const [a, b] = arr;"); + match stmt.kind { + StmtKind::VarDecl { + kind: VarKind::Const, + declarators, + } => { + assert!(matches!( + declarators[0].pattern.kind, + PatternKind::Array { .. } + )); + } + _ => panic!("expected const decl"), + } + } + + #[test] + fn test_destructuring_object() { + let stmt = parse_stmt("let { a, b: c } = obj;"); + match stmt.kind { + StmtKind::VarDecl { declarators, .. } => match &declarators[0].pattern.kind { + PatternKind::Object { properties, .. } => { + assert_eq!(properties.len(), 2); + assert!(properties[0].shorthand); + assert!(!properties[1].shorthand); + } + _ => panic!("expected object pattern"), + }, + _ => panic!("expected var decl"), + } + } + + // ── Function declaration ──────────────────────────── + + #[test] + fn test_function_decl() { + let stmt = parse_stmt("function add(a, b) { return a + b; }"); + match stmt.kind { + StmtKind::FunctionDecl(def) => { + assert_eq!(def.id, Some("add".into())); + assert_eq!(def.params.len(), 2); + assert!(!def.is_async); + assert!(!def.is_generator); + assert_eq!(def.body.len(), 1); + } + _ => panic!("expected function decl"), + } + } + + #[test] + fn test_generator_function() { + let stmt = parse_stmt("function* gen() { yield 1; }"); + match stmt.kind { + StmtKind::FunctionDecl(def) => { + assert!(def.is_generator); + } + _ => panic!("expected function decl"), + } + } + + #[test] + fn test_async_function() { + let stmt = parse_stmt("async function fetch() { await get(); }"); + match stmt.kind { + StmtKind::FunctionDecl(def) => { + assert!(def.is_async); + } + _ => panic!("expected function decl"), + } + } + + #[test] + fn test_default_params() { + let stmt = parse_stmt("function f(a, b = 10) {}"); + match stmt.kind { + StmtKind::FunctionDecl(def) => { + assert_eq!(def.params.len(), 2); + assert!(matches!(def.params[1].kind, PatternKind::Assign { .. })); + } + _ => panic!("expected function decl"), + } + } + + #[test] + fn test_rest_params() { + let stmt = parse_stmt("function f(...args) {}"); + match stmt.kind { + StmtKind::FunctionDecl(def) => { + assert_eq!(def.params.len(), 1); + assert!(matches!( + def.params[0].kind, + PatternKind::Identifier(ref n) if n == "args" + )); + } + _ => panic!("expected function decl"), + } + } + + // ── Class declaration ─────────────────────────────── + + #[test] + fn test_class_decl() { + let stmt = parse_stmt("class Foo { constructor() {} method() {} }"); + match stmt.kind { + StmtKind::ClassDecl(def) => { + assert_eq!(def.id, Some("Foo".into())); + assert_eq!(def.body.len(), 2); + } + _ => panic!("expected class decl"), + } + } + + #[test] + fn test_class_extends() { + let stmt = parse_stmt("class Bar extends Foo {}"); + match stmt.kind { + StmtKind::ClassDecl(def) => { + assert!(def.super_class.is_some()); + } + _ => panic!("expected class decl"), + } + } + + #[test] + fn test_class_getter_setter() { + let stmt = parse_stmt("class C { get x() { return 1; } set x(v) {} }"); + match stmt.kind { + StmtKind::ClassDecl(def) => { + assert_eq!(def.body.len(), 2); + match &def.body[0].kind { + ClassMemberKind::Method { kind, .. } => { + assert_eq!(*kind, MethodKind::Get); + } + _ => panic!("expected getter"), + } + } + _ => panic!("expected class decl"), + } + } + + // ── Control flow statements ───────────────────────── + + #[test] + fn test_if_else() { + let stmt = parse_stmt("if (x) { a(); } else { b(); }"); + match stmt.kind { + StmtKind::If { alternate, .. } => { + assert!(alternate.is_some()); + } + _ => panic!("expected if"), + } + } + + #[test] + fn test_for_loop() { + let stmt = parse_stmt("for (let i = 0; i < 10; i++) {}"); + match stmt.kind { + StmtKind::For { + init, test, update, .. + } => { + assert!(init.is_some()); + assert!(test.is_some()); + assert!(update.is_some()); + } + _ => panic!("expected for"), + } + } + + #[test] + fn test_for_in() { + let stmt = parse_stmt("for (const key in obj) {}"); + assert!(matches!(stmt.kind, StmtKind::ForIn { .. })); + } + + #[test] + fn test_for_of() { + let stmt = parse_stmt("for (const item of arr) {}"); + assert!(matches!(stmt.kind, StmtKind::ForOf { .. })); + } + + #[test] + fn test_while_loop() { + let stmt = parse_stmt("while (true) {}"); + assert!(matches!(stmt.kind, StmtKind::While { .. })); + } + + #[test] + fn test_do_while() { + let stmt = parse_stmt("do {} while (true);"); + assert!(matches!(stmt.kind, StmtKind::DoWhile { .. })); + } + + #[test] + fn test_switch() { + let stmt = parse_stmt("switch (x) { case 1: break; default: break; }"); + match stmt.kind { + StmtKind::Switch { cases, .. } => { + assert_eq!(cases.len(), 2); + assert!(cases[0].test.is_some()); + assert!(cases[1].test.is_none()); // default + } + _ => panic!("expected switch"), + } + } + + #[test] + fn test_try_catch() { + let stmt = parse_stmt("try { f(); } catch (e) { g(); }"); + match stmt.kind { + StmtKind::Try { + handler, finalizer, .. + } => { + assert!(handler.is_some()); + assert!(finalizer.is_none()); + } + _ => panic!("expected try"), + } + } + + #[test] + fn test_try_catch_finally() { + let stmt = parse_stmt("try {} catch (e) {} finally {}"); + match stmt.kind { + StmtKind::Try { + handler, finalizer, .. + } => { + assert!(handler.is_some()); + assert!(finalizer.is_some()); + } + _ => panic!("expected try"), + } + } + + #[test] + fn test_return() { + let stmt = parse_stmt("function f() { return 42; }"); + match stmt.kind { + StmtKind::FunctionDecl(def) => match &def.body[0].kind { + StmtKind::Return(Some(expr)) => { + assert_eq!(expr.kind, ExprKind::Number(42.0)); + } + _ => panic!("expected return"), + }, + _ => panic!("expected function"), + } + } + + #[test] + fn test_throw() { + let stmt = parse_stmt("function f() { throw new Error(); }"); + match stmt.kind { + StmtKind::FunctionDecl(def) => { + assert!(matches!(def.body[0].kind, StmtKind::Throw(_))); + } + _ => panic!("expected function"), + } + } + + #[test] + fn test_break_continue() { + let prog = parse("while (true) { break; continue; }"); + let while_stmt = &prog.body[0]; + match &while_stmt.kind { + StmtKind::While { body, .. } => match &body.kind { + StmtKind::Block(stmts) => { + assert!(matches!(stmts[0].kind, StmtKind::Break(None))); + assert!(matches!(stmts[1].kind, StmtKind::Continue(None))); + } + _ => panic!("expected block"), + }, + _ => panic!("expected while"), + } + } + + #[test] + fn test_labeled_statement() { + let stmt = parse_stmt("outer: for (;;) {}"); + match stmt.kind { + StmtKind::Labeled { label, .. } => { + assert_eq!(label, "outer"); + } + _ => panic!("expected labeled"), + } + } + + // ── Import / Export ───────────────────────────────── + + #[test] + fn test_import_default() { + let stmt = parse_stmt("import foo from \"mod\";"); + match stmt.kind { + StmtKind::Import { specifiers, source } => { + assert_eq!(source, "mod"); + assert_eq!(specifiers.len(), 1); + assert!(matches!(&specifiers[0], ImportSpecifier::Default(n) if n == "foo")); + } + _ => panic!("expected import"), + } + } + + #[test] + fn test_import_named() { + let prog = Parser::parse_module("import { a, b as c } from \"mod\";").unwrap(); + match &prog.body[0].kind { + StmtKind::Import { specifiers, .. } => { + assert_eq!(specifiers.len(), 2); + } + _ => panic!("expected import"), + } + } + + #[test] + fn test_import_namespace() { + let prog = Parser::parse_module("import * as ns from \"mod\";").unwrap(); + match &prog.body[0].kind { + StmtKind::Import { specifiers, .. } => { + assert!(matches!(&specifiers[0], ImportSpecifier::Namespace(n) if n == "ns")); + } + _ => panic!("expected import"), + } + } + + #[test] + fn test_export_default() { + let prog = Parser::parse_module("export default 42;").unwrap(); + match &prog.body[0].kind { + StmtKind::Export(ExportDecl::Default(expr)) => { + assert_eq!(expr.kind, ExprKind::Number(42.0)); + } + _ => panic!("expected export default"), + } + } + + #[test] + fn test_export_named() { + let prog = Parser::parse_module("export { a, b as c };").unwrap(); + match &prog.body[0].kind { + StmtKind::Export(ExportDecl::Named { specifiers, .. }) => { + assert_eq!(specifiers.len(), 2); + } + _ => panic!("expected export named"), + } + } + + #[test] + fn test_export_declaration() { + let prog = Parser::parse_module("export const x = 1;").unwrap(); + match &prog.body[0].kind { + StmtKind::Export(ExportDecl::Declaration(_)) => {} + _ => panic!("expected export declaration"), + } + } + + // ── ASI (Automatic Semicolon Insertion) ───────────── + + #[test] + fn test_asi_newline() { + let prog = parse("let x = 1\nlet y = 2"); + assert_eq!(prog.body.len(), 2); + } + + #[test] + fn test_asi_before_rbrace() { + let stmt = parse_stmt("function f() { return 1 }"); + match stmt.kind { + StmtKind::FunctionDecl(def) => { + assert_eq!(def.body.len(), 1); + } + _ => panic!("expected function"), + } + } + + // ── Complex programs ──────────────────────────────── + + #[test] + fn test_fibonacci() { + let src = r#" + function fib(n) { + if (n <= 1) return n; + return fib(n - 1) + fib(n - 2); + } + "#; + let prog = parse(src); + assert_eq!(prog.body.len(), 1); + assert!(matches!(prog.body[0].kind, StmtKind::FunctionDecl(_))); + } + + #[test] + fn test_class_with_methods() { + let src = r#" + class Animal { + constructor(name) { + this.name = name; + } + speak() { + return this.name; + } + static create(name) { + return new Animal(name); + } + } + "#; + let prog = parse(src); + assert_eq!(prog.body.len(), 1); + match &prog.body[0].kind { + StmtKind::ClassDecl(def) => { + assert_eq!(def.body.len(), 3); + } + _ => panic!("expected class"), + } + } + + #[test] + fn test_for_of_destructuring() { + let stmt = parse_stmt("for (const [k, v] of map) {}"); + match stmt.kind { + StmtKind::ForOf { left, .. } => match left { + ForInOfLeft::VarDecl { pattern, .. } => { + assert!(matches!(pattern.kind, PatternKind::Array { .. })); + } + _ => panic!("expected var decl"), + }, + _ => panic!("expected for-of"), + } + } + + #[test] + fn test_multiple_statements() { + let src = "let x = 1; let y = 2; x + y;"; + let prog = parse(src); + assert_eq!(prog.body.len(), 3); + } + + // ── Error cases ───────────────────────────────────── + + #[test] + fn test_error_unexpected_token() { + assert!(Parser::parse(")").is_err()); + } + + #[test] + fn test_error_unterminated_block() { + assert!(Parser::parse("{ let x = 1;").is_err()); + } + + #[test] + fn test_error_missing_paren() { + assert!(Parser::parse("if x {}").is_err()); + } +}