use std::{
fmt::{
self,
Formatter,
Debug,
Display,
},
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: 0 }
}
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 lines_newline(string: &str) -> Vec<String> {
return string.split("\n").map(|l| l.to_string() + "\n").collect();
}
fn lines(string: &str) -> Vec<String> {
return string.split("\n").map(|l| l.to_string()).collect();
}
fn line_index(string: &str, index: usize) -> Option<(usize, usize)> {
let lines = Span::lines_newline(&string[..index]);
let line = lines.len() - 1;
let col = lines.last()?.chars().count() - 1;
return Some((line, col));
}
}
impl Debug for Span {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::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<'_>) -> fmt::Result {
if self.is_empty() {
panic!("Can't display the section corresponding with an empty Span")
}
let full_source = &self.source.as_ref().unwrap().contents;
let lines = Span::lines(&full_source);
let (start_line, start_col) = match Span::line_index(full_source, self.offset) {
Some(li) => li,
None => unreachable!(),
};
let (end_line, _end_col) = match Span::line_index(full_source, self.end()) {
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.max(1)),
);
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 build(spanneds: &Vec<Spanned<T>>) -> Span {
let spans = spanneds.iter()
.map(|s| s.span.clone())
.collect::<Vec<Span>>();
Span::join(spans)
}
pub fn map<B, E>(self, f: fn(T) -> Result<B, E>) -> Result<Spanned<B>, E> {
Ok(Spanned::new(f(self.item)?, self.span))
}
}
#[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\
"
)
}
}