microcad_lang/resolve/
symbol_table.rs

1// Copyright © 2024-2025 The µcad authors <info@ucad.xyz>
2// SPDX-License-Identifier: AGPL-3.0-or-later
3
4use derive_more::{Deref, DerefMut};
5use indexmap::IndexSet;
6
7use crate::{resolve::*, syntax::*};
8
9/// *Symbol table* holding global symbols.
10#[derive(Default, Deref, DerefMut)]
11pub struct SymbolTable(SymbolMap);
12
13impl SymbolTable {
14    /// Add a new symbol to the table
15    pub fn add_symbol(&mut self, symbol: Symbol) -> ResolveResult<()> {
16        self.insert_symbol(symbol.id(), symbol.clone())
17    }
18
19    /// Add a new symbol to the table
20    pub fn insert_symbol(&mut self, id: Identifier, symbol: Symbol) -> ResolveResult<()> {
21        log::trace!("insert symbol: {id}");
22        if let Some(symbol) = self.insert(id, symbol.clone()) {
23            Err(ResolveError::SymbolAlreadyDefined(symbol.full_name()))
24        } else {
25            Ok(())
26        }
27    }
28
29    pub(super) fn symbols(&self) -> Symbols {
30        self.values().cloned().collect()
31    }
32
33    /// Return a list of symbols which could not or have not been checked.
34    pub fn unchecked(&self) -> Symbols {
35        self.recursive_collect(|symbol| symbol.is_checked())
36    }
37
38    /// Return a list of unused private symbols
39    ///
40    /// Use this after eval for any useful result.
41    pub fn unused_private(&self) -> Symbols {
42        let used_in_module = &mut IndexSet::new();
43        let mut symbols = self.recursive_collect(|symbol| {
44            if let Some(in_module) = symbol.in_module()
45                && symbol.is_used()
46            {
47                used_in_module.insert(in_module);
48            }
49            symbol.is_unused_private()
50        });
51
52        symbols.retain(|symbol| {
53            if let Some(in_module) = symbol.in_module() {
54                !used_in_module.contains(&in_module)
55            } else {
56                true
57            }
58        });
59        symbols.sort_by_key(|s| s.full_name());
60        symbols
61    }
62
63    /// Search all ids which require target mode (e.g. `assert_valid`)
64    pub(super) fn search_target_mode_ids(&self) -> IdentifierSet {
65        self.recursive_collect(|symbol| symbol.is_target_mode())
66            .iter()
67            .map(|symbol| symbol.id())
68            .collect()
69    }
70
71    pub(super) fn recursive_collect<F>(&self, mut f: F) -> Symbols
72    where
73        F: FnMut(&Symbol) -> bool,
74    {
75        let mut result = vec![];
76        self.values().for_each(|symbol| {
77            symbol.recursive_collect(&mut f, &mut result);
78        });
79        result.into()
80    }
81
82    #[allow(dead_code)]
83    pub(super) fn recursive_for_each<F>(&self, f: F)
84    where
85        F: Fn(&Identifier, &Symbol),
86    {
87        self.iter().for_each(|(id, symbol)| {
88            symbol.recursive_for_each(id, &f);
89        });
90    }
91
92    pub(super) fn recursive_for_each_mut<F>(&mut self, f: F)
93    where
94        F: Fn(&Identifier, &mut Symbol),
95    {
96        self.iter_mut().for_each(|(id, symbol)| {
97            symbol.recursive_for_each_mut(id, &f);
98        });
99    }
100
101    /// Search a *symbol* by it's *qualified name* **and** within a *symbol* given by name.
102    ///
103    /// If both are found
104    /// # Arguments
105    /// - `name`: *qualified name* to search for.
106    /// - `within`: Searches in the *symbol* with this name too.
107    /// - `target`: What to search for
108    pub(crate) fn lookup_within_name(
109        &self,
110        name: &QualifiedName,
111        within: &QualifiedName,
112        target: LookupTarget,
113    ) -> ResolveResult<Symbol> {
114        self.lookup_within(name, &self.search(within, false)?, target)
115    }
116}
117
118impl WriteToFile for SymbolTable {}
119
120impl Lookup for SymbolTable {
121    /// Lookup a symbol from global symbols.
122    fn lookup(&self, name: &QualifiedName, target: LookupTarget) -> ResolveResult<Symbol> {
123        log::trace!(
124            "{lookup} for global symbol '{name:?}'",
125            lookup = crate::mark!(LOOKUP)
126        );
127        self.deny_super(name)?;
128
129        let symbol = match self.search(name, true) {
130            Ok(symbol) => {
131                if target.matches(&symbol) {
132                    symbol
133                } else {
134                    log::trace!(
135                        "{not_found} global symbol: {name:?}",
136                        not_found = crate::mark!(NOT_FOUND_INTERIM),
137                    );
138                    return Err(ResolveError::WrongTarget);
139                }
140            }
141            Err(err) => {
142                log::trace!(
143                    "{not_found} global symbol: {name:?}",
144                    not_found = crate::mark!(NOT_FOUND_INTERIM),
145                );
146                return Err(err)?;
147            }
148        };
149        symbol.set_check();
150        log::trace!(
151            "{found} global symbol: {symbol:?}",
152            found = crate::mark!(FOUND_INTERIM),
153        );
154        Ok(symbol)
155    }
156
157    fn ambiguity_error(ambiguous: QualifiedName, others: QualifiedNames) -> ResolveError {
158        ResolveError::AmbiguousSymbol(ambiguous, others)
159    }
160}
161
162impl std::fmt::Display for SymbolTable {
163    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
164        writeln!(
165            f,
166            "{}",
167            self.iter()
168                .map(|(_, symbol)| symbol)
169                .filter(|symbol| !symbol.is_deleted())
170                .map(|symbol| symbol.full_name().to_string())
171                .collect::<Vec<_>>()
172                .join(", ")
173        )
174    }
175}
176
177impl std::fmt::Debug for SymbolTable {
178    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
179        writeln!(f, "{:?}", self.0)
180    }
181}