• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 crate::parser_state::{ParseAttempts, ParsingToken, RulesCallStack};
13 use alloc::borrow::Cow;
14 use alloc::borrow::ToOwned;
15 use alloc::boxed::Box;
16 use alloc::collections::{BTreeMap, BTreeSet};
17 use alloc::format;
18 use alloc::string::String;
19 use alloc::string::ToString;
20 use alloc::vec;
21 use alloc::vec::Vec;
22 use core::cmp;
23 use core::fmt;
24 use core::mem;
25 
26 use crate::position::Position;
27 use crate::span::Span;
28 use crate::RuleType;
29 
30 /// Parse-related error type.
31 #[derive(Clone, Debug, Eq, Hash, PartialEq)]
32 #[cfg_attr(feature = "std", derive(thiserror::Error))]
33 pub struct Error<R> {
34     /// Variant of the error
35     pub variant: ErrorVariant<R>,
36     /// Location within the input string
37     pub location: InputLocation,
38     /// Line/column within the input string
39     pub line_col: LineColLocation,
40     path: Option<String>,
41     line: String,
42     continued_line: Option<String>,
43     parse_attempts: Option<ParseAttempts<R>>,
44 }
45 
46 /// Different kinds of parsing errors.
47 #[derive(Clone, Debug, Eq, Hash, PartialEq)]
48 #[cfg_attr(feature = "std", derive(thiserror::Error))]
49 pub enum ErrorVariant<R> {
50     /// Generated parsing error with expected and unexpected `Rule`s
51     ParsingError {
52         /// Positive attempts
53         positives: Vec<R>,
54         /// Negative attempts
55         negatives: Vec<R>,
56     },
57     /// Custom error with a message
58     CustomError {
59         /// Short explanation
60         message: String,
61     },
62 }
63 
64 /// Where an `Error` has occurred.
65 #[derive(Clone, Debug, Eq, Hash, PartialEq)]
66 pub enum InputLocation {
67     /// `Error` was created by `Error::new_from_pos`
68     Pos(usize),
69     /// `Error` was created by `Error::new_from_span`
70     Span((usize, usize)),
71 }
72 
73 /// Line/column where an `Error` has occurred.
74 #[derive(Clone, Debug, Eq, Hash, PartialEq)]
75 pub enum LineColLocation {
76     /// Line/column pair if `Error` was created by `Error::new_from_pos`
77     Pos((usize, usize)),
78     /// Line/column pairs if `Error` was created by `Error::new_from_span`
79     Span((usize, usize), (usize, usize)),
80 }
81 
82 impl From<Position<'_>> for LineColLocation {
from(value: Position<'_>) -> Self83     fn from(value: Position<'_>) -> Self {
84         Self::Pos(value.line_col())
85     }
86 }
87 
88 impl From<Span<'_>> for LineColLocation {
from(value: Span<'_>) -> Self89     fn from(value: Span<'_>) -> Self {
90         let (start, end) = value.split();
91         Self::Span(start.line_col(), end.line_col())
92     }
93 }
94 
95 /// Function mapping rule to its helper message defined by user.
96 pub type RuleToMessageFn<R> = Box<dyn Fn(&R) -> Option<String>>;
97 /// Function mapping string element to bool denoting whether it's a whitespace defined by user.
98 pub type IsWhitespaceFn = Box<dyn Fn(String) -> bool>;
99 
100 impl ParsingToken {
is_whitespace(&self, is_whitespace: &IsWhitespaceFn) -> bool101     pub fn is_whitespace(&self, is_whitespace: &IsWhitespaceFn) -> bool {
102         match self {
103             ParsingToken::Sensitive { token } => is_whitespace(token.clone()),
104             ParsingToken::Insensitive { token } => is_whitespace(token.clone()),
105             ParsingToken::Range { .. } => false,
106             ParsingToken::BuiltInRule => false,
107         }
108     }
109 }
110 
111 impl<R: RuleType> ParseAttempts<R> {
112     /// Helper formatting function to get message informing about tokens we've
113     /// (un)expected to see.
114     /// Used as a part of `parse_attempts_error`.
tokens_helper_messages( &self, is_whitespace_fn: &IsWhitespaceFn, spacing: &str, ) -> Vec<String>115     fn tokens_helper_messages(
116         &self,
117         is_whitespace_fn: &IsWhitespaceFn,
118         spacing: &str,
119     ) -> Vec<String> {
120         let mut helper_messages = Vec::new();
121         let tokens_header_pairs = vec![
122             (self.expected_tokens(), "expected"),
123             (self.unexpected_tokens(), "unexpected"),
124         ];
125 
126         for (tokens, header) in &tokens_header_pairs {
127             if tokens.is_empty() {
128                 continue;
129             }
130 
131             let mut helper_tokens_message = format!("{spacing}note: {header} ");
132             helper_tokens_message.push_str(if tokens.len() == 1 {
133                 "token: "
134             } else {
135                 "one of tokens: "
136             });
137 
138             let expected_tokens_set: BTreeSet<String> = tokens
139                 .iter()
140                 .map(|token| {
141                     if token.is_whitespace(is_whitespace_fn) {
142                         String::from("WHITESPACE")
143                     } else {
144                         format!("`{}`", token)
145                     }
146                 })
147                 .collect();
148 
149             helper_tokens_message.push_str(
150                 &expected_tokens_set
151                     .iter()
152                     .cloned()
153                     .collect::<Vec<String>>()
154                     .join(", "),
155             );
156             helper_messages.push(helper_tokens_message);
157         }
158 
159         helper_messages
160     }
161 }
162 
163 impl<R: RuleType> Error<R> {
164     /// Creates `Error` from `ErrorVariant` and `Position`.
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     /// let error = Error::new_from_pos(
181     ///     ErrorVariant::ParsingError {
182     ///         positives: vec![Rule::open_paren],
183     ///         negatives: vec![Rule::closed_paren],
184     ///     },
185     ///     pos
186     /// );
187     ///
188     /// println!("{}", error);
189     /// ```
new_from_pos(variant: ErrorVariant<R>, pos: Position<'_>) -> Error<R>190     pub fn new_from_pos(variant: ErrorVariant<R>, pos: Position<'_>) -> Error<R> {
191         let visualize_ws = pos.match_char('\n') || pos.match_char('\r');
192         let line_of = pos.line_of();
193         let line = if visualize_ws {
194             visualize_whitespace(line_of)
195         } else {
196             line_of.replace(&['\r', '\n'][..], "")
197         };
198         Error {
199             variant,
200             location: InputLocation::Pos(pos.pos()),
201             path: None,
202             line,
203             continued_line: None,
204             line_col: LineColLocation::Pos(pos.line_col()),
205             parse_attempts: None,
206         }
207     }
208 
209     /// Wrapper function to track `parse_attempts` as a result
210     /// of `state` function call in `parser_state.rs`.
new_from_pos_with_parsing_attempts( variant: ErrorVariant<R>, pos: Position<'_>, parse_attempts: ParseAttempts<R>, ) -> Error<R>211     pub(crate) fn new_from_pos_with_parsing_attempts(
212         variant: ErrorVariant<R>,
213         pos: Position<'_>,
214         parse_attempts: ParseAttempts<R>,
215     ) -> Error<R> {
216         let mut error = Self::new_from_pos(variant, pos);
217         error.parse_attempts = Some(parse_attempts);
218         error
219     }
220 
221     /// Creates `Error` from `ErrorVariant` and `Span`.
222     ///
223     /// # Examples
224     ///
225     /// ```
226     /// # use pest::error::{Error, ErrorVariant};
227     /// # use pest::{Position, Span};
228     /// # #[allow(non_camel_case_types)]
229     /// # #[allow(dead_code)]
230     /// # #[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
231     /// # enum Rule {
232     /// #     open_paren,
233     /// #     closed_paren
234     /// # }
235     /// # let input = "";
236     /// # let start = Position::from_start(input);
237     /// # let end = start.clone();
238     /// # let span = start.span(&end);
239     /// let error = Error::new_from_span(
240     ///     ErrorVariant::ParsingError {
241     ///         positives: vec![Rule::open_paren],
242     ///         negatives: vec![Rule::closed_paren],
243     ///     },
244     ///     span
245     /// );
246     ///
247     /// println!("{}", error);
248     /// ```
new_from_span(variant: ErrorVariant<R>, span: Span<'_>) -> Error<R>249     pub fn new_from_span(variant: ErrorVariant<R>, span: Span<'_>) -> Error<R> {
250         let end = span.end_pos();
251         let mut end_line_col = end.line_col();
252         // end position is after a \n, so we want to point to the visual lf symbol
253         if end_line_col.1 == 1 {
254             let mut visual_end = end;
255             visual_end.skip_back(1);
256             let lc = visual_end.line_col();
257             end_line_col = (lc.0, lc.1 + 1);
258         };
259 
260         let mut line_iter = span.lines();
261         let sl = line_iter.next().unwrap_or("");
262         let mut chars = span.as_str().chars();
263         let visualize_ws = matches!(chars.next(), Some('\n') | Some('\r'))
264             || matches!(chars.last(), Some('\n') | Some('\r'));
265         let start_line = if visualize_ws {
266             visualize_whitespace(sl)
267         } else {
268             sl.to_owned().replace(&['\r', '\n'][..], "")
269         };
270         let ll = line_iter.last();
271         let continued_line = if visualize_ws {
272             ll.map(str::to_owned)
273         } else {
274             ll.map(visualize_whitespace)
275         };
276 
277         Error {
278             variant,
279             location: InputLocation::Span((span.start(), end.pos())),
280             path: None,
281             line: start_line,
282             continued_line,
283             line_col: LineColLocation::Span(span.start_pos().line_col(), end_line_col),
284             parse_attempts: None,
285         }
286     }
287 
288     /// Returns `Error` variant with `path` which is shown when formatted with `Display`.
289     ///
290     /// # Examples
291     ///
292     /// ```
293     /// # use pest::error::{Error, ErrorVariant};
294     /// # use pest::Position;
295     /// # #[allow(non_camel_case_types)]
296     /// # #[allow(dead_code)]
297     /// # #[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
298     /// # enum Rule {
299     /// #     open_paren,
300     /// #     closed_paren
301     /// # }
302     /// # let input = "";
303     /// # let pos = Position::from_start(input);
304     /// Error::new_from_pos(
305     ///     ErrorVariant::ParsingError {
306     ///         positives: vec![Rule::open_paren],
307     ///         negatives: vec![Rule::closed_paren],
308     ///     },
309     ///     pos
310     /// ).with_path("file.rs");
311     /// ```
with_path(mut self, path: &str) -> Error<R>312     pub fn with_path(mut self, path: &str) -> Error<R> {
313         self.path = Some(path.to_owned());
314 
315         self
316     }
317 
318     /// Returns the path set using [`Error::with_path()`].
319     ///
320     /// # Examples
321     ///
322     /// ```
323     /// # use pest::error::{Error, ErrorVariant};
324     /// # use pest::Position;
325     /// # #[allow(non_camel_case_types)]
326     /// # #[allow(dead_code)]
327     /// # #[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
328     /// # enum Rule {
329     /// #     open_paren,
330     /// #     closed_paren
331     /// # }
332     /// # let input = "";
333     /// # let pos = Position::from_start(input);
334     /// # let error = Error::new_from_pos(
335     /// #     ErrorVariant::ParsingError {
336     /// #         positives: vec![Rule::open_paren],
337     /// #         negatives: vec![Rule::closed_paren],
338     /// #     },
339     /// #     pos);
340     /// let error = error.with_path("file.rs");
341     /// assert_eq!(Some("file.rs"), error.path());
342     /// ```
path(&self) -> Option<&str>343     pub fn path(&self) -> Option<&str> {
344         self.path.as_deref()
345     }
346 
347     /// Returns the line that the error is on.
line(&self) -> &str348     pub fn line(&self) -> &str {
349         self.line.as_str()
350     }
351 
352     /// Renames all `Rule`s if this is a [`ParsingError`]. It does nothing when called on a
353     /// [`CustomError`].
354     ///
355     /// Useful in order to rename verbose rules or have detailed per-`Rule` formatting.
356     ///
357     /// [`ParsingError`]: enum.ErrorVariant.html#variant.ParsingError
358     /// [`CustomError`]: enum.ErrorVariant.html#variant.CustomError
359     ///
360     /// # Examples
361     ///
362     /// ```
363     /// # use pest::error::{Error, ErrorVariant};
364     /// # use pest::Position;
365     /// # #[allow(non_camel_case_types)]
366     /// # #[allow(dead_code)]
367     /// # #[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
368     /// # enum Rule {
369     /// #     open_paren,
370     /// #     closed_paren
371     /// # }
372     /// # let input = "";
373     /// # let pos = Position::from_start(input);
374     /// Error::new_from_pos(
375     ///     ErrorVariant::ParsingError {
376     ///         positives: vec![Rule::open_paren],
377     ///         negatives: vec![Rule::closed_paren],
378     ///     },
379     ///     pos
380     /// ).renamed_rules(|rule| {
381     ///     match *rule {
382     ///         Rule::open_paren => "(".to_owned(),
383     ///         Rule::closed_paren => "closed paren".to_owned()
384     ///     }
385     /// });
386     /// ```
renamed_rules<F>(mut self, f: F) -> Error<R> where F: FnMut(&R) -> String,387     pub fn renamed_rules<F>(mut self, f: F) -> Error<R>
388     where
389         F: FnMut(&R) -> String,
390     {
391         let variant = match self.variant {
392             ErrorVariant::ParsingError {
393                 positives,
394                 negatives,
395             } => {
396                 let message = Error::parsing_error_message(&positives, &negatives, f);
397                 ErrorVariant::CustomError { message }
398             }
399             variant => variant,
400         };
401 
402         self.variant = variant;
403 
404         self
405     }
406 
407     /// Get detailed information about errored rules sequence.
408     /// Returns `Some(results)` only for `ParsingError`.
parse_attempts(&self) -> Option<ParseAttempts<R>>409     pub fn parse_attempts(&self) -> Option<ParseAttempts<R>> {
410         self.parse_attempts.clone()
411     }
412 
413     /// Get error message based on parsing attempts.
414     /// Returns `None` in case self `parse_attempts` is `None`.
parse_attempts_error( &self, input: &str, rule_to_message: &RuleToMessageFn<R>, is_whitespace: &IsWhitespaceFn, ) -> Option<Error<R>>415     pub fn parse_attempts_error(
416         &self,
417         input: &str,
418         rule_to_message: &RuleToMessageFn<R>,
419         is_whitespace: &IsWhitespaceFn,
420     ) -> Option<Error<R>> {
421         let attempts = if let Some(ref parse_attempts) = self.parse_attempts {
422             parse_attempts.clone()
423         } else {
424             return None;
425         };
426 
427         let spacing = self.spacing() + "   ";
428         let error_position = attempts.max_position;
429         let message = {
430             let mut help_lines: Vec<String> = Vec::new();
431             help_lines.push(String::from("error: parsing error occurred."));
432 
433             // Note: at least one of `(un)expected_tokens` must not be empty.
434             for tokens_helper_message in attempts.tokens_helper_messages(is_whitespace, &spacing) {
435                 help_lines.push(tokens_helper_message);
436             }
437 
438             let call_stacks = attempts.call_stacks();
439             // Group call stacks by their parents so that we can print common header and
440             // several sub helper messages.
441             let mut call_stacks_parents_groups: BTreeMap<Option<R>, Vec<RulesCallStack<R>>> =
442                 BTreeMap::new();
443             for call_stack in call_stacks {
444                 call_stacks_parents_groups
445                     .entry(call_stack.parent)
446                     .or_default()
447                     .push(call_stack);
448             }
449 
450             for (group_parent, group) in call_stacks_parents_groups {
451                 if let Some(parent_rule) = group_parent {
452                     let mut contains_meaningful_info = false;
453                     help_lines.push(format!(
454                         "{spacing}help: {}",
455                         if let Some(message) = rule_to_message(&parent_rule) {
456                             contains_meaningful_info = true;
457                             message
458                         } else {
459                             String::from("[Unknown parent rule]")
460                         }
461                     ));
462                     for call_stack in group {
463                         if let Some(r) = call_stack.deepest.get_rule() {
464                             if let Some(message) = rule_to_message(r) {
465                                 contains_meaningful_info = true;
466                                 help_lines.push(format!("{spacing}      - {message}"));
467                             }
468                         }
469                     }
470                     if !contains_meaningful_info {
471                         // Have to remove useless line for unknown parent rule.
472                         help_lines.pop();
473                     }
474                 } else {
475                     for call_stack in group {
476                         // Note that `deepest` rule may be `None`. E.g. in case it corresponds
477                         // to WHITESPACE expected token which has no parent rule (on the top level
478                         // parsing).
479                         if let Some(r) = call_stack.deepest.get_rule() {
480                             let helper_message = rule_to_message(r);
481                             if let Some(helper_message) = helper_message {
482                                 help_lines.push(format!("{spacing}help: {helper_message}"));
483                             }
484                         }
485                     }
486                 }
487             }
488 
489             help_lines.join("\n")
490         };
491         let error = Error::new_from_pos(
492             ErrorVariant::CustomError { message },
493             Position::new_internal(input, error_position),
494         );
495         Some(error)
496     }
497 
start(&self) -> (usize, usize)498     fn start(&self) -> (usize, usize) {
499         match self.line_col {
500             LineColLocation::Pos(line_col) => line_col,
501             LineColLocation::Span(start_line_col, _) => start_line_col,
502         }
503     }
504 
spacing(&self) -> String505     fn spacing(&self) -> String {
506         let line = match self.line_col {
507             LineColLocation::Pos((line, _)) => line,
508             LineColLocation::Span((start_line, _), (end_line, _)) => cmp::max(start_line, end_line),
509         };
510 
511         let line_str_len = format!("{}", line).len();
512 
513         let mut spacing = String::new();
514         for _ in 0..line_str_len {
515             spacing.push(' ');
516         }
517 
518         spacing
519     }
520 
underline(&self) -> String521     fn underline(&self) -> String {
522         let mut underline = String::new();
523 
524         let mut start = self.start().1;
525         let end = match self.line_col {
526             LineColLocation::Span(_, (_, mut end)) => {
527                 let inverted_cols = start > end;
528                 if inverted_cols {
529                     mem::swap(&mut start, &mut end);
530                     start -= 1;
531                     end += 1;
532                 }
533 
534                 Some(end)
535             }
536             _ => None,
537         };
538         let offset = start - 1;
539         let line_chars = self.line.chars();
540 
541         for c in line_chars.take(offset) {
542             match c {
543                 '\t' => underline.push('\t'),
544                 _ => underline.push(' '),
545             }
546         }
547 
548         if let Some(end) = end {
549             underline.push('^');
550             if end - start > 1 {
551                 for _ in 2..(end - start) {
552                     underline.push('-');
553                 }
554                 underline.push('^');
555             }
556         } else {
557             underline.push_str("^---")
558         }
559 
560         underline
561     }
562 
message(&self) -> String563     fn message(&self) -> String {
564         self.variant.message().to_string()
565     }
566 
parsing_error_message<F>(positives: &[R], negatives: &[R], mut f: F) -> String where F: FnMut(&R) -> String,567     fn parsing_error_message<F>(positives: &[R], negatives: &[R], mut f: F) -> String
568     where
569         F: FnMut(&R) -> String,
570     {
571         match (negatives.is_empty(), positives.is_empty()) {
572             (false, false) => format!(
573                 "unexpected {}; expected {}",
574                 Error::enumerate(negatives, &mut f),
575                 Error::enumerate(positives, &mut f)
576             ),
577             (false, true) => format!("unexpected {}", Error::enumerate(negatives, &mut f)),
578             (true, false) => format!("expected {}", Error::enumerate(positives, &mut f)),
579             (true, true) => "unknown parsing error".to_owned(),
580         }
581     }
582 
enumerate<F>(rules: &[R], f: &mut F) -> String where F: FnMut(&R) -> String,583     fn enumerate<F>(rules: &[R], f: &mut F) -> String
584     where
585         F: FnMut(&R) -> String,
586     {
587         match rules.len() {
588             1 => f(&rules[0]),
589             2 => format!("{} or {}", f(&rules[0]), f(&rules[1])),
590             l => {
591                 let non_separated = f(&rules[l - 1]);
592                 let separated = rules
593                     .iter()
594                     .take(l - 1)
595                     .map(f)
596                     .collect::<Vec<_>>()
597                     .join(", ");
598                 format!("{}, or {}", separated, non_separated)
599             }
600         }
601     }
602 
format(&self) -> String603     pub(crate) fn format(&self) -> String {
604         let spacing = self.spacing();
605         let path = self
606             .path
607             .as_ref()
608             .map(|path| format!("{}:", path))
609             .unwrap_or_default();
610 
611         let pair = (self.line_col.clone(), &self.continued_line);
612         if let (LineColLocation::Span(_, end), Some(ref continued_line)) = pair {
613             let has_line_gap = end.0 - self.start().0 > 1;
614             if has_line_gap {
615                 format!(
616                     "{s    }--> {p}{ls}:{c}\n\
617                      {s    } |\n\
618                      {ls:w$} | {line}\n\
619                      {s    } | ...\n\
620                      {le:w$} | {continued_line}\n\
621                      {s    } | {underline}\n\
622                      {s    } |\n\
623                      {s    } = {message}",
624                     s = spacing,
625                     w = spacing.len(),
626                     p = path,
627                     ls = self.start().0,
628                     le = end.0,
629                     c = self.start().1,
630                     line = self.line,
631                     continued_line = continued_line,
632                     underline = self.underline(),
633                     message = self.message()
634                 )
635             } else {
636                 format!(
637                     "{s    }--> {p}{ls}:{c}\n\
638                      {s    } |\n\
639                      {ls:w$} | {line}\n\
640                      {le:w$} | {continued_line}\n\
641                      {s    } | {underline}\n\
642                      {s    } |\n\
643                      {s    } = {message}",
644                     s = spacing,
645                     w = spacing.len(),
646                     p = path,
647                     ls = self.start().0,
648                     le = end.0,
649                     c = self.start().1,
650                     line = self.line,
651                     continued_line = continued_line,
652                     underline = self.underline(),
653                     message = self.message()
654                 )
655             }
656         } else {
657             format!(
658                 "{s}--> {p}{l}:{c}\n\
659                  {s} |\n\
660                  {l} | {line}\n\
661                  {s} | {underline}\n\
662                  {s} |\n\
663                  {s} = {message}",
664                 s = spacing,
665                 p = path,
666                 l = self.start().0,
667                 c = self.start().1,
668                 line = self.line,
669                 underline = self.underline(),
670                 message = self.message()
671             )
672         }
673     }
674 
675     #[cfg(feature = "miette-error")]
676     /// Turns an error into a [miette](crates.io/miette) Diagnostic.
into_miette(self) -> impl ::miette::Diagnostic677     pub fn into_miette(self) -> impl ::miette::Diagnostic {
678         miette_adapter::MietteAdapter(self)
679     }
680 }
681 
682 impl<R: RuleType> ErrorVariant<R> {
683     ///
684     /// Returns the error message for [`ErrorVariant`]
685     ///
686     /// If [`ErrorVariant`] is [`CustomError`], it returns a
687     /// [`Cow::Borrowed`] reference to [`message`]. If [`ErrorVariant`] is [`ParsingError`], a
688     /// [`Cow::Owned`] containing "expected [ErrorVariant::ParsingError::positives] [ErrorVariant::ParsingError::negatives]" is returned.
689     ///
690     /// [`ErrorVariant`]: enum.ErrorVariant.html
691     /// [`CustomError`]: enum.ErrorVariant.html#variant.CustomError
692     /// [`ParsingError`]: enum.ErrorVariant.html#variant.ParsingError
693     /// [`Cow::Owned`]: https://doc.rust-lang.org/std/borrow/enum.Cow.html#variant.Owned
694     /// [`Cow::Borrowed`]: https://doc.rust-lang.org/std/borrow/enum.Cow.html#variant.Borrowed
695     /// [`message`]: enum.ErrorVariant.html#variant.CustomError.field.message
696     /// # Examples
697     ///
698     /// ```
699     /// # use pest::error::ErrorVariant;
700     /// let variant = ErrorVariant::<()>::CustomError {
701     ///     message: String::from("unexpected error")
702     /// };
703     ///
704     /// println!("{}", variant.message());
message(&self) -> Cow<'_, str>705     pub fn message(&self) -> Cow<'_, str> {
706         match self {
707             ErrorVariant::ParsingError {
708                 ref positives,
709                 ref negatives,
710             } => Cow::Owned(Error::parsing_error_message(positives, negatives, |r| {
711                 format!("{:?}", r)
712             })),
713             ErrorVariant::CustomError { ref message } => Cow::Borrowed(message),
714         }
715     }
716 }
717 
718 impl<R: RuleType> fmt::Display for Error<R> {
fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result719     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
720         write!(f, "{}", self.format())
721     }
722 }
723 
724 impl<R: RuleType> fmt::Display for ErrorVariant<R> {
fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result725     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
726         match self {
727             ErrorVariant::ParsingError { .. } => write!(f, "parsing error: {}", self.message()),
728             ErrorVariant::CustomError { .. } => write!(f, "{}", self.message()),
729         }
730     }
731 }
732 
visualize_whitespace(input: &str) -> String733 fn visualize_whitespace(input: &str) -> String {
734     input.to_owned().replace('\r', "␍").replace('\n', "␊")
735 }
736 
737 #[cfg(feature = "miette-error")]
738 mod miette_adapter {
739     use alloc::string::ToString;
740     use std::boxed::Box;
741 
742     use crate::error::LineColLocation;
743 
744     use super::{Error, RuleType};
745 
746     use miette::{Diagnostic, LabeledSpan, SourceCode};
747 
748     #[derive(thiserror::Error, Debug)]
749     #[error("Failure to parse at {:?}", self.0.line_col)]
750     pub(crate) struct MietteAdapter<R: RuleType>(pub(crate) Error<R>);
751 
752     impl<R: RuleType> Diagnostic for MietteAdapter<R> {
source_code(&self) -> Option<&dyn SourceCode>753         fn source_code(&self) -> Option<&dyn SourceCode> {
754             Some(&self.0.line)
755         }
756 
labels(&self) -> Option<Box<dyn Iterator<Item = LabeledSpan>>>757         fn labels(&self) -> Option<Box<dyn Iterator<Item = LabeledSpan>>> {
758             let message = self.0.variant.message().to_string();
759 
760             let (offset, length) = match self.0.line_col {
761                 LineColLocation::Pos((_, c)) => (c - 1, 1),
762                 LineColLocation::Span((_, start_c), (_, end_c)) => {
763                     (start_c - 1, end_c - start_c + 1)
764                 }
765             };
766 
767             let span = LabeledSpan::new(Some(message), offset, length);
768 
769             Some(Box::new(std::iter::once(span)))
770         }
771 
help<'a>(&'a self) -> Option<Box<dyn core::fmt::Display + 'a>>772         fn help<'a>(&'a self) -> Option<Box<dyn core::fmt::Display + 'a>> {
773             Some(Box::new(self.0.message()))
774         }
775     }
776 }
777 
778 #[cfg(test)]
779 mod tests {
780     use super::*;
781     use alloc::vec;
782 
783     #[test]
display_parsing_error_mixed()784     fn display_parsing_error_mixed() {
785         let input = "ab\ncd\nef";
786         let pos = Position::new(input, 4).unwrap();
787         let error: Error<u32> = Error::new_from_pos(
788             ErrorVariant::ParsingError {
789                 positives: vec![1, 2, 3],
790                 negatives: vec![4, 5, 6],
791             },
792             pos,
793         );
794 
795         assert_eq!(
796             format!("{}", error),
797             [
798                 " --> 2:2",
799                 "  |",
800                 "2 | cd",
801                 "  |  ^---",
802                 "  |",
803                 "  = unexpected 4, 5, or 6; expected 1, 2, or 3"
804             ]
805             .join("\n")
806         );
807     }
808 
809     #[test]
display_parsing_error_positives()810     fn display_parsing_error_positives() {
811         let input = "ab\ncd\nef";
812         let pos = Position::new(input, 4).unwrap();
813         let error: Error<u32> = Error::new_from_pos(
814             ErrorVariant::ParsingError {
815                 positives: vec![1, 2],
816                 negatives: vec![],
817             },
818             pos,
819         );
820 
821         assert_eq!(
822             format!("{}", error),
823             [
824                 " --> 2:2",
825                 "  |",
826                 "2 | cd",
827                 "  |  ^---",
828                 "  |",
829                 "  = expected 1 or 2"
830             ]
831             .join("\n")
832         );
833     }
834 
835     #[test]
display_parsing_error_negatives()836     fn display_parsing_error_negatives() {
837         let input = "ab\ncd\nef";
838         let pos = Position::new(input, 4).unwrap();
839         let error: Error<u32> = Error::new_from_pos(
840             ErrorVariant::ParsingError {
841                 positives: vec![],
842                 negatives: vec![4, 5, 6],
843             },
844             pos,
845         );
846 
847         assert_eq!(
848             format!("{}", error),
849             [
850                 " --> 2:2",
851                 "  |",
852                 "2 | cd",
853                 "  |  ^---",
854                 "  |",
855                 "  = unexpected 4, 5, or 6"
856             ]
857             .join("\n")
858         );
859     }
860 
861     #[test]
display_parsing_error_unknown()862     fn display_parsing_error_unknown() {
863         let input = "ab\ncd\nef";
864         let pos = Position::new(input, 4).unwrap();
865         let error: Error<u32> = Error::new_from_pos(
866             ErrorVariant::ParsingError {
867                 positives: vec![],
868                 negatives: vec![],
869             },
870             pos,
871         );
872 
873         assert_eq!(
874             format!("{}", error),
875             [
876                 " --> 2:2",
877                 "  |",
878                 "2 | cd",
879                 "  |  ^---",
880                 "  |",
881                 "  = unknown parsing error"
882             ]
883             .join("\n")
884         );
885     }
886 
887     #[test]
display_custom_pos()888     fn display_custom_pos() {
889         let input = "ab\ncd\nef";
890         let pos = Position::new(input, 4).unwrap();
891         let error: Error<u32> = Error::new_from_pos(
892             ErrorVariant::CustomError {
893                 message: "error: big one".to_owned(),
894             },
895             pos,
896         );
897 
898         assert_eq!(
899             format!("{}", error),
900             [
901                 " --> 2:2",
902                 "  |",
903                 "2 | cd",
904                 "  |  ^---",
905                 "  |",
906                 "  = error: big one"
907             ]
908             .join("\n")
909         );
910     }
911 
912     #[test]
display_custom_span_two_lines()913     fn display_custom_span_two_lines() {
914         let input = "ab\ncd\nefgh";
915         let start = Position::new(input, 4).unwrap();
916         let end = Position::new(input, 9).unwrap();
917         let error: Error<u32> = Error::new_from_span(
918             ErrorVariant::CustomError {
919                 message: "error: big one".to_owned(),
920             },
921             start.span(&end),
922         );
923 
924         assert_eq!(
925             format!("{}", error),
926             [
927                 " --> 2:2",
928                 "  |",
929                 "2 | cd",
930                 "3 | efgh",
931                 "  |  ^^",
932                 "  |",
933                 "  = error: big one"
934             ]
935             .join("\n")
936         );
937     }
938 
939     #[test]
display_custom_span_three_lines()940     fn display_custom_span_three_lines() {
941         let input = "ab\ncd\nefgh";
942         let start = Position::new(input, 1).unwrap();
943         let end = Position::new(input, 9).unwrap();
944         let error: Error<u32> = Error::new_from_span(
945             ErrorVariant::CustomError {
946                 message: "error: big one".to_owned(),
947             },
948             start.span(&end),
949         );
950 
951         assert_eq!(
952             format!("{}", error),
953             [
954                 " --> 1:2",
955                 "  |",
956                 "1 | ab",
957                 "  | ...",
958                 "3 | efgh",
959                 "  |  ^^",
960                 "  |",
961                 "  = error: big one"
962             ]
963             .join("\n")
964         );
965     }
966 
967     #[test]
display_custom_span_two_lines_inverted_cols()968     fn display_custom_span_two_lines_inverted_cols() {
969         let input = "abcdef\ngh";
970         let start = Position::new(input, 5).unwrap();
971         let end = Position::new(input, 8).unwrap();
972         let error: Error<u32> = Error::new_from_span(
973             ErrorVariant::CustomError {
974                 message: "error: big one".to_owned(),
975             },
976             start.span(&end),
977         );
978 
979         assert_eq!(
980             format!("{}", error),
981             [
982                 " --> 1:6",
983                 "  |",
984                 "1 | abcdef",
985                 "2 | gh",
986                 "  | ^----^",
987                 "  |",
988                 "  = error: big one"
989             ]
990             .join("\n")
991         );
992     }
993 
994     #[test]
display_custom_span_end_after_newline()995     fn display_custom_span_end_after_newline() {
996         let input = "abcdef\n";
997         let start = Position::new(input, 0).unwrap();
998         let end = Position::new(input, 7).unwrap();
999         assert!(start.at_start());
1000         assert!(end.at_end());
1001 
1002         let error: Error<u32> = Error::new_from_span(
1003             ErrorVariant::CustomError {
1004                 message: "error: big one".to_owned(),
1005             },
1006             start.span(&end),
1007         );
1008 
1009         assert_eq!(
1010             format!("{}", error),
1011             [
1012                 " --> 1:1",
1013                 "  |",
1014                 "1 | abcdef␊",
1015                 "  | ^-----^",
1016                 "  |",
1017                 "  = error: big one"
1018             ]
1019             .join("\n")
1020         );
1021     }
1022 
1023     #[test]
display_custom_span_empty()1024     fn display_custom_span_empty() {
1025         let input = "";
1026         let start = Position::new(input, 0).unwrap();
1027         let end = Position::new(input, 0).unwrap();
1028         assert!(start.at_start());
1029         assert!(end.at_end());
1030 
1031         let error: Error<u32> = Error::new_from_span(
1032             ErrorVariant::CustomError {
1033                 message: "error: empty".to_owned(),
1034             },
1035             start.span(&end),
1036         );
1037 
1038         assert_eq!(
1039             format!("{}", error),
1040             [
1041                 " --> 1:1",
1042                 "  |",
1043                 "1 | ",
1044                 "  | ^",
1045                 "  |",
1046                 "  = error: empty"
1047             ]
1048             .join("\n")
1049         );
1050     }
1051 
1052     #[test]
mapped_parsing_error()1053     fn mapped_parsing_error() {
1054         let input = "ab\ncd\nef";
1055         let pos = Position::new(input, 4).unwrap();
1056         let error: Error<u32> = Error::new_from_pos(
1057             ErrorVariant::ParsingError {
1058                 positives: vec![1, 2, 3],
1059                 negatives: vec![4, 5, 6],
1060             },
1061             pos,
1062         )
1063         .renamed_rules(|n| format!("{}", n + 1));
1064 
1065         assert_eq!(
1066             format!("{}", error),
1067             [
1068                 " --> 2:2",
1069                 "  |",
1070                 "2 | cd",
1071                 "  |  ^---",
1072                 "  |",
1073                 "  = unexpected 5, 6, or 7; expected 2, 3, or 4"
1074             ]
1075             .join("\n")
1076         );
1077     }
1078 
1079     #[test]
error_with_path()1080     fn error_with_path() {
1081         let input = "ab\ncd\nef";
1082         let pos = Position::new(input, 4).unwrap();
1083         let error: Error<u32> = Error::new_from_pos(
1084             ErrorVariant::ParsingError {
1085                 positives: vec![1, 2, 3],
1086                 negatives: vec![4, 5, 6],
1087             },
1088             pos,
1089         )
1090         .with_path("file.rs");
1091 
1092         assert_eq!(
1093             format!("{}", error),
1094             [
1095                 " --> file.rs:2:2",
1096                 "  |",
1097                 "2 | cd",
1098                 "  |  ^---",
1099                 "  |",
1100                 "  = unexpected 4, 5, or 6; expected 1, 2, or 3"
1101             ]
1102             .join("\n")
1103         );
1104     }
1105 
1106     #[test]
underline_with_tabs()1107     fn underline_with_tabs() {
1108         let input = "a\txbc";
1109         let pos = Position::new(input, 2).unwrap();
1110         let error: Error<u32> = Error::new_from_pos(
1111             ErrorVariant::ParsingError {
1112                 positives: vec![1, 2, 3],
1113                 negatives: vec![4, 5, 6],
1114             },
1115             pos,
1116         )
1117         .with_path("file.rs");
1118 
1119         assert_eq!(
1120             format!("{}", error),
1121             [
1122                 " --> file.rs:1:3",
1123                 "  |",
1124                 "1 | a	xbc",
1125                 "  |  	^---",
1126                 "  |",
1127                 "  = unexpected 4, 5, or 6; expected 1, 2, or 3"
1128             ]
1129             .join("\n")
1130         );
1131     }
1132 
1133     #[test]
pos_to_lcl_conversion()1134     fn pos_to_lcl_conversion() {
1135         let input = "input";
1136 
1137         let pos = Position::new(input, 2).unwrap();
1138 
1139         assert_eq!(LineColLocation::Pos(pos.line_col()), pos.into());
1140     }
1141 
1142     #[test]
span_to_lcl_conversion()1143     fn span_to_lcl_conversion() {
1144         let input = "input";
1145 
1146         let span = Span::new(input, 2, 4).unwrap();
1147         let (start, end) = span.split();
1148 
1149         assert_eq!(
1150             LineColLocation::Span(start.line_col(), end.line_col()),
1151             span.into()
1152         );
1153     }
1154 
1155     #[cfg(feature = "miette-error")]
1156     #[test]
miette_error()1157     fn miette_error() {
1158         let input = "abc\ndef";
1159         let pos = Position::new(input, 4).unwrap();
1160         let error: Error<u32> = Error::new_from_pos(
1161             ErrorVariant::ParsingError {
1162                 positives: vec![1, 2, 3],
1163                 negatives: vec![4, 5, 6],
1164             },
1165             pos,
1166         );
1167 
1168         let miette_error = miette::Error::new(error.into_miette());
1169 
1170         assert_eq!(
1171             format!("{:?}", miette_error),
1172             [
1173                 "",
1174                 "  \u{1b}[31m×\u{1b}[0m Failure to parse at Pos((2, 1))",
1175                 "   ╭────",
1176                 " \u{1b}[2m1\u{1b}[0m │ def",
1177                 "   · \u{1b}[35;1m┬\u{1b}[0m",
1178                 "   · \u{1b}[35;1m╰── \u{1b}[35;1munexpected 4, 5, or 6; expected 1, 2, or 3\u{1b}[0m\u{1b}[0m",
1179                 "   ╰────",
1180                 "\u{1b}[36m  help: \u{1b}[0munexpected 4, 5, or 6; expected 1, 2, or 3\n"
1181             ]
1182             .join("\n")
1183         );
1184     }
1185 }
1186