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, 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}
76
77#[derive(Default)]
78pub struct Compiler {
79    functions: Vec<(String, FuncDef)>,
80    tries: Vec<TryDef>,
81    loops: Vec<LoopCtx>,
82    tmp: usize,
83}
84
85/// Compile a parsed program.
86pub fn compile(stmts: &[Stmt]) -> Result<Program, String> {
87    let mut c = Compiler::default();
88    let mut b = ChunkBuilder::new();
89    // Hoist function declarations to the top (JS function hoisting).
90    c.hoist_funcs(&mut b, stmts)?;
91    c.compile_stmts(&mut b, stmts)?;
92    Ok(Program {
93        main: b.build(),
94        functions: c.functions,
95        tries: c.tries,
96    })
97}
98
99fn argc(n: usize) -> Result<u8, String> {
100    u8::try_from(n).map_err(|_| "too many arguments (>255) for one call".to_string())
101}
102
103impl Compiler {
104    // ── emit helpers ─────────────────────────────────────────────────────
105    fn name_const(&self, b: &mut ChunkBuilder, s: &str) {
106        let k = b.add_constant(Value::str(s));
107        b.emit(Op::LoadConst(k), 0);
108    }
109    fn strlit(&self, b: &mut ChunkBuilder, s: &str) {
110        let k = b.add_constant(Value::str(s));
111        b.emit(Op::LoadConst(k), 0);
112        b.emit(Op::CallBuiltin(ops::MKSTR, 1), 0);
113    }
114    fn tmp_name(&mut self, tag: &str) -> String {
115        let n = format!(".{tag}{}", self.tmp);
116        self.tmp += 1;
117        n
118    }
119
120    /// Emit MKFUNC for a compiled function template and leave the closure on the
121    /// stack.
122    fn emit_mkfunc(&self, b: &mut ChunkBuilder, def_id: usize) {
123        b.emit(Op::LoadInt(def_id as i64), 0);
124        b.emit(Op::CallBuiltin(ops::MKFUNC, 1), 0);
125    }
126
127    fn hoist_funcs(&mut self, b: &mut ChunkBuilder, stmts: &[Stmt]) -> Result<(), String> {
128        for s in stmts {
129            if let StmtKind::FuncDecl { name, params, body } = &s.kind {
130                let def_id = self.build_function(name, params, body)?;
131                self.emit_mkfunc(b, def_id);
132                self.declare(b, &Expr::Ident(name.clone()));
133            }
134        }
135        Ok(())
136    }
137
138    fn compile_stmts(&mut self, b: &mut ChunkBuilder, stmts: &[Stmt]) -> Result<(), String> {
139        for s in stmts {
140            self.compile_stmt(b, s)?;
141        }
142        Ok(())
143    }
144
145    fn compile_stmt(&mut self, b: &mut ChunkBuilder, s: &Stmt) -> Result<(), String> {
146        let line = s.line;
147        match &s.kind {
148            StmtKind::Expr(e) => {
149                self.compile_expr(b, e)?;
150                b.emit(Op::Pop, line);
151            }
152            StmtKind::Empty => {}
153            StmtKind::FuncDecl { .. } => {} // hoisted at block entry
154            StmtKind::Decl { decls, .. } => {
155                for d in decls {
156                    match &d.init {
157                        Some(v) => self.compile_expr(b, v)?,
158                        None => {
159                            b.emit(Op::LoadUndef, line);
160                        }
161                    }
162                    self.compile_bind(b, &d.target, true)?;
163                }
164            }
165            StmtKind::Block(body) => {
166                self.hoist_funcs(b, body)?;
167                self.compile_stmts(b, body)?;
168            }
169            StmtKind::If { test, cons, alt } => self.compile_if(b, test, cons, alt)?,
170            StmtKind::While { test, body } => self.compile_while(b, test, body)?,
171            StmtKind::DoWhile { body, test } => self.compile_do_while(b, body, test)?,
172            StmtKind::For {
173                init,
174                test,
175                update,
176                body,
177            } => self.compile_for(b, init, test, update, body)?,
178            StmtKind::ForOf {
179                decl_kind,
180                target,
181                iter,
182                body,
183            } => self.compile_for_of(b, decl_kind.is_some(), target, iter, body)?,
184            StmtKind::ForIn {
185                decl_kind,
186                target,
187                object,
188                body,
189            } => self.compile_for_in(b, decl_kind.is_some(), target, object, body)?,
190            StmtKind::Switch { disc, cases } => self.compile_switch(b, disc, cases)?,
191            StmtKind::Return(e) => {
192                match e {
193                    Some(e) => self.compile_expr(b, e)?,
194                    None => {
195                        b.emit(Op::LoadUndef, line);
196                    }
197                }
198                b.emit(Op::CallBuiltin(ops::SIG_RETURN, 1), line);
199            }
200            StmtKind::Break(_) => {
201                let j = b.emit(Op::Jump(0), line);
202                self.loops
203                    .last_mut()
204                    .ok_or("SyntaxError: 'break' outside loop")?
205                    .breaks
206                    .push(j);
207            }
208            StmtKind::Continue(_) => {
209                let j = b.emit(Op::Jump(0), line);
210                self.loops
211                    .iter_mut()
212                    .rev()
213                    .find(|c| c.catches_continue)
214                    .ok_or("SyntaxError: 'continue' outside loop")?
215                    .continues
216                    .push(j);
217            }
218            StmtKind::Throw(e) => {
219                self.compile_expr(b, e)?;
220                b.emit(Op::CallBuiltin(ops::THROW, 1), line);
221            }
222            StmtKind::Try {
223                block,
224                handler,
225                finalizer,
226            } => self.compile_try(b, block, handler, finalizer)?,
227        }
228        Ok(())
229    }
230
231    // ── binding / assignment ─────────────────────────────────────────────
232    /// Store the value on top of the stack into `target`. `declare` chooses
233    /// `DECLARE` (new binding) vs `SETLOCAL` (existing binding / global).
234    fn compile_bind(&mut self, b: &mut ChunkBuilder, target: &Expr, declare: bool) -> Result<(), String> {
235        match target {
236            Expr::Ident(_) => {
237                if declare {
238                    self.declare(b, target);
239                } else {
240                    self.store_simple(b, target)?;
241                }
242            }
243            Expr::Member { .. } | Expr::Index { .. } => {
244                self.store_simple(b, target)?;
245            }
246            Expr::Array(items) => self.destructure_array(b, items, declare)?,
247            Expr::Object(props) => self.destructure_object(b, props, declare)?,
248            Expr::Assign { target, value } => {
249                // Pattern element with a default: use it when TOS is undefined.
250                b.emit(Op::Dup, 0);
251                b.emit(Op::LoadUndef, 0);
252                b.emit(Op::CallBuiltin(ops::STRICT_EQ, 2), 0);
253                let jf = b.emit(Op::JumpIfFalse(0), 0);
254                b.emit(Op::Pop, 0); // drop the undefined
255                self.compile_expr(b, value)?;
256                let end = b.current_pos();
257                b.patch_jump(jf, end);
258                self.compile_bind(b, target, declare)?;
259            }
260            _ => return Err("SyntaxError: invalid assignment target".into()),
261        }
262        Ok(())
263    }
264
265    /// Emit a `DECLARE` of a simple name binding, consuming TOS value.
266    fn declare(&self, b: &mut ChunkBuilder, target: &Expr) {
267        if let Expr::Ident(n) = target {
268            self.name_const(b, n);
269            b.emit(Op::Swap, 0);
270            b.emit(Op::CallBuiltin(ops::DECLARE, 2), 0);
271            b.emit(Op::Pop, 0);
272        }
273    }
274
275    /// Store TOS into an lvalue (Ident/Member/Index), leaving nothing.
276    fn store_simple(&mut self, b: &mut ChunkBuilder, target: &Expr) -> Result<(), String> {
277        match target {
278            Expr::Ident(n) => {
279                self.name_const(b, n);
280                b.emit(Op::Swap, 0);
281                b.emit(Op::CallBuiltin(ops::SETLOCAL, 2), 0);
282                b.emit(Op::Pop, 0);
283            }
284            Expr::Member { object, property, .. } => {
285                self.compile_expr(b, object)?; // [value, recv]
286                self.name_const(b, property); // [value, recv, name]
287                b.emit(Op::Rot, 0); // [recv, name, value]
288                b.emit(Op::CallBuiltin(ops::SETATTR, 3), 0);
289                b.emit(Op::Pop, 0);
290            }
291            Expr::Index { object, index, .. } => {
292                self.compile_expr(b, object)?; // [value, recv]
293                self.compile_expr(b, index)?; // [value, recv, idx]
294                b.emit(Op::Rot, 0); // [recv, idx, value]
295                b.emit(Op::CallBuiltin(ops::SETITEM, 3), 0);
296                b.emit(Op::Pop, 0);
297            }
298            _ => return Err("SyntaxError: invalid assignment target".into()),
299        }
300        Ok(())
301    }
302
303    fn destructure_array(&mut self, b: &mut ChunkBuilder, items: &[Expr], declare: bool) -> Result<(), String> {
304        let star_idx = items
305            .iter()
306            .position(|e| matches!(e, Expr::Spread(_)))
307            .map(|i| i as i64)
308            .unwrap_or(-1);
309        b.emit(Op::LoadInt(items.len() as i64), 0);
310        b.emit(Op::LoadInt(star_idx), 0);
311        b.emit(Op::CallBuiltin(ops::UNPACK, 3), 0); // pushes items[0]..items[n-1], items[0] on top
312        for it in items {
313            match it {
314                Expr::Undefined => {
315                    b.emit(Op::Pop, 0); // hole
316                }
317                Expr::Spread(inner) => self.compile_bind(b, inner, declare)?,
318                _ => self.compile_bind(b, it, declare)?,
319            }
320        }
321        Ok(())
322    }
323
324    fn destructure_object(&mut self, b: &mut ChunkBuilder, props: &[Prop], declare: bool) -> Result<(), String> {
325        // Object value on TOS; keep it, read each key, bind, then drop.
326        let obj_tmp = self.tmp_name("destr");
327        self.name_const(b, &obj_tmp);
328        b.emit(Op::Swap, 0);
329        b.emit(Op::CallBuiltin(ops::DECLARE, 2), 0);
330        b.emit(Op::Pop, 0);
331        // Collect statically-known destructured key names, for a `...rest`.
332        let mut named: Vec<String> = Vec::new();
333        for p in props {
334            match p {
335                Prop::KeyValue { key, value, .. } => {
336                    if let Expr::Str(s) = key {
337                        named.push(s.clone());
338                    }
339                    // Load obj, read key.
340                    self.load_local(b, &obj_tmp);
341                    self.compile_expr(b, key)?;
342                    b.emit(Op::CallBuiltin(ops::GETITEM, 2), 0); // [value]
343                    self.compile_bind(b, value, declare)?;
344                }
345                Prop::Spread(target) => {
346                    self.load_local(b, &obj_tmp);
347                    for k in &named {
348                        self.strlit(b, k);
349                    }
350                    b.emit(Op::CallBuiltin(ops::MKARR, argc(named.len())?), 0);
351                    b.emit(Op::CallBuiltin(ops::OBJ_REST, 2), 0); // [rest_object]
352                    self.compile_bind(b, target, declare)?;
353                }
354            }
355        }
356        Ok(())
357    }
358
359    fn load_local(&self, b: &mut ChunkBuilder, name: &str) {
360        self.name_const(b, name);
361        b.emit(Op::CallBuiltin(ops::GETLOCAL, 1), 0);
362    }
363
364    // ── control flow ─────────────────────────────────────────────────────
365    fn compile_condition(&mut self, b: &mut ChunkBuilder, e: &Expr) -> Result<(), String> {
366        self.compile_expr(b, e)?;
367        b.emit(Op::CallBuiltin(ops::TRUTHY, 1), 0);
368        Ok(())
369    }
370
371    fn compile_if(&mut self, b: &mut ChunkBuilder, test: &Expr, cons: &Stmt, alt: &Option<Box<Stmt>>) -> Result<(), String> {
372        self.compile_condition(b, test)?;
373        let jfalse = b.emit(Op::JumpIfFalse(0), 0);
374        self.compile_stmt(b, cons)?;
375        if let Some(alt) = alt {
376            let jend = b.emit(Op::Jump(0), 0);
377            let else_start = b.current_pos();
378            b.patch_jump(jfalse, else_start);
379            self.compile_stmt(b, alt)?;
380            let end = b.current_pos();
381            b.patch_jump(jend, end);
382        } else {
383            let end = b.current_pos();
384            b.patch_jump(jfalse, end);
385        }
386        Ok(())
387    }
388
389    fn compile_while(&mut self, b: &mut ChunkBuilder, test: &Expr, body: &Stmt) -> Result<(), String> {
390        let start = b.current_pos();
391        self.compile_condition(b, test)?;
392        let jfalse = b.emit(Op::JumpIfFalse(0), 0);
393        self.loops.push(LoopCtx {
394            breaks: Vec::new(),
395            continues: Vec::new(),
396            catches_continue: true,
397        });
398        self.compile_stmt(b, body)?;
399        b.emit(Op::Jump(start), 0);
400        let ctx = self.loops.pop().unwrap();
401        for c in ctx.continues {
402            b.patch_jump(c, start);
403        }
404        let end = b.current_pos();
405        b.patch_jump(jfalse, end);
406        for br in ctx.breaks {
407            b.patch_jump(br, end);
408        }
409        Ok(())
410    }
411
412    fn compile_do_while(&mut self, b: &mut ChunkBuilder, body: &Stmt, test: &Expr) -> Result<(), String> {
413        let start = b.current_pos();
414        self.loops.push(LoopCtx {
415            breaks: Vec::new(),
416            continues: Vec::new(),
417            catches_continue: true,
418        });
419        self.compile_stmt(b, body)?;
420        let cont_target = b.current_pos();
421        self.compile_condition(b, test)?;
422        b.emit(Op::JumpIfTrue(start), 0);
423        let ctx = self.loops.pop().unwrap();
424        for c in ctx.continues {
425            b.patch_jump(c, cont_target);
426        }
427        let end = b.current_pos();
428        for br in ctx.breaks {
429            b.patch_jump(br, end);
430        }
431        Ok(())
432    }
433
434    fn compile_for(
435        &mut self,
436        b: &mut ChunkBuilder,
437        init: &Option<Box<Stmt>>,
438        test: &Option<Expr>,
439        update: &Option<Expr>,
440        body: &Stmt,
441    ) -> Result<(), String> {
442        if let Some(init) = init {
443            self.compile_stmt(b, init)?;
444        }
445        let start = b.current_pos();
446        let jfalse = match test {
447            Some(t) => {
448                self.compile_condition(b, t)?;
449                Some(b.emit(Op::JumpIfFalse(0), 0))
450            }
451            None => None,
452        };
453        self.loops.push(LoopCtx {
454            breaks: Vec::new(),
455            continues: Vec::new(),
456            catches_continue: true,
457        });
458        self.compile_stmt(b, body)?;
459        let cont_target = b.current_pos();
460        if let Some(u) = update {
461            self.compile_expr(b, u)?;
462            b.emit(Op::Pop, 0);
463        }
464        b.emit(Op::Jump(start), 0);
465        let ctx = self.loops.pop().unwrap();
466        for c in ctx.continues {
467            b.patch_jump(c, cont_target);
468        }
469        let end = b.current_pos();
470        if let Some(jf) = jfalse {
471            b.patch_jump(jf, end);
472        }
473        for br in ctx.breaks {
474            b.patch_jump(br, end);
475        }
476        Ok(())
477    }
478
479    fn compile_for_of(&mut self, b: &mut ChunkBuilder, declare: bool, target: &Expr, iter: &Expr, body: &Stmt) -> Result<(), String> {
480        self.compile_expr(b, iter)?;
481        b.emit(Op::CallBuiltin(ops::GETITER, 1), 0); // [iterator]
482        self.loop_over(b, declare, target, body)
483    }
484
485    fn compile_for_in(&mut self, b: &mut ChunkBuilder, declare: bool, target: &Expr, object: &Expr, body: &Stmt) -> Result<(), String> {
486        self.compile_expr(b, object)?;
487        b.emit(Op::CallBuiltin(ops::FORIN_KEYS, 1), 0); // [keys_array]
488        b.emit(Op::CallBuiltin(ops::GETITER, 1), 0); // [iterator]
489        self.loop_over(b, declare, target, body)
490    }
491
492    /// Shared loop tail for for-of / for-in: iterator on TOS.
493    fn loop_over(&mut self, b: &mut ChunkBuilder, declare: bool, target: &Expr, body: &Stmt) -> Result<(), String> {
494        let start = b.current_pos();
495        b.emit(Op::CallBuiltin(ops::FORITER, 0), 0); // [iterator, value, has_next]
496        let jdone = b.emit(Op::JumpIfFalse(0), 0); // pops has_next
497        self.compile_bind(b, target, declare)?; // consumes value -> [iterator]
498        self.loops.push(LoopCtx {
499            breaks: Vec::new(),
500            continues: Vec::new(),
501            catches_continue: true,
502        });
503        self.compile_stmt(b, body)?;
504        b.emit(Op::Jump(start), 0);
505        let ctx = self.loops.pop().unwrap();
506        for c in ctx.continues {
507            b.patch_jump(c, start);
508        }
509        let done = b.current_pos();
510        b.patch_jump(jdone, done);
511        b.emit(Op::Pop, 0); // drop iterator
512        let jafter = b.emit(Op::Jump(0), 0);
513        let break_target = b.current_pos();
514        b.emit(Op::Pop, 0); // drop iterator on break
515        let end = b.current_pos();
516        b.patch_jump(jafter, end);
517        for br in ctx.breaks {
518            b.patch_jump(br, break_target);
519        }
520        Ok(())
521    }
522
523    fn compile_switch(&mut self, b: &mut ChunkBuilder, disc: &Expr, cases: &[SwitchCase]) -> Result<(), String> {
524        let disc_tmp = self.tmp_name("switch");
525        self.compile_expr(b, disc)?;
526        self.name_const(b, &disc_tmp);
527        b.emit(Op::Swap, 0);
528        b.emit(Op::CallBuiltin(ops::DECLARE, 2), 0);
529        b.emit(Op::Pop, 0);
530        // Emit the test chain: `if (disc === caseTest) goto bodyN`.
531        let mut body_jumps: Vec<Option<usize>> = Vec::new();
532        let mut default_idx: Option<usize> = None;
533        for (i, case) in cases.iter().enumerate() {
534            match &case.test {
535                Some(t) => {
536                    self.load_local(b, &disc_tmp);
537                    self.compile_expr(b, t)?;
538                    b.emit(Op::CallBuiltin(ops::STRICT_EQ, 2), 0);
539                    let j = b.emit(Op::JumpIfTrue(0), 0);
540                    body_jumps.push(Some(j));
541                }
542                None => {
543                    default_idx = Some(i);
544                    body_jumps.push(None);
545                }
546            }
547        }
548        // No test matched: jump to default (if any) or end.
549        let no_match_jump = b.emit(Op::Jump(0), 0);
550        self.loops.push(LoopCtx {
551            breaks: Vec::new(),
552            continues: Vec::new(),
553            catches_continue: false,
554        });
555        let mut body_starts: Vec<usize> = Vec::new();
556        for case in cases {
557            body_starts.push(b.current_pos());
558            self.compile_stmts(b, &case.body)?;
559        }
560        let end = b.current_pos();
561        // Patch each case test-jump to its body start.
562        for (i, j) in body_jumps.iter().enumerate() {
563            if let Some(j) = j {
564                b.patch_jump(*j, body_starts[i]);
565            }
566        }
567        match default_idx {
568            Some(i) => b.patch_jump(no_match_jump, body_starts[i]),
569            None => b.patch_jump(no_match_jump, end),
570        }
571        let ctx = self.loops.pop().unwrap();
572        for br in ctx.breaks {
573            b.patch_jump(br, end);
574        }
575        Ok(())
576    }
577
578    fn compile_try(
579        &mut self,
580        b: &mut ChunkBuilder,
581        block: &[Stmt],
582        handler: &Option<(Option<Expr>, Vec<Stmt>)>,
583        finalizer: &Option<Vec<Stmt>>,
584    ) -> Result<(), String> {
585        let block_chunk = self.compile_block_chunk(block)?;
586        let handler_def = match handler {
587            Some((param, body)) => {
588                let param_name = match param {
589                    Some(Expr::Ident(n)) => Some(n.clone()),
590                    _ => None,
591                };
592                let hbody = self.compile_block_chunk(body)?;
593                Some((param_name, hbody))
594            }
595            None => None,
596        };
597        let final_chunk = match finalizer {
598            Some(f) => Some(self.compile_block_chunk(f)?),
599            None => None,
600        };
601        let id = self.tries.len();
602        self.tries.push(TryDef {
603            block: block_chunk,
604            handler: handler_def,
605            finalizer: final_chunk,
606        });
607        b.emit(Op::LoadInt(id as i64), 0);
608        b.emit(Op::CallBuiltin(ops::TRY, 1), 0);
609        b.emit(Op::Pop, 0);
610        Ok(())
611    }
612
613    fn compile_block_chunk(&mut self, stmts: &[Stmt]) -> Result<Chunk, String> {
614        let mut cb = ChunkBuilder::new();
615        self.hoist_funcs(&mut cb, stmts)?;
616        self.compile_stmts(&mut cb, stmts)?;
617        Ok(cb.build())
618    }
619
620    // ── functions ────────────────────────────────────────────────────────
621    fn build_function(&mut self, name: &str, params: &[Param], body: &[Stmt]) -> Result<usize, String> {
622        let (slots, prologue) = self.lower_params(params)?;
623        let mut fb = ChunkBuilder::new();
624        // Function-body function hoisting.
625        self.hoist_funcs(&mut fb, &prologue)?;
626        self.hoist_funcs(&mut fb, body)?;
627        self.compile_stmts(&mut fb, &prologue)?;
628        self.compile_stmts(&mut fb, body)?;
629        let def = FuncDef {
630            name: name.to_string(),
631            params: slots,
632            chunk: fb.build(),
633            is_arrow: false,
634        };
635        self.functions.push((name.to_string(), def));
636        Ok(self.functions.len() - 1)
637    }
638
639    fn build_arrow(&mut self, params: &[Param], body: &FnBody) -> Result<usize, String> {
640        let stmts = match body {
641            FnBody::Block(b) => b.clone(),
642            FnBody::Expr(e) => vec![Stmt::from(StmtKind::Return(Some((**e).clone())))],
643        };
644        let id = self.build_function("", params, &stmts)?;
645        // Mark the template as an arrow so `this` is captured lexically.
646        self.functions[id].1.is_arrow = true;
647        Ok(id)
648    }
649
650    /// Lower a formal-parameter list into simple slots plus prologue statements
651    /// (defaults + destructuring), executed at the top of the body.
652    fn lower_params(&mut self, params: &[Param]) -> Result<(Vec<ParamSlot>, Vec<Stmt>), String> {
653        let mut slots = Vec::new();
654        let mut prologue: Vec<Stmt> = Vec::new();
655        for (i, p) in params.iter().enumerate() {
656            if p.rest {
657                let name = match &p.pattern {
658                    Expr::Ident(n) => n.clone(),
659                    _ => return Err("SyntaxError: rest parameter must be an identifier".into()),
660                };
661                slots.push(ParamSlot {
662                    name,
663                    rest: true,
664                    has_default: false,
665                });
666                continue;
667            }
668            match &p.pattern {
669                Expr::Ident(name) => {
670                    slots.push(ParamSlot {
671                        name: name.clone(),
672                        rest: false,
673                        has_default: p.default.is_some(),
674                    });
675                    if let Some(d) = &p.default {
676                        prologue.push(default_stmt(name, d));
677                    }
678                }
679                pattern => {
680                    let synth = format!(".param{i}");
681                    slots.push(ParamSlot {
682                        name: synth.clone(),
683                        rest: false,
684                        has_default: p.default.is_some(),
685                    });
686                    if let Some(d) = &p.default {
687                        prologue.push(default_stmt(&synth, d));
688                    }
689                    prologue.push(Stmt::from(StmtKind::Decl {
690                        kind: DeclKind::Let,
691                        decls: vec![Declarator {
692                            target: pattern.clone(),
693                            init: Some(Expr::Ident(synth)),
694                        }],
695                    }));
696                }
697            }
698        }
699        Ok((slots, prologue))
700    }
701
702    // ── expressions ──────────────────────────────────────────────────────
703    fn compile_expr(&mut self, b: &mut ChunkBuilder, e: &Expr) -> Result<(), String> {
704        match e {
705            Expr::Undefined => {
706                b.emit(Op::LoadUndef, 0);
707            }
708            Expr::Null => {
709                b.emit(Op::CallBuiltin(ops::LOAD_NULL, 0), 0);
710            }
711            Expr::True => {
712                b.emit(Op::LoadTrue, 0);
713            }
714            Expr::False => {
715                b.emit(Op::LoadFalse, 0);
716            }
717            Expr::Number(n) => {
718                b.emit(Op::LoadFloat(*n), 0);
719            }
720            Expr::Str(s) => self.strlit(b, s),
721            Expr::Template { quasis, exprs } => self.compile_template(b, quasis, exprs)?,
722            Expr::Ident(n) => self.load_local(b, n),
723            Expr::This => {
724                b.emit(Op::CallBuiltin(ops::THIS, 0), 0);
725            }
726            Expr::Array(items) => self.compile_array(b, items)?,
727            Expr::Object(props) => self.compile_object(b, props)?,
728            Expr::Spread(inner) => self.compile_expr(b, inner)?,
729            Expr::Logical(op, l, r) => self.compile_logical(b, *op, l, r)?,
730            Expr::Unary(op, e) => self.compile_unary(b, *op, e)?,
731            Expr::Binary(op, l, r) => self.compile_binary(b, *op, l, r)?,
732            Expr::Conditional { test, cons, alt } => {
733                self.compile_condition(b, test)?;
734                let jf = b.emit(Op::JumpIfFalse(0), 0);
735                self.compile_expr(b, cons)?;
736                let je = b.emit(Op::Jump(0), 0);
737                let els = b.current_pos();
738                b.patch_jump(jf, els);
739                self.compile_expr(b, alt)?;
740                let end = b.current_pos();
741                b.patch_jump(je, end);
742            }
743            Expr::Assign { target, value } => {
744                self.compile_expr(b, value)?;
745                b.emit(Op::Dup, 0); // assignment yields the value
746                self.compile_bind(b, target, false)?;
747            }
748            Expr::Update { op, prefix, target } => self.compile_update(b, *op, *prefix, target)?,
749            Expr::Call { func, args, optional } => self.compile_call(b, func, args, *optional)?,
750            Expr::New { callee, args } => self.compile_new(b, callee, args)?,
751            Expr::Member { object, property, optional } => {
752                self.compile_member(b, object, property, *optional)?
753            }
754            Expr::Index { object, index, optional } => {
755                self.compile_index(b, object, index, *optional)?
756            }
757            Expr::Function { params, body, is_arrow, name } => {
758                let def_id = if *is_arrow {
759                    self.build_arrow(params, body)?
760                } else {
761                    let n = name.clone().unwrap_or_default();
762                    let stmts = match body {
763                        FnBody::Block(b) => b.clone(),
764                        FnBody::Expr(e) => vec![Stmt::from(StmtKind::Return(Some((**e).clone())))],
765                    };
766                    self.build_function(&n, params, &stmts)?
767                };
768                self.emit_mkfunc(b, def_id);
769            }
770            Expr::Sequence(items) => {
771                for (i, it) in items.iter().enumerate() {
772                    self.compile_expr(b, it)?;
773                    if i + 1 < items.len() {
774                        b.emit(Op::Pop, 0);
775                    }
776                }
777            }
778        }
779        Ok(())
780    }
781
782    fn compile_template(&mut self, b: &mut ChunkBuilder, quasis: &[String], exprs: &[Expr]) -> Result<(), String> {
783        let mut n = 0;
784        for (i, q) in quasis.iter().enumerate() {
785            let k = b.add_constant(Value::str(q));
786            b.emit(Op::LoadConst(k), 0);
787            n += 1;
788            if i < exprs.len() {
789                self.compile_expr(b, &exprs[i])?;
790                b.emit(Op::CallBuiltin(ops::TOSTR, 1), 0);
791                n += 1;
792            }
793        }
794        b.emit(Op::CallBuiltin(ops::MKSTR, argc(n)?), 0);
795        Ok(())
796    }
797
798    fn compile_array(&mut self, b: &mut ChunkBuilder, items: &[Expr]) -> Result<(), String> {
799        if items.iter().any(|e| matches!(e, Expr::Spread(_))) {
800            // (tag, value) pairs; tag 1 = spread.
801            for it in items {
802                match it {
803                    Expr::Spread(inner) => {
804                        b.emit(Op::LoadInt(1), 0);
805                        self.compile_expr(b, inner)?;
806                    }
807                    _ => {
808                        b.emit(Op::LoadInt(0), 0);
809                        self.compile_expr(b, it)?;
810                    }
811                }
812            }
813            b.emit(Op::CallBuiltin(ops::BUILD_ARGS, argc(items.len() * 2)?), 0);
814        } else {
815            for it in items {
816                self.compile_expr(b, it)?;
817            }
818            b.emit(Op::CallBuiltin(ops::MKARR, argc(items.len())?), 0);
819        }
820        Ok(())
821    }
822
823    fn compile_object(&mut self, b: &mut ChunkBuilder, props: &[Prop]) -> Result<(), String> {
824        // (tag, key, val) triples; tag 1 = ...spread (key holds the source obj).
825        for p in props {
826            match p {
827                Prop::KeyValue { key, value, .. } => {
828                    b.emit(Op::LoadInt(0), 0);
829                    // Key coerces to a string property name.
830                    self.compile_expr(b, key)?;
831                    b.emit(Op::CallBuiltin(ops::TOSTR, 1), 0);
832                    self.compile_expr(b, value)?;
833                }
834                Prop::Spread(src) => {
835                    b.emit(Op::LoadInt(1), 0);
836                    self.compile_expr(b, src)?;
837                    b.emit(Op::LoadUndef, 0);
838                }
839            }
840        }
841        b.emit(Op::CallBuiltin(ops::MKOBJ, argc(props.len() * 3)?), 0);
842        Ok(())
843    }
844
845    fn compile_logical(&mut self, b: &mut ChunkBuilder, op: LogicalOp, l: &Expr, r: &Expr) -> Result<(), String> {
846        self.compile_expr(b, l)?;
847        b.emit(Op::Dup, 0);
848        let test_op = match op {
849            LogicalOp::And | LogicalOp::Or => ops::TRUTHY,
850            LogicalOp::Nullish => ops::NULLISH,
851        };
852        b.emit(Op::CallBuiltin(test_op, 1), 0);
853        let jump = match op {
854            LogicalOp::And => b.emit(Op::JumpIfFalse(0), 0), // false -> keep left
855            LogicalOp::Or => b.emit(Op::JumpIfTrue(0), 0),   // true -> keep left
856            LogicalOp::Nullish => b.emit(Op::JumpIfFalse(0), 0), // not-nullish -> keep left
857        };
858        b.emit(Op::Pop, 0); // drop left, evaluate right
859        self.compile_expr(b, r)?;
860        let end = b.current_pos();
861        b.patch_jump(jump, end);
862        Ok(())
863    }
864
865    fn compile_unary(&mut self, b: &mut ChunkBuilder, op: UnOp, e: &Expr) -> Result<(), String> {
866        match op {
867            UnOp::Neg => {
868                self.compile_expr(b, e)?;
869                b.emit(Op::Negate, 0);
870            }
871            UnOp::Not => {
872                self.compile_condition(b, e)?;
873                b.emit(Op::LogNot, 0);
874            }
875            UnOp::Pos => {
876                b.emit(Op::LoadInt(unop::POS), 0);
877                self.compile_expr(b, e)?;
878                b.emit(Op::CallBuiltin(ops::UNARY, 2), 0);
879            }
880            UnOp::BitNot => {
881                b.emit(Op::LoadInt(unop::BITNOT), 0);
882                self.compile_expr(b, e)?;
883                b.emit(Op::CallBuiltin(ops::UNARY, 2), 0);
884            }
885            UnOp::TypeOf => {
886                self.compile_expr(b, e)?;
887                b.emit(Op::CallBuiltin(ops::TYPEOF, 1), 0);
888            }
889            UnOp::Void => {
890                self.compile_expr(b, e)?;
891                b.emit(Op::Pop, 0);
892                b.emit(Op::LoadUndef, 0);
893            }
894            UnOp::Delete => match e {
895                Expr::Member { object, property, .. } => {
896                    self.compile_expr(b, object)?;
897                    self.name_const(b, property);
898                    b.emit(Op::CallBuiltin(ops::DELPROP_NAME, 2), 0);
899                }
900                Expr::Index { object, index, .. } => {
901                    self.compile_expr(b, object)?;
902                    self.compile_expr(b, index)?;
903                    b.emit(Op::CallBuiltin(ops::DELITEM, 2), 0);
904                }
905                _ => {
906                    b.emit(Op::LoadTrue, 0);
907                }
908            },
909        }
910        Ok(())
911    }
912
913    fn compile_binary(&mut self, b: &mut ChunkBuilder, op: BinOp, l: &Expr, r: &Expr) -> Result<(), String> {
914        // Native fast path (JIT-traceable); the numeric hook supplies JS
915        // semantics for non-number operands.
916        macro_rules! native {
917            ($opc:expr) => {{
918                self.compile_expr(b, l)?;
919                self.compile_expr(b, r)?;
920                b.emit($opc, 0);
921                return Ok(());
922            }};
923        }
924        match op {
925            BinOp::Add => native!(Op::Add),
926            BinOp::Sub => native!(Op::Sub),
927            BinOp::Mul => native!(Op::Mul),
928            BinOp::Div => {
929                // NOT native `Op::Div`: fusevm returns `Undef` for a zero divisor,
930                // but JS needs `x/0 === ±Infinity` / `0/0 === NaN`, so `/` is a
931                // builtin (fusevm's own documented pattern for non-default `/`).
932                self.compile_expr(b, l)?;
933                self.compile_expr(b, r)?;
934                b.emit(Op::CallBuiltin(ops::DIV, 2), 0);
935                return Ok(());
936            }
937            BinOp::Mod => native!(Op::Mod),
938            BinOp::Pow => native!(Op::Pow),
939            BinOp::Lt => native!(Op::NumLt),
940            BinOp::Le => native!(Op::NumLe),
941            BinOp::Gt => native!(Op::NumGt),
942            BinOp::Ge => native!(Op::NumGe),
943            BinOp::EqEqEq => {
944                self.compile_expr(b, l)?;
945                self.compile_expr(b, r)?;
946                b.emit(Op::CallBuiltin(ops::STRICT_EQ, 2), 0);
947            }
948            BinOp::NeEqEq => {
949                self.compile_expr(b, l)?;
950                self.compile_expr(b, r)?;
951                b.emit(Op::CallBuiltin(ops::STRICT_EQ, 2), 0);
952                b.emit(Op::LogNot, 0);
953            }
954            BinOp::EqEq => {
955                self.compile_expr(b, l)?;
956                self.compile_expr(b, r)?;
957                b.emit(Op::CallBuiltin(ops::LOOSE_EQ, 2), 0);
958            }
959            BinOp::NeEq => {
960                self.compile_expr(b, l)?;
961                self.compile_expr(b, r)?;
962                b.emit(Op::CallBuiltin(ops::LOOSE_EQ, 2), 0);
963                b.emit(Op::LogNot, 0);
964            }
965            BinOp::In => {
966                self.compile_expr(b, l)?;
967                self.compile_expr(b, r)?;
968                b.emit(Op::CallBuiltin(ops::CONTAINS, 2), 0);
969            }
970            BinOp::InstanceOf => {
971                self.compile_expr(b, l)?;
972                self.compile_expr(b, r)?;
973                b.emit(Op::CallBuiltin(ops::INSTANCEOF, 2), 0);
974            }
975            BinOp::BitAnd => self.emit_bitwise(b, bop::BITAND, l, r)?,
976            BinOp::BitOr => self.emit_bitwise(b, bop::BITOR, l, r)?,
977            BinOp::BitXor => self.emit_bitwise(b, bop::BITXOR, l, r)?,
978            BinOp::Shl => self.emit_bitwise(b, bop::SHL, l, r)?,
979            BinOp::Shr => self.emit_bitwise(b, bop::SHR, l, r)?,
980            BinOp::UShr => self.emit_bitwise(b, bop::USHR, l, r)?,
981        }
982        Ok(())
983    }
984
985    fn emit_bitwise(&mut self, b: &mut ChunkBuilder, tag: i64, l: &Expr, r: &Expr) -> Result<(), String> {
986        b.emit(Op::LoadInt(tag), 0);
987        self.compile_expr(b, l)?;
988        self.compile_expr(b, r)?;
989        b.emit(Op::CallBuiltin(ops::BINOP, 3), 0);
990        Ok(())
991    }
992
993    fn compile_update(&mut self, b: &mut ChunkBuilder, op: UpdateOp, prefix: bool, target: &Expr) -> Result<(), String> {
994        // Desugar to `target = target +/- 1`, yielding the pre/post value.
995        let one = Expr::Number(1.0);
996        let bin = if matches!(op, UpdateOp::Inc) { BinOp::Add } else { BinOp::Sub };
997        if prefix {
998            // ++x: compute new, store, yield new.
999            let newv = Expr::Binary(bin, Box::new(target.clone()), Box::new(one));
1000            self.compile_expr(b, &newv)?;
1001            b.emit(Op::Dup, 0);
1002            self.compile_bind(b, target, false)?;
1003        } else {
1004            // x++: yield old (as number), store new.
1005            // Push old coerced to number (+old), keep a copy, add 1, store.
1006            b.emit(Op::LoadInt(unop::POS), 0);
1007            self.compile_expr(b, target)?;
1008            b.emit(Op::CallBuiltin(ops::UNARY, 2), 0); // [oldNum]
1009            b.emit(Op::Dup, 0); // [oldNum, oldNum]
1010            b.emit(Op::LoadFloat(1.0), 0);
1011            match op {
1012                UpdateOp::Inc => b.emit(Op::Add, 0),
1013                UpdateOp::Dec => b.emit(Op::Sub, 0),
1014            };
1015            self.compile_bind(b, target, false)?; // stores new -> [oldNum]
1016        }
1017        Ok(())
1018    }
1019
1020    fn compile_member(&mut self, b: &mut ChunkBuilder, object: &Expr, property: &str, optional: bool) -> Result<(), String> {
1021        self.compile_expr(b, object)?;
1022        if optional {
1023            let jshort = self.emit_optional_guard(b);
1024            self.name_const(b, property);
1025            b.emit(Op::CallBuiltin(ops::GETATTR, 2), 0);
1026            let end = b.current_pos();
1027            b.patch_jump(jshort, end);
1028        } else {
1029            self.name_const(b, property);
1030            b.emit(Op::CallBuiltin(ops::GETATTR, 2), 0);
1031        }
1032        Ok(())
1033    }
1034
1035    fn compile_index(&mut self, b: &mut ChunkBuilder, object: &Expr, index: &Expr, optional: bool) -> Result<(), String> {
1036        self.compile_expr(b, object)?;
1037        if optional {
1038            let jshort = self.emit_optional_guard(b);
1039            self.compile_expr(b, index)?;
1040            b.emit(Op::CallBuiltin(ops::GETITEM, 2), 0);
1041            let end = b.current_pos();
1042            b.patch_jump(jshort, end);
1043        } else {
1044            self.compile_expr(b, index)?;
1045            b.emit(Op::CallBuiltin(ops::GETITEM, 2), 0);
1046        }
1047        Ok(())
1048    }
1049
1050    /// For an optional access: object on TOS. If nullish, replace with undefined
1051    /// and jump over the access. Returns the jump index to patch to the end.
1052    fn emit_optional_guard(&mut self, b: &mut ChunkBuilder) -> usize {
1053        b.emit(Op::Dup, 0);
1054        b.emit(Op::CallBuiltin(ops::NULLISH, 1), 0);
1055        let jnull = b.emit(Op::JumpIfFalse(0), 0); // not nullish -> continue access
1056        // nullish: drop object, push undefined, jump to end.
1057        b.emit(Op::Pop, 0);
1058        b.emit(Op::LoadUndef, 0);
1059        let jend = b.emit(Op::Jump(0), 0);
1060        let cont = b.current_pos();
1061        b.patch_jump(jnull, cont);
1062        jend
1063    }
1064
1065    fn compile_call(&mut self, b: &mut ChunkBuilder, func: &Expr, args: &[Expr], _optional: bool) -> Result<(), String> {
1066        let has_spread = args.iter().any(|a| matches!(a, Expr::Spread(_)));
1067        match func {
1068            Expr::Member { object, property, .. } => {
1069                self.compile_expr(b, object)?;
1070                self.name_const(b, property);
1071                if has_spread {
1072                    self.compile_spread_args(b, args)?; // [recv, name, argsArray]
1073                    b.emit(Op::CallBuiltin(ops::APPLY_METHOD, 3), 0);
1074                } else {
1075                    for a in args {
1076                        self.compile_expr(b, a)?;
1077                    }
1078                    b.emit(Op::CallBuiltin(ops::CALL_METHOD, argc(2 + args.len())?), 0);
1079                }
1080            }
1081            Expr::Index { object, index, .. } => {
1082                // recv[expr](args) — evaluate as a method via computed name.
1083                self.compile_expr(b, object)?; // [recv]
1084                b.emit(Op::Dup, 0); // [recv, recv]
1085                self.compile_expr(b, index)?; // [recv, recv, idx]
1086                b.emit(Op::CallBuiltin(ops::GETITEM, 2), 0); // [recv, fn]
1087                b.emit(Op::Swap, 0); // [fn, recv]... but APPLY needs callable then this
1088                // Fall back: call the function value with `this`=recv via CALL_VALUE
1089                // (this-binding for computed method calls is approximated).
1090                b.emit(Op::Pop, 0); // drop recv; keep fn on stack: [fn]
1091                if has_spread {
1092                    self.compile_spread_args(b, args)?;
1093                    b.emit(Op::CallBuiltin(ops::APPLY, 2), 0);
1094                } else {
1095                    for a in args {
1096                        self.compile_expr(b, a)?;
1097                    }
1098                    b.emit(Op::CallBuiltin(ops::CALL_VALUE, argc(1 + args.len())?), 0);
1099                }
1100            }
1101            Expr::Ident(n) => {
1102                self.name_const(b, n);
1103                if has_spread {
1104                    self.compile_spread_args(b, args)?; // [name, argsArray]
1105                    // Resolve name to a value, then APPLY.
1106                    b.emit(Op::Swap, 0); // [argsArray, name]
1107                    b.emit(Op::CallBuiltin(ops::GETLOCAL, 1), 0); // [argsArray, fn]
1108                    b.emit(Op::Swap, 0); // [fn, argsArray]
1109                    b.emit(Op::CallBuiltin(ops::APPLY, 2), 0);
1110                } else {
1111                    for a in args {
1112                        self.compile_expr(b, a)?;
1113                    }
1114                    b.emit(Op::CallBuiltin(ops::CALL, argc(1 + args.len())?), 0);
1115                }
1116            }
1117            _ => {
1118                self.compile_expr(b, func)?;
1119                if has_spread {
1120                    self.compile_spread_args(b, args)?;
1121                    b.emit(Op::CallBuiltin(ops::APPLY, 2), 0);
1122                } else {
1123                    for a in args {
1124                        self.compile_expr(b, a)?;
1125                    }
1126                    b.emit(Op::CallBuiltin(ops::CALL_VALUE, argc(1 + args.len())?), 0);
1127                }
1128            }
1129        }
1130        Ok(())
1131    }
1132
1133    /// Build a flat args array from a mix of plain args and `...spread` args.
1134    fn compile_spread_args(&mut self, b: &mut ChunkBuilder, args: &[Expr]) -> Result<(), String> {
1135        for a in args {
1136            match a {
1137                Expr::Spread(inner) => {
1138                    b.emit(Op::LoadInt(1), 0);
1139                    self.compile_expr(b, inner)?;
1140                }
1141                _ => {
1142                    b.emit(Op::LoadInt(0), 0);
1143                    self.compile_expr(b, a)?;
1144                }
1145            }
1146        }
1147        b.emit(Op::CallBuiltin(ops::BUILD_ARGS, argc(args.len() * 2)?), 0);
1148        Ok(())
1149    }
1150
1151    fn compile_new(&mut self, b: &mut ChunkBuilder, callee: &Expr, args: &[Expr]) -> Result<(), String> {
1152        self.compile_expr(b, callee)?;
1153        for a in args {
1154            self.compile_expr(b, a)?;
1155        }
1156        b.emit(Op::CallBuiltin(ops::NEW, argc(1 + args.len())?), 0);
1157        Ok(())
1158    }
1159}
1160
1161/// A prologue statement applying a parameter default: `if (name === undefined)
1162/// name = default;`.
1163fn default_stmt(name: &str, default: &Expr) -> Stmt {
1164    Stmt::from(StmtKind::If {
1165        test: Expr::Binary(
1166            BinOp::EqEqEq,
1167            Box::new(Expr::Ident(name.to_string())),
1168            Box::new(Expr::Undefined),
1169        ),
1170        cons: Box::new(Stmt::from(StmtKind::Expr(Expr::Assign {
1171            target: Box::new(Expr::Ident(name.to_string())),
1172            value: Box::new(default.clone()),
1173        }))),
1174        alt: None,
1175    })
1176}