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 fn report_source_issue(level: LogLevel, context: &Context, message: &str) { let mut reporter = SourceIssueReporter::new(context, level); reporter.format_source_span(context.source); let details = reporter.details; // Print the completed message. match level { LogLevel::Info => log_info(message, Some(details)), LogLevel::Warn => log_warn(message, Some(details)), LogLevel::Error => log_error(message, Some(details)), LogLevel::Fatal => log_fatal(message, Some(details)), } } struct SourceIssueReporter<'a> { context: &'a Context<'a>, details: InkedString, level: LogLevel, digits: usize, } impl<'a> SourceIssueReporter<'a> { pub fn new(context: &'a Context<'a>, level: LogLevel) -> Self { let details = InkedString::new(); let digits = get_end_line_number(context.source).to_string().len(); Self { context, level, details, digits } } pub fn format_source_span(&mut self, source: &SourceSpan) { let colour = match self.level { LogLevel::Info => Colour::Blue, LogLevel::Warn => Colour::Yellow, LogLevel::Error => Colour::Red, LogLevel::Fatal => Colour::Red, }; // Print location line. let arrow = "-->"; let w = self.digits + 3; self.details.push(ink!("{arrow:>w$} ").blue()); let location = source.location(); self.details.push(ink!("{location}\n")); // Print each source line. 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. let line_num = location_i + 1; let w = self.digits; self.details.push(ink!(" {line_num:>w$} | ").blue()); let mut line_colour = None; for (i, c) in source_line.chars().enumerate() { if i == left { line_colour = Some(colour) } if i == right { line_colour = None } self.details.push(ink!("{c}").set_colour(line_colour)); } self.details.push(ink!("\n")); // Print an underline on the final line. if line_i + 1 == line_count { // Print source code underline. let space = " "; let w = self.digits; self.details.push(ink!(" {space:>w$} | ").blue()); for _ in 0..left { self.details.push(ink!(" ")); } for _ in left..right { self.details.push(ink!("^").set_colour(Some(colour))); } self.details.push(ink!("\n")); } } // Recurse. if let Some(child) = &source.child { self.format_source_span(&child); } } } // 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, } }