1use std::collections::{BTreeMap, BTreeSet};
2use std::path::{Path, PathBuf};
3
4use crate::syntax::ast::{ImportDecl, Program};
5use crate::syntax::{
6 self,
7 ast::{FileId, SourceSpan},
8};
9
10mod error;
11mod qualify;
12
13pub use error::ModuleError;
14
15const STD_MODULE_NAME: &str = "std";
16const STD_MODULE_SOURCE: &str = include_str!("../../std/std.rk");
17const STD_STRING_MODULE_NAME: &str = "std::string";
18const STD_STRING_MODULE_SOURCE: &str = include_str!("../../std/string.rk");
19
20fn bundled_std_module_source(module_path: &str) -> Option<&'static str> {
22 match module_path {
23 STD_MODULE_NAME => Some(STD_MODULE_SOURCE),
24 STD_STRING_MODULE_NAME => Some(STD_STRING_MODULE_SOURCE),
25 _ => None,
26 }
27}
28
29#[derive(Debug, Clone, Copy, PartialEq, Eq)]
30enum ResolveMode {
31 Native,
32 Browser,
33}
34
35#[derive(Debug, Clone)]
36struct LoadedModule {
37 module_path: String,
38 source_name: String,
39 source_text: String,
40 file_id: FileId,
41 is_entry: bool,
42}
43
44pub fn resolve_for_native(entry_path: &Path, entry_source: &str) -> Result<Program, ModuleError> {
46 resolve_modules(entry_path, entry_source, ResolveMode::Native)
47}
48
49pub fn resolve_for_browser(entry_path: &Path, entry_source: &str) -> Result<Program, ModuleError> {
51 resolve_modules(entry_path, entry_source, ResolveMode::Browser)
52}
53
54fn resolve_modules(
56 entry_path: &Path,
57 entry_source: &str,
58 mode: ResolveMode,
59) -> Result<Program, ModuleError> {
60 let source_name = entry_path.display().to_string();
61 let root_dir = entry_path
62 .parent()
63 .map(Path::to_path_buf)
64 .unwrap_or_else(|| PathBuf::from("."));
65
66 let mut loaded = Vec::<LoadedModule>::new();
67 let mut seen = BTreeSet::<String>::new();
68 let mut stack = Vec::<String>::new();
69
70 let mut next_file_id = 0_u32;
71
72 let entry = LoadedModule {
73 module_path: "playground".to_owned(),
74 source_name,
75 source_text: entry_source.to_owned(),
76 file_id: allocate_file_id(&mut next_file_id),
77 is_entry: true,
78 };
79 load_module_recursive(
80 &entry,
81 mode,
82 &root_dir,
83 &mut seen,
84 &mut stack,
85 &mut loaded,
86 &mut next_file_id,
87 )?;
88
89 merge_loaded_modules(&loaded)
90}
91
92fn allocate_file_id(next_file_id: &mut u32) -> FileId {
94 let current = *next_file_id;
95 *next_file_id = next_file_id
96 .checked_add(1)
97 .expect("module parser file id overflow");
98 FileId::from_u32(current)
99}
100
101fn load_module_recursive(
103 module: &LoadedModule,
104 mode: ResolveMode,
105 root_dir: &Path,
106 seen: &mut BTreeSet<String>,
107 stack: &mut Vec<String>,
108 out: &mut Vec<LoadedModule>,
109 next_file_id: &mut u32,
110) -> Result<(), ModuleError> {
111 if seen.contains(&module.module_path) {
112 return Ok(());
113 }
114
115 stack.push(module.module_path.clone());
116 let parsed = parse_module(module)?;
117 ensure_no_duplicate_imports(module, &parsed.imports)?;
118 out.push(module.clone());
119 let _ = seen.insert(module.module_path.clone());
120
121 for import in &parsed.imports {
122 let imported = load_import_module(import, module, mode, root_dir, next_file_id)?;
123 if stack.contains(&imported.module_path) {
124 let mut cycle = stack.clone();
125 cycle.push(imported.module_path.clone());
126 return Err(ModuleError::ImportCycle {
127 cycle: cycle.join(" -> "),
128 source_name: module.source_name.clone(),
129 span: import.span,
130 });
131 }
132 load_module_recursive(&imported, mode, root_dir, seen, stack, out, next_file_id)?;
133 }
134
135 let _ = stack.pop();
136 Ok(())
137}
138
139fn parse_module(module: &LoadedModule) -> Result<Program, ModuleError> {
141 syntax::parse_program_with_file_id(module.file_id, &module.source_text).map_err(|error| {
142 ModuleError::Parse {
143 module: module.module_path.clone(),
144 source: module.source_name.clone(),
145 error,
146 }
147 })
148}
149
150fn load_import_module(
152 import: &ImportDecl,
153 from: &LoadedModule,
154 mode: ResolveMode,
155 root_dir: &Path,
156 next_file_id: &mut u32,
157) -> Result<LoadedModule, ModuleError> {
158 let import_path = import.path.as_str();
159 if let Some(source_text) = bundled_std_module_source(import_path) {
160 let source_name = if import_path == STD_MODULE_NAME {
161 "<std>/std.rk".to_owned()
162 } else {
163 format!("<std>/{}.rk", import_path.replace("::", "/"))
164 };
165 return Ok(LoadedModule {
166 module_path: import_path.to_owned(),
167 source_name,
168 source_text: source_text.to_owned(),
169 file_id: allocate_file_id(next_file_id),
170 is_entry: false,
171 });
172 }
173
174 if mode == ResolveMode::Browser {
175 let allowed =
176 if from.module_path == STD_MODULE_NAME || from.module_path.starts_with("std::") {
177 "std and std::*"
178 } else {
179 STD_MODULE_NAME
180 };
181 return Err(ModuleError::BrowserStaticImportUnsupported {
182 allowed: allowed.to_owned(),
183 found: import_path.to_owned(),
184 source_name: from.source_name.clone(),
185 span: import.span,
186 });
187 }
188
189 let rel = format!("{}.rk", import_path.replace("::", "/"));
190 let file_path = root_dir.join(&rel);
191 if !file_path.exists() {
192 return Err(ModuleError::NotFound {
193 module: import_path.to_owned(),
194 from: from.module_path.clone(),
195 path: file_path.display().to_string(),
196 source_name: from.source_name.clone(),
197 span: import.span,
198 });
199 }
200
201 let source_text = std::fs::read_to_string(&file_path).map_err(|error| ModuleError::Io {
202 path: file_path.display().to_string(),
203 error,
204 })?;
205 Ok(LoadedModule {
206 module_path: import_path.to_owned(),
207 source_name: file_path.display().to_string(),
208 source_text,
209 file_id: allocate_file_id(next_file_id),
210 is_entry: false,
211 })
212}
213
214fn ensure_no_duplicate_imports(
216 module: &LoadedModule,
217 imports: &[ImportDecl],
218) -> Result<(), ModuleError> {
219 let mut seen = BTreeMap::<String, SourceSpan>::new();
220 for import in imports {
221 if let Some(first_span) = seen.get(import.path.as_str()) {
222 return Err(ModuleError::DuplicateImport {
223 module: module.module_path.clone(),
224 import: import.path.clone(),
225 source_name: module.source_name.clone(),
226 first_span: *first_span,
227 duplicate_span: import.span,
228 });
229 }
230 let _ = seen.insert(import.path.clone(), import.span);
231 }
232 Ok(())
233}
234
235fn merge_loaded_modules(modules: &[LoadedModule]) -> Result<Program, ModuleError> {
237 let mut merged = Program {
238 imports: Vec::new(),
239 functions: Vec::new(),
240 meta_functions: Vec::new(),
241 extern_modules: Vec::new(),
242 structs: Vec::new(),
243 enums: Vec::new(),
244 };
245
246 for module in modules {
247 let mut parsed = parse_module(module)?;
248 if module.is_entry {
249 parsed.imports.clear();
250 merged.functions.append(&mut parsed.functions);
251 merged.meta_functions.append(&mut parsed.meta_functions);
252 merged.extern_modules.append(&mut parsed.extern_modules);
253 merged.structs.append(&mut parsed.structs);
254 merged.enums.append(&mut parsed.enums);
255 continue;
256 }
257
258 qualify::qualify_program_module(&mut parsed, module.module_path.as_str());
259 parsed.imports.clear();
260 merged.functions.append(&mut parsed.functions);
261 merged.meta_functions.append(&mut parsed.meta_functions);
262 merged.extern_modules.append(&mut parsed.extern_modules);
263 merged.structs.append(&mut parsed.structs);
264 merged.enums.append(&mut parsed.enums);
265 }
266
267 Ok(merged)
268}
269
270#[cfg(test)]
271mod tests;