glslang/hlsl/hlslGrammar.cpp

1590 lines
44 KiB
C++
Raw Normal View History

//
//Copyright (C) 2016 Google, 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"
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, "");
}
// Only process the next token if it is an identifier.
// Return true if it was an identifier.
bool HlslGrammar::acceptIdentifier(HlslToken& idToken)
{
if (peekTokenClass(EHTokIdentifier)) {
idToken = token;
advanceToken();
return true;
}
return false;
}
// compilationUnit
// : list of externalDeclaration
//
bool HlslGrammar::acceptCompilationUnit()
{
TIntermNode* unitNode = nullptr;
while (! peekTokenClass(EHTokNone)) {
// externalDeclaration
TIntermNode* declarationNode;
if (! acceptDeclaration(declarationNode))
return false;
// hook it up
unitNode = intermediate.growAggregate(unitNode, declarationNode);
}
// set root of AST
intermediate.setTreeRoot(unitNode);
return true;
}
// declaration
// : SEMICOLON
// : fully_specified_type SEMICOLON
// | fully_specified_type identifier SEMICOLON
// | fully_specified_type identifier = expression SEMICOLON
// | fully_specified_type identifier function_parameters SEMICOLON // function prototype
// | fully_specified_type identifier function_parameters COLON semantic compound_statement // function definition
//
// 'node' could get created if the declaration creates code, like an initializer
// or a function body.
//
bool HlslGrammar::acceptDeclaration(TIntermNode*& node)
{
node = nullptr;
// fully_specified_type
TType type;
if (! acceptFullySpecifiedType(type))
return false;
// identifier
HlslToken idToken;
if (acceptIdentifier(idToken)) {
// = expression
TIntermTyped* expressionNode = nullptr;
if (acceptTokenClass(EHTokAssign)) {
if (! acceptExpression(expressionNode)) {
expected("initializer");
return false;
}
}
// SEMICOLON
if (acceptTokenClass(EHTokSemicolon)) {
node = parseContext.declareVariable(idToken.loc, *idToken.string, type, 0, expressionNode);
return true;
}
// function_parameters
TFunction* function = new TFunction(idToken.string, type);
if (acceptFunctionParameters(*function)) {
// COLON semantic
acceptSemantic();
// compound_statement
if (peekTokenClass(EHTokLeftBrace))
return acceptFunctionDefinition(*function, node);
// SEMICOLON
if (acceptTokenClass(EHTokSemicolon))
return true;
return false;
}
}
// SEMICOLON
if (acceptTokenClass(EHTokSemicolon))
return true;
return true;
}
// fully_specified_type
// : type_specifier
// | type_qualifier type_specifier
//
bool HlslGrammar::acceptFullySpecifiedType(TType& type)
{
// type_qualifier
TQualifier qualifier;
qualifier.clear();
acceptQualifier(qualifier);
// type_specifier
if (! acceptType(type))
return false;
type.getQualifier() = qualifier;
return true;
}
// If token is a qualifier, return its token class and advance to the next
// qualifier. Otherwise, return false, and don't advance.
void HlslGrammar::acceptQualifier(TQualifier& qualifier)
{
switch (peek()) {
case EHTokUniform:
qualifier.storage = EvqUniform;
break;
case EHTokConst:
qualifier.storage = EvqConst;
break;
default:
return;
}
advanceToken();
}
// 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)
{
switch (peek()) {
case EHTokStruct:
return acceptStruct(type);
break;
case EHTokIdentifier:
// An identifier could be for a user-defined type.
// Note we cache the symbol table lookup, to save for a later rule
// when this is not a type.
token.symbol = parseContext.symbolTable.find(*token.string);
if (token.symbol && token.symbol->getAsVariable() && token.symbol->getAsVariable()->isUserType()) {
type.shallowCopy(token.symbol->getType());
advanceToken();
return true;
} else
return false;
case EHTokVoid:
new(&type) TType(EbtVoid);
break;
case EHTokFloat:
new(&type) TType(EbtFloat);
break;
case EHTokFloat1:
new(&type) TType(EbtFloat);
type.makeVector();
break;
case EHTokFloat2:
new(&type) TType(EbtFloat, EvqTemporary, 2);
break;
case EHTokFloat3:
new(&type) TType(EbtFloat, EvqTemporary, 3);
break;
case EHTokFloat4:
new(&type) TType(EbtFloat, EvqTemporary, 4);
break;
case EHTokDouble:
new(&type) TType(EbtDouble);
break;
case EHTokDouble1:
new(&type) TType(EbtDouble);
type.makeVector();
break;
case EHTokDouble2:
new(&type) TType(EbtDouble, EvqTemporary, 2);
break;
case EHTokDouble3:
new(&type) TType(EbtDouble, EvqTemporary, 3);
break;
case EHTokDouble4:
new(&type) TType(EbtDouble, EvqTemporary, 4);
break;
case EHTokInt:
case EHTokDword:
new(&type) TType(EbtInt);
break;
case EHTokInt1:
new(&type) TType(EbtInt);
type.makeVector();
break;
case EHTokInt2:
new(&type) TType(EbtInt, EvqTemporary, 2);
break;
case EHTokInt3:
new(&type) TType(EbtInt, EvqTemporary, 3);
break;
case EHTokInt4:
new(&type) TType(EbtInt, EvqTemporary, 4);
break;
case EHTokUint:
new(&type) TType(EbtUint);
break;
case EHTokUint1:
new(&type) TType(EbtUint);
type.makeVector();
break;
case EHTokUint2:
new(&type) TType(EbtUint, EvqTemporary, 2);
break;
case EHTokUint3:
new(&type) TType(EbtUint, EvqTemporary, 3);
break;
case EHTokUint4:
new(&type) TType(EbtUint, EvqTemporary, 4);
break;
case EHTokBool:
new(&type) TType(EbtBool);
break;
case EHTokBool1:
new(&type) TType(EbtBool);
type.makeVector();
break;
case EHTokBool2:
new(&type) TType(EbtBool, EvqTemporary, 2);
break;
case EHTokBool3:
new(&type) TType(EbtBool, EvqTemporary, 3);
break;
case EHTokBool4:
new(&type) TType(EbtBool, EvqTemporary, 4);
break;
case EHTokInt1x1:
new(&type) TType(EbtInt, EvqTemporary, 0, 1, 1);
break;
case EHTokInt1x2:
new(&type) TType(EbtInt, EvqTemporary, 0, 2, 1);
break;
case EHTokInt1x3:
new(&type) TType(EbtInt, EvqTemporary, 0, 3, 1);
break;
case EHTokInt1x4:
new(&type) TType(EbtInt, EvqTemporary, 0, 4, 1);
break;
case EHTokInt2x1:
new(&type) TType(EbtInt, EvqTemporary, 0, 1, 2);
break;
case EHTokInt2x2:
new(&type) TType(EbtInt, EvqTemporary, 0, 2, 2);
break;
case EHTokInt2x3:
new(&type) TType(EbtInt, EvqTemporary, 0, 3, 2);
break;
case EHTokInt2x4:
new(&type) TType(EbtInt, EvqTemporary, 0, 4, 2);
break;
case EHTokInt3x1:
new(&type) TType(EbtInt, EvqTemporary, 0, 1, 3);
break;
case EHTokInt3x2:
new(&type) TType(EbtInt, EvqTemporary, 0, 2, 3);
break;
case EHTokInt3x3:
new(&type) TType(EbtInt, EvqTemporary, 0, 3, 3);
break;
case EHTokInt3x4:
new(&type) TType(EbtInt, EvqTemporary, 0, 4, 3);
break;
case EHTokInt4x1:
new(&type) TType(EbtInt, EvqTemporary, 0, 1, 4);
break;
case EHTokInt4x2:
new(&type) TType(EbtInt, EvqTemporary, 0, 2, 4);
break;
case EHTokInt4x3:
new(&type) TType(EbtInt, EvqTemporary, 0, 3, 4);
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, 2, 1);
break;
case EHTokUint1x3:
new(&type) TType(EbtUint, EvqTemporary, 0, 3, 1);
break;
case EHTokUint1x4:
new(&type) TType(EbtUint, EvqTemporary, 0, 4, 1);
break;
case EHTokUint2x1:
new(&type) TType(EbtUint, EvqTemporary, 0, 1, 2);
break;
case EHTokUint2x2:
new(&type) TType(EbtUint, EvqTemporary, 0, 2, 2);
break;
case EHTokUint2x3:
new(&type) TType(EbtUint, EvqTemporary, 0, 3, 2);
break;
case EHTokUint2x4:
new(&type) TType(EbtUint, EvqTemporary, 0, 4, 2);
break;
case EHTokUint3x1:
new(&type) TType(EbtUint, EvqTemporary, 0, 1, 3);
break;
case EHTokUint3x2:
new(&type) TType(EbtUint, EvqTemporary, 0, 2, 3);
break;
case EHTokUint3x3:
new(&type) TType(EbtUint, EvqTemporary, 0, 3, 3);
break;
case EHTokUint3x4:
new(&type) TType(EbtUint, EvqTemporary, 0, 4, 3);
break;
case EHTokUint4x1:
new(&type) TType(EbtUint, EvqTemporary, 0, 1, 4);
break;
case EHTokUint4x2:
new(&type) TType(EbtUint, EvqTemporary, 0, 2, 4);
break;
case EHTokUint4x3:
new(&type) TType(EbtUint, EvqTemporary, 0, 3, 4);
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, 2, 1);
break;
case EHTokBool1x3:
new(&type) TType(EbtBool, EvqTemporary, 0, 3, 1);
break;
case EHTokBool1x4:
new(&type) TType(EbtBool, EvqTemporary, 0, 4, 1);
break;
case EHTokBool2x1:
new(&type) TType(EbtBool, EvqTemporary, 0, 1, 2);
break;
case EHTokBool2x2:
new(&type) TType(EbtBool, EvqTemporary, 0, 2, 2);
break;
case EHTokBool2x3:
new(&type) TType(EbtBool, EvqTemporary, 0, 3, 2);
break;
case EHTokBool2x4:
new(&type) TType(EbtBool, EvqTemporary, 0, 4, 2);
break;
case EHTokBool3x1:
new(&type) TType(EbtBool, EvqTemporary, 0, 1, 3);
break;
case EHTokBool3x2:
new(&type) TType(EbtBool, EvqTemporary, 0, 2, 3);
break;
case EHTokBool3x3:
new(&type) TType(EbtBool, EvqTemporary, 0, 3, 3);
break;
case EHTokBool3x4:
new(&type) TType(EbtBool, EvqTemporary, 0, 4, 3);
break;
case EHTokBool4x1:
new(&type) TType(EbtBool, EvqTemporary, 0, 1, 4);
break;
case EHTokBool4x2:
new(&type) TType(EbtBool, EvqTemporary, 0, 2, 4);
break;
case EHTokBool4x3:
new(&type) TType(EbtBool, EvqTemporary, 0, 3, 4);
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, 2, 1);
break;
case EHTokFloat1x3:
new(&type) TType(EbtFloat, EvqTemporary, 0, 3, 1);
break;
case EHTokFloat1x4:
new(&type) TType(EbtFloat, EvqTemporary, 0, 4, 1);
break;
case EHTokFloat2x1:
new(&type) TType(EbtFloat, EvqTemporary, 0, 1, 2);
break;
case EHTokFloat2x2:
new(&type) TType(EbtFloat, EvqTemporary, 0, 2, 2);
break;
case EHTokFloat2x3:
new(&type) TType(EbtFloat, EvqTemporary, 0, 3, 2);
break;
case EHTokFloat2x4:
new(&type) TType(EbtFloat, EvqTemporary, 0, 4, 2);
break;
case EHTokFloat3x1:
new(&type) TType(EbtFloat, EvqTemporary, 0, 1, 3);
break;
case EHTokFloat3x2:
new(&type) TType(EbtFloat, EvqTemporary, 0, 2, 3);
break;
case EHTokFloat3x3:
new(&type) TType(EbtFloat, EvqTemporary, 0, 3, 3);
break;
case EHTokFloat3x4:
new(&type) TType(EbtFloat, EvqTemporary, 0, 4, 3);
break;
case EHTokFloat4x1:
new(&type) TType(EbtFloat, EvqTemporary, 0, 1, 4);
break;
case EHTokFloat4x2:
new(&type) TType(EbtFloat, EvqTemporary, 0, 2, 4);
break;
case EHTokFloat4x3:
new(&type) TType(EbtFloat, EvqTemporary, 0, 3, 4);
break;
case EHTokFloat4x4:
new(&type) TType(EbtFloat, EvqTemporary, 0, 4, 4);
break;
case EHTokDouble1x1:
new(&type) TType(EbtDouble, EvqTemporary, 0, 1, 1);
break;
case EHTokDouble1x2:
new(&type) TType(EbtDouble, EvqTemporary, 0, 2, 1);
break;
case EHTokDouble1x3:
new(&type) TType(EbtDouble, EvqTemporary, 0, 3, 1);
break;
case EHTokDouble1x4:
new(&type) TType(EbtDouble, EvqTemporary, 0, 4, 1);
break;
case EHTokDouble2x1:
new(&type) TType(EbtDouble, EvqTemporary, 0, 1, 2);
break;
case EHTokDouble2x2:
new(&type) TType(EbtDouble, EvqTemporary, 0, 2, 2);
break;
case EHTokDouble2x3:
new(&type) TType(EbtDouble, EvqTemporary, 0, 3, 2);
break;
case EHTokDouble2x4:
new(&type) TType(EbtDouble, EvqTemporary, 0, 4, 2);
break;
case EHTokDouble3x1:
new(&type) TType(EbtDouble, EvqTemporary, 0, 1, 3);
break;
case EHTokDouble3x2:
new(&type) TType(EbtDouble, EvqTemporary, 0, 2, 3);
break;
case EHTokDouble3x3:
new(&type) TType(EbtDouble, EvqTemporary, 0, 3, 3);
break;
case EHTokDouble3x4:
new(&type) TType(EbtDouble, EvqTemporary, 0, 4, 3);
break;
case EHTokDouble4x1:
new(&type) TType(EbtDouble, EvqTemporary, 0, 1, 4);
break;
case EHTokDouble4x2:
new(&type) TType(EbtDouble, EvqTemporary, 0, 2, 4);
break;
case EHTokDouble4x3:
new(&type) TType(EbtDouble, EvqTemporary, 0, 3, 4);
break;
case EHTokDouble4x4:
new(&type) TType(EbtDouble, EvqTemporary, 0, 4, 4);
break;
default:
return false;
}
advanceToken();
return true;
}
// struct
// : STRUCT IDENTIFIER LEFT_BRACE struct_declaration_list RIGHT_BRACE
// | STRUCT LEFT_BRACE struct_declaration_list RIGHT_BRACE
//
bool HlslGrammar::acceptStruct(TType& type)
{
// STRUCT
if (! acceptTokenClass(EHTokStruct))
return false;
// IDENTIFIER
TString structName = "";
if (peekTokenClass(EHTokIdentifier)) {
structName = *token.string;
advanceToken();
}
// LEFT_BRACE
if (! acceptTokenClass(EHTokLeftBrace)) {
expected("{");
return false;
}
// struct_declaration_list
TTypeList* typeList;
if (! acceptStructDeclarationList(typeList)) {
expected("struct member declarations");
return false;
}
// RIGHT_BRACE
if (! acceptTokenClass(EHTokRightBrace)) {
expected("}");
return false;
}
// create the user-defined type
new(&type) TType(typeList, structName);
// If it was named, which means it can be reused later, add
// it to the symbol table.
if (structName.size() > 0) {
TVariable* userTypeDef = new TVariable(&structName, type, true);
if (! parseContext.symbolTable.insert(*userTypeDef))
parseContext.error(token.loc, "redefinition", structName.c_str(), "struct");
}
return true;
}
// struct_declaration_list
// : struct_declaration SEMI_COLON struct_declaration SEMI_COLON ...
//
// struct_declaration
// : fully_specified_type struct_declarator COMMA struct_declarator ...
//
// struct_declarator
// : IDENTIFIER
// | IDENTIFIER array_specifier
//
bool HlslGrammar::acceptStructDeclarationList(TTypeList*& typeList)
{
typeList = new TTypeList();
do {
// success on seeing the RIGHT_BRACE coming up
if (peekTokenClass(EHTokRightBrace))
return true;
// struct_declaration
// fully_specified_type
TType memberType;
if (! acceptFullySpecifiedType(memberType)) {
expected("member type");
return false;
}
// struct_declarator COMMA struct_declarator ...
do {
// peek IDENTIFIER
if (! peekTokenClass(EHTokIdentifier)) {
expected("member name");
return false;
}
// add it to the list of members
TTypeLoc member = { new TType(EbtVoid), token.loc };
member.type->shallowCopy(memberType);
member.type->setFieldName(*token.string);
typeList->push_back(member);
// accept IDENTIFIER
advanceToken();
// array_specifier
// TODO
// success on seeing the SEMICOLON coming up
if (peekTokenClass(EHTokSemicolon))
break;
// COMMA
if (! acceptTokenClass(EHTokComma)) {
expected(",");
return false;
}
} while (true);
// SEMI_COLON
if (! acceptTokenClass(EHTokSemicolon)) {
expected(";");
return false;
}
} while (true);
}
// 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;
}
// parameter_declaration
// : fully_specified_type
// | fully_specified_type identifier
//
bool HlslGrammar::acceptParameterDeclaration(TFunction& function)
{
// fully_specified_type
TType* type = new TType;
if (! acceptFullySpecifiedType(*type))
return false;
// identifier
HlslToken idToken;
acceptIdentifier(idToken);
TParameter param = { idToken.string, type };
function.addParameter(param);
return true;
}
// Do the work to create the function definition in addition to
// parsing the body (compound_statement).
bool HlslGrammar::acceptFunctionDefinition(TFunction& function, TIntermNode*& node)
{
TFunction* functionDeclarator = parseContext.handleFunctionDeclarator(token.loc, function, false /* not prototype */);
// This does a pushScope()
node = parseContext.handleFunctionDefinition(token.loc, *functionDeclarator);
// compound_statement
TIntermNode* functionBody = nullptr;
if (acceptCompoundStatement(functionBody)) {
node = intermediate.growAggregate(node, functionBody);
intermediate.setAggregateOperator(node, EOpFunction, functionDeclarator->getType(), token.loc);
node->getAsAggregate()->setName(functionDeclarator->getMangledName().c_str());
parseContext.popScope();
return true;
}
return false;
}
// Accept an expression with parenthesis around it, where
// the parenthesis ARE NOT expression parenthesis, but the
// syntactically required ones like in "if ( expression )"
//
// Note this one is not set up to be speculative; as it gives
// errors if not found.
//
bool HlslGrammar::acceptParenExpression(TIntermTyped*& expression)
{
// LEFT_PAREN
if (! acceptTokenClass(EHTokLeftParen))
expected("(");
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)
{
2016-06-03 15:17:51 +00:00
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);
}
// Accept an assignment expression, where assignment operations
// associate right-to-left. This is, it is implicit, for example
//
// a op (b op (c op d))
//
// assigment_expression
// : binary_expression op binary_expression op binary_expression ...
//
bool HlslGrammar::acceptAssignmentExpression(TIntermTyped*& node)
{
if (! acceptBinaryExpression(node, PlLogicalOr))
return false;
TOperator assignOp = HlslOpMap::assignment(peek());
if (assignOp == EOpNull)
return true;
// ... op
TSourceLoc loc = token.loc;
advanceToken();
// ... binary_expression
// But, 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 = intermediate.addAssign(assignOp, node, rightNode, loc);
if (! peekTokenClass(EHTokComma))
return true;
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;
TOperator op = HlslOpMap::binary(peek());
PrecedenceLevel tokenLevel = HlslOpMap::precedenceLevel(op);
if (tokenLevel < precedenceLevel)
return true;
do {
// ... 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 (! peekTokenClass(EHTokComma))
return true;
} while (true);
}
// unary_expression
// : (type) unary_expression
// | + unary_expression
// | - unary_expression
// | ! unary_expression
// | ~ unary_expression
// | ++ unary_expression
// | -- unary_expression
// | postfix_expression
//
bool HlslGrammar::acceptUnaryExpression(TIntermTyped*& node)
{
// (type) unary_expression
// Have to look two steps ahead, because this could be, e.g., a
// postfix_expression instead, since that also starts with at "(".
if (acceptTokenClass(EHTokLeftParen)) {
TType castType;
if (acceptType(castType)) {
if (! acceptTokenClass(EHTokRightParen)) {
expected(")");
return false;
}
// We've matched "(type)" now, get the expression to cast
TSourceLoc loc = token.loc;
if (! acceptUnaryExpression(node))
return false;
// Hook it up like a constructor
TFunction* constructorFunction = parseContext.handleConstructorCall(loc, castType);
if (constructorFunction == nullptr) {
expected("type that can be constructed");
return false;
}
TIntermTyped* arguments = nullptr;
parseContext.handleFunctionArgument(constructorFunction, arguments, node);
node = parseContext.handleFunctionCall(loc, constructorFunction, arguments);
return true;
} else {
// This 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);
return node != nullptr;
}
// postfix_expression
// : LEFT_PAREN expression RIGHT_PAREN
// | literal
// | constructor
// | identifier
// | function_call
// | postfix_expression LEFT_BRACKET integer_expression RIGHT_BRACKET
// | postfix_expression DOT IDENTIFIER
// | postfix_expression INC_OP
// | postfix_expression DEC_OP
//
bool HlslGrammar::acceptPostfixExpression(TIntermTyped*& node)
{
// Not implemented as self-recursive:
// The logical "right recursion" is done with an 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), go on to the
} else if (acceptConstructor(node)) {
// constructor (nothing else to do yet)
} else if (acceptIdentifier(idToken)) {
// identifier or function_call name
if (! peekTokenClass(EHTokLeftParen)) {
node = parseContext.handleVariable(idToken.loc, idToken.symbol, token.string);
} else if (acceptFunctionCall(idToken, node)) {
// function_call (nothing else to do yet)
} else {
expected("function call arguments");
return false;
}
} else {
// nothing found, can't post operate
return false;
}
// 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:
advanceToken();
break;
default:
return true;
}
// We have a valid post-unary operator, process it.
switch (postOp) {
case EOpIndexDirectStruct:
// todo
break;
case EOpIndexIndirect:
{
TIntermTyped* indexNode = nullptr;
if (! acceptExpression(indexNode) ||
! peekTokenClass(EHTokRightBracket)) {
expected("expression followed by ']'");
return false;
}
// todo: node = intermediate.addBinaryMath(
}
case EOpPostIncrement:
case EOpPostDecrement:
node = intermediate.addUnaryMath(postOp, node, loc);
break;
default:
assert(0);
break;
}
} while (true);
}
// constructor
// : type argument_list
//
bool HlslGrammar::acceptConstructor(TIntermTyped*& node)
{
// type
TType type;
if (acceptType(type)) {
TFunction* constructorFunction = parseContext.handleConstructorCall(token.loc, type);
if (constructorFunction == nullptr)
return false;
// arguments
TIntermTyped* arguments = nullptr;
if (! acceptArguments(constructorFunction, arguments)) {
expected("constructor arguments");
return false;
}
// hook it up
node = parseContext.handleFunctionCall(arguments->getLoc(), constructorFunction, arguments);
return true;
}
return false;
}
// The function_call identifier was already recognized, and passed in as idToken.
//
// function_call
// : [idToken] arguments
//
bool HlslGrammar::acceptFunctionCall(HlslToken idToken, TIntermTyped*& node)
{
// arguments
TFunction* function = new TFunction(idToken.string, TType(EbtVoid));
TIntermTyped* arguments = nullptr;
if (! acceptArguments(function, arguments))
return false;
node = parseContext.handleFunctionCall(idToken.loc, function, arguments);
return true;
}
// arguments
// : LEFT_PAREN expression COMMA expression COMMA ... RIGHT_PAREN
//
// The arguments are pushed onto the 'function' argument list and
// onto the 'arguments' aggregate.
//
bool HlslGrammar::acceptArguments(TFunction* function, TIntermTyped*& arguments)
{
// LEFT_PAREN
if (! acceptTokenClass(EHTokLeftParen))
return false;
do {
// expression
TIntermTyped* arg;
if (! acceptAssignmentExpression(arg))
break;
// hook it up
parseContext.handleFunctionArgument(function, arguments, arg);
// COMMA
if (! acceptTokenClass(EHTokComma))
break;
} while (true);
// RIGHT_PAREN
if (! acceptTokenClass(EHTokRightParen)) {
expected(")");
return false;
}
return true;
}
bool HlslGrammar::acceptLiteral(TIntermTyped*& node)
{
switch (token.tokenClass) {
case EHTokIntConstant:
node = intermediate.addConstantUnion(token.i, token.loc, true);
break;
case 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;
default:
return false;
}
advanceToken();
return true;
}
// compound_statement
// : LEFT_CURLY statement statement ... RIGHT_CURLY
//
bool HlslGrammar::acceptCompoundStatement(TIntermNode*& retStatement)
{
TIntermAggregate* compoundStatement = nullptr;
// LEFT_CURLY
if (! acceptTokenClass(EHTokLeftBrace))
return false;
// statement statement ...
TIntermNode* statement = nullptr;
while (acceptStatement(statement)) {
// hook it up
compoundStatement = intermediate.growAggregate(compoundStatement, statement);
}
if (compoundStatement)
compoundStatement->setOperator(EOpSequence);
retStatement = compoundStatement;
// RIGHT_CURLY
return acceptTokenClass(EHTokRightBrace);
}
bool HlslGrammar::acceptScopedStatement(TIntermNode*& statement)
{
parseContext.pushScope();
bool result = acceptStatement(statement);
parseContext.popScope();
return result;
}
bool HlslGrammar::acceptScopedCompoundStatement(TIntermNode*& statement)
{
parseContext.pushScope();
bool result = acceptCompoundStatement(statement);
parseContext.popScope();
return result;
}
// statement
// : attributes attributed_statement
//
// attributed_statement
// : compound_statement
// | SEMICOLON
// | expression SEMICOLON
// | declaration_statement
// | selection_statement
// | switch_statement
// | case_label
// | iteration_statement
// | jump_statement
//
bool HlslGrammar::acceptStatement(TIntermNode*& statement)
{
statement = nullptr;
// attributes
acceptAttributes();
// attributed_statement
switch (peek()) {
case EHTokLeftBrace:
return acceptScopedCompoundStatement(statement);
case EHTokIf:
return acceptSelectionStatement(statement);
case EHTokSwitch:
return acceptSwitchStatement(statement);
case EHTokFor:
case EHTokDo:
case EHTokWhile:
return acceptIterationStatement(statement);
case EHTokContinue:
case EHTokBreak:
case EHTokDiscard:
case EHTokReturn:
return acceptJumpStatement(statement);
case EHTokCase:
return acceptCaseLabel(statement);
case EHTokSemicolon:
return acceptTokenClass(EHTokSemicolon);
case EHTokRightBrace:
// Performance: not strictly necessary, but stops a bunch of hunting early,
// and is how sequences of statements end.
return false;
default:
{
// declaration
if (acceptDeclaration(statement))
return true;
// expression
TIntermTyped* node;
if (acceptExpression(node))
statement = node;
else
return false;
// SEMICOLON (following an expression)
if (! acceptTokenClass(EHTokSemicolon)) {
expected(";");
return false;
}
}
}
return true;
}
// attributes
// : list of zero or more of: LEFT_BRACKET attribute RIGHT_BRACKET
//
// attribute:
// : UNROLL
// | UNROLL LEFT_PAREN literal RIGHT_PAREN
// | FASTOPT
// | ALLOW_UAV_CONDITION
// | BRANCH
// | FLATTEN
// | FORCECASE
// | CALL
//
void HlslGrammar::acceptAttributes()
{
// For now, accept the [ XXX(X) ] syntax, but drop.
// TODO: subset to correct set? Pass on?
do {
// LEFT_BRACKET?
if (! acceptTokenClass(EHTokLeftBracket))
return;
// attribute
if (peekTokenClass(EHTokIdentifier)) {
// 'token.string' is the attribute
advanceToken();
} else if (! peekTokenClass(EHTokRightBracket)) {
expected("identifier");
advanceToken();
}
// (x)
if (acceptTokenClass(EHTokLeftParen)) {
TIntermTyped* node;
if (! acceptLiteral(node))
expected("literal");
// 'node' has the literal in it
if (! acceptTokenClass(EHTokRightParen))
expected(")");
}
// RIGHT_BRACKET
if (acceptTokenClass(EHTokRightBracket))
continue;
expected("]");
return;
} while (true);
}
// selection_statement
// : IF LEFT_PAREN expression RIGHT_PAREN statement
// : IF LEFT_PAREN expression RIGHT_PAREN statement ELSE statement
//
bool HlslGrammar::acceptSelectionStatement(TIntermNode*& statement)
{
TSourceLoc loc = token.loc;
// IF
if (! acceptTokenClass(EHTokIf))
return false;
// so that something declared in the condition is scoped to the lifetimes
// of the then-else statements
parseContext.pushScope();
// LEFT_PAREN expression RIGHT_PAREN
TIntermTyped* condition;
if (! acceptParenExpression(condition))
return false;
// create the child statements
TIntermNodePair thenElse = { nullptr, nullptr };
// then statement
if (! acceptScopedStatement(thenElse.node1)) {
expected("then statement");
return false;
}
// ELSE
if (acceptTokenClass(EHTokElse)) {
// else statement
if (! acceptScopedStatement(thenElse.node2)) {
expected("else statement");
return false;
}
}
// Put the pieces together
statement = intermediate.addSelection(condition, thenElse, loc);
parseContext.popScope();
return true;
}
bool HlslGrammar::acceptSwitchStatement(TIntermNode*& statement)
{
return false;
}
// iteration_statement
// : WHILE LEFT_PAREN condition RIGHT_PAREN statement
// | DO LEFT_BRACE statement RIGHT_BRACE WHILE LEFT_PAREN expression RIGHT_PAREN SEMICOLON
// | FOR LEFT_PAREN for_init_statement for_rest_statement RIGHT_PAREN statement
//
// Non-speculative, only call if it needs to be found; WHILE or DO or FOR already seen.
bool HlslGrammar::acceptIterationStatement(TIntermNode*& statement)
{
TSourceLoc loc = token.loc;
TIntermTyped* condition = nullptr;
EHlslTokenClass loop = peek();
assert(loop == EHTokDo || loop == EHTokFor || loop == EHTokWhile);
// WHILE or DO or FOR
advanceToken();
switch (loop) {
case EHTokWhile:
// so that something declared in the condition is scoped to the lifetime
// of the while sub-statement
parseContext.pushScope();
parseContext.nestLooping();
// LEFT_PAREN condition RIGHT_PAREN
if (! acceptParenExpression(condition))
return false;
// statement
if (! acceptScopedStatement(statement)) {
expected("while sub-statement");
return false;
}
parseContext.unnestLooping();
parseContext.popScope();
statement = intermediate.addLoop(statement, condition, nullptr, true, loc);
return true;
case EHTokDo:
parseContext.nestLooping();
if (! acceptTokenClass(EHTokLeftBrace))
expected("{");
// statement
if (! peekTokenClass(EHTokRightBrace) && ! acceptScopedStatement(statement)) {
expected("do sub-statement");
return false;
}
if (! acceptTokenClass(EHTokRightBrace))
expected("}");
// WHILE
if (! acceptTokenClass(EHTokWhile)) {
expected("while");
return false;
}
// LEFT_PAREN condition RIGHT_PAREN
TIntermTyped* condition;
if (! acceptParenExpression(condition))
return false;
if (! acceptTokenClass(EHTokSemicolon))
expected(";");
parseContext.unnestLooping();
statement = intermediate.addLoop(statement, condition, 0, false, loc);
return true;
case EHTokFor:
{
// LEFT_PAREN
if (! acceptTokenClass(EHTokLeftParen))
expected("(");
// so that something declared in the condition is scoped to the lifetime
// of the for sub-statement
parseContext.pushScope();
// initializer SEMI_COLON
TIntermTyped* initializer = nullptr; // TODO, "for (initializer" needs to support decl. statement
acceptExpression(initializer);
if (! acceptTokenClass(EHTokSemicolon))
expected(";");
parseContext.nestLooping();
// condition SEMI_COLON
acceptExpression(condition);
if (! acceptTokenClass(EHTokSemicolon))
expected(";");
// iterator SEMI_COLON
TIntermTyped* iterator = nullptr;
acceptExpression(iterator);
if (! acceptTokenClass(EHTokRightParen))
expected(")");
// statement
if (! acceptScopedStatement(statement)) {
expected("for sub-statement");
return false;
}
statement = intermediate.addForLoop(statement, initializer, condition, iterator, true, loc);
parseContext.popScope();
parseContext.unnestLooping();
return true;
}
default:
return false;
}
}
// jump_statement
// : CONTINUE SEMICOLON
// | BREAK SEMICOLON
// | DISCARD SEMICOLON
// | RETURN SEMICOLON
// | RETURN expression SEMICOLON
//
bool HlslGrammar::acceptJumpStatement(TIntermNode*& statement)
{
switch (peek()) {
case EHTokContinue:
case EHTokBreak:
case EHTokDiscard:
// TODO
return false;
case EHTokReturn:
// return
if (acceptTokenClass(EHTokReturn)) {
// expression
TIntermTyped* node;
if (acceptExpression(node)) {
// hook it up
statement = intermediate.addBranch(EOpReturn, node, token.loc);
} else
statement = intermediate.addBranch(EOpReturn, token.loc);
// SEMICOLON
if (! acceptTokenClass(EHTokSemicolon)) {
expected(";");
return false;
}
return true;
}
default:
return false;
}
}
bool HlslGrammar::acceptCaseLabel(TIntermNode*& statement)
{
return false;
}
// COLON semantic
bool HlslGrammar::acceptSemantic()
{
// COLON
if (acceptTokenClass(EHTokColon)) {
// semantic
HlslToken idToken;
if (! acceptIdentifier(idToken)) {
expected("semantic");
return false;
}
}
return true;
}
} // end namespace glslang