// // Copyright (C) 2016 Google, Inc. // Copyright (C) 2016 LunarG, 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: // // Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // // 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. // // Neither the name of Google, Inc., nor the names of its // contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND 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 THE // COPYRIGHT HOLDERS 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. // // // This is a set of mutually recursive methods implementing the HLSL grammar. // Generally, each returns // - through an argument: a type specifically appropriate to which rule it // recognized // - through the return value: true/false to indicate whether or not it // recognized its rule // // As much as possible, only grammar recognition should happen in this file, // with all other work being farmed out to hlslParseHelper.cpp, which in turn // will build the AST. // // The next token, yet to be "accepted" is always sitting in 'token'. // When a method says it accepts a rule, that means all tokens involved // in the rule will have been consumed, and none left in 'token'. // #include "hlslTokens.h" #include "hlslGrammar.h" #include "hlslAttributes.h" namespace glslang { // Root entry point to this recursive decent parser. // Return true if compilation unit was successfully accepted. bool HlslGrammar::parse() { advanceToken(); return acceptCompilationUnit(); } void HlslGrammar::expected(const char* syntax) { parseContext.error(token.loc, "Expected", syntax, ""); } void HlslGrammar::unimplemented(const char* error) { parseContext.error(token.loc, "Unimplemented", error, ""); } // Only process the next token if it is an identifier. // Return true if it was an identifier. bool HlslGrammar::acceptIdentifier(HlslToken& idToken) { if (peekTokenClass(EHTokIdentifier)) { idToken = token; advanceToken(); return true; } // Even though "sample", "bool", "float", etc keywords (for types, interpolation modifiers), // they ARE still accepted as identifiers. This is not a dense space: e.g, "void" is not a // valid identifier, nor is "linear". This code special cases the known instances of this, so // e.g, "int sample;" or "float float;" is accepted. Other cases can be added here if needed. TString* idString = nullptr; switch (peek()) { case EHTokSample: idString = NewPoolTString("sample"); break; case EHTokHalf: idString = NewPoolTString("half"); break; case EHTokBool: idString = NewPoolTString("bool"); break; case EHTokFloat: idString = NewPoolTString("float"); break; case EHTokDouble: idString = NewPoolTString("double"); break; case EHTokInt: idString = NewPoolTString("int"); break; case EHTokUint: idString = NewPoolTString("uint"); break; case EHTokMin16float: idString = NewPoolTString("min16float"); break; case EHTokMin10float: idString = NewPoolTString("min10float"); break; case EHTokMin16int: idString = NewPoolTString("min16int"); break; case EHTokMin12int: idString = NewPoolTString("min12int"); break; default: return false; } token.string = idString; token.tokenClass = EHTokIdentifier; token.symbol = nullptr; idToken = token; advanceToken(); return true; } // compilationUnit // : list of externalDeclaration // | SEMICOLONS // bool HlslGrammar::acceptCompilationUnit() { TIntermNode* unitNode = nullptr; while (! peekTokenClass(EHTokNone)) { // HLSL allows semicolons between global declarations, e.g, between functions. if (acceptTokenClass(EHTokSemicolon)) continue; // externalDeclaration if (! acceptDeclaration(unitNode)) return false; } // set root of AST if (unitNode && !unitNode->getAsAggregate()) unitNode = intermediate.growAggregate(nullptr, unitNode); intermediate.setTreeRoot(unitNode); return true; } // sampler_state // : LEFT_BRACE [sampler_state_assignment ... ] RIGHT_BRACE // // sampler_state_assignment // : sampler_state_identifier EQUAL value SEMICOLON // // sampler_state_identifier // : ADDRESSU // | ADDRESSV // | ADDRESSW // | BORDERCOLOR // | FILTER // | MAXANISOTROPY // | MAXLOD // | MINLOD // | MIPLODBIAS // bool HlslGrammar::acceptSamplerState() { // TODO: this should be genericized to accept a list of valid tokens and // return token/value pairs. Presently it is specific to texture values. if (! acceptTokenClass(EHTokLeftBrace)) return true; parseContext.warn(token.loc, "unimplemented", "immediate sampler state", ""); do { // read state name HlslToken state; if (! acceptIdentifier(state)) break; // end of list // FXC accepts any case TString stateName = *state.string; std::transform(stateName.begin(), stateName.end(), stateName.begin(), ::tolower); if (! acceptTokenClass(EHTokAssign)) { expected("assign"); return false; } if (stateName == "minlod" || stateName == "maxlod") { if (! peekTokenClass(EHTokIntConstant)) { expected("integer"); return false; } TIntermTyped* lod = nullptr; if (! acceptLiteral(lod)) // should never fail, since we just looked for an integer return false; } else if (stateName == "maxanisotropy") { if (! peekTokenClass(EHTokIntConstant)) { expected("integer"); return false; } TIntermTyped* maxAnisotropy = nullptr; if (! acceptLiteral(maxAnisotropy)) // should never fail, since we just looked for an integer return false; } else if (stateName == "filter") { HlslToken filterMode; if (! acceptIdentifier(filterMode)) { expected("filter mode"); return false; } } else if (stateName == "addressu" || stateName == "addressv" || stateName == "addressw") { HlslToken addrMode; if (! acceptIdentifier(addrMode)) { expected("texture address mode"); return false; } } else if (stateName == "miplodbias") { TIntermTyped* lodBias = nullptr; if (! acceptLiteral(lodBias)) { expected("lod bias"); return false; } } else if (stateName == "bordercolor") { return false; } else { expected("texture state"); return false; } // SEMICOLON if (! acceptTokenClass(EHTokSemicolon)) { expected("semicolon"); return false; } } while (true); if (! acceptTokenClass(EHTokRightBrace)) return false; return true; } // sampler_declaration_dx9 // : SAMPLER identifier EQUAL sampler_type sampler_state // bool HlslGrammar::acceptSamplerDeclarationDX9(TType& /*type*/) { if (! acceptTokenClass(EHTokSampler)) return false; // TODO: remove this when DX9 style declarations are implemented. unimplemented("Direct3D 9 sampler declaration"); // read sampler name HlslToken name; if (! acceptIdentifier(name)) { expected("sampler name"); return false; } if (! acceptTokenClass(EHTokAssign)) { expected("="); return false; } return false; } // declaration // : sampler_declaration_dx9 post_decls SEMICOLON // | fully_specified_type declarator_list SEMICOLON // | fully_specified_type identifier function_parameters post_decls compound_statement // function definition // | fully_specified_type identifier sampler_state post_decls compound_statement // sampler definition // | typedef declaration // // declarator_list // : declarator COMMA declarator COMMA declarator... // zero or more declarators // // declarator // : identifier array_specifier post_decls // | identifier array_specifier post_decls EQUAL assignment_expression // | identifier function_parameters post_decls // function prototype // // Parsing has to go pretty far in to know whether it's a variable, prototype, or // function definition, so the implementation below doesn't perfectly divide up the grammar // as above. (The 'identifier' in the first item in init_declarator list is the // same as 'identifier' for function declarations.) // // This can generate more than one subtree, one per initializer or a function body. // All initializer subtrees are put in their own aggregate node, making one top-level // node for all the initializers. Each function created is a top-level node to grow // into the passed-in nodeList. // // If 'nodeList' is passed in as non-null, it must an aggregate to extend for // each top-level node the declaration creates. Otherwise, if only one top-level // node in generated here, that is want is returned in nodeList. // bool HlslGrammar::acceptDeclaration(TIntermNode*& nodeList) { bool declarator_list = false; // true when processing comma separation // attributes TAttributeMap attributes; acceptAttributes(attributes); // typedef bool typedefDecl = acceptTokenClass(EHTokTypedef); TType declaredType; // DX9 sampler declaration use a different syntax // DX9 shaders need to run through HLSL compiler (fxc) via a back compat mode, it isn't going to // be possible to simultaneously compile D3D10+ style shaders and DX9 shaders. If we want to compile DX9 // HLSL shaders, this will have to be a master level switch // As such, the sampler keyword in D3D10+ turns into an automatic sampler type, and is commonly used // For that reason, this line is commented out // if (acceptSamplerDeclarationDX9(declaredType)) // return true; // fully_specified_type if (! acceptFullySpecifiedType(declaredType, nodeList)) return false; // identifier HlslToken idToken; TIntermAggregate* initializers = nullptr; while (acceptIdentifier(idToken)) { if (peekTokenClass(EHTokLeftParen)) { // looks like function parameters TString* fnName = idToken.string; // Potentially rename shader entry point function. No-op most of the time. parseContext.renameShaderFunction(fnName); // function_parameters TFunction& function = *new TFunction(fnName, declaredType); if (!acceptFunctionParameters(function)) { expected("function parameter list"); return false; } // post_decls acceptPostDecls(function.getWritableType().getQualifier()); // compound_statement (function body definition) or just a prototype? if (peekTokenClass(EHTokLeftBrace)) { if (declarator_list) parseContext.error(idToken.loc, "function body can't be in a declarator list", "{", ""); if (typedefDecl) parseContext.error(idToken.loc, "function body can't be in a typedef", "{", ""); return acceptFunctionDefinition(function, nodeList, attributes); } else { if (typedefDecl) parseContext.error(idToken.loc, "function typedefs not implemented", "{", ""); parseContext.handleFunctionDeclarator(idToken.loc, function, true); } } else { // A variable declaration. Fix the storage qualifier if it's a global. if (declaredType.getQualifier().storage == EvqTemporary && parseContext.symbolTable.atGlobalLevel()) declaredType.getQualifier().storage = EvqUniform; // We can handle multiple variables per type declaration, so // the number of types can expand when arrayness is different. TType variableType; variableType.shallowCopy(declaredType); // recognize array_specifier TArraySizes* arraySizes = nullptr; acceptArraySpecifier(arraySizes); // Fix arrayness in the variableType if (declaredType.isImplicitlySizedArray()) { // Because "int[] a = int[2](...), b = int[3](...)" makes two arrays a and b // of different sizes, for this case sharing the shallow copy of arrayness // with the parseType oversubscribes it, so get a deep copy of the arrayness. variableType.newArraySizes(declaredType.getArraySizes()); } if (arraySizes || variableType.isArray()) { // In the most general case, arrayness is potentially coming both from the // declared type and from the variable: "int[] a[];" or just one or the other. // Merge it all to the variableType, so all arrayness is part of the variableType. parseContext.arrayDimMerge(variableType, arraySizes); } // samplers accept immediate sampler state if (variableType.getBasicType() == EbtSampler) { if (! acceptSamplerState()) return false; } // post_decls acceptPostDecls(variableType.getQualifier()); // EQUAL assignment_expression TIntermTyped* expressionNode = nullptr; if (acceptTokenClass(EHTokAssign)) { if (typedefDecl) parseContext.error(idToken.loc, "can't have an initializer", "typedef", ""); if (! acceptAssignmentExpression(expressionNode)) { expected("initializer"); return false; } } // TODO: things scoped within an annotation need their own name space; // TODO: strings are not yet handled. if (variableType.getBasicType() != EbtString && parseContext.getAnnotationNestingLevel() == 0) { if (typedefDecl) parseContext.declareTypedef(idToken.loc, *idToken.string, variableType); else if (variableType.getBasicType() == EbtBlock) parseContext.declareBlock(idToken.loc, variableType, idToken.string); else { if (variableType.getQualifier().storage == EvqUniform && ! variableType.containsOpaque()) { // this isn't really an individual variable, but a member of the $Global buffer parseContext.growGlobalUniformBlock(idToken.loc, variableType, *idToken.string); } else { // Declare the variable and add any initializer code to the AST. // The top-level node is always made into an aggregate, as that's // historically how the AST has been. initializers = intermediate.growAggregate(initializers, parseContext.declareVariable(idToken.loc, *idToken.string, variableType, expressionNode), idToken.loc); } } } } if (acceptTokenClass(EHTokComma)) { declarator_list = true; continue; } }; // The top-level initializer node is a sequence. if (initializers != nullptr) initializers->setOperator(EOpSequence); // Add the initializers' aggregate to the nodeList we were handed. if (nodeList) nodeList = intermediate.growAggregate(nodeList, initializers); else nodeList = initializers; // SEMICOLON if (! acceptTokenClass(EHTokSemicolon)) { // This may have been a false detection of what appeared to be a declaration, but // was actually an assignment such as "float = 4", where "float" is an identifier. // We put the token back to let further parsing happen for cases where that may // happen. This errors on the side of caution, and mostly triggers the error. if (peek() == EHTokAssign || peek() == EHTokLeftBracket || peek() == EHTokDot || peek() == EHTokComma) recedeToken(); else expected(";"); return false; } return true; } // control_declaration // : fully_specified_type identifier EQUAL expression // bool HlslGrammar::acceptControlDeclaration(TIntermNode*& node) { node = nullptr; // fully_specified_type TType type; if (! acceptFullySpecifiedType(type)) return false; // filter out type casts if (peekTokenClass(EHTokLeftParen)) { recedeToken(); return false; } // identifier HlslToken idToken; if (! acceptIdentifier(idToken)) { expected("identifier"); return false; } // EQUAL TIntermTyped* expressionNode = nullptr; if (! acceptTokenClass(EHTokAssign)) { expected("="); return false; } // expression if (! acceptExpression(expressionNode)) { expected("initializer"); return false; } node = parseContext.declareVariable(idToken.loc, *idToken.string, type, expressionNode); return true; } // fully_specified_type // : type_specifier // | type_qualifier type_specifier // bool HlslGrammar::acceptFullySpecifiedType(TType& type) { TIntermNode* nodeList = nullptr; return acceptFullySpecifiedType(type, nodeList); } bool HlslGrammar::acceptFullySpecifiedType(TType& type, TIntermNode*& nodeList) { // type_qualifier TQualifier qualifier; qualifier.clear(); if (! acceptQualifier(qualifier)) return false; TSourceLoc loc = token.loc; // type_specifier if (! acceptType(type, nodeList)) { // If this is not a type, we may have inadvertently gone down a wrong path // by parsing "sample", which can be treated like either an identifier or a // qualifier. Back it out, if we did. if (qualifier.sample) recedeToken(); return false; } if (type.getBasicType() == EbtBlock) { // the type was a block, which set some parts of the qualifier parseContext.mergeQualifiers(type.getQualifier(), qualifier); // further, it can create an anonymous instance of the block if (peekTokenClass(EHTokSemicolon)) parseContext.declareBlock(loc, type); } else { // Some qualifiers are set when parsing the type. Merge those with // whatever comes from acceptQualifier. assert(qualifier.layoutFormat == ElfNone); qualifier.layoutFormat = type.getQualifier().layoutFormat; qualifier.precision = type.getQualifier().precision; if (type.getQualifier().storage == EvqVaryingOut || type.getQualifier().storage == EvqBuffer) { qualifier.storage = type.getQualifier().storage; qualifier.readonly = type.getQualifier().readonly; } type.getQualifier() = qualifier; } return true; } // type_qualifier // : qualifier qualifier ... // // Zero or more of these, so this can't return false. // bool HlslGrammar::acceptQualifier(TQualifier& qualifier) { do { switch (peek()) { case EHTokStatic: qualifier.storage = parseContext.symbolTable.atGlobalLevel() ? EvqGlobal : EvqTemporary; break; case EHTokExtern: // TODO: no meaning in glslang? break; case EHTokShared: // TODO: hint break; case EHTokGroupShared: qualifier.storage = EvqShared; break; case EHTokUniform: qualifier.storage = EvqUniform; break; case EHTokConst: qualifier.storage = EvqConst; break; case EHTokVolatile: qualifier.volatil = true; break; case EHTokLinear: qualifier.smooth = true; break; case EHTokCentroid: qualifier.centroid = true; break; case EHTokNointerpolation: qualifier.flat = true; break; case EHTokNoperspective: qualifier.nopersp = true; break; case EHTokSample: qualifier.sample = true; break; case EHTokRowMajor: qualifier.layoutMatrix = ElmColumnMajor; break; case EHTokColumnMajor: qualifier.layoutMatrix = ElmRowMajor; break; case EHTokPrecise: qualifier.noContraction = true; break; case EHTokIn: qualifier.storage = EvqIn; break; case EHTokOut: qualifier.storage = EvqOut; break; case EHTokInOut: qualifier.storage = EvqInOut; break; case EHTokLayout: if (! acceptLayoutQualifierList(qualifier)) return false; continue; case EHTokGloballyCoherent: qualifier.coherent = true; break; // GS geometries: these are specified on stage input variables, and are an error (not verified here) // for output variables. case EHTokPoint: qualifier.storage = EvqIn; if (!parseContext.handleInputGeometry(token.loc, ElgPoints)) return false; break; case EHTokLine: qualifier.storage = EvqIn; if (!parseContext.handleInputGeometry(token.loc, ElgLines)) return false; break; case EHTokTriangle: qualifier.storage = EvqIn; if (!parseContext.handleInputGeometry(token.loc, ElgTriangles)) return false; break; case EHTokLineAdj: qualifier.storage = EvqIn; if (!parseContext.handleInputGeometry(token.loc, ElgLinesAdjacency)) return false; break; case EHTokTriangleAdj: qualifier.storage = EvqIn; if (!parseContext.handleInputGeometry(token.loc, ElgTrianglesAdjacency)) return false; break; default: return true; } advanceToken(); } while (true); } // layout_qualifier_list // : LAYOUT LEFT_PAREN layout_qualifier COMMA layout_qualifier ... RIGHT_PAREN // // layout_qualifier // : identifier // | identifier EQUAL expression // // Zero or more of these, so this can't return false. // bool HlslGrammar::acceptLayoutQualifierList(TQualifier& qualifier) { if (! acceptTokenClass(EHTokLayout)) return false; // LEFT_PAREN if (! acceptTokenClass(EHTokLeftParen)) return false; do { // identifier HlslToken idToken; if (! acceptIdentifier(idToken)) break; // EQUAL expression if (acceptTokenClass(EHTokAssign)) { TIntermTyped* expr; if (! acceptConditionalExpression(expr)) { expected("expression"); return false; } parseContext.setLayoutQualifier(idToken.loc, qualifier, *idToken.string, expr); } else parseContext.setLayoutQualifier(idToken.loc, qualifier, *idToken.string); // COMMA if (! acceptTokenClass(EHTokComma)) break; } while (true); // RIGHT_PAREN if (! acceptTokenClass(EHTokRightParen)) { expected(")"); return false; } return true; } // template_type // : FLOAT // | DOUBLE // | INT // | DWORD // | UINT // | BOOL // bool HlslGrammar::acceptTemplateVecMatBasicType(TBasicType& basicType) { switch (peek()) { case EHTokFloat: basicType = EbtFloat; break; case EHTokDouble: basicType = EbtDouble; break; case EHTokInt: case EHTokDword: basicType = EbtInt; break; case EHTokUint: basicType = EbtUint; break; case EHTokBool: basicType = EbtBool; break; default: return false; } advanceToken(); return true; } // vector_template_type // : VECTOR // | VECTOR LEFT_ANGLE template_type COMMA integer_literal RIGHT_ANGLE // bool HlslGrammar::acceptVectorTemplateType(TType& type) { if (! acceptTokenClass(EHTokVector)) return false; if (! acceptTokenClass(EHTokLeftAngle)) { // in HLSL, 'vector' alone means float4. new(&type) TType(EbtFloat, EvqTemporary, 4); return true; } TBasicType basicType; if (! acceptTemplateVecMatBasicType(basicType)) { expected("scalar type"); return false; } // COMMA if (! acceptTokenClass(EHTokComma)) { expected(","); return false; } // integer if (! peekTokenClass(EHTokIntConstant)) { expected("literal integer"); return false; } TIntermTyped* vecSize; if (! acceptLiteral(vecSize)) return false; const int vecSizeI = vecSize->getAsConstantUnion()->getConstArray()[0].getIConst(); new(&type) TType(basicType, EvqTemporary, vecSizeI); if (vecSizeI == 1) type.makeVector(); if (!acceptTokenClass(EHTokRightAngle)) { expected("right angle bracket"); return false; } return true; } // matrix_template_type // : MATRIX // | MATRIX LEFT_ANGLE template_type COMMA integer_literal COMMA integer_literal RIGHT_ANGLE // bool HlslGrammar::acceptMatrixTemplateType(TType& type) { if (! acceptTokenClass(EHTokMatrix)) return false; if (! acceptTokenClass(EHTokLeftAngle)) { // in HLSL, 'matrix' alone means float4x4. new(&type) TType(EbtFloat, EvqTemporary, 0, 4, 4); return true; } TBasicType basicType; if (! acceptTemplateVecMatBasicType(basicType)) { expected("scalar type"); return false; } // COMMA if (! acceptTokenClass(EHTokComma)) { expected(","); return false; } // integer rows if (! peekTokenClass(EHTokIntConstant)) { expected("literal integer"); return false; } TIntermTyped* rows; if (! acceptLiteral(rows)) return false; // COMMA if (! acceptTokenClass(EHTokComma)) { expected(","); return false; } // integer cols if (! peekTokenClass(EHTokIntConstant)) { expected("literal integer"); return false; } TIntermTyped* cols; if (! acceptLiteral(cols)) return false; new(&type) TType(basicType, EvqTemporary, 0, rows->getAsConstantUnion()->getConstArray()[0].getIConst(), cols->getAsConstantUnion()->getConstArray()[0].getIConst()); if (!acceptTokenClass(EHTokRightAngle)) { expected("right angle bracket"); return false; } return true; } // layout_geometry // : LINESTREAM // | POINTSTREAM // | TRIANGLESTREAM // bool HlslGrammar::acceptOutputPrimitiveGeometry(TLayoutGeometry& geometry) { // read geometry type const EHlslTokenClass geometryType = peek(); switch (geometryType) { case EHTokPointStream: geometry = ElgPoints; break; case EHTokLineStream: geometry = ElgLineStrip; break; case EHTokTriangleStream: geometry = ElgTriangleStrip; break; default: return false; // not a layout geometry } advanceToken(); // consume the layout keyword return true; } // tessellation_decl_type // : INPUTPATCH // | OUTPUTPATCH // bool HlslGrammar::acceptTessellationDeclType() { // read geometry type const EHlslTokenClass tessType = peek(); switch (tessType) { case EHTokInputPatch: break; case EHTokOutputPatch: break; default: return false; // not a tessellation decl } advanceToken(); // consume the keyword return true; } // tessellation_patch_template_type // : tessellation_decl_type LEFT_ANGLE type comma integer_literal RIGHT_ANGLE // bool HlslGrammar::acceptTessellationPatchTemplateType(TType& type) { if (! acceptTessellationDeclType()) return false; if (! acceptTokenClass(EHTokLeftAngle)) return false; if (! acceptType(type)) { expected("tessellation patch type"); return false; } if (! acceptTokenClass(EHTokComma)) return false; // integer size if (! peekTokenClass(EHTokIntConstant)) { expected("literal integer"); return false; } TIntermTyped* size; if (! acceptLiteral(size)) return false; TArraySizes* arraySizes = new TArraySizes; arraySizes->addInnerSize(size->getAsConstantUnion()->getConstArray()[0].getIConst()); type.newArraySizes(*arraySizes); if (! acceptTokenClass(EHTokRightAngle)) { expected("right angle bracket"); return false; } return true; } // stream_out_template_type // : output_primitive_geometry_type LEFT_ANGLE type RIGHT_ANGLE // bool HlslGrammar::acceptStreamOutTemplateType(TType& type, TLayoutGeometry& geometry) { geometry = ElgNone; if (! acceptOutputPrimitiveGeometry(geometry)) return false; if (! acceptTokenClass(EHTokLeftAngle)) return false; if (! acceptType(type)) { expected("stream output type"); return false; } type.getQualifier().storage = EvqVaryingOut; if (! acceptTokenClass(EHTokRightAngle)) { expected("right angle bracket"); return false; } return true; } // annotations // : LEFT_ANGLE declaration SEMI_COLON ... declaration SEMICOLON RIGHT_ANGLE // bool HlslGrammar::acceptAnnotations(TQualifier&) { if (! acceptTokenClass(EHTokLeftAngle)) return false; // note that we are nesting a name space parseContext.nestAnnotations(); // declaration SEMI_COLON ... declaration SEMICOLON RIGHT_ANGLE do { // eat any extra SEMI_COLON; don't know if the grammar calls for this or not while (acceptTokenClass(EHTokSemicolon)) ; if (acceptTokenClass(EHTokRightAngle)) break; // declaration TIntermNode* node = nullptr; if (! acceptDeclaration(node)) { expected("declaration in annotation"); return false; } } while (true); parseContext.unnestAnnotations(); return true; } // sampler_type // : SAMPLER // | SAMPLER1D // | SAMPLER2D // | SAMPLER3D // | SAMPLERCUBE // | SAMPLERSTATE // | SAMPLERCOMPARISONSTATE bool HlslGrammar::acceptSamplerType(TType& type) { // read sampler type const EHlslTokenClass samplerType = peek(); // TODO: for DX9 // TSamplerDim dim = EsdNone; bool isShadow = false; switch (samplerType) { case EHTokSampler: break; case EHTokSampler1d: /*dim = Esd1D*/; break; case EHTokSampler2d: /*dim = Esd2D*/; break; case EHTokSampler3d: /*dim = Esd3D*/; break; case EHTokSamplerCube: /*dim = EsdCube*/; break; case EHTokSamplerState: break; case EHTokSamplerComparisonState: isShadow = true; break; default: return false; // not a sampler declaration } advanceToken(); // consume the sampler type keyword TArraySizes* arraySizes = nullptr; // TODO: array TSampler sampler; sampler.setPureSampler(isShadow); type.shallowCopy(TType(sampler, EvqUniform, arraySizes)); return true; } // texture_type // | BUFFER // | TEXTURE1D // | TEXTURE1DARRAY // | TEXTURE2D // | TEXTURE2DARRAY // | TEXTURE3D // | TEXTURECUBE // | TEXTURECUBEARRAY // | TEXTURE2DMS // | TEXTURE2DMSARRAY // | RWBUFFER // | RWTEXTURE1D // | RWTEXTURE1DARRAY // | RWTEXTURE2D // | RWTEXTURE2DARRAY // | RWTEXTURE3D bool HlslGrammar::acceptTextureType(TType& type) { const EHlslTokenClass textureType = peek(); TSamplerDim dim = EsdNone; bool array = false; bool ms = false; bool image = false; switch (textureType) { case EHTokBuffer: dim = EsdBuffer; break; case EHTokTexture1d: dim = Esd1D; break; case EHTokTexture1darray: dim = Esd1D; array = true; break; case EHTokTexture2d: dim = Esd2D; break; case EHTokTexture2darray: dim = Esd2D; array = true; break; case EHTokTexture3d: dim = Esd3D; break; case EHTokTextureCube: dim = EsdCube; break; case EHTokTextureCubearray: dim = EsdCube; array = true; break; case EHTokTexture2DMS: dim = Esd2D; ms = true; break; case EHTokTexture2DMSarray: dim = Esd2D; array = true; ms = true; break; case EHTokRWBuffer: dim = EsdBuffer; image=true; break; case EHTokRWTexture1d: dim = Esd1D; array=false; image=true; break; case EHTokRWTexture1darray: dim = Esd1D; array=true; image=true; break; case EHTokRWTexture2d: dim = Esd2D; array=false; image=true; break; case EHTokRWTexture2darray: dim = Esd2D; array=true; image=true; break; case EHTokRWTexture3d: dim = Esd3D; array=false; image=true; break; default: return false; // not a texture declaration } advanceToken(); // consume the texture object keyword TType txType(EbtFloat, EvqUniform, 4); // default type is float4 TIntermTyped* msCount = nullptr; // texture type: required for multisample types and RWBuffer/RWTextures! if (acceptTokenClass(EHTokLeftAngle)) { if (! acceptType(txType)) { expected("scalar or vector type"); return false; } const TBasicType basicRetType = txType.getBasicType() ; if (basicRetType != EbtFloat && basicRetType != EbtUint && basicRetType != EbtInt) { unimplemented("basic type in texture"); return false; } // Buffers can handle small mats if they fit in 4 components if (dim == EsdBuffer && txType.isMatrix()) { if ((txType.getMatrixCols() * txType.getMatrixRows()) > 4) { expected("components < 4 in matrix buffer type"); return false; } // TODO: except we don't handle it yet... unimplemented("matrix type in buffer"); return false; } if (!txType.isScalar() && !txType.isVector()) { expected("scalar or vector type"); return false; } if (ms && acceptTokenClass(EHTokComma)) { // read sample count for multisample types, if given if (! peekTokenClass(EHTokIntConstant)) { expected("multisample count"); return false; } if (! acceptLiteral(msCount)) // should never fail, since we just found an integer return false; } if (! acceptTokenClass(EHTokRightAngle)) { expected("right angle bracket"); return false; } } else if (ms) { expected("texture type for multisample"); return false; } else if (image) { expected("type for RWTexture/RWBuffer"); return false; } TArraySizes* arraySizes = nullptr; const bool shadow = false; // declared on the sampler TSampler sampler; TLayoutFormat format = ElfNone; // Buffer, RWBuffer and RWTexture (images) require a TLayoutFormat. We handle only a limit set. if (image || dim == EsdBuffer) format = parseContext.getLayoutFromTxType(token.loc, txType); // Non-image Buffers are combined if (dim == EsdBuffer && !image) { sampler.set(txType.getBasicType(), dim, array); } else { // DX10 textures are separated. TODO: DX9. if (image) { sampler.setImage(txType.getBasicType(), dim, array, shadow, ms); } else { sampler.setTexture(txType.getBasicType(), dim, array, shadow, ms); } } // Remember the declared vector size. sampler.vectorSize = txType.getVectorSize(); type.shallowCopy(TType(sampler, EvqUniform, arraySizes)); type.getQualifier().layoutFormat = format; return true; } // If token is for a type, update 'type' with the type information, // and return true and advance. // Otherwise, return false, and don't advance bool HlslGrammar::acceptType(TType& type) { TIntermNode* nodeList = nullptr; return acceptType(type, nodeList); } bool HlslGrammar::acceptType(TType& type, TIntermNode*& nodeList) { // Basic types for min* types, broken out here in case of future // changes, e.g, to use native halfs. static const TBasicType min16float_bt = EbtFloat; static const TBasicType min10float_bt = EbtFloat; static const TBasicType half_bt = EbtFloat; static const TBasicType min16int_bt = EbtInt; static const TBasicType min12int_bt = EbtInt; static const TBasicType min16uint_bt = EbtUint; switch (peek()) { case EHTokVector: return acceptVectorTemplateType(type); break; case EHTokMatrix: return acceptMatrixTemplateType(type); break; case EHTokPointStream: // fall through case EHTokLineStream: // ... case EHTokTriangleStream: // ... { TLayoutGeometry geometry; if (! acceptStreamOutTemplateType(type, geometry)) return false; if (! parseContext.handleOutputGeometry(token.loc, geometry)) return false; return true; } case EHTokInputPatch: // fall through case EHTokOutputPatch: // ... { if (! acceptTessellationPatchTemplateType(type)) return false; return true; } case EHTokSampler: // fall through case EHTokSampler1d: // ... case EHTokSampler2d: // ... case EHTokSampler3d: // ... case EHTokSamplerCube: // ... case EHTokSamplerState: // ... case EHTokSamplerComparisonState: // ... return acceptSamplerType(type); break; case EHTokBuffer: // fall through case EHTokTexture1d: // ... case EHTokTexture1darray: // ... case EHTokTexture2d: // ... case EHTokTexture2darray: // ... case EHTokTexture3d: // ... case EHTokTextureCube: // ... case EHTokTextureCubearray: // ... case EHTokTexture2DMS: // ... case EHTokTexture2DMSarray: // ... case EHTokRWTexture1d: // ... case EHTokRWTexture1darray: // ... case EHTokRWTexture2d: // ... case EHTokRWTexture2darray: // ... case EHTokRWTexture3d: // ... case EHTokRWBuffer: // ... return acceptTextureType(type); break; case EHTokAppendStructuredBuffer: case EHTokByteAddressBuffer: case EHTokConsumeStructuredBuffer: case EHTokRWByteAddressBuffer: case EHTokRWStructuredBuffer: case EHTokStructuredBuffer: return acceptStructBufferType(type); break; case EHTokClass: case EHTokStruct: case EHTokCBuffer: case EHTokTBuffer: return acceptStruct(type, nodeList); case EHTokIdentifier: // An identifier could be for a user-defined type. // Note we cache the symbol table lookup, to save for a later rule // when this is not a type. token.symbol = parseContext.lookupUserType(*token.string, type); if (token.symbol != nullptr) { advanceToken(); return true; } else return false; case EHTokVoid: new(&type) TType(EbtVoid); break; case EHTokString: new(&type) TType(EbtString); break; case EHTokFloat: new(&type) TType(EbtFloat); break; case EHTokFloat1: new(&type) TType(EbtFloat); type.makeVector(); break; case EHTokFloat2: new(&type) TType(EbtFloat, EvqTemporary, 2); break; case EHTokFloat3: new(&type) TType(EbtFloat, EvqTemporary, 3); break; case EHTokFloat4: new(&type) TType(EbtFloat, EvqTemporary, 4); break; case EHTokDouble: new(&type) TType(EbtDouble); break; case EHTokDouble1: new(&type) TType(EbtDouble); type.makeVector(); break; case EHTokDouble2: new(&type) TType(EbtDouble, EvqTemporary, 2); break; case EHTokDouble3: new(&type) TType(EbtDouble, EvqTemporary, 3); break; case EHTokDouble4: new(&type) TType(EbtDouble, EvqTemporary, 4); break; case EHTokInt: case EHTokDword: new(&type) TType(EbtInt); break; case EHTokInt1: new(&type) TType(EbtInt); type.makeVector(); break; case EHTokInt2: new(&type) TType(EbtInt, EvqTemporary, 2); break; case EHTokInt3: new(&type) TType(EbtInt, EvqTemporary, 3); break; case EHTokInt4: new(&type) TType(EbtInt, EvqTemporary, 4); break; case EHTokUint: new(&type) TType(EbtUint); break; case EHTokUint1: new(&type) TType(EbtUint); type.makeVector(); break; case EHTokUint2: new(&type) TType(EbtUint, EvqTemporary, 2); break; case EHTokUint3: new(&type) TType(EbtUint, EvqTemporary, 3); break; case EHTokUint4: new(&type) TType(EbtUint, EvqTemporary, 4); break; case EHTokBool: new(&type) TType(EbtBool); break; case EHTokBool1: new(&type) TType(EbtBool); type.makeVector(); break; case EHTokBool2: new(&type) TType(EbtBool, EvqTemporary, 2); break; case EHTokBool3: new(&type) TType(EbtBool, EvqTemporary, 3); break; case EHTokBool4: new(&type) TType(EbtBool, EvqTemporary, 4); break; case EHTokHalf: new(&type) TType(half_bt, EvqTemporary, EpqMedium); break; case EHTokHalf1: new(&type) TType(half_bt, EvqTemporary, EpqMedium); type.makeVector(); break; case EHTokHalf2: new(&type) TType(half_bt, EvqTemporary, EpqMedium, 2); break; case EHTokHalf3: new(&type) TType(half_bt, EvqTemporary, EpqMedium, 3); break; case EHTokHalf4: new(&type) TType(half_bt, EvqTemporary, EpqMedium, 4); break; case EHTokMin16float: new(&type) TType(min16float_bt, EvqTemporary, EpqMedium); break; case EHTokMin16float1: new(&type) TType(min16float_bt, EvqTemporary, EpqMedium); type.makeVector(); break; case EHTokMin16float2: new(&type) TType(min16float_bt, EvqTemporary, EpqMedium, 2); break; case EHTokMin16float3: new(&type) TType(min16float_bt, EvqTemporary, EpqMedium, 3); break; case EHTokMin16float4: new(&type) TType(min16float_bt, EvqTemporary, EpqMedium, 4); break; case EHTokMin10float: new(&type) TType(min10float_bt, EvqTemporary, EpqMedium); break; case EHTokMin10float1: new(&type) TType(min10float_bt, EvqTemporary, EpqMedium); type.makeVector(); break; case EHTokMin10float2: new(&type) TType(min10float_bt, EvqTemporary, EpqMedium, 2); break; case EHTokMin10float3: new(&type) TType(min10float_bt, EvqTemporary, EpqMedium, 3); break; case EHTokMin10float4: new(&type) TType(min10float_bt, EvqTemporary, EpqMedium, 4); break; case EHTokMin16int: new(&type) TType(min16int_bt, EvqTemporary, EpqMedium); break; case EHTokMin16int1: new(&type) TType(min16int_bt, EvqTemporary, EpqMedium); type.makeVector(); break; case EHTokMin16int2: new(&type) TType(min16int_bt, EvqTemporary, EpqMedium, 2); break; case EHTokMin16int3: new(&type) TType(min16int_bt, EvqTemporary, EpqMedium, 3); break; case EHTokMin16int4: new(&type) TType(min16int_bt, EvqTemporary, EpqMedium, 4); break; case EHTokMin12int: new(&type) TType(min12int_bt, EvqTemporary, EpqMedium); break; case EHTokMin12int1: new(&type) TType(min12int_bt, EvqTemporary, EpqMedium); type.makeVector(); break; case EHTokMin12int2: new(&type) TType(min12int_bt, EvqTemporary, EpqMedium, 2); break; case EHTokMin12int3: new(&type) TType(min12int_bt, EvqTemporary, EpqMedium, 3); break; case EHTokMin12int4: new(&type) TType(min12int_bt, EvqTemporary, EpqMedium, 4); break; case EHTokMin16uint: new(&type) TType(min16uint_bt, EvqTemporary, EpqMedium); break; case EHTokMin16uint1: new(&type) TType(min16uint_bt, EvqTemporary, EpqMedium); type.makeVector(); break; case EHTokMin16uint2: new(&type) TType(min16uint_bt, EvqTemporary, EpqMedium, 2); break; case EHTokMin16uint3: new(&type) TType(min16uint_bt, EvqTemporary, EpqMedium, 3); break; case EHTokMin16uint4: new(&type) TType(min16uint_bt, EvqTemporary, EpqMedium, 4); break; case EHTokInt1x1: new(&type) TType(EbtInt, EvqTemporary, 0, 1, 1); break; case EHTokInt1x2: new(&type) TType(EbtInt, EvqTemporary, 0, 1, 2); break; case EHTokInt1x3: new(&type) TType(EbtInt, EvqTemporary, 0, 1, 3); break; case EHTokInt1x4: new(&type) TType(EbtInt, EvqTemporary, 0, 1, 4); break; case EHTokInt2x1: new(&type) TType(EbtInt, EvqTemporary, 0, 2, 1); break; case EHTokInt2x2: new(&type) TType(EbtInt, EvqTemporary, 0, 2, 2); break; case EHTokInt2x3: new(&type) TType(EbtInt, EvqTemporary, 0, 2, 3); break; case EHTokInt2x4: new(&type) TType(EbtInt, EvqTemporary, 0, 2, 4); break; case EHTokInt3x1: new(&type) TType(EbtInt, EvqTemporary, 0, 3, 1); break; case EHTokInt3x2: new(&type) TType(EbtInt, EvqTemporary, 0, 3, 2); break; case EHTokInt3x3: new(&type) TType(EbtInt, EvqTemporary, 0, 3, 3); break; case EHTokInt3x4: new(&type) TType(EbtInt, EvqTemporary, 0, 3, 4); break; case EHTokInt4x1: new(&type) TType(EbtInt, EvqTemporary, 0, 4, 1); break; case EHTokInt4x2: new(&type) TType(EbtInt, EvqTemporary, 0, 4, 2); break; case EHTokInt4x3: new(&type) TType(EbtInt, EvqTemporary, 0, 4, 3); break; case EHTokInt4x4: new(&type) TType(EbtInt, EvqTemporary, 0, 4, 4); break; case EHTokUint1x1: new(&type) TType(EbtUint, EvqTemporary, 0, 1, 1); break; case EHTokUint1x2: new(&type) TType(EbtUint, EvqTemporary, 0, 1, 2); break; case EHTokUint1x3: new(&type) TType(EbtUint, EvqTemporary, 0, 1, 3); break; case EHTokUint1x4: new(&type) TType(EbtUint, EvqTemporary, 0, 1, 4); break; case EHTokUint2x1: new(&type) TType(EbtUint, EvqTemporary, 0, 2, 1); break; case EHTokUint2x2: new(&type) TType(EbtUint, EvqTemporary, 0, 2, 2); break; case EHTokUint2x3: new(&type) TType(EbtUint, EvqTemporary, 0, 2, 3); break; case EHTokUint2x4: new(&type) TType(EbtUint, EvqTemporary, 0, 2, 4); break; case EHTokUint3x1: new(&type) TType(EbtUint, EvqTemporary, 0, 3, 1); break; case EHTokUint3x2: new(&type) TType(EbtUint, EvqTemporary, 0, 3, 2); break; case EHTokUint3x3: new(&type) TType(EbtUint, EvqTemporary, 0, 3, 3); break; case EHTokUint3x4: new(&type) TType(EbtUint, EvqTemporary, 0, 3, 4); break; case EHTokUint4x1: new(&type) TType(EbtUint, EvqTemporary, 0, 4, 1); break; case EHTokUint4x2: new(&type) TType(EbtUint, EvqTemporary, 0, 4, 2); break; case EHTokUint4x3: new(&type) TType(EbtUint, EvqTemporary, 0, 4, 3); break; case EHTokUint4x4: new(&type) TType(EbtUint, EvqTemporary, 0, 4, 4); break; case EHTokBool1x1: new(&type) TType(EbtBool, EvqTemporary, 0, 1, 1); break; case EHTokBool1x2: new(&type) TType(EbtBool, EvqTemporary, 0, 1, 2); break; case EHTokBool1x3: new(&type) TType(EbtBool, EvqTemporary, 0, 1, 3); break; case EHTokBool1x4: new(&type) TType(EbtBool, EvqTemporary, 0, 1, 4); break; case EHTokBool2x1: new(&type) TType(EbtBool, EvqTemporary, 0, 2, 1); break; case EHTokBool2x2: new(&type) TType(EbtBool, EvqTemporary, 0, 2, 2); break; case EHTokBool2x3: new(&type) TType(EbtBool, EvqTemporary, 0, 2, 3); break; case EHTokBool2x4: new(&type) TType(EbtBool, EvqTemporary, 0, 2, 4); break; case EHTokBool3x1: new(&type) TType(EbtBool, EvqTemporary, 0, 3, 1); break; case EHTokBool3x2: new(&type) TType(EbtBool, EvqTemporary, 0, 3, 2); break; case EHTokBool3x3: new(&type) TType(EbtBool, EvqTemporary, 0, 3, 3); break; case EHTokBool3x4: new(&type) TType(EbtBool, EvqTemporary, 0, 3, 4); break; case EHTokBool4x1: new(&type) TType(EbtBool, EvqTemporary, 0, 4, 1); break; case EHTokBool4x2: new(&type) TType(EbtBool, EvqTemporary, 0, 4, 2); break; case EHTokBool4x3: new(&type) TType(EbtBool, EvqTemporary, 0, 4, 3); break; case EHTokBool4x4: new(&type) TType(EbtBool, EvqTemporary, 0, 4, 4); break; case EHTokFloat1x1: new(&type) TType(EbtFloat, EvqTemporary, 0, 1, 1); break; case EHTokFloat1x2: new(&type) TType(EbtFloat, EvqTemporary, 0, 1, 2); break; case EHTokFloat1x3: new(&type) TType(EbtFloat, EvqTemporary, 0, 1, 3); break; case EHTokFloat1x4: new(&type) TType(EbtFloat, EvqTemporary, 0, 1, 4); break; case EHTokFloat2x1: new(&type) TType(EbtFloat, EvqTemporary, 0, 2, 1); break; case EHTokFloat2x2: new(&type) TType(EbtFloat, EvqTemporary, 0, 2, 2); break; case EHTokFloat2x3: new(&type) TType(EbtFloat, EvqTemporary, 0, 2, 3); break; case EHTokFloat2x4: new(&type) TType(EbtFloat, EvqTemporary, 0, 2, 4); break; case EHTokFloat3x1: new(&type) TType(EbtFloat, EvqTemporary, 0, 3, 1); break; case EHTokFloat3x2: new(&type) TType(EbtFloat, EvqTemporary, 0, 3, 2); break; case EHTokFloat3x3: new(&type) TType(EbtFloat, EvqTemporary, 0, 3, 3); break; case EHTokFloat3x4: new(&type) TType(EbtFloat, EvqTemporary, 0, 3, 4); break; case EHTokFloat4x1: new(&type) TType(EbtFloat, EvqTemporary, 0, 4, 1); break; case EHTokFloat4x2: new(&type) TType(EbtFloat, EvqTemporary, 0, 4, 2); break; case EHTokFloat4x3: new(&type) TType(EbtFloat, EvqTemporary, 0, 4, 3); break; case EHTokFloat4x4: new(&type) TType(EbtFloat, EvqTemporary, 0, 4, 4); break; case EHTokDouble1x1: new(&type) TType(EbtDouble, EvqTemporary, 0, 1, 1); break; case EHTokDouble1x2: new(&type) TType(EbtDouble, EvqTemporary, 0, 1, 2); break; case EHTokDouble1x3: new(&type) TType(EbtDouble, EvqTemporary, 0, 1, 3); break; case EHTokDouble1x4: new(&type) TType(EbtDouble, EvqTemporary, 0, 1, 4); break; case EHTokDouble2x1: new(&type) TType(EbtDouble, EvqTemporary, 0, 2, 1); break; case EHTokDouble2x2: new(&type) TType(EbtDouble, EvqTemporary, 0, 2, 2); break; case EHTokDouble2x3: new(&type) TType(EbtDouble, EvqTemporary, 0, 2, 3); break; case EHTokDouble2x4: new(&type) TType(EbtDouble, EvqTemporary, 0, 2, 4); break; case EHTokDouble3x1: new(&type) TType(EbtDouble, EvqTemporary, 0, 3, 1); break; case EHTokDouble3x2: new(&type) TType(EbtDouble, EvqTemporary, 0, 3, 2); break; case EHTokDouble3x3: new(&type) TType(EbtDouble, EvqTemporary, 0, 3, 3); break; case EHTokDouble3x4: new(&type) TType(EbtDouble, EvqTemporary, 0, 3, 4); break; case EHTokDouble4x1: new(&type) TType(EbtDouble, EvqTemporary, 0, 4, 1); break; case EHTokDouble4x2: new(&type) TType(EbtDouble, EvqTemporary, 0, 4, 2); break; case EHTokDouble4x3: new(&type) TType(EbtDouble, EvqTemporary, 0, 4, 3); break; case EHTokDouble4x4: new(&type) TType(EbtDouble, EvqTemporary, 0, 4, 4); break; default: return false; } advanceToken(); return true; } // struct // : struct_type IDENTIFIER post_decls LEFT_BRACE struct_declaration_list RIGHT_BRACE // | struct_type post_decls LEFT_BRACE struct_declaration_list RIGHT_BRACE // | struct_type IDENTIFIER // use of previously declared struct type // // struct_type // : STRUCT // | CLASS // | CBUFFER // | TBUFFER // bool HlslGrammar::acceptStruct(TType& type, TIntermNode*& nodeList) { // This storage qualifier will tell us whether it's an AST // block type or just a generic structure type. TStorageQualifier storageQualifier = EvqTemporary; // CBUFFER if (acceptTokenClass(EHTokCBuffer)) storageQualifier = EvqUniform; // TBUFFER else if (acceptTokenClass(EHTokTBuffer)) storageQualifier = EvqBuffer; // CLASS // STRUCT else if (! acceptTokenClass(EHTokClass) && ! acceptTokenClass(EHTokStruct)) return false; // IDENTIFIER TString structName = ""; if (peekTokenClass(EHTokIdentifier)) { structName = *token.string; advanceToken(); } // post_decls TQualifier postDeclQualifier; postDeclQualifier.clear(); bool postDeclsFound = acceptPostDecls(postDeclQualifier); // LEFT_BRACE // struct_type IDENTIFIER if (! acceptTokenClass(EHTokLeftBrace)) { if (structName.size() > 0 && !postDeclsFound && parseContext.lookupUserType(structName, type) != nullptr) { // struct_type IDENTIFIER return true; } else { expected("{"); return false; } } // struct_declaration_list TTypeList* typeList; if (! acceptStructDeclarationList(typeList, nodeList, structName)) { expected("struct member declarations"); return false; } // RIGHT_BRACE if (! acceptTokenClass(EHTokRightBrace)) { expected("}"); return false; } // create the user-defined type if (storageQualifier == EvqTemporary) new(&type) TType(typeList, structName); else { postDeclQualifier.storage = storageQualifier; new(&type) TType(typeList, structName, postDeclQualifier); // sets EbtBlock } parseContext.declareStruct(token.loc, structName, type); return true; } // struct_buffer // : APPENDSTRUCTUREDBUFFER // | BYTEADDRESSBUFFER // | CONSUMESTRUCTUREDBUFFER // | RWBYTEADDRESSBUFFER // | RWSTRUCTUREDBUFFER // | STRUCTUREDBUFFER bool HlslGrammar::acceptStructBufferType(TType& type) { const EHlslTokenClass structBuffType = peek(); // TODO: globallycoherent bool hasTemplateType = true; bool readonly = false; TStorageQualifier storage = EvqBuffer; switch (structBuffType) { case EHTokAppendStructuredBuffer: unimplemented("AppendStructuredBuffer"); return false; case EHTokByteAddressBuffer: hasTemplateType = false; readonly = true; break; case EHTokConsumeStructuredBuffer: unimplemented("ConsumeStructuredBuffer"); return false; case EHTokRWByteAddressBuffer: hasTemplateType = false; break; case EHTokRWStructuredBuffer: break; case EHTokStructuredBuffer: readonly = true; break; default: return false; // not a structure buffer type } advanceToken(); // consume the structure keyword // type on which this StructedBuffer is templatized. E.g, StructedBuffer ==> MyStruct TType* templateType = new TType; if (hasTemplateType) { if (! acceptTokenClass(EHTokLeftAngle)) { expected("left angle bracket"); return false; } if (! acceptType(*templateType)) { expected("type"); return false; } if (! acceptTokenClass(EHTokRightAngle)) { expected("right angle bracket"); return false; } } else { // byte address buffers have no explicit type. TType uintType(EbtUint, storage); templateType->shallowCopy(uintType); } // Create an unsized array out of that type. // TODO: does this work if it's already an array type? TArraySizes unsizedArray; unsizedArray.addInnerSize(UnsizedArraySize); templateType->newArraySizes(unsizedArray); templateType->getQualifier().storage = storage; // field name is canonical for all structbuffers templateType->setFieldName("@data"); // Create block type. TODO: hidden internal uint member when needed TTypeList* blockStruct = new TTypeList; TTypeLoc member = { templateType, token.loc }; blockStruct->push_back(member); // This is the type of the buffer block (SSBO) TType blockType(blockStruct, "", templateType->getQualifier()); blockType.getQualifier().storage = storage; blockType.getQualifier().readonly = readonly; // We may have created an equivalent type before, in which case we should use its // deep structure. parseContext.shareStructBufferType(blockType); type.shallowCopy(blockType); return true; } // struct_declaration_list // : struct_declaration SEMI_COLON struct_declaration SEMI_COLON ... // // struct_declaration // : fully_specified_type struct_declarator COMMA struct_declarator ... // | fully_specified_type IDENTIFIER function_parameters post_decls compound_statement // member-function definition // // struct_declarator // : IDENTIFIER post_decls // | IDENTIFIER array_specifier post_decls // | IDENTIFIER function_parameters post_decls // member-function prototype // bool HlslGrammar::acceptStructDeclarationList(TTypeList*& typeList, TIntermNode*& nodeList, const TString& typeName) { typeList = new TTypeList(); HlslToken idToken; do { // success on seeing the RIGHT_BRACE coming up if (peekTokenClass(EHTokRightBrace)) return true; // struct_declaration bool declarator_list = false; // fully_specified_type TType memberType; if (! acceptFullySpecifiedType(memberType, nodeList)) { expected("member type"); return false; } // struct_declarator COMMA struct_declarator ... bool functionDefinitionAccepted = false; do { if (! acceptIdentifier(idToken)) { expected("member name"); return false; } if (peekTokenClass(EHTokLeftParen)) { // function_parameters if (!declarator_list) { functionDefinitionAccepted = acceptMemberFunctionDefinition(nodeList, typeName, memberType, *idToken.string); if (functionDefinitionAccepted) break; } expected("member-function definition"); return false; } else { // add it to the list of members TTypeLoc member = { new TType(EbtVoid), token.loc }; member.type->shallowCopy(memberType); member.type->setFieldName(*idToken.string); typeList->push_back(member); // array_specifier TArraySizes* arraySizes = nullptr; acceptArraySpecifier(arraySizes); if (arraySizes) typeList->back().type->newArraySizes(*arraySizes); acceptPostDecls(member.type->getQualifier()); // EQUAL assignment_expression if (acceptTokenClass(EHTokAssign)) { parseContext.warn(idToken.loc, "struct-member initializers ignored", "typedef", ""); TIntermTyped* expressionNode = nullptr; if (! acceptAssignmentExpression(expressionNode)) { expected("initializer"); return false; } } } // success on seeing the SEMICOLON coming up if (peekTokenClass(EHTokSemicolon)) break; // COMMA if (acceptTokenClass(EHTokComma)) declarator_list = true; else { expected(","); return false; } } while (true); // SEMI_COLON if (! functionDefinitionAccepted && ! acceptTokenClass(EHTokSemicolon)) { expected(";"); return false; } } while (true); } // member_function_definition // | function_parameters post_decls compound_statement // // Expects type to have EvqGlobal for a static member and // EvqTemporary for non-static member. bool HlslGrammar::acceptMemberFunctionDefinition(TIntermNode*& nodeList, const TString& typeName, const TType& type, const TString& memberName) { // watch early returns... parseContext.pushThis(typeName); bool accepted = false; TString* functionName = parseContext.getFullMemberFunctionName(memberName, type.getQualifier().storage == EvqGlobal); TFunction& function = *new TFunction(functionName, type); // function_parameters if (acceptFunctionParameters(function)) { // post_decls acceptPostDecls(function.getWritableType().getQualifier()); // compound_statement (function body definition) if (peekTokenClass(EHTokLeftBrace)) { if (function.getType().getQualifier().storage != EvqGlobal) { expected("only static member functions are accepted"); return false; } TAttributeMap attributes; accepted = acceptFunctionDefinition(function, nodeList, attributes); } } else expected("function parameter list"); parseContext.popThis(); return accepted; } // function_parameters // : LEFT_PAREN parameter_declaration COMMA parameter_declaration ... RIGHT_PAREN // | LEFT_PAREN VOID RIGHT_PAREN // bool HlslGrammar::acceptFunctionParameters(TFunction& function) { // LEFT_PAREN if (! acceptTokenClass(EHTokLeftParen)) return false; // VOID RIGHT_PAREN if (! acceptTokenClass(EHTokVoid)) { do { // parameter_declaration if (! acceptParameterDeclaration(function)) break; // COMMA if (! acceptTokenClass(EHTokComma)) break; } while (true); } // RIGHT_PAREN if (! acceptTokenClass(EHTokRightParen)) { expected(")"); return false; } return true; } // default_parameter_declaration // : EQUAL conditional_expression // : EQUAL initializer bool HlslGrammar::acceptDefaultParameterDeclaration(const TType& type, TIntermTyped*& node) { node = nullptr; // Valid not to have a default_parameter_declaration if (!acceptTokenClass(EHTokAssign)) return true; if (!acceptConditionalExpression(node)) { if (!acceptInitializer(node)) return false; // For initializer lists, we have to const-fold into a constructor for the type, so build // that. TFunction* constructor = parseContext.handleConstructorCall(token.loc, type); if (constructor == nullptr) // cannot construct return false; TIntermTyped* arguments = nullptr; for (int i = 0; i < int(node->getAsAggregate()->getSequence().size()); i++) parseContext.handleFunctionArgument(constructor, arguments, node->getAsAggregate()->getSequence()[i]->getAsTyped()); node = parseContext.handleFunctionCall(token.loc, constructor, node); } // If this is simply a constant, we can use it directly. if (node->getAsConstantUnion()) return true; // Otherwise, it has to be const-foldable. TIntermTyped* origNode = node; node = intermediate.fold(node->getAsAggregate()); if (node != nullptr && origNode != node) return true; parseContext.error(token.loc, "invalid default parameter value", "", ""); return false; } // parameter_declaration // : fully_specified_type post_decls [ = default_parameter_declaration ] // | fully_specified_type identifier array_specifier post_decls [ = default_parameter_declaration ] // bool HlslGrammar::acceptParameterDeclaration(TFunction& function) { // fully_specified_type TType* type = new TType; if (! acceptFullySpecifiedType(*type)) return false; // identifier HlslToken idToken; acceptIdentifier(idToken); // array_specifier TArraySizes* arraySizes = nullptr; acceptArraySpecifier(arraySizes); if (arraySizes) { if (arraySizes->isImplicit()) { parseContext.error(token.loc, "function parameter array cannot be implicitly sized", "", ""); return false; } type->newArraySizes(*arraySizes); } // post_decls acceptPostDecls(type->getQualifier()); TIntermTyped* defaultValue; if (!acceptDefaultParameterDeclaration(*type, defaultValue)) return false; parseContext.paramFix(*type); // If any prior parameters have default values, all the parameters after that must as well. if (defaultValue == nullptr && function.getDefaultParamCount() > 0) { parseContext.error(idToken.loc, "invalid parameter after default value parameters", idToken.string->c_str(), ""); return false; } TParameter param = { idToken.string, type, defaultValue }; function.addParameter(param); return true; } // Do the work to create the function definition in addition to // parsing the body (compound_statement). bool HlslGrammar::acceptFunctionDefinition(TFunction& function, TIntermNode*& nodeList, const TAttributeMap& attributes) { TFunction& functionDeclarator = parseContext.handleFunctionDeclarator(token.loc, function, false /* not prototype */); TSourceLoc loc = token.loc; // we might get back and entry-point TIntermNode* entryPointNode = nullptr; // This does a pushScope() TIntermNode* functionNode = parseContext.handleFunctionDefinition(loc, functionDeclarator, attributes, entryPointNode); // compound_statement TIntermNode* functionBody = nullptr; if (! acceptCompoundStatement(functionBody)) return false; // this does a popScope() parseContext.handleFunctionBody(loc, functionDeclarator, functionBody, functionNode); // Hook up the 1 or 2 function definitions. nodeList = intermediate.growAggregate(nodeList, functionNode); nodeList = intermediate.growAggregate(nodeList, entryPointNode); return true; } // Accept an expression with parenthesis around it, where // the parenthesis ARE NOT expression parenthesis, but the // syntactically required ones like in "if ( expression )". // // Also accepts a declaration expression; "if (int a = expression)". // // Note this one is not set up to be speculative; as it gives // errors if not found. // bool HlslGrammar::acceptParenExpression(TIntermTyped*& expression) { // LEFT_PAREN if (! acceptTokenClass(EHTokLeftParen)) expected("("); bool decl = false; TIntermNode* declNode = nullptr; decl = acceptControlDeclaration(declNode); if (decl) { if (declNode == nullptr || declNode->getAsTyped() == nullptr) { expected("initialized declaration"); return false; } else expression = declNode->getAsTyped(); } else { // no declaration if (! acceptExpression(expression)) { expected("expression"); return false; } } // RIGHT_PAREN if (! acceptTokenClass(EHTokRightParen)) expected(")"); return true; } // The top-level full expression recognizer. // // expression // : assignment_expression COMMA assignment_expression COMMA assignment_expression ... // bool HlslGrammar::acceptExpression(TIntermTyped*& node) { node = nullptr; // assignment_expression if (! acceptAssignmentExpression(node)) return false; if (! peekTokenClass(EHTokComma)) return true; do { // ... COMMA TSourceLoc loc = token.loc; advanceToken(); // ... assignment_expression TIntermTyped* rightNode = nullptr; if (! acceptAssignmentExpression(rightNode)) { expected("assignment expression"); return false; } node = intermediate.addComma(node, rightNode, loc); if (! peekTokenClass(EHTokComma)) return true; } while (true); } // initializer // : LEFT_BRACE RIGHT_BRACE // | LEFT_BRACE initializer_list RIGHT_BRACE // // initializer_list // : assignment_expression COMMA assignment_expression COMMA ... // bool HlslGrammar::acceptInitializer(TIntermTyped*& node) { // LEFT_BRACE if (! acceptTokenClass(EHTokLeftBrace)) return false; // RIGHT_BRACE TSourceLoc loc = token.loc; if (acceptTokenClass(EHTokRightBrace)) { // a zero-length initializer list node = intermediate.makeAggregate(loc); return true; } // initializer_list node = nullptr; do { // assignment_expression TIntermTyped* expr; if (! acceptAssignmentExpression(expr)) { expected("assignment expression in initializer list"); return false; } node = intermediate.growAggregate(node, expr, loc); // COMMA if (acceptTokenClass(EHTokComma)) { if (acceptTokenClass(EHTokRightBrace)) // allow trailing comma return true; continue; } // RIGHT_BRACE if (acceptTokenClass(EHTokRightBrace)) return true; expected(", or }"); return false; } while (true); } // Accept an assignment expression, where assignment operations // associate right-to-left. That is, it is implicit, for example // // a op (b op (c op d)) // // assigment_expression // : initializer // | conditional_expression // | conditional_expression assign_op conditional_expression assign_op conditional_expression ... // bool HlslGrammar::acceptAssignmentExpression(TIntermTyped*& node) { // initializer if (peekTokenClass(EHTokLeftBrace)) { if (acceptInitializer(node)) return true; expected("initializer"); return false; } // conditional_expression if (! acceptConditionalExpression(node)) return false; // assignment operation? TOperator assignOp = HlslOpMap::assignment(peek()); if (assignOp == EOpNull) return true; // assign_op TSourceLoc loc = token.loc; advanceToken(); // conditional_expression assign_op conditional_expression ... // Done by recursing this function, which automatically // gets the right-to-left associativity. TIntermTyped* rightNode = nullptr; if (! acceptAssignmentExpression(rightNode)) { expected("assignment expression"); return false; } node = parseContext.handleAssign(loc, assignOp, node, rightNode); node = parseContext.handleLvalue(loc, "assign", node); if (node == nullptr) { parseContext.error(loc, "could not create assignment", "", ""); return false; } if (! peekTokenClass(EHTokComma)) return true; return true; } // Accept a conditional expression, which associates right-to-left, // accomplished by the "true" expression calling down to lower // precedence levels than this level. // // conditional_expression // : binary_expression // | binary_expression QUESTION expression COLON assignment_expression // bool HlslGrammar::acceptConditionalExpression(TIntermTyped*& node) { // binary_expression if (! acceptBinaryExpression(node, PlLogicalOr)) return false; if (! acceptTokenClass(EHTokQuestion)) return true; TIntermTyped* trueNode = nullptr; if (! acceptExpression(trueNode)) { expected("expression after ?"); return false; } TSourceLoc loc = token.loc; if (! acceptTokenClass(EHTokColon)) { expected(":"); return false; } TIntermTyped* falseNode = nullptr; if (! acceptAssignmentExpression(falseNode)) { expected("expression after :"); return false; } node = intermediate.addSelection(node, trueNode, falseNode, loc); return true; } // Accept a binary expression, for binary operations that // associate left-to-right. This is, it is implicit, for example // // ((a op b) op c) op d // // binary_expression // : expression op expression op expression ... // // where 'expression' is the next higher level in precedence. // bool HlslGrammar::acceptBinaryExpression(TIntermTyped*& node, PrecedenceLevel precedenceLevel) { if (precedenceLevel > PlMul) return acceptUnaryExpression(node); // assignment_expression if (! acceptBinaryExpression(node, (PrecedenceLevel)(precedenceLevel + 1))) return false; do { TOperator op = HlslOpMap::binary(peek()); PrecedenceLevel tokenLevel = HlslOpMap::precedenceLevel(op); if (tokenLevel < precedenceLevel) return true; // ... op TSourceLoc loc = token.loc; advanceToken(); // ... expression TIntermTyped* rightNode = nullptr; if (! acceptBinaryExpression(rightNode, (PrecedenceLevel)(precedenceLevel + 1))) { expected("expression"); return false; } node = intermediate.addBinaryMath(op, node, rightNode, loc); if (node == nullptr) { parseContext.error(loc, "Could not perform requested binary operation", "", ""); return false; } } while (true); } // unary_expression // : (type) unary_expression // | + unary_expression // | - unary_expression // | ! unary_expression // | ~ unary_expression // | ++ unary_expression // | -- unary_expression // | postfix_expression // bool HlslGrammar::acceptUnaryExpression(TIntermTyped*& node) { // (type) unary_expression // Have to look two steps ahead, because this could be, e.g., a // postfix_expression instead, since that also starts with at "(". if (acceptTokenClass(EHTokLeftParen)) { TType castType; if (acceptType(castType)) { if (acceptTokenClass(EHTokRightParen)) { // We've matched "(type)" now, get the expression to cast TSourceLoc loc = token.loc; if (! acceptUnaryExpression(node)) return false; // Hook it up like a constructor TFunction* constructorFunction = parseContext.handleConstructorCall(loc, castType); if (constructorFunction == nullptr) { expected("type that can be constructed"); return false; } TIntermTyped* arguments = nullptr; parseContext.handleFunctionArgument(constructorFunction, arguments, node); node = parseContext.handleFunctionCall(loc, constructorFunction, arguments); return true; } else { // This could be a parenthesized constructor, ala (int(3)), and we just accepted // the '(int' part. We must back up twice. recedeToken(); recedeToken(); } } else { // This isn't a type cast, but it still started "(", so if it is a // unary expression, it can only be a postfix_expression, so try that. // Back it up first. recedeToken(); return acceptPostfixExpression(node); } } // peek for "op unary_expression" TOperator unaryOp = HlslOpMap::preUnary(peek()); // postfix_expression (if no unary operator) if (unaryOp == EOpNull) return acceptPostfixExpression(node); // op unary_expression TSourceLoc loc = token.loc; advanceToken(); if (! acceptUnaryExpression(node)) return false; // + is a no-op if (unaryOp == EOpAdd) return true; node = intermediate.addUnaryMath(unaryOp, node, loc); // These unary ops require lvalues if (unaryOp == EOpPreIncrement || unaryOp == EOpPreDecrement) node = parseContext.handleLvalue(loc, "unary operator", node); return node != nullptr; } // postfix_expression // : LEFT_PAREN expression RIGHT_PAREN // | literal // | constructor // | identifier // | function_call // | postfix_expression LEFT_BRACKET integer_expression RIGHT_BRACKET // | postfix_expression DOT IDENTIFIER // | postfix_expression DOT IDENTIFIER arguments // | postfix_expression COLONCOLON IDENTIFIER arguments // | postfix_expression INC_OP // | postfix_expression DEC_OP // bool HlslGrammar::acceptPostfixExpression(TIntermTyped*& node) { // Not implemented as self-recursive: // The logical "right recursion" is done with a loop at the end // idToken will pick up either a variable or a function name in a function call HlslToken idToken; // scopeBase will pick up the type symbol on the left of '::' TSymbol* scopeBase = nullptr; // Find something before the postfix operations, as they can't operate // on nothing. So, no "return true", they fall through, only "return false". if (acceptTokenClass(EHTokLeftParen)) { // LEFT_PAREN expression RIGHT_PAREN if (! acceptExpression(node)) { expected("expression"); return false; } if (! acceptTokenClass(EHTokRightParen)) { expected(")"); return false; } } else if (acceptLiteral(node)) { // literal (nothing else to do yet), go on to the } else if (acceptConstructor(node)) { // constructor (nothing else to do yet) } else if (acceptIdentifier(idToken)) { // user-type, identifier, or function name if (peekTokenClass(EHTokColonColon)) { TType type; scopeBase = parseContext.lookupUserType(*idToken.string, type); if (scopeBase == nullptr) { expected("type left of ::"); return false; } } else if (! peekTokenClass(EHTokLeftParen)) { node = parseContext.handleVariable(idToken.loc, idToken.symbol, idToken.string); } else if (acceptFunctionCall(idToken, node)) { // function_call (nothing else to do yet) } else { expected("function call arguments"); return false; } } else { // nothing found, can't post operate return false; } // This is to guarantee we do this no matter how we get out of the stack frame. // This way there's no bug if an early return forgets to do it. struct tFinalize { tFinalize(HlslParseContext& p) : parseContext(p) { } ~tFinalize() { parseContext.finalizeFlattening(); } HlslParseContext& parseContext; private: const tFinalize& operator=(const tFinalize&) { return *this; } tFinalize(const tFinalize& f) : parseContext(f.parseContext) { } } finalize(parseContext); // Initialize the flattening accumulation data, so we can track data across multiple bracket or // dot operators. This can also be nested, e.g, for [], so we have to track each nesting // level: hence the init and finalize. Even though in practice these must be // constants, they are parsed no matter what. parseContext.initFlattening(); // Something was found, chain as many postfix operations as exist. do { TSourceLoc loc = token.loc; TOperator postOp = HlslOpMap::postUnary(peek()); // Consume only a valid post-unary operator, otherwise we are done. switch (postOp) { case EOpIndexDirectStruct: case EOpIndexIndirect: case EOpPostIncrement: case EOpPostDecrement: case EOpScoping: advanceToken(); break; default: return true; } // We have a valid post-unary operator, process it. switch (postOp) { case EOpScoping: case EOpIndexDirectStruct: { // DOT IDENTIFIER // includes swizzles, member variables, and member functions HlslToken field; if (! acceptIdentifier(field)) { expected("swizzle or member"); return false; } if (peekTokenClass(EHTokLeftParen)) { // member function TIntermTyped* thisNode = node; // arguments if (! acceptFunctionCall(field, node, thisNode, scopeBase)) { expected("function parameters"); return false; } } else node = parseContext.handleDotDereference(field.loc, node, *field.string); break; } case EOpIndexIndirect: { // LEFT_BRACKET integer_expression RIGHT_BRACKET TIntermTyped* indexNode = nullptr; if (! acceptExpression(indexNode) || ! peekTokenClass(EHTokRightBracket)) { expected("expression followed by ']'"); return false; } advanceToken(); node = parseContext.handleBracketDereference(indexNode->getLoc(), node, indexNode); break; } case EOpPostIncrement: // INC_OP // fall through case EOpPostDecrement: // DEC_OP node = intermediate.addUnaryMath(postOp, node, loc); node = parseContext.handleLvalue(loc, "unary operator", node); break; default: assert(0); break; } } while (true); } // constructor // : type argument_list // bool HlslGrammar::acceptConstructor(TIntermTyped*& node) { // type TType type; if (acceptType(type)) { TFunction* constructorFunction = parseContext.handleConstructorCall(token.loc, type); if (constructorFunction == nullptr) return false; // arguments TIntermTyped* arguments = nullptr; if (! acceptArguments(constructorFunction, arguments)) { // It's possible this is a type keyword used as an identifier. Put the token back // for later use. recedeToken(); return false; } // hook it up node = parseContext.handleFunctionCall(arguments->getLoc(), constructorFunction, arguments); return true; } return false; } // The function_call identifier was already recognized, and passed in as idToken. // // function_call // : [idToken] arguments // bool HlslGrammar::acceptFunctionCall(HlslToken callToken, TIntermTyped*& node, TIntermTyped* baseObject, const TSymbol* baseType) { // name TString* functionName = nullptr; if ((baseObject == nullptr && baseType == nullptr) || parseContext.isBuiltInMethod(callToken.loc, baseObject, *callToken.string)) functionName = callToken.string; else { functionName = NewPoolTString(""); if (baseObject != nullptr) { functionName->append(baseObject->getType().getTypeName().c_str()); functionName->append("."); } else if (baseType != nullptr) { functionName->append(baseType->getType().getTypeName()); functionName->append("::"); } functionName->append(*callToken.string); } // function TFunction* function = new TFunction(functionName, TType(EbtVoid)); // arguments // Non-static member functions have an implicit first argument of the base object. TIntermTyped* arguments = nullptr; if (baseObject != nullptr) parseContext.handleFunctionArgument(function, arguments, baseObject); if (! acceptArguments(function, arguments)) return false; // call node = parseContext.handleFunctionCall(callToken.loc, function, arguments); return true; } // arguments // : LEFT_PAREN expression COMMA expression COMMA ... RIGHT_PAREN // // The arguments are pushed onto the 'function' argument list and // onto the 'arguments' aggregate. // bool HlslGrammar::acceptArguments(TFunction* function, TIntermTyped*& arguments) { // LEFT_PAREN if (! acceptTokenClass(EHTokLeftParen)) return false; do { // expression TIntermTyped* arg; if (! acceptAssignmentExpression(arg)) break; // hook it up parseContext.handleFunctionArgument(function, arguments, arg); // COMMA if (! acceptTokenClass(EHTokComma)) break; } while (true); // RIGHT_PAREN if (! acceptTokenClass(EHTokRightParen)) { expected(")"); return false; } return true; } bool HlslGrammar::acceptLiteral(TIntermTyped*& node) { switch (token.tokenClass) { case EHTokIntConstant: node = intermediate.addConstantUnion(token.i, token.loc, true); break; case EHTokUintConstant: node = intermediate.addConstantUnion(token.u, token.loc, true); break; case EHTokFloatConstant: node = intermediate.addConstantUnion(token.d, EbtFloat, token.loc, true); break; case EHTokDoubleConstant: node = intermediate.addConstantUnion(token.d, EbtDouble, token.loc, true); break; case EHTokBoolConstant: node = intermediate.addConstantUnion(token.b, token.loc, true); break; case EHTokStringConstant: node = intermediate.addConstantUnion(token.string, token.loc, true); break; default: return false; } advanceToken(); return true; } // compound_statement // : LEFT_CURLY statement statement ... RIGHT_CURLY // bool HlslGrammar::acceptCompoundStatement(TIntermNode*& retStatement) { TIntermAggregate* compoundStatement = nullptr; // LEFT_CURLY if (! acceptTokenClass(EHTokLeftBrace)) return false; // statement statement ... TIntermNode* statement = nullptr; while (acceptStatement(statement)) { TIntermBranch* branch = statement ? statement->getAsBranchNode() : nullptr; if (branch != nullptr && (branch->getFlowOp() == EOpCase || branch->getFlowOp() == EOpDefault)) { // hook up individual subsequences within a switch statement parseContext.wrapupSwitchSubsequence(compoundStatement, statement); compoundStatement = nullptr; } else { // hook it up to the growing compound statement compoundStatement = intermediate.growAggregate(compoundStatement, statement); } } if (compoundStatement) compoundStatement->setOperator(EOpSequence); retStatement = compoundStatement; // RIGHT_CURLY return acceptTokenClass(EHTokRightBrace); } bool HlslGrammar::acceptScopedStatement(TIntermNode*& statement) { parseContext.pushScope(); bool result = acceptStatement(statement); parseContext.popScope(); return result; } bool HlslGrammar::acceptScopedCompoundStatement(TIntermNode*& statement) { parseContext.pushScope(); bool result = acceptCompoundStatement(statement); parseContext.popScope(); return result; } // statement // : attributes attributed_statement // // attributed_statement // : compound_statement // | SEMICOLON // | expression SEMICOLON // | declaration_statement // | selection_statement // | switch_statement // | case_label // | iteration_statement // | jump_statement // bool HlslGrammar::acceptStatement(TIntermNode*& statement) { statement = nullptr; // attributes TAttributeMap attributes; acceptAttributes(attributes); // attributed_statement switch (peek()) { case EHTokLeftBrace: return acceptScopedCompoundStatement(statement); case EHTokIf: return acceptSelectionStatement(statement); case EHTokSwitch: return acceptSwitchStatement(statement); case EHTokFor: case EHTokDo: case EHTokWhile: return acceptIterationStatement(statement); case EHTokContinue: case EHTokBreak: case EHTokDiscard: case EHTokReturn: return acceptJumpStatement(statement); case EHTokCase: return acceptCaseLabel(statement); case EHTokDefault: return acceptDefaultLabel(statement); case EHTokSemicolon: return acceptTokenClass(EHTokSemicolon); case EHTokRightBrace: // Performance: not strictly necessary, but stops a bunch of hunting early, // and is how sequences of statements end. return false; default: { // declaration if (acceptDeclaration(statement)) return true; // expression TIntermTyped* node; if (acceptExpression(node)) statement = node; else return false; // SEMICOLON (following an expression) if (! acceptTokenClass(EHTokSemicolon)) { expected(";"); return false; } } } return true; } // attributes // : list of zero or more of: LEFT_BRACKET attribute RIGHT_BRACKET // // attribute: // : UNROLL // | UNROLL LEFT_PAREN literal RIGHT_PAREN // | FASTOPT // | ALLOW_UAV_CONDITION // | BRANCH // | FLATTEN // | FORCECASE // | CALL // | DOMAIN // | EARLYDEPTHSTENCIL // | INSTANCE // | MAXTESSFACTOR // | OUTPUTCONTROLPOINTS // | OUTPUTTOPOLOGY // | PARTITIONING // | PATCHCONSTANTFUNC // | NUMTHREADS LEFT_PAREN x_size, y_size,z z_size RIGHT_PAREN // void HlslGrammar::acceptAttributes(TAttributeMap& attributes) { // For now, accept the [ XXX(X) ] syntax, but drop all but // numthreads, which is used to set the CS local size. // TODO: subset to correct set? Pass on? do { HlslToken idToken; // LEFT_BRACKET? if (! acceptTokenClass(EHTokLeftBracket)) return; // attribute if (acceptIdentifier(idToken)) { // 'idToken.string' is the attribute } else if (! peekTokenClass(EHTokRightBracket)) { expected("identifier"); advanceToken(); } TIntermAggregate* expressions = nullptr; // (x, ...) if (acceptTokenClass(EHTokLeftParen)) { expressions = new TIntermAggregate; TIntermTyped* node; bool expectingExpression = false; while (acceptAssignmentExpression(node)) { expectingExpression = false; expressions->getSequence().push_back(node); if (acceptTokenClass(EHTokComma)) expectingExpression = true; } // 'expressions' is an aggregate with the expressions in it if (! acceptTokenClass(EHTokRightParen)) expected(")"); // Error for partial or missing expression if (expectingExpression || expressions->getSequence().empty()) expected("expression"); } // RIGHT_BRACKET if (!acceptTokenClass(EHTokRightBracket)) { expected("]"); return; } // Add any values we found into the attribute map. This accepts // (and ignores) values not mapping to a known TAttributeType; attributes.setAttribute(idToken.string, expressions); } while (true); } // selection_statement // : IF LEFT_PAREN expression RIGHT_PAREN statement // : IF LEFT_PAREN expression RIGHT_PAREN statement ELSE statement // bool HlslGrammar::acceptSelectionStatement(TIntermNode*& statement) { TSourceLoc loc = token.loc; // IF if (! acceptTokenClass(EHTokIf)) return false; // so that something declared in the condition is scoped to the lifetimes // of the then-else statements parseContext.pushScope(); // LEFT_PAREN expression RIGHT_PAREN TIntermTyped* condition; if (! acceptParenExpression(condition)) return false; // create the child statements TIntermNodePair thenElse = { nullptr, nullptr }; // then statement if (! acceptScopedStatement(thenElse.node1)) { expected("then statement"); return false; } // ELSE if (acceptTokenClass(EHTokElse)) { // else statement if (! acceptScopedStatement(thenElse.node2)) { expected("else statement"); return false; } } // Put the pieces together statement = intermediate.addSelection(condition, thenElse, loc); parseContext.popScope(); return true; } // switch_statement // : SWITCH LEFT_PAREN expression RIGHT_PAREN compound_statement // bool HlslGrammar::acceptSwitchStatement(TIntermNode*& statement) { // SWITCH TSourceLoc loc = token.loc; if (! acceptTokenClass(EHTokSwitch)) return false; // LEFT_PAREN expression RIGHT_PAREN parseContext.pushScope(); TIntermTyped* switchExpression; if (! acceptParenExpression(switchExpression)) { parseContext.popScope(); return false; } // compound_statement parseContext.pushSwitchSequence(new TIntermSequence); bool statementOkay = acceptCompoundStatement(statement); if (statementOkay) statement = parseContext.addSwitch(loc, switchExpression, statement ? statement->getAsAggregate() : nullptr); parseContext.popSwitchSequence(); parseContext.popScope(); return statementOkay; } // iteration_statement // : WHILE LEFT_PAREN condition RIGHT_PAREN statement // | DO LEFT_BRACE statement RIGHT_BRACE WHILE LEFT_PAREN expression RIGHT_PAREN SEMICOLON // | FOR LEFT_PAREN for_init_statement for_rest_statement RIGHT_PAREN statement // // Non-speculative, only call if it needs to be found; WHILE or DO or FOR already seen. bool HlslGrammar::acceptIterationStatement(TIntermNode*& statement) { TSourceLoc loc = token.loc; TIntermTyped* condition = nullptr; EHlslTokenClass loop = peek(); assert(loop == EHTokDo || loop == EHTokFor || loop == EHTokWhile); // WHILE or DO or FOR advanceToken(); switch (loop) { case EHTokWhile: // so that something declared in the condition is scoped to the lifetime // of the while sub-statement parseContext.pushScope(); parseContext.nestLooping(); // LEFT_PAREN condition RIGHT_PAREN if (! acceptParenExpression(condition)) return false; // statement if (! acceptScopedStatement(statement)) { expected("while sub-statement"); return false; } parseContext.unnestLooping(); parseContext.popScope(); statement = intermediate.addLoop(statement, condition, nullptr, true, loc); return true; case EHTokDo: parseContext.nestLooping(); if (! acceptTokenClass(EHTokLeftBrace)) expected("{"); // statement if (! peekTokenClass(EHTokRightBrace) && ! acceptScopedStatement(statement)) { expected("do sub-statement"); return false; } if (! acceptTokenClass(EHTokRightBrace)) expected("}"); // WHILE if (! acceptTokenClass(EHTokWhile)) { expected("while"); return false; } // LEFT_PAREN condition RIGHT_PAREN TIntermTyped* condition; if (! acceptParenExpression(condition)) return false; if (! acceptTokenClass(EHTokSemicolon)) expected(";"); parseContext.unnestLooping(); statement = intermediate.addLoop(statement, condition, 0, false, loc); return true; case EHTokFor: { // LEFT_PAREN if (! acceptTokenClass(EHTokLeftParen)) expected("("); // so that something declared in the condition is scoped to the lifetime // of the for sub-statement parseContext.pushScope(); // initializer TIntermNode* initNode = nullptr; if (! acceptControlDeclaration(initNode)) { TIntermTyped* initExpr = nullptr; acceptExpression(initExpr); initNode = initExpr; } // SEMI_COLON if (! acceptTokenClass(EHTokSemicolon)) expected(";"); parseContext.nestLooping(); // condition SEMI_COLON acceptExpression(condition); if (! acceptTokenClass(EHTokSemicolon)) expected(";"); // iterator SEMI_COLON TIntermTyped* iterator = nullptr; acceptExpression(iterator); if (! acceptTokenClass(EHTokRightParen)) expected(")"); // statement if (! acceptScopedStatement(statement)) { expected("for sub-statement"); return false; } statement = intermediate.addForLoop(statement, initNode, condition, iterator, true, loc); parseContext.popScope(); parseContext.unnestLooping(); return true; } default: return false; } } // jump_statement // : CONTINUE SEMICOLON // | BREAK SEMICOLON // | DISCARD SEMICOLON // | RETURN SEMICOLON // | RETURN expression SEMICOLON // bool HlslGrammar::acceptJumpStatement(TIntermNode*& statement) { EHlslTokenClass jump = peek(); switch (jump) { case EHTokContinue: case EHTokBreak: case EHTokDiscard: case EHTokReturn: advanceToken(); break; default: // not something we handle in this function return false; } switch (jump) { case EHTokContinue: statement = intermediate.addBranch(EOpContinue, token.loc); break; case EHTokBreak: statement = intermediate.addBranch(EOpBreak, token.loc); break; case EHTokDiscard: statement = intermediate.addBranch(EOpKill, token.loc); break; case EHTokReturn: { // expression TIntermTyped* node; if (acceptExpression(node)) { // hook it up statement = parseContext.handleReturnValue(token.loc, node); } else statement = intermediate.addBranch(EOpReturn, token.loc); break; } default: assert(0); return false; } // SEMICOLON if (! acceptTokenClass(EHTokSemicolon)) expected(";"); return true; } // case_label // : CASE expression COLON // bool HlslGrammar::acceptCaseLabel(TIntermNode*& statement) { TSourceLoc loc = token.loc; if (! acceptTokenClass(EHTokCase)) return false; TIntermTyped* expression; if (! acceptExpression(expression)) { expected("case expression"); return false; } if (! acceptTokenClass(EHTokColon)) { expected(":"); return false; } statement = parseContext.intermediate.addBranch(EOpCase, expression, loc); return true; } // default_label // : DEFAULT COLON // bool HlslGrammar::acceptDefaultLabel(TIntermNode*& statement) { TSourceLoc loc = token.loc; if (! acceptTokenClass(EHTokDefault)) return false; if (! acceptTokenClass(EHTokColon)) { expected(":"); return false; } statement = parseContext.intermediate.addBranch(EOpDefault, loc); return true; } // array_specifier // : LEFT_BRACKET integer_expression RGHT_BRACKET ... // optional // : LEFT_BRACKET RGHT_BRACKET // optional // void HlslGrammar::acceptArraySpecifier(TArraySizes*& arraySizes) { arraySizes = nullptr; // Early-out if there aren't any array dimensions if (!peekTokenClass(EHTokLeftBracket)) return; // If we get here, we have at least one array dimension. This will track the sizes we find. arraySizes = new TArraySizes; // Collect each array dimension. while (acceptTokenClass(EHTokLeftBracket)) { TSourceLoc loc = token.loc; TIntermTyped* sizeExpr = nullptr; // Array sizing expression is optional. If omitted, array will be later sized by initializer list. const bool hasArraySize = acceptAssignmentExpression(sizeExpr); if (! acceptTokenClass(EHTokRightBracket)) { expected("]"); return; } if (hasArraySize) { TArraySize arraySize; parseContext.arraySizeCheck(loc, sizeExpr, arraySize); arraySizes->addInnerSize(arraySize); } else { arraySizes->addInnerSize(0); // sized by initializers. } } } // post_decls // : COLON semantic // optional // COLON PACKOFFSET LEFT_PAREN c[Subcomponent][.component] RIGHT_PAREN // optional // COLON REGISTER LEFT_PAREN [shader_profile,] Type#[subcomp]opt (COMMA SPACEN)opt RIGHT_PAREN // optional // COLON LAYOUT layout_qualifier_list // annotations // optional // // Return true if any tokens were accepted. That is, // false can be returned on successfully recognizing nothing, // not necessarily meaning bad syntax. // bool HlslGrammar::acceptPostDecls(TQualifier& qualifier) { bool found = false; do { // COLON if (acceptTokenClass(EHTokColon)) { found = true; HlslToken idToken; if (peekTokenClass(EHTokLayout)) acceptLayoutQualifierList(qualifier); else if (acceptTokenClass(EHTokPackOffset)) { // PACKOFFSET LEFT_PAREN c[Subcomponent][.component] RIGHT_PAREN if (! acceptTokenClass(EHTokLeftParen)) { expected("("); return false; } HlslToken locationToken; if (! acceptIdentifier(locationToken)) { expected("c[subcomponent][.component]"); return false; } HlslToken componentToken; if (acceptTokenClass(EHTokDot)) { if (! acceptIdentifier(componentToken)) { expected("component"); return false; } } if (! acceptTokenClass(EHTokRightParen)) { expected(")"); break; } parseContext.handlePackOffset(locationToken.loc, qualifier, *locationToken.string, componentToken.string); } else if (! acceptIdentifier(idToken)) { expected("layout, semantic, packoffset, or register"); return false; } else if (*idToken.string == "register") { // REGISTER LEFT_PAREN [shader_profile,] Type#[subcomp]opt (COMMA SPACEN)opt RIGHT_PAREN // LEFT_PAREN if (! acceptTokenClass(EHTokLeftParen)) { expected("("); return false; } HlslToken registerDesc; // for Type# HlslToken profile; if (! acceptIdentifier(registerDesc)) { expected("register number description"); return false; } if (registerDesc.string->size() > 1 && !isdigit((*registerDesc.string)[1]) && acceptTokenClass(EHTokComma)) { // Then we didn't really see the registerDesc yet, it was // actually the profile. Adjust... profile = registerDesc; if (! acceptIdentifier(registerDesc)) { expected("register number description"); return false; } } int subComponent = 0; if (acceptTokenClass(EHTokLeftBracket)) { // LEFT_BRACKET subcomponent RIGHT_BRACKET if (! peekTokenClass(EHTokIntConstant)) { expected("literal integer"); return false; } subComponent = token.i; advanceToken(); if (! acceptTokenClass(EHTokRightBracket)) { expected("]"); break; } } // (COMMA SPACEN)opt HlslToken spaceDesc; if (acceptTokenClass(EHTokComma)) { if (! acceptIdentifier(spaceDesc)) { expected ("space identifier"); return false; } } // RIGHT_PAREN if (! acceptTokenClass(EHTokRightParen)) { expected(")"); break; } parseContext.handleRegister(registerDesc.loc, qualifier, profile.string, *registerDesc.string, subComponent, spaceDesc.string); } else { // semantic, in idToken.string parseContext.handleSemantic(idToken.loc, qualifier, mapSemantic(*idToken.string)); } } else if (peekTokenClass(EHTokLeftAngle)) { found = true; acceptAnnotations(qualifier); } else break; } while (true); return found; } } // end namespace glslang