diff options
Diffstat (limited to 'src/reports')
-rw-r--r-- | src/reports/mod.rs | 144 |
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, } } |