use std::{
error::Error,
fmt::Display,
mem::MaybeUninit,
ops::{BitAnd, BitOr, Not},
ptr::null_mut,
range::Range,
};
use minrx_sys::{
minrx_regcomp_flags_t, minrx_regcomp_flags_t_MINRX_REG_BRACE_COMPAT,
minrx_regcomp_flags_t_MINRX_REG_BRACK_ESCAPE, minrx_regcomp_flags_t_MINRX_REG_EXTENDED,
minrx_regcomp_flags_t_MINRX_REG_EXTENSIONS_BSD, minrx_regcomp_flags_t_MINRX_REG_EXTENSIONS_GNU,
minrx_regcomp_flags_t_MINRX_REG_ICASE, minrx_regcomp_flags_t_MINRX_REG_MINDISABLE,
minrx_regcomp_flags_t_MINRX_REG_MINIMAL, minrx_regcomp_flags_t_MINRX_REG_NATIVE1B,
minrx_regcomp_flags_t_MINRX_REG_NEWLINE, minrx_regcomp_flags_t_MINRX_REG_NOSUB, minrx_regerror,
minrx_regex_t, minrx_regexec_flags_t, minrx_regexec_flags_t_MINRX_REG_FIRSTSUB,
minrx_regexec_flags_t_MINRX_REG_NOFIRSTBYTES, minrx_regexec_flags_t_MINRX_REG_NOSUBRESET,
minrx_regexec_flags_t_MINRX_REG_NOTBOL, minrx_regexec_flags_t_MINRX_REG_NOTEOL,
minrx_regexec_flags_t_MINRX_REG_RESUME, minrx_regfree, minrx_regmatch_t, minrx_regncomp,
minrx_regnexec, minrx_result_t, minrx_result_t_MINRX_REG_BADBR,
minrx_result_t_MINRX_REG_BADPAT, minrx_result_t_MINRX_REG_BADRPT,
minrx_result_t_MINRX_REG_EBRACE, minrx_result_t_MINRX_REG_EBRACK,
minrx_result_t_MINRX_REG_ECOLLATE, minrx_result_t_MINRX_REG_ECTYPE,
minrx_result_t_MINRX_REG_EESCAPE, minrx_result_t_MINRX_REG_EPAREN,
minrx_result_t_MINRX_REG_ERANGE, minrx_result_t_MINRX_REG_ESPACE,
minrx_result_t_MINRX_REG_ESUBREG, minrx_result_t_MINRX_REG_NOMATCH,
minrx_result_t_MINRX_REG_SUCCESS, minrx_result_t_MINRX_REG_UNKNOWN,
};
#[repr(transparent)]
pub struct Regex(minrx_regex_t);
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Match {
pub start: usize,
pub end: usize,
}
pub struct MatchIter<'r, 'h> {
regex: &'r mut Regex,
haystack: &'h [u8],
rm: minrx_regmatch_t,
options: MatchOptions,
resuming: bool,
is_done: bool,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct RegexBuilder(minrx_regcomp_flags_t);
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct MatchOptions(minrx_regexec_flags_t);
#[repr(u32)]
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum BuildError {
BadPattern(String) = minrx_result_t_MINRX_REG_BADPAT,
BadBracket(String) = minrx_result_t_MINRX_REG_BADBR,
BadRepetition(String) = minrx_result_t_MINRX_REG_BADRPT,
UnbalancedBrace(String) = minrx_result_t_MINRX_REG_EBRACE,
UnbalancedBracket(String) = minrx_result_t_MINRX_REG_EBRACK,
InvalidCollate(String) = minrx_result_t_MINRX_REG_ECOLLATE,
InvalidClass(String) = minrx_result_t_MINRX_REG_ECTYPE,
InvalidEscape(String) = minrx_result_t_MINRX_REG_EESCAPE,
UnbalancedParen(String) = minrx_result_t_MINRX_REG_EPAREN,
InvalidEndpoint(String) = minrx_result_t_MINRX_REG_ERANGE,
AllocError(String) = minrx_result_t_MINRX_REG_ESPACE,
InvalidDigitEscape(String) = minrx_result_t_MINRX_REG_ESUBREG,
Unknown(String) = minrx_result_t_MINRX_REG_UNKNOWN,
}
#[repr(u32)]
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum MatchError {
AllocError(String) = minrx_result_t_MINRX_REG_ESPACE,
Unknown(String) = minrx_result_t_MINRX_REG_UNKNOWN,
}
impl Regex {
pub fn new(pattern: impl AsRef<[u8]>) -> Result<Self, BuildError> {
RegexBuilder::new().build(pattern)
}
pub fn capture_count(&self) -> usize {
self.0.re_nsub as _
}
pub fn find_matches(
&mut self,
subject: impl AsRef<[u8]>,
) -> Result<Option<Box<[Option<Match>]>>, MatchError> {
self.find_matches_with(subject, MatchOptions::new())
}
pub fn is_match(&mut self, subject: impl AsRef<[u8]>) -> Result<bool, MatchError> {
self.is_match_with(subject, MatchOptions::new())
}
pub fn find_matches_with(
&mut self,
haystack: impl AsRef<[u8]>,
options: MatchOptions,
) -> Result<Option<Box<[Option<Match>]>>, MatchError> {
let subject = haystack.as_ref();
let mut buf = Vec::with_capacity(self.0.re_nsub + 1);
let res = unsafe {
minrx_regnexec(
&raw mut self.0,
subject.len(),
subject.as_ptr(),
buf.capacity(),
buf.as_mut_ptr(),
options.0 as _,
)
} as minrx_result_t;
MatchError::from_raw(res, self).map(|found| {
found.then(|| {
unsafe { buf.set_len(buf.capacity()) };
buf.into_iter()
.map(|m| {
Some(Match {
start: m.rm_so.try_into().ok()?,
end: m.rm_eo.try_into().ok()?,
})
})
.collect()
})
})
}
pub fn is_match_with(
&mut self,
haystack: impl AsRef<[u8]>,
options: MatchOptions,
) -> Result<bool, MatchError> {
let subject = haystack.as_ref();
let res = unsafe {
minrx_regnexec(
&raw mut self.0,
subject.len(),
subject.as_ptr(),
0,
null_mut(),
options.0 as _,
)
} as minrx_result_t;
MatchError::from_raw(res, self)
}
pub fn find_iter<'r, 'h>(
&'r mut self,
haystack: &'h (impl AsRef<[u8]> + ?Sized),
) -> MatchIter<'r, 'h> {
self.find_iter_with_flags(haystack, MatchOptions::new())
}
pub fn find_iter_with_flags<'r, 'h>(
&'r mut self,
haystack: &'h (impl AsRef<[u8]> + ?Sized),
options: MatchOptions,
) -> MatchIter<'r, 'h> {
MatchIter {
regex: self,
haystack: haystack.as_ref(),
rm: minrx_regmatch_t { rm_so: 0, rm_eo: 0 },
options,
resuming: false,
is_done: false,
}
}
}
impl RegexBuilder {
pub fn new() -> Self {
Self(minrx_regcomp_flags_t_MINRX_REG_EXTENDED)
}
pub fn build(&self, pattern: impl AsRef<[u8]>) -> Result<Regex, BuildError> {
let pattern = pattern.as_ref();
let mut regex = MaybeUninit::uninit();
let res = unsafe {
minrx_regncomp(
regex.as_mut_ptr(),
pattern.len(),
pattern.as_ptr(),
self.0 as _,
)
} as minrx_result_t;
BuildError::from_raw(res, &mut regex)?;
let regex = unsafe { regex.assume_init() };
Ok(Regex(regex))
}
pub fn extended(&mut self, _enable: bool) -> &mut Self {
self
}
pub fn case_insensitive(&mut self, enable: bool) -> &mut Self {
self.0 = mask(self.0, minrx_regcomp_flags_t_MINRX_REG_ICASE, enable);
self
}
pub fn swap_greed(&mut self, enable: bool) -> &mut Self {
self.0 = mask(self.0, minrx_regcomp_flags_t_MINRX_REG_MINIMAL, enable);
self
}
pub fn multi_line(&mut self, enable: bool) -> &mut Self {
self.0 = mask(self.0, minrx_regcomp_flags_t_MINRX_REG_NEWLINE, enable);
self
}
pub fn no_substrings(&mut self, enable: bool) -> &mut Self {
self.0 = mask(self.0, minrx_regcomp_flags_t_MINRX_REG_NOSUB, enable);
self
}
pub fn brace_compat(&mut self, enable: bool) -> &mut Self {
self.0 = mask(self.0, minrx_regcomp_flags_t_MINRX_REG_BRACE_COMPAT, enable);
self
}
pub fn escapes_in_brackets(&mut self, enable: bool) -> &mut Self {
self.0 = mask(self.0, minrx_regcomp_flags_t_MINRX_REG_BRACK_ESCAPE, enable);
self
}
pub fn bsd_extensions(&mut self, enable: bool) -> &mut Self {
self.0 = mask(
self.0,
minrx_regcomp_flags_t_MINRX_REG_EXTENSIONS_BSD,
enable,
);
self
}
pub fn gnu_extensions(&mut self, enable: bool) -> &mut Self {
self.0 = mask(
self.0,
minrx_regcomp_flags_t_MINRX_REG_EXTENSIONS_GNU,
enable,
);
self
}
pub fn native_encoding(&mut self, enable: bool) -> &mut Self {
self.0 = mask(self.0, minrx_regcomp_flags_t_MINRX_REG_NATIVE1B, enable);
self
}
pub fn disable_min_reps(&mut self, enable: bool) -> &mut Self {
self.0 = mask(self.0, minrx_regcomp_flags_t_MINRX_REG_MINDISABLE, enable);
self
}
}
impl MatchOptions {
pub fn new() -> Self {
Self(0)
}
pub fn not_bol(&mut self, enable: bool) -> &mut Self {
self.0 = mask(self.0, minrx_regexec_flags_t_MINRX_REG_NOTBOL, enable);
self
}
pub fn not_eol(&mut self, enable: bool) -> &mut Self {
self.0 = mask(self.0, minrx_regexec_flags_t_MINRX_REG_NOTEOL, enable);
self
}
pub fn first_subexpr(&mut self, enable: bool) -> &mut Self {
self.0 = mask(self.0, minrx_regexec_flags_t_MINRX_REG_FIRSTSUB, enable);
self
}
pub fn no_subexpr_reset(&mut self, enable: bool) -> &mut Self {
self.0 = mask(self.0, minrx_regexec_flags_t_MINRX_REG_NOSUBRESET, enable);
self
}
fn resume(&mut self, enable: bool) -> &mut Self {
self.0 = mask(self.0, minrx_regexec_flags_t_MINRX_REG_RESUME, enable);
self
}
pub fn no_first_bytes(&mut self, enable: bool) -> &mut Self {
self.0 = mask(self.0, minrx_regexec_flags_t_MINRX_REG_NOFIRSTBYTES, enable);
self
}
}
impl Default for RegexBuilder {
fn default() -> Self {
Self::new()
}
}
impl Default for MatchOptions {
fn default() -> Self {
Self::new()
}
}
impl BuildError {
fn from_raw(res: minrx_result_t, regex: &mut MaybeUninit<minrx_regex_t>) -> Result<(), Self> {
let err = || regerror(res, regex.as_ptr());
let err = match res {
res if res == minrx_result_t_MINRX_REG_SUCCESS => return Ok(()),
res if res == minrx_result_t_MINRX_REG_BADPAT => Err(Self::BadPattern(err())),
res if res == minrx_result_t_MINRX_REG_BADBR => Err(Self::BadBracket(err())),
res if res == minrx_result_t_MINRX_REG_BADRPT => Err(Self::BadRepetition(err())),
res if res == minrx_result_t_MINRX_REG_EBRACE => Err(Self::UnbalancedBrace(err())),
res if res == minrx_result_t_MINRX_REG_EBRACK => Err(Self::UnbalancedBracket(err())),
res if res == minrx_result_t_MINRX_REG_ECOLLATE => Err(Self::InvalidCollate(err())),
res if res == minrx_result_t_MINRX_REG_ECTYPE => Err(Self::InvalidClass(err())),
res if res == minrx_result_t_MINRX_REG_EESCAPE => Err(Self::InvalidEscape(err())),
res if res == minrx_result_t_MINRX_REG_EPAREN => Err(Self::UnbalancedParen(err())),
res if res == minrx_result_t_MINRX_REG_ERANGE => Err(Self::InvalidEndpoint(err())),
res if res == minrx_result_t_MINRX_REG_ESPACE => Err(Self::AllocError(err())),
res if res == minrx_result_t_MINRX_REG_ESUBREG => Err(Self::InvalidDigitEscape(err())),
_ => Err(Self::Unknown(err())),
};
drop(Regex(unsafe { regex.assume_init() }));
err
}
}
impl MatchError {
fn from_raw(res: minrx_result_t, regex: &Regex) -> Result<bool, Self> {
let err = || regerror(res, &raw const regex.0);
match res {
res if res == minrx_result_t_MINRX_REG_SUCCESS => Ok(true),
res if res == minrx_result_t_MINRX_REG_NOMATCH => Ok(false),
res if res == minrx_result_t_MINRX_REG_ESPACE => Err(Self::AllocError(err())),
_ => Err(Self::Unknown(err())),
}
}
}
impl<'r, 'h> Iterator for MatchIter<'r, 'h> {
type Item = Result<Match, MatchError>;
fn next(&mut self) -> Option<Self::Item> {
if self.is_done {
return None;
}
self.options.resume(self.resuming);
let res = unsafe {
minrx_regnexec(
&raw mut self.regex.0,
self.haystack.len(),
self.haystack.as_ptr(),
1,
&raw mut self.rm,
self.options.0 as _,
)
};
match MatchError::from_raw(res as _, self.regex) {
Ok(true) => {
let so = self.rm.rm_so as usize;
let eo = self.rm.rm_eo as usize;
if so == eo {
if eo >= self.haystack.len() {
self.is_done = true;
} else {
self.rm.rm_eo = (eo + 1) as _;
}
}
self.resuming = true;
Some(Ok(Match { start: so, end: eo }))
}
Ok(false) => {
self.is_done = true;
None
}
Err(e) => {
self.is_done = true;
Some(Err(e))
}
}
}
}
#[inline]
fn mask<T>(set: T, bit: T, enable: bool) -> T
where
T: BitOr<Output = T> + BitAnd<Output = T> + Not<Output = T>,
{
if enable { set | bit } else { set & !bit }
}
fn regerror(res: minrx_result_t, regex: *const minrx_regex_t) -> String {
let mut buf = Vec::with_capacity(53);
let new_len = unsafe { minrx_regerror(res as _, regex, buf.as_mut_ptr(), buf.capacity()) };
if new_len > buf.capacity() {
buf.reserve_exact(new_len);
unsafe { minrx_regerror(res as _, regex, buf.as_mut_ptr(), buf.capacity()) };
}
unsafe { buf.set_len(new_len.saturating_sub(1)) }; String::from_utf8_lossy(&buf).to_string()
}
impl Drop for Regex {
fn drop(&mut self) {
unsafe { minrx_regfree(&raw mut self.0) };
}
}
unsafe impl Send for Regex {}
impl From<Match> for std::ops::Range<usize> {
fn from(value: Match) -> Self {
value.start..value.end
}
}
impl Match {
pub fn range(&self) -> Range<usize> {
(self.start..self.end).into()
}
}
impl From<Match> for Range<usize> {
fn from(value: Match) -> Self {
value.range()
}
}
impl Display for BuildError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
BuildError::BadPattern(s)
| BuildError::BadBracket(s)
| BuildError::BadRepetition(s)
| BuildError::UnbalancedBrace(s)
| BuildError::UnbalancedBracket(s)
| BuildError::InvalidCollate(s)
| BuildError::InvalidClass(s)
| BuildError::InvalidEscape(s)
| BuildError::UnbalancedParen(s)
| BuildError::InvalidEndpoint(s)
| BuildError::AllocError(s)
| BuildError::InvalidDigitEscape(s)
| BuildError::Unknown(s) => f.write_str(s),
}
}
}
impl Display for MatchError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
MatchError::AllocError(s) | MatchError::Unknown(s) => f.write_str(s),
}
}
}
impl Error for BuildError {}
impl Error for MatchError {}