use std::ffi::CString;
use std::marker::PhantomData;
use std::path::Path;
use std::ptr::{self, NonNull};
use std::rc::Rc;
use crate::{Error, ErrorKind, Query, Result, Tree, ffi};
pub struct Bank {
pub(crate) raw: NonNull<ffi::Handle>,
_thread: PhantomData<Rc<()>>,
}
impl Bank {
pub fn open(path: impl AsRef<Path>) -> Result<Self> {
if crate::abi_version() != 2 {
return Err(Error::new(
ErrorKind::AbiMismatch,
"libjnt ABI 2 is required",
));
}
let path = path.as_ref();
#[cfg(unix)]
let path_bytes = {
use std::os::unix::ffi::OsStrExt;
path.as_os_str().as_bytes()
};
#[cfg(not(unix))]
let path_bytes = path
.to_str()
.ok_or_else(|| Error::new(ErrorKind::Invalid, "grammar path must be UTF-8"))?
.as_bytes();
let path = CString::new(path_bytes)
.map_err(|_| Error::new(ErrorKind::Invalid, "grammar path contains NUL"))?;
let mut raw = ptr::null_mut();
Error::status(unsafe { ffi::jnt_open(path.as_ptr(), &mut raw) })?;
Ok(Self {
raw: Error::handle(raw)?,
_thread: PhantomData,
})
}
pub fn languages(&self) -> Vec<&[u8]> {
let count = unsafe { ffi::jnt_bank_count(self.raw.as_ptr()) };
(0..count)
.map(|i| {
let mut len = 0;
unsafe {
let pointer = ffi::jnt_bank_title(self.raw.as_ptr(), i, &mut len);
ffi::bytes(pointer, len)
}
})
.collect()
}
pub fn parser(&self, language: Option<&str>) -> Result<Parser<'_>> {
Parser::new(BankOwner::Borrowed(self), language)
}
pub fn into_parser(self, language: Option<&str>) -> Result<Parser<'static>> {
Parser::new(BankOwner::Owned(self), language)
}
}
impl Drop for Bank {
fn drop(&mut self) {
unsafe { ffi::jnt_close(self.raw.as_ptr()) };
}
}
enum BankOwner<'bank> {
Borrowed(&'bank Bank),
Owned(Bank),
}
impl BankOwner<'_> {
fn bank(&self) -> &Bank {
match self {
Self::Borrowed(bank) => bank,
Self::Owned(bank) => bank,
}
}
}
pub struct Parser<'bank> {
pub(crate) raw: NonNull<ffi::Handle>,
_bank: BankOwner<'bank>,
}
impl<'bank> Parser<'bank> {
fn new(bank: BankOwner<'bank>, language: Option<&str>) -> Result<Self> {
let language = language
.map(CString::new)
.transpose()
.map_err(|_| Error::new(ErrorKind::Invalid, "language contains NUL"))?;
let mut raw = ptr::null_mut();
Error::status(unsafe {
ffi::jnt_parser_new(
bank.bank().raw.as_ptr(),
language.as_ref().map_or(ptr::null(), |s| s.as_ptr()),
&mut raw,
)
})?;
Ok(Self {
raw: Error::handle(raw)?,
_bank: bank,
})
}
}
impl Parser<'_> {
pub fn language(&self) -> &[u8] {
let mut len = 0;
unsafe {
let pointer = ffi::jnt_parser_language(self.raw.as_ptr(), &mut len);
ffi::bytes(pointer, len)
}
}
pub fn blind_terminals(&self) -> u32 {
unsafe { ffi::jnt_parser_blind(self.raw.as_ptr()) }
}
pub fn parse(&self, source: impl AsRef<[u8]>) -> Result<Tree<'_>> {
let tree = self.parse_partial(source)?;
if tree.confidence().is_strict() {
Ok(tree)
} else {
let confidence = tree.confidence();
Err(Error {
kind: ErrorKind::ParseRefused,
message: format!("strict parse refused: {confidence:?}"),
confidence: Some(confidence),
})
}
}
pub fn parse_partial(&self, source: impl AsRef<[u8]>) -> Result<Tree<'_>> {
Tree::parse(self, source.as_ref())
}
pub fn query(&self, source: &str) -> Result<Query<'_>> {
Query::compile(self, source)
}
}
impl Drop for Parser<'_> {
fn drop(&mut self) {
unsafe { ffi::jnt_parser_free(self.raw.as_ptr()) };
}
}