Skip to main content

parol_runtime/errors/
reports.rs

1#[cfg(feature = "reporting")]
2use std::fs;
3#[cfg(feature = "reporting")]
4use std::ops::Range;
5use std::path::Path;
6
7use crate::ParolError;
8#[cfg(feature = "reporting")]
9use crate::{LexerError, ParserError, Span, SyntaxError};
10#[cfg(feature = "reporting")]
11use codespan_reporting::diagnostic::{Diagnostic, Label};
12#[cfg(feature = "reporting")]
13use codespan_reporting::files::SimpleFiles;
14#[cfg(feature = "reporting")]
15use codespan_reporting::term::{self, termcolor::StandardStream};
16
17/// *Trait for parol's error reporting*
18///
19/// Implement this trait and provide an own implementation for [Report::report_user_error] when you
20/// want to contribute your own error reporting for your error types.
21///
22/// If you don't want to report own errors then simply use it's default implementation like this:
23/// ```
24/// use parol_runtime::Report;
25/// use parol_macros::parol;
26///
27/// struct MyErrorReporter;
28/// impl Report for MyErrorReporter {
29///     #[cfg(not(feature = "reporting"))]
30///     fn report_user_error(err: &anyhow::Error) -> anyhow::Result<()> { Ok(()) }
31///     #[cfg(not(feature = "reporting"))]
32///     fn report_error<T>(err: &parol_runtime::ParolError, file_name: T) -> anyhow::Result<()>
33///     where T: AsRef<std::path::Path>
34///     { Ok(()) }
35/// };
36///
37/// let err = parol!("Crucial problem!");   // Suppose that this error comes from a call of `parse`
38/// MyErrorReporter::report_error(&err, "my_file.xyz").unwrap_or_default();
39/// ```
40pub trait Report {
41    ///
42    /// Implement this method if you want to provide your own error reporting for your own error
43    /// types.
44    /// Doing so you can hook into the error reporting process.
45    ///
46    /// Examples are `parol`'s `ParolErrorReporter` or `basic_interpreter`'s `BasicErrorReporter`.
47    ///
48    /// The method's argument value is obtained from a `parol_runtime::ParolError::UserError`'s
49    /// content. It should return Ok(()) if reporting succeeds and an error value if the reporting
50    /// itself fails somehow.
51    ///
52    #[cfg(feature = "reporting")]
53    fn report_user_error(err: &anyhow::Error) -> anyhow::Result<()> {
54        let mut writer = StandardStream::stderr(term::termcolor::ColorChoice::Auto);
55        let config = codespan_reporting::term::Config::default();
56        let files = SimpleFiles::<String, String>::new();
57        let result = term::emit_to_write_style(
58            &mut writer,
59            &config,
60            &files,
61            &Diagnostic::error()
62                .with_message("User error")
63                .with_notes(vec![
64                    err.to_string(),
65                    err.source()
66                        .map_or("No details".to_string(), |s| s.to_string()),
67                ]),
68        );
69        result.map_err(|e| anyhow::anyhow!(e))
70    }
71
72    #[cfg(not(feature = "reporting"))]
73    fn report_user_error(err: &anyhow::Error) -> anyhow::Result<()>;
74
75    /// You don't need to implement this method because it contains the reporting functionality for
76    /// errors from parol_runtime itself.
77    #[cfg(feature = "reporting")]
78    fn report_error<T>(err: &ParolError, file_name: T) -> anyhow::Result<()>
79    where
80        T: AsRef<Path>,
81    {
82        let config = codespan_reporting::term::Config::default();
83
84        let mut files = SimpleFiles::new();
85        let content = fs::read_to_string(file_name.as_ref()).unwrap_or_default();
86        let file_id = files.add(file_name.as_ref().display().to_string(), content);
87
88        let report_lexer_error = |err: &LexerError| -> anyhow::Result<()> {
89            let mut writer = StandardStream::stderr(term::termcolor::ColorChoice::Auto);
90            match err {
91                LexerError::TokenBufferEmptyError => Ok(term::emit_to_write_style(
92                    &mut writer,
93                    &config,
94                    &files,
95                    &Diagnostic::bug()
96                        .with_message("No valid token read")
97                        .with_code("parol_runtime::lexer::empty_token_buffer")
98                        .with_notes(vec!["Token buffer is empty".to_string()]),
99                )?),
100                LexerError::InternalError(e) => Ok(term::emit_to_write_style(
101                    &mut writer,
102                    &config,
103                    &files,
104                    &Diagnostic::bug()
105                        .with_message(format!("Internal lexer error: {e}"))
106                        .with_code("parol_runtime::lexer::internal_error"),
107                )?),
108                LexerError::LookaheadExceedsMaximum => Ok(term::emit_to_write_style(
109                    &mut writer,
110                    &config,
111                    &files,
112                    &Diagnostic::bug()
113                        .with_message("Lookahead exceeds maximum".to_string())
114                        .with_code("parol_runtime::lexer::lookahead_exceeds_maximum"),
115                )?),
116                LexerError::LookaheadExceedsTokenBufferLength => Ok(term::emit_to_write_style(
117                    &mut writer,
118                    &config,
119                    &files,
120                    &Diagnostic::bug()
121                        .with_message("Lookahead exceeds token buffer length".to_string())
122                        .with_code("parol_runtime::lexer::lookahead_exceeds_token_buffer_length"),
123                )?),
124                LexerError::ScannerStackEmptyError => Ok(term::emit_to_write_style(
125                    &mut writer,
126                    &config,
127                    &files,
128                    &Diagnostic::bug()
129                        .with_message("Tried to pop from empty scanner stack".to_string())
130                        .with_code("parol_runtime::lexer::pop_from_empty_scanner_stack")
131                        .with_notes(vec![
132                            "Check balance of %push and %pop directives in your grammar"
133                                .to_string(),
134                        ]),
135                )?),
136                LexerError::RecoveryError(e) => Ok(term::emit_to_write_style(
137                    &mut writer,
138                    &config,
139                    &files,
140                    &Diagnostic::bug()
141                        .with_message(format!("Lexer recovery error: {e}"))
142                        .with_code("parol_runtime::lexer::recovery"),
143                )?),
144            }
145        };
146
147        let report_parser_error = |err: &ParserError| -> anyhow::Result<()> {
148            let mut writer = StandardStream::stderr(term::termcolor::ColorChoice::Auto);
149            match err {
150                ParserError::TreeError { source } => Ok(term::emit_to_write_style(
151                    &mut writer,
152                    &config,
153                    &files,
154                    &Diagnostic::bug()
155                        .with_message(format!("Error from syntree crate: {source}"))
156                        .with_code("parol_runtime::parser::syntree_error")
157                        .with_notes(vec!["Internal error".to_string()]),
158                )?),
159                ParserError::DataError(e) => Ok(term::emit_to_write_style(
160                    &mut writer,
161                    &config,
162                    &files,
163                    &Diagnostic::bug()
164                        .with_message(format!("Data error: {e}"))
165                        .with_code("parol_runtime::lexer::internal_error")
166                        .with_notes(vec!["Error in generated source".to_string()]),
167                )?),
168                ParserError::PredictionError { cause } => Ok(term::emit_to_write_style(
169                    &mut writer,
170                    &config,
171                    &files,
172                    &Diagnostic::error()
173                        .with_message("Error in input")
174                        .with_code("parol_runtime::lookahead::production_prediction_error")
175                        .with_notes(vec![cause.to_string()]),
176                )?),
177                ParserError::SyntaxErrors { entries } => {
178                    entries.iter().try_for_each(
179                        |SyntaxError {
180                             cause,
181                             error_location,
182                             unexpected_tokens,
183                             expected_tokens,
184                             source,
185                             ..
186                         }|
187                         -> anyhow::Result<()> {
188                            if let Some(source) = source {
189                                Self::report_error(source, file_name.as_ref())?;
190                            }
191                            let range: Range<usize> = if unexpected_tokens.is_empty() {
192                                (&**error_location).into()
193                            } else {
194                                (&unexpected_tokens[0].token).into()
195                            };
196                            let unexpected_tokens_labels =
197                                unexpected_tokens.iter().fold(vec![], |mut acc, un| {
198                                    acc.push(
199                                        Label::secondary(
200                                            file_id,
201                                            Into::<Range<usize>>::into(&un.token),
202                                        )
203                                        .with_message(un.token_type.clone()),
204                                    );
205                                    acc
206                                });
207                            Ok(term::emit_to_write_style(
208                                &mut writer,
209                                &config,
210                                &files,
211                                &Diagnostic::error()
212                                    .with_message("Syntax error")
213                                    .with_code("parol_runtime::parser::syntax_error")
214                                    .with_labels(vec![
215                                        Label::primary(file_id, range).with_message("Found"),
216                                    ])
217                                    .with_labels(unexpected_tokens_labels)
218                                    .with_notes(if expected_tokens.is_empty() {
219                                        vec![cause.to_string()]
220                                    } else {
221                                        vec![
222                                            format!("Expecting {}", expected_tokens),
223                                            cause.to_string(),
224                                        ]
225                                    }),
226                            )?)
227                        },
228                    )?;
229                    Ok(term::emit_to_write_style(
230                        &mut writer,
231                        &config,
232                        &files,
233                        &Diagnostic::error()
234                            .with_message(format!("{} syntax error(s) found", entries.len())),
235                    )?)
236                }
237                ParserError::UnprocessedInput { last_token, .. } => {
238                    let un_span: Span = (Into::<Range<usize>>::into(&**last_token)).into();
239                    Ok(term::emit_to_write_style(
240                        &mut writer,
241                        &config,
242                        &files,
243                        &Diagnostic::error()
244                            .with_message("Unprocessed input is left after parsing has finished")
245                            .with_code("parol_runtime::parser::unprocessed_input")
246                            .with_labels(vec![
247                                Label::primary(file_id, un_span).with_message("Unprocessed"),
248                            ])
249                            .with_notes(vec![
250                                "Unprocessed input could be a problem in your grammar.".to_string(),
251                            ]),
252                    )?)
253                }
254                ParserError::Unsupported {
255                    context,
256                    error_location,
257                } => {
258                    let range: Range<usize> = (&**error_location).into();
259                    Ok(term::emit_to_write_style(
260                        &mut writer,
261                        &config,
262                        &files,
263                        &Diagnostic::error()
264                            .with_message("Unsupported language feature")
265                            .with_code("parol_runtime::parser::unsupported")
266                            .with_labels(vec![
267                                Label::primary(file_id, range).with_message("Unsupported"),
268                            ])
269                            .with_notes(vec![format!("Context: {context}")]),
270                    )?)
271                }
272                ParserError::InternalError(e) => Ok(term::emit_to_write_style(
273                    &mut writer,
274                    &config,
275                    &files,
276                    &Diagnostic::bug()
277                        .with_message(format!("Internal parser error: {e}"))
278                        .with_code("parol_runtime::parser::internal_error")
279                        .with_notes(vec!["This may be a bug. Please report it!".to_string()]),
280                )?),
281                ParserError::TooManyErrors { count } => Ok(term::emit_to_write_style(
282                    &mut writer,
283                    &config,
284                    &files,
285                    &Diagnostic::error()
286                        .with_message(format!("Too many errors: {count}"))
287                        .with_code("parol_runtime::parser::too_many_errors")
288                        .with_notes(vec![
289                            "The parser has stopped because too many errors occurred.".to_string(),
290                        ]),
291                )?),
292                ParserError::RecoveryFailed => Ok(term::emit_to_write_style(
293                    &mut writer,
294                    &config,
295                    &files,
296                    &Diagnostic::error()
297                        .with_message("Error recovery failed")
298                        .with_code("parol_runtime::parser::recovery_failed")
299                        .with_notes(vec![
300                            "The parser has stopped because error recovery failed.".to_string(),
301                        ]),
302                )?),
303            }
304        };
305
306        match err {
307            ParolError::ParserError(e) => report_parser_error(e),
308            ParolError::LexerError(e) => report_lexer_error(e),
309            ParolError::UserError(e) => Self::report_user_error(e),
310        }
311    }
312
313    #[cfg(not(feature = "reporting"))]
314    fn report_error<T>(err: &ParolError, file_name: T) -> anyhow::Result<()>
315    where
316        T: AsRef<Path>;
317}