1use std::borrow::Cow;
6use std::fmt::{Debug, Display};
7
8use partiql_common::syntax::location::Located;
9use thiserror::Error;
10
11#[cfg(feature = "serde")]
12use serde::{Deserialize, Serialize};
13
14#[derive(Error, Debug, Clone, PartialEq, Eq, Hash)]
19#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
20#[non_exhaustive]
21pub enum LexError<'input> {
22 #[error("Lexing error: invalid input `{}`", .0)]
24 InvalidInput(Cow<'input, str>),
25 #[error("Lexing error: unterminated embedded document literal")]
27 UnterminatedDocLiteral,
28 #[error("Lexing error: unterminated comment")]
30 UnterminatedComment,
31 #[error("Lexing error: unknown error")]
33 Unknown,
34}
35
36#[derive(Error, Debug, Clone, PartialEq, Eq, Hash)]
41#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
42#[non_exhaustive]
43pub enum ParseError<'input, Loc>
44where
45 Loc: Display,
46{
47 #[error("Syntax Error: {} at `{}`", _0.inner, _0.location)]
49 SyntaxError(Located<String, Loc>),
50
51 #[error("Unexpected end of input")]
53 UnexpectedEndOfInput(Loc),
54
55 #[error("Unknown parse error at `{}`", _0)]
57 Unknown(Loc),
58
59 #[error("Unexpected token `{}` at `{}`", _0.inner.token, _0.location)]
61 UnexpectedToken(UnexpectedToken<'input, Loc>),
62
63 #[error("{} at `{}`", _0.inner, _0.location)]
65 LexicalError(Located<LexError<'input>, Loc>),
66
67 #[error("Illegal State: {0}")]
69 IllegalState(String),
70}
71
72#[derive(Debug, Clone, PartialEq, Eq, Hash)]
73#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
74pub struct UnexpectedTokenData<'input> {
75 pub token: Cow<'input, str>,
77 }
79pub type UnexpectedToken<'input, L> = Located<UnexpectedTokenData<'input>, L>;
80
81impl<'input, Loc: Debug> ParseError<'input, Loc>
82where
83 Loc: Display,
84{
85 pub fn map_loc<F, Loc2>(self, mut tx: F) -> ParseError<'input, Loc2>
87 where
88 Loc2: Display,
89 F: FnMut(Loc) -> Loc2,
90 {
91 match self {
92 ParseError::SyntaxError(l) => ParseError::SyntaxError(l.map_loc(tx)),
93 ParseError::UnexpectedEndOfInput(l) => ParseError::UnexpectedEndOfInput((tx)(l)),
94 ParseError::UnexpectedToken(l) => ParseError::UnexpectedToken(l.map_loc(tx)),
95 ParseError::LexicalError(l) => ParseError::LexicalError(l.map_loc(tx)),
96 ParseError::IllegalState(s) => ParseError::IllegalState(s),
97 _ => ParseError::IllegalState("Unhandled internal error".to_string()),
98 }
99 }
100}
101
102#[cfg(test)]
103mod tests {
104 use super::*;
105 use partiql_common::syntax::location::{ByteOffset, BytePosition, LineAndColumn, ToLocated};
106 use std::num::NonZeroUsize;
107
108 #[test]
109 fn syntax_error() {
110 let e1 = ParseError::SyntaxError("oops".to_string().to_located(
111 BytePosition::from(ByteOffset::from(255))..BytePosition::from(ByteOffset::from(512)),
112 ));
113
114 let e2 = e1.map_loc(|BytePosition(x)| BytePosition(x - 2));
115 assert_eq!(e2.to_string(), "Syntax Error: oops at `(b253..b510)`");
116 }
117
118 #[test]
119 fn unexpected_token() {
120 let e1 = ParseError::UnexpectedToken(
121 UnexpectedTokenData { token: "/".into() }
122 .to_located(BytePosition(0.into())..ByteOffset::from(1).into()),
123 );
124
125 let e2 = e1.map_loc(|x| BytePosition(x.0 + 1));
126 assert_eq!(e2.to_string(), "Unexpected token `/` at `(b1..b2)`");
127 }
128
129 #[test]
130 fn lexical_error() {
131 let lex = LexError::InvalidInput("🤷".into())
132 .to_located(LineAndColumn::new(1, 1).unwrap()..LineAndColumn::new(5, 5).unwrap());
133 let e1 = ParseError::LexicalError(lex);
134
135 let e2 = e1.map_loc(|LineAndColumn { line, column }| LineAndColumn {
136 line,
137 column: NonZeroUsize::new(column.get() + 10).unwrap(),
138 });
139 assert_eq!(
140 e2.to_string(),
141 "Lexing error: invalid input `🤷` at `(1:11..5:15)`"
142 );
143 }
144
145 #[test]
146 fn illegal_state() {
147 let e1: ParseError<'_, BytePosition> = ParseError::IllegalState("uh oh".to_string());
148
149 let e2 = e1.map_loc(|x| x);
150 assert_eq!(e2.to_string(), "Illegal State: uh oh");
151 }
152}