Skip to main content

wgsl_parse/
error.rs

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