lutra_compiler/
project.rs1use std::path;
2use std::str::FromStr;
3
4use indexmap::IndexMap;
5use itertools::Itertools;
6
7use crate::pr;
8
9#[derive(Debug)]
11pub struct Project {
12 pub source: SourceTree,
14
15 pub root_module: pr::ModuleDef,
17
18 pub ordering: Vec<Vec<pr::Path>>,
21
22 pub dependencies: Vec<Dependency>,
23}
24
25#[derive(Debug)]
26pub struct Dependency {
27 pub name: String,
28
29 pub inner: Project,
30}
31
32#[derive(Debug, Clone)]
40pub struct SourceTree {
41 root: path::PathBuf,
44
45 sources: IndexMap<path::PathBuf, String>,
48}
49
50impl SourceTree {
51 pub fn empty() -> Self {
52 SourceTree {
53 sources: Default::default(),
54 root: path::PathBuf::new(),
55 }
56 }
57
58 pub fn single(path: path::PathBuf, content: String) -> Self {
59 SourceTree {
60 sources: [(path.clone(), content)].into(),
61 root: path::PathBuf::new(),
62 }
63 }
64
65 pub fn new<I>(iter: I, root: path::PathBuf) -> Self
66 where
67 I: IntoIterator<Item = (path::PathBuf, String)>,
68 {
69 SourceTree {
70 sources: IndexMap::from_iter(iter),
71 root,
72 }
73 }
74
75 pub fn is_empty(&self) -> bool {
76 self.sources.len() == 0
77 }
78
79 pub fn insert(&mut self, path: path::PathBuf, content: String) {
80 self.sources.insert(path, content);
81 }
82
83 pub fn replace(&mut self, path: &path::Path, content: String) -> Option<String> {
84 let source = self.sources.get_mut(path)?;
85 Some(std::mem::replace(source, content))
86 }
87
88 pub fn get_ids(&self) -> impl Iterator<Item = u16> {
89 0..self.sources.len() as u16
90 }
91 pub fn get_sources(&self) -> impl Iterator<Item = (&path::PathBuf, &String)> {
92 self.sources.iter()
93 }
94
95 pub fn get_by_id(&self, source_id: u16) -> Option<(&path::Path, &str)> {
96 self.sources
97 .get_index(source_id as usize)
98 .map(|(p, c)| (p.as_path(), c.as_str()))
99 }
100
101 pub fn get_by_path(&self, path: &path::Path) -> Option<(u16, &str)> {
102 self.sources
103 .get_full(path)
104 .map(|(i, _, content)| (i as u16, content.as_str()))
105 }
106
107 pub fn get_source_display_paths(&self) -> impl Iterator<Item = &path::Path> {
108 self.sources
109 .keys()
110 .map(|path| self.get_display_path(path).unwrap())
111 }
112
113 pub fn get_absolute_path(&self, path: impl AsRef<path::Path>) -> path::PathBuf {
115 let path = path.as_ref();
116 if path.as_os_str().is_empty() {
117 self.root.to_path_buf()
118 } else {
119 self.get_project_dir().join(path)
120 }
121 }
122
123 pub fn get_relative_path<'a>(
126 &self,
127 absolute_path: &'a path::Path,
128 ) -> Result<&'a path::Path, path::StripPrefixError> {
129 absolute_path.strip_prefix(&self.root)
130 }
131
132 pub fn get_project_dir(&self) -> &path::Path {
137 if self.root.extension().is_some_and(|e| e == "lt") {
138 self.root.parent().unwrap()
139 } else {
140 &self.root
141 }
142 }
143
144 pub fn get_display_path<'a>(&'a self, path: &'a path::Path) -> Option<&'a path::Path> {
149 if path.is_absolute() {
150 path.strip_prefix(self.get_project_dir()).ok()
151 } else if path.as_os_str().is_empty() {
152 self.root.file_name().map(path::Path::new)
153 } else {
154 Some(path)
155 }
156 }
157}
158
159impl std::fmt::Display for SourceTree {
160 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
161 let mut r = format!("path: {}\nsources:\n", self.root.to_string_lossy());
162
163 for source in self.sources.keys().sorted() {
164 r += "- ";
165 r += &source.to_string_lossy();
166 r += "\n";
167 }
168
169 f.write_str(&r)
170 }
171}
172
173pub struct SourceOverlay<'a> {
175 tree: &'a SourceTree,
176
177 snippet_path: path::PathBuf,
178 snippet: &'a str,
179}
180
181impl<'a> SourceOverlay<'a> {
182 pub fn new(tree: &'a SourceTree, snippet: &'a str, snippet_path: Option<&str>) -> Self {
183 Self {
184 tree,
185 snippet,
186 snippet_path: snippet_path
187 .map(|s| path::PathBuf::from_str(s).unwrap())
188 .unwrap_or_default(),
189 }
190 }
191
192 pub const fn overlay_id() -> u16 {
193 u16::MAX
194 }
195}
196
197pub(crate) trait SourceProvider {
198 fn get_root(&self) -> &path::Path;
199
200 fn get_by_id(&self, id: u16) -> Option<(&path::Path, &str)>;
201
202 fn get_by_path(&self, path: &path::Path) -> Option<(u16, &str)>;
203}
204
205impl SourceProvider for SourceTree {
206 fn get_root(&self) -> &path::Path {
207 &self.root
208 }
209 fn get_by_id(&self, id: u16) -> Option<(&path::Path, &str)> {
210 SourceTree::get_by_id(self, id)
211 }
212
213 fn get_by_path(&self, path: &path::Path) -> Option<(u16, &str)> {
214 SourceTree::get_by_path(self, path)
215 }
216}
217
218impl<'a> SourceProvider for SourceOverlay<'a> {
219 fn get_root(&self) -> &path::Path {
220 &self.tree.root
221 }
222
223 fn get_by_id(&self, id: u16) -> Option<(&path::Path, &str)> {
224 if id == SourceOverlay::overlay_id() {
225 Some((self.snippet_path.as_path(), self.snippet))
226 } else {
227 self.tree.get_by_id(id)
228 }
229 }
230
231 fn get_by_path(&self, path: &path::Path) -> Option<(u16, &str)> {
232 if path == self.snippet_path {
233 Some((SourceOverlay::overlay_id(), self.snippet))
234 } else {
235 self.tree.get_by_path(path)
236 }
237 }
238}