feat: 实现 Iterator.from 全局绑定

This commit is contained in:
2026-06-14 17:58:16 +08:00
parent cf55db1b26
commit c2da5fb2f3
5 changed files with 268 additions and 0 deletions
@@ -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<KernelCore>, 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)));
}
+2
View File
@@ -4,6 +4,7 @@ pub mod bind_date;
pub mod bind_error; pub mod bind_error;
pub mod bind_function; pub mod bind_function;
pub mod bind_global; pub mod bind_global;
pub mod bind_iterator;
pub mod bind_json; pub mod bind_json;
pub mod bind_map; pub mod bind_map;
pub mod bind_math; pub mod bind_math;
@@ -97,6 +98,7 @@ pub fn init_kernel_builtins(core: &Arc<KernelCore>, session: &mut KernelSession)
bind_function::bind_function(core, session, global); bind_function::bind_function(core, session, global);
bind_regexp::bind_regexp(core, session, global); bind_regexp::bind_regexp(core, session, global);
bind_symbol::bind_symbol(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_global::bind_global(core, session, global);
bind_stubs::bind_stubs(core, session, global); bind_stubs::bind_stubs(core, session, global);
+178
View File
@@ -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<JsValue, NativeResult> {
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<JsValue> {
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()
}
+1
View File
@@ -4,6 +4,7 @@ pub mod date;
pub mod error; pub mod error;
pub mod function; pub mod function;
pub mod global; pub mod global;
pub mod iterator;
pub mod json; pub mod json;
pub mod map; pub mod map;
pub mod math; pub mod math;
@@ -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<JsValue, String> {
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());
}