1use std::collections::{HashMap, HashSet};
54use std::path::{Path, PathBuf};
55use thiserror::Error;
56
57use sha2::{Digest, Sha256};
58
59use crate::syntax::*;
60use crate::{parse_source, SyntaxError};
61
62#[derive(Debug, Error)]
63pub enum LoadError {
64 #[error("read {path}: {source}")]
65 Io {
66 path: String,
67 #[source]
68 source: std::io::Error,
69 },
70 #[error("parse {path}: {source}")]
71 Syntax {
72 path: String,
73 #[source]
74 source: SyntaxError,
75 },
76 #[error("import cycle: {chain}")]
77 Cycle { chain: String },
78 #[error("import \"{reference}\" from {importer}: file not found")]
79 NotFound { importer: String, reference: String },
80 #[error("local imports (`./`, `../`, `/`) require a base path; cannot resolve from a string source")]
81 LocalImportInStringSource,
82}
83
84pub fn load_program(entry: &Path) -> Result<Program, LoadError> {
87 let entry_canonical = entry.canonicalize().map_err(|source| LoadError::Io {
88 path: entry.display().to_string(),
89 source,
90 })?;
91 let mut state = LoaderState {
92 in_progress: Vec::new(),
93 loaded: HashSet::new(),
94 prefixes: HashMap::new(),
95 };
96 state.prefixes.insert(entry_canonical.clone(), String::new());
99 state.load(&entry_canonical)
100}
101
102pub fn load_program_from_str(src: &str) -> Result<Program, LoadError> {
105 let prog = parse_source(src).map_err(|source| LoadError::Syntax {
106 path: "<input>".into(),
107 source,
108 })?;
109 for item in &prog.items {
110 if let Item::Import(imp) = item {
111 if is_path_import(&imp.reference) {
112 return Err(LoadError::LocalImportInStringSource);
113 }
114 }
115 }
116 Ok(prog)
117}
118
119struct LoaderState {
120 in_progress: Vec<PathBuf>,
121 loaded: HashSet<PathBuf>,
125 prefixes: HashMap<PathBuf, String>,
128}
129
130impl LoaderState {
131 fn prefix_for(&mut self, canonical: &Path) -> String {
132 if let Some(p) = self.prefixes.get(canonical) {
133 return p.clone();
134 }
135 let stem = canonical
136 .file_stem()
137 .and_then(|s| s.to_str())
138 .unwrap_or("module");
139 let mut hasher = Sha256::new();
140 hasher.update(canonical.to_string_lossy().as_bytes());
141 let digest = hasher.finalize();
142 let prefix = format!("{stem}_{:08x}", u32::from_be_bytes([
143 digest[0], digest[1], digest[2], digest[3],
144 ]));
145 self.prefixes.insert(canonical.to_path_buf(), prefix.clone());
146 prefix
147 }
148
149 fn load(&mut self, canonical: &Path) -> Result<Program, LoadError> {
150 if self.in_progress.contains(&canonical.to_path_buf()) {
151 let mut chain: Vec<String> = self
152 .in_progress
153 .iter()
154 .map(|p| p.display().to_string())
155 .collect();
156 chain.push(canonical.display().to_string());
157 return Err(LoadError::Cycle {
158 chain: chain.join(" -> "),
159 });
160 }
161 if self.loaded.contains(canonical) {
168 return Ok(Program { items: Vec::new() });
169 }
170 self.in_progress.push(canonical.to_path_buf());
171
172 let src = std::fs::read_to_string(canonical).map_err(|source| LoadError::Io {
173 path: canonical.display().to_string(),
174 source,
175 })?;
176 let prog = parse_source(&src).map_err(|source| LoadError::Syntax {
177 path: canonical.display().to_string(),
178 source,
179 })?;
180
181 let local_names: HashSet<String> = prog
182 .items
183 .iter()
184 .filter_map(|item| match item {
185 Item::FnDecl(fd) => Some(fd.name.clone()),
186 Item::TypeDecl(td) => Some(td.name.clone()),
187 _ => None,
188 })
189 .collect();
190
191 let mut path_imports: HashMap<String, String> = HashMap::new();
193 let mut merged_children: Vec<Item> = Vec::new();
194 let mut std_imports: Vec<Item> = Vec::new();
195 let mut my_items: Vec<Item> = Vec::new();
196
197 for item in prog.items {
198 match item {
199 Item::Import(ref imp) if is_path_import(&imp.reference) => {
200 let resolved = resolve_import(canonical, &imp.reference)?;
201 let child_prefix = self.prefix_for(&resolved);
202 path_imports.insert(imp.alias.clone(), child_prefix);
203 let child_prog = self.load(&resolved)?;
204 merged_children.extend(child_prog.items);
205 }
206 Item::Import(_) => std_imports.push(item),
207 _ => my_items.push(item),
208 }
209 }
210
211 let my_prefix = self.prefix_for(canonical);
212 let mangler = Mangler {
213 prefix: my_prefix,
214 local_names: &local_names,
215 path_imports: &path_imports,
216 };
217 let mangled: Vec<Item> = my_items
218 .into_iter()
219 .map(|i| mangler.mangle_item(i))
220 .collect();
221
222 self.in_progress.pop();
223 self.loaded.insert(canonical.to_path_buf());
224
225 let mut out: Vec<Item> = Vec::new();
228 for s in std_imports {
229 if !merged_children.iter().any(|m| m == &s) {
230 out.push(s);
231 }
232 }
233 out.extend(merged_children);
234 out.extend(mangled);
235 Ok(Program { items: out })
236 }
237}
238
239fn is_path_import(reference: &str) -> bool {
240 reference.starts_with("./") || reference.starts_with("../") || reference.starts_with('/')
241}
242
243fn resolve_import(importer: &Path, reference: &str) -> Result<PathBuf, LoadError> {
244 let importer_dir = importer.parent().unwrap_or_else(|| Path::new("."));
245 let mut resolved: PathBuf = if reference.starts_with('/') {
246 PathBuf::from(reference)
247 } else {
248 importer_dir.join(reference)
249 };
250 if resolved.extension().is_none() {
251 resolved.set_extension("lex");
252 }
253 if !resolved.exists() {
254 return Err(LoadError::NotFound {
255 importer: importer.display().to_string(),
256 reference: reference.to_string(),
257 });
258 }
259 Ok(resolved)
260}
261
262struct Mangler<'a> {
263 prefix: String,
266 local_names: &'a HashSet<String>,
267 path_imports: &'a HashMap<String, String>,
271}
272
273impl<'a> Mangler<'a> {
274 fn qualify(&self, name: &str) -> String {
275 if self.prefix.is_empty() {
276 name.to_string()
277 } else {
278 format!("{}.{}", self.prefix, name)
279 }
280 }
281
282 fn mangle_item(&self, item: Item) -> Item {
283 match item {
284 Item::Import(imp) => Item::Import(imp),
285 Item::TypeDecl(td) => Item::TypeDecl(self.mangle_type_decl(td)),
286 Item::FnDecl(fd) => Item::FnDecl(self.mangle_fn_decl(fd)),
287 }
288 }
289
290 fn mangle_type_decl(&self, td: TypeDecl) -> TypeDecl {
291 TypeDecl {
292 name: self.qualify(&td.name),
293 params: td.params,
294 definition: self.mangle_type_expr(td.definition),
295 }
296 }
297
298 fn mangle_fn_decl(&self, fd: FnDecl) -> FnDecl {
299 let mut shadow = HashSet::new();
300 for p in &fd.params {
301 shadow.insert(p.name.clone());
302 }
303 FnDecl {
304 name: self.qualify(&fd.name),
305 type_params: fd.type_params,
306 params: fd
307 .params
308 .into_iter()
309 .map(|p| Param {
310 name: p.name,
311 ty: self.mangle_type_expr(p.ty),
312 })
313 .collect(),
314 effects: fd.effects,
315 return_type: self.mangle_type_expr(fd.return_type),
316 body: self.mangle_block(fd.body, &shadow),
317 }
318 }
319
320 fn mangle_type_expr(&self, te: TypeExpr) -> TypeExpr {
321 match te {
322 TypeExpr::Named { name, args } => TypeExpr::Named {
323 name: self.rewrite_type_name(&name),
324 args: args.into_iter().map(|a| self.mangle_type_expr(a)).collect(),
325 },
326 TypeExpr::Record(fields) => TypeExpr::Record(
327 fields
328 .into_iter()
329 .map(|f| TypeField {
330 name: f.name,
331 ty: self.mangle_type_expr(f.ty),
332 })
333 .collect(),
334 ),
335 TypeExpr::Tuple(items) => {
336 TypeExpr::Tuple(items.into_iter().map(|t| self.mangle_type_expr(t)).collect())
337 }
338 TypeExpr::Function {
339 params,
340 effects,
341 ret,
342 } => TypeExpr::Function {
343 params: params
344 .into_iter()
345 .map(|t| self.mangle_type_expr(t))
346 .collect(),
347 effects,
348 ret: Box::new(self.mangle_type_expr(*ret)),
349 },
350 TypeExpr::Union(variants) => TypeExpr::Union(
351 variants
352 .into_iter()
353 .map(|v| UnionVariant {
354 name: v.name,
355 payload: v.payload.map(|t| self.mangle_type_expr(t)),
356 })
357 .collect(),
358 ),
359 }
360 }
361
362 fn rewrite_type_name(&self, name: &str) -> String {
364 if let Some((alias, rest)) = name.split_once('.') {
365 if let Some(child) = self.path_imports.get(alias) {
366 return format!("{child}.{rest}");
367 }
368 return name.to_string();
369 }
370 if self.local_names.contains(name) {
371 return self.qualify(name);
372 }
373 name.to_string()
374 }
375
376 fn mangle_block(&self, b: Block, shadow: &HashSet<String>) -> Block {
377 let mut shadow = shadow.clone();
378 let statements = b
379 .statements
380 .into_iter()
381 .map(|s| match s {
382 Statement::Let { name, ty, value } => {
383 let value = self.mangle_expr(value, &shadow);
384 let ty = ty.map(|t| self.mangle_type_expr(t));
385 shadow.insert(name.clone());
386 Statement::Let { name, ty, value }
387 }
388 Statement::Expr(e) => Statement::Expr(self.mangle_expr(e, &shadow)),
389 })
390 .collect();
391 let result = Box::new(self.mangle_expr(*b.result, &shadow));
392 Block { statements, result }
393 }
394
395 fn mangle_expr(&self, e: Expr, shadow: &HashSet<String>) -> Expr {
396 match e {
397 Expr::Lit(_) => e,
398 Expr::Var(name) => {
399 if !shadow.contains(&name) && self.local_names.contains(&name) {
400 Expr::Var(self.qualify(&name))
401 } else {
402 Expr::Var(name)
403 }
404 }
405 Expr::Block(b) => Expr::Block(self.mangle_block(b, shadow)),
406 Expr::Call { callee, args } => {
407 let mangled_args: Vec<Expr> = args
408 .into_iter()
409 .map(|a| self.mangle_expr(a, shadow))
410 .collect();
411 if let Expr::Field { value, field } = (*callee).clone() {
412 if let Expr::Var(alias) = *value {
413 if !shadow.contains(&alias) {
414 if let Some(child) = self.path_imports.get(&alias) {
415 return Expr::Call {
416 callee: Box::new(Expr::Var(format!("{child}.{field}"))),
417 args: mangled_args,
418 };
419 }
420 }
421 }
422 }
423 Expr::Call {
424 callee: Box::new(self.mangle_expr(*callee, shadow)),
425 args: mangled_args,
426 }
427 }
428 Expr::Pipe { left, right } => Expr::Pipe {
429 left: Box::new(self.mangle_expr(*left, shadow)),
430 right: Box::new(self.mangle_expr(*right, shadow)),
431 },
432 Expr::Try(inner) => Expr::Try(Box::new(self.mangle_expr(*inner, shadow))),
433 Expr::Field { value, field } => {
434 if let Expr::Var(alias) = (*value).clone() {
435 if !shadow.contains(&alias) {
436 if let Some(child) = self.path_imports.get(&alias) {
437 return Expr::Var(format!("{child}.{field}"));
438 }
439 }
440 }
441 Expr::Field {
442 value: Box::new(self.mangle_expr(*value, shadow)),
443 field,
444 }
445 }
446 Expr::BinOp { op, lhs, rhs } => Expr::BinOp {
447 op,
448 lhs: Box::new(self.mangle_expr(*lhs, shadow)),
449 rhs: Box::new(self.mangle_expr(*rhs, shadow)),
450 },
451 Expr::UnaryOp { op, expr } => Expr::UnaryOp {
452 op,
453 expr: Box::new(self.mangle_expr(*expr, shadow)),
454 },
455 Expr::If {
456 cond,
457 then_block,
458 else_block,
459 } => Expr::If {
460 cond: Box::new(self.mangle_expr(*cond, shadow)),
461 then_block: self.mangle_block(then_block, shadow),
462 else_block: self.mangle_block(else_block, shadow),
463 },
464 Expr::Match { scrutinee, arms } => Expr::Match {
465 scrutinee: Box::new(self.mangle_expr(*scrutinee, shadow)),
466 arms: arms
467 .into_iter()
468 .map(|a| {
469 let mut arm_shadow = shadow.clone();
470 collect_pattern_binders(&a.pattern, &mut arm_shadow);
471 Arm {
472 pattern: self.mangle_pattern(a.pattern),
473 body: self.mangle_expr(a.body, &arm_shadow),
474 }
475 })
476 .collect(),
477 },
478 Expr::RecordLit(fields) => Expr::RecordLit(
479 fields
480 .into_iter()
481 .map(|f| RecordLitField {
482 name: f.name,
483 value: self.mangle_expr(f.value, shadow),
484 })
485 .collect(),
486 ),
487 Expr::TupleLit(items) => Expr::TupleLit(
488 items
489 .into_iter()
490 .map(|i| self.mangle_expr(i, shadow))
491 .collect(),
492 ),
493 Expr::ListLit(items) => Expr::ListLit(
494 items
495 .into_iter()
496 .map(|i| self.mangle_expr(i, shadow))
497 .collect(),
498 ),
499 Expr::Constructor { name, args } => Expr::Constructor {
500 name,
501 args: args
502 .into_iter()
503 .map(|a| self.mangle_expr(a, shadow))
504 .collect(),
505 },
506 Expr::Lambda(lambda) => {
507 let mut lam_shadow = shadow.clone();
508 for p in &lambda.params {
509 lam_shadow.insert(p.name.clone());
510 }
511 Expr::Lambda(Box::new(Lambda {
512 params: lambda
513 .params
514 .into_iter()
515 .map(|p| Param {
516 name: p.name,
517 ty: self.mangle_type_expr(p.ty),
518 })
519 .collect(),
520 return_type: self.mangle_type_expr(lambda.return_type),
521 effects: lambda.effects,
522 body: self.mangle_block(lambda.body, &lam_shadow),
523 }))
524 }
525 }
526 }
527
528 fn mangle_pattern(&self, p: Pattern) -> Pattern {
529 match p {
530 Pattern::Constructor { name, args } => Pattern::Constructor {
531 name,
532 args: args.into_iter().map(|a| self.mangle_pattern(a)).collect(),
533 },
534 Pattern::Record { fields, rest } => Pattern::Record {
535 fields: fields
536 .into_iter()
537 .map(|f| RecordPatField {
538 name: f.name,
539 pattern: f.pattern.map(|p| self.mangle_pattern(p)),
540 })
541 .collect(),
542 rest,
543 },
544 Pattern::Tuple(items) => {
545 Pattern::Tuple(items.into_iter().map(|p| self.mangle_pattern(p)).collect())
546 }
547 Pattern::Lit(_) | Pattern::Var(_) | Pattern::Wild => p,
548 }
549 }
550}
551
552fn collect_pattern_binders(p: &Pattern, out: &mut HashSet<String>) {
553 match p {
554 Pattern::Var(name) => {
555 out.insert(name.clone());
556 }
557 Pattern::Constructor { args, .. } => {
558 for a in args {
559 collect_pattern_binders(a, out);
560 }
561 }
562 Pattern::Record { fields, .. } => {
563 for f in fields {
564 match &f.pattern {
565 Some(p) => collect_pattern_binders(p, out),
566 None => {
568 out.insert(f.name.clone());
569 }
570 }
571 }
572 }
573 Pattern::Tuple(items) => {
574 for p in items {
575 collect_pattern_binders(p, out);
576 }
577 }
578 Pattern::Lit(_) | Pattern::Wild => {}
579 }
580}