diff options
Diffstat (limited to 'src/3rdparty/glslang/hlsl/hlslGrammar.cpp')
-rw-r--r-- | src/3rdparty/glslang/hlsl/hlslGrammar.cpp | 4170 |
1 files changed, 4170 insertions, 0 deletions
diff --git a/src/3rdparty/glslang/hlsl/hlslGrammar.cpp b/src/3rdparty/glslang/hlsl/hlslGrammar.cpp new file mode 100644 index 0000000..45cf5d5 --- /dev/null +++ b/src/3rdparty/glslang/hlsl/hlslGrammar.cpp @@ -0,0 +1,4170 @@ +// +// Copyright (C) 2016-2018 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, ""); +} + +// IDENTIFIER +// THIS +// type that can be used as IDENTIFIER +// +// Only process the next token if it is an identifier. +// Return true if it was an identifier. +bool HlslGrammar::acceptIdentifier(HlslToken& idToken) +{ + // IDENTIFIER + if (peekTokenClass(EHTokIdentifier)) { + idToken = token; + advanceToken(); + return true; + } + + // THIS + // -> maps to the IDENTIFIER spelled with the internal special name for 'this' + if (peekTokenClass(EHTokThis)) { + idToken = token; + advanceToken(); + idToken.tokenClass = EHTokIdentifier; + idToken.string = NewPoolTString(intermediate.implicitThisName); + return true; + } + + // type that can be used as IDENTIFIER + + // 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. + + const char* idString = getTypeString(peek()); + if (idString == nullptr) + return false; + + token.string = NewPoolTString(idString); + token.tokenClass = EHTokIdentifier; + idToken = token; + typeIdentifiers = true; + + advanceToken(); + + return true; +} + +// compilationUnit +// : declaration_list EOF +// +bool HlslGrammar::acceptCompilationUnit() +{ + if (! acceptDeclarationList(unitNode)) + return false; + + if (! peekTokenClass(EHTokNone)) + return false; + + // set root of AST + if (unitNode && !unitNode->getAsAggregate()) + unitNode = intermediate.growAggregate(nullptr, unitNode); + intermediate.setTreeRoot(unitNode); + + return true; +} + +// Recognize the following, but with the extra condition that it can be +// successfully terminated by EOF or '}'. +// +// declaration_list +// : list of declaration_or_semicolon followed by EOF or RIGHT_BRACE +// +// declaration_or_semicolon +// : declaration +// : SEMICOLON +// +bool HlslGrammar::acceptDeclarationList(TIntermNode*& nodeList) +{ + do { + // HLSL allows extra semicolons between global declarations + do { } while (acceptTokenClass(EHTokSemicolon)); + + // EOF or RIGHT_BRACE + if (peekTokenClass(EHTokNone) || peekTokenClass(EHTokRightBrace)) + return true; + + // declaration + if (! acceptDeclaration(nodeList)) + return false; + } while (true); + + 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 +// : attributes attributed_declaration +// | NAMESPACE IDENTIFIER LEFT_BRACE declaration_list RIGHT_BRACE +// +// attributed_declaration +// : sampler_declaration_dx9 post_decls SEMICOLON +// | fully_specified_type // for cbuffer/tbuffer +// | fully_specified_type declarator_list SEMICOLON // for non cbuffer/tbuffer +// | 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 be 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) +{ + // NAMESPACE IDENTIFIER LEFT_BRACE declaration_list RIGHT_BRACE + if (acceptTokenClass(EHTokNamespace)) { + HlslToken namespaceToken; + if (!acceptIdentifier(namespaceToken)) { + expected("namespace name"); + return false; + } + parseContext.pushNamespace(*namespaceToken.string); + if (!acceptTokenClass(EHTokLeftBrace)) { + expected("{"); + return false; + } + if (!acceptDeclarationList(nodeList)) { + expected("declaration list"); + return false; + } + if (!acceptTokenClass(EHTokRightBrace)) { + expected("}"); + return false; + } + parseContext.popNamespace(); + return true; + } + + bool declarator_list = false; // true when processing comma separation + + // attributes + TFunctionDeclarator declarator; + acceptAttributes(declarator.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; + + bool forbidDeclarators = (peekTokenClass(EHTokCBuffer) || peekTokenClass(EHTokTBuffer)); + // fully_specified_type + if (! acceptFullySpecifiedType(declaredType, nodeList, declarator.attributes, forbidDeclarators)) + return false; + + // cbuffer and tbuffer end with the closing '}'. + // No semicolon is included. + if (forbidDeclarators) + return true; + + // declarator_list + // : declarator + // : identifier + HlslToken idToken; + TIntermAggregate* initializers = nullptr; + while (acceptIdentifier(idToken)) { + TString *fullName = idToken.string; + if (parseContext.symbolTable.atGlobalLevel()) + parseContext.getFullNamespaceName(fullName); + if (peekTokenClass(EHTokLeftParen)) { + // looks like function parameters + + // merge in the attributes into the return type + parseContext.transferTypeAttributes(token.loc, declarator.attributes, declaredType, true); + + // Potentially rename shader entry point function. No-op most of the time. + parseContext.renameShaderFunction(fullName); + + // function_parameters + declarator.function = new TFunction(fullName, declaredType); + if (!acceptFunctionParameters(*declarator.function)) { + expected("function parameter list"); + return false; + } + + // post_decls + acceptPostDecls(declarator.function->getWritableType().getQualifier()); + + // compound_statement (function body definition) or just a prototype? + declarator.loc = token.loc; + 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(declarator, nodeList, nullptr); + } else { + if (typedefDecl) + parseContext.error(idToken.loc, "function typedefs not implemented", "{", ""); + parseContext.handleFunctionDeclarator(declarator.loc, *declarator.function, true); + } + } else { + // A variable declaration. + + // merge in the attributes, the first time around, into the shared type + if (! declarator_list) + parseContext.transferTypeAttributes(token.loc, declarator.attributes, declaredType); + + // Fix the storage qualifier if it's a global. + if (declaredType.getQualifier().storage == EvqTemporary && parseContext.symbolTable.atGlobalLevel()) + declaredType.getQualifier().storage = EvqUniform; + + // recognize array_specifier + TArraySizes* arraySizes = nullptr; + acceptArraySpecifier(arraySizes); + + // We can handle multiple variables per type declaration, so + // the number of types can expand when arrayness is different. + TType variableType; + variableType.shallowCopy(declaredType); + + // 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. + variableType.transferArraySizes(arraySizes); + variableType.copyArrayInnerSizes(declaredType.getArraySizes()); + + // 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, *fullName, variableType); + else if (variableType.getBasicType() == EbtBlock) { + if (expressionNode) + parseContext.error(idToken.loc, "buffer aliasing not yet supported", "block initializer", ""); + parseContext.declareBlock(idToken.loc, variableType, fullName); + parseContext.declareStructBufferCounter(idToken.loc, variableType, *fullName); + } 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, *fullName); + } 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, *fullName, variableType, expressionNode), + idToken.loc); + } + } + } + } + + // COMMA + if (acceptTokenClass(EHTokComma)) + declarator_list = true; + } + + // The top-level initializer node is a sequence. + if (initializers != nullptr) + initializers->setOperator(EOpSequence); + + // if we have a locally scoped static, it needs a globally scoped initializer + if (declaredType.getQualifier().storage == EvqGlobal && !parseContext.symbolTable.atGlobalLevel()) { + unitNode = intermediate.growAggregate(unitNode, initializers, idToken.loc); + } else { + // 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; + TAttributes attributes; + + // fully_specified_type + TType type; + if (! acceptFullySpecifiedType(type, attributes)) + return false; + + if (attributes.size() > 0) + parseContext.warn(token.loc, "attributes don't apply to control declaration", "", ""); + + // 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, const TAttributes& attributes) +{ + TIntermNode* nodeList = nullptr; + return acceptFullySpecifiedType(type, nodeList, attributes); +} +bool HlslGrammar::acceptFullySpecifiedType(TType& type, TIntermNode*& nodeList, const TAttributes& attributes, bool forbidDeclarators) +{ + // 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); + + // merge in the attributes + parseContext.transferTypeAttributes(token.loc, attributes, type); + + // further, it can create an anonymous instance of the block + // (cbuffer and tbuffer don't consume the next identifier, and + // should set forbidDeclarators) + if (forbidDeclarators || peek() != EHTokIdentifier) + 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 == EvqOut || + type.getQualifier().storage == EvqBuffer) { + qualifier.storage = type.getQualifier().storage; + qualifier.readonly = type.getQualifier().readonly; + } + + if (type.isBuiltIn()) + qualifier.builtIn = type.getQualifier().builtIn; + + 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 = EvqGlobal; + 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 = (qualifier.storage == EvqOut) ? EvqInOut : EvqIn; + break; + case EHTokOut: + qualifier.storage = (qualifier.storage == EvqIn) ? EvqInOut : EvqOut; + break; + case EHTokInOut: + qualifier.storage = EvqInOut; + break; + case EHTokLayout: + if (! acceptLayoutQualifierList(qualifier)) + return false; + continue; + case EHTokGloballyCoherent: + qualifier.coherent = true; + break; + case EHTokInline: + // TODO: map this to SPIR-V function control + 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(TBuiltInVariable& patchType) +{ + // read geometry type + const EHlslTokenClass tessType = peek(); + + switch (tessType) { + case EHTokInputPatch: patchType = EbvInputPatch; break; + case EHTokOutputPatch: patchType = EbvOutputPatch; 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) +{ + TBuiltInVariable patchType; + + if (! acceptTessellationDeclType(patchType)) + 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.transferArraySizes(arraySizes); + type.getQualifier().builtIn = patchType; + + 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 = EvqOut; + type.getQualifier().builtIn = EbvGsOutputStream; + + 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; +} + +// subpass input type +// : SUBPASSINPUT +// | SUBPASSINPUT VECTOR LEFT_ANGLE template_type RIGHT_ANGLE +// | SUBPASSINPUTMS +// | SUBPASSINPUTMS VECTOR LEFT_ANGLE template_type RIGHT_ANGLE +bool HlslGrammar::acceptSubpassInputType(TType& type) +{ + // read subpass type + const EHlslTokenClass subpassInputType = peek(); + + bool multisample; + + switch (subpassInputType) { + case EHTokSubpassInput: multisample = false; break; + case EHTokSubpassInputMS: multisample = true; break; + default: + return false; // not a subpass input declaration + } + + advanceToken(); // consume the sampler type keyword + + TType subpassType(EbtFloat, EvqUniform, 4); // default type is float4 + + if (acceptTokenClass(EHTokLeftAngle)) { + if (! acceptType(subpassType)) { + expected("scalar or vector type"); + return false; + } + + const TBasicType basicRetType = subpassType.getBasicType() ; + + switch (basicRetType) { + case EbtFloat: + case EbtUint: + case EbtInt: + case EbtStruct: + break; + default: + unimplemented("basic type in subpass input"); + return false; + } + + if (! acceptTokenClass(EHTokRightAngle)) { + expected("right angle bracket"); + return false; + } + } + + const TBasicType subpassBasicType = subpassType.isStruct() ? (*subpassType.getStruct())[0].type->getBasicType() + : subpassType.getBasicType(); + + TSampler sampler; + sampler.setSubpass(subpassBasicType, multisample); + + // Remember the declared return type. Function returns false on error. + if (!parseContext.setTextureReturnType(sampler, subpassType, token.loc)) + return false; + + type.shallowCopy(TType(sampler, EvqUniform)); + + return true; +} + +// sampler_type for DX9 compatibility +// : SAMPLER +// | SAMPLER1D +// | SAMPLER2D +// | SAMPLER3D +// | SAMPLERCUBE +bool HlslGrammar::acceptSamplerTypeDX9(TType &type) +{ + // read sampler type + const EHlslTokenClass samplerType = peek(); + + TSamplerDim dim = EsdNone; + TType txType(EbtFloat, EvqUniform, 4); // default type is float4 + + bool isShadow = false; + + switch (samplerType) + { + case EHTokSampler: dim = Esd2D; break; + case EHTokSampler1d: dim = Esd1D; break; + case EHTokSampler2d: dim = Esd2D; break; + case EHTokSampler3d: dim = Esd3D; break; + case EHTokSamplerCube: dim = EsdCube; break; + default: + return false; // not a dx9 sampler declaration + } + + advanceToken(); // consume the sampler type keyword + + TArraySizes *arraySizes = nullptr; // TODO: array + + TSampler sampler; + sampler.set(txType.getBasicType(), dim, false, isShadow, false); + + if (!parseContext.setTextureReturnType(sampler, txType, token.loc)) + return false; + + type.shallowCopy(TType(sampler, EvqUniform, arraySizes)); + type.getQualifier().layoutFormat = ElfNone; + + 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; + bool combined = true; + + switch (textureType) { + case EHTokBuffer: dim = EsdBuffer; combined = false; 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() ; + + switch (basicRetType) { + case EbtFloat: + case EbtUint: + case EbtInt: + case EbtStruct: + break; + default: + 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() && !txType.isStruct()) { + expected("scalar, vector, or struct 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); + + const TBasicType txBasicType = txType.isStruct() ? (*txType.getStruct())[0].type->getBasicType() + : txType.getBasicType(); + + // 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(txBasicType, dim, array, shadow, ms); + } else { + sampler.setTexture(txBasicType, dim, array, shadow, ms); + } + } + + // Remember the declared return type. Function returns false on error. + if (!parseContext.setTextureReturnType(sampler, txType, token.loc)) + return false; + + // Force uncombined, if necessary + if (!combined) + sampler.combined = false; + + 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, use native halfs if the option allows them. + bool enable16BitTypes = parseContext.hlslEnable16BitTypes(); + + const TBasicType min16float_bt = enable16BitTypes ? EbtFloat16 : EbtFloat; + const TBasicType min10float_bt = enable16BitTypes ? EbtFloat16 : EbtFloat; + const TBasicType half_bt = enable16BitTypes ? EbtFloat16 : EbtFloat; + const TBasicType min16int_bt = enable16BitTypes ? EbtInt16 : EbtInt; + const TBasicType min12int_bt = enable16BitTypes ? EbtInt16 : EbtInt; + const TBasicType min16uint_bt = enable16BitTypes ? EbtUint16 : EbtUint; + + // Some types might have turned into identifiers. Take the hit for checking + // when this has happened. + if (typeIdentifiers) { + const char* identifierString = getTypeString(peek()); + if (identifierString != nullptr) { + TString name = identifierString; + // if it's an identifier, it's not a type + if (parseContext.symbolTable.find(name) != nullptr) + return false; + } + } + + bool isUnorm = false; + bool isSnorm = false; + + // Accept snorm and unorm. Presently, this is ignored, save for an error check below. + switch (peek()) { + case EHTokUnorm: + isUnorm = true; + advanceToken(); // eat the token + break; + case EHTokSNorm: + isSnorm = true; + advanceToken(); // eat the token + break; + default: + break; + } + + 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: // ... + if (parseContext.hlslDX9Compatible()) + return acceptSamplerTypeDX9(type); + else + return acceptSamplerType(type); + break; + + case EHTokSamplerState: // fall through + case EHTokSamplerComparisonState: // ... + return acceptSamplerType(type); + break; + + case EHTokSubpassInput: // fall through + case EHTokSubpassInputMS: // ... + return acceptSubpassInputType(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 EHTokTextureBuffer: + return acceptTextureBufferType(type); + break; + + case EHTokConstantBuffer: + return acceptConstantBufferType(type); + + 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. + if (parseContext.lookupUserType(*token.string, type) != 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 EHTokUint64: + new(&type) TType(EbtUint64); + 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); + break; + case EHTokHalf1: + new(&type) TType(half_bt, EvqTemporary); + type.makeVector(); + break; + case EHTokHalf2: + new(&type) TType(half_bt, EvqTemporary, 2); + break; + case EHTokHalf3: + new(&type) TType(half_bt, EvqTemporary, 3); + break; + case EHTokHalf4: + new(&type) TType(half_bt, EvqTemporary, 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 EHTokHalf1x1: + new(&type) TType(half_bt, EvqTemporary, 0, 1, 1); + break; + case EHTokHalf1x2: + new(&type) TType(half_bt, EvqTemporary, 0, 1, 2); + break; + case EHTokHalf1x3: + new(&type) TType(half_bt, EvqTemporary, 0, 1, 3); + break; + case EHTokHalf1x4: + new(&type) TType(half_bt, EvqTemporary, 0, 1, 4); + break; + case EHTokHalf2x1: + new(&type) TType(half_bt, EvqTemporary, 0, 2, 1); + break; + case EHTokHalf2x2: + new(&type) TType(half_bt, EvqTemporary, 0, 2, 2); + break; + case EHTokHalf2x3: + new(&type) TType(half_bt, EvqTemporary, 0, 2, 3); + break; + case EHTokHalf2x4: + new(&type) TType(half_bt, EvqTemporary, 0, 2, 4); + break; + case EHTokHalf3x1: + new(&type) TType(half_bt, EvqTemporary, 0, 3, 1); + break; + case EHTokHalf3x2: + new(&type) TType(half_bt, EvqTemporary, 0, 3, 2); + break; + case EHTokHalf3x3: + new(&type) TType(half_bt, EvqTemporary, 0, 3, 3); + break; + case EHTokHalf3x4: + new(&type) TType(half_bt, EvqTemporary, 0, 3, 4); + break; + case EHTokHalf4x1: + new(&type) TType(half_bt, EvqTemporary, 0, 4, 1); + break; + case EHTokHalf4x2: + new(&type) TType(half_bt, EvqTemporary, 0, 4, 2); + break; + case EHTokHalf4x3: + new(&type) TType(half_bt, EvqTemporary, 0, 4, 3); + break; + case EHTokHalf4x4: + new(&type) TType(half_bt, 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(); + + if ((isUnorm || isSnorm) && !type.isFloatingDomain()) { + parseContext.error(token.loc, "unorm and snorm only valid in floating point domain", "", ""); + return false; + } + + 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; + bool readonly = false; + + if (acceptTokenClass(EHTokCBuffer)) { + // CBUFFER + storageQualifier = EvqUniform; + } else if (acceptTokenClass(EHTokTBuffer)) { + // TBUFFER + storageQualifier = EvqBuffer; + readonly = true; + } else if (! acceptTokenClass(EHTokClass) && ! acceptTokenClass(EHTokStruct)) { + // Neither CLASS nor STRUCT + return false; + } + + // Now known to be one of CBUFFER, TBUFFER, CLASS, or STRUCT + + + // IDENTIFIER. It might also be a keyword which can double as an identifier. + // For example: 'cbuffer ConstantBuffer' or 'struct ConstantBuffer' is legal. + // 'cbuffer int' is also legal, and 'struct int' appears rejected only because + // it attempts to redefine the 'int' type. + const char* idString = getTypeString(peek()); + TString structName = ""; + if (peekTokenClass(EHTokIdentifier) || idString != nullptr) { + if (idString != nullptr) + structName = *idString; + else + structName = *token.string; + advanceToken(); + } + + // post_decls + TQualifier postDeclQualifier; + postDeclQualifier.clear(); + bool postDeclsFound = acceptPostDecls(postDeclQualifier); + + // LEFT_BRACE, or + // 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; + // Save each member function so they can be processed after we have a fully formed 'this'. + TVector<TFunctionDeclarator> functionDeclarators; + + parseContext.pushNamespace(structName); + bool acceptedList = acceptStructDeclarationList(typeList, nodeList, functionDeclarators); + parseContext.popNamespace(); + + if (! acceptedList) { + 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; + postDeclQualifier.readonly = readonly; + new(&type) TType(typeList, structName, postDeclQualifier); // sets EbtBlock + } + + parseContext.declareStruct(token.loc, structName, type); + + // For member functions: now that we know the type of 'this', go back and + // - add their implicit argument with 'this' (not to the mangling, just the argument list) + // - parse the functions, their tokens were saved for deferred parsing (now) + for (int b = 0; b < (int)functionDeclarators.size(); ++b) { + // update signature + if (functionDeclarators[b].function->hasImplicitThis()) + functionDeclarators[b].function->addThisParameter(type, intermediate.implicitThisName); + } + + // All member functions get parsed inside the class/struct namespace and with the + // class/struct members in a symbol-table level. + parseContext.pushNamespace(structName); + parseContext.pushThisScope(type, functionDeclarators); + bool deferredSuccess = true; + for (int b = 0; b < (int)functionDeclarators.size() && deferredSuccess; ++b) { + // parse body + pushTokenStream(functionDeclarators[b].body); + if (! acceptFunctionBody(functionDeclarators[b], nodeList)) + deferredSuccess = false; + popTokenStream(); + } + parseContext.popThisScope(); + parseContext.popNamespace(); + + return deferredSuccess; +} + +// constantbuffer +// : CONSTANTBUFFER LEFT_ANGLE type RIGHT_ANGLE +bool HlslGrammar::acceptConstantBufferType(TType& type) +{ + if (! acceptTokenClass(EHTokConstantBuffer)) + return false; + + if (! acceptTokenClass(EHTokLeftAngle)) { + expected("left angle bracket"); + return false; + } + + TType templateType; + if (! acceptType(templateType)) { + expected("type"); + return false; + } + + if (! acceptTokenClass(EHTokRightAngle)) { + expected("right angle bracket"); + return false; + } + + TQualifier postDeclQualifier; + postDeclQualifier.clear(); + postDeclQualifier.storage = EvqUniform; + + if (templateType.isStruct()) { + // Make a block from the type parsed as the template argument + TTypeList* typeList = templateType.getWritableStruct(); + new(&type) TType(typeList, "", postDeclQualifier); // sets EbtBlock + + type.getQualifier().storage = EvqUniform; + + return true; + } else { + parseContext.error(token.loc, "non-structure type in ConstantBuffer", "", ""); + return false; + } +} + +// texture_buffer +// : TEXTUREBUFFER LEFT_ANGLE type RIGHT_ANGLE +bool HlslGrammar::acceptTextureBufferType(TType& type) +{ + if (! acceptTokenClass(EHTokTextureBuffer)) + return false; + + if (! acceptTokenClass(EHTokLeftAngle)) { + expected("left angle bracket"); + return false; + } + + TType templateType; + if (! acceptType(templateType)) { + expected("type"); + return false; + } + + if (! acceptTokenClass(EHTokRightAngle)) { + expected("right angle bracket"); + return false; + } + + templateType.getQualifier().storage = EvqBuffer; + templateType.getQualifier().readonly = true; + + TType blockType(templateType.getWritableStruct(), "", templateType.getQualifier()); + + blockType.getQualifier().storage = EvqBuffer; + blockType.getQualifier().readonly = true; + + type.shallowCopy(blockType); + + 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; + TBuiltInVariable builtinType = EbvNone; + + switch (structBuffType) { + case EHTokAppendStructuredBuffer: + builtinType = EbvAppendConsume; + break; + case EHTokByteAddressBuffer: + hasTemplateType = false; + readonly = true; + builtinType = EbvByteAddressBuffer; + break; + case EHTokConsumeStructuredBuffer: + builtinType = EbvAppendConsume; + break; + case EHTokRWByteAddressBuffer: + hasTemplateType = false; + builtinType = EbvRWByteAddressBuffer; + break; + case EHTokRWStructuredBuffer: + builtinType = EbvRWStructuredBuffer; + break; + case EHTokStructuredBuffer: + builtinType = EbvStructuredBuffer; + 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> ==> 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 = new TArraySizes; + unsizedArray->addInnerSize(UnsizedArraySize); + templateType->transferArraySizes(unsizedArray); + templateType->getQualifier().storage = storage; + + // field name is canonical for all structbuffers + templateType->setFieldName("@data"); + + 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; + blockType.getQualifier().builtIn = builtinType; + + // 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 +// : attributes fully_specified_type struct_declarator COMMA struct_declarator ... +// | attributes 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, + TVector<TFunctionDeclarator>& declarators) +{ + typeList = new TTypeList(); + HlslToken idToken; + + do { + // success on seeing the RIGHT_BRACE coming up + if (peekTokenClass(EHTokRightBrace)) + break; + + // struct_declaration + + // attributes + TAttributes attributes; + acceptAttributes(attributes); + + bool declarator_list = false; + + // fully_specified_type + TType memberType; + if (! acceptFullySpecifiedType(memberType, nodeList, attributes)) { + expected("member type"); + return false; + } + + // merge in the attributes + parseContext.transferTypeAttributes(token.loc, attributes, memberType); + + // 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) { + declarators.resize(declarators.size() + 1); + // request a token stream for deferred processing + functionDefinitionAccepted = acceptMemberFunctionDefinition(nodeList, memberType, *idToken.string, + declarators.back()); + 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->transferArraySizes(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); + + return 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 TType& type, TString& memberName, + TFunctionDeclarator& declarator) +{ + bool accepted = false; + + TString* functionName = &memberName; + parseContext.getFullNamespaceName(functionName); + declarator.function = new TFunction(functionName, type); + if (type.getQualifier().storage == EvqTemporary) + declarator.function->setImplicitThis(); + else + declarator.function->setIllegalImplicitThis(); + + // function_parameters + if (acceptFunctionParameters(*declarator.function)) { + // post_decls + acceptPostDecls(declarator.function->getWritableType().getQualifier()); + + // compound_statement (function body definition) + if (peekTokenClass(EHTokLeftBrace)) { + declarator.loc = token.loc; + declarator.body = new TVector<HlslToken>; + accepted = acceptFunctionDefinition(declarator, nodeList, declarator.body); + } + } else + expected("function parameter list"); + + 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.makeConstructorCall(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 (node == nullptr) + return false; + + // 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 +// : attributes attributed_declaration +// +// attributed_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) +{ + // attributes + TAttributes attributes; + acceptAttributes(attributes); + + // fully_specified_type + TType* type = new TType; + if (! acceptFullySpecifiedType(*type, attributes)) + return false; + + // merge in the attributes + parseContext.transferTypeAttributes(token.loc, attributes, *type); + + // identifier + HlslToken idToken; + acceptIdentifier(idToken); + + // array_specifier + TArraySizes* arraySizes = nullptr; + acceptArraySpecifier(arraySizes); + if (arraySizes) { + if (arraySizes->hasUnsized()) { + parseContext.error(token.loc, "function parameter requires array size", "[]", ""); + return false; + } + + type->transferArraySizes(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). +// +// If 'deferredTokens' are passed in, just get the token stream, +// don't process. +// +bool HlslGrammar::acceptFunctionDefinition(TFunctionDeclarator& declarator, TIntermNode*& nodeList, + TVector<HlslToken>* deferredTokens) +{ + parseContext.handleFunctionDeclarator(declarator.loc, *declarator.function, false /* not prototype */); + + if (deferredTokens) + return captureBlockTokens(*deferredTokens); + else + return acceptFunctionBody(declarator, nodeList); +} + +bool HlslGrammar::acceptFunctionBody(TFunctionDeclarator& declarator, TIntermNode*& nodeList) +{ + // we might get back an entry-point + TIntermNode* entryPointNode = nullptr; + + // This does a pushScope() + TIntermNode* functionNode = parseContext.handleFunctionDefinition(declarator.loc, *declarator.function, + declarator.attributes, entryPointNode); + + // compound_statement + TIntermNode* functionBody = nullptr; + if (! acceptCompoundStatement(functionBody)) + return false; + + // this does a popScope() + parseContext.handleFunctionBody(declarator.loc, *declarator.function, 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) +{ + expression = nullptr; + + // 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; + } + + const bool firstNode = (node == nullptr); + + node = intermediate.growAggregate(node, expr, loc); + + // If every sub-node in the list has qualifier EvqConst, the returned node becomes + // EvqConst. Otherwise, it becomes EvqTemporary. That doesn't happen with e.g. + // EvqIn or EvqPosition, since the collection isn't EvqPosition if all the members are. + if (firstNode && expr->getQualifier().storage == EvqConst) + node->getQualifier().storage = EvqConst; + else if (expr->getQualifier().storage != EvqConst) + node->getQualifier().storage = EvqTemporary; + + // 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; + + node = parseContext.convertConditionalExpression(token.loc, node, false); + if (node == nullptr) + return false; + + ++parseContext.controlFlowNestingLevel; // this only needs to work right if no errors + + 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; + } + + --parseContext.controlFlowNestingLevel; + + 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)) { + // recognize any array_specifier as part of the type + TArraySizes* arraySizes = nullptr; + acceptArraySpecifier(arraySizes); + if (arraySizes != nullptr) + castType.transferArraySizes(arraySizes); + TSourceLoc loc = token.loc; + if (acceptTokenClass(EHTokRightParen)) { + // We've matched "(type)" now, get the expression to cast + if (! acceptUnaryExpression(node)) + return false; + + // Hook it up like a constructor + TFunction* constructorFunction = parseContext.makeConstructorCall(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 node != nullptr; + } else { + // This could be a parenthesized constructor, ala (int(3)), and we just accepted + // the '(int' part. We must back up twice. + recedeToken(); + recedeToken(); + + // Note, there are no array constructors like + // (float[2](...)) + if (arraySizes != nullptr) + parseContext.error(loc, "parenthesized array constructor not allowed", "([]())", "", ""); + } + } 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 [ COLONCOLON IDENTIFIER [ COLONCOLON IDENTIFIER ... ] ] +// | function_call +// | postfix_expression LEFT_BRACKET integer_expression RIGHT_BRACKET +// | postfix_expression DOT IDENTIFIER +// | postfix_expression DOT IDENTIFIER arguments +// | postfix_expression 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; + + // 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) + } else if (acceptConstructor(node)) { + // constructor (nothing else to do yet) + } else if (acceptIdentifier(idToken)) { + // user-type, namespace name, variable, or function name + TString* fullName = idToken.string; + while (acceptTokenClass(EHTokColonColon)) { + // user-type or namespace name + fullName = NewPoolTString(fullName->c_str()); + fullName->append(parseContext.scopeMangler); + if (acceptIdentifier(idToken)) + fullName->append(*idToken.string); + else { + expected("identifier after ::"); + return false; + } + } + if (! peekTokenClass(EHTokLeftParen)) { + node = parseContext.handleVariable(idToken.loc, fullName); + if (node == nullptr) + return false; + } else if (acceptFunctionCall(idToken.loc, *fullName, node, nullptr)) { + // function_call (nothing else to do yet) + } else { + expected("function call arguments"); + return false; + } + } else { + // nothing found, can't post operate + return false; + } + + // 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.loc, *field.string, node, thisNode)) { + 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); + if (node == nullptr) + return false; + 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.makeConstructorCall(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 node != nullptr; + } + + return false; +} + +// The function_call identifier was already recognized, and passed in as idToken. +// +// function_call +// : [idToken] arguments +// +bool HlslGrammar::acceptFunctionCall(const TSourceLoc& loc, TString& name, TIntermTyped*& node, TIntermTyped* baseObject) +{ + // name + TString* functionName = nullptr; + if (baseObject == nullptr) { + functionName = &name; + } else if (parseContext.isBuiltInMethod(loc, baseObject, name)) { + // Built-in methods are not in the symbol table as methods, but as global functions + // taking an explicit 'this' as the first argument. + functionName = NewPoolTString(BUILTIN_PREFIX); + functionName->append(name); + } else { + if (! baseObject->getType().isStruct()) { + expected("structure"); + return false; + } + functionName = NewPoolTString(""); + functionName->append(baseObject->getType().getTypeName()); + parseContext.addScopeMangler(*functionName); + functionName->append(name); + } + + // function + TFunction* function = new TFunction(functionName, TType(EbtVoid)); + + // arguments + TIntermTyped* arguments = nullptr; + if (baseObject != nullptr) { + // Non-static member functions have an implicit first argument of the base object. + parseContext.handleFunctionArgument(function, arguments, baseObject); + } + if (! acceptArguments(function, arguments)) + return false; + + // call + node = parseContext.handleFunctionCall(loc, function, arguments); + + return node != nullptr; +} + +// 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; + + // RIGHT_PAREN + if (acceptTokenClass(EHTokRightParen)) + return true; + + // must now be at least one expression... + do { + // expression + TIntermTyped* arg; + if (! acceptAssignmentExpression(arg)) + return false; + + // 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 EHTokFloat16Constant: + node = intermediate.addConstantUnion(token.d, EbtFloat16, 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; +} + +// simple_statement +// : SEMICOLON +// | declaration_statement +// | expression SEMICOLON +// +bool HlslGrammar::acceptSimpleStatement(TIntermNode*& statement) +{ + // SEMICOLON + if (acceptTokenClass(EHTokSemicolon)) + return true; + + // declaration + if (acceptDeclaration(statement)) + return true; + + // expression + TIntermTyped* node; + if (acceptExpression(node)) + statement = node; + else + return false; + + // SEMICOLON (following an expression) + if (acceptTokenClass(EHTokSemicolon)) + return true; + else { + expected(";"); + return false; + } +} + +// 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 +// | simple_statement +// | selection_statement +// | switch_statement +// | case_label +// | default_label +// | iteration_statement +// | jump_statement +// +bool HlslGrammar::acceptStatement(TIntermNode*& statement) +{ + statement = nullptr; + + // attributes + TAttributes attributes; + acceptAttributes(attributes); + + // attributed_statement + switch (peek()) { + case EHTokLeftBrace: + return acceptScopedCompoundStatement(statement); + + case EHTokIf: + return acceptSelectionStatement(statement, attributes); + + case EHTokSwitch: + return acceptSwitchStatement(statement, attributes); + + case EHTokFor: + case EHTokDo: + case EHTokWhile: + return acceptIterationStatement(statement, attributes); + + case EHTokContinue: + case EHTokBreak: + case EHTokDiscard: + case EHTokReturn: + return acceptJumpStatement(statement); + + case EHTokCase: + return acceptCaseLabel(statement); + case EHTokDefault: + return acceptDefaultLabel(statement); + + case EHTokRightBrace: + // Performance: not strictly necessary, but stops a bunch of hunting early, + // and is how sequences of statements end. + return false; + + default: + return acceptSimpleStatement(statement); + } + + return true; +} + +// attributes +// : [zero or more:] bracketed-attribute +// +// bracketed-attribute: +// : LEFT_BRACKET scoped-attribute RIGHT_BRACKET +// : LEFT_BRACKET LEFT_BRACKET scoped-attribute RIGHT_BRACKET RIGHT_BRACKET +// +// scoped-attribute: +// : attribute +// | namespace COLON COLON attribute +// +// 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(TAttributes& 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 attributeToken; + + // LEFT_BRACKET? + if (! acceptTokenClass(EHTokLeftBracket)) + return; + // another LEFT_BRACKET? + bool doubleBrackets = false; + if (acceptTokenClass(EHTokLeftBracket)) + doubleBrackets = true; + + // attribute? (could be namespace; will adjust later) + if (!acceptIdentifier(attributeToken)) { + if (!peekTokenClass(EHTokRightBracket)) { + expected("namespace or attribute identifier"); + advanceToken(); + } + } + + TString nameSpace; + if (acceptTokenClass(EHTokColonColon)) { + // namespace COLON COLON + nameSpace = *attributeToken.string; + // attribute + if (!acceptIdentifier(attributeToken)) { + expected("attribute identifier"); + return; + } + } + + 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; + } + // another RIGHT_BRACKET? + if (doubleBrackets && !acceptTokenClass(EHTokRightBracket)) { + expected("]]"); + return; + } + + // Add any values we found into the attribute map. + if (attributeToken.string != nullptr) { + TAttributeType attributeType = parseContext.attributeFromName(nameSpace, *attributeToken.string); + if (attributeType == EatNone) + parseContext.warn(attributeToken.loc, "unrecognized attribute", attributeToken.string->c_str(), ""); + else { + TAttributeArgs attributeArgs = { attributeType, expressions }; + attributes.push_back(attributeArgs); + } + } + } 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, const TAttributes& attributes) +{ + 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; + condition = parseContext.convertConditionalExpression(loc, condition); + if (condition == nullptr) + return false; + + // create the child statements + TIntermNodePair thenElse = { nullptr, nullptr }; + + ++parseContext.controlFlowNestingLevel; // this only needs to work right if no errors + + // 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.handleSelectionAttributes(loc, statement->getAsSelectionNode(), attributes); + + parseContext.popScope(); + --parseContext.controlFlowNestingLevel; + + return true; +} + +// switch_statement +// : SWITCH LEFT_PAREN expression RIGHT_PAREN compound_statement +// +bool HlslGrammar::acceptSwitchStatement(TIntermNode*& statement, const TAttributes& attributes) +{ + // 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); + + ++parseContext.controlFlowNestingLevel; + bool statementOkay = acceptCompoundStatement(statement); + --parseContext.controlFlowNestingLevel; + + if (statementOkay) + statement = parseContext.addSwitch(loc, switchExpression, statement ? statement->getAsAggregate() : nullptr, + attributes); + + 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, const TAttributes& attributes) +{ + TSourceLoc loc = token.loc; + TIntermTyped* condition = nullptr; + + EHlslTokenClass loop = peek(); + assert(loop == EHTokDo || loop == EHTokFor || loop == EHTokWhile); + + // WHILE or DO or FOR + advanceToken(); + + TIntermLoop* loopNode = nullptr; + switch (loop) { + case EHTokWhile: + // so that something declared in the condition is scoped to the lifetime + // of the while sub-statement + parseContext.pushScope(); // this only needs to work right if no errors + parseContext.nestLooping(); + ++parseContext.controlFlowNestingLevel; + + // LEFT_PAREN condition RIGHT_PAREN + if (! acceptParenExpression(condition)) + return false; + condition = parseContext.convertConditionalExpression(loc, condition); + if (condition == nullptr) + return false; + + // statement + if (! acceptScopedStatement(statement)) { + expected("while sub-statement"); + return false; + } + + parseContext.unnestLooping(); + parseContext.popScope(); + --parseContext.controlFlowNestingLevel; + + loopNode = intermediate.addLoop(statement, condition, nullptr, true, loc); + statement = loopNode; + break; + + case EHTokDo: + parseContext.nestLooping(); // this only needs to work right if no errors + ++parseContext.controlFlowNestingLevel; + + // statement + if (! acceptScopedStatement(statement)) { + expected("do sub-statement"); + return false; + } + + // WHILE + if (! acceptTokenClass(EHTokWhile)) { + expected("while"); + return false; + } + + // LEFT_PAREN condition RIGHT_PAREN + if (! acceptParenExpression(condition)) + return false; + condition = parseContext.convertConditionalExpression(loc, condition); + if (condition == nullptr) + return false; + + if (! acceptTokenClass(EHTokSemicolon)) + expected(";"); + + parseContext.unnestLooping(); + --parseContext.controlFlowNestingLevel; + + loopNode = intermediate.addLoop(statement, condition, 0, false, loc); + statement = loopNode; + break; + + 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 (! acceptSimpleStatement(initNode)) + expected("for-loop initializer statement"); + + parseContext.nestLooping(); // this only needs to work right if no errors + ++parseContext.controlFlowNestingLevel; + + // condition SEMI_COLON + acceptExpression(condition); + if (! acceptTokenClass(EHTokSemicolon)) + expected(";"); + if (condition != nullptr) { + condition = parseContext.convertConditionalExpression(loc, condition); + if (condition == nullptr) + return false; + } + + // 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, loopNode); + + parseContext.popScope(); + parseContext.unnestLooping(); + --parseContext.controlFlowNestingLevel; + + break; + } + + default: + return false; + } + + parseContext.handleLoopAttributes(loc, loopNode, attributes); + return true; +} + +// 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); + if (parseContext.loopNestingLevel == 0) { + expected("loop"); + return false; + } + break; + case EHTokBreak: + statement = intermediate.addBranch(EOpBreak, token.loc); + if (parseContext.loopNestingLevel == 0 && parseContext.switchSequenceStack.size() == 0) { + expected("loop or switch"); + return false; + } + 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 + TString semanticUpperCase = *idToken.string; + std::transform(semanticUpperCase.begin(), semanticUpperCase.end(), semanticUpperCase.begin(), ::toupper); + parseContext.handleSemantic(idToken.loc, qualifier, mapSemantic(semanticUpperCase.c_str()), semanticUpperCase); + } + } else if (peekTokenClass(EHTokLeftAngle)) { + found = true; + acceptAnnotations(qualifier); + } else + break; + + } while (true); + + return found; +} + +// +// Get the stream of tokens from the scanner, but skip all syntactic/semantic +// processing. +// +bool HlslGrammar::captureBlockTokens(TVector<HlslToken>& tokens) +{ + if (! peekTokenClass(EHTokLeftBrace)) + return false; + + int braceCount = 0; + + do { + switch (peek()) { + case EHTokLeftBrace: + ++braceCount; + break; + case EHTokRightBrace: + --braceCount; + break; + case EHTokNone: + // End of input before balance { } is bad... + return false; + default: + break; + } + + tokens.push_back(token); + advanceToken(); + } while (braceCount > 0); + + return true; +} + +// Return a string for just the types that can also be declared as an identifier. +const char* HlslGrammar::getTypeString(EHlslTokenClass tokenClass) const +{ + switch (tokenClass) { + case EHTokSample: return "sample"; + case EHTokHalf: return "half"; + case EHTokHalf1x1: return "half1x1"; + case EHTokHalf1x2: return "half1x2"; + case EHTokHalf1x3: return "half1x3"; + case EHTokHalf1x4: return "half1x4"; + case EHTokHalf2x1: return "half2x1"; + case EHTokHalf2x2: return "half2x2"; + case EHTokHalf2x3: return "half2x3"; + case EHTokHalf2x4: return "half2x4"; + case EHTokHalf3x1: return "half3x1"; + case EHTokHalf3x2: return "half3x2"; + case EHTokHalf3x3: return "half3x3"; + case EHTokHalf3x4: return "half3x4"; + case EHTokHalf4x1: return "half4x1"; + case EHTokHalf4x2: return "half4x2"; + case EHTokHalf4x3: return "half4x3"; + case EHTokHalf4x4: return "half4x4"; + case EHTokBool: return "bool"; + case EHTokFloat: return "float"; + case EHTokDouble: return "double"; + case EHTokInt: return "int"; + case EHTokUint: return "uint"; + case EHTokMin16float: return "min16float"; + case EHTokMin10float: return "min10float"; + case EHTokMin16int: return "min16int"; + case EHTokMin12int: return "min12int"; + case EHTokConstantBuffer: return "ConstantBuffer"; + case EHTokLayout: return "layout"; + default: + return nullptr; + } +} + +} // end namespace glslang |