1use std::fs;
2use std::ops::Range;
3
4use crate::{GrammarAnalysisError, ParolParserError};
5use parol_runtime::{
6 Report,
7 codespan_reporting::{
8 diagnostic::{Diagnostic, Label},
9 files::SimpleFiles,
10 term::{self, Config, termcolor::StandardStream},
11 },
12};
13
14pub struct ParolErrorReporter {}
16
17impl Report for ParolErrorReporter {
18 fn report_user_error(err: &anyhow::Error) -> anyhow::Result<()> {
19 let files: SimpleFiles<String, String> = SimpleFiles::new();
20 let writer = StandardStream::stderr(term::termcolor::ColorChoice::Auto);
21 let config = Config::default();
22
23 if let Some(err) = err.downcast_ref::<ParolParserError>() {
24 match err {
25 ParolParserError::UnknownScanner {
26 context,
27 name,
28 input,
29 token,
30 } => {
31 let mut files = SimpleFiles::new();
32 let content = fs::read_to_string(input).unwrap_or_default();
33 let file_id = files.add(input.display().to_string(), content);
34
35 Ok(term::emit(
36 &mut writer.lock(),
37 &config,
38 &files,
39 &Diagnostic::error()
40 .with_message(format!("{context} - Unknown scanner {name}"))
41 .with_code("parol::parser::unknown_scanner")
42 .with_labels(vec![Label::primary(file_id, Into::<Range<usize>>::into(token))])
43 .with_notes(vec!["Undeclared scanner found. Please declare a scanner via %scanner name {{...}}".to_string()])
44 )?)
45 }
46 ParolParserError::EmptyGroup {
47 context,
48 input,
49 start,
50 end,
51 } => {
52 let mut files = SimpleFiles::new();
53 let content = fs::read_to_string(input).unwrap_or_default();
54 let file_id = files.add(input.display().to_string(), content);
55
56 Ok(term::emit(
57 &mut writer.lock(),
58 &config,
59 &files,
60 &Diagnostic::error()
61 .with_message(format!("{context} - Empty Group not allowed"))
62 .with_code("parol::parser::empty_group")
63 .with_labels(vec![
64 Label::primary(file_id, Into::<Range<usize>>::into(start))
65 .with_message("Start"),
66 Label::primary(file_id, Into::<Range<usize>>::into(end))
67 .with_message("End"),
68 ])
69 .with_notes(vec!["Empty groups can be safely removed.".to_string()]),
70 )?)
71 }
72 ParolParserError::EmptyOptional {
73 context,
74 input,
75 start,
76 end,
77 } => {
78 let mut files = SimpleFiles::new();
79 let content = fs::read_to_string(input).unwrap_or_default();
80 let file_id = files.add(input.display().to_string(), content);
81
82 Ok(term::emit(
83 &mut writer.lock(),
84 &config,
85 &files,
86 &Diagnostic::error()
87 .with_message(format!("{context} - Empty Optionals not allowed"))
88 .with_code("parol::parser::empty_optional")
89 .with_labels(vec![
90 Label::primary(file_id, Into::<Range<usize>>::into(start))
91 .with_message("Start"),
92 Label::primary(file_id, Into::<Range<usize>>::into(end))
93 .with_message("End"),
94 ])
95 .with_notes(vec!["Empty optionals can be safely removed.".to_string()]),
96 )?)
97 }
98 ParolParserError::EmptyRepetition {
99 context,
100 input,
101 start,
102 end,
103 } => {
104 let mut files = SimpleFiles::new();
105 let content = fs::read_to_string(input).unwrap_or_default();
106 let file_id = files.add(input.display().to_string(), content);
107
108 Ok(term::emit(
109 &mut writer.lock(),
110 &config,
111 &files,
112 &Diagnostic::error()
113 .with_message(format!("{context} - Empty Repetitions not allowed"))
114 .with_code("parol::parser::empty_repetition")
115 .with_labels(vec![
116 Label::primary(file_id, Into::<Range<usize>>::into(start))
117 .with_message("Start"),
118 Label::primary(file_id, Into::<Range<usize>>::into(end))
119 .with_message("End"),
120 ])
121 .with_notes(vec![
122 "Empty repetitions can be safely removed.".to_string(),
123 ]),
124 )?)
125 }
126 ParolParserError::ConflictingTokenAliases {
127 first_alias,
128 second_alias,
129 expanded,
130 input,
131 first,
132 second,
133 } => {
134 let mut files = SimpleFiles::new();
135 let content = fs::read_to_string(input).unwrap_or_default();
136 let file_id = files.add(input.display().to_string(), content);
137
138 Ok(term::emit(
139 &mut writer.lock(),
140 &config,
141 &files,
142 &Diagnostic::error()
143 .with_message(format!(
144 r"Multiple token aliases that expand to the same text:
145 '{first_alias}' and '{second_alias}' expand both to '{expanded}'."
146 ))
147 .with_code("parol::parser::conflicting_token_aliases")
148 .with_labels(vec![
149 Label::primary(file_id, Into::<Range<usize>>::into(first))
150 .with_message("First alias"),
151 Label::primary(file_id, Into::<Range<usize>>::into(second))
152 .with_message("Second alias"),
153 ])
154 .with_notes(vec![
155 "Consider using only one single terminal instead of two."
156 .to_string(),
157 ]),
158 )?)
159 }
160 ParolParserError::EmptyScanners { empty_scanners } => Ok(term::emit(
161 &mut writer.lock(),
162 &config,
163 &files,
164 &Diagnostic::error()
165 .with_message(format!("Empty scanner states ({empty_scanners:?}) found"))
166 .with_code("parol::parser::empty_scanner_states")
167 .with_notes(vec![
168 "Assign at least one terminal or remove them.".to_string(),
169 ]),
170 )?),
171 ParolParserError::UnsupportedGrammarType {
172 grammar_type,
173 input,
174 token,
175 } => {
176 let mut files = SimpleFiles::new();
177 let content = fs::read_to_string(input).unwrap_or_default();
178 let file_id = files.add(input.display().to_string(), content);
179
180 Ok(term::emit(
181 &mut writer.lock(),
182 &config,
183 &files,
184 &Diagnostic::error()
185 .with_message(format!("{grammar_type} - Unsupported grammar type"))
186 .with_code("parol::parser::unsupported_grammar_type")
187 .with_labels(vec![Label::primary(
188 file_id,
189 Into::<Range<usize>>::into(token),
190 )])
191 .with_notes(vec![
192 "Only 'LL(k)' and 'LALR(1)' are supported. Use RawString literals here.".to_string()
193 ]),
194 )?)
195 }
196 ParolParserError::UnsupportedFeature {
197 feature,
198 hint,
199 input,
200 token,
201 } => {
202 let mut files = SimpleFiles::new();
203 let content = fs::read_to_string(input).unwrap_or_default();
204 let file_id = files.add(input.display().to_string(), content);
205
206 Ok(term::emit(
207 &mut writer.lock(),
208 &config,
209 &files,
210 &Diagnostic::error()
211 .with_message(format!("{feature} - Unsupported feature"))
212 .with_code("parol::parser::unsupported_feature")
213 .with_labels(vec![Label::primary(
214 file_id,
215 Into::<Range<usize>>::into(token),
216 )])
217 .with_notes(vec![
218 "This feature is not supported.".to_string(),
219 hint.to_string(),
220 ]),
221 )?)
222 }
223 ParolParserError::InvalidTokenInTransition {
224 context,
225 token,
226 input,
227 location,
228 } => {
229 let mut files = SimpleFiles::new();
230 let content = fs::read_to_string(input).unwrap_or_default();
231 let file_id = files.add(input.display().to_string(), content);
232
233 Ok(term::emit(
234 &mut writer.lock(),
235 &config,
236 &files,
237 &Diagnostic::error()
238 .with_message(format!(
239 "{context} - Invalid token '{token}' in transition. Use a primary non-terminal for the token."
240 ))
241 .with_code("parol::parser::invalid_token_in_transition")
242 .with_labels(vec![Label::primary(
243 file_id,
244 Into::<Range<usize>>::into(location),
245 )])
246 .with_notes(vec![
247 "Please use a primary non-terminal for the token.".to_string()
248 ]),
249 )?)
250 }
251 ParolParserError::TokenIsNotInScanner {
252 context,
253 scanner,
254 token,
255 input,
256 location,
257 } => {
258 let mut files = SimpleFiles::new();
259 let content = fs::read_to_string(input).unwrap_or_default();
260 let file_id = files.add(input.display().to_string(), content);
261
262 Ok(term::emit(
263 &mut writer.lock(),
264 &config,
265 &files,
266 &Diagnostic::error()
267 .with_message(format!(
268 "{context} - Token '{token}' is not defined in scanner '{scanner}'"
269 ))
270 .with_code("parol::parser::token_is_not_in_scanner")
271 .with_labels(vec![Label::primary(
272 file_id,
273 Into::<Range<usize>>::into(location),
274 )])
275 .with_notes(vec![
276 "Only tokens used in a scanner can initiate a transition from it to another scanner."
277 .to_string(),
278 ]),
279 )?)
280 }
281 ParolParserError::MixedScannerSwitching {
282 context,
283 input,
284 location,
285 } => {
286 let mut files = SimpleFiles::new();
287 let content = fs::read_to_string(input).unwrap_or_default();
288 let file_id = files.add(input.display().to_string(), content);
289
290 Ok(term::emit(
291 &mut writer.lock(),
292 &config,
293 &files,
294 &Diagnostic::error()
295 .with_message(format!("{context} - Mixed scanner switching is not allowed"))
296 .with_code("parol::parser::mixed_scanner_switching")
297 .with_labels(vec![Label::primary(
298 file_id,
299 Into::<Range<usize>>::into(location),
300 )])
301 .with_notes(vec![
302 "Use either parser-based or scanner-based switching.".to_string(),
303 "Parser-based switching is done via the %sc, %push and %pop directives in productions.".to_string(),
304 "Scanner-based switching is done via the %on directive in the header of the grammar file.".to_string(),
305 ]),
306 )?)
307 }
308 }
309 } else if let Some(err) = err.downcast_ref::<GrammarAnalysisError>() {
310 match err {
311 GrammarAnalysisError::LeftRecursion { recursions } => {
312 let non_terminals = recursions
313 .iter()
314 .map(|r| r.name.to_string())
315 .collect::<Vec<String>>()
316 .join(", ");
317 return Ok(term::emit(
318 &mut writer.lock(),
319 &config,
320 &files,
321 &Diagnostic::error()
322 .with_message("Grammar contains left-recursions")
323 .with_code("parol::analysis::left_recursion")
324 .with_notes(vec![
325 "Left-recursions detected.".to_string(),
326 non_terminals,
327 "Please rework your grammar to remove these recursions."
328 .to_string(),
329 ]),
330 )?);
331 }
332 GrammarAnalysisError::UnreachableNonTerminals { non_terminals } => {
333 let non_terminals = non_terminals
334 .iter()
335 .map(|r| r.hint.clone())
336 .collect::<Vec<String>>()
337 .join(", ");
338 return Ok(term::emit(
339 &mut writer.lock(),
340 &config,
341 &files,
342 &Diagnostic::error()
343 .with_message("Grammar contains unreachable non-terminals")
344 .with_code("parol::analysis::unreachable_non_terminals")
345 .with_notes(vec![
346 "Non-terminals:".to_string(),
347 non_terminals,
348 "Unreachable non-terminals are not allowed. If not used they can be safely removed.".to_string(),
349 ]),
350 )?);
351 }
352 GrammarAnalysisError::NonProductiveNonTerminals { non_terminals } => {
353 let non_terminals = non_terminals
354 .iter()
355 .map(|r| r.hint.clone())
356 .collect::<Vec<String>>()
357 .join(", ");
358 return Ok(term::emit(
359 &mut writer.lock(),
360 &config,
361 &files,
362 &Diagnostic::error()
363 .with_message("Grammar contains nonproductive non-terminals")
364 .with_code("parol::analysis::nonproductive_non_terminals")
365 .with_notes(vec![
366 "Non-terminals:".to_string(),
367 non_terminals,
368 "Nonproductive non-terminals are not allowed. If not used they can be safely removed.".to_string(),
369 ]),
370 )?);
371 }
372 GrammarAnalysisError::MaxKExceeded { max_k } => {
373 return Ok(term::emit(
374 &mut writer.lock(),
375 &config,
376 &files,
377 &Diagnostic::error()
378 .with_message(format!("Maximum lookahead of {max_k} exceeded"))
379 .with_code("parol::analysis::max_k_exceeded")
380 .with_notes(vec!["Please examine your grammar.".to_string()]),
381 )?);
382 }
383 GrammarAnalysisError::LALR1ParseTableConstructionFailed { conflict } => {
384 return Ok(term::emit(
385 &mut writer.lock(),
386 &config,
387 &files,
388 &Diagnostic::error()
389 .with_message("LALR(1) parse table construction failed with conflicts")
390 .with_code("parol::analysis::lalr1_parse_table_construction_failed")
391 .with_notes(vec![
392 "Please examine your grammar.".to_string(),
393 format!("{}", conflict),
394 ]),
395 )?);
396 }
397 }
398 } else {
399 let result = term::emit(
400 &mut writer.lock(),
401 &config,
402 &files,
403 &Diagnostic::error()
404 .with_message("Parol error")
405 .with_notes(vec![
406 err.to_string(),
407 err.source()
408 .map_or("No details".to_string(), |s| s.to_string()),
409 ]),
410 );
411 result.map_err(|e| anyhow::anyhow!(e))
412 }
413 }
414}