use crate::*;
use SemanticTokenVariant as SemVar;
use SemanticParseError as SemErr;
use SyntacticParseError as SynErr;
const NORMAL: &str = "\x1b[0m";
const BOLD: &str = "\x1b[1m";
const DIM: &str = "\x1b[2m";
const WHITE: &str = "\x1b[37m";
const RED: &str = "\x1b[31m";
const YELLOW: &str = "\x1b[33m";
const BLUE: &str = "\x1b[34m";
pub struct Context<'a> {
pub source_code: &'a str,
pub source: &'a SourceSpan,
}
/// Print all errors found in the semantic tokens, including those inside macro
/// definitions. Returns true if at least one error was printed.
pub fn print_semantic_errors(semantic_tokens: &[SemanticToken], source_code: &str) -> bool {
let mut found_error = false;
for semantic_token in semantic_tokens {
match &semantic_token.variant {
SemVar::Error(err) => {
let context = Context {
source_code: source_code,
source: &semantic_token.source,
};
found_error = true;
print_semantic_error(&err, context)
}
SemVar::MacroDefinition(definition) => {
for body_token in &definition.body_tokens {
if let SemVar::Error(err) = &body_token.variant {
let context = Context {
source_code: source_code,
source: &body_token.source,
};
found_error = true;
print_semantic_error(err, context)
}
}
}
_ => (),
}
}
return found_error;
}
fn print_semantic_error(error: &SemanticParseError, context: Context) {
let message = get_message_for_semantic_error(error);
print_error(&message, context);
}
fn get_message_for_semantic_error(error: &SemanticParseError) -> String {
match error {
SemErr::LabelDefinitionInMacroDefinition =>
format!("Labels cannot be defined inside a macro"),
SemErr::MacroDefinitionInMacroDefinition =>
format!("Macros cannot be defined inside a macro"),
SemErr::StrayMacroTerminator =>
format!("Macro definition terminator is missing a macro definition"),
SemErr::StrayBlockClose =>
format!("Block was not opened, add a '{{' character to open"),
SemErr::UnclosedBlock =>
format!("Block was not closed, add a '}}' character to close"),
SemErr::UndefinedSymbol(name) =>
format!("Undefined symbol, no label or macro has been defined with the name '{name}'"),
SemErr::RedefinedSymbol((_, source)) =>
format!("Redefined symbol, first defined at {}", source.location()),
SemErr::MacroInvocationBeforeDefinition((_, source)) =>
format!("Macro used before definition, definition is at {}", source.location()),
SemErr:: SyntaxError(syntax_error) => match syntax_error {
SynErr::UnterminatedComment =>
format!("Unclosed comment, add a ')' character to close"),
SynErr::UnterminatedRawString =>
format!("Unclosed string, add a ' character to close"),
SynErr::UnterminatedNullString =>
format!("Unclosed string, add a \" character to close"),
SynErr::InvalidPaddingValue(_) =>
format!("Padding value must be two or four hexidecimal digits"),
}
}
}
pub fn print_resolver_errors(resolver: &SymbolResolver) -> bool {
let mut found_error = false;
for reference in &resolver.unresolved {
found_error = true;
let message = format!(
"Undefined symbol, no label or macro has been defined with the name '{}'",
&reference.symbol.source.string,
);
let source_code = resolver.get_source_code_for_tracked_symbol(reference);
let source = &reference.symbol.source;
print_error(&message, Context { source_code, source } )
}
for redefinition in &resolver.redefinitions {
found_error = true;
let definition = resolver.definitions.get(redefinition.1).unwrap();
let message = format!(
"Redefined symbol, first defined at {}",
&definition.symbol.source.in_merged,
);
let source_code = resolver.get_source_code_for_tracked_symbol(&redefinition.0);
let source = &redefinition.0.symbol.source;
print_error(&message, Context { source_code, source } )
}
return found_error;
}
/// The `ids` argument contains a list of the IDs of the source units which
/// cyclicly depend on one another.
pub fn print_cyclic_source_units(ids: &[usize], resolver: &SymbolResolver) {
eprintln!("{BOLD}{RED}[ERROR]{WHITE}: Some libraries contain a dependency cycle{NORMAL}");
for id in ids {
if let Some(unit) = resolver.source_units.get(*id) {
let path = &unit.source_unit.main.path;
let path_str = path.as_os_str().to_string_lossy();
if let Some(name_str) = get_unit_name(&unit.source_unit) {
eprintln!("{name_str}{NORMAL}{DIM} ({path_str}){NORMAL}");
} else {
eprintln!("{path_str}");
};
// Print parents involved in dependency cycle.
for parent_id in &unit.parent_ids {
if !ids.contains(parent_id) { continue; }
if let Some(parent_unit) = resolver.source_units.get(*parent_id) {
let parent_path = &parent_unit.source_unit.main.path;
let parent_path_str = parent_path.as_os_str().to_string_lossy();
let parent_name_str = match get_unit_name(&parent_unit.source_unit) {
Some(parent_name_str) => parent_name_str,
None => parent_path_str.to_string(),
};
eprintln!(" => {parent_name_str} {DIM}({parent_path_str}){NORMAL}");
}
}
}
}
}
pub fn print_error(message: &str, context: Context) {
print_source_issue(message, context, SourceIssueVariant::Error);
}
pub fn print_warning(message: &str, context: Context) {
print_source_issue(message, context, SourceIssueVariant::Warning);
}
fn print_source_issue(message: &str, context: Context, variant: SourceIssueVariant) {
let (label, colour) = match variant {
SourceIssueVariant::Warning => ("WARNING", YELLOW),
SourceIssueVariant::Error => ("ERROR", RED),
};
// Prepare variables.
let location = &context.source.in_merged;
let y = location.start.line + 1;
let digits = y.to_string().len();
let arrow = "-->";
let space = " ";
// Print message and file path.
eprintln!("{BOLD}{colour}[{label}]{WHITE}: {message}{NORMAL}");
eprintln!("{BLUE}{arrow:>w$}{NORMAL} {location}{NORMAL}", w=digits+3);
if let Some(source) = &context.source.in_source {
eprintln!("{BLUE}{arrow:>w$}{NORMAL} {source}{NORMAL}", w=digits+3);
}
let start = location.start.column;
let end = location.end.column + 1;
// Print source code line.
eprint!("{BLUE} {y} | {NORMAL}");
let line = get_line_from_source_code(context.source_code, location.start.line);
for (i, c) in line.chars().enumerate() {
if i == start { eprint!("{colour}") }
if i == end { eprint!("{NORMAL}") }
eprint!("{c}");
}
eprintln!("{NORMAL}");
// Print source code underline.
eprint!("{BLUE} {space:>w$} | {NORMAL}", w=digits);
for _ in 0..start { eprint!(" "); }
eprint!("{colour}");
for _ in start..end { eprint!("^"); }
eprintln!("{NORMAL}");
}
fn get_line_from_source_code(source_code: &str, line: usize) -> &str {
source_code.split('\n').nth(line).unwrap_or("<error reading line from source>")
}
enum SourceIssueVariant {
Warning,
Error,
}
/// Print a tree containing the name and path of each source unit.
pub fn print_source_tree(resolver: &SymbolResolver) {
eprintln!(".");
let len = resolver.root_unit_ids.len();
for (i, id) in resolver.root_unit_ids.iter().enumerate() {
let end = i + 1 == len;
print_source_tree_leaf(resolver, *id, Vec::new(), end);
}
eprintln!();
}
fn print_source_tree_leaf(resolver: &SymbolResolver, id: usize, mut levels: Vec<bool>, end: bool) {
// A level entry is true if all entries in that level have been printed.
for level in &levels {
match level {
false => eprint!("│ "),
true => eprint!(" "),
}
}
// The end value is true if all siblings of this entry have been printed.
match end {
false => eprint!("├── "),
true => eprint!("└── "),
}
if let Some(unit) = resolver.source_units.get(id) {
let path_str = &unit.source_unit.main.path.as_os_str().to_string_lossy();
if let Some(name_str) = get_unit_name(&unit.source_unit) {
eprint!("{name_str}{BLUE}");
if unit.source_unit.head.is_some() { eprint!(" +head") }
if unit.source_unit.tail.is_some() { eprint!(" +tail") }
let mut unresolved = 0;
for symbol in &resolver.unresolved {
if symbol.source_id == id { unresolved += 1; }
}
if unresolved > 0 { eprint!("{RED} ({unresolved})"); }
eprintln!("{NORMAL} {DIM}({path_str}){NORMAL}");
} else {
eprintln!("{path_str}");
}
levels.push(end);
let len = unit.child_ids.len();
for (i, id) in unit.child_ids.iter().enumerate() {
let end = i + 1 == len;
print_source_tree_leaf(resolver, *id, levels.clone(), end);
}
} else {
eprintln!("<error loading source unit details>");
}
}
fn get_unit_name(source_unit: &SourceUnit) -> Option<String> {
source_unit.main.path.file_name().map(|s| s.to_string_lossy().to_string())
}