llmcc_core/
lang_registry.rs1use std::collections::HashMap;
8use std::sync::Arc;
9
10use crate::lang_def::{LanguageTraitImpl, ParseTree};
11
12pub trait LanguageHandler: Send + Sync {
15 fn name(&self) -> &'static str;
17
18 fn extensions(&self) -> &'static [&'static str];
20
21 fn manifest_name(&self) -> &'static str;
23
24 fn supports_extension(&self, ext: &str) -> bool {
26 self.extensions().contains(&ext)
27 }
28
29 fn parse(&self, text: &[u8]) -> Option<Box<dyn ParseTree>>;
31}
32
33pub struct LanguageHandlerImpl<L> {
35 _marker: std::marker::PhantomData<L>,
36 name: &'static str,
37}
38
39impl<L> LanguageHandlerImpl<L>
40where
41 L: LanguageTraitImpl,
42{
43 pub fn new(name: &'static str) -> Self {
45 Self {
46 _marker: std::marker::PhantomData,
47 name,
48 }
49 }
50}
51
52impl<L> LanguageHandler for LanguageHandlerImpl<L>
53where
54 L: LanguageTraitImpl + Send + Sync + 'static,
55{
56 fn name(&self) -> &'static str {
57 self.name
58 }
59
60 fn extensions(&self) -> &'static [&'static str] {
61 L::supported_extensions()
62 }
63
64 fn manifest_name(&self) -> &'static str {
65 L::manifest_name()
66 }
67
68 fn parse(&self, text: &[u8]) -> Option<Box<dyn ParseTree>> {
69 L::parse(text)
70 }
71}
72
73pub struct LanguageRegistry {
75 handlers: HashMap<&'static str, Arc<dyn LanguageHandler>>,
77 extension_map: HashMap<&'static str, Arc<dyn LanguageHandler>>,
79}
80
81impl Default for LanguageRegistry {
82 fn default() -> Self {
83 Self::new()
84 }
85}
86
87impl LanguageRegistry {
88 pub fn new() -> Self {
90 Self {
91 handlers: HashMap::new(),
92 extension_map: HashMap::new(),
93 }
94 }
95
96 pub fn register(&mut self, handler: Arc<dyn LanguageHandler>) {
98 let name = handler.name();
99 self.handlers.insert(name, handler.clone());
101 for ext in handler.extensions() {
103 self.extension_map.insert(*ext, handler.clone());
104 }
105 }
106
107 pub fn register_language<L>(&mut self, name: &'static str)
109 where
110 L: LanguageTraitImpl + Send + Sync + 'static,
111 {
112 let handler = Arc::new(LanguageHandlerImpl::<L>::new(name));
113 self.register(handler);
114 }
115
116 pub fn get_by_name(&self, name: &str) -> Option<Arc<dyn LanguageHandler>> {
118 self.handlers.get(name).cloned()
119 }
120
121 pub fn get_by_extension(&self, ext: &str) -> Option<Arc<dyn LanguageHandler>> {
123 self.extension_map.get(ext).cloned()
124 }
125
126 pub fn all_extensions(&self) -> Vec<&'static str> {
128 self.extension_map.keys().copied().collect()
129 }
130
131 pub fn all_languages(&self) -> Vec<&'static str> {
133 self.handlers.keys().copied().collect()
134 }
135
136 pub fn partition_files(&self, files: &[String]) -> HashMap<&'static str, Vec<String>> {
138 let mut partitions: HashMap<&'static str, Vec<String>> = HashMap::new();
139
140 for file in files {
141 let path = std::path::Path::new(file);
142 if let Some(ext) = path.extension().and_then(|e| e.to_str())
143 && let Some(handler) = self.get_by_extension(ext)
144 {
145 partitions
146 .entry(handler.name())
147 .or_default()
148 .push(file.clone());
149 }
150 }
151
152 partitions
153 }
154
155 pub fn is_empty(&self) -> bool {
157 self.handlers.is_empty()
158 }
159
160 pub fn len(&self) -> usize {
162 self.handlers.len()
163 }
164}
165
166#[cfg(test)]
167mod tests {
168 use super::*;
169
170 struct MockHandler {
172 name: &'static str,
173 extensions: &'static [&'static str],
174 }
175
176 impl LanguageHandler for MockHandler {
177 fn name(&self) -> &'static str {
178 self.name
179 }
180
181 fn extensions(&self) -> &'static [&'static str] {
182 self.extensions
183 }
184
185 fn manifest_name(&self) -> &'static str {
186 "mock.toml"
187 }
188
189 fn parse(&self, _text: &[u8]) -> Option<Box<dyn ParseTree>> {
190 None
191 }
192 }
193
194 #[test]
195 fn test_registry_basics() {
196 let mut registry = LanguageRegistry::new();
197
198 let rust_handler = Arc::new(MockHandler {
199 name: "rust",
200 extensions: &["rs"],
201 });
202 let ts_handler = Arc::new(MockHandler {
203 name: "typescript",
204 extensions: &["ts", "tsx"],
205 });
206
207 registry.register(rust_handler);
208 registry.register(ts_handler);
209
210 assert_eq!(registry.len(), 2);
211 assert!(registry.get_by_name("rust").is_some());
212 assert!(registry.get_by_extension("ts").is_some());
213 assert!(registry.get_by_extension("tsx").is_some());
214 }
215
216 #[test]
217 fn test_partition_files() {
218 let mut registry = LanguageRegistry::new();
219
220 let rust_handler = Arc::new(MockHandler {
221 name: "rust",
222 extensions: &["rs"],
223 });
224 let ts_handler = Arc::new(MockHandler {
225 name: "typescript",
226 extensions: &["ts"],
227 });
228
229 registry.register(rust_handler);
230 registry.register(ts_handler);
231
232 let files = vec![
233 "src/main.rs".to_string(),
234 "src/lib.rs".to_string(),
235 "src/index.ts".to_string(),
236 "src/unknown.py".to_string(),
237 ];
238
239 let partitions = registry.partition_files(&files);
240
241 assert_eq!(partitions.get("rust").map(|v| v.len()), Some(2));
242 assert_eq!(partitions.get("typescript").map(|v| v.len()), Some(1));
243 assert!(!partitions.contains_key("python"));
244 }
245}