mirror of
https://github.com/pjh456/OxideJS.git
synced 2026-10-23 18:21:13 +08:00
1740 lines
75 KiB
Rust
1740 lines
75 KiB
Rust
use std::borrow::Cow;
|
||
|
||
use oxide_kernel::shape_forge::EMPTY_SHAPE_ID;
|
||
use oxide_types::object::JsObject;
|
||
use oxide_types::value::JsValue;
|
||
|
||
use oxide_runtime_api::{NativeResult, VmHost};
|
||
|
||
use crate::builtins_debug;
|
||
use crate::builtins_error;
|
||
|
||
mod common;
|
||
use common::try_string;
|
||
pub(crate) use common::*;
|
||
|
||
// ── 正则替换(单元口径) ────────────────────────────────────────────────
|
||
|
||
/// 展开单个匹配的 replacement `$` 引用(`$$`、`$&`、`` $` ``、`$'`、`$n`),
|
||
/// 输入/输出均为单元序列。
|
||
fn expand_dollar_units(text: &[u16], m: ®ress::Match, replacement: &[u16]) -> Vec<u16> {
|
||
let mut out = Vec::new();
|
||
let mut i = 0;
|
||
while i < replacement.len() {
|
||
if replacement[i] != 0x24 {
|
||
out.push(replacement[i]);
|
||
i += 1;
|
||
continue;
|
||
}
|
||
let range = m.range();
|
||
let rest = &replacement[i..];
|
||
if rest.starts_with(&[0x24, 0x24]) {
|
||
out.push(0x24);
|
||
i += 2;
|
||
} else if rest.starts_with(&[0x24, 0x26]) {
|
||
out.extend_from_slice(&text[range.start..range.end]);
|
||
i += 2;
|
||
} else if rest.starts_with(&[0x24, 0x60]) {
|
||
out.extend_from_slice(&text[..range.start]);
|
||
i += 2;
|
||
} else if rest.starts_with(&[0x24, 0x27]) {
|
||
out.extend_from_slice(&text[range.end..]);
|
||
i += 2;
|
||
} else if i + 1 < replacement.len() && (0x30..=0x39).contains(&replacement[i + 1]) {
|
||
// $digits:digitCount 取 2(后续尚有数字)否则 1;两位数值越界回退
|
||
// 一位(次位留给后续按字面处理);仍越界(含 $0)→ 整段 ref 字面。
|
||
let digit_count = if i + 2 < replacement.len() && (0x30..=0x39).contains(&replacement[i + 2]) {
|
||
2
|
||
} else {
|
||
1
|
||
};
|
||
let d1 = (replacement[i + 1] - 0x30) as u32;
|
||
let mut index = if digit_count == 2 { d1 * 10 + (replacement[i + 2] - 0x30) as u32 } else { d1 };
|
||
let mut ref_len = 1 + digit_count;
|
||
let capture_len = (m.groups().count() - 1) as u32;
|
||
if index > capture_len && digit_count == 2 {
|
||
index = d1;
|
||
ref_len = 2;
|
||
}
|
||
if (1..=capture_len).contains(&index) {
|
||
if let Some(g) = m.group(index as usize) {
|
||
out.extend_from_slice(&text[g.start..g.end]);
|
||
}
|
||
} else {
|
||
out.extend_from_slice(&replacement[i..i + ref_len]);
|
||
}
|
||
i += ref_len;
|
||
} else {
|
||
out.push(0x24);
|
||
i += 1;
|
||
}
|
||
}
|
||
out
|
||
}
|
||
|
||
/// 正则替换的手动实现(单元口径):按 JS 语义展开 replacement 中的 `$` 引用,
|
||
/// global 全替换否则首个。
|
||
pub(crate) fn regex_replace_manual_units(
|
||
regex: ®ress::Regex, text: &[u16], replacement: &[u16], global: bool,
|
||
) -> Vec<u16> {
|
||
let mut out = Vec::new();
|
||
let mut last_end = 0;
|
||
let matches: Vec<regress::Match> = if global {
|
||
regex.find_from_utf16(text, 0).collect()
|
||
} else {
|
||
regex.find_from_utf16(text, 0).take(1).collect()
|
||
};
|
||
for m in &matches {
|
||
let range = m.range();
|
||
out.extend_from_slice(&text[last_end..range.start]);
|
||
out.extend_from_slice(&expand_dollar_units(text, m, replacement));
|
||
last_end = range.end;
|
||
}
|
||
out.extend_from_slice(&text[last_end..]);
|
||
out
|
||
}
|
||
|
||
/// 调用函数 replacer 并把返回值转为单元序列;调用抛出的异常原样恢复。
|
||
fn call_replacer<H: VmHost>(vm: &mut H, replacer: JsValue, cb_args: &[JsValue]) -> Result<Vec<u16>, JsValue> {
|
||
match vm.call_function_sync(replacer, JsValue::undefined(), cb_args) {
|
||
Ok(result) => match oxide_runtime_api::to_units_full(result, vm) {
|
||
Ok(units) => Ok(units),
|
||
Err(_) => {
|
||
// 转换失败(Symbol 等)或回调异常均须原样传播。
|
||
if let Some(exc) = vm.take_uncaught_value() {
|
||
return Err(exc);
|
||
}
|
||
Err(crate::error::create_type_error(vm, "Cannot convert a Symbol value to a string"))
|
||
}
|
||
},
|
||
Err(err) => Err(vm
|
||
.take_uncaught_value()
|
||
.unwrap_or_else(|| crate::error::create_type_error(vm, &format!("replace replacer: {}", err)))),
|
||
}
|
||
}
|
||
|
||
/// 函数 replacer 的回调参数:匹配串、各捕获组(未匹配为 undefined)、position
|
||
/// (码元口径)、原字符串。原字符串参数由调用方预构传入(`text_arg`),
|
||
/// 避免每匹配复制整个源串——字符串不可变,同一 `JsValue` 可安全复用。
|
||
fn replacer_cb_args<H: VmHost>(vm: &mut H, text: &[u16], m: ®ress::Match, text_arg: JsValue) -> Vec<JsValue> {
|
||
let range = m.range();
|
||
let mut cb_args: Vec<JsValue> = Vec::with_capacity(m.captures.len() + 2);
|
||
cb_args.push(vm.new_string_units(&text[range.start..range.end]));
|
||
for i in 1..=m.captures.len() {
|
||
match m.group(i) {
|
||
Some(g) => cb_args.push(vm.new_string_units(&text[g.start..g.end])),
|
||
None => cb_args.push(JsValue::undefined()),
|
||
}
|
||
}
|
||
cb_args.push(JsValue::int(range.start as i32));
|
||
cb_args.push(text_arg);
|
||
cb_args
|
||
}
|
||
|
||
/// 正则模式 + 函数 replacer:global 全替换否则替换首个,逐匹配调用回调,
|
||
/// 返回值转单元序列作为替换文本(不展开 `$` 引用)。`text_arg` 为回调第 4 参
|
||
/// (原字符串),调用方预构一次,回调期按值复用。
|
||
pub(crate) fn regex_replace_fn<H: VmHost>(
|
||
vm: &mut H, regex: ®ress::Regex, text: &[u16], replacer: JsValue, global: bool, text_arg: JsValue,
|
||
) -> NativeResult {
|
||
let matches: Vec<regress::Match> = if global {
|
||
regex.find_from_utf16(text, 0).collect()
|
||
} else {
|
||
regex.find_from_utf16(text, 0).take(1).collect()
|
||
};
|
||
let mut out = Vec::new();
|
||
let mut last_end = 0;
|
||
for m in &matches {
|
||
let range = m.range();
|
||
out.extend_from_slice(&text[last_end..range.start]);
|
||
let cb_args = replacer_cb_args(vm, text, m, text_arg);
|
||
let repl = try_string!(call_replacer(vm, replacer, &cb_args));
|
||
out.extend_from_slice(&repl);
|
||
last_end = range.end;
|
||
}
|
||
out.extend_from_slice(&text[last_end..]);
|
||
NativeResult::Ok(vm.new_string_units_owned(out))
|
||
}
|
||
|
||
/// 单元序列上的整体替换(空模式在每个单元边界插入替换文本,与 String::replace
|
||
/// 口径一致)。
|
||
fn replace_units_all(hay: &[u16], from: &[u16], to: &[u16]) -> Vec<u16> {
|
||
if from.is_empty() {
|
||
let mut out = Vec::with_capacity(hay.len() + (hay.len() + 1) * to.len());
|
||
out.extend_from_slice(to);
|
||
for &u in hay {
|
||
out.push(u);
|
||
out.extend_from_slice(to);
|
||
}
|
||
return out;
|
||
}
|
||
let mut out = Vec::with_capacity(hay.len());
|
||
let mut start = 0;
|
||
while let Some(p) = find_units(hay, from, start) {
|
||
out.extend_from_slice(&hay[start..p]);
|
||
out.extend_from_slice(to);
|
||
start = p + from.len();
|
||
}
|
||
out.extend_from_slice(&hay[start..]);
|
||
out
|
||
}
|
||
|
||
/// 单元序列上的首个替换;未命中返回原序列拷贝。空模式在位置 0 命中
|
||
/// (插入替换文本于串首,与 String::replace 口径一致)。
|
||
fn replace_units_first(hay: &[u16], from: &[u16], to: &[u16]) -> Vec<u16> {
|
||
if from.is_empty() {
|
||
let mut out = Vec::with_capacity(hay.len() + to.len());
|
||
out.extend_from_slice(to);
|
||
out.extend_from_slice(hay);
|
||
return out;
|
||
}
|
||
if let Some(rel) = find_units(hay, from, 0) {
|
||
let mut out = Vec::with_capacity(hay.len());
|
||
out.extend_from_slice(&hay[..rel]);
|
||
out.extend_from_slice(to);
|
||
out.extend_from_slice(&hay[rel + from.len()..]);
|
||
return out;
|
||
}
|
||
hay.to_vec()
|
||
}
|
||
|
||
/// 字符串模式 + 函数 replacer:all 全替换否则替换首个。回调参数
|
||
/// `(match, position, string)`(无捕获组)。空模式在每个单元边界匹配一次。
|
||
/// `text_arg` 为回调第 4 参(原字符串),调用方预构一次。
|
||
fn string_replace_fn<H: VmHost>(
|
||
vm: &mut H, text: &[u16], pattern: &[u16], replacer: JsValue, all: bool, text_arg: JsValue,
|
||
) -> NativeResult {
|
||
let search_length = pattern.len();
|
||
let mut positions: Vec<usize> = Vec::new();
|
||
if search_length == 0 {
|
||
positions.push(0);
|
||
if all {
|
||
for i in 1..=text.len() {
|
||
positions.push(i);
|
||
}
|
||
}
|
||
} else {
|
||
let mut start = 0;
|
||
while let Some(p) = find_units(text, pattern, start) {
|
||
positions.push(p);
|
||
if !all {
|
||
break;
|
||
}
|
||
start = p + search_length;
|
||
}
|
||
}
|
||
let mut out = Vec::new();
|
||
let mut last_end = 0;
|
||
for p in positions {
|
||
out.extend_from_slice(&text[last_end..p]);
|
||
let cb_args = [vm.new_string_units(&text[p..p + search_length]), JsValue::int(p as i32), text_arg];
|
||
let repl = try_string!(call_replacer(vm, replacer, &cb_args));
|
||
out.extend_from_slice(&repl);
|
||
last_end = p + search_length;
|
||
}
|
||
out.extend_from_slice(&text[last_end..]);
|
||
NativeResult::Ok(vm.new_string_units_owned(out))
|
||
}
|
||
|
||
fn is_regexp_obj<H: VmHost>(val: JsValue, vm: &H) -> bool {
|
||
if !val.is_object() {
|
||
return false;
|
||
}
|
||
let ptr = val.as_js_object_ptr();
|
||
if ptr.is_null() {
|
||
return false;
|
||
}
|
||
let obj = unsafe { &*ptr };
|
||
let proto = obj.proto();
|
||
if !proto.is_object() {
|
||
return false;
|
||
}
|
||
let proto_ptr = proto.as_js_object_ptr();
|
||
if proto_ptr.is_null() {
|
||
return false;
|
||
}
|
||
let rp = vm.session().builtin_world().regexp_proto.as_ptr() as *mut JsObject;
|
||
std::ptr::eq(proto_ptr, rp)
|
||
}
|
||
|
||
// ── 静态方法 / 构造 ─────────────────────────────────────────────────────
|
||
|
||
/// `String.fromCharCode(...codes)`:把各参数按 ToUint32 低 16 位转为单元拼接
|
||
/// 为字符串(surrogate 区间合法产出孤立 surrogate 单元)。
|
||
pub fn string_from_char_code<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||
builtins_debug!("String.prototype.fromCharCode called with {} args", args.len());
|
||
let mut units: Vec<u16> = Vec::new();
|
||
for &arg_reg in args.iter().skip(1) {
|
||
let code = oxide_runtime_api::to_uint32(vm.reg(arg_reg)) & 0xFFFF;
|
||
units.push(code as u16);
|
||
}
|
||
NativeResult::Ok(vm.new_string_units_owned(units))
|
||
}
|
||
|
||
/// `String.fromCodePoint(...codes)`:把各参数按 ToNumber 语义转成 code point
|
||
/// (0..0x10FFFF 的整数)拼接为字符串;非整数、NaN 或越界抛 RangeError,
|
||
/// Symbol 抛 TypeError。surrogate 区间按现行规范接受(产出孤立 surrogate
|
||
/// 单元,单元载荷可承载)。
|
||
pub fn string_from_code_point<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||
builtins_debug!("String.fromCodePoint called with {} args", args.len());
|
||
let mut units: Vec<u16> = Vec::new();
|
||
// 经 native_arg_count/native_arg_at 读取:大实参集(如 harness 的
|
||
// `String.fromCodePoint.apply(null, codePoints)` 每块 10000 码位)在寄存器
|
||
// 窗口外走 spill 溢出区,小实参集与既有寄存器路径一致。
|
||
for i in 0..vm.native_arg_count(args) {
|
||
let n = match oxide_runtime_api::to_number_full(vm.native_arg_at(args, i), vm) {
|
||
Ok(n) => n,
|
||
Err(_) => {
|
||
// ToNumber 触发对象 valueOf/toString 抛出的原生异常须原样传播,
|
||
// 否则会被展平为普通 Error 丢失原始异常对象。
|
||
if let Some(exc) = vm.take_uncaught_value() {
|
||
return NativeResult::Err(exc);
|
||
}
|
||
return NativeResult::Err(crate::error::create_type_error(vm, "Cannot convert value to a number"));
|
||
}
|
||
};
|
||
if !n.is_finite() || n.trunc() != n || n < 0.0 || n > 0x10FFFF as f64 {
|
||
return NativeResult::Err(crate::error::create_range_error(vm, "Invalid code point"));
|
||
}
|
||
let code = n as u32;
|
||
if code < 0x10000 {
|
||
units.push(code as u16);
|
||
} else {
|
||
let v = code - 0x10000;
|
||
units.push(0xD800 + (v >> 10) as u16);
|
||
units.push(0xDC00 + (v & 0x3FF) as u16);
|
||
}
|
||
}
|
||
NativeResult::Ok(vm.new_string_units_owned(units))
|
||
}
|
||
|
||
/// `String.prototype.valueOf`:返回包装对象的原始字符串;其它 this 抛 TypeError。
|
||
pub fn string_value_of<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||
builtins_debug!("String.prototype.valueOf called with {} args", args.len());
|
||
let this_val = vm.reg(args[0]);
|
||
if this_val.is_string() {
|
||
return NativeResult::Ok(this_val);
|
||
}
|
||
if this_val.is_object() {
|
||
let ptr = this_val.as_js_object_ptr();
|
||
if !ptr.is_null() {
|
||
let obj = unsafe { &*ptr };
|
||
if obj.is_string_obj() {
|
||
return NativeResult::Ok(obj.get_prop_at(0));
|
||
}
|
||
}
|
||
}
|
||
builtins_error!("String.prototype.valueOf: invalid receiver");
|
||
NativeResult::Err(crate::error::create_type_error(
|
||
vm,
|
||
"String.prototype.valueOf called on non-String object",
|
||
))
|
||
}
|
||
|
||
/// JS `String()` 构造逻辑:把参数转成字符串(单元保真);new 语义返回
|
||
/// `[[StringData]]` 包装对象。
|
||
pub fn string_constructor<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||
let string_proto = vm.session().builtin_world().string_proto.as_ptr() as *mut JsObject;
|
||
let this_val = vm.reg(if args.is_empty() { 0 } else { args[0] });
|
||
let is_ctor = if this_val.is_object() {
|
||
let ptr = this_val.as_js_object_ptr();
|
||
if ptr.is_null() {
|
||
false
|
||
} else {
|
||
let proto_ptr = unsafe { (*ptr).proto().as_js_object_ptr() };
|
||
!proto_ptr.is_null() && std::ptr::eq(proto_ptr, string_proto)
|
||
}
|
||
} else {
|
||
false
|
||
};
|
||
|
||
let str_val = if args.len() > 1 {
|
||
let v = vm.reg(args[1]);
|
||
if is_ctor && v.is_symbol() {
|
||
// new String(Symbol):构造器路径走 ToString(规范步骤 3b),
|
||
// 对 Symbol 抛 TypeError——与函数调用路径(3a 描述串)相异。
|
||
return NativeResult::Err(crate::error::create_type_error(vm, "Cannot convert a Symbol value to a string"));
|
||
}
|
||
// 对象参数须经 ToPrimitive/ToString 完整转换;函数调用路径的 Symbol
|
||
// 走 SymbolDescriptiveString(规范步骤 3a,不经 ToString)。
|
||
let v = oxide_runtime_api::to_string_for_string_constructor(v, vm);
|
||
match v {
|
||
Ok(s) => vm.new_string_owned(s),
|
||
Err(_) => {
|
||
// ToString on an object may throw via toString/valueOf; propagate the original exception.
|
||
if let Some(exc) = vm.take_uncaught_value() {
|
||
return NativeResult::Err(exc);
|
||
}
|
||
return NativeResult::Err(crate::error::create_type_error(vm, "Cannot convert value to a string"));
|
||
}
|
||
}
|
||
} else {
|
||
vm.new_string("")
|
||
};
|
||
|
||
if !is_ctor {
|
||
return NativeResult::Ok(str_val);
|
||
}
|
||
|
||
let obj = unsafe { &mut *this_val.as_js_object_ptr() };
|
||
obj.type_tag = JsObject::OBJ_TYPE_STRING_OBJ;
|
||
obj.push_prop(str_val);
|
||
NativeResult::Ok(this_val)
|
||
}
|
||
|
||
/// `String.prototype.toString`:返回包装对象的原始字符串;其它 this 抛 TypeError。
|
||
pub fn string_to_string<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||
let this_val = vm.reg(args[0]);
|
||
if this_val.is_string() {
|
||
return NativeResult::Ok(this_val);
|
||
}
|
||
if this_val.is_object() {
|
||
let ptr = this_val.as_js_object_ptr();
|
||
if !ptr.is_null() {
|
||
let obj = unsafe { &*ptr };
|
||
if obj.is_string_obj() {
|
||
return NativeResult::Ok(obj.get_prop_at(0));
|
||
}
|
||
}
|
||
}
|
||
NativeResult::Err(crate::error::create_type_error(
|
||
vm,
|
||
"String.prototype.toString called on non-String object",
|
||
))
|
||
}
|
||
|
||
// ── 查找 / 索引 ─────────────────────────────────────────────────────────
|
||
|
||
/// `String.prototype.indexOf(searchString, position)`:按码元查找首次出现位置。
|
||
pub fn string_index_of<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||
builtins_debug!("String.prototype.indexOf called with {} args", args.len());
|
||
// 参数转换先行:search 缺省为 undefined(经 ToString 得 "undefined" 参与查找),
|
||
// position 缺省 0;均可能触发对象 ToString/ToNumber(&mut 路径)。
|
||
// 单元序列落地为 owned:借用落地后与后续 &mut 调用无交叠。
|
||
let search_val = if args.len() >= 2 { vm.reg(args[1]) } else { JsValue::undefined() };
|
||
let search: Vec<u16> = try_string!(as_units(vm, search_val)).into_owned();
|
||
let pos_raw = if args.len() > 2 {
|
||
vm.coerce_number_bounded(vm.reg(args[2])).unwrap_or(f64::NAN) as usize
|
||
} else {
|
||
0
|
||
};
|
||
let s: Vec<u16> = try_string!(this_units(vm, args)).into_owned();
|
||
let n = s.len();
|
||
let pos = pos_raw.min(n);
|
||
|
||
if search.is_empty() {
|
||
return NativeResult::Ok(JsValue::int(pos as i32));
|
||
}
|
||
if let Some(idx) = find_units(&s, &search, pos) {
|
||
return NativeResult::Ok(JsValue::int(idx as i32));
|
||
}
|
||
NativeResult::Ok(JsValue::int(-1))
|
||
}
|
||
|
||
/// `String.prototype.includes(searchString, position)`:是否包含子串。
|
||
pub fn string_includes<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||
builtins_debug!("String.prototype.includes called with {} args", args.len());
|
||
// 参数转换先行(&mut 路径):search 缺省为 undefined(经 ToString 得 "undefined")。
|
||
let search_val = if args.len() >= 2 { vm.reg(args[1]) } else { JsValue::undefined() };
|
||
let search: Vec<u16> = try_string!(as_units(vm, search_val)).into_owned();
|
||
let pos_raw = if args.len() > 2 {
|
||
vm.coerce_number_bounded(vm.reg(args[2])).unwrap_or(f64::NAN) as usize
|
||
} else {
|
||
0
|
||
};
|
||
let s: Vec<u16> = try_string!(this_units(vm, args)).into_owned();
|
||
let pos = pos_raw.min(s.len());
|
||
|
||
if search.is_empty() {
|
||
return NativeResult::Ok(JsValue::bool(true));
|
||
}
|
||
NativeResult::Ok(JsValue::bool(find_units(&s, &search, pos).is_some()))
|
||
}
|
||
|
||
/// `String.prototype.charAt(index)`:返回指定码元位置的 1 单元字符串;越界返回空串。
|
||
pub fn string_char_at<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||
builtins_debug!("String.prototype.charAt called with {} args", args.len());
|
||
// index 可能触发对象 ToNumber(&mut 路径),先行转换。
|
||
let idx = if args.len() >= 2 {
|
||
vm.coerce_number_bounded(vm.reg(args[1])).unwrap_or(f64::NAN) as i32
|
||
} else {
|
||
0
|
||
};
|
||
let s: Vec<u16> = try_string!(this_units(vm, args)).into_owned();
|
||
if idx < 0 || idx as usize >= s.len() {
|
||
return NativeResult::Ok(vm.new_string(""));
|
||
}
|
||
let u = s[idx as usize];
|
||
// ASCII 单元走单字符缓存零分配,其余(含孤立 surrogate)回落 1 单元串创建。
|
||
match vm.single_unit(u) {
|
||
Some(v) => NativeResult::Ok(v),
|
||
None => NativeResult::Ok(vm.new_string_units(&[u])),
|
||
}
|
||
}
|
||
|
||
/// `String.prototype.charCodeAt(index)`:返回指定码元位置的 code unit;越界返回 NaN。
|
||
pub fn string_char_code_at<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||
builtins_debug!("String.prototype.charCodeAt called with {} args", args.len());
|
||
// index 可能触发对象 ToNumber(&mut 路径),先行转换。
|
||
let idx = if args.len() >= 2 {
|
||
vm.coerce_number_bounded(vm.reg(args[1])).unwrap_or(f64::NAN) as i32
|
||
} else {
|
||
0
|
||
};
|
||
let s = try_string!(this_units(vm, args));
|
||
if idx < 0 || idx as usize >= s.len() {
|
||
return NativeResult::Ok(JsValue::float(f64::NAN));
|
||
}
|
||
NativeResult::Ok(JsValue::int(s[idx as usize] as i32))
|
||
}
|
||
|
||
/// `String.prototype.codePointAt(pos)`:按码元位置取 code point(surrogate 对
|
||
/// 合并);越界或孤立代理返回 undefined。
|
||
pub fn string_code_point_at<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||
builtins_debug!("String.prototype.codePointAt called with {} args", args.len());
|
||
// pos 转换先行(&mut 路径),后取 this 单元序列定位。
|
||
let pos = if args.len() > 1 {
|
||
let pos_val = vm.reg(args[1]);
|
||
if pos_val.is_symbol() {
|
||
return NativeResult::Err(crate::error::create_type_error(vm, "Cannot convert a Symbol value to a number"));
|
||
}
|
||
match vm.coerce_number_bounded(pos_val) {
|
||
Ok(n) => n,
|
||
Err(_) => {
|
||
// pos 对象 valueOf/toString 抛出的异常须原样传播。
|
||
if let Some(exc) = vm.take_uncaught_value() {
|
||
return NativeResult::Err(exc);
|
||
}
|
||
return NativeResult::Err(crate::error::create_type_error(vm, "Cannot convert argument to a number"));
|
||
}
|
||
}
|
||
} else {
|
||
0.0
|
||
};
|
||
let pos = if pos.is_nan() { 0.0 } else { pos.trunc() };
|
||
if pos < 0.0 || pos > u32::MAX as f64 {
|
||
return NativeResult::Ok(JsValue::undefined());
|
||
}
|
||
let pos = pos as usize;
|
||
let s = try_string!(this_units(vm, args));
|
||
if pos >= s.len() {
|
||
return NativeResult::Ok(JsValue::undefined());
|
||
}
|
||
let first = s[pos];
|
||
if (0xD800..=0xDBFF).contains(&first) && pos + 1 < s.len() {
|
||
let second = s[pos + 1];
|
||
if (0xDC00..=0xDFFF).contains(&second) {
|
||
let cp = 0x10000 + (((first - 0xD800) as u32) << 10) + (second - 0xDC00) as u32;
|
||
return NativeResult::Ok(JsValue::int(cp as i32));
|
||
}
|
||
}
|
||
NativeResult::Ok(JsValue::int(first as i32))
|
||
}
|
||
|
||
/// `String.prototype.lastIndexOf(searchString, position)`:从后往前查找首次
|
||
/// 出现位置(起始位置 ≤ position,position 缺省串长)。
|
||
pub fn string_last_index_of<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||
builtins_debug!("String.prototype.lastIndexOf called with {} args", args.len());
|
||
// 参数转换先行(&mut 路径):search 缺省为 undefined(经 ToString 得 "undefined")。
|
||
let search_val = if args.len() >= 2 { vm.reg(args[1]) } else { JsValue::undefined() };
|
||
let search: Vec<u16> = try_string!(as_units(vm, search_val)).into_owned();
|
||
let pos_raw = if args.len() > 2 {
|
||
let p = oxide_runtime_api::to_integer_or_infinity(vm.reg(args[2]));
|
||
if p.is_nan() {
|
||
None
|
||
} else {
|
||
Some(p as usize)
|
||
}
|
||
} else {
|
||
None
|
||
};
|
||
let s: Vec<u16> = try_string!(this_units(vm, args)).into_owned();
|
||
let n = s.len();
|
||
let pos = match pos_raw {
|
||
Some(p) => p.min(n),
|
||
None => n,
|
||
};
|
||
|
||
if search.is_empty() {
|
||
return NativeResult::Ok(JsValue::int(pos as i32));
|
||
}
|
||
// 窗口 = 前 pos+1 个码元:起始位置 ≤ pos 的末次出现。
|
||
let window = (pos + 1).min(n);
|
||
if let Some(idx) = rfind_units(&s, &search, window) {
|
||
return NativeResult::Ok(JsValue::int(idx as i32));
|
||
}
|
||
NativeResult::Ok(JsValue::int(-1))
|
||
}
|
||
|
||
// ── 切片 / 变换 ─────────────────────────────────────────────────────────
|
||
|
||
/// `String.prototype.concat(...strings)`:拼接 this 与各参数返回新字符串
|
||
/// (单元口径,参数经完整 ToString)。
|
||
pub fn string_concat<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||
builtins_debug!("String.prototype.concat called with {} args", args.len());
|
||
let mut units = try_string!(this_units(vm, args)).into_owned();
|
||
for &arg_reg in args.iter().skip(1) {
|
||
match oxide_runtime_api::to_string_value_full(vm.reg(arg_reg), vm) {
|
||
Ok(v) => units.extend(vm.string_units(v).as_ref()),
|
||
Err(_) => {
|
||
// ToString on an object may throw via toString/valueOf; propagate the original exception.
|
||
if let Some(exc) = vm.take_uncaught_value() {
|
||
return NativeResult::Err(exc);
|
||
}
|
||
return NativeResult::Err(crate::error::create_type_error(vm, "Cannot convert value to a string"));
|
||
}
|
||
}
|
||
}
|
||
NativeResult::Ok(vm.new_string_units_owned(units))
|
||
}
|
||
|
||
/// `String.prototype.slice(start, end)`:按码元区间(支持负索引)取子串。
|
||
pub fn string_slice<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||
builtins_debug!("String.prototype.slice called with {} args", args.len());
|
||
// 位置参数先行(&mut 转换),后借 this 取子串。
|
||
let start_raw = if args.len() > 1 {
|
||
Some(vm.coerce_number_bounded(vm.reg(args[1])).unwrap_or(f64::NAN) as i32)
|
||
} else {
|
||
None
|
||
};
|
||
let end_raw = if args.len() > 2 {
|
||
Some(vm.coerce_number_bounded(vm.reg(args[2])).unwrap_or(f64::NAN) as i32)
|
||
} else {
|
||
None
|
||
};
|
||
let s: Vec<u16> = try_string!(this_units(vm, args)).into_owned();
|
||
let n = s.len() as i32;
|
||
let start = match start_raw {
|
||
Some(v) => {
|
||
if v < 0 {
|
||
(n + v).max(0)
|
||
} else {
|
||
v.min(n)
|
||
}
|
||
}
|
||
None => 0,
|
||
};
|
||
let end = match end_raw {
|
||
Some(v) => {
|
||
if v < 0 {
|
||
(n + v).max(0)
|
||
} else {
|
||
v.min(n)
|
||
}
|
||
}
|
||
None => n,
|
||
};
|
||
// 码元区间精确切片(孤立 surrogate 按单元保留,不吸附字符边界)。
|
||
let out: Vec<u16> = if start < end {
|
||
let (a, b) = (start as usize, end as usize);
|
||
s[a.min(s.len())..b.min(s.len())].to_vec()
|
||
} else {
|
||
Vec::new()
|
||
};
|
||
NativeResult::Ok(vm.new_string_units_owned(out))
|
||
}
|
||
|
||
/// `String.prototype.substring(start, end)`:取子串,start/end 自动对调且取非负。
|
||
pub fn string_substring<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||
builtins_debug!("String.prototype.substring called with {} args", args.len());
|
||
// 寄存器取值先行(纯函数),后借 this 取子串。
|
||
let start_arg = if args.len() > 1 { Some(vm.reg(args[1])) } else { None };
|
||
let end_arg = if args.len() > 2 { Some(vm.reg(args[2])) } else { None };
|
||
let s: Vec<u16> = try_string!(this_units(vm, args)).into_owned();
|
||
let n = s.len() as i32;
|
||
let mut start = match start_arg {
|
||
Some(v) => {
|
||
let v = oxide_runtime_api::to_integer_or_infinity(v);
|
||
if v.is_nan() || v < 0.0 {
|
||
0
|
||
} else {
|
||
(v as i32).min(n)
|
||
}
|
||
}
|
||
None => 0,
|
||
};
|
||
let mut end = match end_arg {
|
||
Some(v) => {
|
||
let v = oxide_runtime_api::to_integer_or_infinity(v);
|
||
if v.is_nan() || v < 0.0 {
|
||
0
|
||
} else {
|
||
(v as i32).min(n)
|
||
}
|
||
}
|
||
None => n,
|
||
};
|
||
if start > end {
|
||
std::mem::swap(&mut start, &mut end);
|
||
}
|
||
let out: Vec<u16> = s[start as usize..end as usize].to_vec();
|
||
NativeResult::Ok(vm.new_string_units_owned(out))
|
||
}
|
||
|
||
/// `String.prototype.substr(start, length)`:从 start 起取 length 个码元
|
||
/// (Annex B,支持负 start)。
|
||
pub fn string_substr<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||
builtins_debug!("String.prototype.substr called with {} args", args.len());
|
||
// 寄存器取值先行(纯函数),后借 this 截取。
|
||
let start_arg = if args.len() > 1 { Some(vm.reg(args[1])) } else { None };
|
||
let length_arg = if args.len() > 2 { Some(vm.reg(args[2])) } else { None };
|
||
let s: Vec<u16> = try_string!(this_units(vm, args)).into_owned();
|
||
let len = s.len() as isize;
|
||
let start = match start_arg {
|
||
Some(v) => {
|
||
let n = oxide_runtime_api::to_integer_or_infinity(v) as isize;
|
||
if n < 0 {
|
||
(len + n).max(0)
|
||
} else {
|
||
n.min(len)
|
||
}
|
||
}
|
||
None => 0,
|
||
} as usize;
|
||
let length = match length_arg {
|
||
Some(v) => (oxide_runtime_api::to_integer_or_infinity(v) as isize).max(0) as usize,
|
||
None => len as usize - start,
|
||
};
|
||
let count = length.min(len as usize - start);
|
||
let out: Vec<u16> = s[start..start + count].to_vec();
|
||
NativeResult::Ok(vm.new_string_units_owned(out))
|
||
}
|
||
|
||
/// `String.prototype.at(index)`:按码元索引取 1 单元字符串(支持负索引);
|
||
/// 越界返回 undefined。
|
||
pub fn string_at<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||
builtins_debug!("String.prototype.at called with {} args", args.len());
|
||
let idx = if args.len() > 1 {
|
||
oxide_runtime_api::to_integer_or_infinity(vm.reg(args[1])) as i32
|
||
} else {
|
||
0
|
||
};
|
||
let s = try_string!(this_units(vm, args));
|
||
let len = s.len() as i32;
|
||
let idx = if idx < 0 { len + idx } else { idx };
|
||
if idx < 0 || idx >= len {
|
||
return NativeResult::Ok(JsValue::undefined());
|
||
}
|
||
let u = s[idx as usize];
|
||
match vm.single_unit(u) {
|
||
Some(v) => NativeResult::Ok(v),
|
||
None => NativeResult::Ok(vm.new_string_units(&[u])),
|
||
}
|
||
}
|
||
|
||
/// `String.prototype.toUpperCase`:全大写转换(良形段映射,孤立 surrogate
|
||
/// 单元原样保留)。
|
||
pub fn string_to_upper_case<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||
builtins_debug!("String.prototype.toUpperCase called with {} args", args.len());
|
||
let s = try_string!(this_units(vm, args));
|
||
let out = map_well_formed_segments(&s, |seg| seg.to_uppercase());
|
||
NativeResult::Ok(vm.new_string_units_owned(out))
|
||
}
|
||
|
||
/// `String.prototype.toLowerCase`:全小写转换(良形段映射,孤立 surrogate
|
||
/// 单元原样保留)。
|
||
pub fn string_to_lower_case<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||
builtins_debug!("String.prototype.toLowerCase called with {} args", args.len());
|
||
let s = try_string!(this_units(vm, args));
|
||
let out = map_well_formed_segments(&s, |seg| seg.to_lowercase());
|
||
NativeResult::Ok(vm.new_string_units_owned(out))
|
||
}
|
||
|
||
/// `String.prototype.trim`:去除两端白空格(单元口径)。
|
||
pub fn string_trim<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||
builtins_debug!("String.prototype.trim called with {} args", args.len());
|
||
let s: Vec<u16> = try_string!(this_units(vm, args)).into_owned();
|
||
let n = s.len();
|
||
let mut start = 0;
|
||
while start < n && is_trim_unit(s[start]) {
|
||
start += 1;
|
||
}
|
||
let mut end = n;
|
||
while end > start && is_trim_unit(s[end - 1]) {
|
||
end -= 1;
|
||
}
|
||
NativeResult::Ok(vm.new_string_units_owned(s[start..end].to_vec()))
|
||
}
|
||
|
||
/// `String.prototype.trimStart`:去除头部白空格。
|
||
pub fn string_trim_start<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||
builtins_debug!("String.prototype.trimStart called with {} args", args.len());
|
||
let s: Vec<u16> = try_string!(this_units(vm, args)).into_owned();
|
||
let mut start = 0;
|
||
while start < s.len() && is_trim_unit(s[start]) {
|
||
start += 1;
|
||
}
|
||
NativeResult::Ok(vm.new_string_units_owned(s[start..].to_vec()))
|
||
}
|
||
|
||
/// `String.prototype.trimEnd`:去除尾部白空格。
|
||
pub fn string_trim_end<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||
builtins_debug!("String.prototype.trimEnd called with {} args", args.len());
|
||
let s: Vec<u16> = try_string!(this_units(vm, args)).into_owned();
|
||
let mut end = s.len();
|
||
while end > 0 && is_trim_unit(s[end - 1]) {
|
||
end -= 1;
|
||
}
|
||
NativeResult::Ok(vm.new_string_units_owned(s[..end].to_vec()))
|
||
}
|
||
|
||
/// `String.prototype.repeat(count)`:重复字符串 count 次(当前上限 10000 防滥用)。
|
||
pub fn string_repeat<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||
builtins_debug!("String.prototype.repeat called with {} args", args.len());
|
||
// count 转换先行(&mut 路径),后借 this 重复。
|
||
let n = if args.len() > 1 {
|
||
(vm.coerce_number_bounded(vm.reg(args[1])).unwrap_or(f64::NAN) as usize).min(10000)
|
||
} else {
|
||
1
|
||
};
|
||
let s = try_string!(this_units(vm, args));
|
||
let mut out = Vec::with_capacity(s.len() * n);
|
||
for _ in 0..n {
|
||
out.extend_from_slice(&s);
|
||
}
|
||
NativeResult::Ok(vm.new_string_units_owned(out))
|
||
}
|
||
|
||
/// `String.prototype.padStart(targetLength, padString)`:在头部补足 padString
|
||
/// 到目标码点数。
|
||
pub fn string_pad_start<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||
builtins_debug!("String.prototype.padStart called with {} args", args.len());
|
||
// 参数转换先行(&mut 路径):targetLength 与 padString 均可能触发对象转换。
|
||
let target_arg = if args.len() > 1 {
|
||
Some(vm.coerce_number_bounded(vm.reg(args[1])).unwrap_or(f64::NAN) as usize)
|
||
} else {
|
||
None
|
||
};
|
||
let pad_val = if args.len() > 2 { vm.reg(args[2]) } else { JsValue::undefined() };
|
||
// 规范口径:padString 为 undefined(含显式传入)回落空格填充。
|
||
let pad: Vec<u16> = if pad_val.is_undefined() {
|
||
vec![0x20]
|
||
} else {
|
||
try_string!(as_units(vm, pad_val)).into_owned()
|
||
};
|
||
let s: Vec<u16> = try_string!(this_units(vm, args)).into_owned();
|
||
let s_len = code_point_count(&s);
|
||
let target = target_arg.unwrap_or(s_len);
|
||
if target > 10000 {
|
||
builtins_error!("String.prototype.padStart: invalid receiver");
|
||
return NativeResult::Err(crate::error::create_range_error(vm, "Invalid string length"));
|
||
}
|
||
if s_len >= target || pad.is_empty() {
|
||
return NativeResult::Ok(vm.new_string_units_owned(s.to_vec()));
|
||
}
|
||
let needed = target - s_len;
|
||
let pad_len = code_point_count(&pad).max(1);
|
||
let reps = needed.div_ceil(pad_len);
|
||
let mut pad_rep = Vec::with_capacity(pad.len() * reps);
|
||
for _ in 0..reps {
|
||
pad_rep.extend_from_slice(&pad);
|
||
}
|
||
let prefix = take_code_points(&pad_rep, needed);
|
||
let mut out = Vec::with_capacity(prefix.len() + s.len());
|
||
out.extend_from_slice(prefix);
|
||
out.extend_from_slice(&s);
|
||
NativeResult::Ok(vm.new_string_units_owned(out))
|
||
}
|
||
|
||
/// `String.prototype.padEnd(targetLength, padString)`:在尾部补足 padString
|
||
/// 到目标码点数。
|
||
pub fn string_pad_end<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||
builtins_debug!("String.prototype.padEnd called with {} args", args.len());
|
||
// 参数转换先行(&mut 路径):targetLength 与 padString 均可能触发对象转换。
|
||
let target_arg = if args.len() > 1 {
|
||
Some(vm.coerce_number_bounded(vm.reg(args[1])).unwrap_or(f64::NAN) as usize)
|
||
} else {
|
||
None
|
||
};
|
||
let pad_val = if args.len() > 2 { vm.reg(args[2]) } else { JsValue::undefined() };
|
||
// 规范口径:padString 为 undefined(含显式传入)回落空格填充。
|
||
let pad: Vec<u16> = if pad_val.is_undefined() {
|
||
vec![0x20]
|
||
} else {
|
||
try_string!(as_units(vm, pad_val)).into_owned()
|
||
};
|
||
let s: Vec<u16> = try_string!(this_units(vm, args)).into_owned();
|
||
let s_len = code_point_count(&s);
|
||
let target = target_arg.unwrap_or(s_len);
|
||
if target > 10000 {
|
||
builtins_error!("String.prototype.padEnd: invalid receiver");
|
||
return NativeResult::Err(crate::error::create_range_error(vm, "Invalid string length"));
|
||
}
|
||
if s_len >= target || pad.is_empty() {
|
||
return NativeResult::Ok(vm.new_string_units_owned(s.to_vec()));
|
||
}
|
||
let needed = target - s_len;
|
||
let pad_len = code_point_count(&pad).max(1);
|
||
let reps = needed.div_ceil(pad_len);
|
||
let mut pad_rep = Vec::with_capacity(pad.len() * reps);
|
||
for _ in 0..reps {
|
||
pad_rep.extend_from_slice(&pad);
|
||
}
|
||
let suffix = take_code_points(&pad_rep, needed);
|
||
let mut out = Vec::with_capacity(s.len() + suffix.len());
|
||
out.extend_from_slice(&s);
|
||
out.extend_from_slice(suffix);
|
||
NativeResult::Ok(vm.new_string_units_owned(out))
|
||
}
|
||
|
||
/// `String.prototype.startsWith(searchString, position)`:是否以指定子串开头
|
||
/// (position 起的前缀码元比较,不做字符边界吸附——规格口径)。
|
||
pub fn string_starts_with<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||
builtins_debug!("String.prototype.startsWith called with {} args", args.len());
|
||
// 参数转换先行(&mut 路径):search 缺省为 undefined(经 ToString 得 "undefined")。
|
||
let search_val = if args.len() >= 2 { vm.reg(args[1]) } else { JsValue::undefined() };
|
||
let search: Vec<u16> = try_string!(as_units(vm, search_val)).into_owned();
|
||
let pos_raw = if args.len() > 2 {
|
||
vm.coerce_number_bounded(vm.reg(args[2])).unwrap_or(f64::NAN) as usize
|
||
} else {
|
||
0
|
||
};
|
||
let s: Vec<u16> = try_string!(this_units(vm, args)).into_owned();
|
||
let pos = pos_raw.min(s.len());
|
||
let result = if search.is_empty() { true } else { s[pos..].starts_with(&search) };
|
||
NativeResult::Ok(JsValue::bool(result))
|
||
}
|
||
|
||
/// `String.prototype.endsWith(searchString, endPosition)`:是否以指定子串结尾
|
||
/// (截断至 endPosition 的后缀码元比较,不做字符边界吸附——规格口径)。
|
||
pub fn string_ends_with<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||
builtins_debug!("String.prototype.endsWith called with {} args", args.len());
|
||
// 参数转换先行(&mut 路径):search 缺省为 undefined(经 ToString 得 "undefined")。
|
||
let search_val = if args.len() >= 2 { vm.reg(args[1]) } else { JsValue::undefined() };
|
||
let search: Vec<u16> = try_string!(as_units(vm, search_val)).into_owned();
|
||
let end_pos_raw = if args.len() > 2 {
|
||
vm.coerce_number_bounded(vm.reg(args[2])).unwrap_or(f64::NAN) as usize
|
||
} else {
|
||
usize::MAX
|
||
};
|
||
let s: Vec<u16> = try_string!(this_units(vm, args)).into_owned();
|
||
let end_pos = end_pos_raw.min(s.len());
|
||
let result = if search.is_empty() { true } else { s[..end_pos].ends_with(&search) };
|
||
NativeResult::Ok(JsValue::bool(result))
|
||
}
|
||
|
||
// ── 拆分 / 正则匹配 ─────────────────────────────────────────────────────
|
||
|
||
/// `String.prototype.split(separator, limit)`:按分隔符拆分为字符串数组;
|
||
/// 分隔符可为 RegExp(含捕获组)或字符串。空分隔按单元逐个产出(规格口径,
|
||
/// 孤立 surrogate 为 1 单元元素)。
|
||
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 re_ptr = sep_val.unwrap().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
|
||
}
|
||
} else {
|
||
u32::MAX as usize
|
||
};
|
||
let sep_units: Vec<u16> = if is_undefined_sep || has_native_re {
|
||
Vec::new()
|
||
} else {
|
||
try_string!(as_units(vm, sep_val.unwrap())).into_owned()
|
||
};
|
||
let s: Vec<u16> = try_string!(this_units(vm, args)).into_owned();
|
||
// 规范:separator 为 undefined 时返回 [this]。
|
||
if is_undefined_sep {
|
||
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) };
|
||
let mut parts: Vec<Vec<u16>> = 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(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(s[g.start..g.end].to_vec()),
|
||
None => parts.push(Vec::new()),
|
||
}
|
||
}
|
||
last_end = range.end;
|
||
}
|
||
if last_end <= s.len() && parts.len() < limit {
|
||
parts.push(s[last_end..].to_vec());
|
||
}
|
||
return NativeResult::Ok(make_units_array(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) {
|
||
values.push(match vm.single_unit(u) {
|
||
Some(v) => v,
|
||
None => vm.new_string_units(&[u]),
|
||
});
|
||
}
|
||
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 {
|
||
break;
|
||
}
|
||
match find_units(&s, &sep_units, start) {
|
||
Some(p) => {
|
||
parts.push(s[start..p].to_vec());
|
||
start = p + sep_units.len();
|
||
}
|
||
None => {
|
||
parts.push(s[start..].to_vec());
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
NativeResult::Ok(make_units_array(vm, parts))
|
||
}
|
||
|
||
/// `replace`/`replaceAll` 共用的分叉实现:按 replacer 类型与参数形态选择路径。
|
||
///
|
||
/// # 步骤
|
||
/// 1. receiver 前置校验(RequireObjectCoercible,纯 is_* 读取)。
|
||
/// 2. 纯对象头读取判定分叉:正则身份/编译正则/global 标志/函数 replacer。
|
||
/// 3. 按分支执行替换(单元口径;良形内容的结果与旧文本路径逐位一致)。
|
||
///
|
||
/// # 边界与前提
|
||
/// - 缺省 searchValue/replaceValue 均按 ToString(undefined)="undefined" 处理
|
||
/// (`s.replace()` 全缺省等价于把 "undefined" 替换为 "undefined",结果与
|
||
/// 原串一致;`s.replace("b")` 得到 "aundefinedc" 而非 "ac")。
|
||
/// - replaceAll 遇非 global 正则抛 TypeError;类正则对象(proto 恒等
|
||
/// RegExp.prototype 但无编译正则)replace/replaceAll 统一按 ToString 文本走
|
||
/// 字符串路径。
|
||
fn string_replace_impl<H: VmHost>(vm: &mut H, args: &[u8], all: bool) -> NativeResult {
|
||
let this_val = vm.reg(args[0]);
|
||
|
||
// receiver 前置校验:null/undefined/symbol 抛 TypeError(规范第 1 步
|
||
// RequireObjectCoercible,纯 is_* 读取无用户代码,先于一切分叉)。
|
||
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 pattern_val = if args.len() >= 2 { vm.reg(args[1]) } else { JsValue::undefined() };
|
||
let replacement_val = if args.len() > 2 { vm.reg(args[2]) } else { JsValue::undefined() };
|
||
|
||
// 分叉判定(全部纯读取/纯对象头读,无用户代码):正则对象身份、是否持有
|
||
// 编译正则、global 标志、replacer 是否为函数。
|
||
let is_re = is_regexp_obj(pattern_val, vm);
|
||
let (has_native_re, is_global) = if is_re {
|
||
let re_ptr = pattern_val.as_js_object_ptr();
|
||
// SAFETY: is_re 已保证 pattern_val 为非空对象且 proto 恒等 RegExp.prototype。
|
||
let re = unsafe { &*re_ptr };
|
||
if re.native_fn().is_some() {
|
||
let g = re.hash_props_vec().and_then(|v| v.get(3)).map(|v| v.as_bool()).unwrap_or(false);
|
||
(true, g)
|
||
} else {
|
||
(false, false)
|
||
}
|
||
} else {
|
||
(false, false)
|
||
};
|
||
let replacer_val = if replacement_val.is_object() {
|
||
let o = unsafe { &*replacement_val.as_js_object_ptr() };
|
||
if o.is_function() {
|
||
Some(replacement_val)
|
||
} else {
|
||
None
|
||
}
|
||
} else {
|
||
None
|
||
};
|
||
|
||
// replaceAll 要求正则带 global:非 global 正则直接抛 TypeError(规范 flags 检查)。
|
||
// 类正则对象(无编译正则)无 flags 概念,统一走 as_units 文本路径,不在此检查。
|
||
if all && has_native_re && !is_global {
|
||
return NativeResult::Err(crate::error::create_type_error(
|
||
vm,
|
||
"String.prototype.replaceAll called with a non-global RegExp",
|
||
));
|
||
}
|
||
|
||
// 分支 A:函数 replacer。回调经 call_function_sync(&mut),receiver 单元
|
||
// 序列先落地为 owned(跨回调的硬约束)。
|
||
if let Some(replacer_val) = replacer_val {
|
||
let s = try_string!(this_units(vm, args)).into_owned();
|
||
// 回调第 4 参(原字符串)预构一次:原始字符串 receiver 直接复用 this_val
|
||
// 零拷贝,对象 receiver 提升为单次转换值(原每匹配整串复制)。
|
||
let text_arg = match this_val.is_string() {
|
||
true => this_val,
|
||
false => match oxide_runtime_api::to_string_value_full(this_val, vm) {
|
||
Ok(v) => v,
|
||
Err(_) => {
|
||
// ToString 触发对象 toString/valueOf 抛出的原生异常须原样传播。
|
||
if let Some(exc) = vm.take_uncaught_value() {
|
||
return NativeResult::Err(exc);
|
||
}
|
||
return NativeResult::Err(crate::error::create_type_error(vm, "Cannot convert value to a string"));
|
||
}
|
||
},
|
||
};
|
||
if has_native_re {
|
||
let re_ptr = pattern_val.as_js_object_ptr();
|
||
let re = unsafe { &*re_ptr };
|
||
let fn_ptr = re.native_fn().unwrap();
|
||
// SAFETY: fn_ptr 持有 regexp_constructor 存放的 `Box<regress::Regex>` 指针。
|
||
let regex = unsafe { &*(fn_ptr.as_ptr() as *const regress::Regex) };
|
||
return regex_replace_fn(vm, regex, &s, replacer_val, is_global, text_arg);
|
||
}
|
||
let pattern = try_string!(as_units(vm, pattern_val)).into_owned();
|
||
return string_replace_fn(vm, &s, &pattern, replacer_val, all, text_arg);
|
||
}
|
||
|
||
let replacement_is_string = args.len() <= 2 || replacement_val.is_string();
|
||
|
||
// 分支 B:全原始字符串快路径——三值按载荷形态共享借用(`&H` 共享借用
|
||
// 派生多个单元借用可共存,E0499 只发生在 `&mut` 派生)。
|
||
if this_val.is_string() && pattern_val.is_string() && replacement_is_string {
|
||
let h = &*vm;
|
||
let s = h.string_units(this_val);
|
||
let p = h.string_units(pattern_val);
|
||
let r: Cow<'_, [u16]> = if replacement_val.is_string() {
|
||
h.string_units(replacement_val)
|
||
} else {
|
||
Cow::Owned("undefined".encode_utf16().collect())
|
||
};
|
||
let result = if all {
|
||
replace_units_all(&s, &p, &r)
|
||
} else {
|
||
replace_units_first(&s, &p, &r)
|
||
};
|
||
return NativeResult::Ok(vm.new_string_units_owned(result));
|
||
}
|
||
|
||
// 分支 B2:正则 pattern 快路径——regex 为对象内裸指针(不占 vm 借用),
|
||
// receiver/replacement 共享借用零拷贝。
|
||
if this_val.is_string() && has_native_re && replacement_is_string {
|
||
let re_ptr = pattern_val.as_js_object_ptr();
|
||
let re = unsafe { &*re_ptr };
|
||
let fn_ptr = re.native_fn().unwrap();
|
||
// SAFETY: fn_ptr 持有 regexp_constructor 存放的 `Box<regress::Regex>` 指针。
|
||
let regex = unsafe { &*(fn_ptr.as_ptr() as *const regress::Regex) };
|
||
let h = &*vm;
|
||
let s = h.string_units(this_val);
|
||
let r: Cow<'_, [u16]> = if replacement_val.is_string() {
|
||
h.string_units(replacement_val)
|
||
} else {
|
||
Cow::Owned("undefined".encode_utf16().collect())
|
||
};
|
||
let result = regex_replace_manual_units(regex, &s, &r, is_global);
|
||
return NativeResult::Ok(vm.new_string_units_owned(result));
|
||
}
|
||
|
||
// 分支 C:一般路径(对象参与转换)。原始字符串 receiver 与对象 receiver
|
||
// 统一:参数转换前置(&mut 路径)后按单元序列扫描。
|
||
let s = try_string!(this_units(vm, args)).into_owned();
|
||
let pattern = if has_native_re {
|
||
Vec::new()
|
||
} else if args.len() < 2 {
|
||
"undefined".encode_utf16().collect()
|
||
} else {
|
||
try_string!(as_units(vm, pattern_val)).into_owned()
|
||
};
|
||
let replacement = if args.len() > 2 {
|
||
try_string!(as_units(vm, replacement_val)).into_owned()
|
||
} else {
|
||
"undefined".encode_utf16().collect()
|
||
};
|
||
|
||
// 统一扫描:命中编译正则走手动 $ 展开,否则字符串子串替换。
|
||
if has_native_re {
|
||
let re_ptr = pattern_val.as_js_object_ptr();
|
||
let re = unsafe { &*re_ptr };
|
||
let fn_ptr = re.native_fn().unwrap();
|
||
// SAFETY: fn_ptr 持有 regexp_constructor 存放的 `Box<regress::Regex>` 指针。
|
||
let regex = unsafe { &*(fn_ptr.as_ptr() as *const regress::Regex) };
|
||
let result = regex_replace_manual_units(regex, &s, &replacement, is_global);
|
||
return NativeResult::Ok(vm.new_string_units_owned(result));
|
||
}
|
||
let result = if all {
|
||
replace_units_all(&s, &pattern, &replacement)
|
||
} else {
|
||
replace_units_first(&s, &pattern, &replacement)
|
||
};
|
||
NativeResult::Ok(vm.new_string_units_owned(result))
|
||
}
|
||
|
||
/// `String.prototype.replace(pattern, replacement)`:替换首个匹配;
|
||
/// 支持 RegExp(global 全替换)、字符串以及函数替换器。
|
||
pub fn string_replace<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||
builtins_debug!("String.prototype.replace called with {} args", args.len());
|
||
string_replace_impl(vm, args, false)
|
||
}
|
||
|
||
/// `String.prototype.match(pattern)`:按 RegExp 匹配;global 返回全部匹配数组,
|
||
/// 否则返回首个匹配及捕获组,无匹配返回 null。
|
||
pub fn string_match_fn<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||
builtins_debug!("String.prototype.match called with {} args", args.len());
|
||
// 正则判定与参数字符串转换先行(&mut 路径),后借 this 匹配扫描。
|
||
let pattern_val = if args.len() >= 2 { Some(vm.reg(args[1])) } else { None };
|
||
let is_re = pattern_val.map(|v| is_regexp_obj(v, vm)).unwrap_or(false);
|
||
let pattern: Vec<u16> = match pattern_val {
|
||
Some(v) if !is_re => try_string!(as_units(vm, v)).into_owned(),
|
||
_ => Vec::new(),
|
||
};
|
||
let text = try_string!(this_text(vm, args));
|
||
if args.len() < 2 {
|
||
return NativeResult::Ok(JsValue::null());
|
||
}
|
||
let pattern_val = pattern_val.unwrap();
|
||
if is_re {
|
||
let re_ptr = pattern_val.as_js_object_ptr();
|
||
let re = unsafe { &*re_ptr };
|
||
let fn_ptr = match re.native_fn() {
|
||
Some(p) => p,
|
||
None => return NativeResult::Ok(JsValue::null()),
|
||
};
|
||
// SAFETY: fn_ptr 持有 regexp_constructor 存放的 `Box<regress::Regex>` 指针。
|
||
let regex = unsafe { &*(fn_ptr.as_ptr() as *const regress::Regex) };
|
||
let is_global = re.hash_props_vec().and_then(|v| v.get(3)).map(|v| v.as_bool()).unwrap_or(false);
|
||
if is_global {
|
||
let mut matches: Vec<Vec<u16>> = Vec::new();
|
||
text.for_each_match(regex, |m| {
|
||
let range = m.range();
|
||
matches.push(text.slice(range.start, range.end).into_owned());
|
||
});
|
||
if matches.is_empty() {
|
||
return NativeResult::Ok(JsValue::null());
|
||
}
|
||
return NativeResult::Ok(make_units_array(vm, matches));
|
||
}
|
||
if let Some(m) = text.find_from_units(regex, 0) {
|
||
let range = m.range();
|
||
let mut parts: Vec<Vec<u16>> = Vec::with_capacity(m.captures.len() + 1);
|
||
parts.push(text.slice(range.start, range.end).into_owned());
|
||
for i in 1..=m.captures.len() {
|
||
match m.group(i) {
|
||
Some(g) => parts.push(text.slice(g.start, g.end).into_owned()),
|
||
None => parts.push(Vec::new()),
|
||
}
|
||
}
|
||
return NativeResult::Ok(make_units_array(vm, parts));
|
||
}
|
||
return NativeResult::Ok(JsValue::null());
|
||
}
|
||
// 空模式命中串头:产出单元素空串数组(规格口径)。
|
||
if pattern.is_empty() {
|
||
return NativeResult::Ok(make_units_array(vm, vec![Vec::new()]));
|
||
}
|
||
let s = text.units();
|
||
if let Some(pos) = find_units(&s, &pattern, 0) {
|
||
let matched = s[pos..pos + pattern.len()].to_vec();
|
||
return NativeResult::Ok(make_units_array(vm, vec![matched]));
|
||
}
|
||
NativeResult::Ok(JsValue::null())
|
||
}
|
||
|
||
/// `String.prototype.search(pattern)`:返回首个匹配位置(码元),无匹配返回 -1。
|
||
pub fn string_search<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||
builtins_debug!("String.prototype.search called with {} args", args.len());
|
||
// 参数读取守卫先行:无参调用缺省 searchString 为 undefined(args 仅含 this 槽)。
|
||
let pattern_val = if args.len() >= 2 { vm.reg(args[1]) } else { JsValue::undefined() };
|
||
|
||
// 正则判定与参数转换先行(&mut 路径),后借 this 扫描。
|
||
let is_re = args.len() >= 2 && is_regexp_obj(pattern_val, vm);
|
||
let pattern: Vec<u16> = if args.len() >= 2 && !is_re {
|
||
try_string!(as_units(vm, pattern_val)).into_owned()
|
||
} else {
|
||
Vec::new()
|
||
};
|
||
let text = try_string!(this_text(vm, args));
|
||
if args.len() < 2 {
|
||
return NativeResult::Ok(JsValue::int(-1));
|
||
}
|
||
if is_re {
|
||
let re_ptr = pattern_val.as_js_object_ptr();
|
||
let re = unsafe { &*re_ptr };
|
||
let fn_ptr = match re.native_fn() {
|
||
Some(p) => p,
|
||
None => return NativeResult::Ok(JsValue::int(-1)),
|
||
};
|
||
// SAFETY: fn_ptr 持有 regexp_constructor 存放的 `Box<regress::Regex>` 指针。
|
||
let regex = unsafe { &*(fn_ptr.as_ptr() as *const regress::Regex) };
|
||
if let Some(m) = text.find_from_units(regex, 0) {
|
||
return NativeResult::Ok(JsValue::int(text.unit_pos(m.range().start) as i32));
|
||
}
|
||
return NativeResult::Ok(JsValue::int(-1));
|
||
}
|
||
// 空模式命中串头(规格口径)。
|
||
if pattern.is_empty() {
|
||
return NativeResult::Ok(JsValue::int(0));
|
||
}
|
||
let s = text.units();
|
||
if let Some(pos) = find_units(&s, &pattern, 0) {
|
||
return NativeResult::Ok(JsValue::int(pos as i32));
|
||
}
|
||
NativeResult::Ok(JsValue::int(-1))
|
||
}
|
||
|
||
/// `String.prototype.isWellFormed()`:字符串无孤立 surrogate(每个码元要么是
|
||
/// 合法字符,要么与相邻码元成代理对)时返回 true。
|
||
pub fn string_is_well_formed<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||
builtins_debug!("String.prototype.isWellFormed called with {} args", args.len());
|
||
let s = try_string!(this_units(vm, args));
|
||
let mut i = 0;
|
||
while i < s.len() {
|
||
let u = s[i];
|
||
if (0xDC00..=0xDFFF).contains(&u) {
|
||
return NativeResult::Ok(JsValue::bool(false));
|
||
}
|
||
if (0xD800..=0xDBFF).contains(&u) {
|
||
if !(i + 1 < s.len() && (0xDC00..=0xDFFF).contains(&s[i + 1])) {
|
||
return NativeResult::Ok(JsValue::bool(false));
|
||
}
|
||
i += 2;
|
||
} else {
|
||
i += 1;
|
||
}
|
||
}
|
||
NativeResult::Ok(JsValue::bool(true))
|
||
}
|
||
|
||
/// `String.prototype.toWellFormed()`:把孤立 surrogate 替换为 U+FFFD 返回新字符串。
|
||
pub fn string_to_well_formed<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||
builtins_debug!("String.prototype.toWellFormed called with {} args", args.len());
|
||
let s = try_string!(this_units(vm, args));
|
||
let mut out = Vec::with_capacity(s.len());
|
||
let mut i = 0;
|
||
while i < s.len() {
|
||
let u = s[i];
|
||
if (0xD800..=0xDBFF).contains(&u) {
|
||
if i + 1 < s.len() && (0xDC00..=0xDFFF).contains(&s[i + 1]) {
|
||
out.push(u);
|
||
out.push(s[i + 1]);
|
||
i += 2;
|
||
continue;
|
||
}
|
||
out.push(0xFFFD);
|
||
} else if (0xDC00..=0xDFFF).contains(&u) {
|
||
out.push(0xFFFD);
|
||
} else {
|
||
out.push(u);
|
||
}
|
||
i += 1;
|
||
}
|
||
NativeResult::Ok(vm.new_string_units_owned(out))
|
||
}
|
||
|
||
/// `String.prototype.normalize(form)`:按 NFC/NFD/NFKC/NFKD 规范化为 Unicode
|
||
/// 规范形式(良形段映射,孤立 surrogate 单元原样保留)。
|
||
pub fn string_normalize<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||
builtins_debug!("String.prototype.normalize called with {} args", args.len());
|
||
use unicode_normalization::UnicodeNormalization;
|
||
let form = if args.len() > 1 {
|
||
try_string!(as_units(vm, vm.reg(args[1]))).into_owned()
|
||
} else {
|
||
"NFC".encode_utf16().collect()
|
||
};
|
||
// 规范化形式名按良形文本比对(NFC 等名无孤立 surrogate)。
|
||
let form_name = String::from_utf16_lossy(&form);
|
||
let s = try_string!(this_units(vm, args));
|
||
let out = map_well_formed_segments(&s, |seg| match form_name.as_str() {
|
||
"NFD" => seg.nfd().collect(),
|
||
"NFKC" => seg.nfkc().collect(),
|
||
"NFKD" => seg.nfkd().collect(),
|
||
_ => seg.nfc().collect(),
|
||
});
|
||
NativeResult::Ok(vm.new_string_units_owned(out))
|
||
}
|
||
|
||
pub(crate) const MALL_INPUT: &str = "__mal_input__";
|
||
pub(crate) const MALL_INDEX: &str = "__mal_index__";
|
||
pub(crate) const MALL_RE: &str = "__mal_re__";
|
||
|
||
/// `String.prototype.matchAll(pattern)`:返回带 `next` 的迭代器,逐步产出全部匹配
|
||
/// (要求 RegExp 带 global 标志;普通字符串会被转义成等效正则)。包装器 input
|
||
/// 属性存原始字符串值(单元保真),index 属性为码元游标。
|
||
pub fn string_match_all<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||
builtins_debug!("String.prototype.matchAll called with {} args", args.len());
|
||
let this_val = vm.reg(args[0]);
|
||
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 input_val = match oxide_runtime_api::to_string_value_full(this_val, vm) {
|
||
Ok(v) => v,
|
||
Err(_) => {
|
||
if let Some(exc) = vm.take_uncaught_value() {
|
||
return NativeResult::Err(exc);
|
||
}
|
||
return NativeResult::Err(crate::error::create_type_error(vm, "Cannot convert value to a string"));
|
||
}
|
||
};
|
||
if args.len() < 2 {
|
||
builtins_error!("String.prototype.matchAll: invalid receiver");
|
||
return NativeResult::Err(JsValue::undefined());
|
||
}
|
||
let pattern_val = vm.reg(args[1]);
|
||
|
||
let re_obj = if is_regexp_obj(pattern_val, vm) {
|
||
let re_ptr = pattern_val.as_js_object_ptr();
|
||
let re = unsafe { &*re_ptr };
|
||
let is_global = re.hash_props_vec().and_then(|v| v.get(3)).map(|v| v.as_bool()).unwrap_or(false);
|
||
if !is_global {
|
||
return NativeResult::Err(crate::error::create_type_error(
|
||
vm,
|
||
"String.prototype.matchAll: regex must have global flag",
|
||
));
|
||
}
|
||
pattern_val
|
||
} else {
|
||
let pattern_units = try_string!(as_units(vm, pattern_val)).into_owned();
|
||
// 正则模式按文本编译:单元序列桥接为 lossy 文本(孤立 surrogate 映射
|
||
// FFFD,与 RegExp 构造器的文本口径编译一致)。
|
||
let pattern_str = String::from_utf16_lossy(&pattern_units);
|
||
let escaped = regress::escape(&pattern_str);
|
||
let rx_str = if escaped.is_empty() { String::from("(?:)") } else { format!("({})", escaped) };
|
||
let compiled = match regress::Regex::new(&rx_str) {
|
||
Ok(rx) => rx,
|
||
Err(e) => {
|
||
return NativeResult::Err(crate::error::create_syntax_error(vm, &format!("Invalid regex: {}", e)));
|
||
}
|
||
};
|
||
let object_proto = vm.session().builtin_world().object_proto.as_ptr() as *mut JsObject;
|
||
let mut stub = JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::from_js_object(object_proto));
|
||
// 载体专型标签:native_fn 槽的 Box 经 RegExp 同一守卫释放/深拷贝,
|
||
// 避免每次 matchAll 泄漏一个已编译正则。
|
||
stub.type_tag = oxide_types::object::JsObject::OBJ_TYPE_REGEX_STUB;
|
||
let boxed = Box::new(compiled);
|
||
let raw = Box::into_raw(boxed) as *const u8;
|
||
stub.set_native_fn(Some(unsafe { oxide_types::object::NativeFnPtr::from_raw(raw as *const ()) }));
|
||
let stub_ptr = vm.alloc_object(stub);
|
||
JsValue::from_js_object(stub_ptr)
|
||
};
|
||
|
||
// 构建包装对象。
|
||
builder_wrapper(vm, input_val, re_obj)
|
||
}
|
||
|
||
fn builder_wrapper<H: VmHost>(vm: &mut H, input_val: JsValue, re_obj: JsValue) -> NativeResult {
|
||
// matchAll 迭代器挂 %RegExpStringIteratorPrototype%(链到 %IteratorPrototype%),
|
||
// next 由原型提供(不挂实例 own)。
|
||
let regexp_iter_proto = vm.session().builtin_world().regexp_string_iterator_proto.as_ptr() as *mut JsObject;
|
||
let wrapper = vm.alloc_object(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::from_js_object(regexp_iter_proto)));
|
||
|
||
let wrapper_obj = unsafe { &mut *wrapper };
|
||
let input_si = vm.kernel_core().perm_interner().intern(MALL_INPUT).0;
|
||
let index_si = vm.kernel_core().perm_interner().intern(MALL_INDEX).0;
|
||
let re_si = vm.kernel_core().perm_interner().intern(MALL_RE).0;
|
||
|
||
vm.set_or_create_prop_value(wrapper_obj, input_si, input_val);
|
||
vm.set_or_create_prop_value(wrapper_obj, index_si, JsValue::int(0));
|
||
vm.set_or_create_prop_value(wrapper_obj, re_si, re_obj);
|
||
|
||
NativeResult::Ok(JsValue::from_js_object(wrapper))
|
||
}
|
||
|
||
/// `String.prototype[Symbol.iterator]()`:返回按码元迭代字符的迭代器。
|
||
///
|
||
/// # 步骤
|
||
/// 1. null/undefined 抛 TypeError(RequireObjectCoercible)
|
||
/// 2. this 经 ToString 完整转换(对象取 toString 结果,Symbol 抛 TypeError)
|
||
/// 3. 包成统一迭代器包装(next 逐码元产出,耗尽后 done)
|
||
///
|
||
/// # 边界与前提
|
||
/// - 对象 toString 抛出的原始异常原样传播
|
||
/// - 包装器挂 `%StringIteratorPrototype%` → `%IteratorPrototype%` 链
|
||
pub fn string_symbol_iterator<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||
let this_val = vm.reg(if args.is_empty() { 0 } else { args[0] });
|
||
if this_val.is_null() || this_val.is_undefined() {
|
||
return NativeResult::Err(crate::error::create_type_error(
|
||
vm,
|
||
"String.prototype[Symbol.iterator] called on null or undefined",
|
||
));
|
||
}
|
||
let s_val = match oxide_runtime_api::to_string_value_full(this_val, vm) {
|
||
Ok(v) => v,
|
||
Err(_) => {
|
||
// ToString 触发对象 toString/valueOf 抛出的原生异常须原样传播。
|
||
if let Some(exc) = vm.take_uncaught_value() {
|
||
return NativeResult::Err(exc);
|
||
}
|
||
return NativeResult::Err(crate::error::create_type_error(vm, "Cannot convert value to a string"));
|
||
}
|
||
};
|
||
// String 迭代器挂 %StringIteratorPrototype%(链到 %IteratorPrototype%):
|
||
// 包装机制复用通用路径,仅替换原型为中继层。
|
||
let string_iter_proto = vm.session().builtin_world().string_iterator_proto.as_ptr() as *mut JsObject;
|
||
match crate::iterator::make_iterator_for_value_with_proto(vm, s_val, string_iter_proto) {
|
||
Ok(iterator) => NativeResult::Ok(iterator),
|
||
Err(err) => NativeResult::Err(err),
|
||
}
|
||
}
|
||
|
||
/// `matchAll` 迭代器的 `next`:返回 `{value: 匹配数组, done}`,耗尽后 done 为 true。
|
||
/// 游标为码元位置(两臂统一口径);空匹配推进 1 个码元。
|
||
pub fn string_match_all_next<H: VmHost>(vm: &mut H, 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::error::create_type_error(vm, "matchAll next called on non-object"));
|
||
}
|
||
let wrapper = unsafe { &mut *this_val.as_js_object_ptr() };
|
||
let input_si = vm.kernel_core().perm_interner().intern(MALL_INPUT).0;
|
||
let index_si = vm.kernel_core().perm_interner().intern(MALL_INDEX).0;
|
||
let re_si = vm.kernel_core().perm_interner().intern(MALL_RE).0;
|
||
|
||
let input_val = match vm.ordinary_get(wrapper, input_si, this_val) {
|
||
Ok(v) if v.is_string() => v,
|
||
_ => return make_match_done_result(vm, JsValue::undefined()),
|
||
};
|
||
let idx_val = match vm.ordinary_get(wrapper, index_si, this_val) {
|
||
Ok(v) => v,
|
||
Err(_) => return make_match_done_result(vm, JsValue::undefined()),
|
||
};
|
||
let idx = if idx_val.is_int() { idx_val.as_int().max(0) as usize } else { 0 };
|
||
let re_val = match vm.ordinary_get(wrapper, re_si, this_val) {
|
||
Ok(v) if v.is_object() => v,
|
||
_ => return make_match_done_result(vm, JsValue::undefined()),
|
||
};
|
||
let re_obj = unsafe { &*re_val.as_js_object_ptr() };
|
||
let fn_ptr = match re_obj.native_fn() {
|
||
Some(p) => p,
|
||
None => return make_match_done_result(vm, JsValue::undefined()),
|
||
};
|
||
let regex = unsafe { &*(fn_ptr.as_ptr() as *const regress::Regex) };
|
||
|
||
// 按 input 载荷形态分臂:Flat 走字节通道(游标经码元↔字节换算),
|
||
// 其余走单元通道(游标即码元)。
|
||
// SAFETY: input_val 已校验为字符串值,裸指针借用压缩到单个表达式。
|
||
let sp = unsafe { &*input_val.as_string_ptr() };
|
||
// 耗尽守卫:空匹配正则(如 /(?:)/g、/a*/g)在串尾会反复命中同一末端空
|
||
// 匹配,游标推进越过码元总数后须直接 done,否则同一末端空匹配死循环。
|
||
let total_units = sp.utf16_len() as usize;
|
||
if idx > total_units {
|
||
return make_match_done_result(vm, JsValue::undefined());
|
||
}
|
||
let (next_idx, parts): (usize, Vec<Vec<u16>>) = if sp.is_flat() {
|
||
let s = sp.as_str();
|
||
match regex.find_from(s, unit_to_byte(s, idx)).next() {
|
||
Some(m) => {
|
||
let range = m.range();
|
||
let mut parts = Vec::with_capacity(m.captures.len() + 1);
|
||
parts.push(s[range.start..range.end].encode_utf16().collect());
|
||
for i in 1..=m.captures.len() {
|
||
match m.group(i) {
|
||
Some(g) => parts.push(s[g.start..g.end].encode_utf16().collect()),
|
||
None => parts.push(Vec::new()),
|
||
}
|
||
}
|
||
// 空匹配(range 无推进)须推进至少一个码元,否则同一位置反复
|
||
// 空匹配死循环;非空匹配游标落匹配末尾(码元口径)。
|
||
let end_units = byte_to_unit(s, range.end);
|
||
let next_idx = if range.end > range.start { end_units } else { end_units + 1 };
|
||
(next_idx, parts)
|
||
}
|
||
None => (0, Vec::new()),
|
||
}
|
||
} else {
|
||
let u = sp.units();
|
||
match regex.find_from_utf16(&u, idx).next() {
|
||
Some(m) => {
|
||
let range = m.range();
|
||
let mut parts = Vec::with_capacity(m.captures.len() + 1);
|
||
parts.push(u[range.start..range.end].to_vec());
|
||
for i in 1..=m.captures.len() {
|
||
match m.group(i) {
|
||
Some(g) => parts.push(u[g.start..g.end].to_vec()),
|
||
None => parts.push(Vec::new()),
|
||
}
|
||
}
|
||
let next_idx = if range.end > range.start { range.end } else { range.end + 1 };
|
||
(next_idx, parts)
|
||
}
|
||
None => (0, Vec::new()),
|
||
}
|
||
};
|
||
|
||
if parts.is_empty() {
|
||
return make_match_done_result(vm, JsValue::undefined());
|
||
}
|
||
vm.set_or_create_prop_value(wrapper, index_si, JsValue::int(next_idx as i32));
|
||
let value = make_units_array(vm, parts);
|
||
make_match_done_result(vm, value)
|
||
}
|
||
|
||
fn make_match_done_result<H: VmHost>(vm: &mut H, value: JsValue) -> NativeResult {
|
||
let done = value.is_undefined();
|
||
let object_proto = vm.session().builtin_world().object_proto.as_ptr() as *mut JsObject;
|
||
let obj = vm.alloc_object(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::from_js_object(object_proto)));
|
||
let value_si = vm.kernel_core().perm_interner().intern("value").0;
|
||
let done_si = vm.kernel_core().perm_interner().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));
|
||
NativeResult::Ok(JsValue::from_js_object(obj))
|
||
}
|
||
|
||
/// `String.prototype.replaceAll(pattern, replacement)`:替换全部匹配
|
||
/// (RegExp 或字符串模式)。
|
||
pub fn string_replace_all<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||
builtins_debug!("String.prototype.replaceAll called with {} args", args.len());
|
||
string_replace_impl(vm, args, true)
|
||
}
|
||
|
||
/// `String.raw(template, ...substitutions)`:从模板对象的 `raw` 数组元素与
|
||
/// substitutions 交错拼接,返回原始字符串(单元口径,lone surrogate 保真)。
|
||
///
|
||
/// # 步骤
|
||
/// 1. 取 template 的 `raw` 属性(必须为对象,否则抛 TypeError)
|
||
/// 2. 取 raw 的 `length` 属性(ToLength)
|
||
/// 3. 逐下标读 raw[i] 拼接;仅在段间(i + 1 < length)追加 substitutions[i]
|
||
///
|
||
/// # 边界与前提
|
||
/// - raw[i] 缺失时视为 undefined(ToString 为 "undefined")
|
||
/// - substitutions 缺失时段间追加空串,末段后不追加任何内容
|
||
pub fn string_raw<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||
builtins_debug!("String.raw called with {} args", args.len());
|
||
// Step 1: 取 template 参数(args[1] 为 this 区域后的首个实参)。
|
||
if args.len() < 2 {
|
||
return NativeResult::Err(crate::error::create_type_error(vm, "String.raw: template is required"));
|
||
}
|
||
let template_val = vm.reg(args[1]);
|
||
if !template_val.is_object() || template_val.as_js_object_ptr().is_null() {
|
||
return NativeResult::Err(crate::error::create_type_error(vm, "String.raw: template must be an object"));
|
||
}
|
||
let template_ptr = template_val.as_js_object_ptr();
|
||
|
||
// Step 2: 取 template.raw 属性。
|
||
let raw_si = vm.kernel_core().perm_interner().intern("raw").0;
|
||
let template_obj = unsafe { &*template_ptr };
|
||
let raw_val = match vm.ordinary_get(template_obj, raw_si, template_val) {
|
||
Ok(v) => v,
|
||
Err(e) => {
|
||
let exc = vm
|
||
.take_uncaught_value()
|
||
.unwrap_or_else(|| crate::error::create_type_error(vm, &e));
|
||
return NativeResult::Err(exc);
|
||
}
|
||
};
|
||
if !raw_val.is_object() || raw_val.as_js_object_ptr().is_null() {
|
||
return NativeResult::Err(crate::error::create_type_error(vm, "String.raw: template.raw must be an object"));
|
||
}
|
||
let raw_ptr = raw_val.as_js_object_ptr();
|
||
|
||
// Step 3: 取 raw.length(ToLength,Symbol 抛 TypeError)。
|
||
let length_si = vm.kernel_core().perm_interner().intern("length").0;
|
||
let raw_obj = unsafe { &*raw_ptr };
|
||
let raw_len = match vm.ordinary_get(raw_obj, length_si, raw_val) {
|
||
Ok(v) => {
|
||
// ToLength:经 to_number_full(Symbol 抛 TypeError / 对象 ToPrimitive 异常
|
||
// 原样传播),再按 ToLength 归一并截断(防超大 raw 数组导致 OOM)。
|
||
match oxide_runtime_api::to_number_full(v, vm) {
|
||
Ok(n) => {
|
||
let len = if n.is_nan() || n <= 0.0 { 0.0 } else { n.min(9_007_199_254_740_991.0) };
|
||
(len.trunc() as u64).min(0x1FFFFF) as usize
|
||
}
|
||
Err(msg) => {
|
||
let exc = vm
|
||
.take_uncaught_value()
|
||
.unwrap_or_else(|| crate::error::create_type_error(vm, &msg));
|
||
return NativeResult::Err(exc);
|
||
}
|
||
}
|
||
}
|
||
Err(e) => {
|
||
let exc = vm
|
||
.take_uncaught_value()
|
||
.unwrap_or_else(|| crate::error::create_type_error(vm, &e));
|
||
return NativeResult::Err(exc);
|
||
}
|
||
};
|
||
|
||
// Step 4: 逐下标拼接 raw[i] + substitutions[i]。
|
||
let mut result: Vec<u16> = Vec::new();
|
||
let raw_obj_ref = unsafe { &*raw_ptr };
|
||
for i in 0..raw_len {
|
||
// 读 raw[i]:用 property_key_si 把整数 i 映射为整数键 si,
|
||
// 再经 ordinary_get 沿原型链查找(触发 getter)。
|
||
let index_key = vm.property_key_si(JsValue::int(i as i32));
|
||
let raw_elem = vm.ordinary_get(raw_obj_ref, index_key, raw_val);
|
||
let raw_units = match raw_elem {
|
||
// ToString 完整路径:对象经 ToPrimitive(异常原样传播),Symbol 抛 TypeError。
|
||
Ok(v) => match oxide_runtime_api::to_string_value_full(v, vm) {
|
||
Ok(sv) => vm.string_units(sv).into_owned(),
|
||
Err(msg) => {
|
||
let exc = vm
|
||
.take_uncaught_value()
|
||
.unwrap_or_else(|| crate::error::create_type_error(vm, &msg));
|
||
return NativeResult::Err(exc);
|
||
}
|
||
},
|
||
Err(_) => {
|
||
if let Some(exc) = vm.take_uncaught_value() {
|
||
return NativeResult::Err(exc);
|
||
}
|
||
"undefined".encode_utf16().collect()
|
||
}
|
||
};
|
||
result.extend(raw_units);
|
||
|
||
// 段间(i + 1 < length)才追加 substitutions[i];substitutions 缺失时
|
||
// 追加空串(spec 24.b.i),末段后不再追加任何内容。
|
||
if i + 1 < raw_len {
|
||
let sub_idx = i + 2;
|
||
if sub_idx < args.len() {
|
||
let sub_val = vm.reg(args[sub_idx]);
|
||
// ToString 完整路径:对象 ToPrimitive 异常原样传播,Symbol 抛 TypeError。
|
||
let sub_units = match oxide_runtime_api::to_string_value_full(sub_val, vm) {
|
||
Ok(sv) => vm.string_units(sv).into_owned(),
|
||
Err(msg) => {
|
||
let exc = vm
|
||
.take_uncaught_value()
|
||
.unwrap_or_else(|| crate::error::create_type_error(vm, &msg));
|
||
return NativeResult::Err(exc);
|
||
}
|
||
};
|
||
result.extend(sub_units);
|
||
}
|
||
}
|
||
}
|
||
NativeResult::Ok(vm.new_string_units_owned(result))
|
||
}
|