Skip to main content

comfit_core/
language.rs

1//! Language registration, aliases, and shared compiled queries.
2
3use std::{
4    collections::{BTreeMap, HashSet},
5    sync::{Arc, OnceLock},
6};
7
8use crate::{Error, backend::Backend, query::Query};
9
10/// Queries for each highlighting pass. An empty string disables that pass.
11#[derive(Clone, Copy, Default)]
12pub struct Queries<'a> {
13    /// Token style captures and precedence metadata.
14    pub highlights: &'a str,
15    /// Embedded-language captures and included-range metadata.
16    pub injections: &'a str,
17    /// Lexical scopes, definitions, and references.
18    pub locals: &'a str,
19    /// Bracket captures and nesting scopes.
20    pub rainbows: &'a str,
21}
22
23pub(crate) struct Language<B: Backend> {
24    raw: B::Language,
25    compiled: OnceLock<Compiled<B>>,
26    sources: Option<Queries<'static>>,
27}
28
29pub(crate) struct Compiled<B: Backend> {
30    pub highlights: Query<B::Query>,
31    pub injections: Query<B::Query>,
32    pub locals: Query<B::Query>,
33    pub rainbows: Query<B::Query>,
34}
35
36pub(crate) struct Registry<B: Backend> {
37    languages: BTreeMap<String, Arc<Language<B>>>,
38    aliases: BTreeMap<String, String>,
39}
40
41impl<B: Backend> Clone for Registry<B> {
42    fn clone(&self) -> Self {
43        Self {
44            languages: self.languages.clone(),
45            aliases: self.aliases.clone(),
46        }
47    }
48}
49
50impl<B: Backend> Registry<B> {
51    pub fn new() -> Self {
52        Self {
53            languages: BTreeMap::new(),
54            aliases: BTreeMap::new(),
55        }
56    }
57
58    pub fn load(
59        &mut self,
60        backend: &mut B,
61        id: &str,
62        aliases: &[&str],
63        language: B::Language,
64        queries: Queries<'_>,
65    ) -> Result<(), Error> {
66        self.validate(id, aliases)?;
67        let compiled = Self::compile(backend, &language, queries)?;
68        self.insert(
69            id,
70            aliases,
71            Language {
72                raw: language,
73                compiled: OnceLock::from(compiled),
74                sources: None,
75            },
76        );
77        Ok(())
78    }
79
80    pub fn load_lazy(
81        &mut self,
82        id: &str,
83        aliases: &[&str],
84        language: B::Language,
85        queries: Queries<'static>,
86    ) -> Result<(), Error> {
87        self.validate(id, aliases)?;
88        self.insert(
89            id,
90            aliases,
91            Language {
92                raw: language,
93                compiled: OnceLock::new(),
94                sources: Some(queries),
95            },
96        );
97        Ok(())
98    }
99
100    fn validate(&self, id: &str, aliases: &[&str]) -> Result<(), Error> {
101        let mut names = HashSet::new();
102        for name in std::iter::once(id).chain(aliases.iter().copied()) {
103            if name.is_empty()
104                || !names.insert(name)
105                || self.languages.contains_key(name)
106                || self.aliases.contains_key(name)
107            {
108                return Err(Error::DuplicateLanguage(name.to_owned()));
109            }
110        }
111
112        Ok(())
113    }
114
115    fn compile(
116        backend: &mut B,
117        language: &B::Language,
118        queries: Queries<'_>,
119    ) -> Result<Compiled<B>, Error> {
120        Ok(Compiled {
121            highlights: backend.compile(language, queries.highlights)?,
122            injections: backend.compile(language, queries.injections)?,
123            locals: backend.compile(language, queries.locals)?,
124            rainbows: backend.compile(language, queries.rainbows)?,
125        })
126    }
127
128    fn insert(&mut self, id: &str, aliases: &[&str], language: Language<B>) {
129        self.languages.insert(id.to_owned(), Arc::new(language));
130        for alias in aliases {
131            self.aliases.insert((*alias).to_owned(), id.to_owned());
132        }
133    }
134
135    pub fn get(&self, name: &str) -> Option<(&str, &Language<B>)> {
136        let id = self.aliases.get(name).map_or(name, String::as_str);
137        self.languages
138            .get_key_value(id)
139            .map(|(id, language)| (id.as_str(), language.as_ref()))
140    }
141
142    pub fn resolve(&self, name: &str) -> Result<&str, Error> {
143        self.get(name)
144            .map(|(id, _)| id)
145            .ok_or_else(|| Error::UnknownLanguage(name.to_owned()))
146    }
147
148    pub fn language(
149        &self,
150        backend: &mut B,
151        id: &str,
152    ) -> Result<(&B::Language, &Compiled<B>), Error> {
153        let language = &self.languages[id];
154        if language.compiled.get().is_none() {
155            let compiled = Self::compile(
156                backend,
157                &language.raw,
158                language.sources.expect("lazy query sources"),
159            )?;
160            // Another document may have compiled this shared language concurrently.
161            let _ = language.compiled.set(compiled);
162        }
163        Ok((
164            &language.raw,
165            language.compiled.get().expect("compiled above"),
166        ))
167    }
168}