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