fix: matchAll 迭代器 next 按规范重写为 RegExpExec 协议与内部槽语义

This commit is contained in:
2026-09-26 14:35:21 +00:00
parent 381eebb02d
commit d773cc7282
5 changed files with 213 additions and 119 deletions
+27 -14
View File
@@ -126,7 +126,7 @@ fn get_this_obj<H: VmHost>(vm: &mut H, args: &[u8]) -> Result<*mut JsObject, JsV
/// ToLength(Get) 读值的收尾口径:完整对象强转(对象经 ToPrimitive 触发
/// valueOf/toString,转换异常传播)后按 ToLength 收敛到 [0, 2^53-1]。
fn to_length_value<H: VmHost>(vm: &mut H, val: JsValue) -> Result<usize, JsValue> {
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) => {
@@ -157,7 +157,9 @@ fn set_last_index<H: VmHost>(
/// 以 Get 语义按名读属性:完整属性解析(自身数据/自身 accessor/原型链),
/// getter 抛错时恢复原异常值。
fn rx_get_prop<H: VmHost>(vm: &mut H, obj: *mut JsObject, name: &str, this_val: JsValue) -> Result<JsValue, JsValue> {
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.kernel_core().perm_interner().intern(name).0;
match vm.ordinary_get(unsafe { &*obj }, si, this_val) {
Ok(v) => Ok(v),
@@ -165,9 +167,11 @@ fn rx_get_prop<H: VmHost>(vm: &mut H, obj: *mut JsObject, name: &str, this_val:
}
}
/// 以 Get 语义读结果数组的数值下标属性:下标经规范整数键换算(与字符串
/// "0" 形态同键),读异常恢复原异常值。
fn rx_get_index<H: VmHost>(vm: &mut H, obj: *mut JsObject, idx: i32, receiver: JsValue) -> Result<JsValue, JsValue> {
/// 以 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),
@@ -176,7 +180,7 @@ fn rx_get_index<H: VmHost>(vm: &mut H, obj: *mut JsObject, idx: i32, receiver: J
}
/// 以 Set 语义(strict)按名写属性:写失败(非可写等)恢复原异常值。
fn rx_set_prop<H: VmHost>(
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.kernel_core().perm_interner().intern(name).0;
@@ -257,7 +261,7 @@ fn species_constructor<H: VmHost>(
/// AdvanceStringIndex:零宽匹配后的推进——无条件前移,unicode 口径下起点对
/// 代理对跨两个单元,其余跨一个码元(越出串长同样前移,与实现语义一致)。
fn advance_string_index(units: &[u16], p: usize, unicode: bool) -> usize {
pub(crate) fn advance_string_index(units: &[u16], p: usize, unicode: bool) -> usize {
if unicode
&& p < units.len()
&& (0xD800..=0xDBFF).contains(&units[p])
@@ -615,7 +619,7 @@ pub fn regexp_test<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
///
/// # 副作用
/// - global/sticky 时按匹配结果 Set this.lastIndex(越界/失败置 0、成功置末尾)。
fn rx_search<H: VmHost>(
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> {
@@ -1764,8 +1768,9 @@ pub fn regexp_symbol_split<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
/// 非 RegExp 走 Construct(%RegExp%, «R, "g"»)。
/// 4. lastIndex = ToLength(Get(R, "lastIndex"))——只从 R 读一次;
/// Set(matcher, "lastIndex", lastIndex)。
/// 5. global/fullUnicode 由 flags 串判定(不读构造产物属性)。
/// 6. 迭代器包装保持三槽(input/index/re)形态。
/// 5. global/fullUnicode 由 flags 串判定(不读构造产物属性;u/v 均码点口径)。
/// 6. 迭代器包装为五槽(input/re/done/global/unicode),游标归匹配器
/// lastIndex。
///
/// # 边界与前提
/// - 各属性读(flags/@@match/lastIndex)均传播原异常。
@@ -1788,6 +1793,9 @@ pub fn regexp_symbol_match_all<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResu
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::make_well_known_symbol_key(1);
@@ -1837,16 +1845,21 @@ pub fn regexp_symbol_match_all<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResu
return NativeResult::Err(err);
}
// 迭代器包装:三槽(input/index/re),next 由原型提供。
// 迭代器包装:五槽(input/re/done/global/unicode),next 由原型提供。
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(crate::string::MALL_INPUT).0;
let index_si = vm.kernel_core().perm_interner().intern(crate::string::MALL_INDEX).0;
let re_si = vm.kernel_core().perm_interner().intern(crate::string::MALL_RE).0;
// input 属性存完整转换后的字符串值(单元保真);index 游标为码元口径。
let done_si = vm.kernel_core().perm_interner().intern(crate::string::MALL_DONE).0;
let global_si = vm.kernel_core().perm_interner().intern(crate::string::MALL_GLOBAL).0;
let unicode_si = vm.kernel_core().perm_interner().intern(crate::string::MALL_UNICODE).0;
// [[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, index_si, JsValue::int(last_index as i32));
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))
}
@@ -174,16 +174,6 @@ fn clamp_code_point_start(u: &[u16], idx: usize) -> usize {
}
}
/// 给定码元位置之后的下一个码点边界(空匹配游标推进用):跳过低 surrogate
/// 位到下一码点起点;位置在串尾时返回串长+1(触发耗尽守卫)。
pub(crate) fn next_code_point_boundary(u: &[u16], idx: usize) -> usize {
let mut j = idx + 1;
while j < u.len() && is_low_surrogate(u[j]) && is_high_surrogate(u[j - 1]) {
j += 1;
}
j
}
#[inline]
fn is_low_surrogate(u: u16) -> bool {
(0xDC00..=0xDFFF).contains(&u)
+182 -92
View File
@@ -10,8 +10,8 @@ use crate::regexp::build_groups_object;
use super::common::{try_string, MatchText};
use super::{
as_units, find_units, first_match_units, make_string_array_values, make_units_array, map_well_formed_segments,
next_code_point_boundary, split_limit_to_uint32, this_text, this_units,
as_units, find_units, make_string_array_values, make_units_array, map_well_formed_segments, split_limit_to_uint32,
this_text, this_units,
};
// ── 正则替换(单元口径) ────────────────────────────────────────────────
@@ -760,12 +760,14 @@ pub fn string_normalize<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
}
pub(crate) const MALL_INPUT: &str = "__mal_input__";
pub(crate) const MALL_INDEX: &str = "__mal_index__";
pub(crate) const MALL_RE: &str = "__mal_re__";
pub(crate) const MALL_DONE: &str = "__mal_done__";
pub(crate) const MALL_GLOBAL: &str = "__mal_global__";
pub(crate) const MALL_UNICODE: &str = "__mal_unicode__";
/// `String.prototype.matchAll(pattern)`:返回带 `next` 的迭代器,逐步产出全部匹配
/// (要求 RegExp 带 global 标志;普通字符串会被转义成等效正则)。包装器 input
/// 属性存原始字符串值(单元保真),index 属性为码元游标。
/// 属性存原始字符串值(单元保真),游标归匹配器 lastIndex。
///
/// # 步骤
/// 1. receiver 前置校验(RequireObjectCoercible,纯 is_* 读取)。
@@ -886,6 +888,9 @@ pub fn string_match_all<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
// 载体专型标签:native_fn 槽的 Box 经 RegExp 同一守卫释放/深拷贝,
// 避免每次 matchAll 泄漏一个已编译正则。
stub.type_tag = oxide_types::object::JsObject::OBJ_TYPE_REGEX_STUB;
// 载体语义即 RegExpCreate(pattern, "g"):flags 字段物化 "g",使
// g/y/d 判定(regexp_has_flag 读实例字段)与真 RegExp 同径。
stub.set_regexp_flags(vm.new_string("g"));
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 ()) }));
@@ -961,7 +966,12 @@ fn match_all_construct_invoke<H: VmHost>(vm: &mut H, this_val: JsValue, pattern_
}
/// 构造 matchAll 包装对象:原型挂 `%RegExpStringIteratorPrototype%`,
/// own 属性为 input/index(0)/re 三槽,next 由原型提供。
/// own 属性为 input/re/done/global/unicode 五槽(内部槽物化形态),
/// next 由原型提供。
///
/// # 边界与前提
/// - 字符串 pattern 臂调用方:global 恒真(载体语义 = RegExpCreate(pattern, "g"))、
/// unicode 恒假(无 u/v 标志);游标归匹配器 lastIndex,包装器不存游标。
fn builder_wrapper<H: VmHost>(vm: &mut H, input_val: JsValue, re_obj: JsValue) -> NativeResult {
// matchAll 迭代器挂 %RegExpStringIteratorPrototype%(链到 %IteratorPrototype%),
// next 由原型提供(不挂实例 own)。
@@ -970,12 +980,16 @@ fn builder_wrapper<H: VmHost>(vm: &mut H, input_val: JsValue, re_obj: JsValue) -
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;
let done_si = vm.kernel_core().perm_interner().intern(MALL_DONE).0;
let global_si = vm.kernel_core().perm_interner().intern(MALL_GLOBAL).0;
let unicode_si = vm.kernel_core().perm_interner().intern(MALL_UNICODE).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);
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(true));
vm.set_or_create_prop_value(wrapper_obj, unicode_si, JsValue::bool(false));
NativeResult::Ok(JsValue::from_js_object(wrapper))
}
@@ -1015,8 +1029,30 @@ pub fn string_symbol_iterator<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResul
NativeResult::Ok(crate::iterator::build_iterator_wrapper(vm, s_val, None, true, Some(string_iter_proto)))
}
/// `matchAll` 迭代器的 `next`:返回 `{value: 匹配数组, done}`,耗尽后 done 为 true。
/// 游标为码元位置(两臂统一口径);空匹配推进 1 个码元。
/// `matchAll` 迭代器的 `next`:返回 `{value: 匹配, done: false}`,耗尽后
/// done 为 true。
///
/// # 步骤
/// 1. this 非对象抛 TypeError;五内部槽缺一即抛 TypeError(RequireInternalSlot)。
/// 2. `[[Done]]` 为 true → done 结果。
/// 3. `match = RegExpExec(regexp, string)`:`exec` 属性可调用 →
/// Call(exec, regexp, «string»),结果非对象非 null 抛 TypeError;不可调用 →
/// regexp 持编译正则时落内置搜索核(RegExpBuiltinExec 口径),否则
/// TypeError。
/// 4. match 为 null → 写 `[[Done]]` = true,done 结果。
/// 5. `[[Global]]` 为 false → 写 `[[Done]]` = true,返回单匹配(非 global
/// 匹配器只产一枚)。
/// 6. 空匹配(ToString(match["0"]) 为空串)→ lastIndex 读-推进-写:
/// thisIndex = ToLength(Get(regexp, "lastIndex")),nextIndex =
/// AdvanceStringIndex(u/v 口径跨代理对 2 单元),Set(regexp, "lastIndex")。
/// 7. 返回 `{value: match, done: false}`。
///
/// # 边界与前提
/// - 各读/调/写(exec getter 与调用、match."0" 读与 ToString、lastIndex
/// Get/Set/ToLength)的原异常原值传播。
///
/// # 副作用
/// - 写包装器 `[[Done]]` 槽;空匹配臂写匹配器 lastIndex。
pub fn string_match_all_next<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
let this_val = vm.reg(if args.is_empty() { 0 } else { args[0] });
if !this_val.is_object() {
@@ -1024,104 +1060,158 @@ pub fn string_match_all_next<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult
}
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 done_si = vm.kernel_core().perm_interner().intern(MALL_DONE).0;
let global_si = vm.kernel_core().perm_interner().intern(MALL_GLOBAL).0;
let unicode_si = vm.kernel_core().perm_interner().intern(MALL_UNICODE).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) {
// 内部槽守卫:五槽同批写入,查一槽即全查;缺失即 TypeError。
if vm.get_own_property_slot(wrapper, done_si).is_none() {
return NativeResult::Err(crate::error::create_type_error(
vm,
"matchAll next called on object without internal slots",
));
}
// [[Done]] 为 true → done 结果。
let done_val = match vm.ordinary_get(wrapper, done_si, this_val) {
Ok(v) => v,
Err(_) => return make_match_done_result(vm, JsValue::undefined()),
Err(_) => return NativeResult::Err(crate::iterator::engine_error(vm, "cannot read done slot")),
};
if oxide_runtime_api::to_boolean(done_val) {
return make_match_done_result(vm, JsValue::undefined());
}
// 读余下四槽:input / re / global / unicode。
let input_val = match vm.ordinary_get(wrapper, input_si, this_val) {
Ok(v) => v,
Err(_) => return NativeResult::Err(crate::iterator::engine_error(vm, "cannot read input slot")),
};
let idx_raw = if idx_val.is_int() { idx_val.as_int().max(0) } else { 0 };
let idx = idx_raw as usize;
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) };
// d 标志门控 indices 产出(flags 串实例字段直读,与 exec 同口径)。
let has_indices = crate::regexp::regexp_has_flag(vm, re_obj, 'd');
// 按 input 载荷形态分臂:ASCII Flat 走字节通道(字节==码元,游标与匹配
// 范围同口径,无需换算);非 ASCII Flat 与其余载荷走单元通道,按标志
// 分派(u/v 码点、其余码元,见 first_match_units)。
// 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());
}
// 元素按值混排:匹配片段物化字符串值,未匹配捕获组为 undefined。
let (match_start, next_idx, parts, m_opt): (usize, usize, Vec<JsValue>, Option<regress::Match>) = if sp.is_flat()
&& sp.as_str().is_ascii()
{
let s = sp.as_str();
match regex.find_from(s, idx).next() {
Some(m) => {
let range = m.range();
let mut parts = Vec::with_capacity(m.captures.len() + 1);
parts.push(vm.new_string_units_owned(s[range.start..range.end].encode_utf16().collect()));
for i in 1..=m.captures.len() {
match m.group(i) {
Some(g) => parts.push(vm.new_string_units_owned(s[g.start..g.end].encode_utf16().collect())),
None => parts.push(JsValue::undefined()),
}
}
// 空匹配(range 无推进)须推进至少一个码元,否则同一位置反复
// 空匹配死循环;非空匹配游标落匹配末尾(码元口径)。
let next_idx = if range.end > range.start { range.end } else { range.end + 1 };
(range.start, next_idx, parts, Some(m))
}
None => (0, 0, Vec::new(), None),
_ => {
return NativeResult::Err(crate::error::create_type_error(vm, "matchAll next called on non-object regexp"))
}
};
let is_global = match vm.ordinary_get(wrapper, global_si, this_val) {
Ok(v) => oxide_runtime_api::to_boolean(v),
Err(_) => return NativeResult::Err(crate::iterator::engine_error(vm, "cannot read global slot")),
};
let is_unicode = match vm.ordinary_get(wrapper, unicode_si, this_val) {
Ok(v) => oxide_runtime_api::to_boolean(v),
Err(_) => return NativeResult::Err(crate::iterator::engine_error(vm, "cannot read unicode slot")),
};
let re_ptr = re_val.as_js_object_ptr();
let re_obj = unsafe { &*re_ptr };
// ── RegExpExec(R, S) ──
// Get(R, "exec"):getter 抛错原值传播。
let exec_val = match crate::regexp::rx_get_prop(vm, re_ptr, "exec", re_val) {
Ok(v) => v,
Err(err) => return NativeResult::Err(err),
};
let match_val = if crate::iterator::is_callable(exec_val) {
// Call(exec, R, «S»):调用抛错原值传播;结果非对象非 null 抛 TypeError。
let result = match vm.call_function_sync(exec_val, re_val, &[input_val]) {
Ok(r) => r,
Err(_) => return NativeResult::Err(crate::iterator::engine_error(vm, "RegExp exec call failed")),
};
if !(result.is_null() || result.is_object()) {
return NativeResult::Err(crate::error::create_type_error(
vm,
"RegExp exec result must be an object or null",
));
}
result
} else {
let u = sp.units();
let is_code_point = regex.flags().unicode || regex.flags().unicode_sets;
match first_match_units(regex, &u, idx) {
// 不可调用回落:仅 regexp 持编译正则(真 RegExp 或字符串 pattern 载体)
// 可走内置搜索核,否则 RequireInternalSlot 失败。
let fn_ptr = match re_obj.native_fn() {
Some(p) => p,
None => return NativeResult::Err(crate::error::create_type_error(vm, "RegExp exec is not callable")),
};
// SAFETY: fn_ptr 持有 regexp_constructor 存放的 `Box<regress::Regex>` 指针。
let regex = unsafe { &*(fn_ptr.as_ptr() as *const regress::Regex) };
let text = MatchText::from_value(input_val);
// g/y 标志决定 lastIndex 追踪(与 exec/test 同口径)。
let tracks = crate::regexp::regexp_has_flag(vm, re_obj, 'g') || crate::regexp::regexp_has_flag(vm, re_obj, 'y');
let sticky = crate::regexp::regexp_has_flag(vm, re_obj, 'y');
let m = match crate::regexp::rx_search(vm, re_ptr, re_val, regex, &text, tracks, sticky) {
Ok(opt) => opt,
Err(err) => return NativeResult::Err(err),
};
match m {
None => JsValue::null(),
Some(m) => {
let range = m.range();
let mut parts = Vec::with_capacity(m.captures.len() + 1);
parts.push(vm.new_string_units_owned(u[range.start..range.end].to_vec()));
for i in 1..=m.captures.len() {
match m.group(i) {
Some(g) => parts.push(vm.new_string_units_owned(u[g.start..g.end].to_vec())),
None => parts.push(JsValue::undefined()),
}
}
// 非空匹配游标落匹配末尾;空匹配推进到下一码点边界(u/v
// 标志)或下一码元(其余),串尾空匹配推进到串长+1 触发耗尽。
let next_idx = if range.end > range.start {
range.end
} else if is_code_point {
next_code_point_boundary(&u, range.end)
} else {
range.end + 1
};
(range.start, next_idx, parts, Some(m))
// d 标志门控 indices 产出(与 exec 同口径)。
let has_indices = crate::regexp::regexp_has_flag(vm, re_obj, 'd');
let parts = match_parts(vm, &text, &m);
make_match_result_array(vm, parts, m.range().start as i32, input_val, Some(&m), &text, has_indices)
}
None => (0, 0, Vec::new(), None),
}
};
if parts.is_empty() {
// match 为 null → 写 [[Done]] = true,done 结果。
if match_val.is_null() {
vm.set_or_create_prop_value(wrapper, done_si, JsValue::bool(true));
return make_match_done_result(vm, JsValue::undefined());
}
vm.set_or_create_prop_value(wrapper, index_si, JsValue::int(next_idx as i32));
// 构建结果数组:按元素混排字符串值/undefined + 挂 index/input/groups 属性。
let match_text = MatchText::from_value(input_val);
let value =
make_match_result_array(vm, parts, match_start as i32, input_val, m_opt.as_ref(), &match_text, has_indices);
make_match_done_result(vm, value)
// [[Global]] 为 false → 写 [[Done]] = true,返回单匹配。
if !is_global {
vm.set_or_create_prop_value(wrapper, done_si, JsValue::bool(true));
return make_match_done_result(vm, match_val);
}
// matchString = ToString(Get(match, "0")):下标经整数键换算(数组元素区
// 按整数键存储),getter 与 ToString 抛错原值传播。
let m0 = match crate::regexp::rx_get_index(vm, match_val.as_js_object_ptr(), 0, match_val) {
Ok(v) => v,
Err(err) => return NativeResult::Err(err),
};
let m0_str = match oxide_runtime_api::to_string_value_full(m0, 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"));
}
};
// 空匹配 → lastIndex 读-推进-写(内置臂 lastIndex 为 rx_search 写入的
// 匹配末尾,自定义 exec 臂为用户代码已设值)。
if vm.string_units(m0_str).is_empty() {
let li_val = match crate::regexp::rx_get_prop(vm, re_ptr, "lastIndex", re_val) {
Ok(v) => v,
Err(err) => return NativeResult::Err(err),
};
let this_index = match crate::regexp::to_length_value(vm, li_val) {
Ok(n) => n,
Err(err) => return NativeResult::Err(err),
};
let units = vm.string_units(input_val);
let next_index = crate::regexp::advance_string_index(&units, this_index, is_unicode);
if let Err(err) = crate::regexp::rx_set_prop(vm, re_ptr, "lastIndex", JsValue::int(next_index as i32), re_val) {
return NativeResult::Err(err);
}
}
make_match_done_result(vm, match_val)
}
/// 由匹配结果构建结果数组元素列表:完整匹配字符串值在前,其后逐捕获组
/// (未参与匹配为 undefined),片段经 MatchText 臂的单元口径物化。
fn match_parts<H: VmHost>(vm: &mut H, text: &MatchText, m: &regress::Match) -> Vec<JsValue> {
let range = m.range();
let mut parts = Vec::with_capacity(m.captures.len() + 1);
parts.push(vm.new_string_units_owned(text.slice(range.start, range.end).into_owned()));
for i in 1..=m.captures.len() {
match m.group(i) {
Some(g) => parts.push(vm.new_string_units_owned(text.slice(g.start, g.end).into_owned())),
None => parts.push(JsValue::undefined()),
}
}
parts
}
/// 构建 matchAll 结果数组:元素为匹配字符串值(未匹配捕获组为 undefined),
+1 -1
View File
@@ -684,7 +684,7 @@ fn install_to_string_tags(core: &Arc<KernelCore>, session: &KernelSession) {
(world.map_iterator_proto.as_ptr() as *mut JsObject, "Map Iterator"),
(world.set_iterator_proto.as_ptr() as *mut JsObject, "Set Iterator"),
(world.string_iterator_proto.as_ptr() as *mut JsObject, "String Iterator"),
(world.regexp_string_iterator_proto.as_ptr() as *mut JsObject, "String Iterator"),
(world.regexp_string_iterator_proto.as_ptr() as *mut JsObject, "RegExp String Iterator"),
(world.math_object.as_ptr() as *mut JsObject, "Math"),
(world.json_object.as_ptr() as *mut JsObject, "JSON"),
(world.symbol_proto.as_ptr() as *mut JsObject, "Symbol"),
@@ -69,7 +69,8 @@ fn string_pattern_match_all_iterator_survives_full_collect() {
assert_eq!(to_str(&vm, result), "ab,ab");
}
/// 非 global 正则路径的存活载体同样跨收集保持有效。
/// 非 global 正则路径的存活载体同样跨收集保持有效:非 global 匹配器
/// 只产一枚匹配后 done([[Global]] false 分支),存活判据是该枚匹配。
#[test]
fn non_global_regexp_match_all_iterator_survives_full_collect() {
let mut vm = Vm::new();
@@ -80,7 +81,7 @@ fn non_global_regexp_match_all_iterator_survives_full_collect() {
"var out = []; var st = it.next(); while (!st.done) { out.push(String(st.value[0])); st = it.next(); } out.join(',')",
)
.unwrap();
assert_eq!(to_str(&vm, result), "ab,ab");
assert_eq!(to_str(&vm, result), "ab");
}
/// 载体经内部属性可达,但无 RegExp 属性面:toString 保持普通对象标签。