1use core::fmt::{Display, Formatter, Result};
2use std::{collections::VecDeque, error::Error, io};
3
4use nom::{
5 error::{ContextError, FromExternalError, ParseError},
6 IResult,
7};
8
9use crate::input::Input;
10
11use super::util::until_next_unindented;
12
13pub type ParserResult<'a, O> = IResult<Input<'a>, O, ErrorTree<'a>>;
14
15#[derive(Debug, Clone, PartialEq)]
16pub struct LexerError {
17 pub kind: LexerErrorType,
18}
19
20impl LexerError {
21 pub fn contextualize(&self, input: &str) -> String {
22 match &self.kind {
23 LexerErrorType::MatchingError(report_data) => {
24 let line = report_data.line;
25 let context = until_next_unindented(
26 &input[report_data.context_start_offset..],
27 report_data.offset - report_data.context_start_offset + 1,
28 300,
29 );
30 let pdu_lines = context.match_indices('\n').count();
31 let start_line = report_data.context_start_line;
32 let end_line = report_data.context_start_line + pdu_lines;
33 let column = report_data.column;
34 let n = end_line.checked_ilog10().unwrap_or(0) as usize;
35 let digits = n + 1;
36 let spacer = "─".repeat(n);
37 let indentation = " ".repeat(n);
38 let pdu = context
39 .lines()
40 .enumerate()
41 .fold(String::new(), |acc, (i, l)| {
42 if l.trim().is_empty() {
43 return acc;
44 }
45 let line_no = format!("{:0>digits$}", (start_line + i).to_string());
46 let mut ln = format!("{acc}\n {line_no} │ {}", l.trim_end());
47 if i + start_line == line {
48 ln += " ◀▪▪▪▪▪▪▪▪▪▪ FAILED AT THIS LINE";
49 }
50 ln
51 });
52
53 format!(
54 r#"
55Error matching ASN syntax at while parsing:
56{indentation} ╭─[line {line}, column {column}]
57{indentation} │
58{indentation} │ {pdu}
59{indentation} │
60{spacer}───╯
61 "#
62 )
63 }
64 _ => format!("{self}"),
65 }
66 }
67}
68
69impl<'a> From<nom::Err<ErrorTree<'a>>> for LexerError {
70 fn from(value: nom::Err<ErrorTree<'a>>) -> Self {
71 match value {
72 nom::Err::Incomplete(needed) => Self {
73 kind: LexerErrorType::NotEnoughData(match needed {
74 nom::Needed::Unknown => None,
75 nom::Needed::Size(i) => Some(i.get()),
76 }),
77 },
78 nom::Err::Error(e) | nom::Err::Failure(e) => Self {
79 kind: LexerErrorType::MatchingError(e.into()),
80 },
81 }
82 }
83}
84
85impl From<io::Error> for LexerError {
86 fn from(value: io::Error) -> Self {
87 LexerError {
88 kind: LexerErrorType::IO(value.to_string()),
89 }
90 }
91}
92
93#[derive(Debug, Clone, PartialEq)]
94pub enum LexerErrorType {
95 NotEnoughData(Option<usize>),
96 MatchingError(ReportData),
97 IO(String),
98}
99
100impl Error for LexerError {}
101
102impl Display for LexerError {
103 fn fmt(&self, f: &mut Formatter) -> Result {
104 match &self.kind {
105 LexerErrorType::NotEnoughData(needed) => write!(
106 f,
107 "Unexpected end of input.{}",
108 needed.map_or(String::new(), |i| format!(
109 " Need another {i} characters of input."
110 ))
111 ),
112 LexerErrorType::MatchingError(report_data) => write!(
113 f,
114 "Error matching ASN syntax at while parsing line {} column {}.",
115 report_data.line, report_data.column
116 ),
117 LexerErrorType::IO(reason) => write!(f, "Failed to read ASN.1 source. {reason}"),
118 }
119 }
120}
121
122#[derive(Debug, Clone, PartialEq)]
123pub struct ReportData {
124 pub context_start_line: usize,
125 pub context_start_offset: usize,
126 pub line: usize,
127 pub offset: usize,
128 pub column: usize,
129 pub reason: String,
130 pub unexpected_eof: bool,
131}
132
133impl From<ErrorTree<'_>> for ReportData {
134 fn from(value: ErrorTree<'_>) -> Self {
135 match value {
136 ErrorTree::Leaf { input, kind } => Self {
137 context_start_line: input.context_start_line(),
138 context_start_offset: input.context_start_offset(),
139 line: input.line(),
140 offset: input.offset(),
141 column: input.column(),
142 unexpected_eof: kind == ErrorKind::Nom(nom::error::ErrorKind::Eof),
143 reason: match kind {
144 ErrorKind::Nom(e) => format!("Failed to parse next input. Code: {e:?}"),
145 ErrorKind::External(e) => e,
146 },
147 },
148 ErrorTree::Branch { tip, .. } => Self::from(*tip),
149 ErrorTree::Fork { mut branches } => Self::from(
150 branches
151 .pop_front()
152 .expect("Error Tree branch not to be empty."),
153 ),
154 }
155 }
156}
157
158#[derive(Debug, Clone, PartialEq)]
159pub struct MiscError(pub &'static str);
160
161impl Error for MiscError {}
162
163impl Display for MiscError {
164 fn fmt(&self, f: &mut Formatter<'_>) -> Result {
165 Display::fmt(&self.0, f)
166 }
167}
168
169#[derive(Debug, Clone, PartialEq)]
173pub enum ErrorTree<'a> {
174 Leaf {
175 input: Input<'a>,
176 kind: ErrorKind,
177 },
178 Branch {
179 tip: Box<Self>,
180 links: Vec<ErrorLink<'a>>,
181 },
182 Fork {
183 branches: VecDeque<Self>,
184 },
185}
186
187#[derive(Debug, Clone, PartialEq)]
188pub enum ErrorKind {
189 Nom(nom::error::ErrorKind),
190 External(String),
191}
192
193#[derive(Debug, Clone, PartialEq)]
194pub struct ErrorLink<'a> {
195 input: Input<'a>,
196 context: Context,
197}
198
199#[derive(Debug, Clone, PartialEq)]
200pub enum Context {
201 Name(&'static str),
202 ErrorKind(ErrorKind),
203}
204
205#[derive(Debug, Clone, PartialEq)]
206pub struct ErrorLeaf<'a> {
207 input: Input<'a>,
208 kind: ErrorKind,
209}
210
211impl<'a> ErrorTree<'a> {
212 pub fn into_input(self) -> Input<'a> {
213 match self {
214 ErrorTree::Leaf { input, .. } => input,
215 ErrorTree::Branch { mut links, .. } => {
216 links
217 .pop()
218 .expect("error tree branch to have at least one link")
219 .input
220 }
221 ErrorTree::Fork { mut branches } => branches
222 .pop_back()
223 .expect("error tree branch to have at least one link")
224 .into_input(),
225 }
226 }
227
228 pub fn is_eof_error(&self) -> bool {
229 match self {
230 ErrorTree::Leaf { kind, .. } => kind == &ErrorKind::Nom(nom::error::ErrorKind::Eof),
231 ErrorTree::Branch { tip, .. } => tip.is_eof_error(),
232 ErrorTree::Fork { branches } => branches.back().map_or(false, |b| b.is_eof_error()),
233 }
234 }
235}
236
237impl<'a> ParseError<Input<'a>> for ErrorTree<'a> {
238 fn from_error_kind(input: Input<'a>, kind: nom::error::ErrorKind) -> Self {
239 ErrorTree::Leaf {
240 input,
241 kind: ErrorKind::Nom(kind),
242 }
243 }
244
245 fn append(input: Input<'a>, kind: nom::error::ErrorKind, other: Self) -> Self {
246 match other {
247 fork @ ErrorTree::Fork { .. } if kind == nom::error::ErrorKind::Alt => fork,
248 ErrorTree::Branch { mut links, tip } => {
249 links.push(ErrorLink {
250 input,
251 context: Context::ErrorKind(ErrorKind::Nom(kind)),
252 });
253 ErrorTree::Branch { tip, links }
254 }
255 tip => ErrorTree::Branch {
256 tip: Box::new(tip),
257 links: vec![ErrorLink {
258 input,
259 context: Context::ErrorKind(ErrorKind::Nom(kind)),
260 }],
261 },
262 }
263 }
264
265 fn or(self, other: Self) -> Self {
266 let branches = match (self, other) {
267 (ErrorTree::Fork { branches: mut b_1 }, ErrorTree::Fork { branches: mut b_2 }) => {
268 match b_1.capacity() >= b_2.capacity() {
269 true => {
270 b_1.extend(b_2);
271 b_1
272 }
273 false => {
274 b_2.extend(b_1);
275 b_2
276 }
277 }
278 }
279 (branch, ErrorTree::Fork { mut branches })
280 | (ErrorTree::Fork { mut branches }, branch) => {
281 branches.push_back(branch);
282 branches
283 }
284 (branch_1, branch_2) => vec![branch_1, branch_2].into(),
285 };
286
287 ErrorTree::Fork { branches }
288 }
289}
290
291impl<'a> ContextError<Input<'a>> for ErrorTree<'a> {
292 fn add_context(input: Input<'a>, context: &'static str, other: Self) -> Self {
293 match other {
294 ErrorTree::Branch { tip, mut links } => {
295 links.push(ErrorLink {
296 input,
297 context: Context::Name(context),
298 });
299 ErrorTree::Branch { tip, links }
300 }
301 tip => ErrorTree::Branch {
302 tip: Box::new(tip),
303 links: vec![ErrorLink {
304 input,
305 context: Context::Name(context),
306 }],
307 },
308 }
309 }
310}
311
312impl<'a, E: Error> FromExternalError<Input<'a>, E> for ErrorTree<'a> {
313 fn from_external_error(input: Input<'a>, _: nom::error::ErrorKind, e: E) -> Self {
314 Self::Leaf {
315 input,
316 kind: ErrorKind::External(e.to_string()),
317 }
318 }
319}
320
321#[cfg(test)]
322mod tests {
323 use super::*;
324
325 #[test]
326 fn contextualizes_error() {
327 let input = r#"GAP-Test DEFINITIONS AUTOMATIC TAGS ::= BEGIN
328c-CtxTypeSystemNull ItsAidCtxRef ::= { itsaid content:0, ctx c-ctxRefNull }
329
330ItsAidCtxRef ::= SEQUENCE {
331 itsaid ITSaid,
332 ctx CtxRef
333 }
334
335CtxRef ::INTEGER(0..255)
336c-ctxRefNull CtxRef ::= 0
337
338 END"#;
339 let error = LexerError {
340 kind: LexerErrorType::MatchingError(ReportData {
341 context_start_line: 4,
342 context_start_offset: 123,
343 line: 6,
344 column: 6,
345 offset: 172,
346 reason: "Test".into(),
347 unexpected_eof: false,
348 }),
349 };
350 assert_eq!(
351 error.contextualize(input),
352 r#"
353Error matching ASN syntax at while parsing:
354 ╭─[line 6, column 6]
355 │
356 │
357 5 │ ItsAidCtxRef ::= SEQUENCE {
358 6 │ itsaid ITSaid, ◀▪▪▪▪▪▪▪▪▪▪ FAILED AT THIS LINE
359 7 │ ctx CtxRef
360 8 │ }
361 │
362───╯
363 "#
364 )
365 }
366}