rasn_compiler/lexer/
error.rs

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/// The [ErrorTree] tracks errors along a parsers path.
188/// This error type is a simplified version of [`nom-supreme`](https://github.com/Lucretiel/nom-supreme/)'s
189/// `GenericErrorTree`.
190#[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]
361362363 5 │  ItsAidCtxRef ::= SEQUENCE {
364 6 │   itsaid ITSaid, ◀▪▪▪▪▪▪▪▪▪▪ FAILED AT THIS LINE
365 7 │   ctx CtxRef
366 8 │   }
367368───╯
369        "#
370        )
371    }
372}