prqlc_parser/parser/
perror.rs

1use 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
9// Helper function to convert Rich errors to our Error type
10fn 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                    // Filter out whitespace tokens unless that's all we're expecting
28                    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    // Helper function to create a simple Error object
114    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}