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