1use std::{
4 collections::HashMap,
5 fmt::Debug,
6 num::NonZeroU32,
7 ops::Range,
8 path::{Path, PathBuf},
9 sync::Arc,
10};
11
12use crate::{util, Diagnostic};
13
14#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Ord, PartialOrd)]
16#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
17pub struct FileId(NonZeroU32);
18
19#[derive(Clone, Debug, PartialEq)]
27#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
28pub struct Source {
29 id: FileId,
30 path: PathBuf,
32 contents: Arc<str>,
33 line_offsets: Arc<[usize]>,
36}
37
38#[derive(Debug, Clone, Default, PartialEq)]
40#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
41pub struct SourceList {
42 ids: HashMap<PathBuf, FileId>,
43 sources: HashMap<FileId, Source>,
44}
45
46pub(crate) struct SourceLoader {
47 sources: SourceList,
48 resolver: Box<dyn SourceResolver>,
49}
50
51#[derive(Clone, Debug, Default, PartialEq)]
54#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
55pub struct SourceMap {
56 offsets: Vec<(Range<usize>, (FileId, usize))>,
58}
59
60#[derive(Clone, Debug, thiserror::Error)]
62#[error("Failed to load source at '{}': '{cause}'", Path::new(.path.as_os_str()).display())]
63pub struct SourceLoadError {
64 cause: Arc<str>,
65 path: PathBuf,
66}
67
68pub trait SourceResolver {
79 fn get_contents(&self, path: &Path) -> Result<Arc<str>, SourceLoadError>;
81
82 fn resolve_raw_path(&self, path: &Path, _included_from: Option<&Path>) -> PathBuf {
93 path.to_path_buf()
94 }
95
96 fn canonicalize(&self, path: &Path) -> Result<PathBuf, SourceLoadError> {
104 Ok(path.to_owned())
105 }
106
107 #[doc(hidden)]
109 fn resolve(&self, path: &Path) -> Result<Source, SourceLoadError> {
110 let contents = self.get_contents(path)?;
111 Ok(Source::new(path.to_owned(), contents))
112 }
113
114 #[doc(hidden)]
116 fn type_name(&self) -> &'static str {
117 std::any::type_name::<Self>()
118 }
119}
120
121impl std::fmt::Debug for dyn SourceResolver {
122 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
123 self.type_name().fmt(f)
124 }
125}
126
127impl<F> SourceResolver for F
128where
129 F: Fn(&Path) -> Result<Arc<str>, SourceLoadError>,
130{
131 fn get_contents(&self, path: &Path) -> Result<Arc<str>, SourceLoadError> {
132 (self)(path)
133 }
134}
135
136#[derive(Default)]
140pub struct FileSystemResolver {
141 project_root: PathBuf,
142}
143
144impl FileSystemResolver {
145 pub fn new(project_root: PathBuf) -> Self {
151 Self { project_root }
152 }
153}
154
155impl SourceResolver for FileSystemResolver {
156 fn get_contents(&self, path: &Path) -> Result<Arc<str>, SourceLoadError> {
157 std::fs::read_to_string(path)
158 .map(Into::into)
159 .map_err(|cause| SourceLoadError::new(path.into(), cause))
160 }
161
162 fn resolve_raw_path(&self, path: &Path, included_from: Option<&Path>) -> PathBuf {
163 let path = Path::new(path);
164 let included_from = included_from.map(Path::new).and_then(Path::parent);
165 util::paths::resolve_path(path, &self.project_root, included_from)
166 }
167
168 fn canonicalize(&self, path: &Path) -> Result<PathBuf, SourceLoadError> {
169 std::fs::canonicalize(path).map_err(|io_err| SourceLoadError::new(path.into(), io_err))
170 }
171}
172
173impl FileId {
174 pub(crate) const CURRENT_FILE: FileId = FileId(NonZeroU32::new(1).unwrap());
176
177 pub(crate) fn next() -> FileId {
178 use std::sync::atomic;
179 static COUNTER: atomic::AtomicU32 = atomic::AtomicU32::new(2);
180 FileId(NonZeroU32::new(COUNTER.fetch_add(1, atomic::Ordering::Relaxed)).unwrap())
181 }
182}
183
184impl Source {
185 pub(crate) fn new(path: PathBuf, contents: Arc<str>) -> Self {
186 let line_offsets = line_offsets(&contents);
187 Source {
188 path,
189 id: FileId::next(),
190 contents,
191 line_offsets,
192 }
193 }
194
195 pub fn text(&self) -> &str {
197 &self.contents
198 }
199
200 pub fn path(&self) -> &Path {
206 &self.path
207 }
208
209 pub fn id(&self) -> FileId {
211 self.id
212 }
213
214 pub fn line_col_for_offset(&self, offset: usize) -> (usize, usize) {
216 let offset_idx = match self.line_offsets.binary_search(&offset) {
217 Ok(x) => x,
218 Err(x) => x - 1, };
220 let offset_of_line = self.line_offsets[offset_idx];
221 let offset_in_line = offset - offset_of_line;
222 (offset_idx + 1, offset_in_line)
223 }
224
225 pub fn line_containing_offset(&self, offset: usize) -> (usize, &str) {
227 let offset_idx = match self.line_offsets.binary_search(&offset) {
228 Ok(x) => x,
229 Err(x) => x - 1, };
231 let start_offset = self.line_offsets[offset_idx];
232 let end_offset = self
233 .line_offsets
234 .get(offset_idx + 1)
235 .copied()
236 .unwrap_or(self.contents.len());
237
238 (
239 offset_idx + 1,
240 self.contents[start_offset..end_offset].trim_end_matches('\n'),
241 )
242 }
243
244 pub fn offset_for_line_number(&self, line_number: usize) -> usize {
248 self.line_offsets[line_number - 1]
249 }
250}
251
252fn line_offsets(text: &str) -> Arc<[usize]> {
253 let mut result = vec![0];
255 result.extend(
256 text.bytes()
257 .enumerate()
258 .filter_map(|(i, b)| if b == b'\n' { Some(i + 1) } else { None }),
259 );
260 result.into()
261}
262
263impl SourceMap {
264 pub(crate) fn add_entry(&mut self, src: Range<usize>, dest: (FileId, usize)) {
265 if !src.is_empty() {
266 self.offsets.push((src, dest));
267 }
268 }
269
270 pub(crate) fn resolve_range(&self, global_range: Range<usize>) -> (FileId, Range<usize>) {
272 let (chunk, (file, local_offset)) = self
276 .offsets
277 .iter()
278 .find(|item| item.0.contains(&global_range.start))
279 .unwrap();
280 let chunk_offset = global_range.start - chunk.start;
281 let range_start = *local_offset + chunk_offset;
282 let len = global_range.end - global_range.start;
283 (*file, range_start..range_start + len)
284 }
285}
286
287impl SourceLoader {
288 pub(crate) fn new(resolver: Box<dyn SourceResolver>) -> Self {
289 Self {
290 sources: Default::default(),
291 resolver,
292 }
293 }
294
295 pub(crate) fn into_inner(self) -> Arc<SourceList> {
296 Arc::new(self.sources)
297 }
298
299 pub(crate) fn get(&self, id: &FileId) -> Option<&Source> {
300 self.sources.get(id)
301 }
302
303 pub(crate) fn source_for_path(
313 &mut self,
314 path: &Path,
315 included_by: Option<FileId>,
316 ) -> Result<FileId, SourceLoadError> {
317 let included_by = included_by.map(|id| self.sources.get(&id).unwrap().path.as_path());
318 let path = self.resolver.resolve_raw_path(path.as_ref(), included_by);
319 let canonical = self.resolver.canonicalize(&path)?;
320
321 match self.sources.id_for_path(&canonical) {
322 Some(id) => Ok(id),
323 None => {
324 let source = self.resolver.resolve(&path)?;
325 let id = source.id;
326 self.sources.add(canonical, source);
327 Ok(id)
328 }
329 }
330 }
331}
332
333impl SourceList {
334 pub(crate) fn id_for_path(&self, path: impl AsRef<Path>) -> Option<FileId> {
335 self.ids.get(path.as_ref()).copied()
336 }
337
338 pub(crate) fn get(&self, id: &FileId) -> Option<&Source> {
339 self.sources.get(id)
340 }
341
342 fn add(&mut self, canonical_path: PathBuf, source: Source) {
343 self.ids.insert(canonical_path, source.id);
344 self.sources.insert(source.id, source);
345 }
346
347 pub(crate) fn format_diagnostic(&self, err: &Diagnostic, colorize: bool) -> String {
352 let mut s = String::new();
353 let source = self.get(&err.message.file).unwrap();
354 crate::util::highlighting::write_diagnostic(&mut s, err, source, None, colorize);
355 s
356 }
357}
358
359impl SourceLoadError {
360 pub fn new(path: PathBuf, cause: impl std::fmt::Display) -> Self {
365 Self {
366 cause: cause.to_string().into(),
367 path,
368 }
369 }
370}