diff options
| author | nsfisis <nsfisis@gmail.com> | 2026-08-17 07:36:34 +0900 |
|---|---|---|
| committer | nsfisis <nsfisis@gmail.com> | 2026-08-17 07:36:34 +0900 |
| commit | 9fd6aecad27240ccedab4487f6c157914142ca47 (patch) | |
| tree | 3819360771b9eb9298315e26604cafabeae84e04 /crates/shirabe-php-shim/src/preg.rs | |
| parent | 6b4ce98f20b3cfc14f1c955565a8acd9abcc16a2 (diff) | |
| download | php-shirabe-9fd6aecad27240ccedab4487f6c157914142ca47.tar.gz php-shirabe-9fd6aecad27240ccedab4487f6c157914142ca47.tar.zst php-shirabe-9fd6aecad27240ccedab4487f6c157914142ca47.zip | |
refactor(preg): return the preg_* $matches instead of filling an out-param
PHP fills `$matches` through a by-ref parameter, which the port mirrored
with a `&mut` out-param plus a bool or count return. Every caller then
had to declare an empty binding one line ahead of the call, and nothing
in the type said the binding is only meaningful when the call succeeded.
Return the matches instead: preg_match() and preg_match2() hand back an
Option, and the three preg_match_all* functions hand back the collection
they used to fill.
The occurrence count the two map-shaped preg_match_all* functions used
to return is the length of any one of the map's columns, so it is not
lost -- Preg::match_all() and friends derive it via occurrence_count().
preg_replace2() keeps its `count: Option<&mut usize>`: that one is not
derivable from the replaced string, and callers that do not want it pay
nothing for passing None.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Diffstat (limited to 'crates/shirabe-php-shim/src/preg.rs')
| -rw-r--r-- | crates/shirabe-php-shim/src/preg.rs | 91 |
1 files changed, 32 insertions, 59 deletions
diff --git a/crates/shirabe-php-shim/src/preg.rs b/crates/shirabe-php-shim/src/preg.rs index b16ee81d..c2aa0df3 100644 --- a/crates/shirabe-php-shim/src/preg.rs +++ b/crates/shirabe-php-shim/src/preg.rs @@ -117,34 +117,22 @@ pub fn preg_quote(str: &str, delimiter: Option<char>) -> String { out } -// Returns whether the pattern matched; populates matches[0]=full match, matches[1..]=captures. -// Optional groups that did not participate in the match are stored as None. -pub fn preg_match( - pattern: impl PregPattern, - subject: &str, - matches: &mut Vec<Option<String>>, -) -> bool { +// Returns None if the pattern did not match; otherwise index 0 is the full match and 1.. the +// capture groups. A group that did not participate is None. +pub fn preg_match(pattern: impl PregPattern, subject: &str) -> Option<Vec<Option<String>>> { let __resolved = pattern.resolve(); let (re, _anchored) = __resolved.parts(); - matches.clear(); - match re.captures(subject) { - Some(caps) => { - for g in 0..caps.len() { - matches.push(caps.get(g).map(|m| m.as_str().to_string())); - } - true - } - None => false, - } + let caps = re.captures(subject)?; + Some( + (0..caps.len()) + .map(|g| caps.get(g).map(|m| m.as_str().to_string())) + .collect(), + ) } -// Returns whether the pattern matched, reporting the groups as single_match_map() does. -pub fn preg_match2( - pattern: impl PregPattern, - subject: &str, - matches: &mut PregMatches, - offset: usize, -) -> bool { +// Returns None if the pattern did not match; otherwise the groups as single_match_map() reports +// them. +pub fn preg_match2(pattern: impl PregPattern, subject: &str, offset: usize) -> Option<PregMatches> { let __resolved = pattern.resolve(); let (re, anchored) = __resolved.parts(); // An anchored (`A`) pattern must match starting exactly at `offset`; the `regex` crate cannot @@ -155,15 +143,10 @@ pub fn preg_match2( .filter(|c| c.get(0).map(|m| m.start()) == Some(0)) } else { re.captures_at(subject, offset) - }; + }?; - matches.clear(); - if let Some(caps) = &caps { - let names: Vec<Option<&str>> = re.capture_names().collect(); - *matches = single_match_map(caps, &names); - } - - caps.is_some() + let names: Vec<Option<&str>> = re.capture_names().collect(); + Some(single_match_map(&caps, &names)) } // PREG_PATTERN_ORDER: the outer vec is indexed by capture group, the inner by @@ -181,11 +164,9 @@ pub fn preg_match_all(pattern: impl PregPattern, subject: &str) -> Vec<Vec<Optio groups } -pub fn preg_match_all2( - pattern: impl PregPattern, - subject: &str, - matches: &mut PregMatchesAll, -) -> usize { +// The number of occurrences the caller would get from PHP's return value is the length of any +// one column, `matches[&CaptureKey::ByIndex(0)].len()`. +pub fn preg_match_all2(pattern: impl PregPattern, subject: &str) -> PregMatchesAll { let __resolved = pattern.resolve(); let (re, _anchored) = __resolved.parts(); let group_count = re.captures_len(); @@ -193,16 +174,14 @@ pub fn preg_match_all2( // PREG_PATTERN_ORDER: one column per group, one row per match occurrence. let mut groups: Vec<Vec<Option<String>>> = vec![Vec::new(); group_count]; - let mut count = 0; for caps in re.captures_iter(subject) { - count += 1; for (g, column) in groups.iter_mut().enumerate() { let value = caps.get(g).map(|m| m.as_str().to_string()); column.push(value); } } - matches.clear(); + let mut matches = PregMatchesAll::new(); for (g, column) in groups.into_iter().enumerate() { if let Some(Some(name)) = names.get(g) { matches.insert(CaptureKey::ByName((*name).to_string()), column.clone()); @@ -210,7 +189,7 @@ pub fn preg_match_all2( matches.insert(CaptureKey::ByIndex(g), column); } - count + matches } // PREG_SET_ORDER: the outer vec is indexed by match occurrence, the inner by @@ -219,38 +198,32 @@ pub fn preg_match_all2( pub fn preg_match_all_set_order( pattern: impl PregPattern, subject: &str, - matches: &mut Vec<Vec<Option<String>>>, -) -> usize { +) -> Vec<Vec<Option<String>>> { let __resolved = pattern.resolve(); let (re, _anchored) = __resolved.parts(); - let mut rows: Vec<Vec<Option<String>>> = Vec::new(); - for caps in re.captures_iter(subject) { - rows.push( + re.captures_iter(subject) + .map(|caps| { (0..caps.len()) .map(|g| caps.get(g).map(|m| m.as_str().to_string())) - .collect(), - ); - } - let count = rows.len(); - *matches = rows; - count + .collect() + }) + .collect() } -// A non-participating group is reported as None, at offset -1. +// A non-participating group is reported as None, at offset -1. The number of occurrences the +// caller would get from PHP's return value is the length of any one column, +// `matches[&CaptureKey::ByIndex(0)].len()`. pub fn preg_match_all_offset_capture( pattern: impl PregPattern, subject: &str, - matches: &mut PregMatchesAllWithOffsets, -) -> usize { +) -> PregMatchesAllWithOffsets { let __resolved = pattern.resolve(); let (re, _anchored) = __resolved.parts(); let group_count = re.captures_len(); let names: Vec<Option<&str>> = re.capture_names().collect(); let mut groups: Vec<Vec<(Option<String>, i64)>> = vec![Vec::new(); group_count]; - let mut count = 0; for caps in re.captures_iter(subject) { - count += 1; for (g, column) in groups.iter_mut().enumerate() { let entry = match caps.get(g) { Some(m) => (Some(m.as_str().to_string()), m.start() as i64), @@ -260,7 +233,7 @@ pub fn preg_match_all_offset_capture( } } - matches.clear(); + let mut matches = PregMatchesAllWithOffsets::new(); for (g, column) in groups.into_iter().enumerate() { if let Some(Some(name)) = names.get(g) { matches.insert(CaptureKey::ByName((*name).to_string()), column.clone()); @@ -268,7 +241,7 @@ pub fn preg_match_all_offset_capture( matches.insert(CaptureKey::ByIndex(g), column); } - count + matches } pub fn preg_grep<T: AsRef<str>>( |
