/* * 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. ``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 * 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. */ #import "config.h" #import "AssignmentStatement.h" #import "IdentifierExpression.h" #import "Lexer.h" #import "LiteralExpressions.h" #import "Parser.h" #import "ReturnStatement.h" #import "StructureAccess.h" #import "TypeConversion.h" #import "WGSL.h" #import #import @interface WGSLParserTests : XCTestCase @end @implementation WGSLParserTests - (void)testParsingStruct { auto shader = WGSL::parseLChar("struct B {\n" " a: i32;\n" "}"); if (!shader.has_value()) dataLogLn(shader.error()); XCTAssert(shader.has_value()); XCTAssert(!shader->directives().size()); XCTAssert(shader->structs().size() == 1); XCTAssert(shader->globalVars().size() == 0); XCTAssert(shader->functions().size() == 0); WGSL::AST::StructDecl& str = shader->structs()[0]; XCTAssert(str.name() == "B"_s); XCTAssert(str.attributes().isEmpty()); XCTAssert(str.members().size() == 1); XCTAssert(str.members()[0]->attributes().isEmpty()); XCTAssert(str.members()[0]->name() == "a"_s); XCTAssert(str.members()[0]->type().isNamed()); WGSL::AST::NamedType& memberType = downcast(str.members()[0]->type()); XCTAssert(memberType.name() == "i32"_s); } - (void)testParsingGlobalVariable { auto shader = WGSL::parseLChar("@group(0) @binding(0)\n" "var x: B;\n"); if (!shader.has_value()) dataLogLn(shader.error()); XCTAssert(shader.has_value()); XCTAssert(!shader->directives().size()); XCTAssert(shader->structs().size() == 0); XCTAssert(shader->globalVars().size() == 1); XCTAssert(shader->functions().size() == 0); WGSL::AST::GlobalVariableDecl& var = shader->globalVars()[0]; XCTAssert(var.attributes().size() == 2); XCTAssert(var.attributes()[0]->isGroup()); XCTAssert(downcast(var.attributes()[0].get()).group() == 0); XCTAssert(var.attributes()[1]->isBinding()); XCTAssert(downcast(var.attributes()[1].get()).binding() == 0); XCTAssert(var.name() == "x"_s); XCTAssert(var.maybeQualifier()); XCTAssert(var.maybeQualifier()->storageClass() == WGSL::AST::StorageClass::Storage); XCTAssert(var.maybeQualifier()->accessMode() == WGSL::AST::AccessMode::ReadWrite); XCTAssert(var.maybeTypeDecl()); XCTAssert(var.maybeTypeDecl()->isNamed()); WGSL::AST::NamedType& namedType = downcast(*var.maybeTypeDecl()); XCTAssert(namedType.name() == "B"_s); XCTAssert(!var.maybeInitializer()); } - (void)testParsingFunctionDecl { auto shader = WGSL::parseLChar("@stage(compute)\n" "fn main() {\n" " x.a = 42i;\n" "}"); if (!shader.has_value()) dataLogLn(shader.error()); XCTAssert(shader.has_value()); XCTAssert(!shader->directives().size()); XCTAssert(shader->structs().size() == 0); XCTAssert(shader->globalVars().size() == 0); XCTAssert(shader->functions().size() == 1); WGSL::AST::FunctionDecl& func = shader->functions()[0]; XCTAssert(func.attributes().size() == 1); XCTAssert(func.attributes()[0]->isStage()); XCTAssert(downcast(func.attributes()[0].get()).stage() == WGSL::AST::StageAttribute::Stage::Compute); XCTAssert(func.name() == "main"_s); XCTAssert(func.parameters().size() == 0); XCTAssert(func.returnAttributes().isEmpty()); XCTAssert(func.maybeReturnType() == nullptr); XCTAssert(func.body().statements().size() == 1); XCTAssert(func.body().statements()[0]->isAssignment()); WGSL::AST::AssignmentStatement& stmt = downcast(func.body().statements()[0].get()); XCTAssert(stmt.maybeLhs()); XCTAssert(stmt.maybeLhs()->isStructureAccess()); WGSL::AST::StructureAccess& structAccess = downcast(*stmt.maybeLhs()); XCTAssert(structAccess.base()->isIdentifier()); WGSL::AST::IdentifierExpression base = downcast(structAccess.base().get()); XCTAssert(base.identifier() == "x"_s); XCTAssert(structAccess.fieldName() == "a"_s); XCTAssert(stmt.rhs().isInt32Literal()); WGSL::AST::Int32Literal& rhs = downcast(stmt.rhs()); XCTAssert(rhs.value() == 42); } - (void)testTrivialGraphicsShader { auto shader = WGSL::parseLChar("@stage(vertex)\n" "fn vertexShader(@location(0) x: vec4) -> @builtin(position) vec4 {\n" " return x;\n" "}\n\n" "@stage(fragment)\n" "fn fragmentShader() -> @location(0) vec4 {\n" " return vec4(0.4, 0.4, 0.8, 1.0);\n" "}"); if (!shader.has_value()) dataLogLn(shader.error()); XCTAssert(shader.has_value()); XCTAssert(!shader->directives().size()); XCTAssert(shader->structs().size() == 0); XCTAssert(shader->globalVars().size() == 0); XCTAssert(shader->functions().size() == 2); { WGSL::AST::FunctionDecl& func = shader->functions()[0]; XCTAssert(func.attributes().size() == 1); XCTAssert(func.attributes()[0]->isStage()); XCTAssert(downcast(func.attributes()[0].get()).stage() == WGSL::AST::StageAttribute::Stage::Vertex); XCTAssert(func.name() == "vertexShader"_s); XCTAssert(func.parameters().size() == 1); XCTAssert(func.parameters()[0]->name() == "x"_s); XCTAssert(func.parameters()[0]->attributes().size() == 1); XCTAssert(func.parameters()[0]->attributes()[0]->isLocation()); XCTAssert(downcast(func.parameters()[0]->attributes()[0].get()).location() == 0); XCTAssert(func.parameters()[0]->type().isParameterized()); WGSL::AST::ParameterizedType& paramType = downcast(func.parameters()[0]->type()); XCTAssert(paramType.base() == WGSL::AST::ParameterizedType::Base::Vec4); XCTAssert(paramType.elementType().isNamed()); XCTAssert(downcast(paramType.elementType()).name() == "f32"_s); XCTAssert(func.returnAttributes().size() == 1); XCTAssert(func.returnAttributes()[0]->isBuiltin()); XCTAssert(downcast(func.returnAttributes()[0].get()).name() == "position"_s); XCTAssert(func.maybeReturnType()); XCTAssert(func.maybeReturnType()->isParameterized()); XCTAssert(func.body().statements().size() == 1); XCTAssert(func.body().statements()[0]->isReturn()); WGSL::AST::ReturnStatement& stmt = downcast(func.body().statements()[0].get()); XCTAssert(stmt.maybeExpression()); XCTAssert(stmt.maybeExpression()->isIdentifier()); } { WGSL::AST::FunctionDecl& func = shader->functions()[1]; XCTAssert(func.attributes().size() == 1); XCTAssert(func.attributes()[0]->isStage()); XCTAssert(downcast(func.attributes()[0].get()).stage() == WGSL::AST::StageAttribute::Stage::Fragment); XCTAssert(func.name() == "fragmentShader"_s); XCTAssert(func.parameters().size() == 0); XCTAssert(func.returnAttributes().size() == 1); XCTAssert(func.returnAttributes()[0]->isLocation()); XCTAssert(downcast(func.returnAttributes()[0].get()).location() == 0); XCTAssert(func.maybeReturnType()); XCTAssert(func.maybeReturnType()->isParameterized()); XCTAssert(func.body().statements().size() == 1); XCTAssert(func.body().statements()[0]->isReturn()); WGSL::AST::ReturnStatement& stmt = downcast(func.body().statements()[0].get()); XCTAssert(stmt.maybeExpression()); XCTAssert(stmt.maybeExpression()->isTypeConversion()); WGSL::AST::TypeConversion& expr = downcast(*stmt.maybeExpression()); XCTAssert(expr.typeDecl()->isParameterized()); XCTAssert(expr.arguments().size() == 4); XCTAssert(expr.arguments()[0]->isFloat32Literal()); XCTAssert(expr.arguments()[1]->isFloat32Literal()); XCTAssert(expr.arguments()[2]->isFloat32Literal()); XCTAssert(expr.arguments()[3]->isFloat32Literal()); } } @end