Skip to main content

veryl_parser/
parser_error.rs

1use miette::{self, Diagnostic, NamedSource, SourceSpan};
2use parol_runtime::{ParolError, TokenVec};
3use thiserror::Error;
4
5#[derive(Error, Diagnostic, Debug)]
6pub enum ParserError {
7    #[error(transparent)]
8    #[diagnostic(transparent)]
9    SyntaxError(Box<SyntaxError>),
10
11    #[error(transparent)]
12    ParserError(#[from] parol_runtime::ParserError),
13
14    #[error(transparent)]
15    LexerError(#[from] parol_runtime::LexerError),
16
17    #[error(transparent)]
18    UserError(#[from] anyhow::Error),
19}
20
21#[derive(Error, Diagnostic, Debug)]
22#[diagnostic(code(ParserError::SyntaxError))]
23pub struct SyntaxError {
24    pub cause: String,
25    #[source_code]
26    input: NamedSource<FileSource>,
27    #[label("Error location")]
28    pub error_location: SourceSpan,
29    pub unexpected_tokens: Vec<UnexpectedToken>,
30    pub expected_tokens: ExpectedTokens,
31    #[help]
32    pub help: String,
33}
34
35impl std::fmt::Display for SyntaxError {
36    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
37        // The last lookahead token is the actual divergence; earlier ones form a valid prefix.
38        if let Some(token) = self.unexpected_tokens.last() {
39            // `TokenType::Error` (an unlexable character) displays as "error"; show its text.
40            if token.token_type == TokenType::Error
41                && let Some(text) = &token.text
42            {
43                write!(f, "Unexpected token: '{text}'")
44            } else {
45                write!(f, "Unexpected token: '{}'", token.token_type)
46            }
47        } else {
48            f.write_str("Syntax Error")
49        }
50    }
51}
52
53fn l_angle(unexpected_token: TokenType, _expected_tokens: &ExpectedTokens) -> bool {
54    unexpected_token == TokenType::LAngle
55}
56
57fn r_angle(unexpected_token: TokenType, _expected_tokens: &ExpectedTokens) -> bool {
58    unexpected_token == TokenType::RAngle
59}
60
61fn colon_instead_of_in(unexpected_token: TokenType, expected_tokens: &ExpectedTokens) -> bool {
62    unexpected_token == TokenType::Colon && expected_tokens.any(TokenType::In)
63}
64
65fn comma_instead_of_assignment_operator(
66    unexpected_token: TokenType,
67    expected_tokens: &ExpectedTokens,
68) -> bool {
69    unexpected_token == TokenType::Comma && expected_tokens.any(TokenType::AssignmentOperator)
70}
71
72fn l_brace_instead_of_colon(unexpected_token: TokenType, expected_tokens: &ExpectedTokens) -> bool {
73    unexpected_token == TokenType::LBrace && expected_tokens.any(TokenType::Colon)
74}
75
76fn keyword_as_identifier(unexpected_token: TokenType, expected_tokens: &ExpectedTokens) -> bool {
77    unexpected_token.is_keyword() && expected_tokens.any(TokenType::Identifier)
78}
79
80fn block_or_if_after_else(
81    unexpected_tokens: &[UnexpectedToken],
82    expected_tokens: &ExpectedTokens,
83) -> bool {
84    // `else` is valid after the preceding block, so the hint targets what must follow it.
85    let after_else = unexpected_tokens.len() >= 2
86        && unexpected_tokens[unexpected_tokens.len() - 2].token_type == TokenType::Else;
87    after_else && expected_tokens.any(TokenType::LBrace) && expected_tokens.any(TokenType::If)
88}
89
90impl From<parol_runtime::SyntaxError> for SyntaxError {
91    fn from(value: parol_runtime::SyntaxError) -> Self {
92        let source = value.input.as_deref().map(|f| f.input.as_str());
93        let unexpected_tokens: Vec<UnexpectedToken> = value
94            .unexpected_tokens
95            .into_iter()
96            .map(|v| {
97                let token: SourceSpan = Location(v.token).into();
98                let token_type: TokenType = v.token_type.as_str().into();
99                let text = (token_type == TokenType::Error)
100                    .then(|| {
101                        source.and_then(|s| s.get(token.offset()..token.offset() + token.len()))
102                    })
103                    .flatten()
104                    .map(str::to_string);
105                UnexpectedToken {
106                    name: v.name,
107                    token_type,
108                    token,
109                    text,
110                }
111            })
112            .collect();
113        let expected_tokens: ExpectedTokens = (&value.expected_tokens).into();
114
115        let mut help = String::new();
116        if let Some(token) = unexpected_tokens.last() {
117            let token = token.token_type;
118            if block_or_if_after_else(&unexpected_tokens, &expected_tokens) {
119                help = "'else' must be followed by a block ('{ ... }') or 'if'".to_string();
120            } else if l_angle(token, &expected_tokens) {
121                help = "If you mean \"less than operator\", please use '<:'".to_string();
122            } else if r_angle(token, &expected_tokens) {
123                help = "If you mean \"greater than operator\", please use '>:'".to_string();
124            } else if colon_instead_of_in(token, &expected_tokens) {
125                help = "for declaration doesn't need type specifier (e.g. 'for i in 0..10 {')"
126                    .to_string();
127            } else if comma_instead_of_assignment_operator(token, &expected_tokens) {
128                help = "single case statement with bit concatenation at the left-hand side is not allowed,\nplease surround it by '{}' (e.g. 'x: { {a, b} = 1; }')".to_string();
129            } else if l_brace_instead_of_colon(token, &expected_tokens) {
130                help =
131                    "The first arm of generate-if declaration needs label (e.g. 'if x :label {')"
132                        .to_string();
133            } else if keyword_as_identifier(token, &expected_tokens) {
134                help = format!(
135                    "'{}' is a reserved keyword and cannot be used as an identifier",
136                    token
137                );
138            }
139        }
140
141        Self {
142            cause: value.cause,
143            input: value.input.map(|e| FileSource(*e).into()).unwrap(),
144            // parol points `error_location` at its LA(1), a valid prefix; use the divergence.
145            error_location: unexpected_tokens
146                .last()
147                .map(|t| t.token)
148                .unwrap_or_else(|| Location(*value.error_location).into()),
149            unexpected_tokens,
150            expected_tokens,
151            help,
152        }
153    }
154}
155
156#[derive(Error, Diagnostic, Debug)]
157#[error("Unexpected token: {name} ({token_type})")]
158#[diagnostic(help("Unexpected token"), code(parol_runtime::unexpected_token))]
159pub struct UnexpectedToken {
160    name: String,
161    token_type: TokenType,
162    #[label("Unexpected token")]
163    pub(crate) token: SourceSpan,
164    // Source text, set only for `Error` tokens (see `Display`).
165    text: Option<String>,
166}
167
168include!("generated/token_type_generated.rs");
169
170impl From<ParolError> for ParserError {
171    fn from(x: ParolError) -> ParserError {
172        match x {
173            ParolError::ParserError(x) => match x {
174                parol_runtime::ParserError::SyntaxErrors { mut entries } if !entries.is_empty() => {
175                    ParserError::SyntaxError(Box::new(entries.remove(0).into()))
176                }
177                _ => ParserError::ParserError(x),
178            },
179            ParolError::LexerError(x) => ParserError::LexerError(x),
180            ParolError::UserError(x) => ParserError::UserError(x),
181        }
182    }
183}
184
185struct FileSource(parol_runtime::FileSource);
186
187impl miette::SourceCode for FileSource {
188    fn read_span<'a>(
189        &'a self,
190        span: &SourceSpan,
191        context_lines_before: usize,
192        context_lines_after: usize,
193    ) -> Result<Box<dyn miette::SpanContents<'a> + 'a>, miette::MietteError> {
194        <str as miette::SourceCode>::read_span(
195            &self.0.input,
196            span,
197            context_lines_before,
198            context_lines_after,
199        )
200    }
201}
202
203impl From<FileSource> for NamedSource<FileSource> {
204    fn from(file_source: FileSource) -> Self {
205        let file_name = file_source.0.file_name.clone();
206        let file_name = file_name.to_str().unwrap_or("<Bad file name>");
207        Self::new(file_name, file_source)
208    }
209}
210
211struct Location(parol_runtime::Location);
212
213impl From<Location> for SourceSpan {
214    fn from(location: Location) -> Self {
215        SourceSpan::new((location.0.start as usize).into(), location.0.len())
216    }
217}
218
219#[derive(Debug)]
220pub struct ExpectedTokens(Vec<TokenType>);
221
222impl ExpectedTokens {
223    pub fn any(&self, x: TokenType) -> bool {
224        self.0.contains(&x)
225    }
226}
227
228impl From<&TokenVec> for ExpectedTokens {
229    fn from(value: &TokenVec) -> Self {
230        ExpectedTokens(value.iter().map(|x| x.as_str().into()).collect())
231    }
232}