1use super::overlay::Overlay;
2use crate::name::Name;
3use crate::{PackageName, PhaseError};
4use codespan_reporting::files::{Error as FilesError, Files, SimpleFile};
5use std::ops::Range;
6use std::path::{Path, PathBuf};
7
8#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
9pub struct SourceFileId(usize);
10
11#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
20pub enum SourceRoot {
21 Src,
22 Tests,
23}
24
25impl SourceRoot {
26 pub fn directory(self) -> &'static str {
28 match self {
29 SourceRoot::Src => "src",
30 SourceRoot::Tests => "tests",
31 }
32 }
33}
34
35impl std::fmt::Display for SourceRoot {
36 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
37 f.write_str(self.directory())
38 }
39}
40
41#[derive(Debug)] pub struct SourceFile {
44 module_name: String,
48 root: SourceRoot,
52 relative_path: PathBuf,
53 file: SimpleFile<String, String>,
57}
58
59impl SourceFile {
60 pub fn load(
76 abs_path: PathBuf,
77 root_dir: &Path,
78 root: SourceRoot,
79 package: Option<&PackageName>,
80 overlay: &Overlay,
81 ) -> Result<SourceFile, SourceFileError> {
82 SourceFile::load_private(&abs_path, root_dir, root, package, overlay)
83 .map_err(|error| SourceFileError { error, abs_path })
84 }
85
86 fn load_private(
87 abs_path: &PathBuf,
88 root_dir: &Path,
89 root: SourceRoot,
90 package: Option<&PackageName>,
91 overlay: &Overlay,
92 ) -> Result<SourceFile, SourceFileErrorType> {
93 let relative_path = abs_path.strip_prefix(root_dir)?.to_path_buf();
94
95 let file_name = relative_path
96 .to_str()
97 .ok_or_else(|| SourceFileErrorType::NonUtf8Module(relative_path.clone()))?
98 .to_owned();
99
100 let module_name = file_name
101 .trim_end_matches(".zel")
102 .replace(std::path::MAIN_SEPARATOR, ".");
103
104 let package_path = format!("{}/{}", root, file_name);
105
106 let rendered_name = match package {
110 Some(package) => format!("{}:{}", package, package_path),
111 None => package_path,
112 };
113
114 let source = match overlay.get(abs_path) {
115 Some(text) => text.to_owned(),
116 None => std::fs::read_to_string(abs_path)?,
117 };
118
119 Ok(SourceFile {
120 module_name,
121 root,
122 relative_path,
123 file: SimpleFile::new(rendered_name, source),
124 })
125 }
126
127 pub fn file(&self) -> &SimpleFile<String, String> {
128 &self.file
129 }
130
131 pub fn module_name(&self) -> Name {
136 Name::new(self.module_name.as_str())
137 }
138
139 pub fn package_path(&self) -> String {
146 format!("{}/{}", self.root, self.relative_path.display())
147 }
148}
149
150#[derive(Debug)]
151pub struct SourceFileError {
152 error: SourceFileErrorType,
153 abs_path: PathBuf,
154}
155
156impl SourceFileError {
157 pub(super) fn from_walk_error(root: &Path, error: walkdir::Error) -> SourceFileError {
165 let abs_path = error
166 .path()
167 .map(Path::to_path_buf)
168 .unwrap_or_else(|| root.to_path_buf());
169
170 SourceFileError {
171 error: SourceFileErrorType::Walk(error),
172 abs_path,
173 }
174 }
175}
176
177impl PhaseError for SourceFileError {
182 fn message(&self) -> String {
183 let detail = match &self.error {
184 SourceFileErrorType::InvalidPathPrefix => {
185 "the module path doesn't start with the package path"
186 }
187 SourceFileErrorType::Io(_) => "an I/O error occured while reading the module",
188 SourceFileErrorType::NonUtf8Module(_) => {
189 "module names must be utf-8 encoded"
191 }
192 SourceFileErrorType::Walk(err) => {
193 if err.loop_ancestor().is_some() {
194 "a symbolic link loop was found while walking the package sources"
195 } else if err.io_error().map(std::io::Error::kind)
196 == Some(std::io::ErrorKind::NotFound)
197 {
198 "this path does not exist"
199 } else {
200 "an I/O error occured while walking the package sources"
201 }
202 }
203 };
204
205 format!("{}: {}", self.abs_path.display(), detail)
206 }
207
208 fn notes(&self) -> Vec<String> {
209 match &self.error {
210 SourceFileErrorType::InvalidPathPrefix => vec![],
211 SourceFileErrorType::Io(err) => vec![format!("detailled error: {:?}", err)],
212 SourceFileErrorType::NonUtf8Module(rel_path) => {
213 vec![format!("relative path: {}", rel_path.display())]
215 }
216 SourceFileErrorType::Walk(err) => {
217 let mut notes = vec![];
218 if let Some(ancestor) = err.loop_ancestor() {
219 notes.push(format!(
220 "the link back to {} was found here",
221 ancestor.display()
222 ));
223 }
224 if let Some(io_err) = err.io_error() {
225 notes.push(format!("detailled error: {:?}", io_err));
226 }
227 notes
228 }
229 }
230 }
231}
232
233#[derive(Debug)]
234pub enum SourceFileErrorType {
235 InvalidPathPrefix,
236 Io(std::io::Error),
237 NonUtf8Module(PathBuf),
238 Walk(walkdir::Error),
242}
243
244impl From<std::path::StripPrefixError> for SourceFileErrorType {
245 fn from(_err: std::path::StripPrefixError) -> Self {
246 SourceFileErrorType::InvalidPathPrefix
247 }
248}
249
250impl From<std::io::Error> for SourceFileErrorType {
251 fn from(err: std::io::Error) -> Self {
252 SourceFileErrorType::Io(err)
253 }
254}
255
256pub struct SourceFiles {
265 files: Vec<SourceFile>,
266}
267
268impl Default for SourceFiles {
269 fn default() -> Self {
270 Self::new()
271 }
272}
273
274impl SourceFiles {
275 pub fn new() -> SourceFiles {
277 SourceFiles { files: Vec::new() }
278 }
279
280 pub fn add_file(&mut self, file: SourceFile) -> SourceFileId {
283 let file_id = self.files.len();
284 self.files.push(file);
285 SourceFileId(file_id)
286 }
287
288 pub fn get(&self, file_id: SourceFileId) -> Result<&SimpleFile<String, String>, FilesError> {
290 self.files
291 .get(file_id.0)
292 .map(|f| &f.file)
293 .ok_or(FilesError::FileMissing)
294 }
295
296 pub fn iter(&self) -> impl Iterator<Item = (SourceFileId, &'_ SourceFile)> + '_ {
297 self.files
298 .iter()
299 .enumerate()
300 .map(|(i, v)| (SourceFileId(i), v))
301 }
302
303 }
305
306impl<'a> Files<'a> for SourceFiles {
307 type FileId = SourceFileId;
308 type Name = String;
309 type Source = &'a String;
310
311 fn name(&self, file_id: Self::FileId) -> Result<String, FilesError> {
312 Ok(self.get(file_id)?.name().clone())
313 }
314
315 fn source(&self, file_id: Self::FileId) -> Result<&String, FilesError> {
316 Ok(self.get(file_id)?.source())
317 }
318
319 fn line_index(&self, file_id: Self::FileId, byte_index: usize) -> Result<usize, FilesError> {
320 self.get(file_id)?.line_index((), byte_index)
321 }
322
323 fn line_range(
324 &self,
325 file_id: Self::FileId,
326 line_index: usize,
327 ) -> Result<Range<usize>, FilesError> {
328 self.get(file_id)?.line_range((), line_index)
329 }
330}
331
332#[cfg(test)]
333mod tests {
334
335 use super::*;
336
337 #[test]
338 fn test_make_source_file() {
339 let root_path = Path::new(
340 "/Users/francoismonniot/Projects/github.com/fmonniot/zelkova-lang/std/core/src",
341 );
342 let abs_path = Path::new("/Users/francoismonniot/Projects/github.com/fmonniot/zelkova-lang/std/core/src/Platform/Cmd.zel");
343
344 let res = SourceFile::load(
345 abs_path.to_path_buf(),
346 root_path,
347 SourceRoot::Src,
348 None,
349 &Overlay::new(),
350 );
351 println!("{:?}", res);
352 }
353}