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                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/// The [ErrorTree] tracks errors along a parsers path.
170/// This error type is a simplified version of [`nom-supreme`](https://github.com/Lucretiel/nom-supreme/)'s
171/// `GenericErrorTree`.
172#[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]
355356357 5 │  ItsAidCtxRef ::= SEQUENCE {
358 6 │   itsaid ITSaid, ◀▪▪▪▪▪▪▪▪▪▪ FAILED AT THIS LINE
359 7 │   ctx CtxRef
360 8 │   }
361362───╯
363        "#
364        )
365    }
366}