use std::marker::PhantomData;
use std::ptr::NonNull;
use std::time::Duration;
use super::{Cancel, CancelHandle, Corpus, EngineHandle, PLANE};
use crate::Answer;
use crate::error::{self, Error};
use crate::sys;
const BATCH: usize = 32;
#[repr(C)]
struct CursorHandle {
_opaque: [u8; 0],
}
#[repr(C)]
struct Request {
struct_size: u32,
flags: u32,
max_count: u64,
before_context: u64,
after_context: u64,
pattern: *const u8,
pattern_len: usize,
timeout_ns: u64,
max_results: usize,
cancel: *const CancelHandle,
}
#[derive(Clone, Copy)]
#[repr(C)]
struct Raw {
path: sys::Text,
line: sys::Text,
spans: *const sys::Span,
nspans: usize,
line_number: u64,
kind: u32,
}
impl Default for Raw {
fn default() -> Self {
Self {
path: sys::Text::default(),
line: sys::Text::default(),
spans: std::ptr::null(),
nspans: 0,
line_number: 0,
kind: 0,
}
}
}
const FLAG_MAX_COUNT: u32 = 1 << 4;
const FLAG_INVERT: u32 = 1 << 7;
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
pub enum Kind {
#[default]
Line,
Context,
}
#[derive(Clone, Debug)]
pub struct Query<'p> {
pattern: &'p [u8],
flags: u32,
max_count: u64,
before: u64,
after: u64,
timeout: Option<Duration>,
max_results: Option<usize>,
cancel: Option<Cancel>,
}
impl<'p> Query<'p> {
#[must_use]
pub fn new(pattern: &'p [u8]) -> Self {
Self {
pattern,
flags: 0,
max_count: 0,
before: 0,
after: 0,
timeout: None,
max_results: None,
cancel: None,
}
}
#[must_use]
pub fn fixed(mut self, yes: bool) -> Self {
self.bit(sys::FIXED, yes);
self
}
#[must_use]
pub fn ignore_case(mut self, yes: bool) -> Self {
self.bit(sys::IGNORE_CASE, yes);
self
}
#[must_use]
pub fn smart_case(mut self, yes: bool) -> Self {
self.bit(sys::SMART_CASE, yes);
self
}
#[must_use]
pub fn word(mut self, yes: bool) -> Self {
self.bit(sys::WORD, yes);
self
}
#[must_use]
pub fn ascii(mut self, yes: bool) -> Self {
self.bit(sys::NO_UNICODE, yes);
self
}
#[must_use]
pub fn invert(mut self, yes: bool) -> Self {
self.bit(FLAG_INVERT, yes);
self
}
#[must_use]
pub fn max_count(mut self, n: Option<u64>) -> Self {
self.bit(FLAG_MAX_COUNT, n.is_some());
self.max_count = n.unwrap_or(0);
self
}
#[must_use]
pub fn context(mut self, before: u64, after: u64) -> Self {
self.before = before;
self.after = after;
self
}
#[must_use]
pub fn timeout(mut self, budget: Duration) -> Self {
self.timeout = Some(budget);
self
}
#[must_use]
pub fn max_results(mut self, n: usize) -> Self {
self.max_results = Some(n);
self
}
#[must_use]
pub fn cancel(mut self, cancel: &Cancel) -> Self {
self.cancel = Some(cancel.clone());
self
}
fn bit(&mut self, flag: u32, on: bool) {
if on {
self.flags |= flag;
} else {
self.flags &= !flag;
}
}
fn lower(&self) -> Request {
Request {
struct_size: size_of::<Request>() as u32,
flags: self.flags,
max_count: self.max_count,
before_context: self.before,
after_context: self.after,
pattern: self.pattern.as_ptr(),
pattern_len: self.pattern.len(),
timeout_ns: self.timeout.map_or(0, |budget| {
u64::try_from(budget.as_nanos()).unwrap_or(u64::MAX)
}),
max_results: self.max_results.unwrap_or(0),
cancel: self
.cancel
.as_ref()
.map_or(std::ptr::null(), Cancel::as_ptr),
}
}
}
impl Corpus {
pub fn search<'c>(&'c self, query: &Query<'_>) -> Result<Answer<Search<'c>>, Error> {
let request = query.lower();
let mut out: *mut CursorHandle = std::ptr::null_mut();
let status = unsafe {
ffi::irgx_tree_search(self.handle.as_ptr(), &raw const request, &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(Search {
handle,
cancel: query.cancel.clone(),
corpus: PhantomData,
})
})
.ok_or_else(|| Error::Inconsistent {
message: "the tree plane reported an answer and produced no cursor".to_owned(),
})
}
}
pub struct Search<'c> {
handle: NonNull<CursorHandle>,
cancel: Option<Cancel>,
corpus: PhantomData<&'c Corpus>,
}
impl<'c> Search<'c> {
#[must_use]
pub fn len(&self) -> usize {
unsafe { ffi::irgx_matches_count(self.handle.as_ptr()) }
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.len() == 0
}
pub fn records(&mut self) -> Records<'_> {
Records {
handle: self.handle,
buffer: [Raw::default(); BATCH],
filled: 0,
at: 0,
drained: false,
owner: PhantomData,
}
}
pub fn next_record(&mut self) -> Result<Option<Record<'_>>, Error> {
let mut raw = Raw::default();
let status = unsafe { ffi::irgx_matches_next(self.handle.as_ptr(), &raw mut raw) };
if status < 0 {
return Err(error::plane_fault(status, PLANE));
}
Ok((status == sys::MATCH).then_some(Record {
raw,
owner: PhantomData,
}))
}
}
impl Drop for Search<'_> {
fn drop(&mut self) {
unsafe { ffi::irgx_matches_close(self.handle.as_ptr()) };
drop(self.cancel.take());
}
}
pub struct Records<'s> {
handle: NonNull<CursorHandle>,
buffer: [Raw; BATCH],
filled: usize,
at: usize,
drained: bool,
owner: PhantomData<&'s mut ()>,
}
impl<'s> Iterator for Records<'s> {
type Item = Result<Record<'s>, Error>;
fn next(&mut self) -> Option<Self::Item> {
if self.at == self.filled {
if self.drained {
return None;
}
let mut written = 0usize;
let status = unsafe {
ffi::irgx_matches_next_batch(
self.handle.as_ptr(),
self.buffer.as_mut_ptr(),
BATCH,
&raw mut written,
)
};
if status < 0 {
self.drained = true;
return Some(Err(error::plane_fault(status, PLANE)));
}
if written == 0 {
self.drained = true;
return None;
}
self.drained = written < BATCH;
self.filled = written;
self.at = 0;
}
let raw = self.buffer[self.at];
self.at += 1;
Some(Ok(Record {
raw,
owner: PhantomData,
}))
}
}
#[derive(Clone, Copy)]
pub struct Record<'s> {
raw: Raw,
owner: PhantomData<&'s ()>,
}
impl<'s> Record<'s> {
#[must_use]
pub fn path(&self) -> &'s [u8] {
unsafe { sys::borrowed(&self.raw.path) }
}
#[must_use]
pub fn path_str(&self) -> Option<&'s str> {
std::str::from_utf8(self.path()).ok()
}
#[must_use]
pub fn line(&self) -> &'s [u8] {
unsafe { sys::borrowed(&self.raw.line) }
}
#[must_use]
pub fn line_str(&self) -> Option<&'s str> {
std::str::from_utf8(self.line()).ok()
}
#[must_use]
pub fn line_number(&self) -> u64 {
self.raw.line_number
}
#[must_use]
pub fn kind(&self) -> Kind {
match self.raw.kind {
1 => Kind::Context,
_ => Kind::Line,
}
}
pub fn highlights(&self) -> impl ExactSizeIterator<Item = std::ops::Range<usize>> + 's {
let spans: &'s [sys::Span] = if self.raw.spans.is_null() || self.raw.nspans == 0 {
&[]
} else {
unsafe { std::slice::from_raw_parts(self.raw.spans, self.raw.nspans) }
};
spans
.iter()
.filter_map(|span| span.range())
.map(|(start, end)| start..end)
.collect::<Vec<_>>()
.into_iter()
}
}
impl std::fmt::Debug for Record<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Record")
.field("path", &String::from_utf8_lossy(self.path()))
.field("line_number", &self.raw.line_number)
.field("kind", &self.kind())
.field("line", &String::from_utf8_lossy(self.line()))
.finish()
}
}
mod ffi {
use super::{CursorHandle, EngineHandle, Raw, Request};
unsafe extern "C" {
pub fn irgx_tree_search(
engine: *mut EngineHandle,
req: *const Request,
out: *mut *mut CursorHandle,
) -> i32;
pub fn irgx_matches_next(cursor: *mut CursorHandle, out: *mut Raw) -> i32;
pub fn irgx_matches_next_batch(
cursor: *mut CursorHandle,
out: *mut Raw,
cap: usize,
written: *mut usize,
) -> i32;
pub fn irgx_matches_count(cursor: *const CursorHandle) -> usize;
pub fn irgx_matches_close(cursor: *mut CursorHandle);
}
}