use std::borrow::Cow;
use std::path::{Path, PathBuf};
use std::{fmt, io};
#[cfg(feature = "memory_usage")]
use heapsize::{self, HeapSizeOf};
use index::{ByteIndex, ByteOffset, ColumnIndex, LineIndex, LineOffset, RawIndex, RawOffset};
use span::ByteSpan;
#[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
#[cfg_attr(feature = "serialization", derive(Deserialize, Serialize))]
pub enum FileName {
Real(PathBuf),
Virtual(Cow<'static, str>),
}
impl From<PathBuf> for FileName {
fn from(name: PathBuf) -> FileName {
FileName::real(name)
}
}
impl From<FileName> for PathBuf {
fn from(name: FileName) -> PathBuf {
match name {
FileName::Real(path) => path,
FileName::Virtual(Cow::Owned(owned)) => PathBuf::from(owned),
FileName::Virtual(Cow::Borrowed(borrowed)) => PathBuf::from(borrowed),
}
}
}
impl<'a> From<&'a FileName> for &'a Path {
fn from(name: &'a FileName) -> &'a Path {
match *name {
FileName::Real(ref path) => path,
FileName::Virtual(ref cow) => Path::new(cow.as_ref()),
}
}
}
impl<'a> From<&'a Path> for FileName {
fn from(name: &Path) -> FileName {
FileName::real(name)
}
}
impl From<String> for FileName {
fn from(name: String) -> FileName {
FileName::virtual_(name)
}
}
impl From<&'static str> for FileName {
fn from(name: &'static str) -> FileName {
FileName::virtual_(name)
}
}
impl AsRef<Path> for FileName {
fn as_ref(&self) -> &Path {
match *self {
FileName::Real(ref path) => path.as_ref(),
FileName::Virtual(ref cow) => Path::new(cow.as_ref()),
}
}
}
impl PartialEq<Path> for FileName {
fn eq(&self, other: &Path) -> bool {
self.as_ref() == other
}
}
impl PartialEq<PathBuf> for FileName {
fn eq(&self, other: &PathBuf) -> bool {
self.as_ref() == other.as_path()
}
}
impl FileName {
pub fn real<T: Into<PathBuf>>(name: T) -> FileName {
FileName::Real(name.into())
}
pub fn virtual_<T: Into<Cow<'static, str>>>(name: T) -> FileName {
FileName::Virtual(name.into())
}
}
impl fmt::Display for FileName {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
match *self {
FileName::Real(ref path) => write!(fmt, "{}", path.display()),
FileName::Virtual(ref name) => write!(fmt, "<{}>", name),
}
}
}
#[cfg(feature = "memory_usage")]
impl HeapSizeOf for FileName {
fn heap_size_of_children(&self) -> usize {
match *self {
FileName::Virtual(ref s) => s.heap_size_of_children(),
FileName::Real(ref path) => {
path.to_str()
.map(|s| unsafe { heapsize::heap_size_of(s.as_ptr()) })
.unwrap_or(0)
},
}
}
}
#[derive(Debug, Fail, PartialEq)]
pub enum LineIndexError {
#[fail(display = "Line out of bounds - given: {:?}, max: {:?}", given, max)]
OutOfBounds { given: LineIndex, max: LineIndex },
}
#[derive(Debug, Fail, PartialEq)]
pub enum ByteIndexError {
#[fail(
display = "Byte index out of bounds - given: {}, span: {}",
given, span
)]
OutOfBounds { given: ByteIndex, span: ByteSpan },
#[fail(
display = "Byte index points within a character boundary - given: {}",
given
)]
InvalidCharBoundary { given: ByteIndex },
}
#[derive(Debug, Fail, PartialEq)]
pub enum LocationError {
#[fail(display = "Line out of bounds - given: {:?}, max: {:?}", given, max)]
LineOutOfBounds { given: LineIndex, max: LineIndex },
#[fail(display = "Column out of bounds - given: {:?}, max: {:?}", given, max)]
ColumnOutOfBounds {
given: ColumnIndex,
max: ColumnIndex,
},
}
#[derive(Debug, Fail, PartialEq)]
pub enum SpanError {
#[fail(display = "Span out of bounds - given: {}, span: {}", given, span)]
OutOfBounds { given: ByteSpan, span: ByteSpan },
}
#[derive(Debug)]
#[cfg_attr(feature = "serialization", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "memory_usage", derive(HeapSizeOf))]
pub struct FileMap<S = String> {
name: FileName,
src: S,
span: ByteSpan,
lines: Vec<ByteOffset>,
}
impl FileMap {
pub(crate) fn from_disk<P: Into<PathBuf>>(name: P, start: ByteIndex) -> io::Result<FileMap> {
use std::fs::File;
use std::io::Read;
let name = name.into();
let mut file = File::open(&name)?;
let mut src = String::new();
file.read_to_string(&mut src)?;
Ok(FileMap::with_index(FileName::Real(name), src, start))
}
}
impl<S> FileMap<S>
where
S: AsRef<str>,
{
pub fn new(name: FileName, src: S) -> FileMap<S> {
FileMap::with_index(name, src, ByteIndex(1))
}
pub(crate) fn with_index(name: FileName, src: S, start: ByteIndex) -> FileMap<S> {
use std::iter;
let span = ByteSpan::from_offset(start, ByteOffset::from_str(src.as_ref()));
let lines = {
let newline_off = ByteOffset::from_char_utf8('\n');
let offsets = src
.as_ref()
.match_indices('\n')
.map(|(i, _)| ByteOffset(i as RawOffset) + newline_off);
iter::once(ByteOffset(0)).chain(offsets).collect()
};
FileMap {
name,
src,
span,
lines,
}
}
pub fn name(&self) -> &FileName {
&self.name
}
pub fn src(&self) -> &str {
&self.src.as_ref()
}
pub fn span(&self) -> ByteSpan {
self.span
}
pub fn offset(
&self,
line: LineIndex,
column: ColumnIndex,
) -> Result<ByteOffset, LocationError> {
self.byte_index(line, column)
.map(|index| index - self.span.start())
}
pub fn byte_index(
&self,
line: LineIndex,
column: ColumnIndex,
) -> Result<ByteIndex, LocationError> {
self.line_span(line)
.map_err(
|LineIndexError::OutOfBounds { given, max }| LocationError::LineOutOfBounds {
given,
max,
},
)
.and_then(|span| {
let distance = ColumnIndex(span.end().0 - span.start().0);
if column > distance {
Err(LocationError::ColumnOutOfBounds {
given: column,
max: distance,
})
} else {
Ok(span.start() + ByteOffset::from(column.0 as i64))
}
})
}
pub fn line_offset(&self, index: LineIndex) -> Result<ByteOffset, LineIndexError> {
self.lines
.get(index.to_usize())
.cloned()
.ok_or_else(|| LineIndexError::OutOfBounds {
given: index,
max: LineIndex(self.lines.len() as RawIndex - 1),
})
}
pub fn line_byte_index(&self, index: LineIndex) -> Result<ByteIndex, LineIndexError> {
self.line_offset(index)
.map(|offset| self.span.start() + offset)
}
pub fn line_span(&self, line: LineIndex) -> Result<ByteSpan, LineIndexError> {
let start = self.span.start() + self.line_offset(line)?;
let end = match self.line_offset(line + LineOffset(1)) {
Ok(offset_hi) => self.span.start() + offset_hi,
Err(_) => self.span.end(),
};
Ok(ByteSpan::new(end, start))
}
pub fn location(&self, index: ByteIndex) -> Result<(LineIndex, ColumnIndex), ByteIndexError> {
let line_index = self.find_line(index)?;
let line_span = self.line_span(line_index).unwrap();
let line_slice = self.src_slice(line_span).unwrap();
let byte_col = index - line_span.start();
let column_index =
ColumnIndex(line_slice[..byte_col.to_usize()].chars().count() as RawIndex);
Ok((line_index, column_index))
}
pub fn find_line(&self, index: ByteIndex) -> Result<LineIndex, ByteIndexError> {
if index < self.span.start() || index > self.span.end() {
Err(ByteIndexError::OutOfBounds {
given: index,
span: self.span,
})
} else {
let offset = index - self.span.start();
if self.src.as_ref().is_char_boundary(offset.to_usize()) {
match self.lines.binary_search(&offset) {
Ok(i) => Ok(LineIndex(i as RawIndex)),
Err(i) => Ok(LineIndex(i as RawIndex - 1)),
}
} else {
Err(ByteIndexError::InvalidCharBoundary {
given: self.span.start(),
})
}
}
}
pub fn src_slice(&self, span: ByteSpan) -> Result<&str, SpanError> {
if self.span.contains(span) {
let start = (span.start() - self.span.start()).to_usize();
let end = (span.end() - self.span.start()).to_usize();
Ok(&self.src.as_ref()[start..end])
} else {
Err(SpanError::OutOfBounds {
given: span,
span: self.span,
})
}
}
}
#[cfg(test)]
mod tests {
use std::sync::Arc;
use {CodeMap, FileMap, FileName};
use super::*;
struct TestData {
filemap: Arc<FileMap>,
lines: &'static [&'static str],
}
impl TestData {
fn new() -> TestData {
let mut codemap = CodeMap::new();
let lines = &[
"hello!\n",
"howdy\n",
"\r\n",
"hi萤\n",
"bloop\n",
"goopey\r\n",
];
let filemap = codemap.add_filemap(FileName::Virtual("test".into()), lines.concat());
TestData { filemap, lines }
}
fn byte_offsets(&self) -> Vec<ByteOffset> {
let mut offset = ByteOffset(0);
let mut byte_offsets = Vec::new();
for line in self.lines {
byte_offsets.push(offset);
offset += ByteOffset::from_str(line);
let line_end = if line.ends_with("\r\n") {
offset + -ByteOffset::from_char_utf8('\r') + -ByteOffset::from_char_utf8('\n')
} else if line.ends_with("\n") {
offset + -ByteOffset::from_char_utf8('\n')
} else {
offset
};
byte_offsets.push(line_end);
}
byte_offsets.push(offset);
byte_offsets
}
fn byte_indices(&self) -> Vec<ByteIndex> {
let mut offsets = vec![ByteIndex::none()];
offsets.extend(self.byte_offsets().iter().map(|&off| ByteIndex(1) + off));
let out_of_bounds = *offsets.last().unwrap() + ByteOffset(1);
offsets.push(out_of_bounds);
offsets
}
fn line_indices(&self) -> Vec<LineIndex> {
(0..self.lines.len() + 2)
.map(|i| LineIndex(i as RawIndex))
.collect()
}
}
#[test]
fn offset() {
let test_data = TestData::new();
assert!(test_data
.filemap
.offset(
(test_data.lines.len() as u32 - 1).into(),
(test_data.lines.last().unwrap().len() as u32).into()
)
.is_ok());
assert!(test_data
.filemap
.offset(
(test_data.lines.len() as u32 - 1).into(),
(test_data.lines.last().unwrap().len() as u32 + 1).into()
)
.is_err());
}
#[test]
fn line_offset() {
let test_data = TestData::new();
let offsets: Vec<_> = test_data
.line_indices()
.iter()
.map(|&i| test_data.filemap.line_offset(i))
.collect();
assert_eq!(
offsets,
vec![
Ok(ByteOffset(0)),
Ok(ByteOffset(7)),
Ok(ByteOffset(13)),
Ok(ByteOffset(15)),
Ok(ByteOffset(21)),
Ok(ByteOffset(27)),
Ok(ByteOffset(35)),
Err(LineIndexError::OutOfBounds {
given: LineIndex(7),
max: LineIndex(6),
}),
],
);
}
#[test]
fn line_byte_index() {
let test_data = TestData::new();
let offsets: Vec<_> = test_data
.line_indices()
.iter()
.map(|&i| test_data.filemap.line_byte_index(i))
.collect();
assert_eq!(
offsets,
vec![
Ok(test_data.filemap.span().start() + ByteOffset(0)),
Ok(test_data.filemap.span().start() + ByteOffset(7)),
Ok(test_data.filemap.span().start() + ByteOffset(13)),
Ok(test_data.filemap.span().start() + ByteOffset(15)),
Ok(test_data.filemap.span().start() + ByteOffset(21)),
Ok(test_data.filemap.span().start() + ByteOffset(27)),
Ok(test_data.filemap.span().start() + ByteOffset(35)),
Err(LineIndexError::OutOfBounds {
given: LineIndex(7),
max: LineIndex(6),
}),
],
);
}
#[test]
fn location() {
let test_data = TestData::new();
let lines: Vec<_> = test_data
.byte_indices()
.iter()
.map(|&index| test_data.filemap.location(index))
.collect();
assert_eq!(
lines,
vec![
Err(ByteIndexError::OutOfBounds {
given: ByteIndex(0),
span: test_data.filemap.span(),
}),
Ok((LineIndex(0), ColumnIndex(0))),
Ok((LineIndex(0), ColumnIndex(6))),
Ok((LineIndex(1), ColumnIndex(0))),
Ok((LineIndex(1), ColumnIndex(5))),
Ok((LineIndex(2), ColumnIndex(0))),
Ok((LineIndex(2), ColumnIndex(0))),
Ok((LineIndex(3), ColumnIndex(0))),
Ok((LineIndex(3), ColumnIndex(3))),
Ok((LineIndex(4), ColumnIndex(0))),
Ok((LineIndex(4), ColumnIndex(5))),
Ok((LineIndex(5), ColumnIndex(0))),
Ok((LineIndex(5), ColumnIndex(6))),
Ok((LineIndex(6), ColumnIndex(0))),
Err(ByteIndexError::OutOfBounds {
given: ByteIndex(37),
span: test_data.filemap.span()
}),
],
);
}
#[test]
fn find_line() {
let test_data = TestData::new();
let lines: Vec<_> = test_data
.byte_indices()
.iter()
.map(|&index| test_data.filemap.find_line(index))
.collect();
assert_eq!(
lines,
vec![
Err(ByteIndexError::OutOfBounds {
given: ByteIndex(0),
span: test_data.filemap.span(),
}),
Ok(LineIndex(0)),
Ok(LineIndex(0)),
Ok(LineIndex(1)),
Ok(LineIndex(1)),
Ok(LineIndex(2)),
Ok(LineIndex(2)),
Ok(LineIndex(3)),
Ok(LineIndex(3)),
Ok(LineIndex(4)),
Ok(LineIndex(4)),
Ok(LineIndex(5)),
Ok(LineIndex(5)),
Ok(LineIndex(6)),
Err(ByteIndexError::OutOfBounds {
given: ByteIndex(37),
span: test_data.filemap.span(),
}),
],
);
}
}