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 self.diag.clear()
180 }
181
182 if resolved {
183 log::info!("Resolve OK ({passes_needed} passes).");
184 } else {
185 log::info!("Resolve failed after {passes_needed} passes.");
186 }
187 log::debug!("Resolved symbol table:\n{self:?}");
188
189 self.mode = ResolveMode::Resolved;
190
191 Ok(())
192 }
193
194 fn has_links(&self) -> bool {
195 self.symbol_table
196 .symbols()
197 .iter()
198 .filter(|symbol| !symbol.is_deleted())
199 .any(|symbol| symbol.has_links())
200 }
201
202 pub fn check(&mut self) -> ResolveResult<()> {
204 log::trace!("Checking symbol table");
205 self.mode = ResolveMode::Failed;
206
207 let exclude_ids = self.symbol_table.search_target_mode_ids();
208 log::trace!("Excluding target mode ids: {exclude_ids}");
209
210 if let Err(err) = self
211 .symbol_table
212 .symbols()
213 .iter_mut()
214 .try_for_each(|symbol| symbol.check(self, &exclude_ids))
215 {
216 self.error(&crate::src_ref::SrcRef::default(), err)?;
217 } else if !self.has_errors() {
218 self.mode = ResolveMode::Checked;
219 }
220
221 log::info!("Symbol table OK!");
222
223 let unchecked = self.symbol_table.unchecked();
224 log::trace!(
225 "Symbols never used in ANY code:\n{}",
226 unchecked
227 .iter()
228 .map(|symbol| format!("{symbol:?}"))
229 .collect::<Vec<_>>()
230 .join("\n")
231 );
232 self.unchecked = Some(unchecked);
233
234 Ok(())
235 }
236
237 pub fn symbolize_file(
239 &mut self,
240 visibility: Visibility,
241 parent_path: impl AsRef<std::path::Path>,
242 id: &Identifier,
243 ) -> ResolveResult<Symbol> {
244 let mut symbol = self
245 .sources
246 .load_mod_file(parent_path, id)?
247 .symbolize(visibility, self)?;
248 symbol.set_src_ref(id.src_ref());
249 Ok(symbol)
250 }
251
252 pub fn is_checked(&self) -> bool {
255 self.mode >= ResolveMode::Checked
256 }
257
258 pub fn reload_files(&mut self, files: &[impl AsRef<std::path::Path>]) -> ResolveResult<()> {
260 self.mode = ResolveMode::Failed;
262
263 let replaced = files
265 .iter()
266 .map(|path| self.sources.update_file(path.as_ref()))
267 .collect::<ResolveResult<Vec<_>>>()?;
268
269 replaced.iter().try_for_each(|rep| {
271 self.symbol_table
272 .find_file(rep.old.hash)
273 .map(|mut symbol| -> ResolveResult<()> {
274 symbol.replace(rep.new.symbolize(Visibility::Public, self)?);
275 Ok(())
276 })
277 .expect("symbol of file could not be found")
278 })?;
279
280 replaced.iter().for_each(|rep| {
282 self.symbol_table.recursive_for_each_mut(|_, symbol| {
283 if let Some(link) = symbol.get_link() {
285 if link.starts_with(&rep.old.name) {
287 symbol.reset_visibility();
289 }
290 } else if symbol.source_hash() == rep.old.hash {
291 symbol.delete();
293 }
294 });
295 });
296
297 self.mode = ResolveMode::Symbolized;
299 self.resolve()
300 }
301
302 pub fn symbol_table(&self) -> &SymbolTable {
304 &self.symbol_table
305 }
306}
307
308#[test]
309fn test_update_sub_mod() {
310 use crate::eval::*;
311
312 std::fs::copy(
313 "../tests/test_files/update_files/sub/sub_0.µcad",
314 "../tests/test_files/update_files/sub/sub.µcad",
315 )
316 .expect("test error");
317
318 let root =
319 SourceFile::load("../tests/test_files/update_files/sub/top.µcad").expect("test error");
320 let mut context = ResolveContext::test_create(root, ResolveMode::Checked).expect("test error");
321
322 eprintln!("{context:?}");
323
324 std::fs::copy(
325 "../tests/test_files/update_files/sub/sub_1.µcad",
326 "../tests/test_files/update_files/sub/sub.µcad",
327 )
328 .expect("test error");
329
330 context
331 .reload_files(&["../tests/test_files/update_files/sub/sub.µcad"])
332 .expect("test error");
333
334 eprintln!("{context:?}");
335
336 let mut context = EvalContext::new(
337 context,
338 Stdout::new(),
339 Default::default(),
340 Default::default(),
341 );
342 context.eval().expect("test error");
343 assert!(!context.has_errors());
344}
345
346#[test]
347fn test_update_top_mod() {
348 use crate::eval::*;
349
350 std::fs::copy(
351 "../tests/test_files/update_files/top/top_0.µcad",
352 "../tests/test_files/update_files/top/top.µcad",
353 )
354 .expect("test error");
355
356 let root =
357 SourceFile::load("../tests/test_files/update_files/top/top.µcad").expect("test error");
358 let mut context = ResolveContext::test_create(root, ResolveMode::Checked).expect("test error");
359
360 eprintln!("{context:?}");
361
362 std::fs::copy(
363 "../tests/test_files/update_files/top/top_1.µcad",
364 "../tests/test_files/update_files/top/top.µcad",
365 )
366 .expect("test error");
367
368 context
369 .reload_files(&["../tests/test_files/update_files/top/top.µcad"])
370 .expect("test error");
371
372 eprintln!("{context:?}");
373
374 let mut context = EvalContext::new(
375 context,
376 Stdout::new(),
377 Default::default(),
378 Default::default(),
379 );
380 context.eval().expect("test error");
381 assert!(!context.has_errors());
382}
383
384impl WriteToFile for ResolveContext {}
385
386impl PushDiag for ResolveContext {
387 fn push_diag(&mut self, diag: Diagnostic) -> DiagResult<()> {
388 self.diag.push_diag(diag)
389 }
390}
391
392impl Diag for ResolveContext {
393 fn fmt_diagnosis(&self, f: &mut dyn std::fmt::Write) -> std::fmt::Result {
394 self.diag.pretty_print(f, self)
395 }
396
397 fn warning_count(&self) -> u32 {
398 self.diag.error_count()
399 }
400
401 fn error_count(&self) -> u32 {
402 self.diag.error_count()
403 }
404
405 fn error_lines(&self) -> std::collections::HashSet<usize> {
406 self.diag.error_lines()
407 }
408
409 fn warning_lines(&self) -> std::collections::HashSet<usize> {
410 self.diag.warning_lines()
411 }
412}
413
414impl GetSourceByHash for ResolveContext {
415 fn get_by_hash(&self, hash: u64) -> ResolveResult<std::rc::Rc<SourceFile>> {
416 self.sources.get_by_hash(hash)
417 }
418}
419
420impl std::fmt::Debug for ResolveContext {
421 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
422 writeln!(f, "Sources:\n")?;
423 write!(f, "{:?}", &self.sources)?;
424 writeln!(f, "\nSymbols:\n")?;
425 write!(f, "{:?}", &self.symbol_table)?;
426 let err_count = self.diag.error_count();
427 if err_count == 0 {
428 writeln!(f, "No errors.")?;
429 } else {
430 writeln!(f, "\n{err_count} error(s):\n")?;
431 self.diag.pretty_print(f, &self.sources)?;
432 }
433 if let Some(unchecked) = &self.unchecked {
434 writeln!(f, "\nUnchecked:\n{unchecked}")?;
435 }
436 Ok(())
437 }
438}
439
440impl std::fmt::Display for ResolveContext {
441 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
442 if let Some(unchecked) = &self.unchecked {
443 writeln!(f, "Resolved & checked symbols:\n{}", self.symbol_table)?;
444 if unchecked.is_empty() {
445 writeln!(f, "All symbols are referenced.\n{}", self.symbol_table)?;
446 } else {
447 writeln!(
448 f,
449 "Unreferenced symbols:\n{}\n",
450 unchecked
451 .iter()
452 .filter(|symbol| !symbol.is_deleted())
453 .map(|symbol| symbol.full_name().to_string())
454 .collect::<Vec<_>>()
455 .join(", ")
456 )?;
457 }
458 } else {
459 writeln!(f, "Resolved symbols:\n{}", self.symbol_table)?;
460 }
461 if self.has_errors() {
462 writeln!(
463 f,
464 "{err} error(s) and {warn} warning(s) so far:\n{diag}",
465 err = self.error_count(),
466 warn = self.warning_count(),
467 diag = self.diagnosis()
468 )?;
469 } else {
470 writeln!(f, "No errors so far.")?;
471 }
472 Ok(())
473 }
474}