use std::{
ffi::{OsStr, OsString},
path::PathBuf,
rc::Rc,
sync::Arc,
};
use codespan::ByteIndex;
use codespan_reporting::files::Error;
use nickel_lang_vector::Vector;
use crate::position::RawSpan;
#[derive(
Copy, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, serde::Serialize, serde::Deserialize,
)]
pub struct FileId(u32);
#[derive(Debug, Clone)]
struct File {
name: OsString,
source: Arc<str>,
line_starts: Rc<[ByteIndex]>,
}
impl File {
fn new(name: impl Into<OsString>, source: impl Into<Arc<str>>) -> Self {
let source = source.into();
let line_starts: Vec<_> = std::iter::once(ByteIndex(0))
.chain(
source
.match_indices('\n')
.map(|(i, _)| ByteIndex(i as u32 + 1)),
)
.collect();
File {
name: name.into(),
line_starts: line_starts.into(),
source,
}
}
fn line_index(&self, byte_index: ByteIndex) -> usize {
match self.line_starts.binary_search(&byte_index) {
Ok(line) => line,
Err(next_line) => next_line.checked_sub(1).unwrap(),
}
}
}
#[derive(Debug, Clone, Default)]
pub struct Files {
files: Vector<File, 8>,
first_non_stdlib: usize,
}
impl Files {
pub fn empty() -> Self {
Files::default()
}
pub fn new<Name, Contents, I>(stdlib_modules: I) -> Self
where
Name: Into<OsString>,
Contents: Into<Arc<str>>,
I: IntoIterator<Item = (Name, Contents)>,
{
let files: Vector<_, 8> = stdlib_modules
.into_iter()
.map(|(name, contents)| File::new(name, contents))
.collect();
Files {
first_non_stdlib: files.len(),
files,
}
}
pub fn is_stdlib(&self, id: FileId) -> bool {
(id.0 as usize) < self.first_non_stdlib
}
pub fn stdlib_modules(&self) -> impl Iterator<Item = FileId> + use<> {
(0..self.first_non_stdlib).map(|id| FileId(id as u32))
}
pub fn add(&mut self, name: impl Into<OsString>, source: impl Into<Arc<str>>) -> FileId {
let file_id = FileId(self.files.len() as u32);
self.files.push(File::new(name, source));
file_id
}
pub fn update(&mut self, file_id: FileId, source: impl Into<Arc<str>>) {
let mut old = self.get(file_id).unwrap().clone();
old = File::new(old.name, source);
self.files.set(file_id.0 as usize, old);
}
pub fn source_span(&self, file_id: FileId) -> RawSpan {
let len = self.get(file_id).unwrap().source.len();
RawSpan {
src_id: file_id,
start: ByteIndex(0),
end: ByteIndex(len as u32),
}
}
pub fn name(&self, id: FileId) -> &OsStr {
&self.get(id).unwrap().name
}
pub fn source(&self, id: FileId) -> &str {
self.get(id).unwrap().source.as_ref()
}
pub fn clone_source(&self, id: FileId) -> Arc<str> {
self.get(id).unwrap().source.clone()
}
pub fn source_slice(&self, span: RawSpan) -> &str {
let start: usize = span.start.into();
let end: usize = span.end.into();
&self.source(span.src_id)[start..end]
}
pub fn location(
&self,
id: FileId,
byte_index: impl Into<ByteIndex>,
) -> Result<codespan::Location, Error> {
let file = self.get(id)?;
let byte_index = byte_index.into();
let idx = byte_index.to_usize();
if idx >= file.source.len() {
return Err(Error::IndexTooLarge {
given: idx,
max: file.source.len() - 1,
});
}
let line_idx = file.line_index(byte_index);
let line_start_idx = file.line_starts[line_idx];
let line = file
.source
.get(line_start_idx.to_usize()..idx)
.ok_or(Error::InvalidCharBoundary { given: idx })?;
Ok(codespan::Location {
line: codespan::LineIndex::from(line_idx as u32),
column: codespan::ColumnIndex::from(line.chars().count() as u32),
})
}
fn get(&self, id: FileId) -> Result<&File, Error> {
self.files.get(id.0 as usize).ok_or(Error::FileMissing)
}
pub fn filenames(&self) -> impl Iterator<Item = &OsStr> {
self.files.iter().map(|f| &*f.name)
}
}
impl<'a> codespan_reporting::files::Files<'a> for Files {
type FileId = FileId;
type Name = String;
type Source = &'a str;
fn name(&'a self, id: Self::FileId) -> Result<String, Error> {
Ok(PathBuf::from(&self.get(id)?.name).display().to_string())
}
fn source(
&'a self,
id: Self::FileId,
) -> Result<Self::Source, codespan_reporting::files::Error> {
Ok(self.get(id)?.source.as_ref())
}
fn line_index(
&'a self,
id: Self::FileId,
byte_index: usize,
) -> Result<usize, codespan_reporting::files::Error> {
let file = self.get(id)?;
Ok(file.line_index(ByteIndex(byte_index as u32)))
}
fn line_range(
&'a self,
id: Self::FileId,
line_index: usize,
) -> Result<std::ops::Range<usize>, codespan_reporting::files::Error> {
let file = self.get(id)?;
let starts = &file.line_starts;
let end = starts
.get(line_index + 1)
.copied()
.unwrap_or(ByteIndex(file.source.len() as u32));
Ok(starts[line_index].into()..end.into())
}
}