aboutsummaryrefslogtreecommitdiffhomepage
path: root/crates/shirabe-external-packages/src/composer/pcre/preg.rs
diff options
context:
space:
mode:
Diffstat (limited to 'crates/shirabe-external-packages/src/composer/pcre/preg.rs')
-rw-r--r--crates/shirabe-external-packages/src/composer/pcre/preg.rs368
1 files changed, 15 insertions, 353 deletions
diff --git a/crates/shirabe-external-packages/src/composer/pcre/preg.rs b/crates/shirabe-external-packages/src/composer/pcre/preg.rs
index 2f8c46b..f72835d 100644
--- a/crates/shirabe-external-packages/src/composer/pcre/preg.rs
+++ b/crates/shirabe-external-packages/src/composer/pcre/preg.rs
@@ -10,15 +10,11 @@
//! distinguishes participating from non-participating groups.
use indexmap::IndexMap;
+use shirabe_php_shim::{
+ PREG_OFFSET_CAPTURE, PREG_SET_ORDER, PREG_SPLIT_OFFSET_CAPTURE, PREG_UNMATCHED_AS_NULL,
+};
-const PREG_PATTERN_ORDER: i64 = 1;
-const PREG_SET_ORDER: i64 = 2;
-const PREG_OFFSET_CAPTURE: i64 = 256;
-const PREG_UNMATCHED_AS_NULL: i64 = 512;
-const PREG_SPLIT_NO_EMPTY: i64 = 1;
-const PREG_SPLIT_DELIM_CAPTURE: i64 = 2;
-const PREG_SPLIT_OFFSET_CAPTURE: i64 = 4;
-const PREG_GREP_INVERT: i64 = 1;
+pub use shirabe_php_shim::CaptureKey;
#[derive(Debug)]
pub struct Preg;
@@ -42,7 +38,7 @@ impl Preg {
Self::check_offset_capture(flags, "matchWithOffsets");
let mut internal: IndexMap<CaptureKey, Option<String>> = IndexMap::new();
- let result = preg_match(
+ let result = shirabe_php_shim::preg_match2(
pattern,
subject,
Some(&mut internal),
@@ -81,7 +77,7 @@ impl Preg {
Self::check_set_order(flags);
let mut internal: IndexMap<CaptureKey, Vec<Option<String>>> = IndexMap::new();
- let result = preg_match_all(
+ let result = shirabe_php_shim::preg_match_all2(
pattern,
subject,
Some(&mut internal),
@@ -107,7 +103,7 @@ impl Preg {
Self::check_set_order(flags);
let mut internal: IndexMap<CaptureKey, Vec<(Option<String>, i64)>> = IndexMap::new();
- let result = preg_match_all_offset_capture(
+ let result = shirabe_php_shim::preg_match_all_offset_capture2(
pattern,
subject,
Some(&mut internal),
@@ -151,7 +147,8 @@ impl Preg {
// `$subject` is statically a string here, so the is_scalar/is_array
// guards (ARRAY_MSG / INVALID_TYPE_MSG) of the PHP original are
// unreachable and not reproduced.
- preg_replace(pattern, replacement, subject, limit, count).unwrap_or_else(|| invalid_regex())
+ shirabe_php_shim::preg_replace2(pattern, replacement, subject, limit, count)
+ .unwrap_or_else(|| invalid_regex())
}
pub fn replace_callback<F: FnMut(&IndexMap<CaptureKey, String>) -> String>(
@@ -174,7 +171,7 @@ impl Preg {
replacement(&drop_null_matches_ref(internal))
};
- preg_replace_callback(pattern, adapter, subject, limit, count, flags)
+ shirabe_php_shim::preg_replace_callback2(pattern, adapter, subject, limit, count, flags)
.unwrap_or_else(|| invalid_regex())
}
@@ -188,7 +185,8 @@ impl Preg {
"PREG_SPLIT_OFFSET_CAPTURE is not supported as it changes the type of $matches, use splitWithOffsets() instead"
);
- preg_split(pattern, subject, limit, flags).unwrap_or_else(|| invalid_regex())
+ shirabe_php_shim::preg_split2(pattern, subject, limit, flags)
+ .unwrap_or_else(|| invalid_regex())
}
pub fn grep(pattern: &str, array: &[&str]) -> Vec<String> {
@@ -196,7 +194,7 @@ impl Preg {
}
pub fn grep3(pattern: &str, array: &[&str], flags: i64) -> Vec<String> {
- preg_grep(pattern, array, flags).unwrap_or_else(|| invalid_regex())
+ shirabe_php_shim::preg_grep2(pattern, array, flags).unwrap_or_else(|| invalid_regex())
}
pub fn is_match(pattern: &str, subject: &str) -> bool {
@@ -227,7 +225,7 @@ impl Preg {
matches: &mut IndexMap<String, String>,
) -> bool {
let mut internal: IndexMap<CaptureKey, Option<String>> = IndexMap::new();
- let result = preg_match(
+ let result = shirabe_php_shim::preg_match2(
pattern,
subject,
Some(&mut internal),
@@ -250,7 +248,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<CaptureKey, Option<String>> = IndexMap::new();
- let result = preg_match(pattern, subject, Some(&mut internal), 0, 0)
+ let result = shirabe_php_shim::preg_match2(pattern, subject, Some(&mut internal), 0, 0)
.unwrap_or_else(|| invalid_regex());
if result == 0 {
@@ -366,342 +364,6 @@ fn null_to_empty_offset_match_all(
.collect()
}
-#[derive(Debug, Clone, PartialOrd, Ord, PartialEq, Eq, Hash)]
-pub enum CaptureKey {
- ByIndex(usize),
- ByName(String),
-}
-
-// Returns Some(0|1) on success or None when the underlying preg_match returned
-// false. Unmatched groups are reported as None (PREG_UNMATCHED_AS_NULL).
-pub fn preg_match(
- pattern: &str,
- subject: &str,
- matches: Option<&mut IndexMap<CaptureKey, Option<String>>>,
- flags: i64,
- offset: usize,
-) -> Option<i64> {
- let re = shirabe_php_shim::compile_php_pattern(pattern).ok()?;
- let unmatched_as_null = flags & PREG_UNMATCHED_AS_NULL != 0;
- let caps = re.captures_at(subject, offset);
-
- if let Some(out) = matches {
- out.clear();
- if let Some(caps) = &caps {
- let names: Vec<Option<&str>> = re.capture_names().collect();
- *out = single_match_map(caps, &names, unmatched_as_null);
- }
- }
-
- Some(if caps.is_some() { 1 } else { 0 })
-}
-
-pub fn preg_match_all(
- pattern: &str,
- subject: &str,
- matches: Option<&mut IndexMap<CaptureKey, Vec<Option<String>>>>,
- flags: i64,
- offset: usize,
-) -> Option<i64> {
- let re = shirabe_php_shim::compile_php_pattern(pattern).ok()?;
- 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();
-
- // 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 = 0i64;
- for caps in re.captures_iter(&subject[offset..]) {
- count += 1;
- for (g, column) in groups.iter_mut().enumerate() {
- let value = caps.get(g).map(|m| m.as_str().to_string());
- column.push(if unmatched_as_null {
- value
- } else {
- Some(value.unwrap_or_default())
- });
- }
- }
-
- if let Some(out) = matches {
- out.clear();
- for (g, column) in groups.into_iter().enumerate() {
- if let Some(Some(name)) = names.get(g) {
- out.insert(CaptureKey::ByName((*name).to_string()), column.clone());
- }
- out.insert(CaptureKey::ByIndex(g), column);
- }
- }
-
- Some(count)
-}
-
-pub fn preg_match_all_offset_capture(
- pattern: &str,
- subject: &str,
- matches: Option<&mut IndexMap<CaptureKey, Vec<(Option<String>, i64)>>>,
- flags: i64,
- offset: usize,
-) -> Option<i64> {
- let re = shirabe_php_shim::compile_php_pattern(pattern).ok()?;
- 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();
-
- let mut groups: Vec<Vec<(Option<String>, i64)>> = vec![Vec::new(); group_count];
- let mut count = 0i64;
- for caps in re.captures_iter(&subject[offset..]) {
- 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() + offset) as i64),
- None if unmatched_as_null => (None, -1),
- None => (Some(String::new()), -1),
- };
- column.push(entry);
- }
- }
-
- if let Some(out) = matches {
- out.clear();
- for (g, column) in groups.into_iter().enumerate() {
- if let Some(Some(name)) = names.get(g) {
- out.insert(CaptureKey::ByName((*name).to_string()), column.clone());
- }
- out.insert(CaptureKey::ByIndex(g), column);
- }
- }
-
- Some(count)
-}
-
-pub fn preg_replace(
- pattern: &str,
- replacement: &str,
- subject: &str,
- limit: i64,
- count: Option<&mut usize>,
-) -> Option<String> {
- let re = shirabe_php_shim::compile_php_pattern(pattern).ok()?;
- let limit = if limit < 0 {
- usize::MAX
- } else {
- limit as usize
- };
-
- let mut out: Vec<u8> = Vec::new();
- let mut last = 0usize;
- let mut n = 0usize;
- for caps in re.captures_iter(subject) {
- if n >= limit {
- break;
- }
- let m = caps.get(0).unwrap();
- out.extend_from_slice(&subject.as_bytes()[last..m.start()]);
- expand_php_replacement(replacement, &caps, &mut out);
- last = m.end();
- n += 1;
- }
- out.extend_from_slice(&subject.as_bytes()[last..]);
-
- if let Some(count) = count {
- *count = n;
- }
- Some(String::from_utf8_lossy(&out).into_owned())
-}
-
-pub fn preg_replace_callback<F: FnMut(&IndexMap<CaptureKey, Option<String>>) -> String>(
- pattern: &str,
- mut callback: F,
- subject: &str,
- limit: i64,
- count: Option<&mut usize>,
- flags: i64,
-) -> Option<String> {
- let re = shirabe_php_shim::compile_php_pattern(pattern).ok()?;
- let unmatched_as_null = flags & PREG_UNMATCHED_AS_NULL != 0;
- let names: Vec<Option<&str>> = re.capture_names().collect();
- let limit = if limit < 0 {
- usize::MAX
- } else {
- limit as usize
- };
-
- let mut out: Vec<u8> = Vec::new();
- let mut last = 0usize;
- let mut n = 0usize;
- for caps in re.captures_iter(subject) {
- if n >= limit {
- break;
- }
- let m = caps.get(0).unwrap();
- out.extend_from_slice(&subject.as_bytes()[last..m.start()]);
- let map = single_match_map(&caps, &names, unmatched_as_null);
- out.extend_from_slice(callback(&map).as_bytes());
- last = m.end();
- n += 1;
- }
- out.extend_from_slice(&subject.as_bytes()[last..]);
-
- if let Some(count) = count {
- *count = n;
- }
- Some(String::from_utf8_lossy(&out).into_owned())
-}
-
-pub fn preg_split(pattern: &str, subject: &str, limit: i64, flags: i64) -> Option<Vec<String>> {
- let re = shirabe_php_shim::compile_php_pattern(pattern).ok()?;
- let no_empty = flags & PREG_SPLIT_NO_EMPTY != 0;
- let delim_capture = flags & PREG_SPLIT_DELIM_CAPTURE != 0;
- // `limit` counts the resulting pieces; a non-positive value means no limit.
- let max_delims = if limit > 0 {
- (limit as usize).saturating_sub(1)
- } else {
- usize::MAX
- };
-
- let mut result: Vec<String> = Vec::new();
- let mut push = |s: &str, result: &mut Vec<String>| {
- if !(no_empty && s.is_empty()) {
- result.push(s.to_string());
- }
- };
-
- let mut last = 0usize;
- let mut delims = 0usize;
- for caps in re.captures_iter(subject) {
- if delims >= max_delims {
- break;
- }
- let m = caps.get(0).unwrap();
- push(&subject[last..m.start()], &mut result);
- if delim_capture {
- // Mirror preg_match: trailing unmatched groups are dropped, interior
- // unmatched groups are emitted as "".
- if let Some(last_g) = (1..caps.len()).rev().find(|&g| caps.get(g).is_some()) {
- for g in 1..=last_g {
- push(caps.get(g).map(|x| x.as_str()).unwrap_or(""), &mut result);
- }
- }
- }
- last = m.end();
- delims += 1;
- }
- push(&subject[last..], &mut result);
-
- Some(result)
-}
-
-pub fn preg_grep(pattern: &str, array: &[&str], flags: i64) -> Option<Vec<String>> {
- let re = shirabe_php_shim::compile_php_pattern(pattern).ok()?;
- let invert = flags & PREG_GREP_INVERT != 0;
- Some(
- array
- .iter()
- .filter(|s| re.is_match(s) != invert)
- .map(|s| s.to_string())
- .collect(),
- )
-}
-
-// Builds a single match's `$matches` map with both named and numbered keys
-// (the named key precedes its number). With PREG_UNMATCHED_AS_NULL, every group
-// is present and non-participating ones are None; otherwise classic semantics
-// apply: trailing unmatched groups are dropped and interior ones become "".
-fn single_match_map(
- caps: &regex::Captures,
- names: &[Option<&str>],
- unmatched_as_null: bool,
-) -> IndexMap<CaptureKey, Option<String>> {
- let mut out = IndexMap::new();
- let group_count = caps.len();
- let last_participating = (0..group_count).rev().find(|&i| caps.get(i).is_some());
-
- for i in 0..group_count {
- let m = caps.get(i);
- if !unmatched_as_null && m.is_none() {
- if let Some(last) = last_participating {
- if i > last {
- break;
- }
- }
- }
- let value = if unmatched_as_null {
- m.map(|m| m.as_str().to_string())
- } else {
- Some(m.map(|m| m.as_str().to_string()).unwrap_or_default())
- };
- if let Some(Some(name)) = names.get(i) {
- out.insert(CaptureKey::ByName((*name).to_string()), value.clone());
- }
- out.insert(CaptureKey::ByIndex(i), value);
- }
- out
-}
-
-// Expands a PHP preg replacement template against `caps`, appending bytes to
-// `out`. Backreferences are written as `$1`, `${1}`, `\1` or `\\1`; a literal
-// `$` or `\` not forming a reference is emitted verbatim. Out-of-range or
-// non-participating groups expand to nothing.
-fn expand_php_replacement(template: &str, caps: &regex::Captures, out: &mut Vec<u8>) {
- let bytes = template.as_bytes();
- let mut i = 0;
- while i < bytes.len() {
- match bytes[i] {
- b'\\' if i + 1 < bytes.len() && bytes[i + 1].is_ascii_digit() => {
- let (group, consumed) = replacement_group(&bytes[i + 1..]);
- if let Some(m) = caps.get(group) {
- out.extend_from_slice(m.as_str().as_bytes());
- }
- i += 1 + consumed;
- }
- b'\\' if i + 1 < bytes.len() && bytes[i + 1] == b'\\' => {
- out.push(b'\\');
- i += 2;
- }
- b'$' if i + 1 < bytes.len() && bytes[i + 1] == b'{' => {
- let rest = &bytes[i + 2..];
- match rest.iter().position(|&b| b == b'}') {
- Some(c) if c > 0 && rest[..c].iter().all(|b| b.is_ascii_digit()) => {
- let group: usize =
- std::str::from_utf8(&rest[..c]).unwrap().parse().unwrap();
- if let Some(m) = caps.get(group) {
- out.extend_from_slice(m.as_str().as_bytes());
- }
- i += 2 + c + 1;
- }
- _ => {
- out.push(b'$');
- i += 1;
- }
- }
- }
- b'$' if i + 1 < bytes.len() && bytes[i + 1].is_ascii_digit() => {
- let (group, consumed) = replacement_group(&bytes[i + 1..]);
- if let Some(m) = caps.get(group) {
- out.extend_from_slice(m.as_str().as_bytes());
- }
- i += 1 + consumed;
- }
- b => {
- out.push(b);
- i += 1;
- }
- }
- }
-}
-
-// Reads up to two leading ASCII digits as a PHP backreference group number.
-fn replacement_group(bytes: &[u8]) -> (usize, usize) {
- let mut group = 0usize;
- let mut consumed = 0usize;
- while consumed < 2 && consumed < bytes.len() && bytes[consumed].is_ascii_digit() {
- group = group * 10 + (bytes[consumed] - b'0') as usize;
- consumed += 1;
- }
- (group, consumed)
-}
-
/// Panics if a pattern is invalid instead of throwing a PcreException.
/// TODO: takes regex::Error and shows its message
fn invalid_regex() -> ! {