Skip to main content

wgsl_parse/
parser_support.rs

1//! support functions to be injected in the lalrpop parser.
2
3use std::str::FromStr;
4
5use itertools::Itertools;
6
7use crate::{error::ParseError, span::Span, syntax::*};
8
9type E = ParseError;
10
11#[derive(Clone, Copy, Debug, PartialEq, Eq)]
12pub enum DeclarationScope {
13    Module,
14    Function,
15}
16
17pub fn validate_declaration(decl: &Declaration, scope: DeclarationScope) -> Result<(), ParseError> {
18    // check for required initializers
19    if matches!(decl.kind, DeclarationKind::Const | DeclarationKind::Let)
20        && decl.initializer.is_none()
21    {
22        return Err(ParseError::InvalidDeclaration(
23            decl.kind,
24            "missing required initializer",
25        ));
26    }
27
28    // check for disallowed initializers
29    if let DeclarationKind::Var(Some((a_s, _))) = decl.kind {
30        let initializer_disallowed = match a_s {
31            AddressSpace::Function | AddressSpace::Private => false,
32            AddressSpace::Workgroup
33            | AddressSpace::Uniform
34            | AddressSpace::Storage
35            | AddressSpace::Handle
36            | AddressSpace::Immediate => true,
37            #[cfg(feature = "naga-ext")]
38            AddressSpace::TaskPayload
39            | AddressSpace::RayPayload
40            | AddressSpace::IncomingRayPayload => true,
41        };
42        if initializer_disallowed && decl.initializer.is_some() {
43            return Err(ParseError::InvalidDeclaration(
44                decl.kind,
45                "unexpected initializer",
46            ));
47        }
48    }
49
50    // check for declaration scope
51    match scope {
52        DeclarationScope::Module => match decl.kind {
53            DeclarationKind::Let | DeclarationKind::Var(Some((AddressSpace::Function, _))) => {
54                return Err(ParseError::InvalidDeclaration(
55                    decl.kind,
56                    "not allowed in module scope",
57                ));
58            }
59            DeclarationKind::Const | DeclarationKind::Override | DeclarationKind::Var(_) => {}
60        },
61        DeclarationScope::Function => match decl.kind {
62            DeclarationKind::Const
63            | DeclarationKind::Let
64            | DeclarationKind::Var(None | Some((AddressSpace::Function, _))) => {}
65            DeclarationKind::Override | DeclarationKind::Var(_) => {
66                return Err(ParseError::InvalidDeclaration(
67                    decl.kind,
68                    "not allowed in functions",
69                ));
70            }
71        },
72    }
73
74    Ok(())
75}
76
77/// Make operator chains left-associative and check that they contain all the same operator.
78///
79/// WGSL forbids mixing bitwise operators (`&`, `|`, `^`) and short-circuit operators (`&&`, `||`):
80/// `a & b & c` is valid, but `a & b | c` is a syntax error.
81///
82/// We parse a flat operator chain in the grammar, and use this helper function to turn
83/// the flat list into a left-associative tree, while ensuring that all operators in the
84/// chain are the same.
85pub fn fold_same_operator(
86    mut left: ExpressionNode,
87    right_chain: Vec<(BinaryOperator, ExpressionNode)>,
88) -> Result<Expression, E> {
89    if right_chain.is_empty() {
90        return Ok(left.into_inner());
91    }
92    let first_op = right_chain[0].0;
93    for (operator, right) in right_chain {
94        if operator != first_op {
95            return Err(E::MixedBinaryOperators(first_op, operator));
96        }
97        let span = left.span().extend(right.span());
98        let expr = Expression::Binary(BinaryExpression {
99            operator,
100            left,
101            right,
102        });
103        left = ExpressionNode::new(expr, span);
104    }
105    Ok(left.into_inner())
106}
107
108// HACK: parsing entrypoints.
109// see: https://github.com/lalrpop/lalrpop/issues/65#issuecomment-516769995
110pub enum ParseEntryPoint {
111    TryTemplateList(Span),
112    TranslationUnit(TranslationUnit),
113    GlobalDecl(GlobalDeclaration),
114    Literal(LiteralExpression),
115    GlobalDirective(GlobalDirective),
116    Expression(Expression),
117    Statement(Statement),
118    ImportStatement(ImportStatement),
119}
120
121impl FromStr for DeclarationKind {
122    type Err = ();
123
124    fn from_str(s: &str) -> Result<Self, Self::Err> {
125        match s {
126            "const" => Ok(Self::Const),
127            "override" => Ok(Self::Override),
128            "let" => Ok(Self::Let),
129            "var" => Ok(Self::Var(None)),
130            _ => Err(()),
131        }
132    }
133}
134
135fn one_arg(arguments: Option<Vec<ExpressionNode>>) -> Option<ExpressionNode> {
136    match arguments {
137        Some(mut args) => (args.len() == 1).then(|| args.pop().unwrap()),
138        None => None,
139    }
140}
141fn two_args(arguments: Option<Vec<ExpressionNode>>) -> Option<(ExpressionNode, ExpressionNode)> {
142    match arguments {
143        Some(args) => (args.len() == 2).then(|| args.into_iter().collect_tuple().unwrap()),
144        None => None,
145    }
146}
147fn zero_args(arguments: Option<Vec<ExpressionNode>>) -> bool {
148    arguments.is_none()
149}
150fn ident(expr: ExpressionNode) -> Option<Ident> {
151    match expr.into_inner() {
152        Expression::TypeOrIdentifier(TypeExpression {
153            path: _,
154            ident,
155            template_args: None,
156        }) => Some(ident),
157        _ => None,
158    }
159}
160
161pub(crate) fn parse_attribute(
162    name: String,
163    args: Option<Vec<ExpressionNode>>,
164) -> Result<Attribute, E> {
165    match name.as_str() {
166        "align" => match one_arg(args) {
167            Some(expr) => Ok(Attribute::Align(expr)),
168            _ => Err(E::Attribute("align", "expected 1 argument")),
169        },
170        "binding" => match one_arg(args) {
171            Some(expr) => Ok(Attribute::Binding(expr)),
172            _ => Err(E::Attribute("binding", "expected 1 argument")),
173        },
174        "blend_src" => match one_arg(args) {
175            Some(expr) => Ok(Attribute::BlendSrc(expr)),
176            _ => Err(E::Attribute("blend_src", "expected 1 argument")),
177        },
178        "builtin" => match one_arg(args) {
179            Some(expr) => match ident(expr).and_then(|id| id.name().parse().ok()) {
180                Some(b) => Ok(Attribute::Builtin(b)),
181                _ => Err(E::Attribute(
182                    "builtin",
183                    "the argument is not a valid built-in value name",
184                )),
185            },
186            _ => Err(E::Attribute("builtin", "expected 1 argument")),
187        },
188        "const" => match zero_args(args) {
189            true => Ok(Attribute::Const),
190            false => Err(E::Attribute("const", "expected 0 arguments")),
191        },
192        "diagnostic" => match two_args(args) {
193            Some((e1, e2)) => {
194                let severity = ident(e1).and_then(|id| id.name().parse().ok());
195                let rule = match e2.into_inner() {
196                    Expression::TypeOrIdentifier(TypeExpression {
197                        path: _,
198                        ident,
199                        template_args: None,
200                    }) => Some(ident.name().to_string()),
201                    Expression::NamedComponent(e) => {
202                        ident(e.base).map(|id| format!("{}.{}", id.name(), e.component))
203                    }
204                    _ => None,
205                };
206                match (severity, rule) {
207                    (Some(severity), Some(rule)) => {
208                        Ok(Attribute::Diagnostic(DiagnosticAttribute {
209                            severity,
210                            rule,
211                        }))
212                    }
213                    _ => Err(E::Attribute("diagnostic", "invalid arguments")),
214                }
215            }
216            _ => Err(E::Attribute("diagnostic", "expected 1 argument")),
217        },
218        "group" => match one_arg(args) {
219            Some(expr) => Ok(Attribute::Group(expr)),
220            _ => Err(E::Attribute("group", "expected 1 argument")),
221        },
222        "id" => match one_arg(args) {
223            Some(expr) => Ok(Attribute::Id(expr)),
224            _ => Err(E::Attribute("id", "expected 1 argument")),
225        },
226        "interpolate" => match args {
227            Some(v) if v.len() == 2 => {
228                let (e1, e2) = v.into_iter().collect_tuple().unwrap();
229                let ty = ident(e1).and_then(|id| id.name().parse().ok());
230                let sampling = ident(e2).and_then(|id| id.name().parse().ok());
231                match (ty, sampling) {
232                    (Some(ty), Some(sampling)) => {
233                        Ok(Attribute::Interpolate(InterpolateAttribute {
234                            ty,
235                            sampling: Some(sampling),
236                        }))
237                    }
238                    _ => Err(E::Attribute("interpolate", "invalid arguments")),
239                }
240            }
241            Some(v) if v.len() == 1 => {
242                let e1 = v.into_iter().next().unwrap();
243                let ty = ident(e1).and_then(|id| id.name().parse().ok());
244                match ty {
245                    Some(ty) => Ok(Attribute::Interpolate(InterpolateAttribute {
246                        ty,
247                        sampling: None,
248                    })),
249                    _ => Err(E::Attribute("interpolate", "invalid arguments")),
250                }
251            }
252            _ => Err(E::Attribute("interpolate", "invalid arguments")),
253        },
254
255        "invariant" => match zero_args(args) {
256            true => Ok(Attribute::Invariant),
257            false => Err(E::Attribute("invariant", "expected 0 arguments")),
258        },
259        "location" => match one_arg(args) {
260            Some(expr) => Ok(Attribute::Location(expr)),
261            _ => Err(E::Attribute("location", "expected 1 argument")),
262        },
263        "must_use" => match zero_args(args) {
264            true => Ok(Attribute::MustUse),
265            false => Err(E::Attribute("must_use", "expected 0 arguments")),
266        },
267        "size" => match one_arg(args) {
268            Some(expr) => Ok(Attribute::Size(expr)),
269            _ => Err(E::Attribute("size", "expected 1 argument")),
270        },
271        "workgroup_size" => match args {
272            Some(args) => {
273                let mut it = args.into_iter();
274                match (it.next(), it.next(), it.next(), it.next()) {
275                    (Some(x), y, z, None) => {
276                        Ok(Attribute::WorkgroupSize(WorkgroupSizeAttribute { x, y, z }))
277                    }
278                    _ => Err(E::Attribute("workgroup_size", "expected 1-3 arguments")),
279                }
280            }
281            _ => Err(E::Attribute("workgroup_size", "expected 1-3 arguments")),
282        },
283        "vertex" => match zero_args(args) {
284            true => Ok(Attribute::Vertex),
285            false => Err(E::Attribute("vertex", "expected 0 arguments")),
286        },
287        "fragment" => match zero_args(args) {
288            true => Ok(Attribute::Fragment),
289            false => Err(E::Attribute("fragment", "expected 0 arguments")),
290        },
291        "compute" => match zero_args(args) {
292            true => Ok(Attribute::Compute),
293            false => Err(E::Attribute("compute", "expected 0 arguments")),
294        },
295        #[cfg(feature = "naga-ext")]
296        "task" => match zero_args(args) {
297            true => Ok(Attribute::Task),
298            false => Err(E::Attribute("task", "expected 0 arguments")),
299        },
300        #[cfg(feature = "naga-ext")]
301        "payload" => match one_arg(args) {
302            Some(expr) => Ok(Attribute::Payload(expr)),
303            None => Err(E::Attribute("payload", "expected 1 arguments")),
304        },
305        #[cfg(feature = "naga-ext")]
306        "mesh" => match one_arg(args) {
307            Some(expr) => Ok(Attribute::Mesh(expr)),
308            None => Err(E::Attribute("mesh", "expected 1 arguments")),
309        },
310        #[cfg(feature = "naga-ext")]
311        "ray_generation" => match zero_args(args) {
312            true => Ok(Attribute::RayGeneration),
313            false => Err(E::Attribute("ray_generation", "expected 0 arguments")),
314        },
315        #[cfg(feature = "naga-ext")]
316        "any_hit" => match zero_args(args) {
317            true => Ok(Attribute::AnyHit),
318            false => Err(E::Attribute("any_hit", "expected 0 arguments")),
319        },
320        #[cfg(feature = "naga-ext")]
321        "closest_hit" => match zero_args(args) {
322            true => Ok(Attribute::ClosestHit),
323            false => Err(E::Attribute("closest_hit", "expected 0 arguments")),
324        },
325        #[cfg(feature = "naga-ext")]
326        "miss" => match zero_args(args) {
327            true => Ok(Attribute::Miss),
328            false => Err(E::Attribute("miss", "expected 0 arguments")),
329        },
330        #[cfg(feature = "naga-ext")]
331        "incoming_payload" => match one_arg(args) {
332            Some(expr) => Ok(Attribute::IncomingPayload(expr)),
333            None => Err(E::Attribute("incoming_payload", "expected 1 arguments")),
334        },
335        "if" => match one_arg(args) {
336            Some(expr) => Ok(Attribute::If(expr)),
337            None => Err(E::Attribute("if", "expected 1 argument")),
338        },
339        "elif" => match one_arg(args) {
340            Some(expr) => Ok(Attribute::Elif(expr)),
341            None => Err(E::Attribute("elif", "expected 1 argument")),
342        },
343        "else" => match zero_args(args) {
344            true => Ok(Attribute::Else),
345            false => Err(E::Attribute("else", "expected 0 arguments")),
346        },
347        #[cfg(feature = "generics")]
348        "type" => parse_attr_type(args).map(Attribute::Type),
349        #[cfg(feature = "naga-ext")]
350        "early_depth_test" => match args {
351            Some(args) => {
352                let mut it = args.into_iter();
353                match (it.next(), it.next()) {
354                    (Some(expr), None) => match ident(expr).and_then(|id| id.name().parse().ok()) {
355                        Some(c) => Ok(Attribute::EarlyDepthTest(Some(c))),
356                        _ => Err(E::Attribute(
357                            "early_depth_test",
358                            "the argument must be one of `greater_equal`, `less_equal`, `unchanged`",
359                        )),
360                    },
361                    (None, None) => Ok(Attribute::EarlyDepthTest(None)),
362                    _ => Err(E::Attribute(
363                        "early_depth_test",
364                        "expected 0 or 1 arguments",
365                    )),
366                }
367            }
368            _ => Err(E::Attribute(
369                "early_depth_test",
370                "expected 0 or 1 arguments",
371            )),
372        },
373        _ => Ok(Attribute::Custom(CustomAttribute {
374            name,
375            arguments: args,
376        })),
377    }
378}
379
380// format: @type(T, foo | bar | baz)
381#[cfg(feature = "generics")]
382fn parse_attr_type(arguments: Option<Vec<ExpressionNode>>) -> Result<TypeConstraint, E> {
383    fn parse_rec(expr: Expression) -> Result<Vec<TypeExpression>, E> {
384        match expr {
385            Expression::TypeOrIdentifier(ty) => Ok(vec![ty]),
386            Expression::Binary(BinaryExpression {
387                operator: BinaryOperator::BitwiseOr,
388                left,
389                right,
390            }) => {
391                let ty = match right.into_inner() {
392                    Expression::TypeOrIdentifier(ty) => Ok(ty),
393                    _ => Err(E::Attribute(
394                        "type",
395                        "invalid second argument (type constraint)",
396                    )),
397                }?;
398                let mut v = parse_rec(left.into_inner())?;
399                v.push(ty);
400                Ok(v)
401            }
402            _ => Err(E::Attribute(
403                "type",
404                "invalid second argument (type constraint)",
405            )),
406        }
407    }
408    match two_args(arguments) {
409        Some((e1, e2)) => ident(e1)
410            .map(|ident| {
411                parse_rec(e2.into_inner()).map(|variants| TypeConstraint { ident, variants })
412            })
413            .unwrap_or_else(|| Err(E::Attribute("type", "invalid first argument (type name)"))),
414
415        None => Err(E::Attribute("type", "expected 2 arguments")),
416    }
417}
418
419pub(crate) fn parse_var_template(
420    template_args: TemplateArgs,
421) -> Result<Option<(AddressSpace, Option<AccessMode>)>, E> {
422    match template_args {
423        Some(tplt) => {
424            let mut it = tplt.into_iter();
425            match (it.next(), it.next(), it.next()) {
426                (Some(e1), e2, None) => {
427                    let addr_space = ident(e1.expression)
428                        .and_then(|id| id.name().parse().ok())
429                        .ok_or(E::VarTemplate("invalid address space"))?;
430                    let mut access_mode = None;
431                    if let Some(e2) = e2 {
432                        if addr_space == AddressSpace::Storage {
433                            access_mode = Some(
434                                ident(e2.expression)
435                                    .and_then(|id| id.name().parse().ok())
436                                    .ok_or(E::VarTemplate("invalid access mode"))?,
437                            );
438                        } else {
439                            return Err(E::VarTemplate(
440                                "only variables with `storage` address space can have an access mode",
441                            ));
442                        }
443                    }
444                    Ok(Some((addr_space, access_mode)))
445                }
446                _ => Err(E::VarTemplate("template is empty")),
447            }
448        }
449        None => Ok(None),
450    }
451}
452
453#[cfg(test)]
454mod tests {
455    use std::str::FromStr;
456
457    use super::*;
458
459    #[test]
460    fn component_base_spans_end_before_the_component() {
461        let expression = Expression::from_str("globals.voxel_clip.count").unwrap();
462        let Expression::NamedComponent(outer) = expression else {
463            panic!("expected outer named component");
464        };
465        assert_eq!(outer.base.span(), Span::new(0..18));
466
467        let Expression::NamedComponent(inner) = outer.base.node() else {
468            panic!("expected inner named component");
469        };
470        assert_eq!(inner.base.span(), Span::new(0..7));
471    }
472}