use std::cell::RefCell;
use std::cmp::Ordering;
use std::fmt::Write;
use std::num::IntErrorKind;
use std::num::ParseIntError;
use itertools::Itertools;
use ordered_float::OrderedFloat;
use crate::parser::common::transform_span;
use crate::parser::input::Input;
use crate::parser::token::*;
use crate::span::pretty_print_error;
use crate::Range;
const MAX_DISPLAY_ERROR_COUNT: usize = 60;
#[derive(Clone, Debug)]
pub struct Error<'a> {
pub span: Range,
pub errors: Vec<ErrorKind>,
pub contexts: Vec<(Range, &'static str)>,
pub backtrace: &'a Backtrace,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum ErrorKind {
ExpectToken(TokenKind),
ExpectText(&'static str),
Other(&'static str),
}
#[derive(Debug, Clone, Default)]
pub struct Backtrace {
inner: RefCell<Option<BacktraceInner>>,
}
impl Backtrace {
pub fn new() -> Self {
Self::default()
}
pub fn clear(&self) {
self.inner.replace(None);
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct BacktraceInner {
span: Range,
errors: Vec<ErrorKind>,
}
impl<'a> nom::error::ParseError<Input<'a>> for Error<'a> {
fn from_error_kind(i: Input<'a>, _: nom::error::ErrorKind) -> Self {
Error {
span: transform_span(&i[..1]).unwrap(),
errors: vec![],
contexts: vec![],
backtrace: i.backtrace,
}
}
fn append(_: Input<'a>, _: nom::error::ErrorKind, other: Self) -> Self {
other
}
fn from_char(_: Input<'a>, _: char) -> Self {
unreachable!()
}
fn or(mut self, mut other: Self) -> Self {
match self.span.start.cmp(&other.span.start) {
Ordering::Equal => {
self.errors.append(&mut other.errors);
self.contexts.clear();
self
}
Ordering::Less => other,
Ordering::Greater => self,
}
}
}
impl<'a> nom::error::ContextError<Input<'a>> for Error<'a> {
fn add_context(input: Input<'a>, ctx: &'static str, mut other: Self) -> Self {
other
.contexts
.push((transform_span(&input.tokens[..1]).unwrap(), ctx));
other
}
}
impl<'a> Error<'a> {
pub fn from_error_kind(input: Input<'a>, kind: ErrorKind) -> Self {
let mut inner = input.backtrace.inner.borrow_mut();
if let Some(ref mut inner) = *inner {
match input.tokens[0].span.start.cmp(&inner.span.start) {
Ordering::Equal => {
inner.errors.push(kind);
}
Ordering::Less => (),
Ordering::Greater => {
*inner = BacktraceInner {
span: transform_span(&input.tokens[..1]).unwrap(),
errors: vec![kind],
};
}
}
} else {
*inner = Some(BacktraceInner {
span: transform_span(&input.tokens[..1]).unwrap(),
errors: vec![kind],
})
}
Error {
span: transform_span(&input.tokens[..1]).unwrap(),
errors: vec![kind],
contexts: vec![],
backtrace: input.backtrace,
}
}
}
impl From<fast_float::Error> for ErrorKind {
fn from(_: fast_float::Error) -> Self {
ErrorKind::Other("unable to parse float number")
}
}
impl From<ParseIntError> for ErrorKind {
fn from(err: ParseIntError) -> Self {
let msg = match err.kind() {
IntErrorKind::InvalidDigit => {
"unable to parse number because it contains invalid characters"
}
IntErrorKind::PosOverflow => "unable to parse number because it positively overflowed",
IntErrorKind::NegOverflow => "unable to parse number because it negatively overflowed",
_ => "unable to parse number",
};
ErrorKind::Other(msg)
}
}
pub fn display_parser_error(error: Error, source: &str) -> String {
let inner = &*error.backtrace.inner.borrow();
let inner = match inner {
Some(inner) => inner,
None => return String::new(),
};
let span_text = &source[std::ops::Range::from(inner.span)];
let mut labels = vec![];
for (span, kind) in error
.errors
.iter()
.map(|err| (error.span, err))
.chain(inner.errors.iter().map(|err| (inner.span, err)))
{
if let ErrorKind::Other(msg) = kind {
labels = vec![(span, msg.to_string())];
break;
}
}
if labels.is_empty() {
let mut expected_tokens = error
.errors
.iter()
.chain(&inner.errors)
.filter_map(|kind| match kind {
ErrorKind::ExpectToken(EOI) => None,
ErrorKind::ExpectToken(token) if token.is_keyword() => {
Some(format!("`{:?}`", token))
}
ErrorKind::ExpectToken(token) => Some(format!("<{:?}>", token)),
ErrorKind::ExpectText(text) => Some(format!("`{}`", text)),
_ => None,
})
.unique()
.collect::<Vec<_>>();
expected_tokens.sort_by_cached_key(|token| {
OrderedFloat::from(-strsim::jaro_winkler(
&token.to_lowercase(),
&span_text.to_lowercase(),
))
});
let mut msg = if span_text.is_empty() {
"unexpected end of input".to_string()
} else {
format!("unexpected `{span_text}`")
};
let mut iter = expected_tokens.iter().enumerate().peekable();
while let Some((i, error)) = iter.next() {
if i == MAX_DISPLAY_ERROR_COUNT {
let more = expected_tokens
.len()
.saturating_sub(MAX_DISPLAY_ERROR_COUNT);
write!(msg, ", or {} more ...", more).unwrap();
break;
} else if i == 0 {
msg += ", expecting ";
} else if iter.peek().is_none() && i == 1 {
msg += " or ";
} else if iter.peek().is_none() {
msg += ", or ";
} else {
msg += ", ";
}
msg += error;
}
labels = vec![(inner.span, msg)];
}
labels.extend(
error
.contexts
.iter()
.map(|(span, msg)| (*span, format!("while parsing {}", msg))),
);
pretty_print_error(source, labels)
}