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 = "generate-for declaration doesn't need type specifier (e.g. 'for i in 0..10 {')".to_string();
87            } else if comma_instead_of_assignment_operator(token, &expected_tokens) {
88                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();
89            } else if l_brace_instead_of_colon(token, &expected_tokens) {
90                help =
91                    "The first arm of generate-if declaration needs label (e.g. 'if x :label {')"
92                        .to_string();
93            } else if keyword_as_identifier(token, &expected_tokens) {
94                help = format!(
95                    "'{}' is a reserved keyword and cannot be used as an identifier",
96                    token
97                );
98            }
99        }
100
101        Self {
102            cause: value.cause,
103            input: value.input.map(|e| FileSource(*e).into()).unwrap(),
104            error_location: Location(*value.error_location).into(),
105            unexpected_tokens,
106            expected_tokens,
107            help,
108        }
109    }
110}
111
112#[derive(Error, Diagnostic, Debug)]
113#[error("Unexpected token: {name} ({token_type})")]
114#[diagnostic(help("Unexpected token"), code(parol_runtime::unexpected_token))]
115pub struct UnexpectedToken {
116    name: String,
117    token_type: TokenType,
118    #[label("Unexpected token")]
119    pub(crate) token: SourceSpan,
120}
121
122include!("generated/token_type_generated.rs");
123
124impl From<ParolError> for ParserError {
125    fn from(x: ParolError) -> ParserError {
126        match x {
127            ParolError::ParserError(x) => match x {
128                parol_runtime::ParserError::SyntaxErrors { mut entries } if !entries.is_empty() => {
129                    ParserError::SyntaxError(Box::new(entries.remove(0).into()))
130                }
131                _ => ParserError::ParserError(x),
132            },
133            ParolError::LexerError(x) => ParserError::LexerError(x),
134            ParolError::UserError(x) => ParserError::UserError(x),
135        }
136    }
137}
138
139struct FileSource(parol_runtime::FileSource);
140
141impl miette::SourceCode for FileSource {
142    fn read_span<'a>(
143        &'a self,
144        span: &SourceSpan,
145        context_lines_before: usize,
146        context_lines_after: usize,
147    ) -> Result<Box<dyn miette::SpanContents<'a> + 'a>, miette::MietteError> {
148        <str as miette::SourceCode>::read_span(
149            &self.0.input,
150            span,
151            context_lines_before,
152            context_lines_after,
153        )
154    }
155}
156
157impl From<FileSource> for NamedSource<FileSource> {
158    fn from(file_source: FileSource) -> Self {
159        let file_name = file_source.0.file_name.clone();
160        let file_name = file_name.to_str().unwrap_or("<Bad file name>");
161        Self::new(file_name, file_source)
162    }
163}
164
165struct Location(parol_runtime::Location);
166
167impl From<Location> for SourceSpan {
168    fn from(location: Location) -> Self {
169        SourceSpan::new((location.0.start as usize).into(), location.0.len())
170    }
171}
172
173struct UnexpectedTokens(Vec<parol_runtime::UnexpectedToken>);
174
175impl From<UnexpectedTokens> for Vec<UnexpectedToken> {
176    fn from(value: UnexpectedTokens) -> Self {
177        value
178            .0
179            .into_iter()
180            .map(|v| UnexpectedToken {
181                name: v.name,
182                token_type: v.token_type.as_str().into(),
183                token: Location(v.token).into(),
184            })
185            .collect::<Vec<UnexpectedToken>>()
186    }
187}
188
189#[derive(Debug)]
190pub struct ExpectedTokens(Vec<TokenType>);
191
192impl ExpectedTokens {
193    pub fn any(&self, x: TokenType) -> bool {
194        self.0.contains(&x)
195    }
196}
197
198impl From<&TokenVec> for ExpectedTokens {
199    fn from(value: &TokenVec) -> Self {
200        ExpectedTokens(value.iter().map(|x| x.as_str().into()).collect())
201    }
202}