use pest::error::{ErrorVariant, InputLocation};
use pest::iterators::Pair;
use std::fmt::{self, Display};
use std::str;
use thiserror::Error;
use super::Rule;
#[derive(Debug)]
pub struct ErrorEntry {
pub span: (usize, usize),
pub error: String,
}
impl From<pest::error::Error<Rule>> for ErrorEntry {
fn from(value: pest::error::Error<Rule>) -> Self {
let span = match value.location {
InputLocation::Pos(pos) => (pos, pos + 1),
InputLocation::Span((start, end)) => (start, end),
};
let error = match value.variant {
ErrorVariant::ParsingError {
positives,
negatives,
} => {
let mut message = String::new();
let or_list = |v: &[Rule]| match v {
[] => unreachable!(),
[r0] => format!("{}", r0.name()),
[r0, r1] => format!("{} or {}", r0.name(), r1.name()),
[r0, r1, r2] => format!("{}, {} or {}", r0.name(), r1.name(), r2.name()),
[r0, r1, r2, tail @ ..] => {
format!(
"{}, {}, {} or {} more possibilities",
r0.name(),
r1.name(),
r2.name(),
tail.len()
)
}
};
if negatives.len() > 0 {
message.push_str(&format!("Found {}.", or_list(&negatives)));
}
if positives.len() > 0 {
message.push_str(&format!("Expected {}.", or_list(&positives)));
}
message
}
ErrorVariant::CustomError { message } => message,
};
ErrorEntry { span, error }
}
}
impl ErrorEntry {
pub(super) fn to_string_with(&self, input: &str) -> String {
let (line_start, col_start) = crate::utils::line_col(input, self.span.0);
let (line_end, col_end) = crate::utils::line_col(input, self.span.1);
let mut string = String::new();
let line_display_gap: String = std::iter::repeat(' ')
.take((line_end + 1).to_string().len())
.collect();
string.push_str(&format!(
" {line_display_gap} \u{21e2} Starting at line {}, col {}:\n",
line_start + 1,
col_start + 1
));
string.push_str(&format!(" {line_display_gap} \u{2502}\n"));
for (i, line) in input
.lines()
.enumerate()
.skip(line_start)
.take(line_end - line_start + 1)
{
string.push_str(&format!(" {} \u{2502} {line}\n", i + 1));
let start_point = if line_start != line_end && i != line_start {
0
} else {
col_start
};
let end_point = if line_start != line_end && i != line_end {
line.chars().count()
} else {
col_end
};
string.push_str(&format!(" {line_display_gap} \u{2502} "));
for _ in 0..start_point {
string.push(' ');
}
for _ in 0..(end_point - start_point) {
string.push('^');
}
string.push('\n');
}
string.push_str(&format!(" {line_display_gap} \u{2502}\n"));
string.push_str(&format!(" {line_display_gap} = {}", self.error));
string
}
}
#[derive(Debug)]
pub struct ErrorLogger<'a> {
input: &'a str,
pub errors: Vec<ErrorEntry>,
}
impl ErrorLogger<'_> {
pub(super) fn new(input: &str) -> ErrorLogger {
ErrorLogger {
input,
errors: vec![],
}
}
pub(super) fn absorb<T, E>(&mut self, pair: &Pair<Rule>, r: Result<T, E>) -> T
where
T: Default,
E: ToString,
{
match r {
Ok(ok) => ok,
Err(err) => {
self.errors.push(ErrorEntry {
span: (pair.as_span().start(), pair.as_span().end()),
error: err.to_string(),
});
T::default()
}
}
}
}
#[derive(Debug, Error)]
pub struct ParseError {
pub(super) errors: Vec<String>,
}
impl From<ErrorLogger<'_>> for ParseError {
fn from(value: ErrorLogger<'_>) -> Self {
ParseError {
errors: value
.errors
.into_iter()
.map(|entry| entry.to_string_with(value.input))
.collect(),
}
}
}
impl Display for ParseError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
for error in &self.errors {
write!(f, "\n{error}")?;
}
Ok(())
}
}