use std::{
any::Any,
ffi::{CString, c_int, c_void},
ops::ControlFlow,
panic::{AssertUnwindSafe, catch_unwind, resume_unwind},
ptr::{self, NonNull},
};
use javelin_sys as sys;
use crate::{
Error,
bytes::{Captures, Match},
};
#[derive(Debug)]
pub(crate) struct Code {
raw: NonNull<c_void>,
pub(crate) capture_count: usize,
}
unsafe impl Send for Code {}
unsafe impl Sync for Code {}
impl Code {
pub(crate) fn new(pattern: &str, options: c_int) -> Result<Self, Error> {
let pattern = CString::new(pattern).map_err(|err| Error::NulByte(err.nul_position()))?;
let mut raw = ptr::null_mut();
let result = unsafe { sys::jp_pattern_compile(&mut raw, pattern.as_ptr(), options) };
if result != sys::JP_RESULT_OK {
return Err(Error::Compile(result));
}
let raw = NonNull::new(raw).ok_or(Error::Compile(sys::JP_RESULT_INTERNAL_ERROR))?;
let mut code = Self {
raw,
capture_count: 0,
};
code.capture_count = unsafe { sys::jp_get_number_of_captures(code.raw.as_ptr()) } as usize;
assert!(
(1..=256).contains(&code.capture_count),
"invalid native capture count"
);
Ok(code)
}
pub(crate) fn is_match(&self, subject: &[u8], start: usize) -> bool {
if start > subject.len() {
return false;
}
unsafe {
sys::jp_has_partial_match(
self.raw.as_ptr(),
subject.as_ptr().cast(),
subject.len(),
start,
)
}
}
pub(crate) fn is_full_match(&self, subject: &[u8]) -> bool {
unsafe { sys::jp_has_full_match(self.raw.as_ptr(), subject.as_ptr().cast(), subject.len()) }
}
pub(crate) fn captures<'s>(&self, subject: &'s [u8], start: usize) -> Option<Captures<'s>> {
if start > subject.len() {
return None;
}
let mut pointers = vec![ptr::null(); 2 * self.capture_count];
let matched = unsafe {
sys::jp_partial_match(
self.raw.as_ptr(),
subject.as_ptr().cast(),
subject.len(),
pointers.as_mut_ptr(),
start,
)
};
matched.then(|| captures_from_pointers(subject, &pointers))
}
pub(crate) fn scan<'s, F, B>(
&self,
subject: &'s [u8],
start: usize,
callback: F,
) -> ControlFlow<B>
where
F: FnMut(Match<'s>) -> ControlFlow<B>,
{
let mut state = ScanState::new(subject, callback, self.capture_count);
unsafe {
sys::jp_scan(
self.raw.as_ptr(),
subject.as_ptr().cast(),
subject.len(),
(&mut state as *mut ScanState<'s, F, B>).cast(),
Some(on_match::<F, B>),
start,
);
}
state.finish()
}
pub(crate) fn scan_captures<'s, F, B>(
&self,
subject: &'s [u8],
start: usize,
callback: F,
) -> ControlFlow<B>
where
F: FnMut(Captures<'s>) -> ControlFlow<B>,
{
let mut state = ScanState::new(subject, callback, self.capture_count);
unsafe {
sys::jp_scan_captures(
self.raw.as_ptr(),
subject.as_ptr().cast(),
subject.len(),
(&mut state as *mut ScanState<'s, F, B>).cast(),
Some(on_captures::<F, B>),
start,
);
}
state.finish()
}
}
impl Drop for Code {
fn drop(&mut self) {
unsafe { sys::jp_pattern_free(self.raw.as_ptr()) };
}
}
fn captures_from_pointers<'s>(subject: &'s [u8], pointers: &[*const c_void]) -> Captures<'s> {
let ranges = pointers
.chunks_exact(2)
.map(|pair| {
if pair[0].is_null() || pair[1].is_null() {
return None;
}
let start = (pair[0] as usize)
.checked_sub(subject.as_ptr() as usize)
.expect("invalid capture start");
let end = (pair[1] as usize)
.checked_sub(subject.as_ptr() as usize)
.expect("invalid capture end");
let matched = Match::new(subject, start, end);
Some((matched.start(), matched.end()))
})
.collect();
Captures { subject, ranges }
}
struct ScanState<'s, F, B> {
subject: &'s [u8],
callback: F,
capture_count: usize,
result: ControlFlow<B>,
panic: Option<Box<dyn Any + Send>>,
}
impl<'s, F, B> ScanState<'s, F, B> {
fn new(subject: &'s [u8], callback: F, capture_count: usize) -> Self {
Self {
subject,
callback,
capture_count,
result: ControlFlow::Continue(()),
panic: None,
}
}
fn call(&mut self, invoke: impl FnOnce(&'s [u8], &mut F) -> ControlFlow<B>) -> c_int {
match catch_unwind(AssertUnwindSafe(|| {
invoke(self.subject, &mut self.callback)
})) {
Ok(ControlFlow::Continue(())) => 0,
Ok(ControlFlow::Break(value)) => {
self.result = ControlFlow::Break(value);
1
}
Err(panic) => {
self.panic = Some(panic);
1
}
}
}
fn finish(self) -> ControlFlow<B> {
if let Some(panic) = self.panic {
resume_unwind(panic);
}
self.result
}
}
unsafe extern "C" fn on_match<'s, F, B>(from: usize, to: usize, user: *mut c_void) -> c_int
where
F: FnMut(Match<'s>) -> ControlFlow<B>,
{
let state = unsafe { &mut *user.cast::<ScanState<'s, F, B>>() };
state.call(|subject, callback| callback(Match::new(subject, from, to)))
}
unsafe extern "C" fn on_captures<'s, F, B>(
pointers: *const *const c_void,
count: usize,
user: *mut c_void,
) -> c_int
where
F: FnMut(Captures<'s>) -> ControlFlow<B>,
{
let state = unsafe { &mut *user.cast::<ScanState<'s, F, B>>() };
let expected = state.capture_count;
state.call(|subject, callback| {
assert_eq!(count, expected, "invalid native capture count");
let pointers = unsafe { std::slice::from_raw_parts(pointers, 2 * count) };
callback(captures_from_pointers(subject, pointers))
})
}