diff options
| author | nsfisis <nsfisis@gmail.com> | 2026-08-17 03:09:04 +0900 |
|---|---|---|
| committer | nsfisis <nsfisis@gmail.com> | 2026-08-17 03:09:04 +0900 |
| commit | 8b57e90f5c563bbc1a6060d9f5e94b5d3cd91324 (patch) | |
| tree | a8052aebf264e3cf31c25e2bc5cb612dadde485b /crates/shirabe-pcre/src | |
| parent | fcc6ee5a13cd2e2b27fff66dcfc7e9e1f062a653 (diff) | |
| download | php-shirabe-8b57e90f5c563bbc1a6060d9f5e94b5d3cd91324.tar.gz php-shirabe-8b57e90f5c563bbc1a6060d9f5e94b5d3cd91324.tar.zst php-shirabe-8b57e90f5c563bbc1a6060d9f5e94b5d3cd91324.zip | |
fix(pcre): preserve unmatched groups in Preg::match_all*()
PHP's Preg::matchAll() and matchAllWithOffsets() always set
PREG_UNMATCHED_AS_NULL, so a non-participating group is `null` and its
offset is -1. The Rust wrappers collapsed those to "" and 0, so callers
could not tell a group that did not participate from one that matched an
empty string at offset 0, and the offset value matched no PHP mode at
all.
Hand the shim's representation through unchanged and let each caller
mirror what the PHP original does with it: `isset()` and `(string)`
casts stay lenient, while `assert(is_string(...))` and the
*StrictGroups() variants become `expect()`.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Diffstat (limited to 'crates/shirabe-pcre/src')
| -rw-r--r-- | crates/shirabe-pcre/src/preg.rs | 50 |
1 files changed, 7 insertions, 43 deletions
diff --git a/crates/shirabe-pcre/src/preg.rs b/crates/shirabe-pcre/src/preg.rs index a761608e..822609a9 100644 --- a/crates/shirabe-pcre/src/preg.rs +++ b/crates/shirabe-pcre/src/preg.rs @@ -63,7 +63,7 @@ impl Preg { pub fn match_all3( pattern: impl PregPattern, subject: &str, - matches: Option<&mut IndexMap<CaptureKey, Vec<String>>>, + matches: Option<&mut IndexMap<CaptureKey, Vec<Option<String>>>>, ) -> usize { Self::match_all5(pattern, subject, matches) } @@ -71,13 +71,13 @@ impl Preg { fn match_all5( pattern: impl PregPattern, subject: &str, - matches: Option<&mut IndexMap<CaptureKey, Vec<String>>>, + matches: Option<&mut IndexMap<CaptureKey, Vec<Option<String>>>>, ) -> usize { let mut internal: IndexMap<CaptureKey, Vec<Option<String>>> = IndexMap::new(); let result = preg_match_all2(pattern, subject, &mut internal); if let Some(out) = matches { - *out = null_to_empty_match_all(internal); + *out = internal; } result @@ -86,13 +86,13 @@ impl Preg { fn match_all_with_offsets5( pattern: impl PregPattern, subject: &str, - matches: Option<&mut IndexMap<CaptureKey, Vec<(String, usize)>>>, + matches: Option<&mut IndexMap<CaptureKey, Vec<(Option<String>, i64)>>>, ) -> usize { let mut internal: IndexMap<CaptureKey, Vec<(Option<String>, i64)>> = IndexMap::new(); let result = preg_match_all_offset_capture2(pattern, subject, &mut internal); if let Some(out) = matches { - *out = null_to_empty_offset_match_all(internal); + *out = internal; } result @@ -243,7 +243,7 @@ impl Preg { pub fn is_match_all3( pattern: impl PregPattern, subject: &str, - matches: Option<&mut IndexMap<CaptureKey, Vec<String>>>, + matches: Option<&mut IndexMap<CaptureKey, Vec<Option<String>>>>, ) -> bool { Self::match_all5(pattern, subject, matches) > 0 } @@ -251,7 +251,7 @@ impl Preg { pub fn is_match_all_with_offsets3( pattern: impl PregPattern, subject: &str, - matches: Option<&mut IndexMap<CaptureKey, Vec<(String, usize)>>>, + matches: Option<&mut IndexMap<CaptureKey, Vec<(Option<String>, i64)>>>, ) -> bool { Self::match_all_with_offsets5(pattern, subject, matches) > 0 } @@ -291,39 +291,3 @@ fn drop_null_matches_ref( .filter_map(|(key, value)| value.clone().map(|value| (key.clone(), value))) .collect() } - -// In the `Vec<String>`-valued maps a per-iteration `null` cannot be stored, so -// unmatched groups collapse to "" (the classic non-PREG_UNMATCHED_AS_NULL form). -fn null_to_empty_match_all( - matches: IndexMap<CaptureKey, Vec<Option<String>>>, -) -> IndexMap<CaptureKey, Vec<String>> { - matches - .into_iter() - .map(|(key, values)| { - ( - key, - values - .into_iter() - .map(|value| value.unwrap_or_default()) - .collect(), - ) - }) - .collect() -} - -fn null_to_empty_offset_match_all( - matches: IndexMap<CaptureKey, Vec<(Option<String>, i64)>>, -) -> IndexMap<CaptureKey, Vec<(String, usize)>> { - matches - .into_iter() - .map(|(key, values)| { - ( - key, - values - .into_iter() - .map(|(value, offset)| (value.unwrap_or_default(), offset.max(0) as usize)) - .collect(), - ) - }) - .collect() -} |
