1use std::collections::{HashMap, HashSet};
8use std::path::{Path, PathBuf};
9use std::sync::Arc;
10
11use dashmap::DashMap;
12use rayon::prelude::*;
13use tracing::{debug, debug_span, info, info_span, warn};
14
15use crate::error::LoadError;
16use crate::ir;
17use crate::lower;
18use crate::mib::Mib;
19use crate::parser;
20use crate::scan;
21use crate::searchpath;
22use crate::source::{FindResult, Source};
23use crate::types::{DiagnosticConfig, ResolverStrictness};
24
25pub struct Loader {
60 sources: Vec<Box<dyn Source>>,
61 modules: Option<Vec<String>>,
62 resolver_strictness: ResolverStrictness,
63 diag_config: DiagnosticConfig,
64 system_paths: bool,
65}
66
67impl Default for Loader {
68 fn default() -> Self {
69 Self::new()
70 }
71}
72
73impl Loader {
74 pub fn new() -> Self {
78 Loader {
79 sources: Vec::new(),
80 modules: None,
81 resolver_strictness: ResolverStrictness::Normal,
82 diag_config: DiagnosticConfig::default(),
83 system_paths: false,
84 }
85 }
86
87 pub fn source(mut self, src: Box<dyn Source>) -> Self {
92 self.sources.push(src);
93 self
94 }
95
96 pub fn sources(mut self, srcs: Vec<Box<dyn Source>>) -> Self {
100 self.sources.extend(srcs);
101 self
102 }
103
104 pub fn modules(mut self, names: impl IntoIterator<Item = impl Into<String>>) -> Self {
108 let names: Vec<String> = names.into_iter().map(|n| n.into()).collect();
109 self.modules = Some(names);
110 self
111 }
112
113 pub fn diagnostic_config(mut self, config: DiagnosticConfig) -> Self {
116 self.diag_config = config;
117 self
118 }
119
120 pub fn resolver_strictness(mut self, strictness: ResolverStrictness) -> Self {
122 self.resolver_strictness = strictness;
123 self
124 }
125
126 pub fn system_paths(mut self) -> Self {
132 self.system_paths = true;
133 self
134 }
135}
136
137pub fn load(options: Loader) -> Result<Mib, LoadError> {
150 let requested_module_count = options.modules.as_ref().map_or(0, Vec::len);
151 let load_mode = if options.modules.is_some() {
152 "modules"
153 } else {
154 "all"
155 };
156 let span = info_span!(
157 target: "mib_rs::load",
158 "load",
159 component = "load",
160 mode = load_mode,
161 explicit_source_count = options.sources.len(),
162 requested_module_count = requested_module_count,
163 system_paths = options.system_paths,
164 strictness = ?options.resolver_strictness,
165 reporting = ?options.diag_config.reporting,
166 );
167 let _guard = span.enter();
168
169 let mut sources = options.sources;
170
171 if options.system_paths {
172 debug!(
173 target: "mib_rs::load",
174 component = "load",
175 phase = "source_discovery",
176 "discovering system sources",
177 );
178 sources.extend(searchpath::discover_system_sources());
179 }
180 if sources.is_empty() {
181 return Err(LoadError::NoSources);
182 }
183
184 let strictness = options.resolver_strictness;
185 let diag_config = options.diag_config;
186
187 let (ir_modules, requested_names) = if let Some(names) = options.modules {
188 let mods = load_modules_by_name(&sources, &names, &diag_config)?;
189 (mods, Some(names))
190 } else {
191 let mods = load_all_modules(&sources, &diag_config)?;
192 (mods, None)
193 };
194
195 debug!(
196 target: "mib_rs::load",
197 component = "load",
198 module_count = ir_modules.len(),
199 phase = "resolve",
200 "load pipeline complete, starting resolver",
201 );
202 let mib = crate::mib::resolver::resolve(ir_modules, strictness, &diag_config);
203
204 check_load_result(&mib, &diag_config, requested_names.as_deref())?;
205
206 info!(
207 target: "mib_rs::load",
208 component = "load",
209 module_count = mib.modules_slice().len(),
210 type_count = mib.types_slice().len(),
211 node_count = mib.tree().len(),
212 diagnostic_count = mib.diagnostics().len(),
213 "load complete",
214 );
215 Ok(mib)
216}
217
218impl Loader {
219 pub fn load(self) -> Result<Mib, LoadError> {
231 load(self)
232 }
233}
234
235fn load_all_modules(
237 sources: &[Box<dyn Source>],
238 diag_config: &DiagnosticConfig,
239) -> Result<Vec<ir::Module>, LoadError> {
240 let mut seen = HashSet::new();
242 let mut all_modules: Vec<(usize, String)> = Vec::new();
243 for (src_idx, src) in sources.iter().enumerate() {
244 let names = src.list_modules().map_err(LoadError::Io)?;
245 for name in names {
246 if seen.insert(name.clone()) {
247 all_modules.push((src_idx, name));
248 }
249 }
250 }
251
252 if all_modules.is_empty() {
253 let base = collect_base_modules(HashMap::new());
254 return Ok(base);
255 }
256
257 info!(
258 target: "mib_rs::load",
259 component = "load",
260 phase = "parallel_decode",
261 module_count = all_modules.len(),
262 "parallel loading",
263 );
264
265 let path_cache: DashMap<PathBuf, Arc<Vec<ir::Module>>> = DashMap::new();
267
268 let results: Result<Vec<Option<ir::Module>>, LoadError> = all_modules
270 .par_iter()
271 .map(|(src_idx, name)| {
272 let span = debug_span!(
273 target: "mib_rs::load",
274 "load_module",
275 component = "load",
276 module = %name,
277 source_index = *src_idx,
278 );
279 let _guard = span.enter();
280 let src = &sources[*src_idx];
281 let result = match src.find(name).map_err(LoadError::Io)? {
282 Some(r) => r,
283 None => {
284 debug!(
285 target: "mib_rs::load",
286 component = "load",
287 module = %name,
288 reason = "not_found",
289 "module not found",
290 );
291 return Ok(None);
292 }
293 };
294
295 let cached = path_cache
296 .entry(result.path.clone())
297 .or_insert_with(|| {
298 Arc::new(decode_modules(&result.content, &result.path, diag_config))
299 })
300 .clone();
301
302 let target = cached.iter().find(|m| m.name == *name).cloned();
304 Ok(target)
305 })
306 .collect();
307
308 let results = results?;
309 let mut modules: HashMap<String, ir::Module> = HashMap::new();
310 for module in results.into_iter().flatten() {
311 modules.entry(module.name.clone()).or_insert(module);
312 }
313
314 info!(
315 target: "mib_rs::load",
316 component = "load",
317 phase = "parallel_decode",
318 module_count = modules.len(),
319 "parallel loading complete",
320 );
321
322 Ok(collect_base_modules(modules))
323}
324
325fn load_modules_by_name(
327 sources: &[Box<dyn Source>],
328 names: &[String],
329 diag_config: &DiagnosticConfig,
330) -> Result<Vec<ir::Module>, LoadError> {
331 let mut modules: HashMap<String, ir::Module> = HashMap::new();
332 let mut file_cache: HashMap<PathBuf, Vec<ir::Module>> = HashMap::new();
333
334 fn find_in_sources(
335 sources: &[Box<dyn Source>],
336 name: &str,
337 ) -> Result<Option<FindResult>, LoadError> {
338 for src in sources {
339 match src.find(name).map_err(LoadError::Io)? {
340 Some(result) => return Ok(Some(result)),
341 None => continue,
342 }
343 }
344 Ok(None)
345 }
346
347 fn load_one(
348 name: &str,
349 sources: &[Box<dyn Source>],
350 modules: &mut HashMap<String, ir::Module>,
351 file_cache: &mut HashMap<PathBuf, Vec<ir::Module>>,
352 diag_config: &DiagnosticConfig,
353 ) -> Result<(), LoadError> {
354 if modules.contains_key(name) {
355 return Ok(());
356 }
357
358 if let Some(base) = lower::base_modules::get_base_module(name) {
360 modules.insert(name.to_string(), base.clone());
361 return Ok(());
362 }
363
364 let result = match find_in_sources(sources, name)? {
365 Some(r) => r,
366 None => {
367 debug!(
368 target: "mib_rs::load",
369 component = "load",
370 module = %name,
371 reason = "not_found",
372 "module not found",
373 );
374 return Ok(());
375 }
376 };
377
378 let mods = file_cache
379 .entry(result.path.clone())
380 .or_insert_with(|| decode_modules(&result.content, &result.path, diag_config));
381
382 let target = mods.iter().find(|m| m.name == name);
384 let target = match target {
385 Some(t) => t.clone(),
386 None => return Ok(()),
387 };
388
389 let import_modules: Vec<String> = target
391 .imports
392 .iter()
393 .map(|imp| imp.module.clone())
394 .collect::<HashSet<_>>()
395 .into_iter()
396 .collect();
397
398 modules.insert(name.to_string(), target);
399
400 for dep in import_modules {
402 load_one(&dep, sources, modules, file_cache, diag_config)?;
403 }
404
405 Ok(())
406 }
407
408 for name in names {
409 load_one(name, sources, &mut modules, &mut file_cache, diag_config)?;
410 }
411
412 Ok(collect_base_modules(modules))
413}
414
415fn collect_base_modules(mut modules: HashMap<String, ir::Module>) -> Vec<ir::Module> {
417 for &name in lower::base_modules::base_module_names() {
418 if !modules.contains_key(name)
419 && let Some(base) = lower::base_modules::get_base_module(name)
420 {
421 modules.insert(name.to_string(), base.clone());
422 }
423 }
424 let mut mods: Vec<ir::Module> = modules.into_values().collect();
425 mods.sort_by(|a, b| a.name.cmp(&b.name));
426 mods
427}
428
429fn decode_modules(
431 content: &[u8],
432 source_path: &Path,
433 diag_config: &DiagnosticConfig,
434) -> Vec<ir::Module> {
435 let path_display = source_path.display();
436 let span = debug_span!(
437 target: "mib_rs::load",
438 "decode_modules",
439 component = "load",
440 path = %path_display,
441 byte_count = content.len(),
442 );
443 let _guard = span.enter();
444
445 if !scan::looks_like_mib_content(content) {
446 debug!(
447 target: "mib_rs::load",
448 component = "load",
449 path = %path_display,
450 reason = "heuristic_rejected",
451 "content rejected by heuristic",
452 );
453 return Vec::new();
454 }
455
456 let ast_modules = parser::parse(content, diag_config);
457 let path_str = source_path.to_string_lossy();
458 debug!(
459 target: "mib_rs::load",
460 component = "load",
461 path = %path_display,
462 ast_module_count = ast_modules.len(),
463 "parsed source into AST modules",
464 );
465
466 let mut modules = Vec::new();
467 for am in ast_modules {
468 let mut module = lower::lower(am, content, diag_config);
469 module.source_path = path_str.to_string();
470 modules.push(module);
471 }
472 debug!(
473 target: "mib_rs::load",
474 component = "load",
475 path = %path_display,
476 ir_module_count = modules.len(),
477 "lowered source into IR modules",
478 );
479 modules
480}
481
482fn check_load_result(
484 mib: &Mib,
485 diag_config: &DiagnosticConfig,
486 requested_modules: Option<&[String]>,
487) -> Result<(), LoadError> {
488 if let Some(requested) = requested_modules {
490 let mut missing = Vec::new();
491 for name in requested {
492 if mib.module_by_name(name).is_none() {
493 missing.push(name.clone());
494 }
495 }
496 if !missing.is_empty() {
497 warn!(
498 target: "mib_rs::load",
499 component = "load",
500 reason = "missing_requested_modules",
501 missing_module_count = missing.len(),
502 "requested modules not found",
503 );
504 return Err(LoadError::MissingModules(missing));
505 }
506 }
507
508 for d in mib.diagnostics() {
510 if diag_config.should_fail(d.severity) {
511 warn!(
512 target: "mib_rs::load",
513 component = "load",
514 reason = "diagnostic_threshold",
515 severity = ?d.severity,
516 code = %d.code,
517 "diagnostic threshold exceeded",
518 );
519 return Err(LoadError::DiagnosticThreshold);
520 }
521 }
522
523 Ok(())
524}