1pub mod binding;
15
16use std::collections::BTreeMap;
17use std::fmt;
18use std::path::Path;
19use std::sync::Arc;
20
21use thiserror::Error;
22
23#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
29pub struct LanguageId(&'static str);
30
31impl LanguageId {
32 #[must_use]
34 pub const fn new(id: &'static str) -> Self {
35 Self(id)
36 }
37
38 #[must_use]
40 pub const fn as_str(self) -> &'static str {
41 self.0
42 }
43}
44
45impl fmt::Display for LanguageId {
46 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
47 f.write_str(self.0)
48 }
49}
50
51pub trait Language: Send + Sync {
61 fn id(&self) -> LanguageId;
63
64 fn resolver(&self) -> Option<Arc<dyn binding::BindingResolver>> {
70 None
71 }
72
73 fn extensions(&self) -> &'static [&'static str];
78
79 fn grammar(&self) -> tree_sitter::Language;
81
82 fn grammar_abi(&self) -> usize {
93 self.grammar().abi_version()
94 }
95}
96
97#[derive(Debug, Clone, PartialEq, Eq, Error)]
99pub enum RegistryError {
100 #[error("language `{0}` is already registered")]
102 DuplicateId(String),
103
104 #[error(
106 "extension `.{extension}` is claimed by both `{existing}` and `{incoming}`: \
107 a file cannot belong to two languages"
108 )]
109 DuplicateExtension {
110 extension: String,
112 existing: String,
114 incoming: String,
116 },
117
118 #[error("language `{language}` declared invalid extension `{extension}`: {reason}")]
120 InvalidExtension {
121 language: String,
123 extension: String,
125 reason: &'static str,
127 },
128}
129
130#[derive(Clone, Default)]
132pub struct LanguageRegistry {
133 by_id: BTreeMap<&'static str, Arc<dyn Language>>,
134 by_extension: BTreeMap<&'static str, Arc<dyn Language>>,
135}
136
137impl fmt::Debug for LanguageRegistry {
141 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
142 f.debug_struct("LanguageRegistry")
143 .field("by_id", &self.by_id.keys().collect::<Vec<_>>())
144 .field(
145 "by_extension",
146 &self.by_extension.keys().collect::<Vec<_>>(),
147 )
148 .finish()
149 }
150}
151
152impl LanguageRegistry {
153 #[must_use]
155 pub fn new() -> Self {
156 Self::default()
157 }
158
159 pub fn register(&mut self, language: Arc<dyn Language>) -> Result<(), RegistryError> {
171 let id = language.id().as_str();
172
173 if self.by_id.contains_key(id) {
174 return Err(RegistryError::DuplicateId(id.to_owned()));
175 }
176
177 for extension in language.extensions() {
178 let invalid = |reason: &'static str| RegistryError::InvalidExtension {
179 language: id.to_owned(),
180 extension: (*extension).to_owned(),
181 reason,
182 };
183
184 if extension.is_empty() {
185 return Err(invalid("must not be empty"));
186 }
187 if extension.starts_with('.') {
188 return Err(invalid("must not include the leading dot"));
189 }
190 if extension.chars().any(|c| c.is_ascii_uppercase()) {
191 return Err(invalid(
192 "must be lowercase; lookup lowercases the path's extension",
193 ));
194 }
195 if let Some(existing) = self.by_extension.get(extension) {
196 return Err(RegistryError::DuplicateExtension {
197 extension: (*extension).to_owned(),
198 existing: existing.id().as_str().to_owned(),
199 incoming: id.to_owned(),
200 });
201 }
202 }
203
204 for extension in language.extensions() {
205 self.by_extension.insert(extension, Arc::clone(&language));
206 }
207 self.by_id.insert(id, language);
208 Ok(())
209 }
210
211 #[must_use]
213 pub fn by_id(&self, id: &str) -> Option<&Arc<dyn Language>> {
214 self.by_id.get(id)
215 }
216
217 #[must_use]
222 pub fn for_path(&self, path: impl AsRef<Path>) -> Option<&Arc<dyn Language>> {
223 let extension = path.as_ref().extension()?.to_str()?.to_ascii_lowercase();
224 self.by_extension.get(extension.as_str())
225 }
226
227 pub fn languages(&self) -> impl Iterator<Item = &Arc<dyn Language>> {
229 self.by_id.values()
230 }
231
232 pub fn extensions(&self) -> impl Iterator<Item = &'static str> + '_ {
234 self.by_extension.keys().copied()
235 }
236
237 #[must_use]
239 pub fn len(&self) -> usize {
240 self.by_id.len()
241 }
242
243 #[must_use]
245 pub fn is_empty(&self) -> bool {
246 self.by_id.is_empty()
247 }
248}
249
250#[cfg(test)]
251mod tests {
252 use super::*;
253
254 struct Fake {
257 id: LanguageId,
258 extensions: &'static [&'static str],
259 }
260
261 impl Language for Fake {
262 fn id(&self) -> LanguageId {
263 self.id
264 }
265 fn extensions(&self) -> &'static [&'static str] {
266 self.extensions
267 }
268 fn grammar(&self) -> tree_sitter::Language {
269 unreachable!("registry tests never touch the grammar")
270 }
271 fn grammar_abi(&self) -> usize {
272 0
273 }
274 }
275
276 fn fake(id: &'static str, extensions: &'static [&'static str]) -> Arc<dyn Language> {
277 Arc::new(Fake {
278 id: LanguageId::new(id),
279 extensions,
280 })
281 }
282
283 fn registry(languages: &[Arc<dyn Language>]) -> LanguageRegistry {
284 let mut registry = LanguageRegistry::new();
285 for language in languages {
286 registry.register(Arc::clone(language)).expect("registers");
287 }
288 registry
289 }
290
291 #[test]
292 fn finds_a_language_by_id() {
293 let registry = registry(&[fake("alpha", &["a"])]);
294 assert_eq!(
295 registry.by_id("alpha").expect("present").id().as_str(),
296 "alpha"
297 );
298 assert!(registry.by_id("missing").is_none());
299 }
300
301 #[test]
302 fn finds_a_language_by_path() {
303 let registry = registry(&[fake("alpha", &["a", "aa"]), fake("beta", &["b"])]);
304
305 assert_eq!(
306 registry.for_path("src/x.a").expect("matches").id().as_str(),
307 "alpha"
308 );
309 assert_eq!(
310 registry
311 .for_path("src/x.aa")
312 .expect("matches")
313 .id()
314 .as_str(),
315 "alpha"
316 );
317 assert_eq!(
318 registry.for_path("src/x.b").expect("matches").id().as_str(),
319 "beta"
320 );
321 assert!(registry.for_path("src/x.zzz").is_none());
322 assert!(registry.for_path("src/noextension").is_none());
323 }
324
325 #[test]
326 fn extension_lookup_ignores_case() {
327 let registry = registry(&[fake("alpha", &["a"])]);
330 assert!(registry.for_path("src/x.A").is_some());
331 assert!(registry.for_path("src/x.a").is_some());
332 }
333
334 #[test]
335 fn rejects_a_duplicate_id() {
336 let mut registry = registry(&[fake("alpha", &["a"])]);
337 let err = registry
338 .register(fake("alpha", &["z"]))
339 .expect_err("duplicate id");
340 assert_eq!(err, RegistryError::DuplicateId("alpha".to_owned()));
341 }
342
343 #[test]
344 fn rejects_a_contested_extension() {
345 let mut registry = registry(&[fake("alpha", &["a"])]);
349 let err = registry
350 .register(fake("beta", &["a"]))
351 .expect_err("contested extension");
352
353 match err {
354 RegistryError::DuplicateExtension {
355 extension,
356 existing,
357 incoming,
358 } => {
359 assert_eq!(extension, "a");
360 assert_eq!(existing, "alpha");
361 assert_eq!(incoming, "beta");
362 }
363 other => panic!("wrong error: {other:?}"),
364 }
365 }
366
367 #[test]
368 fn a_rejected_registration_leaves_no_trace() {
369 let mut registry = registry(&[fake("alpha", &["a"])]);
373 let _ = registry.register(fake("beta", &["b", "a", "c"]));
374
375 assert!(registry.by_id("beta").is_none());
376 assert!(
377 registry.for_path("x.b").is_none(),
378 "b must not have been claimed"
379 );
380 assert!(
381 registry.for_path("x.c").is_none(),
382 "c must not have been claimed"
383 );
384 assert_eq!(
385 registry.for_path("x.a").expect("still alpha").id().as_str(),
386 "alpha"
387 );
388 assert_eq!(registry.len(), 1);
389 }
390
391 #[test]
392 fn rejects_malformed_extensions() {
393 let mut registry = LanguageRegistry::new();
394
395 assert!(matches!(
397 registry.register(fake("dotted", &[".a"])),
398 Err(RegistryError::InvalidExtension { .. })
399 ));
400 assert!(matches!(
402 registry.register(fake("shouty", &["A"])),
403 Err(RegistryError::InvalidExtension { .. })
404 ));
405 assert!(matches!(
406 registry.register(fake("empty", &[""])),
407 Err(RegistryError::InvalidExtension { .. })
408 ));
409 assert!(registry.is_empty());
410 }
411
412 #[test]
413 fn iteration_order_is_stable() {
414 let registry = registry(&[
417 fake("zeta", &["z"]),
418 fake("alpha", &["a"]),
419 fake("mu", &["m"]),
420 ]);
421
422 let ids: Vec<&str> = registry.languages().map(|l| l.id().as_str()).collect();
423 assert_eq!(ids, ["alpha", "mu", "zeta"]);
424 assert_eq!(registry.extensions().collect::<Vec<_>>(), ["a", "m", "z"]);
425 }
426
427 #[test]
428 fn an_empty_registry_matches_nothing() {
429 let registry = LanguageRegistry::new();
430 assert!(registry.is_empty());
431 assert_eq!(registry.len(), 0);
432 assert!(registry.for_path("src/x.ts").is_none());
433 assert!(registry.by_id("typescript").is_none());
434 }
435}