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::{Diagnostic, util};
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', '\r']),
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];
254 let mut iter = text.bytes().enumerate().peekable();
255 while let Some((i, b)) = iter.next() {
256 match b {
257 b'\r' if iter.peek().map(|(_, b)| *b) == Some(b'\n') => {
258 let _ = iter.next();
260 result.push(i + 2);
261 }
262 b'\n' | b'\r' => result.push(i + 1),
263 _ => (),
264 }
265 }
266 result.into()
267}
268
269impl SourceMap {
270 pub(crate) fn add_entry(&mut self, src: Range<usize>, dest: (FileId, usize)) {
271 if !src.is_empty() {
272 self.offsets.push((src, dest));
273 }
274 }
275
276 pub(crate) fn resolve_range(&self, global_range: Range<usize>) -> (FileId, Range<usize>) {
278 let (chunk, (file, local_offset)) = self
282 .offsets
283 .iter()
284 .find(|item| item.0.contains(&global_range.start))
285 .unwrap();
286 let chunk_offset = global_range.start - chunk.start;
287 let range_start = *local_offset + chunk_offset;
288 let len = global_range.end - global_range.start;
289 (*file, range_start..range_start + len)
290 }
291}
292
293impl SourceLoader {
294 pub(crate) fn new(resolver: Box<dyn SourceResolver>) -> Self {
295 Self {
296 sources: Default::default(),
297 resolver,
298 }
299 }
300
301 pub(crate) fn into_inner(self) -> Arc<SourceList> {
302 Arc::new(self.sources)
303 }
304
305 pub(crate) fn get(&self, id: &FileId) -> Option<&Source> {
306 self.sources.get(id)
307 }
308
309 pub(crate) fn source_for_path(
319 &mut self,
320 path: &Path,
321 included_by: Option<FileId>,
322 ) -> Result<FileId, SourceLoadError> {
323 let included_by = included_by.map(|id| self.sources.get(&id).unwrap().path.as_path());
324 let path = self.resolver.resolve_raw_path(path.as_ref(), included_by);
325 let canonical = self.resolver.canonicalize(&path)?;
326
327 match self.sources.id_for_path(&canonical) {
328 Some(id) => Ok(id),
329 None => {
330 let source = self.resolver.resolve(&path)?;
331 let id = source.id;
332 self.sources.add(canonical, source);
333 Ok(id)
334 }
335 }
336 }
337}
338
339impl SourceList {
340 pub(crate) fn id_for_path(&self, path: impl AsRef<Path>) -> Option<FileId> {
341 self.ids.get(path.as_ref()).copied()
342 }
343
344 pub(crate) fn get(&self, id: &FileId) -> Option<&Source> {
345 self.sources.get(id)
346 }
347
348 fn add(&mut self, canonical_path: PathBuf, source: Source) {
349 self.ids.insert(canonical_path, source.id);
350 self.sources.insert(source.id, source);
351 }
352
353 pub(crate) fn format_diagnostic(&self, err: &Diagnostic, colorize: bool) -> String {
358 let mut s = String::new();
359 let source = self.get(&err.message.file).unwrap();
360 crate::util::highlighting::write_diagnostic(&mut s, err, source, None, colorize);
361 s
362 }
363}
364
365impl SourceLoadError {
366 pub fn new(path: PathBuf, cause: impl std::fmt::Display) -> Self {
371 Self {
372 cause: cause.to_string().into(),
373 path,
374 }
375 }
376}
377
378#[cfg(test)]
379mod tests {
380 use super::*;
381
382 #[test]
383 fn line_offsets_lf() {
384 let offsets = line_offsets("aaa\nbbb\nccc");
385 assert_eq!(&*offsets, &[0, 4, 8]);
386 }
387
388 #[test]
389 fn line_offsets_cr() {
390 let offsets = line_offsets("aaa\rbbb\rccc");
391 assert_eq!(&*offsets, &[0, 4, 8]);
392 }
393
394 #[test]
395 fn line_offsets_crlf() {
396 let offsets = line_offsets("aaa\r\nbbb\r\nccc");
397 assert_eq!(&*offsets, &[0, 5, 10]);
398 }
399
400 #[test]
401 fn line_offsets_mixed() {
402 let offsets = line_offsets("a\nb\rc\r\nd");
404 assert_eq!(&*offsets, &[0, 2, 4, 7]);
405 }
406}