use colored::Colorize;
use std::fmt::Write;
pub const DEFAULT_MAX_EXCERPT_LINES: usize = 5;
pub const DEFAULT_MAX_ERROR_COUNT: usize = 20;
#[derive(Debug, Clone, Copy)]
pub struct SourceRegion {
pub start: usize,
pub end: usize,
}
impl SourceRegion {
pub fn len (&self) -> usize {
self.end - self.start
}
}
#[derive(Debug, PartialEq)]
pub enum ProblemKind {
Warning,
Error,
}
#[derive(Debug)]
pub struct Problem {
pub region: SourceRegion,
pub msg: String,
pub kind: ProblemKind,
}
impl Problem {
pub fn dump (&self, source: &Source, print_excerpt: bool) {
let is_err = self.kind == ProblemKind::Error;
let color = if is_err { "red" } else { "yellow" };
let line_pre = " | ".color(color);
println!("{}{}: {}", line_pre, if is_err { "Error" } else { "Warning" }.color(color), self.msg);
let (sl, sc) = source.get_line_and_column(self.region.start).expect("Failed to get start line and column for problem");
print!("{}{}at: [{}:{}:{}", line_pre, if is_err { " " } else { " " }, source.origin, sl + 1, sc + 1);
if self.region.len() > 1 {
let (el, ec) = source.get_line_and_column(self.region.end).expect("Failed to get end line and column for problem");
println!(" to {}:{}]", el + 1, ec + 1);
} else {
println!("]");
}
if print_excerpt {
println!("{}\n{}", line_pre, source.get_excerpt(&self.region, is_err));
}
println!("");
}
}
#[derive(Debug)]
pub struct Source {
pub origin: String,
pub body: Vec<char>,
pub problems: Vec<Problem>,
pub error_count: usize,
pub max_errors: usize,
pub max_excerpt_lines: usize,
}
pub struct RegionSlice<'a> {
pub region: SourceRegion,
pub slice: &'a [char],
}
impl<'a> RegionSlice<'a> {
fn len (&self) -> usize {
self.slice.len()
}
}
impl<'a> std::fmt::Display for RegionSlice<'a> {
fn fmt (&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
for c in self.slice.iter() {
if let Err(e) = f.write_fmt(format_args!("{}", c)) {
return Err(e)
}
}
Ok(())
}
}
impl Source {
pub fn new (origin: &str, body: Vec<char>) -> Source {
Source {
origin: origin.to_string(),
body,
problems: Vec::new(),
error_count: 0,
max_errors: DEFAULT_MAX_ERROR_COUNT,
max_excerpt_lines: DEFAULT_MAX_EXCERPT_LINES
}
}
pub fn from_file (origin: &str) -> Result<Source, std::io::Error> {
let body = std::fs::read_to_string(&origin);
match body {
Ok(s) => Ok(Source::new(
origin,
s.chars().collect()
)),
Err(e) => Err(e)
}
}
pub fn get_line_and_column (&self, index: usize) -> Option<(usize, usize)> {
let mut l: usize = 0;
let mut c: usize = 0;
let len = self.body.len();
for i in 0..len {
let ch = self.body[i];
if i == index { return Some((l, c)) }
else if ch == '\n' {
l += 1;
c = 0;
} else {
c += 1;
}
}
if len == index { Some((l, c)) }
else { None }
}
pub fn get_index (&self, (line, column): (usize, usize)) -> Option<usize> {
let mut l: usize = 0;
let mut c: usize = 0;
let len = self.body.len();
for i in 0..len {
if l == line && c == column { return Some(i) }
else if self.body[i] == '\n' {
l += 1;
c = 0;
} else {
c += 1;
}
}
if l == line && c == column { Some(len) }
else { None }
}
pub fn get_region_from_lines_and_columns (&self, start_lc: (usize, usize), end_lc: (usize, usize)) -> Option<SourceRegion> {
if let Some(start) = self.get_index(start_lc) {
if let Some(end) = self.get_index(end_lc) {
return Some(SourceRegion { start, end })
}
}
None
}
pub fn get_region_slice (&self, start: usize, end: usize) -> RegionSlice {
RegionSlice { region: SourceRegion { start, end }, slice: &self.body[start..end] }
}
pub fn get_excerpt (&self, region: &SourceRegion, is_error: bool) -> String {
let len = self.body.len();
assert!(
region.start < region.end && region.end <= len,
"Could not create source excerpt for region {} to {} of file '{}': Indices are invalid (Body length is {})",
region.start, region.end, self.origin, len
);
let color = if is_error { "red" } else { "yellow" };
let line_pre = " | ".color(color);
let line_bar = " |-".color(color);
let mut first_line_start = region.start;
while first_line_start > 0 && self.body[first_line_start - 1] != '\n' { first_line_start -= 1 }
let mut last_line_end = region.end;
while last_line_end < len - 1 && self.body[last_line_end] != '\n' { last_line_end += 1 }
let mut line_segs: Vec<RegionSlice> = Vec::new();
let mut started = true;
let mut line_start = first_line_start;
for i in first_line_start..=last_line_end {
if i >= len {
if started { line_segs.push(self.get_region_slice(line_start, i)); }
break
} else if !started {
started = true;
line_start = i;
} else if self.body[i] == '\n' {
line_segs.push(self.get_region_slice(line_start, i));
started = false;
}
}
let num_segs = line_segs.len();
assert!(
num_segs > 0,
"Could not create source excerpt for region {} to {} of file '{}': Could not gather source lines",
region.start, region.end, self.origin
);
let mut bar = String::new();
let mut excerpt = String::new();
if num_segs == 1 {
let line = line_segs.first().unwrap();
for i in line.region.start..=line.region.end {
if i < region.start {
bar.push('-');
} else if i >= region.start && i < region.end {
bar.push('^');
} else {
break
}
}
excerpt.write_fmt(format_args!("{}{}\n{}{}", line_pre, line, line_bar, bar.color(color))).unwrap();
} else {
let first_line = line_segs.first().unwrap();
for i in first_line.region.start..first_line.region.end {
if i < region.start {
bar.push('-');
} else if i == region.start {
bar.push('^');
break
}
}
excerpt.write_fmt(format_args!("{}{}\n{}{}\n", line_pre, first_line, line_bar, bar.color(color))).unwrap();
for i in 1..self.max_excerpt_lines.min(num_segs - 1) {
excerpt.write_fmt(format_args!("{}{}\n", line_pre, line_segs[i])).unwrap();
}
if num_segs - 1 == self.max_excerpt_lines + 1 {
excerpt.write_fmt(format_args!("{}{}\n", line_pre, line_segs[num_segs - 2])).unwrap();
} else if num_segs - 1 > self.max_excerpt_lines {
excerpt.write_fmt(format_args!("{} ...\n", line_pre)).unwrap();
}
let last_line = line_segs.last().unwrap();
excerpt.write_fmt(format_args!("{}{}\n", line_pre, last_line)).unwrap();
bar.clear();
for i in last_line.region.start..last_line.region.end {
if i < region.end - 1 && i < last_line.region.end - 1 {
bar.push('-');
} else {
bar.push('^');
break
}
}
excerpt.write_fmt(format_args!("{}{}", line_bar, bar.color(color))).unwrap();
}
excerpt
}
pub fn warning (&mut self, region: SourceRegion, msg: String) {
self.problems.push(Problem { region, msg, kind: ProblemKind::Warning })
}
pub fn error (&mut self, region: SourceRegion, msg: String) {
self.problems.push(Problem { region, msg, kind: ProblemKind::Error });
self.error_count += 1;
}
pub fn valid (&self) -> bool {
self.error_count <= self.max_errors
}
pub fn dump_warnings (&self, print_excerpts: bool) {
for problem in self.problems.iter() {
if problem.kind == ProblemKind::Warning {
problem.dump(self, print_excerpts);
}
}
}
pub fn dump_errors (&self, print_excerpts: bool) {
for problem in self.problems.iter() {
if problem.kind == ProblemKind::Error {
problem.dump(self, print_excerpts);
}
}
}
pub fn dump_problems (&self, print_excerpts: bool) {
for problem in self.problems.iter() {
problem.dump(self, print_excerpts);
}
}
pub fn clear_problems (&mut self) {
self.problems.clear();
self.error_count = 0;
}
pub fn get_end_err_region (&self) -> SourceRegion {
let length = self.body.len();
SourceRegion { start: length - 1, end: length }
}
}