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 digits = location.start.line.to_string().len(); let y = location.start.line + 1; 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("") } 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, 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!(""); } } fn get_unit_name(source_unit: &SourceUnit) -> Option { source_unit.main.path.file_name().map(|s| s.to_string_lossy().to_string()) }