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.clone(), search_paths, builtin, diag, ResolveMode::Checked) {
65 Ok(context) => Ok(context),
66 Err(err) => {
67 let mut context = ResolveContext {
69 sources: Sources::load(root, search_paths)?,
70 mode: ResolveMode::Failed,
71 ..Default::default()
72 };
73 context.error(&err.src_ref(), err)?;
74 Ok(context)
75 }
76 }
77 }
78
79 fn create_ex(
80 root: Rc<SourceFile>,
81 search_paths: &[impl AsRef<std::path::Path>],
82 builtin: Option<Symbol>,
83 diag: DiagHandler,
84 mode: ResolveMode,
85 ) -> ResolveResult<Self> {
86 let mut context = Self::new(root, search_paths, diag)?;
87 context.symbolize()?;
88 log::trace!("Symbolized Context:\n{context:?}");
89 if let Some(builtin) = builtin {
90 log::trace!("Added builtin library {id}.", id = builtin.id());
91 context.symbol_table.add_symbol(builtin)?;
92 }
93 if matches!(mode, ResolveMode::Resolved | ResolveMode::Checked) {
94 context.resolve()?;
95 if matches!(mode, ResolveMode::Checked) {
96 context.check()?;
97 }
98 }
99 Ok(context)
100 }
101
102 #[cfg(test)]
103 pub(super) fn test_create(root: Rc<SourceFile>, mode: ResolveMode) -> ResolveResult<Self> {
104 Self::create_ex(
105 root,
106 &[] as &[std::path::PathBuf],
107 None,
108 Default::default(),
109 mode,
110 )
111 }
112
113 #[cfg(test)]
114 pub(super) fn test_add_file(&mut self, file: Rc<SourceFile>) {
115 let symbol = file
116 .symbolize(Visibility::Private, self)
117 .expect("symbolize");
118 self.symbol_table
119 .add_symbol(symbol)
120 .expect("symbolize error");
121 }
122
123 pub(crate) fn symbolize(&mut self) -> ResolveResult<()> {
124 assert!(matches!(self.mode, ResolveMode::Loaded));
125 self.mode = ResolveMode::Failed;
126
127 let named_symbols = self
128 .sources
129 .clone()
130 .iter()
131 .map(|source| {
132 match (
133 self.sources.generate_name_from_path(&source.filename()),
134 source.symbolize(Visibility::Public, self),
135 ) {
136 (Ok(name), Ok(symbol)) => Ok((name, symbol)),
137 (_, Err(err)) | (Err(err), _) => Err(err),
138 }
139 })
140 .collect::<ResolveResult<Vec<_>>>()?;
141
142 for (name, symbol) in named_symbols {
143 if let Some(id) = name.single_identifier() {
144 self.symbol_table.insert_symbol(id.clone(), symbol)?;
145 } else {
146 unreachable!("name is not an id")
147 }
148 }
149
150 self.mode = ResolveMode::Symbolized;
151
152 Ok(())
153 }
154
155 pub(super) fn resolve(&mut self) -> ResolveResult<()> {
156 assert!(matches!(self.mode, ResolveMode::Symbolized));
157 self.mode = ResolveMode::Failed;
158
159 if let Some(std) = self.symbol_table.get(&Identifier::no_ref("std")).cloned() {
161 std.resolve(self)?;
162 }
163
164 const MAX_PASSES: usize = 3;
166 let mut passes_needed = 0;
167 let mut resolved = false;
168 for _ in 0..MAX_PASSES {
169 self.symbol_table
170 .symbols()
171 .iter()
172 .filter(|child| child.is_resolvable())
173 .map(|child| child.resolve(self))
174 .collect::<Result<Vec<_>, _>>()?;
175 passes_needed += 1;
176 if !self.has_links() {
177 resolved = true;
178 break;
179 }
180 self.diag.clear()
181 }
182
183 if resolved {
184 log::info!("Resolve OK ({passes_needed} passes).");
185 } else {
186 log::info!("Resolve failed after {passes_needed} passes.");
187 }
188 log::debug!("Resolved symbol table:\n{self:?}");
189
190 self.mode = ResolveMode::Resolved;
191
192 Ok(())
193 }
194
195 fn has_links(&self) -> bool {
196 self.symbol_table
197 .symbols()
198 .iter()
199 .filter(|symbol| !symbol.is_deleted())
200 .any(|symbol| symbol.has_links())
201 }
202
203 pub fn check(&mut self) -> ResolveResult<()> {
205 log::trace!("Checking symbol table");
206 self.mode = ResolveMode::Failed;
207
208 let exclude_ids = self.symbol_table.search_target_mode_ids();
209 log::trace!("Excluding target mode ids: {exclude_ids}");
210
211 if let Err(err) = self
212 .symbol_table
213 .symbols()
214 .iter_mut()
215 .try_for_each(|symbol| symbol.check(self, &exclude_ids))
216 {
217 self.error(&crate::src_ref::SrcRef::default(), err)?;
218 } else if !self.has_errors() {
219 self.mode = ResolveMode::Checked;
220 }
221
222 log::info!("Symbol table OK!");
223
224 let unchecked = self.symbol_table.unchecked();
225 log::trace!(
226 "Symbols never used in ANY code:\n{}",
227 unchecked
228 .iter()
229 .map(|symbol| format!("{symbol:?}"))
230 .collect::<Vec<_>>()
231 .join("\n")
232 );
233 self.unchecked = Some(unchecked);
234
235 Ok(())
236 }
237
238 pub fn symbolize_file(
240 &mut self,
241 visibility: Visibility,
242 parent_path: impl AsRef<std::path::Path>,
243 id: &Identifier,
244 ) -> ResolveResult<Symbol> {
245 let mut symbol = self
246 .sources
247 .load_mod_file(parent_path, id)?
248 .symbolize(visibility, self)?;
249 symbol.set_src_ref(id.src_ref());
250 Ok(symbol)
251 }
252
253 pub fn is_checked(&self) -> bool {
256 self.mode >= ResolveMode::Checked
257 }
258
259 pub fn reload_files(&mut self, files: &[impl AsRef<std::path::Path>]) -> ResolveResult<()> {
261 self.mode = ResolveMode::Failed;
263
264 let replaced = files
266 .iter()
267 .map(|path| self.sources.update_file(path.as_ref()))
268 .collect::<ResolveResult<Vec<_>>>()?;
269
270 replaced.iter().try_for_each(|rep| {
272 self.symbol_table
273 .find_file(rep.old.hash)
274 .map(|mut symbol| -> ResolveResult<()> {
275 symbol.replace(rep.new.symbolize(Visibility::Public, self)?);
276 Ok(())
277 })
278 .expect("symbol of file could not be found")
279 })?;
280
281 replaced.iter().for_each(|rep| {
283 self.symbol_table.recursive_for_each_mut(|_, symbol| {
284 if let Some(link) = symbol.get_link() {
286 if link.starts_with(&rep.old.name) {
288 symbol.reset_visibility();
290 }
291 } else if symbol.source_hash() == rep.old.hash {
292 symbol.delete();
294 }
295 });
296 });
297
298 self.mode = ResolveMode::Symbolized;
300 self.resolve()
301 }
302
303 pub fn symbol_table(&self) -> &SymbolTable {
305 &self.symbol_table
306 }
307}
308
309#[test]
310fn test_update_sub_mod() {
311 use crate::eval::*;
312
313 std::fs::copy(
314 "../../tests/test_files/update_files/sub/sub_0.µcad",
315 "../../tests/test_files/update_files/sub/sub.µcad",
316 )
317 .expect("test error");
318
319 let root =
320 SourceFile::load("../../tests/test_files/update_files/sub/top.µcad").expect("test error");
321 let mut context = ResolveContext::test_create(root, ResolveMode::Checked).expect("test error");
322
323 eprintln!("{context:?}");
324
325 std::fs::copy(
326 "../../tests/test_files/update_files/sub/sub_1.µcad",
327 "../../tests/test_files/update_files/sub/sub.µcad",
328 )
329 .expect("test error");
330
331 context
332 .reload_files(&["../../tests/test_files/update_files/sub/sub.µcad"])
333 .expect("test error");
334
335 eprintln!("{context:?}");
336
337 let mut context = EvalContext::new(
338 context,
339 Stdout::new(),
340 Default::default(),
341 Default::default(),
342 );
343 context.eval().expect("test error");
344 assert!(!context.has_errors());
345}
346
347#[test]
348fn test_update_top_mod() {
349 use crate::eval::*;
350
351 std::fs::copy(
352 "../../tests/test_files/update_files/top/top_0.µcad",
353 "../../tests/test_files/update_files/top/top.µcad",
354 )
355 .expect("test error");
356
357 let root =
358 SourceFile::load("../../tests/test_files/update_files/top/top.µcad").expect("test error");
359 let mut context = ResolveContext::test_create(root, ResolveMode::Checked).expect("test error");
360
361 eprintln!("{context:?}");
362
363 std::fs::copy(
364 "../../tests/test_files/update_files/top/top_1.µcad",
365 "../../tests/test_files/update_files/top/top.µcad",
366 )
367 .expect("test error");
368
369 context
370 .reload_files(&["../../tests/test_files/update_files/top/top.µcad"])
371 .expect("test error");
372
373 eprintln!("{context:?}");
374
375 let mut context = EvalContext::new(
376 context,
377 Stdout::new(),
378 Default::default(),
379 Default::default(),
380 );
381 context.eval().expect("test error");
382 assert!(!context.has_errors());
383}
384
385impl WriteToFile for ResolveContext {}
386
387impl PushDiag for ResolveContext {
388 fn push_diag(&mut self, diag: Diagnostic) -> DiagResult<()> {
389 self.diag.push_diag(diag)
390 }
391}
392
393impl Diag for ResolveContext {
394 fn fmt_diagnosis(&self, f: &mut dyn std::fmt::Write) -> std::fmt::Result {
395 self.diag.pretty_print(f, self)
396 }
397
398 fn warning_count(&self) -> u32 {
399 self.diag.error_count()
400 }
401
402 fn error_count(&self) -> u32 {
403 self.diag.error_count()
404 }
405
406 fn error_lines(&self) -> std::collections::HashSet<usize> {
407 self.diag.error_lines()
408 }
409
410 fn warning_lines(&self) -> std::collections::HashSet<usize> {
411 self.diag.warning_lines()
412 }
413}
414
415impl GetSourceByHash for ResolveContext {
416 fn get_by_hash(&self, hash: u64) -> ResolveResult<std::rc::Rc<SourceFile>> {
417 self.sources.get_by_hash(hash)
418 }
419}
420
421impl std::fmt::Debug for ResolveContext {
422 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
423 writeln!(f, "Sources:\n")?;
424 write!(f, "{:?}", &self.sources)?;
425 writeln!(f, "\nSymbols:\n")?;
426 write!(f, "{:?}", &self.symbol_table)?;
427 let err_count = self.diag.error_count();
428 if err_count == 0 {
429 writeln!(f, "No errors.")?;
430 } else {
431 writeln!(f, "\n{err_count} error(s):\n")?;
432 self.diag.pretty_print(f, &self.sources)?;
433 }
434 if let Some(unchecked) = &self.unchecked {
435 writeln!(f, "\nUnchecked:\n{unchecked}")?;
436 }
437 Ok(())
438 }
439}
440
441impl std::fmt::Display for ResolveContext {
442 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
443 if let Some(unchecked) = &self.unchecked {
444 writeln!(f, "Resolved & checked symbols:\n{}", self.symbol_table)?;
445 if unchecked.is_empty() {
446 writeln!(f, "All symbols are referenced.\n{}", self.symbol_table)?;
447 } else {
448 writeln!(
449 f,
450 "Unreferenced symbols:\n{}\n",
451 unchecked
452 .iter()
453 .filter(|symbol| !symbol.is_deleted())
454 .map(|symbol| symbol.full_name().to_string())
455 .collect::<Vec<_>>()
456 .join(", ")
457 )?;
458 }
459 } else {
460 writeln!(f, "Resolved symbols:\n{}", self.symbol_table)?;
461 }
462 if self.has_errors() {
463 writeln!(
464 f,
465 "{err} error(s) and {warn} warning(s) so far:\n{diag}",
466 err = self.error_count(),
467 warn = self.warning_count(),
468 diag = self.diagnosis()
469 )?;
470 } else {
471 writeln!(f, "No errors so far.")?;
472 }
473 Ok(())
474 }
475}