use std::collections::HashMap;
use std::path::{Path, PathBuf};
use std::sync::{Arc, Mutex};
use anyhow::Result;
use hjkl_bonsai::runtime::{
AsyncGrammarLoader, Grammar, GrammarLoader, GrammarRegistry, LoadHandle,
};
pub enum GrammarRequest {
Cached(Arc<Grammar>),
Loading { name: String, handle: LoadHandle },
Unknown,
}
pub struct LanguageDirectory {
registry: GrammarRegistry,
async_loader: AsyncGrammarLoader,
cache: Mutex<HashMap<String, Arc<Grammar>>>,
}
impl LanguageDirectory {
pub fn new() -> Result<Self> {
let registry = GrammarRegistry::embedded()?;
let loader = GrammarLoader::user_default(registry.meta())?;
let async_loader = AsyncGrammarLoader::new(loader);
Ok(Self {
registry,
async_loader,
cache: Mutex::new(HashMap::new()),
})
}
pub fn request_for_path(&self, path: &Path) -> GrammarRequest {
let name = match self.registry.name_for_path(path) {
Some(n) => n.to_string(),
None => return GrammarRequest::Unknown,
};
self.request_by_name(&name)
}
pub fn request_by_name(&self, name: &str) -> GrammarRequest {
if let Some(g) = self.cache_get(name) {
return GrammarRequest::Cached(g);
}
let spec = match self.registry.by_name(name) {
Some(s) => s,
None => return GrammarRequest::Unknown,
};
let meta = self.registry.meta();
if let Some(path) = self.async_loader.inner().lookup_fresh(name, spec, meta) {
match Grammar::load_from_path(name, &path) {
Ok(g) => {
let arc = self.cache_insert(name, g);
return GrammarRequest::Cached(arc);
}
Err(e) => {
tracing::debug!("load_from_path({name}) failed after lookup_fresh: {e:#}");
}
}
}
let handle = self
.async_loader
.load_async(name.to_string(), spec.clone(), meta.clone());
GrammarRequest::Loading {
name: name.to_string(),
handle,
}
}
pub fn in_flight_names(&self) -> Vec<String> {
self.async_loader.in_flight_names()
}
pub fn complete_load(&self, name: &str, lib_path: PathBuf) -> Result<Arc<Grammar>> {
let grammar = Grammar::load_from_path(name, &lib_path)?;
Ok(self.cache_insert(name, grammar))
}
pub fn by_name(&self, name: &str) -> Option<Arc<Grammar>> {
if let Some(g) = self.cache_get(name) {
return Some(g);
}
let spec = self.registry.by_name(name)?;
let meta = self.registry.meta();
let grammar = Grammar::load(name, spec, self.async_loader.inner(), meta).ok()?;
Some(self.cache_insert(name, grammar))
}
fn cache_get(&self, name: &str) -> Option<Arc<Grammar>> {
let g = self.cache.lock().ok()?;
g.get(name).cloned()
}
fn cache_insert(&self, name: &str, grammar: Grammar) -> Arc<Grammar> {
let arc = Arc::new(grammar);
if let Ok(mut g) = self.cache.lock() {
if let Some(existing) = g.get(name) {
return existing.clone();
}
g.insert(name.to_string(), arc.clone());
}
arc
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::path::PathBuf;
#[test]
fn request_for_path_returns_unknown_for_unrecognized_extension() {
let dir = LanguageDirectory::new().unwrap();
assert!(matches!(
dir.request_for_path(&PathBuf::from("foo.zzznope")),
GrammarRequest::Unknown
));
}
#[test]
fn request_by_name_returns_unknown_for_nonexistent_lang() {
let dir = LanguageDirectory::new().unwrap();
assert!(matches!(
dir.request_by_name("definitely_not_a_real_language"),
GrammarRequest::Unknown
));
}
}