microcad_lang/eval/symbols/
symbol_table.rs

1// Copyright © 2024-2025 The µcad authors <info@ucad.xyz>
2// SPDX-License-Identifier: AGPL-3.0-or-later
3
4use crate::{eval::*, model::*, rc::*, resolve::*, syntax::*};
5
6/// *Symbol table* holding global and local symbols.
7///
8/// The symbol table consists of the following members:
9///
10/// - One *root [`Symbol`]* resolved from the *initial source file*.
11/// - A [`SourceCache`] of all *loaded source files* (accessible by *qualified name*, *file path* and *source hash*).
12/// - A [`Stack`] of [`StackFrame`]s.
13/// - A [`SymbolMap`] of all *global symbols*.
14///
15/// All these internal structures can be accessed by several implemented traits.
16#[derive(Default)]
17pub struct SymbolTable {
18    sources: Sources,
19    /// Symbol of the initial source file.
20    pub root: Symbol,
21    /// Stack of currently opened scopes with symbols while evaluation.
22    pub stack: Stack,
23    /// Global symbols (including root).
24    pub symbols: SymbolMap,
25    /// Source file diagnostics.
26    pub diag_handler: DiagHandler,
27}
28
29impl SymbolTable {
30    /// Root symbol (symbol node of initially read source file)
31    /// List of all global symbols.
32    /// Stack of currently opened scopes with local symbols while evaluation.
33    /// Source file cache containing all source files loaded in the context and their syntax trees.
34    pub fn new(root: Identifier, symbols: SymbolMap, sources: Sources) -> ResolveResult<Self> {
35        // prepare symbol map
36
37        let symbol_table = Self {
38            sources,
39            root: symbols.search(&QualifiedName::from_id(root))?,
40            stack: Default::default(),
41            symbols,
42            diag_handler: Default::default(),
43        };
44        log::trace!("Initial symbol table:\n{symbol_table}");
45        Ok(symbol_table)
46    }
47
48    /// Fetch local variable from local stack (for testing only).
49    #[cfg(test)]
50    pub fn fetch_local(&self, id: &Identifier) -> EvalResult<Symbol> {
51        self.stack.fetch(id)
52    }
53
54    /// Lookup a symbol from global symbols.
55    pub fn lookup_global(&mut self, name: &QualifiedName) -> ResolveResult<Symbol> {
56        log::trace!("Looking for global symbol '{name:?}'");
57        let symbol = match self.symbols.search(name) {
58            Ok(symbol) => symbol.clone(),
59            Err(err) => return Err(err)?,
60        };
61        log::trace!(
62            "{found} global symbol: {symbol}",
63            found = crate::mark!(FOUND),
64        );
65        Ok(symbol)
66    }
67
68    /// Lookup a symbol from local stack.
69    fn lookup_local(&mut self, name: &QualifiedName) -> EvalResult<Symbol> {
70        log::trace!("Looking for local symbol '{name:?}'");
71        let symbol = if let Some(id) = name.single_identifier() {
72            self.stack.fetch(id)
73        } else {
74            let (id, mut tail) = name.split_first();
75            let local = self.stack.fetch(&id)?;
76            let mut alias = local.full_name();
77            alias.append(&mut tail);
78            log::trace!("Following alias {alias}");
79            self.lookup(&alias)
80        };
81
82        match symbol {
83            Ok(symbol) => {
84                log::trace!(
85                    "{found} local symbol: {symbol}",
86                    found = crate::mark!(FOUND),
87                );
88                Ok(symbol)
89            }
90            Err(err) => Err(err),
91        }
92    }
93
94    fn lookup_within(&mut self, what: &QualifiedName, within: QualifiedName) -> EvalResult<Symbol> {
95        log::trace!("Looking for symbol '{what:?}' within '{within:?}':",);
96
97        // process internal supers
98        let (what, within) = what.dissolve_super(within);
99
100        let parents = self.symbols.path_to(&within)?;
101        for (n, parent) in parents.iter().rev().enumerate() {
102            log::trace!("  Looking in: {:?} for {:?}", parent.full_name(), what);
103            if let Some(symbol) = parent.search(&what) {
104                let alias = self.follow_alias(&symbol)?;
105                if n > 0 {
106                    if symbol.is_private() {
107                        return Err(EvalError::SymbolIsPrivate {
108                            what: what.clone(),
109                            within,
110                        });
111                    }
112                    if alias != symbol && alias.is_private() {
113                        return Err(EvalError::SymbolBehindAliasIsPrivate {
114                            what: what.clone(),
115                            alias: alias.full_name(),
116                            within,
117                        });
118                    }
119                }
120                return Ok(alias);
121            }
122        }
123        Err(EvalError::SymbolNotFound(what.clone()))
124    }
125
126    fn lookup_workbench(&mut self, name: &QualifiedName) -> EvalResult<Symbol> {
127        if let Some(workbench) = &self.stack.current_workbench_name() {
128            log::trace!("Looking for symbol '{name:?}' in current workbench '{workbench:?}'");
129            let name = &name.with_prefix(workbench);
130            match self.lookup_global(name) {
131                Ok(symbol) => {
132                    if symbol.full_name() == *name {
133                        log::trace!(
134                            "{found} symbol in current module: {symbol}",
135                            found = crate::mark!(FOUND),
136                        );
137                        return self.follow_alias(&symbol);
138                    }
139                }
140                Err(err) => return Err(err)?,
141            };
142        }
143        Err(EvalError::SymbolNotFound(name.clone()))
144    }
145
146    fn de_alias(&mut self, name: &QualifiedName) -> QualifiedName {
147        for p in (1..name.len()).rev() {
148            if let Ok(symbol) = self.lookup_global(&QualifiedName::no_ref(name[0..p].to_vec())) {
149                if let SymbolDefinition::Alias(.., alias) = &symbol.borrow().def {
150                    let suffix: QualifiedName = name[p..].iter().cloned().collect();
151                    let new_name = suffix.with_prefix(alias);
152                    log::trace!("De-aliased name: {name:?} into {new_name:?}");
153                    return new_name;
154                }
155            }
156        }
157        name.clone()
158    }
159
160    fn follow_alias(&mut self, symbol: &Symbol) -> EvalResult<Symbol> {
161        // execute alias from any use statement
162        let def = &symbol.borrow().def;
163        if let SymbolDefinition::Alias(.., name) = def {
164            log::trace!("{found} alias => {name:?}", found = crate::mark!(FOUND));
165            Ok(self.lookup(name)?)
166        } else {
167            Ok(symbol.clone())
168        }
169    }
170
171    /// Return search paths of this symbol table.
172    pub fn search_paths(&self) -> &Vec<std::path::PathBuf> {
173        self.sources.search_paths()
174    }
175
176    /// Check if current stack frame is code
177    pub fn is_code(&self) -> bool {
178        !matches!(self.stack.current_frame(), Some(StackFrame::Module(..)))
179    }
180
181    /// Check if current stack frame is a module
182    pub fn is_module(&self) -> bool {
183        matches!(
184            self.stack.current_frame(),
185            Some(StackFrame::Module(..) | StackFrame::Source(..))
186        )
187    }
188}
189
190impl Lookup for SymbolTable {
191    fn lookup(&mut self, name: &QualifiedName) -> EvalResult<Symbol> {
192        log::debug!("Lookup symbol '{name:?}' (at line {:?}):", name.src_ref());
193
194        let name = &self.de_alias(name);
195
196        log::trace!("- lookups -------------------------------------------------------");
197        // collect all symbols that can be found and remember origin
198        let result = [
199            ("local", self.lookup_local(name)),
200            (
201                "module",
202                self.lookup_within(name, self.stack.current_module_name()),
203            ),
204            ("workbench", self.lookup_workbench(name)),
205            ("global", self.lookup_global(name).map_err(|e| e.into())),
206        ]
207        .into_iter();
208
209        log::trace!("- result --------------------------------------------------------");
210        let mut errors = Vec::new();
211
212        // collect ok-results and ambiguity errors
213        let (found, mut ambiguous) = result.fold(
214            (Vec::new(), Vec::new()),
215            |(mut oks, mut ambiguity), (origin, r)| {
216                match r {
217                    Ok(symbol) => oks.push((origin, symbol)),
218                    Err(EvalError::AmbiguousSymbol { ambiguous, others }) => {
219                        ambiguity.push((origin, EvalError::AmbiguousSymbol { ambiguous, others }))
220                    }
221                    Err(
222                        EvalError::SymbolNotFound(_)
223                        | EvalError::ResolveError(ResolveError::SymbolNotFound(_))
224                        | EvalError::LocalNotFound(_)
225                        | EvalError::ResolveError(ResolveError::ExternalPathNotFound(_))
226                        | EvalError::ResolveError(ResolveError::NulHash),
227                    ) => (),
228                    Err(err) => errors.push((origin, err)),
229                }
230                (oks, ambiguity)
231            },
232        );
233
234        // log any unexpected errors and return early
235        if !errors.is_empty() {
236            log::debug!("Unexpected errors while lookup symbol '{name:?}':");
237            errors
238                .iter()
239                .for_each(|(origin, err)| log::error!("Lookup ({origin}) error: {err}"));
240
241            return Err(errors.remove(0).1);
242        }
243
244        // early emit any ambiguity error
245        if !ambiguous.is_empty() {
246            log::debug!(
247                "{ambiguous} Symbol '{name:?}':\n{}",
248                ambiguous
249                    .iter()
250                    .map(|(origin, err)| format!("{origin}: {err}"))
251                    .collect::<Vec<_>>()
252                    .join("\n"),
253                ambiguous = crate::mark!(AMBIGUOUS)
254            );
255            return Err(ambiguous.remove(0).1);
256        }
257
258        // follow aliases
259        let found: Vec<_> = found
260            .into_iter()
261            .filter_map(|(origin, symbol)| {
262                if let Ok(symbol) = self.follow_alias(&symbol) {
263                    Some((origin, symbol))
264                } else {
265                    None
266                }
267            })
268            .collect();
269
270        // check for ambiguity in what's left
271        match found.first() {
272            Some((origin, first)) => {
273                // check if all findings point to the same symbol
274                if !found.iter().all(|(_, x)| Rc::ptr_eq(x, first)) {
275                    log::debug!(
276                        "{ambiguous} symbol '{name:?}' in {origin}:\n{self}",
277                        ambiguous = crate::mark!(AMBIGUOUS),
278                        origin = found
279                            .iter()
280                            .map(|(id, _)| *id)
281                            .collect::<Vec<_>>()
282                            .join(" and ")
283                    );
284                    Err(EvalError::AmbiguousSymbol {
285                        ambiguous: name.clone(),
286                        others: found.iter().map(|(_, x)| x.clone()).collect(),
287                    })
288                } else {
289                    log::debug!(
290                        "{found} symbol '{name:?}' in {origin}",
291                        found = crate::mark!(FOUND_INTERIM)
292                    );
293                    Ok(first.clone())
294                }
295            }
296            None => {
297                log::debug!(
298                    "{not_found} Symbol '{name:?}'",
299                    not_found = crate::mark!(NOT_FOUND_INTERIM)
300                );
301
302                Err(EvalError::SymbolNotFound(name.clone()))
303            }
304        }
305    }
306}
307
308impl Locals for SymbolTable {
309    fn set_local_value(&mut self, id: Identifier, value: Value) -> EvalResult<()> {
310        self.stack.set_local_value(id, value)
311    }
312
313    fn get_local_value(&self, id: &Identifier) -> EvalResult<Value> {
314        self.stack.get_local_value(id)
315    }
316
317    fn open(&mut self, frame: StackFrame) {
318        self.stack.open(frame);
319    }
320
321    fn close(&mut self) {
322        self.stack.close();
323    }
324
325    fn fetch(&self, id: &Identifier) -> EvalResult<Symbol> {
326        self.stack.fetch(id)
327    }
328
329    fn get_model(&self) -> EvalResult<Model> {
330        self.stack.get_model()
331    }
332
333    fn current_name(&self) -> QualifiedName {
334        self.stack.current_name()
335    }
336}
337
338impl UseSymbol for SymbolTable {
339    fn use_symbol(
340        &mut self,
341        visibility: Visibility,
342        name: &QualifiedName,
343        id: Option<Identifier>,
344        within: &QualifiedName,
345    ) -> EvalResult<Symbol> {
346        log::debug!("Using symbol {name:?}");
347
348        let symbol = self.lookup(name)?;
349        if self.is_module() {
350            let id = id.clone().unwrap_or(symbol.id());
351            let symbol = symbol.clone_with_visibility(visibility);
352            if within.is_empty() {
353                self.symbols.insert(id, symbol);
354            } else {
355                self.symbols
356                    .search(within)?
357                    .borrow_mut()
358                    .children
359                    .insert(id, symbol);
360            }
361            log::trace!("Symbol Table:\n{}", self.symbols);
362        }
363
364        if self.is_code() {
365            self.stack.put_local(id, symbol.clone())?;
366            log::trace!("Local Stack:\n{}", self.stack);
367        }
368
369        Ok(symbol)
370    }
371
372    fn use_symbols_of(
373        &mut self,
374        visibility: Visibility,
375        name: &QualifiedName,
376        within: &QualifiedName,
377    ) -> EvalResult<Symbol> {
378        log::debug!("Using all symbols in {name:?}");
379
380        let symbol = self.lookup(name)?;
381        if symbol.is_empty() {
382            Err(EvalError::NoSymbolsToUse(symbol.full_name()))
383        } else {
384            if self.is_module() {
385                for (id, symbol) in symbol.borrow().children.iter() {
386                    let symbol = symbol.clone_with_visibility(visibility);
387                    if within.is_empty() {
388                        self.symbols.insert(id.clone(), symbol);
389                    } else {
390                        self.symbols
391                            .search(within)?
392                            .borrow_mut()
393                            .children
394                            .insert(id.clone(), symbol);
395                    }
396                }
397                log::trace!("Symbol Table:\n{}", self.symbols);
398            }
399
400            if self.is_code() {
401                for (id, symbol) in symbol.borrow().children.iter() {
402                    self.stack.put_local(Some(id.clone()), symbol.clone())?;
403                }
404                log::trace!("Local Stack:\n{}", self.stack);
405            }
406            Ok(symbol)
407        }
408    }
409}
410
411impl std::fmt::Display for SymbolTable {
412    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
413        write!(f, "\nLoaded files:\n{}", self.sources)?;
414        writeln!(f, "\nCurrent: {}", self.stack.current_name())?;
415        writeln!(f, "\nModule: {}", self.stack.current_module_name())?;
416        write!(f, "\nLocals Stack:\n{}", self.stack)?;
417        writeln!(f, "\nCall Stack:")?;
418        self.stack.pretty_print_call_trace(f, &self.sources)?;
419        writeln!(f, "\nSymbols:\n{}", self.symbols)
420    }
421}
422
423impl GetSourceByHash for SymbolTable {
424    fn get_by_hash(&self, hash: u64) -> ResolveResult<std::rc::Rc<SourceFile>> {
425        self.sources.get_by_hash(hash)
426    }
427}