1use alloc::{
2 format,
3 string::{String, ToString},
4 vec::Vec,
5};
6use hashbrown::{HashMap, HashSet};
7#[cfg(feature = "std")]
8use std::path::PathBuf;
9
10use crate::{
11 ast::{ItemKind, UseTree},
12 lexer::Lexer,
13 modules::{LoadedModule, ModuleExports, ModuleImports, Program},
14 parser::Parser,
15 LustError, Result,
16};
17
18#[derive(Debug, Clone)]
19pub struct EmbeddedModule<'a> {
20 pub module: &'a str,
21 pub parent: Option<&'a str>,
22 pub source: Option<&'a str>,
23}
24
25pub fn build_directory_map(entries: &[EmbeddedModule<'_>]) -> HashMap<String, Vec<String>> {
26 let mut map: HashMap<String, Vec<String>> = HashMap::new();
27 for entry in entries {
28 let parent = entry.parent.unwrap_or("");
29 map.entry(parent.to_string())
30 .or_default()
31 .push(entry.module.to_string());
32 }
33
34 for children in map.values_mut() {
35 children.sort();
36 }
37 map
38}
39
40pub fn load_program_from_embedded(
41 entries: &[EmbeddedModule<'_>],
42 entry_module: &str,
43) -> Result<Program> {
44 let mut module_names: HashSet<String> = entries.iter().map(|e| e.module.to_string()).collect();
45
46 let mut registry: HashMap<String, LoadedModule> = HashMap::new();
47 for entry in entries {
48 if let Some(source) = entry.source {
49 let module = parse_module(entry.module, source)?;
50 registry.insert(entry.module.to_string(), module);
51 } else {
52 module_names.insert(entry.module.to_string());
53 }
54 }
55
56 let dependency_map = build_dependency_map(®istry, &module_names);
57 let mut ordered = Vec::new();
58 let mut visited = HashSet::new();
59 let mut stack = HashSet::new();
60
61 for module in registry.keys().cloned().collect::<Vec<_>>() {
62 visit_dependencies(
63 &module,
64 &dependency_map,
65 &mut visited,
66 &mut stack,
67 &mut ordered,
68 )?;
69 }
70
71 for module in ordered {
72 finalize_module(&module_names, &mut registry, &module)?;
73 }
74
75 let mut modules: Vec<LoadedModule> = registry.into_values().collect();
76 modules.sort_by(|a, b| a.path.cmp(&b.path));
77
78 Ok(Program {
79 modules,
80 entry_module: entry_module.to_string(),
81 })
82}
83
84fn parse_module(module: &str, source: &str) -> Result<LoadedModule> {
85 let mut lexer = Lexer::new(source);
86 let tokens = lexer.tokenize()?;
87 let mut parser = Parser::new(tokens);
88 let items = parser.parse()?;
89
90 Ok(LoadedModule {
91 path: module.to_string(),
92 items,
93 imports: ModuleImports::default(),
94 exports: ModuleExports::default(),
95 init_function: None,
96 #[cfg(feature = "std")]
97 source_path: PathBuf::new(),
98 })
99}
100
101fn build_dependency_map(
102 modules: &HashMap<String, LoadedModule>,
103 module_names: &HashSet<String>,
104) -> HashMap<String, Vec<String>> {
105 let mut deps = HashMap::new();
106 for (name, module) in modules {
107 let collected = collect_dependencies(module, module_names);
108 deps.insert(name.clone(), collected);
109 }
110
111 deps
112}
113
114fn collect_dependencies(module: &LoadedModule, module_names: &HashSet<String>) -> Vec<String> {
115 let mut deps = HashSet::new();
116 for item in &module.items {
117 if let ItemKind::Use { public: _, tree } = &item.kind {
118 collect_deps_from_use(tree, module_names, &mut deps);
119 }
120 }
121
122 deps.into_iter().collect()
123}
124
125fn collect_deps_from_use(
126 tree: &UseTree,
127 module_names: &HashSet<String>,
128 deps: &mut HashSet<String>,
129) {
130 match tree {
131 UseTree::Path { path, .. } => {
132 let full = path.join(".");
133 if module_names.contains(&full) {
134 deps.insert(full);
135 } else if path.len() > 1 {
136 deps.insert(path[..path.len() - 1].join("."));
137 }
138 }
139 UseTree::Group { prefix, items } => {
140 let module = prefix.join(".");
141 if !module.is_empty() {
142 deps.insert(module);
143 }
144
145 for item in items {
146 if item.path.len() > 1 {
147 let mut combined = prefix.clone();
148 combined.extend(item.path[..item.path.len() - 1].iter().cloned());
149 let module_path = combined.join(".");
150 if !module_path.is_empty() {
151 deps.insert(module_path);
152 }
153 }
154 }
155 }
156 UseTree::Glob { prefix } => {
157 deps.insert(prefix.join("."));
158 }
159 }
160}
161
162fn visit_dependencies(
163 module: &str,
164 deps: &HashMap<String, Vec<String>>,
165 visited: &mut HashSet<String>,
166 stack: &mut HashSet<String>,
167 ordered: &mut Vec<String>,
168) -> Result<()> {
169 if visited.contains(module) {
170 return Ok(());
171 }
172
173 if !stack.insert(module.to_string()) {
174 return Err(LustError::Unknown(format!(
175 "Cyclic dependency detected while loading module '{}'",
176 module
177 )));
178 }
179
180 if let Some(list) = deps.get(module) {
181 for dep in list {
182 visit_dependencies(dep, deps, visited, stack, ordered)?;
183 }
184 }
185
186 stack.remove(module);
187 visited.insert(module.to_string());
188 ordered.push(module.to_string());
189 Ok(())
190}
191
192fn finalize_module(
193 module_names: &HashSet<String>,
194 registry: &mut HashMap<String, LoadedModule>,
195 module_name: &str,
196) -> Result<()> {
197 let mut module = registry
198 .remove(module_name)
199 .ok_or_else(|| LustError::Unknown(format!("Unknown module '{}'", module_name)))?;
200
201 let registry_ref = ModuleRegistryView { modules: registry };
202 for item in &module.items {
203 if let ItemKind::Use { tree, .. } = &item.kind {
204 process_use_tree(®istry_ref, module_names, tree, &mut module.imports)?;
205 }
206 }
207
208 for item in &module.items {
209 if let ItemKind::Use { public: true, tree } = &item.kind {
210 apply_reexport(®istry_ref, module_names, tree, &mut module.exports)?;
211 }
212 }
213
214 let tail = simple_tail(module_name);
215 module
216 .imports
217 .module_aliases
218 .entry(tail.to_string())
219 .or_insert_with(|| module_name.to_string());
220
221 registry.insert(module_name.to_string(), module);
222 Ok(())
223}
224
225struct ModuleRegistryView<'a> {
226 modules: &'a HashMap<String, LoadedModule>,
227}
228
229impl<'a> ModuleRegistryView<'a> {
230 fn get(&self, name: &str) -> Option<&'a LoadedModule> {
231 self.modules.get(name)
232 }
233}
234
235fn process_use_tree(
236 registry: &ModuleRegistryView<'_>,
237 module_names: &HashSet<String>,
238 tree: &UseTree,
239 imports: &mut ModuleImports,
240) -> Result<()> {
241 match tree {
242 UseTree::Path { path, alias, .. } => {
243 let full = path.join(".");
244 if module_names.contains(&full) {
245 let alias_name = alias
246 .clone()
247 .unwrap_or_else(|| path.last().unwrap().clone());
248 imports.module_aliases.insert(alias_name, full);
249 } else if path.len() > 1 {
250 let module = path[..path.len() - 1].join(".");
251 let item = path.last().unwrap().clone();
252 let alias_name = alias.clone().unwrap_or_else(|| item.clone());
253 let classification = classify_import_target(registry, &module, &item);
254 let fq = format!("{}.{}", module, item);
255 if classification.import_value {
256 imports
257 .function_aliases
258 .insert(alias_name.clone(), fq.clone());
259 }
260
261 if classification.import_type {
262 imports.type_aliases.insert(alias_name, fq);
263 }
264 }
265 }
266 UseTree::Group { prefix, items } => {
267 for item in items {
268 if item.path.is_empty() {
269 continue;
270 }
271
272 let alias_name = item
273 .alias
274 .clone()
275 .unwrap_or_else(|| item.path.last().unwrap().clone());
276 let mut full_segments = prefix.clone();
277 full_segments.extend(item.path.clone());
278 let full = full_segments.join(".");
279 if module_names.contains(&full) {
280 imports.module_aliases.insert(alias_name, full);
281 continue;
282 }
283
284 let mut module_segments = full_segments.clone();
285 let item_name = module_segments.pop().unwrap();
286 let module_path = module_segments.join(".");
287 let fq_name = if module_path.is_empty() {
288 item_name.clone()
289 } else {
290 format!("{}.{}", module_path, item_name)
291 };
292 let classification = classify_import_target(registry, &module_path, &item_name);
293 if classification.import_value {
294 imports
295 .function_aliases
296 .insert(alias_name.clone(), fq_name.clone());
297 }
298
299 if classification.import_type {
300 imports.type_aliases.insert(alias_name, fq_name);
301 }
302 }
303 }
304 UseTree::Glob { prefix } => {
305 let module = prefix.join(".");
306 if let Some(loaded) = registry.get(&module) {
307 for (name, fq) in &loaded.exports.functions {
308 imports.function_aliases.insert(name.clone(), fq.clone());
309 }
310
311 for (name, fq) in &loaded.exports.types {
312 imports.type_aliases.insert(name.clone(), fq.clone());
313 }
314 }
315
316 if !module.is_empty() {
317 let alias_name = prefix.last().cloned().unwrap_or_else(|| module.clone());
318 imports.module_aliases.insert(alias_name, module);
319 }
320 }
321 }
322
323 Ok(())
324}
325
326fn apply_reexport(
327 registry: &ModuleRegistryView<'_>,
328 module_names: &HashSet<String>,
329 tree: &UseTree,
330 exports: &mut ModuleExports,
331) -> Result<()> {
332 match tree {
333 UseTree::Path { path, alias, .. } => {
334 if path.len() == 1 {
335 return Ok(());
336 }
337
338 let module = path[..path.len() - 1].join(".");
339 let item = path.last().unwrap().clone();
340 let alias_name = alias.clone().unwrap_or_else(|| item.clone());
341 let fq = format!("{}.{}", module, item);
342 let classification = classify_import_target(registry, &module, &item);
343 if classification.import_type {
344 exports.types.insert(alias_name.clone(), fq.clone());
345 }
346
347 if classification.import_value {
348 exports.functions.insert(alias_name, fq);
349 }
350
351 Ok(())
352 }
353 UseTree::Group { prefix, items } => {
354 for item in items {
355 if item.path.is_empty() {
356 continue;
357 }
358
359 let mut full_segments = prefix.clone();
360 full_segments.extend(item.path.clone());
361 let full = full_segments.join(".");
362 if module_names.contains(&full) {
363 continue;
364 }
365
366 let mut module_segments = full_segments.clone();
367 let item_name = module_segments.pop().unwrap();
368 let module_path = module_segments.join(".");
369 let fq_name = if module_path.is_empty() {
370 item_name.clone()
371 } else {
372 format!("{}.{}", module_path, item_name)
373 };
374 let alias_name = item
375 .alias
376 .clone()
377 .unwrap_or_else(|| item.path.last().unwrap().clone());
378 let classification = classify_import_target(registry, &module_path, &item_name);
379 if classification.import_type {
380 exports.types.insert(alias_name.clone(), fq_name.clone());
381 }
382
383 if classification.import_value {
384 exports.functions.insert(alias_name, fq_name);
385 }
386 }
387
388 Ok(())
389 }
390 UseTree::Glob { prefix } => {
391 let module = prefix.join(".");
392 if let Some(loaded) = registry.get(&module) {
393 for (n, fq) in &loaded.exports.types {
394 exports.types.insert(n.clone(), fq.clone());
395 }
396
397 for (n, fq) in &loaded.exports.functions {
398 exports.functions.insert(n.clone(), fq.clone());
399 }
400 }
401
402 Ok(())
403 }
404 }
405}
406
407#[derive(Clone, Copy)]
408struct ImportResolution {
409 import_value: bool,
410 import_type: bool,
411}
412
413impl ImportResolution {
414 fn both() -> Self {
415 Self {
416 import_value: true,
417 import_type: true,
418 }
419 }
420}
421
422fn classify_import_target(
423 registry: &ModuleRegistryView<'_>,
424 module_path: &str,
425 item_name: &str,
426) -> ImportResolution {
427 if module_path.is_empty() {
428 return ImportResolution::both();
429 }
430
431 if let Some(module) = registry.get(module_path) {
432 let has_value = module.exports.functions.contains_key(item_name);
433 let has_type = module.exports.types.contains_key(item_name);
434 if has_value || has_type {
435 return ImportResolution {
436 import_value: has_value,
437 import_type: has_type,
438 };
439 }
440 }
441
442 ImportResolution::both()
443}
444
445fn simple_tail(module_path: &str) -> &str {
446 module_path
447 .rsplit_once('.')
448 .map(|(_, n)| n)
449 .unwrap_or(module_path)
450}