use std::ptr::NonNull;
use crate::error::{Error, fault};
use crate::pattern::expect;
use crate::pool::{Pool, Recipe};
use crate::sys;
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
pub enum Pick {
#[default]
Longest,
Shortest,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum Why {
Syntax,
States,
WordContext,
BufferAnchor,
Unknown(u32),
}
impl Why {
fn of(raw: u32) -> Self {
match raw {
sys::MUNCH_SYNTAX => Self::Syntax,
sys::MUNCH_STATES => Self::States,
sys::MUNCH_WORD_CONTEXT => Self::WordContext,
sys::MUNCH_BUFFER_ANCHOR => Self::BufferAnchor,
other => Self::Unknown(other),
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct Refusal {
pub pattern: usize,
pub why: Why,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Token {
len: usize,
patterns: Box<[u32]>,
}
impl Token {
#[must_use]
pub fn len(&self) -> usize {
self.len
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.len == 0
}
#[must_use]
pub fn patterns(&self) -> &[u32] {
&self.patterns
}
#[must_use]
pub fn range(&self, at: usize) -> std::ops::Range<usize> {
at..at + self.len
}
}
pub struct Munch {
pool: Pool<Anchored>,
declined: Box<[Refusal]>,
admitted: usize,
}
impl Munch {
pub fn new<I, S>(patterns: I) -> Result<Self, Error>
where
S: AsRef<str>,
I: IntoIterator<Item = S>,
{
MunchBuilder::new(patterns).build()
}
#[must_use]
pub fn patterns(&self) -> &[String] {
&self.pool.recipe().patterns
}
#[must_use]
pub fn len(&self) -> usize {
self.patterns().len()
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.patterns().is_empty()
}
#[must_use]
pub fn declined(&self) -> &[Refusal] {
&self.declined
}
#[must_use]
pub fn admitted(&self) -> usize {
self.admitted
}
#[must_use]
pub fn token(&self, text: &str, at: usize) -> Option<Token> {
expect(self.try_token(text, at))
}
pub fn try_token(&self, text: &str, at: usize) -> Result<Option<Token>, Error> {
self.owned(text, at, None, Pick::Longest)
}
#[must_use]
pub fn token_among(&self, text: &str, at: usize, allow: &[u32]) -> Option<Token> {
expect(self.try_token_among(text, at, allow))
}
pub fn try_token_among(
&self,
text: &str,
at: usize,
allow: &[u32],
) -> Result<Option<Token>, Error> {
self.owned(text, at, Some(allow), Pick::Longest)
}
#[must_use]
pub fn shortest_among(&self, text: &str, at: usize, allow: &[u32]) -> Option<Token> {
expect(self.try_shortest_among(text, at, allow))
}
pub fn try_shortest_among(
&self,
text: &str,
at: usize,
allow: &[u32],
) -> Result<Option<Token>, Error> {
self.owned(text, at, Some(allow), Pick::Shortest)
}
pub fn scan_into(
&self,
text: &str,
at: usize,
allow: Option<&[u32]>,
pick: Pick,
winners: &mut Vec<u32>,
) -> Result<Option<usize>, Error> {
winners.clear();
let body = text.as_bytes();
if at > body.len() {
return Err(Error::Inconsistent {
message: format!("scanned at {at} in a text of {} bytes", body.len()),
});
}
let room = self.admitted;
winners.resize(room, 0);
let mut tok = sys::MunchToken::default();
let lease = self.pool.lease()?;
let status = unsafe {
sys::irgx_munch_scan(
lease.raw(),
body.as_ptr(),
body.len(),
at,
allow.map_or(std::ptr::null(), <[u32]>::as_ptr),
allow.map_or(0, <[u32]>::len),
match pick {
Pick::Longest => sys::MUNCH_LONGEST,
Pick::Shortest => sys::MUNCH_SHORTEST,
},
&raw mut tok,
winners.as_mut_ptr(),
room,
)
};
drop(lease);
if status < 0 {
winners.clear();
return Err(fault(status, |status, detail| Error::Search {
status,
detail,
}));
}
if status != sys::MATCH {
winners.clear();
return Ok(None);
}
if tok.count > room {
winners.clear();
return Err(Error::Inconsistent {
message: format!(
"a slate admitting {room} patterns reported {} winning",
tok.count
),
});
}
winners.truncate(tok.count);
Ok(Some(tok.len))
}
fn owned(
&self,
text: &str,
at: usize,
allow: Option<&[u32]>,
pick: Pick,
) -> Result<Option<Token>, Error> {
let mut winners = Vec::new();
let Some(len) = self.scan_into(text, at, allow, pick, &mut winners)? else {
return Ok(None);
};
Ok(Some(Token {
len,
patterns: winners.into(),
}))
}
}
#[derive(Clone, Debug)]
pub struct MunchBuilder {
patterns: Vec<String>,
flags: u32,
}
impl MunchBuilder {
#[must_use]
pub fn new<I, S>(patterns: I) -> Self
where
S: AsRef<str>,
I: IntoIterator<Item = S>,
{
Self {
patterns: patterns
.into_iter()
.map(|one| one.as_ref().to_owned())
.collect(),
flags: 0,
}
}
pub fn ignore_case(&mut self, yes: bool) -> &mut Self {
self.set(sys::IGNORE_CASE, yes)
}
pub fn unicode(&mut self, yes: bool) -> &mut Self {
self.set(sys::NO_UNICODE, !yes)
}
pub fn dot_matches_new_line(&mut self, yes: bool) -> &mut Self {
self.set(sys::DOTALL, yes)
}
pub fn build(&self) -> Result<Munch, Error> {
let pool = Pool::new(Anchored {
patterns: self.patterns.clone().into(),
flags: self.flags,
})?;
let (declined, admitted) = survey(&pool)?;
Ok(Munch {
pool,
declined,
admitted,
})
}
fn set(&mut self, bit: u32, yes: bool) -> &mut Self {
if yes {
self.flags |= bit;
} else {
self.flags &= !bit;
}
self
}
}
fn survey(pool: &Pool<Anchored>) -> Result<(Box<[Refusal]>, usize), Error> {
let total = pool.recipe().patterns.len();
let mut raw = vec![sys::MunchRefusal::default(); total];
let lease = pool.lease()?;
let mut written: usize = 0;
let status = unsafe {
sys::irgx_munch_declined(lease.raw(), raw.as_mut_ptr(), raw.len(), &raw mut written)
};
let admitted = unsafe { sys::irgx_munch_len(lease.raw()) };
drop(lease);
if status < 0 {
return Err(fault(status, |status, detail| Error::Search {
status,
detail,
}));
}
if written > total || admitted + written != total {
return Err(Error::Inconsistent {
message: format!(
"a slate of {total} patterns reported {admitted} seated and {written} declined"
),
});
}
let refusals = raw[..written]
.iter()
.map(|r| Refusal {
pattern: r.pattern as usize,
why: Why::of(r.why),
})
.collect();
Ok((refusals, admitted))
}
struct Anchored {
patterns: Box<[String]>,
flags: u32,
}
impl Recipe for Anchored {
type Raw = sys::Munch;
fn compile(&self) -> Result<NonNull<sys::Munch>, Error> {
let list: Vec<sys::MunchPattern> = self
.patterns
.iter()
.map(|one| sys::MunchPattern {
pattern: one.as_ptr(),
len: one.len(),
})
.collect();
let mut out: *mut sys::Munch = std::ptr::null_mut();
let status =
unsafe { sys::irgx_munch_compile(list.as_ptr(), list.len(), self.flags, &raw mut out) };
if status == sys::STALE {
return Err(Error::NothingLexable {
offered: self.patterns.len(),
});
}
if status < 0 {
return Err(fault(status, |status, detail| Error::Search {
status,
detail,
}));
}
crate::pool::wrote(out, "irgx_munch_compile")
}
unsafe fn release(raw: NonNull<sys::Munch>) {
unsafe { sys::irgx_munch_free(raw.as_ptr()) }
}
}
impl Clone for Munch {
fn clone(&self) -> Self {
let slate = self.pool.recipe();
expect(
MunchBuilder {
patterns: slate.patterns.to_vec(),
flags: slate.flags,
}
.build(),
)
}
}
impl std::fmt::Debug for Munch {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Munch")
.field("patterns", &self.patterns())
.field("declined", &self.declined)
.finish()
}
}