1 // pest. The Elegant Parser
2 // Copyright (c) 2018 Dragoș Tiselice
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.
10 //! Types for different kinds of parsing failures.
17 use position
::Position
;
21 /// Parse-related error type.
22 #[derive(Clone, Debug, Eq, Hash, PartialEq)]
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
,
32 continued_line
: Option
<String
>,
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
45 /// Custom error with a message
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`
57 /// `Error` was created by `Error::new_from_span`
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`
66 /// Line/column pairs if `Error` was created by `Error::new_from_span`
67 Span((usize, usize), (usize, usize)),
70 impl<R
: RuleType
> Error
<R
> {
71 /// Creates `Error` from `ErrorVariant` and `Position`.
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)]
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]
95 /// println!("{}", error);
97 #[allow(clippy::needless_pass_by_value)]
98 pub fn new_from_pos(variant
: ErrorVariant
<R
>, pos
: Position
) -> Error
<R
> {
101 location
: InputLocation
::Pos(pos
.pos()),
103 line
: visualize_whitespace(pos
.line_of()),
104 continued_line
: None
,
105 line_col
: LineColLocation
::Pos(pos
.line_col()),
109 /// Creates `Error` from `ErrorVariant` and `Span`.
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)]
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]
135 /// println!("{}", error);
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();
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);
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
);
156 location
: InputLocation
::Span((span
.start(), end
.pos())),
160 line_col
: LineColLocation
::Span(span
.start_pos().line_col(), end_line_col
),
164 /// Returns `Error` variant with `path` which is shown when formatted with `Display`.
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)]
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]
186 /// ).with_path("file.rs");
188 pub fn with_path(mut self, path
: &str) -> Error
<R
> {
189 self.path
= Some(path
.to_owned());
194 /// Renames all `Rule`s if this is a [`ParsingError`]. It does nothing when called on a
197 /// Useful in order to rename verbose rules or have detailed per-`Rule` formatting.
199 /// [`ParsingError`]: enum.ErrorVariant.html#variant.ParsingError
200 /// [`CustomError`]: enum.ErrorVariant.html#variant.CustomError
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)]
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]
222 /// ).renamed_rules(|rule| {
224 /// Rule::open_paren => "(".to_owned(),
225 /// Rule::closed_paren => "closed paren".to_owned()
229 pub fn renamed_rules
<F
>(mut self, f
: F
) -> Error
<R
>
231 F
: FnMut(&R
) -> String
,
233 let variant
= match self.variant
{
234 ErrorVariant
::ParsingError
{
238 let message
= Error
::parsing_error_message(&positives
, &negatives
, f
);
239 ErrorVariant
::CustomError { message }
244 self.variant
= variant
;
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
,
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
),
262 let line_str_len
= format
!("{}", line
).len();
264 let mut spacing
= String
::new();
265 for _
in 0..line_str_len
{
272 fn underline(&self) -> String
{
273 let mut underline
= String
::new();
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
;
280 mem
::swap(&mut start
, &mut end
);
289 let offset
= start
- 1;
295 if let Some(end
) = end
{
298 for _
in 2..(end
- start
) {
306 underline
.push_str("^---")
312 fn message(&self) -> String
{
314 ErrorVariant
::ParsingError
{
317 } => Error
::parsing_error_message(positives
, negatives
, |r
| format
!("{:?}", r
)),
318 ErrorVariant
::CustomError { ref message }
=> message
.clone(),
322 fn parsing_error_message
<F
>(positives
: &[R
], negatives
: &[R
], mut f
: F
) -> String
324 F
: FnMut(&R
) -> String
,
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
)
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(),
338 fn enumerate
<F
>(rules
: &[R
], f
: &mut F
) -> String
340 F
: FnMut(&R
) -> String
,
344 2 => format
!("{} or {}", f(&rules
[0]), f(&rules
[1])),
346 let separated
= rules
352 format
!("{}, or {}", separated
, f(&rules
[l
- 1]))
357 pub(crate) fn format(&self) -> String
{
358 let spacing
= self.spacing();
362 .map(|path
| format
!("{}:", path
))
363 .unwrap_or_default();
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;
370 "{s }--> {p}{ls}:{c}\n\
374 {le:w$} | {continued_line}\n\
375 {s } | {underline}\n\
385 continued_line
= continued_line
,
386 underline
= self.underline(),
387 message
= self.message()
391 "{s }--> {p}{ls}:{c}\n\
394 {le:w$} | {continued_line}\n\
395 {s } | {underline}\n\
405 continued_line
= continued_line
,
406 underline
= self.underline(),
407 message
= self.message()
412 "{s}--> {p}{l}:{c}\n\
423 underline
= self.underline(),
424 message
= self.message()
430 impl<R
: RuleType
> fmt
::Display
for Error
<R
> {
431 fn fmt(&self, f
: &mut fmt
::Formatter
) -> fmt
::Result
{
432 write
!(f
, "{}", self.format())
436 impl<'i
, R
: RuleType
> error
::Error
for Error
<R
> {
437 fn description(&self) -> &str {
439 ErrorVariant
::ParsingError { .. }
=> "parsing error",
440 ErrorVariant
::CustomError { ref message }
=> message
,
445 fn visualize_whitespace(input
: &str) -> String
{
446 input
.to_owned().replace('
\r'
, "␍").replace('
\n'
, "␊")
451 use super::super::position
;
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],
467 format
!("{}", error
),
474 " = unexpected 4, 5, or 6; expected 1, 2, or 3",
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],
493 format
!("{}", error
),
500 " = expected 1 or 2",
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
{
513 negatives
: vec
![4, 5, 6],
519 format
!("{}", error
),
526 " = unexpected 4, 5, or 6",
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
{
545 format
!("{}", error
),
552 " = unknown parsing error",
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(),
570 format
!("{}", error
),
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(),
596 format
!("{}", error
),
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(),
623 format
!("{}", error
),
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(),
651 format
!("{}", error
),
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());
673 let error
: Error
<u32> = Error
::new_from_span(
674 ErrorVariant
::CustomError
{
675 message
: "error: big one".to_owned(),
681 format
!("{}", error
),
695 fn display_custom_span_empty() {
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());
702 let error
: Error
<u32> = Error
::new_from_span(
703 ErrorVariant
::CustomError
{
704 message
: "error: empty".to_owned(),
710 format
!("{}", error
),
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],
734 .renamed_rules(|n
| format
!("{}", n
+ 1));
737 format
!("{}", error
),
744 " = unexpected 5, 6, or 7; expected 2, 3, or 4",
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],
761 .with_path("file.rs");
764 format
!("{}", error
),
771 " = unexpected 4, 5, or 6; expected 1, 2, or 3",