1use std::collections::{HashMap, HashSet};
72use std::path::{Path, PathBuf};
73use thiserror::Error;
74
75use sha2::{Digest, Sha256};
76
77use crate::syntax::*;
78use crate::workspace::{resolve_package_import, PackageError};
79use crate::{parse_source, SyntaxError};
80
81#[derive(Debug, Error)]
82pub enum LoadError {
83 #[error("read {path}: {source}")]
84 Io {
85 path: String,
86 #[source]
87 source: std::io::Error,
88 },
89 #[error("parse {path}: {source}")]
90 Syntax {
91 path: String,
92 #[source]
93 source: SyntaxError,
94 },
95 #[error("import cycle: {chain}")]
96 Cycle { chain: String },
97 #[error("import \"{reference}\" from {importer}: file not found")]
98 NotFound { importer: String, reference: String },
99 #[error("local imports (`./`, `../`, `/`) require a base path; cannot resolve from a string source")]
100 LocalImportInStringSource,
101 #[error("package import error: {0}")]
102 Package(#[from] PackageError),
103}
104
105pub fn load_program(entry: &Path) -> Result<Program, LoadError> {
108 load_rooted(entry, None)
109}
110
111pub fn load_program_with_root(entry: &Path, root: &Path) -> Result<Program, LoadError> {
121 let root = root.canonicalize().unwrap_or_else(|_| root.to_path_buf());
126 load_rooted(entry, Some(root))
127}
128
129fn load_rooted(entry: &Path, prefix_root: Option<PathBuf>) -> Result<Program, LoadError> {
130 let entry_canonical = entry.canonicalize().map_err(|source| LoadError::Io {
131 path: entry.display().to_string(),
132 source,
133 })?;
134 let mut state = LoaderState {
135 in_progress: Vec::new(),
136 loaded: HashSet::new(),
137 prefixes: HashMap::new(),
138 prefix_root,
139 };
140 state.prefixes.insert(entry_canonical.clone(), String::new());
143 state.load(&entry_canonical)
144}
145
146pub fn load_program_from_str(src: &str) -> Result<Program, LoadError> {
149 let prog = parse_source(src).map_err(|source| LoadError::Syntax {
150 path: "<input>".into(),
151 source,
152 })?;
153 for item in &prog.items {
154 if let Item::Import(imp) = item {
155 if is_path_import(&imp.reference)
156 || split_package_import(&imp.reference).is_some()
157 {
158 return Err(LoadError::LocalImportInStringSource);
159 }
160 }
161 }
162 Ok(prog)
163}
164
165struct LoaderState {
166 in_progress: Vec<PathBuf>,
167 loaded: HashSet<PathBuf>,
171 prefixes: HashMap<PathBuf, String>,
174 prefix_root: Option<PathBuf>,
179}
180
181impl LoaderState {
182 fn prefix_for(&mut self, canonical: &Path) -> String {
183 if let Some(p) = self.prefixes.get(canonical) {
184 return p.clone();
185 }
186 let stem = canonical
187 .file_stem()
188 .and_then(|s| s.to_str())
189 .unwrap_or("module");
190 let mut hasher = Sha256::new();
191 hasher.update(self.mangling_key(canonical).as_bytes());
192 let digest = hasher.finalize();
193 let prefix = format!("{stem}_{:08x}", u32::from_be_bytes([
194 digest[0], digest[1], digest[2], digest[3],
195 ]));
196 self.prefixes.insert(canonical.to_path_buf(), prefix.clone());
197 prefix
198 }
199
200 fn mangling_key(&self, canonical: &Path) -> String {
206 if let Some(root) = &self.prefix_root {
207 if let Ok(rel) = canonical.strip_prefix(root) {
208 let key = rel
209 .components()
210 .map(|c| c.as_os_str().to_string_lossy())
211 .collect::<Vec<_>>()
212 .join("/");
213 if !key.is_empty() {
217 return key;
218 }
219 }
220 }
221 canonical.to_string_lossy().into_owned()
222 }
223
224 fn load(&mut self, canonical: &Path) -> Result<Program, LoadError> {
225 if self.in_progress.contains(&canonical.to_path_buf()) {
226 let mut chain: Vec<String> = self
227 .in_progress
228 .iter()
229 .map(|p| p.display().to_string())
230 .collect();
231 chain.push(canonical.display().to_string());
232 return Err(LoadError::Cycle {
233 chain: chain.join(" -> "),
234 });
235 }
236 if self.loaded.contains(canonical) {
243 return Ok(Program {
244 items: Vec::new(),
245 leading_comments: Vec::new(),
246 trailing_comments: Vec::new(),
247 });
248 }
249 self.in_progress.push(canonical.to_path_buf());
250
251 let src = std::fs::read_to_string(canonical).map_err(|source| LoadError::Io {
252 path: canonical.display().to_string(),
253 source,
254 })?;
255 let prog = parse_source(&src).map_err(|source| LoadError::Syntax {
256 path: canonical.display().to_string(),
257 source,
258 })?;
259
260 let local_names: HashSet<String> = prog
261 .items
262 .iter()
263 .filter_map(|item| match item {
264 Item::FnDecl(fd) => Some(fd.name.clone()),
265 Item::TypeDecl(td) => Some(td.name.clone()),
266 _ => None,
267 })
268 .collect();
269
270 let mut path_imports: HashMap<String, String> = HashMap::new();
272 let mut merged_children: Vec<Item> = Vec::new();
273 let mut std_imports: Vec<Item> = Vec::new();
274 let mut my_items: Vec<Item> = Vec::new();
275
276 for item in prog.items {
277 match item {
278 Item::Import(ref imp) if is_path_import(&imp.reference) => {
279 let resolved = resolve_import(canonical, &imp.reference)?;
280 let child_prefix = self.prefix_for(&resolved);
281 path_imports.insert(imp.alias.clone(), child_prefix);
282 let child_prog = self.load(&resolved)?;
283 merged_children.extend(child_prog.items);
284 }
285 Item::Import(ref imp)
286 if split_package_import(&imp.reference).is_some() =>
287 {
288 let (pkg, module) =
289 split_package_import(&imp.reference).unwrap();
290 let resolved =
291 resolve_package_import(canonical, pkg, module)
292 .map_err(LoadError::Package)?
293 .canonicalize()
294 .map_err(|source| LoadError::Io {
295 path: imp.reference.clone(),
296 source,
297 })?;
298 let child_prefix = self.prefix_for(&resolved);
299 path_imports.insert(imp.alias.clone(), child_prefix);
300 let child_prog = self.load(&resolved)?;
301 merged_children.extend(child_prog.items);
302 }
303 Item::Import(_) => std_imports.push(item),
304 _ => my_items.push(item),
305 }
306 }
307
308 let my_prefix = self.prefix_for(canonical);
309 let mangler = Mangler {
310 prefix: my_prefix,
311 local_names: &local_names,
312 path_imports: &path_imports,
313 };
314 let mangled: Vec<Item> = my_items
315 .into_iter()
316 .map(|i| mangler.mangle_item(i))
317 .collect();
318
319 self.in_progress.pop();
320 self.loaded.insert(canonical.to_path_buf());
321
322 let mut out: Vec<Item> = Vec::new();
325 for s in std_imports {
326 if !merged_children.iter().any(|m| m == &s) {
327 out.push(s);
328 }
329 }
330 out.extend(merged_children);
331 out.extend(mangled);
332 Ok(Program {
339 items: out,
340 leading_comments: Vec::new(),
341 trailing_comments: Vec::new(),
342 })
343 }
344}
345
346fn is_path_import(reference: &str) -> bool {
347 reference.starts_with("./") || reference.starts_with("../") || reference.starts_with('/')
348}
349
350fn split_package_import(reference: &str) -> Option<(&str, &str)> {
354 if reference.starts_with("./")
355 || reference.starts_with("../")
356 || reference.starts_with('/')
357 || reference.starts_with("std.")
358 {
359 return None;
360 }
361 reference.split_once('/')
362}
363
364fn resolve_import(importer: &Path, reference: &str) -> Result<PathBuf, LoadError> {
365 let importer_dir = importer.parent().unwrap_or_else(|| Path::new("."));
366 let mut resolved: PathBuf = if reference.starts_with('/') {
367 PathBuf::from(reference)
368 } else {
369 importer_dir.join(reference)
370 };
371 if resolved.extension().is_none() {
372 resolved.set_extension("lex");
373 }
374 if !resolved.exists() {
375 return Err(LoadError::NotFound {
376 importer: importer.display().to_string(),
377 reference: reference.to_string(),
378 });
379 }
380 resolved.canonicalize().map_err(|source| LoadError::Io {
384 path: resolved.display().to_string(),
385 source,
386 })
387}
388
389struct Mangler<'a> {
390 prefix: String,
393 local_names: &'a HashSet<String>,
394 path_imports: &'a HashMap<String, String>,
398}
399
400impl<'a> Mangler<'a> {
401 fn qualify(&self, name: &str) -> String {
402 if self.prefix.is_empty() {
403 name.to_string()
404 } else {
405 format!("{}.{}", self.prefix, name)
406 }
407 }
408
409 fn mangle_item(&self, item: Item) -> Item {
410 match item {
411 Item::Import(imp) => Item::Import(imp),
412 Item::TypeDecl(td) => Item::TypeDecl(self.mangle_type_decl(td)),
413 Item::FnDecl(fd) => Item::FnDecl(self.mangle_fn_decl(fd)),
414 }
415 }
416
417 fn mangle_type_decl(&self, td: TypeDecl) -> TypeDecl {
418 TypeDecl {
419 name: self.qualify(&td.name),
420 params: td.params,
421 definition: self.mangle_type_expr(td.definition),
422 leading_comments: td.leading_comments,
423 }
424 }
425
426 fn mangle_fn_decl(&self, fd: FnDecl) -> FnDecl {
427 let mut shadow = HashSet::new();
428 for p in &fd.params {
429 shadow.insert(p.name.clone());
430 }
431 let empty_shadow = HashSet::new();
437 let examples = fd
438 .examples
439 .into_iter()
440 .map(|ex| Example {
441 args: ex
442 .args
443 .into_iter()
444 .map(|a| self.mangle_expr(a, &empty_shadow))
445 .collect(),
446 expected: self.mangle_expr(ex.expected, &empty_shadow),
447 })
448 .collect();
449 FnDecl {
450 name: self.qualify(&fd.name),
451 type_params: fd.type_params,
452 params: fd
453 .params
454 .into_iter()
455 .map(|p| Param {
456 name: p.name,
457 ty: self.mangle_type_expr(p.ty),
458 })
459 .collect(),
460 effects: fd.effects,
461 effect_row_var: fd.effect_row_var,
462 return_type: self.mangle_type_expr(fd.return_type),
463 body: self.mangle_block(fd.body, &shadow),
464 examples,
465 leading_comments: fd.leading_comments,
466 }
467 }
468
469 fn mangle_type_expr(&self, te: TypeExpr) -> TypeExpr {
470 match te {
471 TypeExpr::Named { name, args } => TypeExpr::Named {
472 name: self.rewrite_type_name(&name),
473 args: args.into_iter().map(|a| self.mangle_type_expr(a)).collect(),
474 },
475 TypeExpr::Record(fields) => TypeExpr::Record(
476 fields
477 .into_iter()
478 .map(|f| TypeField {
479 name: f.name,
480 ty: self.mangle_type_expr(f.ty),
481 })
482 .collect(),
483 ),
484 TypeExpr::RecordWithSpreads { spreads, fields } => TypeExpr::RecordWithSpreads {
485 spreads: spreads.into_iter().map(|s| self.rewrite_type_name(&s)).collect(),
486 fields: fields
487 .into_iter()
488 .map(|f| TypeField {
489 name: f.name,
490 ty: self.mangle_type_expr(f.ty),
491 })
492 .collect(),
493 },
494 TypeExpr::Tuple(items) => {
495 TypeExpr::Tuple(items.into_iter().map(|t| self.mangle_type_expr(t)).collect())
496 }
497 TypeExpr::Function {
498 params,
499 effects,
500 effect_row_var,
501 ret,
502 } => TypeExpr::Function {
503 params: params
504 .into_iter()
505 .map(|t| self.mangle_type_expr(t))
506 .collect(),
507 effects,
508 effect_row_var,
509 ret: Box::new(self.mangle_type_expr(*ret)),
510 },
511 TypeExpr::Union(variants) => TypeExpr::Union(
512 variants
513 .into_iter()
514 .map(|v| UnionVariant {
515 name: v.name,
516 payload: v.payload.map(|t| self.mangle_type_expr(t)),
517 })
518 .collect(),
519 ),
520 TypeExpr::Refined { base, binding, predicate } => TypeExpr::Refined {
521 base: Box::new(self.mangle_type_expr(*base)),
522 binding,
523 predicate,
528 },
529 }
530 }
531
532 fn rewrite_type_name(&self, name: &str) -> String {
534 if let Some((alias, rest)) = name.split_once('.') {
535 if let Some(child) = self.path_imports.get(alias) {
536 return format!("{child}.{rest}");
537 }
538 return name.to_string();
539 }
540 if self.local_names.contains(name) {
541 return self.qualify(name);
542 }
543 name.to_string()
544 }
545
546 fn mangle_block(&self, b: Block, shadow: &HashSet<String>) -> Block {
547 let mut shadow = shadow.clone();
548 let statements = b
549 .statements
550 .into_iter()
551 .map(|s| match s {
552 Statement::Let { name, ty, value } => {
553 let value = self.mangle_expr(value, &shadow);
554 let ty = ty.map(|t| self.mangle_type_expr(t));
555 shadow.insert(name.clone());
556 Statement::Let { name, ty, value }
557 }
558 Statement::Expr(e) => Statement::Expr(self.mangle_expr(e, &shadow)),
559 })
560 .collect();
561 let result = Box::new(self.mangle_expr(*b.result, &shadow));
562 Block { statements, result }
563 }
564
565 fn mangle_expr(&self, e: Expr, shadow: &HashSet<String>) -> Expr {
566 match e {
567 Expr::Lit(_) => e,
568 Expr::Var(name) => {
569 if !shadow.contains(&name) && self.local_names.contains(&name) {
570 Expr::Var(self.qualify(&name))
571 } else {
572 Expr::Var(name)
573 }
574 }
575 Expr::Block(b) => Expr::Block(self.mangle_block(b, shadow)),
576 Expr::Call { callee, args } => {
577 let mangled_args: Vec<Expr> = args
578 .into_iter()
579 .map(|a| self.mangle_expr(a, shadow))
580 .collect();
581 if let Expr::Field { value, field } = (*callee).clone() {
582 if let Expr::Var(alias) = *value {
583 if !shadow.contains(&alias) {
584 if let Some(child) = self.path_imports.get(&alias) {
585 return Expr::Call {
586 callee: Box::new(Expr::Var(format!("{child}.{field}"))),
587 args: mangled_args,
588 };
589 }
590 }
591 }
592 }
593 Expr::Call {
594 callee: Box::new(self.mangle_expr(*callee, shadow)),
595 args: mangled_args,
596 }
597 }
598 Expr::Pipe { left, right } => Expr::Pipe {
599 left: Box::new(self.mangle_expr(*left, shadow)),
600 right: Box::new(self.mangle_expr(*right, shadow)),
601 },
602 Expr::Try(inner) => Expr::Try(Box::new(self.mangle_expr(*inner, shadow))),
603 Expr::Field { value, field } => {
604 if let Expr::Var(alias) = (*value).clone() {
605 if !shadow.contains(&alias) {
606 if let Some(child) = self.path_imports.get(&alias) {
607 return Expr::Var(format!("{child}.{field}"));
608 }
609 }
610 }
611 Expr::Field {
612 value: Box::new(self.mangle_expr(*value, shadow)),
613 field,
614 }
615 }
616 Expr::BinOp { op, lhs, rhs } => Expr::BinOp {
617 op,
618 lhs: Box::new(self.mangle_expr(*lhs, shadow)),
619 rhs: Box::new(self.mangle_expr(*rhs, shadow)),
620 },
621 Expr::UnaryOp { op, expr } => Expr::UnaryOp {
622 op,
623 expr: Box::new(self.mangle_expr(*expr, shadow)),
624 },
625 Expr::If {
626 cond,
627 then_block,
628 else_block,
629 } => Expr::If {
630 cond: Box::new(self.mangle_expr(*cond, shadow)),
631 then_block: self.mangle_block(then_block, shadow),
632 else_block: self.mangle_block(else_block, shadow),
633 },
634 Expr::Match { scrutinee, arms } => Expr::Match {
635 scrutinee: Box::new(self.mangle_expr(*scrutinee, shadow)),
636 arms: arms
637 .into_iter()
638 .map(|a| {
639 let mut arm_shadow = shadow.clone();
640 collect_pattern_binders(&a.pattern, &mut arm_shadow);
641 Arm {
642 pattern: self.mangle_pattern(a.pattern),
643 body: self.mangle_expr(a.body, &arm_shadow),
644 }
645 })
646 .collect(),
647 },
648 Expr::RecordLit(fields) => Expr::RecordLit(
649 fields
650 .into_iter()
651 .map(|f| RecordLitField {
652 name: f.name,
653 value: self.mangle_expr(f.value, shadow),
654 })
655 .collect(),
656 ),
657 Expr::TupleLit(items) => Expr::TupleLit(
658 items
659 .into_iter()
660 .map(|i| self.mangle_expr(i, shadow))
661 .collect(),
662 ),
663 Expr::ListLit(items) => Expr::ListLit(
664 items
665 .into_iter()
666 .map(|i| self.mangle_expr(i, shadow))
667 .collect(),
668 ),
669 Expr::Constructor { name, args } => Expr::Constructor {
670 name,
671 args: args
672 .into_iter()
673 .map(|a| self.mangle_expr(a, shadow))
674 .collect(),
675 },
676 Expr::Ascription { value, ty } => Expr::Ascription {
677 value: Box::new(self.mangle_expr(*value, shadow)),
678 ty: self.mangle_type_expr(ty),
679 },
680 Expr::Lambda(lambda) => {
681 let mut lam_shadow = shadow.clone();
682 for p in &lambda.params {
683 lam_shadow.insert(p.name.clone());
684 }
685 Expr::Lambda(Box::new(Lambda {
686 params: lambda
687 .params
688 .into_iter()
689 .map(|p| Param {
690 name: p.name,
691 ty: self.mangle_type_expr(p.ty),
692 })
693 .collect(),
694 return_type: self.mangle_type_expr(lambda.return_type),
695 effects: lambda.effects,
696 effect_row_var: lambda.effect_row_var,
697 body: self.mangle_block(lambda.body, &lam_shadow),
698 }))
699 }
700 }
701 }
702
703 fn mangle_pattern(&self, p: Pattern) -> Pattern {
704 match p {
705 Pattern::Constructor { name, args } => Pattern::Constructor {
706 name,
707 args: args.into_iter().map(|a| self.mangle_pattern(a)).collect(),
708 },
709 Pattern::Record { fields, rest } => Pattern::Record {
710 fields: fields
711 .into_iter()
712 .map(|f| RecordPatField {
713 name: f.name,
714 pattern: f.pattern.map(|p| self.mangle_pattern(p)),
715 })
716 .collect(),
717 rest,
718 },
719 Pattern::Tuple(items) => {
720 Pattern::Tuple(items.into_iter().map(|p| self.mangle_pattern(p)).collect())
721 }
722 Pattern::Lit(_) | Pattern::Var(_) | Pattern::Wild => p,
723 }
724 }
725}
726
727fn collect_pattern_binders(p: &Pattern, out: &mut HashSet<String>) {
728 match p {
729 Pattern::Var(name) => {
730 out.insert(name.clone());
731 }
732 Pattern::Constructor { args, .. } => {
733 for a in args {
734 collect_pattern_binders(a, out);
735 }
736 }
737 Pattern::Record { fields, .. } => {
738 for f in fields {
739 match &f.pattern {
740 Some(p) => collect_pattern_binders(p, out),
741 None => {
743 out.insert(f.name.clone());
744 }
745 }
746 }
747 }
748 Pattern::Tuple(items) => {
749 for p in items {
750 collect_pattern_binders(p, out);
751 }
752 }
753 Pattern::Lit(_) | Pattern::Wild => {}
754 }
755}