feat: 实现for-of与解构赋值
This commit is contained in:
@@ -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(
|
||||
|
||||
@@ -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),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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> {
|
||||
|
||||
@@ -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
@@ -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]
|
||||
|
||||
@@ -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(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
Reference in New Issue
Block a user