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