blob: 26e7e6f35a7556b282d3ca82e3d7b216996318cd [file] [log] [blame]
/*
* Copyright (C) 2022 Apple Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
* THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "Parser.h"
#include "ParserPrivate.h"
#include "config.h"
#include "AST/Attribute.h"
#include "AST/Expression.h"
#include "AST/Expressions/IdentifierExpression.h"
#include "AST/Expressions/LiteralExpressions.h"
#include "AST/Expressions/StructureAccess.h"
#include "AST/Expressions/TypeConversion.h"
#include "AST/GlobalDecl.h"
#include "AST/Statement.h"
#include "AST/Statements/AssignmentStatement.h"
#include "AST/Statements/CompoundStatement.h"
#include "AST/Statements/ReturnStatement.h"
#include "AST/StructureDecl.h"
#include "Lexer.h"
#include <wtf/text/StringBuilder.h>
namespace WGSL {
#define START_PARSE() \
auto _startOfElementPosition = m_lexer.currentPosition();
#define CURRENT_SOURCE_SPAN() \
SourceSpan(_startOfElementPosition, m_lexer.currentPosition())
#define RETURN_NODE(type, ...) \
do { \
AST::type astNodeResult(CURRENT_SOURCE_SPAN(), __VA_ARGS__); \
return { WTFMove(astNodeResult) }; \
} while (false)
// Passing 0 arguments beyond the type to RETURN_NODE is invalid because of a stupid limitation of the C preprocessor
#define RETURN_NODE_NO_ARGS(type) \
do { \
AST::type astNodeResult(CURRENT_SOURCE_SPAN()); \
return { WTFMove(astNodeResult) }; \
} while (false)
#define RETURN_NODE_REF(type, ...) \
return { makeUniqueRef<AST::type>(CURRENT_SOURCE_SPAN(), __VA_ARGS__) };
// Passing 0 arguments beyond the type to RETURN_NODE_REF is invalid because of a stupid limitation of the C preprocessor
#define RETURN_NODE_REF_NO_ARGS(type) \
return { makeUniqueRef<AST::type>(CURRENT_SOURCE_SPAN()) };
#define FAIL(string) \
return makeUnexpected(Error(string, CURRENT_SOURCE_SPAN()));
// Warning: cannot use the do..while trick because it defines a new identifier named `name`.
// So do not use after an if/for/while without braces.
#define PARSE(name, element, ...) \
auto name##Expected = parse##element(__VA_ARGS__); \
if (!name##Expected) \
return makeUnexpected(name##Expected.error()); \
auto& name = *name##Expected;
// Warning: cannot use the do..while trick because it defines a new identifier named `name`.
// So do not use after an if/for/while without braces.
#define CONSUME_TYPE_NAMED(name, type) \
auto name##Expected = consumeType(TokenType::type); \
if (!name##Expected) { \
StringBuilder builder; \
builder.append("Expected a "); \
builder.append(toString(TokenType::type)); \
builder.append(", but got a "); \
builder.append(toString(name##Expected.error())); \
FAIL(builder.toString()); \
} \
auto& name = *name##Expected;
#define CONSUME_TYPE(type) \
do { \
auto expectedToken = consumeType(TokenType::type); \
if (!expectedToken) { \
StringBuilder builder; \
builder.append("Expected a "); \
builder.append(toString(TokenType::type)); \
builder.append(", but got a "); \
builder.append(toString(expectedToken.error())); \
FAIL(builder.toString()); \
} \
} while (false)
template<typename Lexer>
Expected<AST::ShaderModule, Error> parse(const String& wgsl)
{
Lexer lexer(wgsl);
Parser parser(lexer);
return parser.parseShader();
}
Expected<AST::ShaderModule, Error> parseLChar(const String& wgsl)
{
return parse<Lexer<LChar>>(wgsl);
}
Expected<AST::ShaderModule, Error> parseUChar(const String& wgsl)
{
return parse<Lexer<UChar>>(wgsl);
}
template<typename Lexer>
Expected<Token, TokenType> Parser<Lexer>::consumeType(TokenType type)
{
if (current().m_type == type) {
Expected<Token, TokenType> result = { m_current };
m_current = m_lexer.lex();
return result;
}
return makeUnexpected(current().m_type);
}
template<typename Lexer>
void Parser<Lexer>::consume()
{
m_current = m_lexer.lex();
}
template<typename Lexer>
Expected<AST::ShaderModule, Error> Parser<Lexer>::parseShader()
{
START_PARSE();
Vector<UniqueRef<AST::GlobalDirective>> directives;
// FIXME: parse directives here.
Vector<UniqueRef<AST::GlobalDecl>> decls;
while (!m_lexer.isAtEndOfFile()) {
PARSE(globalDecl, GlobalDecl)
decls.append(WTFMove(globalDecl));
}
RETURN_NODE(ShaderModule, WTFMove(directives), WTFMove(decls));
}
template<typename Lexer>
Expected<UniqueRef<AST::GlobalDecl>, Error> Parser<Lexer>::parseGlobalDecl()
{
START_PARSE();
PARSE(attributes, Attributes);
switch (current().m_type) {
case TokenType::KeywordStruct: {
PARSE(structDecl, StructDecl, WTFMove(attributes));
return { makeUniqueRef<AST::StructDecl>(WTFMove(structDecl)) };
}
case TokenType::KeywordVar: {
PARSE(varDecl, GlobalVariableDecl, WTFMove(attributes));
CONSUME_TYPE(Semicolon);
return { makeUniqueRef<AST::GlobalVariableDecl>(WTFMove(varDecl)) };
}
case TokenType::KeywordFn: {
PARSE(fn, FunctionDecl, WTFMove(attributes));
return { makeUniqueRef<AST::FunctionDecl>(WTFMove(fn)) };
}
default:
FAIL("Trying to parse a GlobalDecl, expected 'var', 'fn', or 'struct'."_s);
}
}
template<typename Lexer>
Expected<AST::Attributes, Error> Parser<Lexer>::parseAttributes()
{
AST::Attributes attributes;
while (current().m_type == TokenType::Attribute) {
PARSE(firstAttribute, Attribute);
attributes.append(WTFMove(firstAttribute));
}
return { WTFMove(attributes) };
}
template<typename Lexer>
Expected<UniqueRef<AST::Attribute>, Error> Parser<Lexer>::parseAttribute()
{
START_PARSE();
CONSUME_TYPE(Attribute);
CONSUME_TYPE_NAMED(ident, Identifier);
if (ident.m_ident == "group"_s) {
CONSUME_TYPE(ParenLeft);
// FIXME: should more kinds of literals be accepted here?
CONSUME_TYPE_NAMED(id, IntegerLiteral);
CONSUME_TYPE(ParenRight);
RETURN_NODE_REF(GroupAttribute, id.m_literalValue);
}
if (ident.m_ident == "binding"_s) {
CONSUME_TYPE(ParenLeft);
// FIXME: should more kinds of literals be accepted here?
CONSUME_TYPE_NAMED(id, IntegerLiteral);
CONSUME_TYPE(ParenRight);
RETURN_NODE_REF(BindingAttribute, id.m_literalValue);
}
if (ident.m_ident == "stage"_s) {
CONSUME_TYPE(ParenLeft);
CONSUME_TYPE_NAMED(stage, Identifier);
CONSUME_TYPE(ParenRight);
if (stage.m_ident == "compute"_s)
RETURN_NODE_REF(StageAttribute, AST::StageAttribute::Stage::Compute);
if (stage.m_ident == "vertex"_s)
RETURN_NODE_REF(StageAttribute, AST::StageAttribute::Stage::Vertex);
if (stage.m_ident == "fragment"_s)
RETURN_NODE_REF(StageAttribute, AST::StageAttribute::Stage::Fragment);
FAIL("Invalid stage attribute, the only options are 'compute', 'vertex', 'fragment'."_s);
}
if (ident.m_ident == "location"_s) {
CONSUME_TYPE(ParenLeft);
// FIXME: should more kinds of literals be accepted here?
CONSUME_TYPE_NAMED(id, IntegerLiteral);
CONSUME_TYPE(ParenRight);
RETURN_NODE_REF(LocationAttribute, id.m_literalValue);
}
if (ident.m_ident == "builtin"_s) {
CONSUME_TYPE(ParenLeft);
CONSUME_TYPE_NAMED(name, Identifier);
CONSUME_TYPE(ParenRight);
RETURN_NODE_REF(BuiltinAttribute, name.m_ident);
}
FAIL("Unknown attribute, the only supported attributes are 'group', 'binding', 'stage'."_s);
}
template<typename Lexer>
Expected<AST::StructDecl, Error> Parser<Lexer>::parseStructDecl(AST::Attributes&& attributes)
{
START_PARSE();
CONSUME_TYPE(KeywordStruct);
CONSUME_TYPE_NAMED(name, Identifier);
CONSUME_TYPE(BraceLeft);
Vector<UniqueRef<AST::StructMember>> members;
while (current().m_type != TokenType::BraceRight) {
PARSE(member, StructMember);
members.append(makeUniqueRef<AST::StructMember>(WTFMove(member)));
}
CONSUME_TYPE(BraceRight);
RETURN_NODE(StructDecl, name.m_ident, WTFMove(members), WTFMove(attributes));
}
template<typename Lexer>
Expected<AST::StructMember, Error> Parser<Lexer>::parseStructMember()
{
START_PARSE();
PARSE(attributes, Attributes);
CONSUME_TYPE_NAMED(name, Identifier);
CONSUME_TYPE(Colon);
PARSE(type, TypeDecl);
CONSUME_TYPE(Semicolon);
RETURN_NODE(StructMember, name.m_ident, WTFMove(type), WTFMove(attributes));
}
template<typename Lexer>
Expected<UniqueRef<AST::TypeDecl>, Error> Parser<Lexer>::parseTypeDecl()
{
START_PARSE();
if (current().m_type == TokenType::KeywordI32) {
consume();
RETURN_NODE_REF(NamedType, StringView { "i32"_s });
}
if (current().m_type == TokenType::KeywordF32) {
consume();
RETURN_NODE_REF(NamedType, StringView { "f32"_s });
}
if (current().m_type == TokenType::KeywordU32) {
consume();
RETURN_NODE_REF(NamedType, StringView { "u32"_s });
}
if (current().m_type == TokenType::KeywordBool) {
consume();
RETURN_NODE_REF(NamedType, StringView { "bool"_s });
}
if (current().m_type == TokenType::Identifier) {
CONSUME_TYPE_NAMED(name, Identifier);
return parseTypeDeclAfterIdentifier(WTFMove(name.m_ident), _startOfElementPosition);
}
FAIL("Tried parsing a type and it did not start with an identifier"_s);
}
template<typename Lexer>
Expected<UniqueRef<AST::TypeDecl>, Error> Parser<Lexer>::parseTypeDeclAfterIdentifier(StringView&& name, SourcePosition _startOfElementPosition)
{
if (auto kind = AST::ParameterizedType::stringViewToKind(name)) {
CONSUME_TYPE(LT);
PARSE(elementType, TypeDecl);
CONSUME_TYPE(GT);
RETURN_NODE_REF(ParameterizedType, *kind, WTFMove(elementType));
}
RETURN_NODE_REF(NamedType, WTFMove(name));
}
template<typename Lexer>
Expected<AST::GlobalVariableDecl, Error> Parser<Lexer>::parseGlobalVariableDecl(AST::Attributes&& attributes)
{
START_PARSE();
CONSUME_TYPE(KeywordVar);
std::unique_ptr<AST::VariableQualifier> maybeQualifier = nullptr;
if (current().m_type == TokenType::LT) {
PARSE(variableQualifier, VariableQualifier);
maybeQualifier = WTF::makeUnique<AST::VariableQualifier>(WTFMove(variableQualifier));
}
CONSUME_TYPE_NAMED(name, Identifier);
std::unique_ptr<AST::TypeDecl> maybeType = nullptr;
if (current().m_type == TokenType::Colon) {
consume();
PARSE(typeDecl, TypeDecl);
maybeType = typeDecl.moveToUniquePtr();
}
std::unique_ptr<AST::Expression> maybeInitializer = nullptr;
// FIXME: initializer
RETURN_NODE(GlobalVariableDecl, name.m_ident, WTFMove(maybeQualifier), WTFMove(maybeType), WTFMove(maybeInitializer), WTFMove(attributes));
}
template<typename Lexer>
Expected<AST::VariableQualifier, Error> Parser<Lexer>::parseVariableQualifier()
{
START_PARSE();
CONSUME_TYPE(LT);
PARSE(storageClass, StorageClass);
// FIXME: verify that Read is the correct default in all cases.
AST::AccessMode accessMode = AST::AccessMode::Read;
if (current().m_type == TokenType::Comma) {
consume();
PARSE(actualAccessMode, AccessMode);
accessMode = actualAccessMode;
}
CONSUME_TYPE(GT);
RETURN_NODE(VariableQualifier, storageClass, accessMode);
}
template<typename Lexer>
Expected<AST::StorageClass, Error> Parser<Lexer>::parseStorageClass()
{
START_PARSE();
if (current().m_type == TokenType::KeywordFunction) {
consume();
return { AST::StorageClass::Function };
}
if (current().m_type == TokenType::KeywordPrivate) {
consume();
return { AST::StorageClass::Private };
}
if (current().m_type == TokenType::KeywordWorkgroup) {
consume();
return { AST::StorageClass::Workgroup };
}
if (current().m_type == TokenType::KeywordUniform) {
consume();
return { AST::StorageClass::Uniform };
}
if (current().m_type == TokenType::KeywordStorage) {
consume();
return { AST::StorageClass::Storage };
}
FAIL("Expected one of 'function'/'private'/'storage'/'uniform'/'workgroup'"_s);
}
template<typename Lexer>
Expected<AST::AccessMode, Error> Parser<Lexer>::parseAccessMode()
{
START_PARSE();
if (current().m_type == TokenType::KeywordRead) {
consume();
return { AST::AccessMode::Read };
}
if (current().m_type == TokenType::KeywordWrite) {
consume();
return { AST::AccessMode::Write };
}
if (current().m_type == TokenType::KeywordReadWrite) {
consume();
return { AST::AccessMode::ReadWrite };
}
FAIL("Expected one of 'read'/'write'/'read_write'"_s);
}
template<typename Lexer>
Expected<AST::FunctionDecl, Error> Parser<Lexer>::parseFunctionDecl(AST::Attributes&& attributes)
{
START_PARSE();
CONSUME_TYPE(KeywordFn);
CONSUME_TYPE_NAMED(name, Identifier);
CONSUME_TYPE(ParenLeft);
Vector<UniqueRef<AST::Parameter>> parameters;
while (current().m_type != TokenType::ParenRight) {
PARSE(parameter, Parameter);
parameters.append(makeUniqueRef<AST::Parameter>(WTFMove(parameter)));
}
CONSUME_TYPE(ParenRight);
AST::Attributes returnAttributes;
std::unique_ptr<AST::TypeDecl> maybeReturnType = nullptr;
if (current().m_type == TokenType::Arrow) {
consume();
PARSE(parsedReturnAttributes, Attributes);
returnAttributes = WTFMove(parsedReturnAttributes);
PARSE(type, TypeDecl);
maybeReturnType = type.moveToUniquePtr();
}
PARSE(body, CompoundStatement);
RETURN_NODE(FunctionDecl, name.m_ident, WTFMove(parameters), WTFMove(maybeReturnType), WTFMove(body), WTFMove(attributes), WTFMove(returnAttributes));
}
template<typename Lexer>
Expected<AST::Parameter, Error> Parser<Lexer>::parseParameter()
{
START_PARSE();
PARSE(attributes, Attributes);
CONSUME_TYPE_NAMED(name, Identifier)
CONSUME_TYPE(Colon);
PARSE(type, TypeDecl);
RETURN_NODE(Parameter, name.m_ident, WTFMove(type), WTFMove(attributes));
}
template<typename Lexer>
Expected<UniqueRef<AST::Statement>, Error> Parser<Lexer>::parseStatement()
{
START_PARSE();
switch (current().m_type) {
case TokenType::BraceLeft: {
PARSE(compoundStmt, CompoundStatement);
return { makeUniqueRef<AST::CompoundStatement>(WTFMove(compoundStmt)) };
}
case TokenType::Semicolon: {
consume();
Vector<UniqueRef<AST::Statement>> statements;
return { makeUniqueRef<AST::CompoundStatement>(CURRENT_SOURCE_SPAN(), WTFMove(statements)) };
}
case TokenType::KeywordReturn: {
PARSE(returnStmt, ReturnStatement);
CONSUME_TYPE(Semicolon);
return { makeUniqueRef<AST::ReturnStatement>(WTFMove(returnStmt)) };
}
case TokenType::Identifier: {
// FIXME: there will be other cases here eventually for function calls
PARSE(lhs, LHSExpression);
CONSUME_TYPE(Equal);
PARSE(rhs, Expression);
CONSUME_TYPE(Semicolon);
RETURN_NODE_REF(AssignmentStatement, lhs.moveToUniquePtr(), WTFMove(rhs));
}
default:
FAIL("Not a valid statement"_s);
}
}
template<typename Lexer>
Expected<AST::CompoundStatement, Error> Parser<Lexer>::parseCompoundStatement()
{
START_PARSE();
CONSUME_TYPE(BraceLeft);
Vector<UniqueRef<AST::Statement>> statements;
while (current().m_type != TokenType::BraceRight) {
PARSE(stmt, Statement);
statements.append(WTFMove(stmt));
}
CONSUME_TYPE(BraceRight);
RETURN_NODE(CompoundStatement, WTFMove(statements));
}
template<typename Lexer>
Expected<AST::ReturnStatement, Error> Parser<Lexer>::parseReturnStatement()
{
START_PARSE();
CONSUME_TYPE(KeywordReturn);
if (current().m_type == TokenType::Semicolon) {
RETURN_NODE(ReturnStatement, { });
}
PARSE(expr, ShortCircuitOrExpression);
RETURN_NODE(ReturnStatement, expr.moveToUniquePtr());
}
template<typename Lexer>
Expected<UniqueRef<AST::Expression>, Error> Parser<Lexer>::parseShortCircuitOrExpression()
{
// FIXME: fill in
return parseRelationalExpression();
}
template<typename Lexer>
Expected<UniqueRef<AST::Expression>, Error> Parser<Lexer>::parseRelationalExpression()
{
// FIXME: fill in
return parseShiftExpression();
}
template<typename Lexer>
Expected<UniqueRef<AST::Expression>, Error> Parser<Lexer>::parseShiftExpression()
{
// FIXME: fill in
return parseAdditiveExpression();
}
template<typename Lexer>
Expected<UniqueRef<AST::Expression>, Error> Parser<Lexer>::parseAdditiveExpression()
{
// FIXME: fill in
return parseMultiplicativeExpression();
}
template<typename Lexer>
Expected<UniqueRef<AST::Expression>, Error> Parser<Lexer>::parseMultiplicativeExpression()
{
// FIXME: fill in
return parseUnaryExpression();
}
template<typename Lexer>
Expected<UniqueRef<AST::Expression>, Error> Parser<Lexer>::parseUnaryExpression()
{
// FIXME: fill in
return parseSingularExpression();
}
template<typename Lexer>
Expected<UniqueRef<AST::Expression>, Error> Parser<Lexer>::parseSingularExpression()
{
START_PARSE();
PARSE(base, PrimaryExpression);
return parsePostfixExpression(WTFMove(base), _startOfElementPosition);
}
template<typename Lexer>
Expected<UniqueRef<AST::Expression>, Error> Parser<Lexer>::parsePostfixExpression(UniqueRef<AST::Expression>&& base, SourcePosition startPosition)
{
START_PARSE();
UniqueRef<AST::Expression> expr = WTFMove(base);
// FIXME: add the case for array/vector/matrix access
while (current().m_type == TokenType::Period) {
consume();
CONSUME_TYPE_NAMED(fieldName, Identifier);
SourceSpan span(startPosition, m_lexer.currentPosition());
expr = makeUniqueRef<AST::StructureAccess>(span, WTFMove(expr), fieldName.m_ident);
}
return { WTFMove(expr) };
}
template<typename Lexer>
Expected<UniqueRef<AST::Expression>, Error> Parser<Lexer>::parsePrimaryExpression()
{
START_PARSE();
switch (current().m_type) {
case TokenType::ParenLeft: {
consume();
PARSE(expr, Expression);
CONSUME_TYPE(ParenRight);
return { WTFMove(expr) };
}
case TokenType::Identifier: {
CONSUME_TYPE_NAMED(ident, Identifier);
if (ident.m_ident == "true"_s) {
RETURN_NODE_REF(BoolLiteral, true);
}
if (ident.m_ident == "false"_s) {
RETURN_NODE_REF(BoolLiteral, false);
}
if (current().m_type == TokenType::LT || current().m_type == TokenType::ParenLeft) {
PARSE(type, TypeDeclAfterIdentifier, WTFMove(ident.m_ident), _startOfElementPosition);
PARSE(arguments, ArgumentExpressionList);
RETURN_NODE_REF(TypeConversion, WTFMove(type), WTFMove(arguments));
}
RETURN_NODE_REF(IdentifierExpression, ident.m_ident);
}
case TokenType::IntegerLiteralSigned: {
CONSUME_TYPE_NAMED(lit, IntegerLiteralSigned);
RETURN_NODE_REF(Int32Literal, lit.m_literalValue);
}
case TokenType::IntegerLiteralUnsigned: {
CONSUME_TYPE_NAMED(lit, IntegerLiteralUnsigned);
RETURN_NODE_REF(Uint32Literal, lit.m_literalValue);
}
case TokenType::DecimalFloatLiteral: {
CONSUME_TYPE_NAMED(lit, DecimalFloatLiteral);
RETURN_NODE_REF(Float32Literal, lit.m_literalValue);
}
// FIXME: HexFloatLiteral and IntegerLiteral
default:
break;
}
FAIL("Expected one of '(', a literal, or an identifier"_s);
}
template<typename Lexer>
Expected<UniqueRef<AST::Expression>, Error> Parser<Lexer>::parseExpression()
{
// FIXME: Fill in
return parseRelationalExpression();
}
template<typename Lexer>
Expected<UniqueRef<AST::Expression>, Error> Parser<Lexer>::parseLHSExpression()
{
START_PARSE();
// FIXME: Add the possibility of a prefix
PARSE(base, CoreLHSExpression);
return parsePostfixExpression(WTFMove(base), _startOfElementPosition);
}
template<typename Lexer>
Expected<UniqueRef<AST::Expression>, Error> Parser<Lexer>::parseCoreLHSExpression()
{
START_PARSE();
switch (current().m_type) {
case TokenType::ParenLeft: {
consume();
PARSE(expr, LHSExpression);
CONSUME_TYPE(ParenRight);
return { WTFMove(expr) };
}
case TokenType::Identifier: {
CONSUME_TYPE_NAMED(ident, Identifier);
RETURN_NODE_REF(IdentifierExpression, ident.m_ident);
}
default:
break;
}
FAIL("Tried to parse the left-hand side of an assignment and failed"_s);
}
template<typename Lexer>
Expected<Vector<UniqueRef<AST::Expression>>, Error> Parser<Lexer>::parseArgumentExpressionList()
{
START_PARSE();
CONSUME_TYPE(ParenLeft);
Vector<UniqueRef<AST::Expression>> arguments;
while (current().m_type != TokenType::ParenRight) {
PARSE(expr, Expression);
arguments.append(WTFMove(expr));
if (current().m_type != TokenType::ParenRight) {
CONSUME_TYPE(Comma);
}
}
CONSUME_TYPE(ParenRight);
return { WTFMove(arguments) };
}
} // namespace WGSL