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
17pub trait Report {
41 #[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 #[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}