Skip to main content

nodejs/
parser.rs

1//! JavaScript parser: token stream → AST.
2//!
3//! Recursive descent with precedence climbing for binary operators. Automatic
4//! Semicolon Insertion is applied at statement boundaries using the
5//! `newline_before` flag the lexer records on every token. Arrow functions are
6//! detected at assignment level by looking ahead for `=>` after a parameter
7//! list. Template-literal `${...}` fields are re-parsed here from the raw source
8//! the lexer captured.
9
10use crate::ast::*;
11use crate::lexer::{lex, Tok, Token};
12
13const KEYWORDS: &[&str] = &[
14    "var", "let", "const", "function", "return", "if", "else", "while", "do", "for", "of", "in",
15    "switch", "case", "default", "break", "continue", "true", "false", "null", "this", "new",
16    "typeof", "void", "delete", "instanceof", "throw", "try", "catch", "finally",
17];
18
19fn is_keyword(s: &str) -> bool {
20    KEYWORDS.contains(&s)
21}
22
23struct Parser {
24    toks: Vec<Token>,
25    pos: usize,
26}
27
28/// Parse a complete JS program into a statement list.
29pub fn parse(src: &str) -> Result<Vec<Stmt>, String> {
30    let toks = lex(src)?;
31    let mut p = Parser { toks, pos: 0 };
32    let mut out = Vec::new();
33    while !p.at_eof() {
34        out.push(p.parse_stmt()?);
35    }
36    Ok(out)
37}
38
39impl Parser {
40    // ── token helpers ────────────────────────────────────────────────────
41    fn cur(&self) -> &Token {
42        &self.toks[self.pos]
43    }
44    fn tok(&self) -> &Tok {
45        &self.toks[self.pos].tok
46    }
47    fn line(&self) -> u32 {
48        self.toks[self.pos].line
49    }
50    fn at_eof(&self) -> bool {
51        matches!(self.tok(), Tok::Eof)
52    }
53    fn newline_before(&self) -> bool {
54        self.cur().newline_before
55    }
56    fn advance(&mut self) -> Tok {
57        let t = self.toks[self.pos].tok.clone();
58        if self.pos + 1 < self.toks.len() {
59            self.pos += 1;
60        }
61        t
62    }
63
64    /// True if the current token is the punctuation `s`.
65    fn is_punct(&self, s: &str) -> bool {
66        matches!(self.tok(), Tok::Punct(p) if p == s)
67    }
68    /// True if the current token is the identifier/keyword `s`.
69    fn is_kw(&self, s: &str) -> bool {
70        matches!(self.tok(), Tok::Ident(i) if i == s)
71    }
72    /// Consume the punctuation `s` if present.
73    fn eat_punct(&mut self, s: &str) -> bool {
74        if self.is_punct(s) {
75            self.advance();
76            true
77        } else {
78            false
79        }
80    }
81    fn eat_kw(&mut self, s: &str) -> bool {
82        if self.is_kw(s) {
83            self.advance();
84            true
85        } else {
86            false
87        }
88    }
89    fn expect_punct(&mut self, s: &str) -> Result<(), String> {
90        if self.eat_punct(s) {
91            Ok(())
92        } else {
93            Err(format!(
94                "SyntaxError: expected '{s}' but found {:?} (line {})",
95                self.tok(),
96                self.line()
97            ))
98        }
99    }
100
101    /// Consume an identifier name (any non-punct ident, including keywords used
102    /// as property names when `allow_kw`).
103    fn ident_name(&mut self) -> Result<String, String> {
104        match self.tok().clone() {
105            Tok::Ident(s) => {
106                self.advance();
107                Ok(s)
108            }
109            other => Err(format!(
110                "SyntaxError: expected identifier but found {other:?} (line {})",
111                self.line()
112            )),
113        }
114    }
115
116    /// Apply ASI: consume an explicit `;`, or accept a newline / `}` / EOF.
117    fn semicolon(&mut self) -> Result<(), String> {
118        if self.eat_punct(";") {
119            return Ok(());
120        }
121        if self.newline_before() || self.is_punct("}") || self.at_eof() {
122            return Ok(());
123        }
124        Err(format!(
125            "SyntaxError: expected ';' but found {:?} (line {})",
126            self.tok(),
127            self.line()
128        ))
129    }
130
131    // ── statements ───────────────────────────────────────────────────────
132    fn parse_stmt(&mut self) -> Result<Stmt, String> {
133        let line = self.line();
134        let kind = match self.tok().clone() {
135            Tok::Punct(p) if p == "{" => {
136                self.advance();
137                StmtKind::Block(self.parse_block_body()?)
138            }
139            Tok::Punct(p) if p == ";" => {
140                self.advance();
141                StmtKind::Empty
142            }
143            Tok::Ident(kw) if kw == "var" || kw == "let" || kw == "const" => {
144                let k = self.parse_decl_kind();
145                let decls = self.parse_declarators()?;
146                self.semicolon()?;
147                StmtKind::Decl { kind: k, decls }
148            }
149            Tok::Ident(kw) if kw == "function" => {
150                self.advance();
151                let name = self.ident_name()?;
152                let params = self.parse_params()?;
153                self.expect_punct("{")?;
154                let body = self.parse_block_body()?;
155                StmtKind::FuncDecl { name, params, body }
156            }
157            Tok::Ident(kw) if kw == "if" => self.parse_if()?,
158            Tok::Ident(kw) if kw == "while" => self.parse_while()?,
159            Tok::Ident(kw) if kw == "do" => self.parse_do_while()?,
160            Tok::Ident(kw) if kw == "for" => self.parse_for()?,
161            Tok::Ident(kw) if kw == "switch" => self.parse_switch()?,
162            Tok::Ident(kw) if kw == "return" => {
163                self.advance();
164                let arg = if self.is_punct(";") || self.is_punct("}") || self.newline_before() || self.at_eof() {
165                    None
166                } else {
167                    Some(self.parse_expr()?)
168                };
169                self.semicolon()?;
170                StmtKind::Return(arg)
171            }
172            Tok::Ident(kw) if kw == "break" => {
173                self.advance();
174                let label = self.opt_label();
175                self.semicolon()?;
176                StmtKind::Break(label)
177            }
178            Tok::Ident(kw) if kw == "continue" => {
179                self.advance();
180                let label = self.opt_label();
181                self.semicolon()?;
182                StmtKind::Continue(label)
183            }
184            Tok::Ident(kw) if kw == "throw" => {
185                self.advance();
186                let e = self.parse_expr()?;
187                self.semicolon()?;
188                StmtKind::Throw(e)
189            }
190            Tok::Ident(kw) if kw == "try" => self.parse_try()?,
191            _ => {
192                let e = self.parse_expr()?;
193                self.semicolon()?;
194                StmtKind::Expr(e)
195            }
196        };
197        Ok(Stmt::new(kind, line))
198    }
199
200    /// An optional non-newline label after break/continue.
201    fn opt_label(&mut self) -> Option<String> {
202        if self.newline_before() {
203            return None;
204        }
205        if let Tok::Ident(s) = self.tok() {
206            if !is_keyword(s) {
207                let s = s.clone();
208                self.advance();
209                return Some(s);
210            }
211        }
212        None
213    }
214
215    /// Parse statements up to (and consuming) the closing `}`.
216    fn parse_block_body(&mut self) -> Result<Vec<Stmt>, String> {
217        let mut out = Vec::new();
218        while !self.is_punct("}") && !self.at_eof() {
219            out.push(self.parse_stmt()?);
220        }
221        self.expect_punct("}")?;
222        Ok(out)
223    }
224
225    fn parse_decl_kind(&mut self) -> DeclKind {
226        let k = match self.tok() {
227            Tok::Ident(s) if s == "let" => DeclKind::Let,
228            Tok::Ident(s) if s == "const" => DeclKind::Const,
229            _ => DeclKind::Var,
230        };
231        self.advance();
232        k
233    }
234
235    fn parse_declarators(&mut self) -> Result<Vec<Declarator>, String> {
236        let mut decls = Vec::new();
237        loop {
238            let target = self.parse_binding_target()?;
239            let init = if self.eat_punct("=") {
240                Some(self.parse_assign()?)
241            } else {
242                None
243            };
244            decls.push(Declarator { target, init });
245            if !self.eat_punct(",") {
246                break;
247            }
248        }
249        Ok(decls)
250    }
251
252    /// A binding target: identifier or array/object destructuring pattern.
253    fn parse_binding_target(&mut self) -> Result<Expr, String> {
254        if self.is_punct("[") {
255            self.parse_array_literal()
256        } else if self.is_punct("{") {
257            self.parse_object_literal()
258        } else {
259            Ok(Expr::Ident(self.ident_name()?))
260        }
261    }
262
263    fn parse_if(&mut self) -> Result<StmtKind, String> {
264        self.advance(); // if
265        self.expect_punct("(")?;
266        let test = self.parse_expr()?;
267        self.expect_punct(")")?;
268        let cons = Box::new(self.parse_stmt()?);
269        let alt = if self.eat_kw("else") {
270            Some(Box::new(self.parse_stmt()?))
271        } else {
272            None
273        };
274        Ok(StmtKind::If { test, cons, alt })
275    }
276
277    fn parse_while(&mut self) -> Result<StmtKind, String> {
278        self.advance();
279        self.expect_punct("(")?;
280        let test = self.parse_expr()?;
281        self.expect_punct(")")?;
282        let body = Box::new(self.parse_stmt()?);
283        Ok(StmtKind::While { test, body })
284    }
285
286    fn parse_do_while(&mut self) -> Result<StmtKind, String> {
287        self.advance();
288        let body = Box::new(self.parse_stmt()?);
289        if !self.eat_kw("while") {
290            return Err(format!("SyntaxError: expected 'while' (line {})", self.line()));
291        }
292        self.expect_punct("(")?;
293        let test = self.parse_expr()?;
294        self.expect_punct(")")?;
295        self.semicolon()?;
296        Ok(StmtKind::DoWhile { body, test })
297    }
298
299    fn parse_for(&mut self) -> Result<StmtKind, String> {
300        self.advance();
301        self.expect_punct("(")?;
302        // Optional declaration or expression init.
303        let decl_kind = match self.tok() {
304            Tok::Ident(s) if s == "var" || s == "let" || s == "const" => Some(self.parse_decl_kind()),
305            _ => None,
306        };
307        // Empty init: `for (;;)`.
308        if decl_kind.is_none() && self.is_punct(";") {
309            return self.parse_c_for(None);
310        }
311        // Parse the first binding/expression, then decide of/in vs C-style.
312        let first_target = if decl_kind.is_some() {
313            self.parse_binding_target()?
314        } else {
315            self.parse_expr_no_in()?
316        };
317        if self.eat_kw("of") {
318            let iter = self.parse_assign()?;
319            self.expect_punct(")")?;
320            let body = Box::new(self.parse_stmt()?);
321            return Ok(StmtKind::ForOf {
322                decl_kind,
323                target: first_target,
324                iter,
325                body,
326            });
327        }
328        if self.eat_kw("in") {
329            let object = self.parse_assign()?;
330            self.expect_punct(")")?;
331            let body = Box::new(self.parse_stmt()?);
332            return Ok(StmtKind::ForIn {
333                decl_kind,
334                target: first_target,
335                object,
336                body,
337            });
338        }
339        // C-style: reconstruct the init statement.
340        let init_stmt = if let Some(k) = decl_kind {
341            let init = if self.eat_punct("=") {
342                Some(self.parse_assign()?)
343            } else {
344                None
345            };
346            let mut decls = vec![Declarator { target: first_target, init }];
347            while self.eat_punct(",") {
348                let target = self.parse_binding_target()?;
349                let init = if self.eat_punct("=") {
350                    Some(self.parse_assign()?)
351                } else {
352                    None
353                };
354                decls.push(Declarator { target, init });
355            }
356            StmtKind::Decl { kind: k, decls }
357        } else {
358            StmtKind::Expr(first_target)
359        };
360        self.parse_c_for(Some(Stmt::from(init_stmt)))
361    }
362
363    fn parse_c_for(&mut self, init: Option<Stmt>) -> Result<StmtKind, String> {
364        self.expect_punct(";")?;
365        let test = if self.is_punct(";") {
366            None
367        } else {
368            Some(self.parse_expr()?)
369        };
370        self.expect_punct(";")?;
371        let update = if self.is_punct(")") {
372            None
373        } else {
374            Some(self.parse_expr()?)
375        };
376        self.expect_punct(")")?;
377        let body = Box::new(self.parse_stmt()?);
378        Ok(StmtKind::For {
379            init: init.map(Box::new),
380            test,
381            update,
382            body,
383        })
384    }
385
386    fn parse_switch(&mut self) -> Result<StmtKind, String> {
387        self.advance();
388        self.expect_punct("(")?;
389        let disc = self.parse_expr()?;
390        self.expect_punct(")")?;
391        self.expect_punct("{")?;
392        let mut cases = Vec::new();
393        while !self.is_punct("}") && !self.at_eof() {
394            let test = if self.eat_kw("case") {
395                let e = self.parse_expr()?;
396                Some(e)
397            } else if self.eat_kw("default") {
398                None
399            } else {
400                return Err(format!(
401                    "SyntaxError: expected 'case' or 'default' (line {})",
402                    self.line()
403                ));
404            };
405            self.expect_punct(":")?;
406            let mut body = Vec::new();
407            while !self.is_punct("}") && !self.is_kw("case") && !self.is_kw("default") && !self.at_eof() {
408                body.push(self.parse_stmt()?);
409            }
410            cases.push(SwitchCase { test, body });
411        }
412        self.expect_punct("}")?;
413        Ok(StmtKind::Switch { disc, cases })
414    }
415
416    fn parse_try(&mut self) -> Result<StmtKind, String> {
417        self.advance();
418        self.expect_punct("{")?;
419        let block = self.parse_block_body()?;
420        let handler = if self.eat_kw("catch") {
421            let param = if self.eat_punct("(") {
422                let p = self.parse_binding_target()?;
423                self.expect_punct(")")?;
424                Some(p)
425            } else {
426                None
427            };
428            self.expect_punct("{")?;
429            let body = self.parse_block_body()?;
430            Some((param, body))
431        } else {
432            None
433        };
434        let finalizer = if self.eat_kw("finally") {
435            self.expect_punct("{")?;
436            Some(self.parse_block_body()?)
437        } else {
438            None
439        };
440        Ok(StmtKind::Try {
441            block,
442            handler,
443            finalizer,
444        })
445    }
446
447    // ── expressions ──────────────────────────────────────────────────────
448    /// Full expression, including the comma sequence operator.
449    fn parse_expr(&mut self) -> Result<Expr, String> {
450        let first = self.parse_assign()?;
451        if self.is_punct(",") {
452            let mut items = vec![first];
453            while self.eat_punct(",") {
454                items.push(self.parse_assign()?);
455            }
456            Ok(Expr::Sequence(items))
457        } else {
458            Ok(first)
459        }
460    }
461
462    /// Like `parse_expr` but stops before `in` (used in `for` init position).
463    fn parse_expr_no_in(&mut self) -> Result<Expr, String> {
464        // For simplicity the no-in variant only parses an assignment/LHS chain,
465        // which is sufficient for `for (x in ...)` / `for (x of ...)` heads.
466        self.parse_assign()
467    }
468
469    fn parse_assign(&mut self) -> Result<Expr, String> {
470        // Arrow function detection.
471        if let Some(arrow) = self.try_parse_arrow()? {
472            return Ok(arrow);
473        }
474        let left = self.parse_conditional()?;
475        // Assignment operators (right-associative).
476        let op = match self.tok() {
477            Tok::Punct(p) => p.clone(),
478            _ => return Ok(left),
479        };
480        let compound = match op.as_str() {
481            "=" => None,
482            "+=" => Some(BinOp::Add),
483            "-=" => Some(BinOp::Sub),
484            "*=" => Some(BinOp::Mul),
485            "/=" => Some(BinOp::Div),
486            "%=" => Some(BinOp::Mod),
487            "**=" => Some(BinOp::Pow),
488            "&=" => Some(BinOp::BitAnd),
489            "|=" => Some(BinOp::BitOr),
490            "^=" => Some(BinOp::BitXor),
491            "<<=" => Some(BinOp::Shl),
492            ">>=" => Some(BinOp::Shr),
493            ">>>=" => Some(BinOp::UShr),
494            "&&=" | "||=" | "??=" => {
495                // Logical assignment.
496                self.advance();
497                let value = self.parse_assign()?;
498                let lop = match op.as_str() {
499                    "&&=" => LogicalOp::And,
500                    "||=" => LogicalOp::Or,
501                    _ => LogicalOp::Nullish,
502                };
503                return Ok(Expr::Assign {
504                    target: Box::new(left.clone()),
505                    value: Box::new(Expr::Logical(lop, Box::new(left), Box::new(value))),
506                });
507            }
508            _ => return Ok(left),
509        };
510        self.advance();
511        let value = self.parse_assign()?;
512        let value = match compound {
513            None => value,
514            Some(b) => Expr::Binary(b, Box::new(left.clone()), Box::new(value)),
515        };
516        Ok(Expr::Assign {
517            target: Box::new(left),
518            value: Box::new(value),
519        })
520    }
521
522    fn parse_conditional(&mut self) -> Result<Expr, String> {
523        let test = self.parse_binary(0)?;
524        if self.eat_punct("?") {
525            let cons = self.parse_assign()?;
526            self.expect_punct(":")?;
527            let alt = self.parse_assign()?;
528            Ok(Expr::Conditional {
529                test: Box::new(test),
530                cons: Box::new(cons),
531                alt: Box::new(alt),
532            })
533        } else {
534            Ok(test)
535        }
536    }
537
538    /// Precedence-climbing binary parser. Handles `&& || ??` as logical nodes.
539    fn parse_binary(&mut self, min_prec: u8) -> Result<Expr, String> {
540        let mut left = self.parse_unary()?;
541        while let Some((prec, right_assoc, logical, bin)) = self.bin_info() {
542            if prec < min_prec {
543                break;
544            }
545            self.advance();
546            let next_min = if right_assoc { prec } else { prec + 1 };
547            let right = self.parse_binary(next_min)?;
548            left = if let Some(lop) = logical {
549                Expr::Logical(lop, Box::new(left), Box::new(right))
550            } else {
551                Expr::Binary(bin.unwrap(), Box::new(left), Box::new(right))
552            };
553        }
554        Ok(left)
555    }
556
557    /// `(precedence, right_assoc, logical_op, bin_op)` for the current token.
558    fn bin_info(&self) -> Option<(u8, bool, Option<LogicalOp>, Option<BinOp>)> {
559        let p = match self.tok() {
560            Tok::Punct(p) => p.as_str(),
561            Tok::Ident(s) if s == "in" => "in",
562            Tok::Ident(s) if s == "instanceof" => "instanceof",
563            _ => return None,
564        };
565        let (prec, ra, log, bin) = match p {
566            "??" => (1, false, Some(LogicalOp::Nullish), None),
567            "||" => (2, false, Some(LogicalOp::Or), None),
568            "&&" => (3, false, Some(LogicalOp::And), None),
569            "|" => (4, false, None, Some(BinOp::BitOr)),
570            "^" => (5, false, None, Some(BinOp::BitXor)),
571            "&" => (6, false, None, Some(BinOp::BitAnd)),
572            "==" => (7, false, None, Some(BinOp::EqEq)),
573            "!=" => (7, false, None, Some(BinOp::NeEq)),
574            "===" => (7, false, None, Some(BinOp::EqEqEq)),
575            "!==" => (7, false, None, Some(BinOp::NeEqEq)),
576            "<" => (8, false, None, Some(BinOp::Lt)),
577            "<=" => (8, false, None, Some(BinOp::Le)),
578            ">" => (8, false, None, Some(BinOp::Gt)),
579            ">=" => (8, false, None, Some(BinOp::Ge)),
580            "in" => (8, false, None, Some(BinOp::In)),
581            "instanceof" => (8, false, None, Some(BinOp::InstanceOf)),
582            "<<" => (9, false, None, Some(BinOp::Shl)),
583            ">>" => (9, false, None, Some(BinOp::Shr)),
584            ">>>" => (9, false, None, Some(BinOp::UShr)),
585            "+" => (10, false, None, Some(BinOp::Add)),
586            "-" => (10, false, None, Some(BinOp::Sub)),
587            "*" => (11, false, None, Some(BinOp::Mul)),
588            "/" => (11, false, None, Some(BinOp::Div)),
589            "%" => (11, false, None, Some(BinOp::Mod)),
590            "**" => (12, true, None, Some(BinOp::Pow)),
591            _ => return None,
592        };
593        Some((prec, ra, log, bin))
594    }
595
596    fn parse_unary(&mut self) -> Result<Expr, String> {
597        let op = match self.tok() {
598            Tok::Punct(p) if p == "!" => Some(UnOp::Not),
599            Tok::Punct(p) if p == "~" => Some(UnOp::BitNot),
600            Tok::Punct(p) if p == "+" => Some(UnOp::Pos),
601            Tok::Punct(p) if p == "-" => Some(UnOp::Neg),
602            Tok::Ident(s) if s == "typeof" => Some(UnOp::TypeOf),
603            Tok::Ident(s) if s == "void" => Some(UnOp::Void),
604            Tok::Ident(s) if s == "delete" => Some(UnOp::Delete),
605            _ => None,
606        };
607        if let Some(op) = op {
608            self.advance();
609            let e = self.parse_unary()?;
610            return Ok(Expr::Unary(op, Box::new(e)));
611        }
612        // Prefix ++/--.
613        if self.is_punct("++") || self.is_punct("--") {
614            let op = if self.is_punct("++") { UpdateOp::Inc } else { UpdateOp::Dec };
615            self.advance();
616            let e = self.parse_unary()?;
617            return Ok(Expr::Update {
618                op,
619                prefix: true,
620                target: Box::new(e),
621            });
622        }
623        self.parse_postfix()
624    }
625
626    fn parse_postfix(&mut self) -> Result<Expr, String> {
627        let mut e = self.parse_call_member()?;
628        // Postfix ++/-- (no line break before).
629        if (self.is_punct("++") || self.is_punct("--")) && !self.newline_before() {
630            let op = if self.is_punct("++") { UpdateOp::Inc } else { UpdateOp::Dec };
631            self.advance();
632            e = Expr::Update {
633                op,
634                prefix: false,
635                target: Box::new(e),
636            };
637        }
638        Ok(e)
639    }
640
641    fn parse_call_member(&mut self) -> Result<Expr, String> {
642        let mut e = if self.eat_kw("new") {
643            let callee = self.parse_call_member_no_call()?;
644            let args = if self.is_punct("(") {
645                self.parse_args()?
646            } else {
647                Vec::new()
648            };
649            Expr::New {
650                callee: Box::new(callee),
651                args,
652            }
653        } else {
654            self.parse_primary()?
655        };
656        loop {
657            if self.eat_punct(".") {
658                let property = self.ident_name()?;
659                e = Expr::Member {
660                    object: Box::new(e),
661                    property,
662                    optional: false,
663                };
664            } else if self.eat_punct("?.") {
665                if self.is_punct("(") {
666                    let args = self.parse_args()?;
667                    e = Expr::Call {
668                        func: Box::new(e),
669                        args,
670                        optional: true,
671                    };
672                } else if self.is_punct("[") {
673                    self.advance();
674                    let index = self.parse_expr()?;
675                    self.expect_punct("]")?;
676                    e = Expr::Index {
677                        object: Box::new(e),
678                        index: Box::new(index),
679                        optional: true,
680                    };
681                } else {
682                    let property = self.ident_name()?;
683                    e = Expr::Member {
684                        object: Box::new(e),
685                        property,
686                        optional: true,
687                    };
688                }
689            } else if self.is_punct("[") {
690                self.advance();
691                let index = self.parse_expr()?;
692                self.expect_punct("]")?;
693                e = Expr::Index {
694                    object: Box::new(e),
695                    index: Box::new(index),
696                    optional: false,
697                };
698            } else if self.is_punct("(") {
699                let args = self.parse_args()?;
700                e = Expr::Call {
701                    func: Box::new(e),
702                    args,
703                    optional: false,
704                };
705            } else {
706                break;
707            }
708        }
709        Ok(e)
710    }
711
712    /// Member chain without a trailing call — the `new X.Y` callee grammar.
713    fn parse_call_member_no_call(&mut self) -> Result<Expr, String> {
714        let mut e = self.parse_primary()?;
715        loop {
716            if self.eat_punct(".") {
717                let property = self.ident_name()?;
718                e = Expr::Member {
719                    object: Box::new(e),
720                    property,
721                    optional: false,
722                };
723            } else if self.is_punct("[") {
724                self.advance();
725                let index = self.parse_expr()?;
726                self.expect_punct("]")?;
727                e = Expr::Index {
728                    object: Box::new(e),
729                    index: Box::new(index),
730                    optional: false,
731                };
732            } else {
733                break;
734            }
735        }
736        Ok(e)
737    }
738
739    fn parse_args(&mut self) -> Result<Vec<Expr>, String> {
740        self.expect_punct("(")?;
741        let mut args = Vec::new();
742        while !self.is_punct(")") {
743            if self.eat_punct("...") {
744                let e = self.parse_assign()?;
745                args.push(Expr::Spread(Box::new(e)));
746            } else {
747                args.push(self.parse_assign()?);
748            }
749            if !self.eat_punct(",") {
750                break;
751            }
752        }
753        self.expect_punct(")")?;
754        Ok(args)
755    }
756
757    fn parse_primary(&mut self) -> Result<Expr, String> {
758        match self.tok().clone() {
759            Tok::Num(n) => {
760                self.advance();
761                Ok(Expr::Number(n))
762            }
763            Tok::Str(s) => {
764                self.advance();
765                Ok(Expr::Str(s))
766            }
767            Tok::Template { quasis, exprs } => {
768                self.advance();
769                let mut parsed = Vec::new();
770                for src in &exprs {
771                    parsed.push(parse_expr_source(src)?);
772                }
773                Ok(Expr::Template {
774                    quasis,
775                    exprs: parsed,
776                })
777            }
778            Tok::Punct(p) if p == "(" => {
779                self.advance();
780                let e = self.parse_expr()?;
781                self.expect_punct(")")?;
782                Ok(e)
783            }
784            Tok::Punct(p) if p == "[" => self.parse_array_literal(),
785            Tok::Punct(p) if p == "{" => self.parse_object_literal(),
786            Tok::Ident(s) => {
787                match s.as_str() {
788                    "true" => {
789                        self.advance();
790                        Ok(Expr::True)
791                    }
792                    "false" => {
793                        self.advance();
794                        Ok(Expr::False)
795                    }
796                    "null" => {
797                        self.advance();
798                        Ok(Expr::Null)
799                    }
800                    "this" => {
801                        self.advance();
802                        Ok(Expr::This)
803                    }
804                    "function" => {
805                        self.advance();
806                        let name = if let Tok::Ident(n) = self.tok() {
807                            if !is_keyword(n) {
808                                let n = n.clone();
809                                self.advance();
810                                Some(n)
811                            } else {
812                                None
813                            }
814                        } else {
815                            None
816                        };
817                        let params = self.parse_params()?;
818                        self.expect_punct("{")?;
819                        let body = self.parse_block_body()?;
820                        Ok(Expr::Function {
821                            params,
822                            body: FnBody::Block(body),
823                            is_arrow: false,
824                            name,
825                        })
826                    }
827                    _ if is_keyword(&s) => Err(format!(
828                        "SyntaxError: unexpected keyword '{s}' (line {})",
829                        self.line()
830                    )),
831                    _ => {
832                        self.advance();
833                        Ok(Expr::Ident(s))
834                    }
835                }
836            }
837            other => Err(format!(
838                "SyntaxError: unexpected token {other:?} (line {})",
839                self.line()
840            )),
841        }
842    }
843
844    fn parse_array_literal(&mut self) -> Result<Expr, String> {
845        self.expect_punct("[")?;
846        let mut items = Vec::new();
847        while !self.is_punct("]") {
848            if self.is_punct(",") {
849                // Elision (hole) — represent as undefined.
850                items.push(Expr::Undefined);
851                self.advance();
852                continue;
853            }
854            if self.eat_punct("...") {
855                let e = self.parse_assign()?;
856                items.push(Expr::Spread(Box::new(e)));
857            } else {
858                items.push(self.parse_assign()?);
859            }
860            if !self.eat_punct(",") {
861                break;
862            }
863        }
864        self.expect_punct("]")?;
865        Ok(Expr::Array(items))
866    }
867
868    fn parse_object_literal(&mut self) -> Result<Expr, String> {
869        self.expect_punct("{")?;
870        let mut props = Vec::new();
871        while !self.is_punct("}") {
872            if self.eat_punct("...") {
873                let e = self.parse_assign()?;
874                props.push(Prop::Spread(e));
875                if !self.eat_punct(",") {
876                    break;
877                }
878                continue;
879            }
880            // Computed key `[expr]`.
881            let (key, computed) = if self.is_punct("[") {
882                self.advance();
883                let k = self.parse_assign()?;
884                self.expect_punct("]")?;
885                (k, true)
886            } else {
887                match self.tok().clone() {
888                    Tok::Str(s) => {
889                        self.advance();
890                        (Expr::Str(s), false)
891                    }
892                    Tok::Num(n) => {
893                        self.advance();
894                        (Expr::Str(crate::host::fmt_number(n)), false)
895                    }
896                    Tok::Ident(s) => {
897                        self.advance();
898                        (Expr::Str(s), false)
899                    }
900                    other => {
901                        return Err(format!(
902                            "SyntaxError: bad object key {other:?} (line {})",
903                            self.line()
904                        ))
905                    }
906                }
907            };
908            // Method shorthand `key(params) { }`.
909            if self.is_punct("(") {
910                let params = self.parse_params()?;
911                self.expect_punct("{")?;
912                let body = self.parse_block_body()?;
913                let f = Expr::Function {
914                    params,
915                    body: FnBody::Block(body),
916                    is_arrow: false,
917                    name: None,
918                };
919                props.push(Prop::KeyValue {
920                    key,
921                    value: f,
922                    computed,
923                });
924            } else if self.eat_punct(":") {
925                let value = self.parse_assign()?;
926                props.push(Prop::KeyValue {
927                    key,
928                    value,
929                    computed,
930                });
931            } else {
932                // Shorthand `{ x }` -> key "x", value ident x. Or with default
933                // in a destructuring pattern: `{ x = 1 }`.
934                let name = match &key {
935                    Expr::Str(s) => s.clone(),
936                    _ => return Err(format!("SyntaxError: bad shorthand (line {})", self.line())),
937                };
938                let value = if self.eat_punct("=") {
939                    // Pattern default; represent as Assign so destructuring reads it.
940                    let d = self.parse_assign()?;
941                    Expr::Assign {
942                        target: Box::new(Expr::Ident(name.clone())),
943                        value: Box::new(d),
944                    }
945                } else {
946                    Expr::Ident(name)
947                };
948                props.push(Prop::KeyValue {
949                    key,
950                    value,
951                    computed,
952                });
953            }
954            if !self.eat_punct(",") {
955                break;
956            }
957        }
958        self.expect_punct("}")?;
959        Ok(Expr::Object(props))
960    }
961
962    // ── functions / arrows ───────────────────────────────────────────────
963    fn parse_params(&mut self) -> Result<Vec<Param>, String> {
964        self.expect_punct("(")?;
965        let mut params = Vec::new();
966        while !self.is_punct(")") {
967            let rest = self.eat_punct("...");
968            let pattern = self.parse_binding_target()?;
969            let default = if !rest && self.eat_punct("=") {
970                Some(self.parse_assign()?)
971            } else {
972                None
973            };
974            params.push(Param {
975                pattern,
976                default,
977                rest,
978            });
979            if !self.eat_punct(",") {
980                break;
981            }
982        }
983        self.expect_punct(")")?;
984        Ok(params)
985    }
986
987    /// Try to parse an arrow function starting at the current position. Returns
988    /// `None` (without consuming) if the head is not an arrow.
989    fn try_parse_arrow(&mut self) -> Result<Option<Expr>, String> {
990        // `ident => ...`
991        if let Tok::Ident(name) = self.tok() {
992            if !is_keyword(name) && self.peek_is_arrow_after(1) {
993                let name = name.clone();
994                self.advance(); // ident
995                self.advance(); // =>
996                let body = self.parse_arrow_body()?;
997                return Ok(Some(Expr::Function {
998                    params: vec![Param {
999                        pattern: Expr::Ident(name),
1000                        default: None,
1001                        rest: false,
1002                    }],
1003                    body,
1004                    is_arrow: true,
1005                    name: None,
1006                }));
1007            }
1008        }
1009        // `( ... ) => ...`
1010        if self.is_punct("(") {
1011            if let Some(close) = self.matching_paren(self.pos) {
1012                let after = close + 1;
1013                if matches!(self.toks.get(after).map(|t| &t.tok), Some(Tok::Punct(p)) if p == "=>") {
1014                    let params = self.parse_params()?;
1015                    self.expect_punct("=>")?;
1016                    let body = self.parse_arrow_body()?;
1017                    return Ok(Some(Expr::Function {
1018                        params,
1019                        body,
1020                        is_arrow: true,
1021                        name: None,
1022                    }));
1023                }
1024            }
1025        }
1026        Ok(None)
1027    }
1028
1029    fn parse_arrow_body(&mut self) -> Result<FnBody, String> {
1030        if self.is_punct("{") {
1031            self.advance();
1032            Ok(FnBody::Block(self.parse_block_body()?))
1033        } else {
1034            Ok(FnBody::Expr(Box::new(self.parse_assign()?)))
1035        }
1036    }
1037
1038    /// Whether the token `n` positions ahead is `=>`.
1039    fn peek_is_arrow_after(&self, n: usize) -> bool {
1040        matches!(self.toks.get(self.pos + n).map(|t| &t.tok), Some(Tok::Punct(p)) if p == "=>")
1041    }
1042
1043    /// Index of the `)` matching the `(` at `open`, skipping nested brackets.
1044    fn matching_paren(&self, open: usize) -> Option<usize> {
1045        let mut depth = 0i32;
1046        let mut i = open;
1047        while i < self.toks.len() {
1048            match &self.toks[i].tok {
1049                Tok::Punct(p) if p == "(" || p == "[" || p == "{" => depth += 1,
1050                Tok::Punct(p) if p == ")" || p == "]" || p == "}" => {
1051                    depth -= 1;
1052                    if depth == 0 {
1053                        return Some(i);
1054                    }
1055                }
1056                Tok::Eof => return None,
1057                _ => {}
1058            }
1059            i += 1;
1060        }
1061        None
1062    }
1063}
1064
1065/// Parse a template-literal `${...}` field's raw source into an expression.
1066fn parse_expr_source(src: &str) -> Result<Expr, String> {
1067    let toks = lex(src)?;
1068    let mut p = Parser { toks, pos: 0 };
1069    let e = p.parse_expr()?;
1070    Ok(e)
1071}