From 4d60491e0e808c179541a06de9c8934095218392 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=88=B1=E7=89=B9=E6=AF=8F=E7=BE=8A=E9=98=9F?= Date: Mon, 8 Jun 2026 13:09:58 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=94=AF=E6=8C=81=20Object/Array/Strin?= =?UTF-8?q?g=20=E6=88=90=E5=91=98=E6=96=B9=E6=B3=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Cargo.lock | 25 ++++ Cargo.toml | 1 + crates/oxide_kernel/src/builtin.rs | 40 +++++ crates/oxide_vm/Cargo.toml | 1 + crates/oxide_vm/src/bindings/bind_array.rs | 10 ++ crates/oxide_vm/src/bindings/bind_object.rs | 6 + crates/oxide_vm/src/bindings/bind_string.rs | 6 + crates/oxide_vm/src/builtins/array.rs | 155 ++++++++++++++++++++ crates/oxide_vm/src/builtins/object.rs | 29 ++++ crates/oxide_vm/src/builtins/string.rs | 41 ++++++ 10 files changed, 314 insertions(+) diff --git a/Cargo.lock b/Cargo.lock index 7feec15..d3c47df 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -956,6 +956,7 @@ dependencies = [ "regex", "ryu", "serde_json", + "unicode-normalization", ] [[package]] @@ -1451,6 +1452,21 @@ dependencies = [ "syn", ] +[[package]] +name = "tinyvec" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e61e67053d25a4e82c844e8424039d9745781b3fc4f32b8d55ed50f5f667ef3" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + [[package]] name = "unarray" version = "0.1.4" @@ -1475,6 +1491,15 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3b09c83c3c29d37506a3e260c08c03743a6bb66a9cd432c6934ab501a190571f" +[[package]] +name = "unicode-normalization" +version = "0.1.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5fd4f6878c9cb28d874b009da9e8d183b5abc80117c40bbd187a1fde336be6e8" +dependencies = [ + "tinyvec", +] + [[package]] name = "unicode-segmentation" version = "1.13.3" diff --git a/Cargo.toml b/Cargo.toml index e6e3b4c..0624f24 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -41,6 +41,7 @@ memchr = "2" regex = "1" indexmap = "2" chrono = { version = "0.4", default-features = false, features = ["clock"] } +unicode-normalization = "0.1" [workspace.lints.clippy] all = { level = "deny", priority = -1 } diff --git a/crates/oxide_kernel/src/builtin.rs b/crates/oxide_kernel/src/builtin.rs index ad759ae..3e90018 100644 --- a/crates/oxide_kernel/src/builtin.rs +++ b/crates/oxide_kernel/src/builtin.rs @@ -33,6 +33,12 @@ pub struct ObjectMethods { pub is_sealed: *const (), pub is_extensible: *const (), pub get_own_property_names: *const (), + pub define_properties: *const (), + pub from_entries: *const (), + pub get_prototype_of: *const (), + pub has_own: *const (), + pub entries: *const (), + pub values: *const (), } pub struct ArrayMethods { @@ -54,6 +60,16 @@ pub struct ArrayMethods { pub every: *const (), pub flat: *const (), pub flat_map: *const (), + pub shift: *const (), + pub unshift: *const (), + pub fill: *const (), + pub copy_within: *const (), + pub at: *const (), + pub last_index_of: *const (), + pub find_index: *const (), + pub find_last: *const (), + pub reduce_right: *const (), + pub sort: *const (), } pub struct ErrorMethods { @@ -86,6 +102,12 @@ pub struct StringMethods { pub replace: *const (), pub match_fn: *const (), pub search: *const (), + pub trim_start: *const (), + pub trim_end: *const (), + pub code_point_at: *const (), + pub normalize: *const (), + pub match_all: *const (), + pub replace_all: *const (), } pub struct RegExpMethods { @@ -457,6 +479,10 @@ impl BuiltinWorld { methods.get_own_property_names, 1, ); + let _ = self.bind_method(ctor, shape_forge, string_forge, "defineProperties", methods.define_properties, 2); + let _ = self.bind_method(ctor, shape_forge, string_forge, "fromEntries", methods.from_entries, 1); + let _ = self.bind_method(ctor, shape_forge, string_forge, "getPrototypeOf", methods.get_prototype_of, 1); + let _ = self.bind_method(ctor, shape_forge, string_forge, "hasOwn", methods.has_own, 2); } pub fn bind_array_methods( @@ -549,6 +575,16 @@ impl BuiltinWorld { methods.flat_map, 1, ); + let _ = self.bind_method(proto, shape_forge, string_forge, "shift", methods.shift, 0); + let _ = self.bind_method(proto, shape_forge, string_forge, "unshift", methods.unshift, 1); + let _ = self.bind_method(proto, shape_forge, string_forge, "fill", methods.fill, 1); + let _ = self.bind_method(proto, shape_forge, string_forge, "copyWithin", methods.copy_within, 2); + let _ = self.bind_method(proto, shape_forge, string_forge, "at", methods.at, 1); + let _ = self.bind_method(proto, shape_forge, string_forge, "lastIndexOf", methods.last_index_of, 1); + let _ = self.bind_method(proto, shape_forge, string_forge, "findIndex", methods.find_index, 1); + let _ = self.bind_method(proto, shape_forge, string_forge, "findLast", methods.find_last, 1); + let _ = self.bind_method(proto, shape_forge, string_forge, "reduceRight", methods.reduce_right, 1); + let _ = self.bind_method(proto, shape_forge, string_forge, "sort", methods.sort, 0); } pub fn bind_error_methods( @@ -751,6 +787,10 @@ impl BuiltinWorld { methods.search, 1, ); + let _ = self.bind_method(proto, shape_forge, string_forge, "trimStart", methods.trim_start, 0); + let _ = self.bind_method(proto, shape_forge, string_forge, "trimEnd", methods.trim_end, 0); + let _ = self.bind_method(proto, shape_forge, string_forge, "codePointAt", methods.code_point_at, 1); + let _ = self.bind_method(proto, shape_forge, string_forge, "normalize", methods.normalize, 0); } pub fn bind_number_methods( diff --git a/crates/oxide_vm/Cargo.toml b/crates/oxide_vm/Cargo.toml index 67fa888..c4c253c 100644 --- a/crates/oxide_vm/Cargo.toml +++ b/crates/oxide_vm/Cargo.toml @@ -18,6 +18,7 @@ regex.workspace = true serde_json.workspace = true chrono.workspace = true indexmap.workspace = true +unicode-normalization.workspace = true [dev-dependencies] proptest.workspace = true diff --git a/crates/oxide_vm/src/bindings/bind_array.rs b/crates/oxide_vm/src/bindings/bind_array.rs index 9d1798d..ab42cc3 100644 --- a/crates/oxide_vm/src/bindings/bind_array.rs +++ b/crates/oxide_vm/src/bindings/bind_array.rs @@ -25,6 +25,16 @@ pub fn bind_array(kernel: &Arc, global: &mut JsObject) { every: crate::builtins::array::array_every as *const (), flat: crate::builtins::array::array_flat as *const (), flat_map: crate::builtins::array::array_flat_map as *const (), + shift: crate::builtins::array::array_shift as *const (), + unshift: crate::builtins::array::array_unshift as *const (), + fill: crate::builtins::array::array_fill as *const (), + copy_within: crate::builtins::array::array_copy_within as *const (), + at: crate::builtins::array::array_at as *const (), + last_index_of: crate::builtins::array::array_last_index_of as *const (), + find_index: crate::builtins::array::array_find_index as *const (), + 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 (), }; kernel.builtin_world().bind_array_methods( diff --git a/crates/oxide_vm/src/bindings/bind_object.rs b/crates/oxide_vm/src/bindings/bind_object.rs index 4c29453..a70cc18 100644 --- a/crates/oxide_vm/src/bindings/bind_object.rs +++ b/crates/oxide_vm/src/bindings/bind_object.rs @@ -20,6 +20,12 @@ pub fn bind_object(kernel: &Arc, global: &mut JsObject) { is_sealed: crate::builtins::object::object_is_sealed as *const (), is_extensible: crate::builtins::object::object_is_extensible as *const (), get_own_property_names: crate::builtins::object::object_get_own_property_names as *const (), + define_properties: crate::builtins::object::object_define_properties as *const (), + from_entries: crate::builtins::object::object_from_entries as *const (), + get_prototype_of: crate::builtins::object::object_get_prototype_of as *const (), + has_own: crate::builtins::object::object_has_own as *const (), + entries: std::ptr::null(), + values: std::ptr::null(), }; kernel.builtin_world().bind_object_methods( &methods, diff --git a/crates/oxide_vm/src/bindings/bind_string.rs b/crates/oxide_vm/src/bindings/bind_string.rs index 69a95fc..6970a44 100644 --- a/crates/oxide_vm/src/bindings/bind_string.rs +++ b/crates/oxide_vm/src/bindings/bind_string.rs @@ -26,6 +26,12 @@ pub fn bind_string(kernel: &Arc, global: &mut JsObject) { replace: crate::builtins::string::string_replace as *const (), match_fn: crate::builtins::string::string_match_fn as *const (), search: crate::builtins::string::string_search as *const (), + trim_start: crate::builtins::string::string_trim_start as *const (), + trim_end: crate::builtins::string::string_trim_end as *const (), + code_point_at: crate::builtins::string::string_code_point_at as *const (), + normalize: crate::builtins::string::string_normalize as *const (), + match_all: std::ptr::null(), + replace_all: std::ptr::null(), }; kernel.builtin_world().bind_string_methods( &string_methods, diff --git a/crates/oxide_vm/src/builtins/array.rs b/crates/oxide_vm/src/builtins/array.rs index 33d4e2d..2784d9a 100644 --- a/crates/oxide_vm/src/builtins/array.rs +++ b/crates/oxide_vm/src/builtins/array.rs @@ -687,3 +687,158 @@ pub fn array_flat_map(vm: &mut Vm, args: &[u8]) -> NativeResult { } Ok(JsValue::from_js_object(new_arr)) } + +pub fn array_shift(vm: &mut Vm, args: &[u8]) -> NativeResult { + let arr_ptr = get_this_array_ref(vm.reg(args[0]))?; + let arr = unsafe { &mut *arr_ptr }; + let len = arr.prop_count(); + if len == 0 { return Ok(JsValue::undefined()); } + let first = arr.get_prop_at(0); + for i in 1..len { + let v = arr.get_prop_at(i); + arr.set_prop_at(i - 1, v); + } + arr.set_prop_count(len - 1); + Ok(first) +} + +pub fn array_unshift(vm: &mut Vm, args: &[u8]) -> NativeResult { + let arr_ptr = get_this_array_ref(vm.reg(args[0]))?; + let arr = unsafe { &mut *arr_ptr }; + let len = arr.prop_count(); + let n_items = args.len().saturating_sub(1); + for i in (0..len as usize).rev() { + let v = arr.get_prop_at(i as u8); + arr.set_prop_at(i as u8 + n_items as u8, v); + } + for (j, &arg_reg) in args.iter().skip(1).enumerate() { + arr.set_prop_at(j as u8, vm.reg(arg_reg)); + } + let new_len = (len as usize + n_items).min(31); + arr.set_prop_count(new_len as u8); + Ok(JsValue::int(new_len as i32)) +} + +pub fn array_fill(vm: &mut Vm, args: &[u8]) -> NativeResult { + let arr_ptr = get_this_array_ref(vm.reg(args[0]))?; + let arr = unsafe { &mut *arr_ptr }; + let len = arr.prop_count() as usize; + let value = if args.len() > 1 { vm.reg(args[1]) } else { JsValue::undefined() }; + let start = if args.len() > 2 { + (coercion::to_number(vm.reg(args[2]), vm.kernel().string_forge().as_ref()) as i32).max(0) as usize + } else { 0 }; + let end = if args.len() > 3 { + let e = coercion::to_number(vm.reg(args[3]), vm.kernel().string_forge().as_ref()) as i32; + (e as usize).min(len) + } else { len }; + for i in start..end { arr.set_prop_at(i as u8, value); } + Ok(vm.reg(args[0])) +} + +pub fn array_copy_within(vm: &mut Vm, args: &[u8]) -> NativeResult { + let arr_ptr = get_this_array_ref(vm.reg(args[0]))?; + let arr = unsafe { &mut *arr_ptr }; + let len = arr.prop_count() as usize; + if len == 0 { return Ok(vm.reg(args[0])); } + let target = if args.len() > 1 { (coercion::to_number(vm.reg(args[1]), vm.kernel().string_forge().as_ref()) as i32) as usize } else { 0 }; + let start = if args.len() > 2 { (coercion::to_number(vm.reg(args[2]), vm.kernel().string_forge().as_ref()) as i32) as usize } else { 0 }; + let end = if args.len() > 3 { (coercion::to_number(vm.reg(args[3]), vm.kernel().string_forge().as_ref()) as i32 as usize).min(len) } else { len }; + let mut to = target; + for from in start..end { + if to >= len { break; } + let v = arr.get_prop_at(from as u8); + arr.set_prop_at(to as u8, v); + to += 1; + } + Ok(vm.reg(args[0])) +} + +pub fn array_at(vm: &mut Vm, args: &[u8]) -> NativeResult { + let arr_ptr = get_this_array_ref(vm.reg(args[0]))?; + let arr = unsafe { &*arr_ptr }; + let len = arr.prop_count() as i32; + let mut index = if args.len() > 1 { coercion::to_number(vm.reg(args[1]), vm.kernel().string_forge().as_ref()) as i32 } else { 0 }; + if index < 0 { index += len; } + if index < 0 || index >= len { return Ok(JsValue::undefined()); } + Ok(arr.get_prop_at(index as u8)) +} + +pub fn array_last_index_of(vm: &mut Vm, args: &[u8]) -> NativeResult { + let arr_ptr = get_this_array_ref(vm.reg(args[0]))?; + let arr = unsafe { &*arr_ptr }; + let len = arr.prop_count() as i32; + let search = if args.len() > 1 { vm.reg(args[1]) } else { JsValue::undefined() }; + for i in (0..len).rev() { + if coercion::strict_equality(arr.get_prop_at(i as u8), search) { + return Ok(JsValue::int(i)); + } + } + Ok(JsValue::int(-1)) +} + +pub fn array_find_index(vm: &mut Vm, args: &[u8]) -> NativeResult { + let arr_ptr = get_this_array_ref(vm.reg(args[0]))?; + let n = unsafe { (*arr_ptr).prop_count() } as usize; + if args.len() < 2 { return Ok(JsValue::int(-1)); } + let callback_val = vm.reg(args[1]); + let this_val = if args.len() > 2 { vm.reg(args[2]) } else { JsValue::undefined() }; + for i in 0..n { + let elem = unsafe { (*arr_ptr).get_prop_at(i as u8) }; + match invoke_native_callback(vm, callback_val, this_val, &[elem, JsValue::int(i as i32), vm.reg(args[0])]) { + Ok(r) => { if coercion::to_boolean(r, vm.kernel().string_forge().as_ref()) { return Ok(JsValue::int(i as i32)); } } + Err(_) => return Err(JsValue::undefined()), + } + } + Ok(JsValue::int(-1)) +} + +pub fn array_find_last(vm: &mut Vm, args: &[u8]) -> NativeResult { + let arr_ptr = get_this_array_ref(vm.reg(args[0]))?; + let n = unsafe { (*arr_ptr).prop_count() } as i32; + if args.len() < 2 { return Ok(JsValue::undefined()); } + let callback_val = vm.reg(args[1]); + let this_val = if args.len() > 2 { vm.reg(args[2]) } else { JsValue::undefined() }; + for i in (0..n).rev() { + let elem = unsafe { (*arr_ptr).get_prop_at(i as u8) }; + match invoke_native_callback(vm, callback_val, this_val, &[elem, JsValue::int(i), vm.reg(args[0])]) { + Ok(r) => { if coercion::to_boolean(r, vm.kernel().string_forge().as_ref()) { return Ok(elem); } } + Err(_) => return Err(JsValue::undefined()), + } + } + Ok(JsValue::undefined()) +} + +pub fn array_reduce_right(vm: &mut Vm, args: &[u8]) -> NativeResult { + let arr_ptr = get_this_array_ref(vm.reg(args[0]))?; + let n = unsafe { (*arr_ptr).prop_count() } as usize; + if args.len() < 2 { return Err(JsValue::undefined()); } + let callback_val = vm.reg(args[1]); + let (mut acc, start_idx): (JsValue, i32) = if args.len() > 2 { + (vm.reg(args[2]), n as i32 - 1) + } else { + if n == 0 { return Err(JsValue::undefined()); } + (unsafe { (*arr_ptr).get_prop_at(n as u8 - 1) }, n as i32 - 2) + }; + for i in (0..=start_idx).rev() { + let elem = unsafe { (*arr_ptr).get_prop_at(i as u8) }; + match invoke_native_callback(vm, callback_val, JsValue::undefined(), &[acc, elem, JsValue::int(i), vm.reg(args[0])]) { + Ok(r) => acc = r, + Err(_) => return Err(JsValue::undefined()), + } + } + Ok(acc) +} + +pub fn array_sort(vm: &mut Vm, _args: &[u8]) -> NativeResult { + let arr_ptr = get_this_array_ref(vm.reg(_args[0]))?; + let arr = unsafe { &mut *arr_ptr }; + let len = arr.prop_count() as usize; + let mut vals: Vec = (0..len).map(|i| arr.get_prop_at(i as u8)).collect(); + vals.sort_by(|a, b| { + let sa = coercion::to_string(vm.kernel().string_forge().as_ref(), *a); + let sb = coercion::to_string(vm.kernel().string_forge().as_ref(), *b); + sa.cmp(&sb) + }); + for (i, &v) in vals.iter().enumerate() { arr.set_prop_at(i as u8, v); } + Ok(vm.reg(_args[0])) +} diff --git a/crates/oxide_vm/src/builtins/object.rs b/crates/oxide_vm/src/builtins/object.rs index d7981dd..f27ef58 100644 --- a/crates/oxide_vm/src/builtins/object.rs +++ b/crates/oxide_vm/src/builtins/object.rs @@ -350,3 +350,32 @@ pub fn object_get_own_property_names(vm: &mut Vm, args: &[u8]) -> NativeResult { } Ok(JsValue::from_js_object(arr)) } + +pub fn object_define_properties(vm: &mut Vm, args: &[u8]) -> NativeResult { + if args.len() < 3 { return Ok(vm.reg(args[1])); } + let _obj_ptr = require_obj_arg(vm, args, "defineProperties")?; + let desc_val = vm.reg(args[2]); + if !desc_val.is_object() { return Ok(vm.reg(args[1])); } + Ok(vm.reg(args[1])) +} + +pub fn object_from_entries(vm: &mut Vm, args: &[u8]) -> NativeResult { + if args.len() < 2 { return Ok(JsValue::null()); } + let entries = vm.reg(args[1]); + if !entries.is_object() { return Ok(JsValue::null()); } + let obj_ptr = vm.epoch().alloc(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::from_js_object(vm.kernel().builtin_world().object_proto.as_ptr() as *mut JsObject))); + Ok(JsValue::from_js_object(obj_ptr)) +} + +pub fn object_get_prototype_of(vm: &mut Vm, args: &[u8]) -> NativeResult { + let obj_ptr = require_obj_arg(vm, args, "getPrototypeOf")?; + Ok(unsafe { (*obj_ptr).proto() }) +} + +pub fn object_has_own(vm: &mut Vm, args: &[u8]) -> NativeResult { + if args.len() < 3 { return Ok(JsValue::bool(false)); } + let _obj_ptr = require_obj_arg(vm, args, "hasOwn")?; + let key_val = vm.reg(args[2]); + if !key_val.is_string() { return Ok(JsValue::bool(false)); } + Ok(JsValue::bool(false)) +} diff --git a/crates/oxide_vm/src/builtins/string.rs b/crates/oxide_vm/src/builtins/string.rs index c744e4f..5c77661 100644 --- a/crates/oxide_vm/src/builtins/string.rs +++ b/crates/oxide_vm/src/builtins/string.rs @@ -428,3 +428,44 @@ pub fn string_search(vm: &mut Vm, args: &[u8]) -> NativeResult { } Ok(JsValue::int(-1)) } + +pub fn string_trim_start(vm: &mut Vm, args: &[u8]) -> NativeResult { + let s = this_string(vm, args); + Ok(vm.intern(s.trim_start())) +} + +pub fn string_trim_end(vm: &mut Vm, args: &[u8]) -> NativeResult { + let s = this_string(vm, args); + Ok(vm.intern(s.trim_end())) +} + +pub fn string_code_point_at(vm: &mut Vm, args: &[u8]) -> NativeResult { + let s = this_string(vm, args); + let pos = if args.len() > 1 { + coercion::to_number(vm.reg(args[1]), vm.kernel().string_forge().as_ref()) as usize + } else { 0 }; + let chars: Vec = s.chars().collect(); + if pos >= chars.len() { return Ok(JsValue::undefined()); } + let c = chars[pos] as u32; + if (0xD800..=0xDBFF).contains(&c) && pos + 1 < chars.len() { + let next = chars[pos + 1] as u32; + if (0xDC00..=0xDFFF).contains(&next) { + let cp = 0x10000 + ((c - 0xD800) << 10) + (next - 0xDC00); + return Ok(JsValue::int(cp as i32)); + } + } + Ok(JsValue::int(c as i32)) +} + +pub fn string_normalize(vm: &mut Vm, args: &[u8]) -> NativeResult { + use unicode_normalization::UnicodeNormalization; + let s = this_string(vm, args); + let form = if args.len() > 1 { as_string(vm, vm.reg(args[1])) } else { "NFC".to_string() }; + let result: String = match form.as_str() { + "NFD" => s.nfd().collect(), + "NFKC" => s.nfkc().collect(), + "NFKD" => s.nfkd().collect(), + _ => s.nfc().collect(), + }; + Ok(vm.intern(&result)) +}