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