From c2da5fb2f3cb0e957687942a47d38791105fc164 Mon Sep 17 00:00:00 2001 From: pjh456 <147148383@qq.com> Date: Sun, 14 Jun 2026 17:58:16 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=AE=9E=E7=8E=B0=20Iterator.from=20?= =?UTF-8?q?=E5=85=A8=E5=B1=80=E7=BB=91=E5=AE=9A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- crates/oxide_vm/src/bindings/bind_iterator.rs | 24 +++ crates/oxide_vm/src/bindings/mod.rs | 2 + crates/oxide_vm/src/builtins/iterator.rs | 178 ++++++++++++++++++ crates/oxide_vm/src/builtins/mod.rs | 1 + .../oxide_vm/tests/builtins_iterator_tests.rs | 63 +++++++ 5 files changed, 268 insertions(+) create mode 100644 crates/oxide_vm/src/bindings/bind_iterator.rs create mode 100644 crates/oxide_vm/src/builtins/iterator.rs create mode 100644 crates/oxide_vm/tests/builtins_iterator_tests.rs diff --git a/crates/oxide_vm/src/bindings/bind_iterator.rs b/crates/oxide_vm/src/bindings/bind_iterator.rs new file mode 100644 index 00000000..dbcd2631 --- /dev/null +++ b/crates/oxide_vm/src/bindings/bind_iterator.rs @@ -0,0 +1,24 @@ +use std::sync::Arc; + +use oxide_kernel::kernel::{KernelCore, KernelSession}; +use oxide_kernel::shape_forge::EMPTY_SHAPE_ID; +use oxide_types::object::JsObject; +use oxide_types::value::JsValue; + +use crate::bindings::{apply_binding_table, bind_global_value, configure_native_constructor}; + +pub fn bind_iterator(core: &Arc, session: &KernelSession, global: &mut JsObject) { + let function_proto = session.builtin_world().function_proto.as_ptr() as *mut JsObject; + let mut iterator = Box::new(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::from_js_object(function_proto))); + iterator.set_function(true); + configure_native_constructor(&mut iterator, crate::builtins::iterator::iterator_constructor as *const (), 0); + + apply_binding_table( + session.builtin_world(), + &mut iterator, + core, + &[("from", crate::builtins::iterator::iterator_from as *const (), 1)], + ); + + bind_global_value(core, global, "Iterator", JsValue::from_js_object(Box::into_raw(iterator))); +} diff --git a/crates/oxide_vm/src/bindings/mod.rs b/crates/oxide_vm/src/bindings/mod.rs index 7f8e1e6b..eaddb37a 100644 --- a/crates/oxide_vm/src/bindings/mod.rs +++ b/crates/oxide_vm/src/bindings/mod.rs @@ -4,6 +4,7 @@ pub mod bind_date; pub mod bind_error; pub mod bind_function; pub mod bind_global; +pub mod bind_iterator; pub mod bind_json; pub mod bind_map; pub mod bind_math; @@ -97,6 +98,7 @@ pub fn init_kernel_builtins(core: &Arc, session: &mut KernelSession) bind_function::bind_function(core, session, global); bind_regexp::bind_regexp(core, session, global); bind_symbol::bind_symbol(core, session, global); + bind_iterator::bind_iterator(core, session, global); bind_global::bind_global(core, session, global); bind_stubs::bind_stubs(core, session, global); diff --git a/crates/oxide_vm/src/builtins/iterator.rs b/crates/oxide_vm/src/builtins/iterator.rs new file mode 100644 index 00000000..4824a4bc --- /dev/null +++ b/crates/oxide_vm/src/builtins/iterator.rs @@ -0,0 +1,178 @@ +use oxide_kernel::shape_forge::EMPTY_SHAPE_ID; +use oxide_types::object::JsObject; +use oxide_types::value::JsValue; + +use crate::coercion; +use crate::native::NativeResult; +use crate::vm::Vm; + +const INNER_PROP: &str = "__inner__"; +const INDEX_PROP: &str = "__index__"; + +pub fn iterator_constructor(vm: &mut Vm, _args: &[u8]) -> NativeResult { + NativeResult::Err(crate::builtins::error::create_type_error(vm, "Iterator is not a constructor")) +} + +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, + }; + + let object_proto = vm.session().builtin_world().object_proto.as_ptr() as *mut JsObject; + let wrapper = vm + .epoch() + .alloc(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::from_js_object(object_proto))); + + let inner_si = vm.kernel_core().string_forge().intern(INNER_PROP).0; + let index_si = vm.kernel_core().string_forge().intern(INDEX_PROP).0; + let next_si = vm.kernel_core().string_forge().intern("next").0; + let wrapper_obj = unsafe { &mut *wrapper }; + vm.set_or_create_prop_value(wrapper_obj, inner_si, inner); + vm.set_or_create_prop_value(wrapper_obj, index_si, JsValue::int(0)); + + 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)) +} + +pub fn iterator_wrapper_next(vm: &mut Vm, args: &[u8]) -> NativeResult { + let this_val = vm.reg(if args.is_empty() { 0 } else { args[0] }); + if !this_val.is_object() { + return NativeResult::Err(crate::builtins::error::create_type_error( + vm, + "Iterator wrapper next called on non-object", + )); + } + + let wrapper = unsafe { &mut *this_val.as_js_object_ptr() }; + let inner_si = vm.kernel_core().string_forge().intern(INNER_PROP).0; + let index_si = vm.kernel_core().string_forge().intern(INDEX_PROP).0; + let inner = match vm.ordinary_get(wrapper, inner_si, this_val) { + Ok(inner) if !inner.is_undefined() => inner, + _ => { + return NativeResult::Err(crate::builtins::error::create_type_error( + vm, + "Iterator wrapper has no inner iterator", + )) + } + }; + + if let Some(result) = next_array_like(vm, wrapper, inner, index_si) { + return NativeResult::Ok(result); + } + + if inner.is_object() { + let inner_obj = unsafe { &*inner.as_js_object_ptr() }; + let next_si = vm.kernel_core().string_forge().intern("next").0; + let next = match vm.ordinary_get(inner_obj, next_si, inner) { + Ok(next) => next, + Err(err) => return NativeResult::Err(crate::builtins::error::create_type_error(vm, &err)), + }; + return match vm.call_function_sync(next, inner, &[]) { + Ok(result) => NativeResult::Ok(result), + Err(err) => NativeResult::Err(crate::builtins::error::create_type_error(vm, &err)), + }; + } + + NativeResult::Err(crate::builtins::error::create_type_error(vm, "value is not iterable")) +} + +fn get_iterator(vm: &mut Vm, value: JsValue) -> Result { + if value.is_string() || is_array_value(value) { + return Ok(value); + } + + if value.is_object() { + let obj = unsafe { &*value.as_js_object_ptr() }; + let next_si = vm.kernel_core().string_forge().intern("next").0; + if let Ok(next) = vm.ordinary_get(obj, next_si, value) { + if is_callable(next) { + return Ok(value); + } + } + } + + Err(NativeResult::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 { + if is_array_value(inner) { + let index = current_index(vm, wrapper, index_si); + let arr = unsafe { &*inner.as_js_object_ptr() }; + if index < arr.prop_count() as usize { + let value = arr.get_prop_at(index); + vm.set_or_create_prop_value(wrapper, index_si, JsValue::int((index + 1) as i32)); + return Some(make_iter_result(vm, value, false)); + } + return Some(make_iter_result(vm, JsValue::undefined(), true)); + } + + if inner.is_string() { + let index = current_index(vm, wrapper, index_si); + let source = coercion::to_string(vm.kernel_core().string_forge().as_ref(), inner); + let mut chars = source.chars(); + if let Some(ch) = chars.nth(index) { + vm.set_or_create_prop_value(wrapper, index_si, JsValue::int((index + 1) as i32)); + let value = vm.intern(&ch.to_string()); + return Some(make_iter_result(vm, value, false)); + } + return Some(make_iter_result(vm, JsValue::undefined(), true)); + } + + None +} + +fn current_index(vm: &mut Vm, wrapper: &JsObject, index_si: u32) -> usize { + match vm.ordinary_get(wrapper, index_si, JsValue::undefined()) { + Ok(value) if value.is_int() => value.as_int().max(0) as usize, + Ok(value) if value.is_double() => value.as_double().max(0.0) as usize, + _ => 0, + } +} + +fn make_iter_result(vm: &mut Vm, value: JsValue, done: bool) -> JsValue { + let object_proto = vm.session().builtin_world().object_proto.as_ptr() as *mut JsObject; + let obj = vm + .epoch() + .alloc(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::from_js_object(object_proto))); + let value_si = vm.kernel_core().string_forge().intern("value").0; + let done_si = vm.kernel_core().string_forge().intern("done").0; + let obj_ref = unsafe { &mut *obj }; + vm.set_or_create_prop_value(obj_ref, value_si, value); + vm.set_or_create_prop_value(obj_ref, done_si, JsValue::bool(done)); + JsValue::from_js_object(obj) +} + +fn make_native_function(vm: &mut Vm, name: &str, native_fn: *const (), arg_count: u8) -> JsValue { + let function_proto = vm.session().builtin_world().function_proto.as_ptr() as *mut JsObject; + let mut func = JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::from_js_object(function_proto)); + func.set_function(true); + // SAFETY: native_fn is provided from a NativeFn fn item. + func.set_native_fn(Some(unsafe { oxide_types::object::NativeFnPtr::from_raw(native_fn) })); + func.set_native_arg_count(arg_count); + let func = vm.epoch().alloc(func); + let name_si = vm.kernel_core().string_forge().intern("name").0; + let value = vm.intern(name); + let func_ref = unsafe { &mut *func }; + vm.set_or_create_prop_value(func_ref, name_si, value); + JsValue::from_js_object(func) +} + +fn is_array_value(value: JsValue) -> bool { + if !value.is_object() { + return false; + } + let ptr = value.as_js_object_ptr(); + !ptr.is_null() && unsafe { &*ptr }.is_array() +} + +fn is_callable(value: JsValue) -> bool { + if !value.is_object() { + return false; + } + let ptr = value.as_js_object_ptr(); + !ptr.is_null() && unsafe { &*ptr }.is_function() +} diff --git a/crates/oxide_vm/src/builtins/mod.rs b/crates/oxide_vm/src/builtins/mod.rs index e581bbca..40af9abd 100644 --- a/crates/oxide_vm/src/builtins/mod.rs +++ b/crates/oxide_vm/src/builtins/mod.rs @@ -4,6 +4,7 @@ pub mod date; pub mod error; pub mod function; pub mod global; +pub mod iterator; pub mod json; pub mod map; pub mod math; diff --git a/crates/oxide_vm/tests/builtins_iterator_tests.rs b/crates/oxide_vm/tests/builtins_iterator_tests.rs new file mode 100644 index 00000000..597d5d1a --- /dev/null +++ b/crates/oxide_vm/tests/builtins_iterator_tests.rs @@ -0,0 +1,63 @@ +use oxide_compiler::compiler::Compiler; +use oxide_types::value::JsValue; +use oxide_vm::vm::Vm; + +fn eval(vm: &mut Vm, source: &str) -> Result { + let allocator = oxide_parser::Allocator::default(); + let program = oxide_parser::parse(&allocator, source).map_err(|e| format!("Parse error: {:?}", e))?; + let module = Compiler::new().compile(&program).map_err(|e| format!("Compile error: {}", e))?; + vm.run(&module) +} + +fn to_str(vm: &Vm, val: JsValue) -> String { + vm.kernel_core() + .string_forge() + .lookup(val.as_string_index()) + .unwrap_or_default() +} + +#[test] +fn iterator_global_exists() { + let mut vm = Vm::new(); + let result = eval(&mut vm, "typeof Iterator").unwrap(); + assert_eq!(to_str(&vm, result), "function"); +} + +#[test] +fn iterator_from_array_returns_values_until_done() { + let mut vm = Vm::new(); + let result = eval( + &mut vm, + "var iter = Iterator.from([1, 2, 3]); \ + var a = iter.next(); var b = iter.next(); var c = iter.next(); var d = iter.next(); \ + a.value === 1 && a.done === false && b.value === 2 && c.value === 3 && d.done === true", + ) + .unwrap(); + assert!(result.as_bool()); +} + +#[test] +fn iterator_from_string_returns_chars() { + let mut vm = Vm::new(); + let result = eval(&mut vm, "var iter = Iterator.from('abc'); iter.next().value").unwrap(); + assert_eq!(to_str(&vm, result), "a"); +} + +#[test] +fn iterator_from_iterator_forwards_next() { + let mut vm = Vm::new(); + let result = eval( + &mut vm, + "var inner = { next: function() { return { value: 7, done: false }; } }; \ + Iterator.from(inner).next().value", + ) + .unwrap(); + assert_eq!(result.as_int(), 7); +} + +#[test] +fn new_iterator_throws_type_error() { + let mut vm = Vm::new(); + let result = eval(&mut vm, "try { new Iterator() } catch (e) { e instanceof TypeError }").unwrap(); + assert!(result.as_bool()); +}