use std::{path::Path, rc::Rc};
use fragile::Fragile;
pub type Location = (usize, usize);
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn location() {
let input: Rc<str>= Rc::from(
"01234
56789abcdef",
);
let lines: Rc<[usize]> = vec![
0,
6
].into();
let span = Span::new(
Path::new("a cool filename"),
(0, 3),
(1, 6),
3,
11,
input.clone(),
lines.clone(),
);
assert_eq!(&*span.file(), Path::new("a cool filename"));
assert_eq!(span.start(), (0, 3));
assert_eq!(span.end(), (1, 6));
assert_eq!(span.start_byte(), 3);
assert_eq!(span.end_byte(), 11);
assert_eq!(span.text(), &*input);
assert_eq!(span.lines(), &*lines);
let span = Span::new(
Path::new(""),
(0, 0),
(0, 0),
0,
0,
input.clone(),
lines.clone(),
);
assert_eq!(&*span.file(), Path::new(""));
assert_eq!(span.start(), (0, 0));
assert_eq!(span.end(), (0, 0));
assert_eq!(span.start_byte(), 0);
assert_eq!(span.end_byte(), 0);
assert_eq!(span.text(), &*input);
assert_eq!(span.lines(), &*lines);
}
#[test]
#[should_panic]
fn wrong_location() {
Span::new(Path::new("some file"), (1, 0), (0, 0), 1, 0, "", Vec::new());
}
#[test]
#[should_panic]
fn wrong_location2() {
Span::new(Path::new("some file"), (1, 5), (1, 3), 8, 6, "", Vec::new());
}
}
#[derive(Debug, PartialEq, Eq, Clone)]
pub struct Span {
file: Rc<Path>,
start: Location,
end: Location,
start_byte: usize,
end_byte: usize,
text: Rc<str>,
lines: Rc<[usize]>,
}
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 == 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,
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,
self.end.0 + 1,
)
}
}
}
impl Span {
pub fn new(
file: impl Into<Rc<Path>>,
start: Location,
end: Location,
start_byte: usize,
end_byte: usize,
text: impl Into<Rc<str>>,
lines: impl Into<Rc<[usize]>>,
) -> Self {
assert!(start.0 < end.0 || (start.0 == end.0 && start.1 <= end.1)); let file = file.into();
let text = text.into();
let lines = lines.into();
Self {
file,
start,
end,
start_byte,
end_byte,
text,
lines,
}
}
pub fn sup(&self, other: &Self) -> Self {
Self {
file: self.file.clone(),
start_byte: self.start_byte.min(other.start_byte),
end_byte: self.end_byte.max(other.end_byte),
start: self.start.min(other.start),
end: self.end.max(other.end),
text: self.text.clone(),
lines: self.lines.clone(),
}
}
pub fn file(&self) -> Rc<Path> {
self.file.clone()
}
pub fn start(&self) -> Location {
self.start
}
pub fn end(&self) -> Location {
self.end
}
pub fn start_byte(&self) -> usize {
self.start_byte
}
pub fn end_byte(&self) -> usize {
self.end_byte
}
pub fn line_bytes_of_line(&self, line_number: usize) -> (usize, usize) {
let start = self.lines[line_number];
let end = if line_number+1 == self.lines.len() {
self.text.len()
} else {
self.lines[line_number+1]
};
(start, end)
}
pub fn text(&self) -> &str {
&self.text
}
pub fn lines(&self) -> &[usize] {
&self.lines
}
}
impl From<&Span> for Fragile<Span> {
fn from(location: &Span) -> Fragile<Span> {
Fragile::new(location.clone())
}
}