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