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