226 lines
6.5 KiB
Rust
226 lines
6.5 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();
|
|
|
|
// Make the span relative to the current line
|
|
let contexted_span = {
|
|
// `spanned_line` should be a subset of `line`
|
|
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 {
|
|
// 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() > 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;
|
|
}
|