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, °ree) 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(¤t) {
438 continue;
439 }
440 self.module_system.load_module(¤t)?;
441 processed.insert(current.clone());
442 let deps = self.module_system.get_dependencies(¤t);
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}