]>
Commit | Line | Data |
---|---|---|
83c7162d XL |
1 | // pest. The Elegant Parser |
2 | // Copyright (c) 2018 Dragoș Tiselice | |
3 | // | |
4 | // Licensed under the Apache License, Version 2.0 | |
5 | // <LICENSE-APACHE or http://www.apache.org/licenses/LICENSE-2.0> or the MIT | |
6 | // license <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your | |
7 | // option. All files in the project carrying such notice may not be copied, | |
8 | // modified, or distributed except according to those terms. | |
9 | ||
9fa01778 XL |
10 | //! Types for different kinds of parsing failures. |
11 | ||
f2b60f7d FG |
12 | use alloc::borrow::Cow; |
13 | use alloc::borrow::ToOwned; | |
14 | use alloc::format; | |
15 | use alloc::string::String; | |
16 | use alloc::string::ToString; | |
17 | use alloc::vec::Vec; | |
18 | use core::cmp; | |
19 | use core::fmt; | |
20 | use core::mem; | |
21 | ||
22 | use crate::position::Position; | |
23 | use crate::span::Span; | |
24 | use crate::RuleType; | |
9fa01778 XL |
25 | |
26 | /// Parse-related error type. | |
27 | #[derive(Clone, Debug, Eq, Hash, PartialEq)] | |
f2b60f7d | 28 | #[cfg_attr(feature = "std", derive(thiserror::Error))] |
9fa01778 XL |
29 | pub struct Error<R> { |
30 | /// Variant of the error | |
31 | pub variant: ErrorVariant<R>, | |
32 | /// Location within the input string | |
33 | pub location: InputLocation, | |
34 | /// Line/column within the input string | |
35 | pub line_col: LineColLocation, | |
36 | path: Option<String>, | |
37 | line: String, | |
38 | continued_line: Option<String>, | |
39 | } | |
83c7162d | 40 | |
9fa01778 | 41 | /// Different kinds of parsing errors. |
83c7162d | 42 | #[derive(Clone, Debug, Eq, Hash, PartialEq)] |
f2b60f7d | 43 | #[cfg_attr(feature = "std", derive(thiserror::Error))] |
9fa01778 XL |
44 | pub enum ErrorVariant<R> { |
45 | /// Generated parsing error with expected and unexpected `Rule`s | |
83c7162d XL |
46 | ParsingError { |
47 | /// Positive attempts | |
48 | positives: Vec<R>, | |
49 | /// Negative attempts | |
50 | negatives: Vec<R>, | |
83c7162d | 51 | }, |
9fa01778 XL |
52 | /// Custom error with a message |
53 | CustomError { | |
83c7162d XL |
54 | /// Short explanation |
55 | message: String, | |
83c7162d | 56 | }, |
83c7162d XL |
57 | } |
58 | ||
9fa01778 XL |
59 | /// Where an `Error` has occurred. |
60 | #[derive(Clone, Debug, Eq, Hash, PartialEq)] | |
61 | pub enum InputLocation { | |
62 | /// `Error` was created by `Error::new_from_pos` | |
63 | Pos(usize), | |
64 | /// `Error` was created by `Error::new_from_span` | |
65 | Span((usize, usize)), | |
66 | } | |
67 | ||
68 | /// Line/column where an `Error` has occurred. | |
69 | #[derive(Clone, Debug, Eq, Hash, PartialEq)] | |
70 | pub enum LineColLocation { | |
71 | /// Line/column pair if `Error` was created by `Error::new_from_pos` | |
72 | Pos((usize, usize)), | |
73 | /// Line/column pairs if `Error` was created by `Error::new_from_span` | |
74 | Span((usize, usize), (usize, usize)), | |
75 | } | |
76 | ||
fe692bf9 FG |
77 | impl From<Position<'_>> for LineColLocation { |
78 | fn from(value: Position<'_>) -> Self { | |
79 | Self::Pos(value.line_col()) | |
80 | } | |
81 | } | |
82 | ||
83 | impl From<Span<'_>> for LineColLocation { | |
84 | fn from(value: Span<'_>) -> Self { | |
85 | let (start, end) = value.split(); | |
86 | Self::Span(start.line_col(), end.line_col()) | |
87 | } | |
88 | } | |
89 | ||
9fa01778 XL |
90 | impl<R: RuleType> Error<R> { |
91 | /// Creates `Error` from `ErrorVariant` and `Position`. | |
92 | /// | |
93 | /// # Examples | |
94 | /// | |
95 | /// ``` | |
96 | /// # use pest::error::{Error, ErrorVariant}; | |
97 | /// # use pest::Position; | |
98 | /// # #[allow(non_camel_case_types)] | |
99 | /// # #[allow(dead_code)] | |
100 | /// # #[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] | |
101 | /// # enum Rule { | |
102 | /// # open_paren, | |
103 | /// # closed_paren | |
104 | /// # } | |
105 | /// # let input = ""; | |
106 | /// # let pos = Position::from_start(input); | |
107 | /// let error = Error::new_from_pos( | |
108 | /// ErrorVariant::ParsingError { | |
109 | /// positives: vec![Rule::open_paren], | |
110 | /// negatives: vec![Rule::closed_paren] | |
111 | /// }, | |
112 | /// pos | |
113 | /// ); | |
114 | /// | |
115 | /// println!("{}", error); | |
116 | /// ``` | |
9c376795 | 117 | pub fn new_from_pos(variant: ErrorVariant<R>, pos: Position<'_>) -> Error<R> { |
f2b60f7d FG |
118 | let visualize_ws = pos.match_char('\n') || pos.match_char('\r'); |
119 | let line_of = pos.line_of(); | |
120 | let line = if visualize_ws { | |
121 | visualize_whitespace(line_of) | |
122 | } else { | |
123 | line_of.replace(&['\r', '\n'][..], "") | |
124 | }; | |
9fa01778 XL |
125 | Error { |
126 | variant, | |
127 | location: InputLocation::Pos(pos.pos()), | |
128 | path: None, | |
f2b60f7d | 129 | line, |
9fa01778 XL |
130 | continued_line: None, |
131 | line_col: LineColLocation::Pos(pos.line_col()), | |
132 | } | |
133 | } | |
134 | ||
135 | /// Creates `Error` from `ErrorVariant` and `Span`. | |
136 | /// | |
137 | /// # Examples | |
138 | /// | |
139 | /// ``` | |
140 | /// # use pest::error::{Error, ErrorVariant}; | |
141 | /// # use pest::{Position, Span}; | |
142 | /// # #[allow(non_camel_case_types)] | |
143 | /// # #[allow(dead_code)] | |
144 | /// # #[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] | |
145 | /// # enum Rule { | |
146 | /// # open_paren, | |
147 | /// # closed_paren | |
148 | /// # } | |
149 | /// # let input = ""; | |
150 | /// # let start = Position::from_start(input); | |
151 | /// # let end = start.clone(); | |
152 | /// # let span = start.span(&end); | |
153 | /// let error = Error::new_from_span( | |
154 | /// ErrorVariant::ParsingError { | |
155 | /// positives: vec![Rule::open_paren], | |
156 | /// negatives: vec![Rule::closed_paren] | |
157 | /// }, | |
158 | /// span | |
159 | /// ); | |
160 | /// | |
161 | /// println!("{}", error); | |
162 | /// ``` | |
9c376795 | 163 | pub fn new_from_span(variant: ErrorVariant<R>, span: Span<'_>) -> Error<R> { |
9fa01778 | 164 | let end = span.end_pos(); |
9fa01778 XL |
165 | let mut end_line_col = end.line_col(); |
166 | // end position is after a \n, so we want to point to the visual lf symbol | |
167 | if end_line_col.1 == 1 { | |
f2b60f7d | 168 | let mut visual_end = end; |
9fa01778 XL |
169 | visual_end.skip_back(1); |
170 | let lc = visual_end.line_col(); | |
171 | end_line_col = (lc.0, lc.1 + 1); | |
172 | }; | |
173 | ||
174 | let mut line_iter = span.lines(); | |
f2b60f7d FG |
175 | let sl = line_iter.next().unwrap_or(""); |
176 | let mut chars = span.as_str().chars(); | |
177 | let visualize_ws = matches!(chars.next(), Some('\n') | Some('\r')) | |
178 | || matches!(chars.last(), Some('\n') | Some('\r')); | |
179 | let start_line = if visualize_ws { | |
180 | visualize_whitespace(sl) | |
181 | } else { | |
182 | sl.to_owned().replace(&['\r', '\n'][..], "") | |
183 | }; | |
184 | let ll = line_iter.last(); | |
185 | let continued_line = if visualize_ws { | |
9c376795 | 186 | ll.map(str::to_owned) |
f2b60f7d FG |
187 | } else { |
188 | ll.map(visualize_whitespace) | |
189 | }; | |
9fa01778 XL |
190 | |
191 | Error { | |
192 | variant, | |
193 | location: InputLocation::Span((span.start(), end.pos())), | |
194 | path: None, | |
195 | line: start_line, | |
196 | continued_line, | |
197 | line_col: LineColLocation::Span(span.start_pos().line_col(), end_line_col), | |
198 | } | |
199 | } | |
200 | ||
201 | /// Returns `Error` variant with `path` which is shown when formatted with `Display`. | |
202 | /// | |
203 | /// # Examples | |
204 | /// | |
205 | /// ``` | |
206 | /// # use pest::error::{Error, ErrorVariant}; | |
207 | /// # use pest::Position; | |
208 | /// # #[allow(non_camel_case_types)] | |
209 | /// # #[allow(dead_code)] | |
210 | /// # #[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] | |
211 | /// # enum Rule { | |
212 | /// # open_paren, | |
213 | /// # closed_paren | |
214 | /// # } | |
215 | /// # let input = ""; | |
216 | /// # let pos = Position::from_start(input); | |
217 | /// Error::new_from_pos( | |
218 | /// ErrorVariant::ParsingError { | |
219 | /// positives: vec![Rule::open_paren], | |
220 | /// negatives: vec![Rule::closed_paren] | |
221 | /// }, | |
222 | /// pos | |
223 | /// ).with_path("file.rs"); | |
224 | /// ``` | |
225 | pub fn with_path(mut self, path: &str) -> Error<R> { | |
226 | self.path = Some(path.to_owned()); | |
227 | ||
228 | self | |
229 | } | |
230 | ||
f2b60f7d FG |
231 | /// Returns the path set using [`Error::with_path()`]. |
232 | /// | |
233 | /// # Examples | |
234 | /// | |
235 | /// ``` | |
236 | /// # use pest::error::{Error, ErrorVariant}; | |
237 | /// # use pest::Position; | |
238 | /// # #[allow(non_camel_case_types)] | |
239 | /// # #[allow(dead_code)] | |
240 | /// # #[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] | |
241 | /// # enum Rule { | |
242 | /// # open_paren, | |
243 | /// # closed_paren | |
244 | /// # } | |
245 | /// # let input = ""; | |
246 | /// # let pos = Position::from_start(input); | |
247 | /// # let error = Error::new_from_pos( | |
248 | /// # ErrorVariant::ParsingError { | |
249 | /// # positives: vec![Rule::open_paren], | |
250 | /// # negatives: vec![Rule::closed_paren] | |
251 | /// # }, | |
252 | /// # pos); | |
253 | /// let error = error.with_path("file.rs"); | |
254 | /// assert_eq!(Some("file.rs"), error.path()); | |
255 | /// ``` | |
256 | pub fn path(&self) -> Option<&str> { | |
257 | self.path.as_deref() | |
258 | } | |
259 | ||
260 | /// Returns the line that the error is on. | |
261 | pub fn line(&self) -> &str { | |
262 | self.line.as_str() | |
263 | } | |
264 | ||
9fa01778 XL |
265 | /// Renames all `Rule`s if this is a [`ParsingError`]. It does nothing when called on a |
266 | /// [`CustomError`]. | |
83c7162d XL |
267 | /// |
268 | /// Useful in order to rename verbose rules or have detailed per-`Rule` formatting. | |
269 | /// | |
9fa01778 XL |
270 | /// [`ParsingError`]: enum.ErrorVariant.html#variant.ParsingError |
271 | /// [`CustomError`]: enum.ErrorVariant.html#variant.CustomError | |
272 | /// | |
83c7162d XL |
273 | /// # Examples |
274 | /// | |
275 | /// ``` | |
9fa01778 | 276 | /// # use pest::error::{Error, ErrorVariant}; |
83c7162d XL |
277 | /// # use pest::Position; |
278 | /// # #[allow(non_camel_case_types)] | |
279 | /// # #[allow(dead_code)] | |
280 | /// # #[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] | |
281 | /// # enum Rule { | |
282 | /// # open_paren, | |
283 | /// # closed_paren | |
284 | /// # } | |
285 | /// # let input = ""; | |
286 | /// # let pos = Position::from_start(input); | |
9fa01778 XL |
287 | /// Error::new_from_pos( |
288 | /// ErrorVariant::ParsingError { | |
289 | /// positives: vec![Rule::open_paren], | |
290 | /// negatives: vec![Rule::closed_paren] | |
291 | /// }, | |
292 | /// pos | |
293 | /// ).renamed_rules(|rule| { | |
83c7162d XL |
294 | /// match *rule { |
295 | /// Rule::open_paren => "(".to_owned(), | |
296 | /// Rule::closed_paren => "closed paren".to_owned() | |
297 | /// } | |
298 | /// }); | |
299 | /// ``` | |
9fa01778 | 300 | pub fn renamed_rules<F>(mut self, f: F) -> Error<R> |
83c7162d | 301 | where |
9fa01778 | 302 | F: FnMut(&R) -> String, |
83c7162d | 303 | { |
9fa01778 XL |
304 | let variant = match self.variant { |
305 | ErrorVariant::ParsingError { | |
83c7162d XL |
306 | positives, |
307 | negatives, | |
83c7162d | 308 | } => { |
9fa01778 XL |
309 | let message = Error::parsing_error_message(&positives, &negatives, f); |
310 | ErrorVariant::CustomError { message } | |
83c7162d | 311 | } |
9fa01778 XL |
312 | variant => variant, |
313 | }; | |
83c7162d | 314 | |
9fa01778 | 315 | self.variant = variant; |
83c7162d | 316 | |
9fa01778 | 317 | self |
83c7162d | 318 | } |
83c7162d | 319 | |
9fa01778 XL |
320 | fn start(&self) -> (usize, usize) { |
321 | match self.line_col { | |
322 | LineColLocation::Pos(line_col) => line_col, | |
323 | LineColLocation::Span(start_line_col, _) => start_line_col, | |
83c7162d XL |
324 | } |
325 | } | |
83c7162d | 326 | |
9fa01778 XL |
327 | fn spacing(&self) -> String { |
328 | let line = match self.line_col { | |
329 | LineColLocation::Pos((line, _)) => line, | |
330 | LineColLocation::Span((start_line, _), (end_line, _)) => cmp::max(start_line, end_line), | |
331 | }; | |
83c7162d | 332 | |
9fa01778 | 333 | let line_str_len = format!("{}", line).len(); |
83c7162d | 334 | |
9fa01778 XL |
335 | let mut spacing = String::new(); |
336 | for _ in 0..line_str_len { | |
337 | spacing.push(' '); | |
83c7162d | 338 | } |
83c7162d | 339 | |
9fa01778 | 340 | spacing |
83c7162d XL |
341 | } |
342 | ||
9fa01778 XL |
343 | fn underline(&self) -> String { |
344 | let mut underline = String::new(); | |
83c7162d | 345 | |
9fa01778 XL |
346 | let mut start = self.start().1; |
347 | let end = match self.line_col { | |
348 | LineColLocation::Span(_, (_, mut end)) => { | |
349 | let inverted_cols = start > end; | |
350 | if inverted_cols { | |
351 | mem::swap(&mut start, &mut end); | |
352 | start -= 1; | |
353 | end += 1; | |
354 | } | |
83c7162d | 355 | |
9fa01778 XL |
356 | Some(end) |
357 | } | |
358 | _ => None, | |
359 | }; | |
360 | let offset = start - 1; | |
3dfed10e | 361 | let line_chars = self.line.chars(); |
83c7162d | 362 | |
3dfed10e XL |
363 | for c in line_chars.take(offset) { |
364 | match c { | |
365 | '\t' => underline.push('\t'), | |
366 | _ => underline.push(' '), | |
367 | } | |
83c7162d XL |
368 | } |
369 | ||
9fa01778 | 370 | if let Some(end) = end { |
f2b60f7d | 371 | underline.push('^'); |
9fa01778 | 372 | if end - start > 1 { |
9fa01778 XL |
373 | for _ in 2..(end - start) { |
374 | underline.push('-'); | |
375 | } | |
376 | underline.push('^'); | |
83c7162d | 377 | } |
9fa01778 XL |
378 | } else { |
379 | underline.push_str("^---") | |
380 | } | |
83c7162d | 381 | |
9fa01778 XL |
382 | underline |
383 | } | |
83c7162d | 384 | |
9fa01778 | 385 | fn message(&self) -> String { |
f2b60f7d | 386 | self.variant.message().to_string() |
83c7162d XL |
387 | } |
388 | ||
9fa01778 XL |
389 | fn parsing_error_message<F>(positives: &[R], negatives: &[R], mut f: F) -> String |
390 | where | |
391 | F: FnMut(&R) -> String, | |
392 | { | |
393 | match (negatives.is_empty(), positives.is_empty()) { | |
394 | (false, false) => format!( | |
395 | "unexpected {}; expected {}", | |
396 | Error::enumerate(negatives, &mut f), | |
397 | Error::enumerate(positives, &mut f) | |
398 | ), | |
399 | (false, true) => format!("unexpected {}", Error::enumerate(negatives, &mut f)), | |
400 | (true, false) => format!("expected {}", Error::enumerate(positives, &mut f)), | |
401 | (true, true) => "unknown parsing error".to_owned(), | |
402 | } | |
403 | } | |
83c7162d | 404 | |
9fa01778 XL |
405 | fn enumerate<F>(rules: &[R], f: &mut F) -> String |
406 | where | |
407 | F: FnMut(&R) -> String, | |
408 | { | |
409 | match rules.len() { | |
410 | 1 => f(&rules[0]), | |
411 | 2 => format!("{} or {}", f(&rules[0]), f(&rules[1])), | |
412 | l => { | |
f2b60f7d | 413 | let non_separated = f(&rules[l - 1]); |
9fa01778 XL |
414 | let separated = rules |
415 | .iter() | |
416 | .take(l - 1) | |
f2b60f7d | 417 | .map(f) |
9fa01778 XL |
418 | .collect::<Vec<_>>() |
419 | .join(", "); | |
f2b60f7d | 420 | format!("{}, or {}", separated, non_separated) |
9fa01778 XL |
421 | } |
422 | } | |
423 | } | |
83c7162d | 424 | |
9fa01778 XL |
425 | pub(crate) fn format(&self) -> String { |
426 | let spacing = self.spacing(); | |
427 | let path = self | |
428 | .path | |
429 | .as_ref() | |
430 | .map(|path| format!("{}:", path)) | |
431 | .unwrap_or_default(); | |
432 | ||
433 | let pair = (self.line_col.clone(), &self.continued_line); | |
fe692bf9 | 434 | if let (LineColLocation::Span(_, end), Some(ref continued_line)) = pair { |
9fa01778 XL |
435 | let has_line_gap = end.0 - self.start().0 > 1; |
436 | if has_line_gap { | |
437 | format!( | |
438 | "{s }--> {p}{ls}:{c}\n\ | |
439 | {s } |\n\ | |
440 | {ls:w$} | {line}\n\ | |
441 | {s } | ...\n\ | |
442 | {le:w$} | {continued_line}\n\ | |
443 | {s } | {underline}\n\ | |
444 | {s } |\n\ | |
445 | {s } = {message}", | |
446 | s = spacing, | |
447 | w = spacing.len(), | |
448 | p = path, | |
449 | ls = self.start().0, | |
450 | le = end.0, | |
451 | c = self.start().1, | |
452 | line = self.line, | |
453 | continued_line = continued_line, | |
454 | underline = self.underline(), | |
455 | message = self.message() | |
456 | ) | |
457 | } else { | |
458 | format!( | |
459 | "{s }--> {p}{ls}:{c}\n\ | |
460 | {s } |\n\ | |
461 | {ls:w$} | {line}\n\ | |
462 | {le:w$} | {continued_line}\n\ | |
463 | {s } | {underline}\n\ | |
464 | {s } |\n\ | |
465 | {s } = {message}", | |
466 | s = spacing, | |
467 | w = spacing.len(), | |
468 | p = path, | |
469 | ls = self.start().0, | |
470 | le = end.0, | |
471 | c = self.start().1, | |
472 | line = self.line, | |
473 | continued_line = continued_line, | |
474 | underline = self.underline(), | |
475 | message = self.message() | |
476 | ) | |
477 | } | |
478 | } else { | |
479 | format!( | |
480 | "{s}--> {p}{l}:{c}\n\ | |
481 | {s} |\n\ | |
482 | {l} | {line}\n\ | |
483 | {s} | {underline}\n\ | |
484 | {s} |\n\ | |
485 | {s} = {message}", | |
486 | s = spacing, | |
487 | p = path, | |
488 | l = self.start().0, | |
489 | c = self.start().1, | |
490 | line = self.line, | |
491 | underline = self.underline(), | |
492 | message = self.message() | |
493 | ) | |
494 | } | |
495 | } | |
83c7162d XL |
496 | } |
497 | ||
f2b60f7d FG |
498 | impl<R: RuleType> ErrorVariant<R> { |
499 | /// | |
500 | /// Returns the error message for [`ErrorVariant`] | |
501 | /// | |
502 | /// If [`ErrorVariant`] is [`CustomError`], it returns a | |
503 | /// [`Cow::Borrowed`] reference to [`message`]. If [`ErrorVariant`] is [`ParsingError`], a | |
504 | /// [`Cow::Owned`] containing "expected [ErrorVariant::ParsingError::positives] [ErrorVariant::ParsingError::negatives]" is returned. | |
505 | /// | |
506 | /// [`ErrorVariant`]: enum.ErrorVariant.html | |
507 | /// [`CustomError`]: enum.ErrorVariant.html#variant.CustomError | |
508 | /// [`ParsingError`]: enum.ErrorVariant.html#variant.ParsingError | |
509 | /// [`Cow::Owned`]: https://doc.rust-lang.org/std/borrow/enum.Cow.html#variant.Owned | |
510 | /// [`Cow::Borrowed`]: https://doc.rust-lang.org/std/borrow/enum.Cow.html#variant.Borrowed | |
511 | /// [`message`]: enum.ErrorVariant.html#variant.CustomError.field.message | |
512 | /// # Examples | |
513 | /// | |
514 | /// ``` | |
515 | /// # use pest::error::ErrorVariant; | |
516 | /// let variant = ErrorVariant::<()>::CustomError { | |
517 | /// message: String::from("unexpected error") | |
518 | /// }; | |
519 | /// | |
520 | /// println!("{}", variant.message()); | |
9c376795 | 521 | pub fn message(&self) -> Cow<'_, str> { |
f2b60f7d FG |
522 | match self { |
523 | ErrorVariant::ParsingError { | |
524 | ref positives, | |
525 | ref negatives, | |
526 | } => Cow::Owned(Error::parsing_error_message(positives, negatives, |r| { | |
527 | format!("{:?}", r) | |
528 | })), | |
529 | ErrorVariant::CustomError { ref message } => Cow::Borrowed(message), | |
530 | } | |
531 | } | |
532 | } | |
533 | ||
9fa01778 | 534 | impl<R: RuleType> fmt::Display for Error<R> { |
9c376795 | 535 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
9fa01778 | 536 | write!(f, "{}", self.format()) |
83c7162d XL |
537 | } |
538 | } | |
539 | ||
f2b60f7d | 540 | impl<R: RuleType> fmt::Display for ErrorVariant<R> { |
9c376795 | 541 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
f2b60f7d FG |
542 | match self { |
543 | ErrorVariant::ParsingError { .. } => write!(f, "parsing error: {}", self.message()), | |
544 | ErrorVariant::CustomError { .. } => write!(f, "{}", self.message()), | |
83c7162d XL |
545 | } |
546 | } | |
547 | } | |
548 | ||
9fa01778 XL |
549 | fn visualize_whitespace(input: &str) -> String { |
550 | input.to_owned().replace('\r', "␍").replace('\n', "␊") | |
551 | } | |
552 | ||
83c7162d XL |
553 | #[cfg(test)] |
554 | mod tests { | |
83c7162d | 555 | use super::super::position; |
9fa01778 | 556 | use super::*; |
f2b60f7d | 557 | use alloc::vec; |
83c7162d XL |
558 | |
559 | #[test] | |
560 | fn display_parsing_error_mixed() { | |
561 | let input = "ab\ncd\nef"; | |
9fa01778 XL |
562 | let pos = position::Position::new(input, 4).unwrap(); |
563 | let error: Error<u32> = Error::new_from_pos( | |
564 | ErrorVariant::ParsingError { | |
565 | positives: vec![1, 2, 3], | |
566 | negatives: vec![4, 5, 6], | |
567 | }, | |
568 | pos, | |
569 | ); | |
83c7162d XL |
570 | |
571 | assert_eq!( | |
572 | format!("{}", error), | |
4b012472 | 573 | [ |
83c7162d XL |
574 | " --> 2:2", |
575 | " |", | |
f2b60f7d | 576 | "2 | cd", |
83c7162d XL |
577 | " | ^---", |
578 | " |", | |
4b012472 | 579 | " = unexpected 4, 5, or 6; expected 1, 2, or 3" |
9fa01778 XL |
580 | ] |
581 | .join("\n") | |
83c7162d XL |
582 | ); |
583 | } | |
584 | ||
585 | #[test] | |
586 | fn display_parsing_error_positives() { | |
587 | let input = "ab\ncd\nef"; | |
9fa01778 XL |
588 | let pos = position::Position::new(input, 4).unwrap(); |
589 | let error: Error<u32> = Error::new_from_pos( | |
590 | ErrorVariant::ParsingError { | |
591 | positives: vec![1, 2], | |
592 | negatives: vec![], | |
593 | }, | |
594 | pos, | |
595 | ); | |
83c7162d XL |
596 | |
597 | assert_eq!( | |
598 | format!("{}", error), | |
4b012472 | 599 | [ |
83c7162d XL |
600 | " --> 2:2", |
601 | " |", | |
f2b60f7d | 602 | "2 | cd", |
83c7162d XL |
603 | " | ^---", |
604 | " |", | |
4b012472 | 605 | " = expected 1 or 2" |
9fa01778 XL |
606 | ] |
607 | .join("\n") | |
83c7162d XL |
608 | ); |
609 | } | |
610 | ||
611 | #[test] | |
612 | fn display_parsing_error_negatives() { | |
613 | let input = "ab\ncd\nef"; | |
9fa01778 XL |
614 | let pos = position::Position::new(input, 4).unwrap(); |
615 | let error: Error<u32> = Error::new_from_pos( | |
616 | ErrorVariant::ParsingError { | |
617 | positives: vec![], | |
618 | negatives: vec![4, 5, 6], | |
619 | }, | |
620 | pos, | |
621 | ); | |
83c7162d XL |
622 | |
623 | assert_eq!( | |
624 | format!("{}", error), | |
4b012472 | 625 | [ |
83c7162d XL |
626 | " --> 2:2", |
627 | " |", | |
f2b60f7d | 628 | "2 | cd", |
83c7162d XL |
629 | " | ^---", |
630 | " |", | |
4b012472 | 631 | " = unexpected 4, 5, or 6" |
9fa01778 XL |
632 | ] |
633 | .join("\n") | |
83c7162d XL |
634 | ); |
635 | } | |
636 | ||
637 | #[test] | |
638 | fn display_parsing_error_unknown() { | |
639 | let input = "ab\ncd\nef"; | |
9fa01778 XL |
640 | let pos = position::Position::new(input, 4).unwrap(); |
641 | let error: Error<u32> = Error::new_from_pos( | |
642 | ErrorVariant::ParsingError { | |
643 | positives: vec![], | |
644 | negatives: vec![], | |
645 | }, | |
646 | pos, | |
647 | ); | |
83c7162d XL |
648 | |
649 | assert_eq!( | |
650 | format!("{}", error), | |
4b012472 | 651 | [ |
83c7162d XL |
652 | " --> 2:2", |
653 | " |", | |
f2b60f7d | 654 | "2 | cd", |
83c7162d XL |
655 | " | ^---", |
656 | " |", | |
4b012472 | 657 | " = unknown parsing error" |
9fa01778 XL |
658 | ] |
659 | .join("\n") | |
83c7162d XL |
660 | ); |
661 | } | |
662 | ||
663 | #[test] | |
664 | fn display_custom_pos() { | |
665 | let input = "ab\ncd\nef"; | |
9fa01778 XL |
666 | let pos = position::Position::new(input, 4).unwrap(); |
667 | let error: Error<u32> = Error::new_from_pos( | |
668 | ErrorVariant::CustomError { | |
669 | message: "error: big one".to_owned(), | |
670 | }, | |
671 | pos, | |
672 | ); | |
83c7162d XL |
673 | |
674 | assert_eq!( | |
675 | format!("{}", error), | |
4b012472 | 676 | [ |
83c7162d XL |
677 | " --> 2:2", |
678 | " |", | |
f2b60f7d | 679 | "2 | cd", |
83c7162d XL |
680 | " | ^---", |
681 | " |", | |
4b012472 | 682 | " = error: big one" |
9fa01778 XL |
683 | ] |
684 | .join("\n") | |
83c7162d XL |
685 | ); |
686 | } | |
687 | ||
688 | #[test] | |
689 | fn display_custom_span_two_lines() { | |
690 | let input = "ab\ncd\nefgh"; | |
9fa01778 XL |
691 | let start = position::Position::new(input, 4).unwrap(); |
692 | let end = position::Position::new(input, 9).unwrap(); | |
693 | let error: Error<u32> = Error::new_from_span( | |
694 | ErrorVariant::CustomError { | |
695 | message: "error: big one".to_owned(), | |
696 | }, | |
697 | start.span(&end), | |
698 | ); | |
83c7162d XL |
699 | |
700 | assert_eq!( | |
701 | format!("{}", error), | |
4b012472 | 702 | [ |
83c7162d XL |
703 | " --> 2:2", |
704 | " |", | |
f2b60f7d | 705 | "2 | cd", |
83c7162d XL |
706 | "3 | efgh", |
707 | " | ^^", | |
708 | " |", | |
4b012472 | 709 | " = error: big one" |
9fa01778 XL |
710 | ] |
711 | .join("\n") | |
83c7162d XL |
712 | ); |
713 | } | |
714 | ||
715 | #[test] | |
716 | fn display_custom_span_three_lines() { | |
717 | let input = "ab\ncd\nefgh"; | |
9fa01778 XL |
718 | let start = position::Position::new(input, 1).unwrap(); |
719 | let end = position::Position::new(input, 9).unwrap(); | |
720 | let error: Error<u32> = Error::new_from_span( | |
721 | ErrorVariant::CustomError { | |
722 | message: "error: big one".to_owned(), | |
723 | }, | |
724 | start.span(&end), | |
725 | ); | |
83c7162d XL |
726 | |
727 | assert_eq!( | |
728 | format!("{}", error), | |
4b012472 | 729 | [ |
83c7162d XL |
730 | " --> 1:2", |
731 | " |", | |
f2b60f7d | 732 | "1 | ab", |
83c7162d XL |
733 | " | ...", |
734 | "3 | efgh", | |
735 | " | ^^", | |
736 | " |", | |
4b012472 | 737 | " = error: big one" |
9fa01778 XL |
738 | ] |
739 | .join("\n") | |
83c7162d XL |
740 | ); |
741 | } | |
742 | ||
743 | #[test] | |
9fa01778 | 744 | fn display_custom_span_two_lines_inverted_cols() { |
83c7162d | 745 | let input = "abcdef\ngh"; |
9fa01778 XL |
746 | let start = position::Position::new(input, 5).unwrap(); |
747 | let end = position::Position::new(input, 8).unwrap(); | |
748 | let error: Error<u32> = Error::new_from_span( | |
749 | ErrorVariant::CustomError { | |
750 | message: "error: big one".to_owned(), | |
751 | }, | |
752 | start.span(&end), | |
753 | ); | |
83c7162d XL |
754 | |
755 | assert_eq!( | |
756 | format!("{}", error), | |
4b012472 | 757 | [ |
83c7162d XL |
758 | " --> 1:6", |
759 | " |", | |
f2b60f7d | 760 | "1 | abcdef", |
83c7162d XL |
761 | "2 | gh", |
762 | " | ^----^", | |
763 | " |", | |
4b012472 | 764 | " = error: big one" |
9fa01778 XL |
765 | ] |
766 | .join("\n") | |
767 | ); | |
768 | } | |
769 | ||
770 | #[test] | |
771 | fn display_custom_span_end_after_newline() { | |
772 | let input = "abcdef\n"; | |
773 | let start = position::Position::new(input, 0).unwrap(); | |
774 | let end = position::Position::new(input, 7).unwrap(); | |
775 | assert!(start.at_start()); | |
776 | assert!(end.at_end()); | |
777 | ||
778 | let error: Error<u32> = Error::new_from_span( | |
779 | ErrorVariant::CustomError { | |
780 | message: "error: big one".to_owned(), | |
781 | }, | |
782 | start.span(&end), | |
783 | ); | |
784 | ||
785 | assert_eq!( | |
786 | format!("{}", error), | |
4b012472 | 787 | [ |
9fa01778 XL |
788 | " --> 1:1", |
789 | " |", | |
790 | "1 | abcdef␊", | |
791 | " | ^-----^", | |
792 | " |", | |
4b012472 | 793 | " = error: big one" |
9fa01778 XL |
794 | ] |
795 | .join("\n") | |
796 | ); | |
797 | } | |
798 | ||
799 | #[test] | |
800 | fn display_custom_span_empty() { | |
801 | let input = ""; | |
802 | let start = position::Position::new(input, 0).unwrap(); | |
803 | let end = position::Position::new(input, 0).unwrap(); | |
804 | assert!(start.at_start()); | |
805 | assert!(end.at_end()); | |
806 | ||
807 | let error: Error<u32> = Error::new_from_span( | |
808 | ErrorVariant::CustomError { | |
809 | message: "error: empty".to_owned(), | |
810 | }, | |
811 | start.span(&end), | |
812 | ); | |
813 | ||
814 | assert_eq!( | |
815 | format!("{}", error), | |
4b012472 | 816 | [ |
9fa01778 XL |
817 | " --> 1:1", |
818 | " |", | |
819 | "1 | ", | |
820 | " | ^", | |
821 | " |", | |
4b012472 | 822 | " = error: empty" |
9fa01778 XL |
823 | ] |
824 | .join("\n") | |
83c7162d XL |
825 | ); |
826 | } | |
827 | ||
828 | #[test] | |
829 | fn mapped_parsing_error() { | |
830 | let input = "ab\ncd\nef"; | |
9fa01778 XL |
831 | let pos = position::Position::new(input, 4).unwrap(); |
832 | let error: Error<u32> = Error::new_from_pos( | |
833 | ErrorVariant::ParsingError { | |
834 | positives: vec![1, 2, 3], | |
835 | negatives: vec![4, 5, 6], | |
836 | }, | |
837 | pos, | |
838 | ) | |
839 | .renamed_rules(|n| format!("{}", n + 1)); | |
83c7162d XL |
840 | |
841 | assert_eq!( | |
842 | format!("{}", error), | |
4b012472 | 843 | [ |
83c7162d XL |
844 | " --> 2:2", |
845 | " |", | |
f2b60f7d | 846 | "2 | cd", |
83c7162d XL |
847 | " | ^---", |
848 | " |", | |
4b012472 | 849 | " = unexpected 5, 6, or 7; expected 2, 3, or 4" |
9fa01778 XL |
850 | ] |
851 | .join("\n") | |
852 | ); | |
853 | } | |
854 | ||
855 | #[test] | |
856 | fn error_with_path() { | |
857 | let input = "ab\ncd\nef"; | |
858 | let pos = position::Position::new(input, 4).unwrap(); | |
859 | let error: Error<u32> = Error::new_from_pos( | |
860 | ErrorVariant::ParsingError { | |
861 | positives: vec![1, 2, 3], | |
862 | negatives: vec![4, 5, 6], | |
863 | }, | |
864 | pos, | |
865 | ) | |
866 | .with_path("file.rs"); | |
867 | ||
868 | assert_eq!( | |
869 | format!("{}", error), | |
4b012472 | 870 | [ |
9fa01778 XL |
871 | " --> file.rs:2:2", |
872 | " |", | |
f2b60f7d | 873 | "2 | cd", |
9fa01778 XL |
874 | " | ^---", |
875 | " |", | |
4b012472 | 876 | " = unexpected 4, 5, or 6; expected 1, 2, or 3" |
9fa01778 XL |
877 | ] |
878 | .join("\n") | |
83c7162d XL |
879 | ); |
880 | } | |
3dfed10e XL |
881 | |
882 | #[test] | |
883 | fn underline_with_tabs() { | |
884 | let input = "a\txbc"; | |
885 | let pos = position::Position::new(input, 2).unwrap(); | |
886 | let error: Error<u32> = Error::new_from_pos( | |
887 | ErrorVariant::ParsingError { | |
888 | positives: vec![1, 2, 3], | |
889 | negatives: vec![4, 5, 6], | |
890 | }, | |
891 | pos, | |
892 | ) | |
893 | .with_path("file.rs"); | |
894 | ||
895 | assert_eq!( | |
896 | format!("{}", error), | |
4b012472 | 897 | [ |
3dfed10e XL |
898 | " --> file.rs:1:3", |
899 | " |", | |
900 | "1 | a xbc", | |
901 | " | ^---", | |
902 | " |", | |
4b012472 | 903 | " = unexpected 4, 5, or 6; expected 1, 2, or 3" |
3dfed10e XL |
904 | ] |
905 | .join("\n") | |
906 | ); | |
907 | } | |
fe692bf9 FG |
908 | |
909 | #[test] | |
910 | fn pos_to_lcl_conversion() { | |
911 | let input = "input"; | |
912 | ||
913 | let pos = Position::new(input, 2).unwrap(); | |
914 | ||
915 | assert_eq!(LineColLocation::Pos(pos.line_col()), pos.into()); | |
916 | } | |
917 | ||
918 | #[test] | |
919 | fn span_to_lcl_conversion() { | |
920 | let input = "input"; | |
921 | ||
922 | let span = Span::new(input, 2, 4).unwrap(); | |
923 | let (start, end) = span.split(); | |
924 | ||
925 | assert_eq!( | |
926 | LineColLocation::Span(start.line_col(), end.line_col()), | |
927 | span.into() | |
928 | ); | |
929 | } | |
83c7162d | 930 | } |