use std::str::FromStr;
use crate::{error::ParseError, lexer::Token};
use crate::span::{Spanned, Span};
use crate::syntax::*;
use crate::parser_support::*;
// this grammar follows closely the wgsl spec.
// following the spec at this date: https://www.w3.org/TR/2024/WD-WGSL-20240731/
grammar;
extern {
type Location = usize;
type Error = (usize, ParseError, usize);
enum Token {
// syntactic tokens
// https://www.w3.org/TR/WGSL/#syntactic-tokens
"&" => Token::SymAnd,
"&&" => Token::SymAndAnd,
"->" => Token::SymArrow,
"@" => Token::SymAttr,
"/" => Token::SymForwardSlash,
"!" => Token::SymBang,
"[" => Token::SymBracketLeft,
"]" => Token::SymBracketRight,
"{" => Token::SymBraceLeft,
"}" => Token::SymBraceRight,
":" => Token::SymColon,
"," => Token::SymComma,
"=" => Token::SymEqual,
"==" => Token::SymEqualEqual,
"!=" => Token::SymNotEqual,
">" => Token::SymGreaterThan,
">=" => Token::SymGreaterThanEqual,
">>" => Token::SymShiftRight,
"<" => Token::SymLessThan,
"<=" => Token::SymLessThanEqual,
"<<" => Token::SymShiftLeft,
"%" => Token::SymModulo,
"-" => Token::SymMinus,
"--" => Token::SymMinusMinus,
"." => Token::SymPeriod,
"+" => Token::SymPlus,
"++" => Token::SymPlusPlus,
"|" => Token::SymOr,
"||" => Token::SymOrOr,
"(" => Token::SymParenLeft,
")" => Token::SymParenRight,
";" => Token::SymSemicolon,
"*" => Token::SymStar,
"~" => Token::SymTilde,
"_" => Token::SymUnderscore,
"^" => Token::SymXor,
"+=" => Token::SymPlusEqual,
"-=" => Token::SymMinusEqual,
"*=" => Token::SymTimesEqual,
"/=" => Token::SymDivisionEqual,
"%=" => Token::SymModuloEqual,
"&=" => Token::SymAndEqual,
"|=" => Token::SymOrEqual,
"^=" => Token::SymXorEqual,
">>=" => Token::SymShiftRightAssign,
"<<=" => Token::SymShiftLeftAssign,
// keywords
// https://www.w3.org/TR/WGSL/#keyword-summary
"alias" => Token::KwAlias,
"break" => Token::KwBreak,
"case" => Token::KwCase,
"const" => Token::KwConst,
"const_assert" => Token::KwConstAssert,
"continue" => Token::KwContinue,
"continuing" => Token::KwContinuing,
"default" => Token::KwDefault,
"diagnostic" => Token::KwDiagnostic,
"discard" => Token::KwDiscard,
"else" => Token::KwElse,
"enable" => Token::KwEnable,
"false" => Token::KwFalse,
"fn" => Token::KwFn,
"for" => Token::KwFor,
"if" => Token::KwIf,
"let" => Token::KwLet,
"loop" => Token::KwLoop,
"override" => Token::KwOverride,
"requires" => Token::KwRequires,
"return" => Token::KwReturn,
"struct" => Token::KwStruct,
"switch" => Token::KwSwitch,
"true" => Token::KwTrue,
"var" => Token::KwVar,
"while" => Token::KwWhile,
IdentPatternToken => Token::Ident(<String>),
ReservedWord => Token::ReservedWord(<String>),
TokAbstractInt => Token::AbstractInt(<i64>),
TokAbstractFloat => Token::AbstractFloat(<f64>),
TokI32 => Token::I32(<i32>),
TokU32 => Token::U32(<u32>),
TokF32 => Token::F32(<f32>),
TokF16 => Token::F16(<f32>),
TokTemplateArgsStart => Token::TemplateArgsStart,
TokTemplateArgsEnd => Token::TemplateArgsEnd,
// extension: wesl-imports
// https://github.com/wgsl-tooling-wg/wesl-spec/blob/imports-update/Imports.md
// date: 2025-01-18, hash: 2db8e7f681087db6bdcd4a254963deb5c0159775
#[cfg(feature = "imports")]
"::" => Token::SymColonColon,
#[cfg(feature = "imports")]
"self" => Token::KwSelf,
#[cfg(feature = "imports")]
"super" => Token::KwSuper,
#[cfg(feature = "imports")]
"package" => Token::KwPackage,
#[cfg(feature = "imports")]
"as" => Token::KwAs,
#[cfg(feature = "imports")]
"import" => Token::KwImport,
// naga extensions
#[cfg(feature = "naga-ext")]
TokI64 => Token::I64(<i64>),
#[cfg(feature = "naga-ext")]
TokU64 => Token::U64(<u64>),
#[cfg(feature = "naga-ext")]
TokF64 => Token::F64(<f64>),
}
}
// these are context-dependent names. e.g. the @const attribute.
Keyword: String = {
"alias" => <>.to_string(),
"break" => <>.to_string(),
"case" => <>.to_string(),
"const" => <>.to_string(),
"const_assert" => <>.to_string(),
"continue" => <>.to_string(),
"continuing" => <>.to_string(),
"default" => <>.to_string(),
"diagnostic" => <>.to_string(),
"discard" => <>.to_string(),
"else" => <>.to_string(),
"enable" => <>.to_string(),
"false" => <>.to_string(),
"fn" => <>.to_string(),
"for" => <>.to_string(),
"if" => <>.to_string(),
"let" => <>.to_string(),
"loop" => <>.to_string(),
"override" => <>.to_string(),
"requires" => <>.to_string(),
"return" => <>.to_string(),
"struct" => <>.to_string(),
"switch" => <>.to_string(),
"true" => <>.to_string(),
"var" => <>.to_string(),
"while" => <>.to_string(),
#[cfg(feature = "imports")]
"self" => <>.to_string(),
#[cfg(feature = "imports")]
"super" => <>.to_string(),
#[cfg(feature = "imports")]
"as" => <>.to_string(),
#[cfg(feature = "imports")]
"import" => <>.to_string(),
};
// the grammar rules are laid out in the same order as in the spec.
// following the spec at this date: https://www.w3.org/TR/2024/WD-WGSL-20240731/
// custom entrypoint called by the lexer when it sees [Token::Ident, Token::SymLessThan].
// if this parse succeeds, the next token emitted by the lexer will be TokTemplateList.
pub TryTemplateList: Span = {
<l:@L> TokTemplateArgsStart TemplateArgCommaList TokTemplateArgsEnd <r:@R> => Span::new(l..r),
};
// =====================
// === Begin grammar ===
// =====================
// 2. WGSL MODULE
// https://www.w3.org/TR/WGSL/#wgsl-module
pub TranslationUnit: TranslationUnit = {
#[cfg(not(feature = "imports"))]
<global_directives: GlobalDirective*> <global_declarations: GlobalDeclarationNode*> => TranslationUnit {
global_directives, global_declarations
},
#[cfg(feature = "imports")]
<imports: ImportStatement*> <global_directives: GlobalDirective*> <global_declarations: GlobalDeclarationNode*> => TranslationUnit {
imports, global_directives, global_declarations
},
};
pub GlobalDecl: GlobalDeclaration = {
";" => GlobalDeclaration::Void,
<GlobalVariableDecl> ";" => GlobalDeclaration::Declaration(<>),
<GlobalValueDecl> ";" => GlobalDeclaration::Declaration(<>),
<TypeAliasDecl> ";" => GlobalDeclaration::TypeAlias(<>),
<StructDecl> => GlobalDeclaration::Struct(<>),
<FunctionDecl> => GlobalDeclaration::Function(<>),
<ConstAssertStatement> ";" => GlobalDeclaration::ConstAssert(<>),
};
GlobalDeclarationNode: GlobalDeclarationNode = Spanned<GlobalDecl>;
DiagnosticRuleName: String = {
DiagnosticNameToken,
<first: DiagnosticNameToken> "." <last: DiagnosticNameToken> => format!("{first}.{last}"),
};
// 3. TEXTUAL STRUCTURE
// https://www.w3.org/TR/WGSL/textual-structure#
// XXX: non-conformant
// https://www.w3.org/TR/WGSL/#syntax-literal
pub Literal: LiteralExpression = {
TokAbstractInt => LiteralExpression::AbstractInt(<>),
TokAbstractFloat => LiteralExpression::AbstractFloat(<>),
TokI32 => LiteralExpression::I32(<>),
TokU32 => LiteralExpression::U32(<>),
TokF32 => LiteralExpression::F32(<>),
TokF16 => LiteralExpression::F16(<>),
#[cfg(feature = "naga-ext")]
TokI64 => LiteralExpression::I64(<>),
#[cfg(feature = "naga-ext")]
TokU64 => LiteralExpression::U64(<>),
#[cfg(feature = "naga-ext")]
TokF64 => LiteralExpression::F64(<>),
BoolLiteral,
};
BoolLiteral: LiteralExpression = {
"true" => LiteralExpression::Bool(true),
"false" => LiteralExpression::Bool(false),
};
Ident: Ident = {
IdentPatternToken => {
Ident::new(<>)
},
<l: @L> <word: ReservedWord> <r: @R> =>? {
Err(lalrpop_util::ParseError::User{ error: (l, ParseError::ReservedWord(word), r) })
},
};
MemberIdent: Ident = {
IdentPatternToken => {
Ident::new(<>)
},
<l: @L> <word: ReservedWord> <r: @R> =>? {
Err(lalrpop_util::ParseError::User{ error: (l, ParseError::ReservedWord(word), r) })
},
};
DiagnosticNameToken: String = {
IdentPatternToken => <>,
<l: @L> <word: ReservedWord> <r: @R> =>? {
Err(lalrpop_util::ParseError::User{ error: (l, ParseError::ReservedWord(word), r) })
},
};
SeverityControlName: DiagnosticSeverity = {
<l: @L> <ident: IdentPatternToken> <r: @R> =>? {
DiagnosticSeverity::from_str(&ident)
.map_err(|()| lalrpop_util::ParseError::User{ error: (l, ParseError::DiagnosticSeverity, r) })
},
<l: @L> <word: ReservedWord> <r: @R> =>? {
Err(lalrpop_util::ParseError::User{ error: (l, ParseError::ReservedWord(word), r) })
},
};
EnableExtensionName: String = {
IdentPatternToken => <>,
<l: @L> <word: ReservedWord> <r: @R> =>? {
Err(lalrpop_util::ParseError::User{ error: (l, ParseError::ReservedWord(word), r) })
},
};
SoftwareExtensionName: String = {
IdentPatternToken => <>,
<l: @L> <word: ReservedWord> <r: @R> =>? {
Err(lalrpop_util::ParseError::User{ error: (l, ParseError::ReservedWord(word), r) })
},
};
InterpolateSamplingName: String = {
IdentPatternToken => <>,
<l: @L> <word: ReservedWord> <r: @R> =>? {
Err(lalrpop_util::ParseError::User{ error: (l, ParseError::ReservedWord(word), r) })
},
};
SwizzleName: String = {
IdentPatternToken => <>,
<l: @L> <word: ReservedWord> <r: @R> =>? {
Err(lalrpop_util::ParseError::User{ error: (l, ParseError::ReservedWord(word), r) })
},
};
TemplateList: Vec<TemplateArg> = {
TokTemplateArgsStart <TemplateArgCommaList> TokTemplateArgsEnd,
};
TemplateArgCommaList: Vec<TemplateArg> = {
Comma1<TemplateArgExpression>,
};
TemplateArgExpression: TemplateArg = {
ExpressionNode => TemplateArg { expression: <> },
};
// 4. DIRECTIVES
// https://www.w3.org/TR/WGSL/#directives
pub GlobalDirective: GlobalDirective = {
DiagnosticDirective => GlobalDirective::Diagnostic(<>),
EnableDirective => GlobalDirective::Enable(<>),
RequiresDirective => GlobalDirective::Requires(<>),
};
EnableDirective: EnableDirective = {
#[cfg(not(feature = "attributes"))]
"enable" <extensions: EnableExtensionList> ";" => EnableDirective {
extensions
},
#[cfg(feature = "attributes")]
<attributes: AttributeNode*> "enable" <extensions: EnableExtensionList> ";" => EnableDirective {
attributes, extensions
},
};
EnableExtensionList: Vec<String> = {
Comma1<EnableExtensionName>,
};
RequiresDirective: RequiresDirective = {
#[cfg(not(feature = "attributes"))]
"requires" <extensions: SoftwareExtensionList> ";" => RequiresDirective {
extensions
},
#[cfg(feature = "attributes")]
<attributes: AttributeNode*> "requires" <extensions: SoftwareExtensionList> ";" => RequiresDirective {
attributes, extensions
},
};
SoftwareExtensionList: Vec<String> = {
Comma1<SoftwareExtensionName>,
};
DiagnosticDirective: DiagnosticDirective = {
#[cfg(not(feature = "attributes"))]
"diagnostic" <DiagnosticControl> ";" => {
let (severity, rule_name) = <>;
DiagnosticDirective { severity, rule_name }
},
#[cfg(feature = "attributes")]
<AttributeNode*> "diagnostic" <DiagnosticControl> ";" => {
let (attributes, (severity, rule_name)) = (<>);
DiagnosticDirective { attributes, severity, rule_name }
},
};
// 5. DECLARATION AND SCOPE
// https://www.w3.org/TR/WGSL/#declaration-and-scope
// 6. TYPES
// https://www.w3.org/TR/WGSL/#types
StructDecl: Struct = {
#[cfg(not(feature = "attributes"))]
"struct" <ident: Ident> <members: StructBodyDecl> => Struct {
ident, members
},
#[cfg(feature = "attributes")]
<attributes: AttributeNode*> "struct" <ident: Ident> <members: StructBodyDecl> => Struct {
attributes, ident, members
},
};
StructBodyDecl: Vec<StructMemberNode> = {
"{" <Comma1<StructMemberNode>> "}",
};
StructMember: StructMember = {
<attributes: AttributeNode*> <ident: MemberIdent> ":" <ty: TypeSpecifier> => StructMember {
attributes, ident, ty
},
};
StructMemberNode: StructMemberNode = Spanned<StructMember>;
TypeAliasDecl: TypeAlias = {
#[cfg(not(feature = "attributes"))]
"alias" <ident: Ident> "=" <ty: TypeSpecifier> => TypeAlias {
ident, ty
},
#[cfg(feature = "attributes")]
<attributes: AttributeNode*> "alias" <ident: Ident> "=" <ty: TypeSpecifier> => TypeAlias {
attributes, ident, ty
},
};
TypeSpecifier: TypeExpression = {
TemplateElaboratedIdent,
};
TemplateElaboratedIdent: TypeExpression = {
#[cfg(not(feature = "imports"))]
<ident: Ident> <template_args: TemplateList?> => TypeExpression {
ident, template_args
},
#[cfg(feature = "imports")]
<path: ModulePath?> <ident: Ident> <template_args: TemplateList?> => TypeExpression {
path, ident, template_args
},
};
// 7. VARIABLE AND VALUE DECLARATIONS
// https://www.w3.org/TR/WGSL/#var-and-value
VariableOrValueStatement: Declaration = {
VariableDecl,
<mut decl: VariableDecl> "=" <initializer: ExpressionNode> => {
decl.initializer = Some(initializer);
decl
},
#[cfg(not(feature = "attributes"))]
"let" <id_ty: OptionallyTypedIdent> "=" <initializer: ExpressionNode> => {
let (ident, ty) = id_ty;
Declaration {
attributes: Vec::new(),
kind: DeclarationKind::Let,
ident,
ty,
initializer: Some(initializer),
}
},
#[cfg(not(feature = "attributes"))]
"const" <id_ty: OptionallyTypedIdent> "=" <initializer: ExpressionNode> => {
let (ident, ty) = id_ty;
Declaration {
attributes: Vec::new(),
kind: DeclarationKind::Const,
ident,
ty,
initializer: Some(initializer),
}
},
#[cfg(feature = "attributes")]
<attributes: AttributeNode*> "let" <id_ty: OptionallyTypedIdent> "=" <initializer: ExpressionNode> => {
let (ident, ty) = id_ty;
Declaration {
attributes,
kind: DeclarationKind::Let,
ident,
ty,
initializer: Some(initializer),
}
},
#[cfg(feature = "attributes")]
<attributes: AttributeNode*> "const" <id_ty: OptionallyTypedIdent> "=" <initializer: ExpressionNode> => {
let (ident, ty) = id_ty;
Declaration {
attributes,
kind: DeclarationKind::Const,
ident,
ty,
initializer: Some(initializer),
}
},
};
VariableDecl: Declaration = {
#[cfg(not(feature = "attributes"))]
<l: @L> "var" <template_args: TemplateList?> <r: @R> <id_ty: OptionallyTypedIdent> =>? {
let (ident, ty) = id_ty;
let address_space = parse_var_template(template_args)
.map_err(|e| lalrpop_util::ParseError::User{ error: (l, e, r) })?;
Ok(Declaration {
attributes: Vec::new(),
kind: DeclarationKind::Var(address_space),
ident,
ty,
initializer: None,
})
},
#[cfg(feature = "attributes")]
<attributes: AttributeNode*> <l: @L> "var" <template_args: TemplateList?> <r: @R> <id_ty: OptionallyTypedIdent> =>? {
let (ident, ty) = id_ty;
let address_space = parse_var_template(template_args)
.map_err(|e| lalrpop_util::ParseError::User{ error: (l, e, r) })?;
Ok(Declaration {
attributes,
kind: DeclarationKind::Var(address_space),
ident,
ty,
initializer: None,
})
},
};
OptionallyTypedIdent: (Ident, Option<TypeExpression>) = {
<Ident> <(":" <TypeSpecifier>)?>,
};
GlobalVariableDecl: Declaration = {
#[cfg(not(feature = "attributes"))]
<attributes: AttributeNode*> <mut decl: VariableDecl> <initializer: ("=" <ExpressionNode>)?> => {
decl.attributes = attributes;
decl.initializer = initializer;
decl
},
#[cfg(feature = "attributes")]
<mut decl: VariableDecl> <initializer: ("=" <ExpressionNode>)?> => {
decl.initializer = initializer;
decl
},
};
GlobalValueDecl: Declaration = {
#[cfg(not(feature = "attributes"))]
"const" <id_ty: OptionallyTypedIdent> "=" <initializer: ExpressionNode> => {
let (ident, ty) = id_ty;
Declaration {
attributes: Vec::new(),
kind: DeclarationKind::Const,
ident,
ty,
initializer: Some(initializer),
}
},
<attributes: AttributeNode*> "override" <id_ty: OptionallyTypedIdent> <initializer: ("=" <ExpressionNode>)?> => {
let (ident, ty) = id_ty;
Declaration {
attributes,
kind: DeclarationKind::Override,
ident,
ty,
initializer,
}
},
#[cfg(feature = "attributes")]
<attributes: AttributeNode*> "const" <id_ty: OptionallyTypedIdent> "=" <initializer: ExpressionNode> => {
let (ident, ty) = id_ty;
Declaration {
attributes,
kind: DeclarationKind::Const,
ident,
ty,
initializer: Some(initializer),
}
},
};
// 8. EXPRESSIONS
// https://www.w3.org/TR/WGSL/#expressions
PrimaryExpression: Expression = {
TemplateElaboratedIdent => Expression::TypeOrIdentifier(<>),
CallExpression,
Literal => Expression::Literal(<>),
ParenExpression => Expression::Parenthesized(<>),
};
CallExpression: Expression = {
CallPhrase => Expression::FunctionCall(<>),
};
CallPhrase: FunctionCall = {
<ty: TemplateElaboratedIdent> <arguments: ArgumentExpressionList> => {
FunctionCallExpression {
ty, arguments
}
},
};
ParenExpression: ParenthesizedExpression = {
"(" <Spanned<Expression>> ")" => ParenthesizedExpression {
expression: <>
},
};
ArgumentExpressionList: Vec<ExpressionNode> = {
"(" <ExpressionCommaList?> ")" => <>.unwrap_or_default(),
};
ExpressionCommaList: Vec<ExpressionNode> = {
Comma1<ExpressionNode>,
};
// was modified from the spec, but should be equivalent
ComponentOrSwizzleSpecifier: Vec<Spanned<Component>> = {
// "[" Expression "]" ComponentOrSwizzleSpecifier?,
// "." MemberIdent ComponentOrSwizzleSpecifier?,
// "." SwizzleName ComponentOrSwizzleSpecifier ?,
Spanned<SingleComponentOrSwizzleSpecifier>+
};
SingleComponentOrSwizzleSpecifier: Component = {
"[" <Spanned<Expression>> "]" => Component::Index(<>),
"." <MemberIdent> => Component::Named(<>),
}
UnaryExpression: Expression = {
SingularExpression,
"-" <operand: Spanned<UnaryExpression>> => Expression::Unary(UnaryExpression {
operator: UnaryOperator::Negation, operand
}),
"!" <operand: Spanned<UnaryExpression>> => Expression::Unary(UnaryExpression {
operator: UnaryOperator::LogicalNegation, operand
}),
"~" <operand: Spanned<UnaryExpression>> => Expression::Unary(UnaryExpression {
operator: UnaryOperator::BitwiseComplement, operand
}),
"*" <operand: Spanned<UnaryExpression>> => Expression::Unary(UnaryExpression {
operator: UnaryOperator::Indirection, operand
}),
"&" <operand: Spanned<UnaryExpression>> => Expression::Unary(UnaryExpression {
operator: UnaryOperator::AddressOf, operand
}),
};
SingularExpression: Expression = {
<expression: WithSpan<PrimaryExpression>> <components: ComponentOrSwizzleSpecifier?> => match components {
Some(components) => apply_components(expression.0, expression.1, components),
None => expression.0
},
};
// XXX: this production rule is used for statements preceded by attributes (wesl `@if` extension).
// because this sentence is not LR: `@attribute(x) (y) = z`.
// so we only allow attributes on statements that don't start with `(`.
// see https://github.com/wgsl-tooling-wg/wesl-rs/issues/162
LhsExpressionNoParen: Expression = {
#[cfg(not(feature = "imports"))]
<l: @L> <ident: Ident> <r: @R> <components: ComponentOrSwizzleSpecifier?> => {
let span = (l..r).into();
let expression = Expression::TypeOrIdentifier(TypeExpression { ident, template_args: None });
match components {
Some(components) => apply_components(expression, span, components),
None => expression
}
},
#[cfg(feature = "imports")]
<l: @L> <path: ModulePath?> <ident: Ident> <r: @R> <components: ComponentOrSwizzleSpecifier?> => {
let span = (l..r).into();
let expression = Expression::TypeOrIdentifier(TypeExpression { path, ident, template_args: None });
match components {
Some(components) => apply_components(expression, span, components),
None => expression
}
},
"*" <operand: Spanned<LhsExpression>> => Expression::Unary(UnaryExpression {
operator: UnaryOperator::Indirection, operand
}),
"&" <operand: Spanned<LhsExpression>> => Expression::Unary(UnaryExpression {
operator: UnaryOperator::AddressOf, operand
}),
};
LhsExpressionParen: Expression = {
<l: @L> "(" <expression: Spanned<LhsExpression>> ")" <r: @R> <components: ComponentOrSwizzleSpecifier?> => {
let span = (l..r).into();
let expression = Expression::Parenthesized(ParenthesizedExpression { expression });
match components {
Some(components) => apply_components(expression, span, components),
None => expression
}
},
};
LhsExpression: Expression = {
LhsExpressionNoParen => <>,
LhsExpressionParen => <>,
};
MultiplicativeExpression: Expression = {
UnaryExpression,
<left: Spanned<MultiplicativeExpression>> <operator: MultiplicativeOperator> <right: Spanned<UnaryExpression>> => Expression::Binary(BinaryExpression {
operator, left, right
}),
};
MultiplicativeOperator: BinaryOperator = {
"*" => BinaryOperator::Multiplication,
"/" => BinaryOperator::Division,
"%" => BinaryOperator::Remainder,
};
AdditiveExpression: Expression = {
MultiplicativeExpression,
<left: Spanned<AdditiveExpression>> <operator: AdditiveOperator> <right: Spanned<MultiplicativeExpression>> => Expression::Binary(BinaryExpression {
operator, left, right
}),
// this handles the special case `x = 5--7` which must be parsed as `x = 5 - (-7)`
<left: Spanned<AdditiveExpression>> <l: @L> "--" <right: Spanned<MultiplicativeExpression>> => {
let outer_span = Span::new((l+1)..right.span().range().end);
let right = Spanned::new(Expression::Unary(UnaryExpression {
operator: UnaryOperator::Negation, operand: right,
}), outer_span);
Expression::Binary(BinaryExpression {
operator: BinaryOperator::Subtraction, left, right
})
},
};
AdditiveOperator: BinaryOperator = {
"+" => BinaryOperator::Addition,
"-" => BinaryOperator::Subtraction,
};
ShiftExpression: Expression = {
AdditiveExpression,
<left: Spanned<UnaryExpression>> "<<" <right: Spanned<UnaryExpression>> => Expression::Binary(BinaryExpression {
operator: BinaryOperator::ShiftLeft, left, right
}),
<left: Spanned<UnaryExpression>> ">>" <right: Spanned<UnaryExpression>> => Expression::Binary(BinaryExpression {
operator: BinaryOperator::ShiftRight, left, right
}),
};
RelationalExpression: Expression = {
ShiftExpression,
<left: Spanned<ShiftExpression>> "<" <right: Spanned<ShiftExpression>> => Expression::Binary(BinaryExpression {
operator: BinaryOperator::LessThan, left, right
}),
<left: Spanned<ShiftExpression>> ">" <right: Spanned<ShiftExpression>> => Expression::Binary(BinaryExpression {
operator: BinaryOperator::GreaterThan, left, right
}),
<left: Spanned<ShiftExpression>> "<=" <right: Spanned<ShiftExpression>> => Expression::Binary(BinaryExpression {
operator: BinaryOperator::LessThanEqual, left, right
}),
<left: Spanned<ShiftExpression>> ">=" <right: Spanned<ShiftExpression>> => Expression::Binary(BinaryExpression {
operator: BinaryOperator::GreaterThanEqual, left, right
}),
<left: Spanned<ShiftExpression>> "==" <right: Spanned<ShiftExpression>> => Expression::Binary(BinaryExpression {
operator: BinaryOperator::Equality, left, right
}),
<left: Spanned<ShiftExpression>> "!=" <right: Spanned<ShiftExpression>> => Expression::Binary(BinaryExpression {
operator: BinaryOperator::Inequality, left, right
}),
};
ShortCircuitAndExpression: Expression = {
RelationalExpression,
<left: Spanned<ShortCircuitAndExpression>> "&&" <right: Spanned<RelationalExpression>> => Expression::Binary(BinaryExpression {
operator: BinaryOperator::ShortCircuitAnd, left, right
}),
};
ShortCircuitOrExpression: Expression = {
RelationalExpression,
<left: Spanned<ShortCircuitOrExpression>> "||" <right: Spanned<RelationalExpression>> => Expression::Binary(BinaryExpression {
operator: BinaryOperator::ShortCircuitOr, left, right
}),
};
BinaryOrExpression: Expression = {
UnaryExpression,
<left: Spanned<BinaryOrExpression>> "|" <right: Spanned<UnaryExpression>> => Expression::Binary(BinaryExpression {
operator: BinaryOperator::BitwiseOr, left, right
}),
};
BinaryAndExpression: Expression = {
UnaryExpression,
<left: Spanned<BinaryAndExpression>> "&" <right: Spanned<UnaryExpression>> => Expression::Binary(BinaryExpression {
operator: BinaryOperator::BitwiseAnd, left, right
}),
};
BinaryXorExpression: Expression = {
UnaryExpression,
<left: Spanned<BinaryXorExpression>> "^" <right: Spanned<UnaryExpression>> => Expression::Binary(BinaryExpression {
operator: BinaryOperator::BitwiseXor, left, right
}),
};
BitwiseExpression: Expression = {
<left: Spanned<BinaryAndExpression>> "&" <right: Spanned<UnaryExpression>> => Expression::Binary(BinaryExpression {
operator: BinaryOperator::BitwiseAnd, left, right
}),
<left: Spanned<BinaryOrExpression>> "|" <right: Spanned<UnaryExpression>> => Expression::Binary(BinaryExpression {
operator: BinaryOperator::BitwiseOr, left, right
}),
<left: Spanned<BinaryXorExpression>> "^" <right: Spanned<UnaryExpression>> => Expression::Binary(BinaryExpression {
operator: BinaryOperator::BitwiseXor, left, right
}),
};
pub Expression: Expression = {
RelationalExpression,
<left: Spanned<ShortCircuitOrExpression>> "||" <right: Spanned<RelationalExpression>> => Expression::Binary(BinaryExpression {
operator: BinaryOperator::ShortCircuitOr, left, right
}),
<left: Spanned<ShortCircuitAndExpression>> "&&" <right: Spanned<RelationalExpression>> => Expression::Binary(BinaryExpression {
operator: BinaryOperator::ShortCircuitAnd, left, right
}),
BitwiseExpression,
};
ExpressionNode: ExpressionNode = Spanned<Expression>;
// 9. STATEMENTS
// https://www.w3.org/TR/WGSL/#statements
CompoundStatement: CompoundStatement = {
<attributes: AttributeNode*> "{" <statements: StatementNode*> "}" => CompoundStatement {
attributes, statements
},
};
#[cfg(not(feature = "attributes"))]
AssignmentStatement: AssignmentStatement = {
<lhs: Spanned<LhsExpression>> "=" <rhs: ExpressionNode> => AssignmentStatement {
operator: AssignmentOperator::Equal, lhs, rhs
},
<lhs: Spanned<LhsExpression>> <operator: CompoundAssignmentOperator> <rhs: ExpressionNode> => AssignmentStatement {
operator, lhs, rhs
},
<l: @R> "_" <r: @R> "=" <rhs: ExpressionNode> => {
let lhs = Expression::TypeOrIdentifier(Ident::new("_".to_string()).into());
let span = (l..r).into();
let lhs = Spanned::new(lhs, span);
AssignmentStatement { operator: AssignmentOperator::Equal, lhs, rhs }
},
};
#[cfg(feature = "attributes")]
AssignmentStatement: AssignmentStatement = {
<attributes: AttributeNode*> <lhs: Spanned<LhsExpressionNoParen>> "=" <rhs: ExpressionNode> => AssignmentStatement {
attributes, operator: AssignmentOperator::Equal, lhs, rhs
},
<attributes: AttributeNode*> <lhs: Spanned<LhsExpressionNoParen>> <operator: CompoundAssignmentOperator> <rhs: ExpressionNode> => AssignmentStatement {
attributes, operator, lhs, rhs
},
<lhs: Spanned<LhsExpressionParen>> "=" <rhs: ExpressionNode> => AssignmentStatement {
attributes: vec![], operator: AssignmentOperator::Equal, lhs, rhs
},
<lhs: Spanned<LhsExpressionParen>> <operator: CompoundAssignmentOperator> <rhs: ExpressionNode> => AssignmentStatement {
attributes: vec![], operator, lhs, rhs
},
<attributes: AttributeNode*> <l: @L> "_" <r: @R> "=" <rhs: ExpressionNode> => {
let lhs = Expression::TypeOrIdentifier(Ident::new("_".to_string()).into());
let span = (l..r).into();
let lhs = Spanned::new(lhs, span);
AssignmentStatement { attributes, operator: AssignmentOperator::Equal, lhs, rhs }
},
};
CompoundAssignmentOperator: AssignmentOperator = {
"+=" => AssignmentOperator::PlusEqual,
"-=" => AssignmentOperator::MinusEqual,
"*=" => AssignmentOperator::TimesEqual,
"/=" => AssignmentOperator::DivisionEqual,
"%=" => AssignmentOperator::ModuloEqual,
"&=" => AssignmentOperator::AndEqual,
"|=" => AssignmentOperator::OrEqual,
"^=" => AssignmentOperator::XorEqual,
">>=" => AssignmentOperator::ShiftRightAssign,
"<<=" => AssignmentOperator::ShiftLeftAssign,
};
IncrementStatement: IncrementStatement = {
#[cfg(not(feature = "attributes"))]
<expression: Spanned<LhsExpression>> "++" => IncrementStatement {
expression
},
#[cfg(feature = "attributes")]
<attributes: AttributeNode*> <expression: Spanned<LhsExpressionNoParen>> "++" => IncrementStatement {
attributes, expression
},
#[cfg(feature = "attributes")]
<expression: Spanned<LhsExpressionParen>> "++" => IncrementStatement {
attributes: vec![], expression
},
};
DecrementStatement: DecrementStatement = {
#[cfg(not(feature = "attributes"))]
<expression: Spanned<LhsExpression>> "--" => DecrementStatement {
expression
},
#[cfg(feature = "attributes")]
<attributes: AttributeNode*> <expression: Spanned<LhsExpressionNoParen>> "--" => DecrementStatement {
attributes, expression
},
#[cfg(feature = "attributes")]
<expression: Spanned<LhsExpressionParen>> "--" => DecrementStatement {
attributes: vec![], expression
},
};
IfStatement: IfStatement = {
<attributes: AttributeNode*> <if_clause: IfClause> <else_if_clauses: ElseIfClause*> <else_clause: ElseClause?> => IfStatement {
attributes, if_clause, else_if_clauses, else_clause
},
};
IfClause: IfClause = "if" <expression: ExpressionNode> <body: CompoundStatement> => IfClause {
expression, body
};
ElseIfClause: ElseIfClause = {
#[cfg(not(feature = "attributes"))]
"else" "if" <expression: ExpressionNode> <body: CompoundStatement> => ElseIfClause {
expression, body
},
#[cfg(feature = "attributes")]
"else" "if" <expression: ExpressionNode> <body: CompoundStatement> => ElseIfClause {
attributes: vec![], expression, body
},
} ;
ElseClause: ElseClause = {
#[cfg(not(feature = "attributes"))]
"else" <body: CompoundStatement> => ElseClause {
body
},
#[cfg(feature = "attributes")]
"else" <body: CompoundStatement> => ElseClause {
attributes: vec![], body
},
}
SwitchStatement: SwitchStatement = {
<attributes: AttributeNode*> "switch" <expression: ExpressionNode> <body: SwitchBody> => {
let (body_attributes, clauses) = body;
SwitchStatement {
attributes, expression, body_attributes, clauses
}
},
};
SwitchBody: (Vec<AttributeNode>, Vec<SwitchClause>) = {
<AttributeNode*> "{" <SwitchClause+> "}",
};
SwitchClause: SwitchClause = {
CaseClause,
DefaultAloneClause,
};
CaseClause: SwitchClause = {
#[cfg(not(feature = "attributes"))]
"case" <case_selectors: CaseSelectors> ":"? <body: CompoundStatement> => SwitchClause {
case_selectors, body
},
#[cfg(feature = "attributes")]
<attributes: AttributeNode*> "case" <case_selectors: CaseSelectors> ":"? <body: CompoundStatement> => SwitchClause {
attributes, case_selectors, body
},
};
DefaultAloneClause: SwitchClause = {
#[cfg(not(feature = "attributes"))]
"default" ":"? <body: CompoundStatement> => SwitchClause {
case_selectors: vec![CaseSelector::Default], body
},
#[cfg(feature = "attributes")]
<attributes: AttributeNode*> "default" ":"? <body: CompoundStatement> => SwitchClause {
attributes, case_selectors: vec![CaseSelector::Default], body
},
};
CaseSelectors: Vec<CaseSelector> = {
Comma1<CaseSelector>,
};
CaseSelector: CaseSelector = {
"default" => CaseSelector::Default,
ExpressionNode => CaseSelector::Expression(<>),
};
LoopStatement: LoopStatement = {
<attributes: AttributeNode*> "loop" <body_attributes: AttributeNode*> "{" <statements: StatementNode*> <continuing: ContinuingStatement?> "}" => {
let body = CompoundStatement { attributes: body_attributes, statements };
LoopStatement {
attributes, body, continuing
}
},
};
ForStatement: ForStatement = {
<attributes: AttributeNode*> "for" "(" <header: ForHeader> ")" <body: CompoundStatement> => {
let (initializer, condition, update) = header;
ForStatement {
attributes, initializer, condition, update, body
}
},
};
ForHeader: (Option<StatementNode>, Option<ExpressionNode>, Option<StatementNode>) = {
<ForInit?> ";" <ExpressionNode?> ";" <ForUpdate?>,
};
ForInit: StatementNode = {
<statement: WithSpan<VariableOrValueStatement>> => {
let (statement, span) = statement;
Spanned::new(Statement::Declaration(statement), span)
},
<statement: WithSpan<VariableUpdatingStatement>> => {
let (statement, span) = statement;
Spanned::new(statement, span)
},
<statement: WithSpan<FuncCallStatement>> => {
let (statement, span) = statement;
Spanned::new(Statement::FunctionCall(statement), span)
},
};
ForUpdate: StatementNode = {
<statement: WithSpan<VariableUpdatingStatement>> => {
let (statement, span) = statement;
Spanned::new(statement, span)
},
<statement: WithSpan<FuncCallStatement>> => {
let (statement, span) = statement;
Spanned::new(Statement::FunctionCall(statement), span)
},
};
WhileStatement: WhileStatement = {
<attributes: AttributeNode*> "while" <condition: ExpressionNode> <body: CompoundStatement> => WhileStatement {
attributes, condition, body
},
};
BreakStatement: BreakStatement = {
#[cfg(not(feature = "attributes"))]
"break" => BreakStatement {},
#[cfg(feature = "attributes")]
<attributes: AttributeNode*> "break" => BreakStatement {
attributes
},
};
BreakIfStatement: BreakIfStatement = {
#[cfg(not(feature = "attributes"))]
"break" "if" <expression: ExpressionNode> ";" => BreakIfStatement {
expression
},
#[cfg(feature = "attributes")]
<attributes: AttributeNode*> "break" "if" <expression: ExpressionNode> ";" => BreakIfStatement {
attributes, expression
},
};
ContinueStatement: ContinueStatement = {
#[cfg(not(feature = "attributes"))]
"continue" => ContinueStatement {},
#[cfg(feature = "attributes")]
<attributes: AttributeNode*> "continue" => ContinueStatement {
attributes
},
};
ContinuingStatement: ContinuingStatement = {
#[cfg(not(feature = "attributes"))]
"continuing" <ContinuingCompoundStatement>,
#[cfg(feature = "attributes")]
<AttributeNode*> "continuing" <ContinuingCompoundStatement> => {
let (attributes, mut statement) = (<>);
statement.attributes = attributes;
statement
},
};
ContinuingCompoundStatement: ContinuingStatement = {
#[cfg(not(feature = "attributes"))]
<attributes: AttributeNode*> "{" <statements: StatementNode*> <break_if: BreakIfStatement?> "}" => {
let body = CompoundStatement { attributes, statements };
ContinuingStatement { body, break_if }
},
#[cfg(feature = "attributes")]
<attributes: AttributeNode*> "{" <statements: StatementNode*> <break_if: BreakIfStatement?> "}" => {
let body = CompoundStatement { attributes, statements };
ContinuingStatement { attributes: Vec::new(), body, break_if }
},
};
ReturnStatement: ReturnStatement = {
#[cfg(not(feature = "attributes"))]
"return" <expression: ExpressionNode?> => ReturnStatement {
expression
},
#[cfg(feature = "attributes")]
<attributes: AttributeNode*> "return" <expression: ExpressionNode?> => ReturnStatement {
attributes, expression
},
};
DiscardStatement: DiscardStatement = {
#[cfg(not(feature = "attributes"))]
"discard" => DiscardStatement {},
#[cfg(feature = "attributes")]
<attributes: AttributeNode*> "discard" => DiscardStatement {
attributes
},
};
FuncCallStatement: FunctionCallStatement = {
#[cfg(not(feature = "attributes"))]
<call: CallPhrase> => FunctionCallStatement {
call
},
#[cfg(feature = "attributes")]
<attributes: AttributeNode*> <call: CallPhrase> => FunctionCallStatement {
attributes, call
},
}
ConstAssertStatement: ConstAssertStatement = {
#[cfg(not(feature = "attributes"))]
"const_assert" <expression: ExpressionNode> => ConstAssertStatement {
expression
},
#[cfg(feature = "attributes")]
<attributes: AttributeNode*> "const_assert" <expression: ExpressionNode> => ConstAssertStatement {
attributes, expression
},
};
pub Statement: Statement = {
";" => Statement::Void,
<ReturnStatement> ";" => Statement::Return(<>),
<IfStatement> => Statement::If(<>),
<SwitchStatement> => Statement::Switch(<>),
<LoopStatement> => Statement::Loop(<>),
<ForStatement> => Statement::For(<>),
<WhileStatement> => Statement::While(<>),
<FuncCallStatement> ";" => Statement::FunctionCall(<>),
<VariableOrValueStatement> ";" => Statement::Declaration(<>),
<BreakStatement> ";" => Statement::Break(<>),
<ContinueStatement> ";" => Statement::Continue(<>),
<DiscardStatement> ";" => Statement::Discard(<>),
<VariableUpdatingStatement> ";" => <>,
<CompoundStatement> => Statement::Compound(<>),
<ConstAssertStatement> ";" => Statement::ConstAssert(<>),
};
StatementNode: StatementNode = Spanned<Statement>;
VariableUpdatingStatement: Statement = {
AssignmentStatement => Statement::Assignment(<>),
IncrementStatement => Statement::Increment(<>),
DecrementStatement => Statement::Decrement(<>),
};
// 10. FUNCTIONS
// https://www.w3.org/TR/WGSL/#functions
FunctionDecl: Function = {
<attributes: AttributeNode*> <header: FunctionHeader> <body: CompoundStatement> => {
let (ident, parameters, return_attributes, return_type) = header;
Function { attributes, ident, parameters, return_attributes, return_type, body }
},
};
FunctionHeader: (Ident, Vec<FormalParameter>, Vec<AttributeNode>, Option<TypeExpression>) = {
"fn" <ident: Ident> "(" <parameters: ParamList?> ")" <ret: ("->" <AttributeNode*> <TemplateElaboratedIdent>)?> => {
let (return_attributes, return_type) = ret.map(|(return_attributes, return_type)| {
(return_attributes, Some(return_type))
}).unwrap_or_default();
let parameters = parameters.unwrap_or_default();
(ident, parameters, return_attributes, return_type)
},
};
ParamList: Vec<FormalParameter> = {
Comma1<Param>,
};
Param: FormalParameter = {
<attributes: AttributeNode*> <ident: Ident> ":" <ty: TypeSpecifier> => FormalParameter {
attributes, ident, ty
},
};
// 11. ATTRIBUTES
// https://www.w3.org/TR/WGSL/#attributes
Attribute: Attribute = {
<l: @L> "@" <name: IdentPatternToken> <arguments: ArgumentExpressionList?> <r: @R> =>? {
parse_attribute(name, arguments)
.map_err(|e| lalrpop_util::ParseError::User{ error: (l, e, r) })
},
<l: @L> "@" <name: Keyword> <arguments: ArgumentExpressionList?> <r: @R> =>? {
parse_attribute(name, arguments)
.map_err(|e| lalrpop_util::ParseError::User{ error: (l, e, r) })
},
<l: @L> "@" <name: ReservedWord> <arguments: ArgumentExpressionList?> <r: @R> =>? {
parse_attribute(name, arguments)
.map_err(|e| lalrpop_util::ParseError::User{ error: (l, e, r) })
},
};
AttributeNode: AttributeNode = Spanned<Attribute>;
DiagnosticControl: (DiagnosticSeverity, String) = {
"(" <SeverityControlName> "," <DiagnosticRuleName> ","? ")",
};
// EXTENSION: WESL-IMPORTS
// https://github.com/wgsl-tooling-wg/wesl-spec/blob/imports-update/Imports.md
// date: 2025-01-18, hash: 2db8e7f681087db6bdcd4a254963deb5c0159775
#[cfg(feature = "imports")]
PathIdent: String = {
IdentPatternToken => <>,
ReservedWord => <>,
// All keywords and reserved words are allowed module names, except import-related ones (import, as, self, super)
// The "else" keyword is the only exception, because of a LR(1) conflict: if () {} else::call_fn();
// see wesl-spec#127 and wesl-spec#76
"alias" => <>.to_string(),
"break" => <>.to_string(),
"case" => <>.to_string(),
"const" => <>.to_string(),
"const_assert" => <>.to_string(),
"continue" => <>.to_string(),
"continuing" => <>.to_string(),
"default" => <>.to_string(),
"diagnostic" => <>.to_string(),
"discard" => <>.to_string(),
"enable" => <>.to_string(),
"false" => <>.to_string(),
"fn" => <>.to_string(),
"for" => <>.to_string(),
"if" => <>.to_string(),
"let" => <>.to_string(),
"loop" => <>.to_string(),
"override" => <>.to_string(),
"requires" => <>.to_string(),
"return" => <>.to_string(),
"struct" => <>.to_string(),
"switch" => <>.to_string(),
"true" => <>.to_string(),
"var" => <>.to_string(),
"while" => <>.to_string(),
};
#[cfg(feature = "imports")]
pub ImportStatement: ImportStatement = {
#[cfg(not(feature = "attributes"))]
"import" <path: ModulePath?> <content: ImportContent> ";" => {
ImportStatement { path, content }
},
#[cfg(feature = "attributes")]
<attributes: AttributeNode*> "import" <path: ModulePath?> <content: ImportContent> ";" => {
ImportStatement { attributes, path, content }
},
};
#[cfg(feature = "imports")]
PathOrigin: PathOrigin = {
// NOTE: `self::` is no longer part of the spec.
"self" "::" => {
PathOrigin::Relative(0)
},
"super" "::" <parents: ("super" "::")*> => {
let n = 1 + parents.len();
PathOrigin::Relative(n)
},
"package" "::" => {
PathOrigin::Absolute
},
<name: PathIdent> "::" => {
PathOrigin::Package(name)
},
};
#[inline]
#[cfg(feature = "imports")]
ModulePath: ModulePath = {
<origin: PathOrigin> <components: (<PathIdent> "::")*> => ModulePath {
origin, components
},
};
#[cfg(feature = "imports")]
Import: Import = {
<path: (<PathIdent> "::")*> <item: ImportItem> => Import {
path, content: ImportContent::Item(item)
},
<path: (<PathIdent> "::")+> <coll: ImportCollection> => Import {
path, content: ImportContent::Collection(coll)
},
};
#[cfg(feature = "imports")]
ImportContent: ImportContent = {
ImportItem => ImportContent::Item(<>),
ImportCollection => ImportContent::Collection(<>),
};
#[cfg(feature = "imports")]
ImportItem: ImportItem = {
<ident: PathIdent> <rename: ("as" <PathIdent>)?> => ImportItem {
ident: Ident::new(ident), rename: rename.map(Ident::new)
},
};
#[cfg(feature = "imports")]
ImportCollection: Vec<Import> = {
"{" <Comma1<Import>> "}"
};
// ===================
// === End grammar ===
// ===================
// ----------- below: macros -----------
Comma<T>: Vec<T> = {
<mut v: (<T> ",")*> <e: T?> => match e {
Some(e) => {
v.push(e);
v
}
None => v,
}
};
Spanned<T>: Spanned<T> = <@L> <T> <@R> => {
let (l, t, r) = (<>);
let span = (l..r).into();
Spanned::new(t, span)
};
WithSpan<T>: (T, Span) = <@L> <T> <@R> => {
let (l, t, r) = (<>);
let span = (l..r).into();
(t, span)
};
Comma1<T> = Sep1<",", T>;
Sep1<S, T>: Vec<T> = {
<mut v: (<T> S)*> <e: T> S? => {
v.push(e);
v
}
};