1mod analysis;
2
3pub use analysis::{SymbolInfo, TargetMap, TargetSpan};
4
5use std::path;
6use std::str::FromStr;
7use std::sync::Arc;
8
9use indexmap::IndexMap;
10use itertools::Itertools;
11
12use crate::pr;
13
14#[derive(Debug)]
16pub struct Project {
17 pub source: SourceTree,
19
20 pub root_module: pr::ModuleDef,
22
23 pub ordering: Vec<Vec<pr::Path>>,
26
27 pub dependencies: Vec<Dependency>,
28
29 pub target_map: analysis::TargetMap,
32}
33
34#[derive(Debug, Clone)]
35pub struct Dependency {
36 pub name: String,
37
38 pub inner: Arc<Project>,
39}
40
41impl Project {
42 pub fn find_by_annotation(&self, name: &str) -> Vec<pr::Path> {
48 let mut result = Vec::new();
49 find_by_annotation_in(&self.root_module, name, pr::Path::empty(), &mut result);
50 result
51 }
52}
53
54fn find_by_annotation_in(
55 module: &pr::ModuleDef,
56 annotation_name: &str,
57 mut path: pr::Path,
58 result: &mut Vec<pr::Path>,
59) {
60 if has_annotation(&module.annotations, annotation_name) {
61 result.push(path.clone());
62 }
63
64 for (name, def) in &module.defs {
65 path.push(name.clone());
66
67 if has_annotation(&def.annotations, annotation_name) {
68 result.push(path.clone());
69 }
70
71 if let pr::DefKind::Module(inner) = &def.kind {
72 find_by_annotation_in(inner, annotation_name, path.clone(), result);
73 }
74
75 path.pop();
76 }
77}
78
79fn has_annotation(annotations: &[pr::Annotation], annotation_name: &str) -> bool {
80 annotations
81 .iter()
82 .any(|ann| is_named(&ann.expr, annotation_name))
83}
84
85fn is_named(expr: &pr::Expr, name: &str) -> bool {
86 is_ident(expr, name)
87 || matches!(&expr.kind, pr::ExprKind::Call(call) if is_ident(&call.subject, name))
88}
89
90fn is_ident(expr: &pr::Expr, name: &str) -> bool {
91 matches!(&expr.kind, pr::ExprKind::Ident(path) if (path.len() == 1 && path.first() == name))
92}
93
94#[derive(Debug, Clone)]
102pub struct SourceTree {
103 root: path::PathBuf,
106
107 sources: IndexMap<path::PathBuf, String>,
110}
111
112impl SourceTree {
113 pub fn empty() -> Self {
114 SourceTree {
115 sources: Default::default(),
116 root: path::PathBuf::new(),
117 }
118 }
119
120 pub fn single(path: path::PathBuf, content: String) -> Self {
121 SourceTree {
122 sources: [(path.clone(), content)].into(),
123 root: path::PathBuf::new(),
124 }
125 }
126
127 pub fn new<I>(iter: I, root: path::PathBuf) -> Self
128 where
129 I: IntoIterator<Item = (path::PathBuf, String)>,
130 {
131 SourceTree {
132 sources: IndexMap::from_iter(iter),
133 root,
134 }
135 }
136
137 pub fn is_empty(&self) -> bool {
138 self.sources.len() == 0
139 }
140
141 pub fn insert(&mut self, path: path::PathBuf, content: String) {
142 self.sources.insert(path, content);
143 }
144
145 pub fn replace(&mut self, path: &path::Path, content: String) -> Option<String> {
146 let source = self.sources.get_mut(path)?;
147 Some(std::mem::replace(source, content))
148 }
149
150 pub fn get_ids(&self) -> impl Iterator<Item = u16> {
151 0..self.sources.len() as u16
152 }
153 pub fn get_sources(&self) -> impl Iterator<Item = (&path::PathBuf, &String)> {
154 self.sources.iter()
155 }
156 pub fn get_root(&self) -> &std::path::Path {
157 self.root.as_path()
158 }
159
160 pub fn get_by_id(&self, source_id: u16) -> Option<(&path::Path, &str)> {
161 self.sources
162 .get_index(source_id as usize)
163 .map(|(p, c)| (p.as_path(), c.as_str()))
164 }
165
166 pub fn get_by_path(&self, path: &path::Path) -> Option<(u16, &str)> {
167 self.sources
168 .get_full(path)
169 .map(|(i, _, content)| (i as u16, content.as_str()))
170 }
171
172 pub fn get_source_display_paths(&self) -> impl Iterator<Item = &path::Path> {
173 self.sources
174 .keys()
175 .map(|path| self.get_display_path(path).unwrap())
176 }
177
178 pub fn get_absolute_path(&self, path: impl AsRef<path::Path>) -> path::PathBuf {
180 let path = path.as_ref();
181 if path.as_os_str().is_empty() {
182 self.root.to_path_buf()
183 } else {
184 self.get_project_dir().join(path)
185 }
186 }
187
188 pub fn get_relative_path<'a>(
191 &self,
192 absolute_path: &'a path::Path,
193 ) -> Result<&'a path::Path, path::StripPrefixError> {
194 absolute_path.strip_prefix(&self.root)
195 }
196
197 pub fn get_project_dir(&self) -> &path::Path {
202 if self.root.extension().is_some_and(|e| e == "lt") {
203 self.root.parent().unwrap()
204 } else {
205 &self.root
206 }
207 }
208
209 pub fn get_display_path<'a>(&'a self, path: &'a path::Path) -> Option<&'a path::Path> {
214 if path.is_absolute() {
215 path.strip_prefix(self.get_project_dir()).ok()
216 } else if path.as_os_str().is_empty() {
217 self.root.file_name().map(path::Path::new)
218 } else {
219 Some(path)
220 }
221 }
222}
223
224impl std::fmt::Display for SourceTree {
225 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
226 let mut r = format!("path: {}\nsources:\n", self.root.to_string_lossy());
227
228 for source in self.sources.keys().sorted() {
229 r += "- ";
230 r += &source.to_string_lossy();
231 r += "\n";
232 }
233
234 f.write_str(&r)
235 }
236}
237
238pub struct SourceOverlay<'a> {
240 tree: &'a SourceTree,
241
242 snippet_path: path::PathBuf,
243 snippet: &'a str,
244}
245
246impl<'a> SourceOverlay<'a> {
247 pub fn new(tree: &'a SourceTree, snippet: &'a str, snippet_path: Option<&str>) -> Self {
248 Self {
249 tree,
250 snippet,
251 snippet_path: snippet_path
252 .map(|s| path::PathBuf::from_str(s).unwrap())
253 .unwrap_or_default(),
254 }
255 }
256
257 pub const fn overlay_id() -> u16 {
258 u16::MAX
259 }
260}
261
262pub(crate) trait SourceProvider {
263 fn get_root(&self) -> &path::Path;
264
265 fn get_by_id(&self, id: u16) -> Option<(&path::Path, &str)>;
266
267 fn get_by_path(&self, path: &path::Path) -> Option<(u16, &str)>;
268}
269
270impl SourceProvider for SourceTree {
271 fn get_root(&self) -> &path::Path {
272 &self.root
273 }
274 fn get_by_id(&self, id: u16) -> Option<(&path::Path, &str)> {
275 SourceTree::get_by_id(self, id)
276 }
277
278 fn get_by_path(&self, path: &path::Path) -> Option<(u16, &str)> {
279 SourceTree::get_by_path(self, path)
280 }
281}
282
283impl<'a> SourceProvider for SourceOverlay<'a> {
284 fn get_root(&self) -> &path::Path {
285 &self.tree.root
286 }
287
288 fn get_by_id(&self, id: u16) -> Option<(&path::Path, &str)> {
289 if id == SourceOverlay::overlay_id() {
290 Some((self.snippet_path.as_path(), self.snippet))
291 } else {
292 self.tree.get_by_id(id)
293 }
294 }
295
296 fn get_by_path(&self, path: &path::Path) -> Option<(u16, &str)> {
297 if path == self.snippet_path {
298 Some((SourceOverlay::overlay_id(), self.snippet))
299 } else {
300 self.tree.get_by_path(path)
301 }
302 }
303}