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