From abffae07ed350ebb1de98edd2ae246ccddfa6085 Mon Sep 17 00:00:00 2001 From: nsfisis Date: Mon, 17 Aug 2026 03:23:52 +0900 Subject: refactor(preg): merge preg_match_all_offset_capture2() into its sibling The two functions ran the same search and differed only in how they reported a non-participating group: one as ("", 0) in a bespoke struct, the other as (null, -1) in a capture-key map. Neither shape covered both callers, because PHP reaches preg_match_all() with different flags from each: Preg::matchAllWithOffsets() always ORs in PREG_UNMATCHED_AS_NULL, while OutputFormatter::formatAndWrap() passes PREG_OFFSET_CAPTURE alone. Keep the capture-key map, which also exposes named groups, and take the flags that decide between null and "" for an unmatched group. The offset is -1 either way, so the ("", 0) approximation is gone. Co-Authored-By: Claude Opus 5 (1M context) --- crates/shirabe-php-shim/src/preg.rs | 46 +++++-------------------------------- 1 file changed, 6 insertions(+), 40 deletions(-) (limited to 'crates/shirabe-php-shim/src/preg.rs') diff --git a/crates/shirabe-php-shim/src/preg.rs b/crates/shirabe-php-shim/src/preg.rs index 40eb25fb..c762cb95 100644 --- a/crates/shirabe-php-shim/src/preg.rs +++ b/crates/shirabe-php-shim/src/preg.rs @@ -15,17 +15,6 @@ pub enum CaptureKey { ByName(String), } -#[derive(Debug, Default)] -pub struct PregOffsetCaptureMatches { - groups: Vec>, -} - -impl PregOffsetCaptureMatches { - pub fn group(&self, i: usize) -> &[(String, usize)] { - &self.groups[i] - } -} - pub fn preg_quote(str: &str, delimiter: Option) -> String { // Regex pattern compatibility: // PHP's preg_quote escapes `<` and `>` (PCRE treats `\<`/`\>` as literals), but the `regex` @@ -169,40 +158,14 @@ pub fn preg_match_all_set_order( } pub fn preg_match_all_offset_capture( - pattern: impl PregPattern, - subject: &str, - matches: &mut PregOffsetCaptureMatches, -) -> usize { - let __resolved = pattern.resolve(); - let (re, _anchored) = __resolved.parts(); - let group_count = re.captures_len(); - matches.groups = vec![Vec::new(); group_count]; - - let mut count = 0; - for caps in re.captures_iter(subject) { - count += 1; - for g in 0..group_count { - // PHP stores ["", -1] for non-participating groups under - // PREG_OFFSET_CAPTURE; the unsigned offset here approximates -1 as 0, - // which callers must not rely on for absent groups. - let entry = caps - .get(g) - .map(|m| (m.as_str().to_string(), m.start())) - .unwrap_or_else(|| (String::new(), 0)); - matches.groups[g].push(entry); - } - } - - count -} - -pub fn preg_match_all_offset_capture2( pattern: impl PregPattern, subject: &str, matches: &mut indexmap::IndexMap, i64)>>, + flags: i64, ) -> usize { let __resolved = pattern.resolve(); let (re, _anchored) = __resolved.parts(); + let unmatched_as_null = flags & PREG_UNMATCHED_AS_NULL != 0; let group_count = re.captures_len(); let names: Vec> = re.capture_names().collect(); @@ -211,9 +174,12 @@ pub fn preg_match_all_offset_capture2( for caps in re.captures_iter(subject) { count += 1; for (g, column) in groups.iter_mut().enumerate() { + // A non-participating group is reported at offset -1, holding "" or, + // with PREG_UNMATCHED_AS_NULL, null. let entry = match caps.get(g) { Some(m) => (Some(m.as_str().to_string()), m.start() as i64), - None => (None, -1), + None if unmatched_as_null => (None, -1), + None => (Some(String::new()), -1), }; column.push(entry); } -- cgit v1.3.1-4-g156e