diff --git a/crates/oxide_builtins/src/regexp.rs b/crates/oxide_builtins/src/regexp.rs index e8293d35..6f38ef14 100644 --- a/crates/oxide_builtins/src/regexp.rs +++ b/crates/oxide_builtins/src/regexp.rs @@ -6,18 +6,19 @@ use oxide_runtime_api::{NativeResult, VmHost}; use crate::string::{make_units_array, MatchText, OwnedText}; -/// 构建 RegExp match result 的 `groups` 对象:键为命名捕获组名,值为匹配字符串或 undefined。 +/// 构建 RegExp match result 的 `groups` 对象:null 原型(ObjectCreate(null)), +/// 键为命名捕获组名,值为匹配字符串或 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))); + // 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.kernel_core().perm_interner().intern(name).0; let value = match range { @@ -30,6 +31,66 @@ pub(crate) fn build_groups_object(vm: &mut H, m: ®ress::Match, tex JsValue::from_js_object(groups_obj) } +/// 构建 d 标志下的 `indices` 数组:`indices[0]` 为完整匹配 [start, end] 码元对, +/// 其后逐捕获组(未参与匹配的组为 undefined);自有 `groups` 属性为同构 +/// indices.groups 对象(null 原型),全部经 CreateDataProperty 写入。 +pub(crate) fn build_indices_array(vm: &mut H, m: ®ress::Match, text: &MatchText) -> JsValue { + let group_count = m.captures.len(); + let n = 1 + group_count; + let proto = vm.session().builtin_world().array_proto.as_ptr() as *mut JsObject; + let arr = + vm.alloc_object(JsObject::new_array(EMPTY_SHAPE_ID, JsValue::from_js_object(proto), n, vm.epoch().bump())); + 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.kernel_core().perm_interner().intern("groups").0; + vm.set_or_create_prop_value(unsafe { &mut *arr }, groups_si, groups_val); + JsValue::from_js_object(arr) +} + +/// 单个 [start, end] 码元对数组(数组原型,两个自身元素)。 +fn index_pair(vm: &mut H, start: usize, end: usize) -> JsValue { + let proto = vm.session().builtin_world().array_proto.as_ptr() as *mut JsObject; + let arr = + vm.alloc_object(JsObject::new_array(EMPTY_SHAPE_ID, JsValue::from_js_object(proto), 2, vm.epoch().bump())); + 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(vm: &mut H, m: ®ress::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.kernel_core().perm_interner().intern(name).0; + 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(vm: &mut H, args: &[u8]) -> Result<*mut JsObject, JsValue> { let this_val = vm.reg(args[0]); if !this_val.is_object() { @@ -146,7 +207,7 @@ fn rx_get_bool_prop(vm: &mut H, rx: *mut JsObject, name: &str, this_v /// 共享 RegExpExec(R, S):Get(R, "exec") 须可调用,Call(exec, R, «S»), /// 结果非 null 非对象抛 TypeError。exec 自身的抛错恢复原异常值。 -fn regexp_exec_call( +pub(crate) fn regexp_exec_call( vm: &mut H, rx: *mut JsObject, rx_val: JsValue, s_val: JsValue, ) -> Result { let exec_val = rx_get_prop(vm, rx, "exec", rx_val)?; @@ -350,10 +411,15 @@ pub fn regexp_constructor(vm: &mut H, args: &[u8]) -> NativeResult { }; (vm.string_units(source).into_owned(), vm.string_units(flags).into_owned()) } else { - let p = 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)), + // 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) { @@ -633,10 +699,10 @@ pub fn regexp_exec(vm: &mut H, args: &[u8]) -> NativeResult { }; let text = haystack.as_match_text(); - // 标志决定搜索起点与 lastIndex 写回条件。 - let (is_global, is_sticky) = { + // 标志决定搜索起点、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, 'g'), regexp_has_flag(vm, re, 'y'), regexp_has_flag(vm, re, 'd')) }; let tracks_last_index = is_global || is_sticky; @@ -677,6 +743,13 @@ pub fn regexp_exec(vm: &mut H, args: &[u8]) -> NativeResult { let groups_si = vm.kernel_core().perm_interner().intern("groups").0; 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.kernel_core().perm_interner().intern("indices").0; + vm.set_or_create_prop_value(unsafe { &mut *arr }, indices_si, indices_val); + } + // 成功:lastIndex 推进到匹配末尾(仅 global/sticky),Set 语义。 if tracks_last_index { if let Err(err) = set_last_index(vm, re_ptr, this_val, text.unit_pos(range.end)) { diff --git a/crates/oxide_builtins/src/string/regex.rs b/crates/oxide_builtins/src/string/regex.rs index ab05233d..73ab9a12 100644 --- a/crates/oxide_builtins/src/string/regex.rs +++ b/crates/oxide_builtins/src/string/regex.rs @@ -313,14 +313,15 @@ pub fn string_split(vm: &mut H, args: &[u8]) -> NativeResult { let re = unsafe { &*re_ptr }; if let Some(fn_ptr) = re.native_fn() { let regex = unsafe { &*(fn_ptr.as_ptr() as *const regress::Regex) }; - let mut parts: Vec> = Vec::new(); + // 片段物化字符串值,未匹配捕获组推 undefined(结果数组按值混排)。 + let mut parts: Vec = Vec::new(); let mut last_end = 0; for m in regex.find_from_utf16(&s, 0) { if parts.len() >= limit { break; } let range = m.range(); - parts.push(s[last_end..range.start].to_vec()); + parts.push(vm.new_string_units_owned(s[last_end..range.start].to_vec())); if parts.len() >= limit { break; } @@ -329,16 +330,16 @@ pub fn string_split(vm: &mut H, args: &[u8]) -> NativeResult { break; } match m.group(i) { - Some(g) => parts.push(s[g.start..g.end].to_vec()), - None => parts.push(Vec::new()), + Some(g) => parts.push(vm.new_string_units_owned(s[g.start..g.end].to_vec())), + None => parts.push(JsValue::undefined()), } } last_end = range.end; } if last_end <= s.len() && parts.len() < limit { - parts.push(s[last_end..].to_vec()); + parts.push(vm.new_string_units(&s[last_end..])); } - return NativeResult::Ok(make_units_array(vm, parts)); + return NativeResult::Ok(make_string_array_values(vm, parts)); } // 无原生正则的类正则对象回退到字符串路径。 } @@ -570,10 +571,6 @@ pub fn string_match_fn(vm: &mut H, args: &[u8]) -> NativeResult { // 正则判定与参数字符串转换先行(&mut 路径),后借 this 匹配扫描。 let pattern_val = if args.len() >= 2 { Some(vm.reg(args[1])) } else { None }; let is_re = pattern_val.map(|v| is_regexp_obj(v, vm)).unwrap_or(false); - let pattern: Vec = match pattern_val { - Some(v) if !is_re => try_string!(as_units(vm, v)).into_owned(), - _ => Vec::new(), - }; // global 标志前置读:flags 串共享借用与下方 text 转换的 &mut 借用不可重叠。 let is_global = if is_re { let re_val = match pattern_val { @@ -588,23 +585,21 @@ pub fn string_match_fn(vm: &mut H, args: &[u8]) -> NativeResult { false }; let text = try_string!(this_text(vm, args)); - if args.len() < 2 { - return NativeResult::Ok(JsValue::null()); - } + // 缺参按 undefined 模式(规范:构造空模式正则,串头命中)。 let pattern_val = match pattern_val { Some(v) => v, - None => return NativeResult::Err(crate::error::create_type_error(vm, "expected pattern")), + None => JsValue::undefined(), }; if is_re { let re_ptr = pattern_val.as_js_object_ptr(); - let re = unsafe { &*re_ptr }; - let fn_ptr = match re.native_fn() { - Some(p) => p, - None => return NativeResult::Ok(JsValue::null()), - }; - // SAFETY: fn_ptr 持有 regexp_constructor 存放的 `Box` 指针。 - let regex = unsafe { &*(fn_ptr.as_ptr() as *const regress::Regex) }; if is_global { + let re = unsafe { &*re_ptr }; + let fn_ptr = match re.native_fn() { + Some(p) => p, + None => return NativeResult::Ok(JsValue::null()), + }; + // SAFETY: fn_ptr 持有 regexp_constructor 存放的 `Box` 指针。 + let regex = unsafe { &*(fn_ptr.as_ptr() as *const regress::Regex) }; let mut matches: Vec> = Vec::new(); text.for_each_match(regex, |m| { let range = m.range(); @@ -615,30 +610,57 @@ pub fn string_match_fn(vm: &mut H, args: &[u8]) -> NativeResult { } return NativeResult::Ok(make_units_array(vm, matches)); } - if let Some(m) = text.find_from_units(regex, 0) { - let range = m.range(); - let mut parts: Vec> = Vec::with_capacity(m.captures.len() + 1); - parts.push(text.slice(range.start, range.end).into_owned()); - for i in 1..=m.captures.len() { - match m.group(i) { - Some(g) => parts.push(text.slice(g.start, g.end).into_owned()), - None => parts.push(Vec::new()), - } - } - return NativeResult::Ok(make_units_array(vm, parts)); - } - return NativeResult::Ok(JsValue::null()); + // 非 global:GetMethod(rx, @@match) + Invoke,结果原值返回 + // (默认 @@match 非 global 臂交付 exec 结果本体,index/input/groups/ + // indices 与 exec 同源;影子 @@match 可替换)。 + let s_units = text.units().into_owned(); + let s_val = vm.new_string_units_owned(s_units); + let matcher = match rx_get_match_method(vm, re_ptr, pattern_val) { + Ok(v) => v, + Err(e) => return NativeResult::Err(e), + }; + return match vm.call_function_sync(matcher, pattern_val, &[s_val]) { + Ok(r) => NativeResult::Ok(r), + Err(e) => NativeResult::Err(crate::iterator::engine_error(vm, &e)), + }; } - // 空模式命中串头:产出单元素空串数组(规格口径)。 - if pattern.is_empty() { - return NativeResult::Ok(make_units_array(vm, vec![Vec::new()])); + // 非正则模式:Construct %RegExp%(undefined → 空模式、其余经 ToString), + // 再 GetMethod(rx, @@match) + Invoke;转换抛错(含 Symbol)原样传播。 + let s_units = text.units().into_owned(); + let s_val = vm.new_string_units_owned(s_units); + let regexp_ctor = vm.session().builtin_world().regexp_constructor.as_ptr() as *mut JsObject; + let rx_val = match vm.construct_ctor(JsValue::from_js_object(regexp_ctor), &[pattern_val]) { + Ok(v) => v, + Err(e) => return NativeResult::Err(e), + }; + if !rx_val.is_object() { + return NativeResult::Err(crate::error::create_type_error( + vm, + "match pattern constructor must return an object", + )); } - let s = text.units(); - if let Some(pos) = find_units(&s, &pattern, 0) { - let matched = s[pos..pos + pattern.len()].to_vec(); - return NativeResult::Ok(make_units_array(vm, vec![matched])); + let matcher = match rx_get_match_method(vm, rx_val.as_js_object_ptr(), rx_val) { + Ok(v) => v, + Err(e) => return NativeResult::Err(e), + }; + match vm.call_function_sync(matcher, rx_val, &[s_val]) { + Ok(r) => NativeResult::Ok(r), + Err(e) => NativeResult::Err(crate::iterator::engine_error(vm, &e)), } - NativeResult::Ok(JsValue::null()) +} + +/// GetMethod(rx, @@match):可调用返回之,null/undefined/不可调用抛 TypeError; +/// 属性读抛错恢复原异常值。 +fn rx_get_match_method(vm: &mut H, rx: *mut JsObject, this_val: JsValue) -> Result { + let match_key = oxide_types::private_key::make_well_known_symbol_key(1); + let matcher = match vm.ordinary_get(unsafe { &*rx }, match_key, this_val) { + Ok(v) => v, + Err(e) => return Err(crate::iterator::engine_error(vm, &e)), + }; + if !crate::iterator::is_callable(matcher) { + return Err(crate::error::create_type_error(vm, "RegExp @@match is not callable")); + } + Ok(matcher) } /// `String.prototype.search(pattern)`:返回首个匹配位置(码元),无匹配返回 -1。 @@ -1043,6 +1065,8 @@ pub fn string_match_all_next(vm: &mut H, args: &[u8]) -> NativeResult 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 载荷形态分臂:Flat 走字节通道(游标经码元↔字节换算), // 其余走单元通道(游标即码元)。 @@ -1054,17 +1078,18 @@ 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, m_opt): (usize, usize, Vec>, Option) = if sp.is_flat() { + // 元素按值混排:匹配片段物化字符串值,未匹配捕获组为 undefined。 + 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) => { let range = m.range(); let mut parts = Vec::with_capacity(m.captures.len() + 1); - parts.push(s[range.start..range.end].encode_utf16().collect()); + 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(s[g.start..g.end].encode_utf16().collect()), - None => parts.push(Vec::new()), + Some(g) => parts.push(vm.new_string_units_owned(s[g.start..g.end].encode_utf16().collect())), + None => parts.push(JsValue::undefined()), } } // 空匹配(range 无推进)须推进至少一个码元,否则同一位置反复 @@ -1082,11 +1107,11 @@ pub fn string_match_all_next(vm: &mut H, args: &[u8]) -> NativeResult Some(m) => { let range = m.range(); let mut parts = Vec::with_capacity(m.captures.len() + 1); - parts.push(u[range.start..range.end].to_vec()); + 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(u[g.start..g.end].to_vec()), - None => parts.push(Vec::new()), + Some(g) => parts.push(vm.new_string_units_owned(u[g.start..g.end].to_vec())), + None => parts.push(JsValue::undefined()), } } let next_idx = if range.end > range.start { range.end } else { range.end + 1 }; @@ -1100,19 +1125,20 @@ pub fn string_match_all_next(vm: &mut H, args: &[u8]) -> NativeResult return make_match_done_result(vm, JsValue::undefined()); } vm.set_or_create_prop_value(wrapper, index_si, JsValue::int(next_idx as i32)); - // 构建结果数组:按元素物化字符串值 + 挂 index/input/groups 属性。 + // 构建结果数组:按元素混排字符串值/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); + 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) } -/// 构建 matchAll 结果数组:元素为匹配字符串值,附带 index/input/groups 属性。 +/// 构建 matchAll 结果数组:元素为匹配字符串值(未匹配捕获组为 undefined), +/// 附带 index/input/groups 属性(d 标志下另挂 indices,与 exec 结果同面)。 fn make_match_result_array( - vm: &mut H, parts: Vec>, match_index: i32, input_val: JsValue, m: Option<®ress::Match>, - text: &MatchText, + vm: &mut H, parts: Vec, match_index: i32, input_val: JsValue, m: Option<®ress::Match>, + text: &MatchText, has_indices: bool, ) -> 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 = make_string_array_values(vm, parts); let arr_ptr = arr.as_js_object_ptr(); let arr_obj = unsafe { &mut *arr_ptr }; let index_si = vm.kernel_core().perm_interner().intern("index").0; @@ -1125,6 +1151,14 @@ fn make_match_result_array( }; let groups_si = vm.kernel_core().perm_interner().intern("groups").0; vm.set_or_create_prop_value(arr_obj, groups_si, groups_val); + // d 标志:挂 indices 属性(与 exec 结果同口径的码元对数组族)。 + if has_indices { + if let Some(m) = m { + let indices_val = crate::regexp::build_indices_array(vm, m, text); + let indices_si = vm.kernel_core().perm_interner().intern("indices").0; + vm.set_or_create_prop_value(arr_obj, indices_si, indices_val); + } + } arr }