Files
OxideJS-New/crates/oxide_builtins/src/string/mod.rs
T

1740 lines
75 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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: &regress::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: &regress::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: &regress::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: &regress::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))
}