Files
OxideJS-New/crates/oxide_builtins/src/regexp.rs
T

2300 lines
98 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
use oxide_kernel::shape_forge::EMPTY_SHAPE_ID;
use oxide_types::object::{JsObject, NativeFnPtr};
use oxide_types::value::JsValue;
use oxide_runtime_api::{NativeResult, ProtoKind, VmHost};
use crate::string::{make_units_array, MatchText, OwnedText};
/// 构建 RegExp match result 的 `groups` 对象:null 原型(ObjectCreate(null)),
/// 键为命名捕获组名,值为匹配字符串或 undefined。
pub(crate) fn build_groups_object<H: VmHost>(vm: &mut H, m: &regress::Match, text: &MatchText) -> JsValue {
// 收集所有命名组(迭代器惰性,先 collect 判断是否有命名组)。
let named: Vec<_> = m.named_groups().collect();
if named.is_empty() {
return JsValue::undefined();
}
// null 原型:groups 对象不挂 Object.prototype(与 indices.groups 同面)。
let groups_obj = vm.alloc_object(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::null()));
let groups_ptr = unsafe { &mut *groups_obj };
// 遍历所有命名捕获组,按名称设置属性(重复名称取末次已定义的出现)。
for (name, range) in named {
let name_si = vm.perm_intern(name);
let value = match range {
Some(r) => vm.new_string_units_owned(text.slice(r.start, r.end).into_owned()),
None => JsValue::undefined(),
};
vm.set_or_create_prop_value(groups_ptr, name_si, value);
}
JsValue::from_js_object(groups_obj)
}
/// 构建 d 标志下的 `indices` 数组:`indices[0]` 为完整匹配 [start, end] 码元对,
/// 其后逐捕获组(未参与匹配的组为 undefined);自有 `groups` 属性为同构
/// indices.groups 对象(null 原型),全部经 CreateDataProperty 写入。
pub(crate) fn build_indices_array<H: VmHost>(vm: &mut H, m: &regress::Match, text: &MatchText) -> JsValue {
let group_count = m.captures.len();
let n = 1 + group_count;
let proto = vm.builtin_proto(ProtoKind::ArrayProto);
let arr = vm.alloc_object(JsObject::new_array(EMPTY_SHAPE_ID, JsValue::from_js_object(proto), n));
unsafe {
let range = m.range();
(*arr).set_prop_at(0, index_pair(vm, text.unit_pos(range.start), text.unit_pos(range.end)));
for i in 1..=group_count {
let v = match m.group(i) {
Some(g) => index_pair(vm, text.unit_pos(g.start), text.unit_pos(g.end)),
None => JsValue::undefined(),
};
(*arr).set_prop_at(i, v);
}
(*arr).set_prop_count(n);
}
let groups_val = build_indices_groups_object(vm, m, text);
let groups_si = vm.perm_intern("groups");
vm.set_or_create_prop_value(unsafe { &mut *arr }, groups_si, groups_val);
JsValue::from_js_object(arr)
}
/// 单个 [start, end] 码元对数组(数组原型,两个自身元素)。
fn index_pair<H: VmHost>(vm: &mut H, start: usize, end: usize) -> JsValue {
let proto = vm.builtin_proto(ProtoKind::ArrayProto);
let arr = vm.alloc_object(JsObject::new_array(EMPTY_SHAPE_ID, JsValue::from_js_object(proto), 2));
unsafe {
(*arr).set_prop_at(0, JsValue::int(start as i32));
(*arr).set_prop_at(1, JsValue::int(end as i32));
(*arr).set_prop_count(2);
}
JsValue::from_js_object(arr)
}
/// 构建 indices.groups:null 原型,键为命名捕获组名(首现源序),值为
/// [start, end] 码元对(未参与匹配的组为 undefined)。
fn build_indices_groups_object<H: VmHost>(vm: &mut H, m: &regress::Match, text: &MatchText) -> JsValue {
let named: Vec<_> = m.named_groups().collect();
if named.is_empty() {
return JsValue::undefined();
}
let groups_obj = vm.alloc_object(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::null()));
let groups_ptr = unsafe { &mut *groups_obj };
for (name, range) in named {
let name_si = vm.perm_intern(name);
let value = match range {
Some(r) => index_pair(vm, text.unit_pos(r.start), text.unit_pos(r.end)),
None => JsValue::undefined(),
};
vm.set_or_create_prop_value(groups_ptr, name_si, value);
}
JsValue::from_js_object(groups_obj)
}
fn get_regexp_ptr<H: VmHost>(vm: &mut H, args: &[u8]) -> Result<*mut JsObject, JsValue> {
let this_val = vm.reg(args[0]);
if !this_val.is_object() {
return Err(crate::error::create_type_error(vm, "RegExp.prototype method called on non-object"));
}
let ptr = this_val.as_js_object_ptr();
if ptr.is_null() {
return Err(crate::error::create_type_error(vm, "null object"));
}
if !unsafe { &*ptr }.is_regexp_obj() {
return Err(crate::error::create_type_error(vm, "RegExp.prototype method called on non-RegExp object"));
}
Ok(ptr)
}
/// 取 this 为对象:入口门禁只要求 IsObject(规范对 exec 的 this 无内部槽要求,
/// 编译正则槽的可用性在调用方另行判定);非对象(null/undefined/原始值)抛
/// TypeError。
///
/// 与 `get_regexp_ptr`(强校验 is_regexp_obj)并存:exec 走本放宽门禁,
/// test/toString 等入口保持强校验。
fn get_this_obj<H: VmHost>(vm: &mut H, args: &[u8]) -> Result<*mut JsObject, JsValue> {
let this_val = vm.reg(args[0]);
if !this_val.is_object() {
return Err(crate::error::create_type_error(vm, "RegExp.prototype method called on non-object"));
}
let ptr = this_val.as_js_object_ptr();
if ptr.is_null() {
return Err(crate::error::create_type_error(vm, "null object"));
}
Ok(ptr)
}
/// ToLength(Get) 读值的收尾口径:完整对象强转(对象经 ToPrimitive 触发
/// valueOf/toString,转换异常传播)后按 ToLength 收敛到 [0, 2^53-1]。
pub(crate) fn to_length_value<H: VmHost>(vm: &mut H, val: JsValue) -> Result<usize, JsValue> {
let n = match oxide_runtime_api::to_number_full(val, vm) {
Ok(n) => n,
Err(e) => {
if let Some(exc) = vm.take_uncaught_value() {
return Err(exc);
}
return Err(crate::error::create_from_text(vm, &e));
}
};
let len = if n.is_nan() || n <= 0.0 { 0.0 } else { n.min(9_007_199_254_740_991.0) };
Ok(len.trunc() as usize)
}
/// 以 Set 语义(strict)写 lastIndex:非可写属性抛 TypeError。
///
/// # 边界与前提
/// - 写入失败返回格式化错误文本,由调用方经 `create_from_text` 恢复错误种类。
fn set_last_index<H: VmHost>(
vm: &mut H, re_ptr: *mut JsObject, this_val: JsValue, index: usize,
) -> Result<(), JsValue> {
let li_si = vm.perm_intern("lastIndex");
// setter 抛错优先恢复 uncaught 原异常值(与 rx_set_prop 同口径)。
match vm.ordinary_set(unsafe { &mut *re_ptr }, li_si, JsValue::int(index as i32), this_val, true) {
Ok(()) => Ok(()),
Err(err) => Err(crate::iterator::engine_error(vm, &err)),
}
}
/// 以 Get 语义按名读属性:完整属性解析(自身数据/自身 accessor/原型链),
/// getter 抛错时恢复原异常值。
pub(crate) fn rx_get_prop<H: VmHost>(
vm: &mut H, obj: *mut JsObject, name: &str, this_val: JsValue,
) -> Result<JsValue, JsValue> {
let si = vm.perm_intern(name);
match vm.ordinary_get(unsafe { &*obj }, si, this_val) {
Ok(v) => Ok(v),
Err(_) => Err(crate::iterator::engine_error(vm, "cannot read property")),
}
}
/// 以 Get 语义读数值下标属性:下标经规范整数键换算(与字符串 "0" 形态
/// 同键),读异常恢复原异常值。
pub(crate) fn rx_get_index<H: VmHost>(
vm: &mut H, obj: *mut JsObject, idx: i32, receiver: JsValue,
) -> Result<JsValue, JsValue> {
let si = vm.property_key_si(JsValue::int(idx));
match vm.ordinary_get(unsafe { &*obj }, si, receiver) {
Ok(v) => Ok(v),
Err(_) => Err(crate::iterator::engine_error(vm, "cannot read result index")),
}
}
/// 以 Set 语义(strict)按名写属性:写失败(非可写等)恢复原异常值。
pub(crate) fn rx_set_prop<H: VmHost>(
vm: &mut H, obj: *mut JsObject, name: &str, val: JsValue, this_val: JsValue,
) -> Result<(), JsValue> {
let si = vm.perm_intern(name);
// setter 抛错时原异常值留在 uncaught 槽(优先恢复);写保护失败无
// uncaught,经格式化文本的 "TypeError: " 前缀恢复种类。
match vm.ordinary_set(unsafe { &mut *obj }, si, val, this_val, true) {
Ok(()) => Ok(()),
Err(err) => Err(crate::iterator::engine_error(vm, &err)),
}
}
/// live 语义读 flags 串:ToString(Get(rx, "flags")),getter 抛错与
/// ToString 抛错均传播原异常。
pub fn rx_get_flags<H: VmHost>(vm: &mut H, rx: *mut JsObject, this_val: JsValue) -> Result<String, JsValue> {
let val = rx_get_prop(vm, rx, "flags", this_val)?;
// 回退文案用原始文本("TypeError: " 前缀恢复错误种类)。
let s = match oxide_runtime_api::to_string_value_full(val, vm) {
Ok(v) => v,
Err(e) => return Err(crate::iterator::engine_error(vm, &e)),
};
Ok(String::from_utf16_lossy(&vm.string_units(s)))
}
/// live 语义读布尔属性:ToBoolean(Get(rx, name)),getter 抛错传播原异常。
fn rx_get_bool_prop<H: VmHost>(vm: &mut H, rx: *mut JsObject, name: &str, this_val: JsValue) -> Result<bool, JsValue> {
Ok(oxide_runtime_api::to_boolean(rx_get_prop(vm, rx, name, this_val)?))
}
/// 共享 RegExpExec(R, S):Get(R, "exec") 须可调用,Call(exec, R, «S»),
/// 结果非 null 非对象抛 TypeError。exec 自身的抛错恢复原异常值。
pub(crate) fn regexp_exec_call<H: VmHost>(
vm: &mut H, rx: *mut JsObject, rx_val: JsValue, s_val: JsValue,
) -> Result<JsValue, JsValue> {
let exec_val = rx_get_prop(vm, rx, "exec", rx_val)?;
if !crate::iterator::is_callable(exec_val) {
return Err(crate::error::create_type_error(vm, "RegExp exec is not callable"));
}
let s_arg = s_val;
let result = match vm.call_function_sync(exec_val, rx_val, &[s_arg]) {
Ok(r) => r,
Err(_) => return Err(crate::iterator::engine_error(vm, "RegExp exec call failed")),
};
if !(result.is_null() || result.is_object()) {
return Err(crate::error::create_type_error(vm, "RegExp exec result must be an object or null"));
}
Ok(result)
}
/// SpeciesConstructor(R, default):constructor 为 undefined 回 default、
/// 非对象抛 TypeError;@@species 为 undefined/null 回 default、非构造器抛
/// TypeError。属性读抛错恢复原异常值。
fn species_constructor<H: VmHost>(
vm: &mut H, r: *mut JsObject, r_val: JsValue, default: JsValue,
) -> Result<JsValue, JsValue> {
let c = rx_get_prop(vm, r, "constructor", r_val)?;
if c.is_undefined() {
return Ok(default);
}
if !c.is_object() {
return Err(crate::error::create_type_error(vm, "RegExp species constructor must be an object"));
}
// Get(C, @@species) 的 receiver 是构造器 C 本身:访问器 getter 返回
// receiver,传实例会让返回值错指到接收者本体。
let species_key = oxide_types::private_key::encode_symbol_key(vm.realm_id(), 10);
let c_val = JsValue::from_js_object(c.as_js_object_ptr());
let s = match vm.ordinary_get(unsafe { &*c.as_js_object_ptr() }, species_key, c_val) {
Ok(v) => v,
Err(_) => return Err(crate::iterator::engine_error(vm, "cannot read @@species")),
};
if s.is_undefined() || s.is_null() {
return Ok(default);
}
if !crate::array::is_constructor_value(s) {
return Err(crate::error::create_type_error(vm, "RegExp @@species is not a constructor"));
}
Ok(s)
}
/// AdvanceStringIndex:零宽匹配后的推进——无条件前移,unicode 口径下起点对
/// 代理对跨两个单元,其余跨一个码元(越出串长同样前移,与实现语义一致)。
pub(crate) fn advance_string_index(units: &[u16], p: usize, unicode: bool) -> usize {
if unicode
&& p < units.len()
&& (0xD800..=0xDBFF).contains(&units[p])
&& p + 1 < units.len()
&& (0xDC00..=0xDFFF).contains(&units[p + 1])
{
p + 2
} else {
p + 1
}
}
/// GetSubstitution:非函数替换串的 `$` 引用展开(`$$`/`$&`/`` $` ``/`$'`/`$n`/`$<name>`);
/// `$n` 越界(含 $0 与两位回退后仍越界)或未匹配组(None)按字面输出。
/// `$<name>` 经真 Get 从 named_captures 对象读取(receiver 为对象本身,getter
/// 抛错与捕获值 ToString 抛错均恢复原异常上抛);named_captures 为 None 或无闭合
/// `>` 时只字面输出 `$<`,其后文本继续逐码元处理。
pub(crate) fn get_substitution_units<H: VmHost>(
vm: &mut H, matched: &[u16], s: &[u16], position: usize, captured: &[Option<Vec<u16>>],
named_captures: Option<&JsObject>, replacement: &[u16],
) -> Result<Vec<u16>, JsValue> {
let mut out = Vec::new();
let pos = position.min(s.len());
let tail = (pos + matched.len()).min(s.len());
let mut i = 0;
while i < replacement.len() {
if replacement[i] != 0x24 {
out.push(replacement[i]);
i += 1;
continue;
}
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(matched);
i += 2;
} else if rest.starts_with(&[0x24, 0x60]) {
out.extend_from_slice(&s[..pos]);
i += 2;
} else if rest.starts_with(&[0x24, 0x27]) {
out.extend_from_slice(&s[tail..]);
i += 2;
} else if rest.starts_with(&[0x24, 0x3c]) {
// $<name> 命名组引用。named_captures 为 None 或无闭合 > 时只字面
// 输出 `$<`:名段(含其中的 $ 序列)回落主循环继续逐码元处理。
match (rest[2..].iter().position(|&c| c == 0x3e), named_captures) {
(Some(end), Some(obj)) => {
let name_units = &rest[2..2 + end];
let name_str = String::from_utf16_lossy(name_units);
let si = vm.perm_intern(&name_str);
// 真 Get 走完整原型链;getter 抛错恢复原异常上抛。
let cap = match vm.ordinary_get(
obj,
si,
JsValue::from_js_object(obj as *const JsObject as *mut JsObject),
) {
Ok(v) => v,
Err(_) => return Err(crate::iterator::engine_error(vm, "cannot read named capture")),
};
// 捕获 undefined 替换为空串;其余经 ToString(抛错传播)。
if !cap.is_undefined() {
let cap_str = to_string_units(vm, cap)?;
out.extend_from_slice(&cap_str);
}
i += 2 + end + 1;
}
_ => {
out.push(0x24);
out.push(0x3c);
i += 2;
}
}
} else if i + 1 < replacement.len() && (0x30..=0x39).contains(&replacement[i + 1]) {
let digit_count = if i + 2 < replacement.len() && (0x30..=0x39).contains(&replacement[i + 2]) {
2
} else {
1
};
let d1 = (replacement[i + 1] - 0x30) as usize;
let mut index = if digit_count == 2 {
d1 * 10 + (replacement[i + 2] - 0x30) as usize
} else {
d1
};
let mut ref_len = 1 + digit_count;
if index > captured.len() && digit_count == 2 {
index = d1;
ref_len = 2;
}
if index == 0 {
// $0 非替换符号,字面输出(且避开 0-1 下溢)。
out.extend_from_slice(&replacement[i..i + ref_len]);
} else if let Some(cap) = captured.get(index - 1) {
// 未匹配组(None 槽)按空串替换,仅越界才字面回退。
if let Some(u) = cap {
out.extend_from_slice(u);
}
} else {
out.extend_from_slice(&replacement[i..i + ref_len]);
}
i += ref_len;
} else {
out.push(0x24);
i += 1;
}
}
Ok(out)
}
/// 规范化 flags 顺序为规范序(d g i m s u v y):同一组 flag 不论书写顺序
/// 恒得同一串,保证 `flags`/`toString` 输出与 V8 一致。
fn normalize_flags(flags: &str) -> String {
let mut out = String::new();
for c in ['d', 'g', 'i', 'm', 's', 'u', 'v', 'y'] {
if flags.contains(c) {
out.push(c);
}
}
out
}
/// `RegExp(pattern, flags)` 构造逻辑:用 regress 引擎编译模式(ECMAScript 语法,
/// 支持 backreference/lookaround/命名组/v-flag);非法模式抛 SyntaxError。
/// 编译结果存于对象的 native_fn 槽。
pub fn regexp_constructor<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
// [[Source]]/[[Flags]] 是 ToString 结果的**单元序列**:`to_units_full` 直取
// (lossy `to_string` 会把孤立 surrogate 单元折成 FFFD,破坏 source 往返)。
// regress 编译用 lossy 文本(其匹配语义对孤立 surrogate 本即既有缺口,
// 不因本处改变)。
// 模式为已编译 RegExp 实例时 source/flags 取其原始模式文本(flags 参数
// 缺省取实例 flags);其余形态按 ToString 单元序列。
let (pattern_units, flags_units) = if args.len() >= 2 {
let pattern_val = vm.reg(args[1]);
let re_ptr = pattern_val.as_js_object_ptr();
if pattern_val.is_object() && !re_ptr.is_null() && unsafe { &*re_ptr }.is_regexp_obj() {
// source 取实例原始模式字段而非 live 属性:source 访问器输出已
// 转义文本(可安全嵌入字面量),直接编译会破坏模式匹配等价
// (含反斜杠的模式恒失配);原始文本经同一编译器保持往返一致。
let source = unsafe { &*re_ptr }.get_regexp_source();
let flags_val = if args.len() >= 3 {
vm.reg(args[2])
} else {
match rx_get_prop(vm, re_ptr, "flags", pattern_val) {
Ok(v) => v,
Err(e) => return NativeResult::Err(e),
}
};
let flags = match oxide_runtime_api::to_string_value_full(flags_val, vm) {
Ok(v) => v,
Err(e) => return NativeResult::Err(crate::iterator::engine_error(vm, &e)),
};
(vm.string_units(source).into_owned(), vm.string_units(flags).into_owned())
} else {
// undefined 模式按空串编译(null 仍走 ToString → "null")。
let p = if pattern_val.is_undefined() {
Vec::new()
} else {
match oxide_runtime_api::to_units_full(pattern_val, vm) {
Ok(u) => u,
// ToPrimitive 调 toString 抛错时原异常值留在 uncaught 槽,优先恢复。
Err(e) => return NativeResult::Err(crate::iterator::engine_error(vm, &e)),
}
};
let f = if args.len() >= 3 {
match oxide_runtime_api::to_units_full(vm.reg(args[2]), vm) {
Ok(u) => u,
Err(e) => return NativeResult::Err(crate::iterator::engine_error(vm, &e)),
}
} else {
Vec::<u16>::new()
};
(p, f)
}
} else {
(Vec::<u16>::new(), Vec::<u16>::new())
};
let pattern = String::from_utf16_lossy(&pattern_units);
// RegExpInitialize step 5:未知旗与重旗均抛 SyntaxError(regress 的
// Flags::new 静默跳未知旗、重旗幂等吞,构造器侧须前置校验)。
let mut flag_seen = [false; 8];
for &unit in &flags_units {
let ch = char::from_u32(unit as u32).unwrap_or('\u{FFFD}');
let idx = match ch {
'd' => 0,
'g' => 1,
'i' => 2,
'm' => 3,
's' => 4,
'u' => 5,
'v' => 6,
'y' => 7,
_ => {
return NativeResult::Err(crate::error::create_syntax_error(
vm,
&format!("Invalid regular expression flags: '{ch}' is not a valid flag"),
));
}
};
if flag_seen[idx] {
return NativeResult::Err(crate::error::create_syntax_error(
vm,
&format!("Invalid regular expression flags: '{ch}' is duplicated"),
));
}
flag_seen[idx] = true;
}
let flags = normalize_flags(&String::from_utf16_lossy(&flags_units));
let flags_units = flags.encode_utf16().collect::<Vec<u16>>();
// u 与 v 互斥(regress 不校验此约束):编译前置抛 SyntaxError。
if flags.contains('u') && flags.contains('v') {
return NativeResult::Err(crate::error::create_syntax_error(
vm,
"Invalid regular expression flags: 'u' and 'v' are mutually exclusive",
));
}
// 构造路径(new / Reflect.construct,new.target 为对象):复用调用方按
// newTarget.prototype 预分配的 receiver,原型基由 newTarget 决定(子类实例
// proto = 子类 prototype,与 %RegExp.prototype% 不可恒等);普通调用
// (new.target 非对象)自分配 %RegExp.prototype% 基新对象,忽略传入的 this。
let this_val = vm.reg(if args.is_empty() { 0 } else { args[0] });
let new_target = vm.reg(255);
let this_ptr = if this_val.is_object() && new_target.is_object() {
this_val.as_js_object_ptr()
} else {
vm.alloc_object(JsObject::new_empty(
EMPTY_SHAPE_ID,
JsValue::from_js_object(vm.builtin_proto(ProtoKind::RegExpProto)),
))
};
// regress 用 JS flag 字符串编译:g/i/m/s/y/u/v 原样透传(vendored fork
// 原生识别 v 标志,u/v 互斥已在上方前置校验)。
let compiled = regress::Regex::with_flags(&pattern, flags.as_str());
match compiled {
Ok(re) => {
let re_ptr = Box::into_raw(Box::new(re));
// SAFETY: re_ptr 是构造器经 `NativeFnPtr::from_raw(re_ptr as *const ())`
// 写入的 `Box<regress::Regex>` 指针。RegExp 对象把 native_fn 字段复用作
// 已编译 Regex 的存放处——而非 NativeFn 指针。对象存活期间有效;
// VM 重置经 `drop_regexp_native` 释放该 Box。
unsafe {
(*this_ptr).set_native_fn(Some(NativeFnPtr::from_raw(re_ptr as *const ())));
}
}
Err(e) => {
return NativeResult::Err(crate::error::create_syntax_error(
vm,
&format!("Invalid regular expression: {e}"),
));
}
}
// lastIndex 作实例自身属性写入(缺省描述符,与既有构造器口径一致)。
{
let li_si = vm.perm_intern("lastIndex");
let shape_id = vm.make_shape(unsafe { (*this_ptr).shape_id() }, li_si);
unsafe {
let obj = &mut *this_ptr;
obj.set_shape_id(shape_id);
obj.push_prop(JsValue::int(0));
obj.type_tag = JsObject::OBJ_TYPE_REGEXP;
// source/flags 存实例字段:原型访问器从本字段读取,不作为自身属性枚举。
obj.set_regexp_source(vm.new_string_units_owned(pattern_units));
obj.set_regexp_flags(vm.new_string_units_owned(flags_units));
}
}
NativeResult::Ok(JsValue::from_js_object(this_ptr))
}
/// 只读核算持有编译正则对象(RegExp / matchAll 载体)的正则字节(不释放)。
pub fn regexp_native_size(obj: &JsObject) -> u64 {
if !obj.holds_compiled_regex() {
return 0;
}
let Some(ptr) = obj.native_fn() else {
return 0;
};
let regex_ptr = ptr.as_ptr() as *mut regress::Regex;
if regex_ptr.is_null() {
return 0;
}
std::mem::size_of::<regress::Regex>() as u64
}
/// 释放持有编译正则对象(RegExp / matchAll 载体)native_fn 槽中的
/// `regress::Regex`,返回释放字节数。
pub fn drop_regexp_native(obj: &mut JsObject) -> u64 {
let bytes = regexp_native_size(obj);
if bytes == 0 {
return 0;
}
let Some(ptr) = obj.native_fn() else {
return 0;
};
let regex_ptr = ptr.as_ptr() as *mut regress::Regex;
// SAFETY: regex_ptr 非空(regexp_native_size 已验证),Box::from_raw 恰好释放一次。
unsafe { drop(Box::from_raw(regex_ptr)) };
obj.set_native_fn(None);
bytes
}
/// `RegExp.prototype.test(string)`:判断是否匹配。global/sticky 从 lastIndex
/// 开始匹配,sticky 要求匹配恰在 lastIndex 起点。
///
/// # 步骤
/// 1. this 须为 RegExp 实例(内部槽门禁,非对象/非 RegExp 抛 TypeError)。
/// 2. S = ToString(string);flags 串判 global/sticky(g/y 字面量互斥,至多
/// 其一,共用 tracks 分支)。
/// 3. 共享搜索核(与 exec 同一实现):lastIndex 读、越界短路、sticky 锚定
/// 过滤、lastIndex Set 写回。
/// 4. 命中返回 true,否则 false。
///
/// # 边界与前提
/// - lastIndex 负值/NaN/非数字经 ToLength 收敛为 0;值以码元口径计。
/// - 非 global/sticky 时 lastIndex 不读入搜索、不写回(属性保持原值)。
///
/// # 副作用
/// - global/sticky 时按匹配结果 Set this.lastIndex(失败置 0、成功置匹配末尾)。
pub fn regexp_test<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
let re_ptr = match get_regexp_ptr(vm, args) {
Ok(ptr) => ptr,
Err(err) => return NativeResult::Err(err),
};
let this_val = vm.reg(args[0]);
let fn_ptr = {
let re = unsafe { &*re_ptr };
match re.native_fn() {
None => {
return NativeResult::Err(crate::error::create_type_error(vm, "invalid RegExp"));
}
Some(p) => p,
}
};
// SAFETY: fn_ptr 持有 regexp_constructor 存放的 `Box<regress::Regex>` 指针。
let regex = unsafe { &*(fn_ptr.as_ptr() as *const regress::Regex) };
let haystack = match regexp_text_arg(vm, args) {
Ok(s) => s,
Err(err) => return NativeResult::Err(err),
};
let text = haystack.as_match_text();
// 标志决定搜索起点与 lastIndex 写回条件。
let (is_global, is_sticky) = {
let re = unsafe { &*re_ptr };
(regexp_has_flag(vm, re, 'g'), regexp_has_flag(vm, re, 'y'))
};
let tracks_last_index = is_global || is_sticky;
let found = match rx_search(vm, re_ptr, this_val, regex, &text, tracks_last_index, is_sticky) {
Ok(r) => r,
Err(err) => return NativeResult::Err(err),
};
NativeResult::Ok(JsValue::bool(found.is_some()))
}
/// exec/test 共享搜索核(规范 RegExpBuiltinExec 的搜索部分):lastIndex 经
/// Get + ToLength 无条件读取(非 global/sticky 臂读到后丢弃、起点恒 0),
/// 越界短路(先 Set 0),匹配与 sticky 后置锚定过滤,lastIndex Set 写回
/// (失败置 0、成功置匹配末尾,仅 global/sticky,非可写属性抛 TypeError)。
///
/// # 边界与前提
/// - lastIndex 负值/NaN/非数字经 ToLength 收敛为 0;值以码元口径计。
/// - sticky 锚定过滤:底层引擎对 y 不原生锚定,命中起点须恰在 lastIndex 才算有效。
///
/// # 副作用
/// - global/sticky 时按匹配结果 Set this.lastIndex(越界/失败置 0、成功置末尾)。
pub(crate) fn rx_search<H: VmHost>(
vm: &mut H, re_ptr: *mut JsObject, this_val: JsValue, regex: &regress::Regex, text: &MatchText,
tracks_last_index: bool, is_sticky: bool,
) -> Result<Option<regress::Match>, JsValue> {
// lastIndex 读无条件执行(规范先读后在非 global/sticky 臂丢弃);
// 读走 Get + ToLength:完整属性解析,读异常传播原异常。
let last_index = {
let li_si = vm.perm_intern("lastIndex");
match vm.ordinary_get(unsafe { &*re_ptr }, li_si, this_val) {
Ok(val) => to_length_value(vm, val)?,
Err(_) => {
if let Some(exc) = vm.take_uncaught_value() {
return Err(exc);
}
return Err(crate::error::create_type_error(vm, "Cannot read lastIndex"));
}
}
};
// 搜索起点:global/sticky 自 lastIndex 起,其余自 0。u/v 标志下起点须
// 落在码点边界(低 surrogate 位钳回代理对起点,u 标志匹配只起于码点边界)。
let start = if tracks_last_index { last_index } else { 0 };
let start = text.advance_start(regex, start);
// 越界短路:lastIndex 超出串长必不匹配,规范先 Set 0 再空结果。
if start > text.len_units() {
if tracks_last_index {
set_last_index(vm, re_ptr, this_val, 0)?;
}
return Ok(None);
}
// 匹配范围取臂原生命径(Str 臂字节、Units 臂码元);sticky 加后置锚定
// 过滤——底层引擎对 y 不原生锚定,命中起点须恰在推进后的 lastIndex 才算有效。
let match_result = text.find_from_units(regex, start);
let anchored = if is_sticky {
match_result.filter(|m| text.unit_pos(m.range().start) == start)
} else {
match_result
};
let Some(m) = anchored else {
// 失败:仅 global/sticky Set 0。
if tracks_last_index {
set_last_index(vm, re_ptr, this_val, 0)?;
}
return Ok(None);
};
// 成功:lastIndex 推进到匹配末尾(仅 global/sticky),Set 语义。
if tracks_last_index {
set_last_index(vm, re_ptr, this_val, text.unit_pos(m.range().end))?;
}
Ok(Some(m))
}
/// `RegExp.prototype.exec(string)`:执行匹配并返回数组(含捕获组、index、input);
/// 无匹配返回 null。
///
/// # 步骤
/// 1. this 仅需对象(IsObject 门禁);编译正则槽必须存在,非 RegExp 对象无
/// 可匹配模式,同抛 TypeError。
/// 2. S = ToString(string);flags 串判 global/sticky(g/y 字面量互斥,至多
/// 其一,共用 tracks 分支)。
/// 3. 共享搜索核(与 test 同一实现):lastIndex 读(Get + ToLength)、越界
/// 短路(先 Set 0)、sticky 后置锚定过滤、lastIndex Set 写回。
/// 4. 无匹配回 null;命中构建结果数组(捕获组、index、input、groups)。
///
/// # 边界与前提
/// - lastIndex 负值/NaN/非数字经 ToLength 收敛为 0;值以码元口径计。
/// - 非对象 this 抛 TypeError(门禁),对象但无编译正则同样抛 TypeError。
///
/// # 副作用
/// - global/sticky 时按匹配结果 Set this.lastIndex(失败置 0、成功置末尾)。
/// - 命中时分配结果数组。
pub fn regexp_exec<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
let re_ptr = match get_this_obj(vm, args) {
Ok(ptr) => ptr,
Err(err) => return NativeResult::Err(err),
};
let this_val = vm.reg(args[0]);
// 编译正则槽:非 RegExp 对象无可匹配模式。
let fn_ptr = {
let re = unsafe { &*re_ptr };
match re.native_fn() {
None => {
return NativeResult::Err(crate::error::create_type_error(vm, "invalid RegExp"));
}
Some(p) => p,
}
};
// SAFETY: fn_ptr 持有 regexp_constructor 存放的 `Box<regress::Regex>` 指针。
let regex = unsafe { &*(fn_ptr.as_ptr() as *const regress::Regex) };
let haystack = match regexp_text_arg(vm, args) {
Ok(s) => s,
Err(err) => return NativeResult::Err(err),
};
let text = haystack.as_match_text();
// 标志决定搜索起点、lastIndex 写回条件与 indices 产出(d 标志)。
let (is_global, is_sticky, has_indices) = {
let re = unsafe { &*re_ptr };
(regexp_has_flag(vm, re, 'g'), regexp_has_flag(vm, re, 'y'), regexp_has_flag(vm, re, 'd'))
};
let tracks_last_index = is_global || is_sticky;
// 共享搜索核:lastIndex 读、越界短路、sticky 锚定过滤、lastIndex 写回。
let match_result = match rx_search(vm, re_ptr, this_val, regex, &text, tracks_last_index, is_sticky) {
Ok(r) => r,
Err(err) => return NativeResult::Err(err),
};
let Some(m) = match_result else {
return NativeResult::Ok(JsValue::null());
};
// 命中后更新 15 个遗留静态槽(input / lastMatch / lastParen / leftContext /
// rightContext / index / $1-$9):值以非枚举可配置数据属性落 %RegExp% 构造器
// (字符串 / 整数,非对象边)。仅 exec 接线——test 不建结果数组,语料不测
// test 后的槽值,取舍在此。
update_legacy_slots(vm, &m, &text, &haystack);
let range = m.range();
let group_count = m.captures.len();
let n = 1 + group_count;
let proto = vm.builtin_proto(ProtoKind::ArrayProto);
let arr = vm.alloc_object(JsObject::new_array(EMPTY_SHAPE_ID, JsValue::from_js_object(proto), n));
unsafe {
(*arr).set_prop_at(0, vm.new_string_units_owned(text.slice(range.start, range.end).into_owned()));
// 捕获组:未参与匹配的组为 undefined。
for i in 1..=group_count {
match m.group(i) {
Some(g) => (*arr).set_prop_at(i, vm.new_string_units_owned(text.slice(g.start, g.end).into_owned())),
None => (*arr).set_prop_at(i, JsValue::undefined()),
}
}
(*arr).set_prop_count(n);
}
let index_si = vm.perm_intern("index");
vm.set_or_create_prop_value(unsafe { &mut *arr }, index_si, JsValue::int(text.unit_pos(range.start) as i32));
let input_val = haystack.to_value(vm);
let input_si = vm.perm_intern("input");
vm.set_or_create_prop_value(unsafe { &mut *arr }, input_si, input_val);
let groups_val = build_groups_object(vm, &m, &text);
let groups_si = vm.perm_intern("groups");
vm.set_or_create_prop_value(unsafe { &mut *arr }, groups_si, groups_val);
// d 标志:挂 indices 属性(逐组 [start, end] 码元对 + indices.groups 同构对象)。
if has_indices {
let indices_val = build_indices_array(vm, &m, &text);
let indices_si = vm.perm_intern("indices");
vm.set_or_create_prop_value(unsafe { &mut *arr }, indices_si, indices_val);
}
NativeResult::Ok(JsValue::from_js_object(arr))
}
/// `RegExp.prototype.toString`:按 `/source/flags` 形式返回。
pub fn regexp_to_string<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
let re_ptr = match get_regexp_ptr(vm, args) {
Ok(ptr) => ptr,
Err(err) => return NativeResult::Err(err),
};
let re = unsafe { &*re_ptr };
let source = vm.lookup_str(re.get_regexp_source()).unwrap_or_default();
let flags = vm.lookup_str(re.get_regexp_flags()).unwrap_or_default();
let result = format!("/{}/{}", source, flags);
NativeResult::Ok(vm.new_string_owned(result))
}
/// `RegExp.prototype.compile(pattern, flags)`(Annex B):就地重编译本正则并
/// 将 lastIndex 归零,返回 this。
///
/// # 步骤
/// 1. this 门禁:须为对象、`is_regexp_obj()`、且 proto 与 %RegExp.prototype%
/// 精确恒等(非对象 / 非 RegExp / 子类实例均抛 TypeError)。
/// 2. pattern 为 RegExp 实例(`is_regexp_obj()`)时:flags 必须为 undefined
/// (否则 TypeError),且直读 [[OriginalSource]]/[[OriginalFlags]] 槽
/// (不触发属性访问)。
/// 3. pattern 非 RegExp 时:undefined → 空串,否则完整 ToString(异常原样
/// 传播);flags 同。
/// 4. flags 校验(未知 / 重复 / u-v 互斥 → SyntaxError)→ regress 编译(失败
/// → SyntaxError);编译失败时对象状态不变(槽与 lastIndex 均不动)。
/// 5. 编译成功后才更新 [[OriginalSource]]/[[OriginalFlags]] 槽并替换 native_fn
/// 槽(先释放旧 Box 再写新指针,顺序不可反)。
/// 6. Set(this, "lastIndex", 0, true)(strict,可抛 TypeError;此时槽已更新)。
/// 7. 返回 this。
///
/// # 边界与前提
/// - 子类实例(proto = 子类 prototype)在第 1 步即抛 TypeError,不触后续。
/// - pattern 为 RegExp 时不读任何属性(源 / 旗直取内部槽),访问器计数恒 0。
///
/// # 副作用
/// - 编译成功时更新对象内部槽(source / flags / native_fn)与 lastIndex。
pub fn regexp_compile<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
// 第 1 步:this 门禁。
let this_val = vm.reg(args[0]);
if !this_val.is_object() {
return NativeResult::Err(crate::error::create_type_error(vm, "RegExp.prototype.compile called on non-object"));
}
let this_ptr = this_val.as_js_object_ptr();
if this_ptr.is_null() {
return NativeResult::Err(crate::error::create_type_error(vm, "null object"));
}
if !unsafe { &*this_ptr }.is_regexp_obj() {
return NativeResult::Err(crate::error::create_type_error(
vm,
"RegExp.prototype.compile called on non-RegExp object",
));
}
// proto 精确恒等:子类实例(proto = 子类 prototype)抛 TypeError。
let proto_ptr = vm.builtin_proto(ProtoKind::RegExpProto);
if unsafe { &*this_ptr }.proto() != JsValue::from_js_object(proto_ptr) {
return NativeResult::Err(crate::error::create_type_error(
vm,
"RegExp.prototype.compile called on subclass instance",
));
}
// 第 2 / 3 步:pattern / flags 取参。
let pattern_val = if args.len() > 1 { vm.reg(args[1]) } else { JsValue::undefined() };
let flags_val = if args.len() > 2 { vm.reg(args[2]) } else { JsValue::undefined() };
let (pattern_units, flags_units) = if pattern_val.is_object() {
let p_ptr = pattern_val.as_js_object_ptr();
if !p_ptr.is_null() && unsafe { &*p_ptr }.is_regexp_obj() {
// pattern 为 RegExp 实例:flags 必须为 undefined。
if !flags_val.is_undefined() {
return NativeResult::Err(crate::error::create_type_error(
vm,
"RegExp.prototype.compile: flags must be undefined when pattern is a RegExp",
));
}
// 直读 [[OriginalSource]]/[[OriginalFlags]] 槽(不触发属性访问)。
let source = unsafe { &*p_ptr }.get_regexp_source();
let flags = unsafe { &*p_ptr }.get_regexp_flags();
(vm.string_units(source).into_owned(), vm.string_units(flags).into_owned())
} else {
// pattern 非 RegExp:undefined → 空串,否则完整 ToString。
(
match to_pattern_units(vm, pattern_val) {
Ok(u) => u,
Err(e) => return NativeResult::Err(e),
},
match to_pattern_units(vm, flags_val) {
Ok(u) => u,
Err(e) => return NativeResult::Err(e),
},
)
}
} else {
(
match to_pattern_units(vm, pattern_val) {
Ok(u) => u,
Err(e) => return NativeResult::Err(e),
},
match to_pattern_units(vm, flags_val) {
Ok(u) => u,
Err(e) => return NativeResult::Err(e),
},
)
};
let pattern = String::from_utf16_lossy(&pattern_units);
// 第 4 步:flags 校验(未知 / 重复 / u-v 互斥均抛 SyntaxError)。
let mut flag_seen = [false; 8];
for &unit in &flags_units {
let ch = char::from_u32(unit as u32).unwrap_or('\u{FFFD}');
let idx = match ch {
'd' => 0,
'g' => 1,
'i' => 2,
'm' => 3,
's' => 4,
'u' => 5,
'v' => 6,
'y' => 7,
_ => {
return NativeResult::Err(crate::error::create_syntax_error(
vm,
&format!("Invalid regular expression flags: '{ch}' is not a valid flag"),
));
}
};
if flag_seen[idx] {
return NativeResult::Err(crate::error::create_syntax_error(
vm,
&format!("Invalid regular expression flags: '{ch}' is duplicated"),
));
}
flag_seen[idx] = true;
}
let flags = normalize_flags(&String::from_utf16_lossy(&flags_units));
let flags_units = flags.encode_utf16().collect::<Vec<u16>>();
if flags.contains('u') && flags.contains('v') {
return NativeResult::Err(crate::error::create_syntax_error(
vm,
"Invalid regular expression flags: 'u' and 'v' are mutually exclusive",
));
}
// 第 4 步续:regress 编译(失败 → SyntaxError,对象状态不变)。
let re = match regress::Regex::with_flags(&pattern, flags.as_str()) {
Ok(re) => re,
Err(e) => {
return NativeResult::Err(crate::error::create_syntax_error(
vm,
&format!("Invalid regular expression: {e}"),
));
}
};
// 第 5 步:更新槽并替换 native_fn 槽(先释放旧 Box 再写新指针)。
let re_ptr = Box::into_raw(Box::new(re));
unsafe {
let obj = &mut *this_ptr;
drop_regexp_native(obj);
// SAFETY: re_ptr 为 `Box<regress::Regex>` 指针,同构造器写入口径。
obj.set_native_fn(Some(NativeFnPtr::from_raw(re_ptr as *const ())));
obj.set_regexp_source(vm.new_string_units_owned(pattern_units));
obj.set_regexp_flags(vm.new_string_units_owned(flags_units));
}
// 第 6 步:Set(this, "lastIndex", 0, true)(strict,可抛 TypeError)。
if let Err(err) = set_last_index(vm, this_ptr, this_val, 0) {
return NativeResult::Err(err);
}
// 第 7 步:返回 this。
NativeResult::Ok(this_val)
}
/// compile 的 pattern / flags 单元序列转换:undefined → 空串,其余完整
/// ToString(异常原样传播)。
fn to_pattern_units<H: VmHost>(vm: &mut H, val: JsValue) -> Result<Vec<u16>, JsValue> {
if val.is_undefined() {
return Ok(Vec::new());
}
match oxide_runtime_api::to_units_full(val, vm) {
Ok(u) => Ok(u),
Err(e) => Err(crate::iterator::engine_error(vm, &e)),
}
}
/// 遗留静态访问器门禁:`SameValue(%RegExp%, this)` 为假抛 TypeError(非对象、
/// 实例、prototype、子类构造器、全部原始值均抛)。
fn legacy_gate<H: VmHost>(vm: &mut H, args: &[u8]) -> Result<(), JsValue> {
let this_val = vm.reg(args[0]);
if !this_val.is_object() {
return Err(crate::error::create_type_error(vm, "RegExp legacy accessor requires %RegExp% receiver"));
}
let this_ptr = this_val.as_js_object_ptr();
let ctor_ptr = vm.builtin_proto(ProtoKind::RegExpConstructor);
if this_ptr != ctor_ptr {
return Err(crate::error::create_type_error(vm, "RegExp legacy accessor requires %RegExp% receiver"));
}
Ok(())
}
/// 把遗留槽数据属性写入 %RegExp% 构造器:{writable:true, enumerable:false,
/// configurable:true},幂等(既有槽原位覆写值,不追加重复槽)。
fn legacy_slot_write<H: VmHost>(vm: &mut H, slot: &str, val: JsValue) {
let ctor_ptr = vm.builtin_proto(ProtoKind::RegExpConstructor);
let si = vm.perm_intern(slot);
// SAFETY: ctor_ptr 为 builtin world 固定 P 字段,session 独占期间始终有效。
unsafe {
let ctor = &mut *ctor_ptr;
vm.set_or_create_prop_value(ctor, si, val);
if let Some(pos) = vm.get_own_property_slot(ctor, si) {
ctor.set_data_meta(pos, oxide_types::object::PropAttributes::new(true, false, true));
}
}
}
/// 遗留静态 getter:读 %RegExp% 构造器上的槽数据属性(槽未建时返 undefined)。
fn legacy_get<H: VmHost>(vm: &mut H, args: &[u8], slot: &str) -> NativeResult {
if let Err(e) = legacy_gate(vm, args) {
return NativeResult::Err(e);
}
let ctor_ptr = vm.builtin_proto(ProtoKind::RegExpConstructor);
let si = vm.perm_intern(slot);
// SAFETY: ctor_ptr 为 builtin world 固定 P 字段,session 独占期间始终有效。
let val = unsafe {
let ctor = &*ctor_ptr;
match vm.get_own_property_slot(ctor, si) {
Some(pos) => ctor.get_prop_at(pos),
None => JsValue::undefined(),
}
};
NativeResult::Ok(val)
}
/// 遗留静态 setter:把槽数据属性写入 %RegExp% 构造器(幂等数据属性写,不触发
/// 用户代码)。
fn legacy_set<H: VmHost>(vm: &mut H, args: &[u8], slot: &str) -> NativeResult {
if let Err(e) = legacy_gate(vm, args) {
return NativeResult::Err(e);
}
let val = vm.reg(args[1]);
legacy_slot_write(vm, slot, val);
NativeResult::Ok(JsValue::undefined())
}
/// exec 命中后按匹配结果更新 15 个遗留静态槽:input = haystack 文本、
/// lastMatch = 完整匹配串、lastParen = 末捕获组(无捕获组时 undefined)、
/// leftContext = 匹配前文本、rightContext = 匹配后文本、index = 匹配起点
/// (码元口径整数)、$1-$9 = 捕获组 1-9(未参与为 undefined)。
fn update_legacy_slots<H: VmHost>(vm: &mut H, m: &regress::Match, text: &MatchText, haystack: &OwnedText) {
let range = m.range();
let group_count = m.captures.len();
// 各槽值先落入局部再写入(legacy_slot_write 与值分配均可变借 vm,不可同式)。
let input_val = haystack.to_value(vm);
legacy_slot_write(vm, "input", input_val);
let last_match_val = vm.new_string_units_owned(text.slice(range.start, range.end).into_owned());
legacy_slot_write(vm, "lastMatch", last_match_val);
let last_paren = if group_count > 0 {
match m.group(group_count) {
Some(g) => vm.new_string_units_owned(text.slice(g.start, g.end).into_owned()),
None => JsValue::undefined(),
}
} else {
JsValue::undefined()
};
legacy_slot_write(vm, "lastParen", last_paren);
let left_val = vm.new_string_units_owned(text.slice(0, range.start).into_owned());
legacy_slot_write(vm, "leftContext", left_val);
let right_val = vm.new_string_units_owned(text.slice(range.end, text.len_units()).into_owned());
legacy_slot_write(vm, "rightContext", right_val);
legacy_slot_write(vm, "index", JsValue::int(text.unit_pos(range.start) as i32));
for i in 1..=9 {
let cap = if i <= group_count {
match m.group(i) {
Some(g) => vm.new_string_units_owned(text.slice(g.start, g.end).into_owned()),
None => JsValue::undefined(),
}
} else {
JsValue::undefined()
};
legacy_slot_write(vm, &format!("${i}"), cap);
}
}
/// 生成一对遗留 get/set 访问器(共享同一槽名,如 `input` 与 `$_` 同槽)。
macro_rules! legacy_accessor_pair {
($get:ident, $set:ident, $slot:literal) => {
pub fn $get<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
legacy_get(vm, args, $slot)
}
pub fn $set<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
legacy_set(vm, args, $slot)
}
};
}
/// 生成单个遗留 getter(set 恒 undefined,如 `index` 与 `$1`-`$9`)。
macro_rules! legacy_accessor_getter {
($get:ident, $slot:literal) => {
pub fn $get<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
legacy_get(vm, args, $slot)
}
};
}
// 仅 input/$_ 为 get+set 对(唯一带 setter 的遗留访问器);其余共享槽
// (lastMatch / lastParen / leftContext / rightContext)为 getter-only。
legacy_accessor_pair!(regexp_legacy_get_input, regexp_legacy_set_input, "input");
legacy_accessor_getter!(regexp_legacy_get_last_match, "lastMatch");
legacy_accessor_getter!(regexp_legacy_get_last_paren, "lastParen");
legacy_accessor_getter!(regexp_legacy_get_left_context, "leftContext");
legacy_accessor_getter!(regexp_legacy_get_right_context, "rightContext");
legacy_accessor_getter!(regexp_legacy_get_index, "index");
legacy_accessor_getter!(regexp_legacy_get_dollar_1, "$1");
legacy_accessor_getter!(regexp_legacy_get_dollar_2, "$2");
legacy_accessor_getter!(regexp_legacy_get_dollar_3, "$3");
legacy_accessor_getter!(regexp_legacy_get_dollar_4, "$4");
legacy_accessor_getter!(regexp_legacy_get_dollar_5, "$5");
legacy_accessor_getter!(regexp_legacy_get_dollar_6, "$6");
legacy_accessor_getter!(regexp_legacy_get_dollar_7, "$7");
legacy_accessor_getter!(regexp_legacy_get_dollar_8, "$8");
legacy_accessor_getter!(regexp_legacy_get_dollar_9, "$9");
/// `RegExp.escape(string)`:返回语法字符、其它标点、空白/行终止符、孤立
/// surrogate、首字符数字/ASCII 字母均被转义的新字符串;参数非字符串抛 TypeError。
///
/// # 边界与前提
/// - 参数取 args[1](args[0] 为 this,纯字符串变换不使用);缺参按非字符串抛 TypeError。
/// - 变换按码点逐个进行(代理对是单码点,非 BMP 码点原样回编码):控制字符
/// (0x09–0x0D)走单字母转义(\t \n \v \f \r);语法字符反斜杠加字符本身;
/// 其它标点 \xNN;空白/行终止符与孤立 surrogate ≤0xFF 用 \xNN、否则 \uNNNN
/// (十六进制小写);仅首位码点为数字/ASCII 字母 \xNN;其余原样。
pub fn regexp_escape<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
let val = vm.reg(if args.len() > 1 { args[1] } else { args[0] });
if !val.is_string() {
return NativeResult::Err(crate::error::create_type_error(vm, "RegExp.escape expects a string argument"));
}
let units = vm.string_units(val).into_owned();
let mut out: Vec<u16> = Vec::with_capacity(units.len() + 4);
let mut at_start = true;
let mut i = 0;
while i < units.len() {
let (cp, width) = decode_code_point(&units, i);
escape_code_point(&mut out, cp, at_start);
at_start = false;
i += width;
}
NativeResult::Ok(vm.new_string_units_owned(out))
}
/// 从单元序列下标处解出一个码点,返回(码点值,消耗单元数);
/// 高 surrogate 后紧跟低 surrogate 时代理对合为单码点,孤立 surrogate 自成码点。
fn decode_code_point(units: &[u16], i: usize) -> (u32, usize) {
let cu = units[i];
if (0xD800..=0xDBFF).contains(&cu) && i + 1 < units.len() && (0xDC00..=0xDFFF).contains(&units[i + 1]) {
let cp = 0x10000 + (((cu - 0xD800) as u32) << 10) + (units[i + 1] as u32 - 0xDC00_u32);
(cp, 2)
} else {
(cu as u32, 1)
}
}
/// 单个码点的转义变换,结果追加到 out;at_start 仅约束首字符数字/字母规则。
fn escape_code_point(out: &mut Vec<u16>, cp: u32, at_start: bool) {
if cp > 0xFFFF {
// 非 BMP 码点原样回编码(代理对不按孤立 surrogate 转义)。
out.push(0xD800 + ((cp - 0x10000) >> 10) as u16);
out.push(0xDC00 + ((cp - 0x10000) & 0x3FF) as u16);
return;
}
let cu = cp as u16;
match cu {
// 控制字符单字母转义形态。
0x09 => push_escape_literal(out, b't'),
0x0A => push_escape_literal(out, b'n'),
0x0B => push_escape_literal(out, b'v'),
0x0C => push_escape_literal(out, b'f'),
0x0D => push_escape_literal(out, b'r'),
// 语法字符:反斜杠加字符本身。
0x24 | 0x28 | 0x29 | 0x2A | 0x2B | 0x2E | 0x2F | 0x3F | 0x5C | 0x5E | 0x5B | 0x5D | 0x7B | 0x7C | 0x7D => {
out.push(0x5C);
out.push(cu);
}
// 其它标点:\xNN。
0x21 | 0x22 | 0x23 | 0x25 | 0x26 | 0x27 | 0x2C | 0x2D | 0x3A | 0x3B | 0x3C | 0x3D | 0x3E | 0x40 | 0x60
| 0x7E => {
push_hex_escape(out, b'x', cu, 2);
}
// 空白/行终止符与孤立 surrogate:\xNN(≤0xFF)或 \uNNNN。
c if is_ws_or_line_terminator(c) || (0xD800..=0xDFFF).contains(&c) => {
if c <= 0xFF {
push_hex_escape(out, b'x', c, 2);
} else {
push_hex_escape(out, b'u', c, 4);
}
}
// 仅首字符的数字/ASCII 字母:\xNN。
c if at_start && ((0x30..=0x39).contains(&c) || (0x41..=0x5A).contains(&c) || (0x61..=0x7A).contains(&c)) => {
push_hex_escape(out, b'x', c, 2)
}
_ => out.push(cu),
}
}
/// 空白/行终止符单元全集(WhiteSpace 文法 + LineTerminator);
/// 0x09–0x0D 已被 ControlEscape 分支先行拦截,不列于此。
fn is_ws_or_line_terminator(cu: u16) -> bool {
matches!(
cu,
0x20 | 0xA0 | 0xFEFF | 0x1680 | 0x1681 | 0x2000..=0x200A | 0x2028 | 0x2029 | 0x202F | 0x205F | 0x3000
)
}
/// 追加反斜杠加转义字母加字符本身(控制字符单字母转义形态)。
fn push_escape_literal(out: &mut Vec<u16>, letter: u8) {
out.push(0x5C);
out.push(letter as u16);
}
/// 追加反斜杠加转义字母加小写十六进制数字(digit_count = 2 为 \xNN,4 为 \uNNNN)。
fn push_hex_escape(out: &mut Vec<u16>, letter: u8, cu: u16, digit_count: u32) {
out.push(0x5C);
out.push(letter as u16);
for shift in (0..digit_count).rev() {
out.push(HEX_DIGITS[(cu >> (shift * 4)) as usize & 0xF]);
}
}
const HEX_DIGITS: [u16; 16] = [
0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66,
];
/// 读实例 flags 串([[OriginalFlags]] 的物化形态)判单个 flag 码元。
///
/// # 边界与前提
/// - `re` 须为持有编译正则的对象(RegExp 实例或 matchAll 载体);flags 缺失
/// 时各 flag 一律 false。
/// - 直接读实例 regexp_flags 字段(避免通过 ordinary_get 触发原型访问器,
/// 防止在 replace 等 builtin 调用期间触发 GC 导致悬垂)。
pub(crate) fn regexp_has_flag<H: VmHost>(vm: &mut H, re: &JsObject, unit: char) -> bool {
vm.lookup_str(re.get_regexp_flags()).unwrap_or_default().contains(unit)
}
/// 8 个单 flag 只读访问器的公共核:this 为 RegExp.prototype 本身返回
/// undefined;this 非 RegExp 对象抛 TypeError;其余读实例 flags 串判码元。
fn regexp_flag_of<H: VmHost>(vm: &mut H, args: &[u8], unit: char) -> NativeResult {
// proto 恒等判定先于 RegExp 校验:规范对 prototype 本身返回 undefined
// 而非抛错,顺序不可互换。
let this_val = vm.reg(args[0]);
if this_val.is_object() {
let this_ptr = this_val.as_js_object_ptr();
let proto_ptr = vm.builtin_proto(ProtoKind::RegExpProto);
if !this_ptr.is_null() && this_ptr == proto_ptr {
return NativeResult::Ok(JsValue::undefined());
}
}
let re_ptr = match get_regexp_ptr(vm, args) {
Ok(ptr) => ptr,
Err(err) => return NativeResult::Err(err),
};
let re = unsafe { &*re_ptr };
NativeResult::Ok(JsValue::bool(regexp_has_flag(vm, re, unit)))
}
/// `RegExp.prototype.global` getter。
pub fn regexp_get_global<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
regexp_flag_of(vm, args, 'g')
}
/// `RegExp.prototype.ignoreCase` getter。
pub fn regexp_get_ignore_case<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
regexp_flag_of(vm, args, 'i')
}
/// `RegExp.prototype.multiline` getter。
pub fn regexp_get_multiline<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
regexp_flag_of(vm, args, 'm')
}
/// `RegExp.prototype.dotAll` getter。
pub fn regexp_get_dot_all<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
regexp_flag_of(vm, args, 's')
}
/// `RegExp.prototype.sticky` getter。
pub fn regexp_get_sticky<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
regexp_flag_of(vm, args, 'y')
}
/// `RegExp.prototype.unicode` getter。
pub fn regexp_get_unicode<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
regexp_flag_of(vm, args, 'u')
}
/// `RegExp.prototype.hasIndices` getter。
pub fn regexp_get_has_indices<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
regexp_flag_of(vm, args, 'd')
}
/// `RegExp.prototype.unicodeSets` getter。
pub fn regexp_get_unicode_sets<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
regexp_flag_of(vm, args, 'v')
}
/// `RegExp.prototype.flags` getter:返回实例 flags 串。
///
/// 非对象 this 抛 TypeError;持编译正则槽的对象直接返槽串;其余对象
/// (含 prototype 本身)无槽可返,按规范顺序读 8 个旗属性并以 ToBoolean
/// 判位组串。
pub fn regexp_get_flags<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
let this_val = vm.reg(args[0]);
if !this_val.is_object() {
return NativeResult::Err(crate::error::create_type_error(vm, "RegExp.prototype method called on non-object"));
}
let this_ptr = this_val.as_js_object_ptr();
if this_ptr.is_null() {
return NativeResult::Err(crate::error::create_type_error(vm, "null object"));
}
// 持编译正则槽的对象直接返槽串。
// SAFETY: this_ptr 已确认非空对象值,借用即时消费。
if unsafe { &*this_ptr }.is_regexp_obj() {
// SAFETY: 同上,槽串由对象持有。
return NativeResult::Ok(unsafe { &*this_ptr }.get_regexp_flags());
}
// 无槽对象:按规范序(d,g,i,m,s,u,v,y)读 8 个旗属性,ToBoolean 判位
// 组串。每次读都可能触发用户 getter 重入(session GC 可搬移对象),
// 对象指针须每次读前从 this 寄存器(GC 根)重取。
let mut out: Vec<u16> = Vec::with_capacity(8);
for (name, unit) in [
("hasIndices", 'd'),
("global", 'g'),
("ignoreCase", 'i'),
("multiline", 'm'),
("dotAll", 's'),
("unicode", 'u'),
("unicodeSets", 'v'),
("sticky", 'y'),
] {
let this_val = vm.reg(args[0]);
let this_ptr = this_val.as_js_object_ptr();
let v = match rx_get_bool_prop(vm, this_ptr, name, this_val) {
Ok(v) => v,
Err(err) => return NativeResult::Err(err),
};
if v {
out.push(unit as u16);
}
}
NativeResult::Ok(vm.new_string_units(&out))
}
/// `RegExp.prototype.source` getter:返回实例 source 串。
pub fn regexp_get_source<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
let this_val = vm.reg(args[0]);
// proto 恒等判定:规范对 prototype 本身返回 "(?:)"(无 [[OriginalFlags]] 槽时)。
if this_val.is_object() {
let this_ptr = this_val.as_js_object_ptr();
let proto_ptr = vm.builtin_proto(ProtoKind::RegExpProto);
if !this_ptr.is_null() && this_ptr == proto_ptr {
return NativeResult::Ok(vm.new_string_units_owned(vec!['(' as u16, '?' as u16, ':' as u16, ')' as u16]));
}
}
let re_ptr = match get_regexp_ptr(vm, args) {
Ok(ptr) => ptr,
Err(err) => return NativeResult::Err(err),
};
let re = unsafe { &*re_ptr };
let source = re.get_regexp_source();
let flags = re.get_regexp_flags();
let source_str = vm.lookup_str(source).unwrap_or_default();
let flags_str = vm.lookup_str(flags).unwrap_or_default();
NativeResult::Ok(vm.new_string_units_owned(escape_regexp_pattern(&source_str, &flags_str)))
}
/// `EscapeRegExpPattern(pattern, flags)`:按规范转义 `\`、`/`(非 u/v 旗时)
/// 与行终止符,使结果可安全嵌入 `/pattern/flags` 字面量。
fn escape_regexp_pattern(pattern: &str, flags: &str) -> Vec<u16> {
let has_uv = flags.contains('u') || flags.contains('v');
let mut out: Vec<u16> = Vec::with_capacity(pattern.len() + 4);
for ch in pattern.chars() {
match ch {
'/' | '\\' if !has_uv => {
out.push('\\' as u16);
out.push(ch as u16);
}
'\n' => {
out.push('\\' as u16);
out.push('n' as u16);
}
'\r' => {
out.push('\\' as u16);
out.push('r' as u16);
}
'\u{2028}' => {
out.push('\\' as u16);
out.push('u' as u16);
out.push('2' as u16);
out.push('0' as u16);
out.push('2' as u16);
out.push('8' as u16);
}
'\u{2029}' => {
out.push('\\' as u16);
out.push('u' as u16);
out.push('2' as u16);
out.push('0' as u16);
out.push('2' as u16);
out.push('9' as u16);
}
_ => {
out.push(ch as u16);
}
}
}
out
}
/// 取匹配文本参数,按 ToString 语义完整转换(对象经 toString/valueOf);
/// 结果按载荷形态 owned:良形文本走 Str 臂,含孤立 surrogate 等走 Units 臂。
/// 对象 ToString 抛出的原生异常原样传播。
fn regexp_text_arg<H: VmHost>(vm: &mut H, args: &[u8]) -> Result<OwnedText, JsValue> {
let val = vm.reg(if args.len() > 1 { args[1] } else { args[0] });
match oxide_runtime_api::to_string_value_full(val, vm) {
Ok(v) => {
// SAFETY: v 为字符串值,借用即时消费。
let sp = unsafe { &*v.as_string_ptr() };
if sp.is_flat() {
Ok(OwnedText::Str(sp.as_str().to_string()))
} else {
Ok(OwnedText::Units(sp.units().into_owned()))
}
}
Err(_) => {
// ToString 触发对象 toString/valueOf 抛出的原生异常须原样传播。
if let Some(exc) = vm.take_uncaught_value() {
return Err(exc);
}
Err(crate::error::create_type_error(vm, "Cannot convert value to a string"))
}
}
}
/// 替换参数转单元序列:字符串值按载荷形态原样借出,其余经完整 ToString;
/// Symbol 按规范抛 TypeError;对象 ToString 抛出的原生异常原样传播。
pub(crate) fn replacement_units<H: VmHost>(vm: &mut H, val: JsValue) -> Result<Vec<u16>, JsValue> {
match oxide_runtime_api::to_string_value_full(val, vm) {
Ok(v) => Ok(vm.string_units(v).into_owned()),
Err(_) => {
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"))
}
}
}
/// `RegExp.prototype[Symbol.match](string)`:global 正则收集全部匹配串成数组,
/// 非 global 直接返回 exec 结果;无匹配返回 null。
///
/// # 步骤
/// 1. this 仅需对象(IsObject 门禁)。
/// 2. S = ToString(string);flags/global/unicode/unicodeSets 经 Get live 直读
/// (unicode 读无条件先于非 global 返回,读序与规范一致)。
/// 3. 非 global:返回共享 exec(GetMethod + Call)结果原值。
/// 4. global:ToIntegerOrInfinity(Get "lastIndex") 副作用读后 Set 0,再经共享
/// exec 循环逐匹配收集;零宽匹配按 fullUnicode 口径推进 lastIndex。
///
/// # 边界与前提
/// - 各属性读(flags/global/unicode/unicodeSets/lastIndex/结果属性)均传播原异常。
/// - exec 结果须为 null 或对象,否则 TypeError。
///
/// # 副作用
/// - global 时 Set this.lastIndex(先归零,零宽命中后推进)。
pub fn regexp_symbol_match<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
let re_ptr = match get_this_obj(vm, args) {
Ok(ptr) => ptr,
Err(err) => return NativeResult::Err(err),
};
let this_val = vm.reg(args[0]);
let haystack = match regexp_text_arg(vm, args) {
Ok(s) => s,
Err(err) => return NativeResult::Err(err),
};
let s_val = haystack.to_value(vm);
let units = haystack.as_match_text().units().to_vec();
// live 读序:flags → global → unicode(unicode 无条件先于非 global 返回)。
let _flags = match rx_get_flags(vm, re_ptr, this_val) {
Ok(f) => f,
Err(err) => return NativeResult::Err(err),
};
let is_global = match rx_get_bool_prop(vm, re_ptr, "global", this_val) {
Ok(g) => g,
Err(err) => return NativeResult::Err(err),
};
// fullUnicode = u 或 v 标志任一为真(规范 "If flags contains 'u' or flags
// contains 'v'"),两属性 live 读取后或。
let full_unicode = match rx_get_bool_prop(vm, re_ptr, "unicode", this_val) {
Ok(u) => u,
Err(err) => return NativeResult::Err(err),
};
let full_unicode = full_unicode
|| match rx_get_bool_prop(vm, re_ptr, "unicodeSets", this_val) {
Ok(v) => v,
Err(err) => return NativeResult::Err(err),
};
if !is_global {
let result = match regexp_exec_call(vm, re_ptr, this_val, s_val) {
Ok(r) => r,
Err(err) => return NativeResult::Err(err),
};
return NativeResult::Ok(result);
}
// lastIndex = ToIntegerOrInfinity(Get):副作用读,getter 异常传播。
let last_val = match rx_get_prop(vm, re_ptr, "lastIndex", this_val) {
Ok(v) => v,
Err(err) => return NativeResult::Err(err),
};
let _last = oxide_runtime_api::to_integer_or_infinity(last_val);
if let Err(err) = rx_set_prop(vm, re_ptr, "lastIndex", JsValue::int(0), this_val) {
return NativeResult::Err(err);
}
// exec 循环:收集完整匹配串;零宽匹配按 fullUnicode 口径推进 lastIndex。
let mut matches: Vec<JsValue> = Vec::new();
loop {
let result = match regexp_exec_call(vm, re_ptr, this_val, s_val) {
Ok(r) => r,
Err(err) => return NativeResult::Err(err),
};
if result.is_null() {
break;
}
let result_ptr = result.as_js_object_ptr();
let matched_val = match rx_get_index(vm, result_ptr, 0, result) {
Ok(v) => v,
Err(err) => return NativeResult::Err(err),
};
let matched = match oxide_runtime_api::to_string_value_full(matched_val, vm) {
Ok(v) => v,
Err(_) => return NativeResult::Err(crate::iterator::engine_error(vm, "cannot stringify match")),
};
let matched_units = vm.string_units(matched).into_owned();
if matched_units.is_empty() {
let li_val = match rx_get_prop(vm, re_ptr, "lastIndex", this_val) {
Ok(v) => v,
Err(err) => return NativeResult::Err(err),
};
let this_index = match to_length_value(vm, li_val) {
Ok(n) => n,
Err(err) => return NativeResult::Err(err),
};
let next_index = advance_string_index(&units, this_index, full_unicode);
if let Err(err) = // 越出 i32 的大值(2^53 口径)以浮点形态写入。
rx_set_prop(vm, re_ptr, "lastIndex", JsValue::float(next_index as f64), this_val)
{
return NativeResult::Err(err);
}
}
matches.push(vm.new_string_units(&matched_units));
}
if matches.is_empty() {
return NativeResult::Ok(JsValue::null());
}
NativeResult::Ok(crate::string::make_string_array_values(vm, matches))
}
/// `RegExp.prototype[Symbol.replace](string, replacement)`:按 exec 命中替换。
/// 字符串 replacement 展开 `$` 引用(`$$`/`$&`/`` $` ``/`$'`/`$n`),函数
/// replacement 逐匹配调用(groups 非 undefined 时追加为末参);global 全替换,
/// 否则替换首个。
///
/// # 步骤
/// 1. this 仅需对象(IsObject 门禁);S = ToString(string)。
/// 2. 非函数分支先 ToString(replacement)(Symbol 抛 TypeError)。
/// 3. flags/global/unicode/unicodeSets 经 Get live 直读;global 时
/// ToIntegerOrInfinity(Get "lastIndex") 副作用读后 Set 0。
/// 4. 替换体(regexp_replace_results):exec 循环收集结果(零宽匹配按
/// fullUnicode 口径推进 lastIndex),按结果属性逐条展开
/// (matched/captures/index/groups 均 Get 读取,undefined 捕获不入表);
/// position 越上界钳到串长,乱序(position 回退)的替换被忽略。
///
/// # 边界与前提
/// - 各属性读(flags/global/unicode/unicodeSets/lastIndex/结果属性)均传播原异常。
/// - exec 结果须为 null 或对象,否则 TypeError。
///
/// # 副作用
/// - global 时 Set this.lastIndex(先归零,零宽命中后推进)。
pub fn regexp_symbol_replace<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
let re_ptr = match get_this_obj(vm, args) {
Ok(ptr) => ptr,
Err(err) => return NativeResult::Err(err),
};
let this_val = vm.reg(args[0]);
let haystack = match regexp_text_arg(vm, args) {
Ok(s) => s,
Err(err) => return NativeResult::Err(err),
};
let s_val = haystack.to_value(vm);
let units = haystack.as_match_text().units().to_vec();
let replacer_val = if args.len() > 2 { vm.reg(args[2]) } else { JsValue::undefined() };
let functional = crate::iterator::is_callable(replacer_val);
// 非函数分支的替换串先行 ToString(Symbol 抛 TypeError,异常传播)。
let replacement_units: Option<Vec<u16>> = if functional {
None
} else {
match replacement_units(vm, replacer_val) {
Ok(u) => Some(u),
Err(err) => return NativeResult::Err(err),
}
};
// live 读序:flags → global → unicode。
let _flags = match rx_get_flags(vm, re_ptr, this_val) {
Ok(f) => f,
Err(err) => return NativeResult::Err(err),
};
let is_global = match rx_get_bool_prop(vm, re_ptr, "global", this_val) {
Ok(g) => g,
Err(err) => return NativeResult::Err(err),
};
if is_global {
// lastIndex = ToIntegerOrInfinity(Get):副作用读,getter 异常传播。
let last_val = match rx_get_prop(vm, re_ptr, "lastIndex", this_val) {
Ok(v) => v,
Err(err) => return NativeResult::Err(err),
};
let _last = oxide_runtime_api::to_integer_or_infinity(last_val);
if let Err(err) = rx_set_prop(vm, re_ptr, "lastIndex", JsValue::int(0), this_val) {
return NativeResult::Err(err);
}
}
let ctx = ReplacementCtx {
functional,
replacer_val,
units: replacement_units.as_deref(),
};
let out = match regexp_replace_results(vm, re_ptr, this_val, s_val, &units, &ctx, is_global) {
Ok(u) => u,
Err(err) => return NativeResult::Err(err),
};
NativeResult::Ok(vm.new_string_units_owned(out))
}
/// @@replace 共享臂的替换上下文:功能替换器(replacer_val,逐匹配回调)与
/// 非功能替换串单元序列(units)恰好其一在场。
pub(crate) struct ReplacementCtx<'a> {
pub(crate) functional: bool,
pub(crate) replacer_val: JsValue,
pub(crate) units: Option<&'a [u16]>,
}
/// @@replace 共享臂的替换体:exec 循环(global 收集到 null 为止,非 global 至多
/// 一条;零宽命中按 fullUnicode 口径推进 lastIndex)+ 逐结果 SubstitutionRecord
/// 装配 + 逐条 make_replacement;返回装配后的单元序列。
pub(crate) fn regexp_replace_results<H: VmHost>(
vm: &mut H, re_ptr: *mut JsObject, this_val: JsValue, s_val: JsValue, units: &[u16], ctx: &ReplacementCtx<'_>,
is_global: bool,
) -> Result<Vec<u16>, JsValue> {
let length_s = units.len();
// fullUnicode = u 或 v 标志任一为真(规范 "If flags contains 'u' or flags
// contains 'v'"),两属性 live 读取后或。
let full_unicode =
rx_get_bool_prop(vm, re_ptr, "unicode", this_val)? || rx_get_bool_prop(vm, re_ptr, "unicodeSets", this_val)?;
// exec 循环:global 收集到 null 为止,非 global 至多一条。
let mut results: Vec<JsValue> = Vec::new();
loop {
let result = regexp_exec_call(vm, re_ptr, this_val, s_val)?;
if result.is_null() {
break;
}
results.push(result);
if !is_global {
break;
}
// 零宽命中:按 fullUnicode 口径推进 lastIndex。
let result_ptr = result.as_js_object_ptr();
let matched_val = rx_get_index(vm, result_ptr, 0, result)?;
let matched = oxide_runtime_api::to_string_value_full(matched_val, vm)
.map_err(|_| crate::iterator::engine_error(vm, "cannot stringify match"))?;
if vm.string_units(matched).is_empty() {
let li_val = rx_get_prop(vm, re_ptr, "lastIndex", this_val)?;
let this_index = to_length_value(vm, li_val)?;
let next_index = advance_string_index(units, this_index, full_unicode);
// 越出 i32 的大值(2^53 口径)以浮点形态写入。
rx_set_prop(vm, re_ptr, "lastIndex", JsValue::float(next_index as f64), this_val)?;
}
}
// 单匹配(非 global):无命中回原串 S。
if !is_global {
if results.is_empty() {
return Ok(units.to_vec());
}
let result = results[0];
let result_ptr = result.as_js_object_ptr();
let n_captures = result_capture_count(vm, result_ptr, result)?;
let rec = build_substitution_record(vm, result_ptr, result, length_s, n_captures)?;
let replacement = make_replacement(vm, ctx.functional, ctx.replacer_val, ctx.units, &rec, units, s_val)?;
let mut out: Vec<u16> = Vec::with_capacity(length_s + replacement.len());
out.extend_from_slice(&units[..rec.position]);
out.extend_from_slice(&replacement);
// 尾段自命中末尾(position + matchLength)起,被替换串本身不入结果。
let tail_start = (rec.position + rec.matched.len()).min(length_s);
out.extend_from_slice(&units[tail_start..]);
return Ok(out);
}
// global:先定统一捕获宽(各结果 length 的 ToLength 上界减一),
// 再逐条展开替换。
let mut n_captures = 0usize;
for result in &results {
let result_ptr = result.as_js_object_ptr();
let n = result_capture_count(vm, result_ptr, *result)?;
n_captures = n_captures.max(n);
}
let mut out: Vec<u16> = Vec::with_capacity(length_s);
let mut next_source_position = 0usize;
for result in &results {
let result = *result;
let result_ptr = result.as_js_object_ptr();
let rec = build_substitution_record(vm, result_ptr, result, length_s, n_captures)?;
// position 回退(乱序结果)的替换被忽略。
if rec.position < next_source_position {
continue;
}
let replacement = make_replacement(vm, ctx.functional, ctx.replacer_val, ctx.units, &rec, units, s_val)?;
out.extend_from_slice(&units[next_source_position..rec.position]);
out.extend_from_slice(&replacement);
next_source_position = rec.position + rec.matched.len();
}
if next_source_position < length_s {
out.extend_from_slice(&units[next_source_position..]);
}
Ok(out)
}
/// 替换记录:matched(ToString 后单元)、position(ToInteger 后钳 [0, 串长])、
/// captures(undefined 捕获为 None,其余为 ToString 单元)。
struct SubstitutionRecord {
matched: Vec<u16>,
position: usize,
captures: Vec<Option<Vec<u16>>>,
groups: JsValue,
}
/// 结果属性 ToString 收尾:完整强转(对象经 ToPrimitive,Symbol 抛
/// TypeError),转换异常恢复原异常值。
fn to_string_units<H: VmHost>(vm: &mut H, val: JsValue) -> Result<Vec<u16>, JsValue> {
match oxide_runtime_api::to_string_value_full(val, vm) {
Ok(v) => Ok(vm.string_units(v).into_owned()),
Err(_) => Err(crate::iterator::engine_error(vm, "cannot stringify result property")),
}
}
/// 结果捕获数(length 的 ToLength 减一,下界 0):各属性读异常传播。
fn result_capture_count<H: VmHost>(vm: &mut H, result_ptr: *mut JsObject, result: JsValue) -> Result<usize, JsValue> {
let len_val = rx_get_prop(vm, result_ptr, "length", result)?;
let n = to_length_value(vm, len_val)?;
Ok(n.saturating_sub(1))
}
/// 从单条 exec 结果对象按 GetSubstitution 口径读取 matched/index/captures/
/// groups:各属性读异常传播,捕获 undefined 以空串占位;捕获宽度由调用方给
/// (global 统一取各结果上界,非 global 取本结果自身)。
fn build_substitution_record<H: VmHost>(
vm: &mut H, result_ptr: *mut JsObject, result: JsValue, length_s: usize, n_captures: usize,
) -> Result<SubstitutionRecord, JsValue> {
let matched_val = rx_get_index(vm, result_ptr, 0, result)?;
let matched = to_string_units(vm, matched_val)?;
// index 经完整 ToNumber(对象触发 valueOf/toString)后再 ToInteger。
let index_val = rx_get_prop(vm, result_ptr, "index", result)?;
let num = match oxide_runtime_api::to_number_full(index_val, vm) {
Ok(n) => n,
Err(e) => return Err(crate::iterator::engine_error(vm, &e)),
};
let n = oxide_runtime_api::to_integer_or_infinity(JsValue::float(num));
let position = if n.is_nan() || n <= 0.0 { 0 } else { n.min(length_s as f64) as usize };
let mut captures: Vec<Option<Vec<u16>>> = Vec::with_capacity(n_captures);
for i in 1..=n_captures {
let cap_val = rx_get_index(vm, result_ptr, i as i32, result)?;
if cap_val.is_undefined() {
captures.push(None);
} else {
captures.push(Some(to_string_units(vm, cap_val)?));
}
}
let groups = rx_get_prop(vm, result_ptr, "groups", result)?;
Ok(SubstitutionRecord {
matched,
position,
captures,
groups,
})
}
/// 生成单条替换文本:函数分支 Call(replacer, undefined, «matched, ...captures,
/// position, S, groups?»)后 ToString(groups 为 undefined 时不追加末参);
/// 非函数分支 GetSubstitution 展开。
fn make_replacement<H: VmHost>(
vm: &mut H, functional: bool, replacer_val: JsValue, replacement_units: Option<&[u16]>, rec: &SubstitutionRecord,
s_units: &[u16], s_val: JsValue,
) -> Result<Vec<u16>, JsValue> {
if functional {
let mut cb_args: Vec<JsValue> = Vec::with_capacity(rec.captures.len() + 5);
cb_args.push(vm.new_string_units(&rec.matched));
for cap in &rec.captures {
match cap {
Some(u) => cb_args.push(vm.new_string_units(u)),
None => cb_args.push(JsValue::undefined()),
}
}
cb_args.push(JsValue::int(rec.position as i32));
cb_args.push(s_val);
if !rec.groups.is_undefined() {
cb_args.push(rec.groups);
}
let repl_value = match vm.call_function_sync(replacer_val, JsValue::undefined(), &cb_args) {
Ok(v) => v,
Err(_) => return Err(crate::iterator::engine_error(vm, "replacer call failed")),
};
return to_string_units(vm, repl_value);
}
// 非函数分支:namedCaptures 非 undefined 时先 ToObject(null/undefined 抛
// TypeError 恢复原异常上抛),装箱体作为 named_captures 传入。
let named_captures = if rec.groups.is_undefined() {
None
} else {
// ToObject 仅对 null/undefined 抛 TypeError(规范步 l.i 同面)。
let boxed = match oxide_runtime_api::to_object(rec.groups, vm) {
Ok(v) => v,
Err(_) => return Err(crate::error::create_type_error(vm, "Cannot convert null or undefined to object")),
};
let ptr = boxed.as_js_object_ptr();
// SAFETY: boxed 为 ToObject 返回的非空对象指针。
Some(unsafe { &*ptr })
};
get_substitution_units(
vm,
&rec.matched,
s_units,
rec.position,
&rec.captures,
named_captures,
replacement_units.unwrap_or(&[]),
)
}
/// `RegExp.prototype[Symbol.search](string)`:返回首个匹配位置,无匹配返回
/// -1。
///
/// # 步骤
/// 1. this 仅需对象(IsObject 门禁);S = ToString(string)。
/// 2. previous = Get(rx, "lastIndex")(裸读不转换);与 +0 非 SameValue 时
/// Set 0(SameValue 口径:-0 与 +0 相同,不触发写)。
/// 3. 共享 exec 求匹配。
/// 4. 恢复:当前 lastIndex 与 previous 非 SameValue 时 Set 回 previous。
/// 5. null 回 -1,否则 ToNumber(Get(match, "index"))。
///
/// # 边界与前提
/// - lastIndex 读写均 Set/Get 语义,异常传播原异常值。
///
/// # 副作用
/// - lastIndex 先归零再按原值恢复,exec 期间的写入不外泄。
pub fn regexp_symbol_search<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
let re_ptr = match get_this_obj(vm, args) {
Ok(ptr) => ptr,
Err(err) => return NativeResult::Err(err),
};
let this_val = vm.reg(args[0]);
let haystack = match regexp_text_arg(vm, args) {
Ok(s) => s,
Err(err) => return NativeResult::Err(err),
};
let s_val = haystack.to_value(vm);
let previous = match rx_get_prop(vm, re_ptr, "lastIndex", this_val) {
Ok(v) => v,
Err(err) => return NativeResult::Err(err),
};
if !same_value_zero(previous) {
if let Err(err) = rx_set_prop(vm, re_ptr, "lastIndex", JsValue::int(0), this_val) {
return NativeResult::Err(err);
}
}
let match_result = match regexp_exec_call(vm, re_ptr, this_val, s_val) {
Ok(r) => r,
Err(err) => return NativeResult::Err(err),
};
let current = match rx_get_prop(vm, re_ptr, "lastIndex", this_val) {
Ok(v) => v,
Err(err) => return NativeResult::Err(err),
};
if !same_value(previous, current) {
if let Err(err) = rx_set_prop(vm, re_ptr, "lastIndex", previous, this_val) {
return NativeResult::Err(err);
}
}
if match_result.is_null() {
return NativeResult::Ok(JsValue::int(-1));
}
let match_ptr = match_result.as_js_object_ptr();
let index_val = match rx_get_prop(vm, match_ptr, "index", match_result) {
Ok(v) => v,
Err(err) => return NativeResult::Err(err),
};
let n = match oxide_runtime_api::to_number_full(index_val, vm) {
Ok(n) => n,
Err(_) => return NativeResult::Err(crate::iterator::engine_error(vm, "cannot convert index")),
};
// 匹配位置恒为非负整数,与无匹配臂的 -1(int)同口径。
NativeResult::Ok(JsValue::int(n as i32))
}
/// SameValue(val, +0):严格口径,-0 与 +0 不同(规范步骤用 SameValue 非
/// SameValueZero:-0 前值仍须触发 Set 0)。
fn same_value_zero(val: JsValue) -> bool {
if val.is_int() {
return val.as_int() == 0;
}
if val.is_double() {
return val.as_double() == 0.0 && !val.as_double().is_sign_negative();
}
false
}
/// SameValue 全口径比较:同类型同值(-0 与 +0 不同),整数与浮点跨型按数值
/// 比较(符号位参与),其余类型不同恒 false。
fn same_value(a: JsValue, b: JsValue) -> bool {
match (a.js_type(), b.js_type()) {
(oxide_types::value::JsType::Int, oxide_types::value::JsType::Int) => a.as_int() == b.as_int(),
(oxide_types::value::JsType::Double, oxide_types::value::JsType::Double) => {
a.as_double() == b.as_double() && a.as_double().is_sign_negative() == b.as_double().is_sign_negative()
}
(oxide_types::value::JsType::Int, oxide_types::value::JsType::Double)
| (oxide_types::value::JsType::Double, oxide_types::value::JsType::Int) => {
let na = if a.is_int() { a.as_int() as f64 } else { a.as_double() };
let nb = if b.is_int() { b.as_int() as f64 } else { b.as_double() };
na == nb && na.is_sign_negative() == nb.is_sign_negative()
}
(oxide_types::value::JsType::Bool, oxide_types::value::JsType::Bool) => a.as_bool() == b.as_bool(),
(oxide_types::value::JsType::String, oxide_types::value::JsType::String) => {
// SAFETY: 两值均为字符串,按载荷形态逐单元比对。
let (pa, pb) = unsafe { (&*a.as_string_ptr(), &*b.as_string_ptr()) };
std::ptr::eq(pa, pb) || (pa.utf16_len() == pb.utf16_len() && pa.units() == pb.units())
}
(oxide_types::value::JsType::Null, oxide_types::value::JsType::Null)
| (oxide_types::value::JsType::Undefined, oxide_types::value::JsType::Undefined)
| (oxide_types::value::JsType::Symbol, oxide_types::value::JsType::Symbol) => {
a.js_type() == b.js_type() && a == b
}
(oxide_types::value::JsType::Object, oxide_types::value::JsType::Object) => {
a.as_js_object_ptr() == b.as_js_object_ptr()
}
_ => false,
}
}
/// `RegExp.prototype[Symbol.split](string, limit)`:按 splitter(物种构造 +
/// 补 y 标志)的 exec 命中切分,捕获组作为分隔元素插入结果;limit 限制
/// 结果长度,缺省切分全部。
///
/// # 步骤
/// 1. this 仅需对象(IsObject 门禁);S = ToString(string)。
/// 2. C = SpeciesConstructor(rx, %RegExp%);flags = ToString(Get "flags");
/// newFlags = flags 含 y 取原串、否则追加 y。
/// 3. splitter = Construct(C, «rx, newFlags»)。
/// 4. lim = 缺省 2^32-1、否则 ToUint32(limit);lim == 0 直接回空数组。
/// 5. 空串特判:exec 非 null 回 [],否则回 [""]。
/// 6. p = ToLength(Get "lastIndex")、q = p;循环 q < 串长:Set
/// (splitter, "lastIndex", q) 后共享 exec;null 按 unicodeMatching 口径
/// 推进 q;命中取 e = min(ToLength(Get(splitter, "lastIndex")), 串长),
/// e ≠ p 时推片段 + 捕获组并 p = q = e,e == p 时零宽推进。
/// 7. 循环后无条件推尾段 S[p..]。
///
/// # 边界与前提
/// - splitter 可非 RegExp(物种产物),消费面纯走 exec + 属性读。
/// - 各属性读(flags/lastIndex/length/捕获组)均传播原异常。
pub fn regexp_symbol_split<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
let re_ptr = match get_this_obj(vm, args) {
Ok(ptr) => ptr,
Err(err) => return NativeResult::Err(err),
};
let this_val = vm.reg(args[0]);
let haystack = match regexp_text_arg(vm, args) {
Ok(s) => s,
Err(err) => return NativeResult::Err(err),
};
let s_val = haystack.to_value(vm);
let units = haystack.as_match_text().units().to_vec();
let size = units.len();
// lim:缺省或 undefined 为 2^32-1,否则 ToUint32(完整 ToNumber 传播
// 转换异常;NaN/±0/±∞ 归零、负数回绕)。
let lim_val = if args.len() > 2 { vm.reg(args[2]) } else { JsValue::undefined() };
let lim = match crate::string::split_limit_to_uint32(vm, lim_val) {
Ok(n) => n,
Err(e) => return NativeResult::Err(e),
};
if lim == 0 {
return NativeResult::Ok(make_units_array(vm, Vec::new()));
}
// 物种构造 + y 标志:splitter 是后续唯一匹配面。
let regexp_ctor = vm.builtin_proto(ProtoKind::RegExpConstructor);
let regexp_ctor_val = JsValue::from_js_object(regexp_ctor);
let c = match species_constructor(vm, re_ptr, this_val, regexp_ctor_val) {
Ok(c) => c,
Err(err) => return NativeResult::Err(err),
};
let flags = match rx_get_flags(vm, re_ptr, this_val) {
Ok(f) => f,
Err(err) => return NativeResult::Err(err),
};
let unicode_matching = flags.contains('u') || flags.contains('v');
let new_flags = if flags.contains('y') { flags } else { format!("{flags}y") };
let new_flags_val = vm.new_string_owned(new_flags);
let splitter_val = match vm.construct_ctor(c, &[this_val, new_flags_val]) {
Ok(s) => s,
Err(e) => return NativeResult::Err(e),
};
if !splitter_val.is_object() {
return NativeResult::Err(crate::error::create_type_error(vm, "RegExp split splitter must be an object"));
}
let splitter_ptr = splitter_val.as_js_object_ptr();
// 空串特判:exec 非 null 回空数组,否则回单空串数组。
if size == 0 {
let z = match regexp_exec_call(vm, splitter_ptr, splitter_val, s_val) {
Ok(r) => r,
Err(err) => return NativeResult::Err(err),
};
if z.is_null() {
return NativeResult::Ok(make_units_array(vm, vec![Vec::new()]));
}
return NativeResult::Ok(make_units_array(vm, Vec::new()));
}
// p = ToLength(Get "lastIndex"),q 与 p 同值。
let read_position = |vm: &mut H| -> Result<usize, JsValue> {
let v = rx_get_prop(vm, re_ptr, "lastIndex", this_val)?;
to_length_value(vm, v)
};
let mut p = match read_position(vm) {
Ok(n) => n,
Err(err) => return NativeResult::Err(err),
};
let mut q = p;
// 结果推入触顶(n == lim)即按规范早退返回;循环自然结束则无条件推尾段。
// 片段推字符串值,捕获组推原值(undefined 等不转换)。
let mut parts: Vec<JsValue> = Vec::new();
while q < size {
if let Err(err) = rx_set_prop(vm, splitter_ptr, "lastIndex", JsValue::int(q as i32), splitter_val) {
return NativeResult::Err(err);
}
let z = match regexp_exec_call(vm, splitter_ptr, splitter_val, s_val) {
Ok(r) => r,
Err(err) => return NativeResult::Err(err),
};
if z.is_null() {
q = advance_string_index(&units, q, unicode_matching);
continue;
}
let e = {
let li_val = match rx_get_prop(vm, splitter_ptr, "lastIndex", splitter_val) {
Ok(v) => v,
Err(err) => return NativeResult::Err(err),
};
let n = match to_length_value(vm, li_val) {
Ok(n) => n,
Err(err) => return NativeResult::Err(err),
};
n.min(size)
};
if e != p {
parts.push(vm.new_string_units(&units[p..q]));
if parts.len() >= lim {
return NativeResult::Ok(crate::string::make_string_array_values(vm, parts));
}
let cap_num = {
let len_val = match rx_get_prop(vm, z.as_js_object_ptr(), "length", z) {
Ok(v) => v,
Err(err) => return NativeResult::Err(err),
};
match to_length_value(vm, len_val) {
Ok(n) => n,
Err(err) => return NativeResult::Err(err),
}
};
for k in 1..cap_num {
let cap_val = match rx_get_index(vm, z.as_js_object_ptr(), k as i32, z) {
Ok(v) => v,
Err(err) => return NativeResult::Err(err),
};
parts.push(cap_val);
if parts.len() >= lim {
return NativeResult::Ok(crate::string::make_string_array_values(vm, parts));
}
}
p = e;
q = p;
} else {
q = advance_string_index(&units, q, unicode_matching);
}
}
parts.push(vm.new_string_units(&units[p..size]));
NativeResult::Ok(crate::string::make_string_array_values(vm, parts))
}
/// `RegExp.prototype[Symbol.matchAll](string)`:返回按 global 语义逐个产出
/// 匹配数组的迭代器。
///
/// # 步骤
/// 1. this 仅需对象(IsObject 门禁);S = ToString(string)。
/// 2. flags = ToString(Get "flags");isRegExp 旧式判定:Get(R, @@match) 为
/// 布尔取之,否则 true(读序在 flags 之后)。
/// 3. matcher 构造:isRegExp 走 SpeciesConstructor + Construct(C, «R, flags»),
/// 非 RegExp 走 Construct(%RegExp%, «R, "g"»)。
/// 4. lastIndex = ToLength(Get(R, "lastIndex"))——只从 R 读一次;
/// Set(matcher, "lastIndex", lastIndex)。
/// 5. global/fullUnicode 由 flags 串判定(不读构造产物属性;u/v 均码点口径)。
/// 6. 迭代器包装为五槽(input/re/done/global/unicode),游标归匹配器
/// lastIndex。
///
/// # 边界与前提
/// - 各属性读(flags/@@match/lastIndex)均传播原异常。
///
/// # 副作用
/// - 构造 matcher(物种构造可返回任意对象)。
pub fn regexp_symbol_match_all<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
let re_ptr = match get_this_obj(vm, args) {
Ok(ptr) => ptr,
Err(err) => return NativeResult::Err(err),
};
let this_val = vm.reg(args[0]);
let haystack = match regexp_text_arg(vm, args) {
Ok(s) => s,
Err(err) => return NativeResult::Err(err),
};
let s_val = haystack.to_value(vm);
let flags = match rx_get_flags(vm, re_ptr, this_val) {
Ok(f) => f,
Err(err) => return NativeResult::Err(err),
};
// [[Global]]/[[Unicode]] 由 flags 串判定(u/v 均码点口径),不读构造产物属性。
let is_global = flags.contains('g');
let is_unicode = flags.contains('u') || flags.contains('v');
// 旧式 IsRegExp:Get(R, @@match) 为布尔取之,否则 true。
let match_key = oxide_types::private_key::encode_symbol_key(vm.realm_id(), 1);
let matcher_prop = match vm.ordinary_get(unsafe { &*re_ptr }, match_key, this_val) {
Ok(v) => v,
Err(_) => return NativeResult::Err(crate::iterator::engine_error(vm, "cannot read @@match")),
};
let is_regexp = match matcher_prop.js_type() {
oxide_types::value::JsType::Bool => matcher_prop.as_bool(),
_ => true,
};
let regexp_ctor = vm.builtin_proto(ProtoKind::RegExpConstructor);
let regexp_ctor_val = JsValue::from_js_object(regexp_ctor);
let matcher_val = if is_regexp {
let c = match species_constructor(vm, re_ptr, this_val, regexp_ctor_val) {
Ok(c) => c,
Err(err) => return NativeResult::Err(err),
};
let flags_val = vm.new_string_owned(flags);
match vm.construct_ctor(c, &[this_val, flags_val]) {
Ok(m) => m,
Err(e) => return NativeResult::Err(e),
}
} else {
let g_val = vm.new_string_owned("g".to_string());
match vm.construct_ctor(regexp_ctor_val, &[this_val, g_val]) {
Ok(m) => m,
Err(e) => return NativeResult::Err(e),
}
};
if !matcher_val.is_object() {
return NativeResult::Err(crate::error::create_type_error(vm, "RegExp matchAll matcher must be an object"));
}
let matcher_ptr = matcher_val.as_js_object_ptr();
// lastIndex 只从 R 读一次(ToLength),写入 matcher。
let li_val = match rx_get_prop(vm, re_ptr, "lastIndex", this_val) {
Ok(v) => v,
Err(err) => return NativeResult::Err(err),
};
let last_index = match to_length_value(vm, li_val) {
Ok(n) => n,
Err(err) => return NativeResult::Err(err),
};
if let Err(err) = rx_set_prop(vm, matcher_ptr, "lastIndex", JsValue::int(last_index as i32), matcher_val) {
return NativeResult::Err(err);
}
// 迭代器包装:五槽(input/re/done/global/unicode),next 由原型提供。
let regexp_iter_proto = vm.builtin_proto(ProtoKind::RegExpStringIteratorProto);
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.perm_intern(crate::string::MALL_INPUT);
let re_si = vm.perm_intern(crate::string::MALL_RE);
let done_si = vm.perm_intern(crate::string::MALL_DONE);
let global_si = vm.perm_intern(crate::string::MALL_GLOBAL);
let unicode_si = vm.perm_intern(crate::string::MALL_UNICODE);
// [[Global]]/[[Unicode]] 由 flags 串判定(u/v 均码点口径);input 属性存
// 完整转换后的字符串值(单元保真),游标归匹配器 lastIndex(已写入)。
vm.set_or_create_prop_value(wrapper_obj, input_si, s_val);
vm.set_or_create_prop_value(wrapper_obj, re_si, matcher_val);
vm.set_or_create_prop_value(wrapper_obj, done_si, JsValue::bool(false));
vm.set_or_create_prop_value(wrapper_obj, global_si, JsValue::bool(is_global));
vm.set_or_create_prop_value(wrapper_obj, unicode_si, JsValue::bool(is_unicode));
NativeResult::Ok(JsValue::from_js_object(wrapper))
}
#[cfg(test)]
mod tests {
use super::*;
use std::sync::Arc;
use oxide_vm::vm::Vm;
/// 编译并执行单脚本,返回 VM 与完成值。
fn eval(source: &str) -> Result<(Vm, JsValue), String> {
let allocator = oxide_parser::Allocator::default();
let program = oxide_parser::parse(&allocator, source).map_err(|e| format!("parse: {:?}", e))?;
let module = oxide_compiler::compiler::Compiler::new()
.compile(&program)
.map_err(|e| format!("compile: {e}"))?;
let mut vm = Vm::new();
let result = vm.run(&Arc::new(module))?;
Ok((vm, result))
}
/// 读对象自身属性值(按名称),无则 undefined。
fn obj_prop(vm: &Vm, obj: &JsObject, name: &str) -> JsValue {
let si = vm.perm_intern(name);
match vm.get_own_property_slot(obj, si) {
Some(idx) => obj.get_prop_at(idx),
None => JsValue::undefined(),
}
}
/// 读 exec 结果数组第 i 个元素的字符串值(按值)。
fn arr_str(vm: &Vm, arr: &JsObject, i: usize) -> Option<String> {
vm.lookup_str(arr.get_prop_at(i))
}
/// groups.x 取末次已定义出现;result 数组各出现独立交付(首出现被迭代 2
/// 重入清槽,末次出现命中 b)。
#[test]
fn groups_last_defined() {
let (vm, result) = eval(r"/(?:(?<x>a)|(?<x>b)){2}/.exec('ab')").unwrap();
let arr = unsafe { &*result.as_js_object_ptr() };
assert_eq!(arr_str(&vm, arr, 0).as_deref(), Some("ab"));
assert!(arr.get_prop_at(1).is_undefined());
assert_eq!(arr_str(&vm, arr, 2).as_deref(), Some("b"));
let groups = obj_prop(&vm, arr, "groups");
let groups_obj = unsafe { &*groups.as_js_object_ptr() };
assert_eq!(vm.lookup_str(obj_prop(&vm, groups_obj, "x")).as_deref(), Some("b"));
}
/// 同名回引读末次已定义:迭代 2 中末次出现命中 b,回引读 b 命中 "aabb"。
#[test]
fn backref_last_defined_aabb() {
let (vm, result) = eval(r"/(?:(?:(?<x>a)|(?<x>b))\k<x>){2}/.exec('aabb')").unwrap();
let arr = unsafe { &*result.as_js_object_ptr() };
assert_eq!(arr_str(&vm, arr, 0).as_deref(), Some("aabb"));
assert!(arr.get_prop_at(1).is_undefined(), "首出现重入未命中");
assert_eq!(arr_str(&vm, arr, 2).as_deref(), Some("b"));
let groups = obj_prop(&vm, arr, "groups");
let groups_obj = unsafe { &*groups.as_js_object_ptr() };
assert_eq!(vm.lookup_str(obj_prop(&vm, groups_obj, "x")).as_deref(), Some("b"));
}
/// 全部出现未定义时回引空匹配;末次已定义值不匹配时整体失败(不回落空匹配)。
#[test]
fn backref_undefined_empty_and_no_fallthrough() {
let (vm, result) = eval(r"/^(?:(?<a>x)|(?<a>y)|z)\k<a>$/.exec('z')").unwrap();
let arr = unsafe { &*result.as_js_object_ptr() };
assert_eq!(arr_str(&vm, arr, 0).as_deref(), Some("z"));
assert!(arr.get_prop_at(1).is_undefined());
assert!(arr.get_prop_at(2).is_undefined());
// 末次已定义为 x(位 2 为 y,不匹配 x),整体应失败。
let (_, result) = eval(r"/(?:(?<a>y)|(?<a>x)){2}\k<a>/.exec('yxy')").unwrap();
assert!(result.is_null(), "末次已定义值不匹配时整体应失败");
}
}