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",
15    "let",
16    "const",
17    "function",
18    "return",
19    "if",
20    "else",
21    "while",
22    "do",
23    "for",
24    "of",
25    "in",
26    "switch",
27    "case",
28    "default",
29    "break",
30    "continue",
31    "true",
32    "false",
33    "null",
34    "this",
35    "new",
36    "typeof",
37    "void",
38    "delete",
39    "instanceof",
40    "throw",
41    "try",
42    "catch",
43    "finally",
44];
45
46fn is_keyword(s: &str) -> bool {
47    KEYWORDS.contains(&s)
48}
49
50/// The private names one class body declares and the ones its code refers to.
51#[derive(Default)]
52struct PrivateScope {
53    declared: Vec<String>,
54    used: Vec<String>,
55}
56
57fn private_not_declared(name: &str) -> String {
58    format!("SyntaxError: Private field '{name}' must be declared in an enclosing class")
59}
60
61struct Parser {
62    toks: Vec<Token>,
63    pos: usize,
64    /// True while parsing a generator body — enables `yield` as an operator.
65    in_generator: bool,
66    /// True while parsing an async body — enables `await` as an operator.
67    in_async: bool,
68    /// True while parsing a `for` init in LHS position — suppresses `in` as a
69    /// relational operator so `for (x in obj)` (no declaration keyword) parses
70    /// the `in` as the loop separator, not a binary expression. Cleared inside
71    /// any parenthesised/bracketed sub-expression, where `in` is legal again.
72    no_in: bool,
73    /// One frame per enclosing class body, each holding the private names that
74    /// body DECLARES and the ones its code USES. A private name is only in
75    /// scope inside a class that declares it, so an undeclared use is an EARLY
76    /// error (`SyntaxError`) rather than a runtime `TypeError` if the read ever
77    /// happens — a typo in a dead branch used to ship silently.
78    class_scopes: Vec<PrivateScope>,
79}
80
81/// Parse a complete JS program into a statement list. Inline `rust { ... }` FFI
82/// blocks are desugared to `__rust_compile(...)` calls before lexing.
83pub fn parse(src: &str) -> Result<Vec<Stmt>, String> {
84    let src = crate::rust_ffi::desugar(src);
85    let toks = lex(&src)?;
86    let mut p = Parser {
87        toks,
88        pos: 0,
89        in_generator: false,
90        in_async: false,
91        no_in: false,
92        class_scopes: Vec::new(),
93    };
94    let mut out = Vec::new();
95    while !p.at_eof() {
96        out.push(p.parse_stmt()?);
97    }
98    Ok(out)
99}
100
101impl Parser {
102    // ── token helpers ────────────────────────────────────────────────────
103    fn cur(&self) -> &Token {
104        &self.toks[self.pos]
105    }
106    fn tok(&self) -> &Tok {
107        &self.toks[self.pos].tok
108    }
109    fn line(&self) -> u32 {
110        self.toks[self.pos].line
111    }
112    fn at_eof(&self) -> bool {
113        matches!(self.tok(), Tok::Eof)
114    }
115    fn newline_before(&self) -> bool {
116        self.cur().newline_before
117    }
118    fn advance(&mut self) -> Tok {
119        let t = self.toks[self.pos].tok.clone();
120        if self.pos + 1 < self.toks.len() {
121            self.pos += 1;
122        }
123        t
124    }
125
126    /// True if the current token is the punctuation `s`.
127    fn is_punct(&self, s: &str) -> bool {
128        matches!(self.tok(), Tok::Punct(p) if p == s)
129    }
130    /// True if the current token is the identifier/keyword `s`.
131    fn is_kw(&self, s: &str) -> bool {
132        matches!(self.tok(), Tok::Ident(i) if i == s)
133    }
134    /// Consume the punctuation `s` if present.
135    fn eat_punct(&mut self, s: &str) -> bool {
136        if self.is_punct(s) {
137            self.advance();
138            true
139        } else {
140            false
141        }
142    }
143    fn eat_kw(&mut self, s: &str) -> bool {
144        if self.is_kw(s) {
145            self.advance();
146            true
147        } else {
148            false
149        }
150    }
151    fn expect_punct(&mut self, s: &str) -> Result<(), String> {
152        if self.eat_punct(s) {
153            Ok(())
154        } else {
155            Err(format!(
156                "SyntaxError: expected '{s}' but found {:?} (line {})",
157                self.tok(),
158                self.line()
159            ))
160        }
161    }
162
163    /// Consume an identifier name (any non-punct ident, including keywords used
164    /// as property names when `allow_kw`).
165    fn ident_name(&mut self) -> Result<String, String> {
166        match self.tok().clone() {
167            Tok::Ident(s) => {
168                self.advance();
169                Ok(s)
170            }
171            other => Err(format!(
172                "SyntaxError: expected identifier but found {other:?} (line {})",
173                self.line()
174            )),
175        }
176    }
177
178    /// Apply ASI: consume an explicit `;`, or accept a newline / `}` / EOF.
179    fn semicolon(&mut self) -> Result<(), String> {
180        if self.eat_punct(";") {
181            return Ok(());
182        }
183        if self.newline_before() || self.is_punct("}") || self.at_eof() {
184            return Ok(());
185        }
186        Err(format!(
187            "SyntaxError: expected ';' but found {:?} (line {})",
188            self.tok(),
189            self.line()
190        ))
191    }
192
193    // ── statements ───────────────────────────────────────────────────────
194    fn parse_stmt(&mut self) -> Result<Stmt, String> {
195        let line = self.line();
196        let kind = match self.tok().clone() {
197            Tok::Punct(p) if p == "{" => {
198                self.advance();
199                StmtKind::Block(self.parse_block_body()?)
200            }
201            Tok::Punct(p) if p == ";" => {
202                self.advance();
203                StmtKind::Empty
204            }
205            Tok::Ident(kw) if kw == "var" || kw == "let" || kw == "const" => {
206                let k = self.parse_decl_kind();
207                let decls = self.parse_declarators()?;
208                self.semicolon()?;
209                StmtKind::Decl { kind: k, decls }
210            }
211            Tok::Ident(kw) if kw == "function" => self.parse_func_decl(false)?,
212            // `async function …` (declaration). `async` stays a plain identifier
213            // anywhere else (contextual keyword).
214            Tok::Ident(kw)
215                if kw == "async" && self.peek_kw(1, "function") && !self.peek_newline(1) =>
216            {
217                self.advance(); // async
218                self.parse_func_decl(true)?
219            }
220            Tok::Ident(kw) if kw == "class" => {
221                let node = self.parse_class(true)?;
222                StmtKind::ClassDecl(node)
223            }
224            Tok::Ident(kw) if kw == "if" => self.parse_if()?,
225            Tok::Ident(kw) if kw == "while" => self.parse_while()?,
226            Tok::Ident(kw) if kw == "do" => self.parse_do_while()?,
227            Tok::Ident(kw) if kw == "for" => self.parse_for()?,
228            Tok::Ident(kw) if kw == "switch" => self.parse_switch()?,
229            Tok::Ident(kw) if kw == "return" => {
230                self.advance();
231                let arg = if self.is_punct(";")
232                    || self.is_punct("}")
233                    || self.newline_before()
234                    || self.at_eof()
235                {
236                    None
237                } else {
238                    Some(self.parse_expr()?)
239                };
240                self.semicolon()?;
241                StmtKind::Return(arg)
242            }
243            Tok::Ident(kw) if kw == "break" => {
244                self.advance();
245                let label = self.opt_label();
246                self.semicolon()?;
247                StmtKind::Break(label)
248            }
249            Tok::Ident(kw) if kw == "continue" => {
250                self.advance();
251                let label = self.opt_label();
252                self.semicolon()?;
253                StmtKind::Continue(label)
254            }
255            Tok::Ident(kw) if kw == "throw" => {
256                self.advance();
257                let e = self.parse_expr()?;
258                self.semicolon()?;
259                StmtKind::Throw(e)
260            }
261            Tok::Ident(kw) if kw == "try" => self.parse_try()?,
262            // `label: stmt` — a bare identifier immediately followed by `:` at
263            // statement position is a label (never an expression; the reserved
264            // control keywords are all matched above, and switch `case`/`default`
265            // labels are parsed inside `parse_switch`).
266            Tok::Ident(name) if matches!(self.toks.get(self.pos + 1).map(|t| &t.tok), Some(Tok::Punct(p)) if p == ":") =>
267            {
268                self.advance(); // the label identifier
269                self.advance(); // the ':'
270                let body = Box::new(self.parse_stmt()?);
271                StmtKind::Labeled { label: name, body }
272            }
273            _ => {
274                let e = self.parse_expr()?;
275                self.semicolon()?;
276                StmtKind::Expr(e)
277            }
278        };
279        Ok(Stmt::new(kind, line))
280    }
281
282    /// Whether the token `n` ahead is the identifier `kw`.
283    fn peek_kw(&self, n: usize, kw: &str) -> bool {
284        matches!(self.toks.get(self.pos + n).map(|t| &t.tok), Some(Tok::Ident(s)) if s == kw)
285    }
286    /// Whether the token `n` ahead has a newline before it.
287    fn peek_newline(&self, n: usize) -> bool {
288        self.toks
289            .get(self.pos + n)
290            .map(|t| t.newline_before)
291            .unwrap_or(false)
292    }
293
294    /// Parse a `function` declaration (the `function`/`async function` keyword is
295    /// current). `is_async` is true when a preceding `async` was consumed.
296    fn parse_func_decl(&mut self, is_async: bool) -> Result<StmtKind, String> {
297        self.advance(); // function
298        let is_generator = self.eat_punct("*");
299        let name = self.ident_name()?;
300        let params = self.parse_params()?;
301        self.expect_punct("{")?;
302        let body = self.parse_fn_body_block(is_generator, is_async)?;
303        Ok(StmtKind::FuncDecl {
304            name,
305            params,
306            body,
307            is_generator,
308            is_async,
309        })
310    }
311
312    /// Parse a brace-delimited function body under the given generator/async
313    /// context (so `yield`/`await` inside are operators, not identifiers).
314    fn parse_fn_body_block(
315        &mut self,
316        is_generator: bool,
317        is_async: bool,
318    ) -> Result<Vec<Stmt>, String> {
319        let (pg, pa) = (self.in_generator, self.in_async);
320        self.in_generator = is_generator;
321        self.in_async = is_async;
322        let body = self.parse_block_body();
323        self.in_generator = pg;
324        self.in_async = pa;
325        body
326    }
327
328    /// Parse a function *expression* (`function`/`async function`, keyword
329    /// current). Supports `function*` generators.
330    fn parse_function_expr(&mut self, is_async: bool) -> Result<Expr, String> {
331        self.advance(); // function
332        let is_generator = self.eat_punct("*");
333        let name = if let Tok::Ident(n) = self.tok() {
334            if !is_keyword(n) {
335                let n = n.clone();
336                self.advance();
337                Some(n)
338            } else {
339                None
340            }
341        } else {
342            None
343        };
344        let params = self.parse_params()?;
345        self.expect_punct("{")?;
346        let body = self.parse_fn_body_block(is_generator, is_async)?;
347        Ok(Expr::Function {
348            params,
349            body: FnBody::Block(body),
350            is_arrow: false,
351            name,
352            is_generator,
353            is_async,
354            is_method: false,
355        })
356    }
357
358    /// Parse a `class` (the `class` keyword is current). `_decl` distinguishes a
359    /// declaration (name required in strict mode, but we accept optional) from an
360    /// expression.
361    /// 15.7.1: a private name must be declared by an enclosing class body.
362    /// Outside one, `this.#x` is a SyntaxError at parse time — it used to parse
363    /// and then throw a `TypeError` only if the read ever ran, so a typo in a
364    /// dead branch shipped silently.
365    fn check_private_in_scope(&mut self, property: &str) -> Result<(), String> {
366        if !property.starts_with('#') {
367            return Ok(());
368        }
369        match self.class_scopes.last_mut() {
370            // Recorded, not resolved: the declaration may still be ahead of the
371            // use in the same body, so the check runs when the body closes.
372            Some(scope) => scope.used.push(property.to_string()),
373            None => return Err(private_not_declared(property)),
374        }
375        Ok(())
376    }
377
378    fn parse_class(&mut self, _decl: bool) -> Result<ClassNode, String> {
379        self.class_scopes.push(PrivateScope::default());
380        let out = self.parse_class_inner();
381        let scope = self.class_scopes.pop().unwrap_or_default();
382        let out = out?;
383        for name in &scope.used {
384            if scope.declared.contains(name) {
385                continue;
386            }
387            // A nested class may reference a name an OUTER class declares, so an
388            // unresolved use is handed outwards rather than rejected here.
389            match self.class_scopes.last_mut() {
390                Some(outer) => outer.used.push(name.clone()),
391                None => return Err(private_not_declared(name)),
392            }
393        }
394        Ok(out)
395    }
396
397    fn parse_class_inner(&mut self) -> Result<ClassNode, String> {
398        self.advance(); // class
399        let name = if let Tok::Ident(n) = self.tok() {
400            if !is_keyword(n) && n != "extends" {
401                let n = n.clone();
402                self.advance();
403                Some(n)
404            } else {
405                None
406            }
407        } else {
408            None
409        };
410        let parent = if self.eat_kw("extends") {
411            // The superclass is a left-hand-side expression (`extends Base`,
412            // `extends foo.Bar`).
413            Some(Box::new(self.parse_call_member()?))
414        } else {
415            None
416        };
417        self.expect_punct("{")?;
418        let mut members = Vec::new();
419        while !self.is_punct("}") && !self.at_eof() {
420            if self.eat_punct(";") {
421                continue; // stray semicolons between members
422            }
423            let member = self.parse_class_member()?;
424            if let Expr::Str(k) = &member.key {
425                if k.starts_with('#') {
426                    if let Some(scope) = self.class_scopes.last_mut() {
427                        scope.declared.push(k.clone());
428                    }
429                }
430            }
431            members.push(member);
432        }
433        self.expect_punct("}")?;
434        Ok(ClassNode {
435            name,
436            parent,
437            members,
438        })
439    }
440
441    /// Parse one class member: `[static] [get|set|async|*] name(params){…}` or a
442    /// `[static] name [= init];` field.
443    fn parse_class_member(&mut self) -> Result<ClassMember, String> {
444        let is_static = self.is_kw("static") && !self.peek_is_member_punct(1) && {
445            self.advance();
446            true
447        };
448        // `static { … }` — a class static initialization block (ES2022). A brace
449        // where a member key would be is unambiguous: no member name can start
450        // with `{`, so this is checked before the key parse (which otherwise
451        // rejects it as `bad member key Punct("{")`).
452        if is_static && self.is_punct("{") {
453            self.advance();
454            // Its own function context: `yield`/`await` are plain identifiers
455            // inside a static block, whatever encloses the class.
456            let body = self.parse_fn_body_block(false, false)?;
457            return Ok(ClassMember {
458                key: Expr::Str(String::new()),
459                computed: false,
460                kind: MemberKind::StaticBlock,
461                is_static: true,
462                is_generator: false,
463                is_async: false,
464                params: Vec::new(),
465                body,
466                field_init: None,
467            });
468        }
469        // Accessor / async / generator prefixes (each contextual: only a prefix
470        // when followed by another member name, not itself the member name).
471        let mut kind = MemberKind::Method;
472        let mut is_async = false;
473        let mut is_generator = false;
474        if self.is_kw("get") && !self.peek_is_member_punct(1) {
475            self.advance();
476            kind = MemberKind::Get;
477        } else if self.is_kw("set") && !self.peek_is_member_punct(1) {
478            self.advance();
479            kind = MemberKind::Set;
480        } else {
481            if self.is_kw("async") && !self.peek_is_member_punct(1) && !self.peek_newline(1) {
482                self.advance();
483                is_async = true;
484            }
485            if self.eat_punct("*") {
486                is_generator = true;
487            }
488        }
489        // The member key (computed `[expr]`, string, number, or identifier).
490        let (key, computed) = self.parse_property_key()?;
491        // A field (no parentheses) vs a method.
492        if kind == MemberKind::Method && !self.is_punct("(") {
493            let field_init = if self.eat_punct("=") {
494                Some(self.parse_assign()?)
495            } else {
496                None
497            };
498            self.semicolon()?;
499            return Ok(ClassMember {
500                key,
501                computed,
502                kind: MemberKind::Field,
503                is_static,
504                is_generator: false,
505                is_async: false,
506                params: Vec::new(),
507                body: Vec::new(),
508                field_init,
509            });
510        }
511        // A method / accessor / constructor.
512        let is_ctor = !is_static
513            && !computed
514            && matches!(&key, Expr::Str(s) if s == "constructor")
515            && kind == MemberKind::Method;
516        let params = self.parse_params()?;
517        self.expect_punct("{")?;
518        let body = self.parse_fn_body_block(is_generator, is_async)?;
519        Ok(ClassMember {
520            key,
521            computed,
522            kind: if is_ctor {
523                MemberKind::Constructor
524            } else {
525                kind
526            },
527            is_static,
528            is_generator,
529            is_async,
530            params,
531            body,
532            field_init: None,
533        })
534    }
535
536    /// Whether the token `n` ahead is `(`, `=`, `;`, `}`, or a newline-boundary —
537    /// i.e. the current word is itself the member name, not a modifier prefix.
538    fn peek_is_member_punct(&self, n: usize) -> bool {
539        matches!(
540            self.toks.get(self.pos + n).map(|t| &t.tok),
541            Some(Tok::Punct(p)) if p == "(" || p == "=" || p == ";" || p == "}"
542        )
543    }
544
545    /// Parse a property key for a class member / object method: `[expr]` (computed),
546    /// a string, a number, or an identifier (returned as an `Expr::Str`).
547    fn parse_property_key(&mut self) -> Result<(Expr, bool), String> {
548        if self.is_punct("[") {
549            self.advance();
550            let k = self.parse_assign()?;
551            self.expect_punct("]")?;
552            Ok((k, true))
553        } else {
554            match self.tok().clone() {
555                Tok::Str(s) => {
556                    self.advance();
557                    Ok((Expr::Str(s), false))
558                }
559                Tok::Num(n) => {
560                    self.advance();
561                    Ok((Expr::Str(crate::host::fmt_number(n)), false))
562                }
563                Tok::Ident(s) => {
564                    self.advance();
565                    Ok((Expr::Str(s), false))
566                }
567                other => Err(format!(
568                    "SyntaxError: bad member key {other:?} (line {})",
569                    self.line()
570                )),
571            }
572        }
573    }
574
575    /// An optional non-newline label after break/continue.
576    fn opt_label(&mut self) -> Option<String> {
577        if self.newline_before() {
578            return None;
579        }
580        if let Tok::Ident(s) = self.tok() {
581            if !is_keyword(s) {
582                let s = s.clone();
583                self.advance();
584                return Some(s);
585            }
586        }
587        None
588    }
589
590    /// Parse statements up to (and consuming) the closing `}`.
591    fn parse_block_body(&mut self) -> Result<Vec<Stmt>, String> {
592        let mut out = Vec::new();
593        while !self.is_punct("}") && !self.at_eof() {
594            out.push(self.parse_stmt()?);
595        }
596        self.expect_punct("}")?;
597        Ok(out)
598    }
599
600    fn parse_decl_kind(&mut self) -> DeclKind {
601        let k = match self.tok() {
602            Tok::Ident(s) if s == "let" => DeclKind::Let,
603            Tok::Ident(s) if s == "const" => DeclKind::Const,
604            _ => DeclKind::Var,
605        };
606        self.advance();
607        k
608    }
609
610    fn parse_declarators(&mut self) -> Result<Vec<Declarator>, String> {
611        let mut decls = Vec::new();
612        loop {
613            let target = self.parse_binding_target()?;
614            let init = if self.eat_punct("=") {
615                Some(self.parse_assign()?)
616            } else {
617                None
618            };
619            decls.push(Declarator { target, init });
620            if !self.eat_punct(",") {
621                break;
622            }
623        }
624        Ok(decls)
625    }
626
627    /// A binding target: identifier or array/object destructuring pattern.
628    fn parse_binding_target(&mut self) -> Result<Expr, String> {
629        if self.is_punct("[") {
630            self.parse_array_literal()
631        } else if self.is_punct("{") {
632            self.parse_object_literal()
633        } else {
634            Ok(Expr::Ident(self.ident_name()?))
635        }
636    }
637
638    fn parse_if(&mut self) -> Result<StmtKind, String> {
639        self.advance(); // if
640        self.expect_punct("(")?;
641        let test = self.parse_expr()?;
642        self.expect_punct(")")?;
643        let cons = Box::new(self.parse_stmt()?);
644        let alt = if self.eat_kw("else") {
645            Some(Box::new(self.parse_stmt()?))
646        } else {
647            None
648        };
649        Ok(StmtKind::If { test, cons, alt })
650    }
651
652    fn parse_while(&mut self) -> Result<StmtKind, String> {
653        self.advance();
654        self.expect_punct("(")?;
655        let test = self.parse_expr()?;
656        self.expect_punct(")")?;
657        let body = Box::new(self.parse_stmt()?);
658        Ok(StmtKind::While { test, body })
659    }
660
661    fn parse_do_while(&mut self) -> Result<StmtKind, String> {
662        self.advance();
663        let body = Box::new(self.parse_stmt()?);
664        if !self.eat_kw("while") {
665            return Err(format!(
666                "SyntaxError: expected 'while' (line {})",
667                self.line()
668            ));
669        }
670        self.expect_punct("(")?;
671        let test = self.parse_expr()?;
672        self.expect_punct(")")?;
673        self.semicolon()?;
674        Ok(StmtKind::DoWhile { body, test })
675    }
676
677    fn parse_for(&mut self) -> Result<StmtKind, String> {
678        self.advance();
679        // `for await (… of …)` — the async-iteration form (valid in an async body).
680        let is_await = self.eat_kw("await");
681        self.expect_punct("(")?;
682        // Optional declaration or expression init.
683        let decl_kind = match self.tok() {
684            Tok::Ident(s) if s == "var" || s == "let" || s == "const" => {
685                Some(self.parse_decl_kind())
686            }
687            _ => None,
688        };
689        // Empty init: `for (;;)`.
690        if decl_kind.is_none() && self.is_punct(";") {
691            return self.parse_c_for(None);
692        }
693        // Parse the first binding/expression, then decide of/in vs C-style.
694        let first_target = if decl_kind.is_some() {
695            self.parse_binding_target()?
696        } else {
697            self.parse_expr_no_in()?
698        };
699        if self.eat_kw("of") {
700            let iter = self.parse_assign()?;
701            self.expect_punct(")")?;
702            let body = Box::new(self.parse_stmt()?);
703            return Ok(StmtKind::ForOf {
704                decl_kind,
705                target: first_target,
706                iter,
707                body,
708                is_await,
709            });
710        }
711        if self.eat_kw("in") {
712            let object = self.parse_assign()?;
713            self.expect_punct(")")?;
714            let body = Box::new(self.parse_stmt()?);
715            return Ok(StmtKind::ForIn {
716                decl_kind,
717                target: first_target,
718                object,
719                body,
720            });
721        }
722        // C-style: reconstruct the init statement.
723        let init_stmt = if let Some(k) = decl_kind {
724            let init = if self.eat_punct("=") {
725                Some(self.parse_assign()?)
726            } else {
727                None
728            };
729            let mut decls = vec![Declarator {
730                target: first_target,
731                init,
732            }];
733            while self.eat_punct(",") {
734                let target = self.parse_binding_target()?;
735                let init = if self.eat_punct("=") {
736                    Some(self.parse_assign()?)
737                } else {
738                    None
739                };
740                decls.push(Declarator { target, init });
741            }
742            StmtKind::Decl { kind: k, decls }
743        } else {
744            // A non-declaration C-style init may be a comma sequence
745            // (`for (i = 0, n = a.length; …)`) — extend past the first assignment.
746            let init = if self.is_punct(",") {
747                let mut items = vec![first_target];
748                while self.eat_punct(",") {
749                    items.push(self.parse_expr_no_in()?);
750                }
751                Expr::Sequence(items)
752            } else {
753                first_target
754            };
755            StmtKind::Expr(init)
756        };
757        self.parse_c_for(Some(Stmt::from(init_stmt)))
758    }
759
760    fn parse_c_for(&mut self, init: Option<Stmt>) -> Result<StmtKind, String> {
761        self.expect_punct(";")?;
762        let test = if self.is_punct(";") {
763            None
764        } else {
765            Some(self.parse_expr()?)
766        };
767        self.expect_punct(";")?;
768        let update = if self.is_punct(")") {
769            None
770        } else {
771            Some(self.parse_expr()?)
772        };
773        self.expect_punct(")")?;
774        let body = Box::new(self.parse_stmt()?);
775        Ok(StmtKind::For {
776            init: init.map(Box::new),
777            test,
778            update,
779            body,
780        })
781    }
782
783    fn parse_switch(&mut self) -> Result<StmtKind, String> {
784        self.advance();
785        self.expect_punct("(")?;
786        let disc = self.parse_expr()?;
787        self.expect_punct(")")?;
788        self.expect_punct("{")?;
789        let mut cases = Vec::new();
790        while !self.is_punct("}") && !self.at_eof() {
791            let test = if self.eat_kw("case") {
792                let e = self.parse_expr()?;
793                Some(e)
794            } else if self.eat_kw("default") {
795                None
796            } else {
797                return Err(format!(
798                    "SyntaxError: expected 'case' or 'default' (line {})",
799                    self.line()
800                ));
801            };
802            self.expect_punct(":")?;
803            let mut body = Vec::new();
804            while !self.is_punct("}")
805                && !self.is_kw("case")
806                && !self.is_kw("default")
807                && !self.at_eof()
808            {
809                body.push(self.parse_stmt()?);
810            }
811            cases.push(SwitchCase { test, body });
812        }
813        self.expect_punct("}")?;
814        Ok(StmtKind::Switch { disc, cases })
815    }
816
817    fn parse_try(&mut self) -> Result<StmtKind, String> {
818        self.advance();
819        self.expect_punct("{")?;
820        let block = self.parse_block_body()?;
821        let handler = if self.eat_kw("catch") {
822            let param = if self.eat_punct("(") {
823                let p = self.parse_binding_target()?;
824                self.expect_punct(")")?;
825                Some(p)
826            } else {
827                None
828            };
829            self.expect_punct("{")?;
830            let body = self.parse_block_body()?;
831            Some((param, body))
832        } else {
833            None
834        };
835        let finalizer = if self.eat_kw("finally") {
836            self.expect_punct("{")?;
837            Some(self.parse_block_body()?)
838        } else {
839            None
840        };
841        Ok(StmtKind::Try {
842            block,
843            handler,
844            finalizer,
845        })
846    }
847
848    // ── expressions ──────────────────────────────────────────────────────
849    /// Full expression, including the comma sequence operator.
850    fn parse_expr(&mut self) -> Result<Expr, String> {
851        let first = self.parse_assign()?;
852        if self.is_punct(",") {
853            let mut items = vec![first];
854            while self.eat_punct(",") {
855                items.push(self.parse_assign()?);
856            }
857            Ok(Expr::Sequence(items))
858        } else {
859            Ok(first)
860        }
861    }
862
863    /// Like `parse_expr` but stops before `in` (used in `for` init position).
864    fn parse_expr_no_in(&mut self) -> Result<Expr, String> {
865        // For simplicity the no-in variant only parses an assignment/LHS chain,
866        // which is sufficient for `for (x in ...)` / `for (x of ...)` heads.
867        let saved = self.no_in;
868        self.no_in = true;
869        let r = self.parse_assign();
870        self.no_in = saved;
871        r
872    }
873
874    /// Whether `e` is an optional chain — i.e. its member/call SPINE carries a
875    /// `?.` link. Mirrors the compiler's spine walk; used only to decide
876    /// whether a set of parentheses is a chain boundary worth recording.
877    fn has_optional_link(e: &Expr) -> bool {
878        match e {
879            Expr::Member {
880                object, optional, ..
881            } => *optional || Self::has_optional_link(object),
882            Expr::Index {
883                object, optional, ..
884            } => *optional || Self::has_optional_link(object),
885            Expr::Call { func, optional, .. } => *optional || Self::has_optional_link(func),
886            _ => false,
887        }
888    }
889
890    /// Run `f` with `in` re-enabled (inside a parenthesised/bracketed sub-
891    /// expression of a `for` LHS, where the no-in restriction does not apply).
892    fn allow_in<T>(&mut self, f: impl FnOnce(&mut Self) -> Result<T, String>) -> Result<T, String> {
893        let saved = self.no_in;
894        self.no_in = false;
895        let r = f(self);
896        self.no_in = saved;
897        r
898    }
899
900    fn parse_assign(&mut self) -> Result<Expr, String> {
901        // Arrow function detection.
902        if let Some(arrow) = self.try_parse_arrow()? {
903            return Ok(arrow);
904        }
905        let left = self.parse_conditional()?;
906        // Assignment operators (right-associative).
907        let op = match self.tok() {
908            Tok::Punct(p) => p.clone(),
909            _ => return Ok(left),
910        };
911        let compound = match op.as_str() {
912            "=" => None,
913            "+=" => Some(BinOp::Add),
914            "-=" => Some(BinOp::Sub),
915            "*=" => Some(BinOp::Mul),
916            "/=" => Some(BinOp::Div),
917            "%=" => Some(BinOp::Mod),
918            "**=" => Some(BinOp::Pow),
919            "&=" => Some(BinOp::BitAnd),
920            "|=" => Some(BinOp::BitOr),
921            "^=" => Some(BinOp::BitXor),
922            "<<=" => Some(BinOp::Shl),
923            ">>=" => Some(BinOp::Shr),
924            ">>>=" => Some(BinOp::UShr),
925            "&&=" | "||=" | "??=" => {
926                // Logical assignment. The operator is CARRIED, not desugared:
927                // building `Logical(op, left.clone(), value)` here duplicated
928                // the target, so `o[k()] ||= 1` evaluated `k` twice — once for
929                // the read and once for the write — where node evaluates it
930                // once and may not write at all.
931                self.advance();
932                let value = self.parse_assign()?;
933                let lop = match op.as_str() {
934                    "&&=" => LogicalOp::And,
935                    "||=" => LogicalOp::Or,
936                    _ => LogicalOp::Nullish,
937                };
938                return Ok(Expr::Assign {
939                    target: Box::new(left),
940                    op: Some(AssignOp::Logical(lop)),
941                    value: Box::new(value),
942                });
943            }
944            _ => return Ok(left),
945        };
946        self.advance();
947        let value = self.parse_assign()?;
948        Ok(Expr::Assign {
949            target: Box::new(left),
950            op: compound.map(AssignOp::Binary),
951            value: Box::new(value),
952        })
953    }
954
955    fn parse_conditional(&mut self) -> Result<Expr, String> {
956        let test = self.parse_binary(0)?;
957        if self.eat_punct("?") {
958            let cons = self.parse_assign()?;
959            self.expect_punct(":")?;
960            let alt = self.parse_assign()?;
961            Ok(Expr::Conditional {
962                test: Box::new(test),
963                cons: Box::new(cons),
964                alt: Box::new(alt),
965            })
966        } else {
967            Ok(test)
968        }
969    }
970
971    /// Precedence-climbing binary parser. Handles `&& || ??` as logical nodes.
972    fn parse_binary(&mut self, min_prec: u8) -> Result<Expr, String> {
973        let mut left = self.parse_unary()?;
974        while let Some((prec, right_assoc, logical, bin)) = self.bin_info() {
975            if prec < min_prec {
976                break;
977            }
978            self.advance();
979            let next_min = if right_assoc { prec } else { prec + 1 };
980            let right = self.parse_binary(next_min)?;
981            left = if let Some(lop) = logical {
982                Expr::Logical(lop, Box::new(left), Box::new(right))
983            } else {
984                Expr::Binary(bin.unwrap(), Box::new(left), Box::new(right))
985            };
986        }
987        Ok(left)
988    }
989
990    /// `(precedence, right_assoc, logical_op, bin_op)` for the current token.
991    fn bin_info(&self) -> Option<(u8, bool, Option<LogicalOp>, Option<BinOp>)> {
992        let p = match self.tok() {
993            Tok::Punct(p) => p.as_str(),
994            // In a `for` LHS (no-in) context, `in` is the loop separator, not a
995            // relational operator.
996            Tok::Ident(s) if s == "in" => {
997                if self.no_in {
998                    return None;
999                }
1000                "in"
1001            }
1002            Tok::Ident(s) if s == "instanceof" => "instanceof",
1003            _ => return None,
1004        };
1005        let (prec, ra, log, bin) = match p {
1006            "??" => (1, false, Some(LogicalOp::Nullish), None),
1007            "||" => (2, false, Some(LogicalOp::Or), None),
1008            "&&" => (3, false, Some(LogicalOp::And), None),
1009            "|" => (4, false, None, Some(BinOp::BitOr)),
1010            "^" => (5, false, None, Some(BinOp::BitXor)),
1011            "&" => (6, false, None, Some(BinOp::BitAnd)),
1012            "==" => (7, false, None, Some(BinOp::EqEq)),
1013            "!=" => (7, false, None, Some(BinOp::NeEq)),
1014            "===" => (7, false, None, Some(BinOp::EqEqEq)),
1015            "!==" => (7, false, None, Some(BinOp::NeEqEq)),
1016            "<" => (8, false, None, Some(BinOp::Lt)),
1017            "<=" => (8, false, None, Some(BinOp::Le)),
1018            ">" => (8, false, None, Some(BinOp::Gt)),
1019            ">=" => (8, false, None, Some(BinOp::Ge)),
1020            "in" => (8, false, None, Some(BinOp::In)),
1021            "instanceof" => (8, false, None, Some(BinOp::InstanceOf)),
1022            "<<" => (9, false, None, Some(BinOp::Shl)),
1023            ">>" => (9, false, None, Some(BinOp::Shr)),
1024            ">>>" => (9, false, None, Some(BinOp::UShr)),
1025            "+" => (10, false, None, Some(BinOp::Add)),
1026            "-" => (10, false, None, Some(BinOp::Sub)),
1027            "*" => (11, false, None, Some(BinOp::Mul)),
1028            "/" => (11, false, None, Some(BinOp::Div)),
1029            "%" => (11, false, None, Some(BinOp::Mod)),
1030            "**" => (12, true, None, Some(BinOp::Pow)),
1031            _ => return None,
1032        };
1033        Some((prec, ra, log, bin))
1034    }
1035
1036    /// Reject a `**` directly after a just-parsed UnaryExpression. JS only
1037    /// allows an UpdateExpression there (`x++ ** y` and `++x ** y` are fine),
1038    /// so an unparenthesized `-x ** y` / `typeof x ** y` / `await x ** y` is a
1039    /// SyntaxError rather than a silently-reassociated `-(x ** y)`.
1040    fn reject_unary_before_pow(&mut self) -> Result<(), String> {
1041        if self.is_punct("**") {
1042            return Err(format!(
1043                "SyntaxError: Unary operator used immediately before exponentiation \
1044                 expression. Parenthesis must be used to disambiguate operator \
1045                 precedence (line {})",
1046                self.line()
1047            ));
1048        }
1049        Ok(())
1050    }
1051
1052    fn parse_unary(&mut self) -> Result<Expr, String> {
1053        let op = match self.tok() {
1054            Tok::Punct(p) if p == "!" => Some(UnOp::Not),
1055            Tok::Punct(p) if p == "~" => Some(UnOp::BitNot),
1056            Tok::Punct(p) if p == "+" => Some(UnOp::Pos),
1057            Tok::Punct(p) if p == "-" => Some(UnOp::Neg),
1058            Tok::Ident(s) if s == "typeof" => Some(UnOp::TypeOf),
1059            Tok::Ident(s) if s == "void" => Some(UnOp::Void),
1060            Tok::Ident(s) if s == "delete" => Some(UnOp::Delete),
1061            _ => None,
1062        };
1063        if let Some(op) = op {
1064            self.advance();
1065            let e = self.parse_unary()?;
1066            // `ExponentiationExpression : UpdateExpression ** …` — a
1067            // UnaryExpression on the left of `**` is a SyntaxError, so
1068            // `-x ** y` must be written `(-x) ** y` or `-(x ** y)`.
1069            self.reject_unary_before_pow()?;
1070            return Ok(Expr::Unary(op, Box::new(e)));
1071        }
1072        // Prefix ++/--.
1073        if self.is_punct("++") || self.is_punct("--") {
1074            let op = if self.is_punct("++") {
1075                UpdateOp::Inc
1076            } else {
1077                UpdateOp::Dec
1078            };
1079            self.advance();
1080            let e = self.parse_unary()?;
1081            return Ok(Expr::Update {
1082                op,
1083                prefix: true,
1084                target: Box::new(e),
1085            });
1086        }
1087        self.parse_postfix()
1088    }
1089
1090    fn parse_postfix(&mut self) -> Result<Expr, String> {
1091        let mut e = self.parse_call_member()?;
1092        // Postfix ++/-- (no line break before).
1093        if (self.is_punct("++") || self.is_punct("--")) && !self.newline_before() {
1094            let op = if self.is_punct("++") {
1095                UpdateOp::Inc
1096            } else {
1097                UpdateOp::Dec
1098            };
1099            self.advance();
1100            e = Expr::Update {
1101                op,
1102                prefix: false,
1103                target: Box::new(e),
1104            };
1105        }
1106        Ok(e)
1107    }
1108
1109    fn parse_call_member(&mut self) -> Result<Expr, String> {
1110        let mut e = if self.eat_kw("new") {
1111            // `new.target` meta-property.
1112            if self.is_punct(".") {
1113                self.advance();
1114                let prop = self.ident_name()?;
1115                if prop != "target" {
1116                    return Err(format!(
1117                        "SyntaxError: expected 'target' (line {})",
1118                        self.line()
1119                    ));
1120                }
1121                Expr::NewTarget
1122            } else {
1123                let callee = self.parse_call_member_no_call()?;
1124                // An optional chain cannot be the callee of `new` (13.3.5.1):
1125                // `new a?.b()` and `new C?.()` are early errors. Parenthesising
1126                // the chain ends it, so `new (a?.b)()` is legal — which is why
1127                // the check is on the callee's own spine, and the parenthesised
1128                // form records a chain boundary that clears `optional`.
1129                if Self::has_optional_link(&callee) || self.is_punct("?.") {
1130                    return Err(
1131                        "SyntaxError: Invalid optional chain from new expression".to_string()
1132                    );
1133                }
1134                let args = if self.is_punct("(") {
1135                    self.parse_args()?
1136                } else {
1137                    Vec::new()
1138                };
1139                Expr::New {
1140                    callee: Box::new(callee),
1141                    args,
1142                }
1143            }
1144        } else {
1145            self.parse_primary()?
1146        };
1147        loop {
1148            if self.eat_punct(".") {
1149                let property = self.ident_name()?;
1150                self.check_private_in_scope(&property)?;
1151                e = Expr::Member {
1152                    object: Box::new(e),
1153                    property,
1154                    optional: false,
1155                };
1156            } else if self.eat_punct("?.") {
1157                if self.is_punct("(") {
1158                    let args = self.parse_args()?;
1159                    e = Expr::Call {
1160                        func: Box::new(e),
1161                        args,
1162                        optional: true,
1163                    };
1164                } else if self.is_punct("[") {
1165                    self.advance();
1166                    let index = self.allow_in(|p| p.parse_expr())?;
1167                    self.expect_punct("]")?;
1168                    e = Expr::Index {
1169                        object: Box::new(e),
1170                        index: Box::new(index),
1171                        optional: true,
1172                    };
1173                } else {
1174                    let property = self.ident_name()?;
1175                    e = Expr::Member {
1176                        object: Box::new(e),
1177                        property,
1178                        optional: true,
1179                    };
1180                }
1181            } else if self.is_punct("[") {
1182                self.advance();
1183                let index = self.allow_in(|p| p.parse_expr())?;
1184                self.expect_punct("]")?;
1185                e = Expr::Index {
1186                    object: Box::new(e),
1187                    index: Box::new(index),
1188                    optional: false,
1189                };
1190            } else if self.is_punct("(") {
1191                let args = self.parse_args()?;
1192                e = Expr::Call {
1193                    func: Box::new(e),
1194                    args,
1195                    optional: false,
1196                };
1197            } else if matches!(self.tok(), Tok::Template { .. }) {
1198                // A template literal immediately after a callee is a *tagged*
1199                // template: `` tag`...` `` → `tag(strings, ...values)`.
1200                // A tagged template cannot sit on an optional chain (13.3.11.1):
1201                // `a?.b`t`` is an early error, because the tag would have to be
1202                // called even when the chain short-circuited.
1203                if Self::has_optional_link(&e) {
1204                    return Err(
1205                        "SyntaxError: Invalid tagged template on optional chain".to_string()
1206                    );
1207                }
1208                e = self.parse_tagged_template(e)?;
1209            } else {
1210                break;
1211            }
1212        }
1213        Ok(e)
1214    }
1215
1216    /// Parse `` tag`a${x}b` `` into a `TaggedTemplate` node (the tag expression is
1217    /// already parsed as `tag`, and the current token is the template).
1218    fn parse_tagged_template(&mut self, tag: Expr) -> Result<Expr, String> {
1219        let (quasis, raws, exprs_src) = match self.tok().clone() {
1220            Tok::Template {
1221                quasis,
1222                raws,
1223                exprs,
1224            } => (quasis, raws, exprs),
1225            _ => unreachable!(),
1226        };
1227        self.advance();
1228        let mut exprs = Vec::new();
1229        for src in &exprs_src {
1230            exprs.push(parse_expr_source(src)?);
1231        }
1232        Ok(Expr::TaggedTemplate {
1233            tag: Box::new(tag),
1234            quasis,
1235            raws,
1236            exprs,
1237        })
1238    }
1239
1240    /// Member chain without a trailing call — the `new X.Y` callee grammar.
1241    fn parse_call_member_no_call(&mut self) -> Result<Expr, String> {
1242        let mut e = self.parse_primary()?;
1243        loop {
1244            if self.eat_punct(".") {
1245                let property = self.ident_name()?;
1246                self.check_private_in_scope(&property)?;
1247                e = Expr::Member {
1248                    object: Box::new(e),
1249                    property,
1250                    optional: false,
1251                };
1252            } else if self.is_punct("[") {
1253                self.advance();
1254                let index = self.allow_in(|p| p.parse_expr())?;
1255                self.expect_punct("]")?;
1256                e = Expr::Index {
1257                    object: Box::new(e),
1258                    index: Box::new(index),
1259                    optional: false,
1260                };
1261            } else {
1262                break;
1263            }
1264        }
1265        Ok(e)
1266    }
1267
1268    fn parse_args(&mut self) -> Result<Vec<Expr>, String> {
1269        self.expect_punct("(")?;
1270        // Inside a call-argument list `in` is always a relational operator, even
1271        // in a `for` LHS.
1272        let args = self.allow_in(|p| {
1273            let mut args = Vec::new();
1274            while !p.is_punct(")") {
1275                if p.eat_punct("...") {
1276                    let e = p.parse_assign()?;
1277                    args.push(Expr::Spread(Box::new(e)));
1278                } else {
1279                    args.push(p.parse_assign()?);
1280                }
1281                if !p.eat_punct(",") {
1282                    break;
1283                }
1284            }
1285            Ok(args)
1286        })?;
1287        self.expect_punct(")")?;
1288        Ok(args)
1289    }
1290
1291    fn parse_primary(&mut self) -> Result<Expr, String> {
1292        match self.tok().clone() {
1293            Tok::Num(n) => {
1294                self.advance();
1295                Ok(Expr::Number(n))
1296            }
1297            Tok::BigInt(s) => {
1298                self.advance();
1299                Ok(Expr::BigInt(s))
1300            }
1301            Tok::Regex(pat, flags) => {
1302                self.advance();
1303                Ok(Expr::Regex(pat, flags))
1304            }
1305            Tok::Str(s) => {
1306                self.advance();
1307                Ok(Expr::Str(s))
1308            }
1309            Tok::Template {
1310                quasis,
1311                raws: _,
1312                exprs,
1313            } => {
1314                self.advance();
1315                let mut parsed = Vec::new();
1316                for src in &exprs {
1317                    parsed.push(parse_expr_source(src)?);
1318                }
1319                Ok(Expr::Template {
1320                    quasis,
1321                    exprs: parsed,
1322                })
1323            }
1324            Tok::Punct(p) if p == "(" => {
1325                self.advance();
1326                let e = self.parse_expr()?;
1327                self.expect_punct(")")?;
1328                // Parentheses are otherwise erased, but around an OPTIONAL CHAIN
1329                // they are load-bearing: they END the chain, so a `?.` inside
1330                // them cannot short-circuit an access written outside them.
1331                // `o?.a.b` is `undefined` when `o` is nullish; `(o?.a).b` reads
1332                // `.b` off that `undefined` and THROWS (ECMA-262 13.3.1 —
1333                // `ParenthesizedExpression` is not an `OptionalExpression`).
1334                // With the parentheses dropped the two parsed to the same tree
1335                // and both answered `undefined`.
1336                //
1337                // The boundary is spelled as a one-element sequence rather than
1338                // a new node type: `(e)` and `e,` evaluate identically, every
1339                // existing pass already walks `Sequence` correctly, and the
1340                // chain-spine walk does not descend through it — which is the
1341                // whole point. Only chains are wrapped, so no other expression's
1342                // tree shape changes.
1343                if Self::has_optional_link(&e) {
1344                    return Ok(Expr::Sequence(vec![e]));
1345                }
1346                Ok(e)
1347            }
1348            Tok::Punct(p) if p == "[" => self.parse_array_literal(),
1349            Tok::Punct(p) if p == "{" => self.parse_object_literal(),
1350            Tok::Ident(s) => {
1351                match s.as_str() {
1352                    "true" => {
1353                        self.advance();
1354                        Ok(Expr::True)
1355                    }
1356                    "false" => {
1357                        self.advance();
1358                        Ok(Expr::False)
1359                    }
1360                    "null" => {
1361                        self.advance();
1362                        Ok(Expr::Null)
1363                    }
1364                    "this" => {
1365                        self.advance();
1366                        Ok(Expr::This)
1367                    }
1368                    "super" => {
1369                        self.advance();
1370                        Ok(Expr::Super)
1371                    }
1372                    "class" => Ok(Expr::Class(Box::new(self.parse_class(false)?))),
1373                    "function" => self.parse_function_expr(false),
1374                    "async" if self.peek_kw(1, "function") && !self.peek_newline(1) => {
1375                        self.advance(); // async
1376                        self.parse_function_expr(true)
1377                    }
1378                    "yield" if self.in_generator => {
1379                        self.advance();
1380                        let delegate = self.eat_punct("*");
1381                        // `yield` with no argument (before `)`, `]`, `}`, `,`, `;`,
1382                        // newline, or EOF).
1383                        let arg = if delegate
1384                            || !(self.is_punct(")")
1385                                || self.is_punct("]")
1386                                || self.is_punct("}")
1387                                || self.is_punct(",")
1388                                || self.is_punct(";")
1389                                || self.is_punct(":")
1390                                || self.newline_before()
1391                                || self.at_eof())
1392                        {
1393                            Some(Box::new(self.parse_assign()?))
1394                        } else {
1395                            None
1396                        };
1397                        Ok(Expr::Yield { arg, delegate })
1398                    }
1399                    "await" if self.in_async => {
1400                        self.advance();
1401                        let e = self.parse_unary()?;
1402                        // An AwaitExpression is a UnaryExpression, so it too
1403                        // cannot sit directly left of `**`.
1404                        self.reject_unary_before_pow()?;
1405                        Ok(Expr::Await(Box::new(e)))
1406                    }
1407                    _ if is_keyword(&s) => Err(format!(
1408                        "SyntaxError: unexpected keyword '{s}' (line {})",
1409                        self.line()
1410                    )),
1411                    _ => {
1412                        self.advance();
1413                        Ok(Expr::Ident(s))
1414                    }
1415                }
1416            }
1417            other => Err(format!(
1418                "SyntaxError: unexpected token {other:?} (line {})",
1419                self.line()
1420            )),
1421        }
1422    }
1423
1424    fn parse_array_literal(&mut self) -> Result<Expr, String> {
1425        self.expect_punct("[")?;
1426        let mut items = Vec::new();
1427        while !self.is_punct("]") {
1428            if self.is_punct(",") {
1429                // Elision: the element is a HOLE, not a stored `undefined`.
1430                items.push(Expr::Hole);
1431                self.advance();
1432                continue;
1433            }
1434            if self.eat_punct("...") {
1435                let e = self.parse_assign()?;
1436                items.push(Expr::Spread(Box::new(e)));
1437            } else {
1438                items.push(self.parse_assign()?);
1439            }
1440            if !self.eat_punct(",") {
1441                break;
1442            }
1443        }
1444        self.expect_punct("]")?;
1445        Ok(Expr::Array(items))
1446    }
1447
1448    fn parse_object_literal(&mut self) -> Result<Expr, String> {
1449        self.expect_punct("{")?;
1450        let mut props = Vec::new();
1451        while !self.is_punct("}") {
1452            if self.eat_punct("...") {
1453                let e = self.parse_assign()?;
1454                props.push(Prop::Spread(e));
1455                if !self.eat_punct(",") {
1456                    break;
1457                }
1458                continue;
1459            }
1460            // `get key() {}` / `set key(v) {}` accessor (contextual: `get`/`set`
1461            // is a modifier only when followed by another key, not `:`/`(`/`,`).
1462            if (self.is_kw("get") || self.is_kw("set"))
1463                && !self.peek_is_member_punct(1)
1464                && !matches!(self.toks.get(self.pos + 1).map(|t| &t.tok), Some(Tok::Punct(p)) if p == ":" || p == ",")
1465            {
1466                let is_getter = self.is_kw("get");
1467                self.advance();
1468                let (key, computed) = self.parse_property_key()?;
1469                let params = self.parse_params()?;
1470                self.expect_punct("{")?;
1471                let body = self.parse_block_body()?;
1472                let func = Expr::Function {
1473                    params,
1474                    body: FnBody::Block(body),
1475                    is_arrow: false,
1476                    name: None,
1477                    is_generator: false,
1478                    is_async: false,
1479                    is_method: true,
1480                };
1481                props.push(Prop::Accessor {
1482                    key,
1483                    computed,
1484                    is_getter,
1485                    func,
1486                });
1487                if !self.eat_punct(",") {
1488                    break;
1489                }
1490                continue;
1491            }
1492            // Concise-method modifiers: `async` and/or `*` before the key.
1493            let mut m_async = false;
1494            let mut m_gen = false;
1495            if self.is_kw("async")
1496                && !self.peek_is_member_punct(1)
1497                && !self.peek_newline(1)
1498                && !matches!(self.toks.get(self.pos + 1).map(|t| &t.tok), Some(Tok::Punct(p)) if p == ":" || p == ",")
1499            {
1500                self.advance();
1501                m_async = true;
1502            }
1503            if self.is_punct("*") {
1504                self.advance();
1505                m_gen = true;
1506            }
1507            let (key, computed) = self.parse_property_key()?;
1508            // Method shorthand `key(params) { }` (incl. `*gen(){}`, `async m(){}`).
1509            if self.is_punct("(") {
1510                let params = self.parse_params()?;
1511                self.expect_punct("{")?;
1512                let body = self.parse_fn_body_block(m_gen, m_async)?;
1513                let f = Expr::Function {
1514                    params,
1515                    body: FnBody::Block(body),
1516                    is_arrow: false,
1517                    name: None,
1518                    is_generator: m_gen,
1519                    is_async: m_async,
1520                    is_method: true,
1521                };
1522                props.push(Prop::KeyValue {
1523                    key,
1524                    value: f,
1525                    computed,
1526                });
1527            } else if self.eat_punct(":") {
1528                let value = self.parse_assign()?;
1529                props.push(Prop::KeyValue {
1530                    key,
1531                    value,
1532                    computed,
1533                });
1534            } else {
1535                // Shorthand `{ x }` -> key "x", value ident x. Or with default
1536                // in a destructuring pattern: `{ x = 1 }`.
1537                let name = match &key {
1538                    Expr::Str(s) => s.clone(),
1539                    _ => return Err(format!("SyntaxError: bad shorthand (line {})", self.line())),
1540                };
1541                let value = if self.eat_punct("=") {
1542                    // Pattern default; represent as Assign so destructuring reads it.
1543                    let d = self.parse_assign()?;
1544                    Expr::Assign {
1545                        target: Box::new(Expr::Ident(name.clone())),
1546                        op: None,
1547                        value: Box::new(d),
1548                    }
1549                } else {
1550                    Expr::Ident(name)
1551                };
1552                props.push(Prop::KeyValue {
1553                    key,
1554                    value,
1555                    computed,
1556                });
1557            }
1558            if !self.eat_punct(",") {
1559                break;
1560            }
1561        }
1562        self.expect_punct("}")?;
1563        Ok(Expr::Object(props))
1564    }
1565
1566    // ── functions / arrows ───────────────────────────────────────────────
1567    fn parse_params(&mut self) -> Result<Vec<Param>, String> {
1568        self.expect_punct("(")?;
1569        let mut params = Vec::new();
1570        while !self.is_punct(")") {
1571            let rest = self.eat_punct("...");
1572            let pattern = self.parse_binding_target()?;
1573            let default = if !rest && self.eat_punct("=") {
1574                Some(self.parse_assign()?)
1575            } else {
1576                None
1577            };
1578            params.push(Param {
1579                pattern,
1580                default,
1581                rest,
1582            });
1583            if !self.eat_punct(",") {
1584                break;
1585            }
1586        }
1587        self.expect_punct(")")?;
1588        Ok(params)
1589    }
1590
1591    /// Try to parse an arrow function starting at the current position. Returns
1592    /// `None` (without consuming) if the head is not an arrow.
1593    fn try_parse_arrow(&mut self) -> Result<Option<Expr>, String> {
1594        // `async` prefix on an arrow (`async x => …` / `async (…) => …`), only
1595        // when `async` is not itself the parameter and no newline intervenes.
1596        let mut is_async = false;
1597        let mut base = self.pos;
1598        if self.is_kw("async") && !self.peek_newline(1) {
1599            let next = self.toks.get(self.pos + 1).map(|t| &t.tok);
1600            let looks_async_arrow = matches!(next, Some(Tok::Punct(p)) if p == "(")
1601                || matches!(next, Some(Tok::Ident(n)) if !is_keyword(n) && self.peek_is_arrow_after(2));
1602            if looks_async_arrow {
1603                is_async = true;
1604                base += 1;
1605            }
1606        }
1607        // `ident => ...`
1608        if let Some(Tok::Ident(name)) = self.toks.get(base).map(|t| &t.tok) {
1609            if !is_keyword(name)
1610                && matches!(self.toks.get(base + 1).map(|t| &t.tok), Some(Tok::Punct(p)) if p == "=>")
1611            {
1612                let name = name.clone();
1613                if is_async {
1614                    self.advance(); // async
1615                }
1616                self.advance(); // ident
1617                self.advance(); // =>
1618                let body = self.parse_arrow_body(is_async)?;
1619                return Ok(Some(Expr::Function {
1620                    params: vec![Param {
1621                        pattern: Expr::Ident(name),
1622                        default: None,
1623                        rest: false,
1624                    }],
1625                    body,
1626                    is_arrow: true,
1627                    name: None,
1628                    is_generator: false,
1629                    is_async,
1630                    is_method: false,
1631                }));
1632            }
1633        }
1634        // `( ... ) => ...`
1635        if matches!(self.toks.get(base).map(|t| &t.tok), Some(Tok::Punct(p)) if p == "(") {
1636            if let Some(close) = self.matching_paren(base) {
1637                let after = close + 1;
1638                if matches!(self.toks.get(after).map(|t| &t.tok), Some(Tok::Punct(p)) if p == "=>")
1639                {
1640                    if is_async {
1641                        self.advance(); // async
1642                    }
1643                    let params = self.parse_params()?;
1644                    self.expect_punct("=>")?;
1645                    let body = self.parse_arrow_body(is_async)?;
1646                    return Ok(Some(Expr::Function {
1647                        params,
1648                        body,
1649                        is_arrow: true,
1650                        name: None,
1651                        is_generator: false,
1652                        is_async,
1653                        is_method: false,
1654                    }));
1655                }
1656            }
1657        }
1658        Ok(None)
1659    }
1660
1661    fn parse_arrow_body(&mut self, is_async: bool) -> Result<FnBody, String> {
1662        let (pg, pa) = (self.in_generator, self.in_async);
1663        self.in_generator = false;
1664        self.in_async = is_async;
1665        let r = if self.is_punct("{") {
1666            self.advance();
1667            self.parse_block_body().map(FnBody::Block)
1668        } else {
1669            self.parse_assign().map(|e| FnBody::Expr(Box::new(e)))
1670        };
1671        self.in_generator = pg;
1672        self.in_async = pa;
1673        r
1674    }
1675
1676    /// Whether the token `n` positions ahead is `=>`.
1677    fn peek_is_arrow_after(&self, n: usize) -> bool {
1678        matches!(self.toks.get(self.pos + n).map(|t| &t.tok), Some(Tok::Punct(p)) if p == "=>")
1679    }
1680
1681    /// Index of the `)` matching the `(` at `open`, skipping nested brackets.
1682    fn matching_paren(&self, open: usize) -> Option<usize> {
1683        let mut depth = 0i32;
1684        let mut i = open;
1685        while i < self.toks.len() {
1686            match &self.toks[i].tok {
1687                Tok::Punct(p) if p == "(" || p == "[" || p == "{" => depth += 1,
1688                Tok::Punct(p) if p == ")" || p == "]" || p == "}" => {
1689                    depth -= 1;
1690                    if depth == 0 {
1691                        return Some(i);
1692                    }
1693                }
1694                Tok::Eof => return None,
1695                _ => {}
1696            }
1697            i += 1;
1698        }
1699        None
1700    }
1701}
1702
1703/// Parse a template-literal `${...}` field's raw source into an expression.
1704fn parse_expr_source(src: &str) -> Result<Expr, String> {
1705    let toks = lex(src)?;
1706    let mut p = Parser {
1707        toks,
1708        pos: 0,
1709        in_generator: false,
1710        in_async: false,
1711        no_in: false,
1712        class_scopes: Vec::new(),
1713    };
1714    let e = p.parse_expr()?;
1715    Ok(e)
1716}