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