1use crate::{
2 ast::{FunctionDef, Item, ItemKind, UseTree, Visibility},
3 error::{LustError, Result},
4 lexer::Lexer,
5 parser::Parser,
6};
7use alloc::{format, string::String, vec, vec::Vec};
8use hashbrown::{HashMap, HashSet};
9#[cfg(feature = "std")]
10use std::{
11 fs,
12 path::{Path, PathBuf},
13};
14#[derive(Debug, Clone, Default)]
15pub struct ModuleImports {
16 pub function_aliases: HashMap<String, String>,
17 pub module_aliases: HashMap<String, String>,
18 pub type_aliases: HashMap<String, String>,
19}
20
21#[derive(Debug, Clone, Default)]
22pub struct ModuleExports {
23 pub functions: HashMap<String, String>,
24 pub types: HashMap<String, String>,
25}
26
27pub mod embedded;
28
29#[derive(Debug, Clone)]
30pub struct LoadedModule {
31 pub path: String,
32 pub items: Vec<Item>,
33 pub imports: ModuleImports,
34 pub exports: ModuleExports,
35 pub init_function: Option<String>,
36 #[cfg(feature = "std")]
37 pub source_path: PathBuf,
38}
39
40#[derive(Debug, Clone)]
41pub struct Program {
42 pub modules: Vec<LoadedModule>,
43 pub entry_module: String,
44}
45
46pub use embedded::{build_directory_map, load_program_from_embedded, EmbeddedModule};
47#[derive(Clone, Copy, Debug, Default)]
48struct ImportResolution {
49 import_value: bool,
50 import_type: bool,
51}
52
53impl ImportResolution {
54 fn both() -> Self {
55 Self {
56 import_value: true,
57 import_type: true,
58 }
59 }
60}
61
62#[cfg(feature = "std")]
63pub struct ModuleLoader {
64 base_dir: PathBuf,
65 cache: HashMap<String, LoadedModule>,
66 visited: HashSet<String>,
67 source_overrides: HashMap<PathBuf, String>,
68 module_roots: HashMap<String, Vec<ModuleRoot>>,
69}
70
71#[cfg(feature = "std")]
72#[derive(Debug, Clone)]
73struct ModuleRoot {
74 base: PathBuf,
75 root_module: Option<PathBuf>,
76}
77
78#[cfg(feature = "std")]
79impl ModuleLoader {
80 pub fn new(base_dir: impl Into<PathBuf>) -> Self {
81 Self {
82 base_dir: base_dir.into(),
83 cache: HashMap::new(),
84 visited: HashSet::new(),
85 source_overrides: HashMap::new(),
86 module_roots: HashMap::new(),
87 }
88 }
89
90 pub fn set_source_overrides(&mut self, overrides: HashMap<PathBuf, String>) {
91 self.source_overrides = overrides;
92 }
93
94 pub fn set_source_override<P: Into<PathBuf>, S: Into<String>>(&mut self, path: P, source: S) {
95 self.source_overrides.insert(path.into(), source.into());
96 }
97
98 pub fn clear_source_overrides(&mut self) {
99 self.source_overrides.clear();
100 }
101
102 pub fn add_module_root(
103 &mut self,
104 prefix: impl Into<String>,
105 root: impl Into<PathBuf>,
106 root_module: Option<PathBuf>,
107 ) {
108 self.module_roots
109 .entry(prefix.into())
110 .or_default()
111 .push(ModuleRoot {
112 base: root.into(),
113 root_module,
114 });
115 }
116
117 pub fn load_program_from_entry(&mut self, entry_file: &str) -> Result<Program> {
118 let entry_path = Path::new(entry_file);
119 let entry_dir = entry_path.parent().unwrap_or_else(|| Path::new("."));
120 self.base_dir = entry_dir.to_path_buf();
121 let entry_module = Self::module_path_for_file(entry_path);
122 let mut order: Vec<String> = Vec::new();
123 let mut stack: HashSet<String> = HashSet::new();
124 self.load_module_recursive(&entry_module, &mut order, &mut stack, true)?;
125 let modules = order
126 .into_iter()
127 .filter_map(|m| self.cache.get(&m).cloned())
128 .collect::<Vec<_>>();
129 Ok(Program {
130 modules,
131 entry_module,
132 })
133 }
134
135 fn load_module_recursive(
136 &mut self,
137 module_path: &str,
138 order: &mut Vec<String>,
139 stack: &mut HashSet<String>,
140 is_entry: bool,
141 ) -> Result<()> {
142 if self.visited.contains(module_path) {
143 return Ok(());
144 }
145
146 if !stack.insert(module_path.to_string()) {
147 return Ok(());
148 }
149
150 let mut loaded = self.load_single_module(module_path, is_entry)?;
151 self.cache.insert(module_path.to_string(), loaded.clone());
152 let deps = self.collect_dependencies(&loaded.items);
153 for dep in deps {
154 self.load_module_recursive(&dep, order, stack, false)?;
155 }
156
157 self.finalize_module(&mut loaded)?;
158 self.cache.insert(module_path.to_string(), loaded.clone());
159 self.visited.insert(module_path.to_string());
160 order.push(module_path.to_string());
161 stack.remove(module_path);
162 Ok(())
163 }
164
165 fn load_single_module(&self, module_path: &str, is_entry: bool) -> Result<LoadedModule> {
166 let file = self.file_for_module_path(module_path);
167 let source = if let Some(src) = self.source_overrides.get(&file) {
168 src.clone()
169 } else {
170 fs::read_to_string(&file).map_err(|e| {
171 LustError::Unknown(format!("Failed to read module '{}': {}", file.display(), e))
172 })?
173 };
174 let mut lexer = Lexer::new(&source);
175 let tokens = lexer
176 .tokenize()
177 .map_err(|err| Self::attach_module_to_error(err, module_path))?;
178 let mut parser = Parser::new(tokens);
179 let mut items = parser
180 .parse()
181 .map_err(|err| Self::attach_module_to_error(err, module_path))?;
182 let mut imports = ModuleImports::default();
183 let mut exports = ModuleExports::default();
184 let mut new_items: Vec<Item> = Vec::new();
185 let mut init_function: Option<String> = None;
186 for item in items.drain(..) {
187 match &item.kind {
188 ItemKind::Function(func) => {
189 let mut f = func.clone();
190 if !f.is_method && !f.name.contains(':') && !f.name.contains('.') {
191 let fq = format!("{}.{}", module_path, f.name);
192 imports.function_aliases.insert(f.name.clone(), fq.clone());
193 f.name = fq.clone();
194 if matches!(f.visibility, Visibility::Public) {
195 exports
196 .functions
197 .insert(self.simple_name(&f.name).to_string(), f.name.clone());
198 }
199 } else {
200 if matches!(f.visibility, Visibility::Public) {
201 exports
202 .functions
203 .insert(self.simple_name(&f.name).to_string(), f.name.clone());
204 }
205 }
206
207 new_items.push(Item::new(ItemKind::Function(f), item.span));
208 }
209
210 ItemKind::Struct(s) => {
211 if matches!(s.visibility, Visibility::Public) {
212 exports
213 .types
214 .insert(s.name.clone(), format!("{}.{}", module_path, s.name));
215 }
216
217 new_items.push(item);
218 }
219
220 ItemKind::Enum(e) => {
221 if matches!(e.visibility, Visibility::Public) {
222 exports
223 .types
224 .insert(e.name.clone(), format!("{}.{}", module_path, e.name));
225 }
226
227 new_items.push(item);
228 }
229
230 ItemKind::Trait(t) => {
231 if matches!(t.visibility, Visibility::Public) {
232 exports
233 .types
234 .insert(t.name.clone(), format!("{}.{}", module_path, t.name));
235 }
236
237 new_items.push(item);
238 }
239
240 ItemKind::TypeAlias { name, .. } => {
241 exports
242 .types
243 .insert(name.clone(), format!("{}.{}", module_path, name));
244 new_items.push(item);
245 }
246
247 ItemKind::Script(stmts) => {
248 if is_entry {
249 new_items.push(Item::new(ItemKind::Script(stmts.clone()), item.span));
250 } else {
251 let init_name = format!("__init@{}", module_path);
252 let func = FunctionDef {
253 name: init_name.clone(),
254 type_params: vec![],
255 trait_bounds: vec![],
256 params: vec![],
257 return_type: None,
258 body: stmts.clone(),
259 is_method: false,
260 visibility: Visibility::Private,
261 };
262 new_items.push(Item::new(ItemKind::Function(func), item.span));
263 init_function = Some(init_name);
264 }
265 }
266
267 ItemKind::Extern {
268 abi,
269 items: extern_items,
270 } => {
271 let mut rewritten = Vec::new();
272 for extern_item in extern_items {
273 match extern_item {
274 crate::ast::ExternItem::Function {
275 name,
276 params,
277 return_type,
278 } => {
279 let mut new_name = name.clone();
280 if !new_name.contains('.') && !new_name.contains(':') {
281 new_name = format!("{}.{}", module_path, new_name);
282 }
283
284 exports.functions.insert(
285 self.simple_name(&new_name).to_string(),
286 new_name.clone(),
287 );
288 imports.function_aliases.insert(
289 self.simple_name(&new_name).to_string(),
290 new_name.clone(),
291 );
292
293 rewritten.push(crate::ast::ExternItem::Function {
294 name: new_name,
295 params: params.clone(),
296 return_type: return_type.clone(),
297 });
298 }
299 }
300 }
301 new_items.push(Item::new(
302 ItemKind::Extern {
303 abi: abi.clone(),
304 items: rewritten,
305 },
306 item.span,
307 ));
308 }
309
310 _ => {
311 new_items.push(item);
312 }
313 }
314 }
315
316 Ok(LoadedModule {
317 path: module_path.to_string(),
318 items: new_items,
319 imports,
320 exports,
321 init_function,
322 source_path: file,
323 })
324 }
325
326 fn collect_dependencies(&self, items: &[Item]) -> Vec<String> {
327 let mut deps = HashSet::new();
328 for item in items {
329 if let ItemKind::Use { public: _, tree } = &item.kind {
330 self.collect_deps_from_use(tree, &mut deps);
331 }
332 }
333
334 deps.into_iter().collect()
335 }
336
337 fn finalize_module(&mut self, module: &mut LoadedModule) -> Result<()> {
338 for item in &module.items {
339 if let ItemKind::Use { tree, .. } = &item.kind {
340 self.process_use_tree(tree, &mut module.imports)?;
341 }
342 }
343
344 for item in &module.items {
345 if let ItemKind::Use { public: true, tree } = &item.kind {
346 self.apply_reexport(tree, &mut module.exports)?;
347 }
348 }
349
350 module
351 .imports
352 .module_aliases
353 .entry(self.simple_tail(&module.path).to_string())
354 .or_insert_with(|| module.path.clone());
355 Ok(())
356 }
357
358 fn collect_deps_from_use(&self, tree: &UseTree, deps: &mut HashSet<String>) {
359 match tree {
360 UseTree::Path {
361 path,
362 alias: _,
363 import_module: _,
364 } => {
365 let full = path.join(".");
366 let full_file = self.file_for_module_path(&full);
367 if self.module_source_known(&full, &full_file) {
368 deps.insert(full);
369 } else if path.len() > 1 {
370 deps.insert(path[..path.len() - 1].join("."));
371 }
372 }
373
374 UseTree::Group { prefix, items } => {
375 let module = prefix.join(".");
376 if !module.is_empty() {
377 deps.insert(module);
378 }
379
380 for item in items {
381 if item.path.len() > 1 {
382 let mut combined: Vec<String> = prefix.clone();
383 combined.extend(item.path[..item.path.len() - 1].iter().cloned());
384 let module_path = combined.join(".");
385 if !module_path.is_empty() {
386 deps.insert(module_path);
387 }
388 }
389 }
390 }
391
392 UseTree::Glob { prefix } => {
393 deps.insert(prefix.join("."));
394 }
395 }
396 }
397
398 fn process_use_tree(&self, tree: &UseTree, imports: &mut ModuleImports) -> Result<()> {
399 match tree {
400 UseTree::Path { path, alias, .. } => {
401 let full = path.join(".");
402 let full_file = self.file_for_module_path(&full);
403 if self.module_source_known(&full, &full_file) {
404 let alias_name = alias
405 .clone()
406 .unwrap_or_else(|| path.last().unwrap().clone());
407 imports.module_aliases.insert(alias_name, full);
408 } else if path.len() > 1 {
409 let module = path[..path.len() - 1].join(".");
410 let item = path.last().unwrap().clone();
411 let alias_name = alias.clone().unwrap_or_else(|| item.clone());
412 let classification = self.classify_import_target(&module, &item);
413 let fq = format!("{}.{}", module, item);
414 if classification.import_value {
415 imports
416 .function_aliases
417 .insert(alias_name.clone(), fq.clone());
418 }
419
420 if classification.import_type {
421 imports.type_aliases.insert(alias_name, fq);
422 }
423 }
424 }
425
426 UseTree::Group { prefix, items } => {
427 for item in items {
428 if item.path.is_empty() {
429 continue;
430 }
431
432 let alias_name = item
433 .alias
434 .clone()
435 .unwrap_or_else(|| item.path.last().unwrap().clone());
436 let mut full_segments = prefix.clone();
437 full_segments.extend(item.path.clone());
438 let full = full_segments.join(".");
439 let full_file = self.file_for_module_path(&full);
440 if self.module_source_known(&full, &full_file) {
441 imports.module_aliases.insert(alias_name, full);
442 continue;
443 }
444
445 let mut module_segments = full_segments.clone();
446 let item_name = module_segments.pop().unwrap();
447 let module_path = module_segments.join(".");
448 let fq_name = if module_path.is_empty() {
449 item_name.clone()
450 } else {
451 format!("{}.{}", module_path, item_name)
452 };
453 let classification = self.classify_import_target(&module_path, &item_name);
454 if classification.import_value {
455 imports
456 .function_aliases
457 .insert(alias_name.clone(), fq_name.clone());
458 }
459
460 if classification.import_type {
461 imports.type_aliases.insert(alias_name.clone(), fq_name);
462 }
463 }
464 }
465
466 UseTree::Glob { prefix } => {
467 let module = prefix.join(".");
468 if let Some(loaded) = self.cache.get(&module) {
469 for (name, fq) in &loaded.exports.functions {
470 imports.function_aliases.insert(name.clone(), fq.clone());
471 }
472
473 for (name, fq) in &loaded.exports.types {
474 imports.type_aliases.insert(name.clone(), fq.clone());
475 }
476 }
477
478 let alias_name = prefix.last().cloned().unwrap_or_else(|| module.clone());
479 if !module.is_empty() {
480 imports.module_aliases.insert(alias_name, module);
481 }
482 }
483 }
484
485 Ok(())
486 }
487
488 fn attach_module_to_error(error: LustError, module_path: &str) -> LustError {
489 match error {
490 LustError::LexerError {
491 line,
492 column,
493 message,
494 module,
495 } => LustError::LexerError {
496 line,
497 column,
498 message,
499 module: module.or_else(|| Some(module_path.to_string())),
500 },
501 LustError::ParserError {
502 line,
503 column,
504 message,
505 module,
506 } => LustError::ParserError {
507 line,
508 column,
509 message,
510 module: module.or_else(|| Some(module_path.to_string())),
511 },
512 LustError::CompileErrorWithSpan {
513 message,
514 line,
515 column,
516 module,
517 } => LustError::CompileErrorWithSpan {
518 message,
519 line,
520 column,
521 module: module.or_else(|| Some(module_path.to_string())),
522 },
523 other => other,
524 }
525 }
526
527 fn apply_reexport(&self, tree: &UseTree, exports: &mut ModuleExports) -> Result<()> {
528 match tree {
529 UseTree::Path { path, alias, .. } => {
530 if path.len() == 1 {
531 return Ok(());
532 }
533
534 let module = path[..path.len() - 1].join(".");
535 let item = path.last().unwrap().clone();
536 let alias_name = alias.clone().unwrap_or_else(|| item.clone());
537 let fq = format!("{}.{}", module, item);
538 let classification = self.classify_import_target(&module, &item);
539 if classification.import_type {
540 exports.types.insert(alias_name.clone(), fq.clone());
541 }
542
543 if classification.import_value {
544 exports.functions.insert(alias_name, fq);
545 }
546
547 Ok(())
548 }
549
550 UseTree::Group { prefix, items } => {
551 for item in items {
552 if item.path.is_empty() {
553 continue;
554 }
555
556 let alias_name = item
557 .alias
558 .clone()
559 .unwrap_or_else(|| item.path.last().unwrap().clone());
560 let mut full_segments = prefix.clone();
561 full_segments.extend(item.path.clone());
562 let full = full_segments.join(".");
563 let full_file = self.file_for_module_path(&full);
564 if self.module_source_known(&full, &full_file) {
565 continue;
566 }
567
568 let mut module_segments = full_segments.clone();
569 let item_name = module_segments.pop().unwrap();
570 let module_path = module_segments.join(".");
571 let fq_name = if module_path.is_empty() {
572 item_name.clone()
573 } else {
574 format!("{}.{}", module_path, item_name)
575 };
576 let classification = self.classify_import_target(&module_path, &item_name);
577 if classification.import_type {
578 exports.types.insert(alias_name.clone(), fq_name.clone());
579 }
580
581 if classification.import_value {
582 exports.functions.insert(alias_name.clone(), fq_name);
583 }
584 }
585
586 Ok(())
587 }
588
589 UseTree::Glob { prefix } => {
590 let module = prefix.join(".");
591 if let Some(loaded) = self.cache.get(&module) {
592 for (n, fq) in &loaded.exports.types {
593 exports.types.insert(n.clone(), fq.clone());
594 }
595
596 for (n, fq) in &loaded.exports.functions {
597 exports.functions.insert(n.clone(), fq.clone());
598 }
599 }
600
601 Ok(())
602 }
603 }
604 }
605
606 fn simple_name<'a>(&self, qualified: &'a str) -> &'a str {
607 qualified
608 .rsplit_once('.')
609 .map(|(_, n)| n)
610 .unwrap_or(qualified)
611 }
612
613 fn simple_tail<'a>(&self, module_path: &'a str) -> &'a str {
614 module_path
615 .rsplit_once('.')
616 .map(|(_, n)| n)
617 .unwrap_or(module_path)
618 }
619
620 fn module_source_known(&self, module_path: &str, file: &Path) -> bool {
621 file.exists()
622 || self.source_overrides.contains_key(file)
623 || self.cache.contains_key(module_path)
624 }
625
626 fn classify_import_target(&self, module_path: &str, item_name: &str) -> ImportResolution {
627 if module_path.is_empty() {
628 return ImportResolution::both();
629 }
630
631 if let Some(module) = self.cache.get(module_path) {
632 let has_value = module.exports.functions.contains_key(item_name);
633 let has_type = module.exports.types.contains_key(item_name);
634 if has_value || has_type {
635 return ImportResolution {
636 import_value: has_value,
637 import_type: has_type,
638 };
639 }
640 }
641
642 ImportResolution::both()
643 }
644
645 fn file_for_module_path(&self, module_path: &str) -> PathBuf {
646 let segments: Vec<&str> = module_path.split('.').collect();
647 let candidates = self.resolve_dependency_roots(&segments);
648 if !candidates.is_empty() {
649 let mut fallback: Option<PathBuf> = None;
650 for (root, consumed) in candidates.iter().rev() {
651 let candidate = Self::path_from_root(root, &segments, *consumed);
652 if candidate.exists() {
653 return candidate;
654 }
655 if fallback.is_none() {
656 fallback = Some(candidate);
657 }
658 }
659 if let Some(path) = fallback {
660 return path;
661 }
662 }
663
664 let mut fallback = self.base_dir.clone();
665 for seg in &segments {
666 fallback.push(seg);
667 }
668 fallback.set_extension("lust");
669 fallback
670 }
671
672 fn resolve_dependency_roots(&self, segments: &[&str]) -> Vec<(&ModuleRoot, usize)> {
673 let mut matched: Vec<(&ModuleRoot, usize)> = Vec::new();
674 let mut prefix_segments: Vec<&str> = Vec::new();
675 for (index, segment) in segments.iter().enumerate() {
676 prefix_segments.push(*segment);
677 let key = prefix_segments.join(".");
678 if let Some(roots) = self.module_roots.get(&key) {
679 for root in roots {
680 matched.push((root, index + 1));
681 }
682 }
683 }
684 matched
685 }
686
687 fn path_from_root(root: &ModuleRoot, segments: &[&str], consumed: usize) -> PathBuf {
688 let mut path = root.base.clone();
689 if consumed == segments.len() {
690 if let Some(relative) = &root.root_module {
691 path.push(relative);
692 } else if let Some(last) = segments.last() {
693 path.push(format!("{last}.lust"));
694 }
695 return path;
696 }
697 for seg in &segments[consumed..segments.len() - 1] {
698 path.push(seg);
699 }
700 if let Some(last) = segments.last() {
701 path.push(format!("{last}.lust"));
702 }
703 path
704 }
705
706 fn module_path_for_file(path: &Path) -> String {
707 let stem = path.file_stem().and_then(|s| s.to_str()).unwrap_or("");
708 stem.to_string()
709 }
710}
711
712#[cfg(not(feature = "std"))]
713pub struct ModuleLoader {
714 cache: HashMap<String, LoadedModule>,
715 visited: HashSet<String>,
716}
717
718#[cfg(not(feature = "std"))]
719impl ModuleLoader {
720 pub fn new() -> Self {
721 Self {
722 cache: HashMap::new(),
723 visited: HashSet::new(),
724 }
725 }
726
727 pub fn clear_cache(&mut self) {
728 self.cache.clear();
729 self.visited.clear();
730 }
731
732 pub fn load_program_from_modules(
733 &mut self,
734 modules: Vec<LoadedModule>,
735 entry_module: String,
736 ) -> Program {
737 Program {
738 modules,
739 entry_module,
740 }
741 }
742}