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