use std::ops::Range;
pub trait Files<'a> {
type FileId: 'a + Copy + PartialEq;
type Name: 'a + std::fmt::Display;
type Source: 'a + AsRef<str>;
fn name(&'a self, id: Self::FileId) -> Option<Self::Name>;
fn source(&'a self, id: Self::FileId) -> Option<Self::Source>;
fn line_index(&'a self, id: Self::FileId, byte_index: usize) -> Option<usize>;
#[allow(unused_variables)]
fn line_number(&'a self, id: Self::FileId, line_index: usize) -> Option<usize> {
Some(line_index + 1)
}
fn column_number(
&'a self,
id: Self::FileId,
line_index: usize,
byte_index: usize,
) -> Option<usize> {
let source = self.source(id)?;
let line_range = self.line_range(id, line_index)?;
let column_index = column_index(source.as_ref(), line_range, byte_index);
Some(column_index + 1)
}
fn location(&'a self, id: Self::FileId, byte_index: usize) -> Option<Location> {
let line_index = self.line_index(id, byte_index)?;
Some(Location {
line_number: self.line_number(id, line_index)?,
column_number: self.column_number(id, line_index, byte_index)?,
})
}
fn line_range(&'a self, id: Self::FileId, line_index: usize) -> Option<Range<usize>>;
}
#[derive(Debug, Copy, Clone)]
pub struct Location {
pub line_number: usize,
pub column_number: usize,
}
pub fn column_index(source: &str, line_range: Range<usize>, byte_index: usize) -> usize {
let end_index = std::cmp::min(byte_index, std::cmp::min(line_range.end, source.len()));
(line_range.start..end_index)
.filter(|byte_index| source.is_char_boundary(byte_index + 1))
.count()
}
pub fn line_starts<'source>(source: &'source str) -> impl 'source + Iterator<Item = usize> {
std::iter::once(0).chain(source.match_indices('\n').map(|(i, _)| i + 1))
}
#[derive(Debug, Clone)]
pub struct SimpleFile<Name, Source> {
name: Name,
source: Source,
line_starts: Vec<usize>,
}
impl<Name, Source> SimpleFile<Name, Source>
where
Name: std::fmt::Display,
Source: AsRef<str>,
{
pub fn new(name: Name, source: Source) -> SimpleFile<Name, Source> {
SimpleFile {
name,
line_starts: line_starts(source.as_ref()).collect(),
source,
}
}
pub fn name(&self) -> &Name {
&self.name
}
pub fn source(&self) -> &Source {
&self.source
}
fn line_start(&self, line_index: usize) -> Option<usize> {
use std::cmp::Ordering;
match line_index.cmp(&self.line_starts.len()) {
Ordering::Less => self.line_starts.get(line_index).cloned(),
Ordering::Equal => Some(self.source.as_ref().len()),
Ordering::Greater => None,
}
}
}
impl<'a, Name, Source> Files<'a> for SimpleFile<Name, Source>
where
Name: 'a + std::fmt::Display + Clone,
Source: 'a + AsRef<str>,
{
type FileId = ();
type Name = Name;
type Source = &'a str;
fn name(&self, (): ()) -> Option<Name> {
Some(self.name.clone())
}
fn source(&self, (): ()) -> Option<&str> {
Some(self.source.as_ref())
}
fn line_index(&self, (): (), byte_index: usize) -> Option<usize> {
match self.line_starts.binary_search(&byte_index) {
Ok(line) => Some(line),
Err(next_line) => Some(next_line - 1),
}
}
fn line_range(&self, (): (), line_index: usize) -> Option<Range<usize>> {
let line_start = self.line_start(line_index)?;
let next_line_start = self.line_start(line_index + 1)?;
Some(line_start..next_line_start)
}
}
#[derive(Debug, Clone)]
pub struct SimpleFiles<Name, Source> {
files: Vec<SimpleFile<Name, Source>>,
}
impl<Name, Source> SimpleFiles<Name, Source>
where
Name: std::fmt::Display,
Source: AsRef<str>,
{
pub fn new() -> SimpleFiles<Name, Source> {
SimpleFiles { files: Vec::new() }
}
pub fn add(&mut self, name: Name, source: Source) -> usize {
let file_id = self.files.len();
self.files.push(SimpleFile::new(name, source));
file_id
}
pub fn get(&self, file_id: usize) -> Option<&SimpleFile<Name, Source>> {
self.files.get(file_id)
}
}
impl<'a, Name, Source> Files<'a> for SimpleFiles<Name, Source>
where
Name: 'a + std::fmt::Display + Clone,
Source: 'a + AsRef<str>,
{
type FileId = usize;
type Name = Name;
type Source = &'a str;
fn name(&self, file_id: usize) -> Option<Name> {
Some(self.get(file_id)?.name().clone())
}
fn source(&self, file_id: usize) -> Option<&str> {
Some(self.get(file_id)?.source().as_ref())
}
fn line_index(&self, file_id: usize, byte_index: usize) -> Option<usize> {
self.get(file_id)?.line_index((), byte_index)
}
fn line_range(&self, file_id: usize, line_index: usize) -> Option<Range<usize>> {
self.get(file_id)?.line_range((), line_index)
}
}
#[cfg(test)]
mod test {
use super::*;
const TEST_SOURCE: &str = "foo\nbar\r\n\nbaz";
#[test]
fn line_starts() {
let file = SimpleFile::new("test", TEST_SOURCE);
assert_eq!(
file.line_starts,
[
0, 4, 9, 10, ],
);
}
#[test]
fn line_span_sources() {
let file = SimpleFile::new("test", TEST_SOURCE);
let line_sources = (0..4)
.map(|line| {
let line_range = file.line_range((), line).unwrap();
&file.source[line_range]
})
.collect::<Vec<_>>();
assert_eq!(line_sources, ["foo\n", "bar\r\n", "\n", "baz"]);
}
}