Skip to main content

nodejs/
compiler.rs

1//! Lower the JavaScript AST to `fusevm::Chunk`.
2//!
3//! Native fusevm ops carry arithmetic (`+ - * / % **`), the relational
4//! comparisons (`< <= > >=`) and boolean short-circuit so the JIT can trace
5//! them; the strict numeric hook (host) supplies JS semantics for non-numeric
6//! operands (string concat, coercion). Everything JS-specific — name access,
7//! member/index access, calls, object/array construction, iteration — lowers to
8//! a `CallBuiltin` that lands in `builtins.rs`.
9//!
10//! Conditions are normalized through the `TRUTHY` builtin before a native
11//! `JumpIfFalse`, because JS truthiness differs from fusevm's default numeric
12//! truthiness. Compiler-internal name strings travel as native `Value::Str`
13//! constants; JS-level strings are always heap objects built by `MKSTR`.
14
15use crate::ast::*;
16use crate::host::{binop as bop, member, ops, unop, FuncDef, ParamSlot, TryDef};
17use fusevm::{Chunk, ChunkBuilder, Op, Value};
18
19/// A compiled program: the top-level chunk plus the function template table and
20/// the try-block table.
21#[derive(Default)]
22pub struct Program {
23    pub main: Chunk,
24    pub functions: Vec<(String, FuncDef)>,
25    pub tries: Vec<TryDef>,
26}
27
28/// Rebase every func-id and try-id reference so its ids sit above those already
29/// loaded on the host (needed only for incremental loading; a no-op for a single
30/// run).
31pub fn rebase_program(prog: &mut Program, func_off: usize, try_off: usize) {
32    if func_off == 0 && try_off == 0 {
33        return;
34    }
35    rebase_chunk(&mut prog.main, func_off, try_off);
36    for (_, f) in &mut prog.functions {
37        rebase_chunk(&mut f.chunk, func_off, try_off);
38    }
39    for t in &mut prog.tries {
40        rebase_chunk(&mut t.block, func_off, try_off);
41        if let Some((_, hb)) = &mut t.handler {
42            rebase_chunk(hb, func_off, try_off);
43        }
44        if let Some(f) = &mut t.finalizer {
45            rebase_chunk(f, func_off, try_off);
46        }
47    }
48}
49
50fn rebase_chunk(chunk: &mut Chunk, func_off: usize, try_off: usize) {
51    for i in 1..chunk.ops.len() {
52        let off = match chunk.ops[i] {
53            Op::CallBuiltin(id, _) if id == ops::MKFUNC => func_off,
54            Op::CallBuiltin(id, 1) if id == ops::TRY => try_off,
55            _ => continue,
56        };
57        if off == 0 {
58            continue;
59        }
60        if let Op::LoadInt(v) = &mut chunk.ops[i - 1] {
61            *v += off as i64;
62        }
63    }
64    for sub in &mut chunk.sub_chunks {
65        rebase_chunk(sub, func_off, try_off);
66    }
67}
68
69/// Break/continue jump fixups for a loop or switch.
70struct LoopCtx {
71    breaks: Vec<usize>,
72    continues: Vec<usize>,
73    /// Whether `continue` binds here (true for loops, false for `switch`).
74    catches_continue: bool,
75    /// The source label attached to this loop/block, if any (`outer: for …`),
76    /// so labeled `break outer` / `continue outer` can target it directly.
77    label: Option<String>,
78}
79
80#[derive(Default)]
81pub struct Compiler {
82    functions: Vec<(String, FuncDef)>,
83    tries: Vec<TryDef>,
84    loops: Vec<LoopCtx>,
85    tmp: usize,
86    /// A label seen immediately before a loop, consumed by that loop's `LoopCtx`
87    /// (`outer: for (…)`); `None` once claimed.
88    pending_label: Option<String>,
89    /// Emit per-statement `DBG_LINE` markers for the DAP debugger (`node --dap`).
90    debug: bool,
91}
92
93/// Compile a parsed program. `debug` enables per-statement DAP line markers.
94pub fn compile(stmts: &[Stmt], debug: bool) -> Result<Program, String> {
95    let mut c = Compiler {
96        debug,
97        ..Default::default()
98    };
99    let mut b = ChunkBuilder::new();
100    // Hoist function declarations to the top (JS function hoisting).
101    c.hoist_funcs(&mut b, stmts)?;
102    c.compile_stmts(&mut b, stmts)?;
103    Ok(Program {
104        main: b.build(),
105        functions: c.functions,
106        tries: c.tries,
107    })
108}
109
110/// Compile leaving the value of the final top-level expression statement on the
111/// stack (the program's completion value), for `eval`/`vm.runInThisContext`. A
112/// non-expression final statement leaves nothing (→ `undefined`).
113pub fn compile_completion(stmts: &[Stmt], debug: bool) -> Result<Program, String> {
114    let mut c = Compiler {
115        debug,
116        ..Default::default()
117    };
118    let mut b = ChunkBuilder::new();
119    c.hoist_funcs(&mut b, stmts)?;
120    if let Some((last, rest)) = stmts.split_last() {
121        c.compile_stmts(&mut b, rest)?;
122        if let StmtKind::Expr(e) = &last.kind {
123            // The final expression's value is NOT popped — it is the completion.
124            c.compile_expr(&mut b, e)?;
125        } else {
126            c.compile_stmt(&mut b, last)?;
127        }
128    }
129    Ok(Program {
130        main: b.build(),
131        functions: c.functions,
132        tries: c.tries,
133    })
134}
135
136fn argc(n: usize) -> Result<u8, String> {
137    u8::try_from(n).map_err(|_| "too many arguments (>255) for one call".to_string())
138}
139
140impl Compiler {
141    // ── emit helpers ─────────────────────────────────────────────────────
142    fn name_const(&self, b: &mut ChunkBuilder, s: &str) {
143        let k = b.add_constant(Value::str(s));
144        b.emit(Op::LoadConst(k), 0);
145    }
146    fn strlit(&self, b: &mut ChunkBuilder, s: &str) {
147        let k = b.add_constant(Value::str(s));
148        b.emit(Op::LoadConst(k), 0);
149        b.emit(Op::CallBuiltin(ops::MKSTR, 1), 0);
150    }
151    fn tmp_name(&mut self, tag: &str) -> String {
152        let n = format!(".{tag}{}", self.tmp);
153        self.tmp += 1;
154        n
155    }
156
157    /// Emit MKFUNC for a compiled function template and leave the closure on the
158    /// stack.
159    fn emit_mkfunc(&self, b: &mut ChunkBuilder, def_id: usize) {
160        b.emit(Op::LoadInt(def_id as i64), 0);
161        b.emit(Op::CallBuiltin(ops::MKFUNC, 1), 0);
162    }
163
164    fn hoist_funcs(&mut self, b: &mut ChunkBuilder, stmts: &[Stmt]) -> Result<(), String> {
165        for s in stmts {
166            if let StmtKind::FuncDecl {
167                name,
168                params,
169                body,
170                is_generator,
171                is_async,
172            } = &s.kind
173            {
174                let def_id = self.build_function(name, params, body, *is_generator, *is_async)?;
175                self.emit_mkfunc(b, def_id);
176                self.declare(b, &Expr::Ident(name.clone()));
177            }
178        }
179        Ok(())
180    }
181
182    fn compile_stmts(&mut self, b: &mut ChunkBuilder, stmts: &[Stmt]) -> Result<(), String> {
183        for s in stmts {
184            self.compile_stmt(b, s)?;
185        }
186        Ok(())
187    }
188
189    fn compile_stmt(&mut self, b: &mut ChunkBuilder, s: &Stmt) -> Result<(), String> {
190        if self.debug && s.line != 0 {
191            b.emit(Op::LoadInt(s.line as i64), s.line);
192            b.emit(Op::CallBuiltin(ops::DBG_LINE, 1), s.line);
193            b.emit(Op::Pop, s.line);
194        }
195        let line = s.line;
196        match &s.kind {
197            StmtKind::Expr(e) => {
198                self.compile_expr(b, e)?;
199                b.emit(Op::Pop, line);
200            }
201            StmtKind::Empty => {}
202            StmtKind::FuncDecl { .. } => {} // hoisted at block entry
203            StmtKind::ClassDecl(node) => {
204                self.compile_class(b, node)?;
205                // Bind the class to its name in the current scope.
206                if let Some(name) = &node.name {
207                    self.declare(b, &Expr::Ident(name.clone()));
208                } else {
209                    b.emit(Op::Pop, line);
210                }
211            }
212            StmtKind::Decl { decls, .. } => {
213                for d in decls {
214                    match &d.init {
215                        Some(v) => {
216                            self.compile_expr(b, v)?;
217                            // Name inference: `const f = () => {}` / `= function(){}`
218                            // / `= class {}` gives the function/class the name `f`.
219                            if let Expr::Ident(name) = &d.target {
220                                self.infer_name(b, v, name);
221                            }
222                        }
223                        None => {
224                            b.emit(Op::LoadUndef, line);
225                        }
226                    }
227                    self.compile_bind(b, &d.target, true)?;
228                }
229            }
230            StmtKind::Block(body) => {
231                self.hoist_funcs(b, body)?;
232                self.compile_stmts(b, body)?;
233            }
234            StmtKind::If { test, cons, alt } => self.compile_if(b, test, cons, alt)?,
235            StmtKind::While { test, body } => self.compile_while(b, test, body)?,
236            StmtKind::DoWhile { body, test } => self.compile_do_while(b, body, test)?,
237            StmtKind::For {
238                init,
239                test,
240                update,
241                body,
242            } => self.compile_for(b, init, test, update, body)?,
243            StmtKind::ForOf {
244                decl_kind,
245                target,
246                iter,
247                body,
248                is_await,
249            } => {
250                if *is_await {
251                    self.compile_for_await(b, decl_kind.is_some(), target, iter, body)?
252                } else {
253                    self.compile_for_of(b, decl_kind.is_some(), target, iter, body)?
254                }
255            }
256            StmtKind::ForIn {
257                decl_kind,
258                target,
259                object,
260                body,
261            } => self.compile_for_in(b, decl_kind.is_some(), target, object, body)?,
262            StmtKind::Switch { disc, cases } => self.compile_switch(b, disc, cases)?,
263            StmtKind::Return(e) => {
264                match e {
265                    Some(e) => self.compile_expr(b, e)?,
266                    None => {
267                        b.emit(Op::LoadUndef, line);
268                    }
269                }
270                b.emit(Op::CallBuiltin(ops::SIG_RETURN, 1), line);
271            }
272            StmtKind::Labeled { label, body } => self.compile_labeled(b, label, body)?,
273            StmtKind::Break(label) => {
274                let j = b.emit(Op::Jump(0), line);
275                let ctx = match label {
276                    // `break outer`: the nearest enclosing context carrying that label.
277                    Some(name) => self
278                        .loops
279                        .iter_mut()
280                        .rev()
281                        .find(|c| c.label.as_deref() == Some(name.as_str()))
282                        .ok_or_else(|| format!("SyntaxError: Undefined label '{name}'"))?,
283                    None => self
284                        .loops
285                        .last_mut()
286                        .ok_or("SyntaxError: 'break' outside loop")?,
287                };
288                ctx.breaks.push(j);
289            }
290            StmtKind::Continue(label) => {
291                let j = b.emit(Op::Jump(0), line);
292                let ctx = match label {
293                    // `continue outer`: the labeled loop (a label on a non-loop
294                    // cannot catch `continue`).
295                    Some(name) => self
296                        .loops
297                        .iter_mut()
298                        .rev()
299                        .find(|c| c.catches_continue && c.label.as_deref() == Some(name.as_str()))
300                        .ok_or_else(|| {
301                            format!("SyntaxError: Undefined label '{name}' for continue")
302                        })?,
303                    None => self
304                        .loops
305                        .iter_mut()
306                        .rev()
307                        .find(|c| c.catches_continue)
308                        .ok_or("SyntaxError: 'continue' outside loop")?,
309                };
310                ctx.continues.push(j);
311            }
312            StmtKind::Throw(e) => {
313                self.compile_expr(b, e)?;
314                b.emit(Op::CallBuiltin(ops::THROW, 1), line);
315            }
316            StmtKind::Try {
317                block,
318                handler,
319                finalizer,
320            } => self.compile_try(b, block, handler, finalizer)?,
321        }
322        Ok(())
323    }
324
325    // ── binding / assignment ─────────────────────────────────────────────
326    /// Store the value on top of the stack into `target`. `declare` chooses
327    /// `DECLARE` (new binding) vs `SETLOCAL` (existing binding / global).
328    fn compile_bind(
329        &mut self,
330        b: &mut ChunkBuilder,
331        target: &Expr,
332        declare: bool,
333    ) -> Result<(), String> {
334        match target {
335            Expr::Ident(_) => {
336                if declare {
337                    self.declare(b, target);
338                } else {
339                    self.store_simple(b, target)?;
340                }
341            }
342            Expr::Member { .. } | Expr::Index { .. } => {
343                self.store_simple(b, target)?;
344            }
345            Expr::Array(items) => self.destructure_array(b, items, declare)?,
346            Expr::Object(props) => self.destructure_object(b, props, declare)?,
347            Expr::Assign { target, value } => {
348                // Pattern element with a default: use it when TOS is undefined.
349                b.emit(Op::Dup, 0);
350                b.emit(Op::LoadUndef, 0);
351                b.emit(Op::CallBuiltin(ops::STRICT_EQ, 2), 0);
352                let jf = b.emit(Op::JumpIfFalse(0), 0);
353                b.emit(Op::Pop, 0); // drop the undefined
354                self.compile_expr(b, value)?;
355                let end = b.current_pos();
356                b.patch_jump(jf, end);
357                self.compile_bind(b, target, declare)?;
358            }
359            _ => return Err("SyntaxError: invalid assignment target".into()),
360        }
361        Ok(())
362    }
363
364    /// Emit a `DECLARE` of a simple name binding, consuming TOS value.
365    fn declare(&self, b: &mut ChunkBuilder, target: &Expr) {
366        if let Expr::Ident(n) = target {
367            self.name_const(b, n);
368            b.emit(Op::Swap, 0);
369            b.emit(Op::CallBuiltin(ops::DECLARE, 2), 0);
370            b.emit(Op::Pop, 0);
371        }
372    }
373
374    /// Store TOS into an lvalue (Ident/Member/Index), leaving nothing.
375    fn store_simple(&mut self, b: &mut ChunkBuilder, target: &Expr) -> Result<(), String> {
376        match target {
377            Expr::Ident(n) => {
378                self.name_const(b, n);
379                b.emit(Op::Swap, 0);
380                b.emit(Op::CallBuiltin(ops::SETLOCAL, 2), 0);
381                b.emit(Op::Pop, 0);
382            }
383            Expr::Member {
384                object, property, ..
385            } => {
386                self.compile_expr(b, object)?; // [value, recv]
387                self.name_const(b, property); // [value, recv, name]
388                b.emit(Op::Rot, 0); // [recv, name, value]
389                b.emit(Op::CallBuiltin(ops::SETATTR, 3), 0);
390                b.emit(Op::Pop, 0);
391            }
392            Expr::Index { object, index, .. } => {
393                self.compile_expr(b, object)?; // [value, recv]
394                self.compile_expr(b, index)?; // [value, recv, idx]
395                b.emit(Op::Rot, 0); // [recv, idx, value]
396                b.emit(Op::CallBuiltin(ops::SETITEM, 3), 0);
397                b.emit(Op::Pop, 0);
398            }
399            _ => return Err("SyntaxError: invalid assignment target".into()),
400        }
401        Ok(())
402    }
403
404    fn destructure_array(
405        &mut self,
406        b: &mut ChunkBuilder,
407        items: &[Expr],
408        declare: bool,
409    ) -> Result<(), String> {
410        let star_idx = items
411            .iter()
412            .position(|e| matches!(e, Expr::Spread(_)))
413            .map(|i| i as i64)
414            .unwrap_or(-1);
415        b.emit(Op::LoadInt(items.len() as i64), 0);
416        b.emit(Op::LoadInt(star_idx), 0);
417        b.emit(Op::CallBuiltin(ops::UNPACK, 3), 0); // pushes items[0]..items[n-1], items[0] on top
418        for it in items {
419            match it {
420                Expr::Undefined => {
421                    b.emit(Op::Pop, 0); // hole
422                }
423                Expr::Spread(inner) => self.compile_bind(b, inner, declare)?,
424                _ => self.compile_bind(b, it, declare)?,
425            }
426        }
427        Ok(())
428    }
429
430    fn destructure_object(
431        &mut self,
432        b: &mut ChunkBuilder,
433        props: &[Prop],
434        declare: bool,
435    ) -> Result<(), String> {
436        // Object value on TOS; keep it, read each key, bind, then drop.
437        let obj_tmp = self.tmp_name("destr");
438        self.name_const(b, &obj_tmp);
439        b.emit(Op::Swap, 0);
440        b.emit(Op::CallBuiltin(ops::DECLARE, 2), 0);
441        b.emit(Op::Pop, 0);
442        // Collect statically-known destructured key names, for a `...rest`.
443        let mut named: Vec<String> = Vec::new();
444        for p in props {
445            match p {
446                Prop::KeyValue { key, value, .. } => {
447                    if let Expr::Str(s) = key {
448                        named.push(s.clone());
449                    }
450                    // Load obj, read key.
451                    self.load_local(b, &obj_tmp);
452                    self.compile_expr(b, key)?;
453                    b.emit(Op::CallBuiltin(ops::GETITEM, 2), 0); // [value]
454                    self.compile_bind(b, value, declare)?;
455                }
456                Prop::Spread(target) => {
457                    self.load_local(b, &obj_tmp);
458                    for k in &named {
459                        self.strlit(b, k);
460                    }
461                    b.emit(Op::CallBuiltin(ops::MKARR, argc(named.len())?), 0);
462                    b.emit(Op::CallBuiltin(ops::OBJ_REST, 2), 0); // [rest_object]
463                    self.compile_bind(b, target, declare)?;
464                }
465                // Accessors never appear in a destructuring pattern.
466                Prop::Accessor { .. } => {}
467            }
468        }
469        Ok(())
470    }
471
472    fn load_local(&self, b: &mut ChunkBuilder, name: &str) {
473        self.name_const(b, name);
474        b.emit(Op::CallBuiltin(ops::GETLOCAL, 1), 0);
475    }
476
477    // ── control flow ─────────────────────────────────────────────────────
478    fn compile_condition(&mut self, b: &mut ChunkBuilder, e: &Expr) -> Result<(), String> {
479        self.compile_expr(b, e)?;
480        b.emit(Op::CallBuiltin(ops::TRUTHY, 1), 0);
481        Ok(())
482    }
483
484    fn compile_if(
485        &mut self,
486        b: &mut ChunkBuilder,
487        test: &Expr,
488        cons: &Stmt,
489        alt: &Option<Box<Stmt>>,
490    ) -> Result<(), String> {
491        self.compile_condition(b, test)?;
492        let jfalse = b.emit(Op::JumpIfFalse(0), 0);
493        self.compile_stmt(b, cons)?;
494        if let Some(alt) = alt {
495            let jend = b.emit(Op::Jump(0), 0);
496            let else_start = b.current_pos();
497            b.patch_jump(jfalse, else_start);
498            self.compile_stmt(b, alt)?;
499            let end = b.current_pos();
500            b.patch_jump(jend, end);
501        } else {
502            let end = b.current_pos();
503            b.patch_jump(jfalse, end);
504        }
505        Ok(())
506    }
507
508    /// `label: stmt`. If the body is a loop, the label rides into that loop's
509    /// `LoopCtx` (so labeled `break`/`continue` target it); otherwise a break-only
510    /// context spans the body so `break label` can jump past it.
511    fn compile_labeled(
512        &mut self,
513        b: &mut ChunkBuilder,
514        label: &str,
515        body: &Stmt,
516    ) -> Result<(), String> {
517        if matches!(
518            body.kind,
519            StmtKind::While { .. }
520                | StmtKind::DoWhile { .. }
521                | StmtKind::For { .. }
522                | StmtKind::ForOf { .. }
523                | StmtKind::ForIn { .. }
524        ) {
525            self.pending_label = Some(label.to_string());
526            self.compile_stmt(b, body)?;
527            // The loop claimed it; clear any residue defensively.
528            self.pending_label = None;
529        } else {
530            self.loops.push(LoopCtx {
531                breaks: Vec::new(),
532                continues: Vec::new(),
533                catches_continue: false,
534                label: Some(label.to_string()),
535            });
536            self.compile_stmt(b, body)?;
537            let ctx = self.loops.pop().unwrap();
538            let end = b.current_pos();
539            for br in ctx.breaks {
540                b.patch_jump(br, end);
541            }
542        }
543        Ok(())
544    }
545
546    fn compile_while(
547        &mut self,
548        b: &mut ChunkBuilder,
549        test: &Expr,
550        body: &Stmt,
551    ) -> Result<(), String> {
552        let start = b.current_pos();
553        self.compile_condition(b, test)?;
554        let jfalse = b.emit(Op::JumpIfFalse(0), 0);
555        self.loops.push(LoopCtx {
556            breaks: Vec::new(),
557            continues: Vec::new(),
558            catches_continue: true,
559            label: self.pending_label.take(),
560        });
561        self.compile_stmt(b, body)?;
562        b.emit(Op::Jump(start), 0);
563        let ctx = self.loops.pop().unwrap();
564        for c in ctx.continues {
565            b.patch_jump(c, start);
566        }
567        let end = b.current_pos();
568        b.patch_jump(jfalse, end);
569        for br in ctx.breaks {
570            b.patch_jump(br, end);
571        }
572        Ok(())
573    }
574
575    fn compile_do_while(
576        &mut self,
577        b: &mut ChunkBuilder,
578        body: &Stmt,
579        test: &Expr,
580    ) -> Result<(), String> {
581        let start = b.current_pos();
582        self.loops.push(LoopCtx {
583            breaks: Vec::new(),
584            continues: Vec::new(),
585            catches_continue: true,
586            label: self.pending_label.take(),
587        });
588        self.compile_stmt(b, body)?;
589        let cont_target = b.current_pos();
590        self.compile_condition(b, test)?;
591        b.emit(Op::JumpIfTrue(start), 0);
592        let ctx = self.loops.pop().unwrap();
593        for c in ctx.continues {
594            b.patch_jump(c, cont_target);
595        }
596        let end = b.current_pos();
597        for br in ctx.breaks {
598            b.patch_jump(br, end);
599        }
600        Ok(())
601    }
602
603    fn compile_for(
604        &mut self,
605        b: &mut ChunkBuilder,
606        init: &Option<Box<Stmt>>,
607        test: &Option<Expr>,
608        update: &Option<Expr>,
609        body: &Stmt,
610    ) -> Result<(), String> {
611        if let Some(init) = init {
612            self.compile_stmt(b, init)?;
613        }
614        let start = b.current_pos();
615        let jfalse = match test {
616            Some(t) => {
617                self.compile_condition(b, t)?;
618                Some(b.emit(Op::JumpIfFalse(0), 0))
619            }
620            None => None,
621        };
622        self.loops.push(LoopCtx {
623            breaks: Vec::new(),
624            continues: Vec::new(),
625            catches_continue: true,
626            label: self.pending_label.take(),
627        });
628        self.compile_stmt(b, body)?;
629        let cont_target = b.current_pos();
630        if let Some(u) = update {
631            self.compile_expr(b, u)?;
632            b.emit(Op::Pop, 0);
633        }
634        b.emit(Op::Jump(start), 0);
635        let ctx = self.loops.pop().unwrap();
636        for c in ctx.continues {
637            b.patch_jump(c, cont_target);
638        }
639        let end = b.current_pos();
640        if let Some(jf) = jfalse {
641            b.patch_jump(jf, end);
642        }
643        for br in ctx.breaks {
644            b.patch_jump(br, end);
645        }
646        Ok(())
647    }
648
649    fn compile_for_of(
650        &mut self,
651        b: &mut ChunkBuilder,
652        declare: bool,
653        target: &Expr,
654        iter: &Expr,
655        body: &Stmt,
656    ) -> Result<(), String> {
657        self.compile_expr(b, iter)?;
658        b.emit(Op::CallBuiltin(ops::GETITER, 1), 0); // [iterator]
659        self.loop_over(b, declare, target, body)
660    }
661
662    fn compile_for_in(
663        &mut self,
664        b: &mut ChunkBuilder,
665        declare: bool,
666        target: &Expr,
667        object: &Expr,
668        body: &Stmt,
669    ) -> Result<(), String> {
670        self.compile_expr(b, object)?;
671        b.emit(Op::CallBuiltin(ops::FORIN_KEYS, 1), 0); // [keys_array]
672        b.emit(Op::CallBuiltin(ops::GETITER, 1), 0); // [iterator]
673        self.loop_over(b, declare, target, body)
674    }
675
676    /// `for await (target of iterable) body`. Obtains an async iterator, then each
677    /// pass `await`s a `{value, done}` step (a native async iterator's promise, or
678    /// the sync fallback's per-value await). The iterator lives in a temp local.
679    fn compile_for_await(
680        &mut self,
681        b: &mut ChunkBuilder,
682        declare: bool,
683        target: &Expr,
684        iter: &Expr,
685        body: &Stmt,
686    ) -> Result<(), String> {
687        let iter_tmp = self.tmp_name("aiter");
688        self.compile_expr(b, iter)?;
689        b.emit(Op::CallBuiltin(ops::GET_ASYNC_ITER, 1), 0); // [iterator]
690        self.name_const(b, &iter_tmp);
691        b.emit(Op::Swap, 0);
692        b.emit(Op::CallBuiltin(ops::DECLARE, 2), 0);
693        b.emit(Op::Pop, 0);
694        let start = b.current_pos();
695        // step = await ASYNC_STEP(iterator)  -> {value, done}
696        self.load_local(b, &iter_tmp);
697        b.emit(Op::CallBuiltin(ops::ASYNC_STEP, 1), 0); // [stepPromise]
698        b.emit(Op::CallBuiltin(ops::AWAIT, 1), 0); // [step]
699        let step_tmp = self.tmp_name("astep");
700        self.name_const(b, &step_tmp);
701        b.emit(Op::Swap, 0);
702        b.emit(Op::CallBuiltin(ops::DECLARE, 2), 0);
703        b.emit(Op::Pop, 0);
704        // if (step.done) break
705        self.load_local(b, &step_tmp);
706        self.name_const(b, "done");
707        b.emit(Op::CallBuiltin(ops::GETATTR, 2), 0);
708        b.emit(Op::CallBuiltin(ops::TRUTHY, 1), 0);
709        let jdone = b.emit(Op::JumpIfTrue(0), 0);
710        // target = step.value
711        self.load_local(b, &step_tmp);
712        self.name_const(b, "value");
713        b.emit(Op::CallBuiltin(ops::GETATTR, 2), 0); // [value]
714        self.compile_bind(b, target, declare)?;
715        self.loops.push(LoopCtx {
716            breaks: Vec::new(),
717            continues: Vec::new(),
718            catches_continue: true,
719            label: self.pending_label.take(),
720        });
721        self.compile_stmt(b, body)?;
722        b.emit(Op::Jump(start), 0);
723        let ctx = self.loops.pop().unwrap();
724        for c in ctx.continues {
725            b.patch_jump(c, start);
726        }
727        let end = b.current_pos();
728        b.patch_jump(jdone, end);
729        for br in ctx.breaks {
730            b.patch_jump(br, end);
731        }
732        Ok(())
733    }
734
735    /// Shared loop tail for for-of / for-in: iterator on TOS.
736    fn loop_over(
737        &mut self,
738        b: &mut ChunkBuilder,
739        declare: bool,
740        target: &Expr,
741        body: &Stmt,
742    ) -> Result<(), String> {
743        let start = b.current_pos();
744        b.emit(Op::CallBuiltin(ops::FORITER, 0), 0); // [iterator, value, has_next]
745        let jdone = b.emit(Op::JumpIfFalse(0), 0); // pops has_next
746        self.compile_bind(b, target, declare)?; // consumes value -> [iterator]
747        self.loops.push(LoopCtx {
748            breaks: Vec::new(),
749            continues: Vec::new(),
750            catches_continue: true,
751            label: self.pending_label.take(),
752        });
753        self.compile_stmt(b, body)?;
754        b.emit(Op::Jump(start), 0);
755        let ctx = self.loops.pop().unwrap();
756        for c in ctx.continues {
757            b.patch_jump(c, start);
758        }
759        let done = b.current_pos();
760        b.patch_jump(jdone, done);
761        b.emit(Op::Pop, 0); // drop iterator
762        let jafter = b.emit(Op::Jump(0), 0);
763        let break_target = b.current_pos();
764        // `break` out of a for-of closes the iterator (runs a generator's pending
765        // `finally` / calls a user iterator's `.return()`), then drops it.
766        b.emit(Op::CallBuiltin(ops::ITER_CLOSE, 1), 0);
767        let end = b.current_pos();
768        b.patch_jump(jafter, end);
769        for br in ctx.breaks {
770            b.patch_jump(br, break_target);
771        }
772        Ok(())
773    }
774
775    fn compile_switch(
776        &mut self,
777        b: &mut ChunkBuilder,
778        disc: &Expr,
779        cases: &[SwitchCase],
780    ) -> Result<(), String> {
781        let disc_tmp = self.tmp_name("switch");
782        self.compile_expr(b, disc)?;
783        self.name_const(b, &disc_tmp);
784        b.emit(Op::Swap, 0);
785        b.emit(Op::CallBuiltin(ops::DECLARE, 2), 0);
786        b.emit(Op::Pop, 0);
787        // Emit the test chain: `if (disc === caseTest) goto bodyN`.
788        let mut body_jumps: Vec<Option<usize>> = Vec::new();
789        let mut default_idx: Option<usize> = None;
790        for (i, case) in cases.iter().enumerate() {
791            match &case.test {
792                Some(t) => {
793                    self.load_local(b, &disc_tmp);
794                    self.compile_expr(b, t)?;
795                    b.emit(Op::CallBuiltin(ops::STRICT_EQ, 2), 0);
796                    let j = b.emit(Op::JumpIfTrue(0), 0);
797                    body_jumps.push(Some(j));
798                }
799                None => {
800                    default_idx = Some(i);
801                    body_jumps.push(None);
802                }
803            }
804        }
805        // No test matched: jump to default (if any) or end.
806        let no_match_jump = b.emit(Op::Jump(0), 0);
807        self.loops.push(LoopCtx {
808            breaks: Vec::new(),
809            continues: Vec::new(),
810            catches_continue: false,
811            label: None,
812        });
813        let mut body_starts: Vec<usize> = Vec::new();
814        for case in cases {
815            body_starts.push(b.current_pos());
816            self.compile_stmts(b, &case.body)?;
817        }
818        let end = b.current_pos();
819        // Patch each case test-jump to its body start.
820        for (i, j) in body_jumps.iter().enumerate() {
821            if let Some(j) = j {
822                b.patch_jump(*j, body_starts[i]);
823            }
824        }
825        match default_idx {
826            Some(i) => b.patch_jump(no_match_jump, body_starts[i]),
827            None => b.patch_jump(no_match_jump, end),
828        }
829        let ctx = self.loops.pop().unwrap();
830        for br in ctx.breaks {
831            b.patch_jump(br, end);
832        }
833        Ok(())
834    }
835
836    fn compile_try(
837        &mut self,
838        b: &mut ChunkBuilder,
839        block: &[Stmt],
840        handler: &Option<(Option<Expr>, Vec<Stmt>)>,
841        finalizer: &Option<Vec<Stmt>>,
842    ) -> Result<(), String> {
843        let block_chunk = self.compile_block_chunk(block)?;
844        let handler_def = match handler {
845            Some((param, body)) => {
846                let param_name = match param {
847                    Some(Expr::Ident(n)) => Some(n.clone()),
848                    _ => None,
849                };
850                let hbody = self.compile_block_chunk(body)?;
851                Some((param_name, hbody))
852            }
853            None => None,
854        };
855        let final_chunk = match finalizer {
856            Some(f) => Some(self.compile_block_chunk(f)?),
857            None => None,
858        };
859        let id = self.tries.len();
860        self.tries.push(TryDef {
861            block: block_chunk,
862            handler: handler_def,
863            finalizer: final_chunk,
864        });
865        b.emit(Op::LoadInt(id as i64), 0);
866        b.emit(Op::CallBuiltin(ops::TRY, 1), 0);
867        b.emit(Op::Pop, 0);
868        Ok(())
869    }
870
871    fn compile_block_chunk(&mut self, stmts: &[Stmt]) -> Result<Chunk, String> {
872        let mut cb = ChunkBuilder::new();
873        self.hoist_funcs(&mut cb, stmts)?;
874        self.compile_stmts(&mut cb, stmts)?;
875        Ok(cb.build())
876    }
877
878    // ── functions ────────────────────────────────────────────────────────
879    fn build_function(
880        &mut self,
881        name: &str,
882        params: &[Param],
883        body: &[Stmt],
884        is_generator: bool,
885        is_async: bool,
886    ) -> Result<usize, String> {
887        let (slots, prologue) = self.lower_params(params)?;
888        let mut fb = ChunkBuilder::new();
889        // Function-body function hoisting.
890        self.hoist_funcs(&mut fb, &prologue)?;
891        self.hoist_funcs(&mut fb, body)?;
892        self.compile_stmts(&mut fb, &prologue)?;
893        self.compile_stmts(&mut fb, body)?;
894        let def = FuncDef {
895            name: name.to_string(),
896            params: slots,
897            chunk: fb.build(),
898            is_arrow: false,
899            is_generator,
900            is_async,
901        };
902        self.functions.push((name.to_string(), def));
903        Ok(self.functions.len() - 1)
904    }
905
906    fn build_arrow(
907        &mut self,
908        params: &[Param],
909        body: &FnBody,
910        is_async: bool,
911    ) -> Result<usize, String> {
912        let stmts = match body {
913            FnBody::Block(b) => b.clone(),
914            FnBody::Expr(e) => vec![Stmt::from(StmtKind::Return(Some((**e).clone())))],
915        };
916        let id = self.build_function("", params, &stmts, false, is_async)?;
917        // Mark the template as an arrow so `this` is captured lexically.
918        self.functions[id].1.is_arrow = true;
919        Ok(id)
920    }
921
922    // ── classes ──────────────────────────────────────────────────────────
923    /// Lower a `class` to runtime builder ops, leaving the class value on the
924    /// stack: `MKCLASS` (name, parent, ctor) then `DEF_MEMBER`/`DEF_FIELD` for
925    /// each member (each keeps the class on the stack).
926    fn compile_class(&mut self, b: &mut ChunkBuilder, node: &ClassNode) -> Result<(), String> {
927        let cname = node.name.clone().unwrap_or_default();
928        // Push name, parent (or undefined), constructor (or undefined).
929        self.name_const(b, &cname);
930        match &node.parent {
931            Some(p) => self.compile_expr(b, p)?,
932            None => {
933                b.emit(Op::LoadUndef, 0);
934            }
935        }
936        let ctor = node
937            .members
938            .iter()
939            .find(|m| m.kind == MemberKind::Constructor);
940        match ctor {
941            Some(m) => {
942                let def_id = self.build_function(&cname, &m.params, &m.body, false, false)?;
943                self.emit_mkfunc(b, def_id);
944            }
945            None => {
946                b.emit(Op::LoadUndef, 0);
947            }
948        }
949        b.emit(Op::CallBuiltin(ops::MKCLASS, 3), 0); // -> [class]
950
951        for m in &node.members {
952            match m.kind {
953                MemberKind::Constructor => {}
954                MemberKind::Field if m.is_static => {
955                    // A static field is evaluated once at class-definition time and
956                    // set as an own property of the constructor: `[class]` stays on
957                    // the stack, `Dup` it as the SETATTR receiver.
958                    b.emit(Op::Dup, 0); // [class, class]
959                    self.emit_member_key(b, m)?; // [class, class, name]
960                    match &m.field_init {
961                        Some(e) => self.compile_expr(b, e)?,
962                        None => {
963                            b.emit(Op::LoadUndef, 0);
964                        }
965                    }
966                    // [class, class, name, val] -> SETATTR sets on the class -> [class, val]
967                    b.emit(Op::CallBuiltin(ops::SETATTR, 3), 0);
968                    b.emit(Op::Pop, 0); // drop the returned value -> [class]
969                }
970                MemberKind::Field => {
971                    // [class] name thunk -> DEF_FIELD -> [class]
972                    self.emit_member_key(b, m)?;
973                    let init = m.field_init.clone().unwrap_or(Expr::Undefined);
974                    let stmts = vec![Stmt::from(StmtKind::Return(Some(init)))];
975                    let def_id = self.build_function("", &[], &stmts, false, false)?;
976                    self.emit_mkfunc(b, def_id);
977                    b.emit(Op::CallBuiltin(ops::DEF_FIELD, 3), 0);
978                }
979                MemberKind::Method | MemberKind::Get | MemberKind::Set => {
980                    // [class] name kind static fn -> DEF_MEMBER -> [class]
981                    self.emit_member_key(b, m)?;
982                    let kind = match m.kind {
983                        MemberKind::Get => member::GET,
984                        MemberKind::Set => member::SET,
985                        _ => member::METHOD,
986                    };
987                    b.emit(Op::LoadInt(kind), 0);
988                    b.emit(
989                        if m.is_static {
990                            Op::LoadTrue
991                        } else {
992                            Op::LoadFalse
993                        },
994                        0,
995                    );
996                    let mname = match &m.key {
997                        Expr::Str(s) if !m.computed => s.clone(),
998                        _ => String::new(),
999                    };
1000                    let def_id = self.build_function(
1001                        &mname,
1002                        &m.params,
1003                        &m.body,
1004                        m.is_generator,
1005                        m.is_async,
1006                    )?;
1007                    self.emit_mkfunc(b, def_id);
1008                    b.emit(Op::CallBuiltin(ops::DEF_MEMBER, 5), 0);
1009                }
1010            }
1011        }
1012        Ok(())
1013    }
1014
1015    /// If `init` is an anonymous function/arrow/class (value already on TOS), set
1016    /// its `.name` to `name` (JS binding name-inference). No-op otherwise.
1017    fn infer_name(&mut self, b: &mut ChunkBuilder, init: &Expr, name: &str) {
1018        let anon = matches!(init, Expr::Function { name: None, .. } | Expr::Class(_))
1019            && !matches!(init, Expr::Class(node) if node.name.is_some());
1020        if !anon {
1021            return;
1022        }
1023        // [fn] Dup; .name = name; drop the SETATTR result.
1024        b.emit(Op::Dup, 0);
1025        self.name_const(b, "name");
1026        self.strlit(b, name);
1027        b.emit(Op::CallBuiltin(ops::SETATTR, 3), 0);
1028        b.emit(Op::Pop, 0);
1029    }
1030
1031    /// Push a class/object member's property key: a computed expression coerced
1032    /// via `PROPKEY` (Symbol-aware), or a static name constant.
1033    fn emit_member_key(&mut self, b: &mut ChunkBuilder, m: &ClassMember) -> Result<(), String> {
1034        if m.computed {
1035            self.compile_expr(b, &m.key)?;
1036            b.emit(Op::CallBuiltin(ops::PROPKEY, 1), 0);
1037        } else if let Expr::Str(s) = &m.key {
1038            self.name_const(b, s);
1039        } else {
1040            self.compile_expr(b, &m.key)?;
1041            b.emit(Op::CallBuiltin(ops::PROPKEY, 1), 0);
1042        }
1043        Ok(())
1044    }
1045
1046    // ── generators / yield ───────────────────────────────────────────────
1047    fn compile_yield(
1048        &mut self,
1049        b: &mut ChunkBuilder,
1050        arg: &Option<Box<Expr>>,
1051        delegate: bool,
1052    ) -> Result<(), String> {
1053        if delegate {
1054            // `yield* iterable`: iterate, yielding each element.
1055            match arg {
1056                Some(e) => self.compile_expr(b, e)?,
1057                None => {
1058                    b.emit(Op::LoadUndef, 0);
1059                }
1060            }
1061            b.emit(Op::CallBuiltin(ops::GETITER, 1), 0); // [iterator]
1062            let start = b.current_pos();
1063            b.emit(Op::CallBuiltin(ops::FORITER, 0), 0); // [iterator, value, has_next]
1064            let jdone = b.emit(Op::JumpIfFalse(0), 0);
1065            b.emit(Op::CallBuiltin(ops::YIELD, 1), 0); // yield the value -> [iterator, sent]
1066            b.emit(Op::Pop, 0); // drop the sent value
1067            b.emit(Op::Jump(start), 0);
1068            let done = b.current_pos();
1069            b.patch_jump(jdone, done);
1070            b.emit(Op::Pop, 0); // drop the iterator
1071            b.emit(Op::LoadUndef, 0); // `yield*` evaluates to the delegate's return
1072        } else {
1073            match arg {
1074                Some(e) => self.compile_expr(b, e)?,
1075                None => {
1076                    b.emit(Op::LoadUndef, 0);
1077                }
1078            }
1079            // YIELD suspends and leaves the value sent by `.next(x)` on the stack.
1080            b.emit(Op::CallBuiltin(ops::YIELD, 1), 0);
1081        }
1082        Ok(())
1083    }
1084
1085    /// Lower a formal-parameter list into simple slots plus prologue statements
1086    /// (defaults + destructuring), executed at the top of the body.
1087    fn lower_params(&mut self, params: &[Param]) -> Result<(Vec<ParamSlot>, Vec<Stmt>), String> {
1088        let mut slots = Vec::new();
1089        let mut prologue: Vec<Stmt> = Vec::new();
1090        for (i, p) in params.iter().enumerate() {
1091            if p.rest {
1092                let name = match &p.pattern {
1093                    Expr::Ident(n) => n.clone(),
1094                    _ => return Err("SyntaxError: rest parameter must be an identifier".into()),
1095                };
1096                slots.push(ParamSlot {
1097                    name,
1098                    rest: true,
1099                    has_default: false,
1100                });
1101                continue;
1102            }
1103            match &p.pattern {
1104                Expr::Ident(name) => {
1105                    slots.push(ParamSlot {
1106                        name: name.clone(),
1107                        rest: false,
1108                        has_default: p.default.is_some(),
1109                    });
1110                    if let Some(d) = &p.default {
1111                        prologue.push(default_stmt(name, d));
1112                    }
1113                }
1114                pattern => {
1115                    let synth = format!(".param{i}");
1116                    slots.push(ParamSlot {
1117                        name: synth.clone(),
1118                        rest: false,
1119                        has_default: p.default.is_some(),
1120                    });
1121                    if let Some(d) = &p.default {
1122                        prologue.push(default_stmt(&synth, d));
1123                    }
1124                    prologue.push(Stmt::from(StmtKind::Decl {
1125                        kind: DeclKind::Let,
1126                        decls: vec![Declarator {
1127                            target: pattern.clone(),
1128                            init: Some(Expr::Ident(synth)),
1129                        }],
1130                    }));
1131                }
1132            }
1133        }
1134        Ok((slots, prologue))
1135    }
1136
1137    // ── expressions ──────────────────────────────────────────────────────
1138    fn compile_expr(&mut self, b: &mut ChunkBuilder, e: &Expr) -> Result<(), String> {
1139        match e {
1140            Expr::Undefined => {
1141                b.emit(Op::LoadUndef, 0);
1142            }
1143            Expr::Null => {
1144                b.emit(Op::CallBuiltin(ops::LOAD_NULL, 0), 0);
1145            }
1146            Expr::True => {
1147                b.emit(Op::LoadTrue, 0);
1148            }
1149            Expr::False => {
1150                b.emit(Op::LoadFalse, 0);
1151            }
1152            Expr::Number(n) => {
1153                b.emit(Op::LoadFloat(*n), 0);
1154            }
1155            Expr::BigInt(digits) => {
1156                // The canonical decimal digit string travels as a native constant;
1157                // MKBIGINT parses it into a heap BigInt at runtime.
1158                let k = b.add_constant(Value::str(digits));
1159                b.emit(Op::LoadConst(k), 0);
1160                b.emit(Op::CallBuiltin(ops::MKBIGINT, 1), 0);
1161            }
1162            Expr::Regex(pat, flags) => {
1163                let kp = b.add_constant(Value::str(pat));
1164                b.emit(Op::LoadConst(kp), 0);
1165                let kf = b.add_constant(Value::str(flags));
1166                b.emit(Op::LoadConst(kf), 0);
1167                b.emit(Op::CallBuiltin(ops::MKREGEX, 2), 0);
1168            }
1169            Expr::Str(s) => self.strlit(b, s),
1170            Expr::Template { quasis, exprs } => self.compile_template(b, quasis, exprs)?,
1171            Expr::TaggedTemplate {
1172                tag,
1173                quasis,
1174                raws,
1175                exprs,
1176            } => self.compile_tagged_template(b, tag, quasis, raws, exprs)?,
1177            Expr::Ident(n) => self.load_local(b, n),
1178            Expr::This => {
1179                b.emit(Op::CallBuiltin(ops::THIS, 0), 0);
1180            }
1181            Expr::Array(items) => self.compile_array(b, items)?,
1182            Expr::Object(props) => self.compile_object(b, props)?,
1183            Expr::Spread(inner) => self.compile_expr(b, inner)?,
1184            Expr::Logical(op, l, r) => self.compile_logical(b, *op, l, r)?,
1185            Expr::Unary(op, e) => self.compile_unary(b, *op, e)?,
1186            Expr::Binary(op, l, r) => self.compile_binary(b, *op, l, r)?,
1187            Expr::Conditional { test, cons, alt } => {
1188                self.compile_condition(b, test)?;
1189                let jf = b.emit(Op::JumpIfFalse(0), 0);
1190                self.compile_expr(b, cons)?;
1191                let je = b.emit(Op::Jump(0), 0);
1192                let els = b.current_pos();
1193                b.patch_jump(jf, els);
1194                self.compile_expr(b, alt)?;
1195                let end = b.current_pos();
1196                b.patch_jump(je, end);
1197            }
1198            Expr::Assign { target, value } => {
1199                self.compile_expr(b, value)?;
1200                b.emit(Op::Dup, 0); // assignment yields the value
1201                self.compile_bind(b, target, false)?;
1202            }
1203            Expr::Update { op, prefix, target } => self.compile_update(b, *op, *prefix, target)?,
1204            Expr::Call {
1205                func,
1206                args,
1207                optional,
1208            } => self.compile_call(b, func, args, *optional)?,
1209            Expr::New { callee, args } => self.compile_new(b, callee, args)?,
1210            Expr::Member {
1211                object,
1212                property,
1213                optional,
1214            } => self.compile_member(b, object, property, *optional)?,
1215            Expr::Index {
1216                object,
1217                index,
1218                optional,
1219            } => self.compile_index(b, object, index, *optional)?,
1220            Expr::Function {
1221                params,
1222                body,
1223                is_arrow,
1224                name,
1225                is_generator,
1226                is_async,
1227            } => {
1228                let def_id = if *is_arrow {
1229                    self.build_arrow(params, body, *is_async)?
1230                } else {
1231                    let n = name.clone().unwrap_or_default();
1232                    let stmts = match body {
1233                        FnBody::Block(b) => b.clone(),
1234                        FnBody::Expr(e) => vec![Stmt::from(StmtKind::Return(Some((**e).clone())))],
1235                    };
1236                    self.build_function(&n, params, &stmts, *is_generator, *is_async)?
1237                };
1238                self.emit_mkfunc(b, def_id);
1239            }
1240            Expr::Class(node) => self.compile_class(b, node)?,
1241            Expr::Super => {
1242                // Bare `super` only appears as a call/member callee, handled by
1243                // compile_call / compile_member; a stray `super` yields undefined.
1244                b.emit(Op::LoadUndef, 0);
1245            }
1246            Expr::NewTarget => {
1247                b.emit(Op::CallBuiltin(ops::NEW_TARGET, 0), 0);
1248            }
1249            Expr::Yield { arg, delegate } => self.compile_yield(b, arg, *delegate)?,
1250            Expr::Await(inner) => {
1251                self.compile_expr(b, inner)?;
1252                b.emit(Op::CallBuiltin(ops::AWAIT, 1), 0);
1253            }
1254            Expr::Sequence(items) => {
1255                for (i, it) in items.iter().enumerate() {
1256                    self.compile_expr(b, it)?;
1257                    if i + 1 < items.len() {
1258                        b.emit(Op::Pop, 0);
1259                    }
1260                }
1261            }
1262        }
1263        Ok(())
1264    }
1265
1266    fn compile_template(
1267        &mut self,
1268        b: &mut ChunkBuilder,
1269        quasis: &[String],
1270        exprs: &[Expr],
1271    ) -> Result<(), String> {
1272        let mut n = 0;
1273        for (i, q) in quasis.iter().enumerate() {
1274            let k = b.add_constant(Value::str(q));
1275            b.emit(Op::LoadConst(k), 0);
1276            n += 1;
1277            if i < exprs.len() {
1278                self.compile_expr(b, &exprs[i])?;
1279                b.emit(Op::CallBuiltin(ops::TOSTR, 1), 0);
1280                n += 1;
1281            }
1282        }
1283        b.emit(Op::CallBuiltin(ops::MKSTR, argc(n)?), 0);
1284        Ok(())
1285    }
1286
1287    /// Lower a tagged template to `TAG_TMPL`. Operand layout (matching
1288    /// `builtins::b_tag_tmpl`): `[tag, n, m, cooked×n, raw×n, values×m]`, where
1289    /// `n = quasis.len()` and `m = exprs.len()` (`n == m + 1`).
1290    fn compile_tagged_template(
1291        &mut self,
1292        b: &mut ChunkBuilder,
1293        tag: &Expr,
1294        quasis: &[String],
1295        raws: &[String],
1296        exprs: &[Expr],
1297    ) -> Result<(), String> {
1298        self.compile_expr(b, tag)?;
1299        let n = quasis.len();
1300        let m = exprs.len();
1301        b.emit(Op::LoadInt(n as i64), 0);
1302        b.emit(Op::LoadInt(m as i64), 0);
1303        for q in quasis {
1304            self.strlit(b, q); // cooked strings (heap)
1305        }
1306        for r in raws {
1307            self.strlit(b, r); // raw strings (heap)
1308        }
1309        for e in exprs {
1310            self.compile_expr(b, e)?; // substitution values
1311        }
1312        b.emit(Op::CallBuiltin(ops::TAG_TMPL, argc(3 + 2 * n + m)?), 0);
1313        Ok(())
1314    }
1315
1316    fn compile_array(&mut self, b: &mut ChunkBuilder, items: &[Expr]) -> Result<(), String> {
1317        if items.iter().any(|e| matches!(e, Expr::Spread(_))) {
1318            // (tag, value) pairs; tag 1 = spread.
1319            for it in items {
1320                match it {
1321                    Expr::Spread(inner) => {
1322                        b.emit(Op::LoadInt(1), 0);
1323                        self.compile_expr(b, inner)?;
1324                    }
1325                    _ => {
1326                        b.emit(Op::LoadInt(0), 0);
1327                        self.compile_expr(b, it)?;
1328                    }
1329                }
1330            }
1331            b.emit(Op::CallBuiltin(ops::BUILD_ARGS, argc(items.len() * 2)?), 0);
1332        } else if items.len() <= u8::MAX as usize {
1333            for it in items {
1334                self.compile_expr(b, it)?;
1335            }
1336            b.emit(Op::CallBuiltin(ops::MKARR, argc(items.len())?), 0);
1337        } else {
1338            // A literal larger than one CallBuiltin's u8 arg count can hold (the
1339            // generated data tables in iconv-lite hit this): start from an empty
1340            // array and append each element with an indexed store, keeping the
1341            // array on the stack across iterations.
1342            b.emit(Op::CallBuiltin(ops::MKARR, 0), 0); // [arr]
1343            for (i, it) in items.iter().enumerate() {
1344                b.emit(Op::Dup, 0); // [arr, arr]
1345                b.emit(Op::LoadInt(i as i64), 0); // [arr, arr, i]
1346                self.compile_expr(b, it)?; // [arr, arr, i, val]
1347                b.emit(Op::CallBuiltin(ops::SETITEM, 3), 0); // -> [arr, val]
1348                b.emit(Op::Pop, 0); // [arr]
1349            }
1350        }
1351        Ok(())
1352    }
1353
1354    fn compile_object(&mut self, b: &mut ChunkBuilder, props: &[Prop]) -> Result<(), String> {
1355        // (tag, key, val) triples for the data/spread props; tag 1 = ...spread.
1356        // Accessors are installed afterward via DEF_ACCESSOR.
1357        let data: Vec<&Prop> = props
1358            .iter()
1359            .filter(|p| !matches!(p, Prop::Accessor { .. }))
1360            .collect();
1361        let has_spread = data.iter().any(|p| matches!(p, Prop::Spread(_)));
1362        // A spread-free literal with more triples than one CallBuiltin's u8 arg
1363        // count can hold (iconv-lite's generated codepage tables are 150+ keys)
1364        // is built incrementally: start empty, store each key, keeping the object
1365        // on the stack. Spread merges need the single-shot MKOBJ tag path, so
1366        // large-with-spread stays on it (a rare, genuine limitation).
1367        if data.len() * 3 > u8::MAX as usize && !has_spread {
1368            b.emit(Op::CallBuiltin(ops::MKOBJ, 0), 0); // [obj]
1369            for p in &data {
1370                if let Prop::KeyValue { key, value, .. } = p {
1371                    b.emit(Op::Dup, 0); // [obj, obj]
1372                    self.compile_expr(b, key)?;
1373                    b.emit(Op::CallBuiltin(ops::PROPKEY, 1), 0); // [obj, obj, key]
1374                    self.compile_expr(b, value)?; // [obj, obj, key, val]
1375                    b.emit(Op::CallBuiltin(ops::SETITEM, 3), 0); // -> [obj, val]
1376                    b.emit(Op::Pop, 0); // [obj]
1377                }
1378            }
1379            return self.compile_object_accessors(b, props);
1380        }
1381        for p in &data {
1382            match p {
1383                Prop::KeyValue { key, value, .. } => {
1384                    b.emit(Op::LoadInt(0), 0);
1385                    // Key coerces to a property key (Symbol-aware: a Symbol maps to
1386                    // its internal `@@…` key rather than a `String()` coercion).
1387                    self.compile_expr(b, key)?;
1388                    b.emit(Op::CallBuiltin(ops::PROPKEY, 1), 0);
1389                    self.compile_expr(b, value)?;
1390                }
1391                Prop::Spread(src) => {
1392                    b.emit(Op::LoadInt(1), 0);
1393                    self.compile_expr(b, src)?;
1394                    b.emit(Op::LoadUndef, 0);
1395                }
1396                Prop::Accessor { .. } => unreachable!(),
1397            }
1398        }
1399        b.emit(Op::CallBuiltin(ops::MKOBJ, argc(data.len() * 3)?), 0); // [obj]
1400        self.compile_object_accessors(b, props)
1401    }
1402
1403    /// Install any getter/setter accessors of an object literal onto the object
1404    /// left on the stack (shared by the single-shot and incremental build paths).
1405    fn compile_object_accessors(
1406        &mut self,
1407        b: &mut ChunkBuilder,
1408        props: &[Prop],
1409    ) -> Result<(), String> {
1410        for p in props {
1411            if let Prop::Accessor {
1412                key,
1413                computed,
1414                is_getter,
1415                func,
1416            } = p
1417            {
1418                if *computed {
1419                    self.compile_expr(b, key)?;
1420                    b.emit(Op::CallBuiltin(ops::PROPKEY, 1), 0);
1421                } else if let Expr::Str(s) = key {
1422                    self.name_const(b, s);
1423                } else {
1424                    self.compile_expr(b, key)?;
1425                    b.emit(Op::CallBuiltin(ops::PROPKEY, 1), 0);
1426                }
1427                b.emit(
1428                    Op::LoadInt(if *is_getter { member::GET } else { member::SET }),
1429                    0,
1430                );
1431                self.compile_expr(b, func)?;
1432                b.emit(Op::CallBuiltin(ops::DEF_ACCESSOR, 4), 0);
1433            }
1434        }
1435        Ok(())
1436    }
1437
1438    fn compile_logical(
1439        &mut self,
1440        b: &mut ChunkBuilder,
1441        op: LogicalOp,
1442        l: &Expr,
1443        r: &Expr,
1444    ) -> Result<(), String> {
1445        self.compile_expr(b, l)?;
1446        b.emit(Op::Dup, 0);
1447        let test_op = match op {
1448            LogicalOp::And | LogicalOp::Or => ops::TRUTHY,
1449            LogicalOp::Nullish => ops::NULLISH,
1450        };
1451        b.emit(Op::CallBuiltin(test_op, 1), 0);
1452        let jump = match op {
1453            LogicalOp::And => b.emit(Op::JumpIfFalse(0), 0), // false -> keep left
1454            LogicalOp::Or => b.emit(Op::JumpIfTrue(0), 0),   // true -> keep left
1455            LogicalOp::Nullish => b.emit(Op::JumpIfFalse(0), 0), // not-nullish -> keep left
1456        };
1457        b.emit(Op::Pop, 0); // drop left, evaluate right
1458        self.compile_expr(b, r)?;
1459        let end = b.current_pos();
1460        b.patch_jump(jump, end);
1461        Ok(())
1462    }
1463
1464    fn compile_unary(&mut self, b: &mut ChunkBuilder, op: UnOp, e: &Expr) -> Result<(), String> {
1465        match op {
1466            UnOp::Neg => {
1467                self.compile_expr(b, e)?;
1468                b.emit(Op::Negate, 0);
1469            }
1470            UnOp::Not => {
1471                self.compile_condition(b, e)?;
1472                b.emit(Op::LogNot, 0);
1473            }
1474            UnOp::Pos => {
1475                b.emit(Op::LoadInt(unop::POS), 0);
1476                self.compile_expr(b, e)?;
1477                b.emit(Op::CallBuiltin(ops::UNARY, 2), 0);
1478            }
1479            UnOp::BitNot => {
1480                b.emit(Op::LoadInt(unop::BITNOT), 0);
1481                self.compile_expr(b, e)?;
1482                b.emit(Op::CallBuiltin(ops::UNARY, 2), 0);
1483            }
1484            UnOp::TypeOf => {
1485                // `typeof <bare ident>` must NOT throw when the name is unbound —
1486                // JS returns "undefined". Route a plain identifier through a
1487                // non-throwing name read; any other operand evaluates normally.
1488                if let Expr::Ident(n) = e {
1489                    self.name_const(b, n);
1490                    b.emit(Op::CallBuiltin(ops::TYPEOF_NAME, 1), 0);
1491                } else {
1492                    self.compile_expr(b, e)?;
1493                    b.emit(Op::CallBuiltin(ops::TYPEOF, 1), 0);
1494                }
1495            }
1496            UnOp::Void => {
1497                self.compile_expr(b, e)?;
1498                b.emit(Op::Pop, 0);
1499                b.emit(Op::LoadUndef, 0);
1500            }
1501            UnOp::Delete => match e {
1502                Expr::Member {
1503                    object, property, ..
1504                } => {
1505                    self.compile_expr(b, object)?;
1506                    self.name_const(b, property);
1507                    b.emit(Op::CallBuiltin(ops::DELPROP_NAME, 2), 0);
1508                }
1509                Expr::Index { object, index, .. } => {
1510                    self.compile_expr(b, object)?;
1511                    self.compile_expr(b, index)?;
1512                    b.emit(Op::CallBuiltin(ops::DELITEM, 2), 0);
1513                }
1514                _ => {
1515                    b.emit(Op::LoadTrue, 0);
1516                }
1517            },
1518        }
1519        Ok(())
1520    }
1521
1522    fn compile_binary(
1523        &mut self,
1524        b: &mut ChunkBuilder,
1525        op: BinOp,
1526        l: &Expr,
1527        r: &Expr,
1528    ) -> Result<(), String> {
1529        // Native fast path (JIT-traceable); the numeric hook supplies JS
1530        // semantics for non-number operands.
1531        macro_rules! native {
1532            ($opc:expr) => {{
1533                self.compile_expr(b, l)?;
1534                self.compile_expr(b, r)?;
1535                b.emit($opc, 0);
1536                return Ok(());
1537            }};
1538        }
1539        match op {
1540            BinOp::Add => native!(Op::Add),
1541            BinOp::Sub => native!(Op::Sub),
1542            BinOp::Mul => native!(Op::Mul),
1543            BinOp::Div => {
1544                // NOT native `Op::Div`: fusevm returns `Undef` for a zero divisor,
1545                // but JS needs `x/0 === ±Infinity` / `0/0 === NaN`, so `/` is a
1546                // builtin (fusevm's own documented pattern for non-default `/`).
1547                self.compile_expr(b, l)?;
1548                self.compile_expr(b, r)?;
1549                b.emit(Op::CallBuiltin(ops::DIV, 2), 0);
1550                return Ok(());
1551            }
1552            BinOp::Mod => native!(Op::Mod),
1553            BinOp::Pow => native!(Op::Pow),
1554            BinOp::Lt => native!(Op::NumLt),
1555            BinOp::Le => native!(Op::NumLe),
1556            BinOp::Gt => native!(Op::NumGt),
1557            BinOp::Ge => native!(Op::NumGe),
1558            BinOp::EqEqEq => {
1559                self.compile_expr(b, l)?;
1560                self.compile_expr(b, r)?;
1561                b.emit(Op::CallBuiltin(ops::STRICT_EQ, 2), 0);
1562            }
1563            BinOp::NeEqEq => {
1564                self.compile_expr(b, l)?;
1565                self.compile_expr(b, r)?;
1566                b.emit(Op::CallBuiltin(ops::STRICT_EQ, 2), 0);
1567                b.emit(Op::LogNot, 0);
1568            }
1569            BinOp::EqEq => {
1570                self.compile_expr(b, l)?;
1571                self.compile_expr(b, r)?;
1572                b.emit(Op::CallBuiltin(ops::LOOSE_EQ, 2), 0);
1573            }
1574            BinOp::NeEq => {
1575                self.compile_expr(b, l)?;
1576                self.compile_expr(b, r)?;
1577                b.emit(Op::CallBuiltin(ops::LOOSE_EQ, 2), 0);
1578                b.emit(Op::LogNot, 0);
1579            }
1580            BinOp::In => {
1581                self.compile_expr(b, l)?;
1582                self.compile_expr(b, r)?;
1583                b.emit(Op::CallBuiltin(ops::CONTAINS, 2), 0);
1584            }
1585            BinOp::InstanceOf => {
1586                self.compile_expr(b, l)?;
1587                self.compile_expr(b, r)?;
1588                b.emit(Op::CallBuiltin(ops::INSTANCEOF, 2), 0);
1589            }
1590            BinOp::BitAnd => self.emit_bitwise(b, bop::BITAND, l, r)?,
1591            BinOp::BitOr => self.emit_bitwise(b, bop::BITOR, l, r)?,
1592            BinOp::BitXor => self.emit_bitwise(b, bop::BITXOR, l, r)?,
1593            BinOp::Shl => self.emit_bitwise(b, bop::SHL, l, r)?,
1594            BinOp::Shr => self.emit_bitwise(b, bop::SHR, l, r)?,
1595            BinOp::UShr => self.emit_bitwise(b, bop::USHR, l, r)?,
1596        }
1597        Ok(())
1598    }
1599
1600    fn emit_bitwise(
1601        &mut self,
1602        b: &mut ChunkBuilder,
1603        tag: i64,
1604        l: &Expr,
1605        r: &Expr,
1606    ) -> Result<(), String> {
1607        b.emit(Op::LoadInt(tag), 0);
1608        self.compile_expr(b, l)?;
1609        self.compile_expr(b, r)?;
1610        b.emit(Op::CallBuiltin(ops::BINOP, 3), 0);
1611        Ok(())
1612    }
1613
1614    fn compile_update(
1615        &mut self,
1616        b: &mut ChunkBuilder,
1617        op: UpdateOp,
1618        prefix: bool,
1619        target: &Expr,
1620    ) -> Result<(), String> {
1621        // `NUM_STEP(tag, old)` computes `ToNumeric(old)` and `old ± 1` preserving
1622        // the operand's numeric type — so `x++` on a BigInt stays a BigInt
1623        // (`+old`/`old + 1` would throw the mix error). It pushes the coerced old
1624        // value and returns the new value: stack `[tag, old]` → `[oldN, new]`.
1625        let tag = if matches!(op, UpdateOp::Inc) { 1 } else { -1 };
1626        b.emit(Op::LoadInt(tag), 0);
1627        self.compile_expr(b, target)?; // [tag, old]
1628        b.emit(Op::CallBuiltin(ops::NUM_STEP, 2), 0); // [oldN, new]
1629        if prefix {
1630            // ++x: discard oldN, store new, yield new.
1631            b.emit(Op::Swap, 0); // [new, oldN]
1632            b.emit(Op::Pop, 0); // [new]
1633            b.emit(Op::Dup, 0); // [new, new]
1634            self.compile_bind(b, target, false)?; // stores new -> [new]
1635        } else {
1636            // x++: store new, yield oldN.
1637            self.compile_bind(b, target, false)?; // stores new -> [oldN]
1638        }
1639        Ok(())
1640    }
1641
1642    fn compile_member(
1643        &mut self,
1644        b: &mut ChunkBuilder,
1645        object: &Expr,
1646        property: &str,
1647        optional: bool,
1648    ) -> Result<(), String> {
1649        // `super.prop` — read a data/accessor property off the parent prototype.
1650        if matches!(object, Expr::Super) {
1651            self.name_const(b, property);
1652            b.emit(Op::CallBuiltin(ops::SUPER_GET, 1), 0);
1653            return Ok(());
1654        }
1655        self.compile_expr(b, object)?;
1656        if optional {
1657            let jshort = self.emit_optional_guard(b);
1658            self.name_const(b, property);
1659            b.emit(Op::CallBuiltin(ops::GETATTR, 2), 0);
1660            let end = b.current_pos();
1661            b.patch_jump(jshort, end);
1662        } else {
1663            self.name_const(b, property);
1664            b.emit(Op::CallBuiltin(ops::GETATTR, 2), 0);
1665        }
1666        Ok(())
1667    }
1668
1669    fn compile_index(
1670        &mut self,
1671        b: &mut ChunkBuilder,
1672        object: &Expr,
1673        index: &Expr,
1674        optional: bool,
1675    ) -> Result<(), String> {
1676        self.compile_expr(b, object)?;
1677        if optional {
1678            let jshort = self.emit_optional_guard(b);
1679            self.compile_expr(b, index)?;
1680            b.emit(Op::CallBuiltin(ops::GETITEM, 2), 0);
1681            let end = b.current_pos();
1682            b.patch_jump(jshort, end);
1683        } else {
1684            self.compile_expr(b, index)?;
1685            b.emit(Op::CallBuiltin(ops::GETITEM, 2), 0);
1686        }
1687        Ok(())
1688    }
1689
1690    /// For an optional access: object on TOS. If nullish, replace with undefined
1691    /// and jump over the access. Returns the jump index to patch to the end.
1692    fn emit_optional_guard(&mut self, b: &mut ChunkBuilder) -> usize {
1693        b.emit(Op::Dup, 0);
1694        b.emit(Op::CallBuiltin(ops::NULLISH, 1), 0);
1695        let jnull = b.emit(Op::JumpIfFalse(0), 0); // not nullish -> continue access
1696                                                   // nullish: drop object, push undefined, jump to end.
1697        b.emit(Op::Pop, 0);
1698        b.emit(Op::LoadUndef, 0);
1699        let jend = b.emit(Op::Jump(0), 0);
1700        let cont = b.current_pos();
1701        b.patch_jump(jnull, cont);
1702        jend
1703    }
1704
1705    fn compile_call(
1706        &mut self,
1707        b: &mut ChunkBuilder,
1708        func: &Expr,
1709        args: &[Expr],
1710        _optional: bool,
1711    ) -> Result<(), String> {
1712        let has_spread = args.iter().any(|a| matches!(a, Expr::Spread(_)));
1713        match func {
1714            // `super(...args)` — invoke the parent constructor on the current
1715            // `this` (SUPER_CALL runs the parent ctor + this class's field inits).
1716            Expr::Super => {
1717                for a in args {
1718                    self.compile_expr(b, a)?;
1719                }
1720                b.emit(Op::CallBuiltin(ops::SUPER_CALL, argc(args.len())?), 0);
1721                return Ok(());
1722            }
1723            // `super.method(...args)` — resolve the parent method, call it bound to
1724            // the current `this` via `method.call(this, ...args)`.
1725            Expr::Member {
1726                object, property, ..
1727            } if matches!(**object, Expr::Super) => {
1728                self.name_const(b, property);
1729                b.emit(Op::CallBuiltin(ops::SUPER_GET, 1), 0); // [method]
1730                self.name_const(b, "call"); // [method, "call"]
1731                b.emit(Op::CallBuiltin(ops::THIS, 0), 0); // [method, "call", this]
1732                                                          // `method.call(this, ...args)`: compile args (spread expands into
1733                                                          // the flat run) and dispatch as a method call named "call".
1734                for a in args {
1735                    self.compile_expr(b, a)?;
1736                }
1737                b.emit(Op::CallBuiltin(ops::CALL_METHOD, argc(3 + args.len())?), 0);
1738                return Ok(());
1739            }
1740            Expr::Member {
1741                object,
1742                property,
1743                optional,
1744            } => {
1745                self.compile_expr(b, object)?;
1746                // `obj?.method(...)`: if `obj` is nullish, short-circuit the whole
1747                // call to `undefined` (skip the method name, args, and dispatch).
1748                let jshort = if *optional {
1749                    Some(self.emit_optional_guard(b))
1750                } else {
1751                    None
1752                };
1753                self.name_const(b, property);
1754                if has_spread {
1755                    self.compile_spread_args(b, args)?; // [recv, name, argsArray]
1756                    b.emit(Op::CallBuiltin(ops::APPLY_METHOD, 3), 0);
1757                } else {
1758                    for a in args {
1759                        self.compile_expr(b, a)?;
1760                    }
1761                    b.emit(Op::CallBuiltin(ops::CALL_METHOD, argc(2 + args.len())?), 0);
1762                }
1763                if let Some(j) = jshort {
1764                    let end = b.current_pos();
1765                    b.patch_jump(j, end);
1766                }
1767            }
1768            Expr::Index {
1769                object,
1770                index,
1771                optional,
1772            } => {
1773                // recv[expr](args) — evaluate as a method via computed name.
1774                self.compile_expr(b, object)?; // [recv]
1775                                               // `recv?.[expr](...)`: short-circuit to `undefined` when nullish.
1776                let jshort = if *optional {
1777                    Some(self.emit_optional_guard(b))
1778                } else {
1779                    None
1780                };
1781                b.emit(Op::Dup, 0); // [recv, recv]
1782                self.compile_expr(b, index)?; // [recv, recv, idx]
1783                b.emit(Op::CallBuiltin(ops::GETITEM, 2), 0); // [recv, fn]
1784                b.emit(Op::Swap, 0); // [fn, recv]... but APPLY needs callable then this
1785                                     // Fall back: call the function value with `this`=recv via CALL_VALUE
1786                                     // (this-binding for computed method calls is approximated).
1787                b.emit(Op::Pop, 0); // drop recv; keep fn on stack: [fn]
1788                if has_spread {
1789                    self.compile_spread_args(b, args)?;
1790                    b.emit(Op::CallBuiltin(ops::APPLY, 2), 0);
1791                } else {
1792                    for a in args {
1793                        self.compile_expr(b, a)?;
1794                    }
1795                    b.emit(Op::CallBuiltin(ops::CALL_VALUE, argc(1 + args.len())?), 0);
1796                }
1797                if let Some(j) = jshort {
1798                    let end = b.current_pos();
1799                    b.patch_jump(j, end);
1800                }
1801            }
1802            Expr::Ident(n) => {
1803                self.name_const(b, n);
1804                if has_spread {
1805                    self.compile_spread_args(b, args)?; // [name, argsArray]
1806                                                        // Resolve name to a value, then APPLY.
1807                    b.emit(Op::Swap, 0); // [argsArray, name]
1808                    b.emit(Op::CallBuiltin(ops::GETLOCAL, 1), 0); // [argsArray, fn]
1809                    b.emit(Op::Swap, 0); // [fn, argsArray]
1810                    b.emit(Op::CallBuiltin(ops::APPLY, 2), 0);
1811                } else {
1812                    for a in args {
1813                        self.compile_expr(b, a)?;
1814                    }
1815                    b.emit(Op::CallBuiltin(ops::CALL, argc(1 + args.len())?), 0);
1816                }
1817            }
1818            _ => {
1819                self.compile_expr(b, func)?;
1820                if has_spread {
1821                    self.compile_spread_args(b, args)?;
1822                    b.emit(Op::CallBuiltin(ops::APPLY, 2), 0);
1823                } else {
1824                    for a in args {
1825                        self.compile_expr(b, a)?;
1826                    }
1827                    b.emit(Op::CallBuiltin(ops::CALL_VALUE, argc(1 + args.len())?), 0);
1828                }
1829            }
1830        }
1831        Ok(())
1832    }
1833
1834    /// Build a flat args array from a mix of plain args and `...spread` args.
1835    fn compile_spread_args(&mut self, b: &mut ChunkBuilder, args: &[Expr]) -> Result<(), String> {
1836        for a in args {
1837            match a {
1838                Expr::Spread(inner) => {
1839                    b.emit(Op::LoadInt(1), 0);
1840                    self.compile_expr(b, inner)?;
1841                }
1842                _ => {
1843                    b.emit(Op::LoadInt(0), 0);
1844                    self.compile_expr(b, a)?;
1845                }
1846            }
1847        }
1848        b.emit(Op::CallBuiltin(ops::BUILD_ARGS, argc(args.len() * 2)?), 0);
1849        Ok(())
1850    }
1851
1852    fn compile_new(
1853        &mut self,
1854        b: &mut ChunkBuilder,
1855        callee: &Expr,
1856        args: &[Expr],
1857    ) -> Result<(), String> {
1858        self.compile_expr(b, callee)?;
1859        for a in args {
1860            self.compile_expr(b, a)?;
1861        }
1862        b.emit(Op::CallBuiltin(ops::NEW, argc(1 + args.len())?), 0);
1863        Ok(())
1864    }
1865}
1866
1867/// A prologue statement applying a parameter default: `if (name === undefined)
1868/// name = default;`.
1869fn default_stmt(name: &str, default: &Expr) -> Stmt {
1870    Stmt::from(StmtKind::If {
1871        test: Expr::Binary(
1872            BinOp::EqEqEq,
1873            Box::new(Expr::Ident(name.to_string())),
1874            Box::new(Expr::Undefined),
1875        ),
1876        cons: Box::new(Stmt::from(StmtKind::Expr(Expr::Assign {
1877            target: Box::new(Expr::Ident(name.to_string())),
1878            value: Box::new(default.clone()),
1879        }))),
1880        alt: None,
1881    })
1882}