Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
G
glslang
Project
Overview
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
Chen Yisong
glslang
Commits
779e6b40
Commit
779e6b40
authored
Oct 17, 2013
by
John Kessenich
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Add C-style curly-brace initializers.
git-svn-id:
https://cvs.khronos.org/svn/repos/ogl/trunk/ecosystem/public/sdk/tools/glslang@23565
e7fa87d3-cd2b-0410-9028-fcbf551c1848
parent
e7c59c18
Hide whitespace changes
Inline
Side-by-side
Showing
9 changed files
with
372 additions
and
18 deletions
+372
-18
420.tese
Test/420.tese
+72
-0
100.frag.out
Test/baseResults/100.frag.out
+8
-2
420.tese.out
Test/baseResults/420.tese.out
+157
-5
specExamples.frag.out
Test/baseResults/specExamples.frag.out
+20
-1
specExamples.vert.out
Test/baseResults/specExamples.vert.out
+13
-1
ConstantUnion.h
glslang/Include/ConstantUnion.h
+3
-0
ParseHelper.cpp
glslang/MachineIndependent/ParseHelper.cpp
+95
-5
ParseHelper.h
glslang/MachineIndependent/ParseHelper.h
+2
-1
glslang.y
glslang/MachineIndependent/glslang.y
+2
-3
No files found.
Test/420.tese
View file @
779e6b40
#version 420 core
const mat2x2 a = mat2( vec2( 1.0, 0.0 ), vec2( 0.0, 1.0 ) );
mat2x2 b = { vec2( 1.0, 0.0 ), vec2( 0.0, 1.0 ) };
const mat2x2 c = { { 1.0, 0.0, }, { 0.0, 1.0 } };
float a2[2] = { 3.4, 4.2, 5.0 }; // illegal
vec2 b2 = { 1.0, 2.0, 3.0 }; // illegal
mat3x3 c2 = { vec3(0.0), vec3(1.0), vec3(2.0), vec3(3.0) }; // illegal
mat2x2 d = { 1.0, 0.0, 0.0, 1.0 }; // illegal, can't flatten nesting
struct {
float a;
int b;
} e = { 1.2, 2, };
struct {
float a;
int b;
} e2 = { 1, 3 }; // legal, first initializer is converted
struct {
float a;
int b;
} e3 = { 1.2, 2, 3 }; // illegal
int a3 = true; // illegal
vec4 b3[2] = { vec4(0.0), 1.0 }; // illegal
vec4 b4[2] = vec4[2](vec4(0.0), mat2x2(1.0)); // illegal
mat4x2 c3 = { vec3(0.0), vec3(1.0) }; // illegal
struct S1 {
vec4 a;
vec4 b;
};
struct {
float s;
float t;
} d2[] = { S1(vec4(0.0), vec4(1.1)) }; // illegal
float b5[] = { 3.4, 4.2, 5.0, 5.2, 1.1 };
struct S3 {
float f;
mat2x3 m23;
};
struct S4 {
uvec2 uv2;
S3 s[2];
};
const S4 constructed = S4(uvec2(1, 2),
S3[2](S3(3.0, mat2x3(4.0)),
S3(5.0, mat2x3(6.0))));
const S4 curlybad1 = { {1, 2},
{ {3, {4.0, 0, 0.0}, {0.0, 4.0, 0.0 } }, // ERROR, the mat2x3 isn't isolated
{5.0, {6, 0.0, 0.0}, {0.0, 6.0, 0.0 } } } };
const S4 curlyInit = { {1, 2},
{ {3, { {4.0, 0, 0.0}, {0.0, 4.0, 0.0 } } },
{5.0, { {6, 0.0, 0.0}, {0.0, 6.0, 0.0 } } } } };
float vc1, vc2, vc3;
vec3 av3 = vec3(vc1, vc2, vc3);
vec3 bv3 = { vc1, vc2, vc3 };
void main()
{
memoryBarrier();
if (constructed == curlybad1)
;
if (constructed == curlyInit)
;
}
Test/baseResults/100.frag.out
View file @
779e6b40
ERROR: 0:3: '{ } style initializers' : not supported with this profile: es
ERROR: 0:3: 'initializer' : not supported for this version or the enabled extensions
ERROR: 0:3: '=' : cannot convert from 'const int' to '3-element array of mediump int'
ERROR: 0:7: 'attribute' : not supported in this stage: fragment
ERROR: 0:7: 'float' : type requires declaration of default precision qualifier
ERROR: 0:9: '=' : cannot convert from 'const int' to 'mediump float'
...
...
@@ -21,9 +20,16 @@ ERROR: 0:38: 'array comparison' : not supported for this version or the enabled
ERROR: 0:40: 'switch' : Reserved word.
ERROR: 0:40: 'switch statements' : not supported for this version or the enabled extensions
ERROR: 0:45: '' : array size required
ERROR: 2
3
compilation errors. No code generated.
ERROR: 2
2
compilation errors. No code generated.
ERROR: node is still EOpNull!
0:3 Sequence
0:3 move second child to first child (3-element array of mediump int)
0:3 'a' (3-element array of mediump int)
0:3 Constant:
0:3 2 (const int)
0:3 3 (const int)
0:3 4 (const int)
0:17 Function Definition: main( (void)
0:17 Function Parameters:
0:19 Sequence
...
...
Test/baseResults/420.tese.out
View file @
779e6b40
Warning, version 420 is not yet complete; some version-specific features are present, but many are missing.
0:? Sequence
0:3 Function Definition: main( (void)
0:3 Function Parameters:
0:5 Sequence
0:5 MemoryBarrier (void)
ERROR: 0:7: '=' : cannot convert from '3-element array of float' to '2-element array of float'
ERROR: 0:8: 'initializer list' : wrong vector size (or rows in a matrix column): 2-component vector of float
ERROR: 0:9: 'initializer list' : wrong number of matrix columns: 3X3 matrix of float
ERROR: 0:10: 'initializer list' : wrong number of matrix columns: 2X2 matrix of float
ERROR: 0:25: 'initializer list' : wrong number of structure members
ERROR: 0:27: '=' : cannot convert from 'const bool' to 'int'
ERROR: 0:28: 'constructor' : cannot convert parameter 2 from 'const float' to '4-component vector of float'
ERROR: 0:29: 'constructor' : cannot convert parameter 2 from 'const 2X2 matrix of float' to 'const 4-component vector of float'
ERROR: 0:29: 'const 2-element array of 4-component vector of float' : cannot construct with these arguments
ERROR: 0:30: 'initializer list' : wrong number of matrix columns: 4X2 matrix of float
ERROR: 0:40: 'constructor' : cannot convert parameter 1 from 'float' to 'structure'
ERROR: 0:58: 'initializer list' : wrong number of structure members
ERROR: 12 compilation errors. No code generated.
ERROR: node is still EOpNull!
0:4 Sequence
0:4 move second child to first child (2X2 matrix of float)
0:4 'b' (2X2 matrix of float)
0:4 Constant:
0:4 1.000000
0:4 0.000000
0:4 0.000000
0:4 1.000000
0:15 Sequence
0:15 move second child to first child (structure)
0:15 'e' (structure)
0:15 Constant:
0:15 1.200000
0:15 2 (const int)
0:20 Sequence
0:20 move second child to first child (structure)
0:20 'e2' (structure)
0:20 Constant:
0:20 1.000000
0:20 3 (const int)
0:29 Sequence
0:29 move second child to first child (2-element array of 4-component vector of float)
0:29 'b4' (2-element array of 4-component vector of float)
0:29 Construct vec4 (const 2-element array of 4-component vector of float)
0:42 Sequence
0:42 move second child to first child (5-element array of float)
0:42 'b5' (5-element array of float)
0:42 Constant:
0:42 3.400000
0:42 4.200000
0:42 5.000000
0:42 5.200000
0:42 1.100000
0:67 Sequence
0:67 move second child to first child (3-component vector of float)
0:67 'av3' (3-component vector of float)
0:67 Construct vec3 (3-component vector of float)
0:67 'vc1' (float)
0:67 'vc2' (float)
0:67 'vc3' (float)
0:68 Sequence
0:68 move second child to first child (3-component vector of float)
0:68 'bv3' (3-component vector of float)
0:68 Construct vec3 (3-component vector of float)
0:68 'vc1' (float)
0:68 'vc2' (float)
0:68 'vc3' (float)
0:70 Function Definition: main( (void)
0:70 Function Parameters:
0:72 Sequence
0:72 MemoryBarrier (void)
0:74 Test condition and select (void)
0:74 Condition
0:74 Compare Equal (bool)
0:74 Constant:
0:74 1 (const uint)
0:74 2 (const uint)
0:74 3.000000
0:74 4.000000
0:74 0.000000
0:74 0.000000
0:74 0.000000
0:74 4.000000
0:74 0.000000
0:74 5.000000
0:74 6.000000
0:74 0.000000
0:74 0.000000
0:74 0.000000
0:74 6.000000
0:74 0.000000
0:74 'curlybad1' (structure)
0:74 true case is null
0:76 Test condition and select (void)
0:76 Condition
0:76 Constant:
0:76 true (const bool)
0:76 true case is null
0:? Linker Objects
0:? 'a' (const 2X2 matrix of float)
0:? 1.000000
0:? 0.000000
0:? 0.000000
0:? 1.000000
0:? 'b' (2X2 matrix of float)
0:? 'c' (const 2X2 matrix of float)
0:? 1.000000
0:? 0.000000
0:? 0.000000
0:? 1.000000
0:? 'a2' (2-element array of float)
0:? 'b2' (2-component vector of float)
0:? 'c2' (3X3 matrix of float)
0:? 'd' (2X2 matrix of float)
0:? 'e' (structure)
0:? 'e2' (structure)
0:? 'e3' (structure)
0:? 'a3' (int)
0:? 'b3' (2-element array of 4-component vector of float)
0:? 'b4' (2-element array of 4-component vector of float)
0:? 'c3' (4X2 matrix of float)
0:? 'd2' (unsized array of structure)
0:? 'b5' (5-element array of float)
0:? 'constructed' (const structure)
0:? 1 (const uint)
0:? 2 (const uint)
0:? 3.000000
0:? 4.000000
0:? 0.000000
0:? 0.000000
0:? 0.000000
0:? 4.000000
0:? 0.000000
0:? 5.000000
0:? 6.000000
0:? 0.000000
0:? 0.000000
0:? 0.000000
0:? 6.000000
0:? 0.000000
0:? 'curlybad1' (structure)
0:? 'curlyInit' (const structure)
0:? 1 (const uint)
0:? 2 (const uint)
0:? 3.000000
0:? 4.000000
0:? 0.000000
0:? 0.000000
0:? 0.000000
0:? 4.000000
0:? 0.000000
0:? 5.000000
0:? 6.000000
0:? 0.000000
0:? 0.000000
0:? 0.000000
0:? 6.000000
0:? 0.000000
0:? 'vc1' (float)
0:? 'vc2' (float)
0:? 'vc3' (float)
0:? 'av3' (3-component vector of float)
0:? 'bv3' (3-component vector of float)
Test/baseResults/specExamples.frag.out
View file @
779e6b40
...
...
@@ -22,7 +22,7 @@ ERROR: 0:115: 'depth_greater' : unrecognized layout identifier
ERROR: 0:118: 'depth_less' : unrecognized layout identifier
ERROR: 0:121: 'depth_unchanged' : unrecognized layout identifier
ERROR: 0:150: 'constructor' : constructing from a non-dereferenced array
ERROR: 0:152: '
=' : cannot convert from 'const 2-element array of 4-component vector of float' to '3-element array of
4-component vector of float'
ERROR: 0:152: '
constructor' : cannot convert parameter 1 from 'const 2-element array of 4-component vector of float' to '
4-component vector of float'
ERROR: 0:172: 'x' : undeclared identifier
ERROR: 0:172: '[]' : scalar integer expression required
ERROR: 0:172: 'length' : illegal vector field selection
...
...
@@ -202,6 +202,12 @@ ERROR: node is still EOpNull!
0:175 0.000000
0:178 Constant:
0:178 0.000000
0:193 Sequence
0:193 move second child to first child (structure)
0:193 'e' (structure)
0:193 Constant:
0:193 1.200000
0:193 2 (const int)
0:216 Sequence
0:216 Sequence
0:216 move second child to first child (5-element array of float)
...
...
@@ -212,10 +218,23 @@ ERROR: node is still EOpNull!
0:216 5.000000
0:216 5.200000
0:216 1.100000
0:217 Sequence
0:217 move second child to first child (5-element array of float)
0:217 'b' (5-element array of float)
0:217 Constant:
0:217 3.400000
0:217 4.200000
0:217 5.000000
0:217 5.200000
0:217 1.100000
0:218 Sequence
0:218 move second child to first child (5-element array of float)
0:218 'c' (5-element array of float)
0:218 'a' (5-element array of float)
0:219 Sequence
0:219 move second child to first child (5-element array of float)
0:219 'd' (5-element array of float)
0:219 'b' (5-element array of float)
0:? Linker Objects
0:? 'a' (int)
0:? 'b' (int)
...
...
Test/baseResults/specExamples.vert.out
View file @
779e6b40
...
...
@@ -231,7 +231,19 @@ ERROR: node is still EOpNull!
0:188 'g' (float)
0:188 Constant:
0:188 2.000000
0:? Sequence
0:191 Sequence
0:191 Sequence
0:191 move second child to first child (2-element array of 4-component vector of float)
0:191 'b' (2-element array of 4-component vector of float)
0:191 Constant:
0:191 1.000000
0:191 1.000000
0:191 1.000000
0:191 1.000000
0:191 1.000000
0:191 1.000000
0:191 1.000000
0:191 1.000000
0:192 Construct vec4 (3-element array of 4-component vector of float)
0:193 Construct vec4 (3-element array of 4-component vector of float)
0:194 Construct vec4 (3-element array of 4-component vector of float)
...
...
glslang/Include/ConstantUnion.h
View file @
779e6b40
...
...
@@ -456,6 +456,9 @@ public:
if
(
!
unionArray
||
!
rhs
.
unionArray
)
return
false
;
if
(
!
unionArray
||
!
rhs
.
unionArray
)
return
false
;
return
*
unionArray
==
*
rhs
.
unionArray
;
}
bool
operator
!=
(
const
TConstUnionArray
&
rhs
)
const
{
return
!
operator
==
(
rhs
);
}
...
...
glslang/MachineIndependent/ParseHelper.cpp
View file @
779e6b40
...
...
@@ -805,7 +805,7 @@ TIntermTyped* TParseContext::handleFunctionCall(TSourceLoc loc, TFunction* fnCal
//
// It's a constructor, of type 'type'.
//
result
=
addConstructor
(
intermNode
,
type
,
op
,
fnCall
,
loc
);
result
=
addConstructor
(
loc
,
intermNode
,
type
,
op
);
if
(
result
==
0
)
error
(
loc
,
"cannot construct with these arguments"
,
type
.
getCompleteString
().
c_str
(),
""
);
}
...
...
@@ -2256,7 +2256,20 @@ TIntermNode* TParseContext::executeInitializer(TSourceLoc loc, TString& identifi
return
0
;
}
// Fix arrayness if variable is unsized, getting size for initializer
//
// If the initializer was from braces { ... }, we convert the whole subtree to a
// constructor-style subtree, allowing the rest of the code to operate
// identically for both kinds of initializers.
//
initializer
=
convertInitializerList
(
loc
,
variable
->
getType
(),
initializer
);
if
(
!
initializer
)
{
// error recovery; don't leave const without constant values
if
(
qualifier
==
EvqConst
)
variable
->
getWritableType
().
getQualifier
().
storage
=
EvqTemporary
;
return
0
;
}
// Fix arrayness if variable is unsized, getting size from the initializer
if
(
initializer
->
getType
().
isArray
()
&&
initializer
->
getType
().
getArraySize
()
>
0
&&
variable
->
getType
().
isArray
()
&&
variable
->
getType
().
getArraySize
()
==
0
)
variable
->
getWritableType
().
changeArraySize
(
initializer
->
getType
().
getArraySize
());
...
...
@@ -2303,12 +2316,89 @@ TIntermNode* TParseContext::executeInitializer(TSourceLoc loc, TString& identifi
return
0
;
}
//
// Reprocess any initalizer-list { ... } parts of the initializer.
// Need to heirarchically assign correct types and implicit
// conversions. Will do this mimicking the same process used for
// creating a constructor-style initializer, ensuring we get the
// same form.
//
TIntermTyped
*
TParseContext
::
convertInitializerList
(
TSourceLoc
loc
,
const
TType
&
type
,
TIntermTyped
*
initializer
)
{
// Will operate recursively. Once a subtree is found that is constructor style,
// everything below it is already good: Only the "top part" of the initializer
// can be an initializer list, where "top part" can extend for several (or all) levels.
// see if we have bottomed out in the tree within the initializer-list part
TIntermAggregate
*
initList
=
initializer
->
getAsAggregate
();
if
(
!
initList
||
initList
->
getOp
()
!=
EOpNull
)
return
initializer
;
// Of the initializer-list set of nodes, need to process bottom up,
// so recurse deep, then process on the way up.
// Go down the tree here...
if
(
type
.
isArray
())
{
// The type's array might be unsized, which could be okay, so base sizes on the size of the aggregate.
// Later on, initializer execution code will deal with array size logic.
TType
arrayType
;
arrayType
.
shallowCopy
(
type
);
arrayType
.
setArraySizes
(
type
);
arrayType
.
changeArraySize
(
initList
->
getSequence
().
size
());
TType
elementType
;
elementType
.
shallowCopy
(
arrayType
);
// TODO: arrays of arrays: combine this with deref.
elementType
.
dereference
();
for
(
size_t
i
=
0
;
i
<
initList
->
getSequence
().
size
();
++
i
)
{
initList
->
getSequence
()[
i
]
=
convertInitializerList
(
loc
,
elementType
,
initList
->
getSequence
()[
i
]
->
getAsTyped
());
if
(
initList
->
getSequence
()[
i
]
==
0
)
return
0
;
}
return
addConstructor
(
loc
,
initList
,
arrayType
,
mapTypeToConstructorOp
(
arrayType
));
}
else
if
(
type
.
getStruct
())
{
if
(
type
.
getStruct
()
->
size
()
!=
initList
->
getSequence
().
size
())
{
error
(
loc
,
"wrong number of structure members"
,
"initializer list"
,
""
);
return
0
;
}
for
(
size_t
i
=
0
;
i
<
type
.
getStruct
()
->
size
();
++
i
)
{
initList
->
getSequence
()[
i
]
=
convertInitializerList
(
loc
,
*
(
*
type
.
getStruct
())[
i
].
type
,
initList
->
getSequence
()[
i
]
->
getAsTyped
());
if
(
initList
->
getSequence
()[
i
]
==
0
)
return
0
;
}
}
else
if
(
type
.
isMatrix
())
{
if
(
type
.
getMatrixCols
()
!=
initList
->
getSequence
().
size
())
{
error
(
loc
,
"wrong number of matrix columns:"
,
"initializer list"
,
type
.
getCompleteString
().
c_str
());
return
0
;
}
TType
vectorType
;
vectorType
.
shallowCopy
(
type
);
// TODO: arrays of arrays: combine this with deref.
vectorType
.
dereference
();
for
(
int
i
=
0
;
i
<
type
.
getMatrixCols
();
++
i
)
{
initList
->
getSequence
()[
i
]
=
convertInitializerList
(
loc
,
vectorType
,
initList
->
getSequence
()[
i
]
->
getAsTyped
());
if
(
initList
->
getSequence
()[
i
]
==
0
)
return
0
;
}
}
else
if
(
type
.
isVector
())
{
if
(
type
.
getVectorSize
()
!=
initList
->
getSequence
().
size
())
{
error
(
loc
,
"wrong vector size (or rows in a matrix column):"
,
"initializer list"
,
type
.
getCompleteString
().
c_str
());
return
0
;
}
}
else
{
error
(
loc
,
"unexpected initializer-list type:"
,
"initializer list"
,
type
.
getCompleteString
().
c_str
());
return
0
;
}
// now that the subtree is processed, process this node
return
addConstructor
(
loc
,
initList
,
type
,
mapTypeToConstructorOp
(
type
));
}
//
// Test for the correctness of the parameters passed to various constructor functions
// and also convert them to the right data
type if it is
allowed and required.
// and also convert them to the right data
type, if
allowed and required.
//
// Returns 0 for an error or the constructed node (aggregate or typed) for no error.
//
TIntermTyped
*
TParseContext
::
addConstructor
(
T
IntermNode
*
node
,
const
TType
&
type
,
TOperator
op
,
TFunction
*
fnCall
,
TSourceLoc
loc
)
TIntermTyped
*
TParseContext
::
addConstructor
(
T
SourceLoc
loc
,
TIntermNode
*
node
,
const
TType
&
type
,
TOperator
op
)
{
if
(
node
==
0
)
return
0
;
...
...
@@ -2485,7 +2575,7 @@ TIntermTyped* TParseContext::constructStruct(TIntermNode* node, const TType& typ
TIntermTyped
*
converted
=
intermediate
.
addConversion
(
EOpConstructStruct
,
type
,
node
->
getAsTyped
());
if
(
!
converted
||
converted
->
getType
()
!=
type
)
{
error
(
loc
,
""
,
"constructor"
,
"cannot convert parameter %d from '%s' to '%s'"
,
paramCount
,
node
->
getAsTyped
()
->
getType
().
getComplete
TypeString
().
c_str
(),
type
.
getCompleteTyp
eString
().
c_str
());
node
->
getAsTyped
()
->
getType
().
getComplete
String
().
c_str
(),
type
.
getComplet
eString
().
c_str
());
return
0
;
}
...
...
glslang/MachineIndependent/ParseHelper.h
View file @
779e6b40
...
...
@@ -129,7 +129,7 @@ public:
const
TFunction
*
findFunction
(
TSourceLoc
,
TFunction
*
pfnCall
,
bool
*
builtIn
=
0
);
TIntermNode
*
declareVariable
(
TSourceLoc
,
TString
&
identifier
,
TPublicType
&
,
TArraySizes
*
typeArray
=
0
,
TIntermTyped
*
initializer
=
0
);
TIntermTyped
*
addConstructor
(
T
IntermNode
*
,
const
TType
&
,
TOperator
,
TFunction
*
,
TSourceLoc
);
TIntermTyped
*
addConstructor
(
T
SourceLoc
,
TIntermNode
*
,
const
TType
&
,
TOperator
);
TIntermTyped
*
constructStruct
(
TIntermNode
*
,
const
TType
&
,
int
,
TSourceLoc
);
TIntermTyped
*
constructBuiltIn
(
const
TType
&
,
TOperator
,
TIntermNode
*
,
TSourceLoc
,
bool
subset
);
void
addBlock
(
TSourceLoc
,
TTypeList
&
typeList
,
const
TString
*
instanceName
=
0
,
TArraySizes
*
arraySizes
=
0
);
...
...
@@ -172,6 +172,7 @@ protected:
TVariable
*
declareNonArray
(
TSourceLoc
,
TString
&
identifier
,
TType
&
,
bool
&
newDeclaration
);
void
declareArray
(
TSourceLoc
,
TString
&
identifier
,
const
TType
&
,
TSymbol
*&
,
bool
&
newDeclaration
);
TIntermNode
*
executeInitializer
(
TSourceLoc
,
TString
&
identifier
,
TIntermTyped
*
initializer
,
TVariable
*
variable
);
TIntermTyped
*
convertInitializerList
(
TSourceLoc
,
const
TType
&
,
TIntermTyped
*
initializer
);
TOperator
mapTypeToConstructorOp
(
const
TType
&
);
void
finalize
();
...
...
glslang/MachineIndependent/glslang.y
View file @
779e6b40
...
...
@@ -2104,11 +2104,10 @@ initializer
initializer_list
: initializer {
$$ =
$1
;
$$ =
parseContext.intermediate.growAggregate(0, $1, $1->getLoc())
;
}
| initializer_list COMMA initializer {
// TODO: 4.2 functionality: implement the initializer list
$$ = $3;
$$ = parseContext.intermediate.growAggregate($1, $3);
}
;
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment