From 58a97f838c0aeb52990d32f439592d194b567438 Mon Sep 17 00:00:00 2001 From: pjh456 <147148383@qq.com> Date: Mon, 21 Sep 2026 17:58:01 +0000 Subject: [PATCH] =?UTF-8?q?fix:=20RegExp=20source=20getter=20EscapeRegExpP?= =?UTF-8?q?attern=20+=20groups=20=E5=AF=B9=E8=B1=A1=E6=9E=84=E5=BB=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- crates/oxide_builtins/src/regexp.rs | 77 ++++++++++++++++++++-- crates/oxide_builtins/src/string/common.rs | 2 +- crates/oxide_builtins/src/string/regex.rs | 31 ++++++--- 3 files changed, 95 insertions(+), 15 deletions(-) diff --git a/crates/oxide_builtins/src/regexp.rs b/crates/oxide_builtins/src/regexp.rs index a4d89830..5dff46ab 100644 --- a/crates/oxide_builtins/src/regexp.rs +++ b/crates/oxide_builtins/src/regexp.rs @@ -4,7 +4,26 @@ use oxide_types::value::JsValue; use oxide_runtime_api::{NativeResult, VmHost}; -use crate::string::{make_units_array, OwnedText}; +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 { + 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() { + 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()), + None => JsValue::undefined(), + }; + vm.set_or_create_prop_value(groups_ptr, name_si, value); + } + + JsValue::from_js_object(groups_obj) +} fn get_regexp_ptr(vm: &mut H, args: &[u8]) -> Result<*mut JsObject, JsValue> { let this_val = vm.reg(args[0]); @@ -584,8 +603,9 @@ pub fn regexp_exec(vm: &mut H, args: &[u8]) -> NativeResult { let input_val = haystack.to_value(vm); let input_si = vm.kernel_core().perm_interner().intern("input").0; 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.kernel_core().perm_interner().intern("groups").0; - vm.set_or_create_prop_value(unsafe { &mut *arr }, groups_si, JsValue::undefined()); + vm.set_or_create_prop_value(unsafe { &mut *arr }, groups_si, groups_val); // 成功:lastIndex 推进到匹配末尾(仅 global/sticky),Set 语义。 if tracks_last_index { @@ -814,12 +834,12 @@ pub fn regexp_get_flags(vm: &mut H, args: &[u8]) -> NativeResult { /// `RegExp.prototype.source` getter:返回实例 source 串。 pub fn regexp_get_source(vm: &mut H, args: &[u8]) -> NativeResult { let this_val = vm.reg(args[0]); - // proto 恒等判定:规范对 prototype 本身返回 undefined。 + // proto 恒等判定:规范对 prototype 本身返回 "(?:)"(无 [[OriginalFlags]] 槽时)。 if this_val.is_object() { let this_ptr = this_val.as_js_object_ptr(); let proto_ptr = vm.session().builtin_world().regexp_proto.as_ptr() as *mut JsObject; if !this_ptr.is_null() && this_ptr == proto_ptr { - return NativeResult::Ok(JsValue::undefined()); + 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) { @@ -827,7 +847,54 @@ pub fn regexp_get_source(vm: &mut H, args: &[u8]) -> NativeResult { Err(err) => return NativeResult::Err(err), }; let re = unsafe { &*re_ptr }; - NativeResult::Ok(re.get_regexp_source()) + 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 { + let has_uv = flags.contains('u') || flags.contains('v'); + let mut out: Vec = 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); diff --git a/crates/oxide_builtins/src/string/common.rs b/crates/oxide_builtins/src/string/common.rs index 42233fe4..29317a72 100644 --- a/crates/oxide_builtins/src/string/common.rs +++ b/crates/oxide_builtins/src/string/common.rs @@ -34,7 +34,7 @@ pub(crate) enum MatchText<'a> { impl<'a> MatchText<'a> { /// 从字符串值按载荷形态借用:Flat → Str 零拷贝,其余 → Units /// (FlatU16 直接借用、Cons 经扁平化缓存)。 - fn from_value(val: JsValue) -> Self { + pub(crate) fn from_value(val: JsValue) -> Self { debug_assert!(val.is_string()); // SAFETY: val 为字符串值,借用期存活由调用方借用纪律保证。 let sp = unsafe { &*val.as_string_ptr() }; diff --git a/crates/oxide_builtins/src/string/regex.rs b/crates/oxide_builtins/src/string/regex.rs index 6a17f267..4f8a142f 100644 --- a/crates/oxide_builtins/src/string/regex.rs +++ b/crates/oxide_builtins/src/string/regex.rs @@ -8,8 +8,9 @@ use oxide_runtime_api::{NativeResult, VmHost}; use crate::builtins_debug; use crate::builtins_error; +use crate::regexp::build_groups_object; -use super::common::try_string; +use super::common::{try_string, MatchText}; use super::{ as_units, byte_to_unit, find_units, make_string_array_values, make_units_array, map_well_formed_segments, this_text, this_units, unit_to_byte, @@ -1050,7 +1051,7 @@ pub fn string_match_all_next(vm: &mut H, args: &[u8]) -> NativeResult if idx > total_units { return make_match_done_result(vm, JsValue::undefined()); } - let (match_start, next_idx, parts): (usize, usize, Vec>) = if sp.is_flat() { + let (match_start, next_idx, parts, m_opt): (usize, usize, Vec>, Option) = if sp.is_flat() { let s = sp.as_str(); match regex.find_from(s, unit_to_byte(s, idx)).next() { Some(m) => { @@ -1068,9 +1069,9 @@ pub fn string_match_all_next(vm: &mut H, args: &[u8]) -> NativeResult let start_units = byte_to_unit(s, range.start); let end_units = byte_to_unit(s, range.end); let next_idx = if range.end > range.start { end_units } else { end_units + 1 }; - (start_units, next_idx, parts) + (start_units, next_idx, parts, Some(m)) } - None => (0, 0, Vec::new()), + None => (0, 0, Vec::new(), None), } } else { let u = sp.units(); @@ -1086,9 +1087,9 @@ pub fn string_match_all_next(vm: &mut H, args: &[u8]) -> NativeResult } } let next_idx = if range.end > range.start { range.end } else { range.end + 1 }; - (range.start, next_idx, parts) + (range.start, next_idx, parts, Some(m)) } - None => (0, 0, Vec::new()), + None => (0, 0, Vec::new(), None), } }; @@ -1097,12 +1098,20 @@ pub fn string_match_all_next(vm: &mut H, args: &[u8]) -> NativeResult } vm.set_or_create_prop_value(wrapper, index_si, JsValue::int(next_idx as i32)); // 构建结果数组:按元素物化字符串值 + 挂 index/input/groups 属性。 - let value = make_match_result_array(vm, parts, match_start as i32, input_val); + 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); make_match_done_result(vm, value) } /// 构建 matchAll 结果数组:元素为匹配字符串值,附带 index/input/groups 属性。 -fn make_match_result_array(vm: &mut H, parts: Vec>, match_index: i32, input_val: JsValue) -> JsValue { +fn make_match_result_array( + vm: &mut H, + parts: Vec>, + match_index: i32, + input_val: JsValue, + m: Option<®ress::Match>, + text: &MatchText, +) -> JsValue { let values: Vec = parts.into_iter().map(|u| vm.new_string_units_owned(u)).collect(); let arr = make_string_array_values(vm, values); let arr_ptr = arr.as_js_object_ptr(); @@ -1111,8 +1120,12 @@ fn make_match_result_array(vm: &mut H, parts: Vec>, match_in vm.set_or_create_prop_value(arr_obj, index_si, JsValue::int(match_index)); let input_si = vm.kernel_core().perm_interner().intern("input").0; vm.set_or_create_prop_value(arr_obj, input_si, input_val); + let groups_val = match m { + Some(m) => build_groups_object(vm, m, text), + None => JsValue::undefined(), + }; let groups_si = vm.kernel_core().perm_interner().intern("groups").0; - vm.set_or_create_prop_value(arr_obj, groups_si, JsValue::undefined()); + vm.set_or_create_prop_value(arr_obj, groups_si, groups_val); arr }