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;
10
11/// Compiler state for a single function scope.
12struct FunctionCompiler {
13 builder: BytecodeBuilder,
14 /// Maps local variable names to their register slots.
15 locals: Vec<Local>,
16 /// Next free register index.
17 next_reg: u8,
18 /// Stack of loop contexts for break/continue.
19 loop_stack: Vec<LoopCtx>,
20}
21
22#[derive(Debug, Clone)]
23struct Local {
24 name: String,
25 reg: Reg,
26}
27
28struct LoopCtx {
29 /// Label, if this is a labeled loop.
30 label: Option<String>,
31 /// Patch positions for break jumps.
32 break_patches: Vec<usize>,
33 /// Patch positions for continue jumps (patched after body compilation).
34 continue_patches: Vec<usize>,
35}
36
37impl FunctionCompiler {
38 fn new(name: String, param_count: u8) -> Self {
39 Self {
40 builder: BytecodeBuilder::new(name, param_count),
41 locals: Vec::new(),
42 next_reg: 0,
43 loop_stack: Vec::new(),
44 }
45 }
46
47 /// Allocate a register, updating the high-water mark.
48 fn alloc_reg(&mut self) -> Reg {
49 let r = self.next_reg;
50 self.next_reg = self.next_reg.checked_add(1).expect("register overflow");
51 if self.next_reg > self.builder.func.register_count {
52 self.builder.func.register_count = self.next_reg;
53 }
54 r
55 }
56
57 /// Free the last allocated register (must be called in reverse order).
58 fn free_reg(&mut self, r: Reg) {
59 debug_assert_eq!(
60 r,
61 self.next_reg - 1,
62 "registers must be freed in reverse order"
63 );
64 self.next_reg -= 1;
65 }
66
67 /// Look up a local variable by name.
68 fn find_local(&self, name: &str) -> Option<Reg> {
69 self.locals
70 .iter()
71 .rev()
72 .find(|l| l.name == name)
73 .map(|l| l.reg)
74 }
75
76 /// Define a local variable.
77 fn define_local(&mut self, name: &str) -> Reg {
78 let reg = self.alloc_reg();
79 self.locals.push(Local {
80 name: name.to_string(),
81 reg,
82 });
83 reg
84 }
85}
86
87/// Compile a parsed program into a top-level bytecode function.
88pub fn compile(program: &Program) -> Result<Function, JsError> {
89 let mut fc = FunctionCompiler::new("<main>".into(), 0);
90
91 // Reserve r0 for the implicit return value.
92 let result_reg = fc.alloc_reg();
93 fc.builder.emit_reg(Op::LoadUndefined, result_reg);
94
95 compile_stmts(&mut fc, &program.body, result_reg)?;
96
97 fc.builder.emit_reg(Op::Return, result_reg);
98 Ok(fc.builder.finish())
99}
100
101fn compile_stmts(
102 fc: &mut FunctionCompiler,
103 stmts: &[Stmt],
104 result_reg: Reg,
105) -> Result<(), JsError> {
106 for stmt in stmts {
107 compile_stmt(fc, stmt, result_reg)?;
108 }
109 Ok(())
110}
111
112fn compile_stmt(fc: &mut FunctionCompiler, stmt: &Stmt, result_reg: Reg) -> Result<(), JsError> {
113 match &stmt.kind {
114 StmtKind::Expr(expr) => {
115 // Expression statement: compile and store result in result_reg.
116 compile_expr(fc, expr, result_reg)?;
117 }
118
119 StmtKind::Block(stmts) => {
120 let saved_locals = fc.locals.len();
121 let saved_next = fc.next_reg;
122 compile_stmts(fc, stmts, result_reg)?;
123 // Pop locals from this block.
124 fc.locals.truncate(saved_locals);
125 fc.next_reg = saved_next;
126 }
127
128 StmtKind::VarDecl {
129 kind: _,
130 declarators,
131 } => {
132 for decl in declarators {
133 compile_var_declarator(fc, decl)?;
134 }
135 }
136
137 StmtKind::FunctionDecl(func_def) => {
138 compile_function_decl(fc, func_def)?;
139 }
140
141 StmtKind::If {
142 test,
143 consequent,
144 alternate,
145 } => {
146 compile_if(fc, test, consequent, alternate.as_deref(), result_reg)?;
147 }
148
149 StmtKind::While { test, body } => {
150 compile_while(fc, test, body, None, result_reg)?;
151 }
152
153 StmtKind::DoWhile { body, test } => {
154 compile_do_while(fc, body, test, None, result_reg)?;
155 }
156
157 StmtKind::For {
158 init,
159 test,
160 update,
161 body,
162 } => {
163 compile_for(
164 fc,
165 init.as_ref(),
166 test.as_ref(),
167 update.as_ref(),
168 body,
169 None,
170 result_reg,
171 )?;
172 }
173
174 StmtKind::ForIn { left, right, body } => {
175 // For-in is complex; emit a stub that evaluates RHS, then TODO at runtime.
176 let _ = left;
177 let tmp = fc.alloc_reg();
178 compile_expr(fc, right, tmp)?;
179 fc.free_reg(tmp);
180 // For now, just compile the body once (the VM will handle iteration).
181 compile_stmt(fc, body, result_reg)?;
182 }
183
184 StmtKind::ForOf {
185 left,
186 right,
187 body,
188 is_await: _,
189 } => {
190 let _ = left;
191 let tmp = fc.alloc_reg();
192 compile_expr(fc, right, tmp)?;
193 fc.free_reg(tmp);
194 compile_stmt(fc, body, result_reg)?;
195 }
196
197 StmtKind::Return(expr) => {
198 let ret_reg = fc.alloc_reg();
199 if let Some(e) = expr {
200 compile_expr(fc, e, ret_reg)?;
201 } else {
202 fc.builder.emit_reg(Op::LoadUndefined, ret_reg);
203 }
204 fc.builder.emit_reg(Op::Return, ret_reg);
205 fc.free_reg(ret_reg);
206 }
207
208 StmtKind::Throw(expr) => {
209 let tmp = fc.alloc_reg();
210 compile_expr(fc, expr, tmp)?;
211 fc.builder.emit_reg(Op::Throw, tmp);
212 fc.free_reg(tmp);
213 }
214
215 StmtKind::Break(label) => {
216 // Find the matching loop context.
217 let idx = find_loop_ctx(&fc.loop_stack, label.as_deref())
218 .ok_or_else(|| JsError::SyntaxError("break outside of loop".into()))?;
219 let patch = fc.builder.emit_jump(Op::Jump);
220 fc.loop_stack[idx].break_patches.push(patch);
221 }
222
223 StmtKind::Continue(label) => {
224 let idx = find_loop_ctx(&fc.loop_stack, label.as_deref())
225 .ok_or_else(|| JsError::SyntaxError("continue outside of loop".into()))?;
226 let patch = fc.builder.emit_jump(Op::Jump);
227 fc.loop_stack[idx].continue_patches.push(patch);
228 }
229
230 StmtKind::Labeled { label, body } => {
231 // If body is a loop, propagate the label.
232 match &body.kind {
233 StmtKind::While { test, body: inner } => {
234 compile_while(fc, test, inner, Some(label.clone()), result_reg)?;
235 }
236 StmtKind::DoWhile { body: inner, test } => {
237 compile_do_while(fc, inner, test, Some(label.clone()), result_reg)?;
238 }
239 StmtKind::For {
240 init,
241 test,
242 update,
243 body: inner,
244 } => {
245 compile_for(
246 fc,
247 init.as_ref(),
248 test.as_ref(),
249 update.as_ref(),
250 inner,
251 Some(label.clone()),
252 result_reg,
253 )?;
254 }
255 _ => {
256 compile_stmt(fc, body, result_reg)?;
257 }
258 }
259 }
260
261 StmtKind::Switch {
262 discriminant,
263 cases,
264 } => {
265 compile_switch(fc, discriminant, cases, result_reg)?;
266 }
267
268 StmtKind::Try {
269 block,
270 handler,
271 finalizer,
272 } => {
273 // Simplified: compile blocks sequentially.
274 // Real try/catch needs exception table support from the VM.
275 compile_stmts(fc, block, result_reg)?;
276 if let Some(catch) = handler {
277 compile_stmts(fc, &catch.body, result_reg)?;
278 }
279 if let Some(fin) = finalizer {
280 compile_stmts(fc, fin, result_reg)?;
281 }
282 }
283
284 StmtKind::Empty | StmtKind::Debugger => {
285 // No-op.
286 }
287
288 StmtKind::With { object, body } => {
289 // Compile `with` as: evaluate object (discard), then run body.
290 // Proper `with` scope requires VM support.
291 let tmp = fc.alloc_reg();
292 compile_expr(fc, object, tmp)?;
293 fc.free_reg(tmp);
294 compile_stmt(fc, body, result_reg)?;
295 }
296
297 StmtKind::Import { .. } => {
298 // Module imports are resolved before execution; no bytecode needed.
299 }
300
301 StmtKind::Export(export) => {
302 compile_export(fc, export, result_reg)?;
303 }
304
305 StmtKind::ClassDecl(class_def) => {
306 compile_class_decl(fc, class_def)?;
307 }
308 }
309 Ok(())
310}
311
312// ── Variable declarations ───────────────────────────────────
313
314fn compile_var_declarator(fc: &mut FunctionCompiler, decl: &VarDeclarator) -> Result<(), JsError> {
315 match &decl.pattern.kind {
316 PatternKind::Identifier(name) => {
317 let reg = fc.define_local(name);
318 if let Some(init) = &decl.init {
319 compile_expr(fc, init, reg)?;
320 } else {
321 fc.builder.emit_reg(Op::LoadUndefined, reg);
322 }
323 }
324 _ => {
325 // Destructuring: evaluate init, then bind patterns.
326 let tmp = fc.alloc_reg();
327 if let Some(init) = &decl.init {
328 compile_expr(fc, init, tmp)?;
329 } else {
330 fc.builder.emit_reg(Op::LoadUndefined, tmp);
331 }
332 compile_destructuring_pattern(fc, &decl.pattern, tmp)?;
333 fc.free_reg(tmp);
334 }
335 }
336 Ok(())
337}
338
339fn compile_destructuring_pattern(
340 fc: &mut FunctionCompiler,
341 pattern: &Pattern,
342 src: Reg,
343) -> Result<(), JsError> {
344 match &pattern.kind {
345 PatternKind::Identifier(name) => {
346 let reg = fc.define_local(name);
347 fc.builder.emit_reg_reg(Op::Move, reg, src);
348 }
349 PatternKind::Object {
350 properties,
351 rest: _,
352 } => {
353 for prop in properties {
354 let key_name = match &prop.key {
355 PropertyKey::Identifier(s) | PropertyKey::String(s) => s.clone(),
356 _ => {
357 return Err(JsError::SyntaxError(
358 "computed destructuring keys not yet supported".into(),
359 ));
360 }
361 };
362 let val_reg = fc.alloc_reg();
363 let name_idx = fc.builder.add_name(&key_name);
364 fc.builder.emit_get_prop_name(val_reg, src, name_idx);
365 compile_destructuring_pattern(fc, &prop.value, val_reg)?;
366 fc.free_reg(val_reg);
367 }
368 }
369 PatternKind::Array { elements, rest: _ } => {
370 for (i, elem) in elements.iter().enumerate() {
371 if let Some(pat) = elem {
372 let idx_reg = fc.alloc_reg();
373 if i <= 127 {
374 fc.builder.emit_load_int8(idx_reg, i as i8);
375 } else {
376 let ci = fc.builder.add_constant(Constant::Number(i as f64));
377 fc.builder.emit_reg_u16(Op::LoadConst, idx_reg, ci);
378 }
379 let val_reg = fc.alloc_reg();
380 fc.builder.emit_reg3(Op::GetProperty, val_reg, src, idx_reg);
381 compile_destructuring_pattern(fc, pat, val_reg)?;
382 fc.free_reg(val_reg);
383 fc.free_reg(idx_reg);
384 }
385 }
386 }
387 PatternKind::Assign { left, right } => {
388 // Default value: if src is undefined, use default.
389 let val_reg = fc.alloc_reg();
390 fc.builder.emit_reg_reg(Op::Move, val_reg, src);
391 // Check if undefined, if so use default.
392 let check_reg = fc.alloc_reg();
393 let undef_reg = fc.alloc_reg();
394 fc.builder.emit_reg(Op::LoadUndefined, undef_reg);
395 fc.builder
396 .emit_reg3(Op::StrictEq, check_reg, val_reg, undef_reg);
397 fc.free_reg(undef_reg);
398 let patch = fc.builder.emit_cond_jump(Op::JumpIfFalse, check_reg);
399 fc.free_reg(check_reg);
400 // Is undefined → evaluate default.
401 compile_expr(fc, right, val_reg)?;
402 fc.builder.patch_jump(patch);
403 compile_destructuring_pattern(fc, left, val_reg)?;
404 fc.free_reg(val_reg);
405 }
406 }
407 Ok(())
408}
409
410// ── Function declarations ───────────────────────────────────
411
412fn compile_function_decl(fc: &mut FunctionCompiler, func_def: &FunctionDef) -> Result<(), JsError> {
413 let name = func_def.id.clone().unwrap_or_default();
414 let inner = compile_function_body(func_def)?;
415 let func_idx = fc.builder.add_function(inner);
416
417 let reg = fc.define_local(&name);
418 fc.builder.emit_reg_u16(Op::CreateClosure, reg, func_idx);
419 Ok(())
420}
421
422fn compile_function_body(func_def: &FunctionDef) -> Result<Function, JsError> {
423 let name = func_def.id.clone().unwrap_or_default();
424 let param_count = func_def.params.len().min(255) as u8;
425 let mut inner = FunctionCompiler::new(name, param_count);
426
427 // Allocate registers for parameters.
428 for p in &func_def.params {
429 if let PatternKind::Identifier(name) = &p.kind {
430 inner.define_local(name);
431 } else {
432 // Destructuring param: allocate a register for the whole param,
433 // then destructure from it.
434 let _ = inner.alloc_reg();
435 }
436 }
437
438 // Result register for the function body.
439 let result_reg = inner.alloc_reg();
440 inner.builder.emit_reg(Op::LoadUndefined, result_reg);
441
442 compile_stmts(&mut inner, &func_def.body, result_reg)?;
443
444 // Implicit return undefined.
445 inner.builder.emit_reg(Op::Return, result_reg);
446 Ok(inner.builder.finish())
447}
448
449// ── Class declarations ──────────────────────────────────────
450
451fn compile_class_decl(fc: &mut FunctionCompiler, class_def: &ClassDef) -> Result<(), JsError> {
452 let name = class_def.id.clone().unwrap_or_default();
453 let reg = fc.define_local(&name);
454
455 // Find constructor or create empty one.
456 let ctor = class_def.body.iter().find(|m| {
457 matches!(
458 &m.kind,
459 ClassMemberKind::Method {
460 kind: MethodKind::Constructor,
461 ..
462 }
463 )
464 });
465
466 if let Some(member) = ctor {
467 if let ClassMemberKind::Method { value, .. } = &member.kind {
468 let inner = compile_function_body(value)?;
469 let func_idx = fc.builder.add_function(inner);
470 fc.builder.emit_reg_u16(Op::CreateClosure, reg, func_idx);
471 }
472 } else {
473 // No constructor: create a minimal function that returns undefined.
474 let mut empty = BytecodeBuilder::new(name.clone(), 0);
475 let r = 0u8;
476 empty.func.register_count = 1;
477 empty.emit_reg(Op::LoadUndefined, r);
478 empty.emit_reg(Op::Return, r);
479 let func_idx = fc.builder.add_function(empty.finish());
480 fc.builder.emit_reg_u16(Op::CreateClosure, reg, func_idx);
481 }
482
483 // Compile methods: set them as properties on the constructor's prototype.
484 // This is simplified — real class compilation needs prototype chain setup.
485 for member in &class_def.body {
486 match &member.kind {
487 ClassMemberKind::Method {
488 key,
489 value,
490 kind,
491 is_static: _,
492 computed: _,
493 } => {
494 if matches!(kind, MethodKind::Constructor) {
495 continue;
496 }
497 let method_name = match key {
498 PropertyKey::Identifier(s) | PropertyKey::String(s) => s.clone(),
499 _ => continue,
500 };
501 let inner = compile_function_body(value)?;
502 let func_idx = fc.builder.add_function(inner);
503 let method_reg = fc.alloc_reg();
504 fc.builder
505 .emit_reg_u16(Op::CreateClosure, method_reg, func_idx);
506 let name_idx = fc.builder.add_name(&method_name);
507 fc.builder.emit_set_prop_name(reg, name_idx, method_reg);
508 fc.free_reg(method_reg);
509 }
510 ClassMemberKind::Property { .. } => {
511 // Class fields are set in constructor; skip here.
512 }
513 }
514 }
515
516 Ok(())
517}
518
519// ── Export ───────────────────────────────────────────────────
520
521fn compile_export(
522 fc: &mut FunctionCompiler,
523 export: &ExportDecl,
524
525 result_reg: Reg,
526) -> Result<(), JsError> {
527 match export {
528 ExportDecl::Declaration(stmt) => {
529 compile_stmt(fc, stmt, result_reg)?;
530 }
531 ExportDecl::Default(expr) => {
532 compile_expr(fc, expr, result_reg)?;
533 }
534 ExportDecl::Named { .. } | ExportDecl::AllFrom(_) => {
535 // Named re-exports are module-level; no bytecode needed.
536 }
537 }
538 Ok(())
539}
540
541// ── Control flow ────────────────────────────────────────────
542
543fn compile_if(
544 fc: &mut FunctionCompiler,
545 test: &Expr,
546 consequent: &Stmt,
547 alternate: Option<&Stmt>,
548
549 result_reg: Reg,
550) -> Result<(), JsError> {
551 let cond = fc.alloc_reg();
552 compile_expr(fc, test, cond)?;
553 let else_patch = fc.builder.emit_cond_jump(Op::JumpIfFalse, cond);
554 fc.free_reg(cond);
555
556 compile_stmt(fc, consequent, result_reg)?;
557
558 if let Some(alt) = alternate {
559 let end_patch = fc.builder.emit_jump(Op::Jump);
560 fc.builder.patch_jump(else_patch);
561 compile_stmt(fc, alt, result_reg)?;
562 fc.builder.patch_jump(end_patch);
563 } else {
564 fc.builder.patch_jump(else_patch);
565 }
566 Ok(())
567}
568
569fn compile_while(
570 fc: &mut FunctionCompiler,
571 test: &Expr,
572 body: &Stmt,
573 label: Option<String>,
574
575 result_reg: Reg,
576) -> Result<(), JsError> {
577 let loop_start = fc.builder.offset();
578
579 let cond = fc.alloc_reg();
580 compile_expr(fc, test, cond)?;
581 let exit_patch = fc.builder.emit_cond_jump(Op::JumpIfFalse, cond);
582 fc.free_reg(cond);
583
584 fc.loop_stack.push(LoopCtx {
585 label,
586 break_patches: Vec::new(),
587 continue_patches: Vec::new(),
588 });
589
590 compile_stmt(fc, body, result_reg)?;
591 fc.builder.emit_jump_to(loop_start);
592 fc.builder.patch_jump(exit_patch);
593
594 let ctx = fc.loop_stack.pop().unwrap();
595 for patch in ctx.break_patches {
596 fc.builder.patch_jump(patch);
597 }
598 // In a while loop, continue jumps back to the condition check (loop_start).
599 for patch in ctx.continue_patches {
600 fc.builder.patch_jump_to(patch, loop_start);
601 }
602 Ok(())
603}
604
605fn compile_do_while(
606 fc: &mut FunctionCompiler,
607 body: &Stmt,
608 test: &Expr,
609 label: Option<String>,
610
611 result_reg: Reg,
612) -> Result<(), JsError> {
613 let loop_start = fc.builder.offset();
614
615 fc.loop_stack.push(LoopCtx {
616 label,
617 break_patches: Vec::new(),
618 continue_patches: Vec::new(),
619 });
620
621 compile_stmt(fc, body, result_reg)?;
622
623 // continue in do-while should jump here (the condition check).
624 let cond_start = fc.builder.offset();
625
626 let cond = fc.alloc_reg();
627 compile_expr(fc, test, cond)?;
628 fc.builder
629 .emit_cond_jump_to(Op::JumpIfTrue, cond, loop_start);
630 fc.free_reg(cond);
631
632 let ctx = fc.loop_stack.pop().unwrap();
633 for patch in ctx.break_patches {
634 fc.builder.patch_jump(patch);
635 }
636 for patch in ctx.continue_patches {
637 fc.builder.patch_jump_to(patch, cond_start);
638 }
639 Ok(())
640}
641
642fn compile_for(
643 fc: &mut FunctionCompiler,
644 init: Option<&ForInit>,
645 test: Option<&Expr>,
646 update: Option<&Expr>,
647 body: &Stmt,
648 label: Option<String>,
649
650 result_reg: Reg,
651) -> Result<(), JsError> {
652 let saved_locals = fc.locals.len();
653 let saved_next = fc.next_reg;
654
655 // Init.
656 if let Some(init) = init {
657 match init {
658 ForInit::VarDecl {
659 kind: _,
660 declarators,
661 } => {
662 for decl in declarators {
663 compile_var_declarator(fc, decl)?;
664 }
665 }
666 ForInit::Expr(expr) => {
667 let tmp = fc.alloc_reg();
668 compile_expr(fc, expr, tmp)?;
669 fc.free_reg(tmp);
670 }
671 }
672 }
673
674 let loop_start = fc.builder.offset();
675
676 // Test.
677 let exit_patch = if let Some(test) = test {
678 let cond = fc.alloc_reg();
679 compile_expr(fc, test, cond)?;
680 let patch = fc.builder.emit_cond_jump(Op::JumpIfFalse, cond);
681 fc.free_reg(cond);
682 Some(patch)
683 } else {
684 None
685 };
686
687 fc.loop_stack.push(LoopCtx {
688 label,
689 break_patches: Vec::new(),
690 continue_patches: Vec::new(),
691 });
692
693 compile_stmt(fc, body, result_reg)?;
694
695 // continue in a for-loop should jump here (the update expression).
696 let continue_target = fc.builder.offset();
697
698 // Update.
699 if let Some(update) = update {
700 let tmp = fc.alloc_reg();
701 compile_expr(fc, update, tmp)?;
702 fc.free_reg(tmp);
703 }
704
705 fc.builder.emit_jump_to(loop_start);
706
707 if let Some(patch) = exit_patch {
708 fc.builder.patch_jump(patch);
709 }
710
711 let ctx = fc.loop_stack.pop().unwrap();
712 for patch in ctx.break_patches {
713 fc.builder.patch_jump(patch);
714 }
715 for patch in ctx.continue_patches {
716 fc.builder.patch_jump_to(patch, continue_target);
717 }
718
719 fc.locals.truncate(saved_locals);
720 fc.next_reg = saved_next;
721 Ok(())
722}
723
724fn compile_switch(
725 fc: &mut FunctionCompiler,
726 discriminant: &Expr,
727 cases: &[SwitchCase],
728
729 result_reg: Reg,
730) -> Result<(), JsError> {
731 let disc_reg = fc.alloc_reg();
732 compile_expr(fc, discriminant, disc_reg)?;
733
734 // Use a loop context for break statements.
735 fc.loop_stack.push(LoopCtx {
736 label: None,
737 break_patches: Vec::new(),
738 continue_patches: Vec::new(),
739 });
740
741 // Phase 1: emit comparison jumps for each non-default case.
742 // Store (case_index, patch_position) for each case with a test.
743 let mut case_jump_patches: Vec<(usize, usize)> = Vec::new();
744 let mut default_index: Option<usize> = None;
745
746 for (i, case) in cases.iter().enumerate() {
747 if let Some(test) = &case.test {
748 let test_reg = fc.alloc_reg();
749 compile_expr(fc, test, test_reg)?;
750 let cmp_reg = fc.alloc_reg();
751 fc.builder
752 .emit_reg3(Op::StrictEq, cmp_reg, disc_reg, test_reg);
753 let patch = fc.builder.emit_cond_jump(Op::JumpIfTrue, cmp_reg);
754 fc.free_reg(cmp_reg);
755 fc.free_reg(test_reg);
756 case_jump_patches.push((i, patch));
757 } else {
758 default_index = Some(i);
759 }
760 }
761
762 // After all comparisons: jump to default body or end.
763 let fallthrough_patch = fc.builder.emit_jump(Op::Jump);
764
765 // Phase 2: emit case bodies in order (fall-through semantics).
766 let mut body_offsets: Vec<(usize, usize)> = Vec::new();
767 for (i, case) in cases.iter().enumerate() {
768 body_offsets.push((i, fc.builder.offset()));
769 compile_stmts(fc, &case.consequent, result_reg)?;
770 }
771
772 let end_offset = fc.builder.offset();
773
774 // Patch case test jumps to their respective body offsets.
775 for (case_idx, patch) in &case_jump_patches {
776 let body_offset = body_offsets
777 .iter()
778 .find(|(i, _)| i == case_idx)
779 .map(|(_, off)| *off)
780 .unwrap();
781 fc.builder.patch_jump_to(*patch, body_offset);
782 }
783
784 // Patch fallthrough: jump to default body if present, otherwise to end.
785 if let Some(def_idx) = default_index {
786 let default_offset = body_offsets
787 .iter()
788 .find(|(i, _)| *i == def_idx)
789 .map(|(_, off)| *off)
790 .unwrap();
791 fc.builder.patch_jump_to(fallthrough_patch, default_offset);
792 } else {
793 fc.builder.patch_jump_to(fallthrough_patch, end_offset);
794 }
795
796 fc.free_reg(disc_reg);
797
798 let ctx = fc.loop_stack.pop().unwrap();
799 for patch in ctx.break_patches {
800 fc.builder.patch_jump(patch);
801 }
802 Ok(())
803}
804
805fn find_loop_ctx(stack: &[LoopCtx], label: Option<&str>) -> Option<usize> {
806 if let Some(label) = label {
807 stack
808 .iter()
809 .rposition(|ctx| ctx.label.as_deref() == Some(label))
810 } else {
811 if stack.is_empty() {
812 None
813 } else {
814 Some(stack.len() - 1)
815 }
816 }
817}
818
819// ── Expressions ─────────────────────────────────────────────
820
821fn compile_expr(fc: &mut FunctionCompiler, expr: &Expr, dst: Reg) -> Result<(), JsError> {
822 match &expr.kind {
823 ExprKind::Number(n) => {
824 // Optimize small integers.
825 let int_val = *n as i64;
826 if int_val as f64 == *n && (-128..=127).contains(&int_val) {
827 fc.builder.emit_load_int8(dst, int_val as i8);
828 } else {
829 let ci = fc.builder.add_constant(Constant::Number(*n));
830 fc.builder.emit_reg_u16(Op::LoadConst, dst, ci);
831 }
832 }
833
834 ExprKind::String(s) => {
835 let ci = fc.builder.add_constant(Constant::String(s.clone()));
836 fc.builder.emit_reg_u16(Op::LoadConst, dst, ci);
837 }
838
839 ExprKind::Bool(true) => {
840 fc.builder.emit_reg(Op::LoadTrue, dst);
841 }
842
843 ExprKind::Bool(false) => {
844 fc.builder.emit_reg(Op::LoadFalse, dst);
845 }
846
847 ExprKind::Null => {
848 fc.builder.emit_reg(Op::LoadNull, dst);
849 }
850
851 ExprKind::Identifier(name) => {
852 if let Some(local_reg) = fc.find_local(name) {
853 if local_reg != dst {
854 fc.builder.emit_reg_reg(Op::Move, dst, local_reg);
855 }
856 } else {
857 // Global lookup.
858 let ni = fc.builder.add_name(name);
859 fc.builder.emit_load_global(dst, ni);
860 }
861 }
862
863 ExprKind::This => {
864 // `this` is loaded as a global named "this" (the VM binds it).
865 let ni = fc.builder.add_name("this");
866 fc.builder.emit_load_global(dst, ni);
867 }
868
869 ExprKind::Binary { op, left, right } => {
870 let lhs = fc.alloc_reg();
871 compile_expr(fc, left, lhs)?;
872 let rhs = fc.alloc_reg();
873 compile_expr(fc, right, rhs)?;
874 let bytecode_op = binary_op_to_opcode(*op);
875 fc.builder.emit_reg3(bytecode_op, dst, lhs, rhs);
876 fc.free_reg(rhs);
877 fc.free_reg(lhs);
878 }
879
880 ExprKind::Unary { op, argument } => {
881 let src = fc.alloc_reg();
882 compile_expr(fc, argument, src)?;
883 match op {
884 UnaryOp::Minus => fc.builder.emit_reg_reg(Op::Neg, dst, src),
885 UnaryOp::Plus => {
886 // Unary + is a no-op at the bytecode level (coerces to number at runtime).
887 fc.builder.emit_reg_reg(Op::Move, dst, src);
888 }
889 UnaryOp::Not => fc.builder.emit_reg_reg(Op::LogicalNot, dst, src),
890 UnaryOp::BitwiseNot => fc.builder.emit_reg_reg(Op::BitNot, dst, src),
891 UnaryOp::Typeof => fc.builder.emit_reg_reg(Op::TypeOf, dst, src),
892 UnaryOp::Void => fc.builder.emit_reg_reg(Op::Void, dst, src),
893 UnaryOp::Delete => {
894 // Simplified: `delete x` on a simple identifier.
895 // Real delete needs the object+key form.
896 fc.builder.emit_reg(Op::LoadTrue, dst);
897 }
898 }
899 fc.free_reg(src);
900 }
901
902 ExprKind::Update {
903 op,
904 argument,
905 prefix,
906 } => {
907 // Get current value.
908 compile_expr(fc, argument, dst)?;
909
910 let one = fc.alloc_reg();
911 fc.builder.emit_load_int8(one, 1);
912
913 if *prefix {
914 // ++x / --x: modify first, return modified.
915 match op {
916 UpdateOp::Increment => fc.builder.emit_reg3(Op::Add, dst, dst, one),
917 UpdateOp::Decrement => fc.builder.emit_reg3(Op::Sub, dst, dst, one),
918 }
919 // Store back.
920 compile_store(fc, argument, dst)?;
921 } else {
922 // x++ / x--: return original, then modify.
923 let tmp = fc.alloc_reg();
924 fc.builder.emit_reg_reg(Op::Move, tmp, dst);
925 match op {
926 UpdateOp::Increment => fc.builder.emit_reg3(Op::Add, tmp, tmp, one),
927 UpdateOp::Decrement => fc.builder.emit_reg3(Op::Sub, tmp, tmp, one),
928 }
929 compile_store(fc, argument, tmp)?;
930 fc.free_reg(tmp);
931 }
932 fc.free_reg(one);
933 }
934
935 ExprKind::Logical { op, left, right } => {
936 compile_expr(fc, left, dst)?;
937 match op {
938 LogicalOp::And => {
939 // Short-circuit: if falsy, skip right.
940 let skip = fc.builder.emit_cond_jump(Op::JumpIfFalse, dst);
941 compile_expr(fc, right, dst)?;
942 fc.builder.patch_jump(skip);
943 }
944 LogicalOp::Or => {
945 let skip = fc.builder.emit_cond_jump(Op::JumpIfTrue, dst);
946 compile_expr(fc, right, dst)?;
947 fc.builder.patch_jump(skip);
948 }
949 LogicalOp::Nullish => {
950 let skip = fc.builder.emit_cond_jump(Op::JumpIfNullish, dst);
951 // If NOT nullish, skip the right side. Wait — JumpIfNullish
952 // should mean "jump if nullish" so we want: evaluate left,
953 // if NOT nullish skip right.
954 // Let's invert: evaluate left, check if nullish → evaluate right.
955 // We need the jump to skip the "evaluate right" if NOT nullish.
956 // Since JumpIfNullish jumps when nullish, we need the inverse.
957 // Instead: use a two-step approach.
958 //
959 // Actually, rethink: for `a ?? b`:
960 // 1. evaluate a → dst
961 // 2. if dst is NOT null/undefined, jump to end
962 // 3. evaluate b → dst
963 // end:
964 // JumpIfNullish jumps when IS nullish. So we want jump when NOT nullish.
965 // Let's just use a "not nullish" check.
966 // For now: negate and use JumpIfFalse.
967 // Actually simpler: skip right when not nullish.
968 // JumpIfNullish jumps WHEN nullish. We want to jump over right when NOT nullish.
969 // So:
970 // evaluate a → dst
971 // JumpIfNullish dst → evaluate_right
972 // Jump → end
973 // evaluate_right: evaluate b → dst
974 // end:
975 // But we already emitted JumpIfNullish. Let's fix this.
976 // The JumpIfNullish we emitted jumps to "after patch", which is where
977 // we'll put the right-side code. We need another jump to skip right.
978 let end_patch = fc.builder.emit_jump(Op::Jump);
979 fc.builder.patch_jump(skip); // nullish → evaluate right
980 compile_expr(fc, right, dst)?;
981 fc.builder.patch_jump(end_patch);
982 }
983 }
984 }
985
986 ExprKind::Assignment { op, left, right } => {
987 if *op == AssignOp::Assign {
988 compile_expr(fc, right, dst)?;
989 compile_store(fc, left, dst)?;
990 } else {
991 // Compound assignment: load current, operate, store.
992 compile_expr(fc, left, dst)?;
993 let rhs = fc.alloc_reg();
994 compile_expr(fc, right, rhs)?;
995 let arith_op = compound_assign_op(*op);
996 fc.builder.emit_reg3(arith_op, dst, dst, rhs);
997 fc.free_reg(rhs);
998 compile_store(fc, left, dst)?;
999 }
1000 }
1001
1002 ExprKind::Conditional {
1003 test,
1004 consequent,
1005 alternate,
1006 } => {
1007 let cond = fc.alloc_reg();
1008 compile_expr(fc, test, cond)?;
1009 let else_patch = fc.builder.emit_cond_jump(Op::JumpIfFalse, cond);
1010 fc.free_reg(cond);
1011 compile_expr(fc, consequent, dst)?;
1012 let end_patch = fc.builder.emit_jump(Op::Jump);
1013 fc.builder.patch_jump(else_patch);
1014 compile_expr(fc, alternate, dst)?;
1015 fc.builder.patch_jump(end_patch);
1016 }
1017
1018 ExprKind::Call { callee, arguments } => {
1019 let func_reg = fc.alloc_reg();
1020 compile_expr(fc, callee, func_reg)?;
1021
1022 let args_start = fc.next_reg;
1023 let arg_count = arguments.len().min(255) as u8;
1024 for arg in arguments {
1025 let arg_reg = fc.alloc_reg();
1026 compile_expr(fc, arg, arg_reg)?;
1027 }
1028
1029 fc.builder.emit_call(dst, func_reg, args_start, arg_count);
1030
1031 // Free argument registers (in reverse).
1032 for _ in 0..arg_count {
1033 fc.next_reg -= 1;
1034 }
1035 fc.free_reg(func_reg);
1036 }
1037
1038 ExprKind::New { callee, arguments } => {
1039 // For now, compile like a regular call. The VM will differentiate
1040 // based on the `New` vs `Call` distinction (TODO: add NewCall opcode).
1041 let func_reg = fc.alloc_reg();
1042 compile_expr(fc, callee, func_reg)?;
1043
1044 let args_start = fc.next_reg;
1045 let arg_count = arguments.len().min(255) as u8;
1046 for arg in arguments {
1047 let arg_reg = fc.alloc_reg();
1048 compile_expr(fc, arg, arg_reg)?;
1049 }
1050
1051 fc.builder.emit_call(dst, func_reg, args_start, arg_count);
1052
1053 for _ in 0..arg_count {
1054 fc.next_reg -= 1;
1055 }
1056 fc.free_reg(func_reg);
1057 }
1058
1059 ExprKind::Member {
1060 object,
1061 property,
1062 computed,
1063 } => {
1064 let obj_reg = fc.alloc_reg();
1065 compile_expr(fc, object, obj_reg)?;
1066
1067 if !computed {
1068 // Static member: obj.prop → GetPropertyByName.
1069 if let ExprKind::Identifier(name) = &property.kind {
1070 let ni = fc.builder.add_name(name);
1071 fc.builder.emit_get_prop_name(dst, obj_reg, ni);
1072 } else {
1073 let key_reg = fc.alloc_reg();
1074 compile_expr(fc, property, key_reg)?;
1075 fc.builder.emit_reg3(Op::GetProperty, dst, obj_reg, key_reg);
1076 fc.free_reg(key_reg);
1077 }
1078 } else {
1079 // Computed member: obj[expr].
1080 let key_reg = fc.alloc_reg();
1081 compile_expr(fc, property, key_reg)?;
1082 fc.builder.emit_reg3(Op::GetProperty, dst, obj_reg, key_reg);
1083 fc.free_reg(key_reg);
1084 }
1085 fc.free_reg(obj_reg);
1086 }
1087
1088 ExprKind::Array(elements) => {
1089 fc.builder.emit_reg(Op::CreateArray, dst);
1090 for (i, elem) in elements.iter().enumerate() {
1091 if let Some(el) = elem {
1092 let val_reg = fc.alloc_reg();
1093 match el {
1094 ArrayElement::Expr(e) => compile_expr(fc, e, val_reg)?,
1095 ArrayElement::Spread(e) => {
1096 // Spread in array: simplified, just compile the expression.
1097 compile_expr(fc, e, val_reg)?;
1098 }
1099 }
1100 let idx_reg = fc.alloc_reg();
1101 if i <= 127 {
1102 fc.builder.emit_load_int8(idx_reg, i as i8);
1103 } else {
1104 let ci = fc.builder.add_constant(Constant::Number(i as f64));
1105 fc.builder.emit_reg_u16(Op::LoadConst, idx_reg, ci);
1106 }
1107 fc.builder.emit_reg3(Op::SetProperty, dst, idx_reg, val_reg);
1108 fc.free_reg(idx_reg);
1109 fc.free_reg(val_reg);
1110 }
1111 }
1112 }
1113
1114 ExprKind::Object(properties) => {
1115 fc.builder.emit_reg(Op::CreateObject, dst);
1116 for prop in properties {
1117 let val_reg = fc.alloc_reg();
1118 if let Some(value) = &prop.value {
1119 compile_expr(fc, value, val_reg)?;
1120 } else {
1121 // Shorthand: `{ x }` means `{ x: x }`.
1122 if let PropertyKey::Identifier(name) = &prop.key {
1123 if let Some(local) = fc.find_local(name) {
1124 fc.builder.emit_reg_reg(Op::Move, val_reg, local);
1125 } else {
1126 let ni = fc.builder.add_name(name);
1127 fc.builder.emit_load_global(val_reg, ni);
1128 }
1129 } else {
1130 fc.builder.emit_reg(Op::LoadUndefined, val_reg);
1131 }
1132 }
1133
1134 match &prop.key {
1135 PropertyKey::Identifier(name) | PropertyKey::String(name) => {
1136 let ni = fc.builder.add_name(name);
1137 fc.builder.emit_set_prop_name(dst, ni, val_reg);
1138 }
1139 PropertyKey::Number(n) => {
1140 let key_reg = fc.alloc_reg();
1141 let ci = fc.builder.add_constant(Constant::Number(*n));
1142 fc.builder.emit_reg_u16(Op::LoadConst, key_reg, ci);
1143 fc.builder.emit_reg3(Op::SetProperty, dst, key_reg, val_reg);
1144 fc.free_reg(key_reg);
1145 }
1146 PropertyKey::Computed(expr) => {
1147 let key_reg = fc.alloc_reg();
1148 compile_expr(fc, expr, key_reg)?;
1149 fc.builder.emit_reg3(Op::SetProperty, dst, key_reg, val_reg);
1150 fc.free_reg(key_reg);
1151 }
1152 }
1153 fc.free_reg(val_reg);
1154 }
1155 }
1156
1157 ExprKind::Function(func_def) => {
1158 let inner = compile_function_body(func_def)?;
1159 let func_idx = fc.builder.add_function(inner);
1160 fc.builder.emit_reg_u16(Op::CreateClosure, dst, func_idx);
1161 }
1162
1163 ExprKind::Arrow {
1164 params,
1165 body,
1166 is_async: _,
1167 } => {
1168 let param_count = params.len().min(255) as u8;
1169 let mut inner = FunctionCompiler::new("<arrow>".into(), param_count);
1170 for p in params {
1171 if let PatternKind::Identifier(name) = &p.kind {
1172 inner.define_local(name);
1173 } else {
1174 let _ = inner.alloc_reg();
1175 }
1176 }
1177 let result = inner.alloc_reg();
1178 match body {
1179 ArrowBody::Expr(e) => {
1180 compile_expr(&mut inner, e, result)?;
1181 }
1182 ArrowBody::Block(stmts) => {
1183 inner.builder.emit_reg(Op::LoadUndefined, result);
1184 compile_stmts(&mut inner, stmts, result)?;
1185 }
1186 }
1187 inner.builder.emit_reg(Op::Return, result);
1188 let inner_func = inner.builder.finish();
1189 let func_idx = fc.builder.add_function(inner_func);
1190 fc.builder.emit_reg_u16(Op::CreateClosure, dst, func_idx);
1191 }
1192
1193 ExprKind::Class(class_def) => {
1194 // Class expression: compile like class decl but into dst.
1195 let name = class_def.id.clone().unwrap_or_default();
1196 // Find constructor.
1197 let ctor = class_def.body.iter().find(|m| {
1198 matches!(
1199 &m.kind,
1200 ClassMemberKind::Method {
1201 kind: MethodKind::Constructor,
1202 ..
1203 }
1204 )
1205 });
1206 if let Some(member) = ctor {
1207 if let ClassMemberKind::Method { value, .. } = &member.kind {
1208 let inner = compile_function_body(value)?;
1209 let func_idx = fc.builder.add_function(inner);
1210 fc.builder.emit_reg_u16(Op::CreateClosure, dst, func_idx);
1211 }
1212 } else {
1213 let mut empty = BytecodeBuilder::new(name, 0);
1214 let r = 0u8;
1215 empty.func.register_count = 1;
1216 empty.emit_reg(Op::LoadUndefined, r);
1217 empty.emit_reg(Op::Return, r);
1218 let func_idx = fc.builder.add_function(empty.finish());
1219 fc.builder.emit_reg_u16(Op::CreateClosure, dst, func_idx);
1220 }
1221
1222 // Compile methods as properties on the constructor.
1223 for member in &class_def.body {
1224 match &member.kind {
1225 ClassMemberKind::Method {
1226 key,
1227 value,
1228 kind,
1229 is_static: _,
1230 computed: _,
1231 } => {
1232 if matches!(kind, MethodKind::Constructor) {
1233 continue;
1234 }
1235 let method_name = match key {
1236 PropertyKey::Identifier(s) | PropertyKey::String(s) => s.clone(),
1237 _ => continue,
1238 };
1239 let inner = compile_function_body(value)?;
1240 let func_idx = fc.builder.add_function(inner);
1241 let method_reg = fc.alloc_reg();
1242 fc.builder
1243 .emit_reg_u16(Op::CreateClosure, method_reg, func_idx);
1244 let name_idx = fc.builder.add_name(&method_name);
1245 fc.builder.emit_set_prop_name(dst, name_idx, method_reg);
1246 fc.free_reg(method_reg);
1247 }
1248 ClassMemberKind::Property { .. } => {}
1249 }
1250 }
1251 }
1252
1253 ExprKind::Sequence(exprs) => {
1254 for e in exprs {
1255 compile_expr(fc, e, dst)?;
1256 }
1257 }
1258
1259 ExprKind::Spread(inner) => {
1260 compile_expr(fc, inner, dst)?;
1261 }
1262
1263 ExprKind::TemplateLiteral {
1264 quasis,
1265 expressions,
1266 } => {
1267 // Compile template literal as string concatenation.
1268 if quasis.len() == 1 && expressions.is_empty() {
1269 let ci = fc.builder.add_constant(Constant::String(quasis[0].clone()));
1270 fc.builder.emit_reg_u16(Op::LoadConst, dst, ci);
1271 } else {
1272 // Start with first quasi.
1273 let ci = fc.builder.add_constant(Constant::String(quasis[0].clone()));
1274 fc.builder.emit_reg_u16(Op::LoadConst, dst, ci);
1275 for (i, expr) in expressions.iter().enumerate() {
1276 let tmp = fc.alloc_reg();
1277 compile_expr(fc, expr, tmp)?;
1278 fc.builder.emit_reg3(Op::Add, dst, dst, tmp);
1279 fc.free_reg(tmp);
1280 if i + 1 < quasis.len() {
1281 let qi = fc
1282 .builder
1283 .add_constant(Constant::String(quasis[i + 1].clone()));
1284 let tmp2 = fc.alloc_reg();
1285 fc.builder.emit_reg_u16(Op::LoadConst, tmp2, qi);
1286 fc.builder.emit_reg3(Op::Add, dst, dst, tmp2);
1287 fc.free_reg(tmp2);
1288 }
1289 }
1290 }
1291 }
1292
1293 ExprKind::TaggedTemplate { tag, quasi } => {
1294 // Simplified: call tag with the template as argument.
1295 let func_reg = fc.alloc_reg();
1296 compile_expr(fc, tag, func_reg)?;
1297 let arg_reg = fc.alloc_reg();
1298 compile_expr(fc, quasi, arg_reg)?;
1299 fc.builder.emit_call(dst, func_reg, arg_reg, 1);
1300 fc.free_reg(arg_reg);
1301 fc.free_reg(func_reg);
1302 }
1303
1304 ExprKind::Yield {
1305 argument,
1306 delegate: _,
1307 } => {
1308 // Yield is a VM-level operation; for now compile the argument.
1309 if let Some(arg) = argument {
1310 compile_expr(fc, arg, dst)?;
1311 } else {
1312 fc.builder.emit_reg(Op::LoadUndefined, dst);
1313 }
1314 }
1315
1316 ExprKind::Await(inner) => {
1317 // Await is a VM-level operation; compile the argument.
1318 compile_expr(fc, inner, dst)?;
1319 }
1320
1321 ExprKind::RegExp { .. } => {
1322 // RegExp literals are created at runtime by the VM.
1323 fc.builder.emit_reg(Op::LoadUndefined, dst);
1324 }
1325
1326 ExprKind::OptionalChain { base } => {
1327 compile_expr(fc, base, dst)?;
1328 }
1329 }
1330 Ok(())
1331}
1332
1333/// Compile a store operation (assignment target).
1334fn compile_store(fc: &mut FunctionCompiler, target: &Expr, src: Reg) -> Result<(), JsError> {
1335 match &target.kind {
1336 ExprKind::Identifier(name) => {
1337 if let Some(local) = fc.find_local(name) {
1338 if local != src {
1339 fc.builder.emit_reg_reg(Op::Move, local, src);
1340 }
1341 } else {
1342 let ni = fc.builder.add_name(name);
1343 fc.builder.emit_store_global(ni, src);
1344 }
1345 }
1346 ExprKind::Member {
1347 object,
1348 property,
1349 computed,
1350 } => {
1351 let obj_reg = fc.alloc_reg();
1352 compile_expr(fc, object, obj_reg)?;
1353 if !computed {
1354 if let ExprKind::Identifier(name) = &property.kind {
1355 let ni = fc.builder.add_name(name);
1356 fc.builder.emit_set_prop_name(obj_reg, ni, src);
1357 } else {
1358 let key_reg = fc.alloc_reg();
1359 compile_expr(fc, property, key_reg)?;
1360 fc.builder.emit_reg3(Op::SetProperty, obj_reg, key_reg, src);
1361 fc.free_reg(key_reg);
1362 }
1363 } else {
1364 let key_reg = fc.alloc_reg();
1365 compile_expr(fc, property, key_reg)?;
1366 fc.builder.emit_reg3(Op::SetProperty, obj_reg, key_reg, src);
1367 fc.free_reg(key_reg);
1368 }
1369 fc.free_reg(obj_reg);
1370 }
1371 _ => {
1372 // Other assignment targets (destructuring) not handled here.
1373 }
1374 }
1375 Ok(())
1376}
1377
1378fn binary_op_to_opcode(op: BinaryOp) -> Op {
1379 match op {
1380 BinaryOp::Add => Op::Add,
1381 BinaryOp::Sub => Op::Sub,
1382 BinaryOp::Mul => Op::Mul,
1383 BinaryOp::Div => Op::Div,
1384 BinaryOp::Rem => Op::Rem,
1385 BinaryOp::Exp => Op::Exp,
1386 BinaryOp::Eq => Op::Eq,
1387 BinaryOp::Ne => Op::NotEq,
1388 BinaryOp::StrictEq => Op::StrictEq,
1389 BinaryOp::StrictNe => Op::StrictNotEq,
1390 BinaryOp::Lt => Op::LessThan,
1391 BinaryOp::Le => Op::LessEq,
1392 BinaryOp::Gt => Op::GreaterThan,
1393 BinaryOp::Ge => Op::GreaterEq,
1394 BinaryOp::Shl => Op::ShiftLeft,
1395 BinaryOp::Shr => Op::ShiftRight,
1396 BinaryOp::Ushr => Op::UShiftRight,
1397 BinaryOp::BitAnd => Op::BitAnd,
1398 BinaryOp::BitOr => Op::BitOr,
1399 BinaryOp::BitXor => Op::BitXor,
1400 BinaryOp::In => Op::In,
1401 BinaryOp::Instanceof => Op::InstanceOf,
1402 }
1403}
1404
1405fn compound_assign_op(op: AssignOp) -> Op {
1406 match op {
1407 AssignOp::AddAssign => Op::Add,
1408 AssignOp::SubAssign => Op::Sub,
1409 AssignOp::MulAssign => Op::Mul,
1410 AssignOp::DivAssign => Op::Div,
1411 AssignOp::RemAssign => Op::Rem,
1412 AssignOp::ExpAssign => Op::Exp,
1413 AssignOp::ShlAssign => Op::ShiftLeft,
1414 AssignOp::ShrAssign => Op::ShiftRight,
1415 AssignOp::UshrAssign => Op::UShiftRight,
1416 AssignOp::BitAndAssign => Op::BitAnd,
1417 AssignOp::BitOrAssign => Op::BitOr,
1418 AssignOp::BitXorAssign => Op::BitXor,
1419 AssignOp::AndAssign => Op::BitAnd, // logical AND assignment uses short-circuit; simplified here
1420 AssignOp::OrAssign => Op::BitOr, // likewise
1421 AssignOp::NullishAssign => Op::Move, // simplified
1422 AssignOp::Assign => unreachable!(),
1423 }
1424}
1425
1426#[cfg(test)]
1427mod tests {
1428 use super::*;
1429 use crate::parser::Parser;
1430
1431 /// Helper: parse and compile source, return the top-level function.
1432 fn compile_src(src: &str) -> Function {
1433 let program = Parser::parse(src).expect("parse failed");
1434 compile(&program).expect("compile failed")
1435 }
1436
1437 #[test]
1438 fn test_compile_number_literal() {
1439 let f = compile_src("42;");
1440 let dis = f.disassemble();
1441 assert!(dis.contains("LoadInt8 r0, 42"), "got:\n{dis}");
1442 assert!(dis.contains("Return r0"));
1443 }
1444
1445 #[test]
1446 fn test_compile_large_number() {
1447 let f = compile_src("3.14;");
1448 let dis = f.disassemble();
1449 assert!(dis.contains("LoadConst r0, #0"), "got:\n{dis}");
1450 assert!(
1451 f.constants.contains(&Constant::Number(3.14)),
1452 "constants: {:?}",
1453 f.constants
1454 );
1455 }
1456
1457 #[test]
1458 fn test_compile_string() {
1459 let f = compile_src("\"hello\";");
1460 let dis = f.disassemble();
1461 assert!(dis.contains("LoadConst r0, #0"));
1462 assert!(f.constants.contains(&Constant::String("hello".into())));
1463 }
1464
1465 #[test]
1466 fn test_compile_bool_null() {
1467 let f = compile_src("true; false; null;");
1468 let dis = f.disassemble();
1469 assert!(dis.contains("LoadTrue r0"));
1470 assert!(dis.contains("LoadFalse r0"));
1471 assert!(dis.contains("LoadNull r0"));
1472 }
1473
1474 #[test]
1475 fn test_compile_binary_arithmetic() {
1476 let f = compile_src("1 + 2;");
1477 let dis = f.disassemble();
1478 assert!(dis.contains("Add r0, r1, r2"), "got:\n{dis}");
1479 }
1480
1481 #[test]
1482 fn test_compile_nested_arithmetic() {
1483 let f = compile_src("(1 + 2) * 3;");
1484 let dis = f.disassemble();
1485 assert!(dis.contains("Add"), "got:\n{dis}");
1486 assert!(dis.contains("Mul"), "got:\n{dis}");
1487 }
1488
1489 #[test]
1490 fn test_compile_var_decl() {
1491 let f = compile_src("var x = 10; x;");
1492 let dis = f.disassemble();
1493 // x should get a register, then be loaded from that register.
1494 assert!(dis.contains("LoadInt8"), "got:\n{dis}");
1495 assert!(
1496 dis.contains("Move") || dis.contains("LoadInt8"),
1497 "got:\n{dis}"
1498 );
1499 }
1500
1501 #[test]
1502 fn test_compile_let_const() {
1503 let f = compile_src("let a = 1; const b = 2; a + b;");
1504 let dis = f.disassemble();
1505 assert!(dis.contains("Add"), "got:\n{dis}");
1506 }
1507
1508 #[test]
1509 fn test_compile_if_else() {
1510 let f = compile_src("if (true) { 1; } else { 2; }");
1511 let dis = f.disassemble();
1512 assert!(dis.contains("JumpIfFalse"), "got:\n{dis}");
1513 assert!(dis.contains("Jump"), "got:\n{dis}");
1514 }
1515
1516 #[test]
1517 fn test_compile_while() {
1518 let f = compile_src("var i = 0; while (i < 10) { i = i + 1; }");
1519 let dis = f.disassemble();
1520 assert!(dis.contains("LessThan"), "got:\n{dis}");
1521 assert!(dis.contains("JumpIfFalse"), "got:\n{dis}");
1522 assert!(
1523 dis.contains("Jump"),
1524 "backward jump should be present: {dis}"
1525 );
1526 }
1527
1528 #[test]
1529 fn test_compile_do_while() {
1530 let f = compile_src("var i = 0; do { i = i + 1; } while (i < 5);");
1531 let dis = f.disassemble();
1532 assert!(dis.contains("JumpIfTrue"), "got:\n{dis}");
1533 }
1534
1535 #[test]
1536 fn test_compile_for_loop() {
1537 let f = compile_src("for (var i = 0; i < 10; i = i + 1) { i; }");
1538 let dis = f.disassemble();
1539 assert!(dis.contains("LessThan"), "got:\n{dis}");
1540 assert!(dis.contains("JumpIfFalse"), "got:\n{dis}");
1541 }
1542
1543 #[test]
1544 fn test_compile_function_decl() {
1545 let f = compile_src("function add(a, b) { return a + b; }");
1546 let dis = f.disassemble();
1547 assert!(dis.contains("CreateClosure"), "got:\n{dis}");
1548 assert!(!f.functions.is_empty(), "should have nested function");
1549 let inner = &f.functions[0];
1550 assert_eq!(inner.name, "add");
1551 assert_eq!(inner.param_count, 2);
1552 let inner_dis = inner.disassemble();
1553 assert!(inner_dis.contains("Add"), "inner:\n{inner_dis}");
1554 assert!(inner_dis.contains("Return"), "inner:\n{inner_dis}");
1555 }
1556
1557 #[test]
1558 fn test_compile_function_call() {
1559 let f = compile_src("function f() { return 42; } f();");
1560 let dis = f.disassemble();
1561 assert!(dis.contains("Call"), "got:\n{dis}");
1562 }
1563
1564 #[test]
1565 fn test_compile_arrow_function() {
1566 let f = compile_src("var add = (a, b) => a + b;");
1567 let dis = f.disassemble();
1568 assert!(dis.contains("CreateClosure"), "got:\n{dis}");
1569 let inner = &f.functions[0];
1570 assert_eq!(inner.param_count, 2);
1571 }
1572
1573 #[test]
1574 fn test_compile_assignment() {
1575 let f = compile_src("var x = 1; x = x + 2;");
1576 let dis = f.disassemble();
1577 assert!(dis.contains("Add"), "got:\n{dis}");
1578 assert!(
1579 dis.contains("Move"),
1580 "assignment should produce Move:\n{dis}"
1581 );
1582 }
1583
1584 #[test]
1585 fn test_compile_compound_assignment() {
1586 let f = compile_src("var x = 10; x += 5;");
1587 let dis = f.disassemble();
1588 assert!(dis.contains("Add"), "got:\n{dis}");
1589 }
1590
1591 #[test]
1592 fn test_compile_member_access() {
1593 let f = compile_src("var obj = {}; obj.x;");
1594 let dis = f.disassemble();
1595 assert!(dis.contains("CreateObject"), "got:\n{dis}");
1596 assert!(dis.contains("GetPropertyByName"), "got:\n{dis}");
1597 }
1598
1599 #[test]
1600 fn test_compile_computed_member() {
1601 let f = compile_src("var arr = []; arr[0];");
1602 let dis = f.disassemble();
1603 assert!(dis.contains("GetProperty"), "got:\n{dis}");
1604 }
1605
1606 #[test]
1607 fn test_compile_object_literal() {
1608 let f = compile_src("var obj = { a: 1, b: 2 };");
1609 let dis = f.disassemble();
1610 assert!(dis.contains("CreateObject"), "got:\n{dis}");
1611 assert!(dis.contains("SetPropertyByName"), "got:\n{dis}");
1612 }
1613
1614 #[test]
1615 fn test_compile_array_literal() {
1616 let f = compile_src("[1, 2, 3];");
1617 let dis = f.disassemble();
1618 assert!(dis.contains("CreateArray"), "got:\n{dis}");
1619 assert!(dis.contains("SetProperty"), "got:\n{dis}");
1620 }
1621
1622 #[test]
1623 fn test_compile_conditional() {
1624 let f = compile_src("true ? 1 : 2;");
1625 let dis = f.disassemble();
1626 assert!(dis.contains("JumpIfFalse"), "got:\n{dis}");
1627 }
1628
1629 #[test]
1630 fn test_compile_logical_and() {
1631 let f = compile_src("true && false;");
1632 let dis = f.disassemble();
1633 assert!(dis.contains("JumpIfFalse"), "short-circuit:\n{dis}");
1634 }
1635
1636 #[test]
1637 fn test_compile_logical_or() {
1638 let f = compile_src("false || true;");
1639 let dis = f.disassemble();
1640 assert!(dis.contains("JumpIfTrue"), "short-circuit:\n{dis}");
1641 }
1642
1643 #[test]
1644 fn test_compile_typeof() {
1645 let f = compile_src("typeof 42;");
1646 let dis = f.disassemble();
1647 assert!(dis.contains("TypeOf"), "got:\n{dis}");
1648 }
1649
1650 #[test]
1651 fn test_compile_unary_minus() {
1652 let f = compile_src("-42;");
1653 let dis = f.disassemble();
1654 assert!(dis.contains("Neg"), "got:\n{dis}");
1655 }
1656
1657 #[test]
1658 fn test_compile_not() {
1659 let f = compile_src("!true;");
1660 let dis = f.disassemble();
1661 assert!(dis.contains("LogicalNot"), "got:\n{dis}");
1662 }
1663
1664 #[test]
1665 fn test_compile_return() {
1666 let f = compile_src("function f() { return 42; }");
1667 let inner = &f.functions[0];
1668 let dis = inner.disassemble();
1669 assert!(dis.contains("Return"), "got:\n{dis}");
1670 }
1671
1672 #[test]
1673 fn test_compile_empty_return() {
1674 let f = compile_src("function f() { return; }");
1675 let inner = &f.functions[0];
1676 let dis = inner.disassemble();
1677 assert!(dis.contains("LoadUndefined"), "got:\n{dis}");
1678 assert!(dis.contains("Return"), "got:\n{dis}");
1679 }
1680
1681 #[test]
1682 fn test_compile_throw() {
1683 let f = compile_src("function f() { throw 42; }");
1684 let inner = &f.functions[0];
1685 let dis = inner.disassemble();
1686 assert!(dis.contains("Throw"), "got:\n{dis}");
1687 }
1688
1689 #[test]
1690 fn test_compile_this() {
1691 let f = compile_src("this;");
1692 let dis = f.disassemble();
1693 assert!(dis.contains("LoadGlobal"), "got:\n{dis}");
1694 assert!(f.names.contains(&"this".to_string()));
1695 }
1696
1697 #[test]
1698 fn test_compile_global_var() {
1699 let f = compile_src("console;");
1700 let dis = f.disassemble();
1701 assert!(dis.contains("LoadGlobal"), "got:\n{dis}");
1702 assert!(f.names.contains(&"console".to_string()));
1703 }
1704
1705 #[test]
1706 fn test_compile_template_literal() {
1707 let f = compile_src("`hello`;");
1708 assert!(
1709 f.constants.contains(&Constant::String("hello".into())),
1710 "constants: {:?}",
1711 f.constants
1712 );
1713 }
1714
1715 #[test]
1716 fn test_compile_switch() {
1717 let f = compile_src("switch (1) { case 1: 42; break; case 2: 99; break; }");
1718 let dis = f.disassemble();
1719 assert!(dis.contains("StrictEq"), "got:\n{dis}");
1720 }
1721
1722 #[test]
1723 fn test_compile_class() {
1724 let f = compile_src("class Foo { constructor() {} greet() { return 1; } }");
1725 let dis = f.disassemble();
1726 assert!(dis.contains("CreateClosure"), "got:\n{dis}");
1727 }
1728
1729 #[test]
1730 fn test_compile_update_prefix() {
1731 let f = compile_src("var x = 0; ++x;");
1732 let dis = f.disassemble();
1733 assert!(dis.contains("Add"), "got:\n{dis}");
1734 }
1735
1736 #[test]
1737 fn test_compile_comparison() {
1738 let f = compile_src("1 === 2;");
1739 let dis = f.disassemble();
1740 assert!(dis.contains("StrictEq"), "got:\n{dis}");
1741 }
1742
1743 #[test]
1744 fn test_compile_bitwise() {
1745 let f = compile_src("1 & 2;");
1746 let dis = f.disassemble();
1747 assert!(dis.contains("BitAnd"), "got:\n{dis}");
1748 }
1749
1750 #[test]
1751 fn test_compile_void() {
1752 let f = compile_src("void 0;");
1753 let dis = f.disassemble();
1754 assert!(dis.contains("Void"), "got:\n{dis}");
1755 }
1756
1757 #[test]
1758 fn test_disassembler_output_format() {
1759 let f = compile_src("var x = 42; x + 1;");
1760 let dis = f.disassemble();
1761 // Should contain function header.
1762 assert!(dis.contains("function <main>"));
1763 // Should contain code section.
1764 assert!(dis.contains("code:"));
1765 // Should have hex offsets.
1766 assert!(dis.contains("0000"));
1767 }
1768
1769 #[test]
1770 fn test_register_allocation_is_minimal() {
1771 // `var a = 1; var b = 2; a + b;` should use few registers.
1772 let f = compile_src("var a = 1; var b = 2; a + b;");
1773 // r0 = result, r1 = a, r2 = b, r3/r4 = temps for addition
1774 assert!(
1775 f.register_count <= 6,
1776 "too many registers: {}",
1777 f.register_count
1778 );
1779 }
1780
1781 #[test]
1782 fn test_nested_function_closure() {
1783 let f = compile_src("function outer() { function inner() { return 1; } return inner; }");
1784 assert_eq!(f.functions.len(), 1);
1785 let outer = &f.functions[0];
1786 assert_eq!(outer.name, "outer");
1787 assert_eq!(outer.functions.len(), 1);
1788 let inner = &outer.functions[0];
1789 assert_eq!(inner.name, "inner");
1790 }
1791
1792 #[test]
1793 fn test_for_with_no_parts() {
1794 // `for (;;) { break; }` — infinite loop with immediate break.
1795 let f = compile_src("for (;;) { break; }");
1796 let dis = f.disassemble();
1797 assert!(dis.contains("Jump"), "got:\n{dis}");
1798 }
1799
1800 #[test]
1801 fn test_for_continue_targets_update() {
1802 // `continue` in a for-loop must jump to the update expression, not back
1803 // to the condition check. Verify the continue jump goes to the Add (i + 1)
1804 // rather than to the LessThan condition.
1805 let f = compile_src("for (var i = 0; i < 10; i = i + 1) { continue; }");
1806 let dis = f.disassemble();
1807 // The for-loop should contain: LessThan (test), JumpIfFalse (exit),
1808 // Jump (continue), Add (update), Jump (back to test).
1809 assert!(dis.contains("LessThan"), "missing test: {dis}");
1810 assert!(dis.contains("Add"), "missing update: {dis}");
1811 // There should be at least 2 Jump instructions (continue + back-edge).
1812 let jump_count = dis.matches("Jump ").count();
1813 assert!(
1814 jump_count >= 2,
1815 "expected >= 2 jumps for continue + back-edge, got {jump_count}: {dis}"
1816 );
1817 }
1818
1819 #[test]
1820 fn test_do_while_continue_targets_condition() {
1821 // `continue` in do-while must jump to the condition, not the body start.
1822 let f = compile_src("var i = 0; do { i = i + 1; continue; } while (i < 5);");
1823 let dis = f.disassemble();
1824 assert!(dis.contains("LessThan"), "missing condition: {dis}");
1825 assert!(dis.contains("JumpIfTrue"), "missing back-edge: {dis}");
1826 }
1827
1828 #[test]
1829 fn test_switch_default_case() {
1830 // Default case must not corrupt bytecode.
1831 let f = compile_src("switch (1) { case 1: 10; break; default: 20; break; }");
1832 let dis = f.disassemble();
1833 assert!(dis.contains("StrictEq"), "missing case test: {dis}");
1834 // The first instruction should NOT be corrupted.
1835 assert!(
1836 dis.contains("LoadUndefined r0"),
1837 "first instruction corrupted: {dis}"
1838 );
1839 }
1840
1841 #[test]
1842 fn test_switch_only_default() {
1843 // Switch with only a default case.
1844 let f = compile_src("switch (42) { default: 99; }");
1845 let dis = f.disassemble();
1846 // Should compile without panicking and contain the default body.
1847 assert!(dis.contains("LoadInt8"), "got:\n{dis}");
1848 }
1849
1850 #[test]
1851 fn test_class_empty_constructor_has_return() {
1852 // A class without an explicit constructor should produce a function with Return.
1853 let f = compile_src("class Foo {}");
1854 assert!(!f.functions.is_empty(), "should have constructor function");
1855 let ctor = &f.functions[0];
1856 let dis = ctor.disassemble();
1857 assert!(
1858 dis.contains("Return"),
1859 "empty constructor must have Return: {dis}"
1860 );
1861 }
1862
1863 #[test]
1864 fn test_class_expression_compiles_methods() {
1865 // Class expression should compile methods, not just the constructor.
1866 let f = compile_src("var C = class { greet() { return 1; } };");
1867 let dis = f.disassemble();
1868 assert!(
1869 dis.contains("SetPropertyByName"),
1870 "method should be set as property: {dis}"
1871 );
1872 }
1873}