use std::{
borrow::Cow,
error::Error,
fmt::{self, Display, Formatter}
};
use nom::error::{ErrorKind, ParseError as _};
use crate::parser::token;
use super::Span;
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct ParseError<'src>
{
pub errors: Vec<(Span<'src>, NomErrorKind<'src>)>
}
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub enum NomErrorKind<'src>
{
Context(&'static str),
Synthetic(Vec<ParseError<'src>>),
Char(char),
Nom(ErrorKind)
}
impl NomErrorKind<'_>
{
pub fn expectations(&self) -> Vec<Cow<'_, str>>
{
let mut expectations = Vec::new();
match self
{
NomErrorKind::Context(c) =>
{
expected_for_classifier(c).iter().for_each(|c| {
expectations.push(Cow::Borrowed(*c));
});
},
NomErrorKind::Synthetic(merged) =>
{
let rightmost = &merged[0].errors[0].0;
merged.iter().for_each(|error| {
error
.errors
.iter()
.take_while(|(span, _)| {
span.location_offset()
== rightmost.location_offset()
})
.for_each(|(_, kind)| {
expectations.append(&mut kind.expectations())
});
})
},
NomErrorKind::Char(c) =>
{
expectations.push(Cow::Owned(format!("`{}`", c)));
},
NomErrorKind::Nom(e) => match e
{
ErrorKind::Eof =>
{
expectations.push(Cow::Borrowed("end of input"));
},
_ =>
{
}
}
}
expectations
}
}
impl Error for ParseError<'_> {}
impl<'src> nom::error::ParseError<Span<'src>> for ParseError<'src>
{
fn from_error_kind(input: Span<'src>, kind: ErrorKind) -> Self
{
Self {
errors: vec![(input, NomErrorKind::Nom(kind))]
}
}
fn append(input: Span<'src>, kind: ErrorKind, mut other: Self) -> Self
{
other.errors.push((input, NomErrorKind::Nom(kind)));
other
}
fn from_char(input: Span<'src>, c: char) -> Self
{
Self {
errors: vec![(input, NomErrorKind::Char(c))]
}
}
fn or(self, other: Self) -> Self
{
assert!(!self.errors.is_empty());
assert!(!other.errors.is_empty());
match (&self.errors[0], &other.errors[0])
{
((span, _), (other_span, _))
if span.location_offset() > other_span.location_offset() =>
{
self
},
((span, _), (other_span, _))
if span.location_offset() < other_span.location_offset() =>
{
other
},
((input, _), _) => Self {
errors: vec![(
*input,
NomErrorKind::Synthetic(vec![self, other])
)]
}
}
}
}
impl<'src> nom::error::ContextError<Span<'src>> for ParseError<'src>
{
fn add_context(
input: Span<'src>,
ctx: &'static str,
mut other: Self
) -> Self
{
other.errors.push((input, NomErrorKind::Context(ctx)));
other
}
}
impl<'src, E> nom::error::FromExternalError<Span<'src>, E> for ParseError<'src>
{
fn from_external_error(input: Span<'src>, kind: ErrorKind, _e: E) -> Self
{
Self::from_error_kind(input, kind)
}
}
impl Display for ParseError<'_>
{
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result
{
assert!(!self.errors.is_empty());
let rightmost = self.errors[0].0;
let column = rightmost.get_column();
write!(
f,
"Parse error @ {}:{} (byte {}): expected ",
rightmost.location_line(),
column,
rightmost.location_offset()
)?;
let expectations = self
.errors
.iter()
.take_while(|(span, _)| {
span.location_offset() == rightmost.location_offset()
})
.flat_map(|(_, kind)| kind.expectations())
.collect::<Vec<_>>();
writeln!(
f,
"{}, but found {}",
expected(&expectations).unwrap(),
token(rightmost).map_or(Cow::Borrowed("end of input"), |t| {
Cow::Owned(format!("`{}`", t.fragment()))
})
)?;
let line = String::from_utf8_lossy(rightmost.get_line_beginning());
writeln!(f, " {}", line)?;
write!(f, " {}^", " ".repeat(column - 1))
}
}
fn expected<T>(classifiers: &[T]) -> Option<Cow<'_, str>>
where
T: AsRef<str> + Ord
{
if classifiers.is_empty()
{
None
}
else if classifiers.len() == 1
{
Some(Cow::Borrowed(classifiers[0].as_ref()))
}
else
{
let mut expectations = classifiers.iter().collect::<Vec<_>>();
expectations.sort();
expectations.dedup();
Some(match expectations.len()
{
3.. =>
{
let mut s = String::new();
for expectation in &expectations[..expectations.len() - 1]
{
s.push_str(expectation.as_ref());
s.push_str(", ");
}
s.push_str("or ");
let last = expectations.last().unwrap().as_ref();
s.push_str(last);
Cow::Owned(s)
},
2 => Cow::Owned(format!(
"{} or {}",
expectations[0].as_ref(),
expectations[1].as_ref()
)),
1 => Cow::Borrowed(expectations[0].as_ref()),
_ => unreachable!()
})
}
}
fn expected_for_classifier(classifier: &str) -> &'static [&'static str]
{
match classifier
{
FUNCTION_CONTEXT => &[
"parameter definition",
"`{`",
"`[`",
"`(`",
"`-`",
"dice expression",
"integer"
],
PARAMETER_CONTEXT => &["parameter definition"],
NEXT_PARAMETER_CONTEXT => &["`,`"],
FUNCTION_BODY_CONTEXT =>
{
&["`{`", "`[`", "`(`", "`-`", "dice expression", "integer"]
},
EXPRESSION_CONTEXT =>
{
&["`{`", "`[`", "`(`", "`-`", "dice expression", "integer"]
},
RANGE_CONTEXT => &["`[`"],
RANGE_START_CONTEXT =>
{
&["`{`", "`[`", "`(`", "`-`", "dice expression", "integer"]
},
RANGE_END_CONTEXT =>
{
&["`{`", "`[`", "`(`", "`-`", "dice expression", "integer"]
},
DICE_CONTEXT => &["`{`", "`(`", "integer"],
STANDARD_DICE_CONTEXT => &["`{`", "`(`", "integer"],
CUSTOM_DICE_CONTEXT => &["`{`", "`(`", "integer"],
DICE_COUNT_CONTEXT => &["`{`", "`(`", "integer"],
STANDARD_FACES_CONTEXT => &["`{`", "`(`", "integer"],
CUSTOM_FACES_CONTEXT => &["`[`"],
DROP_EXPRESSION_CONTEXT => &["`{`", "`(`", "integer"],
GROUP_CONTEXT => &["`(`"],
VARIABLE_CONTEXT => &["`{`"],
IDENTIFIER_CONTEXT => &["identifier"],
CONSTANT_CONTEXT => &["integer"],
CLOSING_PAREN_CONTEXT => &["`)`"],
CLOSING_BRACKET_CONTEXT => &["`]`"],
CLOSING_BRACE_CONTEXT => &["`}`"],
RIGHT_OPERAND_CONTEXT =>
{
&["`{`", "`[`", "`(`", "`-`", "dice expression", "integer"]
},
DROP_DIRECTION_CONTEXT => &["`lowest`", "`highest`"],
unexpected =>
{
unreachable!("unexpected context classifier: {}", unexpected)
}
}
}
pub(crate) const FUNCTION_CONTEXT: &str = "function";
pub(crate) const PARAMETER_CONTEXT: &str = "parameter";
pub(crate) const NEXT_PARAMETER_CONTEXT: &str = "next parameter";
pub(crate) const FUNCTION_BODY_CONTEXT: &str = "function body";
pub(crate) const EXPRESSION_CONTEXT: &str = "expression";
pub(crate) const RANGE_CONTEXT: &str = "range";
pub(crate) const RANGE_START_CONTEXT: &str = "range start";
pub(crate) const RANGE_END_CONTEXT: &str = "range end";
pub(crate) const DICE_CONTEXT: &str = "dice";
pub(crate) const STANDARD_DICE_CONTEXT: &str = "standard dice";
pub(crate) const CUSTOM_DICE_CONTEXT: &str = "custom dice";
pub(crate) const DICE_COUNT_CONTEXT: &str = "dice count";
pub(crate) const STANDARD_FACES_CONTEXT: &str = "standard faces";
pub(crate) const CUSTOM_FACES_CONTEXT: &str = "custom faces";
pub(crate) const DROP_EXPRESSION_CONTEXT: &str = "drop expression";
pub(crate) const GROUP_CONTEXT: &str = "primary";
pub(crate) const VARIABLE_CONTEXT: &str = "variable";
pub(crate) const IDENTIFIER_CONTEXT: &str = "identifier";
pub(crate) const CONSTANT_CONTEXT: &str = "constant";
pub(crate) const CLOSING_PAREN_CONTEXT: &str = "closing paren";
pub(crate) const CLOSING_BRACKET_CONTEXT: &str = "closing bracket";
pub(crate) const CLOSING_BRACE_CONTEXT: &str = "closing brace";
pub(crate) const RIGHT_OPERAND_CONTEXT: &str = "right operand";
pub(crate) const DROP_DIRECTION_CONTEXT: &str = "drop direction";