we (web engine): Experimental web browser project to understand the limits of Claude
at console-api 3372 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]() or @@asyncIterator(). 907 let iter_method_r = fc.alloc_reg(); 908 let sym_name = if *is_await { 909 "@@asyncIterator" 910 } else { 911 "@@iterator" 912 }; 913 let sym_iter_ni = fc.builder.add_name(sym_name); 914 fc.builder 915 .emit_get_prop_name(iter_method_r, iterable_r, sym_iter_ni); 916 917 // Set `this` = iterable for the call. 918 let this_ni = fc.builder.add_name("this"); 919 fc.builder.emit_store_global(this_ni, iterable_r); 920 921 // Call [@@iterator/@@asyncIterator]() with 0 args. 922 let iterator_r = fc.alloc_reg(); 923 let args_start = fc.next_reg; 924 fc.builder 925 .emit_call(iterator_r, iter_method_r, args_start, 0); 926 927 // Temp registers for next method, result, done, value. 928 let next_method_r = fc.alloc_reg(); 929 let next_ni = fc.builder.add_name("next"); 930 fc.builder 931 .emit_get_prop_name(next_method_r, iterator_r, next_ni); 932 933 let result_obj_r = fc.alloc_reg(); 934 let done_r = fc.alloc_reg(); 935 let value_r = fc.alloc_reg(); 936 937 // Loop start. 938 let loop_start = fc.builder.offset(); 939 940 // Set `this` = iterator for the .next() call. 941 fc.builder.emit_store_global(this_ni, iterator_r); 942 943 // Call iterator.next(). 944 fc.builder 945 .emit_call(result_obj_r, next_method_r, args_start, 0); 946 947 // For await: await the result of .next() (which returns a Promise). 948 if *is_await { 949 let awaited_r = fc.alloc_reg(); 950 fc.builder.emit_await(awaited_r, result_obj_r); 951 fc.builder.emit_reg_reg(Op::Move, result_obj_r, awaited_r); 952 fc.free_reg(awaited_r); 953 } 954 955 // Extract done and value. 956 let done_ni = fc.builder.add_name("done"); 957 let value_ni = fc.builder.add_name("value"); 958 fc.builder.emit_get_prop_name(done_r, result_obj_r, done_ni); 959 960 // Exit if done. 961 let exit_patch = fc.builder.emit_cond_jump(Op::JumpIfTrue, done_r); 962 963 // Extract value. 964 fc.builder 965 .emit_get_prop_name(value_r, result_obj_r, value_ni); 966 967 // Bind the loop variable. 968 match left { 969 ForInOfLeft::VarDecl { kind, pattern } => match &pattern.kind { 970 PatternKind::Identifier(name) => { 971 let is_captured = fc.captured_names.contains(name.as_str()); 972 let is_const = *kind == VarKind::Const; 973 let var_r = fc.define_local_ext(name, is_captured, is_const); 974 if is_captured { 975 fc.builder.emit_reg(Op::NewCell, var_r); 976 fc.builder.emit_reg_reg(Op::CellStore, var_r, value_r); 977 } else { 978 fc.builder.emit_reg_reg(Op::Move, var_r, value_r); 979 } 980 } 981 _ => { 982 // Destructuring pattern in for...of. 983 compile_destructuring_pattern(fc, pattern, value_r)?; 984 } 985 }, 986 ForInOfLeft::Pattern(pattern) => match &pattern.kind { 987 PatternKind::Identifier(name) => { 988 if let Some(local) = fc.find_local_info(name) { 989 let reg = local.reg; 990 let captured = local.is_captured; 991 if captured { 992 fc.builder.emit_reg_reg(Op::CellStore, reg, value_r); 993 } else { 994 fc.builder.emit_reg_reg(Op::Move, reg, value_r); 995 } 996 } else if let Some(uv_idx) = fc.find_upvalue(name) { 997 fc.builder.emit_store_upvalue(uv_idx, value_r); 998 } else { 999 let ni = fc.builder.add_name(name); 1000 fc.builder.emit_store_global(ni, value_r); 1001 } 1002 } 1003 _ => { 1004 compile_destructuring_pattern(fc, pattern, value_r)?; 1005 } 1006 }, 1007 } 1008 1009 // Push loop context for break/continue. 1010 fc.loop_stack.push(LoopCtx { 1011 label: None, 1012 break_patches: Vec::new(), 1013 continue_patches: Vec::new(), 1014 }); 1015 1016 // Compile body. 1017 compile_stmt(fc, body, result_reg)?; 1018 1019 // Jump back to loop start. 1020 fc.builder.emit_jump_to(loop_start); 1021 1022 // Patch exit. 1023 fc.builder.patch_jump(exit_patch); 1024 let ctx = fc.loop_stack.pop().unwrap(); 1025 for patch in ctx.break_patches { 1026 fc.builder.patch_jump(patch); 1027 } 1028 for patch in ctx.continue_patches { 1029 fc.builder.patch_jump_to(patch, loop_start); 1030 } 1031 1032 // Restore locals/regs. 1033 fc.locals.truncate(saved_locals); 1034 fc.next_reg = saved_next; 1035 } 1036 1037 StmtKind::Return(expr) => { 1038 let ret_reg = fc.alloc_reg(); 1039 if let Some(e) = expr { 1040 compile_expr(fc, e, ret_reg)?; 1041 } else { 1042 fc.builder.emit_reg(Op::LoadUndefined, ret_reg); 1043 } 1044 fc.builder.emit_reg(Op::Return, ret_reg); 1045 fc.free_reg(ret_reg); 1046 } 1047 1048 StmtKind::Throw(expr) => { 1049 let tmp = fc.alloc_reg(); 1050 compile_expr(fc, expr, tmp)?; 1051 fc.builder.emit_reg(Op::Throw, tmp); 1052 fc.free_reg(tmp); 1053 } 1054 1055 StmtKind::Break(label) => { 1056 // Find the matching loop context. 1057 let idx = find_loop_ctx(&fc.loop_stack, label.as_deref()) 1058 .ok_or_else(|| JsError::SyntaxError("break outside of loop".into()))?; 1059 let patch = fc.builder.emit_jump(Op::Jump); 1060 fc.loop_stack[idx].break_patches.push(patch); 1061 } 1062 1063 StmtKind::Continue(label) => { 1064 let idx = find_loop_ctx(&fc.loop_stack, label.as_deref()) 1065 .ok_or_else(|| JsError::SyntaxError("continue outside of loop".into()))?; 1066 let patch = fc.builder.emit_jump(Op::Jump); 1067 fc.loop_stack[idx].continue_patches.push(patch); 1068 } 1069 1070 StmtKind::Labeled { label, body } => { 1071 // If body is a loop, propagate the label. 1072 match &body.kind { 1073 StmtKind::While { test, body: inner } => { 1074 compile_while(fc, test, inner, Some(label.clone()), result_reg)?; 1075 } 1076 StmtKind::DoWhile { body: inner, test } => { 1077 compile_do_while(fc, inner, test, Some(label.clone()), result_reg)?; 1078 } 1079 StmtKind::For { 1080 init, 1081 test, 1082 update, 1083 body: inner, 1084 } => { 1085 compile_for( 1086 fc, 1087 init.as_ref(), 1088 test.as_ref(), 1089 update.as_ref(), 1090 inner, 1091 Some(label.clone()), 1092 result_reg, 1093 )?; 1094 } 1095 _ => { 1096 compile_stmt(fc, body, result_reg)?; 1097 } 1098 } 1099 } 1100 1101 StmtKind::Switch { 1102 discriminant, 1103 cases, 1104 } => { 1105 compile_switch(fc, discriminant, cases, result_reg)?; 1106 } 1107 1108 StmtKind::Try { 1109 block, 1110 handler, 1111 finalizer, 1112 } => { 1113 if let Some(catch) = handler { 1114 // The catch register will receive the exception value. Use the 1115 // current next_reg so it doesn't conflict with temporaries 1116 // allocated inside the try block. 1117 let saved_next = fc.next_reg; 1118 let catch_reg = fc.alloc_reg(); 1119 // Immediately "release" it so the try block can reuse registers 1120 // from this point. We remember catch_reg for PushExceptionHandler. 1121 fc.next_reg = saved_next; 1122 1123 // Emit PushExceptionHandler with placeholder offset to catch block. 1124 let catch_patch = fc.builder.emit_push_exception_handler(catch_reg); 1125 1126 let locals_len = fc.locals.len(); 1127 1128 // Compile the try block. 1129 compile_stmts(fc, block, result_reg)?; 1130 1131 // If we reach here, no exception was thrown. Pop handler and 1132 // jump past the catch block. 1133 fc.builder.emit_pop_exception_handler(); 1134 let end_patch = fc.builder.emit_jump(Op::Jump); 1135 1136 // Reset register state for catch block — locals declared in 1137 // the try block are out of scope. 1138 fc.locals.truncate(locals_len); 1139 fc.next_reg = saved_next; 1140 1141 // Patch the exception handler to jump here (catch block start). 1142 fc.builder.patch_jump(catch_patch); 1143 1144 // Bind the catch parameter if present. 1145 if let Some(param) = &catch.param { 1146 if let PatternKind::Identifier(name) = &param.kind { 1147 let is_captured = fc.captured_names.contains(name.as_str()); 1148 let local = fc.define_local_ext(name, is_captured, false); 1149 if is_captured { 1150 fc.builder.emit_reg(Op::NewCell, local); 1151 fc.builder.emit_reg_reg(Op::CellStore, local, catch_reg); 1152 } else { 1153 fc.builder.emit_reg_reg(Op::Move, local, catch_reg); 1154 } 1155 } 1156 } 1157 1158 // Compile the catch body. 1159 compile_stmts(fc, &catch.body, result_reg)?; 1160 1161 // End of catch — restore state. 1162 fc.locals.truncate(locals_len); 1163 fc.next_reg = saved_next; 1164 1165 // Jump target from the try block. 1166 fc.builder.patch_jump(end_patch); 1167 } else { 1168 // No catch handler: just compile the try block. 1169 compile_stmts(fc, block, result_reg)?; 1170 } 1171 1172 // Compile the finally block (always runs after try or catch). 1173 if let Some(fin) = finalizer { 1174 compile_stmts(fc, fin, result_reg)?; 1175 } 1176 } 1177 1178 StmtKind::Empty | StmtKind::Debugger => { 1179 // No-op. 1180 } 1181 1182 StmtKind::With { object, body } => { 1183 // Compile `with` as: evaluate object (discard), then run body. 1184 // Proper `with` scope requires VM support. 1185 let tmp = fc.alloc_reg(); 1186 compile_expr(fc, object, tmp)?; 1187 fc.free_reg(tmp); 1188 compile_stmt(fc, body, result_reg)?; 1189 } 1190 1191 StmtKind::Import { .. } => { 1192 // Module imports are resolved before execution; no bytecode needed. 1193 } 1194 1195 StmtKind::Export(export) => { 1196 compile_export(fc, export, result_reg)?; 1197 } 1198 1199 StmtKind::ClassDecl(class_def) => { 1200 compile_class_decl(fc, class_def)?; 1201 } 1202 } 1203 Ok(()) 1204} 1205 1206// ── Variable declarations ─────────────────────────────────── 1207 1208fn compile_var_declarator( 1209 fc: &mut FunctionCompiler, 1210 decl: &VarDeclarator, 1211 kind: VarKind, 1212) -> Result<(), JsError> { 1213 match &decl.pattern.kind { 1214 PatternKind::Identifier(name) => { 1215 let is_const = kind == VarKind::Const; 1216 let is_captured = fc.captured_names.contains(name.as_str()); 1217 1218 if is_const && decl.init.is_none() { 1219 return Err(JsError::SyntaxError( 1220 "Missing initializer in const declaration".into(), 1221 )); 1222 } 1223 1224 let reg = fc.define_local_ext(name, is_captured, is_const); 1225 1226 if is_captured { 1227 // Allocate a cell for this variable. 1228 fc.builder.emit_reg(Op::NewCell, reg); 1229 if let Some(init) = &decl.init { 1230 let tmp = fc.alloc_reg(); 1231 compile_expr(fc, init, tmp)?; 1232 fc.builder.emit_reg_reg(Op::CellStore, reg, tmp); 1233 fc.free_reg(tmp); 1234 } 1235 // No init => cell stays undefined (already the default). 1236 } else if let Some(init) = &decl.init { 1237 compile_expr(fc, init, reg)?; 1238 } else { 1239 fc.builder.emit_reg(Op::LoadUndefined, reg); 1240 } 1241 } 1242 _ => { 1243 // Destructuring: evaluate init, then bind patterns. 1244 // Note: don't free tmp — destructuring pattern allocates permanent 1245 // local registers above it. The tmp register slot is reused via 1246 // next_reg restoration by the parent scope. 1247 let tmp = fc.alloc_reg(); 1248 if let Some(init) = &decl.init { 1249 compile_expr(fc, init, tmp)?; 1250 } else { 1251 fc.builder.emit_reg(Op::LoadUndefined, tmp); 1252 } 1253 compile_destructuring_pattern(fc, &decl.pattern, tmp)?; 1254 } 1255 } 1256 Ok(()) 1257} 1258 1259fn compile_destructuring_pattern( 1260 fc: &mut FunctionCompiler, 1261 pattern: &Pattern, 1262 src: Reg, 1263) -> Result<(), JsError> { 1264 match &pattern.kind { 1265 PatternKind::Identifier(name) => { 1266 let is_captured = fc.captured_names.contains(name.as_str()); 1267 let reg = fc.define_local_ext(name, is_captured, false); 1268 if is_captured { 1269 fc.builder.emit_reg(Op::NewCell, reg); 1270 fc.builder.emit_reg_reg(Op::CellStore, reg, src); 1271 } else { 1272 fc.builder.emit_reg_reg(Op::Move, reg, src); 1273 } 1274 } 1275 PatternKind::Object { properties, rest } => { 1276 // For each property, extract the value and bind it. 1277 // We use a single temp register that we reuse for each property 1278 // by resetting next_reg after each binding. 1279 for prop in properties { 1280 let key_name = match &prop.key { 1281 PropertyKey::Identifier(s) | PropertyKey::String(s) => s.clone(), 1282 PropertyKey::Computed(expr) => { 1283 let saved = fc.next_reg; 1284 let key_reg = fc.alloc_reg(); 1285 compile_expr(fc, expr, key_reg)?; 1286 let val_reg = fc.alloc_reg(); 1287 fc.builder.emit_reg3(Op::GetProperty, val_reg, src, key_reg); 1288 compile_destructuring_pattern(fc, &prop.value, val_reg)?; 1289 // Temp regs are buried; just let them be. 1290 let _ = saved; 1291 continue; 1292 } 1293 PropertyKey::Number(n) => { 1294 if n.fract() == 0.0 && n.abs() < 1e15 { 1295 format!("{}", *n as i64) 1296 } else { 1297 format!("{n}") 1298 } 1299 } 1300 }; 1301 // For simple identifier patterns, load property directly into 1302 // the target register to avoid LIFO register allocation issues. 1303 if let PatternKind::Identifier(name) = &prop.value.kind { 1304 let is_captured = fc.captured_names.contains(name.as_str()); 1305 let reg = fc.define_local_ext(name, is_captured, false); 1306 let name_idx = fc.builder.add_name(&key_name); 1307 if is_captured { 1308 let tmp = fc.alloc_reg(); 1309 fc.builder.emit_get_prop_name(tmp, src, name_idx); 1310 fc.builder.emit_reg(Op::NewCell, reg); 1311 fc.builder.emit_reg_reg(Op::CellStore, reg, tmp); 1312 fc.free_reg(tmp); 1313 } else { 1314 fc.builder.emit_get_prop_name(reg, src, name_idx); 1315 } 1316 } else { 1317 // Complex inner pattern (nested, default, etc.) 1318 // Allocate temp, extract value, then recurse. 1319 // Temp register won't be freed (LIFO constraint with inner locals). 1320 let val_reg = fc.alloc_reg(); 1321 let name_idx = fc.builder.add_name(&key_name); 1322 fc.builder.emit_get_prop_name(val_reg, src, name_idx); 1323 compile_destructuring_pattern(fc, &prop.value, val_reg)?; 1324 } 1325 } 1326 1327 // Handle rest: collect remaining own enumerable properties. 1328 if let Some(rest_pat) = rest { 1329 // Collect extracted key names for exclusion. 1330 let extracted_keys: Vec<String> = properties 1331 .iter() 1332 .filter_map(|prop| match &prop.key { 1333 PropertyKey::Identifier(s) | PropertyKey::String(s) => Some(s.clone()), 1334 _ => None, 1335 }) 1336 .collect(); 1337 1338 let rest_obj = fc.alloc_reg(); 1339 fc.builder.emit_reg(Op::CreateObject, rest_obj); 1340 1341 let keys_r = fc.alloc_reg(); 1342 fc.builder.emit_reg_reg(Op::ForInInit, keys_r, src); 1343 let idx_r = fc.alloc_reg(); 1344 fc.builder.emit_load_int8(idx_r, 0); 1345 let key_r = fc.alloc_reg(); 1346 let done_r = fc.alloc_reg(); 1347 1348 let loop_start = fc.builder.offset(); 1349 fc.builder 1350 .emit_reg4(Op::ForInNext, key_r, done_r, keys_r, idx_r); 1351 let exit_patch = fc.builder.emit_cond_jump(Op::JumpIfTrue, done_r); 1352 1353 let mut skip_patches = Vec::new(); 1354 for excluded in &extracted_keys { 1355 let excluded_r = fc.alloc_reg(); 1356 let ci = fc.builder.add_constant(Constant::String(excluded.clone())); 1357 fc.builder.emit_reg_u16(Op::LoadConst, excluded_r, ci); 1358 let cmp_r = fc.alloc_reg(); 1359 fc.builder.emit_reg3(Op::StrictEq, cmp_r, key_r, excluded_r); 1360 let skip = fc.builder.emit_cond_jump(Op::JumpIfTrue, cmp_r); 1361 skip_patches.push(skip); 1362 fc.free_reg(cmp_r); 1363 fc.free_reg(excluded_r); 1364 } 1365 1366 let val_r = fc.alloc_reg(); 1367 fc.builder.emit_reg3(Op::GetProperty, val_r, src, key_r); 1368 fc.builder 1369 .emit_reg3(Op::SetProperty, rest_obj, key_r, val_r); 1370 fc.free_reg(val_r); 1371 1372 for patch in skip_patches { 1373 fc.builder.patch_jump(patch); 1374 } 1375 1376 let one_r = fc.alloc_reg(); 1377 fc.builder.emit_load_int8(one_r, 1); 1378 fc.builder.emit_reg3(Op::Add, idx_r, idx_r, one_r); 1379 fc.free_reg(one_r); 1380 1381 fc.builder.emit_jump_to(loop_start); 1382 fc.builder.patch_jump(exit_patch); 1383 1384 fc.free_reg(done_r); 1385 fc.free_reg(key_r); 1386 fc.free_reg(idx_r); 1387 fc.free_reg(keys_r); 1388 1389 compile_destructuring_pattern(fc, rest_pat, rest_obj)?; 1390 } 1391 } 1392 PatternKind::Array { elements, rest } => { 1393 for (i, elem) in elements.iter().enumerate() { 1394 if let Some(pat) = elem { 1395 // For simple identifier patterns, load directly into local. 1396 if let PatternKind::Identifier(name) = &pat.kind { 1397 let is_captured = fc.captured_names.contains(name.as_str()); 1398 let reg = fc.define_local_ext(name, is_captured, false); 1399 let idx_reg = fc.alloc_reg(); 1400 if i <= 127 { 1401 fc.builder.emit_load_int8(idx_reg, i as i8); 1402 } else { 1403 let ci = fc.builder.add_constant(Constant::Number(i as f64)); 1404 fc.builder.emit_reg_u16(Op::LoadConst, idx_reg, ci); 1405 } 1406 if is_captured { 1407 let tmp = fc.alloc_reg(); 1408 fc.builder.emit_reg3(Op::GetProperty, tmp, src, idx_reg); 1409 fc.builder.emit_reg(Op::NewCell, reg); 1410 fc.builder.emit_reg_reg(Op::CellStore, reg, tmp); 1411 fc.free_reg(tmp); 1412 } else { 1413 fc.builder.emit_reg3(Op::GetProperty, reg, src, idx_reg); 1414 } 1415 fc.free_reg(idx_reg); 1416 } else { 1417 // Complex inner pattern (nested, default, etc.) 1418 let idx_reg = fc.alloc_reg(); 1419 if i <= 127 { 1420 fc.builder.emit_load_int8(idx_reg, i as i8); 1421 } else { 1422 let ci = fc.builder.add_constant(Constant::Number(i as f64)); 1423 fc.builder.emit_reg_u16(Op::LoadConst, idx_reg, ci); 1424 } 1425 let val_reg = fc.alloc_reg(); 1426 fc.builder.emit_reg3(Op::GetProperty, val_reg, src, idx_reg); 1427 compile_destructuring_pattern(fc, pat, val_reg)?; 1428 // Don't free val_reg/idx_reg — inner pattern may have 1429 // allocated locals above them. 1430 } 1431 } 1432 } 1433 1434 // Handle rest element: ...rest = src.slice(elements.len()) 1435 if let Some(rest_pat) = rest { 1436 let slice_fn_r = fc.alloc_reg(); 1437 let slice_ni = fc.builder.add_name("slice"); 1438 fc.builder.emit_get_prop_name(slice_fn_r, src, slice_ni); 1439 1440 let this_ni = fc.builder.add_name("this"); 1441 fc.builder.emit_store_global(this_ni, src); 1442 1443 let start_r = fc.alloc_reg(); 1444 let elem_count = elements.len(); 1445 if elem_count <= 127 { 1446 fc.builder.emit_load_int8(start_r, elem_count as i8); 1447 } else { 1448 let ci = fc.builder.add_constant(Constant::Number(elem_count as f64)); 1449 fc.builder.emit_reg_u16(Op::LoadConst, start_r, ci); 1450 } 1451 1452 let rest_val = fc.alloc_reg(); 1453 fc.builder.emit_call(rest_val, slice_fn_r, start_r, 1); 1454 1455 compile_destructuring_pattern(fc, rest_pat, rest_val)?; 1456 // Don't free temps — rest pattern allocates locals. 1457 } 1458 } 1459 PatternKind::Assign { left, right } => { 1460 // Default value: if src is undefined, use default. 1461 let val_reg = fc.alloc_reg(); 1462 fc.builder.emit_reg_reg(Op::Move, val_reg, src); 1463 // Check if undefined, if so use default. 1464 let check_reg = fc.alloc_reg(); 1465 let undef_reg = fc.alloc_reg(); 1466 fc.builder.emit_reg(Op::LoadUndefined, undef_reg); 1467 fc.builder 1468 .emit_reg3(Op::StrictEq, check_reg, val_reg, undef_reg); 1469 fc.free_reg(undef_reg); 1470 let patch = fc.builder.emit_cond_jump(Op::JumpIfFalse, check_reg); 1471 fc.free_reg(check_reg); 1472 // Is undefined → evaluate default. 1473 compile_expr(fc, right, val_reg)?; 1474 fc.builder.patch_jump(patch); 1475 compile_destructuring_pattern(fc, left, val_reg)?; 1476 // Don't free val_reg — inner pattern may have allocated locals. 1477 } 1478 } 1479 Ok(()) 1480} 1481 1482// ── Function declarations ─────────────────────────────────── 1483 1484fn compile_function_decl(fc: &mut FunctionCompiler, func_def: &FunctionDef) -> Result<(), JsError> { 1485 let name = func_def.id.clone().unwrap_or_default(); 1486 let inner = compile_function_body_with_captures(fc, func_def)?; 1487 let func_idx = fc.builder.add_function(inner); 1488 1489 let is_captured = fc.captured_names.contains(name.as_str()); 1490 let reg = fc.define_local_ext(&name, is_captured, false); 1491 1492 if is_captured { 1493 // Create a cell, then create the closure into a temp, then store into cell. 1494 fc.builder.emit_reg(Op::NewCell, reg); 1495 let tmp = fc.alloc_reg(); 1496 fc.builder.emit_reg_u16(Op::CreateClosure, tmp, func_idx); 1497 fc.builder.emit_reg_reg(Op::CellStore, reg, tmp); 1498 // Also store as global. 1499 if !name.is_empty() { 1500 let name_idx = fc.builder.add_name(&name); 1501 fc.builder.emit_store_global(name_idx, tmp); 1502 } 1503 fc.free_reg(tmp); 1504 } else { 1505 fc.builder.emit_reg_u16(Op::CreateClosure, reg, func_idx); 1506 // Also store as global so inner/recursive calls via LoadGlobal can find it. 1507 if !name.is_empty() { 1508 let name_idx = fc.builder.add_name(&name); 1509 fc.builder.emit_store_global(name_idx, reg); 1510 } 1511 } 1512 Ok(()) 1513} 1514 1515/// Compile a function body, resolving upvalue captures from the parent scope. 1516fn compile_function_body_with_captures( 1517 parent: &mut FunctionCompiler, 1518 func_def: &FunctionDef, 1519) -> Result<Function, JsError> { 1520 // 1. Collect free variables of this inner function. 1521 let free_vars = collect_free_vars(&func_def.params, &func_def.body); 1522 1523 // 2. Build upvalue list by resolving free vars against the parent scope. 1524 let mut upvalue_entries = Vec::new(); 1525 for name in &free_vars { 1526 if let Some(local) = parent.find_local_info(name) { 1527 let reg = local.reg; 1528 let is_const = local.is_const; 1529 // Mark the parent's local as captured (if not already). 1530 // We need to update the parent's local, so find the index and mutate. 1531 if let Some(l) = parent.locals.iter_mut().rev().find(|l| l.name == *name) { 1532 l.is_captured = true; 1533 } 1534 upvalue_entries.push(UpvalueEntry { 1535 name: name.clone(), 1536 def: UpvalueDef { 1537 is_local: true, 1538 index: reg, 1539 }, 1540 is_const, 1541 }); 1542 } else if let Some(parent_uv_idx) = parent.find_upvalue(name) { 1543 // Transitive capture: the parent captures it from its own parent. 1544 let is_const = parent.is_upvalue_const(parent_uv_idx); 1545 upvalue_entries.push(UpvalueEntry { 1546 name: name.clone(), 1547 def: UpvalueDef { 1548 is_local: false, 1549 index: parent_uv_idx, 1550 }, 1551 is_const, 1552 }); 1553 } 1554 // If not found in parent or parent's upvalues, it must be a global — no upvalue needed. 1555 } 1556 1557 // 3. Compile the inner function with its own scope. 1558 let mut inner = compile_function_body_inner(func_def, &upvalue_entries)?; 1559 1560 // 4. Attach upvalue definitions to the compiled function. 1561 inner.upvalue_defs = upvalue_entries.iter().map(|e| e.def.clone()).collect(); 1562 1563 Ok(inner) 1564} 1565 1566/// Core function body compilation. The `upvalue_entries` tell this function which 1567/// outer variables it can access via LoadUpvalue/StoreUpvalue. 1568fn compile_function_body_inner( 1569 func_def: &FunctionDef, 1570 upvalue_entries: &[UpvalueEntry], 1571) -> Result<Function, JsError> { 1572 let name = func_def.id.clone().unwrap_or_default(); 1573 let param_count = func_def.params.len().min(255) as u8; 1574 let mut inner = FunctionCompiler::new(name, param_count); 1575 1576 // Copy upvalue entries into the inner compiler so it can resolve references. 1577 for entry in upvalue_entries { 1578 inner.upvalues.push(UpvalueEntry { 1579 name: entry.name.clone(), 1580 def: entry.def.clone(), 1581 is_const: entry.is_const, 1582 }); 1583 } 1584 1585 // Pre-scan to find which of this function's locals are captured by ITS inner functions. 1586 let inner_caps = collect_inner_captures(&func_def.body); 1587 inner.captured_names = inner_caps; 1588 1589 // Allocate registers for parameters. 1590 for p in &func_def.params { 1591 if let PatternKind::Identifier(pname) = &p.kind { 1592 let is_captured = inner.captured_names.contains(pname.as_str()); 1593 inner.define_local_ext(pname, is_captured, false); 1594 } else { 1595 let _ = inner.alloc_reg(); 1596 } 1597 } 1598 1599 // Box captured parameters into cells. 1600 for p in &func_def.params { 1601 if let PatternKind::Identifier(pname) = &p.kind { 1602 if let Some(local) = inner.find_local_info(pname) { 1603 if local.is_captured { 1604 let reg = local.reg; 1605 // Move param value to temp, allocate cell, store value into cell. 1606 let tmp = inner.alloc_reg(); 1607 inner.builder.emit_reg_reg(Op::Move, tmp, reg); 1608 inner.builder.emit_reg(Op::NewCell, reg); 1609 inner.builder.emit_reg_reg(Op::CellStore, reg, tmp); 1610 inner.free_reg(tmp); 1611 } 1612 } 1613 } 1614 } 1615 1616 // Result register for the function body. 1617 let result_reg = inner.alloc_reg(); 1618 inner.builder.emit_reg(Op::LoadUndefined, result_reg); 1619 1620 compile_stmts(&mut inner, &func_def.body, result_reg)?; 1621 1622 // Implicit return undefined. 1623 inner.builder.emit_reg(Op::Return, result_reg); 1624 let mut func = inner.builder.finish(); 1625 func.is_generator = func_def.is_generator; 1626 func.is_async = func_def.is_async; 1627 Ok(func) 1628} 1629 1630// ── Class declarations ────────────────────────────────────── 1631 1632fn compile_class_decl(fc: &mut FunctionCompiler, class_def: &ClassDef) -> Result<(), JsError> { 1633 let name = class_def.id.clone().unwrap_or_default(); 1634 let is_captured = fc.captured_names.contains(name.as_str()); 1635 let reg = fc.define_local_ext(&name, is_captured, false); 1636 1637 // For captured classes, build the constructor into a temp register so we can 1638 // set prototype methods on it before wrapping it in a cell. 1639 let ctor_reg = if is_captured { fc.alloc_reg() } else { reg }; 1640 1641 // Find constructor or create empty one. 1642 let ctor = class_def.body.iter().find(|m| { 1643 matches!( 1644 &m.kind, 1645 ClassMemberKind::Method { 1646 kind: MethodKind::Constructor, 1647 .. 1648 } 1649 ) 1650 }); 1651 1652 if let Some(member) = ctor { 1653 if let ClassMemberKind::Method { value, .. } = &member.kind { 1654 let inner = compile_function_body_with_captures(fc, value)?; 1655 let func_idx = fc.builder.add_function(inner); 1656 fc.builder 1657 .emit_reg_u16(Op::CreateClosure, ctor_reg, func_idx); 1658 } 1659 } else { 1660 // No constructor: create a minimal function that returns undefined. 1661 let mut empty = BytecodeBuilder::new(name.clone(), 0); 1662 let r = 0u8; 1663 empty.func.register_count = 1; 1664 empty.emit_reg(Op::LoadUndefined, r); 1665 empty.emit_reg(Op::Return, r); 1666 let func_idx = fc.builder.add_function(empty.finish()); 1667 fc.builder 1668 .emit_reg_u16(Op::CreateClosure, ctor_reg, func_idx); 1669 } 1670 1671 // Compile methods: set them as properties on the constructor's prototype. 1672 // This is simplified — real class compilation needs prototype chain setup. 1673 for member in &class_def.body { 1674 match &member.kind { 1675 ClassMemberKind::Method { 1676 key, 1677 value, 1678 kind, 1679 is_static: _, 1680 computed: _, 1681 } => { 1682 if matches!(kind, MethodKind::Constructor) { 1683 continue; 1684 } 1685 let method_name = match key { 1686 PropertyKey::Identifier(s) | PropertyKey::String(s) => s.clone(), 1687 _ => continue, 1688 }; 1689 let inner = compile_function_body_with_captures(fc, value)?; 1690 let func_idx = fc.builder.add_function(inner); 1691 let method_reg = fc.alloc_reg(); 1692 fc.builder 1693 .emit_reg_u16(Op::CreateClosure, method_reg, func_idx); 1694 let name_idx = fc.builder.add_name(&method_name); 1695 fc.builder 1696 .emit_set_prop_name(ctor_reg, name_idx, method_reg); 1697 fc.free_reg(method_reg); 1698 } 1699 ClassMemberKind::Property { .. } => { 1700 // Class fields are set in constructor; skip here. 1701 } 1702 } 1703 } 1704 1705 if is_captured { 1706 fc.builder.emit_reg(Op::NewCell, reg); 1707 fc.builder.emit_reg_reg(Op::CellStore, reg, ctor_reg); 1708 fc.free_reg(ctor_reg); 1709 } 1710 1711 Ok(()) 1712} 1713 1714// ── Export ─────────────────────────────────────────────────── 1715 1716fn compile_export( 1717 fc: &mut FunctionCompiler, 1718 export: &ExportDecl, 1719 1720 result_reg: Reg, 1721) -> Result<(), JsError> { 1722 match export { 1723 ExportDecl::Declaration(stmt) => { 1724 compile_stmt(fc, stmt, result_reg)?; 1725 } 1726 ExportDecl::Default(expr) => { 1727 compile_expr(fc, expr, result_reg)?; 1728 } 1729 ExportDecl::Named { .. } | ExportDecl::AllFrom(_) => { 1730 // Named re-exports are module-level; no bytecode needed. 1731 } 1732 } 1733 Ok(()) 1734} 1735 1736// ── Control flow ──────────────────────────────────────────── 1737 1738fn compile_if( 1739 fc: &mut FunctionCompiler, 1740 test: &Expr, 1741 consequent: &Stmt, 1742 alternate: Option<&Stmt>, 1743 1744 result_reg: Reg, 1745) -> Result<(), JsError> { 1746 let cond = fc.alloc_reg(); 1747 compile_expr(fc, test, cond)?; 1748 let else_patch = fc.builder.emit_cond_jump(Op::JumpIfFalse, cond); 1749 fc.free_reg(cond); 1750 1751 compile_stmt(fc, consequent, result_reg)?; 1752 1753 if let Some(alt) = alternate { 1754 let end_patch = fc.builder.emit_jump(Op::Jump); 1755 fc.builder.patch_jump(else_patch); 1756 compile_stmt(fc, alt, result_reg)?; 1757 fc.builder.patch_jump(end_patch); 1758 } else { 1759 fc.builder.patch_jump(else_patch); 1760 } 1761 Ok(()) 1762} 1763 1764fn compile_while( 1765 fc: &mut FunctionCompiler, 1766 test: &Expr, 1767 body: &Stmt, 1768 label: Option<String>, 1769 1770 result_reg: Reg, 1771) -> Result<(), JsError> { 1772 let loop_start = fc.builder.offset(); 1773 1774 let cond = fc.alloc_reg(); 1775 compile_expr(fc, test, cond)?; 1776 let exit_patch = fc.builder.emit_cond_jump(Op::JumpIfFalse, cond); 1777 fc.free_reg(cond); 1778 1779 fc.loop_stack.push(LoopCtx { 1780 label, 1781 break_patches: Vec::new(), 1782 continue_patches: Vec::new(), 1783 }); 1784 1785 compile_stmt(fc, body, result_reg)?; 1786 fc.builder.emit_jump_to(loop_start); 1787 fc.builder.patch_jump(exit_patch); 1788 1789 let ctx = fc.loop_stack.pop().unwrap(); 1790 for patch in ctx.break_patches { 1791 fc.builder.patch_jump(patch); 1792 } 1793 // In a while loop, continue jumps back to the condition check (loop_start). 1794 for patch in ctx.continue_patches { 1795 fc.builder.patch_jump_to(patch, loop_start); 1796 } 1797 Ok(()) 1798} 1799 1800fn compile_do_while( 1801 fc: &mut FunctionCompiler, 1802 body: &Stmt, 1803 test: &Expr, 1804 label: Option<String>, 1805 1806 result_reg: Reg, 1807) -> Result<(), JsError> { 1808 let loop_start = fc.builder.offset(); 1809 1810 fc.loop_stack.push(LoopCtx { 1811 label, 1812 break_patches: Vec::new(), 1813 continue_patches: Vec::new(), 1814 }); 1815 1816 compile_stmt(fc, body, result_reg)?; 1817 1818 // continue in do-while should jump here (the condition check). 1819 let cond_start = fc.builder.offset(); 1820 1821 let cond = fc.alloc_reg(); 1822 compile_expr(fc, test, cond)?; 1823 fc.builder 1824 .emit_cond_jump_to(Op::JumpIfTrue, cond, loop_start); 1825 fc.free_reg(cond); 1826 1827 let ctx = fc.loop_stack.pop().unwrap(); 1828 for patch in ctx.break_patches { 1829 fc.builder.patch_jump(patch); 1830 } 1831 for patch in ctx.continue_patches { 1832 fc.builder.patch_jump_to(patch, cond_start); 1833 } 1834 Ok(()) 1835} 1836 1837fn compile_for( 1838 fc: &mut FunctionCompiler, 1839 init: Option<&ForInit>, 1840 test: Option<&Expr>, 1841 update: Option<&Expr>, 1842 body: &Stmt, 1843 label: Option<String>, 1844 1845 result_reg: Reg, 1846) -> Result<(), JsError> { 1847 let saved_locals = fc.locals.len(); 1848 let saved_next = fc.next_reg; 1849 1850 // Init. 1851 if let Some(init) = init { 1852 match init { 1853 ForInit::VarDecl { kind, declarators } => { 1854 for decl in declarators { 1855 compile_var_declarator(fc, decl, *kind)?; 1856 } 1857 } 1858 ForInit::Expr(expr) => { 1859 let tmp = fc.alloc_reg(); 1860 compile_expr(fc, expr, tmp)?; 1861 fc.free_reg(tmp); 1862 } 1863 } 1864 } 1865 1866 let loop_start = fc.builder.offset(); 1867 1868 // Test. 1869 let exit_patch = if let Some(test) = test { 1870 let cond = fc.alloc_reg(); 1871 compile_expr(fc, test, cond)?; 1872 let patch = fc.builder.emit_cond_jump(Op::JumpIfFalse, cond); 1873 fc.free_reg(cond); 1874 Some(patch) 1875 } else { 1876 None 1877 }; 1878 1879 fc.loop_stack.push(LoopCtx { 1880 label, 1881 break_patches: Vec::new(), 1882 continue_patches: Vec::new(), 1883 }); 1884 1885 compile_stmt(fc, body, result_reg)?; 1886 1887 // continue in a for-loop should jump here (the update expression). 1888 let continue_target = fc.builder.offset(); 1889 1890 // Update. 1891 if let Some(update) = update { 1892 let tmp = fc.alloc_reg(); 1893 compile_expr(fc, update, tmp)?; 1894 fc.free_reg(tmp); 1895 } 1896 1897 fc.builder.emit_jump_to(loop_start); 1898 1899 if let Some(patch) = exit_patch { 1900 fc.builder.patch_jump(patch); 1901 } 1902 1903 let ctx = fc.loop_stack.pop().unwrap(); 1904 for patch in ctx.break_patches { 1905 fc.builder.patch_jump(patch); 1906 } 1907 for patch in ctx.continue_patches { 1908 fc.builder.patch_jump_to(patch, continue_target); 1909 } 1910 1911 fc.locals.truncate(saved_locals); 1912 fc.next_reg = saved_next; 1913 Ok(()) 1914} 1915 1916fn compile_switch( 1917 fc: &mut FunctionCompiler, 1918 discriminant: &Expr, 1919 cases: &[SwitchCase], 1920 1921 result_reg: Reg, 1922) -> Result<(), JsError> { 1923 let disc_reg = fc.alloc_reg(); 1924 compile_expr(fc, discriminant, disc_reg)?; 1925 1926 // Use a loop context for break statements. 1927 fc.loop_stack.push(LoopCtx { 1928 label: None, 1929 break_patches: Vec::new(), 1930 continue_patches: Vec::new(), 1931 }); 1932 1933 // Phase 1: emit comparison jumps for each non-default case. 1934 // Store (case_index, patch_position) for each case with a test. 1935 let mut case_jump_patches: Vec<(usize, usize)> = Vec::new(); 1936 let mut default_index: Option<usize> = None; 1937 1938 for (i, case) in cases.iter().enumerate() { 1939 if let Some(test) = &case.test { 1940 let test_reg = fc.alloc_reg(); 1941 compile_expr(fc, test, test_reg)?; 1942 let cmp_reg = fc.alloc_reg(); 1943 fc.builder 1944 .emit_reg3(Op::StrictEq, cmp_reg, disc_reg, test_reg); 1945 let patch = fc.builder.emit_cond_jump(Op::JumpIfTrue, cmp_reg); 1946 fc.free_reg(cmp_reg); 1947 fc.free_reg(test_reg); 1948 case_jump_patches.push((i, patch)); 1949 } else { 1950 default_index = Some(i); 1951 } 1952 } 1953 1954 // After all comparisons: jump to default body or end. 1955 let fallthrough_patch = fc.builder.emit_jump(Op::Jump); 1956 1957 // Phase 2: emit case bodies in order (fall-through semantics). 1958 let mut body_offsets: Vec<(usize, usize)> = Vec::new(); 1959 for (i, case) in cases.iter().enumerate() { 1960 body_offsets.push((i, fc.builder.offset())); 1961 compile_stmts(fc, &case.consequent, result_reg)?; 1962 } 1963 1964 let end_offset = fc.builder.offset(); 1965 1966 // Patch case test jumps to their respective body offsets. 1967 for (case_idx, patch) in &case_jump_patches { 1968 let body_offset = body_offsets 1969 .iter() 1970 .find(|(i, _)| i == case_idx) 1971 .map(|(_, off)| *off) 1972 .unwrap(); 1973 fc.builder.patch_jump_to(*patch, body_offset); 1974 } 1975 1976 // Patch fallthrough: jump to default body if present, otherwise to end. 1977 if let Some(def_idx) = default_index { 1978 let default_offset = body_offsets 1979 .iter() 1980 .find(|(i, _)| *i == def_idx) 1981 .map(|(_, off)| *off) 1982 .unwrap(); 1983 fc.builder.patch_jump_to(fallthrough_patch, default_offset); 1984 } else { 1985 fc.builder.patch_jump_to(fallthrough_patch, end_offset); 1986 } 1987 1988 fc.free_reg(disc_reg); 1989 1990 let ctx = fc.loop_stack.pop().unwrap(); 1991 for patch in ctx.break_patches { 1992 fc.builder.patch_jump(patch); 1993 } 1994 Ok(()) 1995} 1996 1997fn find_loop_ctx(stack: &[LoopCtx], label: Option<&str>) -> Option<usize> { 1998 if let Some(label) = label { 1999 stack 2000 .iter() 2001 .rposition(|ctx| ctx.label.as_deref() == Some(label)) 2002 } else { 2003 if stack.is_empty() { 2004 None 2005 } else { 2006 Some(stack.len() - 1) 2007 } 2008 } 2009} 2010 2011// ── Expressions ───────────────────────────────────────────── 2012 2013fn compile_expr(fc: &mut FunctionCompiler, expr: &Expr, dst: Reg) -> Result<(), JsError> { 2014 match &expr.kind { 2015 ExprKind::Number(n) => { 2016 // Optimize small integers. 2017 let int_val = *n as i64; 2018 if int_val as f64 == *n && (-128..=127).contains(&int_val) { 2019 fc.builder.emit_load_int8(dst, int_val as i8); 2020 } else { 2021 let ci = fc.builder.add_constant(Constant::Number(*n)); 2022 fc.builder.emit_reg_u16(Op::LoadConst, dst, ci); 2023 } 2024 } 2025 2026 ExprKind::String(s) => { 2027 let ci = fc.builder.add_constant(Constant::String(s.clone())); 2028 fc.builder.emit_reg_u16(Op::LoadConst, dst, ci); 2029 } 2030 2031 ExprKind::Bool(true) => { 2032 fc.builder.emit_reg(Op::LoadTrue, dst); 2033 } 2034 2035 ExprKind::Bool(false) => { 2036 fc.builder.emit_reg(Op::LoadFalse, dst); 2037 } 2038 2039 ExprKind::Null => { 2040 fc.builder.emit_reg(Op::LoadNull, dst); 2041 } 2042 2043 ExprKind::Identifier(name) => { 2044 if let Some(local) = fc.find_local_info(name) { 2045 let reg = local.reg; 2046 let captured = local.is_captured; 2047 if captured { 2048 fc.builder.emit_reg_reg(Op::CellLoad, dst, reg); 2049 } else if reg != dst { 2050 fc.builder.emit_reg_reg(Op::Move, dst, reg); 2051 } 2052 } else if let Some(uv_idx) = fc.find_upvalue(name) { 2053 fc.builder.emit_load_upvalue(dst, uv_idx); 2054 } else { 2055 // Global lookup. 2056 let ni = fc.builder.add_name(name); 2057 fc.builder.emit_load_global(dst, ni); 2058 } 2059 } 2060 2061 ExprKind::This => { 2062 // `this` is loaded as a global named "this" (the VM binds it). 2063 let ni = fc.builder.add_name("this"); 2064 fc.builder.emit_load_global(dst, ni); 2065 } 2066 2067 ExprKind::Binary { op, left, right } => { 2068 let lhs = fc.alloc_reg(); 2069 compile_expr(fc, left, lhs)?; 2070 let rhs = fc.alloc_reg(); 2071 compile_expr(fc, right, rhs)?; 2072 let bytecode_op = binary_op_to_opcode(*op); 2073 fc.builder.emit_reg3(bytecode_op, dst, lhs, rhs); 2074 fc.free_reg(rhs); 2075 fc.free_reg(lhs); 2076 } 2077 2078 ExprKind::Unary { op, argument } => { 2079 if *op == UnaryOp::Delete { 2080 // Handle delete specially: need object + key form for member expressions. 2081 match &argument.kind { 2082 ExprKind::Member { 2083 object, 2084 property, 2085 computed, 2086 } => { 2087 let obj_r = fc.alloc_reg(); 2088 compile_expr(fc, object, obj_r)?; 2089 let key_r = fc.alloc_reg(); 2090 if *computed { 2091 compile_expr(fc, property, key_r)?; 2092 } else if let ExprKind::Identifier(name) = &property.kind { 2093 let ci = fc.builder.add_constant(Constant::String(name.clone())); 2094 fc.builder.emit_reg_u16(Op::LoadConst, key_r, ci); 2095 } else { 2096 compile_expr(fc, property, key_r)?; 2097 } 2098 fc.builder.emit_reg3(Op::Delete, dst, obj_r, key_r); 2099 fc.free_reg(key_r); 2100 fc.free_reg(obj_r); 2101 } 2102 _ => { 2103 // `delete x` on a simple identifier: always true in non-strict mode. 2104 fc.builder.emit_reg(Op::LoadTrue, dst); 2105 } 2106 } 2107 } else { 2108 let src = fc.alloc_reg(); 2109 compile_expr(fc, argument, src)?; 2110 match op { 2111 UnaryOp::Minus => fc.builder.emit_reg_reg(Op::Neg, dst, src), 2112 UnaryOp::Plus => { 2113 fc.builder.emit_reg_reg(Op::Move, dst, src); 2114 } 2115 UnaryOp::Not => fc.builder.emit_reg_reg(Op::LogicalNot, dst, src), 2116 UnaryOp::BitwiseNot => fc.builder.emit_reg_reg(Op::BitNot, dst, src), 2117 UnaryOp::Typeof => fc.builder.emit_reg_reg(Op::TypeOf, dst, src), 2118 UnaryOp::Void => fc.builder.emit_reg_reg(Op::Void, dst, src), 2119 UnaryOp::Delete => unreachable!(), 2120 } 2121 fc.free_reg(src); 2122 } 2123 } 2124 2125 ExprKind::Update { 2126 op, 2127 argument, 2128 prefix, 2129 } => { 2130 // Get current value. 2131 compile_expr(fc, argument, dst)?; 2132 2133 let one = fc.alloc_reg(); 2134 fc.builder.emit_load_int8(one, 1); 2135 2136 if *prefix { 2137 // ++x / --x: modify first, return modified. 2138 match op { 2139 UpdateOp::Increment => fc.builder.emit_reg3(Op::Add, dst, dst, one), 2140 UpdateOp::Decrement => fc.builder.emit_reg3(Op::Sub, dst, dst, one), 2141 } 2142 // Store back. 2143 compile_store(fc, argument, dst)?; 2144 } else { 2145 // x++ / x--: return original, then modify. 2146 let tmp = fc.alloc_reg(); 2147 fc.builder.emit_reg_reg(Op::Move, tmp, dst); 2148 match op { 2149 UpdateOp::Increment => fc.builder.emit_reg3(Op::Add, tmp, tmp, one), 2150 UpdateOp::Decrement => fc.builder.emit_reg3(Op::Sub, tmp, tmp, one), 2151 } 2152 compile_store(fc, argument, tmp)?; 2153 fc.free_reg(tmp); 2154 } 2155 fc.free_reg(one); 2156 } 2157 2158 ExprKind::Logical { op, left, right } => { 2159 compile_expr(fc, left, dst)?; 2160 match op { 2161 LogicalOp::And => { 2162 // Short-circuit: if falsy, skip right. 2163 let skip = fc.builder.emit_cond_jump(Op::JumpIfFalse, dst); 2164 compile_expr(fc, right, dst)?; 2165 fc.builder.patch_jump(skip); 2166 } 2167 LogicalOp::Or => { 2168 let skip = fc.builder.emit_cond_jump(Op::JumpIfTrue, dst); 2169 compile_expr(fc, right, dst)?; 2170 fc.builder.patch_jump(skip); 2171 } 2172 LogicalOp::Nullish => { 2173 let skip = fc.builder.emit_cond_jump(Op::JumpIfNullish, dst); 2174 // If NOT nullish, skip the right side. Wait — JumpIfNullish 2175 // should mean "jump if nullish" so we want: evaluate left, 2176 // if NOT nullish skip right. 2177 // Let's invert: evaluate left, check if nullish → evaluate right. 2178 // We need the jump to skip the "evaluate right" if NOT nullish. 2179 // Since JumpIfNullish jumps when nullish, we need the inverse. 2180 // Instead: use a two-step approach. 2181 // 2182 // Actually, rethink: for `a ?? b`: 2183 // 1. evaluate a → dst 2184 // 2. if dst is NOT null/undefined, jump to end 2185 // 3. evaluate b → dst 2186 // end: 2187 // JumpIfNullish jumps when IS nullish. So we want jump when NOT nullish. 2188 // Let's just use a "not nullish" check. 2189 // For now: negate and use JumpIfFalse. 2190 // Actually simpler: skip right when not nullish. 2191 // JumpIfNullish jumps WHEN nullish. We want to jump over right when NOT nullish. 2192 // So: 2193 // evaluate a → dst 2194 // JumpIfNullish dst → evaluate_right 2195 // Jump → end 2196 // evaluate_right: evaluate b → dst 2197 // end: 2198 // But we already emitted JumpIfNullish. Let's fix this. 2199 // The JumpIfNullish we emitted jumps to "after patch", which is where 2200 // we'll put the right-side code. We need another jump to skip right. 2201 let end_patch = fc.builder.emit_jump(Op::Jump); 2202 fc.builder.patch_jump(skip); // nullish → evaluate right 2203 compile_expr(fc, right, dst)?; 2204 fc.builder.patch_jump(end_patch); 2205 } 2206 } 2207 } 2208 2209 ExprKind::Assignment { op, left, right } => { 2210 if *op == AssignOp::Assign { 2211 compile_expr(fc, right, dst)?; 2212 compile_store(fc, left, dst)?; 2213 } else { 2214 // Compound assignment: load current, operate, store. 2215 compile_expr(fc, left, dst)?; 2216 let rhs = fc.alloc_reg(); 2217 compile_expr(fc, right, rhs)?; 2218 let arith_op = compound_assign_op(*op); 2219 fc.builder.emit_reg3(arith_op, dst, dst, rhs); 2220 fc.free_reg(rhs); 2221 compile_store(fc, left, dst)?; 2222 } 2223 } 2224 2225 ExprKind::Conditional { 2226 test, 2227 consequent, 2228 alternate, 2229 } => { 2230 let cond = fc.alloc_reg(); 2231 compile_expr(fc, test, cond)?; 2232 let else_patch = fc.builder.emit_cond_jump(Op::JumpIfFalse, cond); 2233 fc.free_reg(cond); 2234 compile_expr(fc, consequent, dst)?; 2235 let end_patch = fc.builder.emit_jump(Op::Jump); 2236 fc.builder.patch_jump(else_patch); 2237 compile_expr(fc, alternate, dst)?; 2238 fc.builder.patch_jump(end_patch); 2239 } 2240 2241 ExprKind::Call { callee, arguments } => { 2242 // Detect method calls (obj.method()) to set `this`. 2243 if let ExprKind::Member { 2244 object, 2245 property, 2246 computed, 2247 } = &callee.kind 2248 { 2249 // Layout: [obj_reg] [func_reg] [arg0] [arg1] ... 2250 // We keep obj_reg alive so we can set `this` before the call. 2251 let obj_reg = fc.alloc_reg(); 2252 compile_expr(fc, object, obj_reg)?; 2253 let func_reg = fc.alloc_reg(); 2254 if !computed { 2255 if let ExprKind::Identifier(name) = &property.kind { 2256 let ni = fc.builder.add_name(name); 2257 fc.builder.emit_get_prop_name(func_reg, obj_reg, ni); 2258 } else { 2259 let key_reg = fc.alloc_reg(); 2260 compile_expr(fc, property, key_reg)?; 2261 fc.builder 2262 .emit_reg3(Op::GetProperty, func_reg, obj_reg, key_reg); 2263 fc.free_reg(key_reg); 2264 } 2265 } else { 2266 let key_reg = fc.alloc_reg(); 2267 compile_expr(fc, property, key_reg)?; 2268 fc.builder 2269 .emit_reg3(Op::GetProperty, func_reg, obj_reg, key_reg); 2270 fc.free_reg(key_reg); 2271 } 2272 2273 // Set `this` to the receiver object before calling. 2274 let this_ni = fc.builder.add_name("this"); 2275 fc.builder.emit_store_global(this_ni, obj_reg); 2276 2277 let args_start = fc.next_reg; 2278 let arg_count = arguments.len().min(255) as u8; 2279 for arg in arguments { 2280 let arg_reg = fc.alloc_reg(); 2281 compile_expr(fc, arg, arg_reg)?; 2282 } 2283 2284 fc.builder.emit_call(dst, func_reg, args_start, arg_count); 2285 2286 // Free in LIFO order: args, func_reg, obj_reg. 2287 for _ in 0..arg_count { 2288 fc.next_reg -= 1; 2289 } 2290 fc.free_reg(func_reg); 2291 fc.free_reg(obj_reg); 2292 } else { 2293 let func_reg = fc.alloc_reg(); 2294 compile_expr(fc, callee, func_reg)?; 2295 2296 let args_start = fc.next_reg; 2297 let arg_count = arguments.len().min(255) as u8; 2298 for arg in arguments { 2299 let arg_reg = fc.alloc_reg(); 2300 compile_expr(fc, arg, arg_reg)?; 2301 } 2302 2303 fc.builder.emit_call(dst, func_reg, args_start, arg_count); 2304 2305 for _ in 0..arg_count { 2306 fc.next_reg -= 1; 2307 } 2308 fc.free_reg(func_reg); 2309 } 2310 } 2311 2312 ExprKind::New { callee, arguments } => { 2313 // For now, compile like a regular call. The VM will differentiate 2314 // based on the `New` vs `Call` distinction (TODO: add NewCall opcode). 2315 let func_reg = fc.alloc_reg(); 2316 compile_expr(fc, callee, func_reg)?; 2317 2318 let args_start = fc.next_reg; 2319 let arg_count = arguments.len().min(255) as u8; 2320 for arg in arguments { 2321 let arg_reg = fc.alloc_reg(); 2322 compile_expr(fc, arg, arg_reg)?; 2323 } 2324 2325 fc.builder.emit_call(dst, func_reg, args_start, arg_count); 2326 2327 for _ in 0..arg_count { 2328 fc.next_reg -= 1; 2329 } 2330 fc.free_reg(func_reg); 2331 } 2332 2333 ExprKind::Member { 2334 object, 2335 property, 2336 computed, 2337 } => { 2338 let obj_reg = fc.alloc_reg(); 2339 compile_expr(fc, object, obj_reg)?; 2340 2341 if !computed { 2342 // Static member: obj.prop → GetPropertyByName. 2343 if let ExprKind::Identifier(name) = &property.kind { 2344 let ni = fc.builder.add_name(name); 2345 fc.builder.emit_get_prop_name(dst, obj_reg, ni); 2346 } else { 2347 let key_reg = fc.alloc_reg(); 2348 compile_expr(fc, property, key_reg)?; 2349 fc.builder.emit_reg3(Op::GetProperty, dst, obj_reg, key_reg); 2350 fc.free_reg(key_reg); 2351 } 2352 } else { 2353 // Computed member: obj[expr]. 2354 let key_reg = fc.alloc_reg(); 2355 compile_expr(fc, property, key_reg)?; 2356 fc.builder.emit_reg3(Op::GetProperty, dst, obj_reg, key_reg); 2357 fc.free_reg(key_reg); 2358 } 2359 fc.free_reg(obj_reg); 2360 } 2361 2362 ExprKind::Array(elements) => { 2363 let has_spread = elements 2364 .iter() 2365 .any(|e| matches!(e, Some(ArrayElement::Spread(_)))); 2366 2367 fc.builder.emit_reg(Op::CreateArray, dst); 2368 2369 if has_spread { 2370 // When spreads are present, we track the index dynamically. 2371 // For each normal element, push at current length. 2372 // For spread elements, use the Spread opcode. 2373 for el in elements.iter().flatten() { 2374 match el { 2375 ArrayElement::Expr(e) => { 2376 let val_reg = fc.alloc_reg(); 2377 compile_expr(fc, e, val_reg)?; 2378 // Get current length as index. 2379 let idx_reg = fc.alloc_reg(); 2380 let len_ni = fc.builder.add_name("length"); 2381 fc.builder.emit_get_prop_name(idx_reg, dst, len_ni); 2382 fc.builder.emit_reg3(Op::SetProperty, dst, idx_reg, val_reg); 2383 // Increment length. 2384 let one_r = fc.alloc_reg(); 2385 fc.builder.emit_load_int8(one_r, 1); 2386 fc.builder.emit_reg3(Op::Add, idx_reg, idx_reg, one_r); 2387 fc.builder.emit_set_prop_name(dst, len_ni, idx_reg); 2388 fc.free_reg(one_r); 2389 fc.free_reg(idx_reg); 2390 fc.free_reg(val_reg); 2391 } 2392 ArrayElement::Spread(e) => { 2393 let spread_src = fc.alloc_reg(); 2394 compile_expr(fc, e, spread_src)?; 2395 fc.builder.emit_spread(dst, spread_src); 2396 fc.free_reg(spread_src); 2397 } 2398 } 2399 } 2400 } else { 2401 // No spreads: use simple indexed assignment. 2402 for (i, elem) in elements.iter().enumerate() { 2403 if let Some(ArrayElement::Expr(e)) = elem { 2404 let val_reg = fc.alloc_reg(); 2405 compile_expr(fc, e, val_reg)?; 2406 let idx_reg = fc.alloc_reg(); 2407 if i <= 127 { 2408 fc.builder.emit_load_int8(idx_reg, i as i8); 2409 } else { 2410 let ci = fc.builder.add_constant(Constant::Number(i as f64)); 2411 fc.builder.emit_reg_u16(Op::LoadConst, idx_reg, ci); 2412 } 2413 fc.builder.emit_reg3(Op::SetProperty, dst, idx_reg, val_reg); 2414 fc.free_reg(idx_reg); 2415 fc.free_reg(val_reg); 2416 } 2417 } 2418 // Set length. 2419 if !elements.is_empty() { 2420 let len_name = fc.builder.add_name("length"); 2421 let len_reg = fc.alloc_reg(); 2422 if elements.len() <= 127 { 2423 fc.builder.emit_load_int8(len_reg, elements.len() as i8); 2424 } else { 2425 let ci = fc 2426 .builder 2427 .add_constant(Constant::Number(elements.len() as f64)); 2428 fc.builder.emit_reg_u16(Op::LoadConst, len_reg, ci); 2429 } 2430 fc.builder.emit_set_prop_name(dst, len_name, len_reg); 2431 fc.free_reg(len_reg); 2432 } 2433 } 2434 } 2435 2436 ExprKind::Object(properties) => { 2437 fc.builder.emit_reg(Op::CreateObject, dst); 2438 for prop in properties { 2439 let val_reg = fc.alloc_reg(); 2440 if let Some(value) = &prop.value { 2441 compile_expr(fc, value, val_reg)?; 2442 } else { 2443 // Shorthand: `{ x }` means `{ x: x }`. 2444 if let PropertyKey::Identifier(name) = &prop.key { 2445 if let Some(local) = fc.find_local_info(name) { 2446 let reg = local.reg; 2447 let captured = local.is_captured; 2448 if captured { 2449 fc.builder.emit_reg_reg(Op::CellLoad, val_reg, reg); 2450 } else { 2451 fc.builder.emit_reg_reg(Op::Move, val_reg, reg); 2452 } 2453 } else if let Some(uv_idx) = fc.find_upvalue(name) { 2454 fc.builder.emit_load_upvalue(val_reg, uv_idx); 2455 } else { 2456 let ni = fc.builder.add_name(name); 2457 fc.builder.emit_load_global(val_reg, ni); 2458 } 2459 } else { 2460 fc.builder.emit_reg(Op::LoadUndefined, val_reg); 2461 } 2462 } 2463 2464 match &prop.key { 2465 PropertyKey::Identifier(name) | PropertyKey::String(name) => { 2466 let ni = fc.builder.add_name(name); 2467 fc.builder.emit_set_prop_name(dst, ni, val_reg); 2468 } 2469 PropertyKey::Number(n) => { 2470 let key_reg = fc.alloc_reg(); 2471 let ci = fc.builder.add_constant(Constant::Number(*n)); 2472 fc.builder.emit_reg_u16(Op::LoadConst, key_reg, ci); 2473 fc.builder.emit_reg3(Op::SetProperty, dst, key_reg, val_reg); 2474 fc.free_reg(key_reg); 2475 } 2476 PropertyKey::Computed(expr) => { 2477 let key_reg = fc.alloc_reg(); 2478 compile_expr(fc, expr, key_reg)?; 2479 fc.builder.emit_reg3(Op::SetProperty, dst, key_reg, val_reg); 2480 fc.free_reg(key_reg); 2481 } 2482 } 2483 fc.free_reg(val_reg); 2484 } 2485 } 2486 2487 ExprKind::Function(func_def) => { 2488 let inner = compile_function_body_with_captures(fc, func_def)?; 2489 let func_idx = fc.builder.add_function(inner); 2490 fc.builder.emit_reg_u16(Op::CreateClosure, dst, func_idx); 2491 } 2492 2493 ExprKind::Arrow { 2494 params, 2495 body, 2496 is_async, 2497 } => { 2498 // Collect free variables from the arrow body. 2499 let free_vars = collect_free_vars_arrow(params, body); 2500 2501 // Resolve upvalues against the parent scope. 2502 let mut upvalue_entries = Vec::new(); 2503 for name in &free_vars { 2504 if let Some(local) = fc.find_local_info(name) { 2505 let reg = local.reg; 2506 let is_const = local.is_const; 2507 if let Some(l) = fc.locals.iter_mut().rev().find(|l| l.name == *name) { 2508 l.is_captured = true; 2509 } 2510 upvalue_entries.push(UpvalueEntry { 2511 name: name.clone(), 2512 def: UpvalueDef { 2513 is_local: true, 2514 index: reg, 2515 }, 2516 is_const, 2517 }); 2518 } else if let Some(parent_uv_idx) = fc.find_upvalue(name) { 2519 let is_const = fc.is_upvalue_const(parent_uv_idx); 2520 upvalue_entries.push(UpvalueEntry { 2521 name: name.clone(), 2522 def: UpvalueDef { 2523 is_local: false, 2524 index: parent_uv_idx, 2525 }, 2526 is_const, 2527 }); 2528 } 2529 } 2530 2531 let param_count = params.len().min(255) as u8; 2532 let mut inner = FunctionCompiler::new("<arrow>".into(), param_count); 2533 2534 // Copy upvalue entries. 2535 for entry in &upvalue_entries { 2536 inner.upvalues.push(UpvalueEntry { 2537 name: entry.name.clone(), 2538 def: entry.def.clone(), 2539 is_const: entry.is_const, 2540 }); 2541 } 2542 2543 // Pre-scan for inner captures within the arrow body. 2544 match body { 2545 ArrowBody::Expr(_) => {} 2546 ArrowBody::Block(stmts) => { 2547 inner.captured_names = collect_inner_captures(stmts); 2548 } 2549 } 2550 2551 for p in params { 2552 if let PatternKind::Identifier(pname) = &p.kind { 2553 let is_captured = inner.captured_names.contains(pname.as_str()); 2554 inner.define_local_ext(pname, is_captured, false); 2555 } else { 2556 let _ = inner.alloc_reg(); 2557 } 2558 } 2559 2560 // Box captured parameters. 2561 for p in params { 2562 if let PatternKind::Identifier(pname) = &p.kind { 2563 if let Some(local) = inner.find_local_info(pname) { 2564 if local.is_captured { 2565 let reg = local.reg; 2566 let tmp = inner.alloc_reg(); 2567 inner.builder.emit_reg_reg(Op::Move, tmp, reg); 2568 inner.builder.emit_reg(Op::NewCell, reg); 2569 inner.builder.emit_reg_reg(Op::CellStore, reg, tmp); 2570 inner.free_reg(tmp); 2571 } 2572 } 2573 } 2574 } 2575 2576 let result = inner.alloc_reg(); 2577 match body { 2578 ArrowBody::Expr(e) => { 2579 compile_expr(&mut inner, e, result)?; 2580 } 2581 ArrowBody::Block(stmts) => { 2582 inner.builder.emit_reg(Op::LoadUndefined, result); 2583 compile_stmts(&mut inner, stmts, result)?; 2584 } 2585 } 2586 inner.builder.emit_reg(Op::Return, result); 2587 let mut inner_func = inner.builder.finish(); 2588 inner_func.upvalue_defs = upvalue_entries.iter().map(|e| e.def.clone()).collect(); 2589 inner_func.is_async = *is_async; 2590 let func_idx = fc.builder.add_function(inner_func); 2591 fc.builder.emit_reg_u16(Op::CreateClosure, dst, func_idx); 2592 } 2593 2594 ExprKind::Class(class_def) => { 2595 // Class expression: compile like class decl but into dst. 2596 let name = class_def.id.clone().unwrap_or_default(); 2597 // Find constructor. 2598 let ctor = class_def.body.iter().find(|m| { 2599 matches!( 2600 &m.kind, 2601 ClassMemberKind::Method { 2602 kind: MethodKind::Constructor, 2603 .. 2604 } 2605 ) 2606 }); 2607 if let Some(member) = ctor { 2608 if let ClassMemberKind::Method { value, .. } = &member.kind { 2609 let inner = compile_function_body_with_captures(fc, value)?; 2610 let func_idx = fc.builder.add_function(inner); 2611 fc.builder.emit_reg_u16(Op::CreateClosure, dst, func_idx); 2612 } 2613 } else { 2614 let mut empty = BytecodeBuilder::new(name, 0); 2615 let r = 0u8; 2616 empty.func.register_count = 1; 2617 empty.emit_reg(Op::LoadUndefined, r); 2618 empty.emit_reg(Op::Return, r); 2619 let func_idx = fc.builder.add_function(empty.finish()); 2620 fc.builder.emit_reg_u16(Op::CreateClosure, dst, func_idx); 2621 } 2622 2623 // Compile methods as properties on the constructor. 2624 for member in &class_def.body { 2625 match &member.kind { 2626 ClassMemberKind::Method { 2627 key, 2628 value, 2629 kind, 2630 is_static: _, 2631 computed: _, 2632 } => { 2633 if matches!(kind, MethodKind::Constructor) { 2634 continue; 2635 } 2636 let method_name = match key { 2637 PropertyKey::Identifier(s) | PropertyKey::String(s) => s.clone(), 2638 _ => continue, 2639 }; 2640 let inner = compile_function_body_with_captures(fc, value)?; 2641 let func_idx = fc.builder.add_function(inner); 2642 let method_reg = fc.alloc_reg(); 2643 fc.builder 2644 .emit_reg_u16(Op::CreateClosure, method_reg, func_idx); 2645 let name_idx = fc.builder.add_name(&method_name); 2646 fc.builder.emit_set_prop_name(dst, name_idx, method_reg); 2647 fc.free_reg(method_reg); 2648 } 2649 ClassMemberKind::Property { .. } => {} 2650 } 2651 } 2652 } 2653 2654 ExprKind::Sequence(exprs) => { 2655 for e in exprs { 2656 compile_expr(fc, e, dst)?; 2657 } 2658 } 2659 2660 ExprKind::Spread(inner) => { 2661 compile_expr(fc, inner, dst)?; 2662 } 2663 2664 ExprKind::TemplateLiteral { 2665 quasis, 2666 expressions, 2667 } => { 2668 // Compile template literal as string concatenation. 2669 if quasis.len() == 1 && expressions.is_empty() { 2670 let ci = fc.builder.add_constant(Constant::String(quasis[0].clone())); 2671 fc.builder.emit_reg_u16(Op::LoadConst, dst, ci); 2672 } else { 2673 // Start with first quasi. 2674 let ci = fc.builder.add_constant(Constant::String(quasis[0].clone())); 2675 fc.builder.emit_reg_u16(Op::LoadConst, dst, ci); 2676 for (i, expr) in expressions.iter().enumerate() { 2677 let tmp = fc.alloc_reg(); 2678 compile_expr(fc, expr, tmp)?; 2679 fc.builder.emit_reg3(Op::Add, dst, dst, tmp); 2680 fc.free_reg(tmp); 2681 if i + 1 < quasis.len() { 2682 let qi = fc 2683 .builder 2684 .add_constant(Constant::String(quasis[i + 1].clone())); 2685 let tmp2 = fc.alloc_reg(); 2686 fc.builder.emit_reg_u16(Op::LoadConst, tmp2, qi); 2687 fc.builder.emit_reg3(Op::Add, dst, dst, tmp2); 2688 fc.free_reg(tmp2); 2689 } 2690 } 2691 } 2692 } 2693 2694 ExprKind::TaggedTemplate { tag, quasi } => { 2695 // Simplified: call tag with the template as argument. 2696 let func_reg = fc.alloc_reg(); 2697 compile_expr(fc, tag, func_reg)?; 2698 let arg_reg = fc.alloc_reg(); 2699 compile_expr(fc, quasi, arg_reg)?; 2700 fc.builder.emit_call(dst, func_reg, arg_reg, 1); 2701 fc.free_reg(arg_reg); 2702 fc.free_reg(func_reg); 2703 } 2704 2705 ExprKind::Yield { argument, delegate } => { 2706 if *delegate { 2707 // yield* expr: iterate the sub-iterator and yield each value. 2708 let iter_r = fc.alloc_reg(); 2709 if let Some(arg) = argument { 2710 compile_expr(fc, arg, iter_r)?; 2711 } else { 2712 fc.builder.emit_reg(Op::LoadUndefined, iter_r); 2713 } 2714 2715 // Get iterator from the expression. 2716 let iter_method_r = fc.alloc_reg(); 2717 let sym_iter_ni = fc.builder.add_name("@@iterator"); 2718 fc.builder 2719 .emit_get_prop_name(iter_method_r, iter_r, sym_iter_ni); 2720 let this_ni = fc.builder.add_name("this"); 2721 fc.builder.emit_store_global(this_ni, iter_r); 2722 let iterator_r = fc.alloc_reg(); 2723 let args_start = fc.next_reg; 2724 fc.builder 2725 .emit_call(iterator_r, iter_method_r, args_start, 0); 2726 2727 // Get next method. 2728 let next_r = fc.alloc_reg(); 2729 let next_ni = fc.builder.add_name("next"); 2730 fc.builder.emit_get_prop_name(next_r, iterator_r, next_ni); 2731 2732 let result_r = fc.alloc_reg(); 2733 let done_r = fc.alloc_reg(); 2734 let val_r = fc.alloc_reg(); 2735 2736 let loop_start = fc.builder.offset(); 2737 2738 // Call next(). 2739 fc.builder.emit_store_global(this_ni, iterator_r); 2740 fc.builder.emit_call(result_r, next_r, args_start, 0); 2741 2742 let done_ni = fc.builder.add_name("done"); 2743 let value_ni = fc.builder.add_name("value"); 2744 fc.builder.emit_get_prop_name(done_r, result_r, done_ni); 2745 2746 let exit_patch = fc.builder.emit_cond_jump(Op::JumpIfTrue, done_r); 2747 2748 fc.builder.emit_get_prop_name(val_r, result_r, value_ni); 2749 2750 // Yield the value. 2751 fc.builder.emit_yield(dst, val_r); 2752 2753 // Jump back. 2754 fc.builder.emit_jump_to(loop_start); 2755 2756 // Exit: the last result's value is the yield* expression value. 2757 fc.builder.patch_jump(exit_patch); 2758 fc.builder.emit_get_prop_name(dst, result_r, value_ni); 2759 2760 fc.free_reg(val_r); 2761 fc.free_reg(done_r); 2762 fc.free_reg(result_r); 2763 fc.free_reg(next_r); 2764 fc.free_reg(iterator_r); 2765 fc.free_reg(iter_method_r); 2766 fc.free_reg(iter_r); 2767 } else { 2768 // yield expr: emit Yield opcode. 2769 let src = fc.alloc_reg(); 2770 if let Some(arg) = argument { 2771 compile_expr(fc, arg, src)?; 2772 } else { 2773 fc.builder.emit_reg(Op::LoadUndefined, src); 2774 } 2775 fc.builder.emit_yield(dst, src); 2776 fc.free_reg(src); 2777 } 2778 } 2779 2780 ExprKind::Await(inner) => { 2781 let src = fc.alloc_reg(); 2782 compile_expr(fc, inner, src)?; 2783 fc.builder.emit_await(dst, src); 2784 fc.free_reg(src); 2785 } 2786 2787 ExprKind::RegExp { pattern, flags } => { 2788 // Compile as: RegExp(pattern, flags) — a call to the global constructor. 2789 let func_reg = fc.alloc_reg(); 2790 let name_idx = fc.builder.add_name("RegExp"); 2791 fc.builder.emit_reg_u16(Op::LoadGlobal, func_reg, name_idx); 2792 2793 let args_start = fc.next_reg; 2794 let pat_reg = fc.alloc_reg(); 2795 let pat_idx = fc.builder.add_constant(Constant::String(pattern.clone())); 2796 fc.builder.emit_reg_u16(Op::LoadConst, pat_reg, pat_idx); 2797 2798 let flags_reg = fc.alloc_reg(); 2799 let flags_idx = fc.builder.add_constant(Constant::String(flags.clone())); 2800 fc.builder.emit_reg_u16(Op::LoadConst, flags_reg, flags_idx); 2801 2802 fc.builder.emit_call(dst, func_reg, args_start, 2); 2803 2804 fc.next_reg -= 1; // flags_reg 2805 fc.next_reg -= 1; // pat_reg 2806 fc.free_reg(func_reg); 2807 } 2808 2809 ExprKind::OptionalChain { base } => { 2810 compile_expr(fc, base, dst)?; 2811 } 2812 } 2813 Ok(()) 2814} 2815 2816/// Compile a store operation (assignment target). 2817fn compile_store(fc: &mut FunctionCompiler, target: &Expr, src: Reg) -> Result<(), JsError> { 2818 match &target.kind { 2819 ExprKind::Identifier(name) => { 2820 if let Some(local) = fc.find_local_info(name) { 2821 if local.is_const { 2822 return Err(JsError::SyntaxError(format!( 2823 "Assignment to constant variable '{name}'" 2824 ))); 2825 } 2826 let reg = local.reg; 2827 let captured = local.is_captured; 2828 if captured { 2829 fc.builder.emit_reg_reg(Op::CellStore, reg, src); 2830 } else if reg != src { 2831 fc.builder.emit_reg_reg(Op::Move, reg, src); 2832 } 2833 } else if let Some(uv_idx) = fc.find_upvalue(name) { 2834 if fc.is_upvalue_const(uv_idx) { 2835 return Err(JsError::SyntaxError(format!( 2836 "Assignment to constant variable '{name}'" 2837 ))); 2838 } 2839 fc.builder.emit_store_upvalue(uv_idx, src); 2840 } else { 2841 let ni = fc.builder.add_name(name); 2842 fc.builder.emit_store_global(ni, src); 2843 } 2844 } 2845 ExprKind::Member { 2846 object, 2847 property, 2848 computed, 2849 } => { 2850 let obj_reg = fc.alloc_reg(); 2851 compile_expr(fc, object, obj_reg)?; 2852 if !computed { 2853 if let ExprKind::Identifier(name) = &property.kind { 2854 let ni = fc.builder.add_name(name); 2855 fc.builder.emit_set_prop_name(obj_reg, ni, src); 2856 } else { 2857 let key_reg = fc.alloc_reg(); 2858 compile_expr(fc, property, key_reg)?; 2859 fc.builder.emit_reg3(Op::SetProperty, obj_reg, key_reg, src); 2860 fc.free_reg(key_reg); 2861 } 2862 } else { 2863 let key_reg = fc.alloc_reg(); 2864 compile_expr(fc, property, key_reg)?; 2865 fc.builder.emit_reg3(Op::SetProperty, obj_reg, key_reg, src); 2866 fc.free_reg(key_reg); 2867 } 2868 fc.free_reg(obj_reg); 2869 } 2870 _ => { 2871 // Other assignment targets (destructuring) not handled here. 2872 } 2873 } 2874 Ok(()) 2875} 2876 2877fn binary_op_to_opcode(op: BinaryOp) -> Op { 2878 match op { 2879 BinaryOp::Add => Op::Add, 2880 BinaryOp::Sub => Op::Sub, 2881 BinaryOp::Mul => Op::Mul, 2882 BinaryOp::Div => Op::Div, 2883 BinaryOp::Rem => Op::Rem, 2884 BinaryOp::Exp => Op::Exp, 2885 BinaryOp::Eq => Op::Eq, 2886 BinaryOp::Ne => Op::NotEq, 2887 BinaryOp::StrictEq => Op::StrictEq, 2888 BinaryOp::StrictNe => Op::StrictNotEq, 2889 BinaryOp::Lt => Op::LessThan, 2890 BinaryOp::Le => Op::LessEq, 2891 BinaryOp::Gt => Op::GreaterThan, 2892 BinaryOp::Ge => Op::GreaterEq, 2893 BinaryOp::Shl => Op::ShiftLeft, 2894 BinaryOp::Shr => Op::ShiftRight, 2895 BinaryOp::Ushr => Op::UShiftRight, 2896 BinaryOp::BitAnd => Op::BitAnd, 2897 BinaryOp::BitOr => Op::BitOr, 2898 BinaryOp::BitXor => Op::BitXor, 2899 BinaryOp::In => Op::In, 2900 BinaryOp::Instanceof => Op::InstanceOf, 2901 } 2902} 2903 2904fn compound_assign_op(op: AssignOp) -> Op { 2905 match op { 2906 AssignOp::AddAssign => Op::Add, 2907 AssignOp::SubAssign => Op::Sub, 2908 AssignOp::MulAssign => Op::Mul, 2909 AssignOp::DivAssign => Op::Div, 2910 AssignOp::RemAssign => Op::Rem, 2911 AssignOp::ExpAssign => Op::Exp, 2912 AssignOp::ShlAssign => Op::ShiftLeft, 2913 AssignOp::ShrAssign => Op::ShiftRight, 2914 AssignOp::UshrAssign => Op::UShiftRight, 2915 AssignOp::BitAndAssign => Op::BitAnd, 2916 AssignOp::BitOrAssign => Op::BitOr, 2917 AssignOp::BitXorAssign => Op::BitXor, 2918 AssignOp::AndAssign => Op::BitAnd, // logical AND assignment uses short-circuit; simplified here 2919 AssignOp::OrAssign => Op::BitOr, // likewise 2920 AssignOp::NullishAssign => Op::Move, // simplified 2921 AssignOp::Assign => unreachable!(), 2922 } 2923} 2924 2925#[cfg(test)] 2926mod tests { 2927 use super::*; 2928 use crate::parser::Parser; 2929 2930 /// Helper: parse and compile source, return the top-level function. 2931 fn compile_src(src: &str) -> Function { 2932 let program = Parser::parse(src).expect("parse failed"); 2933 compile(&program).expect("compile failed") 2934 } 2935 2936 #[test] 2937 fn test_compile_number_literal() { 2938 let f = compile_src("42;"); 2939 let dis = f.disassemble(); 2940 assert!(dis.contains("LoadInt8 r0, 42"), "got:\n{dis}"); 2941 assert!(dis.contains("Return r0")); 2942 } 2943 2944 #[test] 2945 fn test_compile_large_number() { 2946 let f = compile_src("3.14;"); 2947 let dis = f.disassemble(); 2948 assert!(dis.contains("LoadConst r0, #0"), "got:\n{dis}"); 2949 assert!( 2950 f.constants.contains(&Constant::Number(3.14)), 2951 "constants: {:?}", 2952 f.constants 2953 ); 2954 } 2955 2956 #[test] 2957 fn test_compile_string() { 2958 let f = compile_src("\"hello\";"); 2959 let dis = f.disassemble(); 2960 assert!(dis.contains("LoadConst r0, #0")); 2961 assert!(f.constants.contains(&Constant::String("hello".into()))); 2962 } 2963 2964 #[test] 2965 fn test_compile_bool_null() { 2966 let f = compile_src("true; false; null;"); 2967 let dis = f.disassemble(); 2968 assert!(dis.contains("LoadTrue r0")); 2969 assert!(dis.contains("LoadFalse r0")); 2970 assert!(dis.contains("LoadNull r0")); 2971 } 2972 2973 #[test] 2974 fn test_compile_binary_arithmetic() { 2975 let f = compile_src("1 + 2;"); 2976 let dis = f.disassemble(); 2977 assert!(dis.contains("Add r0, r1, r2"), "got:\n{dis}"); 2978 } 2979 2980 #[test] 2981 fn test_compile_nested_arithmetic() { 2982 let f = compile_src("(1 + 2) * 3;"); 2983 let dis = f.disassemble(); 2984 assert!(dis.contains("Add"), "got:\n{dis}"); 2985 assert!(dis.contains("Mul"), "got:\n{dis}"); 2986 } 2987 2988 #[test] 2989 fn test_compile_var_decl() { 2990 let f = compile_src("var x = 10; x;"); 2991 let dis = f.disassemble(); 2992 // x should get a register, then be loaded from that register. 2993 assert!(dis.contains("LoadInt8"), "got:\n{dis}"); 2994 assert!( 2995 dis.contains("Move") || dis.contains("LoadInt8"), 2996 "got:\n{dis}" 2997 ); 2998 } 2999 3000 #[test] 3001 fn test_compile_let_const() { 3002 let f = compile_src("let a = 1; const b = 2; a + b;"); 3003 let dis = f.disassemble(); 3004 assert!(dis.contains("Add"), "got:\n{dis}"); 3005 } 3006 3007 #[test] 3008 fn test_compile_if_else() { 3009 let f = compile_src("if (true) { 1; } else { 2; }"); 3010 let dis = f.disassemble(); 3011 assert!(dis.contains("JumpIfFalse"), "got:\n{dis}"); 3012 assert!(dis.contains("Jump"), "got:\n{dis}"); 3013 } 3014 3015 #[test] 3016 fn test_compile_while() { 3017 let f = compile_src("var i = 0; while (i < 10) { i = i + 1; }"); 3018 let dis = f.disassemble(); 3019 assert!(dis.contains("LessThan"), "got:\n{dis}"); 3020 assert!(dis.contains("JumpIfFalse"), "got:\n{dis}"); 3021 assert!( 3022 dis.contains("Jump"), 3023 "backward jump should be present: {dis}" 3024 ); 3025 } 3026 3027 #[test] 3028 fn test_compile_do_while() { 3029 let f = compile_src("var i = 0; do { i = i + 1; } while (i < 5);"); 3030 let dis = f.disassemble(); 3031 assert!(dis.contains("JumpIfTrue"), "got:\n{dis}"); 3032 } 3033 3034 #[test] 3035 fn test_compile_for_loop() { 3036 let f = compile_src("for (var i = 0; i < 10; i = i + 1) { i; }"); 3037 let dis = f.disassemble(); 3038 assert!(dis.contains("LessThan"), "got:\n{dis}"); 3039 assert!(dis.contains("JumpIfFalse"), "got:\n{dis}"); 3040 } 3041 3042 #[test] 3043 fn test_compile_function_decl() { 3044 let f = compile_src("function add(a, b) { return a + b; }"); 3045 let dis = f.disassemble(); 3046 assert!(dis.contains("CreateClosure"), "got:\n{dis}"); 3047 assert!(!f.functions.is_empty(), "should have nested function"); 3048 let inner = &f.functions[0]; 3049 assert_eq!(inner.name, "add"); 3050 assert_eq!(inner.param_count, 2); 3051 let inner_dis = inner.disassemble(); 3052 assert!(inner_dis.contains("Add"), "inner:\n{inner_dis}"); 3053 assert!(inner_dis.contains("Return"), "inner:\n{inner_dis}"); 3054 } 3055 3056 #[test] 3057 fn test_compile_function_call() { 3058 let f = compile_src("function f() { return 42; } f();"); 3059 let dis = f.disassemble(); 3060 assert!(dis.contains("Call"), "got:\n{dis}"); 3061 } 3062 3063 #[test] 3064 fn test_compile_arrow_function() { 3065 let f = compile_src("var add = (a, b) => a + b;"); 3066 let dis = f.disassemble(); 3067 assert!(dis.contains("CreateClosure"), "got:\n{dis}"); 3068 let inner = &f.functions[0]; 3069 assert_eq!(inner.param_count, 2); 3070 } 3071 3072 #[test] 3073 fn test_compile_assignment() { 3074 let f = compile_src("var x = 1; x = x + 2;"); 3075 let dis = f.disassemble(); 3076 assert!(dis.contains("Add"), "got:\n{dis}"); 3077 assert!( 3078 dis.contains("Move"), 3079 "assignment should produce Move:\n{dis}" 3080 ); 3081 } 3082 3083 #[test] 3084 fn test_compile_compound_assignment() { 3085 let f = compile_src("var x = 10; x += 5;"); 3086 let dis = f.disassemble(); 3087 assert!(dis.contains("Add"), "got:\n{dis}"); 3088 } 3089 3090 #[test] 3091 fn test_compile_member_access() { 3092 let f = compile_src("var obj = {}; obj.x;"); 3093 let dis = f.disassemble(); 3094 assert!(dis.contains("CreateObject"), "got:\n{dis}"); 3095 assert!(dis.contains("GetPropertyByName"), "got:\n{dis}"); 3096 } 3097 3098 #[test] 3099 fn test_compile_computed_member() { 3100 let f = compile_src("var arr = []; arr[0];"); 3101 let dis = f.disassemble(); 3102 assert!(dis.contains("GetProperty"), "got:\n{dis}"); 3103 } 3104 3105 #[test] 3106 fn test_compile_object_literal() { 3107 let f = compile_src("var obj = { a: 1, b: 2 };"); 3108 let dis = f.disassemble(); 3109 assert!(dis.contains("CreateObject"), "got:\n{dis}"); 3110 assert!(dis.contains("SetPropertyByName"), "got:\n{dis}"); 3111 } 3112 3113 #[test] 3114 fn test_compile_array_literal() { 3115 let f = compile_src("[1, 2, 3];"); 3116 let dis = f.disassemble(); 3117 assert!(dis.contains("CreateArray"), "got:\n{dis}"); 3118 assert!(dis.contains("SetProperty"), "got:\n{dis}"); 3119 } 3120 3121 #[test] 3122 fn test_compile_conditional() { 3123 let f = compile_src("true ? 1 : 2;"); 3124 let dis = f.disassemble(); 3125 assert!(dis.contains("JumpIfFalse"), "got:\n{dis}"); 3126 } 3127 3128 #[test] 3129 fn test_compile_logical_and() { 3130 let f = compile_src("true && false;"); 3131 let dis = f.disassemble(); 3132 assert!(dis.contains("JumpIfFalse"), "short-circuit:\n{dis}"); 3133 } 3134 3135 #[test] 3136 fn test_compile_logical_or() { 3137 let f = compile_src("false || true;"); 3138 let dis = f.disassemble(); 3139 assert!(dis.contains("JumpIfTrue"), "short-circuit:\n{dis}"); 3140 } 3141 3142 #[test] 3143 fn test_compile_typeof() { 3144 let f = compile_src("typeof 42;"); 3145 let dis = f.disassemble(); 3146 assert!(dis.contains("TypeOf"), "got:\n{dis}"); 3147 } 3148 3149 #[test] 3150 fn test_compile_unary_minus() { 3151 let f = compile_src("-42;"); 3152 let dis = f.disassemble(); 3153 assert!(dis.contains("Neg"), "got:\n{dis}"); 3154 } 3155 3156 #[test] 3157 fn test_compile_not() { 3158 let f = compile_src("!true;"); 3159 let dis = f.disassemble(); 3160 assert!(dis.contains("LogicalNot"), "got:\n{dis}"); 3161 } 3162 3163 #[test] 3164 fn test_compile_return() { 3165 let f = compile_src("function f() { return 42; }"); 3166 let inner = &f.functions[0]; 3167 let dis = inner.disassemble(); 3168 assert!(dis.contains("Return"), "got:\n{dis}"); 3169 } 3170 3171 #[test] 3172 fn test_compile_empty_return() { 3173 let f = compile_src("function f() { return; }"); 3174 let inner = &f.functions[0]; 3175 let dis = inner.disassemble(); 3176 assert!(dis.contains("LoadUndefined"), "got:\n{dis}"); 3177 assert!(dis.contains("Return"), "got:\n{dis}"); 3178 } 3179 3180 #[test] 3181 fn test_compile_throw() { 3182 let f = compile_src("function f() { throw 42; }"); 3183 let inner = &f.functions[0]; 3184 let dis = inner.disassemble(); 3185 assert!(dis.contains("Throw"), "got:\n{dis}"); 3186 } 3187 3188 #[test] 3189 fn test_compile_this() { 3190 let f = compile_src("this;"); 3191 let dis = f.disassemble(); 3192 assert!(dis.contains("LoadGlobal"), "got:\n{dis}"); 3193 assert!(f.names.contains(&"this".to_string())); 3194 } 3195 3196 #[test] 3197 fn test_compile_global_var() { 3198 let f = compile_src("console;"); 3199 let dis = f.disassemble(); 3200 assert!(dis.contains("LoadGlobal"), "got:\n{dis}"); 3201 assert!(f.names.contains(&"console".to_string())); 3202 } 3203 3204 #[test] 3205 fn test_compile_template_literal() { 3206 let f = compile_src("`hello`;"); 3207 assert!( 3208 f.constants.contains(&Constant::String("hello".into())), 3209 "constants: {:?}", 3210 f.constants 3211 ); 3212 } 3213 3214 #[test] 3215 fn test_compile_switch() { 3216 let f = compile_src("switch (1) { case 1: 42; break; case 2: 99; break; }"); 3217 let dis = f.disassemble(); 3218 assert!(dis.contains("StrictEq"), "got:\n{dis}"); 3219 } 3220 3221 #[test] 3222 fn test_compile_class() { 3223 let f = compile_src("class Foo { constructor() {} greet() { return 1; } }"); 3224 let dis = f.disassemble(); 3225 assert!(dis.contains("CreateClosure"), "got:\n{dis}"); 3226 } 3227 3228 #[test] 3229 fn test_compile_update_prefix() { 3230 let f = compile_src("var x = 0; ++x;"); 3231 let dis = f.disassemble(); 3232 assert!(dis.contains("Add"), "got:\n{dis}"); 3233 } 3234 3235 #[test] 3236 fn test_compile_comparison() { 3237 let f = compile_src("1 === 2;"); 3238 let dis = f.disassemble(); 3239 assert!(dis.contains("StrictEq"), "got:\n{dis}"); 3240 } 3241 3242 #[test] 3243 fn test_compile_bitwise() { 3244 let f = compile_src("1 & 2;"); 3245 let dis = f.disassemble(); 3246 assert!(dis.contains("BitAnd"), "got:\n{dis}"); 3247 } 3248 3249 #[test] 3250 fn test_compile_void() { 3251 let f = compile_src("void 0;"); 3252 let dis = f.disassemble(); 3253 assert!(dis.contains("Void"), "got:\n{dis}"); 3254 } 3255 3256 #[test] 3257 fn test_disassembler_output_format() { 3258 let f = compile_src("var x = 42; x + 1;"); 3259 let dis = f.disassemble(); 3260 // Should contain function header. 3261 assert!(dis.contains("function <main>")); 3262 // Should contain code section. 3263 assert!(dis.contains("code:")); 3264 // Should have hex offsets. 3265 assert!(dis.contains("0000")); 3266 } 3267 3268 #[test] 3269 fn test_register_allocation_is_minimal() { 3270 // `var a = 1; var b = 2; a + b;` should use few registers. 3271 let f = compile_src("var a = 1; var b = 2; a + b;"); 3272 // r0 = result, r1 = a, r2 = b, r3/r4 = temps for addition 3273 assert!( 3274 f.register_count <= 6, 3275 "too many registers: {}", 3276 f.register_count 3277 ); 3278 } 3279 3280 #[test] 3281 fn test_nested_function_closure() { 3282 let f = compile_src("function outer() { function inner() { return 1; } return inner; }"); 3283 assert_eq!(f.functions.len(), 1); 3284 let outer = &f.functions[0]; 3285 assert_eq!(outer.name, "outer"); 3286 assert_eq!(outer.functions.len(), 1); 3287 let inner = &outer.functions[0]; 3288 assert_eq!(inner.name, "inner"); 3289 } 3290 3291 #[test] 3292 fn test_for_with_no_parts() { 3293 // `for (;;) { break; }` — infinite loop with immediate break. 3294 let f = compile_src("for (;;) { break; }"); 3295 let dis = f.disassemble(); 3296 assert!(dis.contains("Jump"), "got:\n{dis}"); 3297 } 3298 3299 #[test] 3300 fn test_for_continue_targets_update() { 3301 // `continue` in a for-loop must jump to the update expression, not back 3302 // to the condition check. Verify the continue jump goes to the Add (i + 1) 3303 // rather than to the LessThan condition. 3304 let f = compile_src("for (var i = 0; i < 10; i = i + 1) { continue; }"); 3305 let dis = f.disassemble(); 3306 // The for-loop should contain: LessThan (test), JumpIfFalse (exit), 3307 // Jump (continue), Add (update), Jump (back to test). 3308 assert!(dis.contains("LessThan"), "missing test: {dis}"); 3309 assert!(dis.contains("Add"), "missing update: {dis}"); 3310 // There should be at least 2 Jump instructions (continue + back-edge). 3311 let jump_count = dis.matches("Jump ").count(); 3312 assert!( 3313 jump_count >= 2, 3314 "expected >= 2 jumps for continue + back-edge, got {jump_count}: {dis}" 3315 ); 3316 } 3317 3318 #[test] 3319 fn test_do_while_continue_targets_condition() { 3320 // `continue` in do-while must jump to the condition, not the body start. 3321 let f = compile_src("var i = 0; do { i = i + 1; continue; } while (i < 5);"); 3322 let dis = f.disassemble(); 3323 assert!(dis.contains("LessThan"), "missing condition: {dis}"); 3324 assert!(dis.contains("JumpIfTrue"), "missing back-edge: {dis}"); 3325 } 3326 3327 #[test] 3328 fn test_switch_default_case() { 3329 // Default case must not corrupt bytecode. 3330 let f = compile_src("switch (1) { case 1: 10; break; default: 20; break; }"); 3331 let dis = f.disassemble(); 3332 assert!(dis.contains("StrictEq"), "missing case test: {dis}"); 3333 // The first instruction should NOT be corrupted. 3334 assert!( 3335 dis.contains("LoadUndefined r0"), 3336 "first instruction corrupted: {dis}" 3337 ); 3338 } 3339 3340 #[test] 3341 fn test_switch_only_default() { 3342 // Switch with only a default case. 3343 let f = compile_src("switch (42) { default: 99; }"); 3344 let dis = f.disassemble(); 3345 // Should compile without panicking and contain the default body. 3346 assert!(dis.contains("LoadInt8"), "got:\n{dis}"); 3347 } 3348 3349 #[test] 3350 fn test_class_empty_constructor_has_return() { 3351 // A class without an explicit constructor should produce a function with Return. 3352 let f = compile_src("class Foo {}"); 3353 assert!(!f.functions.is_empty(), "should have constructor function"); 3354 let ctor = &f.functions[0]; 3355 let dis = ctor.disassemble(); 3356 assert!( 3357 dis.contains("Return"), 3358 "empty constructor must have Return: {dis}" 3359 ); 3360 } 3361 3362 #[test] 3363 fn test_class_expression_compiles_methods() { 3364 // Class expression should compile methods, not just the constructor. 3365 let f = compile_src("var C = class { greet() { return 1; } };"); 3366 let dis = f.disassemble(); 3367 assert!( 3368 dis.contains("SetPropertyByName"), 3369 "method should be set as property: {dis}" 3370 ); 3371 } 3372}