feat: 实现for-of与解构赋值

This commit is contained in:
爱特每羊队
2026-06-14 23:45:12 +08:00
parent c766d69d0a
commit 21e3693c63
15 changed files with 891 additions and 81 deletions
@@ -37,6 +37,7 @@ pub fn bind_array(core: &Arc<KernelCore>, session: &KernelSession, global: &mut
find_last: crate::builtins::array::array_find_last as *const (),
reduce_right: crate::builtins::array::array_reduce_right as *const (),
sort: crate::builtins::array::array_sort as *const (),
values: crate::builtins::array::array_values as *const (),
};
session.builtin_world().bind_array_methods(
+8
View File
@@ -840,3 +840,11 @@ pub fn array_sort(vm: &mut Vm, _args: &[u8]) -> NativeResult {
}
NativeResult::Ok(vm.reg(_args[0]))
}
pub fn array_values(vm: &mut Vm, args: &[u8]) -> NativeResult {
let this_val = vm.reg(args[0]);
match crate::builtins::iterator::make_iterator_for_value(vm, this_val) {
Ok(iterator) => NativeResult::Ok(iterator),
Err(err) => NativeResult::Err(err),
}
}
+10 -7
View File
@@ -15,11 +15,14 @@ pub fn iterator_constructor(vm: &mut Vm, _args: &[u8]) -> NativeResult {
pub fn iterator_from(vm: &mut Vm, args: &[u8]) -> NativeResult {
let iterable = if args.len() > 1 { vm.reg(args[1]) } else { JsValue::undefined() };
let inner = match get_iterator(vm, iterable) {
Ok(inner) => inner,
Err(err) => return err,
};
match make_iterator_for_value(vm, iterable) {
Ok(iterator) => NativeResult::Ok(iterator),
Err(err) => NativeResult::Err(err),
}
}
pub(crate) fn make_iterator_for_value(vm: &mut Vm, value: JsValue) -> Result<JsValue, JsValue> {
let inner = get_iterator(vm, value)?;
let object_proto = vm.session().builtin_world().object_proto.as_ptr() as *mut JsObject;
let wrapper = vm
.epoch()
@@ -35,7 +38,7 @@ pub fn iterator_from(vm: &mut Vm, args: &[u8]) -> NativeResult {
let next_fn = make_native_function(vm, "next", iterator_wrapper_next as *const (), 0);
vm.set_or_create_prop_value(wrapper_obj, next_si, next_fn);
NativeResult::Ok(JsValue::from_js_object(wrapper))
Ok(JsValue::from_js_object(wrapper))
}
pub fn iterator_wrapper_next(vm: &mut Vm, args: &[u8]) -> NativeResult {
@@ -80,7 +83,7 @@ pub fn iterator_wrapper_next(vm: &mut Vm, args: &[u8]) -> NativeResult {
NativeResult::Err(crate::builtins::error::create_type_error(vm, "value is not iterable"))
}
fn get_iterator(vm: &mut Vm, value: JsValue) -> Result<JsValue, NativeResult> {
fn get_iterator(vm: &mut Vm, value: JsValue) -> Result<JsValue, JsValue> {
if value.is_string() || is_array_value(value) {
return Ok(value);
}
@@ -95,7 +98,7 @@ fn get_iterator(vm: &mut Vm, value: JsValue) -> Result<JsValue, NativeResult> {
}
}
Err(NativeResult::Err(crate::builtins::error::create_type_error(vm, "value is not iterable")))
Err(crate::builtins::error::create_type_error(vm, "value is not iterable"))
}
fn next_array_like(vm: &mut Vm, wrapper: &mut JsObject, inner: JsValue, index_si: u32) -> Option<JsValue> {
+7 -1
View File
@@ -258,7 +258,13 @@ impl Vm {
let Some(obj_ptr) = self.checked_object_ptr(self.regs[rd], "SET_ELEM on non-object")? else {
return Ok(());
};
let idx = self.regs[a].as_int().max(0) as u32;
let idx = if self.regs[a].is_int() {
self.regs[a].as_int().max(0) as u32
} else if self.regs[a].is_double() {
self.regs[a].as_double().max(0.0) as u32
} else {
0
};
let obj = unsafe { &mut *obj_ptr };
obj.set_prop_at(idx, self.regs[b]);
Ok(())
+50 -23
View File
@@ -125,6 +125,8 @@ pub(crate) struct InlineSyncState {
pub(crate) pending_exception: Option<JsValue>,
pub(crate) pending_error_kind: Option<&'static str>,
pub(crate) for_in_iters: Vec<*mut ForInIter<'static>>,
pub(crate) for_of_iters: Vec<JsValue>,
pub(crate) last_for_of_result: JsValue,
pub(crate) saved_bytecode_stack: Vec<Vec<opcode::Instr>>,
pub(crate) saved_constants_stack: Vec<Vec<JsValue>>,
}
@@ -153,8 +155,8 @@ pub struct Vm {
pub(crate) symbol_counter: u32,
pub(crate) symbol_descriptions: Vec<String>,
pub(crate) symbol_registry: HashMap<String, u32>,
#[allow(dead_code)]
pub(crate) for_of_iters: Vec<*mut u8>,
pub(crate) for_of_iters: Vec<JsValue>,
pub(crate) last_for_of_result: JsValue,
pub(crate) root_reg_limit: u8,
pub(crate) active_reg_limit: u8,
pub(crate) native_call_depth: usize,
@@ -291,11 +293,26 @@ impl Vm {
self.kernel_core.string_forge().intern(&key).0
}
pub(crate) fn array_index_from_property_key(&self, prop_name_si: u32) -> Option<u32> {
let key = self.kernel_core.string_forge().lookup(prop_name_si)?;
if key.is_empty() || (key.len() > 1 && key.starts_with('0')) {
return None;
}
key.parse::<u32>().ok()
}
pub(crate) fn resolve_property(&self, obj: &JsObject, prop_name_si: u32) -> Option<JsValue> {
let length_si = self.kernel_core.string_forge().intern("length").0;
if obj.is_array() && prop_name_si == length_si {
return Some(JsValue::int(obj.prop_count() as i32));
}
if obj.is_array() {
if let Some(index) = self.array_index_from_property_key(prop_name_si) {
if index < obj.prop_vec_len() as u32 {
return Some(obj.get_prop_at(index));
}
}
}
if let Some(pos) = self.kernel_core.shape_forge().lookup_position(obj.shape_id(), prop_name_si) {
let val = obj.get_prop_at(pos);
if !val.is_undefined() || obj.prop_vec_len() > pos as usize {
@@ -325,6 +342,13 @@ impl Vm {
if obj.is_array() && prop_name_si == length_si {
return None;
}
if obj.is_array() {
if let Some(index) = self.array_index_from_property_key(prop_name_si) {
if index < obj.prop_vec_len() as u32 {
return Some(index);
}
}
}
self.kernel_core
.shape_forge()
.lookup_position(obj.shape_id(), prop_name_si)
@@ -1020,19 +1044,23 @@ impl Vm {
}
OpCode::FOR_OF_INIT => {
return Err("opcode FOR_OF_INIT not yet implemented".into());
self.dispatch_for_of_init(a)?;
}
OpCode::FOR_OF_NEXT => {
return Err("opcode FOR_OF_NEXT not yet implemented".into());
self.dispatch_for_of_next(rd)?;
}
OpCode::FOR_OF_DONE => {
return Err("opcode FOR_OF_DONE not yet implemented".into());
self.dispatch_for_of_done(rd)?;
}
OpCode::FOR_OF_CLOSE => {
return Err("opcode FOR_OF_CLOSE not yet implemented".into());
self.dispatch_for_of_close()?;
}
OpCode::REST_OBJECT => {
self.dispatch_rest_object(rd, a)?;
}
OpCode::THROW => match self.dispatch_throw(rd) {
@@ -1157,7 +1185,7 @@ mod tests {
continuation: super::FrameContinuation::None,
});
vm.for_in_iters.push(std::ptr::dangling_mut::<super::ForInIter<'static>>());
vm.for_of_iters.push(std::ptr::dangling_mut::<u8>());
vm.for_of_iters.push(JsValue::undefined());
vm.saved_bytecode_stack
.push(vec![opcode::encode(opcode::OpCode::HALT, 0, 0, 0)]);
vm.saved_constants_stack.push(vec![JsValue::int(1)]);
@@ -1201,22 +1229,21 @@ mod tests {
}
#[test]
fn for_of_opcodes_fail_explicitly() {
for (op, expected) in [
(opcode::OpCode::FOR_OF_INIT, "opcode FOR_OF_INIT not yet implemented"),
(opcode::OpCode::FOR_OF_NEXT, "opcode FOR_OF_NEXT not yet implemented"),
(opcode::OpCode::FOR_OF_DONE, "opcode FOR_OF_DONE not yet implemented"),
(opcode::OpCode::FOR_OF_CLOSE, "opcode FOR_OF_CLOSE not yet implemented"),
] {
let module = CompiledModule {
bytecode: vec![opcode::encode(op, 0, 0, 0), opcode::encode(opcode::OpCode::HALT, 0, 0, 0)],
n_registers: 1,
..CompiledModule::new()
};
let mut vm = Vm::new();
let err = vm.run(&module).expect_err("FOR_OF opcode should fail explicitly");
assert_eq!(err, expected);
}
fn for_of_close_pops_iterator_stack() {
let module = CompiledModule {
bytecode: vec![
opcode::encode(opcode::OpCode::FOR_OF_CLOSE, 0, 0, 0),
opcode::encode(opcode::OpCode::HALT, 0, 0, 0),
],
n_registers: 1,
..CompiledModule::new()
};
let mut vm = Vm::new();
vm.for_of_iters.push(JsValue::undefined());
vm.run(&module).expect("FOR_OF_CLOSE should tolerate non-object sentinel");
assert!(vm.for_of_iters.is_empty());
}
#[test]
+127
View File
@@ -327,4 +327,131 @@ impl Vm {
pub(crate) fn dispatch_for_in_cleanup(&mut self) {
self.for_in_iters.pop();
}
pub(crate) fn dispatch_for_of_init(&mut self, a: usize) -> Result<(), String> {
let iterable = self.regs[a];
match crate::builtins::iterator::make_iterator_for_value(self, iterable) {
Ok(iterator) => {
self.for_of_iters.push(iterator);
self.last_for_of_result = JsValue::undefined();
Ok(())
}
Err(err) => {
self.exception_value = Some(err);
self.pending_error_kind = Some(self.thrown_error_kind(err));
self.unwind()
}
}
}
pub(crate) fn dispatch_for_of_done(&mut self, rd: usize) -> Result<(), String> {
let Some(iterator) = self.for_of_iters.last().copied() else {
return Err("FOR_OF_DONE without active iterator".into());
};
if !iterator.is_object() {
return Err("FOR_OF_DONE iterator is not an object".into());
}
let iter_obj = unsafe { &*iterator.as_js_object_ptr() };
let next_si = self.kernel_core.string_forge().intern("next").0;
let next_fn = self.ordinary_get(iter_obj, next_si, iterator)?;
let result = self.call_function_sync(next_fn, iterator, &[])?;
if !result.is_object() {
return self.raise_type_error("iterator result is not an object");
}
let result_obj = unsafe { &*result.as_js_object_ptr() };
let done_si = self.kernel_core.string_forge().intern("done").0;
let done_val = self.ordinary_get(result_obj, done_si, result)?;
let done = crate::coercion::to_boolean(done_val, self.kernel_core.string_forge().as_ref());
self.last_for_of_result = result;
self.regs[rd] = JsValue::bool(!done);
Ok(())
}
pub(crate) fn dispatch_for_of_next(&mut self, rd: usize) -> Result<(), String> {
let result = self.last_for_of_result;
if !result.is_object() {
self.regs[rd] = JsValue::undefined();
return Ok(());
}
let result_obj = unsafe { &*result.as_js_object_ptr() };
let value_si = self.kernel_core.string_forge().intern("value").0;
self.regs[rd] = self.ordinary_get(result_obj, value_si, result)?;
Ok(())
}
pub(crate) fn dispatch_for_of_close(&mut self) -> Result<(), String> {
let Some(iterator) = self.for_of_iters.pop() else {
return Ok(());
};
if !iterator.is_object() {
return Ok(());
}
let iter_obj = unsafe { &*iterator.as_js_object_ptr() };
let return_si = self.kernel_core.string_forge().intern("return").0;
let return_fn = self.ordinary_get(iter_obj, return_si, iterator)?;
if return_fn.is_object() {
let return_obj = unsafe { &*return_fn.as_js_object_ptr() };
if return_obj.is_function() {
let _ = self.call_function_sync(return_fn, iterator, &[])?;
}
}
Ok(())
}
pub(crate) fn dispatch_rest_object(&mut self, rd: usize, a: usize) -> Result<(), String> {
let src = self.regs[a];
if !src.is_object() {
return self.raise_type_error("Cannot destructure property of null/undefined");
}
let excluded_idx = self.bytecode[self.pc] as usize;
self.pc += 1;
let excluded = self
.constants
.get(excluded_idx)
.and_then(|v| {
if v.is_string() {
self.kernel_core.string_forge().lookup(v.as_string_index())
} else {
None
}
})
.unwrap_or_default();
let excluded: std::collections::HashSet<&str> = excluded.split('\0').filter(|s| !s.is_empty()).collect();
let proto_ptr = self.session.builtin_world().object_proto.as_ptr() as *mut JsObject;
let rest_ptr = self.epoch.alloc(JsObject::new_empty(
oxide_kernel::shape_forge::EMPTY_SHAPE_ID,
JsValue::from_js_object(proto_ptr),
));
let src_obj = unsafe { &*src.as_js_object_ptr() };
let mut cursor = Some(src_obj.shape_id());
while let Some(shape_id) = cursor {
if shape_id == oxide_kernel::shape_forge::EMPTY_SHAPE_ID {
break;
}
let Some(shape) = self.kernel_core.shape_forge().get_shape(shape_id) else {
break;
};
if shape.property_name != u32::MAX {
if let Some(name) = self.kernel_core.string_forge().lookup(shape.property_name) {
if !excluded.contains(name.as_str()) {
if let Some(pos) = self
.kernel_core
.shape_forge()
.lookup_position(src_obj.shape_id(), shape.property_name)
{
let val = src_obj.get_prop_at(pos);
let rest = unsafe { &mut *rest_ptr };
self.set_or_create_prop_value(rest, shape.property_name, val);
}
}
}
}
cursor = shape.parent;
}
self.regs[rd] = JsValue::from_js_object(rest_ptr);
Ok(())
}
}
+7
View File
@@ -23,6 +23,13 @@ impl Vm {
if obj.is_array() && prop_name_si == length_si {
return Ok(JsValue::int(obj.prop_count() as i32));
}
if obj.is_array() {
if let Some(index) = self.array_index_from_property_key(prop_name_si) {
if index < obj.prop_vec_len() as u32 {
return Ok(obj.get_prop_at(index));
}
}
}
if let Some(pos) = self.get_own_property_slot(obj, prop_name_si) {
if let Some(meta) = obj.prop_meta_at(pos) {
if meta.is_accessor {
+4
View File
@@ -40,6 +40,8 @@ impl Vm {
pending_exception: self.pending_exception.take(),
pending_error_kind: self.pending_error_kind.take(),
for_in_iters: std::mem::take(&mut self.for_in_iters),
for_of_iters: std::mem::take(&mut self.for_of_iters),
last_for_of_result: self.last_for_of_result,
saved_bytecode_stack: std::mem::take(&mut self.saved_bytecode_stack),
saved_constants_stack: std::mem::take(&mut self.saved_constants_stack),
});
@@ -78,6 +80,8 @@ impl Vm {
self.pending_exception = saved.pending_exception;
self.pending_error_kind = saved.pending_error_kind;
self.for_in_iters = saved.for_in_iters;
self.for_of_iters = saved.for_of_iters;
self.last_for_of_result = saved.last_for_of_result;
self.saved_bytecode_stack = saved.saved_bytecode_stack;
self.saved_constants_stack = saved.saved_constants_stack;
+3
View File
@@ -44,6 +44,7 @@ impl Vm {
symbol_descriptions: Vec::new(),
symbol_registry: std::collections::HashMap::new(),
for_of_iters: Vec::new(),
last_for_of_result: JsValue::undefined(),
root_reg_limit: 0,
active_reg_limit: 0,
native_call_depth: 0,
@@ -80,6 +81,7 @@ impl Vm {
symbol_descriptions: Vec::new(),
symbol_registry: std::collections::HashMap::new(),
for_of_iters: Vec::new(),
last_for_of_result: JsValue::undefined(),
root_reg_limit: 0,
active_reg_limit: 0,
native_call_depth: 0,
@@ -115,6 +117,7 @@ impl Vm {
self.frames.clear();
self.for_in_iters.clear();
self.for_of_iters.clear();
self.last_for_of_result = JsValue::undefined();
self.saved_bytecode_stack.clear();
self.saved_constants_stack.clear();
self.try_stack.clear();