we (web engine): Experimental web browser project to understand the limits of Claude
at js-vm 1879 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 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}