1use crate::{
2 ast::{FunctionDef, Item, ItemKind, UseTree, Visibility},
3 error::{LustError, Result},
4 lexer::Lexer,
5 parser::Parser,
6 Span,
7};
8use alloc::{format, string::String, vec, vec::Vec};
9use hashbrown::{HashMap, HashSet};
10#[cfg(feature = "std")]
11use std::{
12 fs,
13 path::{Path, PathBuf},
14};
15#[derive(Debug, Clone, Default)]
16pub struct ModuleImports {
17 pub function_aliases: HashMap<String, String>,
18 pub module_aliases: HashMap<String, String>,
19 pub type_aliases: HashMap<String, String>,
20}
21
22#[derive(Debug, Clone, Default)]
23pub struct ModuleExports {
24 pub functions: HashMap<String, String>,
25 pub types: HashMap<String, String>,
26}
27
28pub mod embedded;
29
30#[derive(Debug, Clone)]
31pub struct LoadedModule {
32 pub path: String,
33 pub items: Vec<Item>,
34 pub imports: ModuleImports,
35 pub exports: ModuleExports,
36 pub init_function: Option<String>,
37 #[cfg(feature = "std")]
38 pub source_path: PathBuf,
39}
40
41#[derive(Debug, Clone)]
42pub struct Program {
43 pub modules: Vec<LoadedModule>,
44 pub entry_module: String,
45}
46
47pub use embedded::{build_directory_map, load_program_from_embedded, EmbeddedModule};
48#[derive(Clone, Copy, Debug, Default)]
49struct ImportResolution {
50 import_value: bool,
51 import_type: bool,
52}
53
54impl ImportResolution {
55 fn both() -> Self {
56 Self {
57 import_value: true,
58 import_type: true,
59 }
60 }
61}
62
63#[cfg(feature = "std")]
64pub struct ModuleLoader {
65 base_dir: PathBuf,
66 cache: HashMap<String, LoadedModule>,
67 visited: HashSet<String>,
68 source_overrides: HashMap<PathBuf, String>,
69 module_roots: HashMap<String, Vec<ModuleRoot>>,
70}
71
72#[cfg(feature = "std")]
73#[derive(Debug, Clone)]
74struct ModuleRoot {
75 base: PathBuf,
76 root_module: Option<PathBuf>,
77}
78
79#[cfg(feature = "std")]
80impl ModuleLoader {
81 pub fn new(base_dir: impl Into<PathBuf>) -> Self {
82 Self {
83 base_dir: base_dir.into(),
84 cache: HashMap::new(),
85 visited: HashSet::new(),
86 source_overrides: HashMap::new(),
87 module_roots: HashMap::new(),
88 }
89 }
90
91 pub fn set_source_overrides(&mut self, overrides: HashMap<PathBuf, String>) {
92 self.source_overrides = overrides;
93 }
94
95 pub fn set_source_override<P: Into<PathBuf>, S: Into<String>>(&mut self, path: P, source: S) {
96 self.source_overrides.insert(path.into(), source.into());
97 }
98
99 pub fn clear_source_overrides(&mut self) {
100 self.source_overrides.clear();
101 }
102
103 pub fn add_module_root(
104 &mut self,
105 prefix: impl Into<String>,
106 root: impl Into<PathBuf>,
107 root_module: Option<PathBuf>,
108 ) {
109 self.module_roots
110 .entry(prefix.into())
111 .or_default()
112 .push(ModuleRoot {
113 base: root.into(),
114 root_module,
115 });
116 }
117
118 pub fn load_program_from_entry(&mut self, entry_file: &str) -> Result<Program> {
119 let entry_path = Path::new(entry_file);
120 let entry_dir = entry_path.parent().unwrap_or_else(|| Path::new("."));
121 self.base_dir = entry_dir.to_path_buf();
122 let entry_module = Self::module_path_for_file(entry_path);
123 let mut order: Vec<String> = Vec::new();
124 let mut stack: HashSet<String> = HashSet::new();
125 self.load_module_recursive(&entry_module, &mut order, &mut stack, true)?;
126 let modules = order
127 .into_iter()
128 .filter_map(|m| self.cache.get(&m).cloned())
129 .collect::<Vec<_>>();
130 Ok(Program {
131 modules,
132 entry_module,
133 })
134 }
135
136 fn load_module_recursive(
137 &mut self,
138 module_path: &str,
139 order: &mut Vec<String>,
140 stack: &mut HashSet<String>,
141 is_entry: bool,
142 ) -> Result<()> {
143 if self.visited.contains(module_path) {
144 return Ok(());
145 }
146
147 if !stack.insert(module_path.to_string()) {
148 return Ok(());
149 }
150
151 let mut loaded = self.load_single_module(module_path, is_entry)?;
152 self.cache.insert(module_path.to_string(), loaded.clone());
153 let deps = self.collect_dependencies(&loaded.items);
154 for dep in deps {
155 self.load_module_recursive(&dep, order, stack, false)?;
156 }
157
158 self.finalize_module(&mut loaded)?;
159 self.cache.insert(module_path.to_string(), loaded.clone());
160 self.visited.insert(module_path.to_string());
161 order.push(module_path.to_string());
162 stack.remove(module_path);
163 Ok(())
164 }
165
166 fn load_single_module(&self, module_path: &str, is_entry: bool) -> Result<LoadedModule> {
167 let file = self.file_for_module_path(module_path);
168 let source = if let Some(src) = self.source_overrides.get(&file) {
169 src.clone()
170 } else {
171 match fs::read_to_string(&file) {
172 Ok(src) => src,
173 Err(e) => {
174 if !is_entry && e.kind() == std::io::ErrorKind::NotFound {
176 #[cfg(feature = "std")]
177 eprintln!(
178 "[WARNING] Module '{}' not found, but present in code",
179 module_path
180 );
181 }
183 return Err(LustError::Unknown(format!(
184 "Failed to read module '{}': {}",
185 file.display(),
186 e
187 )));
188 }
189 }
190 };
191 let mut lexer = Lexer::new(&source);
192 let tokens = lexer
193 .tokenize()
194 .map_err(|err| Self::attach_module_to_error(err, module_path))?;
195 let mut parser = Parser::new(tokens);
196 let mut items = parser
197 .parse()
198 .map_err(|err| Self::attach_module_to_error(err, module_path))?;
199 let mut imports = ModuleImports::default();
200 let mut exports = ModuleExports::default();
201 let mut new_items: Vec<Item> = Vec::new();
202 let mut init_function: Option<String> = None;
203 let mut pending_init_stmts: Vec<crate::ast::Stmt> = Vec::new();
204 let mut pending_init_span: Option<Span> = None;
205 for item in items.drain(..) {
206 match &item.kind {
207 ItemKind::Function(func) => {
208 let mut f = func.clone();
209 if !f.is_method && !f.name.contains(':') && !f.name.contains('.') {
210 let fq = format!("{}.{}", module_path, f.name);
211 imports.function_aliases.insert(f.name.clone(), fq.clone());
212 f.name = fq.clone();
213 if matches!(f.visibility, Visibility::Public) {
214 exports
215 .functions
216 .insert(self.simple_name(&f.name).to_string(), f.name.clone());
217 }
218 } else {
219 if matches!(f.visibility, Visibility::Public) {
220 exports
221 .functions
222 .insert(self.simple_name(&f.name).to_string(), f.name.clone());
223 }
224 }
225
226 new_items.push(Item::new(ItemKind::Function(f), item.span));
227 }
228
229 ItemKind::Struct(s) => {
230 if matches!(s.visibility, Visibility::Public) {
231 exports
232 .types
233 .insert(s.name.clone(), format!("{}.{}", module_path, s.name));
234 }
235
236 new_items.push(item);
237 }
238
239 ItemKind::Enum(e) => {
240 if matches!(e.visibility, Visibility::Public) {
241 exports
242 .types
243 .insert(e.name.clone(), format!("{}.{}", module_path, e.name));
244 }
245
246 new_items.push(item);
247 }
248
249 ItemKind::Trait(t) => {
250 if matches!(t.visibility, Visibility::Public) {
251 exports
252 .types
253 .insert(t.name.clone(), format!("{}.{}", module_path, t.name));
254 }
255
256 new_items.push(item);
257 }
258
259 ItemKind::TypeAlias { name, .. } => {
260 exports
261 .types
262 .insert(name.clone(), format!("{}.{}", module_path, name));
263 new_items.push(item);
264 }
265
266 ItemKind::Script(stmts) => {
267 if is_entry {
268 new_items.push(Item::new(ItemKind::Script(stmts.clone()), item.span));
269 } else {
270 if pending_init_span.is_none() {
271 pending_init_span = Some(item.span);
272 }
273 pending_init_stmts.extend(stmts.iter().cloned());
274 }
275 }
276
277 ItemKind::Extern {
278 abi,
279 items: extern_items,
280 } => {
281 let mut rewritten = Vec::new();
282 for extern_item in extern_items {
283 match extern_item {
284 crate::ast::ExternItem::Function {
285 name,
286 params,
287 return_type,
288 } => {
289 let mut new_name = name.clone();
290 if let Some((head, tail)) = new_name.split_once(':') {
291 let qualified_head =
292 if head.contains('.') || head.contains("::") {
293 head.to_string()
294 } else {
295 format!("{}.{}", module_path, head)
296 };
297 new_name = format!("{}:{}", qualified_head, tail);
298 } else if !new_name.contains('.') {
299 new_name = format!("{}.{}", module_path, new_name);
300 }
301
302 exports.functions.insert(
303 self.simple_name(&new_name).to_string(),
304 new_name.clone(),
305 );
306 imports.function_aliases.insert(
307 self.simple_name(&new_name).to_string(),
308 new_name.clone(),
309 );
310
311 rewritten.push(crate::ast::ExternItem::Function {
312 name: new_name,
313 params: params.clone(),
314 return_type: return_type.clone(),
315 });
316 }
317
318 crate::ast::ExternItem::Const { name, ty } => {
319 let qualified = if name.contains('.') {
320 name.clone()
321 } else {
322 format!("{}.{}", module_path, name)
323 };
324 exports.functions.insert(
325 self.simple_name(&qualified).to_string(),
326 qualified.clone(),
327 );
328 imports.function_aliases.insert(
329 self.simple_name(&qualified).to_string(),
330 qualified.clone(),
331 );
332 rewritten.push(crate::ast::ExternItem::Const {
333 name: qualified,
334 ty: ty.clone(),
335 });
336 }
337
338 crate::ast::ExternItem::Struct(def) => {
339 let mut def = def.clone();
340 if !def.name.contains('.') && !def.name.contains("::") {
341 def.name = format!("{}.{}", module_path, def.name);
342 }
343 exports.types.insert(
344 self.simple_name(&def.name).to_string(),
345 def.name.clone(),
346 );
347 rewritten.push(crate::ast::ExternItem::Struct(def));
348 }
349
350 crate::ast::ExternItem::Enum(def) => {
351 let mut def = def.clone();
352 if !def.name.contains('.') && !def.name.contains("::") {
353 def.name = format!("{}.{}", module_path, def.name);
354 }
355 exports.types.insert(
356 self.simple_name(&def.name).to_string(),
357 def.name.clone(),
358 );
359 rewritten.push(crate::ast::ExternItem::Enum(def));
360 }
361 }
362 }
363 new_items.push(Item::new(
364 ItemKind::Extern {
365 abi: abi.clone(),
366 items: rewritten,
367 },
368 item.span,
369 ));
370 }
371
372 _ => {
373 new_items.push(item);
374 }
375 }
376 }
377
378 if !is_entry && !pending_init_stmts.is_empty() {
379 let init_name = format!("__init@{}", module_path);
380 let func = FunctionDef {
381 name: init_name.clone(),
382 type_params: vec![],
383 trait_bounds: vec![],
384 params: vec![],
385 return_type: None,
386 body: pending_init_stmts,
387 is_method: false,
388 visibility: Visibility::Private,
389 };
390 let span = pending_init_span.unwrap_or_else(Span::dummy);
391 new_items.insert(0, Item::new(ItemKind::Function(func), span));
394 init_function = Some(init_name);
395 }
396
397 Ok(LoadedModule {
398 path: module_path.to_string(),
399 items: new_items,
400 imports,
401 exports,
402 init_function,
403 source_path: file,
404 })
405 }
406
407 fn collect_dependencies(&self, items: &[Item]) -> Vec<String> {
408 let mut deps = HashSet::new();
409 for item in items {
410 match &item.kind {
411 ItemKind::Use { public: _, tree } => {
412 self.collect_deps_from_use(tree, &mut deps);
413 }
414 ItemKind::Script(stmts) => {
415 for stmt in stmts {
416 self.collect_deps_from_lua_require_stmt(stmt, &mut deps);
417 }
418 }
419 ItemKind::Function(func) => {
420 for stmt in &func.body {
421 self.collect_deps_from_lua_require_stmt(stmt, &mut deps);
422 }
423 }
424 ItemKind::Const { value, .. } | ItemKind::Static { value, .. } => {
425 self.collect_deps_from_lua_require_expr(value, &mut deps);
426 }
427 ItemKind::Impl(impl_block) => {
428 for method in &impl_block.methods {
429 for stmt in &method.body {
430 self.collect_deps_from_lua_require_stmt(stmt, &mut deps);
431 }
432 }
433 }
434 ItemKind::Trait(trait_def) => {
435 for method in &trait_def.methods {
436 if let Some(default_impl) = &method.default_impl {
437 for stmt in default_impl {
438 self.collect_deps_from_lua_require_stmt(stmt, &mut deps);
439 }
440 }
441 }
442 }
443 _ => {}
444 }
445 }
446
447 deps.into_iter().collect()
448 }
449
450 fn collect_deps_from_lua_require_stmt(
451 &self,
452 stmt: &crate::ast::Stmt,
453 deps: &mut HashSet<String>,
454 ) {
455 use crate::ast::StmtKind;
456 match &stmt.kind {
457 StmtKind::Local { initializer, .. } => {
458 if let Some(values) = initializer {
459 for expr in values {
460 self.collect_deps_from_lua_require_expr(expr, deps);
461 }
462 }
463 }
464 StmtKind::Assign { targets, values } => {
465 for expr in targets {
466 self.collect_deps_from_lua_require_expr(expr, deps);
467 }
468 for expr in values {
469 self.collect_deps_from_lua_require_expr(expr, deps);
470 }
471 }
472 StmtKind::CompoundAssign { target, value, .. } => {
473 self.collect_deps_from_lua_require_expr(target, deps);
474 self.collect_deps_from_lua_require_expr(value, deps);
475 }
476 StmtKind::Expr(expr) => self.collect_deps_from_lua_require_expr(expr, deps),
477 StmtKind::If {
478 condition,
479 then_block,
480 elseif_branches,
481 else_block,
482 } => {
483 self.collect_deps_from_lua_require_expr(condition, deps);
484 for stmt in then_block {
485 self.collect_deps_from_lua_require_stmt(stmt, deps);
486 }
487 for (cond, block) in elseif_branches {
488 self.collect_deps_from_lua_require_expr(cond, deps);
489 for stmt in block {
490 self.collect_deps_from_lua_require_stmt(stmt, deps);
491 }
492 }
493 if let Some(block) = else_block {
494 for stmt in block {
495 self.collect_deps_from_lua_require_stmt(stmt, deps);
496 }
497 }
498 }
499 StmtKind::While { condition, body } => {
500 self.collect_deps_from_lua_require_expr(condition, deps);
501 for stmt in body {
502 self.collect_deps_from_lua_require_stmt(stmt, deps);
503 }
504 }
505 StmtKind::ForNumeric {
506 start,
507 end,
508 step,
509 body,
510 ..
511 } => {
512 self.collect_deps_from_lua_require_expr(start, deps);
513 self.collect_deps_from_lua_require_expr(end, deps);
514 if let Some(step) = step {
515 self.collect_deps_from_lua_require_expr(step, deps);
516 }
517 for stmt in body {
518 self.collect_deps_from_lua_require_stmt(stmt, deps);
519 }
520 }
521 StmtKind::ForIn { iterator, body, .. } => {
522 self.collect_deps_from_lua_require_expr(iterator, deps);
523 for stmt in body {
524 self.collect_deps_from_lua_require_stmt(stmt, deps);
525 }
526 }
527 StmtKind::Return(values) => {
528 for expr in values {
529 self.collect_deps_from_lua_require_expr(expr, deps);
530 }
531 }
532 StmtKind::Block(stmts) => {
533 for stmt in stmts {
534 self.collect_deps_from_lua_require_stmt(stmt, deps);
535 }
536 }
537 StmtKind::Break | StmtKind::Continue => {}
538 }
539 }
540
541 fn collect_deps_from_lua_require_expr(
542 &self,
543 expr: &crate::ast::Expr,
544 deps: &mut HashSet<String>,
545 ) {
546 use crate::ast::{ExprKind, Literal};
547 match &expr.kind {
548 ExprKind::Call { callee, args } => {
549 if self.is_lua_require_callee(callee) {
550 if let Some(name) = args
551 .get(0)
552 .and_then(|arg| self.extract_lua_require_name(arg))
553 {
554 if !Self::is_lua_builtin_module_name(&name) {
555 let file = self.file_for_module_path(&name);
561 if self.module_source_known(&name, &file) {
562 deps.insert(name);
563 }
564 }
565 }
566 }
567 self.collect_deps_from_lua_require_expr(callee, deps);
568 for arg in args {
569 self.collect_deps_from_lua_require_expr(arg, deps);
570 }
571 }
572 ExprKind::MethodCall { receiver, args, .. } => {
573 self.collect_deps_from_lua_require_expr(receiver, deps);
574 for arg in args {
575 self.collect_deps_from_lua_require_expr(arg, deps);
576 }
577 }
578 ExprKind::Binary { left, right, .. } => {
579 self.collect_deps_from_lua_require_expr(left, deps);
580 self.collect_deps_from_lua_require_expr(right, deps);
581 }
582 ExprKind::Unary { operand, .. } => {
583 self.collect_deps_from_lua_require_expr(operand, deps)
584 }
585 ExprKind::FieldAccess { object, .. } => {
586 self.collect_deps_from_lua_require_expr(object, deps)
587 }
588 ExprKind::Index { object, index } => {
589 self.collect_deps_from_lua_require_expr(object, deps);
590 self.collect_deps_from_lua_require_expr(index, deps);
591 }
592 ExprKind::Array(elements) | ExprKind::Tuple(elements) => {
593 for element in elements {
594 self.collect_deps_from_lua_require_expr(element, deps);
595 }
596 }
597 ExprKind::Map(entries) => {
598 for (k, v) in entries {
599 self.collect_deps_from_lua_require_expr(k, deps);
600 self.collect_deps_from_lua_require_expr(v, deps);
601 }
602 }
603 ExprKind::StructLiteral { fields, .. } => {
604 for field in fields {
605 self.collect_deps_from_lua_require_expr(&field.value, deps);
606 }
607 }
608 ExprKind::EnumConstructor { args, .. } => {
609 for arg in args {
610 self.collect_deps_from_lua_require_expr(arg, deps);
611 }
612 }
613 ExprKind::Lambda { body, .. } => self.collect_deps_from_lua_require_expr(body, deps),
614 ExprKind::Paren(inner) => self.collect_deps_from_lua_require_expr(inner, deps),
615 ExprKind::Cast { expr, .. } => self.collect_deps_from_lua_require_expr(expr, deps),
616 ExprKind::TypeCheck { expr, .. } => self.collect_deps_from_lua_require_expr(expr, deps),
617 ExprKind::IsPattern { expr, .. } => self.collect_deps_from_lua_require_expr(expr, deps),
618 ExprKind::If {
619 condition,
620 then_branch,
621 else_branch,
622 } => {
623 self.collect_deps_from_lua_require_expr(condition, deps);
624 self.collect_deps_from_lua_require_expr(then_branch, deps);
625 if let Some(other) = else_branch {
626 self.collect_deps_from_lua_require_expr(other, deps);
627 }
628 }
629 ExprKind::Block(stmts) => {
630 for stmt in stmts {
631 self.collect_deps_from_lua_require_stmt(stmt, deps);
632 }
633 }
634 ExprKind::Return(values) => {
635 for value in values {
636 self.collect_deps_from_lua_require_expr(value, deps);
637 }
638 }
639 ExprKind::Range { start, end, .. } => {
640 self.collect_deps_from_lua_require_expr(start, deps);
641 self.collect_deps_from_lua_require_expr(end, deps);
642 }
643 ExprKind::Literal(Literal::String(_))
644 | ExprKind::Literal(_)
645 | ExprKind::Identifier(_) => {}
646 }
647 }
648
649 fn is_lua_builtin_module_name(name: &str) -> bool {
650 matches!(
651 name,
652 "math" | "table" | "string" | "io" | "os" | "package" | "coroutine" | "debug" | "utf8"
653 )
654 }
655
656 fn is_lua_require_callee(&self, callee: &crate::ast::Expr) -> bool {
657 use crate::ast::ExprKind;
658 match &callee.kind {
659 ExprKind::Identifier(name) => name == "require",
660 ExprKind::FieldAccess { object, field } => {
661 field == "require"
662 && matches!(&object.kind, ExprKind::Identifier(name) if name == "lua")
663 }
664 _ => false,
665 }
666 }
667
668 fn extract_lua_require_name(&self, expr: &crate::ast::Expr) -> Option<String> {
669 use crate::ast::{ExprKind, Literal};
670 match &expr.kind {
671 ExprKind::Literal(Literal::String(s)) => Some(s.clone()),
672 ExprKind::Call { callee, args } if self.is_lua_to_value_callee(callee) => {
673 args.get(0).and_then(|arg| match &arg.kind {
674 ExprKind::Literal(Literal::String(s)) => Some(s.clone()),
675 _ => None,
676 })
677 }
678 _ => None,
679 }
680 }
681
682 fn is_lua_to_value_callee(&self, callee: &crate::ast::Expr) -> bool {
683 use crate::ast::ExprKind;
684 matches!(
685 &callee.kind,
686 ExprKind::FieldAccess { object, field }
687 if field == "to_value"
688 && matches!(&object.kind, ExprKind::Identifier(name) if name == "lua")
689 )
690 }
691
692 fn finalize_module(&mut self, module: &mut LoadedModule) -> Result<()> {
693 for item in &module.items {
694 if let ItemKind::Use { tree, .. } = &item.kind {
695 self.process_use_tree(tree, &mut module.imports)?;
696 }
697 }
698
699 for item in &module.items {
700 if let ItemKind::Use { public: true, tree } = &item.kind {
701 self.apply_reexport(tree, &mut module.exports)?;
702 }
703 }
704
705 module
706 .imports
707 .module_aliases
708 .entry(self.simple_tail(&module.path).to_string())
709 .or_insert_with(|| module.path.clone());
710 Ok(())
711 }
712
713 fn collect_deps_from_use(&self, tree: &UseTree, deps: &mut HashSet<String>) {
714 match tree {
715 UseTree::Path {
716 path,
717 alias: _,
718 import_module: _,
719 } => {
720 let full = path.join(".");
721 let full_file = self.file_for_module_path(&full);
722 if self.module_source_known(&full, &full_file) {
723 deps.insert(full);
724 } else if path.len() > 1 {
725 deps.insert(path[..path.len() - 1].join("."));
726 }
727 }
728
729 UseTree::Group { prefix, items } => {
730 let module = prefix.join(".");
731 if !module.is_empty() {
732 deps.insert(module);
733 }
734
735 for item in items {
736 if item.path.len() > 1 {
737 let mut combined: Vec<String> = prefix.clone();
738 combined.extend(item.path[..item.path.len() - 1].iter().cloned());
739 let module_path = combined.join(".");
740 if !module_path.is_empty() {
741 deps.insert(module_path);
742 }
743 }
744 }
745 }
746
747 UseTree::Glob { prefix } => {
748 deps.insert(prefix.join("."));
749 }
750 }
751 }
752
753 fn process_use_tree(&self, tree: &UseTree, imports: &mut ModuleImports) -> Result<()> {
754 match tree {
755 UseTree::Path { path, alias, .. } => {
756 let full = path.join(".");
757 let full_file = self.file_for_module_path(&full);
758 if self.module_source_known(&full, &full_file) {
759 let alias_name = alias
760 .clone()
761 .unwrap_or_else(|| path.last().unwrap().clone());
762 imports.module_aliases.insert(alias_name, full);
763 } else if path.len() > 1 {
764 let module = path[..path.len() - 1].join(".");
765 let item = path.last().unwrap().clone();
766 let alias_name = alias.clone().unwrap_or_else(|| item.clone());
767 let classification = self.classify_import_target(&module, &item);
768 let fq = format!("{}.{}", module, item);
769 if classification.import_value {
770 imports
771 .function_aliases
772 .insert(alias_name.clone(), fq.clone());
773 }
774
775 if classification.import_type {
776 imports.type_aliases.insert(alias_name, fq);
777 }
778 }
779 }
780
781 UseTree::Group { prefix, items } => {
782 for item in items {
783 if item.path.is_empty() {
784 continue;
785 }
786
787 let alias_name = item
788 .alias
789 .clone()
790 .unwrap_or_else(|| item.path.last().unwrap().clone());
791 let mut full_segments = prefix.clone();
792 full_segments.extend(item.path.clone());
793 let full = full_segments.join(".");
794 let full_file = self.file_for_module_path(&full);
795 if self.module_source_known(&full, &full_file) {
796 imports.module_aliases.insert(alias_name, full);
797 continue;
798 }
799
800 let mut module_segments = full_segments.clone();
801 let item_name = module_segments.pop().unwrap();
802 let module_path = module_segments.join(".");
803 let fq_name = if module_path.is_empty() {
804 item_name.clone()
805 } else {
806 format!("{}.{}", module_path, item_name)
807 };
808 let classification = self.classify_import_target(&module_path, &item_name);
809 if classification.import_value {
810 imports
811 .function_aliases
812 .insert(alias_name.clone(), fq_name.clone());
813 }
814
815 if classification.import_type {
816 imports.type_aliases.insert(alias_name.clone(), fq_name);
817 }
818 }
819 }
820
821 UseTree::Glob { prefix } => {
822 let module = prefix.join(".");
823 if let Some(loaded) = self.cache.get(&module) {
824 for (name, fq) in &loaded.exports.functions {
825 imports.function_aliases.insert(name.clone(), fq.clone());
826 }
827
828 for (name, fq) in &loaded.exports.types {
829 imports.type_aliases.insert(name.clone(), fq.clone());
830 }
831 }
832
833 let alias_name = prefix.last().cloned().unwrap_or_else(|| module.clone());
834 if !module.is_empty() {
835 imports.module_aliases.insert(alias_name, module);
836 }
837 }
838 }
839
840 Ok(())
841 }
842
843 fn attach_module_to_error(error: LustError, module_path: &str) -> LustError {
844 match error {
845 LustError::LexerError {
846 line,
847 column,
848 message,
849 module,
850 } => LustError::LexerError {
851 line,
852 column,
853 message,
854 module: module.or_else(|| Some(module_path.to_string())),
855 },
856 LustError::ParserError {
857 line,
858 column,
859 message,
860 module,
861 } => LustError::ParserError {
862 line,
863 column,
864 message,
865 module: module.or_else(|| Some(module_path.to_string())),
866 },
867 LustError::CompileErrorWithSpan {
868 message,
869 line,
870 column,
871 module,
872 } => LustError::CompileErrorWithSpan {
873 message,
874 line,
875 column,
876 module: module.or_else(|| Some(module_path.to_string())),
877 },
878 other => other,
879 }
880 }
881
882 fn apply_reexport(&self, tree: &UseTree, exports: &mut ModuleExports) -> Result<()> {
883 match tree {
884 UseTree::Path { path, alias, .. } => {
885 if path.len() == 1 {
886 return Ok(());
887 }
888
889 let module = path[..path.len() - 1].join(".");
890 let item = path.last().unwrap().clone();
891 let alias_name = alias.clone().unwrap_or_else(|| item.clone());
892 let fq = format!("{}.{}", module, item);
893 let classification = self.classify_import_target(&module, &item);
894 if classification.import_type {
895 exports.types.insert(alias_name.clone(), fq.clone());
896 }
897
898 if classification.import_value {
899 exports.functions.insert(alias_name, fq);
900 }
901
902 Ok(())
903 }
904
905 UseTree::Group { prefix, items } => {
906 for item in items {
907 if item.path.is_empty() {
908 continue;
909 }
910
911 let alias_name = item
912 .alias
913 .clone()
914 .unwrap_or_else(|| item.path.last().unwrap().clone());
915 let mut full_segments = prefix.clone();
916 full_segments.extend(item.path.clone());
917 let full = full_segments.join(".");
918 let full_file = self.file_for_module_path(&full);
919 if self.module_source_known(&full, &full_file) {
920 continue;
921 }
922
923 let mut module_segments = full_segments.clone();
924 let item_name = module_segments.pop().unwrap();
925 let module_path = module_segments.join(".");
926 let fq_name = if module_path.is_empty() {
927 item_name.clone()
928 } else {
929 format!("{}.{}", module_path, item_name)
930 };
931 let classification = self.classify_import_target(&module_path, &item_name);
932 if classification.import_type {
933 exports.types.insert(alias_name.clone(), fq_name.clone());
934 }
935
936 if classification.import_value {
937 exports.functions.insert(alias_name.clone(), fq_name);
938 }
939 }
940
941 Ok(())
942 }
943
944 UseTree::Glob { prefix } => {
945 let module = prefix.join(".");
946 if let Some(loaded) = self.cache.get(&module) {
947 for (n, fq) in &loaded.exports.types {
948 exports.types.insert(n.clone(), fq.clone());
949 }
950
951 for (n, fq) in &loaded.exports.functions {
952 exports.functions.insert(n.clone(), fq.clone());
953 }
954 }
955
956 Ok(())
957 }
958 }
959 }
960
961 fn simple_name<'a>(&self, qualified: &'a str) -> &'a str {
962 qualified
963 .rsplit_once('.')
964 .map(|(_, n)| n)
965 .unwrap_or(qualified)
966 }
967
968 fn simple_tail<'a>(&self, module_path: &'a str) -> &'a str {
969 module_path
970 .rsplit_once('.')
971 .map(|(_, n)| n)
972 .unwrap_or(module_path)
973 }
974
975 fn module_source_known(&self, module_path: &str, file: &Path) -> bool {
976 file.exists()
977 || self.source_overrides.contains_key(file)
978 || self.cache.contains_key(module_path)
979 }
980
981 fn classify_import_target(&self, module_path: &str, item_name: &str) -> ImportResolution {
982 if module_path.is_empty() {
983 return ImportResolution::both();
984 }
985
986 if let Some(module) = self.cache.get(module_path) {
987 let has_value = module.exports.functions.contains_key(item_name);
988 let has_type = module.exports.types.contains_key(item_name);
989 if has_value || has_type {
990 return ImportResolution {
991 import_value: has_value,
992 import_type: has_type,
993 };
994 }
995 }
996
997 ImportResolution::both()
998 }
999
1000 fn file_for_module_path(&self, module_path: &str) -> PathBuf {
1001 let segments: Vec<&str> = module_path.split('.').collect();
1002 let candidates = self.resolve_dependency_roots(&segments);
1003 if !candidates.is_empty() {
1004 let mut fallback: Option<PathBuf> = None;
1005 for (root, consumed) in candidates.iter().rev() {
1006 let candidate = Self::path_from_root(root, &segments, *consumed);
1007 if candidate.exists() {
1008 return candidate;
1009 }
1010 if fallback.is_none() {
1011 fallback = Some(candidate);
1012 }
1013 }
1014 if let Some(path) = fallback {
1015 return path;
1016 }
1017 }
1018
1019 let mut fallback = self.base_dir.clone();
1020 for seg in &segments {
1021 fallback.push(seg);
1022 }
1023 fallback.set_extension("lust");
1024 fallback
1025 }
1026
1027 fn resolve_dependency_roots(&self, segments: &[&str]) -> Vec<(&ModuleRoot, usize)> {
1028 let mut matched: Vec<(&ModuleRoot, usize)> = Vec::new();
1029 let mut prefix_segments: Vec<&str> = Vec::new();
1030 for (index, segment) in segments.iter().enumerate() {
1031 prefix_segments.push(*segment);
1032 let key = prefix_segments.join(".");
1033 if let Some(roots) = self.module_roots.get(&key) {
1034 for root in roots {
1035 matched.push((root, index + 1));
1036 }
1037 }
1038 }
1039 matched
1040 }
1041
1042 fn path_from_root(root: &ModuleRoot, segments: &[&str], consumed: usize) -> PathBuf {
1043 let mut path = root.base.clone();
1044 if consumed == segments.len() {
1045 if let Some(relative) = &root.root_module {
1046 path.push(relative);
1047 } else if let Some(last) = segments.last() {
1048 path.push(format!("{last}.lust"));
1049 }
1050 return path;
1051 }
1052 for seg in &segments[consumed..segments.len() - 1] {
1053 path.push(seg);
1054 }
1055 if let Some(last) = segments.last() {
1056 path.push(format!("{last}.lust"));
1057 }
1058 path
1059 }
1060
1061 fn module_path_for_file(path: &Path) -> String {
1062 let stem = path.file_stem().and_then(|s| s.to_str()).unwrap_or("");
1063 stem.to_string()
1064 }
1065}
1066
1067#[cfg(not(feature = "std"))]
1068pub struct ModuleLoader {
1069 cache: HashMap<String, LoadedModule>,
1070 visited: HashSet<String>,
1071}
1072
1073#[cfg(not(feature = "std"))]
1074impl ModuleLoader {
1075 pub fn new() -> Self {
1076 Self {
1077 cache: HashMap::new(),
1078 visited: HashSet::new(),
1079 }
1080 }
1081
1082 pub fn clear_cache(&mut self) {
1083 self.cache.clear();
1084 self.visited.clear();
1085 }
1086
1087 pub fn load_program_from_modules(
1088 &mut self,
1089 modules: Vec<LoadedModule>,
1090 entry_module: String,
1091 ) -> Program {
1092 Program {
1093 modules,
1094 entry_module,
1095 }
1096 }
1097}
1098
1099#[cfg(test)]
1100mod tests {
1101 use super::*;
1102 use std::fs;
1103 use std::time::{SystemTime, UNIX_EPOCH};
1104
1105 fn unique_temp_dir(prefix: &str) -> std::path::PathBuf {
1106 let nanos = SystemTime::now()
1107 .duration_since(UNIX_EPOCH)
1108 .unwrap_or_default()
1109 .as_nanos();
1110 let mut dir = std::env::temp_dir();
1111 dir.push(format!("{prefix}_{nanos}"));
1112 dir
1113 }
1114
1115 #[test]
1116 fn merges_multiple_script_chunks_into_single_init() {
1117 let dir = unique_temp_dir("lust_module_loader_test");
1118 fs::create_dir_all(&dir).unwrap();
1119 let entry_path = dir.join("main.lust");
1120 let module_path = dir.join("m.lust");
1121
1122 fs::write(&entry_path, "use m as m\n").unwrap();
1123
1124 fs::write(
1126 &module_path,
1127 r#"
1128local a: int = 1
1129
1130pub function f(): int
1131 return a
1132end
1133
1134local b: int = 2
1135
1136pub function g(): int
1137 return b
1138end
1139
1140local c: int = a + b
1141"#,
1142 )
1143 .unwrap();
1144
1145 let mut loader = ModuleLoader::new(dir.clone());
1146 let program = loader
1147 .load_program_from_entry(entry_path.to_str().unwrap())
1148 .unwrap();
1149
1150 let module = program.modules.iter().find(|m| m.path == "m").unwrap();
1151 assert_eq!(module.init_function.as_deref(), Some("__init@m"));
1152
1153 let init_functions: Vec<&FunctionDef> = module
1154 .items
1155 .iter()
1156 .filter_map(|item| match &item.kind {
1157 ItemKind::Function(f) if f.name == "__init@m" => Some(f),
1158 _ => None,
1159 })
1160 .collect();
1161 assert_eq!(init_functions.len(), 1);
1162 assert_eq!(init_functions[0].body.len(), 3);
1163
1164 let _ = fs::remove_file(entry_path);
1166 let _ = fs::remove_file(module_path);
1167 let _ = fs::remove_dir(dir);
1168 }
1169
1170 #[test]
1171 fn lua_require_does_not_force_missing_modules_as_dependencies() {
1172 let dir = unique_temp_dir("lust_module_loader_lua_require_missing");
1173 fs::create_dir_all(&dir).unwrap();
1174 let entry_path = dir.join("main.lust");
1175 let module_path = dir.join("a.lust");
1176
1177 fs::write(&entry_path, "use a as a\n").unwrap();
1178
1179 fs::write(
1182 &module_path,
1183 r#"
1184use lua as lua
1185
1186local f = function(): unknown
1187 return lua.require("missing.module")
1188end
1189"#,
1190 )
1191 .unwrap();
1192
1193 let mut loader = ModuleLoader::new(dir.clone());
1194 let program = loader
1195 .load_program_from_entry(entry_path.to_str().unwrap())
1196 .unwrap();
1197
1198 assert!(program.modules.iter().any(|m| m.path == "a"));
1199 assert!(!program.modules.iter().any(|m| m.path == "missing.module"));
1200
1201 let _ = fs::remove_file(entry_path);
1203 let _ = fs::remove_file(module_path);
1204 let _ = fs::remove_dir(dir);
1205 }
1206}