From cad52546d624df644349307ad1c1bdab05928d0e Mon Sep 17 00:00:00 2001 From: pjh456 <147148383@qq.com> Date: Thu, 17 Sep 2026 10:51:46 +0000 Subject: [PATCH] =?UTF-8?q?refactor:=20string=20=E5=9F=BA=E7=A1=80?= =?UTF-8?q?=E6=96=B9=E6=B3=95=E6=97=8F=E6=8B=86=E5=87=BA=20basic=20?= =?UTF-8?q?=E6=A8=A1=E5=9D=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- crates/oxide_builtins/src/string/basic.rs | 668 ++++++++++++++++++++++ crates/oxide_builtins/src/string/mod.rs | 659 +-------------------- 2 files changed, 671 insertions(+), 656 deletions(-) create mode 100644 crates/oxide_builtins/src/string/basic.rs diff --git a/crates/oxide_builtins/src/string/basic.rs b/crates/oxide_builtins/src/string/basic.rs new file mode 100644 index 00000000..1f91037d --- /dev/null +++ b/crates/oxide_builtins/src/string/basic.rs @@ -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(vm: &mut H, args: &[u8]) -> NativeResult { + builtins_debug!("String.prototype.fromCharCode called with {} args", args.len()); + let mut units: Vec = 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(vm: &mut H, args: &[u8]) -> NativeResult { + builtins_debug!("String.fromCodePoint called with {} args", args.len()); + let mut units: Vec = 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(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(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(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(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 = 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 = 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(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 = 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 = 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(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 = 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(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(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(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 = 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 = 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(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(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 = 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 = 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(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 = 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 = 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(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 = 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 = s[start..start + count].to_vec(); + NativeResult::Ok(vm.new_string_units_owned(out)) +} + +/// `String.prototype.at(index)`:按码元索引取 1 单元字符串(支持负索引); +/// 越界返回 undefined。 +pub fn string_at(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(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(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(vm: &mut H, args: &[u8]) -> NativeResult { + builtins_debug!("String.prototype.trim called with {} args", args.len()); + let s: Vec = 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(vm: &mut H, args: &[u8]) -> NativeResult { + builtins_debug!("String.prototype.trimStart called with {} args", args.len()); + let s: Vec = 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(vm: &mut H, args: &[u8]) -> NativeResult { + builtins_debug!("String.prototype.trimEnd called with {} args", args.len()); + let s: Vec = 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(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(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 = if pad_val.is_undefined() { + vec![0x20] + } else { + try_string!(as_units(vm, pad_val)).into_owned() + }; + let s: Vec = 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(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 = if pad_val.is_undefined() { + vec![0x20] + } else { + try_string!(as_units(vm, pad_val)).into_owned() + }; + let s: Vec = 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(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 = 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 = 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(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 = 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 = 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)) +} diff --git a/crates/oxide_builtins/src/string/mod.rs b/crates/oxide_builtins/src/string/mod.rs index 62c842bb..d6a77a5d 100644 --- a/crates/oxide_builtins/src/string/mod.rs +++ b/crates/oxide_builtins/src/string/mod.rs @@ -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(val: JsValue, vm: &H) -> bool { std::ptr::eq(proto_ptr, rp) } -// ── 静态方法 / 构造 ───────────────────────────────────────────────────── - -/// `String.fromCharCode(...codes)`:把各参数按 ToUint32 低 16 位转为单元拼接 -/// 为字符串(surrogate 区间合法产出孤立 surrogate 单元)。 -pub fn string_from_char_code(vm: &mut H, args: &[u8]) -> NativeResult { - builtins_debug!("String.prototype.fromCharCode called with {} args", args.len()); - let mut units: Vec = 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(vm: &mut H, args: &[u8]) -> NativeResult { - builtins_debug!("String.fromCodePoint called with {} args", args.len()); - let mut units: Vec = 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(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(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(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(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 = 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 = 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(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 = 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 = 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(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 = 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(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(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(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 = 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 = 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(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(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 = 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 = 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(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 = 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 = 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(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 = 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 = s[start..start + count].to_vec(); - NativeResult::Ok(vm.new_string_units_owned(out)) -} - -/// `String.prototype.at(index)`:按码元索引取 1 单元字符串(支持负索引); -/// 越界返回 undefined。 -pub fn string_at(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(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(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(vm: &mut H, args: &[u8]) -> NativeResult { - builtins_debug!("String.prototype.trim called with {} args", args.len()); - let s: Vec = 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(vm: &mut H, args: &[u8]) -> NativeResult { - builtins_debug!("String.prototype.trimStart called with {} args", args.len()); - let s: Vec = 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(vm: &mut H, args: &[u8]) -> NativeResult { - builtins_debug!("String.prototype.trimEnd called with {} args", args.len()); - let s: Vec = 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(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(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 = if pad_val.is_undefined() { - vec![0x20] - } else { - try_string!(as_units(vm, pad_val)).into_owned() - }; - let s: Vec = 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(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 = if pad_val.is_undefined() { - vec![0x20] - } else { - try_string!(as_units(vm, pad_val)).into_owned() - }; - let s: Vec = 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(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 = 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 = 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(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 = 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 = 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)`:按分隔符拆分为字符串数组;