use codespan_reporting::files;
use codespan_reporting::files::{Files, SimpleFile};
use std::cmp::Ordering;
use std::io::Read;
use std::iter;
use std::ops::{Index, Range};
use std::path::Path;
#[derive(Clone, Debug)]
struct LineDirective {
line_index: usize,
byte_index: usize,
offset: isize,
filename: Option<Range<usize>>,
}
#[derive(Clone, Debug, PartialEq)]
pub struct FileSlice {
name: Range<usize>,
bytes: Range<usize>,
lines: Range<usize>,
offset: isize,
}
#[derive(Debug)]
pub struct PreprocessedFile<Source> {
ids: Vec<FileSlice>,
lines: Vec<Range<usize>>,
contents: Source,
}
impl<'a, S: 'a + AsRef<str>> Files<'a> for PreprocessedFile<S> {
type FileId = &'a FileSlice;
type Name = &'a str;
type Source = &'a str;
fn name(&'a self, id: Self::FileId) -> Result<Self::Name, files::Error> {
Ok(self.contents.as_ref().index(id.name.clone()))
}
fn source(&'a self, _: Self::FileId) -> Result<Self::Source, files::Error> {
Ok(self.contents.as_ref())
}
fn line_index(&'a self, id: Self::FileId, byte_index: usize) -> Result<usize, files::Error> {
if id.bytes.end <= byte_index {
Ok((id.lines.end as isize - 1 - id.offset) as usize)
} else if byte_index < id.bytes.start {
Err(files::Error::FileMissing)
} else {
Ok((self
.lines
.binary_search_by(|bytes| {
if byte_index < bytes.start {
Ordering::Greater
} else if byte_index > bytes.end {
Ordering::Less
} else {
Ordering::Equal
}
})
.unwrap() as isize
- id.offset) as usize)
}
}
fn line_range(
&'a self,
id: Self::FileId,
line_index: usize,
) -> Result<Range<usize>, files::Error> {
self.lines
.get((line_index as isize + id.offset) as usize)
.cloned()
.ok_or(files::Error::LineTooLarge {
given: line_index,
max: self.lines.len(),
})
}
}
impl<Source> PreprocessedFile<Source>
where
Source: AsRef<str>,
{
pub fn new(contents: Source) -> Self {
let mut line_endings = contents
.as_ref()
.match_indices('\n')
.map(|(b, _)| b)
.collect::<Vec<_>>();
match line_endings.last() {
Some(l) if *l == contents.as_ref().len() - 1 => {}
_ => line_endings.push(contents.as_ref().len()),
}
let line_ranges = iter::once(0)
.chain(line_endings.iter().map(|e| *e + 1))
.zip(line_endings.iter())
.map(|(s, e)| s..*e)
.collect::<Vec<_>>();
let directives = line_ranges
.iter()
.enumerate()
.filter(|(_, r)| contents.as_ref()[r.start..r.end].starts_with("#line"))
.map(|(l, r)| {
let str = &contents.as_ref()[r.start..r.end];
if let Some(sep) = str[6..].find(' ') {
let sep = sep + 6;
LineDirective {
line_index: l,
byte_index: r.start,
offset: l as isize + 2 - str[6..sep].parse::<isize>().unwrap(),
filename: Some(r.start + sep + 2..r.start + str.len() - 1),
}
} else {
LineDirective {
line_index: l,
byte_index: r.start,
offset: l as isize + 2 - str[6..].parse::<isize>().unwrap(),
filename: None,
}
}
})
.collect::<Vec<_>>();
let mut current = 0..0;
let mut files = Vec::with_capacity(directives.len() + 2);
if let Some(first) = directives.first() {
if first.line_index > 0 {
files.push(FileSlice {
name: current.clone(),
bytes: 0..first.byte_index,
lines: 0..first.line_index,
offset: 0,
});
}
files.extend(
directives
.iter()
.zip(directives.iter().skip(1))
.map(|(start, end)| {
if let Some(filename) = start.filename.clone() {
current = filename;
}
FileSlice {
name: current.clone(),
bytes: line_ranges[start.line_index + 1].start..end.byte_index,
lines: start.line_index + 1..end.line_index,
offset: start.offset,
}
}),
);
let last_directive = directives.last().unwrap();
if last_directive.line_index + 1 < line_ranges.len() {
files.push(FileSlice {
name: last_directive.filename.clone().unwrap_or(current),
bytes: line_ranges[last_directive.line_index + 1].start
..line_ranges.last().unwrap().end,
lines: last_directive.line_index + 1..line_ranges.len(),
offset: last_directive.offset,
});
}
} else {
files.push(FileSlice {
name: current,
bytes: 0..line_ranges.last().unwrap().end,
lines: 0..line_ranges.len(),
offset: 0,
})
}
PreprocessedFile {
ids: files,
lines: line_ranges,
contents,
}
}
#[inline]
pub fn source(&self) -> &str {
self.contents.as_ref()
}
#[inline]
pub fn len(&self) -> usize {
self.source().len()
}
#[inline]
pub fn is_empty(&self) -> bool {
self.source().is_empty()
}
}
impl PreprocessedFile<String> {
pub fn open<P: AsRef<Path>>(filename: P) -> Result<Self, std::io::Error> {
let mut file = std::fs::File::open(&filename)?;
let mut buf = Vec::new();
file.read_to_end(&mut buf)?;
let contents = format!(
"#line 1 \"{}\"\n{}",
filename.as_ref().to_string_lossy(),
String::from_utf8(buf).expect("invalid UTF-8 characters in file")
);
Ok(PreprocessedFile::new(contents))
}
pub fn from_stdin() -> Result<Self, std::io::Error> {
let mut buf = Vec::new();
std::io::stdin().read_to_end(&mut buf)?;
let contents = String::from_utf8(buf).expect("invalid UTF-8 characters on stdin");
Ok(PreprocessedFile::new(contents))
}
}
pub trait EasyLocation<'a>: Files<'a> {
fn file_id(&'a self, byte_index: usize) -> <Self as Files<'a>>::FileId;
}
impl<'a, S: 'a + AsRef<str>> EasyLocation<'a> for PreprocessedFile<S> {
fn file_id(&'a self, byte_index: usize) -> <Self as Files<'a>>::FileId {
match self.ids.binary_search_by(|x| {
if byte_index < x.bytes.start {
Ordering::Greater
} else if byte_index > x.bytes.end {
Ordering::Less
} else {
Ordering::Equal
}
}) {
Ok(i) => &self.ids[i],
Err(i) if i < self.ids.len() => &self.ids[i],
_ => self.ids.last().unwrap(),
}
}
}
impl<'a, N, S> EasyLocation<'a> for SimpleFile<N, S>
where
N: 'a + std::fmt::Display + Clone,
S: 'a + AsRef<str>,
{
fn file_id(&'a self, _: usize) -> <Self as Files<'a>>::FileId {}
}