use std::ffi::c_int;
use crate::{
Regex, RegexecFlags,
err::{BindingErrorCode, ErrorKind, RegexError, Result},
exec::match_offset,
tre,
};
pub type RegApproxMatchStr<'a> = RegApproxMatch<'a, str>;
pub type RegApproxMatchBytes<'a> = RegApproxMatch<'a, [u8]>;
#[cfg(feature = "approx")]
#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, Hash)]
pub struct RegApproxParams {
cost_ins: i32,
cost_del: i32,
cost_subst: i32,
max_cost: i32,
max_ins: i32,
max_del: i32,
max_subst: i32,
max_err: i32,
}
impl RegApproxParams {
#[must_use]
#[inline]
pub const fn new() -> Self {
Self {
cost_ins: 0,
cost_del: 0,
cost_subst: 0,
max_cost: 0,
max_ins: 0,
max_del: 0,
max_subst: 0,
max_err: 0,
}
}
#[must_use]
#[inline]
pub const fn cost_ins(self, cost_ins: i32) -> Self {
let mut copy = self;
copy.cost_ins = cost_ins;
copy
}
#[must_use]
#[inline]
pub const fn cost_del(self, cost_del: i32) -> Self {
let mut copy = self;
copy.cost_del = cost_del;
copy
}
#[must_use]
#[inline]
pub const fn cost_subst(self, cost_subst: i32) -> Self {
let mut copy = self;
copy.cost_subst = cost_subst;
copy
}
#[must_use]
#[inline]
pub const fn max_cost(self, max_cost: i32) -> Self {
let mut copy = self;
copy.max_cost = max_cost;
copy
}
#[must_use]
#[inline]
pub const fn max_ins(self, max_ins: i32) -> Self {
let mut copy = self;
copy.max_ins = max_ins;
copy
}
#[must_use]
#[inline]
pub const fn max_del(self, max_del: i32) -> Self {
let mut copy = self;
copy.max_del = max_del;
copy
}
#[must_use]
#[inline]
pub const fn max_subst(self, max_subst: i32) -> Self {
let mut copy = self;
copy.max_subst = max_subst;
copy
}
#[must_use]
#[inline]
pub const fn max_err(self, max_err: i32) -> Self {
let mut copy = self;
copy.max_err = max_err;
copy
}
pub(crate) fn to_raw(self) -> Result<tre::regaparams_t> {
fn convert(value: i32) -> Result<c_int> {
c_int::try_from(value).map_err(|error| {
RegexError::new(
ErrorKind::Binding(BindingErrorCode::INVALID_APPROX_PARAM),
&format!("Approximate matching parameter is out of range: {error}"),
)
})
}
Ok(tre::regaparams_t {
cost_ins: convert(self.cost_ins)?,
cost_del: convert(self.cost_del)?,
cost_subst: convert(self.cost_subst)?,
max_cost: convert(self.max_cost)?,
max_ins: convert(self.max_ins)?,
max_del: convert(self.max_del)?,
max_subst: convert(self.max_subst)?,
max_err: convert(self.max_err)?,
})
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct RegApproxMatch<'a, T: ?Sized> {
data: &'a T,
matches: Vec<Option<&'a T>>,
cost: i32,
num_ins: i32,
num_del: i32,
num_subst: i32,
}
impl<'a, T: ?Sized> RegApproxMatch<'a, T> {
pub(crate) const fn new(
data: &'a T,
matches: Vec<Option<&'a T>>,
amatch: tre::regamatch_t,
) -> Self {
Self {
data,
matches,
cost: amatch.cost,
num_ins: amatch.num_ins,
num_del: amatch.num_del,
num_subst: amatch.num_subst,
}
}
#[must_use]
pub const fn cost(&self) -> i32 {
self.cost
}
#[must_use]
pub const fn num_ins(&self) -> i32 {
self.num_ins
}
#[must_use]
pub const fn num_del(&self) -> i32 {
self.num_del
}
#[must_use]
pub const fn num_subst(&self) -> i32 {
self.num_subst
}
#[must_use]
pub const fn get_orig_data(&self) -> &'a T {
self.data
}
#[must_use]
pub fn get_matches(&self) -> &[Option<&'a T>] {
&self.matches
}
#[must_use]
pub fn into_matches(self) -> Vec<Option<&'a T>> {
self.matches
}
}
impl Regex {
#[inline]
pub fn regaexec<'a>(
&self,
string: &'a str,
params: &RegApproxParams,
nmatches: usize,
flags: RegexecFlags,
) -> Result<RegApproxMatchStr<'a>> {
let Some(compiled_reg_obj) = self.as_raw() else {
return Err(RegexError::new(
ErrorKind::Binding(BindingErrorCode::REGEX_VACANT),
"Attempted to unwrap a vacant Regex object",
));
};
let data = string.as_bytes();
let mut match_vec = vec![tre::regmatch_t::default(); nmatches];
let mut amatch = tre::regamatch_t {
nmatch: nmatches,
pmatch: match_vec.as_mut_ptr(),
..Default::default()
};
let result_code = unsafe {
tre::tre_reganexec(
compiled_reg_obj,
data.as_ptr().cast(),
data.len(),
&raw mut amatch,
params.to_raw()?,
flags.bits(),
)
};
if result_code != 0 {
return Err(self.regerror(result_code));
}
let mut result = Vec::with_capacity(nmatches);
for pmatch in match_vec {
if pmatch.rm_so < 0 || pmatch.rm_eo < 0 {
result.push(None);
continue;
}
let start_offset = match_offset(pmatch.rm_so)?;
let end_offset = match_offset(pmatch.rm_eo)?;
let matched = string.get(start_offset..end_offset).ok_or_else(|| {
RegexError::new(
ErrorKind::Binding(BindingErrorCode::ENCODING),
"TRE returned match offsets that are not UTF-8 character boundaries",
)
})?;
result.push(Some(matched));
}
Ok(RegApproxMatchStr::new(string, result, amatch))
}
pub fn regaexec_bytes<'a>(
&self,
data: &'a [u8],
params: &RegApproxParams,
nmatches: usize,
flags: RegexecFlags,
) -> Result<RegApproxMatchBytes<'a>> {
let Some(compiled_reg_obj) = self.as_raw() else {
return Err(RegexError::new(
ErrorKind::Binding(BindingErrorCode::REGEX_VACANT),
"Attempted to unwrap a vacant Regex object",
));
};
let mut match_vec: Vec<tre::regmatch_t> =
vec![tre::regmatch_t { rm_so: 0, rm_eo: 0 }; nmatches];
let mut amatch = tre::regamatch_t {
nmatch: nmatches,
pmatch: match_vec.as_mut_ptr(),
..Default::default()
};
let mut nul_terminated_data = Vec::with_capacity(data.len() + 1);
nul_terminated_data.extend_from_slice(data);
nul_terminated_data.push(0);
let result = unsafe {
tre::tre_regaexecb(
compiled_reg_obj,
nul_terminated_data.as_ptr().cast(),
&raw mut amatch,
params.to_raw()?,
flags.bits(),
)
};
if result != 0 {
return Err(self.regerror(result));
}
let mut result = Vec::with_capacity(nmatches);
for pmatch in match_vec {
if pmatch.rm_so < 0 || pmatch.rm_eo < 0 {
result.push(None);
continue;
}
let start_offset = match_offset(pmatch.rm_so)?;
let end_offset = match_offset(pmatch.rm_eo)?;
result.push(Some(&data[start_offset..end_offset]));
}
Ok(RegApproxMatchBytes::new(data, result, amatch))
}
}
#[inline]
pub fn regaexec<'a>(
compiled_reg: &Regex,
string: &'a str,
params: &RegApproxParams,
nmatches: usize,
flags: RegexecFlags,
) -> Result<RegApproxMatchStr<'a>> {
compiled_reg.regaexec(string, params, nmatches, flags)
}
#[inline]
pub fn regaexec_bytes<'a>(
compiled_reg: &Regex,
data: &'a [u8],
params: &RegApproxParams,
nmatches: usize,
flags: RegexecFlags,
) -> Result<RegApproxMatchBytes<'a>> {
compiled_reg.regaexec_bytes(data, params, nmatches, flags)
}