glslang AEP: Geometry shader features nominally working. (Full semantic check and turn on pending.) Also picked up partial tessellation shader interface, shader_io_blocks, and mirrored OES set of extensions functionality.

git-svn-id: https://cvs.khronos.org/svn/repos/ogl/trunk/ecosystem/public/sdk/tools/glslang@31487 e7fa87d3-cd2b-0410-9028-fcbf551c1848
This commit is contained in:
John Kessenich 2015-06-14 21:36:44 +00:00
parent e5e0f6e37a
commit f6deb6203a
14 changed files with 499 additions and 367 deletions

View file

@ -622,6 +622,7 @@ void TBuiltIns::initialize(int version, EProfile profile)
"\n");
// 120 is correct for both ES and desktop
if (version >= 120) {
commonBuiltins.append(
"mat2 outerProduct(vec2 c, vec2 r);"
@ -653,6 +654,7 @@ void TBuiltIns::initialize(int version, EProfile profile)
"\n");
// 150 is correct for both ES and desktop
if (version >= 150) {
commonBuiltins.append(
"float determinant(mat2 m);"
@ -1043,7 +1045,6 @@ void TBuiltIns::initialize(int version, EProfile profile)
stageBuiltins[EShLangGeometry].append(
"void EmitVertex();"
"void EndPrimitive();"
"\n");
}
@ -1569,7 +1570,7 @@ void TBuiltIns::initialize(int version, EProfile profile)
//
//============================================================================
if (version >= 150) {
if (profile != EEsProfile && version >= 150) {
// Note: "in gl_PerVertex {...} gl_in[gl_MaxPatchVertices];" is declared in initialize() below,
// as it depends on the resource sizing of gl_MaxPatchVertices.
@ -1603,6 +1604,25 @@ void TBuiltIns::initialize(int version, EProfile profile)
"patch out float gl_TessLevelOuter[4];"
"patch out float gl_TessLevelInner[2];"
"\n");
} else {
// Note: "in gl_PerVertex {...} gl_in[gl_MaxPatchVertices];" is declared in initialize() below,
// as it depends on the resource sizing of gl_MaxPatchVertices.
stageBuiltins[EShLangTessControl].append(
"in highp int gl_PatchVerticesIn;"
"in highp int gl_PrimitiveID;"
"in highp int gl_InvocationID;"
"out gl_PerVertex {"
"highp vec4 gl_Position;"
"highp float gl_PointSize;"
);
stageBuiltins[EShLangTessControl].append(
"} gl_out[];"
"patch out highp float gl_TessLevelOuter[4];"
"patch out highp float gl_TessLevelInner[2];"
"\n");
}
//============================================================================
@ -1611,7 +1631,7 @@ void TBuiltIns::initialize(int version, EProfile profile)
//
//============================================================================
if (version >= 150) {
if (profile != EEsProfile && version >= 150) {
// Note: "in gl_PerVertex {...} gl_in[gl_MaxPatchVertices];" is declared in initialize() below,
// as it depends on the resource sizing of gl_MaxPatchVertices.
@ -1645,6 +1665,25 @@ void TBuiltIns::initialize(int version, EProfile profile)
stageBuiltins[EShLangTessEvaluation].append(
"};"
"\n");
} else {
// Note: "in gl_PerVertex {...} gl_in[gl_MaxPatchVertices];" is declared in initialize() below,
// as it depends on the resource sizing of gl_MaxPatchVertices.
stageBuiltins[EShLangTessEvaluation].append(
"in highp int gl_PatchVerticesIn;"
"in highp int gl_PrimitiveID;"
"in highp vec3 gl_TessCoord;"
"patch in highp float gl_TessLevelOuter[4];"
"patch in highp float gl_TessLevelInner[2];"
"out gl_PerVertex {"
"vec4 gl_Position;"
"float gl_PointSize;"
);
stageBuiltins[EShLangTessEvaluation].append(
"};"
"\n");
}
//============================================================================
@ -2923,6 +2962,7 @@ void IdentifyBuiltIns(int version, EProfile profile, EShLanguage language, TSymb
symbolTable.relateToOperator("not", EOpVectorLogicalNot);
symbolTable.relateToOperator("matrixCompMult", EOpMul);
// 120 and 150 are correct for both ES and desktop
if (version >= 120) {
symbolTable.relateToOperator("outerProduct", EOpOuterProduct);
symbolTable.relateToOperator("transpose", EOpTranspose);

View file

@ -2418,15 +2418,13 @@ bool TParseContext::arrayError(TSourceLoc loc, const TType& type)
//
void TParseContext::arraySizeRequiredCheck(TSourceLoc loc, int size)
{
if (size == 0) {
if (size == 0)
error(loc, "array size required", "", "");
size = 1;
}
}
void TParseContext::structArrayCheck(TSourceLoc /*loc*/, TType* type)
void TParseContext::structArrayCheck(TSourceLoc /*loc*/, const TType& type)
{
const TTypeList& structure = *type->getStruct();
const TTypeList& structure = *type.getStruct();
for (int m = 0; m < (int)structure.size(); ++m) {
const TType& member = *structure[m].type;
if (member.isArray() && ! member.isExplicitlySizedArray())
@ -2434,6 +2432,33 @@ void TParseContext::structArrayCheck(TSourceLoc /*loc*/, TType* type)
}
}
void TParseContext::variableArrayUnsizedCheck(TSourceLoc loc, const TType& type, bool initializer)
{
// desktop always allows unsized variable arrays,
// ES always allows them if there is an initializer present to get the size from
if (profile != EEsProfile || initializer)
return;
// for ES, if size isn't coming from an initializer, it has to be explicitly declared now,
// with very few exceptions
switch (language) {
case EShLangGeometry:
if (type.getQualifier().storage == EvqVaryingIn)
if (extensionsTurnedOn(Num_AEP_geometry_shader, AEP_geometry_shader))
return;
break;
case EShLangTessControl:
if (type.getQualifier().storage == EvqVaryingOut)
if (extensionsTurnedOn(Num_AEP_tessellation_shader, AEP_tessellation_shader))
return;
break;
default:
break;
}
arraySizeRequiredCheck(loc, type.getArraySize());
}
void TParseContext::arrayDimError(TSourceLoc loc)
{
requireProfile(loc, EEsProfile | ECoreProfile | ECompatibilityProfile, "arrays of arrays");
@ -2612,7 +2637,7 @@ TSymbol* TParseContext::redeclareBuiltinVariable(TSourceLoc loc, const TString&
// Special case when using GL_ARB_separate_shader_objects
bool ssoPre150 = false; // means the only reason this variable is redeclared is due to this combination
if (version <= 140 && extensionsTurnedOn(1, &GL_ARB_separate_shader_objects)) {
if (profile != EEsProfile && version <= 140 && extensionsTurnedOn(1, &GL_ARB_separate_shader_objects)) {
if (identifier == "gl_Position" ||
identifier == "gl_PointSize" ||
identifier == "gl_ClipVertex" ||
@ -3086,7 +3111,7 @@ void TParseContext::arrayLimitCheck(TSourceLoc loc, const TString& identifier, i
limitCheck(loc, size, "gl_MaxClipDistances", "gl_ClipDistance array size");
}
// See if the provide value is less than the symbol indicated by limit,
// See if the provided value is less than the symbol indicated by limit,
// which should be a constant in the symbol table.
void TParseContext::limitCheck(TSourceLoc loc, int value, const char* limit, const char* feature)
{
@ -3108,18 +3133,19 @@ void TParseContext::finalErrorCheck()
constantIndexExpressionCheck(needsIndexLimitationChecking[i]);
// Check for stages that are enabled by extension.
// Can't do this at the beginning, it is chicken and egg to add a stage by extension.
// Specific stage-specific features were correctly tested for already, this is just
// Can't do this at the beginning, it is chicken and egg to add a stage by
// extension.
// Stage-specific features were correctly tested for already, this is just
// about the stage itself.
switch (language) {
case EShLangGeometry:
if (profile == EEsProfile && version == 310)
requireExtensions(getCurrentLoc(), 1, &GL_EXT_geometry_shader, "geometry shaders");
requireExtensions(getCurrentLoc(), Num_AEP_geometry_shader, AEP_geometry_shader, "geometry shaders");
break;
case EShLangTessControl:
case EShLangTessEvaluation:
if (profile == EEsProfile && version == 310)
requireExtensions(getCurrentLoc(), 1, &GL_EXT_tessellation_shader, "tessellation shaders");
requireExtensions(getCurrentLoc(), Num_AEP_tessellation_shader, AEP_tessellation_shader, "tessellation shaders");
else if (profile != EEsProfile && version < 400)
requireExtensions(getCurrentLoc(), 1, &GL_ARB_tessellation_shader, "tessellation shaders");
break;
@ -3431,6 +3457,7 @@ void TParseContext::setLayoutQualifier(TSourceLoc loc, TPublicType& publicType,
return;
}
if (id == "stream") {
requireProfile(loc, ~EEsProfile, "selecting output stream");
publicType.qualifier.layoutStream = value;
return;
}
@ -4027,9 +4054,7 @@ TIntermNode* TParseContext::declareVariable(TSourceLoc loc, TString& identifier,
if (arraySizes)
type.setArraySizes(arraySizes);
// for ES, if size isn't coming from an initializer, it has to be explicitly declared now
if (profile == EEsProfile && ! initializer)
arraySizeRequiredCheck(loc, type.getArraySize());
variableArrayUnsizedCheck(loc, type, initializer != nullptr);
if (! arrayQualifierError(loc, type.getQualifier()) && ! arrayError(loc, type))
declareArray(loc, identifier, type, symbol, newDeclaration);
@ -4486,38 +4511,7 @@ TIntermTyped* TParseContext::constructStruct(TIntermNode* node, const TType& typ
//
void TParseContext::declareBlock(TSourceLoc loc, TTypeList& typeList, const TString* instanceName, TArraySizes* arraySizes)
{
if (profile == EEsProfile && arraySizes)
arraySizeRequiredCheck(loc, arraySizes->getSize());
switch (currentBlockQualifier.storage) {
case EvqUniform:
profileRequires(loc, EEsProfile, 300, nullptr, "uniform block");
profileRequires(loc, ENoProfile, 140, nullptr, "uniform block");
if (currentBlockQualifier.layoutPacking == ElpStd430)
requireProfile(loc, ~EEsProfile, "std430 on a uniform block");
break;
case EvqBuffer:
requireProfile(loc, EEsProfile | ECoreProfile | ECompatibilityProfile, "buffer block");
profileRequires(loc, ECoreProfile | ECompatibilityProfile, 430, nullptr, "buffer block");
profileRequires(loc, EEsProfile, 310, nullptr, "buffer block");
break;
case EvqVaryingIn:
requireProfile(loc, ~EEsProfile, "input block");
profileRequires(loc, ~EEsProfile, 150, GL_ARB_separate_shader_objects, "input block");
if (language == EShLangVertex)
error(loc, "cannot declare an input block in a vertex shader", "in", "");
break;
case EvqVaryingOut:
requireProfile(loc, ~EEsProfile, "output block");
profileRequires(loc, ~EEsProfile, 150, GL_ARB_separate_shader_objects, "output block");
if (language == EShLangFragment)
error(loc, "cannot declare an output block in a fragment shader", "out", "");
break;
default:
error(loc, "only uniform, buffer, in, or out blocks are supported", blockName->c_str(), "");
return;
}
blockStageIoCheck(loc, currentBlockQualifier.storage, arraySizes);
arrayDimCheck(loc, arraySizes, 0);
// fix and check for member storage qualifiers and types that don't belong within a block
@ -4706,6 +4700,91 @@ void TParseContext::declareBlock(TSourceLoc loc, TTypeList& typeList, const TStr
intermediate.addSymbolLinkageNode(linkage, variable);
}
// Do all block-declaration checking regarding the combination of in/out/uniform/buffer
// with a particular stage and with a given arrayness.
void TParseContext::blockStageIoCheck(TSourceLoc loc, TStorageQualifier storageQualifier, TArraySizes* arraySizes)
{
switch (storageQualifier) {
case EvqUniform:
profileRequires(loc, EEsProfile, 300, nullptr, "uniform block");
profileRequires(loc, ENoProfile, 140, nullptr, "uniform block");
if (currentBlockQualifier.layoutPacking == ElpStd430)
requireProfile(loc, ~EEsProfile, "std430 on a uniform block");
if (profile == EEsProfile && arraySizes)
arraySizeRequiredCheck(loc, arraySizes->getSize());
break;
case EvqBuffer:
requireProfile(loc, EEsProfile | ECoreProfile | ECompatibilityProfile, "buffer block");
profileRequires(loc, ECoreProfile | ECompatibilityProfile, 430, nullptr, "buffer block");
profileRequires(loc, EEsProfile, 310, nullptr, "buffer block");
if (profile == EEsProfile && arraySizes)
arraySizeRequiredCheck(loc, arraySizes->getSize());
break;
case EvqVaryingIn:
profileRequires(loc, ~EEsProfile, 150, GL_ARB_separate_shader_objects, "input block");
switch (language) {
case EShLangVertex:
// It is a compile-time error to have an input block in a vertex shader or an output block in a fragment shader
error(loc, "cannot declare an input block in a vertex shader", "in", "");
break;
case EShLangTessEvaluation:
case EShLangTessControl:
if (profile == EEsProfile && arraySizes)
arraySizeRequiredCheck(loc, arraySizes->getSize());
break;
case EShLangGeometry:
break;
case EShLangFragment:
profileRequires(loc, EEsProfile, 0, Num_AEP_shader_io_blocks, AEP_shader_io_blocks, "fragment input block");
if (profile == EEsProfile && arraySizes)
arraySizeRequiredCheck(loc, arraySizes->getSize());
break;
case EShLangCompute:
// "Compute shaders do not permit user-defined input variables..."
requireStage(loc, (EShLanguageMask)~EShLangComputeMask, "input block");
break;
default:
error(loc, "unexpected stage", "", "");
break;
}
break;
case EvqVaryingOut:
profileRequires(loc, ~EEsProfile, 150, GL_ARB_separate_shader_objects, "output block");
switch (language) {
case EShLangVertex:
profileRequires(loc, EEsProfile, 0, Num_AEP_shader_io_blocks, AEP_shader_io_blocks, "vertex output block");
if (profile == EEsProfile && arraySizes)
arraySizeRequiredCheck(loc, arraySizes->getSize());
break;
case EShLangTessEvaluation:
if (profile == EEsProfile && arraySizes)
arraySizeRequiredCheck(loc, arraySizes->getSize());
break;
case EShLangTessControl:
break;
case EShLangGeometry:
if (profile == EEsProfile && arraySizes)
arraySizeRequiredCheck(loc, arraySizes->getSize());
break;
case EShLangFragment:
// It is a compile-time error to have an input block in a vertex shader or an output block in a fragment shader
error(loc, "cannot declare an output block in a fragment shader", "out", "");
break;
case EShLangCompute:
// "Compute shaders ... do not support user-defined output variables..."
requireStage(loc, (EShLanguageMask)~EShLangComputeMask, "output block");
break;
default:
error(loc, "unexpected stage", "", "");
break;
}
break;
default:
error(loc, "only uniform, buffer, in, or out blocks are supported", blockName->c_str(), "");
return;
}
}
//
// "For a block, this process applies to the entire block, or until the first member
// is reached that has a location layout qualifier. When a block member is declared with a location

View file

@ -123,7 +123,8 @@ public:
bool arrayQualifierError(TSourceLoc, const TQualifier&);
bool arrayError(TSourceLoc, const TType&);
void arraySizeRequiredCheck(TSourceLoc, int size);
void structArrayCheck(TSourceLoc, TType* structure);
void structArrayCheck(TSourceLoc, const TType& structure);
void variableArrayUnsizedCheck(TSourceLoc, const TType&, bool initializer);
void arrayDimError(TSourceLoc);
void arrayDimCheck(TSourceLoc, TArraySizes* sizes1, TArraySizes* sizes2);
void arrayDimCheck(TSourceLoc, const TType*, TArraySizes*);
@ -177,6 +178,7 @@ public:
TIntermTyped* constructStruct(TIntermNode*, const TType&, int, TSourceLoc);
TIntermTyped* constructBuiltIn(const TType&, TOperator, TIntermTyped*, TSourceLoc, bool subset);
void declareBlock(TSourceLoc, TTypeList& typeList, const TString* instanceName = 0, TArraySizes* arraySizes = 0);
void blockStageIoCheck(TSourceLoc, TStorageQualifier, TArraySizes*);
void fixBlockLocations(TSourceLoc, TQualifier&, TTypeList&, bool memberWithLocation, bool memberWithoutLocation);
void fixBlockXfbOffsets(TQualifier&, TTypeList&);
void fixBlockUniformOffsets(TQualifier&, TTypeList&);

View file

@ -178,7 +178,7 @@ void InitializeStageSymbolTable(TBuiltIns& builtIns, int version, EProfile profi
//
// Initialize the full set of shareable symbol tables;
// The common (cross-stage) and those sharable per-stage.
// The common (cross-stage) and those shareable per-stage.
//
bool InitializeSymbolTables(TInfoSink& infoSink, TSymbolTable** commonTable, TSymbolTable** symbolTables, int version, EProfile profile)
{
@ -191,14 +191,24 @@ bool InitializeSymbolTables(TInfoSink& infoSink, TSymbolTable** commonTable, TS
InitializeSymbolTable(builtIns.getCommonString(), version, profile, EShLangFragment, infoSink, *commonTable[EPcFragment]);
// do the per-stage tables
// always have vertex and fragment
InitializeStageSymbolTable(builtIns, version, profile, EShLangVertex, infoSink, commonTable, symbolTables);
InitializeStageSymbolTable(builtIns, version, profile, EShLangFragment, infoSink, commonTable, symbolTables);
if (profile != EEsProfile && version >= 150) {
// check for tessellation
if ((profile != EEsProfile && version >= 150) ||
(profile == EEsProfile && version >= 310)) {
InitializeStageSymbolTable(builtIns, version, profile, EShLangTessControl, infoSink, commonTable, symbolTables);
InitializeStageSymbolTable(builtIns, version, profile, EShLangTessEvaluation, infoSink, commonTable, symbolTables);
}
if (profile != EEsProfile && version >= 150)
// check for geometry
if ((profile != EEsProfile && version >= 150) ||
(profile == EEsProfile && version >= 310))
InitializeStageSymbolTable(builtIns, version, profile, EShLangGeometry, infoSink, commonTable, symbolTables);
// check for compute
if ((profile != EEsProfile && version >= 430) ||
(profile == EEsProfile && version >= 310))
InitializeStageSymbolTable(builtIns, version, profile, EShLangCompute, infoSink, commonTable, symbolTables);

View file

@ -190,6 +190,15 @@ void TParseContext::initializeExtensionBehavior()
extensionBehavior[GL_EXT_tessellation_point_size] = EBhDisablePartial;
extensionBehavior[GL_EXT_texture_buffer] = EBhDisablePartial;
extensionBehavior[GL_EXT_texture_cube_map_array] = EBhDisablePartial;
// OES matching AEP
extensionBehavior[GL_OES_geometry_shader] = EBhDisablePartial;
extensionBehavior[GL_OES_gpu_shader5] = EBhDisablePartial;
extensionBehavior[GL_OES_primitive_bounding_box] = EBhDisablePartial;
extensionBehavior[GL_OES_shader_io_blocks] = EBhDisablePartial;
extensionBehavior[GL_OES_tessellation_shader] = EBhDisablePartial;
extensionBehavior[GL_OES_texture_buffer] = EBhDisablePartial;
extensionBehavior[GL_OES_texture_cube_map_array] = EBhDisablePartial;
}
// Get code that is not part of a shared symbol table, is specific to this shader,
@ -222,6 +231,15 @@ const char* TParseContext::getPreamble()
"#define GL_EXT_tessellation_point_size 1\n"
"#define GL_EXT_texture_buffer 1\n"
"#define GL_EXT_texture_cube_map_array 1\n"
// OES matching AEP
"#define GL_OES_geometry_shader 1\n"
"#define GL_OES_gpu_shader5 1\n"
"#define GL_OES_primitive_bounding_box 1\n"
"#define GL_OES_shader_io_blocks 1\n"
"#define GL_OES_tessellation_shader 1\n"
"#define GL_OES_texture_buffer 1\n"
"#define GL_OES_texture_cube_map_array 1\n"
;
} else {
return
@ -478,8 +496,9 @@ void TParseContext::updateExtensionBehavior(const char* extension, const char* b
// update the requested extension
updateExtensionBehavior(extension, behavior);
// see if need to propagate to everything in AEP
// see if need to propagate to implicitly modified things
if (strcmp(extension, "GL_ANDROID_extension_pack_es31a") == 0) {
// to everything in AEP
updateExtensionBehavior("GL_KHR_blend_equation_advanced", behaviorString);
updateExtensionBehavior("GL_OES_sample_variables", behaviorString);
updateExtensionBehavior("GL_OES_shader_image_atomic", behaviorString);
@ -493,6 +512,16 @@ void TParseContext::updateExtensionBehavior(const char* extension, const char* b
updateExtensionBehavior("GL_EXT_texture_buffer", behaviorString);
updateExtensionBehavior("GL_EXT_texture_cube_map_array", behaviorString);
}
// geometry to io_blocks
else if (strcmp(extension, "GL_EXT_geometry_shader") == 0)
updateExtensionBehavior("GL_EXT_shader_io_blocks", behaviorString);
else if (strcmp(extension, "GL_OES_geometry_shader") == 0)
updateExtensionBehavior("GL_OES_shader_io_blocks", behaviorString);
// tessellation to io_blocks
else if (strcmp(extension, "GL_EXT_tessellation_shader") == 0)
updateExtensionBehavior("GL_EXT_shader_io_blocks", behaviorString);
else if (strcmp(extension, "GL_OES_tessellation_shader") == 0)
updateExtensionBehavior("GL_OES_shader_io_blocks", behaviorString);
}
void TParseContext::updateExtensionBehavior(const char* extension, TExtensionBehavior behavior)

View file

@ -115,6 +115,38 @@ const char* const GL_EXT_tessellation_point_size = "GL_EXT_tessella
const char* const GL_EXT_texture_buffer = "GL_EXT_texture_buffer";
const char* const GL_EXT_texture_cube_map_array = "GL_EXT_texture_cube_map_array";
// OES matching AEP
const char* const GL_OES_geometry_shader = "GL_OES_geometry_shader";
const char* const GL_OES_gpu_shader5 = "GL_OES_gpu_shader5";
const char* const GL_OES_primitive_bounding_box = "GL_OES_primitive_bounding_box";
const char* const GL_OES_shader_io_blocks = "GL_OES_shader_io_blocks";
const char* const GL_OES_tessellation_shader = "GL_OES_tessellation_shader";
const char* const GL_OES_texture_buffer = "GL_OES_texture_buffer";
const char* const GL_OES_texture_cube_map_array = "GL_OES_texture_cube_map_array";
// Arrays of extensions for the above AEP duplications
const char* const AEP_geometry_shader[] = { GL_EXT_geometry_shader, GL_OES_geometry_shader };
const int Num_AEP_geometry_shader = sizeof(AEP_geometry_shader)/sizeof(AEP_geometry_shader[0]);
const char* const AEP_gpu_shader5[] = { GL_EXT_gpu_shader5, GL_OES_gpu_shader5 };
const int Num_AEP_gpu_shader5 = sizeof(AEP_gpu_shader5)/sizeof(AEP_gpu_shader5[0]);
const char* const AEP_primitive_bounding_box[] = { GL_EXT_primitive_bounding_box, GL_OES_primitive_bounding_box };
const int Num_AEP_primitive_bounding_box = sizeof(AEP_primitive_bounding_box)/sizeof(AEP_primitive_bounding_box[0]);
const char* const AEP_shader_io_blocks[] = { GL_EXT_shader_io_blocks, GL_OES_shader_io_blocks };
const int Num_AEP_shader_io_blocks = sizeof(AEP_shader_io_blocks)/sizeof(AEP_shader_io_blocks[0]);
const char* const AEP_tessellation_shader[] = { GL_EXT_tessellation_shader, GL_OES_tessellation_shader };
const int Num_AEP_tessellation_shader = sizeof(AEP_tessellation_shader)/sizeof(AEP_tessellation_shader[0]);
const char* const AEP_texture_buffer[] = { GL_EXT_texture_buffer, GL_OES_texture_buffer };
const int Num_AEP_texture_buffer = sizeof(AEP_texture_buffer)/sizeof(AEP_texture_buffer[0]);
const char* const AEP_texture_cube_map_array[] = { GL_EXT_texture_cube_map_array, GL_OES_texture_cube_map_array };
const int Num_AEP_texture_cube_map_array = sizeof(AEP_texture_cube_map_array)/sizeof(AEP_texture_cube_map_array[0]);
} // end namespace glslang
#endif // _VERSIONS_INCLUDED_

View file

@ -1919,7 +1919,7 @@ precision_qualifier
struct_specifier
: STRUCT IDENTIFIER LEFT_BRACE { parseContext.nestedStructCheck($1.loc); } struct_declaration_list RIGHT_BRACE {
TType* structure = new TType($5, *$2.string);
parseContext.structArrayCheck($2.loc, structure);
parseContext.structArrayCheck($2.loc, *structure);
TVariable* userTypeDef = new TVariable($2.string, *structure, true);
if (! parseContext.symbolTable.insert(*userTypeDef))
parseContext.error($2.loc, "redefinition", $2.string->c_str(), "struct");