nom_language/
error.rs

1use std::fmt;
2
3use nom::{
4  error::{ContextError, ErrorKind, FromExternalError, ParseError},
5  ErrorConvert,
6};
7
8/// This error type accumulates errors and their position when backtracking
9/// through a parse tree. With some post processing,
10/// it can be used to display user friendly error messages
11#[derive(Clone, Debug, Eq, PartialEq)]
12pub struct VerboseError<I> {
13  /// List of errors accumulated by `VerboseError`, containing the affected
14  /// part of input data, and some context
15  pub errors: Vec<(I, VerboseErrorKind)>,
16}
17
18#[derive(Clone, Debug, Eq, PartialEq)]
19/// Error context for `VerboseError`
20pub enum VerboseErrorKind {
21  /// Static string added by the `context` function
22  Context(&'static str),
23  /// Indicates which character was expected by the `char` function
24  Char(char),
25  /// Error kind given by various nom parsers
26  Nom(ErrorKind),
27}
28
29impl<I> ParseError<I> for VerboseError<I> {
30  fn from_error_kind(input: I, kind: ErrorKind) -> Self {
31    VerboseError {
32      errors: vec![(input, VerboseErrorKind::Nom(kind))],
33    }
34  }
35
36  fn append(input: I, kind: ErrorKind, mut other: Self) -> Self {
37    other.errors.push((input, VerboseErrorKind::Nom(kind)));
38    other
39  }
40
41  fn from_char(input: I, c: char) -> Self {
42    VerboseError {
43      errors: vec![(input, VerboseErrorKind::Char(c))],
44    }
45  }
46}
47
48impl<I> ContextError<I> for VerboseError<I> {
49  fn add_context(input: I, ctx: &'static str, mut other: Self) -> Self {
50    other.errors.push((input, VerboseErrorKind::Context(ctx)));
51    other
52  }
53}
54
55impl<I, E> FromExternalError<I, E> for VerboseError<I> {
56  /// Create a new error from an input position and an external error
57  fn from_external_error(input: I, kind: ErrorKind, _e: E) -> Self {
58    Self::from_error_kind(input, kind)
59  }
60}
61
62impl<I: fmt::Display> fmt::Display for VerboseError<I> {
63  fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
64    writeln!(f, "Parse error:")?;
65    for (input, error) in &self.errors {
66      match error {
67        VerboseErrorKind::Nom(e) => writeln!(f, "{:?} at: {}", e, input)?,
68        VerboseErrorKind::Char(c) => writeln!(f, "expected '{}' at: {}", c, input)?,
69        VerboseErrorKind::Context(s) => writeln!(f, "in section '{}', at: {}", s, input)?,
70      }
71    }
72
73    Ok(())
74  }
75}
76
77impl<I: fmt::Debug + fmt::Display> std::error::Error for VerboseError<I> {}
78
79impl From<VerboseError<&[u8]>> for VerboseError<Vec<u8>> {
80  fn from(value: VerboseError<&[u8]>) -> Self {
81    VerboseError {
82      errors: value
83        .errors
84        .into_iter()
85        .map(|(i, e)| (i.to_owned(), e))
86        .collect(),
87    }
88  }
89}
90
91impl From<VerboseError<&str>> for VerboseError<String> {
92  fn from(value: VerboseError<&str>) -> Self {
93    VerboseError {
94      errors: value
95        .errors
96        .into_iter()
97        .map(|(i, e)| (i.to_owned(), e))
98        .collect(),
99    }
100  }
101}
102
103impl<I> ErrorConvert<VerboseError<I>> for VerboseError<(I, usize)> {
104  fn convert(self) -> VerboseError<I> {
105    VerboseError {
106      errors: self.errors.into_iter().map(|(i, e)| (i.0, e)).collect(),
107    }
108  }
109}
110
111impl<I> ErrorConvert<VerboseError<(I, usize)>> for VerboseError<I> {
112  fn convert(self) -> VerboseError<(I, usize)> {
113    VerboseError {
114      errors: self.errors.into_iter().map(|(i, e)| ((i, 0), e)).collect(),
115    }
116  }
117}
118
119/// Transforms a `VerboseError` into a trace with input position information
120///
121/// The errors contain references to input data that must come from `input`,
122/// because nom calculates byte offsets between them
123pub fn convert_error<I: core::ops::Deref<Target = str>>(input: I, e: VerboseError<I>) -> String {
124  use nom::Offset;
125  use std::fmt::Write;
126
127  let mut result = String::new();
128
129  for (i, (substring, kind)) in e.errors.iter().enumerate() {
130    let offset = input.offset(substring);
131
132    if input.is_empty() {
133      match kind {
134        VerboseErrorKind::Char(c) => {
135          write!(&mut result, "{}: expected '{}', got empty input\n\n", i, c)
136        }
137        VerboseErrorKind::Context(s) => write!(&mut result, "{}: in {}, got empty input\n\n", i, s),
138        VerboseErrorKind::Nom(e) => write!(&mut result, "{}: in {:?}, got empty input\n\n", i, e),
139      }
140    } else {
141      let prefix = &input.as_bytes()[..offset];
142
143      // Count the number of newlines in the first `offset` bytes of input
144      let line_number = prefix.iter().filter(|&&b| b == b'\n').count() + 1;
145
146      // Find the line that includes the subslice:
147      // Find the *last* newline before the substring starts
148      let line_begin = prefix
149        .iter()
150        .rev()
151        .position(|&b| b == b'\n')
152        .map(|pos| offset - pos)
153        .unwrap_or(0);
154
155      // Find the full line after that newline
156      let line = input[line_begin..]
157        .lines()
158        .next()
159        .unwrap_or(&input[line_begin..])
160        .trim_end();
161
162      // The (1-indexed) column number is the offset of our substring into that line
163      let column_number = line.offset(substring) + 1;
164
165      match kind {
166        VerboseErrorKind::Char(c) => {
167          if let Some(actual) = substring.chars().next() {
168            write!(
169              &mut result,
170              "{i}: at line {line_number}:\n\
171               {line}\n\
172               {caret:>column$}\n\
173               expected '{expected}', found {actual}\n\n",
174              i = i,
175              line_number = line_number,
176              line = line,
177              caret = '^',
178              column = column_number,
179              expected = c,
180              actual = actual,
181            )
182          } else {
183            write!(
184              &mut result,
185              "{i}: at line {line_number}:\n\
186               {line}\n\
187               {caret:>column$}\n\
188               expected '{expected}', got end of input\n\n",
189              i = i,
190              line_number = line_number,
191              line = line,
192              caret = '^',
193              column = column_number,
194              expected = c,
195            )
196          }
197        }
198        VerboseErrorKind::Context(s) => write!(
199          &mut result,
200          "{i}: at line {line_number}, in {context}:\n\
201             {line}\n\
202             {caret:>column$}\n\n",
203          i = i,
204          line_number = line_number,
205          context = s,
206          line = line,
207          caret = '^',
208          column = column_number,
209        ),
210        VerboseErrorKind::Nom(e) => write!(
211          &mut result,
212          "{i}: at line {line_number}, in {nom_err:?}:\n\
213             {line}\n\
214             {caret:>column$}\n\n",
215          i = i,
216          line_number = line_number,
217          nom_err = e,
218          line = line,
219          caret = '^',
220          column = column_number,
221        ),
222      }
223    }
224    // Because `write!` to a `String` is infallible, this `unwrap` is fine.
225    .unwrap();
226  }
227
228  result
229}
230
231#[test]
232fn convert_error_panic() {
233  use nom::character::complete::char;
234  use nom::IResult;
235
236  let input = "";
237
238  let _result: IResult<_, _, VerboseError<&str>> = char('x')(input);
239}
240
241#[test]
242fn issue_1027_convert_error_panic_nonempty() {
243  use nom::character::complete::char;
244  use nom::sequence::pair;
245  use nom::Err;
246  use nom::IResult;
247  use nom::Parser;
248
249  let input = "a";
250
251  let result: IResult<_, _, VerboseError<&str>> = pair(char('a'), char('b')).parse(input);
252  let err = match result.unwrap_err() {
253    Err::Error(e) => e,
254    _ => unreachable!(),
255  };
256
257  let msg = convert_error(input, err);
258  assert_eq!(
259    msg,
260    "0: at line 1:\na\n ^\nexpected \'b\', got end of input\n\n"
261  );
262}