we (web engine): Experimental web browser project to understand the limits of Claude
at yield-cleanup 3355 lines 125 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; 10use std::collections::HashSet; 11 12/// Compiler state for a single function scope. 13struct FunctionCompiler { 14 builder: BytecodeBuilder, 15 /// Maps local variable names to their register slots. 16 locals: Vec<Local>, 17 /// Next free register index. 18 next_reg: u8, 19 /// Stack of loop contexts for break/continue. 20 loop_stack: Vec<LoopCtx>, 21 /// Upvalues captured from the parent scope (used in inner functions). 22 upvalues: Vec<UpvalueEntry>, 23 /// Set of local variable names that are captured by inner functions. 24 /// Pre-populated before compilation by scanning inner function bodies. 25 captured_names: HashSet<String>, 26} 27 28#[derive(Debug, Clone)] 29struct Local { 30 name: String, 31 reg: Reg, 32 /// Whether this variable is captured by an inner function (stored in a cell). 33 is_captured: bool, 34 /// Whether this variable was declared with `const`. 35 is_const: bool, 36} 37 38/// An upvalue entry tracking how this function captures an outer variable. 39struct UpvalueEntry { 40 /// Name of the captured variable (for dedup during resolution). 41 name: String, 42 /// The resolved upvalue definition. 43 def: UpvalueDef, 44 /// Whether the original declaration was `const`. 45 is_const: bool, 46} 47 48struct LoopCtx { 49 /// Label, if this is a labeled loop. 50 label: Option<String>, 51 /// Patch positions for break jumps. 52 break_patches: Vec<usize>, 53 /// Patch positions for continue jumps (patched after body compilation). 54 continue_patches: Vec<usize>, 55} 56 57impl FunctionCompiler { 58 fn new(name: String, param_count: u8) -> Self { 59 Self { 60 builder: BytecodeBuilder::new(name, param_count), 61 locals: Vec::new(), 62 next_reg: 0, 63 loop_stack: Vec::new(), 64 upvalues: Vec::new(), 65 captured_names: HashSet::new(), 66 } 67 } 68 69 /// Allocate a register, updating the high-water mark. 70 fn alloc_reg(&mut self) -> Reg { 71 let r = self.next_reg; 72 self.next_reg = self.next_reg.checked_add(1).expect("register overflow"); 73 if self.next_reg > self.builder.func.register_count { 74 self.builder.func.register_count = self.next_reg; 75 } 76 r 77 } 78 79 /// Free the last allocated register (must be called in reverse order). 80 fn free_reg(&mut self, r: Reg) { 81 debug_assert_eq!( 82 r, 83 self.next_reg - 1, 84 "registers must be freed in reverse order" 85 ); 86 self.next_reg -= 1; 87 } 88 89 /// Look up a local variable by name, returning full info. 90 fn find_local_info(&self, name: &str) -> Option<&Local> { 91 self.locals.iter().rev().find(|l| l.name == name) 92 } 93 94 /// Look up an upvalue by name, returning its index. 95 fn find_upvalue(&self, name: &str) -> Option<u8> { 96 self.upvalues 97 .iter() 98 .position(|u| u.name == name) 99 .map(|i| i as u8) 100 } 101 102 /// Check if an upvalue is const. 103 fn is_upvalue_const(&self, idx: u8) -> bool { 104 self.upvalues 105 .get(idx as usize) 106 .map(|u| u.is_const) 107 .unwrap_or(false) 108 } 109 110 /// Define a local variable with capture and const flags. 111 fn define_local_ext(&mut self, name: &str, is_captured: bool, is_const: bool) -> Reg { 112 let reg = self.alloc_reg(); 113 self.locals.push(Local { 114 name: name.to_string(), 115 reg, 116 is_captured, 117 is_const, 118 }); 119 reg 120 } 121} 122 123// ── Free variable analysis ────────────────────────────────── 124 125/// Collect identifiers referenced inside `body` that are not declared locally 126/// (params or variable declarations within the body). This set represents the 127/// "free variables" of a function body — variables that must be captured. 128/// This includes transitive free variables from nested functions. 129fn collect_free_vars(params: &[Pattern], body: &[Stmt]) -> HashSet<String> { 130 let mut declared = HashSet::new(); 131 let mut referenced = HashSet::new(); 132 133 // Params are local declarations. 134 for p in params { 135 collect_pattern_names(p, &mut declared); 136 } 137 138 // Collect declarations and references from the body. 139 for stmt in body { 140 collect_stmt_decls(stmt, &mut declared); 141 } 142 for stmt in body { 143 collect_stmt_refs(stmt, &declared, &mut referenced); 144 } 145 146 // Also include transitive free variables from nested inner functions. 147 // If an inner function references `x` and `x` is not declared in THIS scope, 148 // then `x` is also a free variable of THIS function. 149 let inner_caps = collect_inner_captures(body); 150 for name in inner_caps { 151 if !declared.contains(&name) { 152 referenced.insert(name); 153 } 154 } 155 156 referenced 157} 158 159/// Collect free variables from an arrow function body. 160fn collect_free_vars_arrow(params: &[Pattern], body: &ArrowBody) -> HashSet<String> { 161 let mut declared = HashSet::new(); 162 let mut referenced = HashSet::new(); 163 164 for p in params { 165 collect_pattern_names(p, &mut declared); 166 } 167 168 match body { 169 ArrowBody::Expr(expr) => { 170 collect_expr_refs(expr, &declared, &mut referenced); 171 } 172 ArrowBody::Block(stmts) => { 173 for stmt in stmts { 174 collect_stmt_decls(stmt, &mut declared); 175 } 176 for stmt in stmts { 177 collect_stmt_refs(stmt, &declared, &mut referenced); 178 } 179 // Transitive free variables from nested functions. 180 let inner_caps = collect_inner_captures(stmts); 181 for name in inner_caps { 182 if !declared.contains(&name) { 183 referenced.insert(name); 184 } 185 } 186 } 187 } 188 189 referenced 190} 191 192fn collect_pattern_names(pat: &Pattern, names: &mut HashSet<String>) { 193 match &pat.kind { 194 PatternKind::Identifier(name) => { 195 names.insert(name.clone()); 196 } 197 PatternKind::Array { elements, rest } => { 198 for elem in elements.iter().flatten() { 199 collect_pattern_names(elem, names); 200 } 201 if let Some(rest) = rest { 202 collect_pattern_names(rest, names); 203 } 204 } 205 PatternKind::Object { properties, rest } => { 206 for prop in properties { 207 collect_pattern_names(&prop.value, names); 208 } 209 if let Some(rest) = rest { 210 collect_pattern_names(rest, names); 211 } 212 } 213 PatternKind::Assign { left, .. } => { 214 collect_pattern_names(left, names); 215 } 216 } 217} 218 219/// Collect all variable/function declarations in a statement (not recursing into 220/// inner functions — those form their own scope). 221fn collect_stmt_decls(stmt: &Stmt, declared: &mut HashSet<String>) { 222 match &stmt.kind { 223 StmtKind::VarDecl { declarators, .. } => { 224 for d in declarators { 225 collect_pattern_names(&d.pattern, declared); 226 } 227 } 228 StmtKind::FunctionDecl(f) => { 229 if let Some(name) = &f.id { 230 declared.insert(name.clone()); 231 } 232 } 233 StmtKind::ClassDecl(c) => { 234 if let Some(name) = &c.id { 235 declared.insert(name.clone()); 236 } 237 } 238 StmtKind::Block(stmts) => { 239 for s in stmts { 240 collect_stmt_decls(s, declared); 241 } 242 } 243 StmtKind::If { 244 consequent, 245 alternate, 246 .. 247 } => { 248 collect_stmt_decls(consequent, declared); 249 if let Some(alt) = alternate { 250 collect_stmt_decls(alt, declared); 251 } 252 } 253 StmtKind::While { body, .. } 254 | StmtKind::DoWhile { body, .. } 255 | StmtKind::Labeled { body, .. } => { 256 collect_stmt_decls(body, declared); 257 } 258 StmtKind::For { init, body, .. } => { 259 if let Some(ForInit::VarDecl { declarators, .. }) = init { 260 for d in declarators { 261 collect_pattern_names(&d.pattern, declared); 262 } 263 } 264 collect_stmt_decls(body, declared); 265 } 266 StmtKind::ForIn { left, body, .. } | StmtKind::ForOf { left, body, .. } => { 267 if let ForInOfLeft::VarDecl { pattern, .. } = left { 268 collect_pattern_names(pattern, declared); 269 } 270 collect_stmt_decls(body, declared); 271 } 272 StmtKind::Try { 273 block, 274 handler, 275 finalizer, 276 } => { 277 for s in block { 278 collect_stmt_decls(s, declared); 279 } 280 if let Some(h) = handler { 281 if let Some(param) = &h.param { 282 collect_pattern_names(param, declared); 283 } 284 for s in &h.body { 285 collect_stmt_decls(s, declared); 286 } 287 } 288 if let Some(fin) = finalizer { 289 for s in fin { 290 collect_stmt_decls(s, declared); 291 } 292 } 293 } 294 StmtKind::Switch { cases, .. } => { 295 for case in cases { 296 for s in &case.consequent { 297 collect_stmt_decls(s, declared); 298 } 299 } 300 } 301 _ => {} 302 } 303} 304 305/// Collect all identifier references in a statement, excluding inner function scopes. 306/// Identifiers that are in `declared` are local and skipped. 307fn collect_stmt_refs(stmt: &Stmt, declared: &HashSet<String>, refs: &mut HashSet<String>) { 308 match &stmt.kind { 309 StmtKind::Expr(expr) => collect_expr_refs(expr, declared, refs), 310 StmtKind::Block(stmts) => { 311 for s in stmts { 312 collect_stmt_refs(s, declared, refs); 313 } 314 } 315 StmtKind::VarDecl { declarators, .. } => { 316 for d in declarators { 317 if let Some(init) = &d.init { 318 collect_expr_refs(init, declared, refs); 319 } 320 } 321 } 322 StmtKind::FunctionDecl(_) => { 323 // Don't recurse into inner functions — they have their own scope. 324 } 325 StmtKind::If { 326 test, 327 consequent, 328 alternate, 329 } => { 330 collect_expr_refs(test, declared, refs); 331 collect_stmt_refs(consequent, declared, refs); 332 if let Some(alt) = alternate { 333 collect_stmt_refs(alt, declared, refs); 334 } 335 } 336 StmtKind::While { test, body } => { 337 collect_expr_refs(test, declared, refs); 338 collect_stmt_refs(body, declared, refs); 339 } 340 StmtKind::DoWhile { body, test } => { 341 collect_stmt_refs(body, declared, refs); 342 collect_expr_refs(test, declared, refs); 343 } 344 StmtKind::For { 345 init, 346 test, 347 update, 348 body, 349 } => { 350 if let Some(init) = init { 351 match init { 352 ForInit::VarDecl { declarators, .. } => { 353 for d in declarators { 354 if let Some(init) = &d.init { 355 collect_expr_refs(init, declared, refs); 356 } 357 } 358 } 359 ForInit::Expr(e) => collect_expr_refs(e, declared, refs), 360 } 361 } 362 if let Some(t) = test { 363 collect_expr_refs(t, declared, refs); 364 } 365 if let Some(u) = update { 366 collect_expr_refs(u, declared, refs); 367 } 368 collect_stmt_refs(body, declared, refs); 369 } 370 StmtKind::ForIn { right, body, .. } | StmtKind::ForOf { right, body, .. } => { 371 collect_expr_refs(right, declared, refs); 372 collect_stmt_refs(body, declared, refs); 373 } 374 StmtKind::Return(Some(expr)) | StmtKind::Throw(expr) => { 375 collect_expr_refs(expr, declared, refs); 376 } 377 StmtKind::Try { 378 block, 379 handler, 380 finalizer, 381 } => { 382 for s in block { 383 collect_stmt_refs(s, declared, refs); 384 } 385 if let Some(h) = handler { 386 for s in &h.body { 387 collect_stmt_refs(s, declared, refs); 388 } 389 } 390 if let Some(fin) = finalizer { 391 for s in fin { 392 collect_stmt_refs(s, declared, refs); 393 } 394 } 395 } 396 StmtKind::Switch { 397 discriminant, 398 cases, 399 } => { 400 collect_expr_refs(discriminant, declared, refs); 401 for case in cases { 402 if let Some(test) = &case.test { 403 collect_expr_refs(test, declared, refs); 404 } 405 for s in &case.consequent { 406 collect_stmt_refs(s, declared, refs); 407 } 408 } 409 } 410 StmtKind::Labeled { body, .. } => { 411 collect_stmt_refs(body, declared, refs); 412 } 413 _ => {} 414 } 415} 416 417/// Collect identifier references in an expression. Does NOT recurse into 418/// inner function/arrow bodies (those form their own scope). 419fn collect_expr_refs(expr: &Expr, declared: &HashSet<String>, refs: &mut HashSet<String>) { 420 match &expr.kind { 421 ExprKind::Identifier(name) => { 422 if !declared.contains(name) { 423 refs.insert(name.clone()); 424 } 425 } 426 ExprKind::Binary { left, right, .. } 427 | ExprKind::Logical { left, right, .. } 428 | ExprKind::Assignment { left, right, .. } => { 429 collect_expr_refs(left, declared, refs); 430 collect_expr_refs(right, declared, refs); 431 } 432 ExprKind::Unary { argument, .. } | ExprKind::Update { argument, .. } => { 433 collect_expr_refs(argument, declared, refs); 434 } 435 ExprKind::Conditional { 436 test, 437 consequent, 438 alternate, 439 } => { 440 collect_expr_refs(test, declared, refs); 441 collect_expr_refs(consequent, declared, refs); 442 collect_expr_refs(alternate, declared, refs); 443 } 444 ExprKind::Call { callee, arguments } | ExprKind::New { callee, arguments } => { 445 collect_expr_refs(callee, declared, refs); 446 for arg in arguments { 447 collect_expr_refs(arg, declared, refs); 448 } 449 } 450 ExprKind::Member { 451 object, 452 property, 453 computed, 454 .. 455 } => { 456 collect_expr_refs(object, declared, refs); 457 if *computed { 458 collect_expr_refs(property, declared, refs); 459 } 460 } 461 ExprKind::Array(elements) => { 462 for elem in elements.iter().flatten() { 463 match elem { 464 ArrayElement::Expr(e) | ArrayElement::Spread(e) => { 465 collect_expr_refs(e, declared, refs); 466 } 467 } 468 } 469 } 470 ExprKind::Object(props) => { 471 for prop in props { 472 if let PropertyKey::Computed(e) = &prop.key { 473 collect_expr_refs(e, declared, refs); 474 } 475 if let Some(val) = &prop.value { 476 collect_expr_refs(val, declared, refs); 477 } 478 } 479 } 480 ExprKind::Sequence(exprs) => { 481 for e in exprs { 482 collect_expr_refs(e, declared, refs); 483 } 484 } 485 ExprKind::Spread(inner) => { 486 collect_expr_refs(inner, declared, refs); 487 } 488 ExprKind::TemplateLiteral { expressions, .. } => { 489 for e in expressions { 490 collect_expr_refs(e, declared, refs); 491 } 492 } 493 // Function/Arrow/Class bodies are new scopes — don't recurse. 494 ExprKind::Function(_) | ExprKind::Arrow { .. } | ExprKind::Class(_) => {} 495 _ => {} 496 } 497} 498 499/// Collect the free variables of ALL inner functions/arrows within a list of 500/// statements. Returns the set of outer-scope names they reference. 501fn collect_inner_captures(stmts: &[Stmt]) -> HashSet<String> { 502 let mut captures = HashSet::new(); 503 for stmt in stmts { 504 collect_inner_captures_stmt(stmt, &mut captures); 505 } 506 captures 507} 508 509fn collect_inner_captures_stmt(stmt: &Stmt, caps: &mut HashSet<String>) { 510 match &stmt.kind { 511 StmtKind::FunctionDecl(f) => { 512 let fv = collect_free_vars(&f.params, &f.body); 513 caps.extend(fv); 514 } 515 StmtKind::Expr(expr) => collect_inner_captures_expr(expr, caps), 516 StmtKind::VarDecl { declarators, .. } => { 517 for d in declarators { 518 if let Some(init) = &d.init { 519 collect_inner_captures_expr(init, caps); 520 } 521 } 522 } 523 StmtKind::Block(stmts) => { 524 for s in stmts { 525 collect_inner_captures_stmt(s, caps); 526 } 527 } 528 StmtKind::If { 529 test, 530 consequent, 531 alternate, 532 } => { 533 collect_inner_captures_expr(test, caps); 534 collect_inner_captures_stmt(consequent, caps); 535 if let Some(alt) = alternate { 536 collect_inner_captures_stmt(alt, caps); 537 } 538 } 539 StmtKind::While { test, body } => { 540 collect_inner_captures_expr(test, caps); 541 collect_inner_captures_stmt(body, caps); 542 } 543 StmtKind::DoWhile { body, test } => { 544 collect_inner_captures_stmt(body, caps); 545 collect_inner_captures_expr(test, caps); 546 } 547 StmtKind::For { 548 init, 549 test, 550 update, 551 body, 552 } => { 553 if let Some(ForInit::Expr(e)) = init { 554 collect_inner_captures_expr(e, caps); 555 } 556 if let Some(ForInit::VarDecl { declarators, .. }) = init { 557 for d in declarators { 558 if let Some(init) = &d.init { 559 collect_inner_captures_expr(init, caps); 560 } 561 } 562 } 563 if let Some(t) = test { 564 collect_inner_captures_expr(t, caps); 565 } 566 if let Some(u) = update { 567 collect_inner_captures_expr(u, caps); 568 } 569 collect_inner_captures_stmt(body, caps); 570 } 571 StmtKind::ForIn { right, body, .. } | StmtKind::ForOf { right, body, .. } => { 572 collect_inner_captures_expr(right, caps); 573 collect_inner_captures_stmt(body, caps); 574 } 575 StmtKind::Return(Some(expr)) | StmtKind::Throw(expr) => { 576 collect_inner_captures_expr(expr, caps); 577 } 578 StmtKind::Try { 579 block, 580 handler, 581 finalizer, 582 } => { 583 for s in block { 584 collect_inner_captures_stmt(s, caps); 585 } 586 if let Some(h) = handler { 587 for s in &h.body { 588 collect_inner_captures_stmt(s, caps); 589 } 590 } 591 if let Some(fin) = finalizer { 592 for s in fin { 593 collect_inner_captures_stmt(s, caps); 594 } 595 } 596 } 597 StmtKind::Switch { 598 discriminant, 599 cases, 600 } => { 601 collect_inner_captures_expr(discriminant, caps); 602 for case in cases { 603 if let Some(test) = &case.test { 604 collect_inner_captures_expr(test, caps); 605 } 606 for s in &case.consequent { 607 collect_inner_captures_stmt(s, caps); 608 } 609 } 610 } 611 StmtKind::Labeled { body, .. } => { 612 collect_inner_captures_stmt(body, caps); 613 } 614 _ => {} 615 } 616} 617 618fn collect_inner_captures_expr(expr: &Expr, caps: &mut HashSet<String>) { 619 match &expr.kind { 620 ExprKind::Function(f) => { 621 let fv = collect_free_vars(&f.params, &f.body); 622 caps.extend(fv); 623 } 624 ExprKind::Arrow { params, body, .. } => { 625 let fv = collect_free_vars_arrow(params, body); 626 caps.extend(fv); 627 } 628 ExprKind::Binary { left, right, .. } 629 | ExprKind::Logical { left, right, .. } 630 | ExprKind::Assignment { left, right, .. } => { 631 collect_inner_captures_expr(left, caps); 632 collect_inner_captures_expr(right, caps); 633 } 634 ExprKind::Unary { argument, .. } | ExprKind::Update { argument, .. } => { 635 collect_inner_captures_expr(argument, caps); 636 } 637 ExprKind::Conditional { 638 test, 639 consequent, 640 alternate, 641 } => { 642 collect_inner_captures_expr(test, caps); 643 collect_inner_captures_expr(consequent, caps); 644 collect_inner_captures_expr(alternate, caps); 645 } 646 ExprKind::Call { callee, arguments } | ExprKind::New { callee, arguments } => { 647 collect_inner_captures_expr(callee, caps); 648 for arg in arguments { 649 collect_inner_captures_expr(arg, caps); 650 } 651 } 652 ExprKind::Member { 653 object, 654 property, 655 computed, 656 .. 657 } => { 658 collect_inner_captures_expr(object, caps); 659 if *computed { 660 collect_inner_captures_expr(property, caps); 661 } 662 } 663 ExprKind::Array(elements) => { 664 for elem in elements.iter().flatten() { 665 match elem { 666 ArrayElement::Expr(e) | ArrayElement::Spread(e) => { 667 collect_inner_captures_expr(e, caps); 668 } 669 } 670 } 671 } 672 ExprKind::Object(props) => { 673 for prop in props { 674 if let PropertyKey::Computed(e) = &prop.key { 675 collect_inner_captures_expr(e, caps); 676 } 677 if let Some(val) = &prop.value { 678 collect_inner_captures_expr(val, caps); 679 } 680 } 681 } 682 ExprKind::Sequence(exprs) => { 683 for e in exprs { 684 collect_inner_captures_expr(e, caps); 685 } 686 } 687 ExprKind::Spread(inner) => { 688 collect_inner_captures_expr(inner, caps); 689 } 690 ExprKind::TemplateLiteral { expressions, .. } => { 691 for e in expressions { 692 collect_inner_captures_expr(e, caps); 693 } 694 } 695 ExprKind::Class(c) => { 696 for member in &c.body { 697 if let ClassMemberKind::Method { value, .. } = &member.kind { 698 let fv = collect_free_vars(&value.params, &value.body); 699 caps.extend(fv); 700 } 701 } 702 } 703 _ => {} 704 } 705} 706 707/// Compile a parsed program into a top-level bytecode function. 708pub fn compile(program: &Program) -> Result<Function, JsError> { 709 let mut fc = FunctionCompiler::new("<main>".into(), 0); 710 711 // Pre-scan to find which top-level locals are captured by inner functions. 712 fc.captured_names = collect_inner_captures(&program.body); 713 714 // Reserve r0 for the implicit return value. 715 let result_reg = fc.alloc_reg(); 716 fc.builder.emit_reg(Op::LoadUndefined, result_reg); 717 718 compile_stmts(&mut fc, &program.body, result_reg)?; 719 720 fc.builder.emit_reg(Op::Return, result_reg); 721 Ok(fc.builder.finish()) 722} 723 724fn compile_stmts( 725 fc: &mut FunctionCompiler, 726 stmts: &[Stmt], 727 result_reg: Reg, 728) -> Result<(), JsError> { 729 for stmt in stmts { 730 compile_stmt(fc, stmt, result_reg)?; 731 } 732 Ok(()) 733} 734 735fn compile_stmt(fc: &mut FunctionCompiler, stmt: &Stmt, result_reg: Reg) -> Result<(), JsError> { 736 match &stmt.kind { 737 StmtKind::Expr(expr) => { 738 // Expression statement: compile and store result in result_reg. 739 compile_expr(fc, expr, result_reg)?; 740 } 741 742 StmtKind::Block(stmts) => { 743 let saved_locals = fc.locals.len(); 744 let saved_next = fc.next_reg; 745 compile_stmts(fc, stmts, result_reg)?; 746 // Pop locals from this block. 747 fc.locals.truncate(saved_locals); 748 fc.next_reg = saved_next; 749 } 750 751 StmtKind::VarDecl { kind, declarators } => { 752 for decl in declarators { 753 compile_var_declarator(fc, decl, *kind)?; 754 } 755 } 756 757 StmtKind::FunctionDecl(func_def) => { 758 compile_function_decl(fc, func_def)?; 759 } 760 761 StmtKind::If { 762 test, 763 consequent, 764 alternate, 765 } => { 766 compile_if(fc, test, consequent, alternate.as_deref(), result_reg)?; 767 } 768 769 StmtKind::While { test, body } => { 770 compile_while(fc, test, body, None, result_reg)?; 771 } 772 773 StmtKind::DoWhile { body, test } => { 774 compile_do_while(fc, body, test, None, result_reg)?; 775 } 776 777 StmtKind::For { 778 init, 779 test, 780 update, 781 body, 782 } => { 783 compile_for( 784 fc, 785 init.as_ref(), 786 test.as_ref(), 787 update.as_ref(), 788 body, 789 None, 790 result_reg, 791 )?; 792 } 793 794 StmtKind::ForIn { left, right, body } => { 795 let saved_locals = fc.locals.len(); 796 let saved_next = fc.next_reg; 797 798 // Evaluate the RHS object. 799 let obj_r = fc.alloc_reg(); 800 compile_expr(fc, right, obj_r)?; 801 802 // ForInInit: collect enumerable keys into an array. 803 let keys_r = fc.alloc_reg(); 804 fc.builder.emit_reg_reg(Op::ForInInit, keys_r, obj_r); 805 // obj_r is no longer needed but we don't free it (LIFO constraint). 806 807 // Index register (starts at 0). 808 let idx_r = fc.alloc_reg(); 809 fc.builder.emit_load_int8(idx_r, 0); 810 811 // Key and done registers (reused each iteration). 812 let key_r = fc.alloc_reg(); 813 let done_r = fc.alloc_reg(); 814 815 let loop_start = fc.builder.offset(); 816 817 // ForInNext: get next key or done flag. 818 fc.builder 819 .emit_reg4(Op::ForInNext, key_r, done_r, keys_r, idx_r); 820 821 // Exit loop if done. 822 let exit_patch = fc.builder.emit_cond_jump(Op::JumpIfTrue, done_r); 823 824 // Bind the loop variable. 825 match left { 826 ForInOfLeft::VarDecl { kind, pattern } => { 827 if let PatternKind::Identifier(name) = &pattern.kind { 828 let is_captured = fc.captured_names.contains(name.as_str()); 829 let is_const = *kind == VarKind::Const; 830 let var_r = fc.define_local_ext(name, is_captured, is_const); 831 if is_captured { 832 fc.builder.emit_reg(Op::NewCell, var_r); 833 fc.builder.emit_reg_reg(Op::CellStore, var_r, key_r); 834 } else { 835 fc.builder.emit_reg_reg(Op::Move, var_r, key_r); 836 } 837 } 838 } 839 ForInOfLeft::Pattern(pattern) => { 840 if let PatternKind::Identifier(name) = &pattern.kind { 841 if let Some(local) = fc.find_local_info(name) { 842 let reg = local.reg; 843 let captured = local.is_captured; 844 if captured { 845 fc.builder.emit_reg_reg(Op::CellStore, reg, key_r); 846 } else { 847 fc.builder.emit_reg_reg(Op::Move, reg, key_r); 848 } 849 } else if let Some(uv_idx) = fc.find_upvalue(name) { 850 fc.builder.emit_store_upvalue(uv_idx, key_r); 851 } else { 852 let ni = fc.builder.add_name(name); 853 fc.builder.emit_store_global(ni, key_r); 854 } 855 } 856 } 857 } 858 859 // Compile the body. 860 fc.loop_stack.push(LoopCtx { 861 label: None, 862 break_patches: Vec::new(), 863 continue_patches: Vec::new(), 864 }); 865 compile_stmt(fc, body, result_reg)?; 866 867 // Increment index: idx = idx + 1. 868 // Use a temp register for the constant 1. Since we allocate it 869 // after the loop body, we can't free it with LIFO either — the 870 // saved_next restoration handles cleanup. 871 let one_r = fc.alloc_reg(); 872 fc.builder.emit_load_int8(one_r, 1); 873 fc.builder.emit_reg3(Op::Add, idx_r, idx_r, one_r); 874 875 // Jump back to loop start. 876 fc.builder.emit_jump_to(loop_start); 877 878 // Patch exit and continue jumps. 879 fc.builder.patch_jump(exit_patch); 880 let ctx = fc.loop_stack.pop().unwrap(); 881 for patch in ctx.break_patches { 882 fc.builder.patch_jump(patch); 883 } 884 for patch in ctx.continue_patches { 885 fc.builder.patch_jump_to(patch, loop_start); 886 } 887 888 // Restore locals/regs — frees all temporaries at once. 889 fc.locals.truncate(saved_locals); 890 fc.next_reg = saved_next; 891 } 892 893 StmtKind::ForOf { 894 left, 895 right, 896 body, 897 is_await: _, 898 } => { 899 let saved_locals = fc.locals.len(); 900 let saved_next = fc.next_reg; 901 902 // Evaluate the iterable. 903 let iterable_r = fc.alloc_reg(); 904 compile_expr(fc, right, iterable_r)?; 905 906 // Get the iterator: call iterable[@@iterator](). 907 let iter_method_r = fc.alloc_reg(); 908 let sym_iter_ni = fc.builder.add_name("@@iterator"); 909 fc.builder 910 .emit_get_prop_name(iter_method_r, iterable_r, sym_iter_ni); 911 912 // Set `this` = iterable for the call. 913 let this_ni = fc.builder.add_name("this"); 914 fc.builder.emit_store_global(this_ni, iterable_r); 915 916 // Call [@@iterator]() with 0 args. 917 let iterator_r = fc.alloc_reg(); 918 let args_start = fc.next_reg; 919 fc.builder 920 .emit_call(iterator_r, iter_method_r, args_start, 0); 921 922 // Temp registers for next method, result, done, value. 923 let next_method_r = fc.alloc_reg(); 924 let next_ni = fc.builder.add_name("next"); 925 fc.builder 926 .emit_get_prop_name(next_method_r, iterator_r, next_ni); 927 928 let result_obj_r = fc.alloc_reg(); 929 let done_r = fc.alloc_reg(); 930 let value_r = fc.alloc_reg(); 931 932 // Loop start. 933 let loop_start = fc.builder.offset(); 934 935 // Set `this` = iterator for the .next() call. 936 fc.builder.emit_store_global(this_ni, iterator_r); 937 938 // Call iterator.next(). 939 fc.builder 940 .emit_call(result_obj_r, next_method_r, args_start, 0); 941 942 // Extract done and value. 943 let done_ni = fc.builder.add_name("done"); 944 let value_ni = fc.builder.add_name("value"); 945 fc.builder.emit_get_prop_name(done_r, result_obj_r, done_ni); 946 947 // Exit if done. 948 let exit_patch = fc.builder.emit_cond_jump(Op::JumpIfTrue, done_r); 949 950 // Extract value. 951 fc.builder 952 .emit_get_prop_name(value_r, result_obj_r, value_ni); 953 954 // Bind the loop variable. 955 match left { 956 ForInOfLeft::VarDecl { kind, pattern } => match &pattern.kind { 957 PatternKind::Identifier(name) => { 958 let is_captured = fc.captured_names.contains(name.as_str()); 959 let is_const = *kind == VarKind::Const; 960 let var_r = fc.define_local_ext(name, is_captured, is_const); 961 if is_captured { 962 fc.builder.emit_reg(Op::NewCell, var_r); 963 fc.builder.emit_reg_reg(Op::CellStore, var_r, value_r); 964 } else { 965 fc.builder.emit_reg_reg(Op::Move, var_r, value_r); 966 } 967 } 968 _ => { 969 // Destructuring pattern in for...of. 970 compile_destructuring_pattern(fc, pattern, value_r)?; 971 } 972 }, 973 ForInOfLeft::Pattern(pattern) => match &pattern.kind { 974 PatternKind::Identifier(name) => { 975 if let Some(local) = fc.find_local_info(name) { 976 let reg = local.reg; 977 let captured = local.is_captured; 978 if captured { 979 fc.builder.emit_reg_reg(Op::CellStore, reg, value_r); 980 } else { 981 fc.builder.emit_reg_reg(Op::Move, reg, value_r); 982 } 983 } else if let Some(uv_idx) = fc.find_upvalue(name) { 984 fc.builder.emit_store_upvalue(uv_idx, value_r); 985 } else { 986 let ni = fc.builder.add_name(name); 987 fc.builder.emit_store_global(ni, value_r); 988 } 989 } 990 _ => { 991 compile_destructuring_pattern(fc, pattern, value_r)?; 992 } 993 }, 994 } 995 996 // Push loop context for break/continue. 997 fc.loop_stack.push(LoopCtx { 998 label: None, 999 break_patches: Vec::new(), 1000 continue_patches: Vec::new(), 1001 }); 1002 1003 // Compile body. 1004 compile_stmt(fc, body, result_reg)?; 1005 1006 // Jump back to loop start. 1007 fc.builder.emit_jump_to(loop_start); 1008 1009 // Patch exit. 1010 fc.builder.patch_jump(exit_patch); 1011 let ctx = fc.loop_stack.pop().unwrap(); 1012 for patch in ctx.break_patches { 1013 fc.builder.patch_jump(patch); 1014 } 1015 for patch in ctx.continue_patches { 1016 fc.builder.patch_jump_to(patch, loop_start); 1017 } 1018 1019 // Restore locals/regs. 1020 fc.locals.truncate(saved_locals); 1021 fc.next_reg = saved_next; 1022 } 1023 1024 StmtKind::Return(expr) => { 1025 let ret_reg = fc.alloc_reg(); 1026 if let Some(e) = expr { 1027 compile_expr(fc, e, ret_reg)?; 1028 } else { 1029 fc.builder.emit_reg(Op::LoadUndefined, ret_reg); 1030 } 1031 fc.builder.emit_reg(Op::Return, ret_reg); 1032 fc.free_reg(ret_reg); 1033 } 1034 1035 StmtKind::Throw(expr) => { 1036 let tmp = fc.alloc_reg(); 1037 compile_expr(fc, expr, tmp)?; 1038 fc.builder.emit_reg(Op::Throw, tmp); 1039 fc.free_reg(tmp); 1040 } 1041 1042 StmtKind::Break(label) => { 1043 // Find the matching loop context. 1044 let idx = find_loop_ctx(&fc.loop_stack, label.as_deref()) 1045 .ok_or_else(|| JsError::SyntaxError("break outside of loop".into()))?; 1046 let patch = fc.builder.emit_jump(Op::Jump); 1047 fc.loop_stack[idx].break_patches.push(patch); 1048 } 1049 1050 StmtKind::Continue(label) => { 1051 let idx = find_loop_ctx(&fc.loop_stack, label.as_deref()) 1052 .ok_or_else(|| JsError::SyntaxError("continue outside of loop".into()))?; 1053 let patch = fc.builder.emit_jump(Op::Jump); 1054 fc.loop_stack[idx].continue_patches.push(patch); 1055 } 1056 1057 StmtKind::Labeled { label, body } => { 1058 // If body is a loop, propagate the label. 1059 match &body.kind { 1060 StmtKind::While { test, body: inner } => { 1061 compile_while(fc, test, inner, Some(label.clone()), result_reg)?; 1062 } 1063 StmtKind::DoWhile { body: inner, test } => { 1064 compile_do_while(fc, inner, test, Some(label.clone()), result_reg)?; 1065 } 1066 StmtKind::For { 1067 init, 1068 test, 1069 update, 1070 body: inner, 1071 } => { 1072 compile_for( 1073 fc, 1074 init.as_ref(), 1075 test.as_ref(), 1076 update.as_ref(), 1077 inner, 1078 Some(label.clone()), 1079 result_reg, 1080 )?; 1081 } 1082 _ => { 1083 compile_stmt(fc, body, result_reg)?; 1084 } 1085 } 1086 } 1087 1088 StmtKind::Switch { 1089 discriminant, 1090 cases, 1091 } => { 1092 compile_switch(fc, discriminant, cases, result_reg)?; 1093 } 1094 1095 StmtKind::Try { 1096 block, 1097 handler, 1098 finalizer, 1099 } => { 1100 if let Some(catch) = handler { 1101 // The catch register will receive the exception value. Use the 1102 // current next_reg so it doesn't conflict with temporaries 1103 // allocated inside the try block. 1104 let saved_next = fc.next_reg; 1105 let catch_reg = fc.alloc_reg(); 1106 // Immediately "release" it so the try block can reuse registers 1107 // from this point. We remember catch_reg for PushExceptionHandler. 1108 fc.next_reg = saved_next; 1109 1110 // Emit PushExceptionHandler with placeholder offset to catch block. 1111 let catch_patch = fc.builder.emit_push_exception_handler(catch_reg); 1112 1113 let locals_len = fc.locals.len(); 1114 1115 // Compile the try block. 1116 compile_stmts(fc, block, result_reg)?; 1117 1118 // If we reach here, no exception was thrown. Pop handler and 1119 // jump past the catch block. 1120 fc.builder.emit_pop_exception_handler(); 1121 let end_patch = fc.builder.emit_jump(Op::Jump); 1122 1123 // Reset register state for catch block — locals declared in 1124 // the try block are out of scope. 1125 fc.locals.truncate(locals_len); 1126 fc.next_reg = saved_next; 1127 1128 // Patch the exception handler to jump here (catch block start). 1129 fc.builder.patch_jump(catch_patch); 1130 1131 // Bind the catch parameter if present. 1132 if let Some(param) = &catch.param { 1133 if let PatternKind::Identifier(name) = &param.kind { 1134 let is_captured = fc.captured_names.contains(name.as_str()); 1135 let local = fc.define_local_ext(name, is_captured, false); 1136 if is_captured { 1137 fc.builder.emit_reg(Op::NewCell, local); 1138 fc.builder.emit_reg_reg(Op::CellStore, local, catch_reg); 1139 } else { 1140 fc.builder.emit_reg_reg(Op::Move, local, catch_reg); 1141 } 1142 } 1143 } 1144 1145 // Compile the catch body. 1146 compile_stmts(fc, &catch.body, result_reg)?; 1147 1148 // End of catch — restore state. 1149 fc.locals.truncate(locals_len); 1150 fc.next_reg = saved_next; 1151 1152 // Jump target from the try block. 1153 fc.builder.patch_jump(end_patch); 1154 } else { 1155 // No catch handler: just compile the try block. 1156 compile_stmts(fc, block, result_reg)?; 1157 } 1158 1159 // Compile the finally block (always runs after try or catch). 1160 if let Some(fin) = finalizer { 1161 compile_stmts(fc, fin, result_reg)?; 1162 } 1163 } 1164 1165 StmtKind::Empty | StmtKind::Debugger => { 1166 // No-op. 1167 } 1168 1169 StmtKind::With { object, body } => { 1170 // Compile `with` as: evaluate object (discard), then run body. 1171 // Proper `with` scope requires VM support. 1172 let tmp = fc.alloc_reg(); 1173 compile_expr(fc, object, tmp)?; 1174 fc.free_reg(tmp); 1175 compile_stmt(fc, body, result_reg)?; 1176 } 1177 1178 StmtKind::Import { .. } => { 1179 // Module imports are resolved before execution; no bytecode needed. 1180 } 1181 1182 StmtKind::Export(export) => { 1183 compile_export(fc, export, result_reg)?; 1184 } 1185 1186 StmtKind::ClassDecl(class_def) => { 1187 compile_class_decl(fc, class_def)?; 1188 } 1189 } 1190 Ok(()) 1191} 1192 1193// ── Variable declarations ─────────────────────────────────── 1194 1195fn compile_var_declarator( 1196 fc: &mut FunctionCompiler, 1197 decl: &VarDeclarator, 1198 kind: VarKind, 1199) -> Result<(), JsError> { 1200 match &decl.pattern.kind { 1201 PatternKind::Identifier(name) => { 1202 let is_const = kind == VarKind::Const; 1203 let is_captured = fc.captured_names.contains(name.as_str()); 1204 1205 if is_const && decl.init.is_none() { 1206 return Err(JsError::SyntaxError( 1207 "Missing initializer in const declaration".into(), 1208 )); 1209 } 1210 1211 let reg = fc.define_local_ext(name, is_captured, is_const); 1212 1213 if is_captured { 1214 // Allocate a cell for this variable. 1215 fc.builder.emit_reg(Op::NewCell, reg); 1216 if let Some(init) = &decl.init { 1217 let tmp = fc.alloc_reg(); 1218 compile_expr(fc, init, tmp)?; 1219 fc.builder.emit_reg_reg(Op::CellStore, reg, tmp); 1220 fc.free_reg(tmp); 1221 } 1222 // No init => cell stays undefined (already the default). 1223 } else if let Some(init) = &decl.init { 1224 compile_expr(fc, init, reg)?; 1225 } else { 1226 fc.builder.emit_reg(Op::LoadUndefined, reg); 1227 } 1228 } 1229 _ => { 1230 // Destructuring: evaluate init, then bind patterns. 1231 // Note: don't free tmp — destructuring pattern allocates permanent 1232 // local registers above it. The tmp register slot is reused via 1233 // next_reg restoration by the parent scope. 1234 let tmp = fc.alloc_reg(); 1235 if let Some(init) = &decl.init { 1236 compile_expr(fc, init, tmp)?; 1237 } else { 1238 fc.builder.emit_reg(Op::LoadUndefined, tmp); 1239 } 1240 compile_destructuring_pattern(fc, &decl.pattern, tmp)?; 1241 } 1242 } 1243 Ok(()) 1244} 1245 1246fn compile_destructuring_pattern( 1247 fc: &mut FunctionCompiler, 1248 pattern: &Pattern, 1249 src: Reg, 1250) -> Result<(), JsError> { 1251 match &pattern.kind { 1252 PatternKind::Identifier(name) => { 1253 let is_captured = fc.captured_names.contains(name.as_str()); 1254 let reg = fc.define_local_ext(name, is_captured, false); 1255 if is_captured { 1256 fc.builder.emit_reg(Op::NewCell, reg); 1257 fc.builder.emit_reg_reg(Op::CellStore, reg, src); 1258 } else { 1259 fc.builder.emit_reg_reg(Op::Move, reg, src); 1260 } 1261 } 1262 PatternKind::Object { properties, rest } => { 1263 // For each property, extract the value and bind it. 1264 // We use a single temp register that we reuse for each property 1265 // by resetting next_reg after each binding. 1266 for prop in properties { 1267 let key_name = match &prop.key { 1268 PropertyKey::Identifier(s) | PropertyKey::String(s) => s.clone(), 1269 PropertyKey::Computed(expr) => { 1270 let saved = fc.next_reg; 1271 let key_reg = fc.alloc_reg(); 1272 compile_expr(fc, expr, key_reg)?; 1273 let val_reg = fc.alloc_reg(); 1274 fc.builder.emit_reg3(Op::GetProperty, val_reg, src, key_reg); 1275 compile_destructuring_pattern(fc, &prop.value, val_reg)?; 1276 // Temp regs are buried; just let them be. 1277 let _ = saved; 1278 continue; 1279 } 1280 PropertyKey::Number(n) => { 1281 if n.fract() == 0.0 && n.abs() < 1e15 { 1282 format!("{}", *n as i64) 1283 } else { 1284 format!("{n}") 1285 } 1286 } 1287 }; 1288 // For simple identifier patterns, load property directly into 1289 // the target register to avoid LIFO register allocation issues. 1290 if let PatternKind::Identifier(name) = &prop.value.kind { 1291 let is_captured = fc.captured_names.contains(name.as_str()); 1292 let reg = fc.define_local_ext(name, is_captured, false); 1293 let name_idx = fc.builder.add_name(&key_name); 1294 if is_captured { 1295 let tmp = fc.alloc_reg(); 1296 fc.builder.emit_get_prop_name(tmp, src, name_idx); 1297 fc.builder.emit_reg(Op::NewCell, reg); 1298 fc.builder.emit_reg_reg(Op::CellStore, reg, tmp); 1299 fc.free_reg(tmp); 1300 } else { 1301 fc.builder.emit_get_prop_name(reg, src, name_idx); 1302 } 1303 } else { 1304 // Complex inner pattern (nested, default, etc.) 1305 // Allocate temp, extract value, then recurse. 1306 // Temp register won't be freed (LIFO constraint with inner locals). 1307 let val_reg = fc.alloc_reg(); 1308 let name_idx = fc.builder.add_name(&key_name); 1309 fc.builder.emit_get_prop_name(val_reg, src, name_idx); 1310 compile_destructuring_pattern(fc, &prop.value, val_reg)?; 1311 } 1312 } 1313 1314 // Handle rest: collect remaining own enumerable properties. 1315 if let Some(rest_pat) = rest { 1316 // Collect extracted key names for exclusion. 1317 let extracted_keys: Vec<String> = properties 1318 .iter() 1319 .filter_map(|prop| match &prop.key { 1320 PropertyKey::Identifier(s) | PropertyKey::String(s) => Some(s.clone()), 1321 _ => None, 1322 }) 1323 .collect(); 1324 1325 let rest_obj = fc.alloc_reg(); 1326 fc.builder.emit_reg(Op::CreateObject, rest_obj); 1327 1328 let keys_r = fc.alloc_reg(); 1329 fc.builder.emit_reg_reg(Op::ForInInit, keys_r, src); 1330 let idx_r = fc.alloc_reg(); 1331 fc.builder.emit_load_int8(idx_r, 0); 1332 let key_r = fc.alloc_reg(); 1333 let done_r = fc.alloc_reg(); 1334 1335 let loop_start = fc.builder.offset(); 1336 fc.builder 1337 .emit_reg4(Op::ForInNext, key_r, done_r, keys_r, idx_r); 1338 let exit_patch = fc.builder.emit_cond_jump(Op::JumpIfTrue, done_r); 1339 1340 let mut skip_patches = Vec::new(); 1341 for excluded in &extracted_keys { 1342 let excluded_r = fc.alloc_reg(); 1343 let ci = fc.builder.add_constant(Constant::String(excluded.clone())); 1344 fc.builder.emit_reg_u16(Op::LoadConst, excluded_r, ci); 1345 let cmp_r = fc.alloc_reg(); 1346 fc.builder.emit_reg3(Op::StrictEq, cmp_r, key_r, excluded_r); 1347 let skip = fc.builder.emit_cond_jump(Op::JumpIfTrue, cmp_r); 1348 skip_patches.push(skip); 1349 fc.free_reg(cmp_r); 1350 fc.free_reg(excluded_r); 1351 } 1352 1353 let val_r = fc.alloc_reg(); 1354 fc.builder.emit_reg3(Op::GetProperty, val_r, src, key_r); 1355 fc.builder 1356 .emit_reg3(Op::SetProperty, rest_obj, key_r, val_r); 1357 fc.free_reg(val_r); 1358 1359 for patch in skip_patches { 1360 fc.builder.patch_jump(patch); 1361 } 1362 1363 let one_r = fc.alloc_reg(); 1364 fc.builder.emit_load_int8(one_r, 1); 1365 fc.builder.emit_reg3(Op::Add, idx_r, idx_r, one_r); 1366 fc.free_reg(one_r); 1367 1368 fc.builder.emit_jump_to(loop_start); 1369 fc.builder.patch_jump(exit_patch); 1370 1371 fc.free_reg(done_r); 1372 fc.free_reg(key_r); 1373 fc.free_reg(idx_r); 1374 fc.free_reg(keys_r); 1375 1376 compile_destructuring_pattern(fc, rest_pat, rest_obj)?; 1377 } 1378 } 1379 PatternKind::Array { elements, rest } => { 1380 for (i, elem) in elements.iter().enumerate() { 1381 if let Some(pat) = elem { 1382 // For simple identifier patterns, load directly into local. 1383 if let PatternKind::Identifier(name) = &pat.kind { 1384 let is_captured = fc.captured_names.contains(name.as_str()); 1385 let reg = fc.define_local_ext(name, is_captured, false); 1386 let idx_reg = fc.alloc_reg(); 1387 if i <= 127 { 1388 fc.builder.emit_load_int8(idx_reg, i as i8); 1389 } else { 1390 let ci = fc.builder.add_constant(Constant::Number(i as f64)); 1391 fc.builder.emit_reg_u16(Op::LoadConst, idx_reg, ci); 1392 } 1393 if is_captured { 1394 let tmp = fc.alloc_reg(); 1395 fc.builder.emit_reg3(Op::GetProperty, tmp, src, idx_reg); 1396 fc.builder.emit_reg(Op::NewCell, reg); 1397 fc.builder.emit_reg_reg(Op::CellStore, reg, tmp); 1398 fc.free_reg(tmp); 1399 } else { 1400 fc.builder.emit_reg3(Op::GetProperty, reg, src, idx_reg); 1401 } 1402 fc.free_reg(idx_reg); 1403 } else { 1404 // Complex inner pattern (nested, default, etc.) 1405 let idx_reg = fc.alloc_reg(); 1406 if i <= 127 { 1407 fc.builder.emit_load_int8(idx_reg, i as i8); 1408 } else { 1409 let ci = fc.builder.add_constant(Constant::Number(i as f64)); 1410 fc.builder.emit_reg_u16(Op::LoadConst, idx_reg, ci); 1411 } 1412 let val_reg = fc.alloc_reg(); 1413 fc.builder.emit_reg3(Op::GetProperty, val_reg, src, idx_reg); 1414 compile_destructuring_pattern(fc, pat, val_reg)?; 1415 // Don't free val_reg/idx_reg — inner pattern may have 1416 // allocated locals above them. 1417 } 1418 } 1419 } 1420 1421 // Handle rest element: ...rest = src.slice(elements.len()) 1422 if let Some(rest_pat) = rest { 1423 let slice_fn_r = fc.alloc_reg(); 1424 let slice_ni = fc.builder.add_name("slice"); 1425 fc.builder.emit_get_prop_name(slice_fn_r, src, slice_ni); 1426 1427 let this_ni = fc.builder.add_name("this"); 1428 fc.builder.emit_store_global(this_ni, src); 1429 1430 let start_r = fc.alloc_reg(); 1431 let elem_count = elements.len(); 1432 if elem_count <= 127 { 1433 fc.builder.emit_load_int8(start_r, elem_count as i8); 1434 } else { 1435 let ci = fc.builder.add_constant(Constant::Number(elem_count as f64)); 1436 fc.builder.emit_reg_u16(Op::LoadConst, start_r, ci); 1437 } 1438 1439 let rest_val = fc.alloc_reg(); 1440 fc.builder.emit_call(rest_val, slice_fn_r, start_r, 1); 1441 1442 compile_destructuring_pattern(fc, rest_pat, rest_val)?; 1443 // Don't free temps — rest pattern allocates locals. 1444 } 1445 } 1446 PatternKind::Assign { left, right } => { 1447 // Default value: if src is undefined, use default. 1448 let val_reg = fc.alloc_reg(); 1449 fc.builder.emit_reg_reg(Op::Move, val_reg, src); 1450 // Check if undefined, if so use default. 1451 let check_reg = fc.alloc_reg(); 1452 let undef_reg = fc.alloc_reg(); 1453 fc.builder.emit_reg(Op::LoadUndefined, undef_reg); 1454 fc.builder 1455 .emit_reg3(Op::StrictEq, check_reg, val_reg, undef_reg); 1456 fc.free_reg(undef_reg); 1457 let patch = fc.builder.emit_cond_jump(Op::JumpIfFalse, check_reg); 1458 fc.free_reg(check_reg); 1459 // Is undefined → evaluate default. 1460 compile_expr(fc, right, val_reg)?; 1461 fc.builder.patch_jump(patch); 1462 compile_destructuring_pattern(fc, left, val_reg)?; 1463 // Don't free val_reg — inner pattern may have allocated locals. 1464 } 1465 } 1466 Ok(()) 1467} 1468 1469// ── Function declarations ─────────────────────────────────── 1470 1471fn compile_function_decl(fc: &mut FunctionCompiler, func_def: &FunctionDef) -> Result<(), JsError> { 1472 let name = func_def.id.clone().unwrap_or_default(); 1473 let inner = compile_function_body_with_captures(fc, func_def)?; 1474 let func_idx = fc.builder.add_function(inner); 1475 1476 let is_captured = fc.captured_names.contains(name.as_str()); 1477 let reg = fc.define_local_ext(&name, is_captured, false); 1478 1479 if is_captured { 1480 // Create a cell, then create the closure into a temp, then store into cell. 1481 fc.builder.emit_reg(Op::NewCell, reg); 1482 let tmp = fc.alloc_reg(); 1483 fc.builder.emit_reg_u16(Op::CreateClosure, tmp, func_idx); 1484 fc.builder.emit_reg_reg(Op::CellStore, reg, tmp); 1485 // Also store as global. 1486 if !name.is_empty() { 1487 let name_idx = fc.builder.add_name(&name); 1488 fc.builder.emit_store_global(name_idx, tmp); 1489 } 1490 fc.free_reg(tmp); 1491 } else { 1492 fc.builder.emit_reg_u16(Op::CreateClosure, reg, func_idx); 1493 // Also store as global so inner/recursive calls via LoadGlobal can find it. 1494 if !name.is_empty() { 1495 let name_idx = fc.builder.add_name(&name); 1496 fc.builder.emit_store_global(name_idx, reg); 1497 } 1498 } 1499 Ok(()) 1500} 1501 1502/// Compile a function body, resolving upvalue captures from the parent scope. 1503fn compile_function_body_with_captures( 1504 parent: &mut FunctionCompiler, 1505 func_def: &FunctionDef, 1506) -> Result<Function, JsError> { 1507 // 1. Collect free variables of this inner function. 1508 let free_vars = collect_free_vars(&func_def.params, &func_def.body); 1509 1510 // 2. Build upvalue list by resolving free vars against the parent scope. 1511 let mut upvalue_entries = Vec::new(); 1512 for name in &free_vars { 1513 if let Some(local) = parent.find_local_info(name) { 1514 let reg = local.reg; 1515 let is_const = local.is_const; 1516 // Mark the parent's local as captured (if not already). 1517 // We need to update the parent's local, so find the index and mutate. 1518 if let Some(l) = parent.locals.iter_mut().rev().find(|l| l.name == *name) { 1519 l.is_captured = true; 1520 } 1521 upvalue_entries.push(UpvalueEntry { 1522 name: name.clone(), 1523 def: UpvalueDef { 1524 is_local: true, 1525 index: reg, 1526 }, 1527 is_const, 1528 }); 1529 } else if let Some(parent_uv_idx) = parent.find_upvalue(name) { 1530 // Transitive capture: the parent captures it from its own parent. 1531 let is_const = parent.is_upvalue_const(parent_uv_idx); 1532 upvalue_entries.push(UpvalueEntry { 1533 name: name.clone(), 1534 def: UpvalueDef { 1535 is_local: false, 1536 index: parent_uv_idx, 1537 }, 1538 is_const, 1539 }); 1540 } 1541 // If not found in parent or parent's upvalues, it must be a global — no upvalue needed. 1542 } 1543 1544 // 3. Compile the inner function with its own scope. 1545 let mut inner = compile_function_body_inner(func_def, &upvalue_entries)?; 1546 1547 // 4. Attach upvalue definitions to the compiled function. 1548 inner.upvalue_defs = upvalue_entries.iter().map(|e| e.def.clone()).collect(); 1549 1550 Ok(inner) 1551} 1552 1553/// Core function body compilation. The `upvalue_entries` tell this function which 1554/// outer variables it can access via LoadUpvalue/StoreUpvalue. 1555fn compile_function_body_inner( 1556 func_def: &FunctionDef, 1557 upvalue_entries: &[UpvalueEntry], 1558) -> Result<Function, JsError> { 1559 let name = func_def.id.clone().unwrap_or_default(); 1560 let param_count = func_def.params.len().min(255) as u8; 1561 let mut inner = FunctionCompiler::new(name, param_count); 1562 1563 // Copy upvalue entries into the inner compiler so it can resolve references. 1564 for entry in upvalue_entries { 1565 inner.upvalues.push(UpvalueEntry { 1566 name: entry.name.clone(), 1567 def: entry.def.clone(), 1568 is_const: entry.is_const, 1569 }); 1570 } 1571 1572 // Pre-scan to find which of this function's locals are captured by ITS inner functions. 1573 let inner_caps = collect_inner_captures(&func_def.body); 1574 inner.captured_names = inner_caps; 1575 1576 // Allocate registers for parameters. 1577 for p in &func_def.params { 1578 if let PatternKind::Identifier(pname) = &p.kind { 1579 let is_captured = inner.captured_names.contains(pname.as_str()); 1580 inner.define_local_ext(pname, is_captured, false); 1581 } else { 1582 let _ = inner.alloc_reg(); 1583 } 1584 } 1585 1586 // Box captured parameters into cells. 1587 for p in &func_def.params { 1588 if let PatternKind::Identifier(pname) = &p.kind { 1589 if let Some(local) = inner.find_local_info(pname) { 1590 if local.is_captured { 1591 let reg = local.reg; 1592 // Move param value to temp, allocate cell, store value into cell. 1593 let tmp = inner.alloc_reg(); 1594 inner.builder.emit_reg_reg(Op::Move, tmp, reg); 1595 inner.builder.emit_reg(Op::NewCell, reg); 1596 inner.builder.emit_reg_reg(Op::CellStore, reg, tmp); 1597 inner.free_reg(tmp); 1598 } 1599 } 1600 } 1601 } 1602 1603 // Result register for the function body. 1604 let result_reg = inner.alloc_reg(); 1605 inner.builder.emit_reg(Op::LoadUndefined, result_reg); 1606 1607 compile_stmts(&mut inner, &func_def.body, result_reg)?; 1608 1609 // Implicit return undefined. 1610 inner.builder.emit_reg(Op::Return, result_reg); 1611 let mut func = inner.builder.finish(); 1612 func.is_generator = func_def.is_generator; 1613 Ok(func) 1614} 1615 1616// ── Class declarations ────────────────────────────────────── 1617 1618fn compile_class_decl(fc: &mut FunctionCompiler, class_def: &ClassDef) -> Result<(), JsError> { 1619 let name = class_def.id.clone().unwrap_or_default(); 1620 let is_captured = fc.captured_names.contains(name.as_str()); 1621 let reg = fc.define_local_ext(&name, is_captured, false); 1622 1623 // For captured classes, build the constructor into a temp register so we can 1624 // set prototype methods on it before wrapping it in a cell. 1625 let ctor_reg = if is_captured { fc.alloc_reg() } else { reg }; 1626 1627 // Find constructor or create empty one. 1628 let ctor = class_def.body.iter().find(|m| { 1629 matches!( 1630 &m.kind, 1631 ClassMemberKind::Method { 1632 kind: MethodKind::Constructor, 1633 .. 1634 } 1635 ) 1636 }); 1637 1638 if let Some(member) = ctor { 1639 if let ClassMemberKind::Method { value, .. } = &member.kind { 1640 let inner = compile_function_body_with_captures(fc, value)?; 1641 let func_idx = fc.builder.add_function(inner); 1642 fc.builder 1643 .emit_reg_u16(Op::CreateClosure, ctor_reg, func_idx); 1644 } 1645 } else { 1646 // No constructor: create a minimal function that returns undefined. 1647 let mut empty = BytecodeBuilder::new(name.clone(), 0); 1648 let r = 0u8; 1649 empty.func.register_count = 1; 1650 empty.emit_reg(Op::LoadUndefined, r); 1651 empty.emit_reg(Op::Return, r); 1652 let func_idx = fc.builder.add_function(empty.finish()); 1653 fc.builder 1654 .emit_reg_u16(Op::CreateClosure, ctor_reg, func_idx); 1655 } 1656 1657 // Compile methods: set them as properties on the constructor's prototype. 1658 // This is simplified — real class compilation needs prototype chain setup. 1659 for member in &class_def.body { 1660 match &member.kind { 1661 ClassMemberKind::Method { 1662 key, 1663 value, 1664 kind, 1665 is_static: _, 1666 computed: _, 1667 } => { 1668 if matches!(kind, MethodKind::Constructor) { 1669 continue; 1670 } 1671 let method_name = match key { 1672 PropertyKey::Identifier(s) | PropertyKey::String(s) => s.clone(), 1673 _ => continue, 1674 }; 1675 let inner = compile_function_body_with_captures(fc, value)?; 1676 let func_idx = fc.builder.add_function(inner); 1677 let method_reg = fc.alloc_reg(); 1678 fc.builder 1679 .emit_reg_u16(Op::CreateClosure, method_reg, func_idx); 1680 let name_idx = fc.builder.add_name(&method_name); 1681 fc.builder 1682 .emit_set_prop_name(ctor_reg, name_idx, method_reg); 1683 fc.free_reg(method_reg); 1684 } 1685 ClassMemberKind::Property { .. } => { 1686 // Class fields are set in constructor; skip here. 1687 } 1688 } 1689 } 1690 1691 if is_captured { 1692 fc.builder.emit_reg(Op::NewCell, reg); 1693 fc.builder.emit_reg_reg(Op::CellStore, reg, ctor_reg); 1694 fc.free_reg(ctor_reg); 1695 } 1696 1697 Ok(()) 1698} 1699 1700// ── Export ─────────────────────────────────────────────────── 1701 1702fn compile_export( 1703 fc: &mut FunctionCompiler, 1704 export: &ExportDecl, 1705 1706 result_reg: Reg, 1707) -> Result<(), JsError> { 1708 match export { 1709 ExportDecl::Declaration(stmt) => { 1710 compile_stmt(fc, stmt, result_reg)?; 1711 } 1712 ExportDecl::Default(expr) => { 1713 compile_expr(fc, expr, result_reg)?; 1714 } 1715 ExportDecl::Named { .. } | ExportDecl::AllFrom(_) => { 1716 // Named re-exports are module-level; no bytecode needed. 1717 } 1718 } 1719 Ok(()) 1720} 1721 1722// ── Control flow ──────────────────────────────────────────── 1723 1724fn compile_if( 1725 fc: &mut FunctionCompiler, 1726 test: &Expr, 1727 consequent: &Stmt, 1728 alternate: Option<&Stmt>, 1729 1730 result_reg: Reg, 1731) -> Result<(), JsError> { 1732 let cond = fc.alloc_reg(); 1733 compile_expr(fc, test, cond)?; 1734 let else_patch = fc.builder.emit_cond_jump(Op::JumpIfFalse, cond); 1735 fc.free_reg(cond); 1736 1737 compile_stmt(fc, consequent, result_reg)?; 1738 1739 if let Some(alt) = alternate { 1740 let end_patch = fc.builder.emit_jump(Op::Jump); 1741 fc.builder.patch_jump(else_patch); 1742 compile_stmt(fc, alt, result_reg)?; 1743 fc.builder.patch_jump(end_patch); 1744 } else { 1745 fc.builder.patch_jump(else_patch); 1746 } 1747 Ok(()) 1748} 1749 1750fn compile_while( 1751 fc: &mut FunctionCompiler, 1752 test: &Expr, 1753 body: &Stmt, 1754 label: Option<String>, 1755 1756 result_reg: Reg, 1757) -> Result<(), JsError> { 1758 let loop_start = fc.builder.offset(); 1759 1760 let cond = fc.alloc_reg(); 1761 compile_expr(fc, test, cond)?; 1762 let exit_patch = fc.builder.emit_cond_jump(Op::JumpIfFalse, cond); 1763 fc.free_reg(cond); 1764 1765 fc.loop_stack.push(LoopCtx { 1766 label, 1767 break_patches: Vec::new(), 1768 continue_patches: Vec::new(), 1769 }); 1770 1771 compile_stmt(fc, body, result_reg)?; 1772 fc.builder.emit_jump_to(loop_start); 1773 fc.builder.patch_jump(exit_patch); 1774 1775 let ctx = fc.loop_stack.pop().unwrap(); 1776 for patch in ctx.break_patches { 1777 fc.builder.patch_jump(patch); 1778 } 1779 // In a while loop, continue jumps back to the condition check (loop_start). 1780 for patch in ctx.continue_patches { 1781 fc.builder.patch_jump_to(patch, loop_start); 1782 } 1783 Ok(()) 1784} 1785 1786fn compile_do_while( 1787 fc: &mut FunctionCompiler, 1788 body: &Stmt, 1789 test: &Expr, 1790 label: Option<String>, 1791 1792 result_reg: Reg, 1793) -> Result<(), JsError> { 1794 let loop_start = fc.builder.offset(); 1795 1796 fc.loop_stack.push(LoopCtx { 1797 label, 1798 break_patches: Vec::new(), 1799 continue_patches: Vec::new(), 1800 }); 1801 1802 compile_stmt(fc, body, result_reg)?; 1803 1804 // continue in do-while should jump here (the condition check). 1805 let cond_start = fc.builder.offset(); 1806 1807 let cond = fc.alloc_reg(); 1808 compile_expr(fc, test, cond)?; 1809 fc.builder 1810 .emit_cond_jump_to(Op::JumpIfTrue, cond, loop_start); 1811 fc.free_reg(cond); 1812 1813 let ctx = fc.loop_stack.pop().unwrap(); 1814 for patch in ctx.break_patches { 1815 fc.builder.patch_jump(patch); 1816 } 1817 for patch in ctx.continue_patches { 1818 fc.builder.patch_jump_to(patch, cond_start); 1819 } 1820 Ok(()) 1821} 1822 1823fn compile_for( 1824 fc: &mut FunctionCompiler, 1825 init: Option<&ForInit>, 1826 test: Option<&Expr>, 1827 update: Option<&Expr>, 1828 body: &Stmt, 1829 label: Option<String>, 1830 1831 result_reg: Reg, 1832) -> Result<(), JsError> { 1833 let saved_locals = fc.locals.len(); 1834 let saved_next = fc.next_reg; 1835 1836 // Init. 1837 if let Some(init) = init { 1838 match init { 1839 ForInit::VarDecl { kind, declarators } => { 1840 for decl in declarators { 1841 compile_var_declarator(fc, decl, *kind)?; 1842 } 1843 } 1844 ForInit::Expr(expr) => { 1845 let tmp = fc.alloc_reg(); 1846 compile_expr(fc, expr, tmp)?; 1847 fc.free_reg(tmp); 1848 } 1849 } 1850 } 1851 1852 let loop_start = fc.builder.offset(); 1853 1854 // Test. 1855 let exit_patch = if let Some(test) = test { 1856 let cond = fc.alloc_reg(); 1857 compile_expr(fc, test, cond)?; 1858 let patch = fc.builder.emit_cond_jump(Op::JumpIfFalse, cond); 1859 fc.free_reg(cond); 1860 Some(patch) 1861 } else { 1862 None 1863 }; 1864 1865 fc.loop_stack.push(LoopCtx { 1866 label, 1867 break_patches: Vec::new(), 1868 continue_patches: Vec::new(), 1869 }); 1870 1871 compile_stmt(fc, body, result_reg)?; 1872 1873 // continue in a for-loop should jump here (the update expression). 1874 let continue_target = fc.builder.offset(); 1875 1876 // Update. 1877 if let Some(update) = update { 1878 let tmp = fc.alloc_reg(); 1879 compile_expr(fc, update, tmp)?; 1880 fc.free_reg(tmp); 1881 } 1882 1883 fc.builder.emit_jump_to(loop_start); 1884 1885 if let Some(patch) = exit_patch { 1886 fc.builder.patch_jump(patch); 1887 } 1888 1889 let ctx = fc.loop_stack.pop().unwrap(); 1890 for patch in ctx.break_patches { 1891 fc.builder.patch_jump(patch); 1892 } 1893 for patch in ctx.continue_patches { 1894 fc.builder.patch_jump_to(patch, continue_target); 1895 } 1896 1897 fc.locals.truncate(saved_locals); 1898 fc.next_reg = saved_next; 1899 Ok(()) 1900} 1901 1902fn compile_switch( 1903 fc: &mut FunctionCompiler, 1904 discriminant: &Expr, 1905 cases: &[SwitchCase], 1906 1907 result_reg: Reg, 1908) -> Result<(), JsError> { 1909 let disc_reg = fc.alloc_reg(); 1910 compile_expr(fc, discriminant, disc_reg)?; 1911 1912 // Use a loop context for break statements. 1913 fc.loop_stack.push(LoopCtx { 1914 label: None, 1915 break_patches: Vec::new(), 1916 continue_patches: Vec::new(), 1917 }); 1918 1919 // Phase 1: emit comparison jumps for each non-default case. 1920 // Store (case_index, patch_position) for each case with a test. 1921 let mut case_jump_patches: Vec<(usize, usize)> = Vec::new(); 1922 let mut default_index: Option<usize> = None; 1923 1924 for (i, case) in cases.iter().enumerate() { 1925 if let Some(test) = &case.test { 1926 let test_reg = fc.alloc_reg(); 1927 compile_expr(fc, test, test_reg)?; 1928 let cmp_reg = fc.alloc_reg(); 1929 fc.builder 1930 .emit_reg3(Op::StrictEq, cmp_reg, disc_reg, test_reg); 1931 let patch = fc.builder.emit_cond_jump(Op::JumpIfTrue, cmp_reg); 1932 fc.free_reg(cmp_reg); 1933 fc.free_reg(test_reg); 1934 case_jump_patches.push((i, patch)); 1935 } else { 1936 default_index = Some(i); 1937 } 1938 } 1939 1940 // After all comparisons: jump to default body or end. 1941 let fallthrough_patch = fc.builder.emit_jump(Op::Jump); 1942 1943 // Phase 2: emit case bodies in order (fall-through semantics). 1944 let mut body_offsets: Vec<(usize, usize)> = Vec::new(); 1945 for (i, case) in cases.iter().enumerate() { 1946 body_offsets.push((i, fc.builder.offset())); 1947 compile_stmts(fc, &case.consequent, result_reg)?; 1948 } 1949 1950 let end_offset = fc.builder.offset(); 1951 1952 // Patch case test jumps to their respective body offsets. 1953 for (case_idx, patch) in &case_jump_patches { 1954 let body_offset = body_offsets 1955 .iter() 1956 .find(|(i, _)| i == case_idx) 1957 .map(|(_, off)| *off) 1958 .unwrap(); 1959 fc.builder.patch_jump_to(*patch, body_offset); 1960 } 1961 1962 // Patch fallthrough: jump to default body if present, otherwise to end. 1963 if let Some(def_idx) = default_index { 1964 let default_offset = body_offsets 1965 .iter() 1966 .find(|(i, _)| *i == def_idx) 1967 .map(|(_, off)| *off) 1968 .unwrap(); 1969 fc.builder.patch_jump_to(fallthrough_patch, default_offset); 1970 } else { 1971 fc.builder.patch_jump_to(fallthrough_patch, end_offset); 1972 } 1973 1974 fc.free_reg(disc_reg); 1975 1976 let ctx = fc.loop_stack.pop().unwrap(); 1977 for patch in ctx.break_patches { 1978 fc.builder.patch_jump(patch); 1979 } 1980 Ok(()) 1981} 1982 1983fn find_loop_ctx(stack: &[LoopCtx], label: Option<&str>) -> Option<usize> { 1984 if let Some(label) = label { 1985 stack 1986 .iter() 1987 .rposition(|ctx| ctx.label.as_deref() == Some(label)) 1988 } else { 1989 if stack.is_empty() { 1990 None 1991 } else { 1992 Some(stack.len() - 1) 1993 } 1994 } 1995} 1996 1997// ── Expressions ───────────────────────────────────────────── 1998 1999fn compile_expr(fc: &mut FunctionCompiler, expr: &Expr, dst: Reg) -> Result<(), JsError> { 2000 match &expr.kind { 2001 ExprKind::Number(n) => { 2002 // Optimize small integers. 2003 let int_val = *n as i64; 2004 if int_val as f64 == *n && (-128..=127).contains(&int_val) { 2005 fc.builder.emit_load_int8(dst, int_val as i8); 2006 } else { 2007 let ci = fc.builder.add_constant(Constant::Number(*n)); 2008 fc.builder.emit_reg_u16(Op::LoadConst, dst, ci); 2009 } 2010 } 2011 2012 ExprKind::String(s) => { 2013 let ci = fc.builder.add_constant(Constant::String(s.clone())); 2014 fc.builder.emit_reg_u16(Op::LoadConst, dst, ci); 2015 } 2016 2017 ExprKind::Bool(true) => { 2018 fc.builder.emit_reg(Op::LoadTrue, dst); 2019 } 2020 2021 ExprKind::Bool(false) => { 2022 fc.builder.emit_reg(Op::LoadFalse, dst); 2023 } 2024 2025 ExprKind::Null => { 2026 fc.builder.emit_reg(Op::LoadNull, dst); 2027 } 2028 2029 ExprKind::Identifier(name) => { 2030 if let Some(local) = fc.find_local_info(name) { 2031 let reg = local.reg; 2032 let captured = local.is_captured; 2033 if captured { 2034 fc.builder.emit_reg_reg(Op::CellLoad, dst, reg); 2035 } else if reg != dst { 2036 fc.builder.emit_reg_reg(Op::Move, dst, reg); 2037 } 2038 } else if let Some(uv_idx) = fc.find_upvalue(name) { 2039 fc.builder.emit_load_upvalue(dst, uv_idx); 2040 } else { 2041 // Global lookup. 2042 let ni = fc.builder.add_name(name); 2043 fc.builder.emit_load_global(dst, ni); 2044 } 2045 } 2046 2047 ExprKind::This => { 2048 // `this` is loaded as a global named "this" (the VM binds it). 2049 let ni = fc.builder.add_name("this"); 2050 fc.builder.emit_load_global(dst, ni); 2051 } 2052 2053 ExprKind::Binary { op, left, right } => { 2054 let lhs = fc.alloc_reg(); 2055 compile_expr(fc, left, lhs)?; 2056 let rhs = fc.alloc_reg(); 2057 compile_expr(fc, right, rhs)?; 2058 let bytecode_op = binary_op_to_opcode(*op); 2059 fc.builder.emit_reg3(bytecode_op, dst, lhs, rhs); 2060 fc.free_reg(rhs); 2061 fc.free_reg(lhs); 2062 } 2063 2064 ExprKind::Unary { op, argument } => { 2065 if *op == UnaryOp::Delete { 2066 // Handle delete specially: need object + key form for member expressions. 2067 match &argument.kind { 2068 ExprKind::Member { 2069 object, 2070 property, 2071 computed, 2072 } => { 2073 let obj_r = fc.alloc_reg(); 2074 compile_expr(fc, object, obj_r)?; 2075 let key_r = fc.alloc_reg(); 2076 if *computed { 2077 compile_expr(fc, property, key_r)?; 2078 } else if let ExprKind::Identifier(name) = &property.kind { 2079 let ci = fc.builder.add_constant(Constant::String(name.clone())); 2080 fc.builder.emit_reg_u16(Op::LoadConst, key_r, ci); 2081 } else { 2082 compile_expr(fc, property, key_r)?; 2083 } 2084 fc.builder.emit_reg3(Op::Delete, dst, obj_r, key_r); 2085 fc.free_reg(key_r); 2086 fc.free_reg(obj_r); 2087 } 2088 _ => { 2089 // `delete x` on a simple identifier: always true in non-strict mode. 2090 fc.builder.emit_reg(Op::LoadTrue, dst); 2091 } 2092 } 2093 } else { 2094 let src = fc.alloc_reg(); 2095 compile_expr(fc, argument, src)?; 2096 match op { 2097 UnaryOp::Minus => fc.builder.emit_reg_reg(Op::Neg, dst, src), 2098 UnaryOp::Plus => { 2099 fc.builder.emit_reg_reg(Op::Move, dst, src); 2100 } 2101 UnaryOp::Not => fc.builder.emit_reg_reg(Op::LogicalNot, dst, src), 2102 UnaryOp::BitwiseNot => fc.builder.emit_reg_reg(Op::BitNot, dst, src), 2103 UnaryOp::Typeof => fc.builder.emit_reg_reg(Op::TypeOf, dst, src), 2104 UnaryOp::Void => fc.builder.emit_reg_reg(Op::Void, dst, src), 2105 UnaryOp::Delete => unreachable!(), 2106 } 2107 fc.free_reg(src); 2108 } 2109 } 2110 2111 ExprKind::Update { 2112 op, 2113 argument, 2114 prefix, 2115 } => { 2116 // Get current value. 2117 compile_expr(fc, argument, dst)?; 2118 2119 let one = fc.alloc_reg(); 2120 fc.builder.emit_load_int8(one, 1); 2121 2122 if *prefix { 2123 // ++x / --x: modify first, return modified. 2124 match op { 2125 UpdateOp::Increment => fc.builder.emit_reg3(Op::Add, dst, dst, one), 2126 UpdateOp::Decrement => fc.builder.emit_reg3(Op::Sub, dst, dst, one), 2127 } 2128 // Store back. 2129 compile_store(fc, argument, dst)?; 2130 } else { 2131 // x++ / x--: return original, then modify. 2132 let tmp = fc.alloc_reg(); 2133 fc.builder.emit_reg_reg(Op::Move, tmp, dst); 2134 match op { 2135 UpdateOp::Increment => fc.builder.emit_reg3(Op::Add, tmp, tmp, one), 2136 UpdateOp::Decrement => fc.builder.emit_reg3(Op::Sub, tmp, tmp, one), 2137 } 2138 compile_store(fc, argument, tmp)?; 2139 fc.free_reg(tmp); 2140 } 2141 fc.free_reg(one); 2142 } 2143 2144 ExprKind::Logical { op, left, right } => { 2145 compile_expr(fc, left, dst)?; 2146 match op { 2147 LogicalOp::And => { 2148 // Short-circuit: if falsy, skip right. 2149 let skip = fc.builder.emit_cond_jump(Op::JumpIfFalse, dst); 2150 compile_expr(fc, right, dst)?; 2151 fc.builder.patch_jump(skip); 2152 } 2153 LogicalOp::Or => { 2154 let skip = fc.builder.emit_cond_jump(Op::JumpIfTrue, dst); 2155 compile_expr(fc, right, dst)?; 2156 fc.builder.patch_jump(skip); 2157 } 2158 LogicalOp::Nullish => { 2159 let skip = fc.builder.emit_cond_jump(Op::JumpIfNullish, dst); 2160 // If NOT nullish, skip the right side. Wait — JumpIfNullish 2161 // should mean "jump if nullish" so we want: evaluate left, 2162 // if NOT nullish skip right. 2163 // Let's invert: evaluate left, check if nullish → evaluate right. 2164 // We need the jump to skip the "evaluate right" if NOT nullish. 2165 // Since JumpIfNullish jumps when nullish, we need the inverse. 2166 // Instead: use a two-step approach. 2167 // 2168 // Actually, rethink: for `a ?? b`: 2169 // 1. evaluate a → dst 2170 // 2. if dst is NOT null/undefined, jump to end 2171 // 3. evaluate b → dst 2172 // end: 2173 // JumpIfNullish jumps when IS nullish. So we want jump when NOT nullish. 2174 // Let's just use a "not nullish" check. 2175 // For now: negate and use JumpIfFalse. 2176 // Actually simpler: skip right when not nullish. 2177 // JumpIfNullish jumps WHEN nullish. We want to jump over right when NOT nullish. 2178 // So: 2179 // evaluate a → dst 2180 // JumpIfNullish dst → evaluate_right 2181 // Jump → end 2182 // evaluate_right: evaluate b → dst 2183 // end: 2184 // But we already emitted JumpIfNullish. Let's fix this. 2185 // The JumpIfNullish we emitted jumps to "after patch", which is where 2186 // we'll put the right-side code. We need another jump to skip right. 2187 let end_patch = fc.builder.emit_jump(Op::Jump); 2188 fc.builder.patch_jump(skip); // nullish → evaluate right 2189 compile_expr(fc, right, dst)?; 2190 fc.builder.patch_jump(end_patch); 2191 } 2192 } 2193 } 2194 2195 ExprKind::Assignment { op, left, right } => { 2196 if *op == AssignOp::Assign { 2197 compile_expr(fc, right, dst)?; 2198 compile_store(fc, left, dst)?; 2199 } else { 2200 // Compound assignment: load current, operate, store. 2201 compile_expr(fc, left, dst)?; 2202 let rhs = fc.alloc_reg(); 2203 compile_expr(fc, right, rhs)?; 2204 let arith_op = compound_assign_op(*op); 2205 fc.builder.emit_reg3(arith_op, dst, dst, rhs); 2206 fc.free_reg(rhs); 2207 compile_store(fc, left, dst)?; 2208 } 2209 } 2210 2211 ExprKind::Conditional { 2212 test, 2213 consequent, 2214 alternate, 2215 } => { 2216 let cond = fc.alloc_reg(); 2217 compile_expr(fc, test, cond)?; 2218 let else_patch = fc.builder.emit_cond_jump(Op::JumpIfFalse, cond); 2219 fc.free_reg(cond); 2220 compile_expr(fc, consequent, dst)?; 2221 let end_patch = fc.builder.emit_jump(Op::Jump); 2222 fc.builder.patch_jump(else_patch); 2223 compile_expr(fc, alternate, dst)?; 2224 fc.builder.patch_jump(end_patch); 2225 } 2226 2227 ExprKind::Call { callee, arguments } => { 2228 // Detect method calls (obj.method()) to set `this`. 2229 if let ExprKind::Member { 2230 object, 2231 property, 2232 computed, 2233 } = &callee.kind 2234 { 2235 // Layout: [obj_reg] [func_reg] [arg0] [arg1] ... 2236 // We keep obj_reg alive so we can set `this` before the call. 2237 let obj_reg = fc.alloc_reg(); 2238 compile_expr(fc, object, obj_reg)?; 2239 let func_reg = fc.alloc_reg(); 2240 if !computed { 2241 if let ExprKind::Identifier(name) = &property.kind { 2242 let ni = fc.builder.add_name(name); 2243 fc.builder.emit_get_prop_name(func_reg, obj_reg, ni); 2244 } else { 2245 let key_reg = fc.alloc_reg(); 2246 compile_expr(fc, property, key_reg)?; 2247 fc.builder 2248 .emit_reg3(Op::GetProperty, func_reg, obj_reg, key_reg); 2249 fc.free_reg(key_reg); 2250 } 2251 } else { 2252 let key_reg = fc.alloc_reg(); 2253 compile_expr(fc, property, key_reg)?; 2254 fc.builder 2255 .emit_reg3(Op::GetProperty, func_reg, obj_reg, key_reg); 2256 fc.free_reg(key_reg); 2257 } 2258 2259 // Set `this` to the receiver object before calling. 2260 let this_ni = fc.builder.add_name("this"); 2261 fc.builder.emit_store_global(this_ni, obj_reg); 2262 2263 let args_start = fc.next_reg; 2264 let arg_count = arguments.len().min(255) as u8; 2265 for arg in arguments { 2266 let arg_reg = fc.alloc_reg(); 2267 compile_expr(fc, arg, arg_reg)?; 2268 } 2269 2270 fc.builder.emit_call(dst, func_reg, args_start, arg_count); 2271 2272 // Free in LIFO order: args, func_reg, obj_reg. 2273 for _ in 0..arg_count { 2274 fc.next_reg -= 1; 2275 } 2276 fc.free_reg(func_reg); 2277 fc.free_reg(obj_reg); 2278 } else { 2279 let func_reg = fc.alloc_reg(); 2280 compile_expr(fc, callee, func_reg)?; 2281 2282 let args_start = fc.next_reg; 2283 let arg_count = arguments.len().min(255) as u8; 2284 for arg in arguments { 2285 let arg_reg = fc.alloc_reg(); 2286 compile_expr(fc, arg, arg_reg)?; 2287 } 2288 2289 fc.builder.emit_call(dst, func_reg, args_start, arg_count); 2290 2291 for _ in 0..arg_count { 2292 fc.next_reg -= 1; 2293 } 2294 fc.free_reg(func_reg); 2295 } 2296 } 2297 2298 ExprKind::New { callee, arguments } => { 2299 // For now, compile like a regular call. The VM will differentiate 2300 // based on the `New` vs `Call` distinction (TODO: add NewCall opcode). 2301 let func_reg = fc.alloc_reg(); 2302 compile_expr(fc, callee, func_reg)?; 2303 2304 let args_start = fc.next_reg; 2305 let arg_count = arguments.len().min(255) as u8; 2306 for arg in arguments { 2307 let arg_reg = fc.alloc_reg(); 2308 compile_expr(fc, arg, arg_reg)?; 2309 } 2310 2311 fc.builder.emit_call(dst, func_reg, args_start, arg_count); 2312 2313 for _ in 0..arg_count { 2314 fc.next_reg -= 1; 2315 } 2316 fc.free_reg(func_reg); 2317 } 2318 2319 ExprKind::Member { 2320 object, 2321 property, 2322 computed, 2323 } => { 2324 let obj_reg = fc.alloc_reg(); 2325 compile_expr(fc, object, obj_reg)?; 2326 2327 if !computed { 2328 // Static member: obj.prop → GetPropertyByName. 2329 if let ExprKind::Identifier(name) = &property.kind { 2330 let ni = fc.builder.add_name(name); 2331 fc.builder.emit_get_prop_name(dst, obj_reg, ni); 2332 } else { 2333 let key_reg = fc.alloc_reg(); 2334 compile_expr(fc, property, key_reg)?; 2335 fc.builder.emit_reg3(Op::GetProperty, dst, obj_reg, key_reg); 2336 fc.free_reg(key_reg); 2337 } 2338 } else { 2339 // Computed member: obj[expr]. 2340 let key_reg = fc.alloc_reg(); 2341 compile_expr(fc, property, key_reg)?; 2342 fc.builder.emit_reg3(Op::GetProperty, dst, obj_reg, key_reg); 2343 fc.free_reg(key_reg); 2344 } 2345 fc.free_reg(obj_reg); 2346 } 2347 2348 ExprKind::Array(elements) => { 2349 let has_spread = elements 2350 .iter() 2351 .any(|e| matches!(e, Some(ArrayElement::Spread(_)))); 2352 2353 fc.builder.emit_reg(Op::CreateArray, dst); 2354 2355 if has_spread { 2356 // When spreads are present, we track the index dynamically. 2357 // For each normal element, push at current length. 2358 // For spread elements, use the Spread opcode. 2359 for el in elements.iter().flatten() { 2360 match el { 2361 ArrayElement::Expr(e) => { 2362 let val_reg = fc.alloc_reg(); 2363 compile_expr(fc, e, val_reg)?; 2364 // Get current length as index. 2365 let idx_reg = fc.alloc_reg(); 2366 let len_ni = fc.builder.add_name("length"); 2367 fc.builder.emit_get_prop_name(idx_reg, dst, len_ni); 2368 fc.builder.emit_reg3(Op::SetProperty, dst, idx_reg, val_reg); 2369 // Increment length. 2370 let one_r = fc.alloc_reg(); 2371 fc.builder.emit_load_int8(one_r, 1); 2372 fc.builder.emit_reg3(Op::Add, idx_reg, idx_reg, one_r); 2373 fc.builder.emit_set_prop_name(dst, len_ni, idx_reg); 2374 fc.free_reg(one_r); 2375 fc.free_reg(idx_reg); 2376 fc.free_reg(val_reg); 2377 } 2378 ArrayElement::Spread(e) => { 2379 let spread_src = fc.alloc_reg(); 2380 compile_expr(fc, e, spread_src)?; 2381 fc.builder.emit_spread(dst, spread_src); 2382 fc.free_reg(spread_src); 2383 } 2384 } 2385 } 2386 } else { 2387 // No spreads: use simple indexed assignment. 2388 for (i, elem) in elements.iter().enumerate() { 2389 if let Some(ArrayElement::Expr(e)) = elem { 2390 let val_reg = fc.alloc_reg(); 2391 compile_expr(fc, e, val_reg)?; 2392 let idx_reg = fc.alloc_reg(); 2393 if i <= 127 { 2394 fc.builder.emit_load_int8(idx_reg, i as i8); 2395 } else { 2396 let ci = fc.builder.add_constant(Constant::Number(i as f64)); 2397 fc.builder.emit_reg_u16(Op::LoadConst, idx_reg, ci); 2398 } 2399 fc.builder.emit_reg3(Op::SetProperty, dst, idx_reg, val_reg); 2400 fc.free_reg(idx_reg); 2401 fc.free_reg(val_reg); 2402 } 2403 } 2404 // Set length. 2405 if !elements.is_empty() { 2406 let len_name = fc.builder.add_name("length"); 2407 let len_reg = fc.alloc_reg(); 2408 if elements.len() <= 127 { 2409 fc.builder.emit_load_int8(len_reg, elements.len() as i8); 2410 } else { 2411 let ci = fc 2412 .builder 2413 .add_constant(Constant::Number(elements.len() as f64)); 2414 fc.builder.emit_reg_u16(Op::LoadConst, len_reg, ci); 2415 } 2416 fc.builder.emit_set_prop_name(dst, len_name, len_reg); 2417 fc.free_reg(len_reg); 2418 } 2419 } 2420 } 2421 2422 ExprKind::Object(properties) => { 2423 fc.builder.emit_reg(Op::CreateObject, dst); 2424 for prop in properties { 2425 let val_reg = fc.alloc_reg(); 2426 if let Some(value) = &prop.value { 2427 compile_expr(fc, value, val_reg)?; 2428 } else { 2429 // Shorthand: `{ x }` means `{ x: x }`. 2430 if let PropertyKey::Identifier(name) = &prop.key { 2431 if let Some(local) = fc.find_local_info(name) { 2432 let reg = local.reg; 2433 let captured = local.is_captured; 2434 if captured { 2435 fc.builder.emit_reg_reg(Op::CellLoad, val_reg, reg); 2436 } else { 2437 fc.builder.emit_reg_reg(Op::Move, val_reg, reg); 2438 } 2439 } else if let Some(uv_idx) = fc.find_upvalue(name) { 2440 fc.builder.emit_load_upvalue(val_reg, uv_idx); 2441 } else { 2442 let ni = fc.builder.add_name(name); 2443 fc.builder.emit_load_global(val_reg, ni); 2444 } 2445 } else { 2446 fc.builder.emit_reg(Op::LoadUndefined, val_reg); 2447 } 2448 } 2449 2450 match &prop.key { 2451 PropertyKey::Identifier(name) | PropertyKey::String(name) => { 2452 let ni = fc.builder.add_name(name); 2453 fc.builder.emit_set_prop_name(dst, ni, val_reg); 2454 } 2455 PropertyKey::Number(n) => { 2456 let key_reg = fc.alloc_reg(); 2457 let ci = fc.builder.add_constant(Constant::Number(*n)); 2458 fc.builder.emit_reg_u16(Op::LoadConst, key_reg, ci); 2459 fc.builder.emit_reg3(Op::SetProperty, dst, key_reg, val_reg); 2460 fc.free_reg(key_reg); 2461 } 2462 PropertyKey::Computed(expr) => { 2463 let key_reg = fc.alloc_reg(); 2464 compile_expr(fc, expr, key_reg)?; 2465 fc.builder.emit_reg3(Op::SetProperty, dst, key_reg, val_reg); 2466 fc.free_reg(key_reg); 2467 } 2468 } 2469 fc.free_reg(val_reg); 2470 } 2471 } 2472 2473 ExprKind::Function(func_def) => { 2474 let inner = compile_function_body_with_captures(fc, func_def)?; 2475 let func_idx = fc.builder.add_function(inner); 2476 fc.builder.emit_reg_u16(Op::CreateClosure, dst, func_idx); 2477 } 2478 2479 ExprKind::Arrow { 2480 params, 2481 body, 2482 is_async: _, 2483 } => { 2484 // Collect free variables from the arrow body. 2485 let free_vars = collect_free_vars_arrow(params, body); 2486 2487 // Resolve upvalues against the parent scope. 2488 let mut upvalue_entries = Vec::new(); 2489 for name in &free_vars { 2490 if let Some(local) = fc.find_local_info(name) { 2491 let reg = local.reg; 2492 let is_const = local.is_const; 2493 if let Some(l) = fc.locals.iter_mut().rev().find(|l| l.name == *name) { 2494 l.is_captured = true; 2495 } 2496 upvalue_entries.push(UpvalueEntry { 2497 name: name.clone(), 2498 def: UpvalueDef { 2499 is_local: true, 2500 index: reg, 2501 }, 2502 is_const, 2503 }); 2504 } else if let Some(parent_uv_idx) = fc.find_upvalue(name) { 2505 let is_const = fc.is_upvalue_const(parent_uv_idx); 2506 upvalue_entries.push(UpvalueEntry { 2507 name: name.clone(), 2508 def: UpvalueDef { 2509 is_local: false, 2510 index: parent_uv_idx, 2511 }, 2512 is_const, 2513 }); 2514 } 2515 } 2516 2517 let param_count = params.len().min(255) as u8; 2518 let mut inner = FunctionCompiler::new("<arrow>".into(), param_count); 2519 2520 // Copy upvalue entries. 2521 for entry in &upvalue_entries { 2522 inner.upvalues.push(UpvalueEntry { 2523 name: entry.name.clone(), 2524 def: entry.def.clone(), 2525 is_const: entry.is_const, 2526 }); 2527 } 2528 2529 // Pre-scan for inner captures within the arrow body. 2530 match body { 2531 ArrowBody::Expr(_) => {} 2532 ArrowBody::Block(stmts) => { 2533 inner.captured_names = collect_inner_captures(stmts); 2534 } 2535 } 2536 2537 for p in params { 2538 if let PatternKind::Identifier(pname) = &p.kind { 2539 let is_captured = inner.captured_names.contains(pname.as_str()); 2540 inner.define_local_ext(pname, is_captured, false); 2541 } else { 2542 let _ = inner.alloc_reg(); 2543 } 2544 } 2545 2546 // Box captured parameters. 2547 for p in params { 2548 if let PatternKind::Identifier(pname) = &p.kind { 2549 if let Some(local) = inner.find_local_info(pname) { 2550 if local.is_captured { 2551 let reg = local.reg; 2552 let tmp = inner.alloc_reg(); 2553 inner.builder.emit_reg_reg(Op::Move, tmp, reg); 2554 inner.builder.emit_reg(Op::NewCell, reg); 2555 inner.builder.emit_reg_reg(Op::CellStore, reg, tmp); 2556 inner.free_reg(tmp); 2557 } 2558 } 2559 } 2560 } 2561 2562 let result = inner.alloc_reg(); 2563 match body { 2564 ArrowBody::Expr(e) => { 2565 compile_expr(&mut inner, e, result)?; 2566 } 2567 ArrowBody::Block(stmts) => { 2568 inner.builder.emit_reg(Op::LoadUndefined, result); 2569 compile_stmts(&mut inner, stmts, result)?; 2570 } 2571 } 2572 inner.builder.emit_reg(Op::Return, result); 2573 let mut inner_func = inner.builder.finish(); 2574 inner_func.upvalue_defs = upvalue_entries.iter().map(|e| e.def.clone()).collect(); 2575 let func_idx = fc.builder.add_function(inner_func); 2576 fc.builder.emit_reg_u16(Op::CreateClosure, dst, func_idx); 2577 } 2578 2579 ExprKind::Class(class_def) => { 2580 // Class expression: compile like class decl but into dst. 2581 let name = class_def.id.clone().unwrap_or_default(); 2582 // Find constructor. 2583 let ctor = class_def.body.iter().find(|m| { 2584 matches!( 2585 &m.kind, 2586 ClassMemberKind::Method { 2587 kind: MethodKind::Constructor, 2588 .. 2589 } 2590 ) 2591 }); 2592 if let Some(member) = ctor { 2593 if let ClassMemberKind::Method { value, .. } = &member.kind { 2594 let inner = compile_function_body_with_captures(fc, value)?; 2595 let func_idx = fc.builder.add_function(inner); 2596 fc.builder.emit_reg_u16(Op::CreateClosure, dst, func_idx); 2597 } 2598 } else { 2599 let mut empty = BytecodeBuilder::new(name, 0); 2600 let r = 0u8; 2601 empty.func.register_count = 1; 2602 empty.emit_reg(Op::LoadUndefined, r); 2603 empty.emit_reg(Op::Return, r); 2604 let func_idx = fc.builder.add_function(empty.finish()); 2605 fc.builder.emit_reg_u16(Op::CreateClosure, dst, func_idx); 2606 } 2607 2608 // Compile methods as properties on the constructor. 2609 for member in &class_def.body { 2610 match &member.kind { 2611 ClassMemberKind::Method { 2612 key, 2613 value, 2614 kind, 2615 is_static: _, 2616 computed: _, 2617 } => { 2618 if matches!(kind, MethodKind::Constructor) { 2619 continue; 2620 } 2621 let method_name = match key { 2622 PropertyKey::Identifier(s) | PropertyKey::String(s) => s.clone(), 2623 _ => continue, 2624 }; 2625 let inner = compile_function_body_with_captures(fc, value)?; 2626 let func_idx = fc.builder.add_function(inner); 2627 let method_reg = fc.alloc_reg(); 2628 fc.builder 2629 .emit_reg_u16(Op::CreateClosure, method_reg, func_idx); 2630 let name_idx = fc.builder.add_name(&method_name); 2631 fc.builder.emit_set_prop_name(dst, name_idx, method_reg); 2632 fc.free_reg(method_reg); 2633 } 2634 ClassMemberKind::Property { .. } => {} 2635 } 2636 } 2637 } 2638 2639 ExprKind::Sequence(exprs) => { 2640 for e in exprs { 2641 compile_expr(fc, e, dst)?; 2642 } 2643 } 2644 2645 ExprKind::Spread(inner) => { 2646 compile_expr(fc, inner, dst)?; 2647 } 2648 2649 ExprKind::TemplateLiteral { 2650 quasis, 2651 expressions, 2652 } => { 2653 // Compile template literal as string concatenation. 2654 if quasis.len() == 1 && expressions.is_empty() { 2655 let ci = fc.builder.add_constant(Constant::String(quasis[0].clone())); 2656 fc.builder.emit_reg_u16(Op::LoadConst, dst, ci); 2657 } else { 2658 // Start with first quasi. 2659 let ci = fc.builder.add_constant(Constant::String(quasis[0].clone())); 2660 fc.builder.emit_reg_u16(Op::LoadConst, dst, ci); 2661 for (i, expr) in expressions.iter().enumerate() { 2662 let tmp = fc.alloc_reg(); 2663 compile_expr(fc, expr, tmp)?; 2664 fc.builder.emit_reg3(Op::Add, dst, dst, tmp); 2665 fc.free_reg(tmp); 2666 if i + 1 < quasis.len() { 2667 let qi = fc 2668 .builder 2669 .add_constant(Constant::String(quasis[i + 1].clone())); 2670 let tmp2 = fc.alloc_reg(); 2671 fc.builder.emit_reg_u16(Op::LoadConst, tmp2, qi); 2672 fc.builder.emit_reg3(Op::Add, dst, dst, tmp2); 2673 fc.free_reg(tmp2); 2674 } 2675 } 2676 } 2677 } 2678 2679 ExprKind::TaggedTemplate { tag, quasi } => { 2680 // Simplified: call tag with the template as argument. 2681 let func_reg = fc.alloc_reg(); 2682 compile_expr(fc, tag, func_reg)?; 2683 let arg_reg = fc.alloc_reg(); 2684 compile_expr(fc, quasi, arg_reg)?; 2685 fc.builder.emit_call(dst, func_reg, arg_reg, 1); 2686 fc.free_reg(arg_reg); 2687 fc.free_reg(func_reg); 2688 } 2689 2690 ExprKind::Yield { argument, delegate } => { 2691 if *delegate { 2692 // yield* expr: iterate the sub-iterator and yield each value. 2693 let iter_r = fc.alloc_reg(); 2694 if let Some(arg) = argument { 2695 compile_expr(fc, arg, iter_r)?; 2696 } else { 2697 fc.builder.emit_reg(Op::LoadUndefined, iter_r); 2698 } 2699 2700 // Get iterator from the expression. 2701 let iter_method_r = fc.alloc_reg(); 2702 let sym_iter_ni = fc.builder.add_name("@@iterator"); 2703 fc.builder 2704 .emit_get_prop_name(iter_method_r, iter_r, sym_iter_ni); 2705 let this_ni = fc.builder.add_name("this"); 2706 fc.builder.emit_store_global(this_ni, iter_r); 2707 let iterator_r = fc.alloc_reg(); 2708 let args_start = fc.next_reg; 2709 fc.builder 2710 .emit_call(iterator_r, iter_method_r, args_start, 0); 2711 2712 // Get next method. 2713 let next_r = fc.alloc_reg(); 2714 let next_ni = fc.builder.add_name("next"); 2715 fc.builder.emit_get_prop_name(next_r, iterator_r, next_ni); 2716 2717 let result_r = fc.alloc_reg(); 2718 let done_r = fc.alloc_reg(); 2719 let val_r = fc.alloc_reg(); 2720 2721 let loop_start = fc.builder.offset(); 2722 2723 // Call next(). 2724 fc.builder.emit_store_global(this_ni, iterator_r); 2725 fc.builder.emit_call(result_r, next_r, args_start, 0); 2726 2727 let done_ni = fc.builder.add_name("done"); 2728 let value_ni = fc.builder.add_name("value"); 2729 fc.builder.emit_get_prop_name(done_r, result_r, done_ni); 2730 2731 let exit_patch = fc.builder.emit_cond_jump(Op::JumpIfTrue, done_r); 2732 2733 fc.builder.emit_get_prop_name(val_r, result_r, value_ni); 2734 2735 // Yield the value. 2736 fc.builder.emit_yield(dst, val_r); 2737 2738 // Jump back. 2739 fc.builder.emit_jump_to(loop_start); 2740 2741 // Exit: the last result's value is the yield* expression value. 2742 fc.builder.patch_jump(exit_patch); 2743 fc.builder.emit_get_prop_name(dst, result_r, value_ni); 2744 2745 fc.free_reg(val_r); 2746 fc.free_reg(done_r); 2747 fc.free_reg(result_r); 2748 fc.free_reg(next_r); 2749 fc.free_reg(iterator_r); 2750 fc.free_reg(iter_method_r); 2751 fc.free_reg(iter_r); 2752 } else { 2753 // yield expr: emit Yield opcode. 2754 let src = fc.alloc_reg(); 2755 if let Some(arg) = argument { 2756 compile_expr(fc, arg, src)?; 2757 } else { 2758 fc.builder.emit_reg(Op::LoadUndefined, src); 2759 } 2760 fc.builder.emit_yield(dst, src); 2761 fc.free_reg(src); 2762 } 2763 } 2764 2765 ExprKind::Await(inner) => { 2766 // Await is a VM-level operation; compile the argument. 2767 compile_expr(fc, inner, dst)?; 2768 } 2769 2770 ExprKind::RegExp { pattern, flags } => { 2771 // Compile as: RegExp(pattern, flags) — a call to the global constructor. 2772 let func_reg = fc.alloc_reg(); 2773 let name_idx = fc.builder.add_name("RegExp"); 2774 fc.builder.emit_reg_u16(Op::LoadGlobal, func_reg, name_idx); 2775 2776 let args_start = fc.next_reg; 2777 let pat_reg = fc.alloc_reg(); 2778 let pat_idx = fc.builder.add_constant(Constant::String(pattern.clone())); 2779 fc.builder.emit_reg_u16(Op::LoadConst, pat_reg, pat_idx); 2780 2781 let flags_reg = fc.alloc_reg(); 2782 let flags_idx = fc.builder.add_constant(Constant::String(flags.clone())); 2783 fc.builder.emit_reg_u16(Op::LoadConst, flags_reg, flags_idx); 2784 2785 fc.builder.emit_call(dst, func_reg, args_start, 2); 2786 2787 fc.next_reg -= 1; // flags_reg 2788 fc.next_reg -= 1; // pat_reg 2789 fc.free_reg(func_reg); 2790 } 2791 2792 ExprKind::OptionalChain { base } => { 2793 compile_expr(fc, base, dst)?; 2794 } 2795 } 2796 Ok(()) 2797} 2798 2799/// Compile a store operation (assignment target). 2800fn compile_store(fc: &mut FunctionCompiler, target: &Expr, src: Reg) -> Result<(), JsError> { 2801 match &target.kind { 2802 ExprKind::Identifier(name) => { 2803 if let Some(local) = fc.find_local_info(name) { 2804 if local.is_const { 2805 return Err(JsError::SyntaxError(format!( 2806 "Assignment to constant variable '{name}'" 2807 ))); 2808 } 2809 let reg = local.reg; 2810 let captured = local.is_captured; 2811 if captured { 2812 fc.builder.emit_reg_reg(Op::CellStore, reg, src); 2813 } else if reg != src { 2814 fc.builder.emit_reg_reg(Op::Move, reg, src); 2815 } 2816 } else if let Some(uv_idx) = fc.find_upvalue(name) { 2817 if fc.is_upvalue_const(uv_idx) { 2818 return Err(JsError::SyntaxError(format!( 2819 "Assignment to constant variable '{name}'" 2820 ))); 2821 } 2822 fc.builder.emit_store_upvalue(uv_idx, src); 2823 } else { 2824 let ni = fc.builder.add_name(name); 2825 fc.builder.emit_store_global(ni, src); 2826 } 2827 } 2828 ExprKind::Member { 2829 object, 2830 property, 2831 computed, 2832 } => { 2833 let obj_reg = fc.alloc_reg(); 2834 compile_expr(fc, object, obj_reg)?; 2835 if !computed { 2836 if let ExprKind::Identifier(name) = &property.kind { 2837 let ni = fc.builder.add_name(name); 2838 fc.builder.emit_set_prop_name(obj_reg, ni, src); 2839 } else { 2840 let key_reg = fc.alloc_reg(); 2841 compile_expr(fc, property, key_reg)?; 2842 fc.builder.emit_reg3(Op::SetProperty, obj_reg, key_reg, src); 2843 fc.free_reg(key_reg); 2844 } 2845 } else { 2846 let key_reg = fc.alloc_reg(); 2847 compile_expr(fc, property, key_reg)?; 2848 fc.builder.emit_reg3(Op::SetProperty, obj_reg, key_reg, src); 2849 fc.free_reg(key_reg); 2850 } 2851 fc.free_reg(obj_reg); 2852 } 2853 _ => { 2854 // Other assignment targets (destructuring) not handled here. 2855 } 2856 } 2857 Ok(()) 2858} 2859 2860fn binary_op_to_opcode(op: BinaryOp) -> Op { 2861 match op { 2862 BinaryOp::Add => Op::Add, 2863 BinaryOp::Sub => Op::Sub, 2864 BinaryOp::Mul => Op::Mul, 2865 BinaryOp::Div => Op::Div, 2866 BinaryOp::Rem => Op::Rem, 2867 BinaryOp::Exp => Op::Exp, 2868 BinaryOp::Eq => Op::Eq, 2869 BinaryOp::Ne => Op::NotEq, 2870 BinaryOp::StrictEq => Op::StrictEq, 2871 BinaryOp::StrictNe => Op::StrictNotEq, 2872 BinaryOp::Lt => Op::LessThan, 2873 BinaryOp::Le => Op::LessEq, 2874 BinaryOp::Gt => Op::GreaterThan, 2875 BinaryOp::Ge => Op::GreaterEq, 2876 BinaryOp::Shl => Op::ShiftLeft, 2877 BinaryOp::Shr => Op::ShiftRight, 2878 BinaryOp::Ushr => Op::UShiftRight, 2879 BinaryOp::BitAnd => Op::BitAnd, 2880 BinaryOp::BitOr => Op::BitOr, 2881 BinaryOp::BitXor => Op::BitXor, 2882 BinaryOp::In => Op::In, 2883 BinaryOp::Instanceof => Op::InstanceOf, 2884 } 2885} 2886 2887fn compound_assign_op(op: AssignOp) -> Op { 2888 match op { 2889 AssignOp::AddAssign => Op::Add, 2890 AssignOp::SubAssign => Op::Sub, 2891 AssignOp::MulAssign => Op::Mul, 2892 AssignOp::DivAssign => Op::Div, 2893 AssignOp::RemAssign => Op::Rem, 2894 AssignOp::ExpAssign => Op::Exp, 2895 AssignOp::ShlAssign => Op::ShiftLeft, 2896 AssignOp::ShrAssign => Op::ShiftRight, 2897 AssignOp::UshrAssign => Op::UShiftRight, 2898 AssignOp::BitAndAssign => Op::BitAnd, 2899 AssignOp::BitOrAssign => Op::BitOr, 2900 AssignOp::BitXorAssign => Op::BitXor, 2901 AssignOp::AndAssign => Op::BitAnd, // logical AND assignment uses short-circuit; simplified here 2902 AssignOp::OrAssign => Op::BitOr, // likewise 2903 AssignOp::NullishAssign => Op::Move, // simplified 2904 AssignOp::Assign => unreachable!(), 2905 } 2906} 2907 2908#[cfg(test)] 2909mod tests { 2910 use super::*; 2911 use crate::parser::Parser; 2912 2913 /// Helper: parse and compile source, return the top-level function. 2914 fn compile_src(src: &str) -> Function { 2915 let program = Parser::parse(src).expect("parse failed"); 2916 compile(&program).expect("compile failed") 2917 } 2918 2919 #[test] 2920 fn test_compile_number_literal() { 2921 let f = compile_src("42;"); 2922 let dis = f.disassemble(); 2923 assert!(dis.contains("LoadInt8 r0, 42"), "got:\n{dis}"); 2924 assert!(dis.contains("Return r0")); 2925 } 2926 2927 #[test] 2928 fn test_compile_large_number() { 2929 let f = compile_src("3.14;"); 2930 let dis = f.disassemble(); 2931 assert!(dis.contains("LoadConst r0, #0"), "got:\n{dis}"); 2932 assert!( 2933 f.constants.contains(&Constant::Number(3.14)), 2934 "constants: {:?}", 2935 f.constants 2936 ); 2937 } 2938 2939 #[test] 2940 fn test_compile_string() { 2941 let f = compile_src("\"hello\";"); 2942 let dis = f.disassemble(); 2943 assert!(dis.contains("LoadConst r0, #0")); 2944 assert!(f.constants.contains(&Constant::String("hello".into()))); 2945 } 2946 2947 #[test] 2948 fn test_compile_bool_null() { 2949 let f = compile_src("true; false; null;"); 2950 let dis = f.disassemble(); 2951 assert!(dis.contains("LoadTrue r0")); 2952 assert!(dis.contains("LoadFalse r0")); 2953 assert!(dis.contains("LoadNull r0")); 2954 } 2955 2956 #[test] 2957 fn test_compile_binary_arithmetic() { 2958 let f = compile_src("1 + 2;"); 2959 let dis = f.disassemble(); 2960 assert!(dis.contains("Add r0, r1, r2"), "got:\n{dis}"); 2961 } 2962 2963 #[test] 2964 fn test_compile_nested_arithmetic() { 2965 let f = compile_src("(1 + 2) * 3;"); 2966 let dis = f.disassemble(); 2967 assert!(dis.contains("Add"), "got:\n{dis}"); 2968 assert!(dis.contains("Mul"), "got:\n{dis}"); 2969 } 2970 2971 #[test] 2972 fn test_compile_var_decl() { 2973 let f = compile_src("var x = 10; x;"); 2974 let dis = f.disassemble(); 2975 // x should get a register, then be loaded from that register. 2976 assert!(dis.contains("LoadInt8"), "got:\n{dis}"); 2977 assert!( 2978 dis.contains("Move") || dis.contains("LoadInt8"), 2979 "got:\n{dis}" 2980 ); 2981 } 2982 2983 #[test] 2984 fn test_compile_let_const() { 2985 let f = compile_src("let a = 1; const b = 2; a + b;"); 2986 let dis = f.disassemble(); 2987 assert!(dis.contains("Add"), "got:\n{dis}"); 2988 } 2989 2990 #[test] 2991 fn test_compile_if_else() { 2992 let f = compile_src("if (true) { 1; } else { 2; }"); 2993 let dis = f.disassemble(); 2994 assert!(dis.contains("JumpIfFalse"), "got:\n{dis}"); 2995 assert!(dis.contains("Jump"), "got:\n{dis}"); 2996 } 2997 2998 #[test] 2999 fn test_compile_while() { 3000 let f = compile_src("var i = 0; while (i < 10) { i = i + 1; }"); 3001 let dis = f.disassemble(); 3002 assert!(dis.contains("LessThan"), "got:\n{dis}"); 3003 assert!(dis.contains("JumpIfFalse"), "got:\n{dis}"); 3004 assert!( 3005 dis.contains("Jump"), 3006 "backward jump should be present: {dis}" 3007 ); 3008 } 3009 3010 #[test] 3011 fn test_compile_do_while() { 3012 let f = compile_src("var i = 0; do { i = i + 1; } while (i < 5);"); 3013 let dis = f.disassemble(); 3014 assert!(dis.contains("JumpIfTrue"), "got:\n{dis}"); 3015 } 3016 3017 #[test] 3018 fn test_compile_for_loop() { 3019 let f = compile_src("for (var i = 0; i < 10; i = i + 1) { i; }"); 3020 let dis = f.disassemble(); 3021 assert!(dis.contains("LessThan"), "got:\n{dis}"); 3022 assert!(dis.contains("JumpIfFalse"), "got:\n{dis}"); 3023 } 3024 3025 #[test] 3026 fn test_compile_function_decl() { 3027 let f = compile_src("function add(a, b) { return a + b; }"); 3028 let dis = f.disassemble(); 3029 assert!(dis.contains("CreateClosure"), "got:\n{dis}"); 3030 assert!(!f.functions.is_empty(), "should have nested function"); 3031 let inner = &f.functions[0]; 3032 assert_eq!(inner.name, "add"); 3033 assert_eq!(inner.param_count, 2); 3034 let inner_dis = inner.disassemble(); 3035 assert!(inner_dis.contains("Add"), "inner:\n{inner_dis}"); 3036 assert!(inner_dis.contains("Return"), "inner:\n{inner_dis}"); 3037 } 3038 3039 #[test] 3040 fn test_compile_function_call() { 3041 let f = compile_src("function f() { return 42; } f();"); 3042 let dis = f.disassemble(); 3043 assert!(dis.contains("Call"), "got:\n{dis}"); 3044 } 3045 3046 #[test] 3047 fn test_compile_arrow_function() { 3048 let f = compile_src("var add = (a, b) => a + b;"); 3049 let dis = f.disassemble(); 3050 assert!(dis.contains("CreateClosure"), "got:\n{dis}"); 3051 let inner = &f.functions[0]; 3052 assert_eq!(inner.param_count, 2); 3053 } 3054 3055 #[test] 3056 fn test_compile_assignment() { 3057 let f = compile_src("var x = 1; x = x + 2;"); 3058 let dis = f.disassemble(); 3059 assert!(dis.contains("Add"), "got:\n{dis}"); 3060 assert!( 3061 dis.contains("Move"), 3062 "assignment should produce Move:\n{dis}" 3063 ); 3064 } 3065 3066 #[test] 3067 fn test_compile_compound_assignment() { 3068 let f = compile_src("var x = 10; x += 5;"); 3069 let dis = f.disassemble(); 3070 assert!(dis.contains("Add"), "got:\n{dis}"); 3071 } 3072 3073 #[test] 3074 fn test_compile_member_access() { 3075 let f = compile_src("var obj = {}; obj.x;"); 3076 let dis = f.disassemble(); 3077 assert!(dis.contains("CreateObject"), "got:\n{dis}"); 3078 assert!(dis.contains("GetPropertyByName"), "got:\n{dis}"); 3079 } 3080 3081 #[test] 3082 fn test_compile_computed_member() { 3083 let f = compile_src("var arr = []; arr[0];"); 3084 let dis = f.disassemble(); 3085 assert!(dis.contains("GetProperty"), "got:\n{dis}"); 3086 } 3087 3088 #[test] 3089 fn test_compile_object_literal() { 3090 let f = compile_src("var obj = { a: 1, b: 2 };"); 3091 let dis = f.disassemble(); 3092 assert!(dis.contains("CreateObject"), "got:\n{dis}"); 3093 assert!(dis.contains("SetPropertyByName"), "got:\n{dis}"); 3094 } 3095 3096 #[test] 3097 fn test_compile_array_literal() { 3098 let f = compile_src("[1, 2, 3];"); 3099 let dis = f.disassemble(); 3100 assert!(dis.contains("CreateArray"), "got:\n{dis}"); 3101 assert!(dis.contains("SetProperty"), "got:\n{dis}"); 3102 } 3103 3104 #[test] 3105 fn test_compile_conditional() { 3106 let f = compile_src("true ? 1 : 2;"); 3107 let dis = f.disassemble(); 3108 assert!(dis.contains("JumpIfFalse"), "got:\n{dis}"); 3109 } 3110 3111 #[test] 3112 fn test_compile_logical_and() { 3113 let f = compile_src("true && false;"); 3114 let dis = f.disassemble(); 3115 assert!(dis.contains("JumpIfFalse"), "short-circuit:\n{dis}"); 3116 } 3117 3118 #[test] 3119 fn test_compile_logical_or() { 3120 let f = compile_src("false || true;"); 3121 let dis = f.disassemble(); 3122 assert!(dis.contains("JumpIfTrue"), "short-circuit:\n{dis}"); 3123 } 3124 3125 #[test] 3126 fn test_compile_typeof() { 3127 let f = compile_src("typeof 42;"); 3128 let dis = f.disassemble(); 3129 assert!(dis.contains("TypeOf"), "got:\n{dis}"); 3130 } 3131 3132 #[test] 3133 fn test_compile_unary_minus() { 3134 let f = compile_src("-42;"); 3135 let dis = f.disassemble(); 3136 assert!(dis.contains("Neg"), "got:\n{dis}"); 3137 } 3138 3139 #[test] 3140 fn test_compile_not() { 3141 let f = compile_src("!true;"); 3142 let dis = f.disassemble(); 3143 assert!(dis.contains("LogicalNot"), "got:\n{dis}"); 3144 } 3145 3146 #[test] 3147 fn test_compile_return() { 3148 let f = compile_src("function f() { return 42; }"); 3149 let inner = &f.functions[0]; 3150 let dis = inner.disassemble(); 3151 assert!(dis.contains("Return"), "got:\n{dis}"); 3152 } 3153 3154 #[test] 3155 fn test_compile_empty_return() { 3156 let f = compile_src("function f() { return; }"); 3157 let inner = &f.functions[0]; 3158 let dis = inner.disassemble(); 3159 assert!(dis.contains("LoadUndefined"), "got:\n{dis}"); 3160 assert!(dis.contains("Return"), "got:\n{dis}"); 3161 } 3162 3163 #[test] 3164 fn test_compile_throw() { 3165 let f = compile_src("function f() { throw 42; }"); 3166 let inner = &f.functions[0]; 3167 let dis = inner.disassemble(); 3168 assert!(dis.contains("Throw"), "got:\n{dis}"); 3169 } 3170 3171 #[test] 3172 fn test_compile_this() { 3173 let f = compile_src("this;"); 3174 let dis = f.disassemble(); 3175 assert!(dis.contains("LoadGlobal"), "got:\n{dis}"); 3176 assert!(f.names.contains(&"this".to_string())); 3177 } 3178 3179 #[test] 3180 fn test_compile_global_var() { 3181 let f = compile_src("console;"); 3182 let dis = f.disassemble(); 3183 assert!(dis.contains("LoadGlobal"), "got:\n{dis}"); 3184 assert!(f.names.contains(&"console".to_string())); 3185 } 3186 3187 #[test] 3188 fn test_compile_template_literal() { 3189 let f = compile_src("`hello`;"); 3190 assert!( 3191 f.constants.contains(&Constant::String("hello".into())), 3192 "constants: {:?}", 3193 f.constants 3194 ); 3195 } 3196 3197 #[test] 3198 fn test_compile_switch() { 3199 let f = compile_src("switch (1) { case 1: 42; break; case 2: 99; break; }"); 3200 let dis = f.disassemble(); 3201 assert!(dis.contains("StrictEq"), "got:\n{dis}"); 3202 } 3203 3204 #[test] 3205 fn test_compile_class() { 3206 let f = compile_src("class Foo { constructor() {} greet() { return 1; } }"); 3207 let dis = f.disassemble(); 3208 assert!(dis.contains("CreateClosure"), "got:\n{dis}"); 3209 } 3210 3211 #[test] 3212 fn test_compile_update_prefix() { 3213 let f = compile_src("var x = 0; ++x;"); 3214 let dis = f.disassemble(); 3215 assert!(dis.contains("Add"), "got:\n{dis}"); 3216 } 3217 3218 #[test] 3219 fn test_compile_comparison() { 3220 let f = compile_src("1 === 2;"); 3221 let dis = f.disassemble(); 3222 assert!(dis.contains("StrictEq"), "got:\n{dis}"); 3223 } 3224 3225 #[test] 3226 fn test_compile_bitwise() { 3227 let f = compile_src("1 & 2;"); 3228 let dis = f.disassemble(); 3229 assert!(dis.contains("BitAnd"), "got:\n{dis}"); 3230 } 3231 3232 #[test] 3233 fn test_compile_void() { 3234 let f = compile_src("void 0;"); 3235 let dis = f.disassemble(); 3236 assert!(dis.contains("Void"), "got:\n{dis}"); 3237 } 3238 3239 #[test] 3240 fn test_disassembler_output_format() { 3241 let f = compile_src("var x = 42; x + 1;"); 3242 let dis = f.disassemble(); 3243 // Should contain function header. 3244 assert!(dis.contains("function <main>")); 3245 // Should contain code section. 3246 assert!(dis.contains("code:")); 3247 // Should have hex offsets. 3248 assert!(dis.contains("0000")); 3249 } 3250 3251 #[test] 3252 fn test_register_allocation_is_minimal() { 3253 // `var a = 1; var b = 2; a + b;` should use few registers. 3254 let f = compile_src("var a = 1; var b = 2; a + b;"); 3255 // r0 = result, r1 = a, r2 = b, r3/r4 = temps for addition 3256 assert!( 3257 f.register_count <= 6, 3258 "too many registers: {}", 3259 f.register_count 3260 ); 3261 } 3262 3263 #[test] 3264 fn test_nested_function_closure() { 3265 let f = compile_src("function outer() { function inner() { return 1; } return inner; }"); 3266 assert_eq!(f.functions.len(), 1); 3267 let outer = &f.functions[0]; 3268 assert_eq!(outer.name, "outer"); 3269 assert_eq!(outer.functions.len(), 1); 3270 let inner = &outer.functions[0]; 3271 assert_eq!(inner.name, "inner"); 3272 } 3273 3274 #[test] 3275 fn test_for_with_no_parts() { 3276 // `for (;;) { break; }` — infinite loop with immediate break. 3277 let f = compile_src("for (;;) { break; }"); 3278 let dis = f.disassemble(); 3279 assert!(dis.contains("Jump"), "got:\n{dis}"); 3280 } 3281 3282 #[test] 3283 fn test_for_continue_targets_update() { 3284 // `continue` in a for-loop must jump to the update expression, not back 3285 // to the condition check. Verify the continue jump goes to the Add (i + 1) 3286 // rather than to the LessThan condition. 3287 let f = compile_src("for (var i = 0; i < 10; i = i + 1) { continue; }"); 3288 let dis = f.disassemble(); 3289 // The for-loop should contain: LessThan (test), JumpIfFalse (exit), 3290 // Jump (continue), Add (update), Jump (back to test). 3291 assert!(dis.contains("LessThan"), "missing test: {dis}"); 3292 assert!(dis.contains("Add"), "missing update: {dis}"); 3293 // There should be at least 2 Jump instructions (continue + back-edge). 3294 let jump_count = dis.matches("Jump ").count(); 3295 assert!( 3296 jump_count >= 2, 3297 "expected >= 2 jumps for continue + back-edge, got {jump_count}: {dis}" 3298 ); 3299 } 3300 3301 #[test] 3302 fn test_do_while_continue_targets_condition() { 3303 // `continue` in do-while must jump to the condition, not the body start. 3304 let f = compile_src("var i = 0; do { i = i + 1; continue; } while (i < 5);"); 3305 let dis = f.disassemble(); 3306 assert!(dis.contains("LessThan"), "missing condition: {dis}"); 3307 assert!(dis.contains("JumpIfTrue"), "missing back-edge: {dis}"); 3308 } 3309 3310 #[test] 3311 fn test_switch_default_case() { 3312 // Default case must not corrupt bytecode. 3313 let f = compile_src("switch (1) { case 1: 10; break; default: 20; break; }"); 3314 let dis = f.disassemble(); 3315 assert!(dis.contains("StrictEq"), "missing case test: {dis}"); 3316 // The first instruction should NOT be corrupted. 3317 assert!( 3318 dis.contains("LoadUndefined r0"), 3319 "first instruction corrupted: {dis}" 3320 ); 3321 } 3322 3323 #[test] 3324 fn test_switch_only_default() { 3325 // Switch with only a default case. 3326 let f = compile_src("switch (42) { default: 99; }"); 3327 let dis = f.disassemble(); 3328 // Should compile without panicking and contain the default body. 3329 assert!(dis.contains("LoadInt8"), "got:\n{dis}"); 3330 } 3331 3332 #[test] 3333 fn test_class_empty_constructor_has_return() { 3334 // A class without an explicit constructor should produce a function with Return. 3335 let f = compile_src("class Foo {}"); 3336 assert!(!f.functions.is_empty(), "should have constructor function"); 3337 let ctor = &f.functions[0]; 3338 let dis = ctor.disassemble(); 3339 assert!( 3340 dis.contains("Return"), 3341 "empty constructor must have Return: {dis}" 3342 ); 3343 } 3344 3345 #[test] 3346 fn test_class_expression_compiles_methods() { 3347 // Class expression should compile methods, not just the constructor. 3348 let f = compile_src("var C = class { greet() { return 1; } };"); 3349 let dis = f.disassemble(); 3350 assert!( 3351 dis.contains("SetPropertyByName"), 3352 "method should be set as property: {dis}" 3353 ); 3354 } 3355}