wgsl_parse/
error.rs

1use std::{
2    borrow::Cow,
3    fmt::{Debug, Display},
4};
5
6use itertools::Itertools;
7use thiserror::Error;
8
9use crate::{lexer::Token, span::Span};
10
11/// WGSL parse error kind.
12#[derive(Error, Clone, Debug, PartialEq)]
13pub enum ErrorKind {
14    #[error("invalid token")]
15    InvalidToken,
16    #[error("unexpected token `{token}`, expected `{}`", .expected.iter().format(", "))]
17    UnexpectedToken {
18        token: String,
19        expected: Vec<String>,
20    },
21    #[error("unexpected end of file, expected `{}`", .expected.iter().format(", "))]
22    UnexpectedEof { expected: Vec<String> },
23    #[error("extra token `{0}` at the end of the file")]
24    ExtraToken(String),
25    #[error("invalid diagnostic severity")]
26    DiagnosticSeverity,
27    #[error("invalid `{0}` attribute, {1}")]
28    Attribute(&'static str, &'static str),
29    #[error("invalid `var` template arguments, {0}")]
30    VarTemplate(&'static str),
31}
32
33#[derive(Default, Clone, Debug, PartialEq)]
34pub(crate) enum CustomLalrError {
35    #[default]
36    LexerError,
37    DiagnosticSeverity,
38    Attribute(&'static str, &'static str),
39    VarTemplate(&'static str),
40}
41
42type LalrError = lalrpop_util::ParseError<usize, Token, (usize, CustomLalrError, usize)>;
43
44/// WGSL parse error.
45///
46/// This error can be pretty-printed with the source snippet with [`Error::with_source`]
47#[derive(Error, Clone, Debug, PartialEq)]
48pub struct Error {
49    pub error: ErrorKind,
50    pub span: Span,
51}
52
53impl Error {
54    /// Returns an [`ErrorWithSource`], a wrapper type that implements `Display` and prints
55    /// a user-friendly error snippet.
56    pub fn with_source(self, source: Cow<'_, str>) -> ErrorWithSource<'_> {
57        ErrorWithSource::new(self, source)
58    }
59}
60
61impl Display for Error {
62    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
63        write!(f, "chars {:?}: {}", self.span.range(), self.error)
64    }
65}
66
67impl From<LalrError> for Error {
68    fn from(err: LalrError) -> Self {
69        match err {
70            LalrError::InvalidToken { location } => {
71                let span = Span::new(location..location + 1);
72                let error = ErrorKind::InvalidToken;
73                Self { span, error }
74            }
75            LalrError::UnrecognizedEof { location, expected } => {
76                let span = Span::new(location..location + 1);
77                let error = ErrorKind::UnexpectedEof { expected };
78                Self { span, error }
79            }
80            LalrError::UnrecognizedToken {
81                token: (l, token, r),
82                expected,
83            } => {
84                let span = Span::new(l..r);
85                let error = ErrorKind::UnexpectedToken {
86                    token: token.to_string(),
87                    expected,
88                };
89                Self { span, error }
90            }
91            LalrError::ExtraToken {
92                token: (l, token, r),
93            } => {
94                let span = Span::new(l..r);
95                let error = ErrorKind::ExtraToken(token.to_string());
96                Self { span, error }
97            }
98            LalrError::User {
99                error: (l, error, r),
100            } => {
101                let span = Span::new(l..r);
102                let error = match error {
103                    CustomLalrError::DiagnosticSeverity => ErrorKind::DiagnosticSeverity,
104                    CustomLalrError::LexerError => ErrorKind::InvalidToken,
105                    CustomLalrError::Attribute(attr, expected) => {
106                        ErrorKind::Attribute(attr, expected)
107                    }
108                    CustomLalrError::VarTemplate(reason) => ErrorKind::VarTemplate(reason),
109                };
110                Self { span, error }
111            }
112        }
113    }
114}
115
116/// A wrapper type that implements `Display` and prints a user-friendly error snippet.
117#[derive(Clone, Debug, PartialEq)]
118pub struct ErrorWithSource<'s> {
119    pub error: Error,
120    pub source: Cow<'s, str>,
121}
122
123impl std::error::Error for ErrorWithSource<'_> {}
124
125impl<'s> ErrorWithSource<'s> {
126    pub fn new(error: Error, source: Cow<'s, str>) -> Self {
127        Self { error, source }
128    }
129}
130
131impl Display for ErrorWithSource<'_> {
132    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
133        use annotate_snippets::*;
134        let text = format!("{}", self.error.error);
135
136        let annot = Level::Info.span(self.error.span.range());
137        let snip = Snippet::source(&self.source).fold(true).annotation(annot);
138        let msg = Level::Error.title(&text).snippet(snip);
139
140        let renderer = Renderer::styled();
141        let rendered = renderer.render(msg);
142        write!(f, "{rendered}")
143    }
144}