microcad_lang/resolve/
mod.rs

1// Copyright © 2025 The µcad authors <info@ucad.xyz>
2// SPDX-License-Identifier: AGPL-3.0-or-later
3
4//! Single symbol resolving
5//!
6//! After parsing a source file (see [`mod@crate::parse`]) it must be resolved to get a symbol out of it:
7//!
8//! ```no_run
9//! use microcad_lang::{syntax::*, parse::*, resolve::*};
10//!
11//! let source_file = SourceFile::load("my.µcad").expect("parsing success");
12//!
13//! let source_symbol = source_file.resolve();
14//! ```
15//!
16//! To "run" the source file (and get the expected output) it must now be evaluated (see [`crate::eval`])  .
17
18mod externals;
19mod resolve_error;
20mod sources;
21mod symbol;
22mod symbol_definition;
23mod symbol_map;
24
25pub use externals::*;
26pub use resolve_error::*;
27pub use sources::*;
28pub use symbol::*;
29pub use symbol_definition::*;
30pub use symbol_map::*;
31
32use crate::{syntax::*, value::Value};
33
34/// Trait for items which can be fully qualified.
35pub trait FullyQualify {
36    /// Get a fully (up to root of symbol map) qualified name.
37    fn full_name(&self) -> QualifiedName;
38}
39
40trait Resolve<T = Option<Symbol>> {
41    /// Resolve into Symbol
42    fn resolve(&self, parent: &Symbol) -> ResolveResult<T>;
43
44    //    fn resolve2(&self, parent: &Symbol
45}
46
47#[test]
48fn resolve_test() {
49    let root = SourceFile::load("../examples/lego_brick.µcad").expect("loading failed");
50    let sources = Sources::load(root.clone(), &["../lib".into()]).expect("loading failed");
51    let root_id = sources.root().id();
52    let symbols = sources.resolve().expect("resolve failed");
53    log::trace!("Symbols:\n{symbols}");
54
55    crate::eval::SymbolTable::new(root_id, symbols, sources).expect("new symbol table");
56}
57
58impl SourceFile {
59    /// Resolve into Symbol
60    pub fn resolve(&self) -> ResolveResult<Symbol> {
61        let name = self.filename_as_str();
62        log::debug!("Creating symbol from source file {name}");
63        let symbol = Symbol::new(SymbolDefinition::SourceFile(self.clone().into()), None);
64        symbol.borrow_mut().children = self.statements.resolve(&symbol)?;
65        log::trace!("Created symbol from source file {name}:\n{symbol}");
66        Ok(symbol)
67    }
68}
69
70impl Resolve<Symbol> for ModuleDefinition {
71    /// Resolve into Symbol
72    fn resolve(&self, parent: &Symbol) -> ResolveResult<Symbol> {
73        let symbol = Symbol::new(
74            SymbolDefinition::Module(self.clone().into()),
75            Some(parent.clone()),
76        );
77        symbol.borrow_mut().children = self.body.resolve(&symbol)?;
78        Ok(symbol)
79    }
80}
81
82impl Resolve<SymbolMap> for StatementList {
83    fn resolve(&self, parent: &Symbol) -> ResolveResult<SymbolMap> {
84        let mut symbols = SymbolMap::default();
85
86        // Iterate over all statement fetch definitions
87        for statement in &self.0 {
88            if let Some((id, symbol)) = statement.resolve(parent)? {
89                symbols.insert(id, symbol);
90            }
91        }
92
93        Ok(symbols)
94    }
95}
96
97impl Resolve<Option<(Identifier, Symbol)>> for Statement {
98    fn resolve(&self, parent: &Symbol) -> ResolveResult<Option<(Identifier, Symbol)>> {
99        match self {
100            Statement::Workbench(wd) => Ok(Some((wd.id.clone(), wd.resolve(parent)?))),
101            Statement::Module(md) => Ok(Some((md.id.clone(), md.resolve(parent)?))),
102            Statement::Function(fd) => Ok(Some((fd.id.clone(), fd.resolve(parent)?))),
103            Statement::Use(us) => us.resolve(parent),
104            Statement::Assignment(a) => Ok(a
105                .resolve(parent)?
106                .map(|symbol| (a.assignment.id.clone(), symbol))),
107            // Not producing any symbols
108            Statement::Init(_)
109            | Statement::Return(_)
110            | Statement::If(_)
111            | Statement::InnerAttribute(_)
112            | Statement::Expression(_) => Ok(None),
113        }
114    }
115}
116
117impl Resolve<Symbol> for WorkbenchDefinition {
118    fn resolve(&self, parent: &Symbol) -> ResolveResult<Symbol> {
119        let symbol = Symbol::new(
120            SymbolDefinition::Workbench(self.clone().into()),
121            Some(parent.clone()),
122        );
123        symbol.borrow_mut().children = self.body.resolve(&symbol)?;
124        Ok(symbol)
125    }
126}
127
128impl Resolve<Symbol> for FunctionDefinition {
129    fn resolve(&self, parent: &Symbol) -> ResolveResult<Symbol> {
130        let symbol = Symbol::new(
131            SymbolDefinition::Function((*self).clone().into()),
132            Some(parent.clone()),
133        );
134        symbol.borrow_mut().children = self.body.resolve(&symbol)?;
135
136        Ok(symbol)
137    }
138}
139
140impl Resolve for InitDefinition {
141    fn resolve(&self, _parent: &Symbol) -> ResolveResult<Option<Symbol>> {
142        Ok(None)
143    }
144}
145
146impl Resolve<Option<(Identifier, Symbol)>> for UseStatement {
147    fn resolve(&self, parent: &Symbol) -> ResolveResult<Option<(Identifier, Symbol)>> {
148        match self.visibility {
149            Visibility::Private => Ok(None),
150            // Public symbols are put into resolving symbol map
151            Visibility::Public => self.decl.resolve(parent),
152        }
153    }
154}
155
156impl Resolve for ReturnStatement {
157    fn resolve(&self, _parent: &Symbol) -> ResolveResult<Option<Symbol>> {
158        Ok(None)
159    }
160}
161
162impl Resolve for IfStatement {
163    fn resolve(&self, _parent: &Symbol) -> ResolveResult<Option<Symbol>> {
164        Ok(None)
165    }
166}
167
168impl Resolve for Attribute {
169    fn resolve(&self, _parent: &Symbol) -> ResolveResult<Option<Symbol>> {
170        Ok(None)
171    }
172}
173
174impl Resolve for AssignmentStatement {
175    fn resolve(&self, parent: &Symbol) -> ResolveResult<Option<Symbol>> {
176        match (self.assignment.visibility, self.assignment.qualifier) {
177            // properties do not have a visibility
178            (_, Qualifier::Prop) => {
179                if !parent.can_prop() {
180                    Err(ResolveError::DeclNotAllowed(
181                        self.assignment.id.clone(),
182                        parent.full_name(),
183                    ))
184                } else {
185                    Ok(None)
186                }
187            }
188            // constants will be symbols (`pub` shall equal `pub const`)
189            (_, Qualifier::Const) | (Visibility::Public, Qualifier::Value) => {
190                if !parent.can_const() {
191                    Err(ResolveError::DeclNotAllowed(
192                        self.assignment.id.clone(),
193                        parent.full_name(),
194                    ))
195                } else {
196                    log::trace!("Declare private value {}", self.assignment.id);
197                    Ok(Some(Symbol::new(
198                        SymbolDefinition::Constant(
199                            self.assignment.visibility,
200                            self.assignment.id.clone(),
201                            Value::None,
202                        ),
203                        Some(parent.clone()),
204                    )))
205                }
206            }
207            // value go on stack
208            (Visibility::Private, Qualifier::Value) => {
209                if self.assignment.visibility == Visibility::Private && !parent.can_value() {
210                    Err(ResolveError::DeclNotAllowed(
211                        self.assignment.id.clone(),
212                        parent.full_name(),
213                    ))
214                } else {
215                    Ok(None)
216                }
217            }
218        }
219    }
220}
221
222impl Resolve for ExpressionStatement {
223    fn resolve(&self, _parent: &Symbol) -> ResolveResult<Option<Symbol>> {
224        Ok(None)
225    }
226}
227
228impl Resolve<SymbolMap> for Body {
229    fn resolve(&self, parent: &Symbol) -> ResolveResult<SymbolMap> {
230        self.statements.resolve(parent)
231    }
232}
233
234impl Resolve<Option<(Identifier, Symbol)>> for UseDeclaration {
235    fn resolve(&self, parent: &Symbol) -> ResolveResult<Option<(Identifier, Symbol)>> {
236        match self {
237            UseDeclaration::Use(visibility, name) => {
238                let identifier = name.last().expect("Identifier");
239                Ok(Some((
240                    identifier.clone(),
241                    Symbol::new(
242                        SymbolDefinition::Alias(*visibility, identifier.clone(), name.clone()),
243                        Some(parent.clone()),
244                    ),
245                )))
246            }
247            UseDeclaration::UseAll(visibility, _) => Ok(None),
248            UseDeclaration::UseAlias(visibility, name, alias) => Ok(Some((
249                alias.clone(),
250                Symbol::new(
251                    SymbolDefinition::Alias(*visibility, alias.clone(), name.clone()),
252                    Some(parent.clone()),
253                ),
254            ))),
255        }
256    }
257}
258
259#[test]
260fn resolve_source_file() {
261    let source_file =
262        SourceFile::load_from_str(r#"part A() { part B() {} } "#).expect("Valid source");
263
264    let symbol = source_file.resolve().expect("expecting resolve success");
265
266    // file <no file>
267    //  part a
268    //   part b
269    assert!(symbol.get(&"A".into()).is_some());
270    assert!(symbol.get(&"c".into()).is_none());
271
272    assert!(symbol.search(&"A".into()).is_some());
273    assert!(symbol.search(&"A::B".into()).is_some());
274    assert!(symbol.search(&"A::B::C".into()).is_none());
275
276    // use std::print; // Add symbol "print" to current symbol symbol
277    // part M() {
278    //      print("test"); // Use symbol symbol from parent
279    // }
280
281    log::trace!("Symbol symbol:\n{symbol}");
282
283    let b = symbol.search(&"A::B".into()).expect("cant find symbol");
284    assert!(b.search(&"A".into()).is_none());
285
286    //assert!(symbol_node.search_top_down(&["<no file>".into()]).is_some());
287
288    log::trace!("{symbol}");
289}