diag pretty

This commit is contained in:
core 2024-01-16 19:04:22 -05:00
parent adba163511
commit e70ff6ef9d
Signed by: core
GPG Key ID: FDBF740DADDCEECF
10 changed files with 275 additions and 16 deletions

View File

@ -1,3 +1,3 @@
#!/usr/bin/env kabel
declare p = 5;
declare p = 51241Q12;
debug: print using (3 + 5) * 2;

View File

@ -1,3 +1,111 @@
"string lit";
("string lit");# comment
12345*(60+80);
"aaaaaa

View File

@ -41,7 +41,8 @@ fn main() -> Result<(), Box<dyn Error>> {
eprintln!("ICE! Please report this to the kabel developers, along with what you were doing: {:?}", e);
}
KError::UserError(diags) => {
eprintln!("{}", libkabel::diagnostics::emitters::basic::BasicEmitter::emit(diags, text_source));
//eprintln!("{}", libkabel::diagnostics::emitters::basic::BasicEmitter::emit(diags, text_source));
eprintln!("{}", libkabel::diagnostics::emitters::pretty::PrettyEmitter::emit(diags, text_source, argv[1].clone()));
}
}
std::process::exit(1);

View File

@ -7,7 +7,7 @@ pub struct BasicEmitter;
impl Emitter for BasicEmitter {
type Output = String;
fn emit(diag: Vec<Diagnostic>, _source: String) -> Self::Output {
fn emit(diag: Vec<Diagnostic>, _source: String, _source_name: String) -> Self::Output {
let mut output = String::new();
for msg in diag {

View File

@ -3,8 +3,9 @@ use crate::diagnostics::Diagnostic;
pub mod basic;
#[cfg(feature = "pretty-emitter")]
pub mod pretty;
pub(crate) mod util;
pub trait Emitter {
type Output;
fn emit(diag: Vec<Diagnostic>, source: String) -> Self::Output;
fn emit(diag: Vec<Diagnostic>, source: String, source_name: String) -> Self::Output;
}

View File

@ -0,0 +1,110 @@
use crate::diagnostics::{Diagnostic, DiagnosticType};
use crate::diagnostics::emitters::Emitter;
use std::fmt::Write;
use colored::{ColoredString, Colorize};
use crate::diagnostics::emitters::util::{get_line, pos_to_line_col};
pub struct PrettyEmitter;
impl Emitter for PrettyEmitter {
type Output = String;
fn emit(diag: Vec<Diagnostic>, source: String, source_name: String) -> Self::Output {
let mut output = String::new();
for msg in diag {
match msg.diag_type {
DiagnosticType::Error => {
write!(output, "{}", "error".bold().red()).unwrap();
}
DiagnosticType::Warning => {
write!(output, "{}", "warning".bold().yellow()).unwrap();
}
_ => { continue; }
}
writeln!(output, ": {}", msg.message.bold()).unwrap();
let mut biggest_line_no = 0;
for span in &msg.spans {
let (s_line, _) = pos_to_line_col(span.span.start, &source);
let (e_line, _) = pos_to_line_col(span.span.start, &source);
if s_line > biggest_line_no {
biggest_line_no = s_line;
}
if e_line > biggest_line_no {
biggest_line_no = e_line;
}
}
let line_no_padding = biggest_line_no.to_string().len();
for labeled_span in &msg.spans {
let (line, start_col) = pos_to_line_col(labeled_span.span.start, &source);
let (_, end_col) = pos_to_line_col(labeled_span.span.end, &source);
writeln!(output, "{}{} {}:{}:{}", " ".repeat(line_no_padding), "-->".bright_blue().bold(), source_name, line, start_col).unwrap();
let line_hdr_padding = line.to_string().len() + 1;
writeln!(output, "{}{}", " ".repeat(line_hdr_padding), "|".bright_blue().bold()).unwrap();
writeln!(output, "{}{} {} {}", line.to_string().bright_blue().bold(), " ".repeat(line_no_padding - line.to_string().len()), "|".bright_blue().bold(), get_line(line, &source).unwrap()).unwrap();
let mut end_char = match labeled_span.span_type {
DiagnosticType::Error => '^'.to_string().bold().red(),
DiagnosticType::Warning => '^'.to_string().bold().yellow(),
DiagnosticType::Help => {
if labeled_span.label.is_some() {
'-'.to_string().bold().bright_blue()
} else {
'~'.to_string().bold().bright_blue()
}
},
DiagnosticType::SecondaryError => {
if labeled_span.label.is_some() {
'-'.to_string().bold().red()
} else {
'~'.to_string().bold().red()
}
}
};
let message = match (labeled_span.span_type, labeled_span.label.clone()) {
(_, None) => ColoredString::from(""),
(DiagnosticType::Error, Some(e)) => e.bold().red(),
(DiagnosticType::Warning, Some(e)) => e.bold().yellow(),
(DiagnosticType::Help, Some(e)) => e.bold().bright_blue(),
(DiagnosticType::SecondaryError, Some(e)) => e.bold().red()
};
println!("{} {}", labeled_span.span.start, labeled_span.span.end);
println!("{} {}", end_col, start_col);
let underline_length = if end_col == start_col {
end_char = "".into();
1
} else if end_col < start_col {
0
} else {
end_col - start_col
};
let underline = match labeled_span.span_type {
DiagnosticType::Error => "^".repeat(underline_length).to_string().bold().red(),
DiagnosticType::Warning => "^".repeat(underline_length).to_string().bold().yellow(),
DiagnosticType::Help => "~".repeat(underline_length).to_string().bold().bright_blue(),
DiagnosticType::SecondaryError => "~".repeat(underline_length).to_string().bold().red()
};
writeln!(output, "{}{} {}{}{} {}", " ".repeat(line_hdr_padding), "|".bright_blue().bold(), " ".repeat(start_col-1), underline, end_char, message).unwrap();
writeln!(output).unwrap();
}
}
output
}
}

View File

@ -0,0 +1,20 @@
pub fn pos_to_line_col(pos: usize, source: &str) -> (usize, usize) {
let mut line = 0;
let mut col = 1;
for (n, c) in source.chars().enumerate() {
if c == '\n' {
line += 1;
col = 0;
} else {
col += 1;
}
if n == pos {
break;
}
}
(line, col)
}
pub fn get_line(line: usize, source: &str) -> Option<&str> {
source.lines().nth(line)
}

View File

@ -6,14 +6,16 @@ pub mod emitters;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Diagnostic {
pub diag_type: DiagnosticType,
pub spans: Vec<SpanWithLabel>
pub spans: Vec<SpanWithLabel>,
pub message: String,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum DiagnosticType {
Error,
Warning,
Help
Help,
SecondaryError
}
#[derive(Debug, Clone, PartialEq, Eq)]

View File

@ -1,11 +1,11 @@
#[derive(Debug, Clone, PartialEq, Eq, Copy)]
pub struct Span {
pub start: u64,
pub end: u64
pub start: usize,
pub end: usize
}
impl Span {
pub fn new(start: u64, end: u64) -> Self {
pub fn new(start: usize, end: usize) -> Self {
Self { start, end }
}
}

View File

@ -23,8 +23,8 @@ pub fn lexer(text_source: &str) -> Result<Vec<Token>, KError> {
let mut chars = text_source.chars().peekable();
let mut pos: u64 = 0;
let mut span_start: u64 = 0;
let mut pos: usize = 0;
let mut span_start: usize = 0;
while let Some(c) = chars.next() {
pos += 1;
@ -53,21 +53,39 @@ pub fn lexer(text_source: &str) -> Result<Vec<Token>, KError> {
current_token.push(c);
}
} else {
continue; // we're at the end. we should bring a user error
// because this string was not properly delimited
return Err(KError::UserError(vec![
Diagnostic {
diag_type: DiagnosticType::Error,
message: "unterminated string literal".to_string(),
spans: vec![
SpanWithLabel {
span: Span::new(span_start-1, span_start-1),
span_type: DiagnosticType::SecondaryError,
label: Some("string began here".to_string())
},
SpanWithLabel {
span: Span::new(pos, pos),
span_type: DiagnosticType::Error,
label: Some("expected end quote here".to_string())
}
]
}
]));
}
}
State::Numbering => {
current_token.push(c);
// If next char isn't numeric, is at end of this number literal
if let Some(c_peek) = chars.peek() {
current_token.push(c);
if !c_peek.is_ascii_digit() {
let num = match current_token.parse::<f64>() {
Ok(n) => n,
Err(_) => {
println!("{} {}", span_start, pos);
return Err(KError::UserError(vec![
Diagnostic {
message: "invalid numeric literal".to_string(),
diag_type: DiagnosticType::Error,
spans: vec![
SpanWithLabel {
@ -87,7 +105,6 @@ pub fn lexer(text_source: &str) -> Result<Vec<Token>, KError> {
));
state = State::BuildingToken;
current_token = String::new();
span_start = pos + 1;
}
}
}