refactor: string 基础方法族拆出 basic 模块

This commit is contained in:
2026-09-17 10:51:46 +00:00
parent 3c691ef4d6
commit cad52546d6
2 changed files with 671 additions and 656 deletions
+668
View File
@@ -0,0 +1,668 @@
use oxide_runtime_api::{NativeResult, VmHost};
use oxide_types::object::JsObject;
use oxide_types::value::JsValue;
use crate::builtins_debug;
use crate::builtins_error;
use super::common::try_string;
use super::{
as_units, code_point_count, find_units, is_trim_unit, map_well_formed_segments, rfind_units, take_code_points,
this_units,
};
// ── 静态方法 / 构造 ─────────────────────────────────────────────────────
/// `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))
}
+3 -656
View File
@@ -9,7 +9,10 @@ use oxide_runtime_api::{NativeResult, VmHost};
use crate::builtins_debug;
use crate::builtins_error;
mod basic;
mod common;
pub use basic::*;
use common::try_string;
pub(crate) use common::*;
@@ -257,662 +260,6 @@ fn is_regexp_obj<H: VmHost>(val: JsValue, vm: &H) -> bool {
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)`:按分隔符拆分为字符串数组;