fuel_pest/
error.rs

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