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