nom_greedyerror/
lib.rs

1use std::fmt::Debug;
2
3/// This error type accumulates errors and their position when backtracking
4/// through a parse tree. This take a deepest error at `alt` combinator.
5#[derive(Clone, Debug, PartialEq)]
6pub struct GreedyError<I, E> {
7    /// list of errors accumulated by `GreedyError`, containing the affected
8    /// part of input data, and some context
9    pub errors: Vec<(I, GreedyErrorKind<E>)>,
10}
11
12#[derive(Clone, Debug, PartialEq)]
13/// error context for `GreedyError`
14pub enum GreedyErrorKind<E> {
15    /// static string added by the `context` function
16    Context(&'static str),
17    /// indicates which character was expected by the `char` function
18    Char(char),
19    /// error kind given by various nom parsers
20    Nom(E),
21}
22
23impl<I> nom7::error::ParseError<I> for GreedyError<I, nom7::error::ErrorKind>
24where
25    I: Position,
26{
27    fn from_error_kind(input: I, kind: nom7::error::ErrorKind) -> Self {
28        GreedyError {
29            errors: vec![(input, GreedyErrorKind::Nom(kind))],
30        }
31    }
32
33    fn append(input: I, kind: nom7::error::ErrorKind, mut other: Self) -> Self {
34        other.errors.push((input, GreedyErrorKind::Nom(kind)));
35        other
36    }
37
38    fn from_char(input: I, c: char) -> Self {
39        GreedyError {
40            errors: vec![(input, GreedyErrorKind::Char(c))],
41        }
42    }
43
44    fn or(self, other: Self) -> Self {
45        let pos_self = if let Some(x) = self.errors.first() {
46            x.0.position()
47        } else {
48            0
49        };
50        let pos_other = if let Some(x) = other.errors.first() {
51            x.0.position()
52        } else {
53            0
54        };
55        if pos_other > pos_self {
56            other
57        } else {
58            self
59        }
60    }
61}
62
63impl<I> nom7::error::ContextError<I> for GreedyError<I, nom7::error::ErrorKind>
64where
65    I: Position,
66{
67    fn add_context(input: I, ctx: &'static str, mut other: Self) -> Self {
68        other.errors.push((input, GreedyErrorKind::Context(ctx)));
69        other
70    }
71}
72
73/// get the deepest error position
74pub fn error_position<T: Position, E>(e: &GreedyError<T, E>) -> Option<usize> {
75    e.errors.first().map(|x| x.0.position())
76}
77
78pub trait Position {
79    fn position(&self) -> usize;
80}
81
82impl<T: nom7::AsBytes, U> Position for nom_locate4::LocatedSpan<T, U> {
83    fn position(&self) -> usize {
84        self.location_offset()
85    }
86}
87
88pub trait AsStr {
89    fn as_str(&self) -> &str;
90}
91
92impl<'a> AsStr for &'a str {
93    #[inline(always)]
94    fn as_str(&self) -> &str {
95        self
96    }
97}
98
99impl AsStr for str {
100    #[inline(always)]
101    fn as_str(&self) -> &str {
102        self
103    }
104}
105
106impl<T: AsStr + nom7::AsBytes, X> AsStr for nom_locate4::LocatedSpan<T, X> {
107    #[inline]
108    fn as_str(&self) -> &str {
109        self.fragment().as_str()
110    }
111}
112
113/// transforms a `GreedyError` into a trace with input position information
114pub fn convert_error<T: AsStr, U: AsStr, V: Debug>(input: T, e: GreedyError<U, V>) -> String {
115    use nom7::Offset;
116    use std::iter::repeat;
117
118    let lines: Vec<_> = input.as_str().lines().map(String::from).collect();
119
120    let mut result = String::new();
121
122    for (i, (substring, kind)) in e.errors.iter().enumerate() {
123        let mut offset = input.as_str().offset(substring.as_str());
124
125        if lines.is_empty() {
126            match kind {
127                GreedyErrorKind::Char(c) => {
128                    result += &format!("{}: expected '{}', got empty input\n\n", i, c);
129                }
130                GreedyErrorKind::Context(s) => {
131                    result += &format!("{}: in {}, got empty input\n\n", i, s);
132                }
133                GreedyErrorKind::Nom(e) => {
134                    result += &format!("{}: in {:?}, got empty input\n\n", i, e);
135                }
136            }
137        } else {
138            let mut line = 0;
139            let mut column = 0;
140
141            for (j, l) in lines.iter().enumerate() {
142                if offset <= l.len() {
143                    line = j;
144                    column = offset;
145                    break;
146                } else {
147                    offset = offset - l.len() - 1;
148                }
149            }
150
151            match kind {
152                GreedyErrorKind::Char(c) => {
153                    result += &format!("{}: at line {}:\n", i, line);
154                    result += &lines[line];
155                    result += "\n";
156
157                    if column > 0 {
158                        result += &repeat(' ').take(column).collect::<String>();
159                    }
160                    result += "^\n";
161                    result += &format!(
162                        "expected '{}', found {}\n\n",
163                        c,
164                        substring.as_str().chars().next().unwrap()
165                    );
166                }
167                GreedyErrorKind::Context(s) => {
168                    result += &format!("{}: at line {}, in {}:\n", i, line, s);
169                    result += &lines[line];
170                    result += "\n";
171                    if column > 0 {
172                        result += &repeat(' ').take(column).collect::<String>();
173                    }
174                    result += "^\n\n";
175                }
176                GreedyErrorKind::Nom(e) => {
177                    result += &format!("{}: at line {}, in {:?}:\n", i, line, e);
178                    result += &lines[line];
179                    result += "\n";
180                    if column > 0 {
181                        result += &repeat(' ').take(column).collect::<String>();
182                    }
183                    result += "^\n\n";
184                }
185            }
186        }
187    }
188
189    result
190}
191
192#[cfg(test)]
193mod tests_nom7 {
194    use super::*;
195    use nom7::branch::alt;
196    use nom7::character::complete::{alpha1, digit1};
197    use nom7::error::{ErrorKind, ParseError, VerboseError};
198    use nom7::sequence::tuple;
199    use nom7::Err;
200    use nom7::IResult;
201    use nom_locate4::LocatedSpan;
202
203    type Span<'a> = LocatedSpan<&'a str>;
204
205    fn parser<'a, E: ParseError<Span<'a>>>(
206        input: Span<'a>,
207    ) -> IResult<Span<'a>, (Span<'a>, Span<'a>, Span<'a>), E> {
208        alt((
209            tuple((alpha1, digit1, alpha1)),
210            tuple((digit1, alpha1, digit1)),
211        ))(input)
212    }
213
214    #[test]
215    fn test_position() {
216        // VerboseError failed at
217        //   abc012:::
218        //   ^
219        let error = parser::<VerboseError<Span>>(Span::new("abc012:::"));
220        match error {
221            Err(Err::Error(e)) => assert_eq!(e.errors.first().map(|x| x.0.position()), Some(0)),
222            _ => (),
223        };
224
225        // GreedyError failed at
226        //   abc012:::
227        //         ^
228        let error = parser::<GreedyError<Span, ErrorKind>>(Span::new("abc012:::"));
229        match error {
230            Err(Err::Error(e)) => assert_eq!(error_position(&e), Some(6)),
231            _ => (),
232        };
233    }
234
235    #[test]
236    fn test_convert_error() {
237        let error = parser::<GreedyError<Span, ErrorKind>>(Span::new("abc012:::"));
238        let msg = r##"0: at line 0, in Alpha:
239abc012:::
240      ^
241
2421: at line 0, in Alt:
243abc012:::
244^
245
246"##;
247        match error {
248            Err(Err::Error(e)) => assert_eq!(convert_error("abc012:::", e), String::from(msg)),
249            _ => (),
250        };
251    }
252}