use core::fmt;
use thiserror::Error;
use crate::parser::{
lexing::{error::SpannedLexingError, TokenSpan},
parsing,
};
#[derive(Error, Debug)]
pub enum TrixyError {
#[error(transparent)]
Lexing(#[from] SpannedLexingError),
#[error(transparent)]
Parsing(#[from] parsing::unchecked::error::SpannedParsingError),
#[error(transparent)]
Processing(#[from] parsing::checked::error::SpannedParsingError),
}
#[derive(Debug, Clone)]
pub struct ErrorContext {
pub span: TokenSpan,
pub contexted_span: TokenSpan,
pub line_above: String,
pub line_below: String,
pub line: String,
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()
+ 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 spanned_line = &original_file[span.start..span.end];
let matched_line: Vec<_> = line.match_indices(&spanned_line).collect();
assert!(!matched_line.is_empty());
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, &spanned_line);
TokenSpan {
start: *index,
end: (span.end - span.start) + index,
}
};
let line_above = if line_number == 1 {
"".to_owned()
} else {
(*lines
.get((line_number - 1) - 1)
.expect("We checked that this should work"))
.to_owned()
};
let line_below = if lines.len() > line_number {
(*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 {
let ErrorContext {
contexted_span,
line_number,
..
} = self;
let mut output = String::new();
output.push_str("\x1b[92;1m");
line_number.to_string().chars().for_each(|_| {
output.push(' ');
});
for _ in 0..contexted_span.start {
output.push(' ');
}
for _ in contexted_span.start..contexted_span.end {
output.push('^');
}
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, "")
}
}
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;
}