tartan_parsers/
error.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
use core::cmp::min;
use core::fmt;
use nom::error::{ContextError, ErrorKind, ParseError, VerboseError, VerboseErrorKind};
use nom::{AsBytes, IResult};


/// Trait alias combining the nom error traits [`ParseError`] and [`ContextError`].
#[allow(clippy::module_name_repetitions)]
pub trait GeneralParseError<I>
where
    Self: ParseError<I> + ContextError<I>,
{
}

impl<T, I> GeneralParseError<I> for T where T: ParseError<I> + ContextError<I> {}


/// Shortcut for constructing parser errors
#[allow(clippy::missing_errors_doc)]
pub fn err<I, O, E: ParseError<I>>(i: I, kind: ErrorKind) -> IResult<I, O, E> {
    Err(nom::Err::Error(E::from_error_kind(i, kind)))
}


/// Helper struct that prints human-readable position information when formatted with
/// `Display`.
pub struct Position<'a, I: AsBytes = &'a [u8]> {
    /// The state of the parser at the time we are interested in
    pub state: &'a I,
    /// The complete input we are trying to parse
    pub full_input: &'a [u8],
}

impl<I: AsBytes> fmt::Display for Position<'_, I> {
    fn fmt(&self, out: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
        if self.full_input.is_empty() {
            return writeln!(out, "in empty input");
        }

        // NOTE: nom's Offset implementation can panic. Avoid it.
        let full_input_pos = self.full_input.as_ptr() as usize;
        let state_pos = self.state.as_bytes().as_ptr() as usize;
        let Some(offset) = state_pos.checked_sub(full_input_pos) else {
            return writeln!(out, "at unknown/invalid offset");
        };

        let context_start = offset.saturating_sub(10);
        let context_end = min(offset + 10, self.full_input.len());
        let context_slice = &self.full_input[context_start..context_end];

        write!(out, "at offset {offset} ({offset:#x}):\n ")?;
        for byte in context_slice {
            let ascii_char = match byte {
                b if b.is_ascii_graphic() => *b as char,
                b' ' => ' ',
                _ => '.',
            };
            write!(out, " {ascii_char:2}")?;
        }
        write!(out, "\n ")?;
        for byte in context_slice {
            write!(out, " {byte:02x}")?;
        }
        write!(
            out,
            "\n  {caret:>caret_offset$}\n",
            caret = '^',
            caret_offset = (offset - context_start) * 3 + 1,
        )
    }
}


/// A parser error with position info useful for printing human-readable messages.
#[derive(Debug, Clone)]
#[allow(clippy::module_name_repetitions)]
pub struct ErrorWithPosition<'a, I = &'a [u8]>
where
    I: From<&'a [u8]> + AsBytes,
{
    /// The error returned by the parser
    pub error: VerboseError<I>,
    /// The complete input we were trying to parse
    pub full_input: &'a [u8],
}

impl<'a, I> ErrorWithPosition<'a, I>
where
    I: From<&'a [u8]> + AsBytes,
{
    /// Associate a parse error with its full input, to reference the affected position
    /// in error messages.
    pub fn new(wrapped_error: nom::Err<VerboseError<I>>, full_input: &'a [u8]) -> Self {
        match wrapped_error {
            nom::Err::Error(error) | nom::Err::Failure(error) => {
                ErrorWithPosition { error, full_input }
            }
            nom::Err::Incomplete(_) => {
                let error = VerboseError::from_error_kind(
                    I::from(full_input),
                    ErrorKind::Complete,
                );
                ErrorWithPosition { error, full_input }
            }
        }
    }
}

impl<'a, I> fmt::Display for ErrorWithPosition<'a, I>
where
    I: From<&'a [u8]> + AsBytes,
{
    fn fmt(&self, out: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
        if self.error.errors.is_empty() {
            return writeln!(out, "Unknown parsing error");
        }

        for (state, kind) in &self.error.errors {
            match kind {
                VerboseErrorKind::Context(context) => {
                    write!(out, "In {context}")?;
                }
                VerboseErrorKind::Char(c) => {
                    write!(out, "Expected '{c}'")?;
                }
                VerboseErrorKind::Nom(e) => {
                    write!(out, "Failed {}", e.description())?;
                }
            }
            write!(out, " {}", Position { state, full_input: self.full_input })?;
        }

        Ok(())
    }
}