summaryrefslogtreecommitdiff
path: root/src/print.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/print.rs')
-rw-r--r--src/print.rs237
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>");
+ }
+}
+
+