we (web engine): Experimental web browser project to understand the limits of Claude
2
fork

Configure Feed

Select the types of activity you want to include in your feed.

Implement JS iterators, generators, and destructuring

- Add Yield and Spread bytecode opcodes, is_generator flag on Function
- Implement generator functions: function*, yield, yield*, next/return/throw
- Generator objects with suspend/resume via saved registers and IP
- Compile for...of loops using the iterator protocol (@@iterator, .next())
- Add @@iterator to built-in types: Array, String, Map, Set
- Array.prototype.keys/values/entries returning proper iterators
- String.prototype[@@iterator] iterating over characters
- Fix array/object destructuring register allocation (LIFO compliance)
- Add rest element support for array destructuring ([a, ...rest])
- Add rest property support for object destructuring ({a, ...rest})
- Implement spread operator in array literals ([...arr])
- Spread opcode iterates via @@iterator protocol
- Custom iterables work with for...of
- Generator-based iterables work with for...of
- Remove 'generators' from test262 unsupported features list
- 25 new tests covering all features

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

+1731 -71
+269
crates/js/src/builtins.rs
··· 862 862 863 863 let at = make_native(gc, "at", array_at); 864 864 set_builtin_prop(gc, proto, "at", Value::Function(at)); 865 + 866 + // @@iterator: returns an array iterator (values). 867 + let iter = make_native(gc, "[Symbol.iterator]", array_iterator); 868 + set_builtin_prop(gc, proto, "@@iterator", Value::Function(iter)); 869 + 870 + // Array.prototype.keys/values/entries 871 + let keys_fn = make_native(gc, "keys", array_keys_iter); 872 + set_builtin_prop(gc, proto, "keys", Value::Function(keys_fn)); 873 + let values_fn = make_native(gc, "values", array_values_iter); 874 + set_builtin_prop(gc, proto, "values", Value::Function(values_fn)); 875 + let entries_fn = make_native(gc, "entries", array_entries_iter); 876 + set_builtin_prop(gc, proto, "entries", Value::Function(entries_fn)); 865 877 } 866 878 867 879 fn array_push(args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { ··· 1483 1495 } 1484 1496 } 1485 1497 1498 + // ── Array iterators ────────────────────────────────────────── 1499 + 1500 + /// Helper: create an iterator object that yields values from a closure. 1501 + /// `state` is a GcRef to an object with `__items__` (array) and `__idx__` (number). 1502 + fn make_simple_iterator( 1503 + gc: &mut Gc<HeapObject>, 1504 + items: GcRef, 1505 + next_fn: fn(&[Value], &mut NativeContext) -> Result<Value, RuntimeError>, 1506 + ) -> Value { 1507 + let mut obj = ObjectData::new(); 1508 + obj.properties.insert( 1509 + "__items__".to_string(), 1510 + Property::builtin(Value::Object(items)), 1511 + ); 1512 + obj.properties 1513 + .insert("__idx__".to_string(), Property::builtin(Value::Number(0.0))); 1514 + let next = gc.alloc(HeapObject::Function(Box::new(FunctionData { 1515 + name: "next".to_string(), 1516 + kind: FunctionKind::Native(NativeFunc { callback: next_fn }), 1517 + prototype_obj: None, 1518 + properties: HashMap::new(), 1519 + upvalues: Vec::new(), 1520 + }))); 1521 + obj.properties 1522 + .insert("next".to_string(), Property::builtin(Value::Function(next))); 1523 + 1524 + // @@iterator returns self. 1525 + let self_iter = gc.alloc(HeapObject::Function(Box::new(FunctionData { 1526 + name: "[Symbol.iterator]".to_string(), 1527 + kind: FunctionKind::Native(NativeFunc { 1528 + callback: iter_self, 1529 + }), 1530 + prototype_obj: None, 1531 + properties: HashMap::new(), 1532 + upvalues: Vec::new(), 1533 + }))); 1534 + obj.properties.insert( 1535 + "@@iterator".to_string(), 1536 + Property::builtin(Value::Function(self_iter)), 1537 + ); 1538 + 1539 + let r = gc.alloc(HeapObject::Object(obj)); 1540 + Value::Object(r) 1541 + } 1542 + 1543 + fn iter_self(_args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 1544 + Ok(ctx.this.clone()) 1545 + } 1546 + 1547 + fn make_iterator_result_native(gc: &mut Gc<HeapObject>, value: Value, done: bool) -> Value { 1548 + let mut obj = ObjectData::new(); 1549 + obj.properties 1550 + .insert("value".to_string(), Property::data(value)); 1551 + obj.properties 1552 + .insert("done".to_string(), Property::data(Value::Boolean(done))); 1553 + let r = gc.alloc(HeapObject::Object(obj)); 1554 + Value::Object(r) 1555 + } 1556 + 1557 + /// Array.prototype[@@iterator]() — same as values(). 1558 + fn array_iterator(_args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 1559 + array_values_iter(_args, ctx) 1560 + } 1561 + 1562 + /// Array.prototype.values() — returns iterator over values. 1563 + fn array_values_iter(_args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 1564 + let obj_ref = ctx 1565 + .this 1566 + .gc_ref() 1567 + .ok_or_else(|| RuntimeError::type_error("values called on non-object"))?; 1568 + Ok(make_simple_iterator(ctx.gc, obj_ref, array_values_next)) 1569 + } 1570 + 1571 + fn array_values_next(_args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 1572 + let iter_ref = ctx 1573 + .this 1574 + .gc_ref() 1575 + .ok_or_else(|| RuntimeError::type_error("next called on non-iterator"))?; 1576 + 1577 + let (items_ref, idx) = get_iter_state(ctx.gc, iter_ref); 1578 + let items_ref = match items_ref { 1579 + Some(r) => r, 1580 + None => return Ok(make_iterator_result_native(ctx.gc, Value::Undefined, true)), 1581 + }; 1582 + 1583 + let len = array_length(ctx.gc, items_ref); 1584 + if idx >= len { 1585 + return Ok(make_iterator_result_native(ctx.gc, Value::Undefined, true)); 1586 + } 1587 + 1588 + let val = array_get(ctx.gc, items_ref, idx); 1589 + set_iter_idx(ctx.gc, iter_ref, idx + 1); 1590 + Ok(make_iterator_result_native(ctx.gc, val, false)) 1591 + } 1592 + 1593 + /// Array.prototype.keys() — returns iterator over indices. 1594 + fn array_keys_iter(_args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 1595 + let obj_ref = ctx 1596 + .this 1597 + .gc_ref() 1598 + .ok_or_else(|| RuntimeError::type_error("keys called on non-object"))?; 1599 + Ok(make_simple_iterator(ctx.gc, obj_ref, array_keys_next)) 1600 + } 1601 + 1602 + fn array_keys_next(_args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 1603 + let iter_ref = ctx 1604 + .this 1605 + .gc_ref() 1606 + .ok_or_else(|| RuntimeError::type_error("next called on non-iterator"))?; 1607 + let (items_ref, idx) = get_iter_state(ctx.gc, iter_ref); 1608 + let items_ref = match items_ref { 1609 + Some(r) => r, 1610 + None => return Ok(make_iterator_result_native(ctx.gc, Value::Undefined, true)), 1611 + }; 1612 + let len = array_length(ctx.gc, items_ref); 1613 + if idx >= len { 1614 + return Ok(make_iterator_result_native(ctx.gc, Value::Undefined, true)); 1615 + } 1616 + set_iter_idx(ctx.gc, iter_ref, idx + 1); 1617 + Ok(make_iterator_result_native( 1618 + ctx.gc, 1619 + Value::Number(idx as f64), 1620 + false, 1621 + )) 1622 + } 1623 + 1624 + /// Array.prototype.entries() — returns iterator over [index, value] pairs. 1625 + fn array_entries_iter(_args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 1626 + let obj_ref = ctx 1627 + .this 1628 + .gc_ref() 1629 + .ok_or_else(|| RuntimeError::type_error("entries called on non-object"))?; 1630 + Ok(make_simple_iterator(ctx.gc, obj_ref, array_entries_next)) 1631 + } 1632 + 1633 + fn array_entries_next(_args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 1634 + let iter_ref = ctx 1635 + .this 1636 + .gc_ref() 1637 + .ok_or_else(|| RuntimeError::type_error("next called on non-iterator"))?; 1638 + let (items_ref, idx) = get_iter_state(ctx.gc, iter_ref); 1639 + let items_ref = match items_ref { 1640 + Some(r) => r, 1641 + None => return Ok(make_iterator_result_native(ctx.gc, Value::Undefined, true)), 1642 + }; 1643 + let len = array_length(ctx.gc, items_ref); 1644 + if idx >= len { 1645 + return Ok(make_iterator_result_native(ctx.gc, Value::Undefined, true)); 1646 + } 1647 + let val = array_get(ctx.gc, items_ref, idx); 1648 + set_iter_idx(ctx.gc, iter_ref, idx + 1); 1649 + 1650 + // Create [index, value] pair array. 1651 + let mut pair = ObjectData::new(); 1652 + pair.properties 1653 + .insert("0".to_string(), Property::data(Value::Number(idx as f64))); 1654 + pair.properties.insert("1".to_string(), Property::data(val)); 1655 + pair.properties.insert( 1656 + "length".to_string(), 1657 + Property { 1658 + value: Value::Number(2.0), 1659 + writable: true, 1660 + enumerable: false, 1661 + configurable: false, 1662 + }, 1663 + ); 1664 + let pair_ref = ctx.gc.alloc(HeapObject::Object(pair)); 1665 + Ok(make_iterator_result_native( 1666 + ctx.gc, 1667 + Value::Object(pair_ref), 1668 + false, 1669 + )) 1670 + } 1671 + 1672 + /// Helper to read __items__ and __idx__ from an iterator state object. 1673 + fn get_iter_state(gc: &Gc<HeapObject>, iter_ref: GcRef) -> (Option<GcRef>, usize) { 1674 + match gc.get(iter_ref) { 1675 + Some(HeapObject::Object(data)) => { 1676 + let items = data 1677 + .properties 1678 + .get("__items__") 1679 + .and_then(|p| p.value.gc_ref()); 1680 + let idx = data 1681 + .properties 1682 + .get("__idx__") 1683 + .map(|p| p.value.to_number() as usize) 1684 + .unwrap_or(0); 1685 + (items, idx) 1686 + } 1687 + _ => (None, 0), 1688 + } 1689 + } 1690 + 1691 + /// Helper to update __idx__ on an iterator state object. 1692 + fn set_iter_idx(gc: &mut Gc<HeapObject>, iter_ref: GcRef, idx: usize) { 1693 + if let Some(HeapObject::Object(data)) = gc.get_mut(iter_ref) { 1694 + data.properties.insert( 1695 + "__idx__".to_string(), 1696 + Property::builtin(Value::Number(idx as f64)), 1697 + ); 1698 + } 1699 + } 1700 + 1486 1701 // ── String built-in ────────────────────────────────────────── 1487 1702 1488 1703 fn init_string_prototype(gc: &mut Gc<HeapObject>, proto: GcRef) { ··· 1521 1736 let f = make_native(gc, name, callback); 1522 1737 set_builtin_prop(gc, proto, name, Value::Function(f)); 1523 1738 } 1739 + 1740 + // @@iterator: iterates over characters. 1741 + let iter_fn = make_native(gc, "[Symbol.iterator]", string_iterator); 1742 + set_builtin_prop(gc, proto, "@@iterator", Value::Function(iter_fn)); 1743 + } 1744 + 1745 + /// String.prototype[@@iterator]() — returns an iterator over characters. 1746 + fn string_iterator(_args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 1747 + let s = ctx.this.to_js_string(ctx.gc); 1748 + 1749 + // Store the string's characters in an array-like object. 1750 + let mut items = ObjectData::new(); 1751 + for (i, ch) in s.chars().enumerate() { 1752 + items 1753 + .properties 1754 + .insert(i.to_string(), Property::data(Value::String(ch.to_string()))); 1755 + } 1756 + items.properties.insert( 1757 + "length".to_string(), 1758 + Property { 1759 + value: Value::Number(s.chars().count() as f64), 1760 + writable: true, 1761 + enumerable: false, 1762 + configurable: false, 1763 + }, 1764 + ); 1765 + let items_ref = ctx.gc.alloc(HeapObject::Object(items)); 1766 + Ok(make_simple_iterator(ctx.gc, items_ref, array_values_next)) 1524 1767 } 1525 1768 1526 1769 fn init_string_constructor(gc: &mut Gc<HeapObject>, str_proto: GcRef) -> GcRef { ··· 4143 4386 let f = make_native(gc, name, callback); 4144 4387 set_builtin_prop(gc, proto, name, Value::Function(f)); 4145 4388 } 4389 + // Map.prototype[@@iterator] — returns an iterator of [key, value] pairs. 4390 + let iter_fn = make_native(gc, "[Symbol.iterator]", map_symbol_iterator); 4391 + set_builtin_prop(gc, proto, "@@iterator", Value::Function(iter_fn)); 4146 4392 } 4147 4393 4148 4394 fn map_proto_set(args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { ··· 4303 4549 Ok(make_value_array(ctx.gc, &items)) 4304 4550 } 4305 4551 4552 + /// Map[@@iterator]() — wraps entries array into an iterator. 4553 + fn map_symbol_iterator(_args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 4554 + let arr_val = map_proto_iter(_args, ctx, IterKind::Entries)?; 4555 + let arr_ref = match arr_val.gc_ref() { 4556 + Some(r) => r, 4557 + None => return Ok(Value::Undefined), 4558 + }; 4559 + Ok(make_simple_iterator(ctx.gc, arr_ref, array_values_next)) 4560 + } 4561 + 4562 + /// Set[@@iterator]() — wraps values array into an iterator. 4563 + fn set_symbol_iterator(_args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 4564 + let arr_val = set_proto_values(_args, ctx)?; 4565 + let arr_ref = match arr_val.gc_ref() { 4566 + Some(r) => r, 4567 + None => return Ok(Value::Undefined), 4568 + }; 4569 + Ok(make_simple_iterator(ctx.gc, arr_ref, array_values_next)) 4570 + } 4571 + 4306 4572 /// Normalize -0 to +0 for Map/Set key equality. 4307 4573 fn normalize_zero(val: Value) -> Value { 4308 4574 if let Value::Number(n) = &val { ··· 4409 4675 let f = make_native(gc, name, callback); 4410 4676 set_builtin_prop(gc, proto, name, Value::Function(f)); 4411 4677 } 4678 + // Set.prototype[@@iterator] — returns an iterator of values. 4679 + let iter_fn = make_native(gc, "[Symbol.iterator]", set_symbol_iterator); 4680 + set_builtin_prop(gc, proto, "@@iterator", Value::Function(iter_fn)); 4412 4681 } 4413 4682 4414 4683 fn set_proto_add(args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> {
+40
crates/js/src/bytecode.rs
··· 162 162 LoadUpvalue = 0x7B, 163 163 /// StoreUpvalue idx(u8), src — store into the closure's captured upvalue cell 164 164 StoreUpvalue = 0x7C, 165 + 166 + // ── Iterator / generator ─────────────────────────────── 167 + /// Yield dst, src — suspend generator, yield src value. On resume, dst gets the 168 + /// value passed to next(). The VM saves the frame state and returns {value, done: false}. 169 + Yield = 0x80, 170 + /// Spread dst_array, src — iterate src via @@iterator, append all elements to dst_array 171 + Spread = 0x81, 165 172 } 166 173 167 174 impl Op { ··· 230 237 0x7A => Some(Op::CellStore), 231 238 0x7B => Some(Op::LoadUpvalue), 232 239 0x7C => Some(Op::StoreUpvalue), 240 + 0x80 => Some(Op::Yield), 241 + 0x81 => Some(Op::Spread), 233 242 _ => None, 234 243 } 235 244 } ··· 274 283 pub source_map: Vec<(u32, u32)>, 275 284 /// Upvalue definitions: how this function captures variables from its parent. 276 285 pub upvalue_defs: Vec<UpvalueDef>, 286 + /// Whether this is a generator function (function*). 287 + pub is_generator: bool, 277 288 } 278 289 279 290 impl Function { ··· 288 299 functions: Vec::new(), 289 300 source_map: Vec::new(), 290 301 upvalue_defs: Vec::new(), 302 + is_generator: false, 291 303 } 292 304 } 293 305 } ··· 527 539 self.emit_u8(src); 528 540 } 529 541 542 + /// Emit: Yield dst, src — suspend generator 543 + pub fn emit_yield(&mut self, dst: Reg, src: Reg) { 544 + self.emit_u8(Op::Yield as u8); 545 + self.emit_u8(dst); 546 + self.emit_u8(src); 547 + } 548 + 549 + /// Emit: Spread dst_array, src — spread iterable into array 550 + pub fn emit_spread(&mut self, dst_array: Reg, src: Reg) { 551 + self.emit_u8(Op::Spread as u8); 552 + self.emit_u8(dst_array); 553 + self.emit_u8(src); 554 + } 555 + 530 556 /// Add a source map entry: current bytecode offset → source line. 531 557 pub fn add_source_map(&mut self, line: u32) { 532 558 let offset = self.offset() as u32; ··· 879 905 pc += 2; 880 906 format!("StoreUpvalue uv{idx}, r{src}") 881 907 } 908 + Op::Yield => { 909 + let dst = code[pc]; 910 + let src = code[pc + 1]; 911 + pc += 2; 912 + format!("Yield r{dst}, r{src}") 913 + } 914 + Op::Spread => { 915 + let dst = code[pc]; 916 + let src = code[pc + 1]; 917 + pc += 2; 918 + format!("Spread r{dst}, r{src}") 919 + } 882 920 }; 883 921 out.push_str(&format!(" {offset:04X} {line}\n")); 884 922 } ··· 972 1010 Op::CellStore, 973 1011 Op::LoadUpvalue, 974 1012 Op::StoreUpvalue, 1013 + Op::Yield, 1014 + Op::Spread, 975 1015 ]; 976 1016 for op in ops { 977 1017 assert_eq!(
+432 -68
crates/js/src/compiler.rs
··· 896 896 body, 897 897 is_await: _, 898 898 } => { 899 - let _ = left; 900 - let tmp = fc.alloc_reg(); 901 - compile_expr(fc, right, tmp)?; 902 - fc.free_reg(tmp); 899 + let saved_locals = fc.locals.len(); 900 + let saved_next = fc.next_reg; 901 + 902 + // Evaluate the iterable. 903 + let iterable_r = fc.alloc_reg(); 904 + compile_expr(fc, right, iterable_r)?; 905 + 906 + // Get the iterator: call iterable[@@iterator](). 907 + let iter_method_r = fc.alloc_reg(); 908 + let sym_iter_ni = fc.builder.add_name("@@iterator"); 909 + fc.builder 910 + .emit_get_prop_name(iter_method_r, iterable_r, sym_iter_ni); 911 + 912 + // Set `this` = iterable for the call. 913 + let this_ni = fc.builder.add_name("this"); 914 + fc.builder.emit_store_global(this_ni, iterable_r); 915 + 916 + // Call [@@iterator]() with 0 args. 917 + let iterator_r = fc.alloc_reg(); 918 + let args_start = fc.next_reg; 919 + fc.builder 920 + .emit_call(iterator_r, iter_method_r, args_start, 0); 921 + 922 + // Temp registers for next method, result, done, value. 923 + let next_method_r = fc.alloc_reg(); 924 + let next_ni = fc.builder.add_name("next"); 925 + fc.builder 926 + .emit_get_prop_name(next_method_r, iterator_r, next_ni); 927 + 928 + let result_obj_r = fc.alloc_reg(); 929 + let done_r = fc.alloc_reg(); 930 + let value_r = fc.alloc_reg(); 931 + 932 + // Loop start. 933 + let loop_start = fc.builder.offset(); 934 + 935 + // Set `this` = iterator for the .next() call. 936 + fc.builder.emit_store_global(this_ni, iterator_r); 937 + 938 + // Call iterator.next(). 939 + fc.builder 940 + .emit_call(result_obj_r, next_method_r, args_start, 0); 941 + 942 + // Extract done and value. 943 + let done_ni = fc.builder.add_name("done"); 944 + let value_ni = fc.builder.add_name("value"); 945 + fc.builder.emit_get_prop_name(done_r, result_obj_r, done_ni); 946 + 947 + // Exit if done. 948 + let exit_patch = fc.builder.emit_cond_jump(Op::JumpIfTrue, done_r); 949 + 950 + // Extract value. 951 + fc.builder 952 + .emit_get_prop_name(value_r, result_obj_r, value_ni); 953 + 954 + // Bind the loop variable. 955 + match left { 956 + ForInOfLeft::VarDecl { kind, pattern } => match &pattern.kind { 957 + PatternKind::Identifier(name) => { 958 + let is_captured = fc.captured_names.contains(name.as_str()); 959 + let is_const = *kind == VarKind::Const; 960 + let var_r = fc.define_local_ext(name, is_captured, is_const); 961 + if is_captured { 962 + fc.builder.emit_reg(Op::NewCell, var_r); 963 + fc.builder.emit_reg_reg(Op::CellStore, var_r, value_r); 964 + } else { 965 + fc.builder.emit_reg_reg(Op::Move, var_r, value_r); 966 + } 967 + } 968 + _ => { 969 + // Destructuring pattern in for...of. 970 + compile_destructuring_pattern(fc, pattern, value_r)?; 971 + } 972 + }, 973 + ForInOfLeft::Pattern(pattern) => match &pattern.kind { 974 + PatternKind::Identifier(name) => { 975 + if let Some(local) = fc.find_local_info(name) { 976 + let reg = local.reg; 977 + let captured = local.is_captured; 978 + if captured { 979 + fc.builder.emit_reg_reg(Op::CellStore, reg, value_r); 980 + } else { 981 + fc.builder.emit_reg_reg(Op::Move, reg, value_r); 982 + } 983 + } else if let Some(uv_idx) = fc.find_upvalue(name) { 984 + fc.builder.emit_store_upvalue(uv_idx, value_r); 985 + } else { 986 + let ni = fc.builder.add_name(name); 987 + fc.builder.emit_store_global(ni, value_r); 988 + } 989 + } 990 + _ => { 991 + compile_destructuring_pattern(fc, pattern, value_r)?; 992 + } 993 + }, 994 + } 995 + 996 + // Push loop context for break/continue. 997 + fc.loop_stack.push(LoopCtx { 998 + label: None, 999 + break_patches: Vec::new(), 1000 + continue_patches: Vec::new(), 1001 + }); 1002 + 1003 + // Compile body. 903 1004 compile_stmt(fc, body, result_reg)?; 1005 + 1006 + // Jump back to loop start. 1007 + fc.builder.emit_jump_to(loop_start); 1008 + 1009 + // Patch exit. 1010 + fc.builder.patch_jump(exit_patch); 1011 + let ctx = fc.loop_stack.pop().unwrap(); 1012 + for patch in ctx.break_patches { 1013 + fc.builder.patch_jump(patch); 1014 + } 1015 + for patch in ctx.continue_patches { 1016 + fc.builder.patch_jump_to(patch, loop_start); 1017 + } 1018 + 1019 + // Restore locals/regs. 1020 + fc.locals.truncate(saved_locals); 1021 + fc.next_reg = saved_next; 904 1022 } 905 1023 906 1024 StmtKind::Return(expr) => { ··· 1110 1228 } 1111 1229 _ => { 1112 1230 // Destructuring: evaluate init, then bind patterns. 1231 + // Note: don't free tmp — destructuring pattern allocates permanent 1232 + // local registers above it. The tmp register slot is reused via 1233 + // next_reg restoration by the parent scope. 1113 1234 let tmp = fc.alloc_reg(); 1114 1235 if let Some(init) = &decl.init { 1115 1236 compile_expr(fc, init, tmp)?; ··· 1117 1238 fc.builder.emit_reg(Op::LoadUndefined, tmp); 1118 1239 } 1119 1240 compile_destructuring_pattern(fc, &decl.pattern, tmp)?; 1120 - fc.free_reg(tmp); 1121 1241 } 1122 1242 } 1123 1243 Ok(()) ··· 1139 1259 fc.builder.emit_reg_reg(Op::Move, reg, src); 1140 1260 } 1141 1261 } 1142 - PatternKind::Object { 1143 - properties, 1144 - rest: _, 1145 - } => { 1262 + PatternKind::Object { properties, rest } => { 1263 + // For each property, extract the value and bind it. 1264 + // We use a single temp register that we reuse for each property 1265 + // by resetting next_reg after each binding. 1146 1266 for prop in properties { 1147 1267 let key_name = match &prop.key { 1148 1268 PropertyKey::Identifier(s) | PropertyKey::String(s) => s.clone(), 1149 - _ => { 1150 - return Err(JsError::SyntaxError( 1151 - "computed destructuring keys not yet supported".into(), 1152 - )); 1269 + PropertyKey::Computed(expr) => { 1270 + let saved = fc.next_reg; 1271 + let key_reg = fc.alloc_reg(); 1272 + compile_expr(fc, expr, key_reg)?; 1273 + let val_reg = fc.alloc_reg(); 1274 + fc.builder.emit_reg3(Op::GetProperty, val_reg, src, key_reg); 1275 + compile_destructuring_pattern(fc, &prop.value, val_reg)?; 1276 + // Temp regs are buried; just let them be. 1277 + let _ = saved; 1278 + continue; 1279 + } 1280 + PropertyKey::Number(n) => { 1281 + if n.fract() == 0.0 && n.abs() < 1e15 { 1282 + format!("{}", *n as i64) 1283 + } else { 1284 + format!("{n}") 1285 + } 1153 1286 } 1154 1287 }; 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); 1288 + // For simple identifier patterns, load property directly into 1289 + // the target register to avoid LIFO register allocation issues. 1290 + if let PatternKind::Identifier(name) = &prop.value.kind { 1291 + let is_captured = fc.captured_names.contains(name.as_str()); 1292 + let reg = fc.define_local_ext(name, is_captured, false); 1293 + let name_idx = fc.builder.add_name(&key_name); 1294 + if is_captured { 1295 + let tmp = fc.alloc_reg(); 1296 + fc.builder.emit_get_prop_name(tmp, src, name_idx); 1297 + fc.builder.emit_reg(Op::NewCell, reg); 1298 + fc.builder.emit_reg_reg(Op::CellStore, reg, tmp); 1299 + fc.free_reg(tmp); 1300 + } else { 1301 + fc.builder.emit_get_prop_name(reg, src, name_idx); 1302 + } 1303 + } else { 1304 + // Complex inner pattern (nested, default, etc.) 1305 + // Allocate temp, extract value, then recurse. 1306 + // Temp register won't be freed (LIFO constraint with inner locals). 1307 + let val_reg = fc.alloc_reg(); 1308 + let name_idx = fc.builder.add_name(&key_name); 1309 + fc.builder.emit_get_prop_name(val_reg, src, name_idx); 1310 + compile_destructuring_pattern(fc, &prop.value, val_reg)?; 1311 + } 1312 + } 1313 + 1314 + // Handle rest: collect remaining own enumerable properties. 1315 + if let Some(rest_pat) = rest { 1316 + // Collect extracted key names for exclusion. 1317 + let extracted_keys: Vec<String> = properties 1318 + .iter() 1319 + .filter_map(|prop| match &prop.key { 1320 + PropertyKey::Identifier(s) | PropertyKey::String(s) => Some(s.clone()), 1321 + _ => None, 1322 + }) 1323 + .collect(); 1324 + 1325 + let rest_obj = fc.alloc_reg(); 1326 + fc.builder.emit_reg(Op::CreateObject, rest_obj); 1327 + 1328 + let keys_r = fc.alloc_reg(); 1329 + fc.builder.emit_reg_reg(Op::ForInInit, keys_r, src); 1330 + let idx_r = fc.alloc_reg(); 1331 + fc.builder.emit_load_int8(idx_r, 0); 1332 + let key_r = fc.alloc_reg(); 1333 + let done_r = fc.alloc_reg(); 1334 + 1335 + let loop_start = fc.builder.offset(); 1336 + fc.builder 1337 + .emit_reg4(Op::ForInNext, key_r, done_r, keys_r, idx_r); 1338 + let exit_patch = fc.builder.emit_cond_jump(Op::JumpIfTrue, done_r); 1339 + 1340 + let mut skip_patches = Vec::new(); 1341 + for excluded in &extracted_keys { 1342 + let excluded_r = fc.alloc_reg(); 1343 + let ci = fc.builder.add_constant(Constant::String(excluded.clone())); 1344 + fc.builder.emit_reg_u16(Op::LoadConst, excluded_r, ci); 1345 + let cmp_r = fc.alloc_reg(); 1346 + fc.builder.emit_reg3(Op::StrictEq, cmp_r, key_r, excluded_r); 1347 + let skip = fc.builder.emit_cond_jump(Op::JumpIfTrue, cmp_r); 1348 + skip_patches.push(skip); 1349 + fc.free_reg(cmp_r); 1350 + fc.free_reg(excluded_r); 1351 + } 1352 + 1353 + let val_r = fc.alloc_reg(); 1354 + fc.builder.emit_reg3(Op::GetProperty, val_r, src, key_r); 1355 + fc.builder 1356 + .emit_reg3(Op::SetProperty, rest_obj, key_r, val_r); 1357 + fc.free_reg(val_r); 1358 + 1359 + for patch in skip_patches { 1360 + fc.builder.patch_jump(patch); 1361 + } 1362 + 1363 + let one_r = fc.alloc_reg(); 1364 + fc.builder.emit_load_int8(one_r, 1); 1365 + fc.builder.emit_reg3(Op::Add, idx_r, idx_r, one_r); 1366 + fc.free_reg(one_r); 1367 + 1368 + fc.builder.emit_jump_to(loop_start); 1369 + fc.builder.patch_jump(exit_patch); 1370 + 1371 + fc.free_reg(done_r); 1372 + fc.free_reg(key_r); 1373 + fc.free_reg(idx_r); 1374 + fc.free_reg(keys_r); 1375 + 1376 + compile_destructuring_pattern(fc, rest_pat, rest_obj)?; 1160 1377 } 1161 1378 } 1162 - PatternKind::Array { elements, rest: _ } => { 1379 + PatternKind::Array { elements, rest } => { 1163 1380 for (i, elem) in elements.iter().enumerate() { 1164 1381 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); 1382 + // For simple identifier patterns, load directly into local. 1383 + if let PatternKind::Identifier(name) = &pat.kind { 1384 + let is_captured = fc.captured_names.contains(name.as_str()); 1385 + let reg = fc.define_local_ext(name, is_captured, false); 1386 + let idx_reg = fc.alloc_reg(); 1387 + if i <= 127 { 1388 + fc.builder.emit_load_int8(idx_reg, i as i8); 1389 + } else { 1390 + let ci = fc.builder.add_constant(Constant::Number(i as f64)); 1391 + fc.builder.emit_reg_u16(Op::LoadConst, idx_reg, ci); 1392 + } 1393 + if is_captured { 1394 + let tmp = fc.alloc_reg(); 1395 + fc.builder.emit_reg3(Op::GetProperty, tmp, src, idx_reg); 1396 + fc.builder.emit_reg(Op::NewCell, reg); 1397 + fc.builder.emit_reg_reg(Op::CellStore, reg, tmp); 1398 + fc.free_reg(tmp); 1399 + } else { 1400 + fc.builder.emit_reg3(Op::GetProperty, reg, src, idx_reg); 1401 + } 1402 + fc.free_reg(idx_reg); 1168 1403 } else { 1169 - let ci = fc.builder.add_constant(Constant::Number(i as f64)); 1170 - fc.builder.emit_reg_u16(Op::LoadConst, idx_reg, ci); 1404 + // Complex inner pattern (nested, default, etc.) 1405 + let idx_reg = fc.alloc_reg(); 1406 + if i <= 127 { 1407 + fc.builder.emit_load_int8(idx_reg, i as i8); 1408 + } else { 1409 + let ci = fc.builder.add_constant(Constant::Number(i as f64)); 1410 + fc.builder.emit_reg_u16(Op::LoadConst, idx_reg, ci); 1411 + } 1412 + let val_reg = fc.alloc_reg(); 1413 + fc.builder.emit_reg3(Op::GetProperty, val_reg, src, idx_reg); 1414 + compile_destructuring_pattern(fc, pat, val_reg)?; 1415 + // Don't free val_reg/idx_reg — inner pattern may have 1416 + // allocated locals above them. 1171 1417 } 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 1418 } 1178 1419 } 1420 + 1421 + // Handle rest element: ...rest = src.slice(elements.len()) 1422 + if let Some(rest_pat) = rest { 1423 + let slice_fn_r = fc.alloc_reg(); 1424 + let slice_ni = fc.builder.add_name("slice"); 1425 + fc.builder.emit_get_prop_name(slice_fn_r, src, slice_ni); 1426 + 1427 + let this_ni = fc.builder.add_name("this"); 1428 + fc.builder.emit_store_global(this_ni, src); 1429 + 1430 + let start_r = fc.alloc_reg(); 1431 + let elem_count = elements.len(); 1432 + if elem_count <= 127 { 1433 + fc.builder.emit_load_int8(start_r, elem_count as i8); 1434 + } else { 1435 + let ci = fc.builder.add_constant(Constant::Number(elem_count as f64)); 1436 + fc.builder.emit_reg_u16(Op::LoadConst, start_r, ci); 1437 + } 1438 + 1439 + let rest_val = fc.alloc_reg(); 1440 + fc.builder.emit_call(rest_val, slice_fn_r, start_r, 1); 1441 + 1442 + compile_destructuring_pattern(fc, rest_pat, rest_val)?; 1443 + // Don't free temps — rest pattern allocates locals. 1444 + } 1179 1445 } 1180 1446 PatternKind::Assign { left, right } => { 1181 1447 // Default value: if src is undefined, use default. ··· 1194 1460 compile_expr(fc, right, val_reg)?; 1195 1461 fc.builder.patch_jump(patch); 1196 1462 compile_destructuring_pattern(fc, left, val_reg)?; 1197 - fc.free_reg(val_reg); 1463 + // Don't free val_reg — inner pattern may have allocated locals. 1198 1464 } 1199 1465 } 1200 1466 Ok(()) ··· 1342 1608 1343 1609 // Implicit return undefined. 1344 1610 inner.builder.emit_reg(Op::Return, result_reg); 1345 - Ok(inner.builder.finish()) 1611 + let mut func = inner.builder.finish(); 1612 + func.is_generator = func_def.is_generator; 1613 + Ok(func) 1346 1614 } 1347 1615 1348 1616 // ── Class declarations ────────────────────────────────────── ··· 2078 2346 } 2079 2347 2080 2348 ExprKind::Array(elements) => { 2349 + let has_spread = elements 2350 + .iter() 2351 + .any(|e| matches!(e, Some(ArrayElement::Spread(_)))); 2352 + 2081 2353 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(); 2354 + 2355 + if has_spread { 2356 + // When spreads are present, we track the index dynamically. 2357 + // For each normal element, push at current length. 2358 + // For spread elements, use the Spread opcode. 2359 + for el in elements.iter().flatten() { 2085 2360 match el { 2086 - ArrayElement::Expr(e) => compile_expr(fc, e, val_reg)?, 2361 + ArrayElement::Expr(e) => { 2362 + let val_reg = fc.alloc_reg(); 2363 + compile_expr(fc, e, val_reg)?; 2364 + // Get current length as index. 2365 + let idx_reg = fc.alloc_reg(); 2366 + let len_ni = fc.builder.add_name("length"); 2367 + fc.builder.emit_get_prop_name(idx_reg, dst, len_ni); 2368 + fc.builder.emit_reg3(Op::SetProperty, dst, idx_reg, val_reg); 2369 + // Increment length. 2370 + let one_r = fc.alloc_reg(); 2371 + fc.builder.emit_load_int8(one_r, 1); 2372 + fc.builder.emit_reg3(Op::Add, idx_reg, idx_reg, one_r); 2373 + fc.builder.emit_set_prop_name(dst, len_ni, idx_reg); 2374 + fc.free_reg(one_r); 2375 + fc.free_reg(idx_reg); 2376 + fc.free_reg(val_reg); 2377 + } 2087 2378 ArrayElement::Spread(e) => { 2088 - // Spread in array: simplified, just compile the expression. 2089 - compile_expr(fc, e, val_reg)?; 2379 + let spread_src = fc.alloc_reg(); 2380 + compile_expr(fc, e, spread_src)?; 2381 + fc.builder.emit_spread(dst, spread_src); 2382 + fc.free_reg(spread_src); 2383 + } 2384 + } 2385 + } 2386 + } else { 2387 + // No spreads: use simple indexed assignment. 2388 + for (i, elem) in elements.iter().enumerate() { 2389 + if let Some(ArrayElement::Expr(e)) = elem { 2390 + let val_reg = fc.alloc_reg(); 2391 + compile_expr(fc, e, val_reg)?; 2392 + let idx_reg = fc.alloc_reg(); 2393 + if i <= 127 { 2394 + fc.builder.emit_load_int8(idx_reg, i as i8); 2395 + } else { 2396 + let ci = fc.builder.add_constant(Constant::Number(i as f64)); 2397 + fc.builder.emit_reg_u16(Op::LoadConst, idx_reg, ci); 2090 2398 } 2399 + fc.builder.emit_reg3(Op::SetProperty, dst, idx_reg, val_reg); 2400 + fc.free_reg(idx_reg); 2401 + fc.free_reg(val_reg); 2091 2402 } 2092 - let idx_reg = fc.alloc_reg(); 2093 - if i <= 127 { 2094 - fc.builder.emit_load_int8(idx_reg, i as i8); 2403 + } 2404 + // Set length. 2405 + if !elements.is_empty() { 2406 + let len_name = fc.builder.add_name("length"); 2407 + let len_reg = fc.alloc_reg(); 2408 + if elements.len() <= 127 { 2409 + fc.builder.emit_load_int8(len_reg, elements.len() as i8); 2095 2410 } else { 2096 - let ci = fc.builder.add_constant(Constant::Number(i as f64)); 2097 - fc.builder.emit_reg_u16(Op::LoadConst, idx_reg, ci); 2411 + let ci = fc 2412 + .builder 2413 + .add_constant(Constant::Number(elements.len() as f64)); 2414 + fc.builder.emit_reg_u16(Op::LoadConst, len_reg, ci); 2098 2415 } 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); 2416 + fc.builder.emit_set_prop_name(dst, len_name, len_reg); 2417 + fc.free_reg(len_reg); 2115 2418 } 2116 - fc.builder.emit_set_prop_name(dst, len_name, len_reg); 2117 - fc.free_reg(len_reg); 2118 2419 } 2119 2420 } 2120 2421 ··· 2386 2687 fc.free_reg(func_reg); 2387 2688 } 2388 2689 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)?; 2690 + ExprKind::Yield { argument, delegate } => { 2691 + if *delegate { 2692 + // yield* expr: iterate the sub-iterator and yield each value. 2693 + let iter_r = fc.alloc_reg(); 2694 + if let Some(arg) = argument { 2695 + compile_expr(fc, arg, iter_r)?; 2696 + } else { 2697 + fc.builder.emit_reg(Op::LoadUndefined, iter_r); 2698 + } 2699 + 2700 + // Get iterator from the expression. 2701 + let iter_method_r = fc.alloc_reg(); 2702 + let sym_iter_ni = fc.builder.add_name("@@iterator"); 2703 + fc.builder 2704 + .emit_get_prop_name(iter_method_r, iter_r, sym_iter_ni); 2705 + let this_ni = fc.builder.add_name("this"); 2706 + fc.builder.emit_store_global(this_ni, iter_r); 2707 + let iterator_r = fc.alloc_reg(); 2708 + let args_start = fc.next_reg; 2709 + fc.builder 2710 + .emit_call(iterator_r, iter_method_r, args_start, 0); 2711 + 2712 + // Get next method. 2713 + let next_r = fc.alloc_reg(); 2714 + let next_ni = fc.builder.add_name("next"); 2715 + fc.builder.emit_get_prop_name(next_r, iterator_r, next_ni); 2716 + 2717 + let result_r = fc.alloc_reg(); 2718 + let done_r = fc.alloc_reg(); 2719 + let val_r = fc.alloc_reg(); 2720 + 2721 + let loop_start = fc.builder.offset(); 2722 + 2723 + // Call next(). 2724 + fc.builder.emit_store_global(this_ni, iterator_r); 2725 + fc.builder.emit_call(result_r, next_r, args_start, 0); 2726 + 2727 + let done_ni = fc.builder.add_name("done"); 2728 + let value_ni = fc.builder.add_name("value"); 2729 + fc.builder.emit_get_prop_name(done_r, result_r, done_ni); 2730 + 2731 + let exit_patch = fc.builder.emit_cond_jump(Op::JumpIfTrue, done_r); 2732 + 2733 + fc.builder.emit_get_prop_name(val_r, result_r, value_ni); 2734 + 2735 + // Yield the value. 2736 + fc.builder.emit_yield(dst, val_r); 2737 + 2738 + // Jump back. 2739 + fc.builder.emit_jump_to(loop_start); 2740 + 2741 + // Exit: the last result's value is the yield* expression value. 2742 + fc.builder.patch_jump(exit_patch); 2743 + fc.builder.emit_get_prop_name(dst, result_r, value_ni); 2744 + 2745 + fc.free_reg(val_r); 2746 + fc.free_reg(done_r); 2747 + fc.free_reg(result_r); 2748 + fc.free_reg(next_r); 2749 + fc.free_reg(iterator_r); 2750 + fc.free_reg(iter_method_r); 2751 + fc.free_reg(iter_r); 2396 2752 } else { 2397 - fc.builder.emit_reg(Op::LoadUndefined, dst); 2753 + // yield expr: emit Yield opcode. 2754 + let src = fc.alloc_reg(); 2755 + if let Some(arg) = argument { 2756 + compile_expr(fc, arg, src)?; 2757 + } else { 2758 + fc.builder.emit_reg(Op::LoadUndefined, src); 2759 + } 2760 + fc.builder.emit_yield(dst, src); 2761 + fc.free_reg(src); 2398 2762 } 2399 2763 } 2400 2764
+990 -2
crates/js/src/vm.rs
··· 12 12 13 13 // ── Heap objects (GC-managed) ──────────────────────────────── 14 14 15 - /// A GC-managed heap object: a plain object, a function, or a closure cell. 15 + /// A GC-managed heap object: a plain object, a function, a closure cell, or a generator. 16 16 pub enum HeapObject { 17 17 Object(ObjectData), 18 18 Function(Box<FunctionData>), 19 19 /// A mutable cell holding one Value — used for closure-captured variables. 20 20 Cell(Value), 21 + /// A suspended generator function instance. 22 + Generator(Box<GeneratorData>), 23 + } 24 + 25 + /// State of a generator object. 26 + #[derive(Debug, Clone, Copy, PartialEq, Eq)] 27 + pub enum GeneratorState { 28 + /// Created but next() not yet called. 29 + NotStarted, 30 + /// Suspended at a yield point. 31 + Suspended, 32 + /// Currently executing (re-entrancy guard). 33 + Executing, 34 + /// Completed (returned or threw). 35 + Completed, 36 + } 37 + 38 + /// Data for a suspended generator function. 39 + pub struct GeneratorData { 40 + pub state: GeneratorState, 41 + /// The generator function's bytecode. 42 + pub func: Function, 43 + /// Captured upvalues. 44 + pub upvalues: Vec<GcRef>, 45 + /// Saved register file for this generator's frame. 46 + pub registers: Vec<Value>, 47 + /// Saved instruction pointer (where to resume). 48 + pub ip: usize, 49 + /// The GcRef of the result prototype (for {value, done} objects). 50 + pub prototype: Option<GcRef>, 21 51 } 22 52 23 53 impl Traceable for HeapObject { ··· 49 79 HeapObject::Cell(val) => { 50 80 if let Some(r) = val.gc_ref() { 51 81 visitor(r); 82 + } 83 + } 84 + HeapObject::Generator(gen) => { 85 + for &uv in &gen.upvalues { 86 + visitor(uv); 87 + } 88 + for val in &gen.registers { 89 + if let Some(r) = val.gc_ref() { 90 + visitor(r); 91 + } 92 + } 93 + if let Some(proto) = gen.prototype { 94 + visitor(proto); 52 95 } 53 96 } 54 97 } ··· 778 821 gc: &mut self.gc, 779 822 this, 780 823 }; 781 - (native.callback)(args, &mut ctx) 824 + let result = (native.callback)(args, &mut ctx)?; 825 + 826 + // Check for generator resume marker. 827 + if let Value::Object(r) = &result { 828 + let is_resume = matches!( 829 + gc_get_property(&self.gc, *r, "__generator_resume__"), 830 + Value::Boolean(true) 831 + ); 832 + if is_resume { 833 + let gen_ref = match gc_get_property(&self.gc, *r, "__gen_ref__") { 834 + Value::Object(gr) => gr, 835 + _ => return Ok(Value::Undefined), 836 + }; 837 + let send_val = gc_get_property(&self.gc, *r, "__send_value__"); 838 + let kind_str = match gc_get_property(&self.gc, *r, "__resume_kind__") { 839 + Value::String(s) => s, 840 + _ => "next".to_string(), 841 + }; 842 + return match kind_str.as_str() { 843 + "next" => self.run_generator(gen_ref, send_val), 844 + "return" => { 845 + if let Some(HeapObject::Generator(gen)) = self.gc.get_mut(gen_ref) { 846 + gen.state = GeneratorState::Completed; 847 + } 848 + Ok(self.make_iterator_result(send_val, true)) 849 + } 850 + "throw" => { 851 + if let Some(HeapObject::Generator(gen)) = self.gc.get_mut(gen_ref) { 852 + gen.state = GeneratorState::Completed; 853 + } 854 + Err(RuntimeError::type_error("Generator throw")) 855 + } 856 + _ => Ok(Value::Undefined), 857 + }; 858 + } 859 + } 860 + 861 + Ok(result) 782 862 } 783 863 FunctionKind::Bytecode(bc) => { 784 864 let callee_func = bc.func; 865 + 866 + // Generator function: create a generator object instead of executing. 867 + if callee_func.is_generator { 868 + let gen_obj = self.create_generator_object(callee_func, upvalues, args); 869 + return Ok(Value::Object(gen_obj)); 870 + } 785 871 786 872 // Save current frames and run the function in isolation. 787 873 let saved_frames = std::mem::take(&mut self.frames); ··· 1018 1104 i32::from_le_bytes(bytes) 1019 1105 } 1020 1106 1107 + // ── Generator helpers ────────────────────────────────────── 1108 + 1109 + /// Create a generator object from a generator function. 1110 + fn create_generator_object( 1111 + &mut self, 1112 + func: Function, 1113 + upvalues: Vec<GcRef>, 1114 + args: &[Value], 1115 + ) -> GcRef { 1116 + // Pre-fill registers with arguments. 1117 + let reg_count = func.register_count as usize; 1118 + let mut regs = vec![Value::Undefined; reg_count]; 1119 + for (i, arg) in args.iter().enumerate() { 1120 + if i < func.param_count as usize { 1121 + regs[i] = arg.clone(); 1122 + } 1123 + } 1124 + 1125 + let gen_data = GeneratorData { 1126 + state: GeneratorState::NotStarted, 1127 + func, 1128 + upvalues, 1129 + registers: regs, 1130 + ip: 0, 1131 + prototype: self.object_prototype, 1132 + }; 1133 + 1134 + let gen_ref = self.gc.alloc(HeapObject::Generator(Box::new(gen_data))); 1135 + 1136 + // Wrap in an object with next/return/throw methods. 1137 + let mut obj = ObjectData::new(); 1138 + obj.prototype = self.object_prototype; 1139 + 1140 + // Store the generator GcRef so methods can find it. 1141 + obj.properties.insert( 1142 + "__gen__".to_string(), 1143 + Property { 1144 + value: Value::Object(gen_ref), 1145 + writable: false, 1146 + enumerable: false, 1147 + configurable: false, 1148 + }, 1149 + ); 1150 + 1151 + // next() method 1152 + let next_fn = self.gc.alloc(HeapObject::Function(Box::new(FunctionData { 1153 + name: "next".to_string(), 1154 + kind: FunctionKind::Native(NativeFunc { 1155 + callback: generator_next, 1156 + }), 1157 + prototype_obj: None, 1158 + properties: HashMap::new(), 1159 + upvalues: Vec::new(), 1160 + }))); 1161 + obj.properties.insert( 1162 + "next".to_string(), 1163 + Property::builtin(Value::Function(next_fn)), 1164 + ); 1165 + 1166 + // return() method 1167 + let return_fn = self.gc.alloc(HeapObject::Function(Box::new(FunctionData { 1168 + name: "return".to_string(), 1169 + kind: FunctionKind::Native(NativeFunc { 1170 + callback: generator_return, 1171 + }), 1172 + prototype_obj: None, 1173 + properties: HashMap::new(), 1174 + upvalues: Vec::new(), 1175 + }))); 1176 + obj.properties.insert( 1177 + "return".to_string(), 1178 + Property::builtin(Value::Function(return_fn)), 1179 + ); 1180 + 1181 + // throw() method 1182 + let throw_fn = self.gc.alloc(HeapObject::Function(Box::new(FunctionData { 1183 + name: "throw".to_string(), 1184 + kind: FunctionKind::Native(NativeFunc { 1185 + callback: generator_throw, 1186 + }), 1187 + prototype_obj: None, 1188 + properties: HashMap::new(), 1189 + upvalues: Vec::new(), 1190 + }))); 1191 + obj.properties.insert( 1192 + "throw".to_string(), 1193 + Property::builtin(Value::Function(throw_fn)), 1194 + ); 1195 + 1196 + // @@iterator method (generators are iterable - returns self) 1197 + let iter_fn = self.gc.alloc(HeapObject::Function(Box::new(FunctionData { 1198 + name: "[Symbol.iterator]".to_string(), 1199 + kind: FunctionKind::Native(NativeFunc { 1200 + callback: generator_symbol_iterator, 1201 + }), 1202 + prototype_obj: None, 1203 + properties: HashMap::new(), 1204 + upvalues: Vec::new(), 1205 + }))); 1206 + obj.properties.insert( 1207 + "@@iterator".to_string(), 1208 + Property::builtin(Value::Function(iter_fn)), 1209 + ); 1210 + 1211 + self.gc.alloc(HeapObject::Object(obj)) 1212 + } 1213 + 1214 + /// Create a {value, done} iterator result object. 1215 + fn make_iterator_result(&mut self, value: Value, done: bool) -> Value { 1216 + let mut obj = ObjectData::new(); 1217 + obj.prototype = self.object_prototype; 1218 + obj.properties 1219 + .insert("value".to_string(), Property::data(value)); 1220 + obj.properties 1221 + .insert("done".to_string(), Property::data(Value::Boolean(done))); 1222 + let gc_ref = self.gc.alloc(HeapObject::Object(obj)); 1223 + Value::Object(gc_ref) 1224 + } 1225 + 1226 + /// Run a generator until its next yield/return. 1227 + /// Returns the yielded/returned value. 1228 + pub fn run_generator( 1229 + &mut self, 1230 + gen_ref: GcRef, 1231 + send_value: Value, 1232 + ) -> Result<Value, RuntimeError> { 1233 + // Extract generator data. 1234 + let (func, upvalues, mut regs, ip, state) = match self.gc.get(gen_ref) { 1235 + Some(HeapObject::Generator(gen)) => { 1236 + if gen.state == GeneratorState::Completed { 1237 + return Ok(self.make_iterator_result(Value::Undefined, true)); 1238 + } 1239 + if gen.state == GeneratorState::Executing { 1240 + return Err(RuntimeError::type_error("Generator is already executing")); 1241 + } 1242 + ( 1243 + gen.func.clone(), 1244 + gen.upvalues.clone(), 1245 + gen.registers.clone(), 1246 + gen.ip, 1247 + gen.state, 1248 + ) 1249 + } 1250 + _ => return Err(RuntimeError::type_error("not a generator")), 1251 + }; 1252 + 1253 + // Mark as executing. 1254 + if let Some(HeapObject::Generator(gen)) = self.gc.get_mut(gen_ref) { 1255 + gen.state = GeneratorState::Executing; 1256 + } 1257 + 1258 + // If resuming from a yield, write the sent value into the yield's dst register. 1259 + if state == GeneratorState::Suspended && ip >= 3 { 1260 + // The Yield instruction was: Yield dst, src (3 bytes total: op + dst + src) 1261 + // After executing Yield, ip points past it. The dst byte is at ip - 2. 1262 + let dst_reg = func.code[ip - 2] as usize; 1263 + regs[dst_reg] = send_value; 1264 + } 1265 + 1266 + // Save current VM state. 1267 + let saved_frames = std::mem::take(&mut self.frames); 1268 + let saved_instructions = self.instructions_executed; 1269 + 1270 + // Use a base past any existing register usage to avoid clobbering 1271 + // the caller's register file. 1272 + let base = saved_frames 1273 + .last() 1274 + .map(|f| f.base + f.func.register_count as usize) 1275 + .unwrap_or(0); 1276 + 1277 + let reg_count = func.register_count as usize; 1278 + self.ensure_registers(base + reg_count + 1); 1279 + 1280 + // Set up registers for the generator. 1281 + for (i, val) in regs.iter().enumerate() { 1282 + self.registers[base + i] = val.clone(); 1283 + } 1284 + 1285 + // Push frame. return_reg points to a slot that holds the generator ref 1286 + // so Yield can find it. We use a slot just past the registers. 1287 + self.registers[base + reg_count] = Value::Object(gen_ref); 1288 + 1289 + self.frames.push(CallFrame { 1290 + func, 1291 + ip, 1292 + base, 1293 + return_reg: base + reg_count, // slot holding gen_ref for Yield to find 1294 + exception_handlers: Vec::new(), 1295 + upvalues, 1296 + }); 1297 + 1298 + let result = self.run(); 1299 + 1300 + // Restore VM state. 1301 + self.frames = saved_frames; 1302 + self.instructions_executed = saved_instructions; 1303 + 1304 + match result { 1305 + Ok(val) => { 1306 + // Normal return from generator (either via Return or end of function). 1307 + // Check if it was a Yield (state == Suspended) or a Return (state stays Executing). 1308 + let gen_state = match self.gc.get(gen_ref) { 1309 + Some(HeapObject::Generator(gen)) => gen.state, 1310 + _ => GeneratorState::Completed, 1311 + }; 1312 + if gen_state == GeneratorState::Suspended { 1313 + // Yield already created the result; `val` is the {value, done} object. 1314 + Ok(val) 1315 + } else { 1316 + // Return: mark completed and wrap result. 1317 + if let Some(HeapObject::Generator(gen)) = self.gc.get_mut(gen_ref) { 1318 + gen.state = GeneratorState::Completed; 1319 + } 1320 + Ok(self.make_iterator_result(val, true)) 1321 + } 1322 + } 1323 + Err(err) => { 1324 + // Generator threw: mark completed. 1325 + if let Some(HeapObject::Generator(gen)) = self.gc.get_mut(gen_ref) { 1326 + gen.state = GeneratorState::Completed; 1327 + } 1328 + Err(err) 1329 + } 1330 + } 1331 + } 1332 + 1333 + // ── Iterator protocol helpers ──────────────────────────────── 1334 + 1335 + /// Get an iterator from a value by calling its [Symbol.iterator]() method. 1336 + pub fn get_iterator(&mut self, iterable: &Value) -> Result<Value, RuntimeError> { 1337 + // Get the @@iterator property. 1338 + let iter_fn = match iterable { 1339 + Value::Object(gc_ref) | Value::Function(gc_ref) => { 1340 + gc_get_property(&self.gc, *gc_ref, "@@iterator") 1341 + } 1342 + Value::String(_) => { 1343 + // Strings have @@iterator on their prototype. 1344 + self.string_prototype 1345 + .map(|p| gc_get_property(&self.gc, p, "@@iterator")) 1346 + .unwrap_or(Value::Undefined) 1347 + } 1348 + _ => Value::Undefined, 1349 + }; 1350 + 1351 + let iter_fn_ref = match iter_fn { 1352 + Value::Function(r) => r, 1353 + _ => { 1354 + return Err(RuntimeError::type_error( 1355 + "object is not iterable (no Symbol.iterator)", 1356 + )); 1357 + } 1358 + }; 1359 + 1360 + // Call [Symbol.iterator]() with `this` set to the iterable. 1361 + // We temporarily set `this` in globals for the native call. 1362 + let old_this = self.globals.get("this").cloned(); 1363 + self.globals.insert("this".to_string(), iterable.clone()); 1364 + let result = self.call_function(iter_fn_ref, &[]); 1365 + match old_this { 1366 + Some(v) => self.globals.insert("this".to_string(), v), 1367 + None => self.globals.remove("this"), 1368 + }; 1369 + result 1370 + } 1371 + 1372 + /// Call iterator.next() and return (value, done). 1373 + pub fn iterator_next(&mut self, iterator: &Value) -> Result<(Value, bool), RuntimeError> { 1374 + let iter_ref = match iterator { 1375 + Value::Object(r) | Value::Function(r) => *r, 1376 + _ => return Err(RuntimeError::type_error("iterator is not an object")), 1377 + }; 1378 + 1379 + let next_fn = gc_get_property(&self.gc, iter_ref, "next"); 1380 + let next_fn_ref = match next_fn { 1381 + Value::Function(r) => r, 1382 + _ => return Err(RuntimeError::type_error("iterator.next is not a function")), 1383 + }; 1384 + 1385 + // Call next() with `this` = iterator. 1386 + let old_this = self.globals.get("this").cloned(); 1387 + self.globals.insert("this".to_string(), iterator.clone()); 1388 + let result = self.call_function(next_fn_ref, &[])?; 1389 + match old_this { 1390 + Some(v) => self.globals.insert("this".to_string(), v), 1391 + None => self.globals.remove("this"), 1392 + }; 1393 + 1394 + // Extract value and done from the result object. 1395 + let (value, done) = match result { 1396 + Value::Object(r) => { 1397 + let val = gc_get_property(&self.gc, r, "value"); 1398 + let d = gc_get_property(&self.gc, r, "done"); 1399 + (val, d.to_boolean()) 1400 + } 1401 + _ => (Value::Undefined, true), 1402 + }; 1403 + 1404 + Ok((value, done)) 1405 + } 1406 + 1021 1407 /// Collect all GcRef values reachable from the mutator (roots for GC). 1022 1408 fn collect_roots(&self) -> Vec<GcRef> { 1023 1409 let mut roots = Vec::new(); ··· 1471 1857 }; 1472 1858 match callback(&args, &mut ctx) { 1473 1859 Ok(val) => { 1860 + // Check if this is a generator resume request. 1861 + if let Value::Object(r) = &val { 1862 + let is_resume = matches!( 1863 + gc_get_property(&self.gc, *r, "__generator_resume__"), 1864 + Value::Boolean(true) 1865 + ); 1866 + if is_resume { 1867 + let gen_ref = match gc_get_property( 1868 + &self.gc, 1869 + *r, 1870 + "__gen_ref__", 1871 + ) { 1872 + Value::Object(gr) => gr, 1873 + _ => { 1874 + self.registers[base + dst as usize] = 1875 + Value::Undefined; 1876 + continue; 1877 + } 1878 + }; 1879 + let send_val = 1880 + gc_get_property(&self.gc, *r, "__send_value__"); 1881 + let kind = match gc_get_property( 1882 + &self.gc, 1883 + *r, 1884 + "__resume_kind__", 1885 + ) { 1886 + Value::String(s) => s, 1887 + _ => "next".to_string(), 1888 + }; 1889 + 1890 + match kind.as_str() { 1891 + "next" => { 1892 + match self.run_generator(gen_ref, send_val) { 1893 + Ok(result) => { 1894 + self.registers[base + dst as usize] = 1895 + result; 1896 + } 1897 + Err(err) => { 1898 + let err_val = 1899 + err.to_value(&mut self.gc); 1900 + if !self.handle_exception(err_val) { 1901 + return Err(err); 1902 + } 1903 + } 1904 + } 1905 + } 1906 + "return" => { 1907 + // Force the generator to complete. 1908 + if let Some(HeapObject::Generator(gen)) = 1909 + self.gc.get_mut(gen_ref) 1910 + { 1911 + gen.state = GeneratorState::Completed; 1912 + } 1913 + let result = 1914 + self.make_iterator_result(send_val, true); 1915 + self.registers[base + dst as usize] = result; 1916 + } 1917 + "throw" => { 1918 + // Mark generator as completed and throw. 1919 + if let Some(HeapObject::Generator(gen)) = 1920 + self.gc.get_mut(gen_ref) 1921 + { 1922 + gen.state = GeneratorState::Completed; 1923 + } 1924 + if !self.handle_exception(send_val) { 1925 + return Err(RuntimeError::type_error( 1926 + "Generator throw", 1927 + )); 1928 + } 1929 + } 1930 + _ => { 1931 + self.registers[base + dst as usize] = 1932 + Value::Undefined; 1933 + } 1934 + } 1935 + continue; 1936 + } 1937 + } 1474 1938 self.registers[base + dst as usize] = val; 1475 1939 } 1476 1940 Err(err) => { ··· 1482 1946 } 1483 1947 } 1484 1948 CallInfo::Bytecode(callee_func, callee_upvalues) => { 1949 + // Generator function: create a generator object instead of executing. 1950 + if callee_func.is_generator { 1951 + let gen_obj = self.create_generator_object( 1952 + callee_func, 1953 + callee_upvalues, 1954 + &args, 1955 + ); 1956 + self.registers[base + dst as usize] = Value::Object(gen_obj); 1957 + continue; 1958 + } 1959 + 1485 1960 if self.frames.len() >= MAX_CALL_DEPTH { 1486 1961 let err = 1487 1962 RuntimeError::range_error("Maximum call stack size exceeded"); ··· 1962 2437 *cell_val = val; 1963 2438 } 1964 2439 } 2440 + 2441 + // ── Iterator / generator ───────────────────────── 2442 + Op::Yield => { 2443 + let _dst = Self::read_u8(&mut self.frames[fi]); 2444 + let src = Self::read_u8(&mut self.frames[fi]); 2445 + let base = self.frames[fi].base; 2446 + let yield_val = self.registers[base + src as usize].clone(); 2447 + 2448 + // Save the generator's state. 2449 + let frame = &self.frames[fi]; 2450 + let gen_ref = match self.registers.get(frame.return_reg) { 2451 + Some(Value::Object(r)) => *r, 2452 + _ => { 2453 + return Err(RuntimeError { 2454 + kind: ErrorKind::Error, 2455 + message: "Yield outside generator".into(), 2456 + }); 2457 + } 2458 + }; 2459 + 2460 + // Save registers and IP into the generator object. 2461 + let saved_ip = self.frames[fi].ip; 2462 + let saved_base = self.frames[fi].base; 2463 + let reg_count = self.frames[fi].func.register_count as usize; 2464 + let saved_regs: Vec<Value> = 2465 + self.registers[saved_base..saved_base + reg_count].to_vec(); 2466 + 2467 + if let Some(HeapObject::Generator(gen)) = self.gc.get_mut(gen_ref) { 2468 + gen.ip = saved_ip; 2469 + gen.registers = saved_regs; 2470 + gen.state = GeneratorState::Suspended; 2471 + } 2472 + 2473 + // Pop the generator frame. 2474 + self.frames.pop(); 2475 + 2476 + // Create {value, done: false} result. 2477 + let result = self.make_iterator_result(yield_val, false); 2478 + 2479 + // If this was the only frame, return the result. 2480 + if self.frames.is_empty() { 2481 + return Ok(result); 2482 + } 2483 + 2484 + // Otherwise, write the result into the generator_next caller's dst. 2485 + let caller_fi = self.frames.len() - 1; 2486 + let return_reg = self.frames[caller_fi].base; 2487 + // The return_reg is stored on the popped frame — but we stored 2488 + // the gen_ref there. We need to use the gen_next's return location. 2489 + // Actually, the generator frame's return_reg IS where the result goes. 2490 + let old_return_reg = self.registers.len().min(saved_base + reg_count); // just use gen_ref loc 2491 + let _ = old_return_reg; 2492 + let _ = return_reg; 2493 + // The result should go to the return register that was set when 2494 + // we pushed the frame. Since we already popped, we stored it at 2495 + // frame.return_reg. Let's look at where that was. 2496 + // Actually: the generator resumes via run_generator which pushed 2497 + // a frame with return_reg set. When Yield pops that frame, it needs 2498 + // to write the result to that return_reg. But the frame is already popped. 2499 + // Let's re-read it before popping. 2500 + // (This path handles generators called from the run loop.) 2501 + 2502 + // Actually, generators always run via run_generator which uses 2503 + // an isolated frame stack. After Yield pops, the frame stack is empty 2504 + // and we fall out of run() with the returned result. 2505 + // So this unreachable path should not happen. 2506 + return Ok(result); 2507 + } 2508 + 2509 + Op::Spread => { 2510 + let dst = Self::read_u8(&mut self.frames[fi]); 2511 + let src = Self::read_u8(&mut self.frames[fi]); 2512 + let base = self.frames[fi].base; 2513 + let iterable = self.registers[base + src as usize].clone(); 2514 + 2515 + // Get the iterator from the iterable. 2516 + let iterator = self.get_iterator(&iterable)?; 2517 + 2518 + // Iterate and push each element into the dst array. 2519 + loop { 2520 + let (value, done) = self.iterator_next(&iterator)?; 2521 + if done { 2522 + break; 2523 + } 2524 + // Push value into dst array. 2525 + let dst_ref = match self.registers[base + dst as usize] { 2526 + Value::Object(r) => r, 2527 + _ => break, 2528 + }; 2529 + if let Some(HeapObject::Object(data)) = self.gc.get_mut(dst_ref) { 2530 + let len = match data.properties.get("length") { 2531 + Some(prop) => prop.value.to_number() as usize, 2532 + None => 0, 2533 + }; 2534 + data.properties 2535 + .insert(len.to_string(), Property::data(value)); 2536 + data.properties.insert( 2537 + "length".to_string(), 2538 + Property { 2539 + value: Value::Number((len + 1) as f64), 2540 + writable: true, 2541 + enumerable: false, 2542 + configurable: false, 2543 + }, 2544 + ); 2545 + } 2546 + } 2547 + } 1965 2548 } 1966 2549 } 1967 2550 } ··· 2031 2614 enum CallInfo { 2032 2615 Native(fn(&[Value], &mut NativeContext) -> Result<Value, RuntimeError>), 2033 2616 Bytecode(Function, Vec<GcRef>), 2617 + } 2618 + 2619 + // ── Generator native callbacks ────────────────────────────── 2620 + 2621 + /// Native callback for generator.next(value). 2622 + /// `this` is the generator wrapper object containing __gen__. 2623 + fn generator_next(args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 2624 + // The generator wrapper stores the actual generator as __gen__. 2625 + let gen_ref = match &ctx.this { 2626 + Value::Object(r) => match gc_get_property(ctx.gc, *r, "__gen__") { 2627 + Value::Object(gen_r) => gen_r, 2628 + _ => return Err(RuntimeError::type_error("not a generator")), 2629 + }, 2630 + _ => return Err(RuntimeError::type_error("not a generator")), 2631 + }; 2632 + 2633 + let send_value = args.first().cloned().unwrap_or(Value::Undefined); 2634 + 2635 + // We can't call run_generator from a NativeContext since we only have &mut Gc. 2636 + // Instead, store the request and let the caller handle it. 2637 + // This is a limitation — we need to restructure. 2638 + // For now, use a different approach: store the gen_ref and value in a special 2639 + // return value that the VM intercepts. 2640 + // Actually, generator.next() needs to be handled specially by the VM. 2641 + // Let's return a sentinel that the VM's call handling can detect. 2642 + 2643 + // Store gen_ref and send_value for the VM to process. 2644 + // We use a special object with __generator_resume__ marker. 2645 + let mut obj = ObjectData::new(); 2646 + obj.properties.insert( 2647 + "__generator_resume__".to_string(), 2648 + Property::builtin(Value::Boolean(true)), 2649 + ); 2650 + obj.properties.insert( 2651 + "__gen_ref__".to_string(), 2652 + Property::builtin(Value::Object(gen_ref)), 2653 + ); 2654 + obj.properties 2655 + .insert("__send_value__".to_string(), Property::builtin(send_value)); 2656 + obj.properties.insert( 2657 + "__resume_kind__".to_string(), 2658 + Property::builtin(Value::String("next".to_string())), 2659 + ); 2660 + let r = ctx.gc.alloc(HeapObject::Object(obj)); 2661 + Ok(Value::Object(r)) 2662 + } 2663 + 2664 + /// Native callback for generator.return(value). 2665 + fn generator_return(args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 2666 + let gen_ref = match &ctx.this { 2667 + Value::Object(r) => match gc_get_property(ctx.gc, *r, "__gen__") { 2668 + Value::Object(gen_r) => gen_r, 2669 + _ => return Err(RuntimeError::type_error("not a generator")), 2670 + }, 2671 + _ => return Err(RuntimeError::type_error("not a generator")), 2672 + }; 2673 + 2674 + let return_value = args.first().cloned().unwrap_or(Value::Undefined); 2675 + 2676 + let mut obj = ObjectData::new(); 2677 + obj.properties.insert( 2678 + "__generator_resume__".to_string(), 2679 + Property::builtin(Value::Boolean(true)), 2680 + ); 2681 + obj.properties.insert( 2682 + "__gen_ref__".to_string(), 2683 + Property::builtin(Value::Object(gen_ref)), 2684 + ); 2685 + obj.properties.insert( 2686 + "__send_value__".to_string(), 2687 + Property::builtin(return_value), 2688 + ); 2689 + obj.properties.insert( 2690 + "__resume_kind__".to_string(), 2691 + Property::builtin(Value::String("return".to_string())), 2692 + ); 2693 + let r = ctx.gc.alloc(HeapObject::Object(obj)); 2694 + Ok(Value::Object(r)) 2695 + } 2696 + 2697 + /// Native callback for generator.throw(error). 2698 + fn generator_throw(args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 2699 + let gen_ref = match &ctx.this { 2700 + Value::Object(r) => match gc_get_property(ctx.gc, *r, "__gen__") { 2701 + Value::Object(gen_r) => gen_r, 2702 + _ => return Err(RuntimeError::type_error("not a generator")), 2703 + }, 2704 + _ => return Err(RuntimeError::type_error("not a generator")), 2705 + }; 2706 + 2707 + let error_value = args.first().cloned().unwrap_or(Value::Undefined); 2708 + 2709 + let mut obj = ObjectData::new(); 2710 + obj.properties.insert( 2711 + "__generator_resume__".to_string(), 2712 + Property::builtin(Value::Boolean(true)), 2713 + ); 2714 + obj.properties.insert( 2715 + "__gen_ref__".to_string(), 2716 + Property::builtin(Value::Object(gen_ref)), 2717 + ); 2718 + obj.properties 2719 + .insert("__send_value__".to_string(), Property::builtin(error_value)); 2720 + obj.properties.insert( 2721 + "__resume_kind__".to_string(), 2722 + Property::builtin(Value::String("throw".to_string())), 2723 + ); 2724 + let r = ctx.gc.alloc(HeapObject::Object(obj)); 2725 + Ok(Value::Object(r)) 2726 + } 2727 + 2728 + /// Native callback for generator[Symbol.iterator]() — returns `this`. 2729 + fn generator_symbol_iterator( 2730 + _args: &[Value], 2731 + ctx: &mut NativeContext, 2732 + ) -> Result<Value, RuntimeError> { 2733 + Ok(ctx.this.clone()) 2034 2734 } 2035 2735 2036 2736 // ── Tests ──────────────────────────────────────────────────── ··· 5435 6135 { 5436 6136 Value::String(s) => assert_eq!(s, "fulfilled,rejected"), 5437 6137 v => panic!("expected 'fulfilled,rejected', got {v:?}"), 6138 + } 6139 + } 6140 + 6141 + // ── Iterator and for...of tests ──────────────────────── 6142 + 6143 + #[test] 6144 + fn test_for_of_array() { 6145 + match eval( 6146 + "var result = ''; var arr = [10, 20, 30]; for (var x of arr) { result = result + x + ','; } result", 6147 + ) 6148 + .unwrap() 6149 + { 6150 + Value::String(s) => assert_eq!(s, "10,20,30,"), 6151 + v => panic!("expected '10,20,30,', got {v:?}"), 6152 + } 6153 + } 6154 + 6155 + #[test] 6156 + fn test_for_of_string() { 6157 + match eval("var result = ''; for (var ch of 'abc') { result = result + ch; } result") 6158 + .unwrap() 6159 + { 6160 + Value::String(s) => assert_eq!(s, "abc"), 6161 + v => panic!("expected 'abc', got {v:?}"), 6162 + } 6163 + } 6164 + 6165 + #[test] 6166 + fn test_for_of_with_break() { 6167 + match eval( 6168 + "var result = 0; for (var x of [1, 2, 3, 4, 5]) { if (x === 3) break; result = result + x; } result", 6169 + ) 6170 + .unwrap() 6171 + { 6172 + Value::Number(n) => assert_eq!(n, 3.0), 6173 + v => panic!("expected 3, got {v:?}"), 6174 + } 6175 + } 6176 + 6177 + #[test] 6178 + fn test_for_of_with_continue() { 6179 + match eval( 6180 + "var result = 0; for (var x of [1, 2, 3, 4, 5]) { if (x === 3) continue; result = result + x; } result", 6181 + ) 6182 + .unwrap() 6183 + { 6184 + Value::Number(n) => assert_eq!(n, 12.0), 6185 + v => panic!("expected 12, got {v:?}"), 6186 + } 6187 + } 6188 + 6189 + // ── Generator tests ──────────────────────────────────── 6190 + 6191 + #[test] 6192 + fn test_generator_typeof() { 6193 + // First test: does gen() return an object? 6194 + match eval("function* gen() { yield 1; } typeof gen()").unwrap() { 6195 + Value::String(s) => assert_eq!(s, "object"), 6196 + v => panic!("expected 'object', got {v:?}"), 6197 + } 6198 + } 6199 + 6200 + #[test] 6201 + fn test_generator_has_next() { 6202 + // Test: does the generator have a next method? 6203 + match eval("function* gen() { yield 1; } var g = gen(); typeof g.next").unwrap() { 6204 + Value::String(s) => assert_eq!(s, "function"), 6205 + v => panic!("expected 'function', got {v:?}"), 6206 + } 6207 + } 6208 + 6209 + #[test] 6210 + fn test_basic_generator() { 6211 + match eval( 6212 + "function* gen() { yield 1; yield 2; yield 3; } 6213 + var g = gen(); 6214 + var a = g.next(); 6215 + a.value", 6216 + ) 6217 + .unwrap() 6218 + { 6219 + Value::Number(n) => assert_eq!(n, 1.0), 6220 + v => panic!("expected 1, got {v:?}"), 6221 + } 6222 + } 6223 + 6224 + #[test] 6225 + fn test_generator_multiple_yields() { 6226 + match eval( 6227 + "function* gen() { yield 10; yield 20; yield 30; } 6228 + var g = gen(); 6229 + var r1 = g.next(); var r2 = g.next(); var r3 = g.next(); var r4 = g.next(); 6230 + '' + r1.value + ',' + r2.value + ',' + r3.value + ',' + r4.done", 6231 + ) 6232 + .unwrap() 6233 + { 6234 + Value::String(s) => assert_eq!(s, "10,20,30,true"), 6235 + v => panic!("expected '10,20,30,true', got {v:?}"), 6236 + } 6237 + } 6238 + 6239 + #[test] 6240 + fn test_generator_send_value() { 6241 + match eval( 6242 + "function* gen() { var x = yield 'hello'; yield x + ' world'; } 6243 + var g = gen(); 6244 + g.next(); 6245 + var r = g.next('beautiful'); 6246 + r.value", 6247 + ) 6248 + .unwrap() 6249 + { 6250 + Value::String(s) => assert_eq!(s, "beautiful world"), 6251 + v => panic!("expected 'beautiful world', got {v:?}"), 6252 + } 6253 + } 6254 + 6255 + #[test] 6256 + fn test_generator_return() { 6257 + match eval( 6258 + "function* gen() { yield 1; yield 2; yield 3; } 6259 + var g = gen(); 6260 + g.next(); 6261 + var r = g['return'](42); 6262 + '' + r.value + ',' + r.done", 6263 + ) 6264 + .unwrap() 6265 + { 6266 + Value::String(s) => assert_eq!(s, "42,true"), 6267 + v => panic!("expected '42,true', got {v:?}"), 6268 + } 6269 + } 6270 + 6271 + #[test] 6272 + fn test_generator_in_for_of() { 6273 + match eval( 6274 + "function* range(start, end) { 6275 + for (var i = start; i < end; i = i + 1) { yield i; } 6276 + } 6277 + var result = 0; 6278 + for (var n of range(1, 5)) { result = result + n; } 6279 + result", 6280 + ) 6281 + .unwrap() 6282 + { 6283 + Value::Number(n) => assert_eq!(n, 10.0), 6284 + v => panic!("expected 10, got {v:?}"), 6285 + } 6286 + } 6287 + 6288 + #[test] 6289 + fn test_generator_with_return_value() { 6290 + match eval( 6291 + "function* gen() { yield 1; return 'done'; } 6292 + var g = gen(); 6293 + var a = g.next(); 6294 + var b = g.next(); 6295 + '' + a.value + ',' + a.done + ',' + b.value + ',' + b.done", 6296 + ) 6297 + .unwrap() 6298 + { 6299 + Value::String(s) => assert_eq!(s, "1,false,done,true"), 6300 + v => panic!("expected '1,false,done,true', got {v:?}"), 6301 + } 6302 + } 6303 + 6304 + // ── Destructuring tests ──────────────────────────────── 6305 + 6306 + #[test] 6307 + fn test_array_destructuring_basic() { 6308 + match eval("var [a, b, c] = [1, 2, 3]; a + b + c").unwrap() { 6309 + Value::Number(n) => assert_eq!(n, 6.0), 6310 + v => panic!("expected 6, got {v:?}"), 6311 + } 6312 + } 6313 + 6314 + #[test] 6315 + fn test_array_destructuring_rest() { 6316 + match eval("var [first, ...rest] = [1, 2, 3, 4]; '' + first + ',' + rest.length").unwrap() { 6317 + Value::String(s) => assert_eq!(s, "1,3"), 6318 + v => panic!("expected '1,3', got {v:?}"), 6319 + } 6320 + } 6321 + 6322 + #[test] 6323 + fn test_array_destructuring_default() { 6324 + match eval("var [a = 10, b = 20] = [1]; '' + a + ',' + b").unwrap() { 6325 + Value::String(s) => assert_eq!(s, "1,20"), 6326 + v => panic!("expected '1,20', got {v:?}"), 6327 + } 6328 + } 6329 + 6330 + #[test] 6331 + fn test_object_destructuring_basic() { 6332 + match eval("var {x, y} = {x: 1, y: 2}; x + y").unwrap() { 6333 + Value::Number(n) => assert_eq!(n, 3.0), 6334 + v => panic!("expected 3, got {v:?}"), 6335 + } 6336 + } 6337 + 6338 + #[test] 6339 + fn test_object_destructuring_alias() { 6340 + match eval("var {x: a, y: b} = {x: 10, y: 20}; a + b").unwrap() { 6341 + Value::Number(n) => assert_eq!(n, 30.0), 6342 + v => panic!("expected 30, got {v:?}"), 6343 + } 6344 + } 6345 + 6346 + #[test] 6347 + fn test_nested_destructuring() { 6348 + match eval("var {a: {b}} = {a: {b: 42}}; b").unwrap() { 6349 + Value::Number(n) => assert_eq!(n, 42.0), 6350 + v => panic!("expected 42, got {v:?}"), 6351 + } 6352 + } 6353 + 6354 + #[test] 6355 + fn test_destructuring_in_for_of() { 6356 + match eval( 6357 + "var result = 0; var pairs = [[1, 2], [3, 4], [5, 6]]; for (var [a, b] of pairs) { result = result + a + b; } result", 6358 + ) 6359 + .unwrap() 6360 + { 6361 + Value::Number(n) => assert_eq!(n, 21.0), 6362 + v => panic!("expected 21, got {v:?}"), 6363 + } 6364 + } 6365 + 6366 + // ── Spread tests ─────────────────────────────────────── 6367 + 6368 + #[test] 6369 + fn test_spread_in_array() { 6370 + match eval("var a = [1, 2, 3]; var b = [0, ...a, 4]; b.length").unwrap() { 6371 + Value::Number(n) => assert_eq!(n, 5.0), 6372 + v => panic!("expected 5, got {v:?}"), 6373 + } 6374 + } 6375 + 6376 + #[test] 6377 + fn test_spread_in_array_values() { 6378 + match eval( 6379 + "var a = [1, 2, 3]; var b = [0, ...a, 4]; '' + b[0] + ',' + b[1] + ',' + b[2] + ',' + b[3] + ',' + b[4]", 6380 + ) 6381 + .unwrap() 6382 + { 6383 + Value::String(s) => assert_eq!(s, "0,1,2,3,4"), 6384 + v => panic!("expected '0,1,2,3,4', got {v:?}"), 6385 + } 6386 + } 6387 + 6388 + // ── Custom iterable tests ────────────────────────────── 6389 + 6390 + #[test] 6391 + fn test_custom_iterable() { 6392 + match eval( 6393 + "var obj = {}; 6394 + obj['@@iterator'] = function() { 6395 + var i = 0; 6396 + return { 6397 + next: function() { 6398 + i = i + 1; 6399 + if (i <= 3) return {value: i, done: false}; 6400 + return {value: undefined, done: true}; 6401 + } 6402 + }; 6403 + }; 6404 + var result = 0; 6405 + for (var v of obj) { result = result + v; } 6406 + result", 6407 + ) 6408 + .unwrap() 6409 + { 6410 + Value::Number(n) => assert_eq!(n, 6.0), 6411 + v => panic!("expected 6, got {v:?}"), 6412 + } 6413 + } 6414 + 6415 + // ── Array.from with iterables ────────────────────────── 6416 + 6417 + #[test] 6418 + fn test_array_keys_values_entries() { 6419 + match eval( 6420 + "var arr = [10, 20, 30]; var r = ''; for (var v of arr.values()) { r = r + v + ','; } r", 6421 + ) 6422 + .unwrap() 6423 + { 6424 + Value::String(s) => assert_eq!(s, "10,20,30,"), 6425 + v => panic!("expected '10,20,30,', got {v:?}"), 5438 6426 } 5439 6427 } 5440 6428 }
-1
crates/js/tests/test262.rs
··· 129 129 "async-iteration", 130 130 "top-level-await", 131 131 // Generators and iterators 132 - "generators", 133 132 "async-generators", 134 133 // Modules 135 134 "import-assertions",