helix/dna/mds/
modules.rs

1use std::path::{Path, PathBuf};
2use std::collections::{HashMap, HashSet, VecDeque};
3use anyhow::{Result, Context};
4use crate::dna::atp::ast::{HelixAst, Declaration};
5use crate::dna::hel::error::{HlxError, 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| HlxError::compilation_error(
155                format!("Parsing error: {}", e),
156                "Check syntax and file format",
157            ))?;
158        let deps = self.extract_dependencies(&ast);
159        let imports = self.extract_imports(&ast)?;
160        let exports = self.extract_exports(&ast)?;
161        let module = Module {
162            path: path.clone(),
163            ast: ast.clone(),
164            exports,
165            imports,
166            metadata: ModuleMetadata {
167                version: "1.0.0".to_string(),
168                description: None,
169                dependencies: Vec::new(),
170                main: false,
171            },
172        };
173        self.dependencies.insert(path.clone(), deps.clone());
174        for dep in deps {
175            self.dependents.entry(dep).or_insert_with(HashSet::new).insert(path.clone());
176        }
177        self.modules.insert(path.clone(), module);
178        self.asts.insert(path, ast);
179        Ok(())
180    }
181    fn extract_dependencies(&self, _ast: &HelixAst) -> HashSet<PathBuf> {
182        let deps = HashSet::new();
183        for decl in &_ast.declarations {
184            match decl {
185                _ => {}
186            }
187        }
188        deps
189    }
190    pub fn resolve_dependencies(&mut self) -> Result<()> {
191        if let Some(cycle) = self.find_circular_dependency() {
192            return Err(
193                HlxError::compilation_error(
194                        format!("Circular dependency detected: {:?}", cycle),
195                        "Check module dependencies",
196                    )
197                    .into(),
198            );
199        }
200        self.resolution_order = self.topological_sort()?;
201        Ok(())
202    }
203    pub fn compilation_order(&self) -> &[PathBuf] {
204        &self.resolution_order
205    }
206    #[allow(dead_code)]
207    fn resolve_import_path(
208        &self,
209        import_path: &str,
210        from_module: &Path,
211    ) -> Result<PathBuf, HlxError> {
212        if import_path.starts_with("./") || import_path.starts_with("../") {
213            let base_dir = from_module.parent().unwrap_or(Path::new("."));
214            return Ok(base_dir.join(import_path).with_extension("hlx"));
215        }
216        if import_path.starts_with("/") {
217            return Ok(PathBuf::from(import_path).with_extension("hlx"));
218        }
219        Ok(PathBuf::from(format!("{}.hlx", import_path)))
220    }
221    fn extract_imports(&self, _ast: &HelixAst) -> Result<ModuleImports, HlxError> {
222        let imports = Vec::new();
223        Ok(ModuleImports { imports })
224    }
225    fn extract_exports(&self, ast: &HelixAst) -> Result<ModuleExports, HlxError> {
226        let mut exports = ModuleExports::default();
227        for (index, declaration) in ast.declarations.iter().enumerate() {
228            match declaration {
229                Declaration::Agent(agent) => {
230                    exports
231                        .agents
232                        .insert(
233                            agent.name.clone(),
234                            AgentExport {
235                                name: agent.name.clone(),
236                                public: true,
237                                location: index,
238                            },
239                        );
240                }
241                Declaration::Workflow(workflow) => {
242                    exports
243                        .workflows
244                        .insert(
245                            workflow.name.clone(),
246                            WorkflowExport {
247                                name: workflow.name.clone(),
248                                public: true,
249                                location: index,
250                            },
251                        );
252                }
253                Declaration::Context(context) => {
254                    exports
255                        .contexts
256                        .insert(
257                            context.name.clone(),
258                            ContextExport {
259                                name: context.name.clone(),
260                                public: true,
261                                location: index,
262                            },
263                        );
264                }
265                Declaration::Crew(crew) => {
266                    exports
267                        .crews
268                        .insert(
269                            crew.name.clone(),
270                            CrewExport {
271                                name: crew.name.clone(),
272                                public: true,
273                                location: index,
274                            },
275                        );
276                }
277                _ => {}
278            }
279        }
280        Ok(exports)
281    }
282    fn find_circular_dependency(&self) -> Option<Vec<PathBuf>> {
283        for (start, _) in &self.dependencies {
284            let mut visited = HashSet::new();
285            let mut rec_stack = HashSet::new();
286            let mut path = Vec::new();
287            if self.has_cycle_util(start, &mut visited, &mut rec_stack, &mut path) {
288                return Some(path);
289            }
290        }
291        None
292    }
293    fn has_cycle_util(
294        &self,
295        node: &PathBuf,
296        visited: &mut HashSet<PathBuf>,
297        rec_stack: &mut HashSet<PathBuf>,
298        path: &mut Vec<PathBuf>,
299    ) -> bool {
300        if rec_stack.contains(node) {
301            path.push(node.clone());
302            return true;
303        }
304        if visited.contains(node) {
305            return false;
306        }
307        visited.insert(node.clone());
308        rec_stack.insert(node.clone());
309        path.push(node.clone());
310        if let Some(deps) = self.dependencies.get(node) {
311            for dep in deps {
312                if self.has_cycle_util(dep, visited, rec_stack, path) {
313                    return true;
314                }
315            }
316        }
317        rec_stack.remove(node);
318        path.pop();
319        false
320    }
321    fn topological_sort(&self) -> Result<Vec<PathBuf>, HlxError> {
322        let mut in_degree: HashMap<PathBuf, usize> = HashMap::new();
323        let mut result = Vec::new();
324        let mut queue = VecDeque::new();
325        for path in self.dependencies.keys() {
326            in_degree.insert(path.clone(), 0);
327        }
328        for (_, deps) in &self.dependencies {
329            for dep in deps {
330                *in_degree.entry(dep.clone()).or_insert(0) += 1;
331            }
332        }
333        for (path, &degree) in &in_degree {
334            if degree == 0 {
335                queue.push_back(path.clone());
336            }
337        }
338        while let Some(node) = queue.pop_front() {
339            result.push(node.clone());
340            if let Some(deps) = self.dependencies.get(&node) {
341                for dep in deps {
342                    if let Some(degree) = in_degree.get_mut(dep) {
343                        *degree -= 1;
344                        if *degree == 0 {
345                            queue.push_back(dep.clone());
346                        }
347                    }
348                }
349            }
350        }
351        if result.len() != self.dependencies.len() {
352            return Err(
353                HlxError::compilation_error(
354                    "Failed to resolve module dependencies",
355                    "Check module dependency resolution",
356                ),
357            );
358        }
359        Ok(result)
360    }
361    pub fn merge_modules(&self) -> Result<HelixAst, HlxError> {
362        let mut merged_declarations = Vec::new();
363        for path in &self.resolution_order {
364            if let Some(module) = self.modules.get(path) {
365                for decl in &module.ast.declarations {
366                    if !Self::declaration_exists(&merged_declarations, decl) {
367                        merged_declarations.push(decl.clone());
368                    }
369                }
370            }
371        }
372        Ok(HelixAst {
373            declarations: merged_declarations,
374        })
375    }
376    fn declaration_exists(declarations: &[Declaration], decl: &Declaration) -> bool {
377        for existing in declarations {
378            match (existing, decl) {
379                (Declaration::Agent(a1), Declaration::Agent(a2)) => {
380                    if a1.name == a2.name {
381                        return true;
382                    }
383                }
384                (Declaration::Workflow(w1), Declaration::Workflow(w2)) => {
385                    if w1.name == w2.name {
386                        return true;
387                    }
388                }
389                (Declaration::Context(c1), Declaration::Context(c2)) => {
390                    if c1.name == c2.name {
391                        return true;
392                    }
393                }
394                (Declaration::Crew(c1), Declaration::Crew(c2)) => {
395                    if c1.name == c2.name {
396                        return true;
397                    }
398                }
399                _ => {}
400            }
401        }
402        false
403    }
404    pub fn modules(&self) -> &HashMap<PathBuf, Module> {
405        &self.modules
406    }
407    pub fn dependency_graph(&self) -> &HashMap<PathBuf, HashSet<PathBuf>> {
408        &self.dependencies
409    }
410    pub fn get_dependencies<P: AsRef<Path>>(&self, path: P) -> HashSet<PathBuf> {
411        self.dependencies.get(path.as_ref()).cloned().unwrap_or_default()
412    }
413    pub fn get_dependents<P: AsRef<Path>>(&self, path: P) -> HashSet<PathBuf> {
414        self.dependents.get(path.as_ref()).cloned().unwrap_or_default()
415    }
416}
417pub struct DependencyBundler {
418    module_system: ModuleSystem,
419}
420impl DependencyBundler {
421    pub fn new() -> Self {
422        Self {
423            module_system: ModuleSystem::new(),
424        }
425    }
426    pub fn add_root<P: AsRef<Path>>(&mut self, path: P) -> Result<()> {
427        let mut queue = VecDeque::new();
428        let mut processed = HashSet::new();
429        queue.push_back(path.as_ref().to_path_buf());
430        while let Some(current) = queue.pop_front() {
431            if processed.contains(&current) {
432                continue;
433            }
434            self.module_system.load_module(&current)?;
435            processed.insert(current.clone());
436            let deps = self.module_system.get_dependencies(&current);
437            for dep in deps {
438                if !processed.contains(&dep) {
439                    queue.push_back(dep);
440                }
441            }
442        }
443        Ok(())
444    }
445    pub fn build_bundle(&mut self) -> Result<HelixAst> {
446        self.module_system.resolve_dependencies()?;
447        self.module_system
448            .merge_modules()
449            .map_err(|e| anyhow::anyhow!("Failed to merge modules: {}", e))
450    }
451    pub fn get_compilation_order(&self) -> &[PathBuf] {
452        self.module_system.compilation_order()
453    }
454}
455impl Default for ModuleSystem {
456    fn default() -> Self {
457        Self::new()
458    }
459}
460impl Default for DependencyBundler {
461    fn default() -> Self {
462        Self::new()
463    }
464}
465pub struct DependencyGraph;
466impl DependencyGraph {
467    pub fn new() -> Self {
468        Self
469    }
470    pub fn check_circular(&self) -> Result<(), String> {
471        Ok(())
472    }
473}
474#[cfg(test)]
475mod tests {
476    use super::*;
477    use tempfile::TempDir;
478    #[test]
479    fn test_module_system_creation() {
480        let module_system = ModuleSystem::new();
481        assert!(module_system.modules.is_empty());
482        assert!(module_system.dependencies.is_empty());
483    }
484    #[test]
485    fn test_module_loading() {
486        let temp_dir = TempDir::new().unwrap();
487        let module_path = temp_dir.path().join("test.hlx");
488        std::fs::write(
489                &module_path,
490                r#"
491            agent "test" {
492                model = "gpt-4"
493            }
494        "#,
495            )
496            .unwrap();
497        let mut module_system = ModuleSystem::new();
498        module_system.load_module(&module_path).unwrap();
499        assert_eq!(module_system.modules.len(), 1);
500        assert!(module_system.modules.contains_key(& module_path));
501    }
502    #[test]
503    fn test_export_extraction() {
504        let temp_dir = TempDir::new().unwrap();
505        let module_path = temp_dir.path().join("test.hlx");
506        std::fs::write(
507                &module_path,
508                r#"
509            agent "analyzer" {
510                model = "gpt-4"
511            }
512            
513            workflow "review" {
514                trigger = "manual"
515            }
516        "#,
517            )
518            .unwrap();
519        let mut module_system = ModuleSystem::new();
520        module_system.load_module(&module_path).unwrap();
521        let module = module_system.modules.get(&module_path).unwrap();
522        assert_eq!(module.exports.agents.len(), 1);
523        assert_eq!(module.exports.workflows.len(), 1);
524        assert!(module.exports.agents.contains_key("analyzer"));
525        assert!(module.exports.workflows.contains_key("review"));
526    }
527    #[test]
528    fn test_dependency_bundler() {
529        let mut bundler = DependencyBundler::new();
530        assert!(bundler.module_system.modules.is_empty());
531    }
532    #[test]
533    fn test_module_resolver() {
534        let mut resolver = ModuleResolver::new();
535        resolver.add_search_path("./test");
536        assert_eq!(resolver.search_paths.len(), 4);
537    }
538}