use crate::grammar_util::*;
use std::fmt::{self, Display};
use std::path::Path;
fn line_locations<'a>(source: &'a str) -> impl 'a + Clone + Iterator<Item = Location> {
source.lines().scan(0, |pos, line| {
let begin = *pos;
*pos += line.len();
let end = *pos;
*pos += 1;
Some(Location(begin, end))
})
}
fn intersecting_line_locations<'a>(
location: Location,
source: &'a str,
) -> impl 'a + Clone + Iterator<Item = (usize, Location)> {
let intersects = move |line_loc: Location| -> bool { line_loc.intersect(location).is_some() };
line_locations(source)
.enumerate()
.skip_while(move |(_, line_loc)| !intersects(*line_loc))
.take_while(move |(_, line_loc)| intersects(*line_loc))
}
#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
pub struct SourceDisplay<'a> {
pub source: &'a str,
pub location: Location,
pub source_path: Option<&'a Path>,
pub underlined: bool,
}
impl<'a> SourceDisplay<'a> {
pub fn new(source: &'a str, location: Location) -> Self {
Self {
source_path: None,
source,
location,
underlined: false,
}
}
}
pub fn source_path_pointer(source_path: &Path) -> impl Clone + Display {
format!("--> {}", source_path.display())
}
impl<'a> Display for SourceDisplay<'a> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let Self {
source,
location,
source_path,
underlined,
} = *self;
let nums_locs = intersecting_line_locations(location, source).map(|(i, loc)| (i + 1, loc));
let max_line_num_digits: usize = match itertools::max(nums_locs.clone().map(|(n, _)| n)) {
Some(max) => max.to_string().len(),
None => panic!("Location is empty or does not map to source"),
};
let write_padding = |f: &mut fmt::Formatter, n: usize| -> fmt::Result {
for _ in 0..n {
write!(f, " ")?;
}
Ok(())
};
if let Some(source_path) = source_path {
let source_path = source_path_pointer(source_path);
let (first_num, _) = nums_locs.clone().next().unwrap();
write_padding(f, max_line_num_digits)?;
write!(f, "{source_path}:{first_num}\n")?;
}
write_padding(f, max_line_num_digits)?;
write!(f, " | \n")?;
for (num, line_loc) in nums_locs {
let line_num_str = num.to_string();
write_padding(f, max_line_num_digits - line_num_str.len())?;
write!(f, "{line_num_str} | ")?;
let Location(line_begin, line_end) = line_loc;
write!(f, "{}\n", &source[line_begin..line_end])?;
if underlined {
write_padding(f, max_line_num_digits)?;
write!(f, " | ")?;
for i in line_begin..line_end {
if Location(i, i + 1).intersect(location).is_some() {
write!(f, "^")?;
} else {
write!(f, " ")?;
}
}
write!(f, "\n")?;
}
}
write_padding(f, max_line_num_digits)?;
write!(f, " | \n")?;
Ok(())
}
}