unsynn/
error.rs

1use crate::{TokenIter, TokenStream, TokenTree};
2use std::sync::Arc;
3
4/// Result type for parsing.
5pub type Result<T> = std::result::Result<T, Error>;
6
7// To keep the Error passing simple and allocation free for the common cases we define these
8// common cases plus adding the generic case as dyn boxed error.
9/// Actual kind of an error.
10#[derive(Clone)]
11pub enum ErrorKind {
12    /// A no error state that can be upgraded by later errors.
13    NoError,
14    /// Parser failed.
15    UnexpectedToken,
16    /// `RangedRepeats` with invalid bound.
17    OutOfRange {
18        /// The min/max bound that failed
19        have: usize,
20        /// The MIN/MAX defined in the type
21        want: usize,
22    },
23    /// A repeating parser detected that the inner parser succeeded without consuming any tokens.
24    InfiniteLoop {
25        /// The type that succeeded without consuming tokens
26        parser_type: &'static str,
27    },
28    /// Something else failed which can be formatted as `String`.
29    Other {
30        /// explanation what failed
31        reason: String,
32    },
33    /// Any other error.
34    Dynamic(Arc<dyn std::error::Error>),
35}
36
37/// Error type for parsing.
38#[must_use]
39#[derive(Clone)]
40pub struct Error {
41    /// Kind of the error.
42    pub kind: ErrorKind,
43    /// type name of what was expected
44    expected: &'static str,
45    /// refines type name for complex parsers
46    refined: Option<&'static str>,
47    at: Option<TokenTree>,
48    /// Cloned iterator positioned at the error
49    after: Option<TokenIter>,
50    // TokenIter position where it happened
51    // on disjunct parsers we use this to determine which error to keep
52    pos: usize,
53}
54
55impl Error {
56    /// Create a `ErrorKind::NoError` error.
57    #[allow(clippy::missing_errors_doc)]
58    pub const fn no_error() -> Self {
59        Error {
60            kind: ErrorKind::NoError,
61            expected: "<NoError>",
62            refined: None,
63            at: None,
64            after: None,
65            pos: 0,
66        }
67    }
68
69    /// Upgrade an error to one with greater pos value.
70    #[allow(clippy::missing_errors_doc)]
71    pub fn upgrade<T>(&mut self, r: Result<T>) -> Result<T> {
72        if let Err(other) = &r {
73            if matches!(self.kind, ErrorKind::NoError) || other.pos > self.pos {
74                *self = other.clone();
75            }
76        }
77        r
78    }
79
80    /// Set the position of the error.
81    ///
82    /// Sometimes the position of the error is not known at the time of creation. This allows
83    /// to adjust it later.
84    pub fn set_pos(&mut self, pos: impl TokenCount) {
85        self.pos = pos.token_count();
86    }
87
88    /// Get the position of the error.
89    #[must_use]
90    pub const fn pos(&self) -> usize {
91        self.pos
92    }
93
94    /// Create a `Result<T>::Err(Error{ kind: ErrorKind::UnexpectedToken })` error at a token iter position.
95    /// Takes the failed token (if available) and a reference to the `TokenIter` past the error.
96    ///
97    /// # Note on position tracking
98    /// The `pos` field is set to `after.counter()`, which represents the number of tokens
99    /// that were consumed up to and including the failed token (if any):
100    /// - If `at` is `Some(token)`: The token was consumed by `next()` which incremented the counter,
101    ///   so `pos` reflects the position AFTER consuming the failed token.
102    /// - If `at` is `None`: The iterator was exhausted, so `pos` reflects how many tokens were
103    ///   successfully consumed before running out.
104    // This semantic is crucial for `Either<>` to correctly identify which alternative parsed
105    // furthest into the token stream.
106    #[allow(clippy::missing_errors_doc)]
107    pub fn unexpected_token<T>(at: Option<TokenTree>, after: &TokenIter) -> Result<T> {
108        // Clone the iterator and collect remaining tokens into a new TokenStream
109        let pos = after.counter();
110        Err(Error {
111            kind: ErrorKind::UnexpectedToken,
112            expected: std::any::type_name::<T>(),
113            refined: None,
114            at,
115            after: Some(after.clone()),
116            pos,
117        })
118    }
119
120    /// Create a `Result<T>::Err(Error{ kind: ErrorKind::UnexpectedToken })` error without a token iter.
121    #[allow(clippy::missing_errors_doc)]
122    pub fn unexpected_end<T>() -> Result<T> {
123        Err(Error {
124            kind: ErrorKind::UnexpectedToken,
125            expected: std::any::type_name::<T>(),
126            refined: None,
127            at: None,
128            after: None,
129            pos: usize::MAX,
130        })
131    }
132
133    /// Create a `Result<T>::Err(Error{ kind: ErrorKind::OutOfRange })` error at a token iter position.
134    /// Takes the failed token (if available) and a reference to the `TokenIter` past the error.
135    #[allow(clippy::missing_errors_doc)]
136    pub fn out_of_range<const LIM: usize, T>(
137        have: usize,
138        at: Option<TokenTree>,
139        after: &TokenIter,
140    ) -> Result<T> {
141        let pos = after.counter();
142        Err(Error {
143            kind: ErrorKind::OutOfRange { have, want: LIM },
144            expected: std::any::type_name::<T>(),
145            refined: None,
146            at,
147            after: Some(after.clone()),
148            pos,
149        })
150    }
151
152    /// Create a `Result<T>::Err(Error{ kind: ErrorKind::Other })` error.  Takes the failed
153    /// token (if available), a reference to the `TokenIter` past the error and a `String`
154    /// describing the error.
155    #[allow(clippy::missing_errors_doc)]
156    pub fn other<T>(at: Option<TokenTree>, after: &TokenIter, reason: String) -> Result<T> {
157        let pos = after.counter();
158        Err(Error {
159            kind: ErrorKind::Other { reason },
160            expected: std::any::type_name::<T>(),
161            refined: None,
162            at,
163            after: Some(after.clone()),
164            pos,
165        })
166    }
167
168    /// Create a `Result<T>::Err(Error{ kind: ErrorKind::InfiniteLoop })` error. Used when a
169    /// repeating (conatianer) parser detects that the inner parser succeeded without
170    /// consuming any tokens, which would lead to an infinite loop.
171    #[allow(clippy::missing_errors_doc)]
172    pub fn infinite_loop<T>(at: Option<TokenTree>, after: &TokenIter) -> Result<T> {
173        let pos = after.counter();
174        Err(Error {
175            kind: ErrorKind::InfiniteLoop {
176                parser_type: std::any::type_name::<T>(),
177            },
178            expected: std::any::type_name::<T>(),
179            refined: None,
180            at,
181            after: Some(after.clone()),
182            pos,
183        })
184    }
185
186    /// Create a `Result<T>::Err(Error{ kind: ErrorKind::Dynamic })` error.  Takes the failed
187    /// token (if available), a reference to the `TokenIter` past the error and a boxed error.
188    #[allow(clippy::missing_errors_doc)]
189    pub fn dynamic<T>(
190        at: Option<TokenTree>,
191        after: &TokenIter,
192        error: impl std::error::Error + Send + Sync + 'static,
193    ) -> Result<T> {
194        let pos = after.counter();
195        Err(Error {
196            kind: ErrorKind::Dynamic(Arc::new(error)),
197            expected: std::any::type_name::<T>(),
198            refined: None,
199            at,
200            after: Some(after.clone()),
201            pos,
202        })
203    }
204
205    /// Returns the refined type name of the parser that failed.
206    #[must_use]
207    pub fn expected_type_name(&self) -> &'static str {
208        self.refined.unwrap_or(self.expected)
209    }
210
211    /// Returns the original/fundamental type name of the parser that failed.
212    #[must_use]
213    pub const fn expected_original_type_name(&self) -> &'static str {
214        self.expected
215    }
216
217    /// Returns a `Option<TokenTree>` where the error happend.
218    #[must_use]
219    pub fn failed_at(&self) -> Option<TokenTree> {
220        self.at.clone()
221    }
222
223    /// Returns a iterator to the tokens after the error
224    ///
225    /// Creates a new `TokenIter` from the stored token stream position
226    #[must_use]
227    pub fn tokens_after(&self) -> TokenIter {
228        self.after
229            .clone()
230            .unwrap_or_else(|| TokenIter::new(TokenStream::new()))
231    }
232}
233
234impl std::error::Error for Error {}
235
236impl std::fmt::Debug for Error {
237    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
238        match &self.kind {
239            ErrorKind::NoError => {
240                write!(f, "NoError")
241            }
242            ErrorKind::UnexpectedToken => {
243                write!(
244                    f,
245                    "Unexpected token: expected {}, found {:?} at {:?}",
246                    self.expected_type_name(),
247                    OptionPP(&self.at),
248                    OptionPP(&self.at.as_ref().map(|s| s.span().start()))
249                )
250            }
251            ErrorKind::OutOfRange { have, want } => {
252                write!(
253                    f,
254                    "RangedRepeats out of bounds: expected {want}, requested {have} at {:?}",
255                    OptionPP(&self.at.as_ref().map(|s| s.span().start()))
256                )
257            }
258            ErrorKind::InfiniteLoop { parser_type } => {
259                write!(
260                    f,
261                    "Infinite loop detected: parser {} succeeded without consuming tokens at {:?}",
262                    parser_type,
263                    OptionPP(&self.at.as_ref().map(|s| s.span().start()))
264                )
265            }
266            ErrorKind::Other { reason } => {
267                write!(
268                    f,
269                    "Parser failed: expected {}, because {reason}, found {:?} at {:?}",
270                    self.expected_type_name(),
271                    OptionPP(&self.at),
272                    OptionPP(&self.at.as_ref().map(|s| s.span().start()))
273                )
274            }
275            ErrorKind::Dynamic(err) => {
276                write!(
277                    f,
278                    "Parser failed: expected {}, because {err}, found {:?} at {:?}",
279                    self.expected_type_name(),
280                    OptionPP(&self.at),
281                    OptionPP(&self.at.as_ref().map(|s| s.span().start()))
282                )
283            }
284        }
285    }
286}
287
288impl std::fmt::Display for Error {
289    #[cfg_attr(test, mutants::skip)]
290    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
291        match &self.kind {
292            ErrorKind::NoError => {
293                write!(f, "NoError")
294            }
295            ErrorKind::UnexpectedToken => {
296                write!(
297                    f,
298                    "Unexpected token: expected {}, found {:?} at {:?}",
299                    self.expected_type_name(),
300                    OptionPP(&self.at),
301                    OptionPP(&self.at.as_ref().map(|s| s.span().start()))
302                )
303            }
304            ErrorKind::OutOfRange { have, want } => {
305                write!(
306                    f,
307                    "RangedRepeats out of bounds: expected {want}, requested {have} at {:?}",
308                    OptionPP(&self.at.as_ref().map(|s| s.span().start()))
309                )
310            }
311            ErrorKind::InfiniteLoop { parser_type } => {
312                write!(
313                    f,
314                    "Infinite loop detected: parser {} succeeded without consuming tokens at {:?}",
315                    parser_type,
316                    OptionPP(&self.at.as_ref().map(|s| s.span().start()))
317                )
318            }
319            ErrorKind::Other { reason } => {
320                write!(
321                    f,
322                    "Parser failed: expected {}, because {reason}, found {:?} at {:?}",
323                    self.expected_type_name(),
324                    OptionPP(&self.at),
325                    OptionPP(&self.at.as_ref().map(|s| s.span().start()))
326                )
327            }
328            ErrorKind::Dynamic(err) => {
329                write!(
330                    f,
331                    "Parser failed: expected {}, because {err}, found {:?} at {:?}",
332                    self.expected_type_name(),
333                    OptionPP(&self.at),
334                    OptionPP(&self.at.as_ref().map(|s| s.span().start()))
335                )
336            }
337        }
338    }
339}
340
341/// Helper Trait for refining error type names. Every parser type in unsynn eventually tries
342/// to parse one of the fundamental types. When parsing fails then that fundamental type name
343/// is recorded as expected type name of the error. Often this is not desired, a user wants to
344/// know the type of parser that actually failed. Since we don't want to keep a stack/vec of
345/// errors for simplicity and performance reasons we provide a way to register refined type
346/// names in errors. Note that this refinement should only be applied to leaves in the
347/// AST. Refining errors on composed types will lead to unexpected results.
348pub trait RefineErr {
349    /// Refines a errors type name to the type name of `T`.
350    #[must_use]
351    fn refine_err<T>(self) -> Self
352    where
353        Self: Sized;
354}
355
356impl<T> RefineErr for Result<T> {
357    fn refine_err<U>(mut self) -> Self
358    where
359        Self: Sized,
360    {
361        if let Err(ref mut err) = self {
362            err.refined = Some(std::any::type_name::<U>());
363        }
364        self
365    }
366}
367
368/// Pretty printer for Options, either prints `None` or `T` without the enclosing Some.
369struct OptionPP<'a, T>(&'a Option<T>);
370
371impl<T: std::fmt::Debug> std::fmt::Debug for OptionPP<'_, T> {
372    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
373        match &self.0 {
374            Some(value) => write!(f, "{value:?}"),
375            None => write!(f, "None"),
376        }
377    }
378}
379
380impl<T: std::fmt::Display> std::fmt::Display for OptionPP<'_, T> {
381    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
382        match &self.0 {
383            Some(value) => write!(f, "{value}"),
384            None => write!(f, "None"),
385        }
386    }
387}
388
389#[test]
390fn test_optionpp() {
391    let none = format!("{}", OptionPP::<i32>(&None));
392    assert_eq!(none, "None");
393    let populated = format!("{}", OptionPP(&Some(42)));
394    assert_eq!(populated, "42");
395    let none = format!("{:?}", OptionPP::<i32>(&None));
396    assert_eq!(none, "None");
397    let populated = format!("{:?}", OptionPP(&Some(42)));
398    assert_eq!(populated, "42");
399}
400
401/// We track the position of the error by counting tokens. This trait is implemented for
402/// references to shadow counted `TokenIter`, and `usize`. The later allows to pass in a
403/// position directly or use `usize::MAX` in case no position data is available (which will
404/// make this error the be the final one when upgrading).
405pub trait TokenCount {
406    /// Get the position of the token iterator.
407    fn token_count(self) -> usize;
408}
409
410// Allows passing a usize directly.
411impl TokenCount for usize {
412    #[inline]
413    fn token_count(self) -> usize {
414        self
415    }
416}
417
418impl TokenCount for &TokenIter {
419    #[inline]
420    fn token_count(self) -> usize {
421        self.counter()
422    }
423}
424
425impl TokenCount for &mut TokenIter {
426    #[inline]
427    fn token_count(self) -> usize {
428        self.counter()
429    }
430}
431
432// implementing for &&mut allows us to pass a &mut TokenIter by reference when it is still needed
433// later. Otherwise it would need to be reborrow '&mut *iter' which is less ergonomic.
434impl TokenCount for &&mut TokenIter {
435    #[inline]
436    fn token_count(self) -> usize {
437        self.counter()
438    }
439}