parsy/
error.rs

1use std::{borrow::Cow, fmt};
2
3use crate::{InputLocation, InputRange, Span};
4
5/// Result of a parsing operation
6///
7/// * In case of success, a [`Span<T>`] with the parsed value
8/// * In case of failure, a [`ParsingError`] with the error details
9pub type ParserResult<T> = ::std::result::Result<Span<T>, ParsingError>;
10
11/// Result of a parsing error
12#[derive(Debug)]
13pub struct ParsingError {
14    /// Content of the error
15    inner: ParsingErrorInner,
16
17    /// Optional atomic error
18    atomic_error: Option<&'static str>,
19
20    /// Optional critical error emssage
21    critical: Option<Cow<'static, str>>,
22}
23
24impl ParsingError {
25    /// Create a new parsing error
26    pub const fn new(inner: ParsingErrorInner) -> Self {
27        Self {
28            inner,
29            atomic_error: None,
30            critical: None,
31        }
32    }
33
34    /// Get the error's inner content
35    pub const fn inner(&self) -> &ParsingErrorInner {
36        &self.inner
37    }
38
39    /// Get the error's inner content (owned)
40    pub fn into_inner(self) -> ParsingErrorInner {
41        self.inner
42    }
43
44    /// Get the error's message if it is critical
45    pub fn critical_message(&self) -> Option<&str> {
46        self.critical.as_deref()
47    }
48
49    /// Check if the error is critical
50    pub const fn is_critical(&self) -> bool {
51        self.critical.is_some()
52    }
53
54    /// Make the error critical, with the provided message
55    pub fn criticalize(mut self, critical: impl Into<Cow<'static, str>>) -> Self {
56        if self.critical.is_none() {
57            self.critical = Some(critical.into());
58        }
59
60        self
61    }
62
63    /// Get the error's message if it is atomic
64    pub const fn atomic_error(&self) -> Option<&'static str> {
65        self.atomic_error
66    }
67
68    /// make the error atomic
69    pub const fn with_atomic_error(mut self, atomic_err: &'static str) -> Self {
70        self.atomic_error = Some(atomic_err);
71        self
72    }
73
74    /// Create an error stating a specific character was expected
75    pub const fn expected_char(range: InputRange, expected: char) -> ParsingError {
76        ParsingError::new(ParsingErrorInner::new(
77            range,
78            ParserExpectation::Char(expected),
79        ))
80    }
81
82    /// Create an error stating a specific string was expected
83    pub const fn expected_str(range: InputRange, expected: &'static str) -> ParsingError {
84        ParsingError::new(ParsingErrorInner::new(
85            range,
86            ParserExpectation::Str(expected),
87        ))
88    }
89
90    /// Create an error with a custom message
91    pub const fn custom(range: InputRange, message: &'static str) -> ParsingError {
92        ParsingError::new(ParsingErrorInner::new(
93            range,
94            ParserExpectation::Custom(message),
95        ))
96    }
97
98    /// Create an error that is not actually an error, but a breakage indicator
99    ///
100    /// See [`ParserExpectation::Break`]
101    pub const fn just_break(loc: InputLocation) -> ParsingError {
102        ParsingError::new(ParsingErrorInner::new(
103            loc.range(0),
104            ParserExpectation::Break,
105        ))
106    }
107}
108
109/// Inner content of a parsing error
110#[derive(Debug)]
111#[must_use]
112pub struct ParsingErrorInner {
113    /// Location of the error
114    at: InputRange,
115
116    /// Failed parser's expectation
117    expected: ParserExpectation,
118}
119
120impl ParsingErrorInner {
121    /// Create a [`ParsingError`]'s inner content
122    pub const fn new(at: InputRange, expected: ParserExpectation) -> Self {
123        Self { at, expected }
124    }
125
126    /// Get the error's location in the source code
127    pub const fn at(&self) -> InputRange {
128        self.at
129    }
130
131    /// Check if the error covers an empty range
132    pub const fn is_empty(&self) -> bool {
133        self.at.len == 0
134    }
135
136    /// Get the expectation of the parser that failed
137    pub const fn expected(&self) -> &ParserExpectation {
138        &self.expected
139    }
140}
141
142/// Type of parser expectation in an error
143#[derive(Debug)]
144pub enum ParserExpectation {
145    /// The parser expected a specific character
146    Char(char),
147
148    /// The parser expected a specific string
149    Str(&'static str),
150
151    /// Custom error message
152    Custom(&'static str),
153
154    /// This is not actually an error, but an indicator that the
155    /// current parser chain should abort. This being inside an error
156    /// allows easier propagation through nested parsers.
157    Break,
158}
159
160impl fmt::Display for ParserExpectation {
161    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
162        match self {
163            Self::Char(c) => write!(f, "expected character '{c}'"),
164            Self::Str(str) => write!(f, "expected string '{str}'"),
165            Self::Custom(custom) => write!(f, "{custom}"),
166            Self::Break => {
167                write!(f, "parser returned a break instruction")
168            }
169        }
170    }
171}