use std::{error::Error as StdError, fmt};
use crate::parser::{Position, Span};
use super::{
cause::Cause,
code::{Code, ERR_EOF},
};
const UNEXPECTED_END_OF_FILE: &str = "unexpected end of file";
pub struct ParseError {
eof: Option<u16>,
stack: Vec<Cause>,
source: Option<Box<dyn StdError + 'static>>,
}
impl ParseError {
#[inline]
pub fn new<S, M>(span: S, code: Code, message: M) -> Self
where
S: Into<Span>,
M: fmt::Display,
{
Self {
eof: if code == ERR_EOF { Some(0) } else { None },
stack: vec![Cause::new(span, code, message)],
source: None,
}
}
#[inline]
pub fn eof(pos: impl Into<Position>) -> Self {
Self::eof_with(pos.into(), UNEXPECTED_END_OF_FILE)
}
#[inline]
pub fn eof_with<M>(pos: impl Into<Position>, message: M) -> Self
where
M: fmt::Display,
{
Self::new(pos.into(), ERR_EOF, message)
}
pub fn push<S, M>(&mut self, span: S, code: Code, message: M)
where
S: Into<Span>,
M: fmt::Display,
{
self.stack.push(Cause::new(span, code, message));
if code == ERR_EOF && self.eof.is_none() {
self.eof = Some((self.stack.len() - 1).try_into().unwrap());
}
}
#[inline]
pub fn push_eof(&mut self, pos: impl Into<Position>) {
self.push(pos.into(), ERR_EOF, UNEXPECTED_END_OF_FILE);
}
#[inline]
pub fn push_eof_with<M>(&mut self, pos: impl Into<Position>, message: M)
where
M: fmt::Display,
{
self.push(pos.into(), ERR_EOF, message);
}
#[inline]
pub fn cause(&self) -> &Cause {
if let Some(cause) = self.eof.as_ref().map(|i| &self.stack[*i as usize]) {
cause
} else {
self.stack.last().expect("empty error stack")
}
}
#[inline]
pub fn causes(&self) -> impl Iterator<Item = &Cause> {
self.stack.iter().rev()
}
#[inline]
pub fn span(&self) -> Span {
self.cause().span()
}
#[inline]
pub fn code(&self) -> Code {
self.cause().code()
}
#[inline]
pub fn message(&self) -> &str {
self.cause().message()
}
#[inline]
pub fn is_eof(&self) -> bool {
self.eof.is_some()
}
#[inline]
pub fn set_source(&mut self, source: impl StdError + 'static) {
self.source = Some(Box::new(source));
}
}
impl StdError for ParseError {
fn source(&self) -> Option<&(dyn StdError + 'static)> {
self.source.as_deref()
}
}
impl PartialEq for ParseError {
fn eq(&self, other: &Self) -> bool {
self.cause() == other.cause()
}
}
impl fmt::Display for ParseError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if f.alternate() {
write!(f, "{:#}", self.cause())
} else {
write!(f, "{}", self.cause())
}
}
}
impl fmt::Debug for ParseError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if f.alternate() {
write!(f, "{:#?}", self.stack.last().expect("empty error stack"))?;
if self.stack.len() > 1 {
write!(f, "\nCaused by:")?;
for cause in self.causes().skip(1) {
write!(f, "\n {:#?}", cause)?;
}
}
if let Some(source) = self.source.as_ref() {
write!(f, "\nSource:\n {}", source)?;
let mut cursor = source.source();
while let Some(source) = cursor {
write!(f, "\n {}", source)?;
cursor = source.source();
}
}
} else {
write!(f, "{:#?}", self.stack.last().expect("empty error stack"))?;
if self.stack.len() > 1 {
write!(f, "\nCaused by:")?;
for cause in self.causes().skip(1) {
write!(f, "\n {:?}", cause)?;
}
}
}
Ok(())
}
}