1pub mod binding;
9
10use std::collections::BTreeMap;
11use std::fmt;
12use std::path::Path;
13use std::sync::Arc;
14
15use thiserror::Error;
16
17#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
23pub struct LanguageId(&'static str);
24
25impl LanguageId {
26 #[must_use]
28 pub const fn new(id: &'static str) -> Self {
29 Self(id)
30 }
31
32 #[must_use]
34 pub const fn as_str(self) -> &'static str {
35 self.0
36 }
37}
38
39impl fmt::Display for LanguageId {
40 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
41 f.write_str(self.0)
42 }
43}
44
45pub trait Language: Send + Sync {
55 fn id(&self) -> LanguageId;
57
58 fn resolver(&self) -> Option<Arc<dyn binding::BindingResolver>> {
64 None
65 }
66
67 fn extensions(&self) -> &'static [&'static str];
72
73 fn grammar(&self) -> tree_sitter::Language;
75
76 fn grammar_abi(&self) -> usize {
87 self.grammar().abi_version()
88 }
89}
90
91#[derive(Debug, Clone, PartialEq, Eq, Error)]
93pub enum RegistryError {
94 #[error("language `{0}` is already registered")]
96 DuplicateId(String),
97
98 #[error(
100 "extension `.{extension}` is claimed by both `{existing}` and `{incoming}`: \
101 a file cannot belong to two languages"
102 )]
103 DuplicateExtension {
104 extension: String,
106 existing: String,
108 incoming: String,
110 },
111
112 #[error("language `{language}` declared invalid extension `{extension}`: {reason}")]
114 InvalidExtension {
115 language: String,
117 extension: String,
119 reason: &'static str,
121 },
122}
123
124#[derive(Clone, Default)]
126pub struct LanguageRegistry {
127 by_id: BTreeMap<&'static str, Arc<dyn Language>>,
128 by_extension: BTreeMap<&'static str, Arc<dyn Language>>,
129}
130
131impl fmt::Debug for LanguageRegistry {
135 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
136 f.debug_struct("LanguageRegistry")
137 .field("by_id", &self.by_id.keys().collect::<Vec<_>>())
138 .field(
139 "by_extension",
140 &self.by_extension.keys().collect::<Vec<_>>(),
141 )
142 .finish()
143 }
144}
145
146impl LanguageRegistry {
147 #[must_use]
149 pub fn new() -> Self {
150 Self::default()
151 }
152
153 pub fn register(&mut self, language: Arc<dyn Language>) -> Result<(), RegistryError> {
165 let id = language.id().as_str();
166
167 if self.by_id.contains_key(id) {
168 return Err(RegistryError::DuplicateId(id.to_owned()));
169 }
170
171 for extension in language.extensions() {
172 let invalid = |reason: &'static str| RegistryError::InvalidExtension {
173 language: id.to_owned(),
174 extension: (*extension).to_owned(),
175 reason,
176 };
177
178 if extension.is_empty() {
179 return Err(invalid("must not be empty"));
180 }
181 if extension.starts_with('.') {
182 return Err(invalid("must not include the leading dot"));
183 }
184 if extension.chars().any(|c| c.is_ascii_uppercase()) {
185 return Err(invalid(
186 "must be lowercase; lookup lowercases the path's extension",
187 ));
188 }
189 if let Some(existing) = self.by_extension.get(extension) {
190 return Err(RegistryError::DuplicateExtension {
191 extension: (*extension).to_owned(),
192 existing: existing.id().as_str().to_owned(),
193 incoming: id.to_owned(),
194 });
195 }
196 }
197
198 for extension in language.extensions() {
199 self.by_extension.insert(extension, Arc::clone(&language));
200 }
201 self.by_id.insert(id, language);
202 Ok(())
203 }
204
205 #[must_use]
207 pub fn by_id(&self, id: &str) -> Option<&Arc<dyn Language>> {
208 self.by_id.get(id)
209 }
210
211 #[must_use]
216 pub fn for_path(&self, path: impl AsRef<Path>) -> Option<&Arc<dyn Language>> {
217 let extension = path.as_ref().extension()?.to_str()?.to_ascii_lowercase();
218 self.by_extension.get(extension.as_str())
219 }
220
221 pub fn languages(&self) -> impl Iterator<Item = &Arc<dyn Language>> {
223 self.by_id.values()
224 }
225
226 pub fn extensions(&self) -> impl Iterator<Item = &'static str> + '_ {
228 self.by_extension.keys().copied()
229 }
230
231 #[must_use]
233 pub fn len(&self) -> usize {
234 self.by_id.len()
235 }
236
237 #[must_use]
239 pub fn is_empty(&self) -> bool {
240 self.by_id.is_empty()
241 }
242}
243
244#[cfg(test)]
245mod tests {
246 use super::*;
247
248 struct Fake {
251 id: LanguageId,
252 extensions: &'static [&'static str],
253 }
254
255 impl Language for Fake {
256 fn id(&self) -> LanguageId {
257 self.id
258 }
259 fn extensions(&self) -> &'static [&'static str] {
260 self.extensions
261 }
262 fn grammar(&self) -> tree_sitter::Language {
263 unreachable!("registry tests never touch the grammar")
264 }
265 fn grammar_abi(&self) -> usize {
266 0
267 }
268 }
269
270 fn fake(id: &'static str, extensions: &'static [&'static str]) -> Arc<dyn Language> {
271 Arc::new(Fake {
272 id: LanguageId::new(id),
273 extensions,
274 })
275 }
276
277 fn registry(languages: &[Arc<dyn Language>]) -> LanguageRegistry {
278 let mut registry = LanguageRegistry::new();
279 for language in languages {
280 registry.register(Arc::clone(language)).expect("registers");
281 }
282 registry
283 }
284
285 #[test]
286 fn finds_a_language_by_id() {
287 let registry = registry(&[fake("alpha", &["a"])]);
288 assert_eq!(
289 registry.by_id("alpha").expect("present").id().as_str(),
290 "alpha"
291 );
292 assert!(registry.by_id("missing").is_none());
293 }
294
295 #[test]
296 fn finds_a_language_by_path() {
297 let registry = registry(&[fake("alpha", &["a", "aa"]), fake("beta", &["b"])]);
298
299 assert_eq!(
300 registry.for_path("src/x.a").expect("matches").id().as_str(),
301 "alpha"
302 );
303 assert_eq!(
304 registry
305 .for_path("src/x.aa")
306 .expect("matches")
307 .id()
308 .as_str(),
309 "alpha"
310 );
311 assert_eq!(
312 registry.for_path("src/x.b").expect("matches").id().as_str(),
313 "beta"
314 );
315 assert!(registry.for_path("src/x.zzz").is_none());
316 assert!(registry.for_path("src/noextension").is_none());
317 }
318
319 #[test]
320 fn extension_lookup_ignores_case() {
321 let registry = registry(&[fake("alpha", &["a"])]);
324 assert!(registry.for_path("src/x.A").is_some());
325 assert!(registry.for_path("src/x.a").is_some());
326 }
327
328 #[test]
329 fn rejects_a_duplicate_id() {
330 let mut registry = registry(&[fake("alpha", &["a"])]);
331 let err = registry
332 .register(fake("alpha", &["z"]))
333 .expect_err("duplicate id");
334 assert_eq!(err, RegistryError::DuplicateId("alpha".to_owned()));
335 }
336
337 #[test]
338 fn rejects_a_contested_extension() {
339 let mut registry = registry(&[fake("alpha", &["a"])]);
343 let err = registry
344 .register(fake("beta", &["a"]))
345 .expect_err("contested extension");
346
347 match err {
348 RegistryError::DuplicateExtension {
349 extension,
350 existing,
351 incoming,
352 } => {
353 assert_eq!(extension, "a");
354 assert_eq!(existing, "alpha");
355 assert_eq!(incoming, "beta");
356 }
357 other => panic!("wrong error: {other:?}"),
358 }
359 }
360
361 #[test]
362 fn a_rejected_registration_leaves_no_trace() {
363 let mut registry = registry(&[fake("alpha", &["a"])]);
367 let _ = registry.register(fake("beta", &["b", "a", "c"]));
368
369 assert!(registry.by_id("beta").is_none());
370 assert!(
371 registry.for_path("x.b").is_none(),
372 "b must not have been claimed"
373 );
374 assert!(
375 registry.for_path("x.c").is_none(),
376 "c must not have been claimed"
377 );
378 assert_eq!(
379 registry.for_path("x.a").expect("still alpha").id().as_str(),
380 "alpha"
381 );
382 assert_eq!(registry.len(), 1);
383 }
384
385 #[test]
386 fn rejects_malformed_extensions() {
387 let mut registry = LanguageRegistry::new();
388
389 assert!(matches!(
391 registry.register(fake("dotted", &[".a"])),
392 Err(RegistryError::InvalidExtension { .. })
393 ));
394 assert!(matches!(
396 registry.register(fake("shouty", &["A"])),
397 Err(RegistryError::InvalidExtension { .. })
398 ));
399 assert!(matches!(
400 registry.register(fake("empty", &[""])),
401 Err(RegistryError::InvalidExtension { .. })
402 ));
403 assert!(registry.is_empty());
404 }
405
406 #[test]
407 fn iteration_order_is_stable() {
408 let registry = registry(&[
411 fake("zeta", &["z"]),
412 fake("alpha", &["a"]),
413 fake("mu", &["m"]),
414 ]);
415
416 let ids: Vec<&str> = registry.languages().map(|l| l.id().as_str()).collect();
417 assert_eq!(ids, ["alpha", "mu", "zeta"]);
418 assert_eq!(registry.extensions().collect::<Vec<_>>(), ["a", "m", "z"]);
419 }
420
421 #[test]
422 fn an_empty_registry_matches_nothing() {
423 let registry = LanguageRegistry::new();
424 assert!(registry.is_empty());
425 assert_eq!(registry.len(), 0);
426 assert!(registry.for_path("src/x.ts").is_none());
427 assert!(registry.by_id("typescript").is_none());
428 }
429}