mirror of
https://github.com/pjh456/OxideJS.git
synced 2026-10-23 18:21:13 +08:00
feat: 实现 Iterator.from 全局绑定
This commit is contained in:
@@ -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)));
|
||||
}
|
||||
@@ -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<KernelCore>, 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);
|
||||
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
Reference in New Issue
Block a user