From c81e9f89d9e4388f36a4427bbaa95eced659d2ce Mon Sep 17 00:00:00 2001 From: nsfisis Date: Mon, 17 Aug 2026 06:03:41 +0900 Subject: refactor(preg): split the PREG_UNMATCHED_AS_NULL preg_* by flag preg_match2() and preg_match_all_offset_capture() each become a pair over a shared private impl, and their flags arguments are gone: no caller passed anything but 0 or PREG_UNMATCHED_AS_NULL. Preg::match5()/is_match5() lose their own flags argument for the same reason -- both call sites passed 0, and the value had nowhere left to go -- so they are renumbered to match4()/is_match4(). PREG_UNMATCHED_AS_NULL itself is now unreferenced. Co-Authored-By: Claude Opus 5 (1M context) --- .../src/php_file_cleaner.rs | 5 +-- crates/shirabe-pcre/src/preg.rs | 32 +++++--------- crates/shirabe-php-shim/src/preg.rs | 51 +++++++++++++++++----- .../src/formatter/output_formatter.rs | 1 - .../src/input/string_input.rs | 5 +-- crates/shirabe/src/console/application.rs | 1 - 6 files changed, 56 insertions(+), 39 deletions(-) (limited to 'crates') diff --git a/crates/shirabe-class-map-generator/src/php_file_cleaner.rs b/crates/shirabe-class-map-generator/src/php_file_cleaner.rs index eb2b84e4..c5952389 100644 --- a/crates/shirabe-class-map-generator/src/php_file_cleaner.rs +++ b/crates/shirabe-class-map-generator/src/php_file_cleaner.rs @@ -145,11 +145,10 @@ impl PhpFileCleaner { if end <= self.len && self.contents[self.index..end] == entry.name { let offset = if self.index > 0 { self.index - 1 } else { 0 }; let mut r#match: IndexMap = IndexMap::new(); - if Preg::is_match5( + if Preg::is_match4( &entry.pattern, &self.contents, Some(&mut r#match), - 0, offset, ) { return clean @@ -294,6 +293,6 @@ impl PhpFileCleaner { } fn r#match(&self, regex: &str, r#match: Option<&mut IndexMap>) -> bool { - Preg::is_match5(regex, &self.contents, r#match, 0, self.index) + Preg::is_match4(regex, &self.contents, r#match, self.index) } } diff --git a/crates/shirabe-pcre/src/preg.rs b/crates/shirabe-pcre/src/preg.rs index fb71c63b..0448f81a 100644 --- a/crates/shirabe-pcre/src/preg.rs +++ b/crates/shirabe-pcre/src/preg.rs @@ -14,8 +14,8 @@ use indexmap::IndexMap; pub use shirabe_php_shim::CaptureKey; use shirabe_php_shim::{ - PREG_UNMATCHED_AS_NULL, PregPattern, preg_grep, preg_match_all_offset_capture, preg_match_all2, - preg_match2, preg_replace_callback, preg_replace2, + PregPattern, preg_grep, preg_match_all_offset_capture_unmatched_as_null, preg_match_all2, + preg_match2, preg_match2_unmatched_as_null, preg_replace_callback, preg_replace2, }; #[derive(Debug)] @@ -27,24 +27,17 @@ impl Preg { subject: &str, matches: Option<&mut IndexMap>, ) -> bool { - Self::match5(pattern, subject, matches, 0, 0) + Self::match4(pattern, subject, matches, 0) } - pub fn match5( + pub fn match4( pattern: impl PregPattern, subject: &str, matches: Option<&mut IndexMap>, - flags: i64, offset: usize, ) -> bool { let mut internal: IndexMap> = IndexMap::new(); - let result = preg_match2( - pattern, - subject, - &mut internal, - flags | PREG_UNMATCHED_AS_NULL, - offset, - ); + let result = preg_match2_unmatched_as_null(pattern, subject, &mut internal, offset); if let Some(out) = matches { *out = drop_null_matches(internal); @@ -73,7 +66,7 @@ impl Preg { ) -> usize { let mut internal: IndexMap, i64)>> = IndexMap::new(); let result = - preg_match_all_offset_capture(pattern, subject, &mut internal, PREG_UNMATCHED_AS_NULL); + preg_match_all_offset_capture_unmatched_as_null(pattern, subject, &mut internal); if let Some(out) = matches { *out = internal; @@ -125,7 +118,7 @@ impl Preg { } pub fn is_match(pattern: impl PregPattern, subject: &str) -> bool { - Self::match5(pattern, subject, None, 0, 0) + Self::match4(pattern, subject, None, 0) } pub fn is_match3( @@ -133,17 +126,16 @@ impl Preg { subject: &str, matches: Option<&mut IndexMap>, ) -> bool { - Self::match5(pattern, subject, matches, 0, 0) + Self::match4(pattern, subject, matches, 0) } - pub fn is_match5( + pub fn is_match4( pattern: impl PregPattern, subject: &str, matches: Option<&mut IndexMap>, - flags: i64, offset: usize, ) -> bool { - Self::match5(pattern, subject, matches, flags, offset) + Self::match4(pattern, subject, matches, offset) } pub fn is_match_named( @@ -152,7 +144,7 @@ impl Preg { matches: &mut IndexMap, ) -> bool { let mut internal: IndexMap> = IndexMap::new(); - let result = preg_match2(pattern, subject, &mut internal, PREG_UNMATCHED_AS_NULL, 0); + let result = preg_match2_unmatched_as_null(pattern, subject, &mut internal, 0); matches.clear(); for (key, value) in internal { @@ -171,7 +163,7 @@ impl Preg { // Classic preg_match semantics (no PREG_UNMATCHED_AS_NULL): trailing // unmatched groups are truncated, interior unmatched groups become "". let mut internal: IndexMap> = IndexMap::new(); - let result = preg_match2(pattern, subject, &mut internal, 0, 0); + let result = preg_match2(pattern, subject, &mut internal, 0); if !result { return None; diff --git a/crates/shirabe-php-shim/src/preg.rs b/crates/shirabe-php-shim/src/preg.rs index 1156129a..0d6a3231 100644 --- a/crates/shirabe-php-shim/src/preg.rs +++ b/crates/shirabe-php-shim/src/preg.rs @@ -1,8 +1,6 @@ use indexmap::IndexMap; use std::sync::{Arc, LazyLock, Mutex}; -pub const PREG_UNMATCHED_AS_NULL: i64 = 512; - #[derive(Debug, Clone, PartialOrd, Ord, PartialEq, Eq, Hash)] pub enum CaptureKey { ByIndex(usize), @@ -51,18 +49,35 @@ pub fn preg_match( } } -// Returns whether the pattern matched. Unmatched groups are reported as None -// (PREG_UNMATCHED_AS_NULL). +// Returns whether the pattern matched, reporting the groups as single_match_map() does. pub fn preg_match2( pattern: impl PregPattern, subject: &str, matches: &mut indexmap::IndexMap>, - flags: i64, offset: usize, +) -> bool { + preg_match2_impl(pattern, subject, matches, offset, false) +} + +// PREG_UNMATCHED_AS_NULL counterpart of preg_match2(). +pub fn preg_match2_unmatched_as_null( + pattern: impl PregPattern, + subject: &str, + matches: &mut indexmap::IndexMap>, + offset: usize, +) -> bool { + preg_match2_impl(pattern, subject, matches, offset, true) +} + +fn preg_match2_impl( + pattern: impl PregPattern, + subject: &str, + matches: &mut indexmap::IndexMap>, + offset: usize, + unmatched_as_null: bool, ) -> bool { let __resolved = pattern.resolve(); let (re, anchored) = __resolved.parts(); - let unmatched_as_null = flags & PREG_UNMATCHED_AS_NULL != 0; // An anchored (`A`) pattern must match starting exactly at `offset`; the `regex` crate cannot // anchor a `captures_at` search, so search the sub-slice beginning at `offset` and require the // match to start at its head. @@ -155,15 +170,33 @@ pub fn preg_match_all_set_order( count } +// A non-participating group is reported at offset -1, holding "". pub fn preg_match_all_offset_capture( pattern: impl PregPattern, subject: &str, matches: &mut indexmap::IndexMap, i64)>>, - flags: i64, +) -> usize { + preg_match_all_offset_capture_impl(pattern, subject, matches, false) +} + +// PREG_UNMATCHED_AS_NULL counterpart of preg_match_all_offset_capture(): a +// non-participating group holds null instead of "". +pub fn preg_match_all_offset_capture_unmatched_as_null( + pattern: impl PregPattern, + subject: &str, + matches: &mut indexmap::IndexMap, i64)>>, +) -> usize { + preg_match_all_offset_capture_impl(pattern, subject, matches, true) +} + +fn preg_match_all_offset_capture_impl( + pattern: impl PregPattern, + subject: &str, + matches: &mut indexmap::IndexMap, i64)>>, + unmatched_as_null: bool, ) -> 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(); @@ -172,8 +205,6 @@ pub fn preg_match_all_offset_capture( 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 if unmatched_as_null => (None, -1), diff --git a/crates/shirabe-symfony-console/src/formatter/output_formatter.rs b/crates/shirabe-symfony-console/src/formatter/output_formatter.rs index 6ce96875..1a3de217 100644 --- a/crates/shirabe-symfony-console/src/formatter/output_formatter.rs +++ b/crates/shirabe-symfony-console/src/formatter/output_formatter.rs @@ -296,7 +296,6 @@ impl WrappableOutputFormatterInterface for OutputFormatter { format!("#<(({open_tag_regex}) | /({close_tag_regex})?)>#ix"), message, &mut matches, - 0, ); let full_matches = matches .get(&CaptureKey::ByIndex(0)) diff --git a/crates/shirabe-symfony-console/src/input/string_input.rs b/crates/shirabe-symfony-console/src/input/string_input.rs index 1ec6e853..d6794957 100644 --- a/crates/shirabe-symfony-console/src/input/string_input.rs +++ b/crates/shirabe-symfony-console/src/input/string_input.rs @@ -58,7 +58,7 @@ impl StringInput { } let mut m: IndexMap> = IndexMap::new(); - if preg_match2(php_regex!(r"/\s+/A"), input, &mut m, 0, cursor as usize) { + if preg_match2(php_regex!(r"/\s+/A"), input, &mut m, cursor as usize) { if token.is_some() { tokens.push(token.take().unwrap()); } @@ -68,7 +68,6 @@ impl StringInput { format!(r#"/([^="'\s]+?)(=?)({}+)/A"#, Self::REGEX_QUOTED_STRING), input, &mut m, - 0, cursor as usize, ) { let inner = shirabe_php_shim::substr( @@ -91,7 +90,6 @@ impl StringInput { format!(r"/{}/A", Self::REGEX_QUOTED_STRING), input, &mut m, - 0, cursor as usize, ) { token = Some(format!( @@ -109,7 +107,6 @@ impl StringInput { format!(r"/{}/A", Self::REGEX_UNQUOTED_STRING), input, &mut m, - 0, cursor as usize, ) { token = Some(format!( diff --git a/crates/shirabe/src/console/application.rs b/crates/shirabe/src/console/application.rs index b55b9e68..e818823e 100644 --- a/crates/shirabe/src/console/application.rs +++ b/crates/shirabe/src/console/application.rs @@ -1796,7 +1796,6 @@ impl Application { php_regex!(r"/.{1,10000}/u"), &utf8_string, &mut m, - 0, offset as usize, ) { let m0 = m[&shirabe_php_shim::CaptureKey::ByIndex(0)] -- cgit v1.3.1-4-g156e