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