microcad_lang_parse/parser/error/
mod.rs1mod rich;
5
6use crate::ParseContext;
7use crate::parser::error::rich::RichPattern;
8use crate::tokens::Token;
9use microcad_lang_base::{Diagnostics, Refer, Span};
10use miette::{Diagnostic, LabeledSpan};
11pub use rich::{Rich, RichReason};
12use std::error::Error;
13use std::fmt::{Display, Formatter};
14use std::iter::once;
15use thiserror::Error;
16
17pub type RichError<'tokens> = Rich<'tokens, Token<'tokens>, Span, ParseErrorKind>;
19
20#[derive(Debug)]
22pub struct ParseError {
23 pub span: Span,
25 error: RichError<'static>,
26}
27
28impl ParseError {
29 pub(crate) fn new<'tokens>(error: RichError<'tokens>) -> Self {
30 Self {
31 span: error.span().clone(),
32 error: error.map_token(Token::into_owned).into_owned(),
33 }
34 }
35}
36
37#[derive(Debug, Error, derive_more::Deref, miette::Diagnostic)]
39pub struct ParseErrors(#[related] pub Vec<ParseError>);
40
41impl ParseErrors {
42 pub fn to_diagnostics(self, context: &ParseContext) -> Diagnostics {
44 let mut diag_list = Diagnostics::default();
45 use microcad_lang_base::{Diagnostic as D, PushDiag};
46
47 for err in self.0 {
48 let span = err.span.clone();
49 diag_list
50 .push_diag(D::Error(
51 Refer::<miette::Report>::new(err.into(), context.src_ref(&span)).into(),
52 ))
53 .expect("Diag list must return no error");
54 }
55
56 diag_list
57 }
58}
59
60impl std::fmt::Display for ParseErrors {
61 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
62 write!(f, "Found {} parse errors", self.0.len())
63 }
64}
65
66impl<'tokens> From<Vec<RichError<'tokens>>> for ParseErrors {
67 fn from(errors: Vec<RichError<'tokens>>) -> Self {
68 Self(errors.into_iter().map(ParseError::new).collect())
69 }
70}
71
72#[derive(Debug, Error, Clone, Diagnostic)]
73pub enum ParseErrorKind {
74 #[error("'{0}' is a reserved keyword")]
75 ReservedKeyword(&'static str),
76 #[error("'{0}' is a reserved keyword and can't be used as an identifier")]
77 ReservedKeywordAsIdentifier(&'static str),
78 #[error("'{0}' is a keyword and can't be used as an identifier")]
79 KeywordAsIdentifier(&'static str),
80 #[error("unclosed string")]
81 UnterminatedString,
82 #[error("Unclosed {kind}")]
83 UnclosedBracket {
84 #[label("{kind} opened here")]
85 open: Span,
86 #[label("expected {kind} to be closed by here with a '{close_token}'")]
87 end: Span,
88 kind: &'static str,
89 close_token: Token<'static>,
90 },
91 #[error("Expression statements need to have a trailing semicolon")]
92 ExpressionMissingSemicolon {
93 #[label("exprected a semicolon after this expression")]
94 span: Span,
95 },
96}
97
98impl Display for ParseError {
99 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
100 match self.error.reason() {
101 RichReason::Custom(error) => write!(f, "{error}"),
102 RichReason::ExpectedFound { expected, .. } => {
103 write!(f, "Expected ")?;
104 let mut expected = expected.iter().filter(|pat| match pat {
105 RichPattern::Label(label) if expected.len() > 1 => label != "whitespace",
107 _ => true,
108 });
109 if let Some(pattern) = expected.next() {
110 write!(f, "{pattern}")?;
111 }
112 let last = expected.next_back();
113 for pattern in expected {
114 write!(f, ", {pattern}")?;
115 }
116 if let Some(pattern) = last {
117 write!(f, " or {pattern}")?;
118 }
119 Ok(())
120 }
121 }
122 }
123}
124
125impl Error for ParseError {}
126
127impl Diagnostic for ParseError {
128 fn help<'a>(&'a self) -> Option<Box<dyn Display + 'a>> {
129 match self.error.reason() {
130 RichReason::Custom(error) => error.help(),
131 _ => None,
132 }
133 }
134
135 fn labels(&self) -> Option<Box<dyn Iterator<Item = LabeledSpan> + '_>> {
136 let msg = match self.error.reason() {
137 RichReason::Custom(error) => {
138 if let Some(labels) = error.labels() {
139 return Some(labels);
140 }
141 error.to_string()
142 }
143 RichReason::ExpectedFound {
144 found: Some(found), ..
145 } if found.is_error() => found.kind().into(),
146 RichReason::ExpectedFound {
147 found: Some(found), ..
148 } => format!("unexpected {}", found.kind()),
149 RichReason::ExpectedFound { found: None, .. } => "unexpected token".into(),
150 };
151 Some(Box::new(once(LabeledSpan::new(
152 Some(msg),
153 self.span.start,
154 self.span.len(),
155 ))))
156 }
157}