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
69 changes: 43 additions & 26 deletions src/parse_result.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
use crate::ast::Ast;
use crate::diagnostics::*;
use crate::slice_file::SliceFile;
use console::style;
use std::collections::HashMap;

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

fn emit_errors(diagnostic_reporter: DiagnosticReporter, files: &HashMap<String, SliceFile>) {
let counts = diagnostic_reporter.get_totals();
for diagnostic in diagnostic_reporter.into_diagnostics() {
// Style the prefix. Note that for `Notes` we do not insert a newline since they should be "attached"
// to the previously emitted diagnostic.
let prefix = match diagnostic.diagnostic_kind {
DiagnosticKind::SyntaxError(_) | DiagnosticKind::LogicError(_) | DiagnosticKind::IOError(_) => {
style("\nerror").red()
}
DiagnosticKind::Warning(_) => style("\nwarning").yellow(),
DiagnosticKind::Note(_) => style("note").white(),
}
.bold();

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",
// Create the message using the prefix
match diagnostic.diagnostic_kind {
DiagnosticKind::Note(_) => {
eprintln!(" {} {}: {}", style("=").blue().bold(), prefix, style(&diagnostic))
}
_ => eprintln!("{}: {}", prefix, style(&diagnostic).bold()),
};

// 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 diagnostic contains a location, show a snippet containing the offending code
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);
eprintln!(" {} {}", style("-->").blue().bold(), path.display());

// 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";
}
// Display the line of code where the error occurred.
let snippet = files.get(&span.file).unwrap().get_snippet(span.start, span.end);
eprintln!("{}", snippet);
}
// Print the message to stderr.
eprintln!("{}", message);
}

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

Expand Down
45 changes: 43 additions & 2 deletions src/slice_file.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

use crate::grammar::{Attribute, Encoding, FileEncoding, Module};
use crate::utils::ptr_util::WeakPtr;
use console::style;
use std::fmt::Write;

type Location = (usize, usize);

Expand Down Expand Up @@ -88,8 +90,47 @@ impl SliceFile {

/// Retrieves a formatted snippet from the slice file. This method expects `start < end`.
pub(crate) fn get_snippet(&self, start: (usize, usize), end: (usize, usize)) -> String {
// TODO we should return nice snippets that snap whole lines and have underlining, etc...
self.raw_text[self.raw_pos(start)..self.raw_pos(end)].to_owned() + "\n"
// The snippet of code on the same line as the error, but directly before it.
let start_snippet = &self.raw_text[self.raw_pos((start.0, 1))..self.raw_pos(start)];

// The snippet of code containing the error.
let error_snippet = &self.raw_text[self.raw_pos(start)..self.raw_pos(end)];

let end_of_error_line = self.raw_text.lines().nth(end.0 - 1).unwrap().len();

// The snippet of code on the same line as the error, but directly after it.
let end_snippet = &self.raw_text[self.raw_pos(end)..self.raw_pos((end.0, end_of_error_line + 1))];

// Create an underline that is the length of the error snippet. For error snippets that span multiple
// lines, the underline is the length of the longest line.
let underline = "-".repeat(error_snippet.lines().map(|line| line.len()).max().unwrap());
let mut line_number = start.0;

// Create a formatted snippet.
let mut snippet = style(" |\n").blue().bold().to_string();
for line in format!("{}{}{}", start_snippet, style(error_snippet), end_snippet).lines() {
writeln!(
snippet,
"{: <4}{} {}",
style(line_number).blue().bold(),
style("|").blue().bold(),
line,
)
.unwrap();
line_number += 1;
}
writeln!(
snippet,
"{}{}{}",
style(" | ").blue().bold(),
" ".repeat(start_snippet.len()),
style(underline).yellow().bold(),
)
.unwrap();
write!(snippet, "{}", style(" |").blue().bold()).unwrap();

// Return the formatted snippet.
snippet
}

/// Converts the provided line and column numbers into an index in the file's raw text.
Expand Down