helix_core/compiler/
modules.rs

1use std::path::{Path, PathBuf};
2use std::collections::{HashMap, HashSet, VecDeque};
3use anyhow::{Result, Context};
4use crate::ast::{HelixAst, Declaration};
5use crate::error::{HelixError, CompilationError, CompilationStage};
6pub struct ModuleSystem {
7    modules: HashMap<PathBuf, Module>,
8    dependencies: HashMap<PathBuf, HashSet<PathBuf>>,
9    dependents: HashMap<PathBuf, HashSet<PathBuf>>,
10    asts: HashMap<PathBuf, HelixAst>,
11    resolution_order: Vec<PathBuf>,
12    #[allow(dead_code)]
13    resolver: ModuleResolver,
14}
15#[derive(Debug, Clone)]
16pub struct Module {
17    pub path: PathBuf,
18    pub ast: HelixAst,
19    pub exports: ModuleExports,
20    pub imports: ModuleImports,
21    pub metadata: ModuleMetadata,
22}
23#[derive(Debug, Clone, Default)]
24pub struct ModuleExports {
25    pub agents: HashMap<String, AgentExport>,
26    pub workflows: HashMap<String, WorkflowExport>,
27    pub contexts: HashMap<String, ContextExport>,
28    pub crews: HashMap<String, CrewExport>,
29    pub types: HashMap<String, TypeExport>,
30}
31#[derive(Debug, Clone, Default)]
32pub struct ModuleImports {
33    pub imports: Vec<ImportStatement>,
34}
35#[derive(Debug, Clone)]
36pub struct ImportStatement {
37    pub path: String,
38    pub items: ImportItems,
39    pub alias: Option<String>,
40}
41#[derive(Debug, Clone)]
42pub enum ImportItems {
43    All,
44    Specific(Vec<ImportItem>),
45    Module,
46}
47#[derive(Debug, Clone)]
48pub struct ImportItem {
49    pub name: String,
50    pub alias: Option<String>,
51    pub item_type: ImportType,
52}
53#[derive(Debug, Clone)]
54pub enum ImportType {
55    Agent,
56    Workflow,
57    Context,
58    Crew,
59    Type,
60    Any,
61}
62#[derive(Debug, Clone)]
63pub struct AgentExport {
64    pub name: String,
65    pub public: bool,
66    pub location: usize,
67}
68#[derive(Debug, Clone)]
69pub struct WorkflowExport {
70    pub name: String,
71    pub public: bool,
72    pub location: usize,
73}
74#[derive(Debug, Clone)]
75pub struct ContextExport {
76    pub name: String,
77    pub public: bool,
78    pub location: usize,
79}
80#[derive(Debug, Clone)]
81pub struct CrewExport {
82    pub name: String,
83    pub public: bool,
84    pub location: usize,
85}
86#[derive(Debug, Clone)]
87pub struct TypeExport {
88    pub name: String,
89    pub public: bool,
90    pub type_def: String,
91}
92#[derive(Debug, Clone)]
93pub struct ModuleMetadata {
94    pub version: String,
95    pub description: Option<String>,
96    pub dependencies: Vec<String>,
97    pub main: bool,
98}
99pub struct ModuleResolver {
100    search_paths: Vec<PathBuf>,
101    cache: HashMap<String, PathBuf>,
102}
103impl ModuleResolver {
104    pub fn new() -> Self {
105        Self {
106            search_paths: vec![
107                PathBuf::from("."), PathBuf::from("./configs"),
108                PathBuf::from("./hlx_modules"),
109            ],
110            cache: HashMap::new(),
111        }
112    }
113    pub fn add_search_path<P: AsRef<Path>>(&mut self, path: P) {
114        self.search_paths.push(path.as_ref().to_path_buf());
115    }
116    pub fn resolve(&mut self, module_name: &str) -> Result<PathBuf> {
117        if let Some(path) = self.cache.get(module_name) {
118            return Ok(path.clone());
119        }
120        let patterns = vec![
121            format!("{}.hlx", module_name), format!("{}/mod.hlx", module_name),
122            format!("hlx/{}.hlx", module_name),
123        ];
124        for search_path in &self.search_paths {
125            for pattern in &patterns {
126                let full_path = search_path.join(pattern);
127                if full_path.exists() {
128                    self.cache.insert(module_name.to_string(), full_path.clone());
129                    return Ok(full_path);
130                }
131            }
132        }
133        Err(anyhow::anyhow!("Module not found: {}", module_name))
134    }
135    pub fn clear_cache(&mut self) {
136        self.cache.clear();
137    }
138}
139impl ModuleSystem {
140    pub fn new() -> Self {
141        Self {
142            modules: HashMap::new(),
143            dependencies: HashMap::new(),
144            dependents: HashMap::new(),
145            asts: HashMap::new(),
146            resolution_order: Vec::new(),
147            resolver: ModuleResolver::new(),
148        }
149    }
150    pub fn load_module(&mut self, path: &Path) -> Result<()> {
151        let path = path.to_path_buf();
152        let content = std::fs::read_to_string(&path).context("Failed to read file")?;
153        let ast = crate::parse(&content)
154            .map_err(|e| HelixError::Compilation(CompilationError {
155                stage: CompilationStage::Parsing,
156                message: e.to_string(),
157                file: Some(path.clone()),
158                recoverable: false,
159            }))?;
160        let deps = self.extract_dependencies(&ast);
161        let imports = self.extract_imports(&ast)?;
162        let exports = self.extract_exports(&ast)?;
163        let module = Module {
164            path: path.clone(),
165            ast: ast.clone(),
166            exports,
167            imports,
168            metadata: ModuleMetadata {
169                version: "1.0.0".to_string(),
170                description: None,
171                dependencies: Vec::new(),
172                main: false,
173            },
174        };
175        self.dependencies.insert(path.clone(), deps.clone());
176        for dep in deps {
177            self.dependents.entry(dep).or_insert_with(HashSet::new).insert(path.clone());
178        }
179        self.modules.insert(path.clone(), module);
180        self.asts.insert(path, ast);
181        Ok(())
182    }
183    fn extract_dependencies(&self, _ast: &HelixAst) -> HashSet<PathBuf> {
184        let deps = HashSet::new();
185        for decl in &_ast.declarations {
186            match decl {
187                _ => {}
188            }
189        }
190        deps
191    }
192    pub fn resolve_dependencies(&mut self) -> Result<()> {
193        if let Some(cycle) = self.find_circular_dependency() {
194            return Err(
195                HelixError::Compilation(CompilationError {
196                        stage: CompilationStage::Validation,
197                        message: format!("Circular dependency detected: {:?}", cycle),
198                        file: cycle.first().cloned(),
199                        recoverable: false,
200                    })
201                    .into(),
202            );
203        }
204        self.resolution_order = self.topological_sort()?;
205        Ok(())
206    }
207    pub fn compilation_order(&self) -> &[PathBuf] {
208        &self.resolution_order
209    }
210    #[allow(dead_code)]
211    fn resolve_import_path(
212        &self,
213        import_path: &str,
214        from_module: &Path,
215    ) -> Result<PathBuf, HelixError> {
216        if import_path.starts_with("./") || import_path.starts_with("../") {
217            let base_dir = from_module.parent().unwrap_or(Path::new("."));
218            return Ok(base_dir.join(import_path).with_extension("hlx"));
219        }
220        if import_path.starts_with("/") {
221            return Ok(PathBuf::from(import_path).with_extension("hlx"));
222        }
223        Ok(PathBuf::from(format!("{}.hlx", import_path)))
224    }
225    fn extract_imports(&self, _ast: &HelixAst) -> Result<ModuleImports, HelixError> {
226        let imports = Vec::new();
227        Ok(ModuleImports { imports })
228    }
229    fn extract_exports(&self, ast: &HelixAst) -> Result<ModuleExports, HelixError> {
230        let mut exports = ModuleExports::default();
231        for (index, declaration) in ast.declarations.iter().enumerate() {
232            match declaration {
233                Declaration::Agent(agent) => {
234                    exports
235                        .agents
236                        .insert(
237                            agent.name.clone(),
238                            AgentExport {
239                                name: agent.name.clone(),
240                                public: true,
241                                location: index,
242                            },
243                        );
244                }
245                Declaration::Workflow(workflow) => {
246                    exports
247                        .workflows
248                        .insert(
249                            workflow.name.clone(),
250                            WorkflowExport {
251                                name: workflow.name.clone(),
252                                public: true,
253                                location: index,
254                            },
255                        );
256                }
257                Declaration::Context(context) => {
258                    exports
259                        .contexts
260                        .insert(
261                            context.name.clone(),
262                            ContextExport {
263                                name: context.name.clone(),
264                                public: true,
265                                location: index,
266                            },
267                        );
268                }
269                Declaration::Crew(crew) => {
270                    exports
271                        .crews
272                        .insert(
273                            crew.name.clone(),
274                            CrewExport {
275                                name: crew.name.clone(),
276                                public: true,
277                                location: index,
278                            },
279                        );
280                }
281                _ => {}
282            }
283        }
284        Ok(exports)
285    }
286    fn find_circular_dependency(&self) -> Option<Vec<PathBuf>> {
287        for (start, _) in &self.dependencies {
288            let mut visited = HashSet::new();
289            let mut rec_stack = HashSet::new();
290            let mut path = Vec::new();
291            if self.has_cycle_util(start, &mut visited, &mut rec_stack, &mut path) {
292                return Some(path);
293            }
294        }
295        None
296    }
297    fn has_cycle_util(
298        &self,
299        node: &PathBuf,
300        visited: &mut HashSet<PathBuf>,
301        rec_stack: &mut HashSet<PathBuf>,
302        path: &mut Vec<PathBuf>,
303    ) -> bool {
304        if rec_stack.contains(node) {
305            path.push(node.clone());
306            return true;
307        }
308        if visited.contains(node) {
309            return false;
310        }
311        visited.insert(node.clone());
312        rec_stack.insert(node.clone());
313        path.push(node.clone());
314        if let Some(deps) = self.dependencies.get(node) {
315            for dep in deps {
316                if self.has_cycle_util(dep, visited, rec_stack, path) {
317                    return true;
318                }
319            }
320        }
321        rec_stack.remove(node);
322        path.pop();
323        false
324    }
325    fn topological_sort(&self) -> Result<Vec<PathBuf>, HelixError> {
326        let mut in_degree: HashMap<PathBuf, usize> = HashMap::new();
327        let mut result = Vec::new();
328        let mut queue = VecDeque::new();
329        for path in self.dependencies.keys() {
330            in_degree.insert(path.clone(), 0);
331        }
332        for (_, deps) in &self.dependencies {
333            for dep in deps {
334                *in_degree.entry(dep.clone()).or_insert(0) += 1;
335            }
336        }
337        for (path, &degree) in &in_degree {
338            if degree == 0 {
339                queue.push_back(path.clone());
340            }
341        }
342        while let Some(node) = queue.pop_front() {
343            result.push(node.clone());
344            if let Some(deps) = self.dependencies.get(&node) {
345                for dep in deps {
346                    if let Some(degree) = in_degree.get_mut(dep) {
347                        *degree -= 1;
348                        if *degree == 0 {
349                            queue.push_back(dep.clone());
350                        }
351                    }
352                }
353            }
354        }
355        if result.len() != self.dependencies.len() {
356            return Err(
357                HelixError::Compilation(CompilationError {
358                    stage: CompilationStage::Validation,
359                    message: "Failed to resolve module dependencies".to_string(),
360                    file: None,
361                    recoverable: false,
362                }),
363            );
364        }
365        Ok(result)
366    }
367    pub fn merge_modules(&self) -> Result<HelixAst, HelixError> {
368        let mut merged_declarations = Vec::new();
369        for path in &self.resolution_order {
370            if let Some(module) = self.modules.get(path) {
371                for decl in &module.ast.declarations {
372                    if !Self::declaration_exists(&merged_declarations, decl) {
373                        merged_declarations.push(decl.clone());
374                    }
375                }
376            }
377        }
378        Ok(HelixAst {
379            declarations: merged_declarations,
380        })
381    }
382    fn declaration_exists(declarations: &[Declaration], decl: &Declaration) -> bool {
383        for existing in declarations {
384            match (existing, decl) {
385                (Declaration::Agent(a1), Declaration::Agent(a2)) => {
386                    if a1.name == a2.name {
387                        return true;
388                    }
389                }
390                (Declaration::Workflow(w1), Declaration::Workflow(w2)) => {
391                    if w1.name == w2.name {
392                        return true;
393                    }
394                }
395                (Declaration::Context(c1), Declaration::Context(c2)) => {
396                    if c1.name == c2.name {
397                        return true;
398                    }
399                }
400                (Declaration::Crew(c1), Declaration::Crew(c2)) => {
401                    if c1.name == c2.name {
402                        return true;
403                    }
404                }
405                _ => {}
406            }
407        }
408        false
409    }
410    pub fn modules(&self) -> &HashMap<PathBuf, Module> {
411        &self.modules
412    }
413    pub fn dependency_graph(&self) -> &HashMap<PathBuf, HashSet<PathBuf>> {
414        &self.dependencies
415    }
416    pub fn get_dependencies<P: AsRef<Path>>(&self, path: P) -> HashSet<PathBuf> {
417        self.dependencies.get(path.as_ref()).cloned().unwrap_or_default()
418    }
419    pub fn get_dependents<P: AsRef<Path>>(&self, path: P) -> HashSet<PathBuf> {
420        self.dependents.get(path.as_ref()).cloned().unwrap_or_default()
421    }
422}
423pub struct DependencyBundler {
424    module_system: ModuleSystem,
425}
426impl DependencyBundler {
427    pub fn new() -> Self {
428        Self {
429            module_system: ModuleSystem::new(),
430        }
431    }
432    pub fn add_root<P: AsRef<Path>>(&mut self, path: P) -> Result<()> {
433        let mut queue = VecDeque::new();
434        let mut processed = HashSet::new();
435        queue.push_back(path.as_ref().to_path_buf());
436        while let Some(current) = queue.pop_front() {
437            if processed.contains(&current) {
438                continue;
439            }
440            self.module_system.load_module(&current)?;
441            processed.insert(current.clone());
442            let deps = self.module_system.get_dependencies(&current);
443            for dep in deps {
444                if !processed.contains(&dep) {
445                    queue.push_back(dep);
446                }
447            }
448        }
449        Ok(())
450    }
451    pub fn build_bundle(&mut self) -> Result<HelixAst> {
452        self.module_system.resolve_dependencies()?;
453        self.module_system
454            .merge_modules()
455            .map_err(|e| anyhow::anyhow!("Failed to merge modules: {}", e))
456    }
457    pub fn get_compilation_order(&self) -> &[PathBuf] {
458        self.module_system.compilation_order()
459    }
460}
461impl Default for ModuleSystem {
462    fn default() -> Self {
463        Self::new()
464    }
465}
466impl Default for DependencyBundler {
467    fn default() -> Self {
468        Self::new()
469    }
470}
471#[cfg(test)]
472mod tests {
473    use super::*;
474    use tempfile::TempDir;
475    #[test]
476    fn test_module_system_creation() {
477        let module_system = ModuleSystem::new();
478        assert!(module_system.modules.is_empty());
479        assert!(module_system.dependencies.is_empty());
480    }
481    #[test]
482    fn test_module_loading() {
483        let temp_dir = TempDir::new().unwrap();
484        let module_path = temp_dir.path().join("test.hlx");
485        std::fs::write(
486                &module_path,
487                r#"
488            agent "test" {
489                model = "gpt-4"
490            }
491        "#,
492            )
493            .unwrap();
494        let mut module_system = ModuleSystem::new();
495        module_system.load_module(&module_path).unwrap();
496        assert_eq!(module_system.modules.len(), 1);
497        assert!(module_system.modules.contains_key(& module_path));
498    }
499    #[test]
500    fn test_export_extraction() {
501        let temp_dir = TempDir::new().unwrap();
502        let module_path = temp_dir.path().join("test.hlx");
503        std::fs::write(
504                &module_path,
505                r#"
506            agent "analyzer" {
507                model = "gpt-4"
508            }
509            
510            workflow "review" {
511                trigger = "manual"
512            }
513        "#,
514            )
515            .unwrap();
516        let mut module_system = ModuleSystem::new();
517        module_system.load_module(&module_path).unwrap();
518        let module = module_system.modules.get(&module_path).unwrap();
519        assert_eq!(module.exports.agents.len(), 1);
520        assert_eq!(module.exports.workflows.len(), 1);
521        assert!(module.exports.agents.contains_key("analyzer"));
522        assert!(module.exports.workflows.contains_key("review"));
523    }
524    #[test]
525    fn test_dependency_bundler() {
526        let mut bundler = DependencyBundler::new();
527        assert!(bundler.module_system.modules.is_empty());
528    }
529    #[test]
530    fn test_module_resolver() {
531        let mut resolver = ModuleResolver::new();
532        resolver.add_search_path("./test");
533        assert_eq!(resolver.search_paths.len(), 4);
534    }
535}