1use crate::error::ResolveError;
2use crisp_ast::generics::{apply_implicit_generics, defined_type_names, prelude_type_set};
3use crisp_ast::item::SourceFile;
4use crisp_parser::Parser;
5use std::collections::BTreeMap;
6use std::fs;
7use std::path::{Path, PathBuf};
8
9#[derive(Debug, Clone)]
10pub struct ModuleNode {
11 pub path: PathBuf,
12 pub module_path: String,
13 pub ast: SourceFile,
14}
15
16#[derive(Debug, Clone)]
17pub struct ModuleGraph {
18 pub crate_root: PathBuf,
19 pub src_root: PathBuf,
20 pub modules: BTreeMap<String, ModuleNode>,
21}
22
23pub fn find_crate_root(start: &Path) -> Option<PathBuf> {
24 let mut dir = if start.is_dir() {
25 start.to_path_buf()
26 } else {
27 start.parent()?.to_path_buf()
28 };
29 loop {
30 if dir.join("crisp.toml").is_file() {
31 return Some(dir);
32 }
33 if !dir.pop() {
34 break;
35 }
36 }
37 None
38}
39
40pub fn load_module_graph(crate_root: &Path) -> Result<ModuleGraph, ResolveError> {
41 let src_root = crate_root.join("src");
42 if !src_root.is_dir() {
43 return Err(ResolveError::NoSrcDir {
44 root: crate_root.display().to_string(),
45 });
46 }
47
48 let mut modules = BTreeMap::new();
49 collect_crp_files(&src_root, &src_root, &mut modules)?;
50
51 if modules.is_empty() {
52 return Err(ResolveError::NoSrcDir {
53 root: crate_root.display().to_string(),
54 });
55 }
56
57 apply_free_type_binders(&mut modules)?;
58
59 Ok(ModuleGraph {
60 crate_root: crate_root.to_path_buf(),
61 src_root,
62 modules,
63 })
64}
65
66fn apply_free_type_binders(modules: &mut BTreeMap<String, ModuleNode>) -> Result<(), ResolveError> {
69 let mut known = prelude_type_set();
70 for node in modules.values() {
71 known.extend(defined_type_names(&node.ast.items));
72 }
73 for node in modules.values_mut() {
74 apply_implicit_generics(&mut node.ast.items, &known).map_err(|shadow| {
75 ResolveError::GenericShadowsType {
76 name: shadow.name,
77 span: shadow.span,
78 }
79 })?;
80 }
81 Ok(())
82}
83
84fn collect_crp_files(
85 src_root: &Path,
86 dir: &Path,
87 out: &mut BTreeMap<String, ModuleNode>,
88) -> Result<(), ResolveError> {
89 for entry in fs::read_dir(dir).map_err(|e| ResolveError::Io {
90 path: dir.display().to_string(),
91 source: e,
92 })? {
93 let entry = entry.map_err(|e| ResolveError::Io {
94 path: dir.display().to_string(),
95 source: e,
96 })?;
97 let path = entry.path();
98 if path.is_dir() {
99 collect_crp_files(src_root, &path, out)?;
100 continue;
101 }
102 let ext = path.extension().and_then(|e| e.to_str());
103 let is_crpi = ext == Some("crpi");
104 if ext != Some("crp") && !is_crpi {
105 continue;
106 }
107 let rel = path.strip_prefix(src_root).map_err(|_| ResolveError::Io {
108 path: path.display().to_string(),
109 source: std::io::Error::new(std::io::ErrorKind::InvalidInput, "bad path"),
110 })?;
111 let rel_mod = rel
112 .with_extension("")
113 .to_string_lossy()
114 .replace(std::path::MAIN_SEPARATOR, ".");
115 let module_path = if is_crpi {
117 format!("__extern.{rel_mod}")
118 } else {
119 rel_mod
120 };
121 let source = fs::read_to_string(&path).map_err(|e| ResolveError::Io {
122 path: path.display().to_string(),
123 source: e,
124 })?;
125 let mut parser = Parser::new(&source).map_err(|e| ResolveError::Parse {
126 path: path.display().to_string(),
127 message: e.primary_message(),
128 pos: e.byte_pos(),
129 })?;
130 let ast = parser.parse_file().map_err(|e| ResolveError::Parse {
131 path: path.display().to_string(),
132 message: e.primary_message(),
133 pos: e.byte_pos(),
134 })?;
135 out.insert(
136 module_path.clone(),
137 ModuleNode {
138 path,
139 module_path,
140 ast,
141 },
142 );
143 }
144 Ok(())
145}