use std::{error::Error as StdError, fmt};
use crate::{
parser::{Input, Span},
Position,
};
use super::{
cause::Cause,
code::{Code, ERR_EOF},
};
const UNEXPECTED_END_OF_FILE: &str = "unexpected end of file";
#[allow(clippy::module_name_repetitions)]
pub struct ParseError {
eof: Option<u16>,
stack: Vec<Cause>,
source: Option<Box<dyn StdError + 'static>>,
semantic: bool,
failure: bool,
}
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,
semantic: false,
failure: false,
}
}
pub fn new_at(input: Input, code: Code, message: impl fmt::Display) -> Self {
if input.is_eof() {
return Self::eof(input).and(input, code, message);
}
Self::new(input, code, message)
}
#[inline]
pub fn eof(pos: impl Into<Position>) -> Self {
Self::new(pos.into(), ERR_EOF, UNEXPECTED_END_OF_FILE)
}
#[must_use]
pub fn and<S, M>(self, span: S, code: Code, message: M) -> Self
where
S: Into<Span>,
M: fmt::Display,
{
let mut this = self;
this.push(span, code, message);
this
}
#[must_use]
pub const fn and_semantic(self) -> Self {
let mut this = self;
this.semantic = true;
this
}
#[must_use]
pub const fn and_failure(self) -> Self {
let mut this = self;
this.failure = true;
this
}
#[must_use]
pub fn and_source(self, source: impl StdError + 'static) -> Self {
let mut this = self;
this.set_source(source);
this
}
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().expect("too many errors"));
}
}
#[inline]
pub fn push_eof(&mut self, pos: impl Into<Position>) {
self.push(pos.into(), ERR_EOF, UNEXPECTED_END_OF_FILE);
}
#[must_use]
#[inline]
pub fn cause(&self) -> &Cause {
self.stack.last().expect("empty error stack")
}
#[inline]
pub fn causes(&self) -> impl Iterator<Item = &Cause> {
self.stack.iter().rev()
}
#[must_use]
#[inline]
pub fn span(&self) -> Span {
self.cause().span()
}
#[must_use]
#[inline]
pub fn code(&self) -> Code {
self.cause().code()
}
#[must_use]
#[inline]
pub fn message(&self) -> &str {
self.cause().message()
}
#[must_use]
#[inline]
pub const fn is_eof(&self) -> bool {
self.eof.is_some()
}
#[must_use]
#[inline]
pub const fn is_semantic(&self) -> bool {
self.semantic
}
#[inline]
pub fn set_semantic(&mut self) {
self.semantic = true;
}
#[must_use]
#[inline]
pub const fn is_failure(&self) -> bool {
self.failure
}
#[inline]
pub fn set_failure(&mut self) {
self.failure = true;
}
#[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(())
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::parser::Input;
#[test]
fn parse_err_size() {
assert_eq!(std::mem::size_of::<ParseError>(), 48);
}
#[test]
fn parse_error_new() {
let pos = Position::new(1, 1, 2);
let error = ParseError::new(pos, ERR_EOF, "unexpected end of file");
assert_eq!(error.cause().span(), pos.into());
assert_eq!(error.cause().code(), ERR_EOF);
assert_eq!(error.cause().message(), "unexpected end of file");
assert!(error.is_eof());
assert!(!error.is_semantic());
assert!(error.source().is_none());
}
#[test]
fn parse_error_new_at() {
let input = Input::new("test");
let error = ParseError::new_at(input, ERR_EOF, "unexpected end of file");
assert_eq!(error.cause().span(), input.into());
assert_eq!(error.cause().code(), ERR_EOF);
assert_eq!(error.cause().message(), "unexpected end of file");
assert!(error.is_eof());
assert!(!error.is_semantic());
assert!(error.source().is_none());
}
#[test]
fn parse_error_eof() {
let pos = Position::new(1, 1, 2);
let error = ParseError::eof(pos);
assert_eq!(error.cause().span(), pos.into());
assert_eq!(error.cause().code(), ERR_EOF);
assert_eq!(error.cause().message(), "unexpected end of file");
assert!(error.is_eof());
assert!(!error.is_semantic());
assert!(error.source().is_none());
}
#[test]
fn parse_error_and() {
let pos = Position::new(1, 1, 2);
let mut error = ParseError::new(pos, ERR_EOF, "unexpected end of file");
error.push(pos, ERR_EOF, "unexpected end of file");
assert_eq!(error.cause().span(), pos.into());
assert_eq!(error.cause().code(), ERR_EOF);
assert_eq!(error.cause().message(), "unexpected end of file");
assert!(error.is_eof());
assert!(!error.is_semantic());
assert!(error.source().is_none());
}
#[test]
fn parse_error_and_semantic() {
let pos = Position::new(1, 1, 2);
let error = ParseError::new(pos, ERR_EOF, "unexpected end of file").and_semantic();
assert_eq!(error.cause().span(), pos.into());
assert_eq!(error.cause().code(), ERR_EOF);
assert_eq!(error.cause().message(), "unexpected end of file");
assert!(error.is_eof());
assert!(error.is_semantic());
assert!(error.source().is_none());
}
#[test]
fn parse_error_and_source() {
let pos = Position::new(1, 1, 2);
let error = ParseError::new(pos, ERR_EOF, "unexpected end of file")
.and_source(std::io::Error::new(std::io::ErrorKind::Other, "test"));
assert_eq!(error.cause().span(), pos.into());
assert_eq!(error.cause().code(), ERR_EOF);
assert_eq!(error.cause().message(), "unexpected end of file");
assert!(error.is_eof());
assert!(!error.is_semantic());
assert!(error.source().is_some());
}
}