1e73685ebSopenharmony_ci//! Source file support for diagnostic reporting.
2e73685ebSopenharmony_ci//!
3e73685ebSopenharmony_ci//! The main trait defined in this module is the [`Files`] trait, which provides
4e73685ebSopenharmony_ci//! provides the minimum amount of functionality required for printing [`Diagnostics`]
5e73685ebSopenharmony_ci//! with the [`term::emit`] function.
6e73685ebSopenharmony_ci//!
7e73685ebSopenharmony_ci//! Simple implementations of this trait are implemented:
8e73685ebSopenharmony_ci//!
9e73685ebSopenharmony_ci//! - [`SimpleFile`]: For single-file use-cases
10e73685ebSopenharmony_ci//! - [`SimpleFiles`]: For multi-file use-cases
11e73685ebSopenharmony_ci//!
12e73685ebSopenharmony_ci//! These data structures provide a pretty minimal API, however,
13e73685ebSopenharmony_ci//! so end-users are encouraged to create their own implementations for their
14e73685ebSopenharmony_ci//! own specific use-cases, such as an implementation that accesses the file
15e73685ebSopenharmony_ci//! system directly (and caches the line start locations), or an implementation
16e73685ebSopenharmony_ci//! using an incremental compilation library like [`salsa`].
17e73685ebSopenharmony_ci//!
18e73685ebSopenharmony_ci//! [`term::emit`]: crate::term::emit
19e73685ebSopenharmony_ci//! [`Diagnostics`]: crate::diagnostic::Diagnostic
20e73685ebSopenharmony_ci//! [`Files`]: Files
21e73685ebSopenharmony_ci//! [`SimpleFile`]: SimpleFile
22e73685ebSopenharmony_ci//! [`SimpleFiles`]: SimpleFiles
23e73685ebSopenharmony_ci//!
24e73685ebSopenharmony_ci//! [`salsa`]: https://crates.io/crates/salsa
25e73685ebSopenharmony_ci
26e73685ebSopenharmony_ciuse std::ops::Range;
27e73685ebSopenharmony_ci
28e73685ebSopenharmony_ci/// An enum representing an error that happened while looking up a file or a piece of content in that file.
29e73685ebSopenharmony_ci#[derive(Debug)]
30e73685ebSopenharmony_ci#[non_exhaustive]
31e73685ebSopenharmony_cipub enum Error {
32e73685ebSopenharmony_ci    /// A required file is not in the file database.
33e73685ebSopenharmony_ci    FileMissing,
34e73685ebSopenharmony_ci    /// The file is present, but does not contain the specified byte index.
35e73685ebSopenharmony_ci    IndexTooLarge { given: usize, max: usize },
36e73685ebSopenharmony_ci    /// The file is present, but does not contain the specified line index.
37e73685ebSopenharmony_ci    LineTooLarge { given: usize, max: usize },
38e73685ebSopenharmony_ci    /// The file is present and contains the specified line index, but the line does not contain the specified column index.
39e73685ebSopenharmony_ci    ColumnTooLarge { given: usize, max: usize },
40e73685ebSopenharmony_ci    /// The given index is contained in the file, but is not a boundary of a UTF-8 code point.
41e73685ebSopenharmony_ci    InvalidCharBoundary { given: usize },
42e73685ebSopenharmony_ci    /// There was a error while doing IO.
43e73685ebSopenharmony_ci    Io(std::io::Error),
44e73685ebSopenharmony_ci}
45e73685ebSopenharmony_ci
46e73685ebSopenharmony_ciimpl From<std::io::Error> for Error {
47e73685ebSopenharmony_ci    fn from(err: std::io::Error) -> Error {
48e73685ebSopenharmony_ci        Error::Io(err)
49e73685ebSopenharmony_ci    }
50e73685ebSopenharmony_ci}
51e73685ebSopenharmony_ci
52e73685ebSopenharmony_ciimpl std::fmt::Display for Error {
53e73685ebSopenharmony_ci    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
54e73685ebSopenharmony_ci        match self {
55e73685ebSopenharmony_ci            Error::FileMissing => write!(f, "file missing"),
56e73685ebSopenharmony_ci            Error::IndexTooLarge { given, max } => {
57e73685ebSopenharmony_ci                write!(f, "invalid index {}, maximum index is {}", given, max)
58e73685ebSopenharmony_ci            }
59e73685ebSopenharmony_ci            Error::LineTooLarge { given, max } => {
60e73685ebSopenharmony_ci                write!(f, "invalid line {}, maximum line is {}", given, max)
61e73685ebSopenharmony_ci            }
62e73685ebSopenharmony_ci            Error::ColumnTooLarge { given, max } => {
63e73685ebSopenharmony_ci                write!(f, "invalid column {}, maximum column {}", given, max)
64e73685ebSopenharmony_ci            }
65e73685ebSopenharmony_ci            Error::InvalidCharBoundary { .. } => write!(f, "index is not a code point boundary"),
66e73685ebSopenharmony_ci            Error::Io(err) => write!(f, "{}", err),
67e73685ebSopenharmony_ci        }
68e73685ebSopenharmony_ci    }
69e73685ebSopenharmony_ci}
70e73685ebSopenharmony_ci
71e73685ebSopenharmony_ciimpl std::error::Error for Error {
72e73685ebSopenharmony_ci    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
73e73685ebSopenharmony_ci        match &self {
74e73685ebSopenharmony_ci            Error::Io(err) => Some(err),
75e73685ebSopenharmony_ci            _ => None,
76e73685ebSopenharmony_ci        }
77e73685ebSopenharmony_ci    }
78e73685ebSopenharmony_ci}
79e73685ebSopenharmony_ci
80e73685ebSopenharmony_ci/// A minimal interface for accessing source files when rendering diagnostics.
81e73685ebSopenharmony_ci///
82e73685ebSopenharmony_ci/// A lifetime parameter `'a` is provided to allow any of the returned values to returned by reference.
83e73685ebSopenharmony_ci/// This is to workaround the lack of higher kinded lifetime parameters.
84e73685ebSopenharmony_ci/// This can be ignored if this is not needed, however.
85e73685ebSopenharmony_cipub trait Files<'a> {
86e73685ebSopenharmony_ci    /// A unique identifier for files in the file provider. This will be used
87e73685ebSopenharmony_ci    /// for rendering `diagnostic::Label`s in the corresponding source files.
88e73685ebSopenharmony_ci    type FileId: 'a + Copy + PartialEq;
89e73685ebSopenharmony_ci    /// The user-facing name of a file, to be displayed in diagnostics.
90e73685ebSopenharmony_ci    type Name: 'a + std::fmt::Display;
91e73685ebSopenharmony_ci    /// The source code of a file.
92e73685ebSopenharmony_ci    type Source: 'a + AsRef<str>;
93e73685ebSopenharmony_ci
94e73685ebSopenharmony_ci    /// The user-facing name of a file.
95e73685ebSopenharmony_ci    fn name(&'a self, id: Self::FileId) -> Result<Self::Name, Error>;
96e73685ebSopenharmony_ci
97e73685ebSopenharmony_ci    /// The source code of a file.
98e73685ebSopenharmony_ci    fn source(&'a self, id: Self::FileId) -> Result<Self::Source, Error>;
99e73685ebSopenharmony_ci
100e73685ebSopenharmony_ci    /// The index of the line at the given byte index.
101e73685ebSopenharmony_ci    /// If the byte index is past the end of the file, returns the maximum line index in the file.
102e73685ebSopenharmony_ci    /// This means that this function only fails if the file is not present.
103e73685ebSopenharmony_ci    ///
104e73685ebSopenharmony_ci    /// # Note for trait implementors
105e73685ebSopenharmony_ci    ///
106e73685ebSopenharmony_ci    /// This can be implemented efficiently by performing a binary search over
107e73685ebSopenharmony_ci    /// a list of line starts that was computed by calling the [`line_starts`]
108e73685ebSopenharmony_ci    /// function that is exported from the [`files`] module. It might be useful
109e73685ebSopenharmony_ci    /// to pre-compute and cache these line starts.
110e73685ebSopenharmony_ci    ///
111e73685ebSopenharmony_ci    /// [`line_starts`]: crate::files::line_starts
112e73685ebSopenharmony_ci    /// [`files`]: crate::files
113e73685ebSopenharmony_ci    fn line_index(&'a self, id: Self::FileId, byte_index: usize) -> Result<usize, Error>;
114e73685ebSopenharmony_ci
115e73685ebSopenharmony_ci    /// The user-facing line number at the given line index.
116e73685ebSopenharmony_ci    /// It is not necessarily checked that the specified line index
117e73685ebSopenharmony_ci    /// is actually in the file.
118e73685ebSopenharmony_ci    ///
119e73685ebSopenharmony_ci    /// # Note for trait implementors
120e73685ebSopenharmony_ci    ///
121e73685ebSopenharmony_ci    /// This is usually 1-indexed from the beginning of the file, but
122e73685ebSopenharmony_ci    /// can be useful for implementing something like the
123e73685ebSopenharmony_ci    /// [C preprocessor's `#line` macro][line-macro].
124e73685ebSopenharmony_ci    ///
125e73685ebSopenharmony_ci    /// [line-macro]: https://en.cppreference.com/w/c/preprocessor/line
126e73685ebSopenharmony_ci    #[allow(unused_variables)]
127e73685ebSopenharmony_ci    fn line_number(&'a self, id: Self::FileId, line_index: usize) -> Result<usize, Error> {
128e73685ebSopenharmony_ci        Ok(line_index + 1)
129e73685ebSopenharmony_ci    }
130e73685ebSopenharmony_ci
131e73685ebSopenharmony_ci    /// The user-facing column number at the given line index and byte index.
132e73685ebSopenharmony_ci    ///
133e73685ebSopenharmony_ci    /// # Note for trait implementors
134e73685ebSopenharmony_ci    ///
135e73685ebSopenharmony_ci    /// This is usually 1-indexed from the the start of the line.
136e73685ebSopenharmony_ci    /// A default implementation is provided, based on the [`column_index`]
137e73685ebSopenharmony_ci    /// function that is exported from the [`files`] module.
138e73685ebSopenharmony_ci    ///
139e73685ebSopenharmony_ci    /// [`files`]: crate::files
140e73685ebSopenharmony_ci    /// [`column_index`]: crate::files::column_index
141e73685ebSopenharmony_ci    fn column_number(
142e73685ebSopenharmony_ci        &'a self,
143e73685ebSopenharmony_ci        id: Self::FileId,
144e73685ebSopenharmony_ci        line_index: usize,
145e73685ebSopenharmony_ci        byte_index: usize,
146e73685ebSopenharmony_ci    ) -> Result<usize, Error> {
147e73685ebSopenharmony_ci        let source = self.source(id)?;
148e73685ebSopenharmony_ci        let line_range = self.line_range(id, line_index)?;
149e73685ebSopenharmony_ci        let column_index = column_index(source.as_ref(), line_range, byte_index);
150e73685ebSopenharmony_ci
151e73685ebSopenharmony_ci        Ok(column_index + 1)
152e73685ebSopenharmony_ci    }
153e73685ebSopenharmony_ci
154e73685ebSopenharmony_ci    /// Convenience method for returning line and column number at the given
155e73685ebSopenharmony_ci    /// byte index in the file.
156e73685ebSopenharmony_ci    fn location(&'a self, id: Self::FileId, byte_index: usize) -> Result<Location, Error> {
157e73685ebSopenharmony_ci        let line_index = self.line_index(id, byte_index)?;
158e73685ebSopenharmony_ci
159e73685ebSopenharmony_ci        Ok(Location {
160e73685ebSopenharmony_ci            line_number: self.line_number(id, line_index)?,
161e73685ebSopenharmony_ci            column_number: self.column_number(id, line_index, byte_index)?,
162e73685ebSopenharmony_ci        })
163e73685ebSopenharmony_ci    }
164e73685ebSopenharmony_ci
165e73685ebSopenharmony_ci    /// The byte range of line in the source of the file.
166e73685ebSopenharmony_ci    fn line_range(&'a self, id: Self::FileId, line_index: usize) -> Result<Range<usize>, Error>;
167e73685ebSopenharmony_ci}
168e73685ebSopenharmony_ci
169e73685ebSopenharmony_ci/// A user-facing location in a source file.
170e73685ebSopenharmony_ci///
171e73685ebSopenharmony_ci/// Returned by [`Files::location`].
172e73685ebSopenharmony_ci///
173e73685ebSopenharmony_ci/// [`Files::location`]: Files::location
174e73685ebSopenharmony_ci#[derive(Debug, Copy, Clone, PartialEq, Eq)]
175e73685ebSopenharmony_cipub struct Location {
176e73685ebSopenharmony_ci    /// The user-facing line number.
177e73685ebSopenharmony_ci    pub line_number: usize,
178e73685ebSopenharmony_ci    /// The user-facing column number.
179e73685ebSopenharmony_ci    pub column_number: usize,
180e73685ebSopenharmony_ci}
181e73685ebSopenharmony_ci
182e73685ebSopenharmony_ci/// The column index at the given byte index in the source file.
183e73685ebSopenharmony_ci/// This is the number of characters to the given byte index.
184e73685ebSopenharmony_ci///
185e73685ebSopenharmony_ci/// If the byte index is smaller than the start of the line, then `0` is returned.
186e73685ebSopenharmony_ci/// If the byte index is past the end of the line, the column index of the last
187e73685ebSopenharmony_ci/// character `+ 1` is returned.
188e73685ebSopenharmony_ci///
189e73685ebSopenharmony_ci/// # Example
190e73685ebSopenharmony_ci///
191e73685ebSopenharmony_ci/// ```rust
192e73685ebSopenharmony_ci/// use codespan_reporting::files;
193e73685ebSopenharmony_ci///
194e73685ebSopenharmony_ci/// let source = "\n\n�∈�\n\n";
195e73685ebSopenharmony_ci///
196e73685ebSopenharmony_ci/// assert_eq!(files::column_index(source, 0..1, 0), 0);
197e73685ebSopenharmony_ci/// assert_eq!(files::column_index(source, 2..13, 0), 0);
198e73685ebSopenharmony_ci/// assert_eq!(files::column_index(source, 2..13, 2 + 0), 0);
199e73685ebSopenharmony_ci/// assert_eq!(files::column_index(source, 2..13, 2 + 1), 0);
200e73685ebSopenharmony_ci/// assert_eq!(files::column_index(source, 2..13, 2 + 4), 1);
201e73685ebSopenharmony_ci/// assert_eq!(files::column_index(source, 2..13, 2 + 8), 2);
202e73685ebSopenharmony_ci/// assert_eq!(files::column_index(source, 2..13, 2 + 10), 2);
203e73685ebSopenharmony_ci/// assert_eq!(files::column_index(source, 2..13, 2 + 11), 3);
204e73685ebSopenharmony_ci/// assert_eq!(files::column_index(source, 2..13, 2 + 12), 3);
205e73685ebSopenharmony_ci/// ```
206e73685ebSopenharmony_cipub fn column_index(source: &str, line_range: Range<usize>, byte_index: usize) -> usize {
207e73685ebSopenharmony_ci    let end_index = std::cmp::min(byte_index, std::cmp::min(line_range.end, source.len()));
208e73685ebSopenharmony_ci
209e73685ebSopenharmony_ci    (line_range.start..end_index)
210e73685ebSopenharmony_ci        .filter(|byte_index| source.is_char_boundary(byte_index + 1))
211e73685ebSopenharmony_ci        .count()
212e73685ebSopenharmony_ci}
213e73685ebSopenharmony_ci
214e73685ebSopenharmony_ci/// Return the starting byte index of each line in the source string.
215e73685ebSopenharmony_ci///
216e73685ebSopenharmony_ci/// This can make it easier to implement [`Files::line_index`] by allowing
217e73685ebSopenharmony_ci/// implementors of [`Files`] to pre-compute the line starts, then search for
218e73685ebSopenharmony_ci/// the corresponding line range, as shown in the example below.
219e73685ebSopenharmony_ci///
220e73685ebSopenharmony_ci/// [`Files`]: Files
221e73685ebSopenharmony_ci/// [`Files::line_index`]: Files::line_index
222e73685ebSopenharmony_ci///
223e73685ebSopenharmony_ci/// # Example
224e73685ebSopenharmony_ci///
225e73685ebSopenharmony_ci/// ```rust
226e73685ebSopenharmony_ci/// use codespan_reporting::files;
227e73685ebSopenharmony_ci///
228e73685ebSopenharmony_ci/// let source = "foo\nbar\r\n\nbaz";
229e73685ebSopenharmony_ci/// let line_starts: Vec<_> = files::line_starts(source).collect();
230e73685ebSopenharmony_ci///
231e73685ebSopenharmony_ci/// assert_eq!(
232e73685ebSopenharmony_ci///     line_starts,
233e73685ebSopenharmony_ci///     [
234e73685ebSopenharmony_ci///         0,  // "foo\n"
235e73685ebSopenharmony_ci///         4,  // "bar\r\n"
236e73685ebSopenharmony_ci///         9,  // ""
237e73685ebSopenharmony_ci///         10, // "baz"
238e73685ebSopenharmony_ci///     ],
239e73685ebSopenharmony_ci/// );
240e73685ebSopenharmony_ci///
241e73685ebSopenharmony_ci/// fn line_index(line_starts: &[usize], byte_index: usize) -> Option<usize> {
242e73685ebSopenharmony_ci///     match line_starts.binary_search(&byte_index) {
243e73685ebSopenharmony_ci///         Ok(line) => Some(line),
244e73685ebSopenharmony_ci///         Err(next_line) => Some(next_line - 1),
245e73685ebSopenharmony_ci///     }
246e73685ebSopenharmony_ci/// }
247e73685ebSopenharmony_ci///
248e73685ebSopenharmony_ci/// assert_eq!(line_index(&line_starts, 5), Some(1));
249e73685ebSopenharmony_ci/// ```
250e73685ebSopenharmony_ci// NOTE: this is copied in `codespan::file::line_starts` and should be kept in sync.
251e73685ebSopenharmony_cipub fn line_starts<'source>(source: &'source str) -> impl 'source + Iterator<Item = usize> {
252e73685ebSopenharmony_ci    std::iter::once(0).chain(source.match_indices('\n').map(|(i, _)| i + 1))
253e73685ebSopenharmony_ci}
254e73685ebSopenharmony_ci
255e73685ebSopenharmony_ci/// A file database that contains a single source file.
256e73685ebSopenharmony_ci///
257e73685ebSopenharmony_ci/// Because there is only single file in this database we use `()` as a [`FileId`].
258e73685ebSopenharmony_ci///
259e73685ebSopenharmony_ci/// This is useful for simple language tests, but it might be worth creating a
260e73685ebSopenharmony_ci/// custom implementation when a language scales beyond a certain size.
261e73685ebSopenharmony_ci///
262e73685ebSopenharmony_ci/// [`FileId`]: Files::FileId
263e73685ebSopenharmony_ci#[derive(Debug, Clone)]
264e73685ebSopenharmony_cipub struct SimpleFile<Name, Source> {
265e73685ebSopenharmony_ci    /// The name of the file.
266e73685ebSopenharmony_ci    name: Name,
267e73685ebSopenharmony_ci    /// The source code of the file.
268e73685ebSopenharmony_ci    source: Source,
269e73685ebSopenharmony_ci    /// The starting byte indices in the source code.
270e73685ebSopenharmony_ci    line_starts: Vec<usize>,
271e73685ebSopenharmony_ci}
272e73685ebSopenharmony_ci
273e73685ebSopenharmony_ciimpl<Name, Source> SimpleFile<Name, Source>
274e73685ebSopenharmony_ciwhere
275e73685ebSopenharmony_ci    Name: std::fmt::Display,
276e73685ebSopenharmony_ci    Source: AsRef<str>,
277e73685ebSopenharmony_ci{
278e73685ebSopenharmony_ci    /// Create a new source file.
279e73685ebSopenharmony_ci    pub fn new(name: Name, source: Source) -> SimpleFile<Name, Source> {
280e73685ebSopenharmony_ci        SimpleFile {
281e73685ebSopenharmony_ci            name,
282e73685ebSopenharmony_ci            line_starts: line_starts(source.as_ref()).collect(),
283e73685ebSopenharmony_ci            source,
284e73685ebSopenharmony_ci        }
285e73685ebSopenharmony_ci    }
286e73685ebSopenharmony_ci
287e73685ebSopenharmony_ci    /// Return the name of the file.
288e73685ebSopenharmony_ci    pub fn name(&self) -> &Name {
289e73685ebSopenharmony_ci        &self.name
290e73685ebSopenharmony_ci    }
291e73685ebSopenharmony_ci
292e73685ebSopenharmony_ci    /// Return the source of the file.
293e73685ebSopenharmony_ci    pub fn source(&self) -> &Source {
294e73685ebSopenharmony_ci        &self.source
295e73685ebSopenharmony_ci    }
296e73685ebSopenharmony_ci
297e73685ebSopenharmony_ci    /// Return the starting byte index of the line with the specified line index.
298e73685ebSopenharmony_ci    /// Convenience method that already generates errors if necessary.
299e73685ebSopenharmony_ci    fn line_start(&self, line_index: usize) -> Result<usize, Error> {
300e73685ebSopenharmony_ci        use std::cmp::Ordering;
301e73685ebSopenharmony_ci
302e73685ebSopenharmony_ci        match line_index.cmp(&self.line_starts.len()) {
303e73685ebSopenharmony_ci            Ordering::Less => Ok(self
304e73685ebSopenharmony_ci                .line_starts
305e73685ebSopenharmony_ci                .get(line_index)
306e73685ebSopenharmony_ci                .cloned()
307e73685ebSopenharmony_ci                .expect("failed despite previous check")),
308e73685ebSopenharmony_ci            Ordering::Equal => Ok(self.source.as_ref().len()),
309e73685ebSopenharmony_ci            Ordering::Greater => Err(Error::LineTooLarge {
310e73685ebSopenharmony_ci                given: line_index,
311e73685ebSopenharmony_ci                max: self.line_starts.len() - 1,
312e73685ebSopenharmony_ci            }),
313e73685ebSopenharmony_ci        }
314e73685ebSopenharmony_ci    }
315e73685ebSopenharmony_ci}
316e73685ebSopenharmony_ci
317e73685ebSopenharmony_ciimpl<'a, Name, Source> Files<'a> for SimpleFile<Name, Source>
318e73685ebSopenharmony_ciwhere
319e73685ebSopenharmony_ci    Name: 'a + std::fmt::Display + Clone,
320e73685ebSopenharmony_ci    Source: 'a + AsRef<str>,
321e73685ebSopenharmony_ci{
322e73685ebSopenharmony_ci    type FileId = ();
323e73685ebSopenharmony_ci    type Name = Name;
324e73685ebSopenharmony_ci    type Source = &'a str;
325e73685ebSopenharmony_ci
326e73685ebSopenharmony_ci    fn name(&self, (): ()) -> Result<Name, Error> {
327e73685ebSopenharmony_ci        Ok(self.name.clone())
328e73685ebSopenharmony_ci    }
329e73685ebSopenharmony_ci
330e73685ebSopenharmony_ci    fn source(&self, (): ()) -> Result<&str, Error> {
331e73685ebSopenharmony_ci        Ok(self.source.as_ref())
332e73685ebSopenharmony_ci    }
333e73685ebSopenharmony_ci
334e73685ebSopenharmony_ci    fn line_index(&self, (): (), byte_index: usize) -> Result<usize, Error> {
335e73685ebSopenharmony_ci        Ok(self
336e73685ebSopenharmony_ci            .line_starts
337e73685ebSopenharmony_ci            .binary_search(&byte_index)
338e73685ebSopenharmony_ci            .unwrap_or_else(|next_line| next_line - 1))
339e73685ebSopenharmony_ci    }
340e73685ebSopenharmony_ci
341e73685ebSopenharmony_ci    fn line_range(&self, (): (), line_index: usize) -> Result<Range<usize>, Error> {
342e73685ebSopenharmony_ci        let line_start = self.line_start(line_index)?;
343e73685ebSopenharmony_ci        let next_line_start = self.line_start(line_index + 1)?;
344e73685ebSopenharmony_ci
345e73685ebSopenharmony_ci        Ok(line_start..next_line_start)
346e73685ebSopenharmony_ci    }
347e73685ebSopenharmony_ci}
348e73685ebSopenharmony_ci
349e73685ebSopenharmony_ci/// A file database that can store multiple source files.
350e73685ebSopenharmony_ci///
351e73685ebSopenharmony_ci/// This is useful for simple language tests, but it might be worth creating a
352e73685ebSopenharmony_ci/// custom implementation when a language scales beyond a certain size.
353e73685ebSopenharmony_ci/// It is a glorified `Vec<SimpleFile>` that implements the `Files` trait.
354e73685ebSopenharmony_ci#[derive(Debug, Clone)]
355e73685ebSopenharmony_cipub struct SimpleFiles<Name, Source> {
356e73685ebSopenharmony_ci    files: Vec<SimpleFile<Name, Source>>,
357e73685ebSopenharmony_ci}
358e73685ebSopenharmony_ci
359e73685ebSopenharmony_ciimpl<Name, Source> SimpleFiles<Name, Source>
360e73685ebSopenharmony_ciwhere
361e73685ebSopenharmony_ci    Name: std::fmt::Display,
362e73685ebSopenharmony_ci    Source: AsRef<str>,
363e73685ebSopenharmony_ci{
364e73685ebSopenharmony_ci    /// Create a new files database.
365e73685ebSopenharmony_ci    pub fn new() -> SimpleFiles<Name, Source> {
366e73685ebSopenharmony_ci        SimpleFiles { files: Vec::new() }
367e73685ebSopenharmony_ci    }
368e73685ebSopenharmony_ci
369e73685ebSopenharmony_ci    /// Add a file to the database, returning the handle that can be used to
370e73685ebSopenharmony_ci    /// refer to it again.
371e73685ebSopenharmony_ci    pub fn add(&mut self, name: Name, source: Source) -> usize {
372e73685ebSopenharmony_ci        let file_id = self.files.len();
373e73685ebSopenharmony_ci        self.files.push(SimpleFile::new(name, source));
374e73685ebSopenharmony_ci        file_id
375e73685ebSopenharmony_ci    }
376e73685ebSopenharmony_ci
377e73685ebSopenharmony_ci    /// Get the file corresponding to the given id.
378e73685ebSopenharmony_ci    pub fn get(&self, file_id: usize) -> Result<&SimpleFile<Name, Source>, Error> {
379e73685ebSopenharmony_ci        self.files.get(file_id).ok_or(Error::FileMissing)
380e73685ebSopenharmony_ci    }
381e73685ebSopenharmony_ci}
382e73685ebSopenharmony_ci
383e73685ebSopenharmony_ciimpl<'a, Name, Source> Files<'a> for SimpleFiles<Name, Source>
384e73685ebSopenharmony_ciwhere
385e73685ebSopenharmony_ci    Name: 'a + std::fmt::Display + Clone,
386e73685ebSopenharmony_ci    Source: 'a + AsRef<str>,
387e73685ebSopenharmony_ci{
388e73685ebSopenharmony_ci    type FileId = usize;
389e73685ebSopenharmony_ci    type Name = Name;
390e73685ebSopenharmony_ci    type Source = &'a str;
391e73685ebSopenharmony_ci
392e73685ebSopenharmony_ci    fn name(&self, file_id: usize) -> Result<Name, Error> {
393e73685ebSopenharmony_ci        Ok(self.get(file_id)?.name().clone())
394e73685ebSopenharmony_ci    }
395e73685ebSopenharmony_ci
396e73685ebSopenharmony_ci    fn source(&self, file_id: usize) -> Result<&str, Error> {
397e73685ebSopenharmony_ci        Ok(self.get(file_id)?.source().as_ref())
398e73685ebSopenharmony_ci    }
399e73685ebSopenharmony_ci
400e73685ebSopenharmony_ci    fn line_index(&self, file_id: usize, byte_index: usize) -> Result<usize, Error> {
401e73685ebSopenharmony_ci        self.get(file_id)?.line_index((), byte_index)
402e73685ebSopenharmony_ci    }
403e73685ebSopenharmony_ci
404e73685ebSopenharmony_ci    fn line_range(&self, file_id: usize, line_index: usize) -> Result<Range<usize>, Error> {
405e73685ebSopenharmony_ci        self.get(file_id)?.line_range((), line_index)
406e73685ebSopenharmony_ci    }
407e73685ebSopenharmony_ci}
408e73685ebSopenharmony_ci
409e73685ebSopenharmony_ci#[cfg(test)]
410e73685ebSopenharmony_cimod test {
411e73685ebSopenharmony_ci    use super::*;
412e73685ebSopenharmony_ci
413e73685ebSopenharmony_ci    const TEST_SOURCE: &str = "foo\nbar\r\n\nbaz";
414e73685ebSopenharmony_ci
415e73685ebSopenharmony_ci    #[test]
416e73685ebSopenharmony_ci    fn line_starts() {
417e73685ebSopenharmony_ci        let file = SimpleFile::new("test", TEST_SOURCE);
418e73685ebSopenharmony_ci
419e73685ebSopenharmony_ci        assert_eq!(
420e73685ebSopenharmony_ci            file.line_starts,
421e73685ebSopenharmony_ci            [
422e73685ebSopenharmony_ci                0,  // "foo\n"
423e73685ebSopenharmony_ci                4,  // "bar\r\n"
424e73685ebSopenharmony_ci                9,  // ""
425e73685ebSopenharmony_ci                10, // "baz"
426e73685ebSopenharmony_ci            ],
427e73685ebSopenharmony_ci        );
428e73685ebSopenharmony_ci    }
429e73685ebSopenharmony_ci
430e73685ebSopenharmony_ci    #[test]
431e73685ebSopenharmony_ci    fn line_span_sources() {
432e73685ebSopenharmony_ci        let file = SimpleFile::new("test", TEST_SOURCE);
433e73685ebSopenharmony_ci
434e73685ebSopenharmony_ci        let line_sources = (0..4)
435e73685ebSopenharmony_ci            .map(|line| {
436e73685ebSopenharmony_ci                let line_range = file.line_range((), line).unwrap();
437e73685ebSopenharmony_ci                &file.source[line_range]
438e73685ebSopenharmony_ci            })
439e73685ebSopenharmony_ci            .collect::<Vec<_>>();
440e73685ebSopenharmony_ci
441e73685ebSopenharmony_ci        assert_eq!(line_sources, ["foo\n", "bar\r\n", "\n", "baz"]);
442e73685ebSopenharmony_ci    }
443e73685ebSopenharmony_ci}
444