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