use std::{
fmt::{
Formatter,
Debug,
Display,
Result
},
usize,
rc::Rc,
};
use crate::common::source::Source;
#[derive(Clone, Eq, PartialEq)]
pub struct Span {
pub source: Option<Rc<Source>>,
pub offset: usize,
pub length: usize,
}
impl Span {
pub fn new(source: &Rc<Source>, offset: usize, length: usize) -> Span {
Span { source: Some(Rc::clone(source)), offset, length }
}
pub fn point(source: &Rc<Source>, offset: usize) -> Span {
Span { source: Some(Rc::clone(source)), offset, length: 1 }
}
pub fn empty() -> Span {
Span { source: None, offset: 0, length: usize::MAX }
}
pub fn is_empty(&self) -> bool {
self.source == None
}
pub fn end(&self) -> usize {
self.offset + self.length
}
pub fn later_than(&self, other: &Span) -> bool {
self.offset > other.offset
|| (self.offset == other.offset
&& self.end() > other.end())
}
pub fn combine(a: &Span, b: &Span) -> Span {
if a.is_empty() { return b.clone(); }
if b.is_empty() { return a.clone(); }
if a.source != b.source {
panic!("Can't combine two Spans with separate sources")
}
let offset = a.offset.min(b.offset);
let end = a.end().max(b.end());
let length = end - offset;
return Span::new(&a.source.as_ref().unwrap(), offset, length);
}
pub fn join(mut spans: Vec<Span>) -> Span {
let mut combined = match spans.pop() {
Some(span) => span,
None => return Span::empty(),
};
while let Some(span) = spans.pop() {
combined = Span::combine(&combined, &span)
}
return combined;
}
pub fn contents(&self) -> String {
if self.is_empty() { panic!("An empty span does not have any contents") }
self.source.as_ref().unwrap().contents[self.offset..(self.end())].to_string()
}
fn split_lines_inclusive(string: &str) -> Vec<&str> {
let newline = "\n";
let mut indicies: Vec<usize> = vec![0];
indicies.append(&mut string
.match_indices(newline).collect::<Vec<(usize, &str)>>()
.into_iter().map(|(s, _)| s + newline.len()).collect()
);
indicies.push(string.len());
let mut lines = vec![];
for i in 0..(indicies.len() - 1) {
lines.push(&string[indicies[i]..indicies[i + 1]]);
}
return lines;
}
fn line_indicies(&self) -> Option<((usize, usize), (usize, usize))> {
if self.is_empty() { panic!("Can not return the line indicies of an empty span") }
let start = self.offset;
let end = self.end();
let full_source = &self.source.as_ref().unwrap().contents;
let start_lines: Vec<&str> = Span::split_lines_inclusive(&full_source[..=start]);
let end_lines: Vec<&str> = Span::split_lines_inclusive(&full_source[..end]);
let start_line = start_lines.len() - 1;
let end_line = end_lines.len() - 1;
let start_col = start_lines.last()?.len() - 1;
let end_col = end_lines.last()?.len() - 1;
return Some(((start_line, start_col), (end_line, end_col)));
}
}
impl Debug for Span {
fn fmt(&self, f: &mut Formatter<'_>) -> Result {
if !self.is_empty() {
write!(f, "Span {{ {:?}, ({}, {}) }}", self.contents(), self.offset, self.length)
} else {
write!(f, "Span {{ Empty }}")
}
}
}
impl Display for Span {
fn fmt(&self, f: &mut Formatter<'_>) -> Result {
if self.is_empty() {
panic!("Can't display the section corresponding with an empty Span")
}
let lines: Vec<&str> = self.source.as_ref().unwrap().contents.lines().collect();
let ((start_line, start_col), (end_line, _end_col)) = match self.line_indicies() {
Some(li) => li,
None => unreachable!(),
};
let readable_start_line = (start_line + 1).to_string();
let readable_end_line = (end_line + 1).to_string();
let readable_start_col = (start_col + 1).to_string();
let padding = readable_end_line.len();
let location = format!(
"In {}:{}:{}",
self.source.clone().unwrap()
.path.to_string_lossy(),
readable_start_line,
readable_start_col
);
let separator = format!(" {} |", " ".repeat(padding));
if start_line == end_line {
let l = lines[end_line];
let line = format!(" {} | {}", readable_end_line, l);
let span = format!(
" {} | {}{}",
" ".repeat(padding),
" ".repeat(start_col),
"^".repeat(self.length),
);
writeln!(f, "{}", location)?;
writeln!(f, "{}", separator)?;
writeln!(f, "{}", line)?;
writeln!(f, "{}", span)?;
writeln!(f, "{}", separator)
} else {
let formatted = lines[start_line..=end_line]
.iter()
.enumerate()
.map(|(i, l)| {
let readable_line_no = (start_line + i + 1).to_string();
let partial_padding = " ".repeat(padding - readable_line_no.len());
format!(" {}{} > {}", partial_padding, readable_line_no, l)
})
.collect::<Vec<String>>()
.join("\n");
writeln!(f, "{}", location)?;
writeln!(f, "{}", separator)?;
writeln!(f, "{}", formatted)?;
writeln!(f, "{}", separator)
}
}
}
#[derive(Debug, Clone, Eq, PartialEq)]
pub struct Spanned<T> {
pub item: T,
pub span: Span,
}
impl<T> Spanned<T> {
pub fn new(item: T, span: Span) -> Spanned<T> {
Spanned { item, span }
}
pub fn into(self) -> T { self.item }
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn combination() {
let source = Source::source("heck, that's awesome");
let a = Span::new(&source, 0, 5);
let b = Span::new(&source, 11, 2);
assert_eq!(Span::combine(&a, &b), Span::new(&source, 0, 13));
}
#[test]
fn span_and_contents() {
let source = Source::source("hello, this is some text!");
let spans = vec![
Span::new(&source, 0, 8),
Span::new(&source, 7, 5),
Span::new(&source, 12, 4),
];
let result = Span::new(&source, 0, 16);
assert_eq!(Span::join(spans).contents(), result.contents());
}
#[test]
fn display() {
let source = Source::source("hello\nbanana boat\nmagination\n");
let span = Span::new(&source, 16, 12);
assert_eq!(format!("{}", span), "\
In ./source:2:11\n \
|\n \
2 > banana boat\n \
3 > magination\n \
|\n\
"
)
}
}