use std::{path::Path, rc::Rc};
use fragile::Fragile;
pub type CharLocation = (usize, usize);
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn location() {
let location =
Span::new(Path::new("a cool filename"), (0, 3), (1, 6));
assert_eq!(&*location.file(), Path::new("a cool filename"));
assert_eq!(location.start(), (0, 3));
assert_eq!(location.end(), (1, 6));
let location = Span::new(Path::new(""), (0, 0), (0, 0));
assert_eq!(&*location.file(), Path::new(""));
assert_eq!(location.start(), (0, 0));
assert_eq!(location.end(), (0, 0));
}
#[test]
fn location2() {
let location_builder = LocationBuilder::new(Path::new("<input>"), "if true and false {\n\tifeat(\"something\")\n}");
assert_eq!(
location_builder.from(0, 2),
Span::new(Path::new("<input>"), (0, 0), (0, 2))
);
assert_eq!(
location_builder.from(3, 7),
Span::new(Path::new("<input>"), (0, 3), (0, 7))
);
assert_eq!(
location_builder.from(8, 11),
Span::new(Path::new("<input>"), (0, 8), (0, 11))
);
assert_eq!(
location_builder.from(12, 17),
Span::new(Path::new("<input>"), (0, 12), (0, 17))
);
assert_eq!(
location_builder.from(18, 19),
Span::new(Path::new("<input>"), (0, 18), (0, 19))
);
assert_eq!(
location_builder.from(21, 26),
Span::new(Path::new("<input>"), (1, 1), (1, 6))
);
assert_eq!(
location_builder.from(26, 27),
Span::new(Path::new("<input>"), (1, 6), (1, 7))
);
assert_eq!(
location_builder.from(27, 38),
Span::new(Path::new("<input>"), (1, 7), (1, 18))
);
assert_eq!(
location_builder.from(38, 39),
Span::new(Path::new("<input>"), (1, 18), (1, 19))
);
assert_eq!(
location_builder.from(40, 41),
Span::new(Path::new("<input>"), (2, 0), (2, 1))
);
}
#[test]
#[should_panic]
fn wrong_location() {
Span::new(Path::new("some file"), (1, 0), (0, 0));
}
#[test]
#[should_panic]
fn wrong_location2() {
Span::new(Path::new("some file"), (1, 5), (1, 3));
}
}
#[derive(Debug, PartialEq, Eq, Clone)]
pub struct Span {
file: Rc<Path>,
start: CharLocation,
end: CharLocation,
}
impl std::fmt::Display for Span {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "in file {}, ", self.file.display())?;
if self.start.0 == self.end.0 {
if self.start.1+1 == self.end.1 {
write!(
f,
"at character {} of line {}",
self.start.1,
self.start.0+1,
)
} else {
write!(
f,
"at characters {}-{} of line {}",
self.start.1,
self.end.1.checked_sub(1).unwrap_or_default(),
self.start.0+1,
)
}
} else {
write!(
f,
"from character {} of line {} to character {} of line {}",
self.start.1,
self.start.0+1,
self.end.1.checked_sub(1).unwrap_or_default(),
self.end.0+1,
)
}
}
}
impl Span {
pub fn new(
file: impl Into<Rc<Path>>,
start: CharLocation,
end: CharLocation,
) -> Self {
assert!(start.0 < end.0 || (start.0 == end.0 && start.1 <= end.1)); let file = file.into();
Self { file, start, end }
}
pub fn extend(left: Self, right: Self) -> Self {
Self {
file: left.file,
start: left.start,
end: right.end,
}
}
pub fn file(&self) -> Rc<Path> {
self.file.clone()
}
pub fn start(&self) -> CharLocation {
self.start
}
pub fn end(&self) -> CharLocation {
self.end
}
}
impl From<&Span> for Fragile<Span> {
fn from(location: &Span) -> Fragile<Span> {
Fragile::new(location.clone())
}
}
#[derive(Debug)]
pub struct LocationBuilder {
file: Rc<Path>,
newlines: Vec<usize>,
}
impl LocationBuilder {
pub fn new(file: impl Into<Rc<Path>>, stream: impl AsRef<str>) -> Self {
let file = file.into();
let newlines = stream
.as_ref()
.chars()
.enumerate()
.filter(|(_, c)| *c == '\n')
.map(|(i, _)| i)
.collect();
Self { file, newlines }
}
pub fn from(&self, start: usize, end: usize) -> Span {
fn pos_to_char_pos(pos: usize, newlines: &[usize]) -> CharLocation {
let i = match newlines.binary_search(&pos) {
Ok(x) | Err(x) => x,
};
if i == 0 {
(i, pos)
} else {
(i, pos - newlines[i - 1] - 1)
}
}
Span::new(
self.file.clone(),
pos_to_char_pos(start, &self.newlines),
pos_to_char_pos(end, &self.newlines),
)
}
}