feat: 支持 Object/Array/String 成员方法

This commit is contained in:
2026-06-08 13:09:58 +08:00
parent b8fbc93bca
commit 107cc1bc37
10 changed files with 314 additions and 0 deletions
Generated
+25
View File
@@ -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"
+1
View File
@@ -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 }
+40
View File
@@ -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(
+1
View File
@@ -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
@@ -25,6 +25,16 @@ pub fn bind_array(kernel: &Arc<OxideKernel>, 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(
@@ -20,6 +20,12 @@ pub fn bind_object(kernel: &Arc<OxideKernel>, 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,
@@ -26,6 +26,12 @@ pub fn bind_string(kernel: &Arc<OxideKernel>, 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,
+155
View File
@@ -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<JsValue> = (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]))
}
+29
View File
@@ -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))
}
+41
View File
@@ -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<char> = 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))
}