Skip to main content

palladium/resolver/
mod.rs

1// Module resolver for Palladium
2// "Finding legends across realms"
3
4use crate::ast::{Import, Program};
5use crate::errors::{CompileError, Result};
6use crate::lexer::Lexer;
7use crate::parser::Parser;
8use std::collections::{HashMap, HashSet};
9use std::fs;
10use std::path::PathBuf;
11
12/// Information about a resolved module
13#[derive(Debug, Clone)]
14pub struct ModuleInfo {
15    pub path: PathBuf,
16    pub ast: Program,
17    pub exports: HashSet<String>, // Names of exported items
18}
19
20/// Module resolver handles finding and loading modules
21pub struct ModuleResolver {
22    /// Search paths for modules (like PYTHONPATH)
23    search_paths: Vec<PathBuf>,
24    /// Cache of already loaded modules
25    loaded_modules: HashMap<String, ModuleInfo>,
26}
27
28impl Default for ModuleResolver {
29    fn default() -> Self {
30        Self::new()
31    }
32}
33
34impl ModuleResolver {
35    pub fn new() -> Self {
36        let mut search_paths = vec![
37            PathBuf::from("."),        // Current directory
38            PathBuf::from("examples"), // Examples directory (temporary for testing)
39        ];
40
41        // Add standard library path if it exists
42        if let Ok(exe_path) = std::env::current_exe() {
43            if let Some(parent) = exe_path.parent() {
44                let std_lib = parent.join("std");
45                if std_lib.exists() {
46                    search_paths.push(std_lib.clone());
47                }
48            }
49        }
50
51        // Check for PALLADIUM_PATH environment variable
52        if let Ok(pd_path) = std::env::var("PALLADIUM_PATH") {
53            for path in pd_path.split(':') {
54                search_paths.push(PathBuf::from(path));
55            }
56        }
57
58        Self {
59            search_paths,
60            loaded_modules: HashMap::new(),
61        }
62    }
63
64    /// Add a search path
65    pub fn add_search_path(&mut self, path: PathBuf) {
66        self.search_paths.push(path);
67    }
68
69    /// Resolve all imports in a program
70    pub fn resolve_program(&mut self, program: &Program) -> Result<HashMap<String, ModuleInfo>> {
71        let mut resolved = HashMap::new();
72
73        for import in &program.imports {
74            let module_name = self.import_to_module_name(import);
75            if !self.loaded_modules.contains_key(&module_name) {
76                self.load_module(&module_name)?;
77            }
78
79            if let Some(module_info) = self.loaded_modules.get(&module_name) {
80                // Filter module info based on imported items if specified
81                let filtered_info = if let Some(items) = &import.items {
82                    self.filter_module_info(module_info, items)
83                } else {
84                    module_info.clone()
85                };
86
87                // Use alias if provided, otherwise use the full module name
88                let key = if let Some(alias) = &import.alias {
89                    alias.clone()
90                } else {
91                    module_name
92                };
93                resolved.insert(key, filtered_info);
94            }
95        }
96
97        Ok(resolved)
98    }
99
100    /// Convert import path to module name
101    fn import_to_module_name(&self, import: &Import) -> String {
102        import.path.join("::")
103    }
104
105    /// Filter module info to only include specified items
106    fn filter_module_info(&self, module_info: &ModuleInfo, items: &[String]) -> ModuleInfo {
107        let item_set: HashSet<_> = items.iter().cloned().collect();
108
109        ModuleInfo {
110            path: module_info.path.clone(),
111            ast: module_info.ast.clone(),
112            exports: module_info
113                .exports
114                .iter()
115                .filter(|export_name| item_set.contains(*export_name))
116                .cloned()
117                .collect(),
118        }
119    }
120
121    /// Load a module by name
122    fn load_module(&mut self, module_name: &str) -> Result<()> {
123        // Convert module name to file path (e.g., "std::io" -> "std/io.pd")
124        let module_path = module_name.replace("::", "/");
125        let file_name = format!("{}.pd", module_path);
126
127        // Try to find the module file
128        let mut module_file = None;
129        for search_path in &self.search_paths {
130            let full_path = search_path.join(&file_name);
131            if full_path.exists() {
132                module_file = Some(full_path);
133                break;
134            }
135        }
136
137        let module_file = module_file
138            .ok_or_else(|| CompileError::Generic(format!("Module '{}' not found", module_name)))?;
139
140        // Read and parse the module
141        let source = fs::read_to_string(&module_file).map_err(|e| {
142            CompileError::Generic(format!("Failed to read module '{}': {}", module_name, e))
143        })?;
144
145        let mut lexer = Lexer::new(&source);
146        let tokens = lexer.collect_tokens()?;
147        let mut parser = Parser::new(tokens);
148        let ast = parser.parse()?;
149
150        // Extract exported items (those marked as 'pub')
151        let mut exports = HashSet::new();
152        for item in &ast.items {
153            match item {
154                crate::ast::Item::Function(func) => {
155                    if matches!(func.visibility, crate::ast::Visibility::Public) {
156                        exports.insert(func.name.clone());
157                    }
158                }
159                crate::ast::Item::Struct(struct_def) => {
160                    if matches!(struct_def.visibility, crate::ast::Visibility::Public) {
161                        exports.insert(struct_def.name.clone());
162                    }
163                }
164                crate::ast::Item::Enum(enum_def) => {
165                    // Note: EnumDef doesn't have a visibility field in the current AST
166                    // This would need to be added to the AST to support private enums
167                    // For now, all enums are treated as public
168                    exports.insert(enum_def.name.clone());
169                }
170                crate::ast::Item::Trait(trait_def) => {
171                    if matches!(trait_def.visibility, crate::ast::Visibility::Public) {
172                        exports.insert(trait_def.name.clone());
173                    }
174                }
175                crate::ast::Item::Impl(_) => {
176                    // Impl blocks don't export names directly
177                }
178                crate::ast::Item::TypeAlias(type_alias) => {
179                    if matches!(type_alias.visibility, crate::ast::Visibility::Public) {
180                        exports.insert(type_alias.name.clone());
181                    }
182                }
183                crate::ast::Item::Macro(_) => {
184                    // Macros are handled during expansion phase, skip here
185                }
186            }
187        }
188
189        // Recursively resolve imports in the loaded module
190        let _sub_modules = self.resolve_program(&ast)?;
191
192        // Store the loaded module
193        let module_info = ModuleInfo {
194            path: module_file,
195            ast,
196            exports,
197        };
198
199        self.loaded_modules
200            .insert(module_name.to_string(), module_info);
201
202        Ok(())
203    }
204
205    /// Get all loaded modules
206    pub fn get_loaded_modules(&self) -> &HashMap<String, ModuleInfo> {
207        &self.loaded_modules
208    }
209}
210
211#[cfg(test)]
212mod tests {
213    use super::*;
214
215    #[test]
216    fn test_module_name_conversion() {
217        let resolver = ModuleResolver::new();
218        let import = Import {
219            path: vec!["std".to_string(), "io".to_string()],
220            items: None,
221            alias: None,
222            span: crate::errors::Span::dummy(),
223        };
224
225        assert_eq!(resolver.import_to_module_name(&import), "std::io");
226    }
227}