we (web engine): Experimental web browser project to understand the limits of Claude
1//! AST → register-based bytecode compiler.
2//!
3//! Walks the AST produced by the parser and emits bytecode instructions.
4//! Uses a simple greedy register allocator: each new temporary gets the next
5//! available register, and registers are freed when no longer needed.
6
7use crate::ast::*;
8use crate::bytecode::*;
9use crate::JsError;
10use std::collections::HashSet;
11
12/// Compiler state for a single function scope.
13struct FunctionCompiler {
14 builder: BytecodeBuilder,
15 /// Maps local variable names to their register slots.
16 locals: Vec<Local>,
17 /// Next free register index.
18 next_reg: u8,
19 /// Stack of loop contexts for break/continue.
20 loop_stack: Vec<LoopCtx>,
21 /// Upvalues captured from the parent scope (used in inner functions).
22 upvalues: Vec<UpvalueEntry>,
23 /// Set of local variable names that are captured by inner functions.
24 /// Pre-populated before compilation by scanning inner function bodies.
25 captured_names: HashSet<String>,
26}
27
28#[derive(Debug, Clone)]
29struct Local {
30 name: String,
31 reg: Reg,
32 /// Whether this variable is captured by an inner function (stored in a cell).
33 is_captured: bool,
34 /// Whether this variable was declared with `const`.
35 is_const: bool,
36}
37
38/// An upvalue entry tracking how this function captures an outer variable.
39struct UpvalueEntry {
40 /// Name of the captured variable (for dedup during resolution).
41 name: String,
42 /// The resolved upvalue definition.
43 def: UpvalueDef,
44 /// Whether the original declaration was `const`.
45 is_const: bool,
46}
47
48struct LoopCtx {
49 /// Label, if this is a labeled loop.
50 label: Option<String>,
51 /// Patch positions for break jumps.
52 break_patches: Vec<usize>,
53 /// Patch positions for continue jumps (patched after body compilation).
54 continue_patches: Vec<usize>,
55}
56
57impl FunctionCompiler {
58 fn new(name: String, param_count: u8) -> Self {
59 Self {
60 builder: BytecodeBuilder::new(name, param_count),
61 locals: Vec::new(),
62 next_reg: 0,
63 loop_stack: Vec::new(),
64 upvalues: Vec::new(),
65 captured_names: HashSet::new(),
66 }
67 }
68
69 /// Allocate a register, updating the high-water mark.
70 fn alloc_reg(&mut self) -> Reg {
71 let r = self.next_reg;
72 self.next_reg = self.next_reg.checked_add(1).expect("register overflow");
73 if self.next_reg > self.builder.func.register_count {
74 self.builder.func.register_count = self.next_reg;
75 }
76 r
77 }
78
79 /// Free the last allocated register (must be called in reverse order).
80 fn free_reg(&mut self, r: Reg) {
81 debug_assert_eq!(
82 r,
83 self.next_reg - 1,
84 "registers must be freed in reverse order"
85 );
86 self.next_reg -= 1;
87 }
88
89 /// Look up a local variable by name, returning full info.
90 fn find_local_info(&self, name: &str) -> Option<&Local> {
91 self.locals.iter().rev().find(|l| l.name == name)
92 }
93
94 /// Look up an upvalue by name, returning its index.
95 fn find_upvalue(&self, name: &str) -> Option<u8> {
96 self.upvalues
97 .iter()
98 .position(|u| u.name == name)
99 .map(|i| i as u8)
100 }
101
102 /// Check if an upvalue is const.
103 fn is_upvalue_const(&self, idx: u8) -> bool {
104 self.upvalues
105 .get(idx as usize)
106 .map(|u| u.is_const)
107 .unwrap_or(false)
108 }
109
110 /// Define a local variable with capture and const flags.
111 fn define_local_ext(&mut self, name: &str, is_captured: bool, is_const: bool) -> Reg {
112 let reg = self.alloc_reg();
113 self.locals.push(Local {
114 name: name.to_string(),
115 reg,
116 is_captured,
117 is_const,
118 });
119 reg
120 }
121}
122
123// ── Free variable analysis ──────────────────────────────────
124
125/// Collect identifiers referenced inside `body` that are not declared locally
126/// (params or variable declarations within the body). This set represents the
127/// "free variables" of a function body — variables that must be captured.
128/// This includes transitive free variables from nested functions.
129fn collect_free_vars(params: &[Pattern], body: &[Stmt]) -> HashSet<String> {
130 let mut declared = HashSet::new();
131 let mut referenced = HashSet::new();
132
133 // Params are local declarations.
134 for p in params {
135 collect_pattern_names(p, &mut declared);
136 }
137
138 // Collect declarations and references from the body.
139 for stmt in body {
140 collect_stmt_decls(stmt, &mut declared);
141 }
142 for stmt in body {
143 collect_stmt_refs(stmt, &declared, &mut referenced);
144 }
145
146 // Also include transitive free variables from nested inner functions.
147 // If an inner function references `x` and `x` is not declared in THIS scope,
148 // then `x` is also a free variable of THIS function.
149 let inner_caps = collect_inner_captures(body);
150 for name in inner_caps {
151 if !declared.contains(&name) {
152 referenced.insert(name);
153 }
154 }
155
156 referenced
157}
158
159/// Collect free variables from an arrow function body.
160fn collect_free_vars_arrow(params: &[Pattern], body: &ArrowBody) -> HashSet<String> {
161 let mut declared = HashSet::new();
162 let mut referenced = HashSet::new();
163
164 for p in params {
165 collect_pattern_names(p, &mut declared);
166 }
167
168 match body {
169 ArrowBody::Expr(expr) => {
170 collect_expr_refs(expr, &declared, &mut referenced);
171 }
172 ArrowBody::Block(stmts) => {
173 for stmt in stmts {
174 collect_stmt_decls(stmt, &mut declared);
175 }
176 for stmt in stmts {
177 collect_stmt_refs(stmt, &declared, &mut referenced);
178 }
179 // Transitive free variables from nested functions.
180 let inner_caps = collect_inner_captures(stmts);
181 for name in inner_caps {
182 if !declared.contains(&name) {
183 referenced.insert(name);
184 }
185 }
186 }
187 }
188
189 referenced
190}
191
192fn collect_pattern_names(pat: &Pattern, names: &mut HashSet<String>) {
193 match &pat.kind {
194 PatternKind::Identifier(name) => {
195 names.insert(name.clone());
196 }
197 PatternKind::Array { elements, rest } => {
198 for elem in elements.iter().flatten() {
199 collect_pattern_names(elem, names);
200 }
201 if let Some(rest) = rest {
202 collect_pattern_names(rest, names);
203 }
204 }
205 PatternKind::Object { properties, rest } => {
206 for prop in properties {
207 collect_pattern_names(&prop.value, names);
208 }
209 if let Some(rest) = rest {
210 collect_pattern_names(rest, names);
211 }
212 }
213 PatternKind::Assign { left, .. } => {
214 collect_pattern_names(left, names);
215 }
216 }
217}
218
219/// Collect all variable/function declarations in a statement (not recursing into
220/// inner functions — those form their own scope).
221fn collect_stmt_decls(stmt: &Stmt, declared: &mut HashSet<String>) {
222 match &stmt.kind {
223 StmtKind::VarDecl { declarators, .. } => {
224 for d in declarators {
225 collect_pattern_names(&d.pattern, declared);
226 }
227 }
228 StmtKind::FunctionDecl(f) => {
229 if let Some(name) = &f.id {
230 declared.insert(name.clone());
231 }
232 }
233 StmtKind::ClassDecl(c) => {
234 if let Some(name) = &c.id {
235 declared.insert(name.clone());
236 }
237 }
238 StmtKind::Block(stmts) => {
239 for s in stmts {
240 collect_stmt_decls(s, declared);
241 }
242 }
243 StmtKind::If {
244 consequent,
245 alternate,
246 ..
247 } => {
248 collect_stmt_decls(consequent, declared);
249 if let Some(alt) = alternate {
250 collect_stmt_decls(alt, declared);
251 }
252 }
253 StmtKind::While { body, .. }
254 | StmtKind::DoWhile { body, .. }
255 | StmtKind::Labeled { body, .. } => {
256 collect_stmt_decls(body, declared);
257 }
258 StmtKind::For { init, body, .. } => {
259 if let Some(ForInit::VarDecl { declarators, .. }) = init {
260 for d in declarators {
261 collect_pattern_names(&d.pattern, declared);
262 }
263 }
264 collect_stmt_decls(body, declared);
265 }
266 StmtKind::ForIn { left, body, .. } | StmtKind::ForOf { left, body, .. } => {
267 if let ForInOfLeft::VarDecl { pattern, .. } = left {
268 collect_pattern_names(pattern, declared);
269 }
270 collect_stmt_decls(body, declared);
271 }
272 StmtKind::Try {
273 block,
274 handler,
275 finalizer,
276 } => {
277 for s in block {
278 collect_stmt_decls(s, declared);
279 }
280 if let Some(h) = handler {
281 if let Some(param) = &h.param {
282 collect_pattern_names(param, declared);
283 }
284 for s in &h.body {
285 collect_stmt_decls(s, declared);
286 }
287 }
288 if let Some(fin) = finalizer {
289 for s in fin {
290 collect_stmt_decls(s, declared);
291 }
292 }
293 }
294 StmtKind::Switch { cases, .. } => {
295 for case in cases {
296 for s in &case.consequent {
297 collect_stmt_decls(s, declared);
298 }
299 }
300 }
301 _ => {}
302 }
303}
304
305/// Collect all identifier references in a statement, excluding inner function scopes.
306/// Identifiers that are in `declared` are local and skipped.
307fn collect_stmt_refs(stmt: &Stmt, declared: &HashSet<String>, refs: &mut HashSet<String>) {
308 match &stmt.kind {
309 StmtKind::Expr(expr) => collect_expr_refs(expr, declared, refs),
310 StmtKind::Block(stmts) => {
311 for s in stmts {
312 collect_stmt_refs(s, declared, refs);
313 }
314 }
315 StmtKind::VarDecl { declarators, .. } => {
316 for d in declarators {
317 if let Some(init) = &d.init {
318 collect_expr_refs(init, declared, refs);
319 }
320 }
321 }
322 StmtKind::FunctionDecl(_) => {
323 // Don't recurse into inner functions — they have their own scope.
324 }
325 StmtKind::If {
326 test,
327 consequent,
328 alternate,
329 } => {
330 collect_expr_refs(test, declared, refs);
331 collect_stmt_refs(consequent, declared, refs);
332 if let Some(alt) = alternate {
333 collect_stmt_refs(alt, declared, refs);
334 }
335 }
336 StmtKind::While { test, body } => {
337 collect_expr_refs(test, declared, refs);
338 collect_stmt_refs(body, declared, refs);
339 }
340 StmtKind::DoWhile { body, test } => {
341 collect_stmt_refs(body, declared, refs);
342 collect_expr_refs(test, declared, refs);
343 }
344 StmtKind::For {
345 init,
346 test,
347 update,
348 body,
349 } => {
350 if let Some(init) = init {
351 match init {
352 ForInit::VarDecl { declarators, .. } => {
353 for d in declarators {
354 if let Some(init) = &d.init {
355 collect_expr_refs(init, declared, refs);
356 }
357 }
358 }
359 ForInit::Expr(e) => collect_expr_refs(e, declared, refs),
360 }
361 }
362 if let Some(t) = test {
363 collect_expr_refs(t, declared, refs);
364 }
365 if let Some(u) = update {
366 collect_expr_refs(u, declared, refs);
367 }
368 collect_stmt_refs(body, declared, refs);
369 }
370 StmtKind::ForIn { right, body, .. } | StmtKind::ForOf { right, body, .. } => {
371 collect_expr_refs(right, declared, refs);
372 collect_stmt_refs(body, declared, refs);
373 }
374 StmtKind::Return(Some(expr)) | StmtKind::Throw(expr) => {
375 collect_expr_refs(expr, declared, refs);
376 }
377 StmtKind::Try {
378 block,
379 handler,
380 finalizer,
381 } => {
382 for s in block {
383 collect_stmt_refs(s, declared, refs);
384 }
385 if let Some(h) = handler {
386 for s in &h.body {
387 collect_stmt_refs(s, declared, refs);
388 }
389 }
390 if let Some(fin) = finalizer {
391 for s in fin {
392 collect_stmt_refs(s, declared, refs);
393 }
394 }
395 }
396 StmtKind::Switch {
397 discriminant,
398 cases,
399 } => {
400 collect_expr_refs(discriminant, declared, refs);
401 for case in cases {
402 if let Some(test) = &case.test {
403 collect_expr_refs(test, declared, refs);
404 }
405 for s in &case.consequent {
406 collect_stmt_refs(s, declared, refs);
407 }
408 }
409 }
410 StmtKind::Labeled { body, .. } => {
411 collect_stmt_refs(body, declared, refs);
412 }
413 _ => {}
414 }
415}
416
417/// Collect identifier references in an expression. Does NOT recurse into
418/// inner function/arrow bodies (those form their own scope).
419fn collect_expr_refs(expr: &Expr, declared: &HashSet<String>, refs: &mut HashSet<String>) {
420 match &expr.kind {
421 ExprKind::Identifier(name) => {
422 if !declared.contains(name) {
423 refs.insert(name.clone());
424 }
425 }
426 ExprKind::Binary { left, right, .. }
427 | ExprKind::Logical { left, right, .. }
428 | ExprKind::Assignment { left, right, .. } => {
429 collect_expr_refs(left, declared, refs);
430 collect_expr_refs(right, declared, refs);
431 }
432 ExprKind::Unary { argument, .. } | ExprKind::Update { argument, .. } => {
433 collect_expr_refs(argument, declared, refs);
434 }
435 ExprKind::Conditional {
436 test,
437 consequent,
438 alternate,
439 } => {
440 collect_expr_refs(test, declared, refs);
441 collect_expr_refs(consequent, declared, refs);
442 collect_expr_refs(alternate, declared, refs);
443 }
444 ExprKind::Call { callee, arguments } | ExprKind::New { callee, arguments } => {
445 collect_expr_refs(callee, declared, refs);
446 for arg in arguments {
447 collect_expr_refs(arg, declared, refs);
448 }
449 }
450 ExprKind::Member {
451 object,
452 property,
453 computed,
454 ..
455 } => {
456 collect_expr_refs(object, declared, refs);
457 if *computed {
458 collect_expr_refs(property, declared, refs);
459 }
460 }
461 ExprKind::Array(elements) => {
462 for elem in elements.iter().flatten() {
463 match elem {
464 ArrayElement::Expr(e) | ArrayElement::Spread(e) => {
465 collect_expr_refs(e, declared, refs);
466 }
467 }
468 }
469 }
470 ExprKind::Object(props) => {
471 for prop in props {
472 if let PropertyKey::Computed(e) = &prop.key {
473 collect_expr_refs(e, declared, refs);
474 }
475 if let Some(val) = &prop.value {
476 collect_expr_refs(val, declared, refs);
477 }
478 }
479 }
480 ExprKind::Sequence(exprs) => {
481 for e in exprs {
482 collect_expr_refs(e, declared, refs);
483 }
484 }
485 ExprKind::Spread(inner) => {
486 collect_expr_refs(inner, declared, refs);
487 }
488 ExprKind::TemplateLiteral { expressions, .. } => {
489 for e in expressions {
490 collect_expr_refs(e, declared, refs);
491 }
492 }
493 // Function/Arrow/Class bodies are new scopes — don't recurse.
494 ExprKind::Function(_) | ExprKind::Arrow { .. } | ExprKind::Class(_) => {}
495 _ => {}
496 }
497}
498
499/// Collect the free variables of ALL inner functions/arrows within a list of
500/// statements. Returns the set of outer-scope names they reference.
501fn collect_inner_captures(stmts: &[Stmt]) -> HashSet<String> {
502 let mut captures = HashSet::new();
503 for stmt in stmts {
504 collect_inner_captures_stmt(stmt, &mut captures);
505 }
506 captures
507}
508
509fn collect_inner_captures_stmt(stmt: &Stmt, caps: &mut HashSet<String>) {
510 match &stmt.kind {
511 StmtKind::FunctionDecl(f) => {
512 let fv = collect_free_vars(&f.params, &f.body);
513 caps.extend(fv);
514 }
515 StmtKind::Expr(expr) => collect_inner_captures_expr(expr, caps),
516 StmtKind::VarDecl { declarators, .. } => {
517 for d in declarators {
518 if let Some(init) = &d.init {
519 collect_inner_captures_expr(init, caps);
520 }
521 }
522 }
523 StmtKind::Block(stmts) => {
524 for s in stmts {
525 collect_inner_captures_stmt(s, caps);
526 }
527 }
528 StmtKind::If {
529 test,
530 consequent,
531 alternate,
532 } => {
533 collect_inner_captures_expr(test, caps);
534 collect_inner_captures_stmt(consequent, caps);
535 if let Some(alt) = alternate {
536 collect_inner_captures_stmt(alt, caps);
537 }
538 }
539 StmtKind::While { test, body } => {
540 collect_inner_captures_expr(test, caps);
541 collect_inner_captures_stmt(body, caps);
542 }
543 StmtKind::DoWhile { body, test } => {
544 collect_inner_captures_stmt(body, caps);
545 collect_inner_captures_expr(test, caps);
546 }
547 StmtKind::For {
548 init,
549 test,
550 update,
551 body,
552 } => {
553 if let Some(ForInit::Expr(e)) = init {
554 collect_inner_captures_expr(e, caps);
555 }
556 if let Some(ForInit::VarDecl { declarators, .. }) = init {
557 for d in declarators {
558 if let Some(init) = &d.init {
559 collect_inner_captures_expr(init, caps);
560 }
561 }
562 }
563 if let Some(t) = test {
564 collect_inner_captures_expr(t, caps);
565 }
566 if let Some(u) = update {
567 collect_inner_captures_expr(u, caps);
568 }
569 collect_inner_captures_stmt(body, caps);
570 }
571 StmtKind::ForIn { right, body, .. } | StmtKind::ForOf { right, body, .. } => {
572 collect_inner_captures_expr(right, caps);
573 collect_inner_captures_stmt(body, caps);
574 }
575 StmtKind::Return(Some(expr)) | StmtKind::Throw(expr) => {
576 collect_inner_captures_expr(expr, caps);
577 }
578 StmtKind::Try {
579 block,
580 handler,
581 finalizer,
582 } => {
583 for s in block {
584 collect_inner_captures_stmt(s, caps);
585 }
586 if let Some(h) = handler {
587 for s in &h.body {
588 collect_inner_captures_stmt(s, caps);
589 }
590 }
591 if let Some(fin) = finalizer {
592 for s in fin {
593 collect_inner_captures_stmt(s, caps);
594 }
595 }
596 }
597 StmtKind::Switch {
598 discriminant,
599 cases,
600 } => {
601 collect_inner_captures_expr(discriminant, caps);
602 for case in cases {
603 if let Some(test) = &case.test {
604 collect_inner_captures_expr(test, caps);
605 }
606 for s in &case.consequent {
607 collect_inner_captures_stmt(s, caps);
608 }
609 }
610 }
611 StmtKind::Labeled { body, .. } => {
612 collect_inner_captures_stmt(body, caps);
613 }
614 _ => {}
615 }
616}
617
618fn collect_inner_captures_expr(expr: &Expr, caps: &mut HashSet<String>) {
619 match &expr.kind {
620 ExprKind::Function(f) => {
621 let fv = collect_free_vars(&f.params, &f.body);
622 caps.extend(fv);
623 }
624 ExprKind::Arrow { params, body, .. } => {
625 let fv = collect_free_vars_arrow(params, body);
626 caps.extend(fv);
627 }
628 ExprKind::Binary { left, right, .. }
629 | ExprKind::Logical { left, right, .. }
630 | ExprKind::Assignment { left, right, .. } => {
631 collect_inner_captures_expr(left, caps);
632 collect_inner_captures_expr(right, caps);
633 }
634 ExprKind::Unary { argument, .. } | ExprKind::Update { argument, .. } => {
635 collect_inner_captures_expr(argument, caps);
636 }
637 ExprKind::Conditional {
638 test,
639 consequent,
640 alternate,
641 } => {
642 collect_inner_captures_expr(test, caps);
643 collect_inner_captures_expr(consequent, caps);
644 collect_inner_captures_expr(alternate, caps);
645 }
646 ExprKind::Call { callee, arguments } | ExprKind::New { callee, arguments } => {
647 collect_inner_captures_expr(callee, caps);
648 for arg in arguments {
649 collect_inner_captures_expr(arg, caps);
650 }
651 }
652 ExprKind::Member {
653 object,
654 property,
655 computed,
656 ..
657 } => {
658 collect_inner_captures_expr(object, caps);
659 if *computed {
660 collect_inner_captures_expr(property, caps);
661 }
662 }
663 ExprKind::Array(elements) => {
664 for elem in elements.iter().flatten() {
665 match elem {
666 ArrayElement::Expr(e) | ArrayElement::Spread(e) => {
667 collect_inner_captures_expr(e, caps);
668 }
669 }
670 }
671 }
672 ExprKind::Object(props) => {
673 for prop in props {
674 if let PropertyKey::Computed(e) = &prop.key {
675 collect_inner_captures_expr(e, caps);
676 }
677 if let Some(val) = &prop.value {
678 collect_inner_captures_expr(val, caps);
679 }
680 }
681 }
682 ExprKind::Sequence(exprs) => {
683 for e in exprs {
684 collect_inner_captures_expr(e, caps);
685 }
686 }
687 ExprKind::Spread(inner) => {
688 collect_inner_captures_expr(inner, caps);
689 }
690 ExprKind::TemplateLiteral { expressions, .. } => {
691 for e in expressions {
692 collect_inner_captures_expr(e, caps);
693 }
694 }
695 ExprKind::Class(c) => {
696 for member in &c.body {
697 if let ClassMemberKind::Method { value, .. } = &member.kind {
698 let fv = collect_free_vars(&value.params, &value.body);
699 caps.extend(fv);
700 }
701 }
702 }
703 _ => {}
704 }
705}
706
707/// Compile a parsed program into a top-level bytecode function.
708pub fn compile(program: &Program) -> Result<Function, JsError> {
709 let mut fc = FunctionCompiler::new("<main>".into(), 0);
710
711 // Pre-scan to find which top-level locals are captured by inner functions.
712 fc.captured_names = collect_inner_captures(&program.body);
713
714 // Reserve r0 for the implicit return value.
715 let result_reg = fc.alloc_reg();
716 fc.builder.emit_reg(Op::LoadUndefined, result_reg);
717
718 compile_stmts(&mut fc, &program.body, result_reg)?;
719
720 fc.builder.emit_reg(Op::Return, result_reg);
721 Ok(fc.builder.finish())
722}
723
724fn compile_stmts(
725 fc: &mut FunctionCompiler,
726 stmts: &[Stmt],
727 result_reg: Reg,
728) -> Result<(), JsError> {
729 for stmt in stmts {
730 compile_stmt(fc, stmt, result_reg)?;
731 }
732 Ok(())
733}
734
735fn compile_stmt(fc: &mut FunctionCompiler, stmt: &Stmt, result_reg: Reg) -> Result<(), JsError> {
736 match &stmt.kind {
737 StmtKind::Expr(expr) => {
738 // Expression statement: compile and store result in result_reg.
739 compile_expr(fc, expr, result_reg)?;
740 }
741
742 StmtKind::Block(stmts) => {
743 let saved_locals = fc.locals.len();
744 let saved_next = fc.next_reg;
745 compile_stmts(fc, stmts, result_reg)?;
746 // Pop locals from this block.
747 fc.locals.truncate(saved_locals);
748 fc.next_reg = saved_next;
749 }
750
751 StmtKind::VarDecl { kind, declarators } => {
752 for decl in declarators {
753 compile_var_declarator(fc, decl, *kind)?;
754 }
755 }
756
757 StmtKind::FunctionDecl(func_def) => {
758 compile_function_decl(fc, func_def)?;
759 }
760
761 StmtKind::If {
762 test,
763 consequent,
764 alternate,
765 } => {
766 compile_if(fc, test, consequent, alternate.as_deref(), result_reg)?;
767 }
768
769 StmtKind::While { test, body } => {
770 compile_while(fc, test, body, None, result_reg)?;
771 }
772
773 StmtKind::DoWhile { body, test } => {
774 compile_do_while(fc, body, test, None, result_reg)?;
775 }
776
777 StmtKind::For {
778 init,
779 test,
780 update,
781 body,
782 } => {
783 compile_for(
784 fc,
785 init.as_ref(),
786 test.as_ref(),
787 update.as_ref(),
788 body,
789 None,
790 result_reg,
791 )?;
792 }
793
794 StmtKind::ForIn { left, right, body } => {
795 let saved_locals = fc.locals.len();
796 let saved_next = fc.next_reg;
797
798 // Evaluate the RHS object.
799 let obj_r = fc.alloc_reg();
800 compile_expr(fc, right, obj_r)?;
801
802 // ForInInit: collect enumerable keys into an array.
803 let keys_r = fc.alloc_reg();
804 fc.builder.emit_reg_reg(Op::ForInInit, keys_r, obj_r);
805 // obj_r is no longer needed but we don't free it (LIFO constraint).
806
807 // Index register (starts at 0).
808 let idx_r = fc.alloc_reg();
809 fc.builder.emit_load_int8(idx_r, 0);
810
811 // Key and done registers (reused each iteration).
812 let key_r = fc.alloc_reg();
813 let done_r = fc.alloc_reg();
814
815 let loop_start = fc.builder.offset();
816
817 // ForInNext: get next key or done flag.
818 fc.builder
819 .emit_reg4(Op::ForInNext, key_r, done_r, keys_r, idx_r);
820
821 // Exit loop if done.
822 let exit_patch = fc.builder.emit_cond_jump(Op::JumpIfTrue, done_r);
823
824 // Bind the loop variable.
825 match left {
826 ForInOfLeft::VarDecl { kind, pattern } => {
827 if let PatternKind::Identifier(name) = &pattern.kind {
828 let is_captured = fc.captured_names.contains(name.as_str());
829 let is_const = *kind == VarKind::Const;
830 let var_r = fc.define_local_ext(name, is_captured, is_const);
831 if is_captured {
832 fc.builder.emit_reg(Op::NewCell, var_r);
833 fc.builder.emit_reg_reg(Op::CellStore, var_r, key_r);
834 } else {
835 fc.builder.emit_reg_reg(Op::Move, var_r, key_r);
836 }
837 }
838 }
839 ForInOfLeft::Pattern(pattern) => {
840 if let PatternKind::Identifier(name) = &pattern.kind {
841 if let Some(local) = fc.find_local_info(name) {
842 let reg = local.reg;
843 let captured = local.is_captured;
844 if captured {
845 fc.builder.emit_reg_reg(Op::CellStore, reg, key_r);
846 } else {
847 fc.builder.emit_reg_reg(Op::Move, reg, key_r);
848 }
849 } else if let Some(uv_idx) = fc.find_upvalue(name) {
850 fc.builder.emit_store_upvalue(uv_idx, key_r);
851 } else {
852 let ni = fc.builder.add_name(name);
853 fc.builder.emit_store_global(ni, key_r);
854 }
855 }
856 }
857 }
858
859 // Compile the body.
860 fc.loop_stack.push(LoopCtx {
861 label: None,
862 break_patches: Vec::new(),
863 continue_patches: Vec::new(),
864 });
865 compile_stmt(fc, body, result_reg)?;
866
867 // Increment index: idx = idx + 1.
868 // Use a temp register for the constant 1. Since we allocate it
869 // after the loop body, we can't free it with LIFO either — the
870 // saved_next restoration handles cleanup.
871 let one_r = fc.alloc_reg();
872 fc.builder.emit_load_int8(one_r, 1);
873 fc.builder.emit_reg3(Op::Add, idx_r, idx_r, one_r);
874
875 // Jump back to loop start.
876 fc.builder.emit_jump_to(loop_start);
877
878 // Patch exit and continue jumps.
879 fc.builder.patch_jump(exit_patch);
880 let ctx = fc.loop_stack.pop().unwrap();
881 for patch in ctx.break_patches {
882 fc.builder.patch_jump(patch);
883 }
884 for patch in ctx.continue_patches {
885 fc.builder.patch_jump_to(patch, loop_start);
886 }
887
888 // Restore locals/regs — frees all temporaries at once.
889 fc.locals.truncate(saved_locals);
890 fc.next_reg = saved_next;
891 }
892
893 StmtKind::ForOf {
894 left,
895 right,
896 body,
897 is_await,
898 } => {
899 let saved_locals = fc.locals.len();
900 let saved_next = fc.next_reg;
901
902 // Evaluate the iterable.
903 let iterable_r = fc.alloc_reg();
904 compile_expr(fc, right, iterable_r)?;
905
906 // Get the iterator: call iterable[@@iterator]() or @@asyncIterator().
907 let iter_method_r = fc.alloc_reg();
908 let sym_name = if *is_await {
909 "@@asyncIterator"
910 } else {
911 "@@iterator"
912 };
913 let sym_iter_ni = fc.builder.add_name(sym_name);
914 fc.builder
915 .emit_get_prop_name(iter_method_r, iterable_r, sym_iter_ni);
916
917 // Set `this` = iterable for the call.
918 let this_ni = fc.builder.add_name("this");
919 fc.builder.emit_store_global(this_ni, iterable_r);
920
921 // Call [@@iterator/@@asyncIterator]() with 0 args.
922 let iterator_r = fc.alloc_reg();
923 let args_start = fc.next_reg;
924 fc.builder
925 .emit_call(iterator_r, iter_method_r, args_start, 0);
926
927 // Temp registers for next method, result, done, value.
928 let next_method_r = fc.alloc_reg();
929 let next_ni = fc.builder.add_name("next");
930 fc.builder
931 .emit_get_prop_name(next_method_r, iterator_r, next_ni);
932
933 let result_obj_r = fc.alloc_reg();
934 let done_r = fc.alloc_reg();
935 let value_r = fc.alloc_reg();
936
937 // Loop start.
938 let loop_start = fc.builder.offset();
939
940 // Set `this` = iterator for the .next() call.
941 fc.builder.emit_store_global(this_ni, iterator_r);
942
943 // Call iterator.next().
944 fc.builder
945 .emit_call(result_obj_r, next_method_r, args_start, 0);
946
947 // For await: await the result of .next() (which returns a Promise).
948 if *is_await {
949 let awaited_r = fc.alloc_reg();
950 fc.builder.emit_await(awaited_r, result_obj_r);
951 fc.builder.emit_reg_reg(Op::Move, result_obj_r, awaited_r);
952 fc.free_reg(awaited_r);
953 }
954
955 // Extract done and value.
956 let done_ni = fc.builder.add_name("done");
957 let value_ni = fc.builder.add_name("value");
958 fc.builder.emit_get_prop_name(done_r, result_obj_r, done_ni);
959
960 // Exit if done.
961 let exit_patch = fc.builder.emit_cond_jump(Op::JumpIfTrue, done_r);
962
963 // Extract value.
964 fc.builder
965 .emit_get_prop_name(value_r, result_obj_r, value_ni);
966
967 // Bind the loop variable.
968 match left {
969 ForInOfLeft::VarDecl { kind, pattern } => match &pattern.kind {
970 PatternKind::Identifier(name) => {
971 let is_captured = fc.captured_names.contains(name.as_str());
972 let is_const = *kind == VarKind::Const;
973 let var_r = fc.define_local_ext(name, is_captured, is_const);
974 if is_captured {
975 fc.builder.emit_reg(Op::NewCell, var_r);
976 fc.builder.emit_reg_reg(Op::CellStore, var_r, value_r);
977 } else {
978 fc.builder.emit_reg_reg(Op::Move, var_r, value_r);
979 }
980 }
981 _ => {
982 // Destructuring pattern in for...of.
983 compile_destructuring_pattern(fc, pattern, value_r)?;
984 }
985 },
986 ForInOfLeft::Pattern(pattern) => match &pattern.kind {
987 PatternKind::Identifier(name) => {
988 if let Some(local) = fc.find_local_info(name) {
989 let reg = local.reg;
990 let captured = local.is_captured;
991 if captured {
992 fc.builder.emit_reg_reg(Op::CellStore, reg, value_r);
993 } else {
994 fc.builder.emit_reg_reg(Op::Move, reg, value_r);
995 }
996 } else if let Some(uv_idx) = fc.find_upvalue(name) {
997 fc.builder.emit_store_upvalue(uv_idx, value_r);
998 } else {
999 let ni = fc.builder.add_name(name);
1000 fc.builder.emit_store_global(ni, value_r);
1001 }
1002 }
1003 _ => {
1004 compile_destructuring_pattern(fc, pattern, value_r)?;
1005 }
1006 },
1007 }
1008
1009 // Push loop context for break/continue.
1010 fc.loop_stack.push(LoopCtx {
1011 label: None,
1012 break_patches: Vec::new(),
1013 continue_patches: Vec::new(),
1014 });
1015
1016 // Compile body.
1017 compile_stmt(fc, body, result_reg)?;
1018
1019 // Jump back to loop start.
1020 fc.builder.emit_jump_to(loop_start);
1021
1022 // Patch exit.
1023 fc.builder.patch_jump(exit_patch);
1024 let ctx = fc.loop_stack.pop().unwrap();
1025 for patch in ctx.break_patches {
1026 fc.builder.patch_jump(patch);
1027 }
1028 for patch in ctx.continue_patches {
1029 fc.builder.patch_jump_to(patch, loop_start);
1030 }
1031
1032 // Restore locals/regs.
1033 fc.locals.truncate(saved_locals);
1034 fc.next_reg = saved_next;
1035 }
1036
1037 StmtKind::Return(expr) => {
1038 let ret_reg = fc.alloc_reg();
1039 if let Some(e) = expr {
1040 compile_expr(fc, e, ret_reg)?;
1041 } else {
1042 fc.builder.emit_reg(Op::LoadUndefined, ret_reg);
1043 }
1044 fc.builder.emit_reg(Op::Return, ret_reg);
1045 fc.free_reg(ret_reg);
1046 }
1047
1048 StmtKind::Throw(expr) => {
1049 let tmp = fc.alloc_reg();
1050 compile_expr(fc, expr, tmp)?;
1051 fc.builder.emit_reg(Op::Throw, tmp);
1052 fc.free_reg(tmp);
1053 }
1054
1055 StmtKind::Break(label) => {
1056 // Find the matching loop context.
1057 let idx = find_loop_ctx(&fc.loop_stack, label.as_deref())
1058 .ok_or_else(|| JsError::SyntaxError("break outside of loop".into()))?;
1059 let patch = fc.builder.emit_jump(Op::Jump);
1060 fc.loop_stack[idx].break_patches.push(patch);
1061 }
1062
1063 StmtKind::Continue(label) => {
1064 let idx = find_loop_ctx(&fc.loop_stack, label.as_deref())
1065 .ok_or_else(|| JsError::SyntaxError("continue outside of loop".into()))?;
1066 let patch = fc.builder.emit_jump(Op::Jump);
1067 fc.loop_stack[idx].continue_patches.push(patch);
1068 }
1069
1070 StmtKind::Labeled { label, body } => {
1071 // If body is a loop, propagate the label.
1072 match &body.kind {
1073 StmtKind::While { test, body: inner } => {
1074 compile_while(fc, test, inner, Some(label.clone()), result_reg)?;
1075 }
1076 StmtKind::DoWhile { body: inner, test } => {
1077 compile_do_while(fc, inner, test, Some(label.clone()), result_reg)?;
1078 }
1079 StmtKind::For {
1080 init,
1081 test,
1082 update,
1083 body: inner,
1084 } => {
1085 compile_for(
1086 fc,
1087 init.as_ref(),
1088 test.as_ref(),
1089 update.as_ref(),
1090 inner,
1091 Some(label.clone()),
1092 result_reg,
1093 )?;
1094 }
1095 _ => {
1096 compile_stmt(fc, body, result_reg)?;
1097 }
1098 }
1099 }
1100
1101 StmtKind::Switch {
1102 discriminant,
1103 cases,
1104 } => {
1105 compile_switch(fc, discriminant, cases, result_reg)?;
1106 }
1107
1108 StmtKind::Try {
1109 block,
1110 handler,
1111 finalizer,
1112 } => {
1113 if let Some(catch) = handler {
1114 // The catch register will receive the exception value. Use the
1115 // current next_reg so it doesn't conflict with temporaries
1116 // allocated inside the try block.
1117 let saved_next = fc.next_reg;
1118 let catch_reg = fc.alloc_reg();
1119 // Immediately "release" it so the try block can reuse registers
1120 // from this point. We remember catch_reg for PushExceptionHandler.
1121 fc.next_reg = saved_next;
1122
1123 // Emit PushExceptionHandler with placeholder offset to catch block.
1124 let catch_patch = fc.builder.emit_push_exception_handler(catch_reg);
1125
1126 let locals_len = fc.locals.len();
1127
1128 // Compile the try block.
1129 compile_stmts(fc, block, result_reg)?;
1130
1131 // If we reach here, no exception was thrown. Pop handler and
1132 // jump past the catch block.
1133 fc.builder.emit_pop_exception_handler();
1134 let end_patch = fc.builder.emit_jump(Op::Jump);
1135
1136 // Reset register state for catch block — locals declared in
1137 // the try block are out of scope.
1138 fc.locals.truncate(locals_len);
1139 fc.next_reg = saved_next;
1140
1141 // Patch the exception handler to jump here (catch block start).
1142 fc.builder.patch_jump(catch_patch);
1143
1144 // Bind the catch parameter if present.
1145 if let Some(param) = &catch.param {
1146 if let PatternKind::Identifier(name) = ¶m.kind {
1147 let is_captured = fc.captured_names.contains(name.as_str());
1148 let local = fc.define_local_ext(name, is_captured, false);
1149 if is_captured {
1150 fc.builder.emit_reg(Op::NewCell, local);
1151 fc.builder.emit_reg_reg(Op::CellStore, local, catch_reg);
1152 } else {
1153 fc.builder.emit_reg_reg(Op::Move, local, catch_reg);
1154 }
1155 }
1156 }
1157
1158 // Compile the catch body.
1159 compile_stmts(fc, &catch.body, result_reg)?;
1160
1161 // End of catch — restore state.
1162 fc.locals.truncate(locals_len);
1163 fc.next_reg = saved_next;
1164
1165 // Jump target from the try block.
1166 fc.builder.patch_jump(end_patch);
1167 } else {
1168 // No catch handler: just compile the try block.
1169 compile_stmts(fc, block, result_reg)?;
1170 }
1171
1172 // Compile the finally block (always runs after try or catch).
1173 if let Some(fin) = finalizer {
1174 compile_stmts(fc, fin, result_reg)?;
1175 }
1176 }
1177
1178 StmtKind::Empty | StmtKind::Debugger => {
1179 // No-op.
1180 }
1181
1182 StmtKind::With { object, body } => {
1183 // Compile `with` as: evaluate object (discard), then run body.
1184 // Proper `with` scope requires VM support.
1185 let tmp = fc.alloc_reg();
1186 compile_expr(fc, object, tmp)?;
1187 fc.free_reg(tmp);
1188 compile_stmt(fc, body, result_reg)?;
1189 }
1190
1191 StmtKind::Import { .. } => {
1192 // Module imports are resolved before execution; no bytecode needed.
1193 }
1194
1195 StmtKind::Export(export) => {
1196 compile_export(fc, export, result_reg)?;
1197 }
1198
1199 StmtKind::ClassDecl(class_def) => {
1200 compile_class_decl(fc, class_def)?;
1201 }
1202 }
1203 Ok(())
1204}
1205
1206// ── Variable declarations ───────────────────────────────────
1207
1208fn compile_var_declarator(
1209 fc: &mut FunctionCompiler,
1210 decl: &VarDeclarator,
1211 kind: VarKind,
1212) -> Result<(), JsError> {
1213 match &decl.pattern.kind {
1214 PatternKind::Identifier(name) => {
1215 let is_const = kind == VarKind::Const;
1216 let is_captured = fc.captured_names.contains(name.as_str());
1217
1218 if is_const && decl.init.is_none() {
1219 return Err(JsError::SyntaxError(
1220 "Missing initializer in const declaration".into(),
1221 ));
1222 }
1223
1224 let reg = fc.define_local_ext(name, is_captured, is_const);
1225
1226 if is_captured {
1227 // Allocate a cell for this variable.
1228 fc.builder.emit_reg(Op::NewCell, reg);
1229 if let Some(init) = &decl.init {
1230 let tmp = fc.alloc_reg();
1231 compile_expr(fc, init, tmp)?;
1232 fc.builder.emit_reg_reg(Op::CellStore, reg, tmp);
1233 fc.free_reg(tmp);
1234 }
1235 // No init => cell stays undefined (already the default).
1236 } else if let Some(init) = &decl.init {
1237 compile_expr(fc, init, reg)?;
1238 } else {
1239 fc.builder.emit_reg(Op::LoadUndefined, reg);
1240 }
1241 }
1242 _ => {
1243 // Destructuring: evaluate init, then bind patterns.
1244 // Note: don't free tmp — destructuring pattern allocates permanent
1245 // local registers above it. The tmp register slot is reused via
1246 // next_reg restoration by the parent scope.
1247 let tmp = fc.alloc_reg();
1248 if let Some(init) = &decl.init {
1249 compile_expr(fc, init, tmp)?;
1250 } else {
1251 fc.builder.emit_reg(Op::LoadUndefined, tmp);
1252 }
1253 compile_destructuring_pattern(fc, &decl.pattern, tmp)?;
1254 }
1255 }
1256 Ok(())
1257}
1258
1259fn compile_destructuring_pattern(
1260 fc: &mut FunctionCompiler,
1261 pattern: &Pattern,
1262 src: Reg,
1263) -> Result<(), JsError> {
1264 match &pattern.kind {
1265 PatternKind::Identifier(name) => {
1266 let is_captured = fc.captured_names.contains(name.as_str());
1267 let reg = fc.define_local_ext(name, is_captured, false);
1268 if is_captured {
1269 fc.builder.emit_reg(Op::NewCell, reg);
1270 fc.builder.emit_reg_reg(Op::CellStore, reg, src);
1271 } else {
1272 fc.builder.emit_reg_reg(Op::Move, reg, src);
1273 }
1274 }
1275 PatternKind::Object { properties, rest } => {
1276 // For each property, extract the value and bind it.
1277 // We use a single temp register that we reuse for each property
1278 // by resetting next_reg after each binding.
1279 for prop in properties {
1280 let key_name = match &prop.key {
1281 PropertyKey::Identifier(s) | PropertyKey::String(s) => s.clone(),
1282 PropertyKey::Computed(expr) => {
1283 let saved = fc.next_reg;
1284 let key_reg = fc.alloc_reg();
1285 compile_expr(fc, expr, key_reg)?;
1286 let val_reg = fc.alloc_reg();
1287 fc.builder.emit_reg3(Op::GetProperty, val_reg, src, key_reg);
1288 compile_destructuring_pattern(fc, &prop.value, val_reg)?;
1289 // Temp regs are buried; just let them be.
1290 let _ = saved;
1291 continue;
1292 }
1293 PropertyKey::Number(n) => {
1294 if n.fract() == 0.0 && n.abs() < 1e15 {
1295 format!("{}", *n as i64)
1296 } else {
1297 format!("{n}")
1298 }
1299 }
1300 };
1301 // For simple identifier patterns, load property directly into
1302 // the target register to avoid LIFO register allocation issues.
1303 if let PatternKind::Identifier(name) = &prop.value.kind {
1304 let is_captured = fc.captured_names.contains(name.as_str());
1305 let reg = fc.define_local_ext(name, is_captured, false);
1306 let name_idx = fc.builder.add_name(&key_name);
1307 if is_captured {
1308 let tmp = fc.alloc_reg();
1309 fc.builder.emit_get_prop_name(tmp, src, name_idx);
1310 fc.builder.emit_reg(Op::NewCell, reg);
1311 fc.builder.emit_reg_reg(Op::CellStore, reg, tmp);
1312 fc.free_reg(tmp);
1313 } else {
1314 fc.builder.emit_get_prop_name(reg, src, name_idx);
1315 }
1316 } else {
1317 // Complex inner pattern (nested, default, etc.)
1318 // Allocate temp, extract value, then recurse.
1319 // Temp register won't be freed (LIFO constraint with inner locals).
1320 let val_reg = fc.alloc_reg();
1321 let name_idx = fc.builder.add_name(&key_name);
1322 fc.builder.emit_get_prop_name(val_reg, src, name_idx);
1323 compile_destructuring_pattern(fc, &prop.value, val_reg)?;
1324 }
1325 }
1326
1327 // Handle rest: collect remaining own enumerable properties.
1328 if let Some(rest_pat) = rest {
1329 // Collect extracted key names for exclusion.
1330 let extracted_keys: Vec<String> = properties
1331 .iter()
1332 .filter_map(|prop| match &prop.key {
1333 PropertyKey::Identifier(s) | PropertyKey::String(s) => Some(s.clone()),
1334 _ => None,
1335 })
1336 .collect();
1337
1338 let rest_obj = fc.alloc_reg();
1339 fc.builder.emit_reg(Op::CreateObject, rest_obj);
1340
1341 let keys_r = fc.alloc_reg();
1342 fc.builder.emit_reg_reg(Op::ForInInit, keys_r, src);
1343 let idx_r = fc.alloc_reg();
1344 fc.builder.emit_load_int8(idx_r, 0);
1345 let key_r = fc.alloc_reg();
1346 let done_r = fc.alloc_reg();
1347
1348 let loop_start = fc.builder.offset();
1349 fc.builder
1350 .emit_reg4(Op::ForInNext, key_r, done_r, keys_r, idx_r);
1351 let exit_patch = fc.builder.emit_cond_jump(Op::JumpIfTrue, done_r);
1352
1353 let mut skip_patches = Vec::new();
1354 for excluded in &extracted_keys {
1355 let excluded_r = fc.alloc_reg();
1356 let ci = fc.builder.add_constant(Constant::String(excluded.clone()));
1357 fc.builder.emit_reg_u16(Op::LoadConst, excluded_r, ci);
1358 let cmp_r = fc.alloc_reg();
1359 fc.builder.emit_reg3(Op::StrictEq, cmp_r, key_r, excluded_r);
1360 let skip = fc.builder.emit_cond_jump(Op::JumpIfTrue, cmp_r);
1361 skip_patches.push(skip);
1362 fc.free_reg(cmp_r);
1363 fc.free_reg(excluded_r);
1364 }
1365
1366 let val_r = fc.alloc_reg();
1367 fc.builder.emit_reg3(Op::GetProperty, val_r, src, key_r);
1368 fc.builder
1369 .emit_reg3(Op::SetProperty, rest_obj, key_r, val_r);
1370 fc.free_reg(val_r);
1371
1372 for patch in skip_patches {
1373 fc.builder.patch_jump(patch);
1374 }
1375
1376 let one_r = fc.alloc_reg();
1377 fc.builder.emit_load_int8(one_r, 1);
1378 fc.builder.emit_reg3(Op::Add, idx_r, idx_r, one_r);
1379 fc.free_reg(one_r);
1380
1381 fc.builder.emit_jump_to(loop_start);
1382 fc.builder.patch_jump(exit_patch);
1383
1384 fc.free_reg(done_r);
1385 fc.free_reg(key_r);
1386 fc.free_reg(idx_r);
1387 fc.free_reg(keys_r);
1388
1389 compile_destructuring_pattern(fc, rest_pat, rest_obj)?;
1390 }
1391 }
1392 PatternKind::Array { elements, rest } => {
1393 for (i, elem) in elements.iter().enumerate() {
1394 if let Some(pat) = elem {
1395 // For simple identifier patterns, load directly into local.
1396 if let PatternKind::Identifier(name) = &pat.kind {
1397 let is_captured = fc.captured_names.contains(name.as_str());
1398 let reg = fc.define_local_ext(name, is_captured, false);
1399 let idx_reg = fc.alloc_reg();
1400 if i <= 127 {
1401 fc.builder.emit_load_int8(idx_reg, i as i8);
1402 } else {
1403 let ci = fc.builder.add_constant(Constant::Number(i as f64));
1404 fc.builder.emit_reg_u16(Op::LoadConst, idx_reg, ci);
1405 }
1406 if is_captured {
1407 let tmp = fc.alloc_reg();
1408 fc.builder.emit_reg3(Op::GetProperty, tmp, src, idx_reg);
1409 fc.builder.emit_reg(Op::NewCell, reg);
1410 fc.builder.emit_reg_reg(Op::CellStore, reg, tmp);
1411 fc.free_reg(tmp);
1412 } else {
1413 fc.builder.emit_reg3(Op::GetProperty, reg, src, idx_reg);
1414 }
1415 fc.free_reg(idx_reg);
1416 } else {
1417 // Complex inner pattern (nested, default, etc.)
1418 let idx_reg = fc.alloc_reg();
1419 if i <= 127 {
1420 fc.builder.emit_load_int8(idx_reg, i as i8);
1421 } else {
1422 let ci = fc.builder.add_constant(Constant::Number(i as f64));
1423 fc.builder.emit_reg_u16(Op::LoadConst, idx_reg, ci);
1424 }
1425 let val_reg = fc.alloc_reg();
1426 fc.builder.emit_reg3(Op::GetProperty, val_reg, src, idx_reg);
1427 compile_destructuring_pattern(fc, pat, val_reg)?;
1428 // Don't free val_reg/idx_reg — inner pattern may have
1429 // allocated locals above them.
1430 }
1431 }
1432 }
1433
1434 // Handle rest element: ...rest = src.slice(elements.len())
1435 if let Some(rest_pat) = rest {
1436 let slice_fn_r = fc.alloc_reg();
1437 let slice_ni = fc.builder.add_name("slice");
1438 fc.builder.emit_get_prop_name(slice_fn_r, src, slice_ni);
1439
1440 let this_ni = fc.builder.add_name("this");
1441 fc.builder.emit_store_global(this_ni, src);
1442
1443 let start_r = fc.alloc_reg();
1444 let elem_count = elements.len();
1445 if elem_count <= 127 {
1446 fc.builder.emit_load_int8(start_r, elem_count as i8);
1447 } else {
1448 let ci = fc.builder.add_constant(Constant::Number(elem_count as f64));
1449 fc.builder.emit_reg_u16(Op::LoadConst, start_r, ci);
1450 }
1451
1452 let rest_val = fc.alloc_reg();
1453 fc.builder.emit_call(rest_val, slice_fn_r, start_r, 1);
1454
1455 compile_destructuring_pattern(fc, rest_pat, rest_val)?;
1456 // Don't free temps — rest pattern allocates locals.
1457 }
1458 }
1459 PatternKind::Assign { left, right } => {
1460 // Default value: if src is undefined, use default.
1461 let val_reg = fc.alloc_reg();
1462 fc.builder.emit_reg_reg(Op::Move, val_reg, src);
1463 // Check if undefined, if so use default.
1464 let check_reg = fc.alloc_reg();
1465 let undef_reg = fc.alloc_reg();
1466 fc.builder.emit_reg(Op::LoadUndefined, undef_reg);
1467 fc.builder
1468 .emit_reg3(Op::StrictEq, check_reg, val_reg, undef_reg);
1469 fc.free_reg(undef_reg);
1470 let patch = fc.builder.emit_cond_jump(Op::JumpIfFalse, check_reg);
1471 fc.free_reg(check_reg);
1472 // Is undefined → evaluate default.
1473 compile_expr(fc, right, val_reg)?;
1474 fc.builder.patch_jump(patch);
1475 compile_destructuring_pattern(fc, left, val_reg)?;
1476 // Don't free val_reg — inner pattern may have allocated locals.
1477 }
1478 }
1479 Ok(())
1480}
1481
1482// ── Function declarations ───────────────────────────────────
1483
1484fn compile_function_decl(fc: &mut FunctionCompiler, func_def: &FunctionDef) -> Result<(), JsError> {
1485 let name = func_def.id.clone().unwrap_or_default();
1486 let inner = compile_function_body_with_captures(fc, func_def)?;
1487 let func_idx = fc.builder.add_function(inner);
1488
1489 let is_captured = fc.captured_names.contains(name.as_str());
1490 let reg = fc.define_local_ext(&name, is_captured, false);
1491
1492 if is_captured {
1493 // Create a cell, then create the closure into a temp, then store into cell.
1494 fc.builder.emit_reg(Op::NewCell, reg);
1495 let tmp = fc.alloc_reg();
1496 fc.builder.emit_reg_u16(Op::CreateClosure, tmp, func_idx);
1497 fc.builder.emit_reg_reg(Op::CellStore, reg, tmp);
1498 // Also store as global.
1499 if !name.is_empty() {
1500 let name_idx = fc.builder.add_name(&name);
1501 fc.builder.emit_store_global(name_idx, tmp);
1502 }
1503 fc.free_reg(tmp);
1504 } else {
1505 fc.builder.emit_reg_u16(Op::CreateClosure, reg, func_idx);
1506 // Also store as global so inner/recursive calls via LoadGlobal can find it.
1507 if !name.is_empty() {
1508 let name_idx = fc.builder.add_name(&name);
1509 fc.builder.emit_store_global(name_idx, reg);
1510 }
1511 }
1512 Ok(())
1513}
1514
1515/// Compile a function body, resolving upvalue captures from the parent scope.
1516fn compile_function_body_with_captures(
1517 parent: &mut FunctionCompiler,
1518 func_def: &FunctionDef,
1519) -> Result<Function, JsError> {
1520 // 1. Collect free variables of this inner function.
1521 let free_vars = collect_free_vars(&func_def.params, &func_def.body);
1522
1523 // 2. Build upvalue list by resolving free vars against the parent scope.
1524 let mut upvalue_entries = Vec::new();
1525 for name in &free_vars {
1526 if let Some(local) = parent.find_local_info(name) {
1527 let reg = local.reg;
1528 let is_const = local.is_const;
1529 // Mark the parent's local as captured (if not already).
1530 // We need to update the parent's local, so find the index and mutate.
1531 if let Some(l) = parent.locals.iter_mut().rev().find(|l| l.name == *name) {
1532 l.is_captured = true;
1533 }
1534 upvalue_entries.push(UpvalueEntry {
1535 name: name.clone(),
1536 def: UpvalueDef {
1537 is_local: true,
1538 index: reg,
1539 },
1540 is_const,
1541 });
1542 } else if let Some(parent_uv_idx) = parent.find_upvalue(name) {
1543 // Transitive capture: the parent captures it from its own parent.
1544 let is_const = parent.is_upvalue_const(parent_uv_idx);
1545 upvalue_entries.push(UpvalueEntry {
1546 name: name.clone(),
1547 def: UpvalueDef {
1548 is_local: false,
1549 index: parent_uv_idx,
1550 },
1551 is_const,
1552 });
1553 }
1554 // If not found in parent or parent's upvalues, it must be a global — no upvalue needed.
1555 }
1556
1557 // 3. Compile the inner function with its own scope.
1558 let mut inner = compile_function_body_inner(func_def, &upvalue_entries)?;
1559
1560 // 4. Attach upvalue definitions to the compiled function.
1561 inner.upvalue_defs = upvalue_entries.iter().map(|e| e.def.clone()).collect();
1562
1563 Ok(inner)
1564}
1565
1566/// Core function body compilation. The `upvalue_entries` tell this function which
1567/// outer variables it can access via LoadUpvalue/StoreUpvalue.
1568fn compile_function_body_inner(
1569 func_def: &FunctionDef,
1570 upvalue_entries: &[UpvalueEntry],
1571) -> Result<Function, JsError> {
1572 let name = func_def.id.clone().unwrap_or_default();
1573 let param_count = func_def.params.len().min(255) as u8;
1574 let mut inner = FunctionCompiler::new(name, param_count);
1575
1576 // Copy upvalue entries into the inner compiler so it can resolve references.
1577 for entry in upvalue_entries {
1578 inner.upvalues.push(UpvalueEntry {
1579 name: entry.name.clone(),
1580 def: entry.def.clone(),
1581 is_const: entry.is_const,
1582 });
1583 }
1584
1585 // Pre-scan to find which of this function's locals are captured by ITS inner functions.
1586 let inner_caps = collect_inner_captures(&func_def.body);
1587 inner.captured_names = inner_caps;
1588
1589 // Allocate registers for parameters.
1590 for p in &func_def.params {
1591 if let PatternKind::Identifier(pname) = &p.kind {
1592 let is_captured = inner.captured_names.contains(pname.as_str());
1593 inner.define_local_ext(pname, is_captured, false);
1594 } else {
1595 let _ = inner.alloc_reg();
1596 }
1597 }
1598
1599 // Box captured parameters into cells.
1600 for p in &func_def.params {
1601 if let PatternKind::Identifier(pname) = &p.kind {
1602 if let Some(local) = inner.find_local_info(pname) {
1603 if local.is_captured {
1604 let reg = local.reg;
1605 // Move param value to temp, allocate cell, store value into cell.
1606 let tmp = inner.alloc_reg();
1607 inner.builder.emit_reg_reg(Op::Move, tmp, reg);
1608 inner.builder.emit_reg(Op::NewCell, reg);
1609 inner.builder.emit_reg_reg(Op::CellStore, reg, tmp);
1610 inner.free_reg(tmp);
1611 }
1612 }
1613 }
1614 }
1615
1616 // Result register for the function body.
1617 let result_reg = inner.alloc_reg();
1618 inner.builder.emit_reg(Op::LoadUndefined, result_reg);
1619
1620 compile_stmts(&mut inner, &func_def.body, result_reg)?;
1621
1622 // Implicit return undefined.
1623 inner.builder.emit_reg(Op::Return, result_reg);
1624 let mut func = inner.builder.finish();
1625 func.is_generator = func_def.is_generator;
1626 func.is_async = func_def.is_async;
1627 Ok(func)
1628}
1629
1630// ── Class declarations ──────────────────────────────────────
1631
1632fn compile_class_decl(fc: &mut FunctionCompiler, class_def: &ClassDef) -> Result<(), JsError> {
1633 let name = class_def.id.clone().unwrap_or_default();
1634 let is_captured = fc.captured_names.contains(name.as_str());
1635 let reg = fc.define_local_ext(&name, is_captured, false);
1636
1637 // For captured classes, build the constructor into a temp register so we can
1638 // set prototype methods on it before wrapping it in a cell.
1639 let ctor_reg = if is_captured { fc.alloc_reg() } else { reg };
1640
1641 // Find constructor or create empty one.
1642 let ctor = class_def.body.iter().find(|m| {
1643 matches!(
1644 &m.kind,
1645 ClassMemberKind::Method {
1646 kind: MethodKind::Constructor,
1647 ..
1648 }
1649 )
1650 });
1651
1652 if let Some(member) = ctor {
1653 if let ClassMemberKind::Method { value, .. } = &member.kind {
1654 let inner = compile_function_body_with_captures(fc, value)?;
1655 let func_idx = fc.builder.add_function(inner);
1656 fc.builder
1657 .emit_reg_u16(Op::CreateClosure, ctor_reg, func_idx);
1658 }
1659 } else {
1660 // No constructor: create a minimal function that returns undefined.
1661 let mut empty = BytecodeBuilder::new(name.clone(), 0);
1662 let r = 0u8;
1663 empty.func.register_count = 1;
1664 empty.emit_reg(Op::LoadUndefined, r);
1665 empty.emit_reg(Op::Return, r);
1666 let func_idx = fc.builder.add_function(empty.finish());
1667 fc.builder
1668 .emit_reg_u16(Op::CreateClosure, ctor_reg, func_idx);
1669 }
1670
1671 // Compile methods: set them as properties on the constructor's prototype.
1672 // This is simplified — real class compilation needs prototype chain setup.
1673 for member in &class_def.body {
1674 match &member.kind {
1675 ClassMemberKind::Method {
1676 key,
1677 value,
1678 kind,
1679 is_static: _,
1680 computed: _,
1681 } => {
1682 if matches!(kind, MethodKind::Constructor) {
1683 continue;
1684 }
1685 let method_name = match key {
1686 PropertyKey::Identifier(s) | PropertyKey::String(s) => s.clone(),
1687 _ => continue,
1688 };
1689 let inner = compile_function_body_with_captures(fc, value)?;
1690 let func_idx = fc.builder.add_function(inner);
1691 let method_reg = fc.alloc_reg();
1692 fc.builder
1693 .emit_reg_u16(Op::CreateClosure, method_reg, func_idx);
1694 let name_idx = fc.builder.add_name(&method_name);
1695 fc.builder
1696 .emit_set_prop_name(ctor_reg, name_idx, method_reg);
1697 fc.free_reg(method_reg);
1698 }
1699 ClassMemberKind::Property { .. } => {
1700 // Class fields are set in constructor; skip here.
1701 }
1702 }
1703 }
1704
1705 if is_captured {
1706 fc.builder.emit_reg(Op::NewCell, reg);
1707 fc.builder.emit_reg_reg(Op::CellStore, reg, ctor_reg);
1708 fc.free_reg(ctor_reg);
1709 }
1710
1711 Ok(())
1712}
1713
1714// ── Export ───────────────────────────────────────────────────
1715
1716fn compile_export(
1717 fc: &mut FunctionCompiler,
1718 export: &ExportDecl,
1719
1720 result_reg: Reg,
1721) -> Result<(), JsError> {
1722 match export {
1723 ExportDecl::Declaration(stmt) => {
1724 compile_stmt(fc, stmt, result_reg)?;
1725 }
1726 ExportDecl::Default(expr) => {
1727 compile_expr(fc, expr, result_reg)?;
1728 }
1729 ExportDecl::Named { .. } | ExportDecl::AllFrom(_) => {
1730 // Named re-exports are module-level; no bytecode needed.
1731 }
1732 }
1733 Ok(())
1734}
1735
1736// ── Control flow ────────────────────────────────────────────
1737
1738fn compile_if(
1739 fc: &mut FunctionCompiler,
1740 test: &Expr,
1741 consequent: &Stmt,
1742 alternate: Option<&Stmt>,
1743
1744 result_reg: Reg,
1745) -> Result<(), JsError> {
1746 let cond = fc.alloc_reg();
1747 compile_expr(fc, test, cond)?;
1748 let else_patch = fc.builder.emit_cond_jump(Op::JumpIfFalse, cond);
1749 fc.free_reg(cond);
1750
1751 compile_stmt(fc, consequent, result_reg)?;
1752
1753 if let Some(alt) = alternate {
1754 let end_patch = fc.builder.emit_jump(Op::Jump);
1755 fc.builder.patch_jump(else_patch);
1756 compile_stmt(fc, alt, result_reg)?;
1757 fc.builder.patch_jump(end_patch);
1758 } else {
1759 fc.builder.patch_jump(else_patch);
1760 }
1761 Ok(())
1762}
1763
1764fn compile_while(
1765 fc: &mut FunctionCompiler,
1766 test: &Expr,
1767 body: &Stmt,
1768 label: Option<String>,
1769
1770 result_reg: Reg,
1771) -> Result<(), JsError> {
1772 let loop_start = fc.builder.offset();
1773
1774 let cond = fc.alloc_reg();
1775 compile_expr(fc, test, cond)?;
1776 let exit_patch = fc.builder.emit_cond_jump(Op::JumpIfFalse, cond);
1777 fc.free_reg(cond);
1778
1779 fc.loop_stack.push(LoopCtx {
1780 label,
1781 break_patches: Vec::new(),
1782 continue_patches: Vec::new(),
1783 });
1784
1785 compile_stmt(fc, body, result_reg)?;
1786 fc.builder.emit_jump_to(loop_start);
1787 fc.builder.patch_jump(exit_patch);
1788
1789 let ctx = fc.loop_stack.pop().unwrap();
1790 for patch in ctx.break_patches {
1791 fc.builder.patch_jump(patch);
1792 }
1793 // In a while loop, continue jumps back to the condition check (loop_start).
1794 for patch in ctx.continue_patches {
1795 fc.builder.patch_jump_to(patch, loop_start);
1796 }
1797 Ok(())
1798}
1799
1800fn compile_do_while(
1801 fc: &mut FunctionCompiler,
1802 body: &Stmt,
1803 test: &Expr,
1804 label: Option<String>,
1805
1806 result_reg: Reg,
1807) -> Result<(), JsError> {
1808 let loop_start = fc.builder.offset();
1809
1810 fc.loop_stack.push(LoopCtx {
1811 label,
1812 break_patches: Vec::new(),
1813 continue_patches: Vec::new(),
1814 });
1815
1816 compile_stmt(fc, body, result_reg)?;
1817
1818 // continue in do-while should jump here (the condition check).
1819 let cond_start = fc.builder.offset();
1820
1821 let cond = fc.alloc_reg();
1822 compile_expr(fc, test, cond)?;
1823 fc.builder
1824 .emit_cond_jump_to(Op::JumpIfTrue, cond, loop_start);
1825 fc.free_reg(cond);
1826
1827 let ctx = fc.loop_stack.pop().unwrap();
1828 for patch in ctx.break_patches {
1829 fc.builder.patch_jump(patch);
1830 }
1831 for patch in ctx.continue_patches {
1832 fc.builder.patch_jump_to(patch, cond_start);
1833 }
1834 Ok(())
1835}
1836
1837fn compile_for(
1838 fc: &mut FunctionCompiler,
1839 init: Option<&ForInit>,
1840 test: Option<&Expr>,
1841 update: Option<&Expr>,
1842 body: &Stmt,
1843 label: Option<String>,
1844
1845 result_reg: Reg,
1846) -> Result<(), JsError> {
1847 let saved_locals = fc.locals.len();
1848 let saved_next = fc.next_reg;
1849
1850 // Init.
1851 if let Some(init) = init {
1852 match init {
1853 ForInit::VarDecl { kind, declarators } => {
1854 for decl in declarators {
1855 compile_var_declarator(fc, decl, *kind)?;
1856 }
1857 }
1858 ForInit::Expr(expr) => {
1859 let tmp = fc.alloc_reg();
1860 compile_expr(fc, expr, tmp)?;
1861 fc.free_reg(tmp);
1862 }
1863 }
1864 }
1865
1866 let loop_start = fc.builder.offset();
1867
1868 // Test.
1869 let exit_patch = if let Some(test) = test {
1870 let cond = fc.alloc_reg();
1871 compile_expr(fc, test, cond)?;
1872 let patch = fc.builder.emit_cond_jump(Op::JumpIfFalse, cond);
1873 fc.free_reg(cond);
1874 Some(patch)
1875 } else {
1876 None
1877 };
1878
1879 fc.loop_stack.push(LoopCtx {
1880 label,
1881 break_patches: Vec::new(),
1882 continue_patches: Vec::new(),
1883 });
1884
1885 compile_stmt(fc, body, result_reg)?;
1886
1887 // continue in a for-loop should jump here (the update expression).
1888 let continue_target = fc.builder.offset();
1889
1890 // Update.
1891 if let Some(update) = update {
1892 let tmp = fc.alloc_reg();
1893 compile_expr(fc, update, tmp)?;
1894 fc.free_reg(tmp);
1895 }
1896
1897 fc.builder.emit_jump_to(loop_start);
1898
1899 if let Some(patch) = exit_patch {
1900 fc.builder.patch_jump(patch);
1901 }
1902
1903 let ctx = fc.loop_stack.pop().unwrap();
1904 for patch in ctx.break_patches {
1905 fc.builder.patch_jump(patch);
1906 }
1907 for patch in ctx.continue_patches {
1908 fc.builder.patch_jump_to(patch, continue_target);
1909 }
1910
1911 fc.locals.truncate(saved_locals);
1912 fc.next_reg = saved_next;
1913 Ok(())
1914}
1915
1916fn compile_switch(
1917 fc: &mut FunctionCompiler,
1918 discriminant: &Expr,
1919 cases: &[SwitchCase],
1920
1921 result_reg: Reg,
1922) -> Result<(), JsError> {
1923 let disc_reg = fc.alloc_reg();
1924 compile_expr(fc, discriminant, disc_reg)?;
1925
1926 // Use a loop context for break statements.
1927 fc.loop_stack.push(LoopCtx {
1928 label: None,
1929 break_patches: Vec::new(),
1930 continue_patches: Vec::new(),
1931 });
1932
1933 // Phase 1: emit comparison jumps for each non-default case.
1934 // Store (case_index, patch_position) for each case with a test.
1935 let mut case_jump_patches: Vec<(usize, usize)> = Vec::new();
1936 let mut default_index: Option<usize> = None;
1937
1938 for (i, case) in cases.iter().enumerate() {
1939 if let Some(test) = &case.test {
1940 let test_reg = fc.alloc_reg();
1941 compile_expr(fc, test, test_reg)?;
1942 let cmp_reg = fc.alloc_reg();
1943 fc.builder
1944 .emit_reg3(Op::StrictEq, cmp_reg, disc_reg, test_reg);
1945 let patch = fc.builder.emit_cond_jump(Op::JumpIfTrue, cmp_reg);
1946 fc.free_reg(cmp_reg);
1947 fc.free_reg(test_reg);
1948 case_jump_patches.push((i, patch));
1949 } else {
1950 default_index = Some(i);
1951 }
1952 }
1953
1954 // After all comparisons: jump to default body or end.
1955 let fallthrough_patch = fc.builder.emit_jump(Op::Jump);
1956
1957 // Phase 2: emit case bodies in order (fall-through semantics).
1958 let mut body_offsets: Vec<(usize, usize)> = Vec::new();
1959 for (i, case) in cases.iter().enumerate() {
1960 body_offsets.push((i, fc.builder.offset()));
1961 compile_stmts(fc, &case.consequent, result_reg)?;
1962 }
1963
1964 let end_offset = fc.builder.offset();
1965
1966 // Patch case test jumps to their respective body offsets.
1967 for (case_idx, patch) in &case_jump_patches {
1968 let body_offset = body_offsets
1969 .iter()
1970 .find(|(i, _)| i == case_idx)
1971 .map(|(_, off)| *off)
1972 .unwrap();
1973 fc.builder.patch_jump_to(*patch, body_offset);
1974 }
1975
1976 // Patch fallthrough: jump to default body if present, otherwise to end.
1977 if let Some(def_idx) = default_index {
1978 let default_offset = body_offsets
1979 .iter()
1980 .find(|(i, _)| *i == def_idx)
1981 .map(|(_, off)| *off)
1982 .unwrap();
1983 fc.builder.patch_jump_to(fallthrough_patch, default_offset);
1984 } else {
1985 fc.builder.patch_jump_to(fallthrough_patch, end_offset);
1986 }
1987
1988 fc.free_reg(disc_reg);
1989
1990 let ctx = fc.loop_stack.pop().unwrap();
1991 for patch in ctx.break_patches {
1992 fc.builder.patch_jump(patch);
1993 }
1994 Ok(())
1995}
1996
1997fn find_loop_ctx(stack: &[LoopCtx], label: Option<&str>) -> Option<usize> {
1998 if let Some(label) = label {
1999 stack
2000 .iter()
2001 .rposition(|ctx| ctx.label.as_deref() == Some(label))
2002 } else {
2003 if stack.is_empty() {
2004 None
2005 } else {
2006 Some(stack.len() - 1)
2007 }
2008 }
2009}
2010
2011// ── Expressions ─────────────────────────────────────────────
2012
2013fn compile_expr(fc: &mut FunctionCompiler, expr: &Expr, dst: Reg) -> Result<(), JsError> {
2014 match &expr.kind {
2015 ExprKind::Number(n) => {
2016 // Optimize small integers.
2017 let int_val = *n as i64;
2018 if int_val as f64 == *n && (-128..=127).contains(&int_val) {
2019 fc.builder.emit_load_int8(dst, int_val as i8);
2020 } else {
2021 let ci = fc.builder.add_constant(Constant::Number(*n));
2022 fc.builder.emit_reg_u16(Op::LoadConst, dst, ci);
2023 }
2024 }
2025
2026 ExprKind::String(s) => {
2027 let ci = fc.builder.add_constant(Constant::String(s.clone()));
2028 fc.builder.emit_reg_u16(Op::LoadConst, dst, ci);
2029 }
2030
2031 ExprKind::Bool(true) => {
2032 fc.builder.emit_reg(Op::LoadTrue, dst);
2033 }
2034
2035 ExprKind::Bool(false) => {
2036 fc.builder.emit_reg(Op::LoadFalse, dst);
2037 }
2038
2039 ExprKind::Null => {
2040 fc.builder.emit_reg(Op::LoadNull, dst);
2041 }
2042
2043 ExprKind::Identifier(name) => {
2044 if let Some(local) = fc.find_local_info(name) {
2045 let reg = local.reg;
2046 let captured = local.is_captured;
2047 if captured {
2048 fc.builder.emit_reg_reg(Op::CellLoad, dst, reg);
2049 } else if reg != dst {
2050 fc.builder.emit_reg_reg(Op::Move, dst, reg);
2051 }
2052 } else if let Some(uv_idx) = fc.find_upvalue(name) {
2053 fc.builder.emit_load_upvalue(dst, uv_idx);
2054 } else {
2055 // Global lookup.
2056 let ni = fc.builder.add_name(name);
2057 fc.builder.emit_load_global(dst, ni);
2058 }
2059 }
2060
2061 ExprKind::This => {
2062 // `this` is loaded as a global named "this" (the VM binds it).
2063 let ni = fc.builder.add_name("this");
2064 fc.builder.emit_load_global(dst, ni);
2065 }
2066
2067 ExprKind::Binary { op, left, right } => {
2068 let lhs = fc.alloc_reg();
2069 compile_expr(fc, left, lhs)?;
2070 let rhs = fc.alloc_reg();
2071 compile_expr(fc, right, rhs)?;
2072 let bytecode_op = binary_op_to_opcode(*op);
2073 fc.builder.emit_reg3(bytecode_op, dst, lhs, rhs);
2074 fc.free_reg(rhs);
2075 fc.free_reg(lhs);
2076 }
2077
2078 ExprKind::Unary { op, argument } => {
2079 if *op == UnaryOp::Delete {
2080 // Handle delete specially: need object + key form for member expressions.
2081 match &argument.kind {
2082 ExprKind::Member {
2083 object,
2084 property,
2085 computed,
2086 } => {
2087 let obj_r = fc.alloc_reg();
2088 compile_expr(fc, object, obj_r)?;
2089 let key_r = fc.alloc_reg();
2090 if *computed {
2091 compile_expr(fc, property, key_r)?;
2092 } else if let ExprKind::Identifier(name) = &property.kind {
2093 let ci = fc.builder.add_constant(Constant::String(name.clone()));
2094 fc.builder.emit_reg_u16(Op::LoadConst, key_r, ci);
2095 } else {
2096 compile_expr(fc, property, key_r)?;
2097 }
2098 fc.builder.emit_reg3(Op::Delete, dst, obj_r, key_r);
2099 fc.free_reg(key_r);
2100 fc.free_reg(obj_r);
2101 }
2102 _ => {
2103 // `delete x` on a simple identifier: always true in non-strict mode.
2104 fc.builder.emit_reg(Op::LoadTrue, dst);
2105 }
2106 }
2107 } else {
2108 let src = fc.alloc_reg();
2109 compile_expr(fc, argument, src)?;
2110 match op {
2111 UnaryOp::Minus => fc.builder.emit_reg_reg(Op::Neg, dst, src),
2112 UnaryOp::Plus => {
2113 fc.builder.emit_reg_reg(Op::Move, dst, src);
2114 }
2115 UnaryOp::Not => fc.builder.emit_reg_reg(Op::LogicalNot, dst, src),
2116 UnaryOp::BitwiseNot => fc.builder.emit_reg_reg(Op::BitNot, dst, src),
2117 UnaryOp::Typeof => fc.builder.emit_reg_reg(Op::TypeOf, dst, src),
2118 UnaryOp::Void => fc.builder.emit_reg_reg(Op::Void, dst, src),
2119 UnaryOp::Delete => unreachable!(),
2120 }
2121 fc.free_reg(src);
2122 }
2123 }
2124
2125 ExprKind::Update {
2126 op,
2127 argument,
2128 prefix,
2129 } => {
2130 // Get current value.
2131 compile_expr(fc, argument, dst)?;
2132
2133 let one = fc.alloc_reg();
2134 fc.builder.emit_load_int8(one, 1);
2135
2136 if *prefix {
2137 // ++x / --x: modify first, return modified.
2138 match op {
2139 UpdateOp::Increment => fc.builder.emit_reg3(Op::Add, dst, dst, one),
2140 UpdateOp::Decrement => fc.builder.emit_reg3(Op::Sub, dst, dst, one),
2141 }
2142 // Store back.
2143 compile_store(fc, argument, dst)?;
2144 } else {
2145 // x++ / x--: return original, then modify.
2146 let tmp = fc.alloc_reg();
2147 fc.builder.emit_reg_reg(Op::Move, tmp, dst);
2148 match op {
2149 UpdateOp::Increment => fc.builder.emit_reg3(Op::Add, tmp, tmp, one),
2150 UpdateOp::Decrement => fc.builder.emit_reg3(Op::Sub, tmp, tmp, one),
2151 }
2152 compile_store(fc, argument, tmp)?;
2153 fc.free_reg(tmp);
2154 }
2155 fc.free_reg(one);
2156 }
2157
2158 ExprKind::Logical { op, left, right } => {
2159 compile_expr(fc, left, dst)?;
2160 match op {
2161 LogicalOp::And => {
2162 // Short-circuit: if falsy, skip right.
2163 let skip = fc.builder.emit_cond_jump(Op::JumpIfFalse, dst);
2164 compile_expr(fc, right, dst)?;
2165 fc.builder.patch_jump(skip);
2166 }
2167 LogicalOp::Or => {
2168 let skip = fc.builder.emit_cond_jump(Op::JumpIfTrue, dst);
2169 compile_expr(fc, right, dst)?;
2170 fc.builder.patch_jump(skip);
2171 }
2172 LogicalOp::Nullish => {
2173 let skip = fc.builder.emit_cond_jump(Op::JumpIfNullish, dst);
2174 // If NOT nullish, skip the right side. Wait — JumpIfNullish
2175 // should mean "jump if nullish" so we want: evaluate left,
2176 // if NOT nullish skip right.
2177 // Let's invert: evaluate left, check if nullish → evaluate right.
2178 // We need the jump to skip the "evaluate right" if NOT nullish.
2179 // Since JumpIfNullish jumps when nullish, we need the inverse.
2180 // Instead: use a two-step approach.
2181 //
2182 // Actually, rethink: for `a ?? b`:
2183 // 1. evaluate a → dst
2184 // 2. if dst is NOT null/undefined, jump to end
2185 // 3. evaluate b → dst
2186 // end:
2187 // JumpIfNullish jumps when IS nullish. So we want jump when NOT nullish.
2188 // Let's just use a "not nullish" check.
2189 // For now: negate and use JumpIfFalse.
2190 // Actually simpler: skip right when not nullish.
2191 // JumpIfNullish jumps WHEN nullish. We want to jump over right when NOT nullish.
2192 // So:
2193 // evaluate a → dst
2194 // JumpIfNullish dst → evaluate_right
2195 // Jump → end
2196 // evaluate_right: evaluate b → dst
2197 // end:
2198 // But we already emitted JumpIfNullish. Let's fix this.
2199 // The JumpIfNullish we emitted jumps to "after patch", which is where
2200 // we'll put the right-side code. We need another jump to skip right.
2201 let end_patch = fc.builder.emit_jump(Op::Jump);
2202 fc.builder.patch_jump(skip); // nullish → evaluate right
2203 compile_expr(fc, right, dst)?;
2204 fc.builder.patch_jump(end_patch);
2205 }
2206 }
2207 }
2208
2209 ExprKind::Assignment { op, left, right } => {
2210 if *op == AssignOp::Assign {
2211 compile_expr(fc, right, dst)?;
2212 compile_store(fc, left, dst)?;
2213 } else {
2214 // Compound assignment: load current, operate, store.
2215 compile_expr(fc, left, dst)?;
2216 let rhs = fc.alloc_reg();
2217 compile_expr(fc, right, rhs)?;
2218 let arith_op = compound_assign_op(*op);
2219 fc.builder.emit_reg3(arith_op, dst, dst, rhs);
2220 fc.free_reg(rhs);
2221 compile_store(fc, left, dst)?;
2222 }
2223 }
2224
2225 ExprKind::Conditional {
2226 test,
2227 consequent,
2228 alternate,
2229 } => {
2230 let cond = fc.alloc_reg();
2231 compile_expr(fc, test, cond)?;
2232 let else_patch = fc.builder.emit_cond_jump(Op::JumpIfFalse, cond);
2233 fc.free_reg(cond);
2234 compile_expr(fc, consequent, dst)?;
2235 let end_patch = fc.builder.emit_jump(Op::Jump);
2236 fc.builder.patch_jump(else_patch);
2237 compile_expr(fc, alternate, dst)?;
2238 fc.builder.patch_jump(end_patch);
2239 }
2240
2241 ExprKind::Call { callee, arguments } => {
2242 // Detect method calls (obj.method()) to set `this`.
2243 if let ExprKind::Member {
2244 object,
2245 property,
2246 computed,
2247 } = &callee.kind
2248 {
2249 // Layout: [obj_reg] [func_reg] [arg0] [arg1] ...
2250 // We keep obj_reg alive so we can set `this` before the call.
2251 let obj_reg = fc.alloc_reg();
2252 compile_expr(fc, object, obj_reg)?;
2253 let func_reg = fc.alloc_reg();
2254 if !computed {
2255 if let ExprKind::Identifier(name) = &property.kind {
2256 let ni = fc.builder.add_name(name);
2257 fc.builder.emit_get_prop_name(func_reg, obj_reg, ni);
2258 } else {
2259 let key_reg = fc.alloc_reg();
2260 compile_expr(fc, property, key_reg)?;
2261 fc.builder
2262 .emit_reg3(Op::GetProperty, func_reg, obj_reg, key_reg);
2263 fc.free_reg(key_reg);
2264 }
2265 } else {
2266 let key_reg = fc.alloc_reg();
2267 compile_expr(fc, property, key_reg)?;
2268 fc.builder
2269 .emit_reg3(Op::GetProperty, func_reg, obj_reg, key_reg);
2270 fc.free_reg(key_reg);
2271 }
2272
2273 // Set `this` to the receiver object before calling.
2274 let this_ni = fc.builder.add_name("this");
2275 fc.builder.emit_store_global(this_ni, obj_reg);
2276
2277 let args_start = fc.next_reg;
2278 let arg_count = arguments.len().min(255) as u8;
2279 for arg in arguments {
2280 let arg_reg = fc.alloc_reg();
2281 compile_expr(fc, arg, arg_reg)?;
2282 }
2283
2284 fc.builder.emit_call(dst, func_reg, args_start, arg_count);
2285
2286 // Free in LIFO order: args, func_reg, obj_reg.
2287 for _ in 0..arg_count {
2288 fc.next_reg -= 1;
2289 }
2290 fc.free_reg(func_reg);
2291 fc.free_reg(obj_reg);
2292 } else {
2293 let func_reg = fc.alloc_reg();
2294 compile_expr(fc, callee, func_reg)?;
2295
2296 let args_start = fc.next_reg;
2297 let arg_count = arguments.len().min(255) as u8;
2298 for arg in arguments {
2299 let arg_reg = fc.alloc_reg();
2300 compile_expr(fc, arg, arg_reg)?;
2301 }
2302
2303 fc.builder.emit_call(dst, func_reg, args_start, arg_count);
2304
2305 for _ in 0..arg_count {
2306 fc.next_reg -= 1;
2307 }
2308 fc.free_reg(func_reg);
2309 }
2310 }
2311
2312 ExprKind::New { callee, arguments } => {
2313 // For now, compile like a regular call. The VM will differentiate
2314 // based on the `New` vs `Call` distinction (TODO: add NewCall opcode).
2315 let func_reg = fc.alloc_reg();
2316 compile_expr(fc, callee, func_reg)?;
2317
2318 let args_start = fc.next_reg;
2319 let arg_count = arguments.len().min(255) as u8;
2320 for arg in arguments {
2321 let arg_reg = fc.alloc_reg();
2322 compile_expr(fc, arg, arg_reg)?;
2323 }
2324
2325 fc.builder.emit_call(dst, func_reg, args_start, arg_count);
2326
2327 for _ in 0..arg_count {
2328 fc.next_reg -= 1;
2329 }
2330 fc.free_reg(func_reg);
2331 }
2332
2333 ExprKind::Member {
2334 object,
2335 property,
2336 computed,
2337 } => {
2338 let obj_reg = fc.alloc_reg();
2339 compile_expr(fc, object, obj_reg)?;
2340
2341 if !computed {
2342 // Static member: obj.prop → GetPropertyByName.
2343 if let ExprKind::Identifier(name) = &property.kind {
2344 let ni = fc.builder.add_name(name);
2345 fc.builder.emit_get_prop_name(dst, obj_reg, ni);
2346 } else {
2347 let key_reg = fc.alloc_reg();
2348 compile_expr(fc, property, key_reg)?;
2349 fc.builder.emit_reg3(Op::GetProperty, dst, obj_reg, key_reg);
2350 fc.free_reg(key_reg);
2351 }
2352 } else {
2353 // Computed member: obj[expr].
2354 let key_reg = fc.alloc_reg();
2355 compile_expr(fc, property, key_reg)?;
2356 fc.builder.emit_reg3(Op::GetProperty, dst, obj_reg, key_reg);
2357 fc.free_reg(key_reg);
2358 }
2359 fc.free_reg(obj_reg);
2360 }
2361
2362 ExprKind::Array(elements) => {
2363 let has_spread = elements
2364 .iter()
2365 .any(|e| matches!(e, Some(ArrayElement::Spread(_))));
2366
2367 fc.builder.emit_reg(Op::CreateArray, dst);
2368
2369 if has_spread {
2370 // When spreads are present, we track the index dynamically.
2371 // For each normal element, push at current length.
2372 // For spread elements, use the Spread opcode.
2373 for el in elements.iter().flatten() {
2374 match el {
2375 ArrayElement::Expr(e) => {
2376 let val_reg = fc.alloc_reg();
2377 compile_expr(fc, e, val_reg)?;
2378 // Get current length as index.
2379 let idx_reg = fc.alloc_reg();
2380 let len_ni = fc.builder.add_name("length");
2381 fc.builder.emit_get_prop_name(idx_reg, dst, len_ni);
2382 fc.builder.emit_reg3(Op::SetProperty, dst, idx_reg, val_reg);
2383 // Increment length.
2384 let one_r = fc.alloc_reg();
2385 fc.builder.emit_load_int8(one_r, 1);
2386 fc.builder.emit_reg3(Op::Add, idx_reg, idx_reg, one_r);
2387 fc.builder.emit_set_prop_name(dst, len_ni, idx_reg);
2388 fc.free_reg(one_r);
2389 fc.free_reg(idx_reg);
2390 fc.free_reg(val_reg);
2391 }
2392 ArrayElement::Spread(e) => {
2393 let spread_src = fc.alloc_reg();
2394 compile_expr(fc, e, spread_src)?;
2395 fc.builder.emit_spread(dst, spread_src);
2396 fc.free_reg(spread_src);
2397 }
2398 }
2399 }
2400 } else {
2401 // No spreads: use simple indexed assignment.
2402 for (i, elem) in elements.iter().enumerate() {
2403 if let Some(ArrayElement::Expr(e)) = elem {
2404 let val_reg = fc.alloc_reg();
2405 compile_expr(fc, e, val_reg)?;
2406 let idx_reg = fc.alloc_reg();
2407 if i <= 127 {
2408 fc.builder.emit_load_int8(idx_reg, i as i8);
2409 } else {
2410 let ci = fc.builder.add_constant(Constant::Number(i as f64));
2411 fc.builder.emit_reg_u16(Op::LoadConst, idx_reg, ci);
2412 }
2413 fc.builder.emit_reg3(Op::SetProperty, dst, idx_reg, val_reg);
2414 fc.free_reg(idx_reg);
2415 fc.free_reg(val_reg);
2416 }
2417 }
2418 // Set length.
2419 if !elements.is_empty() {
2420 let len_name = fc.builder.add_name("length");
2421 let len_reg = fc.alloc_reg();
2422 if elements.len() <= 127 {
2423 fc.builder.emit_load_int8(len_reg, elements.len() as i8);
2424 } else {
2425 let ci = fc
2426 .builder
2427 .add_constant(Constant::Number(elements.len() as f64));
2428 fc.builder.emit_reg_u16(Op::LoadConst, len_reg, ci);
2429 }
2430 fc.builder.emit_set_prop_name(dst, len_name, len_reg);
2431 fc.free_reg(len_reg);
2432 }
2433 }
2434 }
2435
2436 ExprKind::Object(properties) => {
2437 fc.builder.emit_reg(Op::CreateObject, dst);
2438 for prop in properties {
2439 let val_reg = fc.alloc_reg();
2440 if let Some(value) = &prop.value {
2441 compile_expr(fc, value, val_reg)?;
2442 } else {
2443 // Shorthand: `{ x }` means `{ x: x }`.
2444 if let PropertyKey::Identifier(name) = &prop.key {
2445 if let Some(local) = fc.find_local_info(name) {
2446 let reg = local.reg;
2447 let captured = local.is_captured;
2448 if captured {
2449 fc.builder.emit_reg_reg(Op::CellLoad, val_reg, reg);
2450 } else {
2451 fc.builder.emit_reg_reg(Op::Move, val_reg, reg);
2452 }
2453 } else if let Some(uv_idx) = fc.find_upvalue(name) {
2454 fc.builder.emit_load_upvalue(val_reg, uv_idx);
2455 } else {
2456 let ni = fc.builder.add_name(name);
2457 fc.builder.emit_load_global(val_reg, ni);
2458 }
2459 } else {
2460 fc.builder.emit_reg(Op::LoadUndefined, val_reg);
2461 }
2462 }
2463
2464 match &prop.key {
2465 PropertyKey::Identifier(name) | PropertyKey::String(name) => {
2466 let ni = fc.builder.add_name(name);
2467 fc.builder.emit_set_prop_name(dst, ni, val_reg);
2468 }
2469 PropertyKey::Number(n) => {
2470 let key_reg = fc.alloc_reg();
2471 let ci = fc.builder.add_constant(Constant::Number(*n));
2472 fc.builder.emit_reg_u16(Op::LoadConst, key_reg, ci);
2473 fc.builder.emit_reg3(Op::SetProperty, dst, key_reg, val_reg);
2474 fc.free_reg(key_reg);
2475 }
2476 PropertyKey::Computed(expr) => {
2477 let key_reg = fc.alloc_reg();
2478 compile_expr(fc, expr, key_reg)?;
2479 fc.builder.emit_reg3(Op::SetProperty, dst, key_reg, val_reg);
2480 fc.free_reg(key_reg);
2481 }
2482 }
2483 fc.free_reg(val_reg);
2484 }
2485 }
2486
2487 ExprKind::Function(func_def) => {
2488 let inner = compile_function_body_with_captures(fc, func_def)?;
2489 let func_idx = fc.builder.add_function(inner);
2490 fc.builder.emit_reg_u16(Op::CreateClosure, dst, func_idx);
2491 }
2492
2493 ExprKind::Arrow {
2494 params,
2495 body,
2496 is_async,
2497 } => {
2498 // Collect free variables from the arrow body.
2499 let free_vars = collect_free_vars_arrow(params, body);
2500
2501 // Resolve upvalues against the parent scope.
2502 let mut upvalue_entries = Vec::new();
2503 for name in &free_vars {
2504 if let Some(local) = fc.find_local_info(name) {
2505 let reg = local.reg;
2506 let is_const = local.is_const;
2507 if let Some(l) = fc.locals.iter_mut().rev().find(|l| l.name == *name) {
2508 l.is_captured = true;
2509 }
2510 upvalue_entries.push(UpvalueEntry {
2511 name: name.clone(),
2512 def: UpvalueDef {
2513 is_local: true,
2514 index: reg,
2515 },
2516 is_const,
2517 });
2518 } else if let Some(parent_uv_idx) = fc.find_upvalue(name) {
2519 let is_const = fc.is_upvalue_const(parent_uv_idx);
2520 upvalue_entries.push(UpvalueEntry {
2521 name: name.clone(),
2522 def: UpvalueDef {
2523 is_local: false,
2524 index: parent_uv_idx,
2525 },
2526 is_const,
2527 });
2528 }
2529 }
2530
2531 let param_count = params.len().min(255) as u8;
2532 let mut inner = FunctionCompiler::new("<arrow>".into(), param_count);
2533
2534 // Copy upvalue entries.
2535 for entry in &upvalue_entries {
2536 inner.upvalues.push(UpvalueEntry {
2537 name: entry.name.clone(),
2538 def: entry.def.clone(),
2539 is_const: entry.is_const,
2540 });
2541 }
2542
2543 // Pre-scan for inner captures within the arrow body.
2544 match body {
2545 ArrowBody::Expr(_) => {}
2546 ArrowBody::Block(stmts) => {
2547 inner.captured_names = collect_inner_captures(stmts);
2548 }
2549 }
2550
2551 for p in params {
2552 if let PatternKind::Identifier(pname) = &p.kind {
2553 let is_captured = inner.captured_names.contains(pname.as_str());
2554 inner.define_local_ext(pname, is_captured, false);
2555 } else {
2556 let _ = inner.alloc_reg();
2557 }
2558 }
2559
2560 // Box captured parameters.
2561 for p in params {
2562 if let PatternKind::Identifier(pname) = &p.kind {
2563 if let Some(local) = inner.find_local_info(pname) {
2564 if local.is_captured {
2565 let reg = local.reg;
2566 let tmp = inner.alloc_reg();
2567 inner.builder.emit_reg_reg(Op::Move, tmp, reg);
2568 inner.builder.emit_reg(Op::NewCell, reg);
2569 inner.builder.emit_reg_reg(Op::CellStore, reg, tmp);
2570 inner.free_reg(tmp);
2571 }
2572 }
2573 }
2574 }
2575
2576 let result = inner.alloc_reg();
2577 match body {
2578 ArrowBody::Expr(e) => {
2579 compile_expr(&mut inner, e, result)?;
2580 }
2581 ArrowBody::Block(stmts) => {
2582 inner.builder.emit_reg(Op::LoadUndefined, result);
2583 compile_stmts(&mut inner, stmts, result)?;
2584 }
2585 }
2586 inner.builder.emit_reg(Op::Return, result);
2587 let mut inner_func = inner.builder.finish();
2588 inner_func.upvalue_defs = upvalue_entries.iter().map(|e| e.def.clone()).collect();
2589 inner_func.is_async = *is_async;
2590 let func_idx = fc.builder.add_function(inner_func);
2591 fc.builder.emit_reg_u16(Op::CreateClosure, dst, func_idx);
2592 }
2593
2594 ExprKind::Class(class_def) => {
2595 // Class expression: compile like class decl but into dst.
2596 let name = class_def.id.clone().unwrap_or_default();
2597 // Find constructor.
2598 let ctor = class_def.body.iter().find(|m| {
2599 matches!(
2600 &m.kind,
2601 ClassMemberKind::Method {
2602 kind: MethodKind::Constructor,
2603 ..
2604 }
2605 )
2606 });
2607 if let Some(member) = ctor {
2608 if let ClassMemberKind::Method { value, .. } = &member.kind {
2609 let inner = compile_function_body_with_captures(fc, value)?;
2610 let func_idx = fc.builder.add_function(inner);
2611 fc.builder.emit_reg_u16(Op::CreateClosure, dst, func_idx);
2612 }
2613 } else {
2614 let mut empty = BytecodeBuilder::new(name, 0);
2615 let r = 0u8;
2616 empty.func.register_count = 1;
2617 empty.emit_reg(Op::LoadUndefined, r);
2618 empty.emit_reg(Op::Return, r);
2619 let func_idx = fc.builder.add_function(empty.finish());
2620 fc.builder.emit_reg_u16(Op::CreateClosure, dst, func_idx);
2621 }
2622
2623 // Compile methods as properties on the constructor.
2624 for member in &class_def.body {
2625 match &member.kind {
2626 ClassMemberKind::Method {
2627 key,
2628 value,
2629 kind,
2630 is_static: _,
2631 computed: _,
2632 } => {
2633 if matches!(kind, MethodKind::Constructor) {
2634 continue;
2635 }
2636 let method_name = match key {
2637 PropertyKey::Identifier(s) | PropertyKey::String(s) => s.clone(),
2638 _ => continue,
2639 };
2640 let inner = compile_function_body_with_captures(fc, value)?;
2641 let func_idx = fc.builder.add_function(inner);
2642 let method_reg = fc.alloc_reg();
2643 fc.builder
2644 .emit_reg_u16(Op::CreateClosure, method_reg, func_idx);
2645 let name_idx = fc.builder.add_name(&method_name);
2646 fc.builder.emit_set_prop_name(dst, name_idx, method_reg);
2647 fc.free_reg(method_reg);
2648 }
2649 ClassMemberKind::Property { .. } => {}
2650 }
2651 }
2652 }
2653
2654 ExprKind::Sequence(exprs) => {
2655 for e in exprs {
2656 compile_expr(fc, e, dst)?;
2657 }
2658 }
2659
2660 ExprKind::Spread(inner) => {
2661 compile_expr(fc, inner, dst)?;
2662 }
2663
2664 ExprKind::TemplateLiteral {
2665 quasis,
2666 expressions,
2667 } => {
2668 // Compile template literal as string concatenation.
2669 if quasis.len() == 1 && expressions.is_empty() {
2670 let ci = fc.builder.add_constant(Constant::String(quasis[0].clone()));
2671 fc.builder.emit_reg_u16(Op::LoadConst, dst, ci);
2672 } else {
2673 // Start with first quasi.
2674 let ci = fc.builder.add_constant(Constant::String(quasis[0].clone()));
2675 fc.builder.emit_reg_u16(Op::LoadConst, dst, ci);
2676 for (i, expr) in expressions.iter().enumerate() {
2677 let tmp = fc.alloc_reg();
2678 compile_expr(fc, expr, tmp)?;
2679 fc.builder.emit_reg3(Op::Add, dst, dst, tmp);
2680 fc.free_reg(tmp);
2681 if i + 1 < quasis.len() {
2682 let qi = fc
2683 .builder
2684 .add_constant(Constant::String(quasis[i + 1].clone()));
2685 let tmp2 = fc.alloc_reg();
2686 fc.builder.emit_reg_u16(Op::LoadConst, tmp2, qi);
2687 fc.builder.emit_reg3(Op::Add, dst, dst, tmp2);
2688 fc.free_reg(tmp2);
2689 }
2690 }
2691 }
2692 }
2693
2694 ExprKind::TaggedTemplate { tag, quasi } => {
2695 // Simplified: call tag with the template as argument.
2696 let func_reg = fc.alloc_reg();
2697 compile_expr(fc, tag, func_reg)?;
2698 let arg_reg = fc.alloc_reg();
2699 compile_expr(fc, quasi, arg_reg)?;
2700 fc.builder.emit_call(dst, func_reg, arg_reg, 1);
2701 fc.free_reg(arg_reg);
2702 fc.free_reg(func_reg);
2703 }
2704
2705 ExprKind::Yield { argument, delegate } => {
2706 if *delegate {
2707 // yield* expr: iterate the sub-iterator and yield each value.
2708 let iter_r = fc.alloc_reg();
2709 if let Some(arg) = argument {
2710 compile_expr(fc, arg, iter_r)?;
2711 } else {
2712 fc.builder.emit_reg(Op::LoadUndefined, iter_r);
2713 }
2714
2715 // Get iterator from the expression.
2716 let iter_method_r = fc.alloc_reg();
2717 let sym_iter_ni = fc.builder.add_name("@@iterator");
2718 fc.builder
2719 .emit_get_prop_name(iter_method_r, iter_r, sym_iter_ni);
2720 let this_ni = fc.builder.add_name("this");
2721 fc.builder.emit_store_global(this_ni, iter_r);
2722 let iterator_r = fc.alloc_reg();
2723 let args_start = fc.next_reg;
2724 fc.builder
2725 .emit_call(iterator_r, iter_method_r, args_start, 0);
2726
2727 // Get next method.
2728 let next_r = fc.alloc_reg();
2729 let next_ni = fc.builder.add_name("next");
2730 fc.builder.emit_get_prop_name(next_r, iterator_r, next_ni);
2731
2732 let result_r = fc.alloc_reg();
2733 let done_r = fc.alloc_reg();
2734 let val_r = fc.alloc_reg();
2735
2736 let loop_start = fc.builder.offset();
2737
2738 // Call next().
2739 fc.builder.emit_store_global(this_ni, iterator_r);
2740 fc.builder.emit_call(result_r, next_r, args_start, 0);
2741
2742 let done_ni = fc.builder.add_name("done");
2743 let value_ni = fc.builder.add_name("value");
2744 fc.builder.emit_get_prop_name(done_r, result_r, done_ni);
2745
2746 let exit_patch = fc.builder.emit_cond_jump(Op::JumpIfTrue, done_r);
2747
2748 fc.builder.emit_get_prop_name(val_r, result_r, value_ni);
2749
2750 // Yield the value.
2751 fc.builder.emit_yield(dst, val_r);
2752
2753 // Jump back.
2754 fc.builder.emit_jump_to(loop_start);
2755
2756 // Exit: the last result's value is the yield* expression value.
2757 fc.builder.patch_jump(exit_patch);
2758 fc.builder.emit_get_prop_name(dst, result_r, value_ni);
2759
2760 fc.free_reg(val_r);
2761 fc.free_reg(done_r);
2762 fc.free_reg(result_r);
2763 fc.free_reg(next_r);
2764 fc.free_reg(iterator_r);
2765 fc.free_reg(iter_method_r);
2766 fc.free_reg(iter_r);
2767 } else {
2768 // yield expr: emit Yield opcode.
2769 let src = fc.alloc_reg();
2770 if let Some(arg) = argument {
2771 compile_expr(fc, arg, src)?;
2772 } else {
2773 fc.builder.emit_reg(Op::LoadUndefined, src);
2774 }
2775 fc.builder.emit_yield(dst, src);
2776 fc.free_reg(src);
2777 }
2778 }
2779
2780 ExprKind::Await(inner) => {
2781 let src = fc.alloc_reg();
2782 compile_expr(fc, inner, src)?;
2783 fc.builder.emit_await(dst, src);
2784 fc.free_reg(src);
2785 }
2786
2787 ExprKind::RegExp { pattern, flags } => {
2788 // Compile as: RegExp(pattern, flags) — a call to the global constructor.
2789 let func_reg = fc.alloc_reg();
2790 let name_idx = fc.builder.add_name("RegExp");
2791 fc.builder.emit_reg_u16(Op::LoadGlobal, func_reg, name_idx);
2792
2793 let args_start = fc.next_reg;
2794 let pat_reg = fc.alloc_reg();
2795 let pat_idx = fc.builder.add_constant(Constant::String(pattern.clone()));
2796 fc.builder.emit_reg_u16(Op::LoadConst, pat_reg, pat_idx);
2797
2798 let flags_reg = fc.alloc_reg();
2799 let flags_idx = fc.builder.add_constant(Constant::String(flags.clone()));
2800 fc.builder.emit_reg_u16(Op::LoadConst, flags_reg, flags_idx);
2801
2802 fc.builder.emit_call(dst, func_reg, args_start, 2);
2803
2804 fc.next_reg -= 1; // flags_reg
2805 fc.next_reg -= 1; // pat_reg
2806 fc.free_reg(func_reg);
2807 }
2808
2809 ExprKind::OptionalChain { base } => {
2810 compile_expr(fc, base, dst)?;
2811 }
2812 }
2813 Ok(())
2814}
2815
2816/// Compile a store operation (assignment target).
2817fn compile_store(fc: &mut FunctionCompiler, target: &Expr, src: Reg) -> Result<(), JsError> {
2818 match &target.kind {
2819 ExprKind::Identifier(name) => {
2820 if let Some(local) = fc.find_local_info(name) {
2821 if local.is_const {
2822 return Err(JsError::SyntaxError(format!(
2823 "Assignment to constant variable '{name}'"
2824 )));
2825 }
2826 let reg = local.reg;
2827 let captured = local.is_captured;
2828 if captured {
2829 fc.builder.emit_reg_reg(Op::CellStore, reg, src);
2830 } else if reg != src {
2831 fc.builder.emit_reg_reg(Op::Move, reg, src);
2832 }
2833 } else if let Some(uv_idx) = fc.find_upvalue(name) {
2834 if fc.is_upvalue_const(uv_idx) {
2835 return Err(JsError::SyntaxError(format!(
2836 "Assignment to constant variable '{name}'"
2837 )));
2838 }
2839 fc.builder.emit_store_upvalue(uv_idx, src);
2840 } else {
2841 let ni = fc.builder.add_name(name);
2842 fc.builder.emit_store_global(ni, src);
2843 }
2844 }
2845 ExprKind::Member {
2846 object,
2847 property,
2848 computed,
2849 } => {
2850 let obj_reg = fc.alloc_reg();
2851 compile_expr(fc, object, obj_reg)?;
2852 if !computed {
2853 if let ExprKind::Identifier(name) = &property.kind {
2854 let ni = fc.builder.add_name(name);
2855 fc.builder.emit_set_prop_name(obj_reg, ni, src);
2856 } else {
2857 let key_reg = fc.alloc_reg();
2858 compile_expr(fc, property, key_reg)?;
2859 fc.builder.emit_reg3(Op::SetProperty, obj_reg, key_reg, src);
2860 fc.free_reg(key_reg);
2861 }
2862 } else {
2863 let key_reg = fc.alloc_reg();
2864 compile_expr(fc, property, key_reg)?;
2865 fc.builder.emit_reg3(Op::SetProperty, obj_reg, key_reg, src);
2866 fc.free_reg(key_reg);
2867 }
2868 fc.free_reg(obj_reg);
2869 }
2870 _ => {
2871 // Other assignment targets (destructuring) not handled here.
2872 }
2873 }
2874 Ok(())
2875}
2876
2877fn binary_op_to_opcode(op: BinaryOp) -> Op {
2878 match op {
2879 BinaryOp::Add => Op::Add,
2880 BinaryOp::Sub => Op::Sub,
2881 BinaryOp::Mul => Op::Mul,
2882 BinaryOp::Div => Op::Div,
2883 BinaryOp::Rem => Op::Rem,
2884 BinaryOp::Exp => Op::Exp,
2885 BinaryOp::Eq => Op::Eq,
2886 BinaryOp::Ne => Op::NotEq,
2887 BinaryOp::StrictEq => Op::StrictEq,
2888 BinaryOp::StrictNe => Op::StrictNotEq,
2889 BinaryOp::Lt => Op::LessThan,
2890 BinaryOp::Le => Op::LessEq,
2891 BinaryOp::Gt => Op::GreaterThan,
2892 BinaryOp::Ge => Op::GreaterEq,
2893 BinaryOp::Shl => Op::ShiftLeft,
2894 BinaryOp::Shr => Op::ShiftRight,
2895 BinaryOp::Ushr => Op::UShiftRight,
2896 BinaryOp::BitAnd => Op::BitAnd,
2897 BinaryOp::BitOr => Op::BitOr,
2898 BinaryOp::BitXor => Op::BitXor,
2899 BinaryOp::In => Op::In,
2900 BinaryOp::Instanceof => Op::InstanceOf,
2901 }
2902}
2903
2904fn compound_assign_op(op: AssignOp) -> Op {
2905 match op {
2906 AssignOp::AddAssign => Op::Add,
2907 AssignOp::SubAssign => Op::Sub,
2908 AssignOp::MulAssign => Op::Mul,
2909 AssignOp::DivAssign => Op::Div,
2910 AssignOp::RemAssign => Op::Rem,
2911 AssignOp::ExpAssign => Op::Exp,
2912 AssignOp::ShlAssign => Op::ShiftLeft,
2913 AssignOp::ShrAssign => Op::ShiftRight,
2914 AssignOp::UshrAssign => Op::UShiftRight,
2915 AssignOp::BitAndAssign => Op::BitAnd,
2916 AssignOp::BitOrAssign => Op::BitOr,
2917 AssignOp::BitXorAssign => Op::BitXor,
2918 AssignOp::AndAssign => Op::BitAnd, // logical AND assignment uses short-circuit; simplified here
2919 AssignOp::OrAssign => Op::BitOr, // likewise
2920 AssignOp::NullishAssign => Op::Move, // simplified
2921 AssignOp::Assign => unreachable!(),
2922 }
2923}
2924
2925#[cfg(test)]
2926mod tests {
2927 use super::*;
2928 use crate::parser::Parser;
2929
2930 /// Helper: parse and compile source, return the top-level function.
2931 fn compile_src(src: &str) -> Function {
2932 let program = Parser::parse(src).expect("parse failed");
2933 compile(&program).expect("compile failed")
2934 }
2935
2936 #[test]
2937 fn test_compile_number_literal() {
2938 let f = compile_src("42;");
2939 let dis = f.disassemble();
2940 assert!(dis.contains("LoadInt8 r0, 42"), "got:\n{dis}");
2941 assert!(dis.contains("Return r0"));
2942 }
2943
2944 #[test]
2945 fn test_compile_large_number() {
2946 let f = compile_src("3.14;");
2947 let dis = f.disassemble();
2948 assert!(dis.contains("LoadConst r0, #0"), "got:\n{dis}");
2949 assert!(
2950 f.constants.contains(&Constant::Number(3.14)),
2951 "constants: {:?}",
2952 f.constants
2953 );
2954 }
2955
2956 #[test]
2957 fn test_compile_string() {
2958 let f = compile_src("\"hello\";");
2959 let dis = f.disassemble();
2960 assert!(dis.contains("LoadConst r0, #0"));
2961 assert!(f.constants.contains(&Constant::String("hello".into())));
2962 }
2963
2964 #[test]
2965 fn test_compile_bool_null() {
2966 let f = compile_src("true; false; null;");
2967 let dis = f.disassemble();
2968 assert!(dis.contains("LoadTrue r0"));
2969 assert!(dis.contains("LoadFalse r0"));
2970 assert!(dis.contains("LoadNull r0"));
2971 }
2972
2973 #[test]
2974 fn test_compile_binary_arithmetic() {
2975 let f = compile_src("1 + 2;");
2976 let dis = f.disassemble();
2977 assert!(dis.contains("Add r0, r1, r2"), "got:\n{dis}");
2978 }
2979
2980 #[test]
2981 fn test_compile_nested_arithmetic() {
2982 let f = compile_src("(1 + 2) * 3;");
2983 let dis = f.disassemble();
2984 assert!(dis.contains("Add"), "got:\n{dis}");
2985 assert!(dis.contains("Mul"), "got:\n{dis}");
2986 }
2987
2988 #[test]
2989 fn test_compile_var_decl() {
2990 let f = compile_src("var x = 10; x;");
2991 let dis = f.disassemble();
2992 // x should get a register, then be loaded from that register.
2993 assert!(dis.contains("LoadInt8"), "got:\n{dis}");
2994 assert!(
2995 dis.contains("Move") || dis.contains("LoadInt8"),
2996 "got:\n{dis}"
2997 );
2998 }
2999
3000 #[test]
3001 fn test_compile_let_const() {
3002 let f = compile_src("let a = 1; const b = 2; a + b;");
3003 let dis = f.disassemble();
3004 assert!(dis.contains("Add"), "got:\n{dis}");
3005 }
3006
3007 #[test]
3008 fn test_compile_if_else() {
3009 let f = compile_src("if (true) { 1; } else { 2; }");
3010 let dis = f.disassemble();
3011 assert!(dis.contains("JumpIfFalse"), "got:\n{dis}");
3012 assert!(dis.contains("Jump"), "got:\n{dis}");
3013 }
3014
3015 #[test]
3016 fn test_compile_while() {
3017 let f = compile_src("var i = 0; while (i < 10) { i = i + 1; }");
3018 let dis = f.disassemble();
3019 assert!(dis.contains("LessThan"), "got:\n{dis}");
3020 assert!(dis.contains("JumpIfFalse"), "got:\n{dis}");
3021 assert!(
3022 dis.contains("Jump"),
3023 "backward jump should be present: {dis}"
3024 );
3025 }
3026
3027 #[test]
3028 fn test_compile_do_while() {
3029 let f = compile_src("var i = 0; do { i = i + 1; } while (i < 5);");
3030 let dis = f.disassemble();
3031 assert!(dis.contains("JumpIfTrue"), "got:\n{dis}");
3032 }
3033
3034 #[test]
3035 fn test_compile_for_loop() {
3036 let f = compile_src("for (var i = 0; i < 10; i = i + 1) { i; }");
3037 let dis = f.disassemble();
3038 assert!(dis.contains("LessThan"), "got:\n{dis}");
3039 assert!(dis.contains("JumpIfFalse"), "got:\n{dis}");
3040 }
3041
3042 #[test]
3043 fn test_compile_function_decl() {
3044 let f = compile_src("function add(a, b) { return a + b; }");
3045 let dis = f.disassemble();
3046 assert!(dis.contains("CreateClosure"), "got:\n{dis}");
3047 assert!(!f.functions.is_empty(), "should have nested function");
3048 let inner = &f.functions[0];
3049 assert_eq!(inner.name, "add");
3050 assert_eq!(inner.param_count, 2);
3051 let inner_dis = inner.disassemble();
3052 assert!(inner_dis.contains("Add"), "inner:\n{inner_dis}");
3053 assert!(inner_dis.contains("Return"), "inner:\n{inner_dis}");
3054 }
3055
3056 #[test]
3057 fn test_compile_function_call() {
3058 let f = compile_src("function f() { return 42; } f();");
3059 let dis = f.disassemble();
3060 assert!(dis.contains("Call"), "got:\n{dis}");
3061 }
3062
3063 #[test]
3064 fn test_compile_arrow_function() {
3065 let f = compile_src("var add = (a, b) => a + b;");
3066 let dis = f.disassemble();
3067 assert!(dis.contains("CreateClosure"), "got:\n{dis}");
3068 let inner = &f.functions[0];
3069 assert_eq!(inner.param_count, 2);
3070 }
3071
3072 #[test]
3073 fn test_compile_assignment() {
3074 let f = compile_src("var x = 1; x = x + 2;");
3075 let dis = f.disassemble();
3076 assert!(dis.contains("Add"), "got:\n{dis}");
3077 assert!(
3078 dis.contains("Move"),
3079 "assignment should produce Move:\n{dis}"
3080 );
3081 }
3082
3083 #[test]
3084 fn test_compile_compound_assignment() {
3085 let f = compile_src("var x = 10; x += 5;");
3086 let dis = f.disassemble();
3087 assert!(dis.contains("Add"), "got:\n{dis}");
3088 }
3089
3090 #[test]
3091 fn test_compile_member_access() {
3092 let f = compile_src("var obj = {}; obj.x;");
3093 let dis = f.disassemble();
3094 assert!(dis.contains("CreateObject"), "got:\n{dis}");
3095 assert!(dis.contains("GetPropertyByName"), "got:\n{dis}");
3096 }
3097
3098 #[test]
3099 fn test_compile_computed_member() {
3100 let f = compile_src("var arr = []; arr[0];");
3101 let dis = f.disassemble();
3102 assert!(dis.contains("GetProperty"), "got:\n{dis}");
3103 }
3104
3105 #[test]
3106 fn test_compile_object_literal() {
3107 let f = compile_src("var obj = { a: 1, b: 2 };");
3108 let dis = f.disassemble();
3109 assert!(dis.contains("CreateObject"), "got:\n{dis}");
3110 assert!(dis.contains("SetPropertyByName"), "got:\n{dis}");
3111 }
3112
3113 #[test]
3114 fn test_compile_array_literal() {
3115 let f = compile_src("[1, 2, 3];");
3116 let dis = f.disassemble();
3117 assert!(dis.contains("CreateArray"), "got:\n{dis}");
3118 assert!(dis.contains("SetProperty"), "got:\n{dis}");
3119 }
3120
3121 #[test]
3122 fn test_compile_conditional() {
3123 let f = compile_src("true ? 1 : 2;");
3124 let dis = f.disassemble();
3125 assert!(dis.contains("JumpIfFalse"), "got:\n{dis}");
3126 }
3127
3128 #[test]
3129 fn test_compile_logical_and() {
3130 let f = compile_src("true && false;");
3131 let dis = f.disassemble();
3132 assert!(dis.contains("JumpIfFalse"), "short-circuit:\n{dis}");
3133 }
3134
3135 #[test]
3136 fn test_compile_logical_or() {
3137 let f = compile_src("false || true;");
3138 let dis = f.disassemble();
3139 assert!(dis.contains("JumpIfTrue"), "short-circuit:\n{dis}");
3140 }
3141
3142 #[test]
3143 fn test_compile_typeof() {
3144 let f = compile_src("typeof 42;");
3145 let dis = f.disassemble();
3146 assert!(dis.contains("TypeOf"), "got:\n{dis}");
3147 }
3148
3149 #[test]
3150 fn test_compile_unary_minus() {
3151 let f = compile_src("-42;");
3152 let dis = f.disassemble();
3153 assert!(dis.contains("Neg"), "got:\n{dis}");
3154 }
3155
3156 #[test]
3157 fn test_compile_not() {
3158 let f = compile_src("!true;");
3159 let dis = f.disassemble();
3160 assert!(dis.contains("LogicalNot"), "got:\n{dis}");
3161 }
3162
3163 #[test]
3164 fn test_compile_return() {
3165 let f = compile_src("function f() { return 42; }");
3166 let inner = &f.functions[0];
3167 let dis = inner.disassemble();
3168 assert!(dis.contains("Return"), "got:\n{dis}");
3169 }
3170
3171 #[test]
3172 fn test_compile_empty_return() {
3173 let f = compile_src("function f() { return; }");
3174 let inner = &f.functions[0];
3175 let dis = inner.disassemble();
3176 assert!(dis.contains("LoadUndefined"), "got:\n{dis}");
3177 assert!(dis.contains("Return"), "got:\n{dis}");
3178 }
3179
3180 #[test]
3181 fn test_compile_throw() {
3182 let f = compile_src("function f() { throw 42; }");
3183 let inner = &f.functions[0];
3184 let dis = inner.disassemble();
3185 assert!(dis.contains("Throw"), "got:\n{dis}");
3186 }
3187
3188 #[test]
3189 fn test_compile_this() {
3190 let f = compile_src("this;");
3191 let dis = f.disassemble();
3192 assert!(dis.contains("LoadGlobal"), "got:\n{dis}");
3193 assert!(f.names.contains(&"this".to_string()));
3194 }
3195
3196 #[test]
3197 fn test_compile_global_var() {
3198 let f = compile_src("console;");
3199 let dis = f.disassemble();
3200 assert!(dis.contains("LoadGlobal"), "got:\n{dis}");
3201 assert!(f.names.contains(&"console".to_string()));
3202 }
3203
3204 #[test]
3205 fn test_compile_template_literal() {
3206 let f = compile_src("`hello`;");
3207 assert!(
3208 f.constants.contains(&Constant::String("hello".into())),
3209 "constants: {:?}",
3210 f.constants
3211 );
3212 }
3213
3214 #[test]
3215 fn test_compile_switch() {
3216 let f = compile_src("switch (1) { case 1: 42; break; case 2: 99; break; }");
3217 let dis = f.disassemble();
3218 assert!(dis.contains("StrictEq"), "got:\n{dis}");
3219 }
3220
3221 #[test]
3222 fn test_compile_class() {
3223 let f = compile_src("class Foo { constructor() {} greet() { return 1; } }");
3224 let dis = f.disassemble();
3225 assert!(dis.contains("CreateClosure"), "got:\n{dis}");
3226 }
3227
3228 #[test]
3229 fn test_compile_update_prefix() {
3230 let f = compile_src("var x = 0; ++x;");
3231 let dis = f.disassemble();
3232 assert!(dis.contains("Add"), "got:\n{dis}");
3233 }
3234
3235 #[test]
3236 fn test_compile_comparison() {
3237 let f = compile_src("1 === 2;");
3238 let dis = f.disassemble();
3239 assert!(dis.contains("StrictEq"), "got:\n{dis}");
3240 }
3241
3242 #[test]
3243 fn test_compile_bitwise() {
3244 let f = compile_src("1 & 2;");
3245 let dis = f.disassemble();
3246 assert!(dis.contains("BitAnd"), "got:\n{dis}");
3247 }
3248
3249 #[test]
3250 fn test_compile_void() {
3251 let f = compile_src("void 0;");
3252 let dis = f.disassemble();
3253 assert!(dis.contains("Void"), "got:\n{dis}");
3254 }
3255
3256 #[test]
3257 fn test_disassembler_output_format() {
3258 let f = compile_src("var x = 42; x + 1;");
3259 let dis = f.disassemble();
3260 // Should contain function header.
3261 assert!(dis.contains("function <main>"));
3262 // Should contain code section.
3263 assert!(dis.contains("code:"));
3264 // Should have hex offsets.
3265 assert!(dis.contains("0000"));
3266 }
3267
3268 #[test]
3269 fn test_register_allocation_is_minimal() {
3270 // `var a = 1; var b = 2; a + b;` should use few registers.
3271 let f = compile_src("var a = 1; var b = 2; a + b;");
3272 // r0 = result, r1 = a, r2 = b, r3/r4 = temps for addition
3273 assert!(
3274 f.register_count <= 6,
3275 "too many registers: {}",
3276 f.register_count
3277 );
3278 }
3279
3280 #[test]
3281 fn test_nested_function_closure() {
3282 let f = compile_src("function outer() { function inner() { return 1; } return inner; }");
3283 assert_eq!(f.functions.len(), 1);
3284 let outer = &f.functions[0];
3285 assert_eq!(outer.name, "outer");
3286 assert_eq!(outer.functions.len(), 1);
3287 let inner = &outer.functions[0];
3288 assert_eq!(inner.name, "inner");
3289 }
3290
3291 #[test]
3292 fn test_for_with_no_parts() {
3293 // `for (;;) { break; }` — infinite loop with immediate break.
3294 let f = compile_src("for (;;) { break; }");
3295 let dis = f.disassemble();
3296 assert!(dis.contains("Jump"), "got:\n{dis}");
3297 }
3298
3299 #[test]
3300 fn test_for_continue_targets_update() {
3301 // `continue` in a for-loop must jump to the update expression, not back
3302 // to the condition check. Verify the continue jump goes to the Add (i + 1)
3303 // rather than to the LessThan condition.
3304 let f = compile_src("for (var i = 0; i < 10; i = i + 1) { continue; }");
3305 let dis = f.disassemble();
3306 // The for-loop should contain: LessThan (test), JumpIfFalse (exit),
3307 // Jump (continue), Add (update), Jump (back to test).
3308 assert!(dis.contains("LessThan"), "missing test: {dis}");
3309 assert!(dis.contains("Add"), "missing update: {dis}");
3310 // There should be at least 2 Jump instructions (continue + back-edge).
3311 let jump_count = dis.matches("Jump ").count();
3312 assert!(
3313 jump_count >= 2,
3314 "expected >= 2 jumps for continue + back-edge, got {jump_count}: {dis}"
3315 );
3316 }
3317
3318 #[test]
3319 fn test_do_while_continue_targets_condition() {
3320 // `continue` in do-while must jump to the condition, not the body start.
3321 let f = compile_src("var i = 0; do { i = i + 1; continue; } while (i < 5);");
3322 let dis = f.disassemble();
3323 assert!(dis.contains("LessThan"), "missing condition: {dis}");
3324 assert!(dis.contains("JumpIfTrue"), "missing back-edge: {dis}");
3325 }
3326
3327 #[test]
3328 fn test_switch_default_case() {
3329 // Default case must not corrupt bytecode.
3330 let f = compile_src("switch (1) { case 1: 10; break; default: 20; break; }");
3331 let dis = f.disassemble();
3332 assert!(dis.contains("StrictEq"), "missing case test: {dis}");
3333 // The first instruction should NOT be corrupted.
3334 assert!(
3335 dis.contains("LoadUndefined r0"),
3336 "first instruction corrupted: {dis}"
3337 );
3338 }
3339
3340 #[test]
3341 fn test_switch_only_default() {
3342 // Switch with only a default case.
3343 let f = compile_src("switch (42) { default: 99; }");
3344 let dis = f.disassemble();
3345 // Should compile without panicking and contain the default body.
3346 assert!(dis.contains("LoadInt8"), "got:\n{dis}");
3347 }
3348
3349 #[test]
3350 fn test_class_empty_constructor_has_return() {
3351 // A class without an explicit constructor should produce a function with Return.
3352 let f = compile_src("class Foo {}");
3353 assert!(!f.functions.is_empty(), "should have constructor function");
3354 let ctor = &f.functions[0];
3355 let dis = ctor.disassemble();
3356 assert!(
3357 dis.contains("Return"),
3358 "empty constructor must have Return: {dis}"
3359 );
3360 }
3361
3362 #[test]
3363 fn test_class_expression_compiles_methods() {
3364 // Class expression should compile methods, not just the constructor.
3365 let f = compile_src("var C = class { greet() { return 1; } };");
3366 let dis = f.disassemble();
3367 assert!(
3368 dis.contains("SetPropertyByName"),
3369 "method should be set as property: {dis}"
3370 );
3371 }
3372}