use std::borrow::Cow;
use std::fmt::{Debug, Display};
use partiql_source_map::location::Located;
use thiserror::Error;
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
#[derive(Error, Debug, Clone, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[non_exhaustive]
pub enum LexError<'input> {
#[error("Lexing error: invalid input `{}`", .0)]
InvalidInput(Cow<'input, str>),
#[error("Lexing error: unterminated ion literal")]
UnterminatedIonLiteral,
#[error("Lexing error: unterminated comment")]
UnterminatedComment,
#[error("Lexing error: unknown error")]
Unknown,
}
#[derive(Error, Debug, Clone, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[non_exhaustive]
pub enum ParseError<'input, Loc>
where
Loc: Display,
{
#[error("Syntax Error: {} at `{}`", _0.inner, _0.location)]
SyntaxError(Located<String, Loc>),
#[error("Unexpected end of input")]
UnexpectedEndOfInput,
#[error("Unknown parse error at `{}`", _0)]
Unknown(Loc),
#[error("Unexpected token `{}` at `{}`", _0.inner.token, _0.location)]
UnexpectedToken(UnexpectedToken<'input, Loc>),
#[error("{} at `{}`", _0.inner, _0.location)]
LexicalError(Located<LexError<'input>, Loc>),
#[error("Illegal State: {0}")]
IllegalState(String),
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct UnexpectedTokenData<'input> {
pub token: Cow<'input, str>,
}
pub type UnexpectedToken<'input, L> = Located<UnexpectedTokenData<'input>, L>;
impl<'input, Loc: Debug> ParseError<'input, Loc>
where
Loc: Display,
{
pub fn map_loc<F, Loc2>(self, tx: F) -> ParseError<'input, Loc2>
where
Loc2: Display,
F: FnMut(Loc) -> Loc2,
{
match self {
ParseError::SyntaxError(l) => ParseError::SyntaxError(l.map_loc(tx)),
ParseError::UnexpectedEndOfInput => ParseError::UnexpectedEndOfInput,
ParseError::UnexpectedToken(l) => ParseError::UnexpectedToken(l.map_loc(tx)),
ParseError::LexicalError(l) => ParseError::LexicalError(l.map_loc(tx)),
ParseError::IllegalState(s) => ParseError::IllegalState(s),
_ => ParseError::IllegalState("Unhandled internal error".to_string()),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use partiql_source_map::location::{ByteOffset, BytePosition, LineAndColumn, ToLocated};
use std::num::NonZeroUsize;
#[test]
fn syntax_error() {
let e1 = ParseError::SyntaxError("oops".to_string().to_located(
BytePosition::from(ByteOffset::from(255))..BytePosition::from(ByteOffset::from(512)),
));
let e2 = e1.map_loc(|BytePosition(x)| BytePosition(x - 2));
assert_eq!(e2.to_string(), "Syntax Error: oops at `(b253..b510)`")
}
#[test]
fn unexpected_token() {
let e1 = ParseError::UnexpectedToken(
UnexpectedTokenData { token: "/".into() }
.to_located(BytePosition(0.into())..ByteOffset::from(1).into()),
);
let e2 = e1.map_loc(|x| BytePosition(x.0 + 1));
assert_eq!(e2.to_string(), "Unexpected token `/` at `(b1..b2)`")
}
#[test]
fn lexical_error() {
let lex = LexError::InvalidInput("🤷".into())
.to_located(LineAndColumn::new(1, 1).unwrap()..LineAndColumn::new(5, 5).unwrap());
let e1 = ParseError::LexicalError(lex);
let e2 = e1.map_loc(|LineAndColumn { line, column }| LineAndColumn {
line,
column: NonZeroUsize::new(column.get() + 10).unwrap(),
});
assert_eq!(
e2.to_string(),
"Lexing error: invalid input `🤷` at `(1:11..5:15)`"
)
}
#[test]
fn illegal_state() {
let e1: ParseError<BytePosition> = ParseError::IllegalState("uh oh".to_string());
let e2 = e1.map_loc(|x| x);
assert_eq!(e2.to_string(), "Illegal State: uh oh")
}
}