lust/modules/
embedded.rs

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(&registry, &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(&registry_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(&registry_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}