aboutsummaryrefslogtreecommitdiffhomepage
path: root/crates/shirabe-php-shim/src/preg.rs
diff options
context:
space:
mode:
authornsfisis <nsfisis@gmail.com>2026-08-18 01:57:02 +0900
committernsfisis <nsfisis@gmail.com>2026-08-18 01:57:02 +0900
commit34c74255d781ad0a0bf7cc5ad4ec1761bef61e04 (patch)
tree6a4c9e11a0d8e98dccf72a5857db73e74722489b /crates/shirabe-php-shim/src/preg.rs
parent844097edf44bf1424d28e2d5fbefda90c1c8f46c (diff)
downloadphp-shirabe-34c74255d781ad0a0bf7cc5ad4ec1761bef61e04.tar.gz
php-shirabe-34c74255d781ad0a0bf7cc5ad4ec1761bef61e04.tar.zst
php-shirabe-34c74255d781ad0a0bf7cc5ad4ec1761bef61e04.zip
refactor(pcre): drop the two bespoke isMatch variants
is_match_named and is_match_with_indexed_captures reshaped a match into a name-keyed map or a number-positioned vec, each allocating a String per group up front for callers that then read one or two of them. Every one of the eleven call sites ports a plain Preg::isMatch in PHP, so they now call is_match3 and reach for the group they want through get(&CaptureKey::ByIndex(N)) / get(&CaptureKey::ByName(..)), the same way the rest of the tree already reads a match. Falling out of that: PregNamedGroups existed only to type the first variant; PregMatches::iter() only to build both; and PregMatches::pattern only to give iter() the capture names. PregMatches is now a plain wrapper over regex::Captures, so preg_replace_callback no longer clones the resolved pattern for every match, and preg_match_map! is internal to the shim again. SvnDriver::get_file_content and get_change_date recover the flat `isMatch(..) && $match[2] !== null` condition the PHP has, which the vec shape had forced into a nested if. 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.rs54
1 files changed, 18 insertions, 36 deletions
diff --git a/crates/shirabe-php-shim/src/preg.rs b/crates/shirabe-php-shim/src/preg.rs
index 74b7f12d..2686bb59 100644
--- a/crates/shirabe-php-shim/src/preg.rs
+++ b/crates/shirabe-php-shim/src/preg.rs
@@ -8,8 +8,7 @@ pub enum CaptureKey {
}
/// Defines a newtype over `IndexMap` for one of the `$matches` shapes the `preg_*` functions fill
-/// in. Also used by `shirabe_pcre` for the shapes `Composer\Pcre\Preg` adds on top.
-#[macro_export]
+/// in.
macro_rules! preg_match_map {
($(#[$attr:meta])* $vis:vis struct $name:ident($key:ty => $value:ty);) => {
$(#[$attr])*
@@ -58,18 +57,17 @@ macro_rules! preg_match_map {
};
}
-/// A single match's `$matches`: the `regex::Captures` the search produced, held alongside the
-/// pattern that produced it so groups can be read by both their named and their numbered form.
-/// `'h` is the lifetime of the searched subject, which the group values borrow from.
+/// A single match's `$matches`: the `regex::Captures` the search produced, read by either the named
+/// or the numbered form of a capture group. `'h` is the lifetime of the searched subject, which the
+/// group values borrow from.
#[derive(Debug)]
pub struct PregMatches<'h> {
- pattern: ResolvedPattern,
caps: regex::Captures<'h>,
}
impl<'h> PregMatches<'h> {
- fn new(pattern: ResolvedPattern, caps: regex::Captures<'h>) -> Self {
- Self { pattern, caps }
+ fn new(caps: regex::Captures<'h>) -> Self {
+ Self { caps }
}
/// The value of the group `key` names, or `None` if that group did not participate in the
@@ -82,20 +80,6 @@ impl<'h> PregMatches<'h> {
};
group.map(|group| group.as_str())
}
-
- /// Every capture group under both its named and its numbered key (the name preceding its
- /// number), in the order PHP fills `$matches` in.
- pub fn iter(&self) -> impl Iterator<Item = (CaptureKey, Option<&'h str>)> + '_ {
- let (re, _anchored) = self.pattern.parts();
- re.capture_names()
- .enumerate()
- .flat_map(move |(index, name)| {
- let value = self.caps.get(index).map(|group| group.as_str());
- name.map(|name| (CaptureKey::ByName(name.to_string()), value))
- .into_iter()
- .chain(std::iter::once((CaptureKey::ByIndex(index), value)))
- })
- }
}
preg_match_map! {
@@ -169,20 +153,18 @@ pub fn preg_match2<'h>(
offset: usize,
) -> Option<PregMatches<'h>> {
let __resolved = pattern.resolve();
- let caps = {
- let (re, anchored) = __resolved.parts();
- // 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.
- if anchored {
- re.captures(&subject[offset..])
- .filter(|c| c.get(0).map(|m| m.start()) == Some(0))
- } else {
- re.captures_at(subject, offset)
- }
+ let (re, anchored) = __resolved.parts();
+ // 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.
+ let caps = if anchored {
+ re.captures(&subject[offset..])
+ .filter(|c| c.get(0).map(|m| m.start()) == Some(0))
+ } else {
+ re.captures_at(subject, offset)
}?;
- Some(PregMatches::new(__resolved, caps))
+ Some(PregMatches::new(caps))
}
// PREG_PATTERN_ORDER: the outer vec is indexed by capture group, the inner by
@@ -381,7 +363,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 matches = PregMatches::new(__resolved.clone(), caps);
+ let matches = PregMatches::new(caps);
out.extend_from_slice(callback(&matches)?.as_bytes());
last = m.end();
}
@@ -503,7 +485,7 @@ fn translate_php_pattern(pattern: &str) -> anyhow::Result<(String, bool)> {
/// `LazyLock<Regex>`) rather than an owned `regex::Regex` — `regex::Regex::clone()` does not share
/// the underlying meta engine's search-cache pool, so producing a fresh owned clone here would pay
/// a ~10us per-call cache warmup cost regardless of which path produced it (measured).
-#[derive(Debug, Clone)]
+#[derive(Debug)]
pub enum ResolvedPattern {
Cached(Arc<(regex::Regex, bool)>),
Static(&'static regex::Regex, bool),