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