1use crate::{diag::*, rc::*, resolve::*, src_ref::*, syntax::*};
7
8#[derive(Default)]
10pub struct ResolveContext {
11 pub(crate) symbol_table: SymbolTable,
13 pub(crate) sources: Sources,
15 pub(crate) diag: DiagHandler,
17 unchecked: Option<Symbols>,
21 mode: ResolveMode,
23}
24
25#[derive(Default, PartialEq, PartialOrd)]
27pub enum ResolveMode {
28 Failed,
30 #[default]
32 Loaded,
33 Symbolized,
35 Resolved,
37 Checked,
39}
40
41impl ResolveContext {
42 pub fn new(
46 root: Rc<SourceFile>,
47 search_paths: &[impl AsRef<std::path::Path>],
48 diag: DiagHandler,
49 ) -> ResolveResult<Self> {
50 Ok(Self {
51 sources: Sources::load(root.clone(), search_paths)?,
52 diag,
53 ..Default::default()
54 })
55 }
56
57 pub fn create(
59 root: Rc<SourceFile>,
60 search_paths: &[impl AsRef<std::path::Path>],
61 builtin: Option<Symbol>,
62 diag: DiagHandler,
63 ) -> ResolveResult<Self> {
64 match Self::create_ex(root, search_paths, builtin, diag, ResolveMode::Checked) {
65 Ok(context) => Ok(context),
66 Err(err) => {
67 let mut context = ResolveContext {
69 mode: ResolveMode::Failed,
70 ..Default::default()
71 };
72 context.error(&SrcRef(None), err)?;
73 Ok(context)
74 }
75 }
76 }
77
78 fn create_ex(
79 root: Rc<SourceFile>,
80 search_paths: &[impl AsRef<std::path::Path>],
81 builtin: Option<Symbol>,
82 diag: DiagHandler,
83 mode: ResolveMode,
84 ) -> ResolveResult<Self> {
85 let mut context = Self::new(root, search_paths, diag)?;
86 context.symbolize()?;
87 log::trace!("Symbolized Context:\n{context:?}");
88 if let Some(builtin) = builtin {
89 log::trace!("Added builtin library {id}.", id = builtin.id());
90 context.symbol_table.add_symbol(builtin)?;
91 }
92 if matches!(mode, ResolveMode::Resolved | ResolveMode::Checked) {
93 context.resolve()?;
94 if matches!(mode, ResolveMode::Checked) {
95 context.check()?;
96 }
97 }
98 Ok(context)
99 }
100
101 #[cfg(test)]
102 pub(super) fn test_create(root: Rc<SourceFile>, mode: ResolveMode) -> ResolveResult<Self> {
103 Self::create_ex(
104 root,
105 &[] as &[std::path::PathBuf],
106 None,
107 Default::default(),
108 mode,
109 )
110 }
111
112 #[cfg(test)]
113 pub(super) fn test_add_file(&mut self, file: Rc<SourceFile>) {
114 let symbol = file
115 .symbolize(Visibility::Private, self)
116 .expect("symbolize");
117 self.symbol_table
118 .add_symbol(symbol)
119 .expect("symbolize error");
120 }
121
122 pub(crate) fn symbolize(&mut self) -> ResolveResult<()> {
123 assert!(matches!(self.mode, ResolveMode::Loaded));
124 self.mode = ResolveMode::Failed;
125
126 let named_symbols = self
127 .sources
128 .clone()
129 .iter()
130 .map(|source| {
131 match (
132 self.sources.generate_name_from_path(&source.filename()),
133 source.symbolize(Visibility::Public, self),
134 ) {
135 (Ok(name), Ok(symbol)) => Ok((name, symbol)),
136 (_, Err(err)) | (Err(err), _) => Err(err),
137 }
138 })
139 .collect::<ResolveResult<Vec<_>>>()?;
140
141 for (name, symbol) in named_symbols {
142 if let Some(id) = name.single_identifier() {
143 self.symbol_table.insert_symbol(id.clone(), symbol)?;
144 } else {
145 unreachable!("name is not an id")
146 }
147 }
148
149 self.mode = ResolveMode::Symbolized;
150
151 Ok(())
152 }
153
154 pub(super) fn resolve(&mut self) -> ResolveResult<()> {
155 assert!(matches!(self.mode, ResolveMode::Symbolized));
156 self.mode = ResolveMode::Failed;
157
158 if let Some(std) = self.symbol_table.get(&Identifier::no_ref("std")).cloned() {
160 std.resolve(self)?;
161 }
162
163 const MAX_PASSES: usize = 3;
165 let mut passes_needed = 0;
166 let mut resolved = false;
167 for _ in 0..MAX_PASSES {
168 self.symbol_table
169 .symbols()
170 .iter()
171 .filter(|child| child.is_resolvable())
172 .map(|child| child.resolve(self))
173 .collect::<Result<Vec<_>, _>>()?;
174 passes_needed += 1;
175 if !self.has_links() {
176 resolved = true;
177 break;
178 }
179 }
180
181 if resolved {
182 log::info!("Resolve OK ({passes_needed} passes).");
183 } else {
184 log::info!("Resolve failed after {passes_needed} passes.");
185 }
186 log::debug!("Resolved symbol table:\n{self:?}");
187
188 self.mode = ResolveMode::Resolved;
189
190 Ok(())
191 }
192
193 fn has_links(&self) -> bool {
194 self.symbol_table
195 .symbols()
196 .iter()
197 .filter(|symbol| !symbol.is_deleted())
198 .any(|symbol| symbol.has_links())
199 }
200
201 pub fn check(&mut self) -> ResolveResult<()> {
203 log::trace!("Checking symbol table");
204 self.mode = ResolveMode::Failed;
205
206 let exclude_ids = self.symbol_table.search_target_mode_ids()?;
207 log::trace!("Excluding target mode ids: {exclude_ids}");
208
209 if let Err(err) = self
210 .symbol_table
211 .symbols()
212 .iter_mut()
213 .try_for_each(|symbol| symbol.check(self, &exclude_ids))
214 {
215 self.error(&crate::src_ref::SrcRef::default(), err)?;
216 } else if !self.has_errors() {
217 self.mode = ResolveMode::Checked;
218 }
219
220 log::info!("Symbol table OK!");
221
222 let unchecked = self.symbol_table.unchecked();
223 log::trace!(
224 "Symbols never used in ANY code:\n{}",
225 unchecked
226 .iter()
227 .map(|symbol| format!("{symbol:?}"))
228 .collect::<Vec<_>>()
229 .join("\n")
230 );
231 self.unchecked = Some(unchecked);
232
233 Ok(())
234 }
235
236 pub fn symbolize_file(
238 &mut self,
239 visibility: Visibility,
240 parent_path: impl AsRef<std::path::Path>,
241 id: &Identifier,
242 ) -> ResolveResult<Symbol> {
243 self.sources
244 .load_file(parent_path, id)?
245 .symbolize(visibility, self)
246 }
247
248 pub fn is_checked(&self) -> bool {
251 self.mode >= ResolveMode::Checked
252 }
253}
254
255impl WriteToFile for ResolveContext {}
256
257impl PushDiag for ResolveContext {
258 fn push_diag(&mut self, diag: Diagnostic) -> DiagResult<()> {
259 self.diag.push_diag(diag)
260 }
261}
262
263impl Diag for ResolveContext {
264 fn fmt_diagnosis(&self, f: &mut dyn std::fmt::Write) -> std::fmt::Result {
265 self.diag.pretty_print(f, self)
266 }
267
268 fn warning_count(&self) -> u32 {
269 self.diag.error_count()
270 }
271
272 fn error_count(&self) -> u32 {
273 self.diag.error_count()
274 }
275
276 fn error_lines(&self) -> std::collections::HashSet<usize> {
277 self.diag.error_lines()
278 }
279
280 fn warning_lines(&self) -> std::collections::HashSet<usize> {
281 self.diag.warning_lines()
282 }
283}
284
285impl GetSourceByHash for ResolveContext {
286 fn get_by_hash(&self, hash: u64) -> ResolveResult<std::rc::Rc<SourceFile>> {
287 self.sources.get_by_hash(hash)
288 }
289}
290
291impl std::fmt::Debug for ResolveContext {
292 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
293 writeln!(f, "Sources:\n")?;
294 write!(f, "{:?}", &self.sources)?;
295 writeln!(f, "\nSymbols:\n")?;
296 write!(f, "{:?}", &self.symbol_table)?;
297 let err_count = self.diag.error_count();
298 if err_count == 0 {
299 writeln!(f, "No errors.")?;
300 } else {
301 writeln!(f, "\n{err_count} error(s):\n")?;
302 self.diag.pretty_print(f, &self.sources)?;
303 }
304 if let Some(unchecked) = &self.unchecked {
305 writeln!(f, "\nUnchecked:\n{unchecked}")?;
306 }
307 Ok(())
308 }
309}
310
311impl std::fmt::Display for ResolveContext {
312 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
313 if let Some(unchecked) = &self.unchecked {
314 writeln!(f, "Resolved & checked symbols:\n{}", self.symbol_table)?;
315 if unchecked.is_empty() {
316 writeln!(f, "All symbols are referenced.\n{}", self.symbol_table)?;
317 } else {
318 writeln!(
319 f,
320 "Unreferenced symbols:\n{}\n",
321 unchecked
322 .iter()
323 .filter(|symbol| !symbol.is_deleted())
324 .map(|symbol| symbol.full_name().to_string())
325 .collect::<Vec<_>>()
326 .join(", ")
327 )?;
328 }
329 } else {
330 writeln!(f, "Resolved symbols:\n{}", self.symbol_table)?;
331 }
332 if self.has_errors() {
333 writeln!(
334 f,
335 "{err} error(s) and {warn} warning(s) so far:\n{diag}",
336 err = self.error_count(),
337 warn = self.warning_count(),
338 diag = self.diagnosis()
339 )?;
340 } else {
341 writeln!(f, "No errors so far.")?;
342 }
343 Ok(())
344 }
345}