chonk/framework/
error.rs

1//! Error handling and messaging.
2
3use std::ops::Range;
4
5#[derive(Clone, Debug, PartialEq)]
6/// Basic parser error with a custom message type.
7pub enum ParserError<Msg> {
8    /// A byte range representing where an error occured.
9    Range(Range<usize>),
10    /// A stack of multiple errors.
11    Stack(Vec<ParserError<Msg>>),
12    /// An error with a custom message attached.
13    Message(Msg, Box<ParserError<Msg>>),
14}
15
16impl<Msg> ParserError<Msg> {
17    #[inline]
18    pub fn with_message(self, tag: Msg) -> Self {
19        match self {
20            ParserError::Range(_) => ParserError::Message(tag, Box::new(self)),
21            ParserError::Stack(_) => ParserError::Message(tag, Box::new(self)),
22            _ => ParserError::Message(tag, Box::new(self)),
23        }
24    }
25}
26
27/// Convenience methods for [`ParserError`]s.
28pub trait ParserErrorMethods {
29    /// Get the byte range represented by an error.
30    fn bounds(&self) -> Range<usize>;
31}
32
33impl<Msg> ParserErrorMethods for ParserError<Msg> {
34    fn bounds(&self) -> Range<usize> {
35        match self {
36            ParserError::Range(bounds) => bounds.clone(),
37            ParserError::Stack(stack) => stack.bounds(),
38            ParserError::Message(_, stack) => stack.bounds(),
39        }
40    }
41}
42
43impl<Msg> ParserErrorMethods for Vec<ParserError<Msg>> {
44    fn bounds(&self) -> Range<usize> {
45        let mut start = std::usize::MAX;
46        let mut end = 0;
47
48        for error in self {
49            let bounds = error.bounds();
50
51            if start > bounds.start {
52                start = bounds.start;
53            }
54
55            if end < bounds.end {
56                end = bounds.end;
57            }
58        }
59
60        start..end
61    }
62}
63
64impl<Msg> ParserErrorMethods for Option<Vec<ParserError<Msg>>> {
65    fn bounds(&self) -> Range<usize> {
66        if let Some(stack) = self {
67            stack.bounds()
68        } else {
69            0..0
70        }
71    }
72}
73
74#[test]
75fn parser_error_with_message() {
76    assert_eq!(
77        ParserError::Range(0..9).with_message("An error message."),
78        ParserError::Message(
79            "An error message.".into(),
80            Box::new(ParserError::Range(0..9))
81        ),
82    );
83
84    assert_eq!(
85        ParserError::Message(
86            "An error message.".into(),
87            Box::new(ParserError::Range(0..9))
88        )
89        .with_message("Oh no!"),
90        ParserError::Message(
91            "Oh no!".into(),
92            Box::new(ParserError::Message(
93                "An error message.".into(),
94                Box::new(ParserError::Range(0..9))
95            ))
96        )
97    )
98}
99
100#[test]
101fn custom_error_message() {
102    #[derive(Clone, Debug, PartialEq)]
103    enum Message {
104        AnError,
105    }
106
107    assert_eq!(
108        ParserError::Range(0..9).with_message(Message::AnError),
109        ParserError::Message(Message::AnError, Box::new(ParserError::Range(0..9)))
110    )
111}