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        if !self.unexpected_tokens.is_empty() {
38            let token = self.unexpected_tokens[0].token_type;
39            f.write_str(&format!("Unexpected token: '{token}'"))
40        } else {
41            f.write_str("Syntax Error")
42        }
43    }
44}
45
46fn l_angle(unexpected_token: TokenType, _expected_tokens: &ExpectedTokens) -> bool {
47    unexpected_token == TokenType::LAngle
48}
49
50fn r_angle(unexpected_token: TokenType, _expected_tokens: &ExpectedTokens) -> bool {
51    unexpected_token == TokenType::RAngle
52}
53
54fn colon_instead_of_in(unexpected_token: TokenType, expected_tokens: &ExpectedTokens) -> bool {
55    unexpected_token == TokenType::Colon && expected_tokens.any(TokenType::In)
56}
57
58fn comma_instead_of_assignment_operator(
59    unexpected_token: TokenType,
60    expected_tokens: &ExpectedTokens,
61) -> bool {
62    unexpected_token == TokenType::Comma && expected_tokens.any(TokenType::AssignmentOperator)
63}
64
65fn l_brace_instead_of_colon(unexpected_token: TokenType, expected_tokens: &ExpectedTokens) -> bool {
66    unexpected_token == TokenType::LBrace && expected_tokens.any(TokenType::Colon)
67}
68
69fn keyword_as_identifier(unexpected_token: TokenType, expected_tokens: &ExpectedTokens) -> bool {
70    unexpected_token.is_keyword() && expected_tokens.any(TokenType::Identifier)
71}
72
73impl From<parol_runtime::SyntaxError> for SyntaxError {
74    fn from(value: parol_runtime::SyntaxError) -> Self {
75        let unexpected_tokens: Vec<_> = UnexpectedTokens(value.unexpected_tokens).into();
76        let expected_tokens: ExpectedTokens = (&value.expected_tokens).into();
77
78        let mut help = String::new();
79        if !unexpected_tokens.is_empty() {
80            let token = unexpected_tokens[0].token_type;
81            if l_angle(token, &expected_tokens) {
82                help = "If you mean \"less than operator\", please use '<:'".to_string();
83            } else if r_angle(token, &expected_tokens) {
84                help = "If you mean \"greater than operator\", please use '>:'".to_string();
85            } else if colon_instead_of_in(token, &expected_tokens) {
86                help = "for declaration doesn't need type specifier (e.g. 'for i in 0..10 {')"
87                    .to_string();
88            } else if comma_instead_of_assignment_operator(token, &expected_tokens) {
89                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();
90            } else if l_brace_instead_of_colon(token, &expected_tokens) {
91                help =
92                    "The first arm of generate-if declaration needs label (e.g. 'if x :label {')"
93                        .to_string();
94            } else if keyword_as_identifier(token, &expected_tokens) {
95                help = format!(
96                    "'{}' is a reserved keyword and cannot be used as an identifier",
97                    token
98                );
99            }
100        }
101
102        Self {
103            cause: value.cause,
104            input: value.input.map(|e| FileSource(*e).into()).unwrap(),
105            error_location: Location(*value.error_location).into(),
106            unexpected_tokens,
107            expected_tokens,
108            help,
109        }
110    }
111}
112
113#[derive(Error, Diagnostic, Debug)]
114#[error("Unexpected token: {name} ({token_type})")]
115#[diagnostic(help("Unexpected token"), code(parol_runtime::unexpected_token))]
116pub struct UnexpectedToken {
117    name: String,
118    token_type: TokenType,
119    #[label("Unexpected token")]
120    pub(crate) token: SourceSpan,
121}
122
123include!("generated/token_type_generated.rs");
124
125impl From<ParolError> for ParserError {
126    fn from(x: ParolError) -> ParserError {
127        match x {
128            ParolError::ParserError(x) => match x {
129                parol_runtime::ParserError::SyntaxErrors { mut entries } if !entries.is_empty() => {
130                    ParserError::SyntaxError(Box::new(entries.remove(0).into()))
131                }
132                _ => ParserError::ParserError(x),
133            },
134            ParolError::LexerError(x) => ParserError::LexerError(x),
135            ParolError::UserError(x) => ParserError::UserError(x),
136        }
137    }
138}
139
140struct FileSource(parol_runtime::FileSource);
141
142impl miette::SourceCode for FileSource {
143    fn read_span<'a>(
144        &'a self,
145        span: &SourceSpan,
146        context_lines_before: usize,
147        context_lines_after: usize,
148    ) -> Result<Box<dyn miette::SpanContents<'a> + 'a>, miette::MietteError> {
149        <str as miette::SourceCode>::read_span(
150            &self.0.input,
151            span,
152            context_lines_before,
153            context_lines_after,
154        )
155    }
156}
157
158impl From<FileSource> for NamedSource<FileSource> {
159    fn from(file_source: FileSource) -> Self {
160        let file_name = file_source.0.file_name.clone();
161        let file_name = file_name.to_str().unwrap_or("<Bad file name>");
162        Self::new(file_name, file_source)
163    }
164}
165
166struct Location(parol_runtime::Location);
167
168impl From<Location> for SourceSpan {
169    fn from(location: Location) -> Self {
170        SourceSpan::new((location.0.start as usize).into(), location.0.len())
171    }
172}
173
174struct UnexpectedTokens(Vec<parol_runtime::UnexpectedToken>);
175
176impl From<UnexpectedTokens> for Vec<UnexpectedToken> {
177    fn from(value: UnexpectedTokens) -> Self {
178        value
179            .0
180            .into_iter()
181            .map(|v| UnexpectedToken {
182                name: v.name,
183                token_type: v.token_type.as_str().into(),
184                token: Location(v.token).into(),
185            })
186            .collect::<Vec<UnexpectedToken>>()
187    }
188}
189
190#[derive(Debug)]
191pub struct ExpectedTokens(Vec<TokenType>);
192
193impl ExpectedTokens {
194    pub fn any(&self, x: TokenType) -> bool {
195        self.0.contains(&x)
196    }
197}
198
199impl From<&TokenVec> for ExpectedTokens {
200    fn from(value: &TokenVec) -> Self {
201        ExpectedTokens(value.iter().map(|x| x.as_str().into()).collect())
202    }
203}