This repository has been archived on 2024-05-26. You can view files and clone it, but cannot push or open issues or pull requests.
trixy/trixy-parser/src/error.rs

220 lines
6.3 KiB
Rust

/*
* Copyright (C) 2023 - 2024:
* The Trinitrix Project <soispha@vhack.eu, antifallobst@systemausfall.org>
*
* This file is part of the Trixy crate for Trinitrix.
*
* Trixy is free software: you can redistribute it and/or modify
* it under the terms of the Lesser GNU General Public License as
* published by the Free Software Foundation, either version 3 of
* the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* and the Lesser GNU General Public License along with this program.
* If not, see <https://www.gnu.org/licenses/>.
*/
use core::fmt;
use thiserror::Error;
use crate::{
lexing::{error::SpannedLexingError, TokenSpan},
parsing::{self},
};
#[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),
}
/// 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;
}