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 "../examples/update_files/sub/sub_0.µcad",
314 "../examples/update_files/sub/sub.µcad",
315 )
316 .expect("test error");
317
318 let root = SourceFile::load("../examples/update_files/sub/top.µcad").expect("test error");
319 let mut context = ResolveContext::test_create(root, ResolveMode::Checked).expect("test error");
320
321 eprintln!("{context:?}");
322
323 std::fs::copy(
324 "../examples/update_files/sub/sub_1.µcad",
325 "../examples/update_files/sub/sub.µcad",
326 )
327 .expect("test error");
328
329 context
330 .reload_files(&["../examples/update_files/sub/sub.µcad"])
331 .expect("test error");
332
333 eprintln!("{context:?}");
334
335 let mut context = EvalContext::new(
336 context,
337 Stdout::new(),
338 Default::default(),
339 Default::default(),
340 );
341 context.eval().expect("test error");
342 assert!(!context.has_errors());
343}
344
345#[test]
346fn test_update_top_mod() {
347 use crate::eval::*;
348
349 std::fs::copy(
350 "../examples/update_files/top/top_0.µcad",
351 "../examples/update_files/top/top.µcad",
352 )
353 .expect("test error");
354
355 let root = SourceFile::load("../examples/update_files/top/top.µcad").expect("test error");
356 let mut context = ResolveContext::test_create(root, ResolveMode::Checked).expect("test error");
357
358 eprintln!("{context:?}");
359
360 std::fs::copy(
361 "../examples/update_files/top/top_1.µcad",
362 "../examples/update_files/top/top.µcad",
363 )
364 .expect("test error");
365
366 context
367 .reload_files(&["../examples/update_files/top/top.µcad"])
368 .expect("test error");
369
370 eprintln!("{context:?}");
371
372 let mut context = EvalContext::new(
373 context,
374 Stdout::new(),
375 Default::default(),
376 Default::default(),
377 );
378 context.eval().expect("test error");
379 assert!(!context.has_errors());
380}
381
382impl WriteToFile for ResolveContext {}
383
384impl PushDiag for ResolveContext {
385 fn push_diag(&mut self, diag: Diagnostic) -> DiagResult<()> {
386 self.diag.push_diag(diag)
387 }
388}
389
390impl Diag for ResolveContext {
391 fn fmt_diagnosis(&self, f: &mut dyn std::fmt::Write) -> std::fmt::Result {
392 self.diag.pretty_print(f, self)
393 }
394
395 fn warning_count(&self) -> u32 {
396 self.diag.error_count()
397 }
398
399 fn error_count(&self) -> u32 {
400 self.diag.error_count()
401 }
402
403 fn error_lines(&self) -> std::collections::HashSet<usize> {
404 self.diag.error_lines()
405 }
406
407 fn warning_lines(&self) -> std::collections::HashSet<usize> {
408 self.diag.warning_lines()
409 }
410}
411
412impl GetSourceByHash for ResolveContext {
413 fn get_by_hash(&self, hash: u64) -> ResolveResult<std::rc::Rc<SourceFile>> {
414 self.sources.get_by_hash(hash)
415 }
416}
417
418impl std::fmt::Debug for ResolveContext {
419 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
420 writeln!(f, "Sources:\n")?;
421 write!(f, "{:?}", &self.sources)?;
422 writeln!(f, "\nSymbols:\n")?;
423 write!(f, "{:?}", &self.symbol_table)?;
424 let err_count = self.diag.error_count();
425 if err_count == 0 {
426 writeln!(f, "No errors.")?;
427 } else {
428 writeln!(f, "\n{err_count} error(s):\n")?;
429 self.diag.pretty_print(f, &self.sources)?;
430 }
431 if let Some(unchecked) = &self.unchecked {
432 writeln!(f, "\nUnchecked:\n{unchecked}")?;
433 }
434 Ok(())
435 }
436}
437
438impl std::fmt::Display for ResolveContext {
439 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
440 if let Some(unchecked) = &self.unchecked {
441 writeln!(f, "Resolved & checked symbols:\n{}", self.symbol_table)?;
442 if unchecked.is_empty() {
443 writeln!(f, "All symbols are referenced.\n{}", self.symbol_table)?;
444 } else {
445 writeln!(
446 f,
447 "Unreferenced symbols:\n{}\n",
448 unchecked
449 .iter()
450 .filter(|symbol| !symbol.is_deleted())
451 .map(|symbol| symbol.full_name().to_string())
452 .collect::<Vec<_>>()
453 .join(", ")
454 )?;
455 }
456 } else {
457 writeln!(f, "Resolved symbols:\n{}", self.symbol_table)?;
458 }
459 if self.has_errors() {
460 writeln!(
461 f,
462 "{err} error(s) and {warn} warning(s) so far:\n{diag}",
463 err = self.error_count(),
464 warn = self.warning_count(),
465 diag = self.diagnosis()
466 )?;
467 } else {
468 writeln!(f, "No errors so far.")?;
469 }
470 Ok(())
471 }
472}