use once_cell::sync::Lazy;
use std::sync::RwLock;
use tree_sitter::Language;
#[derive(Debug, Clone)]
struct GrammarCacheEntry {
language: Language,
}
#[derive(Debug, Default)]
pub struct GrammarCache {
grammars: RwLock<Vec<Option<GrammarCacheEntry>>>,
}
impl GrammarCache {
pub fn new() -> Self {
Self::default()
}
pub fn get_or_load<F>(
&self,
index: usize,
loader: F,
) -> Result<Language, crate::parse::traits::Error>
where
F: FnOnce() -> Language,
{
{
let read_guard = self.grammars.read().map_err(|e| {
crate::parse::traits::Error::ParseFailed(format!("Cache lock poisoned: {}", e))
})?;
if read_guard.len() > index {
if let Some(entry) = &read_guard[index] {
return Ok(entry.language.clone());
}
}
}
let mut write_guard = self.grammars.write().map_err(|e| {
crate::parse::traits::Error::ParseFailed(format!("Cache lock poisoned: {}", e))
})?;
if write_guard.len() > index {
if let Some(entry) = &write_guard[index] {
return Ok(entry.language.clone());
}
}
while write_guard.len() <= index {
write_guard.push(None);
}
let language = loader();
let entry = GrammarCacheEntry {
language: language.clone(),
};
write_guard[index] = Some(entry);
Ok(language)
}
pub fn len(&self) -> usize {
self.grammars
.read()
.map(|g| g.len())
.unwrap_or_else(|e| {
panic!("Grammar cache lock poisoned: {}. This indicates a serious bug in concurrent access.", e)
})
}
pub fn is_empty(&self) -> bool {
self.len() == 0
}
}
pub static GLOBAL_GRAMMAR_CACHE: Lazy<GrammarCache> = Lazy::new(GrammarCache::new);
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum LanguageId {
Python = 0,
JavaScript = 1,
TypeScript = 2,
Go = 3,
Rust = 4,
Java = 5,
Cpp = 6,
CSharp = 7,
Ruby = 8,
Php = 9,
Lua = 10,
Scala = 11,
C = 12,
Bash = 13,
Json = 14,
Swift = 15,
Kotlin = 16,
Dart = 17,
Html = 18,
Css = 19,
Scss = 20,
Yaml = 21,
Cmake = 22,
Elixir = 24,
Erlang = 25,
Haskell = 26,
Perl = 27,
R = 28,
Zig = 29,
Graphql = 30,
Hcl = 31,
Make = 32,
Elisp = 33,
Julia = 34,
D = 35,
Glsl = 36,
EmbeddedTemplate = 37,
Markdown = 38,
Rst = 39,
Adoc = 40,
PlainText = 41,
}
impl LanguageId {
pub fn from_extension(ext: &str) -> Option<Self> {
match ext.to_lowercase().as_str() {
"py" => Some(LanguageId::Python),
"js" | "mjs" | "cjs" | "jsx" => Some(LanguageId::JavaScript),
"ts" | "tsx" | "mts" | "cts" => Some(LanguageId::TypeScript),
"go" => Some(LanguageId::Go),
"rs" => Some(LanguageId::Rust),
"java" => Some(LanguageId::Java),
"cpp" | "cc" | "cxx" | "hpp" => Some(LanguageId::Cpp),
"h" => Some(LanguageId::C), "c" => Some(LanguageId::C),
"cs" => Some(LanguageId::CSharp),
"rb" => Some(LanguageId::Ruby),
"php" => Some(LanguageId::Php),
"swift" => Some(LanguageId::Swift),
"kt" | "kts" => Some(LanguageId::Kotlin),
"dart" => Some(LanguageId::Dart),
"html" | "htm" => Some(LanguageId::Html),
"css" => Some(LanguageId::Css),
"scss" => Some(LanguageId::Scss),
"yaml" | "yml" => Some(LanguageId::Yaml),
"cmake" => Some(LanguageId::Cmake),
"ex" | "exs" => Some(LanguageId::Elixir),
"erl" | "hrl" => Some(LanguageId::Erlang),
"hs" => Some(LanguageId::Haskell),
"pl" | "pm" => Some(LanguageId::Perl),
"r" => Some(LanguageId::R),
"zig" => Some(LanguageId::Zig),
"graphql" | "gql" => Some(LanguageId::Graphql),
"hcl" | "tf" | "tfvars" => Some(LanguageId::Hcl),
"makefile" | "mak" | "mk" => Some(LanguageId::Make),
"el" => Some(LanguageId::Elisp),
"jl" => Some(LanguageId::Julia),
"d" | "di" => Some(LanguageId::D),
"glsl" | "vert" | "frag" | "comp" => Some(LanguageId::Glsl),
"ejs" | "erb" | "liquid" => Some(LanguageId::EmbeddedTemplate),
"md" | "markdown" => Some(LanguageId::Markdown),
"rst" => Some(LanguageId::Rst),
"adoc" | "asciidoc" => Some(LanguageId::Adoc),
"txt" => Some(LanguageId::PlainText),
"lua" => Some(LanguageId::Lua),
"scala" | "sc" => Some(LanguageId::Scala),
"sh" | "bash" => Some(LanguageId::Bash),
"json" => Some(LanguageId::Json),
_ => None,
}
}
pub fn config(&self) -> &'static crate::parse::traits::LanguageConfig {
match self {
LanguageId::Python => &crate::parse::traits::languages::python::CONFIG,
LanguageId::JavaScript => &crate::parse::traits::languages::javascript::CONFIG,
LanguageId::TypeScript => &crate::parse::traits::languages::typescript::CONFIG,
LanguageId::Go => &crate::parse::traits::languages::go::CONFIG,
LanguageId::Rust => &crate::parse::traits::languages::rust::CONFIG,
LanguageId::Java => &crate::parse::traits::languages::java::CONFIG,
LanguageId::Cpp => &crate::parse::traits::languages::cpp::CONFIG,
LanguageId::CSharp => &crate::parse::traits::languages::csharp::CONFIG,
LanguageId::Ruby => &crate::parse::traits::languages::ruby::CONFIG,
LanguageId::Php => &crate::parse::traits::languages::php::CONFIG,
LanguageId::Lua => &crate::parse::traits::languages::lua::CONFIG,
LanguageId::Scala => &crate::parse::traits::languages::scala::CONFIG,
LanguageId::C => &crate::parse::traits::languages::c::CONFIG,
LanguageId::Bash => &crate::parse::traits::languages::bash::CONFIG,
LanguageId::Json => &crate::parse::traits::languages::json::CONFIG,
LanguageId::Swift => &crate::parse::traits::languages::swift::CONFIG,
LanguageId::Kotlin => &crate::parse::traits::languages::kotlin::CONFIG,
LanguageId::Dart => &crate::parse::traits::languages::dart::CONFIG,
LanguageId::Html => &crate::parse::traits::languages::html::CONFIG,
LanguageId::Css => &crate::parse::traits::languages::css::CONFIG,
LanguageId::Scss => &crate::parse::traits::languages::scss::CONFIG,
LanguageId::Yaml => &crate::parse::traits::languages::yaml::CONFIG,
LanguageId::Cmake => &crate::parse::traits::languages::cmake::CONFIG,
LanguageId::Elixir => &crate::parse::traits::languages::elixir::CONFIG,
LanguageId::Erlang => &crate::parse::traits::languages::erlang::CONFIG,
LanguageId::Haskell => &crate::parse::traits::languages::haskell::CONFIG,
LanguageId::Perl => &crate::parse::traits::languages::perl::CONFIG,
LanguageId::R => &crate::parse::traits::languages::r::CONFIG,
LanguageId::Zig => &crate::parse::traits::languages::zig::CONFIG,
LanguageId::Graphql => &crate::parse::traits::languages::graphql::CONFIG,
LanguageId::Hcl => &crate::parse::traits::languages::hcl::CONFIG,
LanguageId::Make => &crate::parse::traits::languages::make::CONFIG,
LanguageId::Elisp => &crate::parse::traits::languages::elisp::CONFIG,
LanguageId::Julia => &crate::parse::traits::languages::julia::CONFIG,
LanguageId::D => &crate::parse::traits::languages::d::CONFIG,
LanguageId::Glsl => &crate::parse::traits::languages::glsl::CONFIG,
LanguageId::EmbeddedTemplate => {
&crate::parse::traits::languages::embedded_template::CONFIG
}
LanguageId::Markdown => &crate::parse::traits::languages::markdown::CONFIG,
LanguageId::Rst => &crate::parse::traits::languages::rst::CONFIG,
LanguageId::Adoc => &crate::parse::traits::languages::adoc::CONFIG,
LanguageId::PlainText => &crate::parse::traits::languages::plaintext::CONFIG,
}
}
fn load_language(&self) -> Language {
match self {
LanguageId::Python => crate::parse::traits::languages::python::language(),
LanguageId::JavaScript => crate::parse::traits::languages::javascript::language(),
LanguageId::TypeScript => crate::parse::traits::languages::typescript::language(),
LanguageId::Go => crate::parse::traits::languages::go::language(),
LanguageId::Rust => crate::parse::traits::languages::rust::language(),
LanguageId::Java => crate::parse::traits::languages::java::language(),
LanguageId::Cpp => crate::parse::traits::languages::cpp::language(),
LanguageId::CSharp => crate::parse::traits::languages::csharp::language(),
LanguageId::Ruby => crate::parse::traits::languages::ruby::language(),
LanguageId::Php => crate::parse::traits::languages::php::language(),
LanguageId::Lua => crate::parse::traits::languages::lua::language(),
LanguageId::Scala => crate::parse::traits::languages::scala::language(),
LanguageId::C => crate::parse::traits::languages::c::language(),
LanguageId::Bash => crate::parse::traits::languages::bash::language(),
LanguageId::Json => crate::parse::traits::languages::json::language(),
LanguageId::Swift => crate::parse::traits::languages::swift::language(),
LanguageId::Kotlin => crate::parse::traits::languages::kotlin::language(),
LanguageId::Dart => crate::parse::traits::languages::dart::language(),
LanguageId::Html => crate::parse::traits::languages::html::language(),
LanguageId::Css => crate::parse::traits::languages::css::language(),
LanguageId::Scss => crate::parse::traits::languages::scss::language(),
LanguageId::Yaml => crate::parse::traits::languages::yaml::language(),
LanguageId::Cmake => crate::parse::traits::languages::cmake::language(),
LanguageId::Elixir => crate::parse::traits::languages::elixir::language(),
LanguageId::Erlang => crate::parse::traits::languages::erlang::language(),
LanguageId::Haskell => crate::parse::traits::languages::haskell::language(),
LanguageId::Perl => crate::parse::traits::languages::perl::language(),
LanguageId::R => crate::parse::traits::languages::r::language(),
LanguageId::Zig => crate::parse::traits::languages::zig::language(),
LanguageId::Graphql => crate::parse::traits::languages::graphql::language(),
LanguageId::Hcl => crate::parse::traits::languages::hcl::language(),
LanguageId::Make => crate::parse::traits::languages::make::language(),
LanguageId::Elisp => crate::parse::traits::languages::elisp::language(),
LanguageId::Julia => crate::parse::traits::languages::julia::language(),
LanguageId::D => crate::parse::traits::languages::d::language(),
LanguageId::Glsl => crate::parse::traits::languages::glsl::language(),
LanguageId::EmbeddedTemplate => {
crate::parse::traits::languages::embedded_template::language()
}
LanguageId::Markdown | LanguageId::Rst | LanguageId::Adoc | LanguageId::PlainText => {
crate::parse::traits::languages::json::language()
}
}
}
pub fn from_cache(&self) -> Result<Language, crate::parse::traits::Error> {
GLOBAL_GRAMMAR_CACHE.get_or_load(*self as usize, || self.load_language())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_grammar_cache_creation() {
let cache = GrammarCache::new();
assert!(cache.is_empty());
assert_eq!(cache.len(), 0);
}
#[test]
fn test_grammar_cache_lazy_loading() {
let cache = GrammarCache::new();
let lang = cache.get_or_load(0, crate::parse::traits::languages::python::language);
assert!(lang.is_ok());
assert_eq!(cache.len(), 1);
let lang2 = cache.get_or_load(0, || {
panic!("Should not call loader for cached grammar");
});
assert!(lang2.is_ok());
assert_eq!(cache.len(), 1);
}
#[test]
fn test_language_id_from_extension() {
assert_eq!(LanguageId::from_extension("py"), Some(LanguageId::Python));
assert_eq!(
LanguageId::from_extension("js"),
Some(LanguageId::JavaScript)
);
assert_eq!(
LanguageId::from_extension("jsx"),
Some(LanguageId::JavaScript)
);
assert_eq!(LanguageId::from_extension("rs"), Some(LanguageId::Rust));
assert_eq!(LanguageId::from_extension("unknown"), None);
}
#[test]
fn test_language_id_case_insensitive() {
assert_eq!(LanguageId::from_extension("PY"), Some(LanguageId::Python));
assert_eq!(LanguageId::from_extension("Rs"), Some(LanguageId::Rust));
}
#[test]
fn test_language_config_integration() {
let py_config = LanguageId::Python.config();
assert_eq!(py_config.name, "Python");
assert!(py_config.extensions.contains(&"py".to_string()));
let js_config = LanguageId::JavaScript.config();
assert_eq!(js_config.name, "JavaScript");
assert!(js_config.extensions.contains(&"js".to_string()));
}
#[test]
fn test_unified_language_loading() {
let lang = LanguageId::Python.from_cache();
assert!(lang.is_ok());
let lang2 = LanguageId::Python.from_cache();
assert!(lang2.is_ok());
}
}