use oxide_types::object::JsObject; use oxide_types::value::JsValue; use oxide_runtime_api::{NativeResult, VmHost}; use crate::builtins_debug; use crate::builtins_error; use super::common::{ array_ptr, arraylike_get, arraylike_get_or_err, check_array_create_len, clamp_relative, create_new_array, get_this_array_ref, get_this_arraylike, js_array_index, to_integer_or_infinity_bounded, }; use super::from::{array_species_create, create_data_property_or_throw, from_engine_error}; /// `Array.prototype.push(...items)`:追加元素到尾部,返回新长度。 pub fn array_push(vm: &mut H, args: &[u8]) -> NativeResult { builtins_debug!("Array.prototype.push called with {} args", args.len()); let arr_ptr = array_ptr!(vm, args); for &arg_reg in args.iter().skip(1) { let val = vm.promote_if_needed_for_write_ptr(arr_ptr, vm.reg(arg_reg)); // 元素写入须维护 array_prop_count(set_prop_at 对数组自动更新)。 let arr = unsafe { &mut *arr_ptr }; let idx = arr.prop_count(); arr.set_prop_at(idx, val); } let len = unsafe { &*arr_ptr }.prop_count(); NativeResult::Ok(JsValue::int(len as i32)) } /// `Array.prototype.pop()`:移除并返回末位元素;空数组返回 undefined。 pub fn array_pop(vm: &mut H, args: &[u8]) -> NativeResult { builtins_debug!("Array.prototype.pop called with {} args", args.len()); let arr_ptr = array_ptr!(vm, args); let arr = unsafe { &mut *arr_ptr }; let len = arr.prop_count(); if len == 0 { return NativeResult::Ok(JsValue::undefined()); } let last = arr.get_prop_at(len - 1); arr.set_prop_count_fast(len - 1); NativeResult::Ok(last) } /// `Array.prototype.slice(start, end)`:复制区间元素返回新数组(支持负索引)。 pub fn array_slice(vm: &mut H, args: &[u8]) -> NativeResult { builtins_debug!("Array.prototype.slice called with {} args", args.len()); let this_val = vm.reg(args[0]); let (arr_ptr, n, is_array) = match get_this_arraylike(vm, this_val) { Ok(v) => v, Err(e) => return NativeResult::Err(e), }; let n_f = n as f64; // ToIntegerOrInfinity(start),缺省 0;对象经 ToPrimitive(number)。 let rel_start = if args.len() > 1 { match to_integer_or_infinity_bounded(vm, vm.reg(args[1])) { Ok(v) => v, Err(e) => return NativeResult::Err(e), } } else { 0.0 }; // 规范:end 为 undefined 时使用 len;其余走 ToIntegerOrInfinity。 let rel_end = if args.len() > 2 && !vm.reg(args[2]).is_undefined() { match to_integer_or_infinity_bounded(vm, vm.reg(args[2])) { Ok(v) => v, Err(e) => return NativeResult::Err(e), } } else { n_f }; let start = clamp_relative(rel_start, n_f, n); let end = clamp_relative(rel_end, n_f, n); let count = end.saturating_sub(start); if let Err(err) = check_array_create_len(vm, count) { return NativeResult::Err(err); } // ArraySpeciesCreate(O, count):真数组按 constructor/@@species 构造目标。 let a_ptr = match array_species_create(vm, this_val, is_array, count) { Ok(p) => p, Err(err) => return NativeResult::Err(err), }; // HasProperty + Get + CreateDataPropertyOrThrow 逐元素拷贝。 let mut k = start; let mut out_idx = 0usize; while k < end { let key_str = vm.new_string(&k.to_string()); let key_si = vm.property_key_si(key_str); if vm.resolve_property(unsafe { &*arr_ptr }, key_si).is_some() { let elem = arraylike_get_or_err!(vm, arr_ptr, k); let out_key = vm.new_string(&out_idx.to_string()); let out_key_si = vm.property_key_si(out_key); if let Err(err) = create_data_property_or_throw(vm, unsafe { &mut *a_ptr }, out_key_si, elem) { return NativeResult::Err(err); } out_idx += 1; } k += 1; } // 收尾 Set(A, "length", final):普通 Set 语义(可触发继承 setter/异常)。 let a_val = JsValue::from_js_object(a_ptr); let length_key = vm.new_string("length"); let length_si = vm.property_key_si(length_key); if let Err(err) = vm.ordinary_set(unsafe { &mut *a_ptr }, length_si, js_array_index(out_idx), a_val, true) { return NativeResult::Err(from_engine_error(vm, &err)); } NativeResult::Ok(JsValue::from_js_object(a_ptr)) } /// `Array.prototype.splice(start, deleteCount, ...items)`:删除并/或插入元素, /// 返回被删除元素组成的新数组。 pub fn array_splice(vm: &mut H, args: &[u8]) -> NativeResult { builtins_debug!("Array.prototype.splice called with {} args", args.len()); let arr_ptr = array_ptr!(vm, args); let arr = unsafe { &mut *arr_ptr }; let n = arr.prop_count() as usize; let start = if args.len() > 1 { let v = vm.coerce_number_bounded(vm.reg(args[1])).unwrap_or(f64::NAN); let s = v as i32; if s < 0 { (n as i32 + s).max(0) as usize } else { (s as usize).min(n) } } else { 0 }; let delete_count = if args.len() > 2 { let v = vm.coerce_number_bounded(vm.reg(args[2])).unwrap_or(f64::NAN); (v as usize).min(n - start) } else { n - start }; let insert_count = if args.len() > 3 { args.len() - 3 } else { 0 }; let mut removed: Vec = Vec::new(); for i in 0..delete_count { removed.push(arr.get_prop_at(start + i)); } if insert_count > delete_count { let shift = insert_count - delete_count; for i in (start + delete_count..n).rev() { let val = arr.get_prop_at(i); arr.set_prop_at(i + shift, val); } } else if insert_count < delete_count { let shift = delete_count - insert_count; for i in start + delete_count..n { let val = arr.get_prop_at(i); arr.set_prop_at(i - shift, val); } } for i in 0..insert_count { arr.set_prop_at(start + i, vm.reg(args[3 + i])); } let new_len = n + insert_count - delete_count; arr.set_prop_count_fast(new_len); let removed_arr = create_new_array(vm, removed.len()); unsafe { for (i, val) in removed.iter().enumerate() { (*removed_arr).set_prop_at(i, *val); } (*removed_arr).set_prop_count(removed.len()); } NativeResult::Ok(JsValue::from_js_object(removed_arr)) } /// `Array.prototype.concat(...items)`:连接 this 与参数(数组参数展开)返回新数组。 pub fn array_concat(vm: &mut H, args: &[u8]) -> NativeResult { builtins_debug!("Array.prototype.concat called with {} args", args.len()); let this_val = vm.reg(args[0]); let (arr_ptr, n, _is_array) = match get_this_arraylike(vm, this_val) { Ok(v) => v, Err(e) => return NativeResult::Err(e), }; let mut all: Vec = Vec::new(); if let Err(err) = check_array_create_len(vm, n) { return NativeResult::Err(err); } for i in 0..n { all.push(arraylike_get_or_err!(vm, arr_ptr, i)); } for &arg_reg in args.iter().skip(1) { let val = vm.reg(arg_reg); if val.is_object() { let o_ptr = val.as_js_object_ptr(); if !o_ptr.is_null() { let o = unsafe { &*o_ptr }; let on = o.prop_count() as usize; // 真数组时展开元素。 if o.is_array() && on > 0 { for i in 0..on { all.push(o.get_prop_at(i)); } continue; } } } all.push(val); } let new_arr = create_new_array(vm, all.len()); unsafe { for (i, val) in all.iter().enumerate() { (*new_arr).set_prop_at(i, *val); } (*new_arr).set_prop_count(all.len()); } NativeResult::Ok(JsValue::from_js_object(new_arr)) } /// `Array.prototype.join(separator)`:用分隔符连接元素字符串(null/undefined 视为空串)。 pub fn array_join(vm: &mut H, args: &[u8]) -> NativeResult { builtins_debug!("Array.prototype.join called with {} args", args.len()); let this_val = vm.reg(args[0]); let (arr_ptr, n, _is_array) = match get_this_arraylike(vm, this_val) { Ok(v) => v, Err(e) => return NativeResult::Err(e), }; let sep = if args.len() > 1 { oxide_runtime_api::to_string(vm.reg(args[1])) } else { ",".to_string() }; let parts: Vec = match (0..n) .map(|i| { let v = arraylike_get(vm, arr_ptr, i)?; if v.is_undefined() || v.is_null() { Ok(String::new()) } else { Ok(oxide_runtime_api::to_string(v)) } }) .collect::, JsValue>>() { Ok(v) => v, Err(err) => return NativeResult::Err(err), }; let joined = parts.join(&sep); NativeResult::Ok(vm.new_string(&joined)) } /// `Array.prototype.toString`:委托给 join,默认用 `,` 分隔。 pub fn array_to_string(vm: &mut H, args: &[u8]) -> NativeResult { // Array.prototype.toString() 委托给 join,默认用 "," 分隔, // 按规范忽略自身参数。 array_join(vm, &[args[0]]) } /// `Array.prototype.indexOf(searchElement, fromIndex)`:用严格相等查找首个匹配索引,找不到返回 -1。 pub fn array_index_of(vm: &mut H, args: &[u8]) -> NativeResult { builtins_debug!("Array.prototype.indexOf called with {} args", args.len()); let this_val = vm.reg(args[0]); let (ptr, n, _is_array) = match get_this_arraylike(vm, this_val) { Ok(v) => v, Err(e) => return NativeResult::Err(e), }; if n == 0 || args.len() < 2 { return NativeResult::Ok(JsValue::int(-1)); } let target = vm.reg(args[1]); // fromIndex:负索引从尾部折算,NaN 视为 0。 let from_index = if args.len() >= 3 { let v = vm.reg(args[2]); let f = vm.coerce_number_bounded(v).unwrap_or(0.0); let f = if f.is_nan() { 0.0 } else { f.trunc() }; if f >= 0.0 { (f as usize).min(n) } else { let from = n as f64 + f; if from < 0.0 { 0 } else { from as usize } } } else { 0 }; for i in from_index..n { let elem = arraylike_get_or_err!(vm, ptr, i); if oxide_runtime_api::strict_equality(elem, target) { return NativeResult::Ok(js_array_index(i)); } } NativeResult::Ok(JsValue::int(-1)) } /// `Array.prototype.includes(searchElement, fromIndex)`:用 SameValueZero 判断是否包含 /// (NaN 视为存在、+0/-0 视为相同)。 pub fn array_includes(vm: &mut H, args: &[u8]) -> NativeResult { builtins_debug!("Array.prototype.includes called with {} args", args.len()); let this_val = vm.reg(args[0]); let (ptr, n, _is_array) = match get_this_arraylike(vm, this_val) { Ok(v) => v, Err(e) => return NativeResult::Err(e), }; if n == 0 { return NativeResult::Ok(JsValue::bool(false)); } let target = if args.len() > 1 { vm.reg(args[1]) } else { JsValue::undefined() }; let from_index = if args.len() >= 3 { let v = vm.reg(args[2]); let f = vm.coerce_number_bounded(v).unwrap_or(0.0); let f = if f.is_nan() { 0.0 } else { f.trunc() }; if f >= 0.0 { (f as usize).min(n) } else { let from = n as f64 + f; if from < 0.0 { 0 } else { from as usize } } } else { 0 }; for i in from_index..n { let elem = arraylike_get_or_err!(vm, ptr, i); // SameValueZero:NaN 视为相等、+0 与 -0 视为相同。 if oxide_runtime_api::same_value_zero(elem, target) { return NativeResult::Ok(JsValue::bool(true)); } } NativeResult::Ok(JsValue::bool(false)) } /// `Array.prototype.reverse()`:原地反转元素顺序,返回 this。 pub fn array_reverse(vm: &mut H, args: &[u8]) -> NativeResult { builtins_debug!("Array.prototype.reverse called with {} args", args.len()); let arr_ptr = array_ptr!(vm, args); let arr = unsafe { &mut *arr_ptr }; let n = arr.prop_count() as usize; let mut i = 0; let mut j = n.saturating_sub(1); while i < j { let tmp = arr.get_prop_at(i); arr.set_prop_at(i, arr.get_prop_at(j)); arr.set_prop_at(j, tmp); i += 1; j = j.saturating_sub(1); } NativeResult::Ok(vm.reg(args[0])) } /// `Array.prototype.flat(depth)`:按深度递归展开嵌套数组(含循环引用保护)返回新数组。 pub fn array_flat(vm: &mut H, args: &[u8]) -> NativeResult { builtins_debug!("Array.prototype.flat called with {} args", args.len()); let arr_ptr = array_ptr!(vm, args); let arr = unsafe { &*arr_ptr }; let n = arr.prop_count() as usize; let depth = if args.len() > 1 { let n = vm.coerce_number_bounded(vm.reg(args[1])).unwrap_or(f64::NAN); if !n.is_finite() { vm.kernel_core().config.max_call_depth } else { (n as i32).max(1) as usize } } else { 1 } .min(vm.kernel_core().config.max_call_depth); fn flatten(items: &[JsValue], remaining_depth: usize, seen: &mut Vec<*mut JsObject>) -> Vec { let mut out = Vec::new(); for &v in items { if remaining_depth > 0 && v.is_object() { let ptr = v.as_js_object_ptr(); if !ptr.is_null() { if seen.iter().any(|seen_ptr| std::ptr::eq(*seen_ptr, ptr)) { out.push(v); continue; } let o = unsafe { &*ptr }; if o.is_array() { seen.push(ptr); let on = o.prop_count() as usize; let sub: Vec = (0..on).map(|i| o.get_prop_at(i)).collect(); let flat = flatten(&sub, remaining_depth - 1, seen); seen.pop(); out.extend(flat); continue; } } } out.push(v); } out } let all: Vec = (0..n).map(|i| arr.get_prop_at(i)).collect(); let mut seen = vec![arr_ptr]; let flat = flatten(&all, depth, &mut seen); let new_arr = create_new_array(vm, flat.len()); unsafe { for (i, val) in flat.iter().enumerate() { (*new_arr).set_prop_at(i, *val); } (*new_arr).set_prop_count(flat.len()); } NativeResult::Ok(JsValue::from_js_object(new_arr)) } /// `Array.prototype.shift()`:移除并返回首元素,其余元素前移;空数组返回 undefined。 pub fn array_shift(vm: &mut H, args: &[u8]) -> NativeResult { builtins_debug!("Array.prototype.shift called with {} args", args.len()); let arr_ptr = array_ptr!(vm, args); let arr = unsafe { &mut *arr_ptr }; let len = arr.prop_count(); if len == 0 { return NativeResult::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_fast(len - 1); NativeResult::Ok(first) } /// `Array.prototype.unshift(...items)`:插入元素到头部,返回新长度。 pub fn array_unshift(vm: &mut H, args: &[u8]) -> NativeResult { builtins_debug!("Array.prototype.unshift called with {} args", args.len()); let arr_ptr = array_ptr!(vm, args); 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); arr.set_prop_at(i + n_items, v); } for (j, &arg_reg) in args.iter().skip(1).enumerate() { arr.set_prop_at(j, vm.reg(arg_reg)); } let new_len = len as usize + n_items; arr.set_prop_count_fast(new_len); NativeResult::Ok(JsValue::int(new_len as i32)) } /// `Array.prototype.fill(value, start, end)`:用给定值填充区间,返回 this。 pub fn array_fill(vm: &mut H, args: &[u8]) -> NativeResult { builtins_debug!("Array.prototype.fill called with {} args", args.len()); let arr_ptr = array_ptr!(vm, args); let arr = unsafe { &mut *arr_ptr }; let len = arr.prop_count() as isize; let value = if args.len() > 1 { vm.reg(args[1]) } else { JsValue::undefined() }; let rel_start = if args.len() > 2 { oxide_runtime_api::to_integer_or_infinity(vm.reg(args[2])) as isize } else { 0 }; let rel_end = if args.len() > 3 { oxide_runtime_api::to_integer_or_infinity(vm.reg(args[3])) as isize } else { len }; let start = (if rel_start < 0 { (len + rel_start).max(0) } else { rel_start.min(len) }) as usize; let end = (if rel_end < 0 { (len + rel_end).max(0) } else { rel_end.min(len) }) as usize; for i in start..end { arr.set_prop_at(i, value); } NativeResult::Ok(vm.reg(args[0])) } /// `Array.prototype.copyWithin(target, start, end)`:在数组内部复制元素区间,返回 this。 pub fn array_copy_within(vm: &mut H, args: &[u8]) -> NativeResult { builtins_debug!("Array.prototype.copyWithin called with {} args", args.len()); let arr_ptr = array_ptr!(vm, args); let arr = unsafe { &mut *arr_ptr }; let len = arr.prop_count() as isize; if len == 0 { return NativeResult::Ok(vm.reg(args[0])); } let rel_target = if args.len() > 1 { oxide_runtime_api::to_integer_or_infinity(vm.reg(args[1])) as isize } else { 0 }; let rel_start = if args.len() > 2 { oxide_runtime_api::to_integer_or_infinity(vm.reg(args[2])) as isize } else { 0 }; let rel_end = if args.len() > 3 { oxide_runtime_api::to_integer_or_infinity(vm.reg(args[3])) as isize } else { len }; let target = (if rel_target < 0 { (len + rel_target).max(0) } else { rel_target.min(len) }) as usize; let start = (if rel_start < 0 { (len + rel_start).max(0) } else { rel_start.min(len) }) as usize; let end = (if rel_end < 0 { (len + rel_end).max(0) } else { rel_end.min(len) }) as usize; let len_usize = len as usize; for (to, from) in (target..).zip(start..end) { if to >= len_usize { break; } let v = arr.get_prop_at(from); arr.set_prop_at(to, v); } NativeResult::Ok(vm.reg(args[0])) } /// `Array.prototype.at(index)`:按索引取元素,支持负索引;越界返回 undefined。 pub fn array_at(vm: &mut H, args: &[u8]) -> NativeResult { builtins_debug!("Array.prototype.at called with {} args", args.len()); let arr_ptr = array_ptr!(vm, args); let arr = unsafe { &*arr_ptr }; let len = arr.prop_count() as i32; let mut index = if args.len() > 1 { vm.coerce_number_bounded(vm.reg(args[1])).unwrap_or(f64::NAN) as i32 } else { 0 }; if index < 0 { index += len; } if index < 0 || index >= len { return NativeResult::Ok(JsValue::undefined()); } NativeResult::Ok(arr.get_prop_at(index)) } /// `Array.prototype.lastIndexOf(searchElement, fromIndex)`:从后往前查找首个匹配索引。 pub fn array_last_index_of(vm: &mut H, args: &[u8]) -> NativeResult { builtins_debug!("Array.prototype.lastIndexOf called with {} args", args.len()); let this_val = vm.reg(args[0]); let (ptr, n, _is_array) = match get_this_arraylike(vm, this_val) { Ok(v) => v, Err(e) => return NativeResult::Err(e), }; if n == 0 { return NativeResult::Ok(JsValue::int(-1)); } let search = if args.len() > 1 { vm.reg(args[1]) } else { JsValue::undefined() }; // fromIndex(缺省:n-1)。 let from_index_isize: isize = if args.len() >= 3 { let v = vm.reg(args[2]); let f = vm.coerce_number_bounded(v).unwrap_or(0.0); if f.is_nan() { return NativeResult::Ok(JsValue::int(-1)); } let f = f.trunc(); if f >= 0.0 { (f as isize).min(n as isize - 1) } else { n as isize + f as isize } } else { n as isize - 1 }; if from_index_isize < 0 { return NativeResult::Ok(JsValue::int(-1)); } for i in (0..=from_index_isize as usize).rev() { let elem = arraylike_get_or_err!(vm, ptr, i); if oxide_runtime_api::strict_equality(elem, search) { return NativeResult::Ok(js_array_index(i)); } } NativeResult::Ok(JsValue::int(-1)) }