use std::ptr::NonNull;
use crate::error::{self, Error};
use crate::sink;
use crate::sys;
const PLANE: &str = "needles";
#[repr(C)]
struct Handle {
_opaque: [u8; 0],
}
#[repr(C)]
struct Term {
needle: *const u8,
len: usize,
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
pub enum Tier {
#[default]
None,
Memmem,
LiteralSet,
Trawl,
}
impl Tier {
fn from_abi(raw: u32) -> Self {
match raw {
1 => Self::Memmem,
2 => Self::LiteralSet,
3 => Self::Trawl,
_ => Self::None,
}
}
}
#[derive(Clone, Copy, Debug)]
#[repr(C)]
pub struct Shape {
struct_size: u32,
presence_tier: u32,
attributed_tier: u32,
reserved: u32,
count: usize,
longest: usize,
bytes: usize,
}
impl Default for Shape {
fn default() -> Self {
Self {
struct_size: size_of::<Self>() as u32,
presence_tier: 0,
attributed_tier: 0,
reserved: 0,
count: 0,
longest: 0,
bytes: 0,
}
}
}
impl Shape {
#[must_use]
pub fn presence_tier(&self) -> Tier {
Tier::from_abi(self.presence_tier)
}
#[must_use]
pub fn attributed_tier(&self) -> Tier {
Tier::from_abi(self.attributed_tier)
}
#[must_use]
pub fn len(&self) -> usize {
self.count
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.count == 0
}
#[must_use]
pub fn longest(&self) -> usize {
self.longest
}
#[must_use]
pub fn bytes(&self) -> usize {
self.bytes
}
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
#[repr(C)]
pub struct Occurrence {
needle: u32,
reserved: u32,
start: usize,
end: usize,
}
impl Occurrence {
#[must_use]
pub fn needle(&self) -> usize {
self.needle as usize
}
#[must_use]
pub fn range(&self) -> std::ops::Range<usize> {
self.start..self.end
}
#[must_use]
pub fn as_bytes<'t>(&self, text: &'t [u8]) -> &'t [u8] {
&text[self.start..self.end]
}
}
pub struct Needles {
handle: NonNull<Handle>,
offered: usize,
}
impl Needles {
pub fn new<T: AsRef<[u8]>>(terms: &[T]) -> Result<Self, Error> {
let list: Vec<Term> = terms
.iter()
.map(|term| {
let bytes = term.as_ref();
Term {
needle: bytes.as_ptr(),
len: bytes.len(),
}
})
.collect();
let mut out: *mut Handle = std::ptr::null_mut();
const NO_FLAGS: u32 = 0;
let mut culprit = usize::MAX;
let status = unsafe {
ffi::irgx_needles_compile(
list.as_ptr(),
list.len(),
NO_FLAGS,
&raw mut culprit,
&raw mut out,
)
};
if status < 0 {
return Err(refusal(status, culprit, list.len()));
}
let handle = NonNull::new(out).ok_or_else(|| Error::Inconsistent {
message: "the needle plane reported success and produced no handle".to_owned(),
})?;
Ok(Self {
handle,
offered: list.len(),
})
}
#[must_use]
pub fn seated(&self) -> usize {
unsafe { ffi::irgx_needles_len(self.handle.as_ptr()) }
}
#[must_use]
pub fn offered(&self) -> usize {
self.offered
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.seated() == 0
}
pub fn shape(&self) -> Result<Shape, Error> {
let mut out = Shape::default();
let status = unsafe { ffi::irgx_needles_describe(self.handle.as_ptr(), &raw mut out) };
if status < 0 {
return Err(error::plane_fault(status, PLANE));
}
Ok(out)
}
pub fn is_match(&self, text: &[u8]) -> Result<bool, Error> {
let status =
unsafe { ffi::irgx_needles_is_match(self.handle.as_ptr(), text.as_ptr(), text.len()) };
if status < 0 {
return Err(error::plane_fault(status, PLANE));
}
Ok(status == sys::MATCH)
}
pub fn which(&self, text: &[u8]) -> Result<Vec<u32>, Error> {
sink::reap_all(PLANE, self.seated(), |out, cap, written| {
unsafe {
ffi::irgx_needles_which(
self.handle.as_ptr(),
text.as_ptr(),
text.len(),
out,
cap,
written,
)
}
})
}
pub fn find_all(&self, text: &[u8]) -> Result<Vec<Occurrence>, Error> {
const FIRST_GUESS: usize = 64;
sink::reap_all(PLANE, FIRST_GUESS, |out, cap, written| {
unsafe {
ffi::irgx_needles_find_all(
self.handle.as_ptr(),
text.as_ptr(),
text.len(),
out,
cap,
written,
)
}
})
}
}
impl Drop for Needles {
fn drop(&mut self) {
unsafe { ffi::irgx_needles_free(self.handle.as_ptr()) };
}
}
impl std::fmt::Debug for Needles {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Needles")
.field("seated", &self.seated())
.field("shape", &self.shape().ok())
.finish()
}
}
fn refusal(status: i32, culprit: usize, offered: usize) -> Error {
let plane = error::plane_fault(status, PLANE);
let (Error::Plane { status, detail, .. }, true) = (&plane, culprit < offered) else {
return plane;
};
let named = match detail {
Some(why) => format!("term #{culprit}: {why}"),
None => format!("term #{culprit}"),
};
Error::Plane {
plane: PLANE,
status: *status,
detail: Some(named),
}
}
mod ffi {
use super::{Handle, Occurrence, Shape, Term};
unsafe extern "C" {
pub fn irgx_needles_compile(
list: *const Term,
count: usize,
flags: u32,
refused: *mut usize,
out: *mut *mut Handle,
) -> i32;
pub fn irgx_needles_free(handle: *mut Handle);
pub fn irgx_needles_len(handle: *const Handle) -> usize;
pub fn irgx_needles_describe(handle: *const Handle, out: *mut Shape) -> i32;
pub fn irgx_needles_is_match(handle: *mut Handle, text: *const u8, len: usize) -> i32;
pub fn irgx_needles_which(
handle: *mut Handle,
text: *const u8,
len: usize,
out: *mut u32,
cap: usize,
written: *mut usize,
) -> i32;
pub fn irgx_needles_find_all(
handle: *mut Handle,
text: *const u8,
len: usize,
out: *mut Occurrence,
cap: usize,
written: *mut usize,
) -> i32;
}
}