use std::{
error::Error,
fmt::{self, Debug, Display, Formatter, Write},
};
use cascade::cascade;
use indent_write::fmt::IndentWriter;
use joinery::JoinableIterator;
use nom::{
error::{ContextError, ErrorKind as NomErrorKind, FromExternalError, ParseError},
InputLength,
};
use crate::final_parser::{ExtractContext, RecreateContext};
use crate::tag::TagError;
#[non_exhaustive]
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum Expectation {
Tag(&'static str),
Char(char),
Alpha,
Digit,
HexDigit,
OctDigit,
AlphaNumeric,
Space,
Multispace,
CrLf,
Eof,
Something,
}
impl Display for Expectation {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
match *self {
Expectation::Tag(tag) => write!(f, "{:?}", tag),
Expectation::Char(c) => write!(f, "{:?}", c),
Expectation::Alpha => write!(f, "an ascii letter"),
Expectation::Digit => write!(f, "an ascii digit"),
Expectation::HexDigit => write!(f, "a hexadecimal digit"),
Expectation::OctDigit => write!(f, "an octal digit"),
Expectation::AlphaNumeric => write!(f, "an ascii alphanumeric character"),
Expectation::Space => write!(f, "a space or tab"),
Expectation::Multispace => write!(f, "whitespace"),
Expectation::Eof => write!(f, "eof"),
Expectation::CrLf => write!(f, "CRLF"),
Expectation::Something => write!(f, "not eof"),
}
}
}
#[derive(Debug)]
pub enum BaseErrorKind {
Expected(Expectation),
Kind(NomErrorKind),
External(Box<dyn Error + Send + Sync + 'static>),
}
impl Display for BaseErrorKind {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
match *self {
BaseErrorKind::Expected(expectation) => write!(f, "expected {}", expectation),
BaseErrorKind::External(ref err) => {
writeln!(f, "external error:")?;
let mut f = IndentWriter::new(" ", f);
write!(f, "{}", err)
}
BaseErrorKind::Kind(kind) => write!(f, "error in {:?}", kind),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum StackContext {
Kind(NomErrorKind),
Context(&'static str),
}
impl Display for StackContext {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
match *self {
StackContext::Kind(kind) => write!(f, "while parsing {:?}", kind),
StackContext::Context(ctx) => write!(f, "in section {:?}", ctx),
}
}
}
#[derive(Debug)]
pub enum ErrorTree<I> {
Base {
location: I,
kind: BaseErrorKind,
},
Stack {
base: Box<Self>,
contexts: Vec<(I, StackContext)>,
},
Alt(Vec<Self>),
}
impl<I> ErrorTree<I> {
fn map_locations_ref<T>(self, convert_location: &mut impl FnMut(I) -> T) -> ErrorTree<T> {
match self {
ErrorTree::Base { location, kind } => ErrorTree::Base {
location: convert_location(location),
kind,
},
ErrorTree::Stack { base, contexts } => ErrorTree::Stack {
base: Box::new(base.map_locations_ref(convert_location)),
contexts: contexts
.into_iter()
.map(|(location, context)| (convert_location(location), context))
.collect(),
},
ErrorTree::Alt(siblings) => ErrorTree::Alt(
siblings
.into_iter()
.map(|err| err.map_locations_ref(convert_location))
.collect(),
),
}
}
pub fn map_locations<T>(self, mut convert_location: impl FnMut(I) -> T) -> ErrorTree<T> {
self.map_locations_ref(&mut convert_location)
}
}
impl<I: Display> Display for ErrorTree<I> {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
match self {
ErrorTree::Base { location, kind } => write!(f, "{} at {:#}", kind, location),
ErrorTree::Stack { contexts, base } => {
contexts.iter().rev().try_for_each(|(location, context)| {
writeln!(f, "{} at {:#},", context, location)
})?;
base.fmt(f)
}
ErrorTree::Alt(siblings) => {
writeln!(f, "one of:")?;
let mut f = IndentWriter::new(" ", f);
write!(f, "{}", siblings.iter().join_with(", or\n"))
}
}
}
}
impl<I: Display + Debug> Error for ErrorTree<I> {}
impl<I: InputLength> ParseError<I> for ErrorTree<I> {
fn from_error_kind(location: I, kind: NomErrorKind) -> Self {
let kind = match kind {
NomErrorKind::Alpha => BaseErrorKind::Expected(Expectation::Alpha),
NomErrorKind::Digit => BaseErrorKind::Expected(Expectation::Digit),
NomErrorKind::HexDigit => BaseErrorKind::Expected(Expectation::HexDigit),
NomErrorKind::OctDigit => BaseErrorKind::Expected(Expectation::OctDigit),
NomErrorKind::AlphaNumeric => BaseErrorKind::Expected(Expectation::AlphaNumeric),
NomErrorKind::Space => BaseErrorKind::Expected(Expectation::Space),
NomErrorKind::MultiSpace => BaseErrorKind::Expected(Expectation::Multispace),
NomErrorKind::CrLf => BaseErrorKind::Expected(Expectation::CrLf),
NomErrorKind::Eof => match location.input_len() {
0 => BaseErrorKind::Expected(Expectation::Something),
_ => BaseErrorKind::Expected(Expectation::Eof),
},
kind => BaseErrorKind::Kind(kind),
};
ErrorTree::Base { location, kind }
}
fn append(location: I, kind: NomErrorKind, other: Self) -> Self {
let context = (location, StackContext::Kind(kind));
match other {
alt @ ErrorTree::Alt(..) if kind == NomErrorKind::Alt => alt,
ErrorTree::Stack { contexts, base } => ErrorTree::Stack {
base,
contexts: cascade! {
contexts;
..push(context);
},
},
base => ErrorTree::Stack {
base: Box::new(base),
contexts: vec![context],
},
}
}
fn from_char(location: I, character: char) -> Self {
ErrorTree::Base {
location,
kind: BaseErrorKind::Expected(Expectation::Char(character)),
}
}
fn or(self, other: Self) -> Self {
let siblings = match (self, other) {
(ErrorTree::Alt(siblings1), ErrorTree::Alt(siblings2)) => {
match siblings1.capacity() >= siblings2.capacity() {
true => cascade! {siblings1; ..extend(siblings2);},
false => cascade! {siblings2; ..extend(siblings1);},
}
}
(ErrorTree::Alt(siblings), err) | (err, ErrorTree::Alt(siblings)) => cascade! {
siblings;
..push(err);
},
(err1, err2) => vec![err1, err2],
};
ErrorTree::Alt(siblings)
}
}
impl<I> ContextError<I> for ErrorTree<I> {
fn add_context(location: I, ctx: &'static str, other: Self) -> Self {
let context = (location, StackContext::Context(ctx));
match other {
ErrorTree::Stack { contexts, base } => ErrorTree::Stack {
base,
contexts: cascade! {
contexts;
..push(context);
},
},
base => ErrorTree::Stack {
base: Box::new(base),
contexts: vec![context],
},
}
}
}
impl<I, E: Error + Send + Sync + 'static> FromExternalError<I, E> for ErrorTree<I> {
fn from_external_error(location: I, _kind: NomErrorKind, e: E) -> Self {
ErrorTree::Base {
location,
kind: BaseErrorKind::External(Box::new(e)),
}
}
}
impl<I> TagError<I, &'static str> for ErrorTree<I> {
fn from_tag(location: I, tag: &'static str) -> Self {
ErrorTree::Base {
location,
kind: BaseErrorKind::Expected(Expectation::Tag(tag)),
}
}
}
impl<I, T> ExtractContext<I, ErrorTree<T>> for ErrorTree<I>
where
I: Clone,
T: RecreateContext<I>,
{
fn extract_context(self, original_input: I) -> ErrorTree<T> {
self.map_locations(move |location| T::recreate_context(original_input.clone(), location))
}
}