Skip to main content

microcad_syntax/parser/
error.rs

1// Copyright © 2026 The µcad authors <info@ucad.xyz>
2// SPDX-License-Identifier: AGPL-3.0-or-later
3
4use std::error::Error;
5use std::fmt::{Display, Formatter};
6use crate::Span;
7use crate::tokens::Token;
8use chumsky::error::{Rich, RichReason};
9use miette::{Diagnostic, LabeledSpan};
10use std::iter::once;
11
12/// An error from building the abstract syntax tree
13#[derive(Debug)]
14pub struct ParseError {
15    /// The span of the source that caused the error
16    pub span: Span,
17    error: Rich<'static, Token<'static>, Span>,
18}
19
20impl ParseError {
21    pub(crate) fn new<'tokens>(error: Rich<'tokens, Token<'tokens>, Span>) -> Self {
22        Self {
23            span: error.span().clone(),
24            error: error.map_token(Token::into_owned).into_owned(),
25        }
26    }
27}
28
29impl Display for ParseError {
30    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
31        match self.error.reason() {
32            RichReason::Custom(error) => write!(f, "{error}"),
33            RichReason::ExpectedFound {
34                expected, ..
35            } => {
36                write!(f, "Expected ")?;
37                let mut expected = expected.iter();
38                if let Some(pattern) = expected.next() {
39                    write!(f, "{pattern}")?;
40                }
41                let last = expected.next_back();
42                for pattern in expected {
43                    write!(f, ", {pattern}")?;
44                };
45                if let Some(pattern) = last {
46                    write!(f, " or {pattern}")?;
47                }
48                Ok(())
49            },
50        }
51    }
52}
53
54impl Error for ParseError {
55
56}
57
58impl Diagnostic for ParseError {
59    fn labels(&self) -> Option<Box<dyn Iterator<Item=LabeledSpan> + '_>> {
60        let msg = match self.error.reason() {
61            RichReason::Custom(error) => error.clone(),
62            RichReason::ExpectedFound {
63                found: Some(found), ..
64            } if found.is_error() => found.kind().into(),
65            RichReason::ExpectedFound {
66                found: Some(found), ..
67            } => format!("unexpected {}", found.kind()),
68            RichReason::ExpectedFound { found: None, .. } => "unexpected token".into(),
69        };
70        Some(Box::new(once(LabeledSpan::new(
71            Some(msg),
72            self.span.start,
73            self.span.len(),
74        ))))
75    }
76}