use std::path::Path;
use std::ptr::NonNull;
use crate::error::{self, Error};
use crate::sink;
use crate::sys;
use crate::{Answer, Regex};
const PLANE: &str = "sieve";
const PLAN: &str = "winnow";
#[repr(C)]
struct SieveHandle {
_opaque: [u8; 0],
}
#[repr(C)]
struct WinnowHandle {
_opaque: [u8; 0],
}
#[derive(Clone, Copy, Debug)]
#[repr(C)]
pub struct Facts {
struct_size: u32,
doc_count: u32,
path_count: u32,
posting_count: u32,
root_count: u32,
has_crest: u32,
has_codicil: u32,
reserved: u32,
}
impl Default for Facts {
fn default() -> Self {
Self {
struct_size: size_of::<Self>() as u32,
doc_count: 0,
path_count: 0,
posting_count: 0,
root_count: 0,
has_crest: 0,
has_codicil: 0,
reserved: 0,
}
}
}
impl Facts {
#[must_use]
pub fn documents(&self) -> u32 {
self.doc_count
}
#[must_use]
pub fn paths(&self) -> u32 {
self.path_count
}
#[must_use]
pub fn postings(&self) -> u32 {
self.posting_count
}
#[must_use]
pub fn roots(&self) -> u32 {
self.root_count
}
#[must_use]
pub fn has_crest(&self) -> bool {
self.has_crest != 0
}
#[must_use]
pub fn has_codicil(&self) -> bool {
self.has_codicil != 0
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum Freshness {
Anchored {
anchor_ns: i64,
},
Unanchored,
Foreign {
anchor_ns: i64,
},
Unknown {
state: i32,
},
}
#[derive(Clone, Copy)]
#[repr(C)]
struct RawFreshness {
struct_size: u32,
state: i32,
anchor_ns: i64,
reserved: i32,
}
impl Default for RawFreshness {
fn default() -> Self {
Self {
struct_size: size_of::<Self>() as u32,
state: 0,
anchor_ns: 0,
reserved: 0,
}
}
}
impl Freshness {
#[must_use]
pub fn is_usable(&self) -> bool {
matches!(self, Self::Anchored { .. })
}
#[must_use]
pub fn anchor_ns(&self) -> Option<i64> {
match self {
Self::Anchored { anchor_ns } | Self::Foreign { anchor_ns } => Some(*anchor_ns),
Self::Unanchored | Self::Unknown { .. } => None,
}
}
}
#[derive(Clone, Copy, Debug)]
#[repr(C)]
pub struct WinnowFacts {
struct_size: u32,
flags: u32,
clauses: u32,
atoms: u32,
literals: u32,
alternatives: u32,
sieve_active: u32,
idle: u32,
}
impl Default for WinnowFacts {
fn default() -> Self {
Self {
struct_size: size_of::<Self>() as u32,
flags: 0,
clauses: 0,
atoms: 0,
literals: 0,
alternatives: 0,
sieve_active: 0,
idle: 0,
}
}
}
impl WinnowFacts {
#[must_use]
pub fn clauses(&self) -> u32 {
self.clauses
}
#[must_use]
pub fn atoms(&self) -> u32 {
self.atoms
}
#[must_use]
pub fn literals(&self) -> u32 {
self.literals
}
#[must_use]
pub fn alternatives(&self) -> u32 {
self.alternatives
}
#[must_use]
pub fn sieve_active(&self) -> bool {
self.sieve_active != 0
}
#[must_use]
pub fn is_idle(&self) -> bool {
self.idle != 0
}
}
pub struct Sieve {
handle: NonNull<SieveHandle>,
}
impl Sieve {
pub fn here() -> Result<Answer<Self>, Error> {
Self::opened(std::ptr::null(), 0)
}
pub fn at(dir: &Path) -> Result<Answer<Self>, Error> {
let bytes = dir.as_os_str().as_encoded_bytes();
if bytes.is_empty() {
return Err(Error::Plane {
plane: PLANE,
status: crate::Status::INVALID,
detail: Some("an empty directory is not an artifact location".to_owned()),
});
}
Self::opened(bytes.as_ptr(), bytes.len())
}
fn opened(dir: *const u8, len: usize) -> Result<Answer<Self>, Error> {
let mut out: *mut SieveHandle = std::ptr::null_mut();
let status = unsafe { ffi::irgx_sieve_open(dir, len, &raw mut out) };
if status == sys::STALE {
return Ok(Answer::Declined);
}
if status < 0 {
return Err(error::plane_fault(status, PLANE));
}
NonNull::new(out)
.map(|handle| Answer::Given(Self { handle }))
.ok_or_else(|| Error::Inconsistent {
message: "the sieve plane reported success and produced no handle".to_owned(),
})
}
pub fn facts(&self) -> Result<Facts, Error> {
let mut out = Facts::default();
let status = unsafe { ffi::irgx_sieve_describe(self.handle.as_ptr(), &raw mut out) };
if status < 0 {
return Err(error::plane_fault(status, PLANE));
}
Ok(out)
}
pub fn doc_path(&self, doc: u32) -> Result<&[u8], Error> {
self.text(|handle, out| {
unsafe { ffi::irgx_sieve_doc_path(handle, doc, out) }
})
}
pub fn root(&self, i: u32) -> Result<&[u8], Error> {
self.text(|handle, out| {
unsafe { ffi::irgx_sieve_root(handle, i, out) }
})
}
fn text(
&self,
read: impl FnOnce(*const SieveHandle, *mut sys::Text) -> i32,
) -> Result<&[u8], Error> {
let mut out = sys::Text::default();
let status = read(self.handle.as_ptr(), &raw mut out);
if status < 0 {
return Err(error::plane_fault(status, PLANE));
}
Ok(unsafe { sys::borrowed(&out) })
}
pub fn literal(&self, needle: &[u8]) -> Result<Answer<Vec<u32>>, Error> {
sink::reap(PLANE, CANDIDATE_GUESS, |out, cap, written| {
unsafe {
ffi::irgx_sieve_literal(
self.handle.as_ptr(),
needle.as_ptr(),
needle.len(),
out,
cap,
written,
)
}
})
}
pub fn alternation<N: AsRef<[u8]>>(&self, needles: &[N]) -> Result<Answer<Vec<u32>>, Error> {
let list: Vec<sys::Text> = needles
.iter()
.map(|needle| {
let bytes = needle.as_ref();
sys::Text {
ptr: bytes.as_ptr(),
len: bytes.len(),
}
})
.collect();
sink::reap(PLANE, CANDIDATE_GUESS, |out, cap, written| {
unsafe {
ffi::irgx_sieve_alternation(
self.handle.as_ptr(),
list.as_ptr(),
list.len(),
out,
cap,
written,
)
}
})
}
pub fn candidates(&self, plan: &Winnow) -> Result<Answer<Vec<u32>>, Error> {
self.admitted(plan, |handle, w, out, cap, written| {
unsafe { ffi::irgx_sieve_candidates(handle, w, out, cap, written) }
})
}
pub fn reading_list(&self, plan: &Winnow) -> Result<Answer<Vec<u32>>, Error> {
self.admitted(plan, |handle, w, out, cap, written| {
unsafe { ffi::irgx_sieve_reading_list(handle, w, out, cap, written) }
})
}
fn admitted(
&self,
plan: &Winnow,
mut ask: impl FnMut(*mut SieveHandle, *const WinnowHandle, *mut u32, usize, *mut usize) -> i32,
) -> Result<Answer<Vec<u32>>, Error> {
let hint = self.facts().map_or(CANDIDATE_GUESS, |facts| {
(facts.documents() as usize).min(MAX_GUESS)
});
sink::reap(PLANE, hint, |out, cap, written| {
ask(
self.handle.as_ptr(),
plan.handle.as_ptr(),
out,
cap,
written,
)
})
}
pub fn freshness(&self) -> Result<Freshness, Error> {
let mut out = RawFreshness::default();
let status = unsafe { ffi::irgx_sieve_freshness(self.handle.as_ptr(), &raw mut out) };
if status < 0 {
return Err(error::plane_fault(status, PLANE));
}
Ok(match out.state {
1 => Freshness::Anchored {
anchor_ns: out.anchor_ns,
},
2 => Freshness::Unanchored,
3 => Freshness::Foreign {
anchor_ns: out.anchor_ns,
},
state => Freshness::Unknown { state },
})
}
pub fn stale_count(&self) -> Result<Answer<usize>, Error> {
let mut out = 0usize;
let status = unsafe { ffi::irgx_sieve_stale_count(self.handle.as_ptr(), &raw mut out) };
if status == sys::STALE {
return Ok(Answer::Declined);
}
if status < 0 {
return Err(error::plane_fault(status, PLANE));
}
Ok(Answer::Given(out))
}
}
const CANDIDATE_GUESS: usize = 256;
const MAX_GUESS: usize = 4096;
impl Drop for Sieve {
fn drop(&mut self) {
unsafe { ffi::irgx_sieve_close(self.handle.as_ptr()) };
}
}
impl std::fmt::Debug for Sieve {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Sieve")
.field("facts", &self.facts().ok())
.field("freshness", &self.freshness().ok())
.finish()
}
}
pub struct Winnow {
handle: NonNull<WinnowHandle>,
}
impl Winnow {
pub fn of(re: &Regex) -> Result<Self, Error> {
let mut out: *mut WinnowHandle = std::ptr::null_mut();
let status = re.with_handle(|raw| unsafe { ffi::irgx_winnow_of(raw, &raw mut out) })?;
if status < 0 {
return Err(error::plane_fault(status, PLAN));
}
NonNull::new(out)
.map(|handle| Self { handle })
.ok_or_else(|| Error::Inconsistent {
message: "the winnow plane reported success and produced no plan".to_owned(),
})
}
pub fn facts(&self) -> Result<WinnowFacts, Error> {
let mut out = WinnowFacts::default();
let status = unsafe { ffi::irgx_winnow_describe(self.handle.as_ptr(), &raw mut out) };
if status < 0 {
return Err(error::plane_fault(status, PLAN));
}
Ok(out)
}
}
impl Drop for Winnow {
fn drop(&mut self) {
unsafe { ffi::irgx_winnow_free(self.handle.as_ptr()) };
}
}
impl std::fmt::Debug for Winnow {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Winnow")
.field("facts", &self.facts().ok())
.finish()
}
}
mod ffi {
use super::{Facts, RawFreshness, SieveHandle, WinnowFacts, WinnowHandle, sys};
unsafe extern "C" {
pub fn irgx_sieve_open(dir: *const u8, dir_len: usize, out: *mut *mut SieveHandle) -> i32;
pub fn irgx_sieve_close(s: *mut SieveHandle);
pub fn irgx_sieve_describe(s: *const SieveHandle, out: *mut Facts) -> i32;
pub fn irgx_sieve_doc_path(s: *const SieveHandle, doc: u32, out: *mut sys::Text) -> i32;
pub fn irgx_sieve_root(s: *const SieveHandle, i: u32, out: *mut sys::Text) -> i32;
pub fn irgx_sieve_literal(
s: *mut SieveHandle,
needle: *const u8,
len: usize,
out: *mut u32,
cap: usize,
written: *mut usize,
) -> i32;
pub fn irgx_sieve_alternation(
s: *mut SieveHandle,
needles: *const sys::Text,
n: usize,
out: *mut u32,
cap: usize,
written: *mut usize,
) -> i32;
pub fn irgx_sieve_candidates(
s: *mut SieveHandle,
w: *const WinnowHandle,
out: *mut u32,
cap: usize,
written: *mut usize,
) -> i32;
pub fn irgx_sieve_reading_list(
s: *mut SieveHandle,
w: *const WinnowHandle,
out: *mut u32,
cap: usize,
written: *mut usize,
) -> i32;
pub fn irgx_sieve_freshness(s: *const SieveHandle, out: *mut RawFreshness) -> i32;
pub fn irgx_sieve_stale_count(s: *const SieveHandle, out: *mut usize) -> i32;
pub fn irgx_winnow_of(re: *mut sys::Regex, out: *mut *mut WinnowHandle) -> i32;
pub fn irgx_winnow_free(w: *mut WinnowHandle);
pub fn irgx_winnow_describe(w: *const WinnowHandle, out: *mut WinnowFacts) -> i32;
}
}