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