Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Diagnostic console output #236

Merged
merged 11 commits into from
Aug 24, 2022
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ license = "Apache-2.0"
edition = "2018"

[dependencies]
console = "0.15.1"
convert_case = "0.5.0"
pest = "=2.3.0"
pest_derive = "=2.3.0"
Expand Down
2 changes: 1 addition & 1 deletion src/ast/patchers/encoding_patcher.rs
Original file line number Diff line number Diff line change
Expand Up @@ -202,7 +202,7 @@ impl EncodingPatcher<'_> {
if let Some(file_encoding) = &slice_file.encoding {
self.diagnostic_reporter.report(
DiagnosticKind::new_note(format!("file encoding was set to {} here:", &file_encoding.version)),
None,
Some(file_encoding.span()),
);
} else {
self.diagnostic_reporter.report(
Expand Down
123 changes: 94 additions & 29 deletions src/parse_result.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@

use crate::ast::Ast;
use crate::diagnostics::*;
use crate::slice_file::SliceFile;
use crate::slice_file::{SliceFile, Span};
use console::style;
use std::collections::HashMap;

pub struct ParsedData {
Expand All @@ -27,39 +28,103 @@ impl ParsedData {

fn emit_errors(diagnostic_reporter: DiagnosticReporter, files: &HashMap<String, SliceFile>) {
let counts = diagnostic_reporter.get_totals();

for error in diagnostic_reporter.into_diagnostics() {
let prefix = match error.diagnostic_kind {
DiagnosticKind::SyntaxError(_) | DiagnosticKind::LogicError(_) | DiagnosticKind::IOError(_) => "error",
DiagnosticKind::Warning(_) => "warning",
DiagnosticKind::Note(_) => "note",
};

// Insert the prefix at the start of the message.
let mut message = format!("{prefix}: {error}");

if let Some(span) = error.span {
let file = &span.file;
// Specify the span where the error starts on its own line after the message.
message = format!("{message}\n@ '{file}' ({},{})", span.start.0, span.start.1);

// If the span isn't empty, extract a snippet of the text contained within the span.
if span.start != span.end {
message += ":\n";
let file = files.get(&span.file).expect("Slice file not in file map!");
message += &file.get_snippet(span.start, span.end);
} else {
message += "\n";
for diagnostic in diagnostic_reporter.into_diagnostics() {
// Styling the prefix
let prefix = match diagnostic.diagnostic_kind {
DiagnosticKind::SyntaxError(_) | DiagnosticKind::LogicError(_) | DiagnosticKind::IOError(_) => {
style("error").red()
}
DiagnosticKind::Warning(_) => style("warning").yellow(),
DiagnosticKind::Note(_) => style("note").blue(),
}
.bold();

// Notes should be handled separately than the other diagnostics.
match diagnostic.diagnostic_kind {
DiagnosticKind::Note(note) => eprintln!("{}: {}", prefix, style(note).bold()),
_ => eprintln!("\n{}: {}", prefix, style(&diagnostic).bold()),
}
ReeceHumphreys marked this conversation as resolved.
Show resolved Hide resolved
// Print the message to stderr.
eprintln!("{}", message);

if let Some(span) = diagnostic.span {
// Display the file name and line row and column where the error began.
let file_location = format!("{}:{}:{}", &span.file, span.start.0, span.start.1);
let path = std::path::Path::new(&file_location);
let formatted_path = format!(" {} {}", style("-->").blue().bold(), path.display());
eprintln!("{}", formatted_path);
ReeceHumphreys marked this conversation as resolved.
Show resolved Hide resolved

// Display the line of code where the error occurred.
Self::show_error_location(files.get(&span.file).expect("Slice file not in file map!"), &span);
}
}

// Output the total number of errors and warnings.
println!();
match counts.1 {
ReeceHumphreys marked this conversation as resolved.
Show resolved Hide resolved
0 => (),
ReeceHumphreys marked this conversation as resolved.
Show resolved Hide resolved
_ => println!(
"{}: Compilation generated {} warning(s)",
ReeceHumphreys marked this conversation as resolved.
Show resolved Hide resolved
style("Warnings").yellow().bold(),
counts.1
ReeceHumphreys marked this conversation as resolved.
Show resolved Hide resolved
),
}
match counts.0 {
0 => println!("{}: Successfully compiled slice", style("Finished").green().bold()),
_ => println!(
"{}: Compilation failed with {} error(s)",
ReeceHumphreys marked this conversation as resolved.
Show resolved Hide resolved
style("Failed").red().bold(),
counts.0
ReeceHumphreys marked this conversation as resolved.
Show resolved Hide resolved
),
}
}

fn show_error_location(file: &SliceFile, span: &Span) {
ReeceHumphreys marked this conversation as resolved.
Show resolved Hide resolved
// This is a safe unwrap because we know that if a diagnostic is reported with a span, then there must be
// line of code in the file map corresponding to the error.
let end_of_error_line = file.raw_text.lines().nth(span.end.0 - 1).unwrap().len();

let mut start_snippet = file.get_snippet((span.start.0, 1), span.start);
let mut error_snippet = file.get_snippet(span.start, span.end);
let mut end_snippet = file.get_snippet(span.end, (span.end.0, end_of_error_line + 1));

// Pop the newlines added by `get_snippet`
start_snippet.pop();
error_snippet.pop();
end_snippet.pop();

let formatted_error_lines = format!("{}{}{}", start_snippet, style(&error_snippet), end_snippet);
let formatted_error_lines = formatted_error_lines.split('\n').collect::<Vec<&str>>();
ReeceHumphreys marked this conversation as resolved.
Show resolved Hide resolved
ReeceHumphreys marked this conversation as resolved.
Show resolved Hide resolved
let underline = "-".repeat(
error_snippet
.split('\n')
.into_iter()
ReeceHumphreys marked this conversation as resolved.
Show resolved Hide resolved
.map(|line| line.len())
.max()
.unwrap(),
);

let mut line_number = span.start.0;

// Output
eprintln!("{}", style(" |").blue().bold());
for line in &formatted_error_lines {
ReeceHumphreys marked this conversation as resolved.
Show resolved Hide resolved
eprintln!(
"{: <4}{} {}",
style(line_number).blue().bold(),
style("|").blue().bold(),
line
);
line_number += 1;
}

println!(
"Compilation failed with {} error(s) and {} warning(s).\n",
counts.0, counts.1
// Create the formatted error code section block.
let blank_space = " ".repeat(start_snippet.len());
eprintln!(
"{}{}{}",
style(" | ").blue().bold(),
blank_space,
style(underline).yellow().bold()
);
eprintln!("{}", style(" |").blue().bold());
}
}

Expand Down