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