use anyhow::{Context, Result};
use libloading::Library;
use tree_sitter::Language;
use tree_sitter_language::LanguageFn;
use super::loader::GrammarLoader;
use super::manifest::LangSpec;
pub const HIGHLIGHTS_FILE: &str = "highlights.scm";
pub struct Grammar {
name: String,
language: Language,
highlights_scm: String,
_lib: Library,
}
impl Grammar {
pub fn name(&self) -> &str {
&self.name
}
pub fn language(&self) -> &Language {
&self.language
}
pub fn highlights_scm(&self) -> &str {
&self.highlights_scm
}
pub fn load(name: &str, spec: &LangSpec, loader: &GrammarLoader) -> Result<Self> {
let so = loader
.load(name, spec)
.with_context(|| format!("resolve grammar {name}"))?;
let lib =
unsafe { Library::new(&so) }.with_context(|| format!("dlopen {}", so.display()))?;
let symbol = format!("tree_sitter_{}", symbol_name(name));
let raw: unsafe extern "C" fn() -> *const () = unsafe {
*lib.get(symbol.as_bytes())
.with_context(|| format!("missing symbol `{symbol}` in {}", so.display()))?
};
let lang_fn = unsafe { LanguageFn::from_raw(raw) };
let language = Language::from(lang_fn);
let parent = so
.parent()
.with_context(|| format!("grammar {} has no parent dir", so.display()))?;
let highlights_path = parent.join(format!("{name}.scm"));
let highlights_scm = std::fs::read_to_string(&highlights_path).with_context(|| {
format!(
"read highlights query for {name} at {}",
highlights_path.display()
)
})?;
Ok(Self {
name: name.to_string(),
language,
highlights_scm,
_lib: lib,
})
}
pub unsafe fn from_parts(
name: impl Into<String>,
lib: Library,
language: Language,
highlights_scm: impl Into<String>,
) -> Self {
Self {
name: name.into(),
language,
highlights_scm: highlights_scm.into(),
_lib: lib,
}
}
}
fn symbol_name(name: &str) -> String {
name.replace('-', "_")
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn symbol_name_normalizes_hyphens() {
assert_eq!(symbol_name("rust"), "rust");
assert_eq!(symbol_name("c-sharp"), "c_sharp");
assert_eq!(symbol_name("html-erb"), "html_erb");
}
#[test]
#[ignore = "network + compiler: clones tree-sitter-c, builds, installs, dlopens"]
fn load_real_grammar_end_to_end() {
use super::super::compile::GrammarCompiler;
use super::super::source::SourceCache;
let tmp = tempfile::tempdir().unwrap();
let sources = SourceCache::new(tmp.path().join("cache"));
let user_dir = tmp.path().join("user");
let loader = GrammarLoader::new(vec![], user_dir, sources, GrammarCompiler::new());
let spec = LangSpec {
git_url: "https://github.com/tree-sitter/tree-sitter-c".into(),
git_rev: "2a265d69a4caf57108a73ad2ed1e6922dd2f998c".into(),
subpath: None,
extensions: vec!["c".into()],
c_files: vec!["src/parser.c".into()],
query_dir: "queries".into(),
source: None,
};
let grammar = Grammar::load("c", &spec, &loader).unwrap();
assert_eq!(grammar.name(), "c");
let q = tree_sitter::Query::new(grammar.language(), grammar.highlights_scm());
assert!(q.is_ok(), "highlights.scm failed to compile: {:?}", q.err());
}
}