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