mod resolver_error; mod source_hierarchy; mod source_symbols; mod unused_symbols; pub use resolver_error::*; pub use source_hierarchy::*; pub use source_symbols::*; pub use unused_symbols::*; use crate::*; pub use log::LogLevel; pub use ansi::*; pub fn report_source_issue(level: LogLevel, context: &Context, message: &str) { 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}"), } } 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 } } 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 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(""); 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, } }