prqlc_parser/parser/
perror.rs1use chumsky;
2use chumsky::error::Rich;
3
4use crate::error::WithErrorInfo;
5use crate::error::{Error, Reason};
6use crate::lexer::lr::TokenKind;
7use crate::span::Span;
8
9fn rich_error_to_error<T>(
11 span: Span,
12 reason: &chumsky::error::RichReason<T>,
13 token_to_string: impl Fn(&T) -> String,
14 is_whitespace_token: impl Fn(&T) -> bool,
15) -> Error
16where
17 T: std::fmt::Debug,
18{
19 use chumsky::error::RichReason;
20
21 let error = match reason {
22 RichReason::ExpectedFound { expected, found } => {
23 use chumsky::error::RichPattern;
24 let expected_strs: Vec<String> = expected
25 .iter()
26 .filter(|p| {
27 let is_whitespace = match p {
29 RichPattern::EndOfInput => true,
30 RichPattern::Token(t) => is_whitespace_token(t),
31 _ => false,
32 };
33 !is_whitespace
34 || expected.iter().all(|p| match p {
35 RichPattern::EndOfInput => true,
36 RichPattern::Token(t) => is_whitespace_token(t),
37 _ => false,
38 })
39 })
40 .map(|p| match p {
41 RichPattern::Token(t) => token_to_string(t),
42 RichPattern::EndOfInput => "end of input".to_string(),
43 _ => format!("{:?}", p),
44 })
45 .collect();
46
47 let found_str = match found {
48 Some(t) => token_to_string(t),
49 None => "end of input".to_string(),
50 };
51
52 if expected_strs.is_empty() || expected_strs.len() > 10 {
53 Error::new_simple(format!("unexpected {found_str}"))
54 } else {
55 let mut expected_strs = expected_strs;
56 expected_strs.sort();
57
58 let expected_str = match expected_strs.len() {
59 1 => expected_strs[0].clone(),
60 2 => expected_strs.join(" or "),
61 _ => {
62 let last = expected_strs.pop().unwrap();
63 format!("one of {} or {last}", expected_strs.join(", "))
64 }
65 };
66
67 match found {
68 Some(_) => Error::new(Reason::Expected {
69 who: None,
70 expected: expected_str,
71 found: found_str,
72 }),
73 None => Error::new(Reason::Simple(format!(
74 "Expected {expected_str}, but didn't find anything before the end."
75 ))),
76 }
77 }
78 }
79 RichReason::Custom(msg) => Error::new_simple(msg.to_string()),
80 };
81
82 error.with_span(Some(span))
83}
84
85impl<'a> From<Rich<'a, crate::lexer::lr::Token, Span>> for Error {
86 fn from(rich: Rich<'a, crate::lexer::lr::Token, Span>) -> Error {
87 rich_error_to_error(
88 *rich.span(),
89 rich.reason(),
90 |token| format!("{}", token.kind),
91 |token| matches!(token.kind, TokenKind::NewLine | TokenKind::Start),
92 )
93 }
94}
95
96impl<'a> From<Rich<'a, TokenKind, Span>> for Error {
97 fn from(rich: Rich<'a, TokenKind, Span>) -> Error {
98 rich_error_to_error(
99 *rich.span(),
100 rich.reason(),
101 |kind| format!("{}", kind),
102 |kind| matches!(kind, TokenKind::NewLine | TokenKind::Start),
103 )
104 }
105}
106
107#[cfg(test)]
108mod tests {
109 use insta::{assert_debug_snapshot, assert_snapshot};
110
111 use crate::error::{Error, WithErrorInfo};
112
113 fn simple_error(message: &str) -> Error {
115 Error::new_simple(message)
116 }
117
118 #[test]
119 fn test_error_messages() {
120 let error1 = simple_error("test error");
121 assert_snapshot!(error1.to_string(), @r#"Error { kind: Error, span: None, reason: Simple("test error"), hints: [], code: None }"#);
122
123 let error2 = simple_error("another error").with_span(Some(crate::span::Span {
124 start: 0,
125 end: 5,
126 source_id: 0,
127 }));
128 assert_debug_snapshot!(error2, @r#"
129 Error {
130 kind: Error,
131 span: Some(
132 0:0-5,
133 ),
134 reason: Simple(
135 "another error",
136 ),
137 hints: [],
138 code: None,
139 }
140 "#);
141 }
142}