microcad_lang/resolve/
sources.rs

1// Copyright © 2025 The µcad authors <info@ucad.xyz>
2// SPDX-License-Identifier: AGPL-3.0-or-later
3
4//! Source file cache
5
6use crate::{parse::*, rc::*, resolve::*, src_ref::*, syntax::*};
7use std::collections::HashMap;
8
9/// Register of loaded source files and their syntax trees.
10///
11/// Source file definitions ([`SourceFile`]) are stored in a vector (`Vec<Rc<SourceFile>>`)
12/// and mapped by *hash*, *path* and *name* via index to this vector.
13///
14/// The *root model* (given at creation) will be stored but will only be accessible by hash and path
15/// but not by it's qualified name.
16#[derive(Default)]
17pub struct Sources {
18    /// External files read from search path.
19    externals: Externals,
20
21    by_hash: HashMap<u64, usize>,
22    by_path: HashMap<std::path::PathBuf, usize>,
23    by_name: HashMap<QualifiedName, usize>,
24
25    /// External source files.
26    source_files: Vec<Rc<SourceFile>>,
27
28    /// Search paths.
29    search_paths: Vec<std::path::PathBuf>,
30}
31
32impl Sources {
33    /// Create source cache
34    ///
35    /// Inserts the `root` file and loads all files from `search_paths`.
36    pub fn load(root: Rc<SourceFile>, search_paths: &[std::path::PathBuf]) -> ParseResult<Self> {
37        let mut source_files = Vec::new();
38        let mut by_name = HashMap::new();
39        let mut by_hash = HashMap::new();
40        let mut by_path = HashMap::new();
41
42        by_hash.insert(root.hash, 0);
43        by_path.insert(root.filename(), 0);
44        by_name.insert(root.name.clone(), 0);
45        source_files.push(root);
46
47        // search for external source files
48        let externals = Externals::new(search_paths);
49
50        log::trace!("Externals:\n{externals}");
51
52        // load all external source files into cache
53        externals
54            .iter()
55            .try_for_each(|(name, path)| -> Result<(), ParseError> {
56                let source_file = SourceFile::load_with_name(path.clone(), name.clone())?;
57                let index = source_files.len();
58                by_hash.insert(source_file.hash, index);
59                by_path.insert(source_file.filename(), index);
60                by_name.insert(name.clone(), index);
61                source_files.push(source_file);
62                Ok(())
63            })?;
64
65        Ok(Self {
66            externals,
67            source_files,
68            by_hash,
69            by_path,
70            by_name,
71            search_paths: search_paths.to_vec(),
72        })
73    }
74
75    /// Return root file.
76    pub fn root(&self) -> Rc<SourceFile> {
77        self.source_files
78            .first()
79            .expect("empty source cache has not root")
80            .clone()
81    }
82
83    /// Creates symbol map from externals.
84    fn create_modules(externals: &Externals) -> SymbolMap {
85        let mut map = SymbolMap::new();
86        externals.iter().for_each(|(basename, _)| {
87            let (id, name) = basename.split_first();
88            let module = match map.get(&id) {
89                Some(symbol) => symbol.clone(),
90                _ => Symbol::new(
91                    SymbolDefinition::Module(ModuleDefinition::new(Visibility::Public, id.clone())),
92                    None,
93                ),
94            };
95            Self::recursive_create_modules(&module, &name);
96            map.insert(id.clone(), module);
97        });
98        map
99    }
100
101    fn recursive_create_modules(parent: &Symbol, name: &QualifiedName) -> Option<Symbol> {
102        if name.is_empty() {
103            return None;
104        }
105
106        let node_id = name.first().expect("Non-empty qualified name");
107        if let Some(child) = parent.get(node_id) {
108            return Some(child.clone());
109        }
110
111        let child = Symbol::new(
112            SymbolDefinition::Module(ModuleDefinition::new(Visibility::Public, node_id.clone())),
113            None,
114        );
115        Symbol::add_child(parent, child.clone());
116
117        Self::recursive_create_modules(&child, &name.remove_first());
118        Some(child)
119    }
120
121    /// Create initial symbol map from externals.
122    pub fn resolve(&self) -> ResolveResult<SymbolMap> {
123        let mut symbols = Self::create_modules(&self.externals);
124        symbols.insert(
125            self.root().id(),
126            Symbol::new(SymbolDefinition::SourceFile(self.root()), None),
127        );
128
129        self.source_files
130            .iter()
131            .try_for_each(|source_file| -> Result<(), ResolveError> {
132                let name = &source_file.name;
133                log::trace!(
134                    "{resolve} file {path:?} [{name}]",
135                    resolve = crate::mark!(RESOLVE),
136                    path = source_file.filename(),
137                );
138                let symbol = source_file.resolve()?;
139
140                // search module where to place loaded source file into
141                let target = symbols.search(name)?;
142                target.move_children(&symbol);
143
144                Ok(())
145            })?;
146
147        Ok(symbols)
148    }
149
150    /// Return the qualified name of a file by it's path
151    pub fn name_by_path(&self, filename: &std::path::Path) -> ResolveResult<QualifiedName> {
152        Ok(self.externals.get_name(filename)?.clone())
153    }
154
155    /// Convenience function to get a source file by from a `SrcReferrer`.
156    pub fn get_by_src_ref(&self, referrer: &impl SrcReferrer) -> ResolveResult<Rc<SourceFile>> {
157        self.get_by_hash(referrer.src_ref().source_hash())
158    }
159
160    /// Return a string describing the given source code position.
161    pub fn ref_str(&self, referrer: &impl SrcReferrer) -> String {
162        format!(
163            "{}:{}",
164            self.get_by_src_ref(referrer)
165                .expect("Source file not found")
166                .filename_as_str(),
167            referrer.src_ref(),
168        )
169    }
170
171    /// Find a project file by it's file path.
172    pub fn get_by_path(&self, path: &std::path::Path) -> ResolveResult<Rc<SourceFile>> {
173        let path = path.to_path_buf();
174        if let Some(index) = self.by_path.get(&path) {
175            Ok(self.source_files[*index].clone())
176        } else {
177            Err(ResolveError::FileNotFound(path))
178        }
179    }
180
181    /// Get *qualified name* of a file by *hash value*.
182    pub fn get_name_by_hash(&self, hash: u64) -> ResolveResult<&QualifiedName> {
183        match self.get_by_hash(hash) {
184            Ok(file) => self.externals.get_name(&file.filename()),
185            Err(err) => Err(err),
186        }
187    }
188
189    /// Find a project file by the qualified name which represents the file path.
190    pub fn get_by_name(&self, name: &QualifiedName) -> ResolveResult<Rc<SourceFile>> {
191        if let Some(index) = self.by_name.get(name) {
192            Ok(self.source_files[*index].clone())
193        } else {
194            // if not found in symbol tree we try to find an external file to load
195            match self.externals.fetch_external(name) {
196                Ok((name, path)) => {
197                    if self.get_by_path(&path).is_err() {
198                        return Err(ResolveError::SymbolMustBeLoaded(name, path));
199                    }
200                }
201                Err(ResolveError::ExternalSymbolNotFound(_)) => (),
202                Err(err) => return Err(err),
203            }
204            Err(ResolveError::SymbolNotFound(name.clone()))
205        }
206    }
207
208    fn name_from_index(&self, index: usize) -> Option<QualifiedName> {
209        self.by_name
210            .iter()
211            .find(|(_, i)| **i == index)
212            .map(|(name, _)| name.clone())
213    }
214
215    /// Return search paths of this cache.
216    pub fn search_paths(&self) -> &Vec<std::path::PathBuf> {
217        &self.search_paths
218    }
219}
220
221/// Trait that can fetch for a file by it's hash value.
222pub trait GetSourceByHash {
223    /// Find a project file by it's hash value.
224    fn get_by_hash(&self, hash: u64) -> ResolveResult<Rc<SourceFile>>;
225}
226
227impl GetSourceByHash for Sources {
228    /// Find a project file by it's hash value.
229    fn get_by_hash(&self, hash: u64) -> ResolveResult<Rc<SourceFile>> {
230        if let Some(index) = self.by_hash.get(&hash) {
231            Ok(self.source_files[*index].clone())
232        } else if hash == 0 {
233            Err(ResolveError::NulHash)
234        } else {
235            Err(ResolveError::UnknownHash(hash))
236        }
237    }
238}
239
240impl std::fmt::Display for Sources {
241    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
242        for (index, source_file) in self.source_files.iter().enumerate() {
243            let filename = source_file.filename_as_str();
244            let name = self
245                .name_from_index(index)
246                .unwrap_or(QualifiedName::no_ref(vec![]));
247            let hash = source_file.hash;
248            writeln!(f, "[{index}] {name:?} {hash:#x} {filename}")?;
249        }
250        Ok(())
251    }
252}