diff options
Diffstat (limited to 'src/print.rs')
-rw-r--r-- | src/print.rs | 237 |
1 files changed, 237 insertions, 0 deletions
diff --git a/src/print.rs b/src/print.rs new file mode 100644 index 0000000..7f49db2 --- /dev/null +++ b/src/print.rs @@ -0,0 +1,237 @@ +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)) => { + let location = source.in_source.as_ref().unwrap_or(&source.in_merged); + format!("Redefined symbol, first defined at {location}") + } + SemErr::MacroInvocationBeforeDefinition((_, source)) => { + let location = source.in_source.as_ref().unwrap_or(&source.in_merged); + format!("Macro used before definition, definition is at {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; +} + + + +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("<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 = &unit.source_unit.main.path; + let path_str = path.as_os_str().to_string_lossy(); + if let Some(name) = path.file_name() { + let name_str = name.to_string_lossy(); + 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>"); + } +} + + |