astray_core/error/
parse_error.rs

1use crate::{ConsumableToken, Parsable};
2
3#[derive(Debug, Clone, PartialEq, Eq)]
4pub enum ParseErrorType<T>
5where
6    T: ConsumableToken,
7{
8    UnexpectedToken { expected: T, found: T },
9    NoMoreTokens,
10    ParsedButUnmatching { err_msg: String }, // TODO: specify extra fields here which might be useful
11    ConjunctBranchParsingFailure { err_source: Box<ParseError<T>> },
12    DisjunctBranchParsingFailure { err_source: Vec<ParseError<T>> },
13}
14
15// TODO: Refactor type_name
16#[derive(Debug, Clone, PartialEq, Eq)]
17pub struct ParseError<T>
18where
19    T: ConsumableToken,
20{
21    type_name: &'static str,
22    failed_at: usize,
23    pub failure_type: ParseErrorType<T>,
24}
25
26impl<T> ParseError<T>
27where
28    T: ConsumableToken,
29{
30    pub fn new<P>(failed_at: usize, failure_type: ParseErrorType<T>) -> Self
31    where
32        P: Parsable<T>,
33    {
34        Self {
35            type_name: P::identifier(),
36            failed_at,
37            failure_type,
38        }
39    }
40
41    pub fn parsed_but_unmatching<P>(
42        failed_at: usize,
43        result: &P,
44        pattern: Option<&'static str>,
45    ) -> Self
46    where
47        P: Parsable<T>,
48    {
49        let pattern = pattern.unwrap_or("(pattern not provided by user)");
50        let type_name = <P as Parsable<T>>::identifier();
51        let err_msg = format!(
52            "Parsed {:?}: {type_name}, but it did not match pattern '{pattern}'",
53            result
54        );
55        ParseError::new::<P>(failed_at, ParseErrorType::ParsedButUnmatching { err_msg })
56    }
57
58    pub fn no_more_tokens<P>(failed_at: usize) -> Self
59    where
60        P: Parsable<T>,
61    {
62        ParseError::new::<P>(failed_at, ParseErrorType::NoMoreTokens)
63    }
64
65    pub fn from_conjunct_error<P>(other: ParseError<T>) -> Self
66    where
67        P: Parsable<T>,
68    {
69        ParseError::new::<P>(
70            other.failed_at,
71            ParseErrorType::ConjunctBranchParsingFailure {
72                err_source: Box::new(other),
73            },
74        )
75    }
76
77    pub fn from_disjunct_errors<P>(failed_at: usize, err_source: Vec<ParseError<T>>) -> Self
78    where
79        P: Parsable<T>,
80    {
81        ParseError::new::<P>(
82            failed_at,
83            ParseErrorType::DisjunctBranchParsingFailure { err_source },
84        )
85    }
86
87    pub fn to_string(&self, indentation_level: usize) -> String {
88        let tabs = "\t".repeat(indentation_level);
89        match &self.failure_type {
90            ParseErrorType::UnexpectedToken { expected, found } => {
91                let more_tabs = "\t".repeat(indentation_level + 1);
92                format!("{tabs}Unexpected Token Error\n{more_tabs}Expected {:?}\n{more_tabs}Found {:?}\n", expected, found)
93            }
94            ParseErrorType::NoMoreTokens => {
95                format!("{tabs}Ran out of tokens\n")
96            }
97            ParseErrorType::ParsedButUnmatching { err_msg } => {
98                format!("{tabs}{err_msg}")
99            }
100            ParseErrorType::ConjunctBranchParsingFailure { err_source } => {
101                let err_source_str = err_source.to_string(indentation_level + 1);
102                format!(
103                    "{tabs}Failed to parse {}:\n{err_source_str}",
104                    self.type_name
105                )
106            }
107            ParseErrorType::DisjunctBranchParsingFailure { err_source } => {
108                let errors = err_source
109                    .iter()
110                    .map(|e| e.to_string(indentation_level + 1))
111                    .reduce(|accum, curr| accum + "\n" + &curr)
112                    .expect("Enums without variants cannot implement Parsable");
113                format!("{tabs}Failed to parse {}:\n{errors}", self.type_name)
114            }
115        }
116    }
117}
118
119#[cfg(test)]
120mod tests {
121    use crate::token::Token;
122    use crate::{t, Parsable, ParseError};
123
124    #[test]
125    fn to_string() {
126        let pattern1 = "Token::KwReturn";
127        let pattern2 = "Token::LiteralInt(_)";
128        let result =
129            ParseError::from_conjunct_error::<Token>(
130                ParseError::from_disjunct_errors::<Token>(
131                1,
132                vec![
133                    ParseError::parsed_but_unmatching::<Token>(
134                        1,
135                        &t!(return),
136                        Some(pattern1),
137                    ),
138                    ParseError::parsed_but_unmatching::<Token>(
139                        1,
140                        &t!(litint 3),
141                        Some(pattern2),
142                    ),
143                ],
144            ))
145            .to_string(0);
146        let expected_identifier = <Token as Parsable<Token>>::identifier();
147        let expected = format!(
148            "Failed to parse {expected_identifier}:
149\tFailed to parse {expected_identifier}:
150\t\tParsed {:?}: {expected_identifier}, but it did not match pattern '{pattern1}'
151\t\tParsed {:?}: {expected_identifier}, but it did not match pattern '{pattern2}'",
152            t!(return),
153            t!(litint 3),
154        );
155        assert_eq!(expected, result)
156    }
157}