From 66387b563833e72a1995eb003f11303b6d88c8fb Mon Sep 17 00:00:00 2001 From: pjh456 <147148383@qq.com> Date: Mon, 21 Sep 2026 18:04:11 +0000 Subject: [PATCH] =?UTF-8?q?fix:=20Symbol.replace=20$=20=E5=91=BD?= =?UTF-8?q?=E5=90=8D=E7=BB=84=E6=9B=BF=E6=8D=A2=20+=20groups=20=E7=A9=BA?= =?UTF-8?q?=E5=AF=B9=E8=B1=A1=E4=B8=8D=E5=88=9B=E5=BB=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- crates/oxide_builtins/src/regexp.rs | 57 +++++++++++++++++++++++++++-- 1 file changed, 53 insertions(+), 4 deletions(-) diff --git a/crates/oxide_builtins/src/regexp.rs b/crates/oxide_builtins/src/regexp.rs index 5dff46ab..99b81b1e 100644 --- a/crates/oxide_builtins/src/regexp.rs +++ b/crates/oxide_builtins/src/regexp.rs @@ -8,12 +8,17 @@ use crate::string::{make_units_array, MatchText, OwnedText}; /// 构建 RegExp match result 的 `groups` 对象:键为命名捕获组名,值为匹配字符串或 undefined。 pub(crate) fn build_groups_object(vm: &mut H, m: ®ress::Match, text: &MatchText) -> JsValue { + // 收集所有命名组(迭代器惰性,先 collect 判断是否有命名组)。 + let named: Vec<_> = m.named_groups().collect(); + if named.is_empty() { + return JsValue::undefined(); + } let object_proto = vm.session().builtin_world().object_proto.as_ptr() as *mut JsObject; let groups_obj = vm.alloc_object(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::from_js_object(object_proto))); let groups_ptr = unsafe { &mut *groups_obj }; // 遍历所有命名捕获组,按名称设置属性(重复名称按规范取最后一次匹配)。 - for (name, range) in m.named_groups() { + for (name, range) in named { let name_si = vm.kernel_core().perm_interner().intern(name).0; let value = match range { Some(r) => vm.new_string_units_owned(text.slice(r.start, r.end).into_owned()), @@ -201,10 +206,17 @@ fn advance_string_index(units: &[u16], p: usize, unicode: bool) -> usize { } } -/// GetSubstitution:非函数替换串的 `$` 引用展开(`$$`/`$&`/`` $` ``/`$'`/`$n`); +/// GetSubstitution:非函数替换串的 `$` 引用展开(`$$`/`$&`/`` $` ``/`$'`/`$n`/`$`); /// `$n` 越界(含 $0 与两位回退后仍越界)或未匹配组(None)按字面输出。 -fn get_substitution_units( - matched: &[u16], s: &[u16], position: usize, captured: &[Option>], replacement: &[u16], +/// `$` 从 named_captures 对象读取;不存在时字面输出 `$<` 开头部分。 +fn get_substitution_units( + vm: &mut H, + matched: &[u16], + s: &[u16], + position: usize, + captured: &[Option>], + named_captures: Option<&JsObject>, + replacement: &[u16], ) -> Vec { let mut out = Vec::new(); let pos = position.min(s.len()); @@ -229,6 +241,36 @@ fn get_substitution_units( } else if rest.starts_with(&[0x24, 0x27]) { out.extend_from_slice(&s[tail..]); i += 2; + } else if rest.starts_with(&[0x24, 0x3c]) { + // $ 命名组引用:扫描到 > 为止。 + if let Some(end) = rest[2..].iter().position(|&c| c == 0x3e) { + let name_units = &rest[2..2 + end]; + let name_str = String::from_utf16_lossy(name_units); + if let Some(obj) = named_captures { + let si = vm.kernel_core().perm_interner().intern(&name_str).0; + if si != 0 { + let key = JsValue::undefined(); + if let Ok(cap) = vm.ordinary_get(obj, si, key) { + if !cap.is_undefined() { + if let Ok(cap_str) = to_string_units(vm, cap) { + out.extend_from_slice(&cap_str); + } + } + // 组存在(含 undefined):按规范替换为空串或捕获值,跳过 `$`。 + i += 2 + end + 1; + continue; + } + } + } + // 组不存在或无法读取:字面输出 `$<` 到 `>` 的部分。 + out.extend_from_slice(&rest[..end + 3]); + i += end + 3; + } else { + // 没有闭合的 >:字面输出 `$<`。 + 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 @@ -1313,11 +1355,18 @@ fn make_replacement( }; return to_string_units(vm, repl_value); } + let groups_obj = if rec.groups.is_object() && !rec.groups.as_js_object_ptr().is_null() { + Some(unsafe { &*rec.groups.as_js_object_ptr() }) + } else { + None + }; Ok(get_substitution_units( + vm, &rec.matched, s_units, rec.position, &rec.captures, + groups_obj, replacement_units.unwrap_or(&[]), )) }