veryl_parser/
parser_error.rs1use 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 let Some(token) = self.unexpected_tokens.last() {
39 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 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 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 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}