1use std::fmt::Debug;
2
3#[derive(Clone, Debug, PartialEq)]
6pub struct GreedyError<I, E> {
7 pub errors: Vec<(I, GreedyErrorKind<E>)>,
10}
11
12#[derive(Clone, Debug, PartialEq)]
13pub enum GreedyErrorKind<E> {
15 Context(&'static str),
17 Char(char),
19 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
73pub 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
113pub 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 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 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}