diff options
| author | nsfisis <nsfisis@gmail.com> | 2026-08-17 03:23:52 +0900 |
|---|---|---|
| committer | nsfisis <nsfisis@gmail.com> | 2026-08-17 03:23:52 +0900 |
| commit | abffae07ed350ebb1de98edd2ae246ccddfa6085 (patch) | |
| tree | 6c917cc08e39ad95078505951bdea4b9ae538630 /crates | |
| parent | 8b57e90f5c563bbc1a6060d9f5e94b5d3cd91324 (diff) | |
| download | php-shirabe-abffae07ed350ebb1de98edd2ae246ccddfa6085.tar.gz php-shirabe-abffae07ed350ebb1de98edd2ae246ccddfa6085.tar.zst php-shirabe-abffae07ed350ebb1de98edd2ae246ccddfa6085.zip | |
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) <noreply@anthropic.com>
Diffstat (limited to 'crates')
| -rw-r--r-- | crates/shirabe-pcre/src/preg.rs | 5 | ||||
| -rw-r--r-- | crates/shirabe-php-shim/src/preg.rs | 46 | ||||
| -rw-r--r-- | crates/shirabe-symfony-console/src/formatter/output_formatter.rs | 33 |
3 files changed, 31 insertions, 53 deletions
diff --git a/crates/shirabe-pcre/src/preg.rs b/crates/shirabe-pcre/src/preg.rs index 822609a9..8fb0c2e8 100644 --- a/crates/shirabe-pcre/src/preg.rs +++ b/crates/shirabe-pcre/src/preg.rs @@ -15,7 +15,7 @@ use indexmap::IndexMap; pub use shirabe_php_shim::CaptureKey; use shirabe_php_shim::{ PREG_OFFSET_CAPTURE, PREG_SET_ORDER, PREG_SPLIT_OFFSET_CAPTURE, PREG_UNMATCHED_AS_NULL, - PregPattern, preg_grep, preg_match_all_offset_capture2, preg_match_all2, preg_match2, + PregPattern, preg_grep, preg_match_all_offset_capture, preg_match_all2, preg_match2, preg_replace_callback, preg_replace2, preg_split2, }; @@ -89,7 +89,8 @@ impl Preg { 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); + let result = + preg_match_all_offset_capture(pattern, subject, &mut internal, PREG_UNMATCHED_AS_NULL); if let Some(out) = matches { *out = internal; 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<Vec<(String, usize)>>, -} - -impl PregOffsetCaptureMatches { - pub fn group(&self, i: usize) -> &[(String, usize)] { - &self.groups[i] - } -} - pub fn preg_quote(str: &str, delimiter: Option<char>) -> String { // Regex pattern compatibility: // PHP's preg_quote escapes `<` and `>` (PCRE treats `\<`/`\>` as literals), but the `regex` @@ -171,38 +160,12 @@ 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<CaptureKey, Vec<(Option<String>, 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<Option<&str>> = 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); } diff --git a/crates/shirabe-symfony-console/src/formatter/output_formatter.rs b/crates/shirabe-symfony-console/src/formatter/output_formatter.rs index 4943e65b..6ce96875 100644 --- a/crates/shirabe-symfony-console/src/formatter/output_formatter.rs +++ b/crates/shirabe-symfony-console/src/formatter/output_formatter.rs @@ -7,8 +7,8 @@ use crate::formatter::output_formatter_style_interface::OutputFormatterStyleInte use crate::formatter::output_formatter_style_stack::OutputFormatterStyleStack; use crate::formatter::wrappable_output_formatter_interface::WrappableOutputFormatterInterface; use shirabe_php_shim::{ - php_regex, preg_match, preg_match_all, preg_match_all_offset_capture, preg_match_all_set_order, - preg_replace, + CaptureKey, php_regex, preg_match, preg_match_all, preg_match_all_offset_capture, + preg_match_all_set_order, preg_replace, }; use shirabe_symfony_string::b; @@ -290,16 +290,24 @@ impl WrappableOutputFormatterInterface for OutputFormatter { let open_tag_regex = "[a-z](?:[^\\\\<>]* | \\\\.)*"; let close_tag_regex = "[a-z][^<>]*"; let mut current_line_length: i64 = 0; - let mut matches: shirabe_php_shim::PregOffsetCaptureMatches = Default::default(); + let mut matches: indexmap::IndexMap<CaptureKey, Vec<(Option<String>, i64)>> = + indexmap::IndexMap::new(); preg_match_all_offset_capture( format!("#<(({open_tag_regex}) | /({close_tag_regex})?)>#ix"), message, &mut matches, + 0, ); - let count = matches.group(0).len(); - for i in 0..count { - let (text, pos) = matches.group(0)[i].clone(); - let pos = pos as i64; + let full_matches = matches + .get(&CaptureKey::ByIndex(0)) + .cloned() + .unwrap_or_default(); + for (i, match_) in full_matches.iter().enumerate() { + let pos = match_.1; + let text = match_ + .0 + .clone() + .expect("group 0 participates whenever the pattern matches"); if pos != 0 && shirabe_php_shim::byte_at(message, (pos - 1) as usize) == b'\\' { continue; @@ -315,12 +323,15 @@ impl WrappableOutputFormatterInterface for OutputFormatter { // opening tag? let open = shirabe_php_shim::byte_at(&text, 1) != b'/'; let tag = if open { - matches.group(1)[i].0.clone() + matches[&CaptureKey::ByIndex(1)][i] + .0 + .clone() + .expect("group 1 participates whenever the pattern matches") } else { matches - .group(3) - .get(i) - .map(|m| m.0.clone()) + .get(&CaptureKey::ByIndex(3)) + .and_then(|group| group.get(i)) + .and_then(|m| m.0.clone()) .unwrap_or_default() }; |
