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