Initial implementation of layout qualifiers. More to come after uniform blocks are in place.
git-svn-id: https://cvs.khronos.org/svn/repos/ogl/trunk/ecosystem/public/sdk/tools/glslang@21078 e7fa87d3-cd2b-0410-9028-fcbf551c1848
This commit is contained in:
parent
3ed2db58f1
commit
e9942d26f5
29
Test/300layout.frag
Normal file
29
Test/300layout.frag
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
#version 300 es
|
||||||
|
|
||||||
|
in vec4 pos;
|
||||||
|
in vec4 color;
|
||||||
|
|
||||||
|
layout(location = 7) out vec4 c;
|
||||||
|
layout(location = 3) out vec4 p;
|
||||||
|
|
||||||
|
//layout(std140) uniform Transform { // layout of this block is std140
|
||||||
|
// mat4 M1; // row_major
|
||||||
|
// layout(column_major) mat4 M2; // column major
|
||||||
|
// mat3 N1; // row_major
|
||||||
|
//};
|
||||||
|
//
|
||||||
|
//uniform T2 { // layout of this block is shared
|
||||||
|
//...
|
||||||
|
//};
|
||||||
|
//
|
||||||
|
//layout(column_major) uniform T3 { // shared and column_major
|
||||||
|
// mat4 M3; // column_major
|
||||||
|
// layout(row_major) mat4 m4; // row major
|
||||||
|
// mat3 N2; // column_major
|
||||||
|
//};
|
||||||
|
|
||||||
|
void main()
|
||||||
|
{
|
||||||
|
c = color;
|
||||||
|
p = pos;
|
||||||
|
}
|
30
Test/300layout.vert
Normal file
30
Test/300layout.vert
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
#version 300 es
|
||||||
|
|
||||||
|
layout(location = 7) in vec4 c;
|
||||||
|
layout(LocatioN = 3) in vec4 p;
|
||||||
|
out vec4 pos;
|
||||||
|
out vec4 color;
|
||||||
|
|
||||||
|
layout(shared, column_major, row_major) uniform mat4 m4; // default is now shared and row_major
|
||||||
|
|
||||||
|
//layout(std140) uniform Transform { // layout of this block is std140
|
||||||
|
// mat4 M1; // row_major
|
||||||
|
// layout(column_major) mat4 M2; // column major
|
||||||
|
// mat3 N1; // row_major
|
||||||
|
//};
|
||||||
|
//
|
||||||
|
//uniform T2 { // layout of this block is shared
|
||||||
|
//...
|
||||||
|
//};
|
||||||
|
//
|
||||||
|
//layout(column_major) uniform T3 { // shared and column_major
|
||||||
|
// mat4 M3; // column_major
|
||||||
|
// layout(row_major) mat4 m4; // row major
|
||||||
|
// mat3 N2; // column_major
|
||||||
|
//};
|
||||||
|
|
||||||
|
void main()
|
||||||
|
{
|
||||||
|
pos = p * m4;
|
||||||
|
color = c;
|
||||||
|
}
|
@ -24,6 +24,8 @@ comment.frag
|
|||||||
300.vert
|
300.vert
|
||||||
300.frag
|
300.frag
|
||||||
300BuiltIns.frag
|
300BuiltIns.frag
|
||||||
|
300layout.vert
|
||||||
|
300layout.frag
|
||||||
330.frag
|
330.frag
|
||||||
330comp.frag
|
330comp.frag
|
||||||
constErrors.frag
|
constErrors.frag
|
||||||
|
@ -64,7 +64,8 @@ enum TStorageQualifier {
|
|||||||
EvqConst, // User defined constants and non-output parameters in functions
|
EvqConst, // User defined constants and non-output parameters in functions
|
||||||
EvqVaryingIn, // pipeline input, read only
|
EvqVaryingIn, // pipeline input, read only
|
||||||
EvqVaryingOut, // pipeline ouput, read/write
|
EvqVaryingOut, // pipeline ouput, read/write
|
||||||
EvqUniform, // Readonly, vertex and fragment
|
EvqUniform, // read only, shader with app
|
||||||
|
EVqBuffer, // read only, shader with app
|
||||||
|
|
||||||
// parameters
|
// parameters
|
||||||
EvqIn,
|
EvqIn,
|
||||||
@ -102,8 +103,8 @@ __inline const char* getStorageQualifierString(TStorageQualifier q)
|
|||||||
case EvqGlobal: return "global"; break;
|
case EvqGlobal: return "global"; break;
|
||||||
case EvqConst: return "const"; break;
|
case EvqConst: return "const"; break;
|
||||||
case EvqConstReadOnly: return "const (read only)"; break;
|
case EvqConstReadOnly: return "const (read only)"; break;
|
||||||
case EvqVaryingIn: return "shader in"; break;
|
case EvqVaryingIn: return "in"; break;
|
||||||
case EvqVaryingOut: return "shader out"; break;
|
case EvqVaryingOut: return "out"; break;
|
||||||
case EvqUniform: return "uniform"; break;
|
case EvqUniform: return "uniform"; break;
|
||||||
case EvqIn: return "in"; break;
|
case EvqIn: return "in"; break;
|
||||||
case EvqOut: return "out"; break;
|
case EvqOut: return "out"; break;
|
||||||
@ -116,7 +117,7 @@ __inline const char* getStorageQualifierString(TStorageQualifier q)
|
|||||||
case EvqFace: return "gl_FrontFacing"; break;
|
case EvqFace: return "gl_FrontFacing"; break;
|
||||||
case EvqFragCoord: return "gl_FragCoord"; break;
|
case EvqFragCoord: return "gl_FragCoord"; break;
|
||||||
case EvqPointCoord: return "gl_PointCoord"; break;
|
case EvqPointCoord: return "gl_PointCoord"; break;
|
||||||
case EvqFragColor: return "fragment out"; break;
|
case EvqFragColor: return "fragColor"; break;
|
||||||
case EvqFragDepth: return "gl_FragDepth"; break;
|
case EvqFragDepth: return "gl_FragDepth"; break;
|
||||||
default: return "unknown qualifier";
|
default: return "unknown qualifier";
|
||||||
}
|
}
|
||||||
|
@ -171,7 +171,8 @@ inline TArraySizes NewPoolTArraySizes()
|
|||||||
}
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
// TPublicType is a workaround for a problem with the yacc stack, It can't have
|
// TPublicType (coming up after some dependent declarations)
|
||||||
|
// is a workaround for a problem with the yacc stack, It can't have
|
||||||
// types that it thinks have non-trivial constructors. It should
|
// types that it thinks have non-trivial constructors. It should
|
||||||
// just be used while recognizing the grammar, not anything else. Pointers
|
// just be used while recognizing the grammar, not anything else. Pointers
|
||||||
// could be used, but also trying to avoid lots of memory management overhead.
|
// could be used, but also trying to avoid lots of memory management overhead.
|
||||||
@ -180,13 +181,26 @@ inline TArraySizes NewPoolTArraySizes()
|
|||||||
// match up or are named the same or anything like that.
|
// match up or are named the same or anything like that.
|
||||||
//
|
//
|
||||||
|
|
||||||
|
enum TLayoutPacking {
|
||||||
|
ElpNone,
|
||||||
|
ElpShared, // default, but different than saying nothing
|
||||||
|
ElpStd140,
|
||||||
|
ElpStd430,
|
||||||
|
ElpPacked // see bitfield width below
|
||||||
|
};
|
||||||
|
|
||||||
|
enum TLayoutMatrix {
|
||||||
|
ElmNone,
|
||||||
|
ElmRowMajor,
|
||||||
|
ElmColumnMajor // default, but different than saying nothing
|
||||||
|
}; // see bitfield width below
|
||||||
|
|
||||||
class TQualifier {
|
class TQualifier {
|
||||||
public:
|
public:
|
||||||
void clear()
|
void clear()
|
||||||
{
|
{
|
||||||
storage = EvqTemporary;
|
storage = EvqTemporary;
|
||||||
precision = EpqNone;
|
precision = EpqNone;
|
||||||
buffer = false;
|
|
||||||
invariant = false;
|
invariant = false;
|
||||||
centroid = false;
|
centroid = false;
|
||||||
smooth = false;
|
smooth = false;
|
||||||
@ -200,10 +214,10 @@ public:
|
|||||||
restrict = false;
|
restrict = false;
|
||||||
readonly = false;
|
readonly = false;
|
||||||
writeonly = false;
|
writeonly = false;
|
||||||
|
clearLayout();
|
||||||
}
|
}
|
||||||
TStorageQualifier storage : 7;
|
TStorageQualifier storage : 6;
|
||||||
TPrecisionQualifier precision : 3;
|
TPrecisionQualifier precision : 3;
|
||||||
bool buffer : 1;
|
|
||||||
bool invariant : 1;
|
bool invariant : 1;
|
||||||
bool centroid : 1;
|
bool centroid : 1;
|
||||||
bool smooth : 1;
|
bool smooth : 1;
|
||||||
@ -217,6 +231,7 @@ public:
|
|||||||
bool restrict : 1;
|
bool restrict : 1;
|
||||||
bool readonly : 1;
|
bool readonly : 1;
|
||||||
bool writeonly : 1;
|
bool writeonly : 1;
|
||||||
|
|
||||||
bool isMemory()
|
bool isMemory()
|
||||||
{
|
{
|
||||||
return coherent || volatil || restrict || readonly || writeonly;
|
return coherent || volatil || restrict || readonly || writeonly;
|
||||||
@ -229,6 +244,46 @@ public:
|
|||||||
{
|
{
|
||||||
return centroid || patch || sample;
|
return centroid || patch || sample;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Implementing an embedded layout-qualifier class here, since C++ can't have a real class bitfield
|
||||||
|
void clearLayout()
|
||||||
|
{
|
||||||
|
layoutMatrix = ElmNone;
|
||||||
|
layoutPacking = ElpNone;
|
||||||
|
layoutSlotLocation = layoutLocationEnd;
|
||||||
|
}
|
||||||
|
bool hasLayout() const
|
||||||
|
{
|
||||||
|
return layoutMatrix != ElmNone ||
|
||||||
|
layoutPacking != ElpNone ||
|
||||||
|
layoutSlotLocation != layoutLocationEnd;
|
||||||
|
}
|
||||||
|
TLayoutMatrix layoutMatrix : 3;
|
||||||
|
TLayoutPacking layoutPacking : 4;
|
||||||
|
unsigned int layoutSlotLocation : 7; // ins/outs should have small numbers, buffer offsets could be large
|
||||||
|
static const unsigned int layoutLocationEnd = 0x3F;
|
||||||
|
bool hasLocation() const
|
||||||
|
{
|
||||||
|
return layoutSlotLocation != layoutLocationEnd;
|
||||||
|
}
|
||||||
|
static const char* getLayoutPackingString(TLayoutPacking packing)
|
||||||
|
{
|
||||||
|
switch (packing) {
|
||||||
|
case ElpPacked: return "packed";
|
||||||
|
case ElpShared: return "shared";
|
||||||
|
case ElpStd140: return "std140";
|
||||||
|
case ElpStd430: return "std430";
|
||||||
|
default: return "none";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
static const char* getLayoutMatrixString(TLayoutMatrix m)
|
||||||
|
{
|
||||||
|
switch (m) {
|
||||||
|
case ElmColumnMajor: return "column_major";
|
||||||
|
case ElmRowMajor: return "row_major";
|
||||||
|
default: return "none";
|
||||||
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
class TPublicType {
|
class TPublicType {
|
||||||
@ -479,8 +534,17 @@ public:
|
|||||||
char *p = &buf[0];
|
char *p = &buf[0];
|
||||||
char *end = &buf[maxSize];
|
char *end = &buf[maxSize];
|
||||||
|
|
||||||
if (qualifier.buffer)
|
if (qualifier.hasLayout()) {
|
||||||
p += snprintf(p, end - p, "buffer ");
|
p += snprintf(p, end - p, "layout(");
|
||||||
|
if (qualifier.hasLocation())
|
||||||
|
p += snprintf(p, end - p, "location=%d ", qualifier.layoutSlotLocation);
|
||||||
|
if (qualifier.layoutMatrix != ElmNone)
|
||||||
|
p += snprintf(p, end - p, "%s ", TQualifier::getLayoutMatrixString(qualifier.layoutMatrix));
|
||||||
|
if (qualifier.layoutPacking != ElpNone)
|
||||||
|
p += snprintf(p, end - p, "%s ", TQualifier::getLayoutPackingString(qualifier.layoutPacking));
|
||||||
|
p += snprintf(p, end - p, ") ");
|
||||||
|
}
|
||||||
|
|
||||||
if (qualifier.invariant)
|
if (qualifier.invariant)
|
||||||
p += snprintf(p, end - p, "invariant ");
|
p += snprintf(p, end - p, "invariant ");
|
||||||
if (qualifier.centroid)
|
if (qualifier.centroid)
|
||||||
|
@ -38,6 +38,7 @@
|
|||||||
#include "Include/InitializeParseContext.h"
|
#include "Include/InitializeParseContext.h"
|
||||||
#include "osinclude.h"
|
#include "osinclude.h"
|
||||||
#include <stdarg.h>
|
#include <stdarg.h>
|
||||||
|
#include <algorithm>
|
||||||
|
|
||||||
TParseContext::TParseContext(TSymbolTable& symt, TIntermediate& interm, int v, EProfile p, EShLanguage L, TInfoSink& is,
|
TParseContext::TParseContext(TSymbolTable& symt, TIntermediate& interm, int v, EProfile p, EShLanguage L, TInfoSink& is,
|
||||||
bool fc, EShMessages m) :
|
bool fc, EShMessages m) :
|
||||||
@ -706,7 +707,7 @@ bool TParseContext::globalQualifierFixAndErrorCheck(int line, TQualifier& qualif
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (language == EShLangVertex && qualifier.storage == EvqVaryingIn &&
|
if (language == EShLangVertex && qualifier.storage == EvqVaryingIn &&
|
||||||
(qualifier.isAuxillary() || qualifier.isInterpolation() || qualifier.isMemory() || qualifier.buffer || qualifier.invariant)) {
|
(qualifier.isAuxillary() || qualifier.isInterpolation() || qualifier.isMemory() || qualifier.invariant)) {
|
||||||
error(line, "vertex input cannot be further qualified", "", "");
|
error(line, "vertex input cannot be further qualified", "", "");
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
@ -748,9 +749,11 @@ bool TParseContext::mergeQualifiersErrorCheck(int line, TPublicType& left, const
|
|||||||
bad = true;
|
bad = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Layout qualifiers
|
||||||
|
mergeLayoutQualifiers(line, left, right);
|
||||||
|
|
||||||
// other qualifiers
|
// other qualifiers
|
||||||
#define MERGE_SINGLETON(field) bad |= left.qualifier.field && right.qualifier.field; left.qualifier.field |= right.qualifier.field;
|
#define MERGE_SINGLETON(field) bad |= left.qualifier.field && right.qualifier.field; left.qualifier.field |= right.qualifier.field;
|
||||||
MERGE_SINGLETON(buffer);
|
|
||||||
MERGE_SINGLETON(invariant);
|
MERGE_SINGLETON(invariant);
|
||||||
MERGE_SINGLETON(centroid);
|
MERGE_SINGLETON(centroid);
|
||||||
MERGE_SINGLETON(smooth);
|
MERGE_SINGLETON(smooth);
|
||||||
@ -1086,6 +1089,62 @@ bool TParseContext::paramErrorCheck(int line, TStorageQualifier qualifier, TType
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// Layout qualifier stuff.
|
||||||
|
//
|
||||||
|
|
||||||
|
// Put the id's layout qualification into the public type.
|
||||||
|
void TParseContext::setLayoutQualifier(int line, TPublicType& publicType, TString& id)
|
||||||
|
{
|
||||||
|
std::transform(id.begin(), id.end(), id.begin(), ::tolower);
|
||||||
|
if (id == TQualifier::getLayoutMatrixString(ElmColumnMajor))
|
||||||
|
publicType.qualifier.layoutMatrix = ElmColumnMajor;
|
||||||
|
else if (id == TQualifier::getLayoutMatrixString(ElmRowMajor))
|
||||||
|
publicType.qualifier.layoutMatrix = ElmRowMajor;
|
||||||
|
else if (id == TQualifier::getLayoutPackingString(ElpPacked))
|
||||||
|
publicType.qualifier.layoutPacking = ElpPacked;
|
||||||
|
else if (id == TQualifier::getLayoutPackingString(ElpShared))
|
||||||
|
publicType.qualifier.layoutPacking = ElpShared;
|
||||||
|
else if (id == TQualifier::getLayoutPackingString(ElpStd140))
|
||||||
|
publicType.qualifier.layoutPacking = ElpStd140;
|
||||||
|
else if (id == TQualifier::getLayoutPackingString(ElpStd430))
|
||||||
|
publicType.qualifier.layoutPacking = ElpStd430;
|
||||||
|
else if (id == "location")
|
||||||
|
error(line, "requires an integer assignment (e.g., location = 4)", "location", "");
|
||||||
|
else if (id == "binding")
|
||||||
|
error(line, "requires an integer assignment (e.g., binding = 4)", "binding", "");
|
||||||
|
else
|
||||||
|
error(line, "unrecognized layout identifier", id.c_str(), "");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Put the id's layout qualifier value into the public type.
|
||||||
|
void TParseContext::setLayoutQualifier(int line, TPublicType& publicType, TString& id, int value)
|
||||||
|
{
|
||||||
|
std::transform(id.begin(), id.end(), id.begin(), ::tolower);
|
||||||
|
if (id == "location") {
|
||||||
|
if ((unsigned int)value >= TQualifier::layoutLocationEnd)
|
||||||
|
error(line, "value is too large", id.c_str(), "");
|
||||||
|
else
|
||||||
|
publicType.qualifier.layoutSlotLocation = value;
|
||||||
|
} else if (id == "binding")
|
||||||
|
error(line, "not supported", "binding", "");
|
||||||
|
else
|
||||||
|
error(line, "there is no such layout identifier taking an assigned value", id.c_str(), "");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Merge any layout qualifier information from src into dst, leaving everything else in dst alone
|
||||||
|
void TParseContext::mergeLayoutQualifiers(int line, TPublicType& dst, const TPublicType& src)
|
||||||
|
{
|
||||||
|
if (src.qualifier.layoutMatrix != ElmNone)
|
||||||
|
dst.qualifier.layoutMatrix = src.qualifier.layoutMatrix;
|
||||||
|
|
||||||
|
if (src.qualifier.layoutPacking != ElpNone)
|
||||||
|
dst.qualifier.layoutPacking = src.qualifier.layoutPacking;
|
||||||
|
|
||||||
|
if (src.qualifier.hasLocation())
|
||||||
|
dst.qualifier.layoutSlotLocation = src.qualifier.layoutSlotLocation;
|
||||||
|
}
|
||||||
|
|
||||||
/////////////////////////////////////////////////////////////////////////////////
|
/////////////////////////////////////////////////////////////////////////////////
|
||||||
//
|
//
|
||||||
// Non-Errors.
|
// Non-Errors.
|
||||||
|
@ -129,6 +129,11 @@ struct TParseContext {
|
|||||||
bool nonInitConstErrorCheck(int line, TString& identifier, TPublicType& type);
|
bool nonInitConstErrorCheck(int line, TString& identifier, TPublicType& type);
|
||||||
bool nonInitErrorCheck(int line, TString& identifier, TPublicType& type);
|
bool nonInitErrorCheck(int line, TString& identifier, TPublicType& type);
|
||||||
bool paramErrorCheck(int line, TStorageQualifier qualifier, TType* type);
|
bool paramErrorCheck(int line, TStorageQualifier qualifier, TType* type);
|
||||||
|
|
||||||
|
void setLayoutQualifier(int line, TPublicType&, TString&);
|
||||||
|
void setLayoutQualifier(int line, TPublicType&, TString&, int);
|
||||||
|
void mergeLayoutQualifiers(int line, TPublicType& dest, const TPublicType& src);
|
||||||
|
|
||||||
const TFunction* findFunction(int line, TFunction* pfnCall, bool *builtIn = 0);
|
const TFunction* findFunction(int line, TFunction* pfnCall, bool *builtIn = 0);
|
||||||
bool executeInitializer(TSourceLoc line, TString& identifier, TPublicType& pType,
|
bool executeInitializer(TSourceLoc line, TString& identifier, TPublicType& pType,
|
||||||
TIntermTyped* initializer, TIntermNode*& intermNode, TVariable* variable = 0);
|
TIntermTyped* initializer, TIntermNode*& intermNode, TVariable* variable = 0);
|
||||||
|
@ -201,7 +201,7 @@ extern void yyerror(const char*);
|
|||||||
|
|
||||||
%type <interm> array_specifier
|
%type <interm> array_specifier
|
||||||
%type <interm.type> precise_qualifier invariant_qualifier interpolation_qualifier storage_qualifier precision_qualifier
|
%type <interm.type> precise_qualifier invariant_qualifier interpolation_qualifier storage_qualifier precision_qualifier
|
||||||
%type <interm.type> layout_qualifier layout_qualifier_id_list
|
%type <interm.type> layout_qualifier layout_qualifier_id_list layout_qualifier_id
|
||||||
|
|
||||||
%type <interm.type> type_qualifier fully_specified_type type_specifier
|
%type <interm.type> type_qualifier fully_specified_type type_specifier
|
||||||
%type <interm.type> single_type_qualifier
|
%type <interm.type> single_type_qualifier
|
||||||
@ -1622,22 +1622,31 @@ interpolation_qualifier
|
|||||||
|
|
||||||
layout_qualifier
|
layout_qualifier
|
||||||
: LAYOUT LEFT_PAREN layout_qualifier_id_list RIGHT_PAREN {
|
: LAYOUT LEFT_PAREN layout_qualifier_id_list RIGHT_PAREN {
|
||||||
$$.init($1.line);
|
$$ = $3;
|
||||||
}
|
}
|
||||||
;
|
;
|
||||||
|
|
||||||
layout_qualifier_id_list
|
layout_qualifier_id_list
|
||||||
: layout_qualifier_id {
|
: layout_qualifier_id {
|
||||||
|
$$ = $1;
|
||||||
}
|
}
|
||||||
| layout_qualifier_id_list COMMA layout_qualifier_id {
|
| layout_qualifier_id_list COMMA layout_qualifier_id {
|
||||||
|
$$ = $1;
|
||||||
|
parseContext.mergeLayoutQualifiers($2.line, $$, $3);
|
||||||
}
|
}
|
||||||
|
|
||||||
layout_qualifier_id
|
layout_qualifier_id
|
||||||
: IDENTIFIER {
|
: IDENTIFIER {
|
||||||
|
$$.init($1.line);
|
||||||
|
parseContext.setLayoutQualifier($1.line, $$, *$1.string);
|
||||||
}
|
}
|
||||||
| IDENTIFIER EQUAL INTCONSTANT {
|
| IDENTIFIER EQUAL INTCONSTANT {
|
||||||
|
$$.init($1.line);
|
||||||
|
parseContext.setLayoutQualifier($1.line, $$, *$1.string, $3.i);
|
||||||
}
|
}
|
||||||
| SHARED {
|
| SHARED { // because "shared" is both an identifier and a keyword
|
||||||
|
$$.init($1.line);
|
||||||
|
parseContext.setLayoutQualifier($1.line, $$, TString("shared"));
|
||||||
}
|
}
|
||||||
;
|
;
|
||||||
|
|
||||||
@ -1766,8 +1775,7 @@ storage_qualifier
|
|||||||
if (parseContext.globalErrorCheck($1.line, parseContext.symbolTable.atGlobalLevel(), "buffer"))
|
if (parseContext.globalErrorCheck($1.line, parseContext.symbolTable.atGlobalLevel(), "buffer"))
|
||||||
parseContext.recover();
|
parseContext.recover();
|
||||||
$$.init($1.line);
|
$$.init($1.line);
|
||||||
$$.qualifier.storage = EvqUniform;
|
$$.qualifier.storage = EvqUniform; // TODO: functionality: implement BUFFER
|
||||||
$$.qualifier.buffer = true;
|
|
||||||
}
|
}
|
||||||
| SHARED {
|
| SHARED {
|
||||||
$$.init($1.line);
|
$$.init($1.line);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user