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,
}
}