parol/error_report/
reports.rs

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
14/// Error reporter for user errors generated by the parol parser itself.
15pub 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}