]> git.proxmox.com Git - rustc.git/blob - vendor/pest/src/error.rs
New upstream version 1.34.2+dfsg1
[rustc.git] / vendor / pest / src / error.rs
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)]
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)]
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 /// ```
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 /// ```
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
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
256 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
272 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
291 for _ in 0..offset {
292 underline.push(' ');
293 }
294
295 if let Some(end) = end {
296 if end - start > 1 {
297 underline.push('^');
298 for _ in 2..(end - start) {
299 underline.push('-');
300 }
301 underline.push('^');
302 } else {
303 underline.push('^');
304 }
305 } else {
306 underline.push_str("^---")
307 }
308
309 underline
310 }
311
312 fn message(&self) -> String {
313 match self.variant {
314 ErrorVariant::ParsingError {
315 ref positives,
316 ref negatives,
317 } => Error::parsing_error_message(positives, negatives, |r| format!("{:?}", r)),
318 ErrorVariant::CustomError { ref message } => message.clone(),
319 }
320 }
321
322 fn parsing_error_message<F>(positives: &[R], negatives: &[R], mut f: F) -> String
323 where
324 F: FnMut(&R) -> String,
325 {
326 match (negatives.is_empty(), positives.is_empty()) {
327 (false, false) => format!(
328 "unexpected {}; expected {}",
329 Error::enumerate(negatives, &mut f),
330 Error::enumerate(positives, &mut f)
331 ),
332 (false, true) => format!("unexpected {}", Error::enumerate(negatives, &mut f)),
333 (true, false) => format!("expected {}", Error::enumerate(positives, &mut f)),
334 (true, true) => "unknown parsing error".to_owned(),
335 }
336 }
337
338 fn enumerate<F>(rules: &[R], f: &mut F) -> String
339 where
340 F: FnMut(&R) -> String,
341 {
342 match rules.len() {
343 1 => f(&rules[0]),
344 2 => format!("{} or {}", f(&rules[0]), f(&rules[1])),
345 l => {
346 let separated = rules
347 .iter()
348 .take(l - 1)
349 .map(|r| f(r))
350 .collect::<Vec<_>>()
351 .join(", ");
352 format!("{}, or {}", separated, f(&rules[l - 1]))
353 }
354 }
355 }
356
357 pub(crate) fn format(&self) -> String {
358 let spacing = self.spacing();
359 let path = self
360 .path
361 .as_ref()
362 .map(|path| format!("{}:", path))
363 .unwrap_or_default();
364
365 let pair = (self.line_col.clone(), &self.continued_line);
366 if let (LineColLocation::Span(_, end), &Some(ref continued_line)) = pair {
367 let has_line_gap = end.0 - self.start().0 > 1;
368 if has_line_gap {
369 format!(
370 "{s }--> {p}{ls}:{c}\n\
371 {s } |\n\
372 {ls:w$} | {line}\n\
373 {s } | ...\n\
374 {le:w$} | {continued_line}\n\
375 {s } | {underline}\n\
376 {s } |\n\
377 {s } = {message}",
378 s = spacing,
379 w = spacing.len(),
380 p = path,
381 ls = self.start().0,
382 le = end.0,
383 c = self.start().1,
384 line = self.line,
385 continued_line = continued_line,
386 underline = self.underline(),
387 message = self.message()
388 )
389 } else {
390 format!(
391 "{s }--> {p}{ls}:{c}\n\
392 {s } |\n\
393 {ls:w$} | {line}\n\
394 {le:w$} | {continued_line}\n\
395 {s } | {underline}\n\
396 {s } |\n\
397 {s } = {message}",
398 s = spacing,
399 w = spacing.len(),
400 p = path,
401 ls = self.start().0,
402 le = end.0,
403 c = self.start().1,
404 line = self.line,
405 continued_line = continued_line,
406 underline = self.underline(),
407 message = self.message()
408 )
409 }
410 } else {
411 format!(
412 "{s}--> {p}{l}:{c}\n\
413 {s} |\n\
414 {l} | {line}\n\
415 {s} | {underline}\n\
416 {s} |\n\
417 {s} = {message}",
418 s = spacing,
419 p = path,
420 l = self.start().0,
421 c = self.start().1,
422 line = self.line,
423 underline = self.underline(),
424 message = self.message()
425 )
426 }
427 }
428 }
429
430 impl<R: RuleType> fmt::Display for Error<R> {
431 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
432 write!(f, "{}", self.format())
433 }
434 }
435
436 impl<'i, R: RuleType> error::Error for Error<R> {
437 fn description(&self) -> &str {
438 match self.variant {
439 ErrorVariant::ParsingError { .. } => "parsing error",
440 ErrorVariant::CustomError { ref message } => message,
441 }
442 }
443 }
444
445 fn visualize_whitespace(input: &str) -> String {
446 input.to_owned().replace('\r', "␍").replace('\n', "␊")
447 }
448
449 #[cfg(test)]
450 mod tests {
451 use super::super::position;
452 use super::*;
453
454 #[test]
455 fn display_parsing_error_mixed() {
456 let input = "ab\ncd\nef";
457 let pos = position::Position::new(input, 4).unwrap();
458 let error: Error<u32> = Error::new_from_pos(
459 ErrorVariant::ParsingError {
460 positives: vec![1, 2, 3],
461 negatives: vec![4, 5, 6],
462 },
463 pos,
464 );
465
466 assert_eq!(
467 format!("{}", error),
468 vec![
469 " --> 2:2",
470 " |",
471 "2 | cd␊",
472 " | ^---",
473 " |",
474 " = unexpected 4, 5, or 6; expected 1, 2, or 3",
475 ]
476 .join("\n")
477 );
478 }
479
480 #[test]
481 fn display_parsing_error_positives() {
482 let input = "ab\ncd\nef";
483 let pos = position::Position::new(input, 4).unwrap();
484 let error: Error<u32> = Error::new_from_pos(
485 ErrorVariant::ParsingError {
486 positives: vec![1, 2],
487 negatives: vec![],
488 },
489 pos,
490 );
491
492 assert_eq!(
493 format!("{}", error),
494 vec![
495 " --> 2:2",
496 " |",
497 "2 | cd␊",
498 " | ^---",
499 " |",
500 " = expected 1 or 2",
501 ]
502 .join("\n")
503 );
504 }
505
506 #[test]
507 fn display_parsing_error_negatives() {
508 let input = "ab\ncd\nef";
509 let pos = position::Position::new(input, 4).unwrap();
510 let error: Error<u32> = Error::new_from_pos(
511 ErrorVariant::ParsingError {
512 positives: vec![],
513 negatives: vec![4, 5, 6],
514 },
515 pos,
516 );
517
518 assert_eq!(
519 format!("{}", error),
520 vec![
521 " --> 2:2",
522 " |",
523 "2 | cd␊",
524 " | ^---",
525 " |",
526 " = unexpected 4, 5, or 6",
527 ]
528 .join("\n")
529 );
530 }
531
532 #[test]
533 fn display_parsing_error_unknown() {
534 let input = "ab\ncd\nef";
535 let pos = position::Position::new(input, 4).unwrap();
536 let error: Error<u32> = Error::new_from_pos(
537 ErrorVariant::ParsingError {
538 positives: vec![],
539 negatives: vec![],
540 },
541 pos,
542 );
543
544 assert_eq!(
545 format!("{}", error),
546 vec![
547 " --> 2:2",
548 " |",
549 "2 | cd␊",
550 " | ^---",
551 " |",
552 " = unknown parsing error",
553 ]
554 .join("\n")
555 );
556 }
557
558 #[test]
559 fn display_custom_pos() {
560 let input = "ab\ncd\nef";
561 let pos = position::Position::new(input, 4).unwrap();
562 let error: Error<u32> = Error::new_from_pos(
563 ErrorVariant::CustomError {
564 message: "error: big one".to_owned(),
565 },
566 pos,
567 );
568
569 assert_eq!(
570 format!("{}", error),
571 vec![
572 " --> 2:2",
573 " |",
574 "2 | cd␊",
575 " | ^---",
576 " |",
577 " = error: big one",
578 ]
579 .join("\n")
580 );
581 }
582
583 #[test]
584 fn display_custom_span_two_lines() {
585 let input = "ab\ncd\nefgh";
586 let start = position::Position::new(input, 4).unwrap();
587 let end = position::Position::new(input, 9).unwrap();
588 let error: Error<u32> = Error::new_from_span(
589 ErrorVariant::CustomError {
590 message: "error: big one".to_owned(),
591 },
592 start.span(&end),
593 );
594
595 assert_eq!(
596 format!("{}", error),
597 vec![
598 " --> 2:2",
599 " |",
600 "2 | cd␊",
601 "3 | efgh",
602 " | ^^",
603 " |",
604 " = error: big one",
605 ]
606 .join("\n")
607 );
608 }
609
610 #[test]
611 fn display_custom_span_three_lines() {
612 let input = "ab\ncd\nefgh";
613 let start = position::Position::new(input, 1).unwrap();
614 let end = position::Position::new(input, 9).unwrap();
615 let error: Error<u32> = Error::new_from_span(
616 ErrorVariant::CustomError {
617 message: "error: big one".to_owned(),
618 },
619 start.span(&end),
620 );
621
622 assert_eq!(
623 format!("{}", error),
624 vec![
625 " --> 1:2",
626 " |",
627 "1 | ab␊",
628 " | ...",
629 "3 | efgh",
630 " | ^^",
631 " |",
632 " = error: big one",
633 ]
634 .join("\n")
635 );
636 }
637
638 #[test]
639 fn display_custom_span_two_lines_inverted_cols() {
640 let input = "abcdef\ngh";
641 let start = position::Position::new(input, 5).unwrap();
642 let end = position::Position::new(input, 8).unwrap();
643 let error: Error<u32> = Error::new_from_span(
644 ErrorVariant::CustomError {
645 message: "error: big one".to_owned(),
646 },
647 start.span(&end),
648 );
649
650 assert_eq!(
651 format!("{}", error),
652 vec![
653 " --> 1:6",
654 " |",
655 "1 | abcdef␊",
656 "2 | gh",
657 " | ^----^",
658 " |",
659 " = error: big one",
660 ]
661 .join("\n")
662 );
663 }
664
665 #[test]
666 fn display_custom_span_end_after_newline() {
667 let input = "abcdef\n";
668 let start = position::Position::new(input, 0).unwrap();
669 let end = position::Position::new(input, 7).unwrap();
670 assert!(start.at_start());
671 assert!(end.at_end());
672
673 let error: Error<u32> = Error::new_from_span(
674 ErrorVariant::CustomError {
675 message: "error: big one".to_owned(),
676 },
677 start.span(&end),
678 );
679
680 assert_eq!(
681 format!("{}", error),
682 vec![
683 " --> 1:1",
684 " |",
685 "1 | abcdef␊",
686 " | ^-----^",
687 " |",
688 " = error: big one",
689 ]
690 .join("\n")
691 );
692 }
693
694 #[test]
695 fn display_custom_span_empty() {
696 let input = "";
697 let start = position::Position::new(input, 0).unwrap();
698 let end = position::Position::new(input, 0).unwrap();
699 assert!(start.at_start());
700 assert!(end.at_end());
701
702 let error: Error<u32> = Error::new_from_span(
703 ErrorVariant::CustomError {
704 message: "error: empty".to_owned(),
705 },
706 start.span(&end),
707 );
708
709 assert_eq!(
710 format!("{}", error),
711 vec![
712 " --> 1:1",
713 " |",
714 "1 | ",
715 " | ^",
716 " |",
717 " = error: empty",
718 ]
719 .join("\n")
720 );
721 }
722
723 #[test]
724 fn mapped_parsing_error() {
725 let input = "ab\ncd\nef";
726 let pos = position::Position::new(input, 4).unwrap();
727 let error: Error<u32> = Error::new_from_pos(
728 ErrorVariant::ParsingError {
729 positives: vec![1, 2, 3],
730 negatives: vec![4, 5, 6],
731 },
732 pos,
733 )
734 .renamed_rules(|n| format!("{}", n + 1));
735
736 assert_eq!(
737 format!("{}", error),
738 vec![
739 " --> 2:2",
740 " |",
741 "2 | cd␊",
742 " | ^---",
743 " |",
744 " = unexpected 5, 6, or 7; expected 2, 3, or 4",
745 ]
746 .join("\n")
747 );
748 }
749
750 #[test]
751 fn error_with_path() {
752 let input = "ab\ncd\nef";
753 let pos = position::Position::new(input, 4).unwrap();
754 let error: Error<u32> = Error::new_from_pos(
755 ErrorVariant::ParsingError {
756 positives: vec![1, 2, 3],
757 negatives: vec![4, 5, 6],
758 },
759 pos,
760 )
761 .with_path("file.rs");
762
763 assert_eq!(
764 format!("{}", error),
765 vec![
766 " --> file.rs:2:2",
767 " |",
768 "2 | cd␊",
769 " | ^---",
770 " |",
771 " = unexpected 4, 5, or 6; expected 1, 2, or 3",
772 ]
773 .join("\n")
774 );
775 }
776 }