use colored::Colorize;
use std::fmt::Write;
use std::collections::HashMap;
pub const DEFAULT_MAX_EXCERPT_LINES: usize = 5;
pub const DEFAULT_MAX_ERROR_COUNT: usize = 20;
pub type SourceID = u32;
#[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, Clone, Copy)]
pub struct SourceAttribution {
pub region: SourceRegion,
pub source_id: SourceID,
}
impl SourceAttribution {
pub fn len (&self) -> usize {
self.region.len()
}
}
#[derive(Debug, PartialEq)]
pub enum ProblemKind {
Message,
Warning,
Error,
}
#[derive(Debug)]
pub struct Problem {
pub attribution: Option<SourceAttribution>,
pub msg: String,
pub kind: ProblemKind,
pub msg_color: Option<&'static str>,
}
impl Problem {
pub fn dump (&self, source_provider: &SourceProvider, print_excerpt: bool) {
if self.kind == ProblemKind::Message {
let color = self.msg_color.unwrap_or_else(|| "white");
print!("\n{}"," | ".color(color));
if let Some(attribution) = self.attribution {
let source = source_provider.get_source(attribution.source_id).expect("Failed to get source for message attribution");
let (sl, sc) = source.get_line_and_column(attribution.region.start).expect("Failed to get start line and column for message");
print!("[{}:{}:{}]: ", source.origin, sl + 1, sc + 1);
}
print!("{}\n\n", self.msg.color(color));
} else {
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 attribution = self.attribution.expect("Failed to get source attribution for problem");
let source = source_provider.get_source(attribution.source_id).expect("Failed to get source for problem attribution");
let (sl, sc) = source.get_line_and_column(attribution.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 attribution.len() > 1 {
let (el, ec) = source.get_line_and_column(attribution.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(&attribution.region, is_err, source_provider.max_excerpt_lines));
}
println!("");
}
}
}
#[derive(Debug)]
pub struct Source {
pub origin: String,
pub body: Vec<char>,
}
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() {
f.write_fmt(format_args!("{}", c))?;
}
Ok(())
}
}
impl Source {
pub fn new (origin: &str, body: Vec<char>) -> Source {
Source {
origin: origin.to_string(),
body,
}
}
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, max_excerpt_lines: usize) -> 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;
}
}
if started {
line_segs.push(self.get_region_slice(line_start, last_line_end));
}
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..max_excerpt_lines.min(num_segs - 1) {
excerpt.write_fmt(format_args!("{}{}\n", line_pre, line_segs[i])).unwrap();
}
if num_segs - 1 == max_excerpt_lines + 1 {
excerpt.write_fmt(format_args!("{}{}\n", line_pre, line_segs[num_segs - 2])).unwrap();
} else if num_segs - 1 > 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 get_end_err_region (&self) -> SourceRegion {
let length = self.body.len();
SourceRegion { start: length - 1, end: length }
}
}
impl std::fmt::Display for Source {
fn fmt (&self, f: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> {
write!(f, "Source [{}]: \"\"\"\n", self.origin)?;
for ch in &self.body {
write!(f, "{}", ch)?;
}
write!(f, "\n\"\"\"")?;
Ok(())
}
}
#[derive(Debug)]
pub struct SourceProvider {
pub sources: HashMap<SourceID, Source>,
pub source_id_counter: SourceID,
pub problems: Vec<Problem>,
pub error_count: usize,
pub max_errors: usize,
pub max_excerpt_lines: usize,
}
impl SourceProvider {
pub fn new () -> Self {
Self {
sources: HashMap::new(),
source_id_counter: 1,
problems: Vec::new(),
error_count: 0,
max_errors: DEFAULT_MAX_ERROR_COUNT,
max_excerpt_lines: DEFAULT_MAX_EXCERPT_LINES
}
}
pub fn get_source_id_from_origin (&mut self, origin: &str) -> Option<SourceID> {
self.sources.iter().find_map(|(id, src)| if src.origin == origin { Some(*id) } else { None })
}
pub fn register_source_unchecked (&mut self, source: Source) -> SourceID {
let id = self.source_id_counter;
self.source_id_counter += 1;
self.sources.insert(id, source);
id
}
pub fn register_source (&mut self, source: Source) -> SourceID {
if self.get_source_id_from_origin(source.origin.as_str()).is_some() {
panic!("Cannot register multiple Sources with the same origin {}", source.origin);
}
self.register_source_unchecked(source)
}
pub fn load_source_file (&mut self, origin: &str) -> Result<SourceReference, std::io::Error> {
let id = match self.get_source_id_from_origin(origin) {
Some(id) => id,
None => self.register_source_unchecked(Source::from_file(origin)?)
};
Ok(self.get_reference(id))
}
pub fn get_reference (&mut self, id: SourceID) -> SourceReference {
SourceReference {
provider: self,
id
}
}
pub fn get_source (&self, source_id: SourceID) -> Option<&Source> {
self.sources.get(&source_id)
}
pub fn get_source_mut (&mut self, source_id: SourceID) -> Option<&mut Source> {
self.sources.get_mut(&source_id)
}
pub fn message (&mut self, attribution: Option<SourceAttribution>, msg: String, msg_color: Option<&'static str>) {
self.problems.push(Problem { attribution, msg, kind: ProblemKind::Message, msg_color })
}
pub fn warning (&mut self, attribution: SourceAttribution, msg: String) {
self.problems.push(Problem { attribution: Some(attribution), msg, kind: ProblemKind::Warning, msg_color: None })
}
pub fn error (&mut self, attribution: SourceAttribution, msg: String) {
if self.error_count < self.max_errors {
self.problems.push(Problem { attribution: Some(attribution), msg, kind: ProblemKind::Error, msg_color: None });
self.error_count += 1;
if self.error_count == self.max_errors {
self.message(None, format!("Max number of errors ({}) reached", self.max_errors), Some("red"));
}
}
}
pub fn valid (&self) -> bool {
self.error_count < self.max_errors
}
pub fn dump_all_warnings (&self, print_excerpts: bool) {
for problem in self.problems.iter() {
if problem.kind == ProblemKind::Warning
|| problem.kind == ProblemKind::Message {
problem.dump(self, print_excerpts);
}
}
}
pub fn dump_all_errors (&self, print_excerpts: bool) {
for problem in self.problems.iter() {
if problem.kind == ProblemKind::Error
|| problem.kind == ProblemKind::Message {
problem.dump(self, print_excerpts);
}
}
}
pub fn dump_all_problems (&self, print_excerpts: bool) {
for problem in self.problems.iter() {
problem.dump(self, print_excerpts);
}
}
pub fn dump_warnings_for_source (&self, source_id: SourceID, print_excerpts: bool) {
for problem in self.problems.iter() {
if let Some(attr) = problem.attribution {
if attr.source_id == source_id
&& (problem.kind == ProblemKind::Warning || problem.kind == ProblemKind::Message) {
problem.dump(self, print_excerpts)
}
}
}
}
pub fn dump_errors_for_source (&self, source_id: SourceID, print_excerpts: bool) {
for problem in self.problems.iter() {
if let Some(attr) = problem.attribution {
if attr.source_id == source_id
&& (problem.kind == ProblemKind::Error || problem.kind == ProblemKind::Message) {
problem.dump(self, print_excerpts)
}
}
}
}
pub fn dump_problems_for_source (&self, source_id: SourceID, print_excerpts: bool) {
for problem in self.problems.iter() {
if let Some(attr) = problem.attribution {
if attr.source_id == source_id {
problem.dump(self, print_excerpts)
}
}
}
}
pub fn clear_all_problems (&mut self) {
self.problems.clear();
self.error_count = 0;
}
pub fn count_all_problems (&self) -> usize {
self.problems.len()
}
pub fn clear_problems_for_source (&mut self, source_id: SourceID) {
self.problems.retain(|problem| {
if let Some(attr) = problem.attribution {
if attr.source_id == source_id {
return false;
}
}
true
})
}
pub fn count_problems_for_source (&self, source_id: SourceID) -> usize {
let mut count = 0usize;
for problem in self.problems.iter() {
if let Some(attr) = problem.attribution {
if attr.source_id == source_id {
count += 1;
}
}
}
count
}
}
#[derive(Debug)]
pub struct SourceReference<'a> {
pub provider: &'a mut SourceProvider,
pub id: SourceID,
}
impl<'a> SourceReference<'a> {
pub fn get_source (&self) -> Option<&Source> {
self.provider.get_source(self.id)
}
pub fn get_source_mut (&mut self) -> Option<&mut Source> {
self.provider.get_source_mut(self.id)
}
pub fn valid (&self) -> bool {
self.provider.valid()
}
pub fn get_attribution (&self, region: SourceRegion) -> SourceAttribution {
SourceAttribution {
source_id: self.id,
region
}
}
pub fn message (&mut self, region: Option<SourceRegion>, msg: String, msg_color: Option<&'static str>) {
self.provider.message(if let Some(region) = region { Some(SourceAttribution { region, source_id: self.id }) } else { None }, msg, msg_color)
}
pub fn warning (&mut self, region: SourceRegion, msg: String) {
self.provider.warning(SourceAttribution { region, source_id: self.id }, msg)
}
pub fn error (&mut self, region: SourceRegion, msg: String) {
self.provider.error(SourceAttribution { region, source_id: self.id }, msg)
}
pub fn get_origin (&self) -> Option<&str> {
self.get_source().and_then(|source| Some(source.origin.as_str()))
}
pub fn has_problems (&self) -> bool {
self.provider.count_problems_for_source(self.id) > 0
}
pub fn dump_problems (&self, print_excerpts: bool) {
self.provider.dump_problems_for_source(self.id, print_excerpts)
}
}
impl<'a> std::fmt::Display for SourceReference<'a> {
fn fmt (&self, f: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> {
write!(f, "{}", self.get_source().expect("Could not get Source to print SourceReference"))?;
Ok(())
}
}