1use std::fmt::Display;
2
3use chumsky::{error::SimpleReason, Span as ChumskySpan};
4use prql_ast::Span;
5
6use crate::{lexer::Token, PError};
7
8#[derive(Debug)]
9pub struct Error {
10 pub span: Span,
11 pub kind: ErrorKind,
12}
13
14#[derive(Debug)]
15pub enum ErrorKind {
16 Lexer(LexerError),
17 Parser(ParserError),
18}
19
20impl Display for ErrorKind {
21 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
22 match self {
23 ErrorKind::Lexer(err) => write!(f, "{err}"),
24 ErrorKind::Parser(err) => write!(f, "{err}"),
25 }
26 }
27}
28
29#[derive(Debug)]
30pub struct LexerError(String);
31
32#[derive(Debug)]
33pub struct ParserError(String);
34
35impl Display for ParserError {
36 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
37 write!(f, "{}", self.0)
38 }
39}
40
41impl Display for LexerError {
42 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
43 write!(f, "unexpected {}", self.0)
44 }
45}
46
47pub(crate) fn convert_lexer_error(
48 source: &str,
49 e: chumsky::error::Cheap<char>,
50 source_id: u16,
51) -> Error {
52 let found = source
55 .chars()
56 .skip(e.span().start)
57 .take(e.span().end() - e.span().start)
58 .collect();
59 let span = Span {
60 start: e.span().start,
61 end: e.span().end,
62 source_id,
63 };
64
65 Error {
66 span,
67 kind: ErrorKind::Lexer(LexerError(found)),
68 }
69}
70
71pub(crate) fn convert_parser_error(e: PError) -> Error {
72 let mut span = e.span();
73
74 if e.found().is_none() {
75 if span.start > 0 && span.end > 0 {
78 span.start -= 1;
79 span.end -= 1;
80 }
81 }
82
83 if let SimpleReason::Custom(message) = e.reason() {
84 return Error {
85 span: *span,
86 kind: ErrorKind::Parser(ParserError(message.clone())),
87 };
88 }
89
90 fn token_to_string(t: Option<Token>) -> String {
91 t.map(|t| DisplayToken(&t).to_string())
92 .unwrap_or_else(|| "end of input".to_string())
93 }
94
95 let is_all_whitespace = e
96 .expected()
97 .all(|t| matches!(t, None | Some(Token::NewLine)));
98 let expected: Vec<String> = e
99 .expected()
100 .filter(|t| is_all_whitespace || !matches!(t, None | Some(Token::NewLine)))
105 .cloned()
106 .map(token_to_string)
107 .collect();
108
109 let while_parsing = e
110 .label()
111 .map(|l| format!(" while parsing {l}"))
112 .unwrap_or_default();
113
114 if expected.is_empty() || expected.len() > 10 {
115 let label = token_to_string(e.found().cloned());
116
117 return Error {
118 span: *span,
119 kind: ErrorKind::Parser(ParserError(format!("unexpected {label}{while_parsing}"))),
120 };
121 }
122
123 let mut expected = expected;
124 expected.sort();
125
126 let expected = match expected.len() {
127 1 => expected.remove(0),
128 2 => expected.join(" or "),
129 _ => {
130 let last = expected.pop().unwrap();
131 format!("one of {} or {last}", expected.join(", "))
132 }
133 };
134
135 Error {
136 span: *span,
137 kind: ErrorKind::Parser(ParserError(match e.found() {
138 Some(found) => format!(
139 "{who}expected {expected}, but found {found}",
140 who = e.label().map(|l| format!("{l} ")).unwrap_or_default(),
141 found = DisplayToken(found)
142 ),
143
144 None => format!("Expected {expected}, but didn't find anything before the end."),
146 })),
147 }
148}
149
150struct DisplayToken<'a>(&'a Token);
151
152impl std::fmt::Display for DisplayToken<'_> {
153 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
154 match self.0 {
155 Token::NewLine => write!(f, "new line"),
156 Token::Ident(arg0) => {
157 if arg0.is_empty() {
158 write!(f, "an identifier")
159 } else {
160 write!(f, "`{arg0}`")
161 }
162 }
163 Token::Keyword(arg0) => write!(f, "keyword {arg0}"),
164 Token::Literal(..) => write!(f, "literal"),
165 Token::Control(arg0) => write!(f, "{arg0}"),
166
167 Token::ArrowThin => f.write_str("->"),
168 Token::ArrowFat => f.write_str("=>"),
169 Token::Eq => f.write_str("=="),
170 Token::Ne => f.write_str("!="),
171 Token::Gte => f.write_str(">="),
172 Token::Lte => f.write_str("<="),
173 Token::RegexSearch => f.write_str("~="),
174 Token::And => f.write_str("&&"),
175 Token::Or => f.write_str("||"),
176 Token::Coalesce => f.write_str("??"),
177 Token::DivInt => f.write_str("//"),
178 Token::Annotate => f.write_str("@{"),
179
180 Token::Param(id) => write!(f, "${id}"),
181
182 Token::Range {
183 bind_left,
184 bind_right,
185 } => write!(
186 f,
187 "'{}..{}'",
188 if *bind_left { "" } else { " " },
189 if *bind_right { "" } else { " " }
190 ),
191 Token::Interpolation(c, s) => {
192 write!(f, "{c}\"{}\"", s)
193 }
194 }
195 }
196}