use std::fmt;
use std::path::{Path, PathBuf};
use std::ptr::NonNull;
use libloading::{Library, Symbol};
/// supported TS versions, WARNING: update when updating vendored c sources
pub const MIN_COMPATIBLE_ABI_VERSION: u32 = 13;
pub const ABI_VERSION: u32 = 14;
// opaque pointer
enum GrammarData {}
#[repr(transparent)]
#[derive(Clone, Copy, PartialEq, Eq, Hash)]
pub struct Grammar {
ptr: NonNull<GrammarData>,
}
unsafe impl Send for Grammar {}
unsafe impl Sync for Grammar {}
impl std::fmt::Debug for Grammar {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Grammar").finish_non_exhaustive()
}
}
impl Grammar {
/// Loads a shared library containg a tree sitter grammar with name `name`
// from `library_path`.
///
/// # Safety
///
/// `library_path` must be a valid tree sitter grammar
pub unsafe fn new(name: &str, library_path: &Path) -> Result<Grammar, Error> {
let library = unsafe {
Library::new(library_path).map_err(|err| Error::DlOpen {
err,
path: library_path.to_owned(),
})?
};
let language_fn_name = format!("tree_sitter_{}", name.replace('-', "_"));
let grammar = unsafe {
let language_fn: Symbol<unsafe extern "C" fn() -> NonNull<GrammarData>> = library
.get(language_fn_name.as_bytes())
.map_err(|err| Error::DlSym {
err,
symbol: name.to_owned(),
})?;
Grammar { ptr: language_fn() }
};
let version = grammar.version();
if (MIN_COMPATIBLE_ABI_VERSION..=ABI_VERSION).contains(&version) {
std::mem::forget(library);
Ok(grammar)
} else {
Err(Error::IncompatibleVersion { version })
}
}
pub fn version(self) -> u32 {
unsafe { ts_language_version(self) }
}
}
#[derive(thiserror::Error, Debug)]
pub enum Error {
#[error("Error opening dynamic library {path:?}")]
DlOpen {
#[source]
err: libloading::Error,
path: PathBuf,
},
#[error("Failed to load symbol {symbol}")]
DlSym {
#[source]
err: libloading::Error,
symbol: String,
},
#[error("Tried to load grammar with incompatible ABI {version}.")]
IncompatibleVersion { version: u32 },
}
/// An error that occurred when trying to assign an incompatible [`Grammar`] to
/// a [`Parser`].
#[derive(Debug, PartialEq, Eq)]
pub struct IncompatibleGrammarError {
version: u32,
}
impl fmt::Display for IncompatibleGrammarError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(
f,
"Tried to load grammar with incompatible ABI {}.",
self.version,
)
}
}
impl std::error::Error for IncompatibleGrammarError {}
extern "C" {
/// Get the ABI version number for this language. This version number
/// is used to ensure that languages were generated by a compatible version of
/// Tree-sitter. See also [`ts_parser_set_language`].
pub fn ts_language_version(grammar: Grammar) -> u32;
}