summaryrefslogtreecommitdiff
path: root/src/reports
diff options
context:
space:
mode:
Diffstat (limited to 'src/reports')
-rw-r--r--src/reports/mod.rs144
1 files changed, 97 insertions, 47 deletions
diff --git a/src/reports/mod.rs b/src/reports/mod.rs
index 01635b4..bbddba9 100644
--- a/src/reports/mod.rs
+++ b/src/reports/mod.rs
@@ -15,57 +15,107 @@ pub use ansi::*;
pub fn report_source_issue(level: LogLevel, context: &Context, message: &str) {
- // Prepare variables.
- let in_merged = &context.source.in_merged;
- let line_num = in_merged.start.line + 1;
- let digits = line_num.to_string().len();
- let w = digits + 3;
- let arrow = "-->";
- let mut string = message.to_string();
-
- macro_rules! push {
- ($($tokens:tt)*) => { string.push_str(&format!($($tokens)*)) };
+ let mut reporter = SourceIssueReporter::new(context, message, level);
+ reporter.format_source_span(context.source);
+ let string = reporter.output;
+
+ // Print the completed message.
+ match level {
+ LogLevel::Info => log::info!( "{string}"),
+ LogLevel::Warn => log::warn!( "{string}"),
+ LogLevel::Error => log::error!("{string}"),
+ LogLevel::Fatal => log::fatal!("{string}"),
}
+}
+
- // Format message and locations.
- push!("{NORMAL}\n");
- let location = context.source.location();
- push!("{BLUE}{arrow:>w$}{NORMAL} {location}\n", w=w);
-
- // Format source context.
- let left = in_merged.start.column;
- let right = in_merged.end.column + 1;
- let source_line = context.source_code.split('\n').nth(in_merged.start.line)
- .unwrap_or("<error reading line from source>");
- let space = " ";
- let colour = match level {
- LogLevel::Info => BLUE,
- LogLevel::Warn => YELLOW,
- LogLevel::Error => RED,
- LogLevel::Fatal => RED,
- };
-
- // Print source code line.
- push!("{BLUE} {line_num} | {NORMAL}");
- for (i, c) in source_line.chars().enumerate() {
- if i == left { push!("{colour}") }
- if i == right { push!("{NORMAL}") }
- push!("{c}");
+struct SourceIssueReporter<'a> {
+ context: &'a Context<'a>,
+ output: String,
+ level: LogLevel,
+ digits: usize,
+}
+
+impl<'a> SourceIssueReporter<'a> {
+ pub fn new(context: &'a Context<'a>, message: &str, level: LogLevel) -> Self {
+ let output = format!("{message}\n");
+ let digits = get_end_line_number(context.source).to_string().len();
+ Self { context, level, output, digits }
}
- push!("{NORMAL}\n");
- // Print source code underline.
- push!("{BLUE} {space:>w$} | {NORMAL}", w=digits);
- for _ in 0..left { push!(" "); }
- push!("{colour}");
- for _ in left..right { push!("^"); }
- push!("{NORMAL}");
+ pub fn format_source_span(&mut self, source: &SourceSpan) {
+ let colour = match self.level {
+ LogLevel::Info => BLUE,
+ LogLevel::Warn => YELLOW,
+ LogLevel::Error => RED,
+ LogLevel::Fatal => RED,
+ };
- // Print the completed message.
- match level {
- LogLevel::Info => log::info!( "{}", string),
- LogLevel::Warn => log::warn!( "{}", string),
- LogLevel::Error => log::error!("{}", string),
- LogLevel::Fatal => log::fatal!("{}", string),
+ // Print location line.
+ self.output.push_str(NORMAL);
+ let arrow = "-->";
+ let location = source.location();
+ self.push(&format!("{BLUE}{arrow:>w$}{NORMAL} {location}\n", w=self.digits+3));
+
+ let line_count = location.end.line - location.start.line + 1;
+ for line_i in 0..line_count {
+ let merged_i = source.in_merged.start.line + line_i;
+ let location_i = location.start.line + line_i;
+ // Format source context.
+ let source_line = self.context.source_code.split('\n').nth(merged_i)
+ .unwrap_or("<error reading line from source>");
+ let left = match line_i == 0 {
+ true => location.start.column,
+ false => 0,
+ };
+ let right = match line_i + 1 == line_count {
+ true => location.end.column + 1,
+ false => source_line.chars().count(),
+ };
+
+ // Print source code line.
+ self.push(&format!("{BLUE} {line_num:>w$} | {NORMAL}", line_num=location_i+1, w=self.digits));
+ for (i, c) in source_line.chars().enumerate() {
+ if i == left { self.output.push_str(colour) }
+ if i == right { self.output.push_str(NORMAL) }
+ self.output.push(c);
+ }
+ self.output.push_str(NORMAL);
+ self.output.push('\n');
+
+ // Print an underline on the final line.
+ if line_i + 1 == line_count {
+ // Print source code underline.
+ let space = " ";
+ self.push(&format!("{BLUE} {space:>w$} | {NORMAL}", w=self.digits));
+ for _ in 0..left { self.output.push_str(" "); }
+ self.output.push_str(colour);
+ for _ in left..right { self.output.push_str("^"); }
+ self.output.push_str(NORMAL);
+ self.output.push('\n');
+ }
+ }
+
+ // Recurse.
+ if let Some(child) = &source.child {
+ self.format_source_span(&child);
+ }
+
+ // Remove the trailing new-line.
+ self.output.pop();
+ }
+
+ fn push(&mut self, string: &str) {
+ self.output.push_str(&string);
+ }
+}
+
+
+// Return the highest 1-based end line number of any location in the chain.
+fn get_end_line_number(source: &SourceSpan) -> usize {
+ let end_line_number = source.location().end.line + 1;
+ match &source.child {
+ Some(child) => std::cmp::max(end_line_number, get_end_line_number(&child)),
+ None => end_line_number,
}
}