use std::borrow::Cow; use oxide_kernel::shape_forge::EMPTY_SHAPE_ID; use oxide_types::object::JsObject; use oxide_types::value::JsValue; use oxide_runtime_api::{NativeResult, VmHost}; use crate::builtins_debug; use crate::builtins_error; mod common; use common::try_string; pub(crate) use common::*; // ── 正则替换(单元口径) ──────────────────────────────────────────────── /// 展开单个匹配的 replacement `$` 引用(`$$`、`$&`、`` $` ``、`$'`、`$n`), /// 输入/输出均为单元序列。 fn expand_dollar_units(text: &[u16], m: ®ress::Match, replacement: &[u16]) -> Vec { let mut out = Vec::new(); let mut i = 0; while i < replacement.len() { if replacement[i] != 0x24 { out.push(replacement[i]); i += 1; continue; } let range = m.range(); let rest = &replacement[i..]; if rest.starts_with(&[0x24, 0x24]) { out.push(0x24); i += 2; } else if rest.starts_with(&[0x24, 0x26]) { out.extend_from_slice(&text[range.start..range.end]); i += 2; } else if rest.starts_with(&[0x24, 0x60]) { out.extend_from_slice(&text[..range.start]); i += 2; } else if rest.starts_with(&[0x24, 0x27]) { out.extend_from_slice(&text[range.end..]); i += 2; } else if i + 1 < replacement.len() && (0x30..=0x39).contains(&replacement[i + 1]) { // $digits:digitCount 取 2(后续尚有数字)否则 1;两位数值越界回退 // 一位(次位留给后续按字面处理);仍越界(含 $0)→ 整段 ref 字面。 let digit_count = if i + 2 < replacement.len() && (0x30..=0x39).contains(&replacement[i + 2]) { 2 } else { 1 }; let d1 = (replacement[i + 1] - 0x30) as u32; let mut index = if digit_count == 2 { d1 * 10 + (replacement[i + 2] - 0x30) as u32 } else { d1 }; let mut ref_len = 1 + digit_count; let capture_len = (m.groups().count() - 1) as u32; if index > capture_len && digit_count == 2 { index = d1; ref_len = 2; } if (1..=capture_len).contains(&index) { if let Some(g) = m.group(index as usize) { out.extend_from_slice(&text[g.start..g.end]); } } else { out.extend_from_slice(&replacement[i..i + ref_len]); } i += ref_len; } else { out.push(0x24); i += 1; } } out } /// 正则替换的手动实现(单元口径):按 JS 语义展开 replacement 中的 `$` 引用, /// global 全替换否则首个。 pub(crate) fn regex_replace_manual_units( regex: ®ress::Regex, text: &[u16], replacement: &[u16], global: bool, ) -> Vec { let mut out = Vec::new(); let mut last_end = 0; let matches: Vec = if global { regex.find_from_utf16(text, 0).collect() } else { regex.find_from_utf16(text, 0).take(1).collect() }; for m in &matches { let range = m.range(); out.extend_from_slice(&text[last_end..range.start]); out.extend_from_slice(&expand_dollar_units(text, m, replacement)); last_end = range.end; } out.extend_from_slice(&text[last_end..]); out } /// 调用函数 replacer 并把返回值转为单元序列;调用抛出的异常原样恢复。 fn call_replacer(vm: &mut H, replacer: JsValue, cb_args: &[JsValue]) -> Result, JsValue> { match vm.call_function_sync(replacer, JsValue::undefined(), cb_args) { Ok(result) => match oxide_runtime_api::to_units_full(result, vm) { Ok(units) => Ok(units), Err(_) => { // 转换失败(Symbol 等)或回调异常均须原样传播。 if let Some(exc) = vm.take_uncaught_value() { return Err(exc); } Err(crate::error::create_type_error(vm, "Cannot convert a Symbol value to a string")) } }, Err(err) => Err(vm .take_uncaught_value() .unwrap_or_else(|| crate::error::create_type_error(vm, &format!("replace replacer: {}", err)))), } } /// 函数 replacer 的回调参数:匹配串、各捕获组(未匹配为 undefined)、position /// (码元口径)、原字符串。原字符串参数由调用方预构传入(`text_arg`), /// 避免每匹配复制整个源串——字符串不可变,同一 `JsValue` 可安全复用。 fn replacer_cb_args(vm: &mut H, text: &[u16], m: ®ress::Match, text_arg: JsValue) -> Vec { let range = m.range(); let mut cb_args: Vec = Vec::with_capacity(m.captures.len() + 2); cb_args.push(vm.new_string_units(&text[range.start..range.end])); for i in 1..=m.captures.len() { match m.group(i) { Some(g) => cb_args.push(vm.new_string_units(&text[g.start..g.end])), None => cb_args.push(JsValue::undefined()), } } cb_args.push(JsValue::int(range.start as i32)); cb_args.push(text_arg); cb_args } /// 正则模式 + 函数 replacer:global 全替换否则替换首个,逐匹配调用回调, /// 返回值转单元序列作为替换文本(不展开 `$` 引用)。`text_arg` 为回调第 4 参 /// (原字符串),调用方预构一次,回调期按值复用。 pub(crate) fn regex_replace_fn( vm: &mut H, regex: ®ress::Regex, text: &[u16], replacer: JsValue, global: bool, text_arg: JsValue, ) -> NativeResult { let matches: Vec = if global { regex.find_from_utf16(text, 0).collect() } else { regex.find_from_utf16(text, 0).take(1).collect() }; let mut out = Vec::new(); let mut last_end = 0; for m in &matches { let range = m.range(); out.extend_from_slice(&text[last_end..range.start]); let cb_args = replacer_cb_args(vm, text, m, text_arg); let repl = try_string!(call_replacer(vm, replacer, &cb_args)); out.extend_from_slice(&repl); last_end = range.end; } out.extend_from_slice(&text[last_end..]); NativeResult::Ok(vm.new_string_units_owned(out)) } /// 单元序列上的整体替换(空模式在每个单元边界插入替换文本,与 String::replace /// 口径一致)。 fn replace_units_all(hay: &[u16], from: &[u16], to: &[u16]) -> Vec { if from.is_empty() { let mut out = Vec::with_capacity(hay.len() + (hay.len() + 1) * to.len()); out.extend_from_slice(to); for &u in hay { out.push(u); out.extend_from_slice(to); } return out; } let mut out = Vec::with_capacity(hay.len()); let mut start = 0; while let Some(p) = find_units(hay, from, start) { out.extend_from_slice(&hay[start..p]); out.extend_from_slice(to); start = p + from.len(); } out.extend_from_slice(&hay[start..]); out } /// 单元序列上的首个替换;未命中返回原序列拷贝。空模式在位置 0 命中 /// (插入替换文本于串首,与 String::replace 口径一致)。 fn replace_units_first(hay: &[u16], from: &[u16], to: &[u16]) -> Vec { if from.is_empty() { let mut out = Vec::with_capacity(hay.len() + to.len()); out.extend_from_slice(to); out.extend_from_slice(hay); return out; } if let Some(rel) = find_units(hay, from, 0) { let mut out = Vec::with_capacity(hay.len()); out.extend_from_slice(&hay[..rel]); out.extend_from_slice(to); out.extend_from_slice(&hay[rel + from.len()..]); return out; } hay.to_vec() } /// 字符串模式 + 函数 replacer:all 全替换否则替换首个。回调参数 /// `(match, position, string)`(无捕获组)。空模式在每个单元边界匹配一次。 /// `text_arg` 为回调第 4 参(原字符串),调用方预构一次。 fn string_replace_fn( vm: &mut H, text: &[u16], pattern: &[u16], replacer: JsValue, all: bool, text_arg: JsValue, ) -> NativeResult { let search_length = pattern.len(); let mut positions: Vec = Vec::new(); if search_length == 0 { positions.push(0); if all { for i in 1..=text.len() { positions.push(i); } } } else { let mut start = 0; while let Some(p) = find_units(text, pattern, start) { positions.push(p); if !all { break; } start = p + search_length; } } let mut out = Vec::new(); let mut last_end = 0; for p in positions { out.extend_from_slice(&text[last_end..p]); let cb_args = [vm.new_string_units(&text[p..p + search_length]), JsValue::int(p as i32), text_arg]; let repl = try_string!(call_replacer(vm, replacer, &cb_args)); out.extend_from_slice(&repl); last_end = p + search_length; } out.extend_from_slice(&text[last_end..]); NativeResult::Ok(vm.new_string_units_owned(out)) } fn is_regexp_obj(val: JsValue, vm: &H) -> bool { if !val.is_object() { return false; } let ptr = val.as_js_object_ptr(); if ptr.is_null() { return false; } let obj = unsafe { &*ptr }; let proto = obj.proto(); if !proto.is_object() { return false; } let proto_ptr = proto.as_js_object_ptr(); if proto_ptr.is_null() { return false; } let rp = vm.session().builtin_world().regexp_proto.as_ptr() as *mut JsObject; std::ptr::eq(proto_ptr, rp) } // ── 静态方法 / 构造 ───────────────────────────────────────────────────── /// `String.fromCharCode(...codes)`:把各参数按 ToUint32 低 16 位转为单元拼接 /// 为字符串(surrogate 区间合法产出孤立 surrogate 单元)。 pub fn string_from_char_code(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)`:按分隔符拆分为字符串数组; /// 分隔符可为 RegExp(含捕获组)或字符串。空分隔按单元逐个产出(规格口径, /// 孤立 surrogate 为 1 单元元素)。 pub fn string_split(vm: &mut H, args: &[u8]) -> NativeResult { builtins_debug!("String.prototype.split called with {} args", args.len()); // 参数转换先行:separator 为对象时 ToString 可能触发用户代码(&mut 路径), // limit 仅纯函数计算;之后借 this 源串扫描切分。 let sep_val = if args.len() >= 2 { Some(vm.reg(args[1])) } else { None }; let is_undefined_sep = sep_val.map(|v| v.is_undefined()).unwrap_or(true); let is_re = match sep_val { Some(v) if !v.is_undefined() => is_regexp_obj(v, vm), _ => false, }; // 类正则对象是否持有编译正则:native_fn 存在才是真 RegExp 实例,命中正则切分路径; // 无编译正则的类正则对象(如 Object.create(RegExp.prototype))回退字符串路径, // 须按 ToString 文本切分,且转换(&mut 路径)须先于 this 借用完成。 let has_native_re = is_re && { let re_ptr = sep_val.unwrap().as_js_object_ptr(); // SAFETY: is_re 已保证 sep_val 为非空对象且 proto 恒等 RegExp.prototype。 let re = unsafe { &*re_ptr }; re.native_fn().is_some() }; // ToUint32(limit),缺省为 2^32-1。 let limit = if args.len() > 2 { let l = oxide_runtime_api::to_integer_or_infinity(vm.reg(args[2])); if l.is_infinite() { u32::MAX as usize } else { (l.max(0.0).trunc() as u64).min(u32::MAX as u64) as usize } } else { u32::MAX as usize }; let sep_units: Vec = if is_undefined_sep || has_native_re { Vec::new() } else { try_string!(as_units(vm, sep_val.unwrap())).into_owned() }; let s: Vec = try_string!(this_units(vm, args)).into_owned(); // 规范:separator 为 undefined 时返回 [this]。 if is_undefined_sep { return NativeResult::Ok(make_units_array(vm, vec![s.to_vec()])); } if is_re { let re_ptr = sep_val.unwrap().as_js_object_ptr(); let re = unsafe { &*re_ptr }; if let Some(fn_ptr) = re.native_fn() { let regex = unsafe { &*(fn_ptr.as_ptr() as *const regress::Regex) }; let mut parts: Vec> = Vec::new(); let mut last_end = 0; for m in regex.find_from_utf16(&s, 0) { if parts.len() >= limit { break; } let range = m.range(); parts.push(s[last_end..range.start].to_vec()); if parts.len() >= limit { break; } for i in 1..=m.captures.len() { if parts.len() >= limit { break; } match m.group(i) { Some(g) => parts.push(s[g.start..g.end].to_vec()), None => parts.push(Vec::new()), } } last_end = range.end; } if last_end <= s.len() && parts.len() < limit { parts.push(s[last_end..].to_vec()); } return NativeResult::Ok(make_units_array(vm, parts)); } // 无原生正则的类正则对象回退到字符串路径。 } if sep_units.is_empty() { // 每单元一个元素(ASCII 走单字符缓存零分配,其余 1 单元串创建)。 let mut values = Vec::with_capacity(s.len().min(limit)); for &u in s.iter().take(limit) { values.push(match vm.single_unit(u) { Some(v) => v, None => vm.new_string_units(&[u]), }); } return NativeResult::Ok(make_string_array_values(vm, values)); } // 非空字符串分隔:手动查找循环 + limit。 let mut parts: Vec> = Vec::new(); let mut start = 0; loop { if parts.len() >= limit { break; } match find_units(&s, &sep_units, start) { Some(p) => { parts.push(s[start..p].to_vec()); start = p + sep_units.len(); } None => { parts.push(s[start..].to_vec()); break; } } } NativeResult::Ok(make_units_array(vm, parts)) } /// `replace`/`replaceAll` 共用的分叉实现:按 replacer 类型与参数形态选择路径。 /// /// # 步骤 /// 1. receiver 前置校验(RequireObjectCoercible,纯 is_* 读取)。 /// 2. 纯对象头读取判定分叉:正则身份/编译正则/global 标志/函数 replacer。 /// 3. 按分支执行替换(单元口径;良形内容的结果与旧文本路径逐位一致)。 /// /// # 边界与前提 /// - 缺省 searchValue/replaceValue 均按 ToString(undefined)="undefined" 处理 /// (`s.replace()` 全缺省等价于把 "undefined" 替换为 "undefined",结果与 /// 原串一致;`s.replace("b")` 得到 "aundefinedc" 而非 "ac")。 /// - replaceAll 遇非 global 正则抛 TypeError;类正则对象(proto 恒等 /// RegExp.prototype 但无编译正则)replace/replaceAll 统一按 ToString 文本走 /// 字符串路径。 fn string_replace_impl(vm: &mut H, args: &[u8], all: bool) -> NativeResult { let this_val = vm.reg(args[0]); // receiver 前置校验:null/undefined/symbol 抛 TypeError(规范第 1 步 // RequireObjectCoercible,纯 is_* 读取无用户代码,先于一切分叉)。 if this_val.is_null() || this_val.is_undefined() { return NativeResult::Err(crate::error::create_type_error( vm, "String.prototype method called on null or undefined", )); } if this_val.is_symbol() { return NativeResult::Err(crate::error::create_type_error(vm, "Cannot convert a Symbol value to a string")); } let pattern_val = if args.len() >= 2 { vm.reg(args[1]) } else { JsValue::undefined() }; let replacement_val = if args.len() > 2 { vm.reg(args[2]) } else { JsValue::undefined() }; // 分叉判定(全部纯读取/纯对象头读,无用户代码):正则对象身份、是否持有 // 编译正则、global 标志、replacer 是否为函数。 let is_re = is_regexp_obj(pattern_val, vm); let (has_native_re, is_global) = if is_re { let re_ptr = pattern_val.as_js_object_ptr(); // SAFETY: is_re 已保证 pattern_val 为非空对象且 proto 恒等 RegExp.prototype。 let re = unsafe { &*re_ptr }; if re.native_fn().is_some() { let g = re.hash_props_vec().and_then(|v| v.get(3)).map(|v| v.as_bool()).unwrap_or(false); (true, g) } else { (false, false) } } else { (false, false) }; let replacer_val = if replacement_val.is_object() { let o = unsafe { &*replacement_val.as_js_object_ptr() }; if o.is_function() { Some(replacement_val) } else { None } } else { None }; // replaceAll 要求正则带 global:非 global 正则直接抛 TypeError(规范 flags 检查)。 // 类正则对象(无编译正则)无 flags 概念,统一走 as_units 文本路径,不在此检查。 if all && has_native_re && !is_global { return NativeResult::Err(crate::error::create_type_error( vm, "String.prototype.replaceAll called with a non-global RegExp", )); } // 分支 A:函数 replacer。回调经 call_function_sync(&mut),receiver 单元 // 序列先落地为 owned(跨回调的硬约束)。 if let Some(replacer_val) = replacer_val { let s = try_string!(this_units(vm, args)).into_owned(); // 回调第 4 参(原字符串)预构一次:原始字符串 receiver 直接复用 this_val // 零拷贝,对象 receiver 提升为单次转换值(原每匹配整串复制)。 let text_arg = match this_val.is_string() { true => this_val, false => match oxide_runtime_api::to_string_value_full(this_val, vm) { Ok(v) => v, Err(_) => { // ToString 触发对象 toString/valueOf 抛出的原生异常须原样传播。 if let Some(exc) = vm.take_uncaught_value() { return NativeResult::Err(exc); } return NativeResult::Err(crate::error::create_type_error(vm, "Cannot convert value to a string")); } }, }; if has_native_re { let re_ptr = pattern_val.as_js_object_ptr(); let re = unsafe { &*re_ptr }; let fn_ptr = re.native_fn().unwrap(); // SAFETY: fn_ptr 持有 regexp_constructor 存放的 `Box` 指针。 let regex = unsafe { &*(fn_ptr.as_ptr() as *const regress::Regex) }; return regex_replace_fn(vm, regex, &s, replacer_val, is_global, text_arg); } let pattern = try_string!(as_units(vm, pattern_val)).into_owned(); return string_replace_fn(vm, &s, &pattern, replacer_val, all, text_arg); } let replacement_is_string = args.len() <= 2 || replacement_val.is_string(); // 分支 B:全原始字符串快路径——三值按载荷形态共享借用(`&H` 共享借用 // 派生多个单元借用可共存,E0499 只发生在 `&mut` 派生)。 if this_val.is_string() && pattern_val.is_string() && replacement_is_string { let h = &*vm; let s = h.string_units(this_val); let p = h.string_units(pattern_val); let r: Cow<'_, [u16]> = if replacement_val.is_string() { h.string_units(replacement_val) } else { Cow::Owned("undefined".encode_utf16().collect()) }; let result = if all { replace_units_all(&s, &p, &r) } else { replace_units_first(&s, &p, &r) }; return NativeResult::Ok(vm.new_string_units_owned(result)); } // 分支 B2:正则 pattern 快路径——regex 为对象内裸指针(不占 vm 借用), // receiver/replacement 共享借用零拷贝。 if this_val.is_string() && has_native_re && replacement_is_string { let re_ptr = pattern_val.as_js_object_ptr(); let re = unsafe { &*re_ptr }; let fn_ptr = re.native_fn().unwrap(); // SAFETY: fn_ptr 持有 regexp_constructor 存放的 `Box` 指针。 let regex = unsafe { &*(fn_ptr.as_ptr() as *const regress::Regex) }; let h = &*vm; let s = h.string_units(this_val); let r: Cow<'_, [u16]> = if replacement_val.is_string() { h.string_units(replacement_val) } else { Cow::Owned("undefined".encode_utf16().collect()) }; let result = regex_replace_manual_units(regex, &s, &r, is_global); return NativeResult::Ok(vm.new_string_units_owned(result)); } // 分支 C:一般路径(对象参与转换)。原始字符串 receiver 与对象 receiver // 统一:参数转换前置(&mut 路径)后按单元序列扫描。 let s = try_string!(this_units(vm, args)).into_owned(); let pattern = if has_native_re { Vec::new() } else if args.len() < 2 { "undefined".encode_utf16().collect() } else { try_string!(as_units(vm, pattern_val)).into_owned() }; let replacement = if args.len() > 2 { try_string!(as_units(vm, replacement_val)).into_owned() } else { "undefined".encode_utf16().collect() }; // 统一扫描:命中编译正则走手动 $ 展开,否则字符串子串替换。 if has_native_re { let re_ptr = pattern_val.as_js_object_ptr(); let re = unsafe { &*re_ptr }; let fn_ptr = re.native_fn().unwrap(); // SAFETY: fn_ptr 持有 regexp_constructor 存放的 `Box` 指针。 let regex = unsafe { &*(fn_ptr.as_ptr() as *const regress::Regex) }; let result = regex_replace_manual_units(regex, &s, &replacement, is_global); return NativeResult::Ok(vm.new_string_units_owned(result)); } let result = if all { replace_units_all(&s, &pattern, &replacement) } else { replace_units_first(&s, &pattern, &replacement) }; NativeResult::Ok(vm.new_string_units_owned(result)) } /// `String.prototype.replace(pattern, replacement)`:替换首个匹配; /// 支持 RegExp(global 全替换)、字符串以及函数替换器。 pub fn string_replace(vm: &mut H, args: &[u8]) -> NativeResult { builtins_debug!("String.prototype.replace called with {} args", args.len()); string_replace_impl(vm, args, false) } /// `String.prototype.match(pattern)`:按 RegExp 匹配;global 返回全部匹配数组, /// 否则返回首个匹配及捕获组,无匹配返回 null。 pub fn string_match_fn(vm: &mut H, args: &[u8]) -> NativeResult { builtins_debug!("String.prototype.match called with {} args", args.len()); // 正则判定与参数字符串转换先行(&mut 路径),后借 this 匹配扫描。 let pattern_val = if args.len() >= 2 { Some(vm.reg(args[1])) } else { None }; let is_re = pattern_val.map(|v| is_regexp_obj(v, vm)).unwrap_or(false); let pattern: Vec = match pattern_val { Some(v) if !is_re => try_string!(as_units(vm, v)).into_owned(), _ => Vec::new(), }; let text = try_string!(this_text(vm, args)); if args.len() < 2 { return NativeResult::Ok(JsValue::null()); } let pattern_val = pattern_val.unwrap(); if is_re { let re_ptr = pattern_val.as_js_object_ptr(); let re = unsafe { &*re_ptr }; let fn_ptr = match re.native_fn() { Some(p) => p, None => return NativeResult::Ok(JsValue::null()), }; // SAFETY: fn_ptr 持有 regexp_constructor 存放的 `Box` 指针。 let regex = unsafe { &*(fn_ptr.as_ptr() as *const regress::Regex) }; let is_global = re.hash_props_vec().and_then(|v| v.get(3)).map(|v| v.as_bool()).unwrap_or(false); if is_global { let mut matches: Vec> = Vec::new(); text.for_each_match(regex, |m| { let range = m.range(); matches.push(text.slice(range.start, range.end).into_owned()); }); if matches.is_empty() { return NativeResult::Ok(JsValue::null()); } return NativeResult::Ok(make_units_array(vm, matches)); } if let Some(m) = text.find_from_units(regex, 0) { let range = m.range(); let mut parts: Vec> = Vec::with_capacity(m.captures.len() + 1); parts.push(text.slice(range.start, range.end).into_owned()); for i in 1..=m.captures.len() { match m.group(i) { Some(g) => parts.push(text.slice(g.start, g.end).into_owned()), None => parts.push(Vec::new()), } } return NativeResult::Ok(make_units_array(vm, parts)); } return NativeResult::Ok(JsValue::null()); } // 空模式命中串头:产出单元素空串数组(规格口径)。 if pattern.is_empty() { return NativeResult::Ok(make_units_array(vm, vec![Vec::new()])); } let s = text.units(); if let Some(pos) = find_units(&s, &pattern, 0) { let matched = s[pos..pos + pattern.len()].to_vec(); return NativeResult::Ok(make_units_array(vm, vec![matched])); } NativeResult::Ok(JsValue::null()) } /// `String.prototype.search(pattern)`:返回首个匹配位置(码元),无匹配返回 -1。 pub fn string_search(vm: &mut H, args: &[u8]) -> NativeResult { builtins_debug!("String.prototype.search called with {} args", args.len()); // 参数读取守卫先行:无参调用缺省 searchString 为 undefined(args 仅含 this 槽)。 let pattern_val = if args.len() >= 2 { vm.reg(args[1]) } else { JsValue::undefined() }; // 正则判定与参数转换先行(&mut 路径),后借 this 扫描。 let is_re = args.len() >= 2 && is_regexp_obj(pattern_val, vm); let pattern: Vec = if args.len() >= 2 && !is_re { try_string!(as_units(vm, pattern_val)).into_owned() } else { Vec::new() }; let text = try_string!(this_text(vm, args)); if args.len() < 2 { return NativeResult::Ok(JsValue::int(-1)); } if is_re { let re_ptr = pattern_val.as_js_object_ptr(); let re = unsafe { &*re_ptr }; let fn_ptr = match re.native_fn() { Some(p) => p, None => return NativeResult::Ok(JsValue::int(-1)), }; // SAFETY: fn_ptr 持有 regexp_constructor 存放的 `Box` 指针。 let regex = unsafe { &*(fn_ptr.as_ptr() as *const regress::Regex) }; if let Some(m) = text.find_from_units(regex, 0) { return NativeResult::Ok(JsValue::int(text.unit_pos(m.range().start) as i32)); } return NativeResult::Ok(JsValue::int(-1)); } // 空模式命中串头(规格口径)。 if pattern.is_empty() { return NativeResult::Ok(JsValue::int(0)); } let s = text.units(); if let Some(pos) = find_units(&s, &pattern, 0) { return NativeResult::Ok(JsValue::int(pos as i32)); } NativeResult::Ok(JsValue::int(-1)) } /// `String.prototype.isWellFormed()`:字符串无孤立 surrogate(每个码元要么是 /// 合法字符,要么与相邻码元成代理对)时返回 true。 pub fn string_is_well_formed(vm: &mut H, args: &[u8]) -> NativeResult { builtins_debug!("String.prototype.isWellFormed called with {} args", args.len()); let s = try_string!(this_units(vm, args)); let mut i = 0; while i < s.len() { let u = s[i]; if (0xDC00..=0xDFFF).contains(&u) { return NativeResult::Ok(JsValue::bool(false)); } if (0xD800..=0xDBFF).contains(&u) { if !(i + 1 < s.len() && (0xDC00..=0xDFFF).contains(&s[i + 1])) { return NativeResult::Ok(JsValue::bool(false)); } i += 2; } else { i += 1; } } NativeResult::Ok(JsValue::bool(true)) } /// `String.prototype.toWellFormed()`:把孤立 surrogate 替换为 U+FFFD 返回新字符串。 pub fn string_to_well_formed(vm: &mut H, args: &[u8]) -> NativeResult { builtins_debug!("String.prototype.toWellFormed called with {} args", args.len()); let s = try_string!(this_units(vm, args)); let mut out = Vec::with_capacity(s.len()); let mut i = 0; while i < s.len() { let u = s[i]; if (0xD800..=0xDBFF).contains(&u) { if i + 1 < s.len() && (0xDC00..=0xDFFF).contains(&s[i + 1]) { out.push(u); out.push(s[i + 1]); i += 2; continue; } out.push(0xFFFD); } else if (0xDC00..=0xDFFF).contains(&u) { out.push(0xFFFD); } else { out.push(u); } i += 1; } NativeResult::Ok(vm.new_string_units_owned(out)) } /// `String.prototype.normalize(form)`:按 NFC/NFD/NFKC/NFKD 规范化为 Unicode /// 规范形式(良形段映射,孤立 surrogate 单元原样保留)。 pub fn string_normalize(vm: &mut H, args: &[u8]) -> NativeResult { builtins_debug!("String.prototype.normalize called with {} args", args.len()); use unicode_normalization::UnicodeNormalization; let form = if args.len() > 1 { try_string!(as_units(vm, vm.reg(args[1]))).into_owned() } else { "NFC".encode_utf16().collect() }; // 规范化形式名按良形文本比对(NFC 等名无孤立 surrogate)。 let form_name = String::from_utf16_lossy(&form); let s = try_string!(this_units(vm, args)); let out = map_well_formed_segments(&s, |seg| match form_name.as_str() { "NFD" => seg.nfd().collect(), "NFKC" => seg.nfkc().collect(), "NFKD" => seg.nfkd().collect(), _ => seg.nfc().collect(), }); NativeResult::Ok(vm.new_string_units_owned(out)) } pub(crate) const MALL_INPUT: &str = "__mal_input__"; pub(crate) const MALL_INDEX: &str = "__mal_index__"; pub(crate) const MALL_RE: &str = "__mal_re__"; /// `String.prototype.matchAll(pattern)`:返回带 `next` 的迭代器,逐步产出全部匹配 /// (要求 RegExp 带 global 标志;普通字符串会被转义成等效正则)。包装器 input /// 属性存原始字符串值(单元保真),index 属性为码元游标。 pub fn string_match_all(vm: &mut H, args: &[u8]) -> NativeResult { builtins_debug!("String.prototype.matchAll called with {} args", args.len()); let this_val = vm.reg(args[0]); if this_val.is_null() || this_val.is_undefined() { return NativeResult::Err(crate::error::create_type_error( vm, "String.prototype method called on null or undefined", )); } if this_val.is_symbol() { return NativeResult::Err(crate::error::create_type_error(vm, "Cannot convert a Symbol value to a string")); } let input_val = match oxide_runtime_api::to_string_value_full(this_val, vm) { Ok(v) => v, Err(_) => { if let Some(exc) = vm.take_uncaught_value() { return NativeResult::Err(exc); } return NativeResult::Err(crate::error::create_type_error(vm, "Cannot convert value to a string")); } }; if args.len() < 2 { builtins_error!("String.prototype.matchAll: invalid receiver"); return NativeResult::Err(JsValue::undefined()); } let pattern_val = vm.reg(args[1]); let re_obj = if is_regexp_obj(pattern_val, vm) { let re_ptr = pattern_val.as_js_object_ptr(); let re = unsafe { &*re_ptr }; let is_global = re.hash_props_vec().and_then(|v| v.get(3)).map(|v| v.as_bool()).unwrap_or(false); if !is_global { return NativeResult::Err(crate::error::create_type_error( vm, "String.prototype.matchAll: regex must have global flag", )); } pattern_val } else { let pattern_units = try_string!(as_units(vm, pattern_val)).into_owned(); // 正则模式按文本编译:单元序列桥接为 lossy 文本(孤立 surrogate 映射 // FFFD,与 RegExp 构造器的文本口径编译一致)。 let pattern_str = String::from_utf16_lossy(&pattern_units); let escaped = regress::escape(&pattern_str); let rx_str = if escaped.is_empty() { String::from("(?:)") } else { format!("({})", escaped) }; let compiled = match regress::Regex::new(&rx_str) { Ok(rx) => rx, Err(e) => { return NativeResult::Err(crate::error::create_syntax_error(vm, &format!("Invalid regex: {}", e))); } }; let object_proto = vm.session().builtin_world().object_proto.as_ptr() as *mut JsObject; let mut stub = JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::from_js_object(object_proto)); // 载体专型标签:native_fn 槽的 Box 经 RegExp 同一守卫释放/深拷贝, // 避免每次 matchAll 泄漏一个已编译正则。 stub.type_tag = oxide_types::object::JsObject::OBJ_TYPE_REGEX_STUB; let boxed = Box::new(compiled); let raw = Box::into_raw(boxed) as *const u8; stub.set_native_fn(Some(unsafe { oxide_types::object::NativeFnPtr::from_raw(raw as *const ()) })); let stub_ptr = vm.alloc_object(stub); JsValue::from_js_object(stub_ptr) }; // 构建包装对象。 builder_wrapper(vm, input_val, re_obj) } fn builder_wrapper(vm: &mut H, input_val: JsValue, re_obj: JsValue) -> NativeResult { // matchAll 迭代器挂 %RegExpStringIteratorPrototype%(链到 %IteratorPrototype%), // next 由原型提供(不挂实例 own)。 let regexp_iter_proto = vm.session().builtin_world().regexp_string_iterator_proto.as_ptr() as *mut JsObject; let wrapper = vm.alloc_object(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::from_js_object(regexp_iter_proto))); let wrapper_obj = unsafe { &mut *wrapper }; let input_si = vm.kernel_core().perm_interner().intern(MALL_INPUT).0; let index_si = vm.kernel_core().perm_interner().intern(MALL_INDEX).0; let re_si = vm.kernel_core().perm_interner().intern(MALL_RE).0; vm.set_or_create_prop_value(wrapper_obj, input_si, input_val); vm.set_or_create_prop_value(wrapper_obj, index_si, JsValue::int(0)); vm.set_or_create_prop_value(wrapper_obj, re_si, re_obj); NativeResult::Ok(JsValue::from_js_object(wrapper)) } /// `String.prototype[Symbol.iterator]()`:返回按码元迭代字符的迭代器。 /// /// # 步骤 /// 1. null/undefined 抛 TypeError(RequireObjectCoercible) /// 2. this 经 ToString 完整转换(对象取 toString 结果,Symbol 抛 TypeError) /// 3. 包成统一迭代器包装(next 逐码元产出,耗尽后 done) /// /// # 边界与前提 /// - 对象 toString 抛出的原始异常原样传播 /// - 包装器挂 `%StringIteratorPrototype%` → `%IteratorPrototype%` 链 pub fn string_symbol_iterator(vm: &mut H, args: &[u8]) -> NativeResult { let this_val = vm.reg(if args.is_empty() { 0 } else { args[0] }); if this_val.is_null() || this_val.is_undefined() { return NativeResult::Err(crate::error::create_type_error( vm, "String.prototype[Symbol.iterator] called on null or undefined", )); } let s_val = match oxide_runtime_api::to_string_value_full(this_val, vm) { Ok(v) => v, Err(_) => { // ToString 触发对象 toString/valueOf 抛出的原生异常须原样传播。 if let Some(exc) = vm.take_uncaught_value() { return NativeResult::Err(exc); } return NativeResult::Err(crate::error::create_type_error(vm, "Cannot convert value to a string")); } }; // String 迭代器挂 %StringIteratorPrototype%(链到 %IteratorPrototype%): // 包装机制复用通用路径,仅替换原型为中继层。 let string_iter_proto = vm.session().builtin_world().string_iterator_proto.as_ptr() as *mut JsObject; match crate::iterator::make_iterator_for_value_with_proto(vm, s_val, string_iter_proto) { Ok(iterator) => NativeResult::Ok(iterator), Err(err) => NativeResult::Err(err), } } /// `matchAll` 迭代器的 `next`:返回 `{value: 匹配数组, done}`,耗尽后 done 为 true。 /// 游标为码元位置(两臂统一口径);空匹配推进 1 个码元。 pub fn string_match_all_next(vm: &mut H, args: &[u8]) -> NativeResult { let this_val = vm.reg(if args.is_empty() { 0 } else { args[0] }); if !this_val.is_object() { return NativeResult::Err(crate::error::create_type_error(vm, "matchAll next called on non-object")); } let wrapper = unsafe { &mut *this_val.as_js_object_ptr() }; let input_si = vm.kernel_core().perm_interner().intern(MALL_INPUT).0; let index_si = vm.kernel_core().perm_interner().intern(MALL_INDEX).0; let re_si = vm.kernel_core().perm_interner().intern(MALL_RE).0; let input_val = match vm.ordinary_get(wrapper, input_si, this_val) { Ok(v) if v.is_string() => v, _ => return make_match_done_result(vm, JsValue::undefined()), }; let idx_val = match vm.ordinary_get(wrapper, index_si, this_val) { Ok(v) => v, Err(_) => return make_match_done_result(vm, JsValue::undefined()), }; let idx = if idx_val.is_int() { idx_val.as_int().max(0) as usize } else { 0 }; let re_val = match vm.ordinary_get(wrapper, re_si, this_val) { Ok(v) if v.is_object() => v, _ => return make_match_done_result(vm, JsValue::undefined()), }; let re_obj = unsafe { &*re_val.as_js_object_ptr() }; let fn_ptr = match re_obj.native_fn() { Some(p) => p, None => return make_match_done_result(vm, JsValue::undefined()), }; let regex = unsafe { &*(fn_ptr.as_ptr() as *const regress::Regex) }; // 按 input 载荷形态分臂:Flat 走字节通道(游标经码元↔字节换算), // 其余走单元通道(游标即码元)。 // SAFETY: input_val 已校验为字符串值,裸指针借用压缩到单个表达式。 let sp = unsafe { &*input_val.as_string_ptr() }; // 耗尽守卫:空匹配正则(如 /(?:)/g、/a*/g)在串尾会反复命中同一末端空 // 匹配,游标推进越过码元总数后须直接 done,否则同一末端空匹配死循环。 let total_units = sp.utf16_len() as usize; if idx > total_units { return make_match_done_result(vm, JsValue::undefined()); } let (next_idx, parts): (usize, Vec>) = if sp.is_flat() { let s = sp.as_str(); match regex.find_from(s, unit_to_byte(s, idx)).next() { Some(m) => { let range = m.range(); let mut parts = Vec::with_capacity(m.captures.len() + 1); parts.push(s[range.start..range.end].encode_utf16().collect()); for i in 1..=m.captures.len() { match m.group(i) { Some(g) => parts.push(s[g.start..g.end].encode_utf16().collect()), None => parts.push(Vec::new()), } } // 空匹配(range 无推进)须推进至少一个码元,否则同一位置反复 // 空匹配死循环;非空匹配游标落匹配末尾(码元口径)。 let end_units = byte_to_unit(s, range.end); let next_idx = if range.end > range.start { end_units } else { end_units + 1 }; (next_idx, parts) } None => (0, Vec::new()), } } else { let u = sp.units(); match regex.find_from_utf16(&u, idx).next() { Some(m) => { let range = m.range(); let mut parts = Vec::with_capacity(m.captures.len() + 1); parts.push(u[range.start..range.end].to_vec()); for i in 1..=m.captures.len() { match m.group(i) { Some(g) => parts.push(u[g.start..g.end].to_vec()), None => parts.push(Vec::new()), } } let next_idx = if range.end > range.start { range.end } else { range.end + 1 }; (next_idx, parts) } None => (0, Vec::new()), } }; if parts.is_empty() { return make_match_done_result(vm, JsValue::undefined()); } vm.set_or_create_prop_value(wrapper, index_si, JsValue::int(next_idx as i32)); let value = make_units_array(vm, parts); make_match_done_result(vm, value) } fn make_match_done_result(vm: &mut H, value: JsValue) -> NativeResult { let done = value.is_undefined(); let object_proto = vm.session().builtin_world().object_proto.as_ptr() as *mut JsObject; let obj = vm.alloc_object(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::from_js_object(object_proto))); let value_si = vm.kernel_core().perm_interner().intern("value").0; let done_si = vm.kernel_core().perm_interner().intern("done").0; let obj_ref = unsafe { &mut *obj }; vm.set_or_create_prop_value(obj_ref, value_si, value); vm.set_or_create_prop_value(obj_ref, done_si, JsValue::bool(done)); NativeResult::Ok(JsValue::from_js_object(obj)) } /// `String.prototype.replaceAll(pattern, replacement)`:替换全部匹配 /// (RegExp 或字符串模式)。 pub fn string_replace_all(vm: &mut H, args: &[u8]) -> NativeResult { builtins_debug!("String.prototype.replaceAll called with {} args", args.len()); string_replace_impl(vm, args, true) } /// `String.raw(template, ...substitutions)`:从模板对象的 `raw` 数组元素与 /// substitutions 交错拼接,返回原始字符串(单元口径,lone surrogate 保真)。 /// /// # 步骤 /// 1. 取 template 的 `raw` 属性(必须为对象,否则抛 TypeError) /// 2. 取 raw 的 `length` 属性(ToLength) /// 3. 逐下标读 raw[i] 拼接;仅在段间(i + 1 < length)追加 substitutions[i] /// /// # 边界与前提 /// - raw[i] 缺失时视为 undefined(ToString 为 "undefined") /// - substitutions 缺失时段间追加空串,末段后不追加任何内容 pub fn string_raw(vm: &mut H, args: &[u8]) -> NativeResult { builtins_debug!("String.raw called with {} args", args.len()); // Step 1: 取 template 参数(args[1] 为 this 区域后的首个实参)。 if args.len() < 2 { return NativeResult::Err(crate::error::create_type_error(vm, "String.raw: template is required")); } let template_val = vm.reg(args[1]); if !template_val.is_object() || template_val.as_js_object_ptr().is_null() { return NativeResult::Err(crate::error::create_type_error(vm, "String.raw: template must be an object")); } let template_ptr = template_val.as_js_object_ptr(); // Step 2: 取 template.raw 属性。 let raw_si = vm.kernel_core().perm_interner().intern("raw").0; let template_obj = unsafe { &*template_ptr }; let raw_val = match vm.ordinary_get(template_obj, raw_si, template_val) { Ok(v) => v, Err(e) => { let exc = vm .take_uncaught_value() .unwrap_or_else(|| crate::error::create_type_error(vm, &e)); return NativeResult::Err(exc); } }; if !raw_val.is_object() || raw_val.as_js_object_ptr().is_null() { return NativeResult::Err(crate::error::create_type_error(vm, "String.raw: template.raw must be an object")); } let raw_ptr = raw_val.as_js_object_ptr(); // Step 3: 取 raw.length(ToLength,Symbol 抛 TypeError)。 let length_si = vm.kernel_core().perm_interner().intern("length").0; let raw_obj = unsafe { &*raw_ptr }; let raw_len = match vm.ordinary_get(raw_obj, length_si, raw_val) { Ok(v) => { // ToLength:经 to_number_full(Symbol 抛 TypeError / 对象 ToPrimitive 异常 // 原样传播),再按 ToLength 归一并截断(防超大 raw 数组导致 OOM)。 match oxide_runtime_api::to_number_full(v, vm) { Ok(n) => { let len = if n.is_nan() || n <= 0.0 { 0.0 } else { n.min(9_007_199_254_740_991.0) }; (len.trunc() as u64).min(0x1FFFFF) as usize } Err(msg) => { let exc = vm .take_uncaught_value() .unwrap_or_else(|| crate::error::create_type_error(vm, &msg)); return NativeResult::Err(exc); } } } Err(e) => { let exc = vm .take_uncaught_value() .unwrap_or_else(|| crate::error::create_type_error(vm, &e)); return NativeResult::Err(exc); } }; // Step 4: 逐下标拼接 raw[i] + substitutions[i]。 let mut result: Vec = Vec::new(); let raw_obj_ref = unsafe { &*raw_ptr }; for i in 0..raw_len { // 读 raw[i]:用 property_key_si 把整数 i 映射为整数键 si, // 再经 ordinary_get 沿原型链查找(触发 getter)。 let index_key = vm.property_key_si(JsValue::int(i as i32)); let raw_elem = vm.ordinary_get(raw_obj_ref, index_key, raw_val); let raw_units = match raw_elem { // ToString 完整路径:对象经 ToPrimitive(异常原样传播),Symbol 抛 TypeError。 Ok(v) => match oxide_runtime_api::to_string_value_full(v, vm) { Ok(sv) => vm.string_units(sv).into_owned(), Err(msg) => { let exc = vm .take_uncaught_value() .unwrap_or_else(|| crate::error::create_type_error(vm, &msg)); return NativeResult::Err(exc); } }, Err(_) => { if let Some(exc) = vm.take_uncaught_value() { return NativeResult::Err(exc); } "undefined".encode_utf16().collect() } }; result.extend(raw_units); // 段间(i + 1 < length)才追加 substitutions[i];substitutions 缺失时 // 追加空串(spec 24.b.i),末段后不再追加任何内容。 if i + 1 < raw_len { let sub_idx = i + 2; if sub_idx < args.len() { let sub_val = vm.reg(args[sub_idx]); // ToString 完整路径:对象 ToPrimitive 异常原样传播,Symbol 抛 TypeError。 let sub_units = match oxide_runtime_api::to_string_value_full(sub_val, vm) { Ok(sv) => vm.string_units(sv).into_owned(), Err(msg) => { let exc = vm .take_uncaught_value() .unwrap_or_else(|| crate::error::create_type_error(vm, &msg)); return NativeResult::Err(exc); } }; result.extend(sub_units); } } } NativeResult::Ok(vm.new_string_units_owned(result)) }