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 let src_info = if let Some(file_name) = report_data.src_file.as_ref() {
54 format!("Source file: {file_name}:{line}:{column}")
55 } else {
56 format!("line {line}, column {column}")
57 };
58
59 format!(
60 r#"
61Error matching ASN syntax at while parsing:
62{indentation} ╭─[{src_info}]
63{indentation} │
64{indentation} │ {pdu}
65{indentation} │
66{spacer}───╯
67 "#
68 )
69 }
70 _ => format!("{self}"),
71 }
72 }
73}
74
75impl<'a> From<nom::Err<ErrorTree<'a>>> for LexerError {
76 fn from(value: nom::Err<ErrorTree<'a>>) -> Self {
77 match value {
78 nom::Err::Incomplete(needed) => Self {
79 kind: LexerErrorType::NotEnoughData(match needed {
80 nom::Needed::Unknown => None,
81 nom::Needed::Size(i) => Some(i.get()),
82 }),
83 },
84 nom::Err::Error(e) | nom::Err::Failure(e) => Self {
85 kind: LexerErrorType::MatchingError(e.into()),
86 },
87 }
88 }
89}
90
91impl From<io::Error> for LexerError {
92 fn from(value: io::Error) -> Self {
93 LexerError {
94 kind: LexerErrorType::IO(value.to_string()),
95 }
96 }
97}
98
99#[derive(Debug, Clone, PartialEq)]
100pub enum LexerErrorType {
101 NotEnoughData(Option<usize>),
102 MatchingError(ReportData),
103 IO(String),
104}
105
106impl Error for LexerError {}
107
108impl Display for LexerError {
109 fn fmt(&self, f: &mut Formatter) -> Result {
110 match &self.kind {
111 LexerErrorType::NotEnoughData(needed) => write!(
112 f,
113 "Unexpected end of input.{}",
114 needed.map_or(String::new(), |i| format!(
115 " Need another {i} characters of input."
116 ))
117 ),
118 LexerErrorType::MatchingError(report_data) => {
119 let src_info = if let Some(file_name) = report_data.src_file.as_ref() {
120 format!(
121 "source file {file_name}:{}:{}",
122 report_data.line + 1,
123 report_data.column
124 )
125 } else {
126 format!(
127 "line {}, column {}",
128 report_data.line + 1,
129 report_data.column
130 )
131 };
132
133 write!(f, "Error matching ASN syntax at while parsing {src_info}.",)
134 }
135 LexerErrorType::IO(reason) => write!(f, "Failed to read ASN.1 source. {reason}"),
136 }
137 }
138}
139
140#[derive(Debug, Clone, PartialEq)]
141pub struct ReportData {
142 pub src_file: Option<String>,
143 pub context_start_line: usize,
144 pub context_start_offset: usize,
145 pub line: usize,
146 pub offset: usize,
147 pub column: usize,
148 pub reason: String,
149 pub unexpected_eof: bool,
150}
151
152impl From<ErrorTree<'_>> for ReportData {
153 fn from(value: ErrorTree<'_>) -> Self {
154 match value {
155 ErrorTree::Base { input, kind } => Self {
156 src_file: input.src_file(),
157 context_start_line: input.context_start_line(),
158 context_start_offset: input.context_start_offset(),
159 line: input.line(),
160 offset: input.offset(),
161 column: input.column(),
162 unexpected_eof: kind == ErrorKind::Nom(nom::error::ErrorKind::Eof),
163 reason: match kind {
164 ErrorKind::Nom(e) => format!("Failed to parse next input. Code: {e:?}"),
165 ErrorKind::External(e) => e,
166 },
167 },
168 ErrorTree::Stack { base, .. } => Self::from(*base),
169 ErrorTree::Alt(mut alts) => {
170 Self::from(alts.pop_front().expect("ErrorTree::Alt not to be empty."))
171 }
172 }
173 }
174}
175
176#[derive(Debug, Clone, PartialEq)]
177pub struct MiscError(pub &'static str);
178
179impl Error for MiscError {}
180
181impl Display for MiscError {
182 fn fmt(&self, f: &mut Formatter<'_>) -> Result {
183 Display::fmt(&self.0, f)
184 }
185}
186
187#[derive(Debug, Clone, PartialEq)]
191pub enum ErrorTree<'a> {
192 Base {
193 input: Input<'a>,
194 kind: ErrorKind,
195 },
196 Stack {
197 base: Box<Self>,
198 contexts: Vec<StackContext<'a>>,
199 },
200 Alt(VecDeque<Self>),
201}
202
203#[derive(Debug, Clone, PartialEq)]
204pub enum ErrorKind {
205 Nom(nom::error::ErrorKind),
206 External(String),
207}
208
209#[derive(Debug, Clone, PartialEq)]
210pub struct StackContext<'a> {
211 input: Input<'a>,
212 context: Context,
213}
214
215#[derive(Debug, Clone, PartialEq)]
216pub enum Context {
217 Name(&'static str),
218 ErrorKind(ErrorKind),
219}
220
221impl<'a> ErrorTree<'a> {
222 pub fn into_input(self) -> Input<'a> {
223 match self {
224 ErrorTree::Base { input, .. } => input,
225 ErrorTree::Stack { mut contexts, .. } => {
226 contexts
227 .pop()
228 .expect("ErrorTree::Stack to have at least one context")
229 .input
230 }
231 ErrorTree::Alt(mut alts) => alts
232 .pop_back()
233 .expect("ErrorTree:Alt to have at least one alternative")
234 .into_input(),
235 }
236 }
237
238 pub fn is_eof_error(&self) -> bool {
239 match self {
240 ErrorTree::Base { kind, .. } => kind == &ErrorKind::Nom(nom::error::ErrorKind::Eof),
241 ErrorTree::Stack { base, .. } => base.is_eof_error(),
242 ErrorTree::Alt(alts) => alts.back().is_some_and(|b| b.is_eof_error()),
243 }
244 }
245}
246
247impl<'a> ParseError<Input<'a>> for ErrorTree<'a> {
248 fn from_error_kind(input: Input<'a>, kind: nom::error::ErrorKind) -> Self {
249 ErrorTree::Base {
250 input,
251 kind: ErrorKind::Nom(kind),
252 }
253 }
254
255 fn append(input: Input<'a>, kind: nom::error::ErrorKind, other: Self) -> Self {
256 let context = StackContext {
257 input,
258 context: Context::ErrorKind(ErrorKind::Nom(kind)),
259 };
260 match other {
261 alt @ ErrorTree::Alt { .. } if kind == nom::error::ErrorKind::Alt => alt,
262 ErrorTree::Stack { mut contexts, base } => {
263 contexts.push(context);
264 ErrorTree::Stack { base, contexts }
265 }
266 base => ErrorTree::Stack {
267 base: Box::new(base),
268 contexts: vec![context],
269 },
270 }
271 }
272
273 fn or(self, other: Self) -> Self {
274 let alts = match (self, other) {
275 (ErrorTree::Alt(mut alt1), ErrorTree::Alt(mut alt2)) => {
276 match alt1.capacity() >= alt2.capacity() {
277 true => {
278 alt1.extend(alt2);
279 alt1
280 }
281 false => {
282 alt2.extend(alt1);
283 alt2
284 }
285 }
286 }
287 (alt, ErrorTree::Alt(mut alts)) | (ErrorTree::Alt(mut alts), alt) => {
288 alts.push_back(alt);
289 alts
290 }
291 (alt1, alt2) => vec![alt1, alt2].into(),
292 };
293
294 ErrorTree::Alt(alts)
295 }
296}
297
298impl<'a> ContextError<Input<'a>> for ErrorTree<'a> {
299 fn add_context(input: Input<'a>, context: &'static str, other: Self) -> Self {
300 let context = StackContext {
301 input,
302 context: Context::Name(context),
303 };
304 match other {
305 ErrorTree::Stack { base, mut contexts } => {
306 contexts.push(context);
307 ErrorTree::Stack { base, contexts }
308 }
309 base => ErrorTree::Stack {
310 base: Box::new(base),
311 contexts: vec![context],
312 },
313 }
314 }
315}
316
317impl<'a, E: Error> FromExternalError<Input<'a>, E> for ErrorTree<'a> {
318 fn from_external_error(input: Input<'a>, _: nom::error::ErrorKind, e: E) -> Self {
319 Self::Base {
320 input,
321 kind: ErrorKind::External(e.to_string()),
322 }
323 }
324}
325
326#[cfg(test)]
327mod tests {
328 use super::*;
329
330 #[test]
331 fn contextualizes_error() {
332 let input = r#"GAP-Test DEFINITIONS AUTOMATIC TAGS ::= BEGIN
333c-CtxTypeSystemNull ItsAidCtxRef ::= { itsaid content:0, ctx c-ctxRefNull }
334
335ItsAidCtxRef ::= SEQUENCE {
336 itsaid ITSaid,
337 ctx CtxRef
338 }
339
340CtxRef ::INTEGER(0..255)
341c-ctxRefNull CtxRef ::= 0
342
343 END"#;
344 let error = LexerError {
345 kind: LexerErrorType::MatchingError(ReportData {
346 src_file: None,
347 context_start_line: 4,
348 context_start_offset: 123,
349 line: 6,
350 column: 6,
351 offset: 172,
352 reason: "Test".into(),
353 unexpected_eof: false,
354 }),
355 };
356 assert_eq!(
357 error.contextualize(input),
358 r#"
359Error matching ASN syntax at while parsing:
360 ╭─[line 6, column 6]
361 │
362 │
363 5 │ ItsAidCtxRef ::= SEQUENCE {
364 6 │ itsaid ITSaid, ◀▪▪▪▪▪▪▪▪▪▪ FAILED AT THIS LINE
365 7 │ ctx CtxRef
366 8 │ }
367 │
368───╯
369 "#
370 )
371 }
372}