use std::{
borrow::Cow,
fmt::{Debug, Display},
};
use itertools::Itertools;
use thiserror::Error;
use crate::{lexer::Token, span::Span};
#[derive(Error, Clone, Debug, PartialEq)]
pub enum ErrorKind {
#[error("invalid token")]
InvalidToken,
#[error("use of a reserved word `{0}`")]
ReservedWord(String),
#[error("unexpected token `{token}`, expected `{}`", .expected.iter().format(", "))]
UnexpectedToken {
token: String,
expected: Vec<String>,
},
#[error("unexpected end of file, expected `{}`", .expected.iter().format(", "))]
UnexpectedEof { expected: Vec<String> },
#[error("extra token `{0}` at the end of the file")]
ExtraToken(String),
#[error("invalid diagnostic severity")]
DiagnosticSeverity,
#[error("invalid `{0}` attribute, {1}")]
Attribute(&'static str, &'static str),
#[error("invalid `var` template arguments, {0}")]
VarTemplate(&'static str),
}
#[derive(Default, Clone, Debug, PartialEq)]
pub enum ParseError {
#[default]
LexerError,
ReservedWord(String),
DiagnosticSeverity,
Attribute(&'static str, &'static str),
VarTemplate(&'static str),
}
type LalrpopError = lalrpop_util::ParseError<usize, Token, (usize, ParseError, usize)>;
#[derive(Error, Clone, Debug, PartialEq)]
pub struct Error {
pub error: ErrorKind,
pub span: Span,
}
impl Error {
pub fn with_source(self, source: Cow<'_, str>) -> ErrorWithSource<'_> {
ErrorWithSource::new(self, source)
}
}
impl Display for Error {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "chars {:?}: {}", self.span.range(), self.error)
}
}
impl From<LalrpopError> for Error {
fn from(err: LalrpopError) -> Self {
match err {
LalrpopError::InvalidToken { location } => {
let span = Span::new(location..location + 1);
let error = ErrorKind::InvalidToken;
Self { span, error }
}
LalrpopError::UnrecognizedEof { location, expected } => {
let span = Span::new(location..location + 1);
let error = ErrorKind::UnexpectedEof { expected };
Self { span, error }
}
LalrpopError::UnrecognizedToken {
token: (l, token, r),
expected,
} => {
let span = Span::new(l..r);
let error = ErrorKind::UnexpectedToken {
token: token.to_string(),
expected,
};
Self { span, error }
}
LalrpopError::ExtraToken {
token: (l, token, r),
} => {
let span = Span::new(l..r);
let error = ErrorKind::ExtraToken(token.to_string());
Self { span, error }
}
LalrpopError::User {
error: (l, error, r),
} => {
let span = Span::new(l..r);
let error = match error {
ParseError::LexerError => ErrorKind::InvalidToken,
ParseError::ReservedWord(word) => ErrorKind::ReservedWord(word),
ParseError::DiagnosticSeverity => ErrorKind::DiagnosticSeverity,
ParseError::Attribute(attr, expected) => ErrorKind::Attribute(attr, expected),
ParseError::VarTemplate(reason) => ErrorKind::VarTemplate(reason),
};
Self { span, error }
}
}
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct ErrorWithSource<'s> {
pub error: Error,
pub source: Cow<'s, str>,
}
impl std::error::Error for ErrorWithSource<'_> {}
impl<'s> ErrorWithSource<'s> {
pub fn new(error: Error, source: Cow<'s, str>) -> Self {
Self { error, source }
}
}
impl Display for ErrorWithSource<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
use annotate_snippets::*;
let text = format!("{}", self.error.error);
let annot = AnnotationKind::Primary.span(self.error.span.range());
let snip = Snippet::source(self.source.clone())
.fold(true)
.annotation(annot);
let title = Level::ERROR.primary_title(&text);
let group = title.element(snip);
let renderer = Renderer::styled();
let rendered = renderer.render(&[group]);
write!(f, "{rendered}")
}
}