195 lines
5.4 KiB
Rust
195 lines
5.4 KiB
Rust
|
use core::fmt;
|
||
|
|
||
|
use thiserror::Error;
|
||
|
|
||
|
use crate::{
|
||
|
lexing::{error::SpannedLexingError, TokenSpan},
|
||
|
parsing::checked::error::SpannedParsingError,
|
||
|
};
|
||
|
|
||
|
#[derive(Error, Debug)]
|
||
|
pub enum TrixyError {
|
||
|
#[error(transparent)]
|
||
|
Lexing(#[from] SpannedLexingError),
|
||
|
#[error(transparent)]
|
||
|
Parsing(#[from] SpannedParsingError),
|
||
|
}
|
||
|
|
||
|
/// The context of an Error.
|
||
|
#[derive(Debug, Clone)]
|
||
|
pub struct ErrorContext {
|
||
|
/// The span of the error in the source file
|
||
|
pub span: TokenSpan,
|
||
|
/// The span of the error in the context line relative to the context line
|
||
|
pub contexted_span: TokenSpan,
|
||
|
/// The line above the error
|
||
|
pub line_above: String,
|
||
|
/// The line below the error
|
||
|
pub line_below: String,
|
||
|
/// The line in which the error occurred
|
||
|
pub line: String,
|
||
|
/// The line number of the main error line
|
||
|
pub line_number: usize,
|
||
|
}
|
||
|
|
||
|
impl ErrorContext {
|
||
|
pub fn from_span(span: TokenSpan, original_file: &str) -> Self {
|
||
|
let line_number = original_file
|
||
|
.chars()
|
||
|
.take(span.start)
|
||
|
.filter(|a| a == &'\n')
|
||
|
.count()
|
||
|
// This is here, as we are missing one newline with the method above
|
||
|
+ 1;
|
||
|
|
||
|
let lines: Vec<_> = original_file.lines().collect();
|
||
|
|
||
|
let line = (*lines
|
||
|
.get(line_number - 1)
|
||
|
.expect("This should work, as have *at least* one (index = 0) line"))
|
||
|
.to_owned();
|
||
|
|
||
|
let contexted_span = {
|
||
|
let matched_line: Vec<_> = original_file.match_indices(&line).collect();
|
||
|
let (index, matched_line) = matched_line.first().expect("This first index should always match, as we took the line from the string in the first place");
|
||
|
debug_assert_eq!(matched_line, &&line);
|
||
|
TokenSpan {
|
||
|
start: span.start - index,
|
||
|
end: span.end - index,
|
||
|
}
|
||
|
};
|
||
|
|
||
|
let line_above = if line_number == 1 {
|
||
|
// We only have one line, so no line above
|
||
|
"".to_owned()
|
||
|
} else {
|
||
|
(*lines
|
||
|
.get((line_number - 1) - 1)
|
||
|
.expect("We checked that this should work"))
|
||
|
.to_owned()
|
||
|
};
|
||
|
|
||
|
let line_below = if lines.len() - 1 > line_number {
|
||
|
// We have a line after the current line
|
||
|
(*lines
|
||
|
.get((line_number + 1) - 1)
|
||
|
.expect("We checked that this should work"))
|
||
|
.to_owned()
|
||
|
} else {
|
||
|
"".to_owned()
|
||
|
};
|
||
|
|
||
|
Self {
|
||
|
span,
|
||
|
contexted_span,
|
||
|
line_above,
|
||
|
line_below,
|
||
|
line,
|
||
|
line_number,
|
||
|
}
|
||
|
}
|
||
|
|
||
|
pub fn from_index(start: usize, orginal_file: &str) -> Self {
|
||
|
let span = TokenSpan {
|
||
|
start,
|
||
|
end: start + 1,
|
||
|
};
|
||
|
Self::from_span(span, orginal_file)
|
||
|
}
|
||
|
|
||
|
pub fn get_error_line(&self, source_error: &str) -> String {
|
||
|
// deconstruct the structure
|
||
|
let ErrorContext {
|
||
|
contexted_span,
|
||
|
line_number,
|
||
|
..
|
||
|
} = self;
|
||
|
|
||
|
let mut output = String::new();
|
||
|
output.push_str("\x1b[92;1m");
|
||
|
|
||
|
// pad to accommodate the line number printing.
|
||
|
// 32 -> needs two spaces padding to print it
|
||
|
line_number.to_string().chars().for_each(|_| {
|
||
|
output.push(' ');
|
||
|
});
|
||
|
|
||
|
// pad to the beginning of the error
|
||
|
for _ in 0..contexted_span.start {
|
||
|
output.push(' ');
|
||
|
}
|
||
|
|
||
|
// push the error markers
|
||
|
for _ in contexted_span.start..contexted_span.end {
|
||
|
output.push('^');
|
||
|
}
|
||
|
|
||
|
// // pad until end of line
|
||
|
// for _ in contexted_span.end..(line.len() - 1) {
|
||
|
// output.push('-');
|
||
|
// }
|
||
|
//
|
||
|
// additional space to avoid having to end with a '-'
|
||
|
output.push(' ');
|
||
|
|
||
|
output.push_str("help: ");
|
||
|
|
||
|
output.push_str(source_error);
|
||
|
output.push_str("\x1b[0m");
|
||
|
output
|
||
|
}
|
||
|
}
|
||
|
|
||
|
pub trait AdditionalHelp {
|
||
|
fn additional_help(&self) -> String;
|
||
|
}
|
||
|
|
||
|
pub trait ErrorContextDisplay: fmt::Display {
|
||
|
type Error;
|
||
|
|
||
|
fn error_fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result
|
||
|
where
|
||
|
<Self as ErrorContextDisplay>::Error: std::fmt::Display + AdditionalHelp,
|
||
|
{
|
||
|
let error_line = self
|
||
|
.context()
|
||
|
.get_error_line(&self.source().additional_help());
|
||
|
|
||
|
writeln!(f, "\x1b[31;1merror: \x1b[37;1m{}\x1b[0m", self.source())?;
|
||
|
|
||
|
if !self.line_above().is_empty() {
|
||
|
writeln!(
|
||
|
f,
|
||
|
"\x1b[32;1m{} |\x1b[0m {}",
|
||
|
self.line_number() - 1,
|
||
|
self.line_above()
|
||
|
)?;
|
||
|
}
|
||
|
writeln!(
|
||
|
f,
|
||
|
"\x1b[36;1m{} |\x1b[0m {}",
|
||
|
self.line_number(),
|
||
|
self.line()
|
||
|
)?;
|
||
|
writeln!(f, " {}", error_line)?;
|
||
|
if !self.line_below().is_empty() {
|
||
|
writeln!(
|
||
|
f,
|
||
|
"\x1b[32;1m{} |\x1b[0m {}",
|
||
|
self.line_number() + 1,
|
||
|
self.line_below()
|
||
|
)
|
||
|
} else {
|
||
|
write!(f, "")
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// getters
|
||
|
fn context(&self) -> &ErrorContext;
|
||
|
fn source(&self) -> &Self::Error;
|
||
|
fn line_number(&self) -> usize;
|
||
|
fn line_above(&self) -> &str;
|
||
|
fn line_below(&self) -> &str;
|
||
|
fn line(&self) -> &str;
|
||
|
}
|