use std::collections::HashMap;
use std::marker::PhantomData;
use std::ops::Index;
use std::os::raw::c_char;
use std::sync::Arc;
pub const VERSION: &str = env!("CARGO_PKG_VERSION");
enum RealRegex {}
enum RealIter {}
enum RealRegexSet {}
extern "C" {
fn real_compile(pattern: *const c_char, len: usize, flags: u32,
errbuf: *mut c_char, errbuf_len: usize, code: *mut i32) -> *mut RealRegex;
fn real_group_count(re: *const RealRegex) -> usize;
fn real_group_name(re: *const RealRegex, group: usize, buf: *mut c_char, buflen: usize) -> usize;
fn real_free(re: *mut RealRegex);
fn real_find_iter(re: *const RealRegex, text: *const c_char, len: usize) -> *mut RealIter;
fn real_find_iter_at(re: *const RealRegex, text: *const c_char, len: usize, start: usize) -> *mut RealIter;
fn real_iter_next(iter: *mut RealIter, spans: *mut usize) -> i32;
fn real_iter_free(iter: *mut RealIter);
fn real_count_matches(re: *const RealRegex, text: *const c_char, len: usize) -> usize;
fn real_set_compile(patterns: *const *const c_char, lens: *const usize, n: usize, flags: u32,
errbuf: *mut c_char, errbuf_len: usize, code: *mut i32) -> *mut RealRegexSet;
fn real_set_size(set: *const RealRegexSet) -> usize;
fn real_set_free(set: *mut RealRegexSet);
fn real_set_is_match(set: *const RealRegexSet, text: *const c_char, len: usize) -> i32;
fn real_set_matches(set: *const RealRegexSet, text: *const c_char, len: usize, out: *mut u8) -> i32;
}
const DIVERGENCES_URL: &str = "https://github.com/RECHE23/real-regex/blob/main/docs/COMPATIBILITY.md";
const REAL_ERR_UNSUPPORTED: i32 = 2; const DOLLAR_ENDONLY: u32 = 128;
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Error {
Syntax { msg: String, pos: Option<usize> },
Unsupported { construct: String, hint: String },
}
impl Error {
pub fn is_unsupported(&self) -> bool {
matches!(self, Error::Unsupported { .. })
}
fn from_engine(raw: &str, code: i32) -> Error {
let body = raw.strip_prefix("regex_error").unwrap_or(raw).trim_start();
let (pos, msg) = match body.strip_prefix("at ").and_then(|r| r.split_once(':')) {
Some((n, rest)) => (n.trim().parse::<usize>().ok(), rest.trim().to_string()),
None => (None, body.trim_start_matches(':').trim().to_string()),
};
if code == REAL_ERR_UNSUPPORTED {
unsupported_construct(&msg)
} else {
Error::Syntax { msg, pos }
}
}
}
impl std::fmt::Display for Error {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Error::Syntax { msg, pos: Some(p) } => write!(f, "syntax error at {p}: {msg}"),
Error::Syntax { msg, pos: None } => write!(f, "syntax error: {msg}"),
Error::Unsupported { construct, hint } => write!(f, "{construct} ({hint})"),
}
}
}
impl std::error::Error for Error {}
struct GroupInfo {
names: Vec<Option<String>>, by_name: HashMap<String, usize>, }
const CAPS_INLINE_SLOTS: usize = 8;
#[derive(Clone, Debug)]
enum SlotStore {
Inline { len: u8, slots: [usize; CAPS_INLINE_SLOTS] },
Spilled(Box<[usize]>),
}
impl SlotStore {
fn from_flat(src: &[usize]) -> SlotStore {
if src.len() == 2 {
let mut slots = [usize::MAX; CAPS_INLINE_SLOTS];
slots[0] = src[0];
slots[1] = src[1];
return SlotStore::Inline { len: 2, slots };
}
if src.len() <= CAPS_INLINE_SLOTS {
let mut slots = [usize::MAX; CAPS_INLINE_SLOTS];
slots[..src.len()].copy_from_slice(src);
SlotStore::Inline { len: src.len() as u8, slots }
} else {
SlotStore::Spilled(src.to_vec().into_boxed_slice())
}
}
fn as_slice(&self) -> &[usize] {
match self {
SlotStore::Inline { len, slots } => &slots[..*len as usize],
SlotStore::Spilled(b) => b,
}
}
fn group(&self, i: usize) -> Option<(usize, usize)> {
let s = self.as_slice();
let lo = i.checked_mul(2)?;
let a = *s.get(lo)?;
let b = *s.get(lo + 1)?;
if a == usize::MAX {
None
} else {
Some((a, b))
}
}
fn ngroups(&self) -> usize {
self.as_slice().len() / 2
}
}
fn unsupported_construct(construct: &str) -> Error {
Error::Unsupported {
construct: construct.to_string(),
hint: format!(
"unsupported by REAL — see {DIVERGENCES_URL} ; enable the `fallback` feature to delegate this \
pattern to the regex crate (forfeiting the linear-time guarantee for it)"
),
}
}
fn nested_class_syntax(pattern: &[u8]) -> Option<&'static str> {
let mut i = 0;
let mut in_class = false;
let mut class_pos = 0usize; while i < pattern.len() {
let b = pattern[i];
if b == b'\\' {
i += 2; if in_class {
class_pos += 1;
}
continue;
}
if !in_class {
if b == b'[' {
in_class = true;
class_pos = 0;
if pattern.get(i + 1) == Some(&b'^') {
i += 1; }
}
} else if b == b']' {
if class_pos == 0 {
class_pos += 1; } else {
in_class = false;
}
} else if b == b'[' {
return Some("nested character class");
} else if matches!(b, b'&' | b'-' | b'~') && pattern.get(i + 1) == Some(&b) {
return Some("character-class set operation");
} else {
class_pos += 1;
}
i += 1;
}
None
}
fn compile_handle(pattern: &[u8], flags: u32) -> Result<(*mut RealRegex, usize, Arc<GroupInfo>), Error> {
if let Some(construct) = nested_class_syntax(pattern) {
return Err(unsupported_construct(construct)); }
let mut err = [0u8; 256];
let mut code: i32 = 0;
let handle = unsafe {
real_compile(pattern.as_ptr() as *const c_char, pattern.len(), flags | DOLLAR_ENDONLY,
err.as_mut_ptr() as *mut c_char, err.len(), &mut code)
};
if handle.is_null() {
let end = err.iter().position(|&b| b == 0).unwrap_or(err.len());
return Err(Error::from_engine(&String::from_utf8_lossy(&err[..end]), code));
}
let ngroups = unsafe { real_group_count(handle) };
let mut names = Vec::with_capacity(ngroups);
let mut by_name = HashMap::new();
for g in 0..ngroups {
let len = unsafe { real_group_name(handle, g, std::ptr::null_mut(), 0) };
if len == 0 {
names.push(None);
} else {
let mut buf = vec![0u8; len + 1];
unsafe {
real_group_name(handle, g, buf.as_mut_ptr() as *mut c_char, buf.len());
}
let name = String::from_utf8_lossy(&buf[..len]).into_owned();
by_name.insert(name.clone(), g);
names.push(Some(name));
}
}
Ok((handle, ngroups, Arc::new(GroupInfo { names, by_name })))
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Engine {
Real,
Fallback,
}
pub struct Regex {
handle: *mut RealRegex, ngroups: usize, pattern: String,
groups: Arc<GroupInfo>,
#[cfg(feature = "fallback")]
fallback: Option<regex::Regex>, }
unsafe impl Send for Regex {}
unsafe impl Sync for Regex {}
impl Regex {
pub fn new(pattern: &str) -> Result<Regex, Error> {
Regex::with_flags(pattern, 0)
}
pub fn with_flags(pattern: &str, flags: u32) -> Result<Regex, Error> {
let (handle, ngroups, groups) = compile_handle(pattern.as_bytes(), flags)?;
Ok(Regex {
handle,
ngroups,
pattern: pattern.to_string(),
groups,
#[cfg(feature = "fallback")]
fallback: None,
})
}
pub fn engine(&self) -> Engine {
#[cfg(feature = "fallback")]
if self.fallback.is_some() {
return Engine::Fallback;
}
Engine::Real
}
#[cfg(feature = "fallback")]
fn build_fallback(pattern: &str, flags: u32) -> Result<Regex, Error> {
let fb = regex::RegexBuilder::new(pattern)
.case_insensitive(flags & FLAG_ICASE != 0)
.multi_line(flags & FLAG_MULTILINE != 0)
.dot_matches_new_line(flags & FLAG_DOTALL != 0)
.ignore_whitespace(flags & FLAG_VERBOSE != 0)
.unicode(flags & FLAG_ASCII == 0)
.build()
.map_err(|e| Error::Syntax { msg: e.to_string(), pos: None })?;
let ngroups = fb.captures_len();
let mut names = Vec::with_capacity(ngroups);
let mut by_name = HashMap::new();
for (i, n) in fb.capture_names().enumerate() {
match n {
Some(name) => {
by_name.insert(name.to_string(), i);
names.push(Some(name.to_string()));
}
None => names.push(None),
}
}
Ok(Regex {
handle: std::ptr::null_mut(),
ngroups,
pattern: pattern.to_string(),
groups: Arc::new(GroupInfo { names, by_name }),
fallback: Some(fb),
})
}
pub fn as_str(&self) -> &str {
&self.pattern
}
pub fn captures_len(&self) -> usize {
self.ngroups
}
pub fn capture_names(&self) -> impl Iterator<Item = Option<&str>> {
self.groups.names.iter().map(|o| o.as_deref())
}
fn raw<'r, 't>(&'r self, text: &'t str, start: Option<usize>) -> SpanCursor<'r, 't> {
#[cfg(feature = "fallback")]
if let Some(fb) = &self.fallback {
return SpanCursor::Fallback {
it: fb.captures_iter(text),
ngroups: self.ngroups,
min_start: start.unwrap_or(0),
cur: Vec::new(),
};
}
let iter = unsafe {
match start {
None => real_find_iter(self.handle, text.as_ptr() as *const c_char, text.len()),
Some(s) => real_find_iter_at(self.handle, text.as_ptr() as *const c_char, text.len(), s),
}
};
assert!(!iter.is_null(), "real-regex: engine iteration failed");
SpanCursor::Real(RawSpans { iter, handle: self.handle, text: text.as_bytes(), ngroups: self.ngroups, buf: vec![0usize; 2 * self.ngroups], last_end: None, drive_pos: None, utf8: true, _re: PhantomData })
}
fn caps_from<'t>(&self, text: &'t str, cur: &SpanCursor<'_, '_>) -> Captures<'t> {
Captures { text, slots: cur.slot_store(), groups: Arc::clone(&self.groups) }
}
pub fn is_match(&self, text: &str) -> bool {
self.raw(text, None).advance().is_some()
}
pub fn is_match_at(&self, text: &str, start: usize) -> bool {
self.raw(text, Some(start)).advance().is_some()
}
pub fn find<'t>(&self, text: &'t str) -> Option<Match<'t>> {
self.raw(text, None).advance().map(|(a, b)| Match { text, start: a, end: b })
}
pub fn find_at<'t>(&self, text: &'t str, start: usize) -> Option<Match<'t>> {
self.raw(text, Some(start)).advance().map(|(a, b)| Match { text, start: a, end: b })
}
pub fn find_iter<'r, 't>(&'r self, text: &'t str) -> Matches<'r, 't> {
Matches { raw: self.raw(text, None), text }
}
pub fn captures<'t>(&self, text: &'t str) -> Option<Captures<'t>> {
{
let mut c = self.raw(text, None);
c.advance().map(|_| self.caps_from(text, &c))
}
}
pub fn captures_at<'t>(&self, text: &'t str, start: usize) -> Option<Captures<'t>> {
{
let mut c = self.raw(text, Some(start));
c.advance().map(|_| self.caps_from(text, &c))
}
}
pub fn capture_locations(&self) -> CaptureLocations {
CaptureLocations {
slots: vec![0; 2 * self.ngroups],
ngroups: self.ngroups,
}
}
pub fn captures_read<'t>(
&self,
locs: &mut CaptureLocations,
text: &'t str,
) -> Option<Match<'t>> {
self.captures_read_at(locs, text, 0)
}
pub fn captures_read_at<'t>(
&self,
locs: &mut CaptureLocations,
text: &'t str,
start: usize,
) -> Option<Match<'t>> {
locs.ensure(self.ngroups);
let mut c = self.raw(text, if start == 0 { None } else { Some(start) });
let (a, b) = c.advance()?;
c.copy_slots_into(locs);
Some(Match {
text,
start: a,
end: b,
})
}
pub fn captures_read_iter<'r, 't>(
&'r self,
text: &'t str,
) -> CaptureLocationMatches<'r, 't> {
CaptureLocationMatches {
raw: self.raw(text, None),
text,
ngroups: self.ngroups,
}
}
pub fn captures_iter<'r, 't>(&'r self, text: &'t str) -> CaptureMatches<'r, 't> {
CaptureMatches { raw: self.raw(text, None), re: self, text }
}
pub fn shortest_match(&self, text: &str) -> Option<usize> {
#[cfg(feature = "fallback")]
if let Some(fb) = &self.fallback {
return fb.shortest_match(text); }
self.raw(text, None).advance().map(|(_, e)| e)
}
pub fn count_matches(&self, text: &str) -> usize {
#[cfg(feature = "fallback")]
if let Some(fb) = &self.fallback {
return fb.find_iter(text).count();
}
let n = unsafe {
real_count_matches(self.handle, text.as_ptr() as *const c_char, text.len())
};
assert_ne!(n, usize::MAX, "real-regex: count_matches failed");
n
}
}
pub struct RegexSet {
handle: *mut RealRegexSet,
patterns: Vec<String>,
}
unsafe impl Send for RegexSet {}
unsafe impl Sync for RegexSet {}
impl RegexSet {
pub fn new<I, S>(patterns: I) -> Result<RegexSet, Error>
where
I: IntoIterator<Item = S>,
S: AsRef<str>,
{
RegexSet::with_flags(patterns, 0)
}
pub fn with_flags<I, S>(patterns: I, flags: u32) -> Result<RegexSet, Error>
where
I: IntoIterator<Item = S>,
S: AsRef<str>,
{
let owned: Vec<String> = patterns.into_iter().map(|s| s.as_ref().to_string()).collect();
let mut ptrs: Vec<*const c_char> = Vec::with_capacity(owned.len());
let mut lens: Vec<usize> = Vec::with_capacity(owned.len());
for p in &owned {
ptrs.push(p.as_ptr() as *const c_char);
lens.push(p.len());
}
let mut err = [0i8; 512];
let mut code: i32 = 0;
let handle = unsafe {
real_set_compile(
ptrs.as_ptr(),
lens.as_ptr(),
owned.len(),
flags | DOLLAR_ENDONLY,
err.as_mut_ptr(),
err.len(),
&mut code,
)
};
if handle.is_null() {
let raw = unsafe { std::ffi::CStr::from_ptr(err.as_ptr()) }
.to_string_lossy()
.into_owned();
return Err(Error::from_engine(&raw, code));
}
Ok(RegexSet {
handle,
patterns: owned,
})
}
pub fn len(&self) -> usize {
unsafe { real_set_size(self.handle) }
}
pub fn is_empty(&self) -> bool {
self.len() == 0
}
pub fn patterns(&self) -> &[String] {
&self.patterns
}
pub fn is_match(&self, text: &str) -> bool {
let r = unsafe {
real_set_is_match(self.handle, text.as_ptr() as *const c_char, text.len())
};
r == 1
}
pub fn matches(&self, text: &str) -> Vec<bool> {
let n = self.len();
let mut out = vec![0u8; n];
let r = unsafe {
real_set_matches(
self.handle,
text.as_ptr() as *const c_char,
text.len(),
out.as_mut_ptr(),
)
};
assert_eq!(r, 0, "real-regex: regex_set matches failed");
out.into_iter().map(|b| b != 0).collect()
}
pub fn matched_ids(&self, text: &str) -> Vec<usize> {
self.matches(text)
.into_iter()
.enumerate()
.filter_map(|(i, hit)| hit.then_some(i))
.collect()
}
}
impl Drop for RegexSet {
fn drop(&mut self) {
if !self.handle.is_null() {
unsafe { real_set_free(self.handle) }
}
}
}
impl Drop for Regex {
fn drop(&mut self) {
if !self.handle.is_null() {
unsafe { real_free(self.handle) } }
}
}
impl std::fmt::Debug for Regex {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "Regex({:?})", self.pattern)
}
}
struct RawSpans<'r, 't> {
iter: *mut RealIter, handle: *const RealRegex, text: &'t [u8], ngroups: usize,
buf: Vec<usize>, last_end: Option<usize>, drive_pos: Option<usize>, utf8: bool, _re: PhantomData<&'r ()>, }
impl RawSpans<'_, '_> {
fn advance(&mut self) -> Option<(usize, usize)> {
if self.drive_pos.is_some() {
return self.drive_advance();
}
loop {
let got = unsafe { real_iter_next(self.iter, self.buf.as_mut_ptr()) };
match got {
0 => return None,
-1 => panic!("real-regex: engine iteration failed"),
_ => {
let (s0, e0) = (self.buf[0], self.buf[1]); if s0 == e0 {
self.drive_pos = Some(self.last_end.unwrap_or(0));
return self.drive_advance();
}
self.last_end = Some(e0);
return Some((s0, e0));
}
}
}
}
fn search_at(&mut self, pos: usize) -> Option<(usize, usize)> {
if pos > self.text.len() {
return None;
}
let it = unsafe {
real_find_iter_at(self.handle, self.text.as_ptr() as *const c_char, self.text.len(), pos)
};
assert!(!it.is_null(), "real-regex: engine iteration failed");
let got = unsafe { real_iter_next(it, self.buf.as_mut_ptr()) };
unsafe { real_iter_free(it) };
match got {
0 => None,
-1 => panic!("real-regex: engine iteration failed"),
_ => Some((self.buf[0], self.buf[1])),
}
}
fn step_len(&self, pos: usize) -> usize {
if !self.utf8 || pos >= self.text.len() {
return 1;
}
match self.text[pos] {
b if b < 0x80 => 1,
b if b < 0xE0 => 2,
b if b < 0xF0 => 3,
_ => 4,
}
}
fn drive_advance(&mut self) -> Option<(usize, usize)> {
let pos = self.drive_pos.expect("drive_advance in fast mode");
let mut m = self.search_at(pos)?;
if m.0 == m.1 && Some(m.1) == self.last_end {
let next = m.1 + self.step_len(m.1);
m = self.search_at(next)?;
}
self.last_end = Some(m.1);
self.drive_pos = Some(m.1);
Some(m)
}
}
impl Drop for RawSpans<'_, '_> {
fn drop(&mut self) {
unsafe { real_iter_free(self.iter) }
}
}
enum SpanCursor<'r, 't> {
Real(RawSpans<'r, 't>),
#[cfg(feature = "fallback")]
Fallback {
it: regex::CaptureMatches<'r, 't>,
ngroups: usize,
min_start: usize,
cur: Vec<Option<(usize, usize)>>, },
}
impl SpanCursor<'_, '_> {
fn advance(&mut self) -> Option<(usize, usize)> {
match self {
SpanCursor::Real(r) => r.advance(),
#[cfg(feature = "fallback")]
SpanCursor::Fallback { it, ngroups, min_start, cur } => loop {
let caps = it.next()?;
let m0 = caps.get(0).unwrap();
if m0.start() < *min_start {
continue; }
cur.clear();
cur.extend((0..*ngroups).map(|g| caps.get(g).map(|m| (m.start(), m.end()))));
return Some((m0.start(), m0.end()));
},
}
}
fn nslots(&self) -> usize {
match self {
SpanCursor::Real(r) => 2 * r.ngroups,
#[cfg(feature = "fallback")]
SpanCursor::Fallback { ngroups, .. } => 2 * *ngroups,
}
}
fn write_slots(&self, out: &mut [usize]) {
match self {
SpanCursor::Real(r) => out.copy_from_slice(&r.buf),
#[cfg(feature = "fallback")]
SpanCursor::Fallback { cur, .. } => {
for (g, s) in cur.iter().enumerate() {
let (a, b) = s.unwrap_or((usize::MAX, usize::MAX));
out[2 * g] = a;
out[(2 * g) + 1] = b;
}
}
}
}
fn slot_store(&self) -> SlotStore {
match self {
SpanCursor::Real(r) => SlotStore::from_flat(&r.buf),
#[cfg(feature = "fallback")]
SpanCursor::Fallback { .. } => {
let n = self.nslots();
if n <= CAPS_INLINE_SLOTS {
let mut slots = [usize::MAX; CAPS_INLINE_SLOTS];
self.write_slots(&mut slots[..n]);
SlotStore::Inline { len: n as u8, slots }
} else {
let mut v = vec![usize::MAX; n];
self.write_slots(&mut v);
SlotStore::Spilled(v.into_boxed_slice())
}
}
}
}
fn copy_slots_into(&self, locs: &mut CaptureLocations) {
let ngroups = self.nslots() / 2;
locs.ensure(ngroups);
self.write_slots(&mut locs.slots);
}
}
#[derive(Clone, Debug)]
pub struct CaptureLocations {
slots: Vec<usize>, ngroups: usize,
}
impl CaptureLocations {
pub fn len(&self) -> usize {
self.ngroups
}
pub fn is_empty(&self) -> bool {
self.ngroups == 0
}
pub fn get(&self, i: usize) -> Option<(usize, usize)> {
if i >= self.ngroups {
return None;
}
let a = self.slots[2 * i];
let b = self.slots[2 * i + 1];
if a == usize::MAX {
None
} else {
Some((a, b))
}
}
fn ensure(&mut self, ngroups: usize) {
if self.ngroups != ngroups || self.slots.len() != 2 * ngroups {
self.slots.resize(2 * ngroups, 0);
self.ngroups = ngroups;
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct Match<'t> {
text: &'t str,
start: usize,
end: usize,
}
impl<'t> Match<'t> {
pub fn start(&self) -> usize {
self.start
}
pub fn end(&self) -> usize {
self.end
}
pub fn range(&self) -> std::ops::Range<usize> {
self.start..self.end
}
pub fn as_str(&self) -> &'t str {
&self.text[self.start..self.end]
}
pub fn is_empty(&self) -> bool {
self.start == self.end
}
pub fn len(&self) -> usize {
self.end - self.start
}
}
pub struct Captures<'t> {
text: &'t str,
slots: SlotStore,
groups: Arc<GroupInfo>,
}
impl<'t> Captures<'t> {
pub fn get(&self, i: usize) -> Option<Match<'t>> {
self.slots.group(i).map(|(s, e)| Match { text: self.text, start: s, end: e })
}
pub fn name(&self, name: &str) -> Option<Match<'t>> {
self.groups.by_name.get(name).and_then(|&i| self.get(i))
}
pub fn len(&self) -> usize {
self.slots.ngroups()
}
pub fn is_empty(&self) -> bool {
self.slots.ngroups() == 0
}
pub fn iter(&self) -> impl Iterator<Item = Option<Match<'t>>> + '_ {
(0..self.len()).map(move |i| self.get(i))
}
}
impl Index<usize> for Captures<'_> {
type Output = str;
fn index(&self, i: usize) -> &str {
self.get(i).map(|m| m.as_str()).unwrap_or_else(|| panic!("no group at index {i}"))
}
}
impl Index<&str> for Captures<'_> {
type Output = str;
fn index(&self, name: &str) -> &str {
self.name(name).map(|m| m.as_str()).unwrap_or_else(|| panic!("no group named {name:?}"))
}
}
pub struct Matches<'r, 't> {
raw: SpanCursor<'r, 't>,
text: &'t str,
}
impl<'t> Iterator for Matches<'_, 't> {
type Item = Match<'t>;
fn next(&mut self) -> Option<Match<'t>> {
self.raw.advance().map(|(a, b)| Match { text: self.text, start: a, end: b })
}
}
pub struct CaptureMatches<'r, 't> {
raw: SpanCursor<'r, 't>,
re: &'r Regex,
text: &'t str,
}
pub struct CaptureLocationMatches<'r, 't> {
raw: SpanCursor<'r, 't>,
text: &'t str,
ngroups: usize,
}
impl CaptureLocationMatches<'_, '_> {
pub fn len(&self) -> usize {
self.ngroups
}
pub fn is_empty(&self) -> bool {
self.ngroups == 0
}
pub fn get(&self, i: usize) -> Option<(usize, usize)> {
if i >= self.ngroups {
return None;
}
match &self.raw {
SpanCursor::Real(r) => {
let a = r.buf[2 * i];
let b = r.buf[2 * i + 1];
if a == usize::MAX {
None
} else {
Some((a, b))
}
}
#[cfg(feature = "fallback")]
SpanCursor::Fallback { cur, .. } => cur.get(i).copied().flatten(),
}
}
pub fn read_captures(&self, locs: &mut CaptureLocations) {
self.raw.copy_slots_into(locs);
}
}
impl<'t> Iterator for CaptureLocationMatches<'_, 't> {
type Item = Match<'t>;
fn next(&mut self) -> Option<Match<'t>> {
let (a, b) = self.raw.advance()?;
Some(Match {
text: self.text,
start: a,
end: b,
})
}
}
impl<'t> Iterator for CaptureMatches<'_, 't> {
type Item = Captures<'t>;
fn next(&mut self) -> Option<Captures<'t>> {
self.raw.advance().map(|_| self.re.caps_from(self.text, &self.raw))
}
}
const FLAG_ICASE: u32 = 1;
const FLAG_MULTILINE: u32 = 2;
const FLAG_DOTALL: u32 = 4;
const FLAG_VERBOSE: u32 = 16;
const FLAG_ASCII: u32 = 64;
pub struct RegexBuilder {
pattern: String,
flags: u32,
#[cfg(feature = "fallback")]
fallback: bool,
}
impl RegexBuilder {
pub fn new(pattern: &str) -> RegexBuilder {
RegexBuilder {
pattern: pattern.to_string(),
flags: 0,
#[cfg(feature = "fallback")]
fallback: false,
}
}
fn set(&mut self, bit: u32, yes: bool) -> &mut RegexBuilder {
if yes { self.flags |= bit } else { self.flags &= !bit }
self
}
#[cfg(feature = "fallback")]
pub fn fallback(&mut self, yes: bool) -> &mut RegexBuilder {
self.fallback = yes;
self
}
pub fn case_insensitive(&mut self, yes: bool) -> &mut RegexBuilder {
self.set(FLAG_ICASE, yes)
}
pub fn multi_line(&mut self, yes: bool) -> &mut RegexBuilder {
self.set(FLAG_MULTILINE, yes)
}
pub fn dot_matches_new_line(&mut self, yes: bool) -> &mut RegexBuilder {
self.set(FLAG_DOTALL, yes)
}
pub fn ignore_whitespace(&mut self, yes: bool) -> &mut RegexBuilder {
self.set(FLAG_VERBOSE, yes)
}
pub fn unicode(&mut self, yes: bool) -> &mut RegexBuilder {
self.set(FLAG_ASCII, !yes)
}
pub fn size_limit(&mut self, _bytes: usize) -> &mut RegexBuilder {
self
}
pub fn build(&self) -> Result<Regex, Error> {
match Regex::with_flags(&self.pattern, self.flags) {
Ok(re) => Ok(re),
Err(e) => {
#[cfg(feature = "fallback")]
if self.fallback && e.is_unsupported() {
return Regex::build_fallback(&self.pattern, self.flags);
}
Err(e)
}
}
}
}
use std::borrow::Cow;
pub trait Replacer {
fn replace_append(&mut self, caps: &Captures, dst: &mut String);
}
pub struct NoExpand<'a>(pub &'a str);
impl Replacer for NoExpand<'_> {
fn replace_append(&mut self, _caps: &Captures, dst: &mut String) {
dst.push_str(self.0);
}
}
impl Replacer for &str {
fn replace_append(&mut self, caps: &Captures, dst: &mut String) {
expand(caps, self, dst);
}
}
impl Replacer for String {
fn replace_append(&mut self, caps: &Captures, dst: &mut String) {
expand(caps, self, dst);
}
}
impl<F, T> Replacer for F
where
F: FnMut(&Captures) -> T,
T: AsRef<str>,
{
fn replace_append(&mut self, caps: &Captures, dst: &mut String) {
dst.push_str((*self)(caps).as_ref());
}
}
fn expand(caps: &Captures, template: &str, dst: &mut String) {
let mut rest = template;
while let Some(i) = rest.find('$') {
dst.push_str(&rest[..i]);
rest = &rest[i + 1..];
if let Some(stripped) = rest.strip_prefix('$') {
dst.push('$');
rest = stripped;
continue;
}
let (name, after) = if let Some(braced) = rest.strip_prefix('{') {
match braced.find('}') {
Some(j) => (&braced[..j], &braced[j + 1..]),
None => {
dst.push('$');
("", rest)
}
}
} else {
let end = rest.find(|c: char| !(c.is_ascii_alphanumeric() || c == '_')).unwrap_or(rest.len());
(&rest[..end], &rest[end..])
};
rest = after;
if name.is_empty() {
dst.push('$');
continue;
}
let m = match name.parse::<usize>() {
Ok(n) => caps.get(n),
Err(_) => caps.name(name),
};
if let Some(m) = m {
dst.push_str(m.as_str());
}
}
dst.push_str(rest);
}
impl Regex {
pub fn replace<'t, R: Replacer>(&self, text: &'t str, rep: R) -> Cow<'t, str> {
self.replacen(text, 1, rep)
}
pub fn replace_all<'t, R: Replacer>(&self, text: &'t str, rep: R) -> Cow<'t, str> {
self.replacen(text, 0, rep)
}
pub fn replacen<'t, R: Replacer>(&self, text: &'t str, limit: usize, mut rep: R) -> Cow<'t, str> {
let mut out: Option<String> = None;
let mut last = 0;
for (i, caps) in self.captures_iter(text).enumerate() {
if limit != 0 && i >= limit {
break;
}
let m = caps.get(0).unwrap();
let dst = out.get_or_insert_with(|| String::with_capacity(text.len()));
dst.push_str(&text[last..m.start()]);
rep.replace_append(&caps, dst);
last = m.end();
}
match out {
Some(mut dst) => {
dst.push_str(&text[last..]);
Cow::Owned(dst)
}
None => Cow::Borrowed(text),
}
}
pub fn split<'r, 't>(&'r self, text: &'t str) -> Split<'r, 't> {
Split { text, it: self.find_iter(text), last: 0, done: false }
}
pub fn splitn<'r, 't>(&'r self, text: &'t str, limit: usize) -> SplitN<'r, 't> {
SplitN { inner: self.split(text), limit, n: 0 }
}
}
pub struct Split<'r, 't> {
text: &'t str,
it: Matches<'r, 't>,
last: usize,
done: bool,
}
impl<'t> Iterator for Split<'_, 't> {
type Item = &'t str;
fn next(&mut self) -> Option<&'t str> {
if self.done {
return None;
}
match self.it.next() {
Some(m) => {
let piece = &self.text[self.last..m.start()];
self.last = m.end();
Some(piece)
}
None => {
self.done = true;
Some(&self.text[self.last..])
}
}
}
}
pub struct SplitN<'r, 't> {
inner: Split<'r, 't>,
limit: usize,
n: usize,
}
impl<'t> Iterator for SplitN<'_, 't> {
type Item = &'t str;
fn next(&mut self) -> Option<&'t str> {
if self.n >= self.limit {
return None;
}
self.n += 1;
if self.n == self.limit {
if self.inner.done {
return None;
}
self.inner.done = true;
return Some(&self.inner.text[self.inner.last..]);
}
self.inner.next()
}
}
pub mod bytes {
use super::{
compile_handle, real_find_iter, real_find_iter_at, real_free, CaptureLocations, Error,
GroupInfo, RawSpans, RealRegex, SlotStore, FLAG_ASCII, FLAG_DOTALL, FLAG_ICASE,
FLAG_MULTILINE, FLAG_VERBOSE,
};
use std::borrow::Cow;
use std::marker::PhantomData;
use std::ops::Index;
use std::os::raw::c_char;
use std::sync::Arc;
const FLAG_BYTES: u32 = 8;
pub struct Regex {
handle: *mut RealRegex,
ngroups: usize,
pattern: Vec<u8>,
groups: Arc<GroupInfo>,
}
unsafe impl Send for Regex {}
unsafe impl Sync for Regex {}
impl Regex {
pub fn new(pattern: &str) -> Result<Regex, Error> {
Regex::with_flags(pattern.as_bytes(), 0)
}
pub fn with_flags(pattern: &[u8], flags: u32) -> Result<Regex, Error> {
let (handle, ngroups, groups) = compile_handle(pattern, flags | FLAG_BYTES)?;
Ok(Regex { handle, ngroups, pattern: pattern.to_vec(), groups })
}
pub fn as_bytes(&self) -> &[u8] {
&self.pattern
}
pub fn captures_len(&self) -> usize {
self.ngroups
}
pub fn capture_names(&self) -> impl Iterator<Item = Option<&str>> {
self.groups.names.iter().map(|o| o.as_deref())
}
fn raw<'r, 't>(&'r self, text: &'t [u8], start: Option<usize>) -> RawSpans<'r, 't> {
let iter = unsafe {
match start {
None => real_find_iter(self.handle, text.as_ptr() as *const c_char, text.len()),
Some(s) => real_find_iter_at(self.handle, text.as_ptr() as *const c_char, text.len(), s),
}
};
assert!(!iter.is_null(), "real-regex: engine iteration failed");
RawSpans { iter, handle: self.handle, text, ngroups: self.ngroups, buf: vec![0usize; 2 * self.ngroups], last_end: None, drive_pos: None, utf8: false, _re: PhantomData }
}
fn caps_from<'t>(&self, text: &'t [u8], raw: &RawSpans<'_, '_>) -> Captures<'t> {
Captures { text, slots: SlotStore::from_flat(&raw.buf), groups: Arc::clone(&self.groups) }
}
pub fn is_match(&self, text: &[u8]) -> bool {
self.raw(text, None).advance().is_some()
}
pub fn find<'t>(&self, text: &'t [u8]) -> Option<Match<'t>> {
self.raw(text, None).advance().map(|(a, b)| Match { text, start: a, end: b })
}
pub fn find_at<'t>(&self, text: &'t [u8], start: usize) -> Option<Match<'t>> {
self.raw(text, Some(start)).advance().map(|(a, b)| Match { text, start: a, end: b })
}
pub fn find_iter<'r, 't>(&'r self, text: &'t [u8]) -> Matches<'r, 't> {
Matches { raw: self.raw(text, None), text }
}
pub fn is_match_at(&self, text: &[u8], start: usize) -> bool {
self.raw(text, Some(start)).advance().is_some()
}
pub fn captures<'t>(&self, text: &'t [u8]) -> Option<Captures<'t>> {
{
let mut c = self.raw(text, None);
c.advance().map(|_| self.caps_from(text, &c))
}
}
pub fn captures_at<'t>(&self, text: &'t [u8], start: usize) -> Option<Captures<'t>> {
{
let mut c = self.raw(text, Some(start));
c.advance().map(|_| self.caps_from(text, &c))
}
}
pub fn capture_locations(&self) -> CaptureLocations {
CaptureLocations {
slots: vec![0; 2 * self.ngroups],
ngroups: self.ngroups,
}
}
pub fn captures_read<'t>(
&self,
locs: &mut CaptureLocations,
text: &'t [u8],
) -> Option<Match<'t>> {
self.captures_read_at(locs, text, 0)
}
pub fn captures_read_at<'t>(
&self,
locs: &mut CaptureLocations,
text: &'t [u8],
start: usize,
) -> Option<Match<'t>> {
locs.ensure(self.ngroups);
let mut c = self.raw(text, if start == 0 { None } else { Some(start) });
let (a, b) = c.advance()?;
locs.slots.copy_from_slice(&c.buf);
Some(Match {
text,
start: a,
end: b,
})
}
pub fn captures_read_iter<'r, 't>(
&'r self,
text: &'t [u8],
) -> CaptureLocationMatches<'r, 't> {
CaptureLocationMatches {
raw: self.raw(text, None),
text,
ngroups: self.ngroups,
}
}
pub fn captures_iter<'r, 't>(&'r self, text: &'t [u8]) -> CaptureMatches<'r, 't> {
CaptureMatches { raw: self.raw(text, None), re: self, text }
}
pub fn shortest_match(&self, text: &[u8]) -> Option<usize> {
self.raw(text, None).advance().map(|(_, e)| e)
}
pub fn replace<'t, R: Replacer>(&self, text: &'t [u8], rep: R) -> Cow<'t, [u8]> {
self.replacen(text, 1, rep)
}
pub fn replace_all<'t, R: Replacer>(&self, text: &'t [u8], rep: R) -> Cow<'t, [u8]> {
self.replacen(text, 0, rep)
}
pub fn replacen<'t, R: Replacer>(&self, text: &'t [u8], limit: usize, mut rep: R) -> Cow<'t, [u8]> {
let mut out: Option<Vec<u8>> = None;
let mut last = 0;
for (i, caps) in self.captures_iter(text).enumerate() {
if limit != 0 && i >= limit {
break;
}
let m = caps.get(0).unwrap();
let dst = out.get_or_insert_with(|| Vec::with_capacity(text.len()));
dst.extend_from_slice(&text[last..m.start()]);
rep.replace_append(&caps, dst);
last = m.end();
}
match out {
Some(mut dst) => {
dst.extend_from_slice(&text[last..]);
Cow::Owned(dst)
}
None => Cow::Borrowed(text),
}
}
pub fn split<'r, 't>(&'r self, text: &'t [u8]) -> Split<'r, 't> {
Split { text, it: self.find_iter(text), last: 0, done: false }
}
pub fn splitn<'r, 't>(&'r self, text: &'t [u8], limit: usize) -> SplitN<'r, 't> {
SplitN { inner: self.split(text), limit, n: 0 }
}
}
impl Drop for Regex {
fn drop(&mut self) {
unsafe { real_free(self.handle) }
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct Match<'t> {
text: &'t [u8],
start: usize,
end: usize,
}
impl<'t> Match<'t> {
pub fn start(&self) -> usize { self.start }
pub fn end(&self) -> usize { self.end }
pub fn as_bytes(&self) -> &'t [u8] { &self.text[self.start..self.end] }
pub fn range(&self) -> std::ops::Range<usize> { self.start..self.end }
pub fn is_empty(&self) -> bool { self.start == self.end }
pub fn len(&self) -> usize { self.end - self.start }
}
pub struct Captures<'t> {
text: &'t [u8],
slots: SlotStore,
groups: Arc<GroupInfo>,
}
impl<'t> Captures<'t> {
pub fn get(&self, i: usize) -> Option<Match<'t>> {
self.slots.group(i).map(|(s, e)| Match { text: self.text, start: s, end: e })
}
pub fn name(&self, name: &str) -> Option<Match<'t>> {
self.groups.by_name.get(name).and_then(|&i| self.get(i))
}
pub fn len(&self) -> usize { self.slots.ngroups() }
pub fn is_empty(&self) -> bool { self.slots.ngroups() == 0 }
}
impl Index<usize> for Captures<'_> {
type Output = [u8];
fn index(&self, i: usize) -> &[u8] {
self.get(i).map(|m| m.as_bytes()).unwrap_or_else(|| panic!("no group at index {i}"))
}
}
impl Index<&str> for Captures<'_> {
type Output = [u8];
fn index(&self, name: &str) -> &[u8] {
self.name(name).map(|m| m.as_bytes()).unwrap_or_else(|| panic!("no group named {name:?}"))
}
}
pub struct Matches<'r, 't> {
raw: RawSpans<'r, 't>,
text: &'t [u8],
}
impl<'t> Iterator for Matches<'_, 't> {
type Item = Match<'t>;
fn next(&mut self) -> Option<Match<'t>> {
self.raw.advance().map(|(a, b)| Match { text: self.text, start: a, end: b })
}
}
pub struct CaptureMatches<'r, 't> {
raw: RawSpans<'r, 't>,
re: &'r Regex,
text: &'t [u8],
}
pub struct CaptureLocationMatches<'r, 't> {
raw: RawSpans<'r, 't>,
text: &'t [u8],
ngroups: usize,
}
impl CaptureLocationMatches<'_, '_> {
pub fn len(&self) -> usize {
self.ngroups
}
pub fn get(&self, i: usize) -> Option<(usize, usize)> {
if i >= self.ngroups {
return None;
}
let a = self.raw.buf[2 * i];
let b = self.raw.buf[2 * i + 1];
if a == usize::MAX {
None
} else {
Some((a, b))
}
}
pub fn read_captures(&self, locs: &mut CaptureLocations) {
locs.ensure(self.ngroups);
locs.slots.copy_from_slice(&self.raw.buf);
}
}
impl<'t> Iterator for CaptureLocationMatches<'_, 't> {
type Item = Match<'t>;
fn next(&mut self) -> Option<Match<'t>> {
let (a, b) = self.raw.advance()?;
Some(Match {
text: self.text,
start: a,
end: b,
})
}
}
impl<'t> Iterator for CaptureMatches<'_, 't> {
type Item = Captures<'t>;
fn next(&mut self) -> Option<Captures<'t>> {
self.raw.advance().map(|_| self.re.caps_from(self.text, &self.raw))
}
}
pub struct Split<'r, 't> {
text: &'t [u8],
it: Matches<'r, 't>,
last: usize,
done: bool,
}
impl<'t> Iterator for Split<'_, 't> {
type Item = &'t [u8];
fn next(&mut self) -> Option<&'t [u8]> {
if self.done {
return None;
}
match self.it.next() {
Some(m) => {
let piece = &self.text[self.last..m.start()];
self.last = m.end();
Some(piece)
}
None => {
self.done = true;
Some(&self.text[self.last..])
}
}
}
}
pub struct SplitN<'r, 't> {
inner: Split<'r, 't>,
limit: usize,
n: usize,
}
impl<'t> Iterator for SplitN<'_, 't> {
type Item = &'t [u8];
fn next(&mut self) -> Option<&'t [u8]> {
if self.n >= self.limit {
return None;
}
self.n += 1;
if self.n == self.limit {
if self.inner.done {
return None;
}
self.inner.done = true;
return Some(&self.inner.text[self.inner.last..]);
}
self.inner.next()
}
}
pub trait Replacer {
fn replace_append(&mut self, caps: &Captures, dst: &mut Vec<u8>);
}
pub struct NoExpand<'a>(pub &'a [u8]);
impl Replacer for NoExpand<'_> {
fn replace_append(&mut self, _caps: &Captures, dst: &mut Vec<u8>) {
dst.extend_from_slice(self.0);
}
}
impl Replacer for &[u8] {
fn replace_append(&mut self, caps: &Captures, dst: &mut Vec<u8>) {
expand_bytes(caps, self, dst);
}
}
impl<F, T> Replacer for F
where
F: FnMut(&Captures) -> T,
T: AsRef<[u8]>,
{
fn replace_append(&mut self, caps: &Captures, dst: &mut Vec<u8>) {
dst.extend_from_slice((*self)(caps).as_ref());
}
}
fn expand_bytes(caps: &Captures, template: &[u8], dst: &mut Vec<u8>) {
let mut i = 0;
while i < template.len() {
let b = template[i];
if b != b'$' {
dst.push(b);
i += 1;
continue;
}
i += 1; if i < template.len() && template[i] == b'$' {
dst.push(b'$');
i += 1;
continue;
}
let (name, next) = if i < template.len() && template[i] == b'{' {
match template[i + 1..].iter().position(|&c| c == b'}') {
Some(j) => (&template[i + 1..i + 1 + j], i + 1 + j + 1),
None => {
dst.push(b'$');
continue;
}
}
} else {
let mut j = i;
while j < template.len() && (template[j].is_ascii_alphanumeric() || template[j] == b'_') {
j += 1;
}
(&template[i..j], j)
};
i = next;
if name.is_empty() {
dst.push(b'$');
continue;
}
let name_str = std::str::from_utf8(name).unwrap_or("");
let m = match name_str.parse::<usize>() {
Ok(n) => caps.get(n),
Err(_) => caps.name(name_str),
};
if let Some(m) = m {
dst.extend_from_slice(m.as_bytes());
}
}
}
pub struct RegexBuilder {
pattern: Vec<u8>,
flags: u32,
}
impl RegexBuilder {
pub fn new(pattern: &str) -> RegexBuilder {
RegexBuilder { pattern: pattern.as_bytes().to_vec(), flags: 0 }
}
fn set(&mut self, bit: u32, yes: bool) -> &mut RegexBuilder {
if yes { self.flags |= bit } else { self.flags &= !bit }
self
}
pub fn case_insensitive(&mut self, yes: bool) -> &mut RegexBuilder { self.set(FLAG_ICASE, yes) }
pub fn multi_line(&mut self, yes: bool) -> &mut RegexBuilder { self.set(FLAG_MULTILINE, yes) }
pub fn dot_matches_new_line(&mut self, yes: bool) -> &mut RegexBuilder { self.set(FLAG_DOTALL, yes) }
pub fn ignore_whitespace(&mut self, yes: bool) -> &mut RegexBuilder { self.set(FLAG_VERBOSE, yes) }
pub fn unicode(&mut self, yes: bool) -> &mut RegexBuilder { self.set(FLAG_ASCII, !yes) }
pub fn size_limit(&mut self, _bytes: usize) -> &mut RegexBuilder { self }
pub fn build(&self) -> Result<Regex, Error> {
Regex::with_flags(&self.pattern, self.flags)
}
}
}