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 }
180
181 if resolved {
182 log::info!("Resolve OK ({passes_needed} passes).");
183 } else {
184 log::info!("Resolve failed after {passes_needed} passes.");
185 }
186 log::debug!("Resolved symbol table:\n{self:?}");
187
188 self.mode = ResolveMode::Resolved;
189
190 Ok(())
191 }
192
193 fn has_links(&self) -> bool {
194 self.symbol_table
195 .symbols()
196 .iter_mut()
197 .any(|symbol| symbol.has_links())
198 }
199
200 pub fn check(&mut self) -> ResolveResult<()> {
202 log::trace!("Checking symbol table");
203 self.mode = ResolveMode::Failed;
204
205 let exclude_ids = self.symbol_table.search_target_mode_ids()?;
206 log::trace!("Excluding target mode ids: {exclude_ids}");
207
208 if let Err(err) = self
209 .symbol_table
210 .symbols()
211 .iter_mut()
212 .try_for_each(|symbol| symbol.check(self, &exclude_ids))
213 {
214 self.error(&crate::src_ref::SrcRef::default(), err)?;
215 } else if !self.has_errors() {
216 self.mode = ResolveMode::Checked;
217 }
218
219 log::info!("Symbol table OK!");
220
221 let unchecked = self.symbol_table.unchecked();
222 log::trace!(
223 "Symbols never used in ANY code:\n{}",
224 unchecked
225 .iter()
226 .map(|symbol| format!("{symbol:?}"))
227 .collect::<Vec<_>>()
228 .join("\n")
229 );
230 self.unchecked = Some(unchecked);
231
232 Ok(())
233 }
234
235 pub fn symbolize_file(
237 &mut self,
238 visibility: Visibility,
239 parent_path: impl AsRef<std::path::Path>,
240 id: &Identifier,
241 ) -> ResolveResult<Symbol> {
242 self.sources
243 .load_file(parent_path, id)?
244 .symbolize(visibility, self)
245 }
246
247 pub fn is_checked(&self) -> bool {
250 self.mode >= ResolveMode::Checked
251 }
252}
253
254impl WriteToFile for ResolveContext {}
255
256impl PushDiag for ResolveContext {
257 fn push_diag(&mut self, diag: Diagnostic) -> DiagResult<()> {
258 self.diag.push_diag(diag)
259 }
260}
261
262impl Diag for ResolveContext {
263 fn fmt_diagnosis(&self, f: &mut dyn std::fmt::Write) -> std::fmt::Result {
264 self.diag.pretty_print(f, self)
265 }
266
267 fn warning_count(&self) -> u32 {
268 self.diag.error_count()
269 }
270
271 fn error_count(&self) -> u32 {
272 self.diag.error_count()
273 }
274
275 fn error_lines(&self) -> std::collections::HashSet<usize> {
276 self.diag.error_lines()
277 }
278
279 fn warning_lines(&self) -> std::collections::HashSet<usize> {
280 self.diag.warning_lines()
281 }
282}
283
284impl GetSourceByHash for ResolveContext {
285 fn get_by_hash(&self, hash: u64) -> ResolveResult<std::rc::Rc<SourceFile>> {
286 self.sources.get_by_hash(hash)
287 }
288}
289
290impl std::fmt::Debug for ResolveContext {
291 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
292 writeln!(f, "Sources:\n")?;
293 write!(f, "{:?}", &self.sources)?;
294 writeln!(f, "\nSymbols:\n")?;
295 write!(f, "{:?}", &self.symbol_table)?;
296 let err_count = self.diag.error_count();
297 if err_count == 0 {
298 writeln!(f, "No errors.")?;
299 } else {
300 writeln!(f, "\n{err_count} error(s):\n")?;
301 self.diag.pretty_print(f, &self.sources)?;
302 }
303 if let Some(unchecked) = &self.unchecked {
304 writeln!(f, "\nUnchecked:\n{unchecked}")?;
305 }
306 Ok(())
307 }
308}
309
310impl std::fmt::Display for ResolveContext {
311 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
312 if let Some(unchecked) = &self.unchecked {
313 writeln!(f, "Resolved & checked symbols:\n{}", self.symbol_table)?;
314 if unchecked.is_empty() {
315 writeln!(f, "All symbols are referenced.\n{}", self.symbol_table)?;
316 } else {
317 writeln!(
318 f,
319 "Unreferenced symbols:\n{}\n",
320 unchecked
321 .iter()
322 .filter(|symbol| !symbol.is_deleted())
323 .map(|symbol| symbol.full_name().to_string())
324 .collect::<Vec<_>>()
325 .join(", ")
326 )?;
327 }
328 } else {
329 writeln!(f, "Resolved symbols:\n{}", self.symbol_table)?;
330 }
331 if self.has_errors() {
332 writeln!(
333 f,
334 "{err} error(s) and {warn} warning(s) so far:\n{diag}",
335 err = self.error_count(),
336 warn = self.warning_count(),
337 diag = self.diagnosis()
338 )?;
339 } else {
340 writeln!(f, "No errors so far.")?;
341 }
342 Ok(())
343 }
344}