From bc79875f9a0cbacce94722fede76228b06486269 Mon Sep 17 00:00:00 2001 From: nsfisis Date: Mon, 17 Aug 2026 04:45:53 +0900 Subject: refactor(php-shim): split single_match_map() by unmatched_as_null Each branch of the flag is now its own function. The PREG_UNMATCHED_AS_NULL variant needs neither the trailing-group truncation nor the empty-string fallback, so it loses the break condition and the last_participating scan. Co-Authored-By: Claude Opus 5 (1M context) --- crates/shirabe-php-shim/src/preg.rs | 41 +++++++++++++++++++++++++------------ 1 file changed, 28 insertions(+), 13 deletions(-) (limited to 'crates/shirabe-php-shim/src') diff --git a/crates/shirabe-php-shim/src/preg.rs b/crates/shirabe-php-shim/src/preg.rs index 62ad464c..1156129a 100644 --- a/crates/shirabe-php-shim/src/preg.rs +++ b/crates/shirabe-php-shim/src/preg.rs @@ -76,7 +76,11 @@ pub fn preg_match2( matches.clear(); if let Some(caps) = &caps { let names: Vec> = re.capture_names().collect(); - *matches = single_match_map(caps, &names, unmatched_as_null); + *matches = if unmatched_as_null { + single_match_map_unmatched_as_null(caps, &names) + } else { + single_match_map(caps, &names) + }; } caps.is_some() @@ -291,7 +295,7 @@ where for caps in re.captures_iter(subject) { let m = caps.get(0).unwrap(); out.extend_from_slice(&subject.as_bytes()[last..m.start()]); - let map = single_match_map(&caps, &names, false); + let map = single_match_map(&caps, &names); out.extend_from_slice(callback(&map)?.as_bytes()); last = m.end(); } @@ -578,13 +582,11 @@ fn php_match_row(caps: ®ex::Captures) -> Vec { } // Builds a single match's `$matches` map with both named and numbered keys -// (the named key precedes its number). With PREG_UNMATCHED_AS_NULL, every group -// is present and non-participating ones are None; otherwise classic semantics -// apply: trailing unmatched groups are dropped and interior ones become "". +// (the named key precedes its number). Trailing unmatched groups are dropped +// and interior ones become "". fn single_match_map( caps: ®ex::Captures, names: &[Option<&str>], - unmatched_as_null: bool, ) -> indexmap::IndexMap> { let mut out = indexmap::IndexMap::new(); let group_count = caps.len(); @@ -592,18 +594,31 @@ fn single_match_map( for i in 0..group_count { let m = caps.get(i); - if !unmatched_as_null - && m.is_none() + if m.is_none() && let Some(last) = last_participating && i > last { break; } - let value = if unmatched_as_null { - m.map(|m| m.as_str().to_string()) - } else { - Some(m.map(|m| m.as_str().to_string()).unwrap_or_default()) - }; + let value = Some(m.map(|m| m.as_str().to_string()).unwrap_or_default()); + if let Some(Some(name)) = names.get(i) { + out.insert(CaptureKey::ByName((*name).to_string()), value.clone()); + } + out.insert(CaptureKey::ByIndex(i), value); + } + out +} + +// PREG_UNMATCHED_AS_NULL counterpart of single_match_map(): every group is +// present and non-participating ones are None. +fn single_match_map_unmatched_as_null( + caps: ®ex::Captures, + names: &[Option<&str>], +) -> indexmap::IndexMap> { + let mut out = indexmap::IndexMap::new(); + + for i in 0..caps.len() { + let value = caps.get(i).map(|m| m.as_str().to_string()); if let Some(Some(name)) = names.get(i) { out.insert(CaptureKey::ByName((*name).to_string()), value.clone()); } -- cgit v1.3.1-4-g156e