microcad_lang/resolve/
sources.rs1use derive_more::Deref;
7use microcad_lang_base::{
8 GetSourceLocInfoByHash, HashId, ResourceLocation, SourceLocInfo, SrcReferrer,
9};
10
11use crate::{
12 lower::{LowerErrorsWithSource, ir},
13 resolve::*,
14};
15use std::{collections::HashMap, rc::Rc};
16
17#[derive(Deref)]
25pub struct Sources {
26 by_hash: HashMap<u64, usize>,
27 by_path: HashMap<std::path::PathBuf, usize>,
28 by_name: HashMap<ir::QualifiedName, usize>,
29
30 root: Rc<ir::Source>,
32
33 #[deref]
35 pub source_files: Vec<Rc<ir::Source>>,
36
37 search_paths: Vec<std::path::PathBuf>,
39}
40
41impl Sources {
42 pub fn load(
46 root: Rc<ir::Source>,
47 search_paths: Vec<std::path::PathBuf>,
48 ) -> ResolveResult<Self> {
49 let mut source_files = Vec::new();
50 let mut by_name = HashMap::new();
51 let mut by_hash = HashMap::new();
52 let mut by_path = HashMap::new();
53
54 by_hash.insert(root.source_hash(), 0);
55 by_path.insert(root.filename(), 0);
56 by_name.insert(root.name.clone(), 0);
57 source_files.push(root.clone());
58
59 Externals::new(&search_paths)?.iter().try_for_each(
61 |(name, path)| -> Result<(), LowerErrorsWithSource> {
62 let (source_file, error) = ir::Source::load_with_name(path.clone(), name.clone());
63 let index = source_files.len();
64 by_hash.insert(source_file.source_hash(), index);
65 by_path.insert(source_file.filename(), index);
66 by_name.insert(name.clone(), index);
67 source_files.push(source_file);
68 match error {
69 Some(error) => Err(error),
70 None => Ok(()),
71 }
72 },
73 )?;
74
75 Ok(Self {
76 root,
77 source_files,
78 by_hash,
79 by_path,
80 by_name,
81 search_paths: search_paths
82 .iter()
83 .map(|path| {
84 path.canonicalize()
85 .unwrap_or_else(|_| panic!("valid path: {}", path.display()))
86 })
87 .collect(),
88 })
89 }
90
91 pub fn root(&self) -> Rc<ir::Source> {
93 self.root.clone()
94 }
95
96 pub fn insert(&mut self, source_file: Rc<ir::Source>) {
98 let hash = source_file.source_hash();
99 let path = source_file.filename();
100 let name = source_file.name.clone();
101
102 let index = if let Some(index) = self.by_path.get(&path).copied() {
104 self.by_hash.remove(&hash);
105 self.by_name.remove(&name);
106 self.by_path.remove(&path);
107 if self.root.filename() == path {
108 self.root = source_file.clone();
109 }
110 self.source_files[index] = source_file;
111
112 index
113 } else {
114 self.source_files.push(source_file.clone());
115 self.source_files.len() - 1
116 };
117
118 self.by_hash.insert(hash, index);
119 self.by_path.insert(path, index);
120 self.by_name.insert(name, index);
121 }
122
123 pub fn generate_name_from_path(
125 &self,
126 file_path: &std::path::Path,
127 ) -> ResolveResult<ir::QualifiedName> {
128 if self.root.filename() == file_path {
130 return Ok(self.root.id().into());
131 }
132
133 let path = if let Some(path) = self
135 .search_paths
136 .iter()
137 .find_map(|path| file_path.strip_prefix(path).ok())
138 {
139 path.with_extension("")
140 }
141 else if let Some(root_dir) = self.root_dir() {
143 if let Ok(path) = file_path.strip_prefix(root_dir) {
144 path.with_extension("")
145 } else {
146 return Err(ResolveError::InvalidPath(file_path.to_path_buf()));
147 }
148 } else {
149 return Err(ResolveError::InvalidPath(file_path.to_path_buf()));
150 };
151
152 let path = if let Ok(path) = path.strip_prefix(".test") {
154 path.to_path_buf()
155 } else {
156 path
157 };
158
159 let path = if path
161 .iter()
162 .next_back()
163 .map(|s| s.to_string_lossy().to_string())
164 == Some("mod".into())
165 {
166 path.parent()
167 } else {
168 Some(path.as_path())
169 };
170
171 if let Some(path) = path {
173 Ok(path
174 .iter()
175 .map(|name| ir::Identifier::no_ref(name.to_string_lossy().as_ref()))
176 .collect())
177 } else {
178 Err(ResolveError::InvalidPath(file_path.to_path_buf()))
179 }
180 }
181
182 pub fn get_by_src_ref(&self, referrer: &impl SrcReferrer) -> ResolveResult<Rc<ir::Source>> {
184 self.get_by_hash(referrer.src_ref().source_hash())
185 }
186
187 pub fn get_code(&self, referrer: &impl SrcReferrer) -> ResolveResult<String> {
189 Ok(self
190 .get_by_src_ref(referrer)?
191 .get_code(&referrer.src_ref())
192 .to_string())
193 }
194
195 fn name_from_index(&self, index: usize) -> Option<ir::QualifiedName> {
196 self.by_name
197 .iter()
198 .find(|(_, i)| **i == index)
199 .map(|(name, _)| name.clone())
200 }
201
202 pub fn search_paths(&self) -> &Vec<std::path::PathBuf> {
204 &self.search_paths
205 }
206
207 fn root_dir(&self) -> Option<std::path::PathBuf> {
208 self.root.filename().parent().map(|p| p.to_path_buf())
209 }
210
211 pub fn load_mod_file(
213 &mut self,
214 parent_path: impl AsRef<std::path::Path>,
215 id: &ir::Identifier,
216 ) -> ResolveResult<Rc<ir::Source>> {
217 log::trace!(
218 "loading file: {:?} [{id}]",
219 parent_path.as_ref().canonicalize().expect("invalid path")
220 );
221 let file_path = find_mod_file_by_id(parent_path, id)?;
222 let name = self.generate_name_from_path(&file_path)?;
223 let (source_file, error) = ir::Source::load_with_name(&file_path, name);
224 self.insert(source_file.clone());
225 match error {
226 Some(error) => Err(error.into()),
227 None => Ok(source_file),
228 }
229 }
230}
231
232pub trait GetSourceByHash {
234 fn get_by_hash(&self, hash: u64) -> ResolveResult<Rc<ir::Source>>;
236}
237
238impl GetSourceLocInfoByHash for Sources {
239 fn get_source_loc_info_by_hash(&'_ self, hash: HashId) -> Option<SourceLocInfo<'_>> {
240 self.by_hash
241 .get(&hash)
242 .map(|index| self.source_files[*index].source_loc_info())
243 }
244}
245
246impl GetSourceByHash for Sources {
247 fn get_by_hash(&self, hash: u64) -> ResolveResult<Rc<ir::Source>> {
249 if let Some(index) = self.by_hash.get(&hash) {
250 Ok(self.source_files[*index].clone())
251 } else if hash == 0 {
252 Err(ResolveError::NulHash)
253 } else {
254 Err(ResolveError::UnknownHash(hash))
255 }
256 }
257}
258
259impl std::fmt::Display for Sources {
260 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
261 for (index, source_file) in self.source_files.iter().enumerate() {
262 let filename = source_file
263 .to_file_path()
264 .map(|p| p.to_string_lossy().to_string())
265 .unwrap_or("NO FILE".to_string());
266 let name = self.name_from_index(index).unwrap_or_default();
267 let hash = source_file.source_hash();
268 writeln!(f, "[{index}] {name} {hash:#x} {filename}")?;
269 }
270 Ok(())
271 }
272}
273
274impl std::fmt::Debug for Sources {
275 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
276 for (index, source_file) in self.source_files.iter().enumerate() {
277 let filename = source_file
278 .to_file_path()
279 .map(|p| p.to_string_lossy().to_string())
280 .unwrap_or("NO FILE".to_string());
281 let name = self.name_from_index(index).unwrap_or_default();
282 let hash = source_file.source_hash();
283 writeln!(f, "[{index}] {name:?} {hash:#x} {filename}")?;
284 }
285 Ok(())
286 }
287}