use std::ptr::NonNull;
use crate::error::{Error, compile_refusal, fault};
use crate::pattern::expect;
use crate::pool::{Pool, Recipe};
use crate::sys;
pub struct RegexSet {
pool: Pool<Slate>,
}
impl RegexSet {
pub fn new<I, S>(patterns: I) -> Result<Self, Error>
where
S: AsRef<str>,
I: IntoIterator<Item = S>,
{
RegexSetBuilder::new(patterns).build()
}
#[must_use]
pub fn empty() -> Self {
expect(Self::new(std::iter::empty::<&str>()))
}
#[must_use]
pub fn len(&self) -> usize {
self.patterns().len()
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.patterns().is_empty()
}
#[must_use]
pub fn patterns(&self) -> &[String] {
&self.pool.recipe().patterns
}
#[must_use]
pub fn is_match(&self, text: &str) -> bool {
expect(self.try_is_match(text))
}
pub fn try_is_match(&self, text: &str) -> Result<bool, Error> {
let lease = self.pool.lease()?;
let body = text.as_bytes();
let status = unsafe { sys::irgx_slate_is_match(lease.raw(), body.as_ptr(), body.len()) };
if status < 0 {
return Err(fault(status, |status, detail| Error::Search {
status,
detail,
}));
}
Ok(status == sys::MATCH)
}
#[must_use]
pub fn matches(&self, text: &str) -> SetMatches {
expect(self.try_matches(text))
}
pub fn try_matches(&self, text: &str) -> Result<SetMatches, Error> {
let total = self.len();
let mut hits: Vec<u32> = vec![0; total];
let lease = self.pool.lease()?;
let body = text.as_bytes();
let mut written: usize = 0;
let status = unsafe {
sys::irgx_slate_which(
lease.raw(),
body.as_ptr(),
body.len(),
hits.as_mut_ptr(),
hits.len(),
&raw mut written,
)
};
drop(lease);
if status < 0 {
return Err(fault(status, |status, detail| Error::Search {
status,
detail,
}));
}
if written > total {
return Err(Error::Inconsistent {
message: format!("a slate of {total} patterns reported {written} of them matching"),
});
}
hits.truncate(written);
Ok(SetMatches {
hits: hits.into(),
total,
})
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct SetMatches {
hits: Box<[u32]>,
total: usize,
}
impl SetMatches {
#[must_use]
pub fn matched_any(&self) -> bool {
!self.hits.is_empty()
}
#[must_use]
pub fn matched(&self, index: usize) -> bool {
u32::try_from(index).is_ok_and(|wanted| self.hits.binary_search(&wanted).is_ok())
}
#[must_use]
pub fn len(&self) -> usize {
self.total
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.total == 0
}
pub fn iter(&self) -> impl ExactSizeIterator<Item = usize> + DoubleEndedIterator + '_ {
self.hits.iter().map(|&at| at as usize)
}
}
impl IntoIterator for SetMatches {
type Item = usize;
type IntoIter = std::vec::IntoIter<usize>;
fn into_iter(self) -> Self::IntoIter {
self.hits
.iter()
.map(|&at| at as usize)
.collect::<Vec<_>>()
.into_iter()
}
}
impl<'m> IntoIterator for &'m SetMatches {
type Item = usize;
type IntoIter = std::iter::Map<std::slice::Iter<'m, u32>, fn(&'m u32) -> usize>;
fn into_iter(self) -> Self::IntoIter {
fn widen(at: &u32) -> usize {
*at as usize
}
self.hits.iter().map(widen as fn(&u32) -> usize)
}
}
#[derive(Clone, Debug)]
pub struct RegexSetBuilder {
patterns: Vec<String>,
flags: u32,
}
impl RegexSetBuilder {
#[must_use]
pub fn new<I, S>(patterns: I) -> Self
where
S: AsRef<str>,
I: IntoIterator<Item = S>,
{
Self {
patterns: patterns
.into_iter()
.map(|one| one.as_ref().to_owned())
.collect(),
flags: 0,
}
}
pub fn fixed(&mut self, yes: bool) -> &mut Self {
self.set(sys::FIXED, yes)
}
pub fn ignore_case(&mut self, yes: bool) -> &mut Self {
self.set(sys::IGNORE_CASE, yes)
}
pub fn word(&mut self, yes: bool) -> &mut Self {
self.set(sys::WORD, yes)
}
pub fn smart_case(&mut self, yes: bool) -> &mut Self {
self.set(sys::SMART_CASE, yes)
}
pub fn unicode(&mut self, yes: bool) -> &mut Self {
self.set(sys::NO_UNICODE, !yes)
}
pub fn pcre(&mut self, yes: bool) -> &mut Self {
self.set(sys::PCRE, yes)
}
pub fn build(&self) -> Result<RegexSet, Error> {
Ok(RegexSet {
pool: Pool::new(Slate {
patterns: self.patterns.clone().into(),
flags: self.flags,
})?,
})
}
fn set(&mut self, bit: u32, yes: bool) -> &mut Self {
if yes {
self.flags |= bit;
} else {
self.flags &= !bit;
}
self
}
}
struct Slate {
patterns: Box<[String]>,
flags: u32,
}
impl Recipe for Slate {
type Raw = sys::Slate;
fn compile(&self) -> Result<NonNull<sys::Slate>, Error> {
let list: Vec<sys::SlatePattern> = self
.patterns
.iter()
.map(|one| sys::SlatePattern {
pattern: one.as_ptr(),
len: one.len(),
flags: self.flags,
})
.collect();
let mut out: *mut sys::Slate = std::ptr::null_mut();
let mut refused: usize = usize::MAX;
let status = unsafe {
sys::irgx_slate_compile(list.as_ptr(), list.len(), &raw mut refused, &raw mut out)
};
if status < 0 {
let blamed = self.patterns.get(refused).map_or("", String::as_str);
return Err(compile_refusal(status, blamed));
}
crate::pool::wrote(out, "irgx_slate_compile")
}
unsafe fn release(raw: NonNull<sys::Slate>) {
unsafe { sys::irgx_slate_free(raw.as_ptr()) }
}
}
impl Clone for RegexSet {
fn clone(&self) -> Self {
let slate = self.pool.recipe();
expect(
RegexSetBuilder {
patterns: slate.patterns.to_vec(),
flags: slate.flags,
}
.build(),
)
}
}
impl std::fmt::Debug for RegexSet {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_tuple("RegexSet").field(&self.patterns()).finish()
}
}