bend/imports/
mod.rs

1use crate::{
2  diagnostics::{Diagnostics, WarningType},
3  fun::Name,
4};
5use indexmap::{IndexMap, IndexSet};
6use itertools::Itertools;
7use std::fmt::Display;
8
9pub mod book;
10pub mod loader;
11pub mod packages;
12
13pub use loader::*;
14
15pub type BindMap = IndexMap<Name, Name>;
16
17#[derive(Debug, Clone, Default)]
18pub struct ImportCtx {
19  /// Imports declared in the program source.
20  imports: Vec<Import>,
21
22  /// Map from bound names to source package.
23  map: ImportsMap,
24}
25
26impl ImportCtx {
27  pub fn add_import(&mut self, import: Import) {
28    self.imports.push(import);
29  }
30
31  pub fn to_imports(self) -> Vec<Import> {
32    self.imports
33  }
34
35  pub fn sources(&self) -> Vec<&Name> {
36    let mut names = Vec::new();
37    for imps in &self.imports {
38      match &imps.src {
39        BoundSource::None => {}
40        BoundSource::File(f) => names.push(f),
41        BoundSource::Dir(v) => names.extend(v.values()),
42        BoundSource::Either(_, _) => unreachable!("This should be resolved into `File` or `Dir` by now"),
43      }
44    }
45    names
46  }
47}
48
49#[derive(Debug, Clone)]
50pub struct Import {
51  pub path: Name,
52  pub imp_type: ImportType,
53  pub relative: bool,
54  pub src: BoundSource,
55}
56
57impl Import {
58  pub fn new(path: Name, imp_type: ImportType, relative: bool) -> Self {
59    Self { path, imp_type, relative, src: BoundSource::None }
60  }
61}
62
63#[derive(Debug, Clone)]
64pub enum ImportType {
65  Single(Name, Option<Name>),
66  List(Vec<(Name, Option<Name>)>),
67  Glob,
68}
69
70impl Display for ImportType {
71  fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
72    match self {
73      ImportType::Single(n, _) => write!(f, "{n}"),
74      ImportType::List(l) => write!(f, "({})", l.iter().map(|(n, _)| n).join(", ")),
75      ImportType::Glob => write!(f, "*"),
76    }
77  }
78}
79
80#[derive(Debug, Clone)]
81pub enum BoundSource {
82  None,
83  File(Name),
84  Dir(IndexMap<Name, Name>),
85  /// If the bound source is ambiguous between a file or a directory
86  Either(Name, IndexMap<Name, Name>),
87}
88
89#[derive(Debug, Clone, Default)]
90struct ImportsMap {
91  binds: BindMap,
92}
93
94impl ImportsMap {
95  pub fn contains_source(&self, s: &Name) -> bool {
96    self.binds.values().contains(s)
97  }
98
99  fn add_bind(&mut self, src: &str, bind: Name, diag: &mut Diagnostics) {
100    if let Some(old) = self.binds.get(&bind) {
101      let warn = format!("The import '{src}' shadows the imported name '{old}'");
102      diag.add_book_warning(warn, WarningType::ImportShadow);
103    }
104
105    self.binds.insert(bind, Name::new(src));
106  }
107
108  fn add_aliased_bind(&mut self, src: &Name, sub: &Name, alias: Option<&Name>, diag: &mut Diagnostics) {
109    let src = format!("{}/{}", src, sub);
110    let aliased = alias.unwrap_or(sub);
111    self.add_bind(&src, aliased.clone(), diag);
112  }
113
114  fn add_binds(&mut self, names: &IndexSet<Name>, src: &Name, diag: &mut Diagnostics) {
115    for sub in names {
116      self.add_aliased_bind(src, sub, None, diag);
117    }
118  }
119
120  /// Adds all names to the ImportMap in the form `alias/name`.
121  /// If one of the names is equal to the file name, adds as `alias` instead.
122  fn add_file_nested_binds(
123    &mut self,
124    src: &Name,
125    file: &Name,
126    alias: Option<&Name>,
127    names: IndexSet<Name>,
128    diag: &mut Diagnostics,
129  ) {
130    let aliased = alias.unwrap_or(file);
131
132    self.add_nested_binds(src, aliased, names.iter().filter(|&n| n != file), diag);
133
134    if names.contains(file) {
135      let src = format!("{}/{}", src, file);
136      self.add_bind(&src, aliased.clone(), diag);
137    }
138  }
139
140  /// Adds all names to the ImportMap in the form `bind/name`.
141  fn add_nested_binds<'a>(
142    &mut self,
143    src: &Name,
144    bind: &Name,
145    names: impl Iterator<Item = &'a Name>,
146    diag: &mut Diagnostics,
147  ) {
148    for name in names {
149      let src = format!("{}/{}", src, name);
150      let bind = Name::new(format!("{bind}/{name}"));
151      self.add_bind(&src, bind, diag);
152    }
153  }
154}