mirror of
https://github.com/pjh456/OxideJS.git
synced 2026-10-25 03:01:12 +08:00
1385 lines
63 KiB
Rust
1385 lines
63 KiB
Rust
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;
|
||
use crate::regexp::build_groups_object;
|
||
|
||
use super::common::{try_string, MatchText};
|
||
use super::{
|
||
as_units, find_units, make_string_array_values, make_units_array, map_well_formed_segments, split_limit_to_uint32,
|
||
this_text, this_units,
|
||
};
|
||
|
||
// ── 正则替换(单元口径) ────────────────────────────────────────────────
|
||
|
||
/// 调用函数 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)))),
|
||
}
|
||
}
|
||
|
||
/// 判断值是否为 RegExp 对象:非对象、空指针、原型非对象均返回 false,
|
||
/// 原型指针与 `%RegExpPrototype%` 恒等比较。
|
||
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)
|
||
}
|
||
|
||
/// IsRegExp 口径判定:非对象 false;对象一律 Get(@@match) 后 ToBoolean
|
||
/// (真 RegExp 亦读,getter 副作用可观测;getter 抛错恢复原异常上抛)。
|
||
/// 供正则匹配族与 includes/startsWith/endsWith 的 IsRegExp 前置判共用。
|
||
pub(crate) fn is_regexp_live<H: VmHost>(vm: &mut H, val: JsValue) -> Result<bool, JsValue> {
|
||
if !val.is_object() {
|
||
return Ok(false);
|
||
}
|
||
let match_key = oxide_types::private_key::make_well_known_symbol_key(1);
|
||
let ptr = val.as_js_object_ptr();
|
||
// SAFETY: val 已校验为非空对象值。
|
||
let obj = unsafe { &*ptr };
|
||
let b = match vm.ordinary_get(obj, match_key, val) {
|
||
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 @@match"));
|
||
}
|
||
};
|
||
Ok(oxide_runtime_api::to_boolean(b))
|
||
}
|
||
|
||
/// GetMethod(searchValue, @@replace):undefined/null 返回 None,不可调用抛
|
||
/// TypeError,属性读抛错恢复原异常值。
|
||
fn rx_get_replace_method<H: VmHost>(
|
||
vm: &mut H, rx: *mut JsObject, this_val: JsValue,
|
||
) -> Result<Option<JsValue>, JsValue> {
|
||
let replace_key = oxide_types::private_key::make_well_known_symbol_key(2);
|
||
// SAFETY: this_val 为对象值,rx 与其同址。
|
||
let func = match vm.ordinary_get(unsafe { &*rx }, replace_key, this_val) {
|
||
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 @@replace"));
|
||
}
|
||
};
|
||
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.replace is not callable"));
|
||
}
|
||
Ok(Some(func))
|
||
}
|
||
|
||
/// 字符串臂的替换形态:功能替换器(回调值)或非功能替换串单元序列,
|
||
/// 恰好其一在场。
|
||
enum ArmReplacer<'a> {
|
||
Fn(JsValue),
|
||
Text(&'a [u16]),
|
||
}
|
||
|
||
/// 字符串臂:子串定位 + 逐命中替换。功能替换器经
|
||
/// Call(replaceValue, undefined, «matched, position, string»)+ToString;非功能
|
||
/// 走 GetSubstitution(捕获表空、namedCaptures undefined)。空模式在每个码元
|
||
/// 边界命中(replaceAll 含串尾,replace 仅串首)。
|
||
fn string_arm_replace<H: VmHost>(
|
||
vm: &mut H, text: &[u16], search: &[u16], replacer: &ArmReplacer<'_>, all: bool, s_val: JsValue,
|
||
) -> NativeResult {
|
||
let search_length = search.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, search, 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 matched = &text[p..p + search_length];
|
||
let repl = match replacer {
|
||
ArmReplacer::Fn(f) => {
|
||
let cb_args = [vm.new_string_units(matched), JsValue::int(p as i32), s_val];
|
||
try_string!(call_replacer(vm, *f, &cb_args))
|
||
}
|
||
ArmReplacer::Text(r) => match crate::regexp::get_substitution_units(vm, matched, text, p, &[], None, r) {
|
||
Ok(u) => u,
|
||
Err(e) => return NativeResult::Err(e),
|
||
},
|
||
};
|
||
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))
|
||
}
|
||
|
||
// ── 拆分 / 正则匹配 ─────────────────────────────────────────────────────
|
||
|
||
/// 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());
|
||
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"))
|
||
}
|
||
};
|
||
}
|
||
}
|
||
|
||
// string = ToString(this)。
|
||
let s: Vec<u16> = try_string!(this_units(vm, args)).into_owned();
|
||
|
||
// 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 sep_units.is_empty() {
|
||
// 每单元一个元素(ASCII 走单字符缓存零分配,其余 1 单元串创建)。
|
||
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]),
|
||
});
|
||
}
|
||
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() >= lim {
|
||
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` 共用的分叉实现:
|
||
///
|
||
/// # 步骤
|
||
/// 1. receiver 前置校验(RequireObjectCoercible,纯 is_* 读取)。
|
||
/// 2. searchValue 非 null/undefined 时:IsRegExp(真 RegExp 原型恒等;其余对象
|
||
/// Get @@match 后 ToBoolean,getter 抛错传播);replaceAll 命中正则时 live
|
||
/// Get "flags" + RequireObjectCoercible + ToString 须含 "g"。
|
||
/// 3. GetMethod(searchValue, @@replace) 定义(可调用)则
|
||
/// Call(matcher, searchValue, «this, replaceValue»),结果原值返回(不
|
||
/// ToString);真 RegExp 默认经此委托 regexp_symbol_replace(参数槽位与
|
||
/// 直接派发一致)。
|
||
/// 4. 字符串臂:string = ToString(this)、searchString = ToString(searchValue)
|
||
/// (异常传播),子串定位后逐命中替换(string_arm_replace)。
|
||
///
|
||
/// # 边界与前提
|
||
/// - 缺省 searchValue/replaceValue 均按 ToString(undefined)="undefined" 处理
|
||
/// (`s.replace()` 全缺省等价于把 "undefined" 替换为 "undefined",结果与
|
||
/// 原串一致;`s.replace("b")` 得到 "aundefinedc" 而非 "ac")。
|
||
/// - searchValue 为 null/undefined 时跳过整段第 2 步(无 IsRegExp/GetMethod)。
|
||
/// - searchValue 的 @@replace 为 undefined/null(含真 RegExp 自置 undefined)
|
||
/// 回退字符串臂,searchString 取 ToString(searchValue)。
|
||
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() };
|
||
|
||
// 第 2 步:searchValue 非 null/undefined 的对象走 IsRegExp + flags +
|
||
// GetMethod;matcher 定义则 Call 后原值返回。
|
||
if !pattern_val.is_null() && !pattern_val.is_undefined() && pattern_val.is_object() {
|
||
let is_regexp = match is_regexp_live(vm, pattern_val) {
|
||
Ok(b) => b,
|
||
Err(e) => return NativeResult::Err(e),
|
||
};
|
||
if all && is_regexp {
|
||
// 规范序:Get "flags"(单读,getter 异常传播)→
|
||
// RequireObjectCoercible(undefined/null 抛 TypeError)→
|
||
// ToString 须含 "g"(转换异常传播)。
|
||
let re_ptr = pattern_val.as_js_object_ptr();
|
||
// SAFETY: pattern_val 已校验为非空对象值。
|
||
let flags_val = match vm.ordinary_get(
|
||
unsafe { &*re_ptr },
|
||
vm.kernel_core().perm_interner().intern("flags").0,
|
||
pattern_val,
|
||
) {
|
||
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 read flags"));
|
||
}
|
||
};
|
||
if flags_val.is_undefined() || flags_val.is_null() {
|
||
return NativeResult::Err(crate::error::create_type_error(
|
||
vm,
|
||
"String.prototype.replaceAll called on a regex without the global flag",
|
||
));
|
||
}
|
||
let flags = match oxide_runtime_api::to_string_value_full(flags_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"));
|
||
}
|
||
};
|
||
let flags_str = String::from_utf16_lossy(&vm.string_units(flags));
|
||
if !flags_str.contains('g') {
|
||
return NativeResult::Err(crate::error::create_type_error(
|
||
vm,
|
||
"String.prototype.replaceAll called on a regex without the global flag",
|
||
));
|
||
}
|
||
}
|
||
let re_ptr = pattern_val.as_js_object_ptr();
|
||
let matcher = match rx_get_replace_method(vm, re_ptr, pattern_val) {
|
||
Ok(m) => m,
|
||
Err(e) => return NativeResult::Err(e),
|
||
};
|
||
if let Some(matcher) = matcher {
|
||
// Call(matcher, searchValue, « this, replaceValue »)。
|
||
return match vm.call_function_sync(matcher, pattern_val, &[this_val, replacement_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, "replace matcher call failed"))
|
||
}
|
||
};
|
||
}
|
||
}
|
||
|
||
// 字符串臂:string/searchString 完整 ToString(对象 toString/valueOf 抛错
|
||
// 原样传播);replacer 功能判定 IsCallable,非功能替换串先 ToString
|
||
// (Symbol 抛 TypeError,对象转换抛错传播)。
|
||
let (s_units, s_val) = if this_val.is_string() {
|
||
// SAFETY: this_val 为字符串值,借用即时消费。
|
||
let sp = unsafe { &*this_val.as_string_ptr() };
|
||
(sp.units().into_owned(), this_val)
|
||
} else {
|
||
let v = 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"));
|
||
}
|
||
};
|
||
// SAFETY: v 为字符串值,借用即时消费。
|
||
let sp = unsafe { &*v.as_string_ptr() };
|
||
(sp.units().into_owned(), v)
|
||
};
|
||
let search_units = if pattern_val.is_string() {
|
||
// SAFETY: pattern_val 为字符串值,借用即时消费。
|
||
let sp = unsafe { &*pattern_val.as_string_ptr() };
|
||
sp.units().into_owned()
|
||
} else if pattern_val.is_undefined() {
|
||
"undefined".encode_utf16().collect()
|
||
} else {
|
||
match oxide_runtime_api::to_units_full(pattern_val, vm) {
|
||
Ok(u) => u,
|
||
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"));
|
||
}
|
||
}
|
||
};
|
||
let functional = crate::iterator::is_callable(replacement_val);
|
||
let replacement_units: Vec<u16> = if functional {
|
||
Vec::new()
|
||
} else {
|
||
match crate::regexp::replacement_units(vm, replacement_val) {
|
||
Ok(u) => u,
|
||
Err(e) => return NativeResult::Err(e),
|
||
}
|
||
};
|
||
let replacer = if functional {
|
||
ArmReplacer::Fn(replacement_val)
|
||
} else {
|
||
ArmReplacer::Text(&replacement_units)
|
||
};
|
||
string_arm_replace(vm, &s_units, &search_units, &replacer, all, s_val)
|
||
}
|
||
|
||
/// `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);
|
||
// global 标志前置读:flags 串共享借用与下方 text 转换的 &mut 借用不可重叠。
|
||
let is_global = if is_re {
|
||
let re_val = match pattern_val {
|
||
Some(v) => v,
|
||
None => return NativeResult::Err(crate::error::create_type_error(vm, "expected regexp")),
|
||
};
|
||
let re_ptr = re_val.as_js_object_ptr();
|
||
// SAFETY: is_re 已保证 pattern_val 为非空对象且 proto 恒等 RegExp.prototype。
|
||
let re = unsafe { &*re_ptr };
|
||
re.native_fn().is_some() && crate::regexp::regexp_has_flag(vm, re, 'g')
|
||
} else {
|
||
false
|
||
};
|
||
let text = try_string!(this_text(vm, args));
|
||
// 缺参按 undefined 模式(规范:构造空模式正则,串头命中)。
|
||
let pattern_val = match pattern_val {
|
||
Some(v) => v,
|
||
None => JsValue::undefined(),
|
||
};
|
||
if is_re {
|
||
let re_ptr = pattern_val.as_js_object_ptr();
|
||
if is_global {
|
||
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 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));
|
||
}
|
||
// 非 global:GetMethod(rx, @@match) + Invoke,结果原值返回
|
||
// (默认 @@match 非 global 臂交付 exec 结果本体,index/input/groups/
|
||
// indices 与 exec 同源;影子 @@match 可替换)。
|
||
let s_units = text.units().into_owned();
|
||
let s_val = vm.new_string_units_owned(s_units);
|
||
let matcher = match rx_get_match_method(vm, re_ptr, pattern_val) {
|
||
Ok(v) => v,
|
||
Err(e) => return NativeResult::Err(e),
|
||
};
|
||
return match vm.call_function_sync(matcher, pattern_val, &[s_val]) {
|
||
Ok(r) => NativeResult::Ok(r),
|
||
Err(e) => NativeResult::Err(crate::iterator::engine_error(vm, &e)),
|
||
};
|
||
}
|
||
// string 值物化一次(text 借用至此结束),供下方两臂共用。
|
||
let s_units = text.units().into_owned();
|
||
let s_val = vm.new_string_units_owned(s_units);
|
||
// 对象模式:GetMethod(pattern, @@match) 先于构造(规范序);matcher 非
|
||
// undefined 则 Call(matcher, pattern, «string») 原值返回(自定义对象与
|
||
// 自置 @@match 的载体均走此臂)。
|
||
if pattern_val.is_object() {
|
||
let matcher = match rx_get_match_method_opt(vm, pattern_val.as_js_object_ptr(), pattern_val) {
|
||
Ok(m) => m,
|
||
Err(e) => return NativeResult::Err(e),
|
||
};
|
||
if let Some(matcher) = matcher {
|
||
return match vm.call_function_sync(matcher, pattern_val, &[s_val]) {
|
||
Ok(r) => NativeResult::Ok(r),
|
||
Err(e) => NativeResult::Err(crate::iterator::engine_error(vm, &e)),
|
||
};
|
||
}
|
||
}
|
||
// 非对象 / matcher undefined:Construct %RegExp%(undefined → 空模式、其余
|
||
// 经 ToString),再 GetMethod(rx, @@match) + Invoke;转换抛错(含 Symbol)
|
||
// 原样传播。
|
||
let regexp_ctor = vm.session().builtin_world().regexp_constructor.as_ptr() as *mut JsObject;
|
||
let rx_val = match vm.construct_ctor(JsValue::from_js_object(regexp_ctor), &[pattern_val]) {
|
||
Ok(v) => v,
|
||
Err(e) => return NativeResult::Err(e),
|
||
};
|
||
if !rx_val.is_object() {
|
||
return NativeResult::Err(crate::error::create_type_error(
|
||
vm,
|
||
"match pattern constructor must return an object",
|
||
));
|
||
}
|
||
let matcher = match rx_get_match_method(vm, rx_val.as_js_object_ptr(), rx_val) {
|
||
Ok(v) => v,
|
||
Err(e) => return NativeResult::Err(e),
|
||
};
|
||
match vm.call_function_sync(matcher, rx_val, &[s_val]) {
|
||
Ok(r) => NativeResult::Ok(r),
|
||
Err(e) => NativeResult::Err(crate::iterator::engine_error(vm, &e)),
|
||
}
|
||
}
|
||
|
||
/// GetMethod(rx, @@match):可调用返回之,null/undefined/不可调用抛 TypeError;
|
||
/// 属性读抛错恢复原异常值。
|
||
fn rx_get_match_method<H: VmHost>(vm: &mut H, rx: *mut JsObject, this_val: JsValue) -> Result<JsValue, JsValue> {
|
||
let match_key = oxide_types::private_key::make_well_known_symbol_key(1);
|
||
let matcher = match vm.ordinary_get(unsafe { &*rx }, match_key, this_val) {
|
||
Ok(v) => v,
|
||
Err(e) => return Err(crate::iterator::engine_error(vm, &e)),
|
||
};
|
||
if !crate::iterator::is_callable(matcher) {
|
||
return Err(crate::error::create_type_error(vm, "RegExp @@match is not callable"));
|
||
}
|
||
Ok(matcher)
|
||
}
|
||
|
||
/// GetMethod(rx, @@match) 的 Option 形态:undefined/null 返回 None(落构造
|
||
/// 路径),不可调用抛 TypeError,属性读抛错恢复原异常值。
|
||
fn rx_get_match_method_opt<H: VmHost>(
|
||
vm: &mut H, rx: *mut JsObject, this_val: JsValue,
|
||
) -> Result<Option<JsValue>, JsValue> {
|
||
let match_key = oxide_types::private_key::make_well_known_symbol_key(1);
|
||
let func = match vm.ordinary_get(unsafe { &*rx }, match_key, this_val) {
|
||
Ok(v) => v,
|
||
Err(e) => return Err(crate::iterator::engine_error(vm, &e)),
|
||
};
|
||
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.match is not callable"));
|
||
}
|
||
Ok(Some(func))
|
||
}
|
||
|
||
/// GetMethod(rx, @@search):undefined/null 返回 None(落构造路径),不可调用
|
||
/// 抛 TypeError,属性读抛错恢复原异常值。
|
||
fn rx_get_search_method<H: VmHost>(
|
||
vm: &mut H, rx: *mut JsObject, this_val: JsValue,
|
||
) -> Result<Option<JsValue>, JsValue> {
|
||
let search_key = oxide_types::private_key::make_well_known_symbol_key(3);
|
||
let func = match vm.ordinary_get(unsafe { &*rx }, search_key, this_val) {
|
||
Ok(v) => v,
|
||
Err(e) => return Err(crate::iterator::engine_error(vm, &e)),
|
||
};
|
||
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.search is not callable"));
|
||
}
|
||
Ok(Some(func))
|
||
}
|
||
|
||
/// `String.prototype.search(pattern)`:返回首个匹配位置(码元),无匹配返回 -1。
|
||
///
|
||
/// # 步骤
|
||
/// 1. string = ToString(this)(转换异常传播)。
|
||
/// 2. pattern 为对象时:GetMethod(pattern, @@search),searcher 非 undefined 则
|
||
/// Call(searcher, pattern, «string») 原值返回(真 RegExp 默认 @@search 与
|
||
/// 自定义对象均走此臂)。
|
||
/// 3. Construct %RegExp%(null/undefined 映射对应文本,其余经 ToString)→
|
||
/// GetMethod(rx, @@search) + Invoke(rx, «string»)。
|
||
///
|
||
/// # 边界与前提
|
||
/// - 无参 / null 模式走构造路径:空模式命中串头返回 0。
|
||
/// - 构造路径不预转换 pattern:构造器内部完成 ToString(异常原样传播)。
|
||
pub fn string_search<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||
builtins_debug!("String.prototype.search called with {} args", args.len());
|
||
let pattern_val = if args.len() >= 2 { vm.reg(args[1]) } else { JsValue::undefined() };
|
||
let text = try_string!(this_text(vm, args));
|
||
// string 值物化(text 借用结束),供下方两臂共用。
|
||
let s_units = text.units().into_owned();
|
||
let s_val = vm.new_string_units_owned(s_units);
|
||
|
||
// 对象模式:GetMethod(pattern, @@search) 先于构造(规范序);searcher 非
|
||
// undefined 则 Call(searcher, pattern, «string») 原值返回。
|
||
if pattern_val.is_object() {
|
||
let searcher = match rx_get_search_method(vm, pattern_val.as_js_object_ptr(), pattern_val) {
|
||
Ok(m) => m,
|
||
Err(e) => return NativeResult::Err(e),
|
||
};
|
||
if let Some(searcher) = searcher {
|
||
return match vm.call_function_sync(searcher, pattern_val, &[s_val]) {
|
||
Ok(r) => NativeResult::Ok(r),
|
||
Err(e) => NativeResult::Err(crate::iterator::engine_error(vm, &e)),
|
||
};
|
||
}
|
||
}
|
||
|
||
// 非对象 / searcher undefined:Construct %RegExp% + GetMethod(rx, @@search)
|
||
// + Invoke(rx, «string»);转换抛错(含 Symbol)原样传播。
|
||
let regexp_ctor = vm.session().builtin_world().regexp_constructor.as_ptr() as *mut JsObject;
|
||
let rx_val = match vm.construct_ctor(JsValue::from_js_object(regexp_ctor), &[pattern_val]) {
|
||
Ok(v) => v,
|
||
Err(e) => return NativeResult::Err(e),
|
||
};
|
||
if !rx_val.is_object() {
|
||
return NativeResult::Err(crate::error::create_type_error(
|
||
vm,
|
||
"search pattern constructor must return an object",
|
||
));
|
||
}
|
||
let searcher = match rx_get_search_method(vm, rx_val.as_js_object_ptr(), rx_val) {
|
||
Ok(m) => m,
|
||
Err(e) => return NativeResult::Err(e),
|
||
};
|
||
match searcher {
|
||
Some(searcher) => match vm.call_function_sync(searcher, rx_val, &[s_val]) {
|
||
Ok(r) => NativeResult::Ok(r),
|
||
Err(e) => NativeResult::Err(crate::iterator::engine_error(vm, &e)),
|
||
},
|
||
None => NativeResult::Err(crate::error::create_type_error(vm, "RegExp @@search is not callable")),
|
||
}
|
||
}
|
||
|
||
/// `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;
|
||
// form 缺省或 undefined 按 "NFC" 处理(规范第 3 步),其余经 ToString。
|
||
let form_val = if args.len() > 1 { vm.reg(args[1]) } else { JsValue::undefined() };
|
||
let form = if form_val.is_undefined() {
|
||
"NFC".encode_utf16().collect()
|
||
} else {
|
||
try_string!(as_units(vm, form_val)).into_owned()
|
||
};
|
||
// 规范化形式名按良形文本比对(NFC 等名无孤立 surrogate)。
|
||
let form_name = String::from_utf16_lossy(&form);
|
||
// this 转换先于 form 校验(规范第 2 步先于第 4 步)。
|
||
let s = try_string!(this_units(vm, args));
|
||
// form 非四合法值抛 RangeError。
|
||
match form_name.as_str() {
|
||
"NFC" | "NFD" | "NFKC" | "NFKD" => {}
|
||
_ => return NativeResult::Err(crate::error::create_range_error(vm, "normalize form is not valid")),
|
||
}
|
||
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_RE: &str = "__mal_re__";
|
||
pub(crate) const MALL_DONE: &str = "__mal_done__";
|
||
pub(crate) const MALL_GLOBAL: &str = "__mal_global__";
|
||
pub(crate) const MALL_UNICODE: &str = "__mal_unicode__";
|
||
|
||
/// `String.prototype.matchAll(pattern)`:返回带 `next` 的迭代器,逐步产出全部匹配
|
||
/// (要求 RegExp 带 global 标志;普通字符串会被转义成等效正则)。包装器 input
|
||
/// 属性存原始字符串值(单元保真),游标归匹配器 lastIndex。
|
||
///
|
||
/// # 步骤
|
||
/// 1. receiver 前置校验(RequireObjectCoercible,纯 is_* 读取)。
|
||
/// 2. pattern 为 null/undefined:跳过对象分支,直接构造路径。
|
||
/// 3. pattern 为对象:真 RegExp 先 flags 单读 g 判定;GetMethod 全链单读,
|
||
/// undefined/null 落构造路径,非 callable 非空值抛 TypeError,可调用则
|
||
/// Call(matcher, pattern, « thisValue ») 原值返回。
|
||
/// 4. 非对象 pattern:跳过对象分支,按字面文本编译载体正则。
|
||
///
|
||
/// # 边界与前提
|
||
/// - ToString(thisValue) 延迟到构造路径实际消费(matcher 直调臂不触发)。
|
||
/// - 构造路径(RegExpCreate(pattern, "g") + Invoke)复用
|
||
/// `match_all_construct_invoke`,无 callable @@matchAll 时抛 TypeError。
|
||
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]);
|
||
// receiver 前置校验:null/undefined/symbol 抛 TypeError(规范第 1-2 步
|
||
// 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"));
|
||
}
|
||
if args.len() < 2 {
|
||
builtins_error!("String.prototype.matchAll: invalid receiver");
|
||
return NativeResult::Err(JsValue::undefined());
|
||
}
|
||
let pattern_val = vm.reg(args[1]);
|
||
|
||
// 对象分支条件(IsObject)为假:null/undefined pattern 跳过 flags/GetMethod
|
||
// 全部步骤,直接落构造路径。
|
||
if pattern_val.is_null() || pattern_val.is_undefined() {
|
||
return match_all_construct_invoke(vm, this_val, pattern_val);
|
||
}
|
||
|
||
if pattern_val.is_object() {
|
||
// 真 RegExp:flags 单读原始值(getter 副作用/抛错只观察一次)→
|
||
// RequireObjectCoercible → ToString 含 "g" 判定;g 判定先于 GetMethod。
|
||
if is_regexp_obj(pattern_val, vm) {
|
||
let re_ptr = pattern_val.as_js_object_ptr();
|
||
// SAFETY: pattern_val 已校验为非空对象值。
|
||
let re_obj = unsafe { &*re_ptr };
|
||
let flags_si = vm.kernel_core().perm_interner().intern("flags").0;
|
||
let flags_val = match vm.ordinary_get(re_obj, flags_si, pattern_val) {
|
||
Ok(v) => v,
|
||
Err(e) => return NativeResult::err(crate::iterator::engine_error(vm, &e)),
|
||
};
|
||
if flags_val.is_undefined() || flags_val.is_null() {
|
||
return NativeResult::Err(crate::error::create_type_error(
|
||
vm,
|
||
"String.prototype.matchAll: regex must have global flag",
|
||
));
|
||
}
|
||
let flags_sv = match oxide_runtime_api::to_string_value_full(flags_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 !String::from_utf16_lossy(&vm.string_units(flags_sv)).contains('g') {
|
||
return NativeResult::Err(crate::error::create_type_error(
|
||
vm,
|
||
"String.prototype.matchAll: regex must have global flag",
|
||
));
|
||
}
|
||
}
|
||
// GetMethod(pattern, @@matchAll) 全链单次 Get:own 命中 undefined/null
|
||
// 即停(Get 语义),不回退原型链。
|
||
let match_all_key = oxide_types::private_key::make_well_known_symbol_key(7);
|
||
let matcher = match vm.ordinary_get(unsafe { &*pattern_val.as_js_object_ptr() }, match_all_key, pattern_val) {
|
||
Ok(v) => v,
|
||
Err(e) => return NativeResult::err(crate::iterator::engine_error(vm, &e)),
|
||
};
|
||
// GetMethod 返 undefined/null:落构造路径(ToString(thisValue) →
|
||
// RegExpCreate(pattern, "g") → Invoke 产物 @@matchAll)。
|
||
if matcher.is_undefined() || matcher.is_null() {
|
||
return match_all_construct_invoke(vm, this_val, pattern_val);
|
||
}
|
||
if !crate::iterator::is_callable(matcher) {
|
||
return NativeResult::Err(crate::error::create_type_error(
|
||
vm,
|
||
"String.prototype.matchAll: matcher is not a function",
|
||
));
|
||
}
|
||
// Call(matcher, pattern, « thisValue »):实参为原始 this 值。
|
||
return match vm.call_function_sync(matcher, pattern_val, &[this_val]) {
|
||
Ok(v) => NativeResult::Ok(v),
|
||
Err(e) => NativeResult::err(crate::iterator::engine_error(vm, &e)),
|
||
};
|
||
}
|
||
|
||
// 非对象 pattern:对象分支整体跳过,按文本编译为 stub(非捕获组,免额外
|
||
// capture)。
|
||
{
|
||
let pattern_units = try_string!(as_units(vm, pattern_val)).into_owned();
|
||
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;
|
||
// 载体语义即 RegExpCreate(pattern, "g"):flags 字段物化 "g",使
|
||
// g/y/d 判定(regexp_has_flag 读实例字段)与真 RegExp 同径。
|
||
stub.set_regexp_flags(vm.new_string("g"));
|
||
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);
|
||
// string = ToString(thisValue):构造路径消费点,仅此臂实际转换。
|
||
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"));
|
||
}
|
||
};
|
||
builder_wrapper(vm, input_val, JsValue::from_js_object(stub_ptr))
|
||
}
|
||
}
|
||
|
||
/// 构造路径(规范步骤 4-6):string = ToString(thisValue) →
|
||
/// regexp = RegExpCreate(pattern, "g") → Invoke(regexp, @@matchAll, « string »)。
|
||
///
|
||
/// # 边界与前提
|
||
/// - pattern 为 null/undefined 时构造器内部映射(null → "null" 文本、
|
||
/// undefined → 空模式),不在此处重复转换
|
||
/// - 构造产物无 callable @@matchAll(含原型链删除)时 Invoke 抛 TypeError
|
||
/// - 对象 toString/valueOf 抛出的原始异常原样传播
|
||
fn match_all_construct_invoke<H: VmHost>(vm: &mut H, this_val: JsValue, pattern_val: JsValue) -> NativeResult {
|
||
// string = ToString(thisValue)。
|
||
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"));
|
||
}
|
||
};
|
||
|
||
// regexp = RegExpCreate(pattern, "g")。
|
||
let g_str = vm.new_string("g");
|
||
let rx_val = match vm.construct_ctor(
|
||
JsValue::from_js_object(vm.session().builtin_world().regexp_constructor.as_ptr() as *mut JsObject),
|
||
&[pattern_val, g_str],
|
||
) {
|
||
Ok(v) => v,
|
||
Err(e) => return NativeResult::Err(e),
|
||
};
|
||
if !rx_val.is_object() {
|
||
return NativeResult::Err(crate::error::create_type_error(
|
||
vm,
|
||
"match pattern constructor must return an object",
|
||
));
|
||
}
|
||
|
||
// Invoke(regexp, @@matchAll, « string »):产物全链 Get,无 callable 抛 TypeError。
|
||
let match_all_key = oxide_types::private_key::make_well_known_symbol_key(7);
|
||
let rx_ptr = rx_val.as_js_object_ptr();
|
||
let rx_this = JsValue::from_js_object(rx_ptr);
|
||
let matcher = match vm.ordinary_get(unsafe { &*rx_ptr }, match_all_key, rx_this) {
|
||
Ok(v) => v,
|
||
Err(e) => return NativeResult::err(crate::iterator::engine_error(vm, &e)),
|
||
};
|
||
if matcher.is_undefined() || matcher.is_null() || !crate::iterator::is_callable(matcher) {
|
||
return NativeResult::Err(crate::error::create_type_error(
|
||
vm,
|
||
"RegExp.prototype[Symbol.matchAll] is not a function",
|
||
));
|
||
}
|
||
match vm.call_function_sync(matcher, rx_this, &[input_val]) {
|
||
Ok(v) => NativeResult::Ok(v),
|
||
Err(e) => NativeResult::err(crate::iterator::engine_error(vm, &e)),
|
||
}
|
||
}
|
||
|
||
/// 构造 matchAll 包装对象:原型挂 `%RegExpStringIteratorPrototype%`,
|
||
/// own 属性为 input/re/done/global/unicode 五槽(内部槽物化形态),
|
||
/// next 由原型提供。
|
||
///
|
||
/// # 边界与前提
|
||
/// - 字符串 pattern 臂调用方:global 恒真(载体语义 = RegExpCreate(pattern, "g"))、
|
||
/// unicode 恒假(无 u/v 标志);游标归匹配器 lastIndex,包装器不存游标。
|
||
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 re_si = vm.kernel_core().perm_interner().intern(MALL_RE).0;
|
||
let done_si = vm.kernel_core().perm_interner().intern(MALL_DONE).0;
|
||
let global_si = vm.kernel_core().perm_interner().intern(MALL_GLOBAL).0;
|
||
let unicode_si = vm.kernel_core().perm_interner().intern(MALL_UNICODE).0;
|
||
|
||
vm.set_or_create_prop_value(wrapper_obj, input_si, input_val);
|
||
vm.set_or_create_prop_value(wrapper_obj, re_si, re_obj);
|
||
vm.set_or_create_prop_value(wrapper_obj, done_si, JsValue::bool(false));
|
||
vm.set_or_create_prop_value(wrapper_obj, global_si, JsValue::bool(true));
|
||
vm.set_or_create_prop_value(wrapper_obj, unicode_si, JsValue::bool(false));
|
||
|
||
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 迭代器:内层即 ToString 结果,不再回读 @@iterator。本函数语义
|
||
// 只由 this 决定,属性表被删除/置 null/被覆盖均不影响已保存引用的直调。
|
||
// 迭代器挂 %StringIteratorPrototype%(链到 %IteratorPrototype%);串内层不暴露 return。
|
||
let string_iter_proto = vm.session().builtin_world().string_iterator_proto.as_ptr() as *mut JsObject;
|
||
NativeResult::Ok(crate::iterator::build_iterator_wrapper(vm, s_val, None, true, Some(string_iter_proto)))
|
||
}
|
||
|
||
/// `matchAll` 迭代器的 `next`:返回 `{value: 匹配, done: false}`,耗尽后
|
||
/// done 为 true。
|
||
///
|
||
/// # 步骤
|
||
/// 1. this 非对象抛 TypeError;五内部槽缺一即抛 TypeError(RequireInternalSlot)。
|
||
/// 2. `[[Done]]` 为 true → done 结果。
|
||
/// 3. `match = RegExpExec(regexp, string)`:`exec` 属性可调用 →
|
||
/// Call(exec, regexp, «string»),结果非对象非 null 抛 TypeError;不可调用 →
|
||
/// regexp 持编译正则时落内置搜索核(RegExpBuiltinExec 口径),否则
|
||
/// TypeError。
|
||
/// 4. match 为 null → 写 `[[Done]]` = true,done 结果。
|
||
/// 5. `[[Global]]` 为 false → 写 `[[Done]]` = true,返回单匹配(非 global
|
||
/// 匹配器只产一枚)。
|
||
/// 6. 空匹配(ToString(match["0"]) 为空串)→ lastIndex 读-推进-写:
|
||
/// thisIndex = ToLength(Get(regexp, "lastIndex")),nextIndex =
|
||
/// AdvanceStringIndex(u/v 口径跨代理对 2 单元),Set(regexp, "lastIndex")。
|
||
/// 7. 返回 `{value: match, done: false}`。
|
||
///
|
||
/// # 边界与前提
|
||
/// - 各读/调/写(exec getter 与调用、match."0" 读与 ToString、lastIndex
|
||
/// Get/Set/ToLength)的原异常原值传播。
|
||
///
|
||
/// # 副作用
|
||
/// - 写包装器 `[[Done]]` 槽;空匹配臂写匹配器 lastIndex。
|
||
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 re_si = vm.kernel_core().perm_interner().intern(MALL_RE).0;
|
||
let done_si = vm.kernel_core().perm_interner().intern(MALL_DONE).0;
|
||
let global_si = vm.kernel_core().perm_interner().intern(MALL_GLOBAL).0;
|
||
let unicode_si = vm.kernel_core().perm_interner().intern(MALL_UNICODE).0;
|
||
|
||
// 内部槽守卫:五槽同批写入,查一槽即全查;缺失即 TypeError。
|
||
if vm.get_own_property_slot(wrapper, done_si).is_none() {
|
||
return NativeResult::Err(crate::error::create_type_error(
|
||
vm,
|
||
"matchAll next called on object without internal slots",
|
||
));
|
||
}
|
||
|
||
// [[Done]] 为 true → done 结果。
|
||
let done_val = match vm.ordinary_get(wrapper, done_si, this_val) {
|
||
Ok(v) => v,
|
||
Err(_) => return NativeResult::Err(crate::iterator::engine_error(vm, "cannot read done slot")),
|
||
};
|
||
if oxide_runtime_api::to_boolean(done_val) {
|
||
return make_match_done_result(vm, JsValue::undefined());
|
||
}
|
||
|
||
// 读余下四槽:input / re / global / unicode。
|
||
let input_val = match vm.ordinary_get(wrapper, input_si, this_val) {
|
||
Ok(v) => v,
|
||
Err(_) => return NativeResult::Err(crate::iterator::engine_error(vm, "cannot read input slot")),
|
||
};
|
||
let re_val = match vm.ordinary_get(wrapper, re_si, this_val) {
|
||
Ok(v) if v.is_object() => v,
|
||
_ => {
|
||
return NativeResult::Err(crate::error::create_type_error(vm, "matchAll next called on non-object regexp"))
|
||
}
|
||
};
|
||
let is_global = match vm.ordinary_get(wrapper, global_si, this_val) {
|
||
Ok(v) => oxide_runtime_api::to_boolean(v),
|
||
Err(_) => return NativeResult::Err(crate::iterator::engine_error(vm, "cannot read global slot")),
|
||
};
|
||
let is_unicode = match vm.ordinary_get(wrapper, unicode_si, this_val) {
|
||
Ok(v) => oxide_runtime_api::to_boolean(v),
|
||
Err(_) => return NativeResult::Err(crate::iterator::engine_error(vm, "cannot read unicode slot")),
|
||
};
|
||
let re_ptr = re_val.as_js_object_ptr();
|
||
let re_obj = unsafe { &*re_ptr };
|
||
|
||
// ── RegExpExec(R, S) ──
|
||
|
||
// Get(R, "exec"):getter 抛错原值传播。
|
||
let exec_val = match crate::regexp::rx_get_prop(vm, re_ptr, "exec", re_val) {
|
||
Ok(v) => v,
|
||
Err(err) => return NativeResult::Err(err),
|
||
};
|
||
let match_val = if crate::iterator::is_callable(exec_val) {
|
||
// Call(exec, R, «S»):调用抛错原值传播;结果非对象非 null 抛 TypeError。
|
||
let result = match vm.call_function_sync(exec_val, re_val, &[input_val]) {
|
||
Ok(r) => r,
|
||
Err(_) => return NativeResult::Err(crate::iterator::engine_error(vm, "RegExp exec call failed")),
|
||
};
|
||
if !(result.is_null() || result.is_object()) {
|
||
return NativeResult::Err(crate::error::create_type_error(
|
||
vm,
|
||
"RegExp exec result must be an object or null",
|
||
));
|
||
}
|
||
result
|
||
} else {
|
||
// 不可调用回落:仅 regexp 持编译正则(真 RegExp 或字符串 pattern 载体)
|
||
// 可走内置搜索核,否则 RequireInternalSlot 失败。
|
||
let fn_ptr = match re_obj.native_fn() {
|
||
Some(p) => p,
|
||
None => return NativeResult::Err(crate::error::create_type_error(vm, "RegExp exec is not callable")),
|
||
};
|
||
// SAFETY: fn_ptr 持有 regexp_constructor 存放的 `Box<regress::Regex>` 指针。
|
||
let regex = unsafe { &*(fn_ptr.as_ptr() as *const regress::Regex) };
|
||
let text = MatchText::from_value(input_val);
|
||
// g/y 标志决定 lastIndex 追踪(与 exec/test 同口径)。
|
||
let tracks = crate::regexp::regexp_has_flag(vm, re_obj, 'g') || crate::regexp::regexp_has_flag(vm, re_obj, 'y');
|
||
let sticky = crate::regexp::regexp_has_flag(vm, re_obj, 'y');
|
||
let m = match crate::regexp::rx_search(vm, re_ptr, re_val, regex, &text, tracks, sticky) {
|
||
Ok(opt) => opt,
|
||
Err(err) => return NativeResult::Err(err),
|
||
};
|
||
match m {
|
||
None => JsValue::null(),
|
||
Some(m) => {
|
||
// d 标志门控 indices 产出(与 exec 同口径)。
|
||
let has_indices = crate::regexp::regexp_has_flag(vm, re_obj, 'd');
|
||
let parts = match_parts(vm, &text, &m);
|
||
make_match_result_array(vm, parts, m.range().start as i32, input_val, Some(&m), &text, has_indices)
|
||
}
|
||
}
|
||
};
|
||
|
||
// match 为 null → 写 [[Done]] = true,done 结果。
|
||
if match_val.is_null() {
|
||
vm.set_or_create_prop_value(wrapper, done_si, JsValue::bool(true));
|
||
return make_match_done_result(vm, JsValue::undefined());
|
||
}
|
||
|
||
// [[Global]] 为 false → 写 [[Done]] = true,返回单匹配。
|
||
if !is_global {
|
||
vm.set_or_create_prop_value(wrapper, done_si, JsValue::bool(true));
|
||
return make_match_done_result(vm, match_val);
|
||
}
|
||
|
||
// matchString = ToString(Get(match, "0")):下标经整数键换算(数组元素区
|
||
// 按整数键存储),getter 与 ToString 抛错原值传播。
|
||
let m0 = match crate::regexp::rx_get_index(vm, match_val.as_js_object_ptr(), 0, match_val) {
|
||
Ok(v) => v,
|
||
Err(err) => return NativeResult::Err(err),
|
||
};
|
||
let m0_str = match oxide_runtime_api::to_string_value_full(m0, 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"));
|
||
}
|
||
};
|
||
// 空匹配 → lastIndex 读-推进-写(内置臂 lastIndex 为 rx_search 写入的
|
||
// 匹配末尾,自定义 exec 臂为用户代码已设值)。
|
||
if vm.string_units(m0_str).is_empty() {
|
||
let li_val = match crate::regexp::rx_get_prop(vm, re_ptr, "lastIndex", re_val) {
|
||
Ok(v) => v,
|
||
Err(err) => return NativeResult::Err(err),
|
||
};
|
||
let this_index = match crate::regexp::to_length_value(vm, li_val) {
|
||
Ok(n) => n,
|
||
Err(err) => return NativeResult::Err(err),
|
||
};
|
||
let units = vm.string_units(input_val);
|
||
let next_index = crate::regexp::advance_string_index(&units, this_index, is_unicode);
|
||
if let Err(err) = crate::regexp::rx_set_prop(vm, re_ptr, "lastIndex", JsValue::int(next_index as i32), re_val) {
|
||
return NativeResult::Err(err);
|
||
}
|
||
}
|
||
make_match_done_result(vm, match_val)
|
||
}
|
||
|
||
/// 由匹配结果构建结果数组元素列表:完整匹配字符串值在前,其后逐捕获组
|
||
/// (未参与匹配为 undefined),片段经 MatchText 臂的单元口径物化。
|
||
fn match_parts<H: VmHost>(vm: &mut H, text: &MatchText, m: ®ress::Match) -> Vec<JsValue> {
|
||
let range = m.range();
|
||
let mut parts = Vec::with_capacity(m.captures.len() + 1);
|
||
parts.push(vm.new_string_units_owned(text.slice(range.start, range.end).into_owned()));
|
||
for i in 1..=m.captures.len() {
|
||
match m.group(i) {
|
||
Some(g) => parts.push(vm.new_string_units_owned(text.slice(g.start, g.end).into_owned())),
|
||
None => parts.push(JsValue::undefined()),
|
||
}
|
||
}
|
||
parts
|
||
}
|
||
|
||
/// 构建 matchAll 结果数组:元素为匹配字符串值(未匹配捕获组为 undefined),
|
||
/// 附带 index/input/groups 属性(d 标志下另挂 indices,与 exec 结果同面)。
|
||
fn make_match_result_array<H: VmHost>(
|
||
vm: &mut H, parts: Vec<JsValue>, match_index: i32, input_val: JsValue, m: Option<®ress::Match>,
|
||
text: &MatchText, has_indices: bool,
|
||
) -> JsValue {
|
||
let arr = make_string_array_values(vm, parts);
|
||
let arr_ptr = arr.as_js_object_ptr();
|
||
let arr_obj = unsafe { &mut *arr_ptr };
|
||
let index_si = vm.kernel_core().perm_interner().intern("index").0;
|
||
vm.set_or_create_prop_value(arr_obj, index_si, JsValue::int(match_index));
|
||
let input_si = vm.kernel_core().perm_interner().intern("input").0;
|
||
vm.set_or_create_prop_value(arr_obj, input_si, input_val);
|
||
let groups_val = match m {
|
||
Some(m) => build_groups_object(vm, m, text),
|
||
None => JsValue::undefined(),
|
||
};
|
||
let groups_si = vm.kernel_core().perm_interner().intern("groups").0;
|
||
vm.set_or_create_prop_value(arr_obj, groups_si, groups_val);
|
||
// d 标志:挂 indices 属性(与 exec 结果同口径的码元对数组族)。
|
||
if has_indices {
|
||
if let Some(m) = m {
|
||
let indices_val = crate::regexp::build_indices_array(vm, m, text);
|
||
let indices_si = vm.kernel_core().perm_interner().intern("indices").0;
|
||
vm.set_or_create_prop_value(arr_obj, indices_si, indices_val);
|
||
}
|
||
}
|
||
arr
|
||
}
|
||
|
||
/// 构造迭代器结果对象 `{value, done}`(done 仅在 value 为 undefined 时为 true)。
|
||
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 缺失时
|
||
// 追加空串,末段后不再追加任何内容。
|
||
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))
|
||
}
|