llmcc_core/
lang_registry.rs

1//! Dynamic language registry for multi-language support.
2//!
3//! This module provides runtime polymorphism over language handlers,
4//! allowing any number of languages to be registered and used dynamically
5//! without requiring compile-time generic parameters.
6
7use std::collections::HashMap;
8use std::sync::Arc;
9
10use crate::lang_def::{LanguageTraitImpl, ParseTree};
11
12/// Object-safe language handler trait.
13/// This wraps static `LanguageTrait` methods into dynamic dispatch.
14pub trait LanguageHandler: Send + Sync {
15    /// Get the unique name of this language (e.g., "rust", "typescript")
16    fn name(&self) -> &'static str;
17
18    /// Get supported file extensions for this language
19    fn extensions(&self) -> &'static [&'static str];
20
21    /// Get the manifest file name (e.g., "Cargo.toml", "package.json")
22    fn manifest_name(&self) -> &'static str;
23
24    /// Check if a file extension is supported by this language
25    fn supports_extension(&self, ext: &str) -> bool {
26        self.extensions().contains(&ext)
27    }
28
29    /// Parse source code and return a generic parse tree
30    fn parse(&self, text: &[u8]) -> Option<Box<dyn ParseTree>>;
31}
32
33/// A language handler implementation that wraps a LanguageTraitImpl.
34pub 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    /// Create a new handler for the given language
44    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
73/// Registry of available language handlers.
74pub struct LanguageRegistry {
75    /// Map from language name to handler
76    handlers: HashMap<&'static str, Arc<dyn LanguageHandler>>,
77    /// Map from extension to handler
78    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    /// Create a new empty registry
89    pub fn new() -> Self {
90        Self {
91            handlers: HashMap::new(),
92            extension_map: HashMap::new(),
93        }
94    }
95
96    /// Register a language handler
97    pub fn register(&mut self, handler: Arc<dyn LanguageHandler>) {
98        let name = handler.name();
99        // Register by name
100        self.handlers.insert(name, handler.clone());
101        // Register by each extension
102        for ext in handler.extensions() {
103            self.extension_map.insert(*ext, handler.clone());
104        }
105    }
106
107    /// Register a language by its LanguageTraitImpl type
108    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    /// Get a handler by language name
117    pub fn get_by_name(&self, name: &str) -> Option<Arc<dyn LanguageHandler>> {
118        self.handlers.get(name).cloned()
119    }
120
121    /// Get a handler by file extension
122    pub fn get_by_extension(&self, ext: &str) -> Option<Arc<dyn LanguageHandler>> {
123        self.extension_map.get(ext).cloned()
124    }
125
126    /// Get all registered extensions
127    pub fn all_extensions(&self) -> Vec<&'static str> {
128        self.extension_map.keys().copied().collect()
129    }
130
131    /// Get all registered language names
132    pub fn all_languages(&self) -> Vec<&'static str> {
133        self.handlers.keys().copied().collect()
134    }
135
136    /// Partition files by their language handler
137    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    /// Check if the registry has any handlers registered
156    pub fn is_empty(&self) -> bool {
157        self.handlers.is_empty()
158    }
159
160    /// Get the number of registered languages
161    pub fn len(&self) -> usize {
162        self.handlers.len()
163    }
164}
165
166#[cfg(test)]
167mod tests {
168    use super::*;
169
170    // Test with mock language handler
171    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}