mirror of
https://github.com/pjh456/OxideJS.git
synced 2026-10-24 10:41:11 +08:00
fix: split 委托 Symbol.split 并补零宽与 limit 口径
This commit is contained in:
@@ -350,6 +350,26 @@ pub(crate) fn as_units<'a, H: VmHost>(vm: &'a mut H, val: JsValue) -> Result<Cow
|
||||
}
|
||||
}
|
||||
|
||||
// ── split limit 换算 ────────────────────────────────────────────────────
|
||||
|
||||
/// split 的 limit 参数 → ToUint32 口径的 usize:undefined 为 2^32-1(全量);
|
||||
/// 其余值经完整 ToNumber(对象走 ToPrimitive number hint,Symbol 抛 TypeError),
|
||||
/// NaN/±0/±∞ 归 0,有限值按 mod 2^32 回绕(负数回绕,-1 为 2^32-1)。
|
||||
/// 对象转换抛出的原生异常原样传播。
|
||||
pub(crate) fn split_limit_to_uint32<H: VmHost>(vm: &mut H, limit_val: JsValue) -> Result<usize, JsValue> {
|
||||
if limit_val.is_undefined() {
|
||||
return Ok(u32::MAX as usize);
|
||||
}
|
||||
let n = match oxide_runtime_api::to_number_full(limit_val, vm) {
|
||||
Ok(n) => n,
|
||||
Err(e) => return Err(crate::iterator::engine_error(vm, &e)),
|
||||
};
|
||||
if n.is_nan() || n == 0.0 || n.is_infinite() {
|
||||
return Ok(0);
|
||||
}
|
||||
Ok(n.trunc().rem_euclid(4_294_967_296.0) as usize)
|
||||
}
|
||||
|
||||
// ── 数组产出 ─────────────────────────────────────────────────────────────
|
||||
|
||||
/// 以已构造的字符串值构建字符串数组(元素零拷贝落地),供逐单元产出路径
|
||||
|
||||
@@ -11,7 +11,7 @@ use crate::regexp::build_groups_object;
|
||||
use super::common::{try_string, MatchText};
|
||||
use super::{
|
||||
as_units, byte_to_unit, find_units, make_string_array_values, make_units_array, map_well_formed_segments,
|
||||
this_text, this_units, unit_to_byte,
|
||||
split_limit_to_uint32, this_text, this_units, unit_to_byte,
|
||||
};
|
||||
|
||||
// ── 正则替换(单元口径) ────────────────────────────────────────────────
|
||||
@@ -162,90 +162,109 @@ fn string_arm_replace<H: VmHost>(
|
||||
|
||||
// ── 拆分 / 正则匹配 ─────────────────────────────────────────────────────
|
||||
|
||||
/// GetMethod(sep, @@split):undefined/null 返回 None(落字符串臂),不可调用
|
||||
/// 抛 TypeError,属性读抛错恢复原异常值。
|
||||
fn rx_get_split_method<H: VmHost>(vm: &mut H, sep: JsValue) -> Result<Option<JsValue>, JsValue> {
|
||||
let split_key = oxide_types::private_key::make_well_known_symbol_key(4);
|
||||
let ptr = sep.as_js_object_ptr();
|
||||
// SAFETY: 调用方已校验 sep 为非空对象值。
|
||||
let func = match vm.ordinary_get(unsafe { &*ptr }, split_key, sep) {
|
||||
Ok(v) => v,
|
||||
Err(_) => {
|
||||
if let Some(exc) = vm.take_uncaught_value() {
|
||||
return Err(exc);
|
||||
}
|
||||
return Err(crate::error::create_type_error(vm, "cannot read @@split"));
|
||||
}
|
||||
};
|
||||
if func.is_undefined() || func.is_null() {
|
||||
return Ok(None);
|
||||
}
|
||||
if !crate::iterator::is_callable(func) {
|
||||
return Err(crate::error::create_type_error(vm, "Symbol.split is not callable"));
|
||||
}
|
||||
Ok(Some(func))
|
||||
}
|
||||
|
||||
/// `String.prototype.split(separator, limit)`:按分隔符拆分为字符串数组;
|
||||
/// 分隔符可为 RegExp(含捕获组)或字符串。空分隔按单元逐个产出(规格口径,
|
||||
/// 孤立 surrogate 为 1 单元元素)。
|
||||
///
|
||||
/// # 步骤
|
||||
/// 1. receiver 前置校验(RequireObjectCoercible,纯 is_* 读取,先于一切分叉)。
|
||||
/// 2. separator 为对象时:GetMethod(separator, @@split),可调用则
|
||||
/// Call(splitter, separator, «thisValue, limit»)——传原始 this/limit
|
||||
/// 寄存器不预转换,结果原值返回(RegExp 分隔经此委托 Symbol.split)。
|
||||
/// 3. string = ToString(this)(转换异常传播)。
|
||||
/// 4. lim:limit 缺省或 undefined 为 2^32-1,否则 ToUint32(mod 2^32 回绕)。
|
||||
/// 5. separatorString = ToString(separator)(转换异常传播;先于 lim = 0 判定)。
|
||||
/// 6. lim = 0 → 空数组(先于 sep-undefined 判定)。
|
||||
/// 7. separator 为 undefined → [string]。
|
||||
/// 8. 空分隔逐码元产出(clamp(lim, 0, 串长));非空分隔搜索循环,尾段恒推。
|
||||
///
|
||||
/// # 边界与前提
|
||||
/// - 规范序位:GetMethod 派发先于 ToString(this),ToUint32(limit) 先于
|
||||
/// ToString(separator),ToString(separator) 先于 lim = 0 判定,
|
||||
/// lim = 0 判定先于 sep-undefined 判定。
|
||||
/// - 各转换(this/limit/separator)抛出的原生异常原样传播。
|
||||
pub fn string_split<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||
builtins_debug!("String.prototype.split called with {} args", args.len());
|
||||
// 参数转换先行:separator 为对象时 ToString 可能触发用户代码(&mut 路径),
|
||||
// limit 仅纯函数计算;之后借 this 源串扫描切分。
|
||||
let sep_val = if args.len() >= 2 { Some(vm.reg(args[1])) } else { None };
|
||||
let is_undefined_sep = sep_val.map(|v| v.is_undefined()).unwrap_or(true);
|
||||
let is_re = match sep_val {
|
||||
Some(v) if !v.is_undefined() => is_regexp_obj(v, vm),
|
||||
_ => false,
|
||||
};
|
||||
// 类正则对象是否持有编译正则:native_fn 存在才是真 RegExp 实例,命中正则切分路径;
|
||||
// 无编译正则的类正则对象(如 Object.create(RegExp.prototype))回退字符串路径,
|
||||
// 须按 ToString 文本切分,且转换(&mut 路径)须先于 this 借用完成。
|
||||
let has_native_re = is_re && {
|
||||
let sep = sep_val.expect("separator present when is_re is true");
|
||||
let re_ptr = sep.as_js_object_ptr();
|
||||
// SAFETY: is_re 已保证 sep_val 为非空对象且 proto 恒等 RegExp.prototype。
|
||||
let re = unsafe { &*re_ptr };
|
||||
re.native_fn().is_some()
|
||||
};
|
||||
// ToUint32(limit),缺省为 2^32-1。
|
||||
let limit = if args.len() > 2 {
|
||||
let l = oxide_runtime_api::to_integer_or_infinity(vm.reg(args[2]));
|
||||
if l.is_infinite() {
|
||||
u32::MAX as usize
|
||||
} else {
|
||||
(l.max(0.0).trunc() as u64).min(u32::MAX as u64) as usize
|
||||
let this_val = vm.reg(args[0]);
|
||||
|
||||
// receiver 前置校验:null/undefined 抛 TypeError(RequireObjectCoercible,
|
||||
// 纯 is_* 读取无用户代码,先于一切分叉);symbol 的 ToString 必败,提前抛。
|
||||
if this_val.is_null() || this_val.is_undefined() {
|
||||
return NativeResult::Err(crate::error::create_type_error(
|
||||
vm,
|
||||
"String.prototype method called on null or undefined",
|
||||
));
|
||||
}
|
||||
if this_val.is_symbol() {
|
||||
return NativeResult::Err(crate::error::create_type_error(vm, "Cannot convert a Symbol value to a string"));
|
||||
}
|
||||
|
||||
let sep_val = if args.len() >= 2 { vm.reg(args[1]) } else { JsValue::undefined() };
|
||||
let limit_val = if args.len() > 2 { vm.reg(args[2]) } else { JsValue::undefined() };
|
||||
|
||||
// 对象分隔符经 GetMethod 派发:调用定义则 Call 后原值返回,须先于
|
||||
// ToString(this) 短路(实参为原始寄存器,不预转换)。
|
||||
if sep_val.is_object() {
|
||||
let splitter = try_string!(rx_get_split_method(vm, sep_val));
|
||||
if let Some(splitter) = splitter {
|
||||
return match vm.call_function_sync(splitter, sep_val, &[this_val, limit_val]) {
|
||||
Ok(r) => NativeResult::Ok(r),
|
||||
Err(_) => {
|
||||
if let Some(exc) = vm.take_uncaught_value() {
|
||||
return NativeResult::Err(exc);
|
||||
}
|
||||
NativeResult::Err(crate::error::create_type_error(vm, "split matcher call failed"))
|
||||
}
|
||||
};
|
||||
}
|
||||
} else {
|
||||
u32::MAX as usize
|
||||
};
|
||||
let sep_units: Vec<u16> = if is_undefined_sep || has_native_re {
|
||||
Vec::new()
|
||||
} else {
|
||||
let sep = sep_val.expect("separator present when not undefined and not native regexp");
|
||||
try_string!(as_units(vm, sep)).into_owned()
|
||||
};
|
||||
}
|
||||
|
||||
// string = ToString(this)。
|
||||
let s: Vec<u16> = try_string!(this_units(vm, args)).into_owned();
|
||||
// 规范:separator 为 undefined 时返回 [this]。
|
||||
if is_undefined_sep {
|
||||
|
||||
// lim = ToUint32(limit):undefined 为 2^32-1,对象经完整 ToNumber(异常传播)。
|
||||
let lim = try_string!(split_limit_to_uint32(vm, limit_val));
|
||||
|
||||
// separatorString = ToString(separator)(先于 lim = 0 判定:sep 转换
|
||||
// 抛错时 lim = 0 的早退不生效)。
|
||||
let sep_units: Vec<u16> = try_string!(as_units(vm, sep_val)).into_owned();
|
||||
|
||||
if lim == 0 {
|
||||
return NativeResult::Ok(make_units_array(vm, Vec::new()));
|
||||
}
|
||||
// separator 为 undefined 时返回单元素 [string]。
|
||||
if sep_val.is_undefined() {
|
||||
return NativeResult::Ok(make_units_array(vm, vec![s.to_vec()]));
|
||||
}
|
||||
if is_re {
|
||||
let re_ptr = sep_val.unwrap().as_js_object_ptr();
|
||||
let re = unsafe { &*re_ptr };
|
||||
if let Some(fn_ptr) = re.native_fn() {
|
||||
let regex = unsafe { &*(fn_ptr.as_ptr() as *const regress::Regex) };
|
||||
// 片段物化字符串值,未匹配捕获组推 undefined(结果数组按值混排)。
|
||||
let mut parts: Vec<JsValue> = Vec::new();
|
||||
let mut last_end = 0;
|
||||
for m in regex.find_from_utf16(&s, 0) {
|
||||
if parts.len() >= limit {
|
||||
break;
|
||||
}
|
||||
let range = m.range();
|
||||
parts.push(vm.new_string_units_owned(s[last_end..range.start].to_vec()));
|
||||
if parts.len() >= limit {
|
||||
break;
|
||||
}
|
||||
for i in 1..=m.captures.len() {
|
||||
if parts.len() >= limit {
|
||||
break;
|
||||
}
|
||||
match m.group(i) {
|
||||
Some(g) => parts.push(vm.new_string_units_owned(s[g.start..g.end].to_vec())),
|
||||
None => parts.push(JsValue::undefined()),
|
||||
}
|
||||
}
|
||||
last_end = range.end;
|
||||
}
|
||||
if last_end <= s.len() && parts.len() < limit {
|
||||
parts.push(vm.new_string_units(&s[last_end..]));
|
||||
}
|
||||
return NativeResult::Ok(make_string_array_values(vm, parts));
|
||||
}
|
||||
// 无原生正则的类正则对象回退到字符串路径。
|
||||
}
|
||||
|
||||
if sep_units.is_empty() {
|
||||
// 每单元一个元素(ASCII 走单字符缓存零分配,其余 1 单元串创建)。
|
||||
let mut values = Vec::with_capacity(s.len().min(limit));
|
||||
for &u in s.iter().take(limit) {
|
||||
let mut values = Vec::with_capacity(s.len().min(lim));
|
||||
for &u in s.iter().take(lim) {
|
||||
values.push(match vm.single_unit(u) {
|
||||
Some(v) => v,
|
||||
None => vm.new_string_units(&[u]),
|
||||
@@ -253,11 +272,12 @@ pub fn string_split<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||
}
|
||||
return NativeResult::Ok(make_string_array_values(vm, values));
|
||||
}
|
||||
|
||||
// 非空字符串分隔:手动查找循环 + limit。
|
||||
let mut parts: Vec<Vec<u16>> = Vec::new();
|
||||
let mut start = 0;
|
||||
loop {
|
||||
if parts.len() >= limit {
|
||||
if parts.len() >= lim {
|
||||
break;
|
||||
}
|
||||
match find_units(&s, &sep_units, start) {
|
||||
|
||||
Reference in New Issue
Block a user