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
71e04d62
Commit
71e04d62
authored
Dec 06, 2015
by
John Kessenich
Browse files
Options
Browse Files
Download
Plain Diff
Merge pull request #100 from mawww/scanner-optim
Scanner optimisations
parents
27657498
6998987e
Hide whitespace changes
Inline
Side-by-side
Showing
2 changed files
with
80 additions
and
57 deletions
+80
-57
Scan.cpp
glslang/MachineIndependent/Scan.cpp
+27
-4
PpScanner.cpp
glslang/MachineIndependent/preprocessor/PpScanner.cpp
+53
-53
No files found.
glslang/MachineIndependent/Scan.cpp
View file @
71e04d62
...
@@ -293,10 +293,33 @@ int yylex(YYSTYPE* glslangTokenDesc, glslang::TParseContext& parseContext)
...
@@ -293,10 +293,33 @@ int yylex(YYSTYPE* glslangTokenDesc, glslang::TParseContext& parseContext)
namespace
{
namespace
{
struct
str_eq
{
bool
operator
()(
const
char
*
lhs
,
const
char
*
rhs
)
const
{
return
strcmp
(
lhs
,
rhs
)
==
0
;
}
};
struct
str_hash
{
size_t
operator
()(
const
char
*
str
)
const
{
// djb2
unsigned
long
hash
=
5381
;
int
c
;
while
(
c
=
*
str
++
)
hash
=
((
hash
<<
5
)
+
hash
)
+
c
;
return
hash
;
}
};
// A single global usable by all threads, by all versions, by all languages.
// A single global usable by all threads, by all versions, by all languages.
// After a single process-level initialization, this is read only and thread safe
// After a single process-level initialization, this is read only and thread safe
std
::
unordered_map
<
std
::
string
,
int
>*
KeywordMap
=
nullptr
;
std
::
unordered_map
<
const
char
*
,
int
,
str_hash
,
str_eq
>*
KeywordMap
=
nullptr
;
std
::
unordered_set
<
std
::
string
>*
ReservedSet
=
nullptr
;
std
::
unordered_set
<
const
char
*
,
str_hash
,
str_eq
>*
ReservedSet
=
nullptr
;
};
};
...
@@ -309,7 +332,7 @@ void TScanContext::fillInKeywordMap()
...
@@ -309,7 +332,7 @@ void TScanContext::fillInKeywordMap()
// but, the only risk is if two threads called simultaneously
// but, the only risk is if two threads called simultaneously
return
;
return
;
}
}
KeywordMap
=
new
std
::
unordered_map
<
std
::
string
,
int
>
;
KeywordMap
=
new
std
::
unordered_map
<
const
char
*
,
int
,
str_hash
,
str_eq
>
;
(
*
KeywordMap
)[
"const"
]
=
CONST
;
(
*
KeywordMap
)[
"const"
]
=
CONST
;
(
*
KeywordMap
)[
"uniform"
]
=
UNIFORM
;
(
*
KeywordMap
)[
"uniform"
]
=
UNIFORM
;
...
@@ -481,7 +504,7 @@ void TScanContext::fillInKeywordMap()
...
@@ -481,7 +504,7 @@ void TScanContext::fillInKeywordMap()
(
*
KeywordMap
)[
"resource"
]
=
RESOURCE
;
(
*
KeywordMap
)[
"resource"
]
=
RESOURCE
;
(
*
KeywordMap
)[
"superp"
]
=
SUPERP
;
(
*
KeywordMap
)[
"superp"
]
=
SUPERP
;
ReservedSet
=
new
std
::
unordered_set
<
std
::
string
>
;
ReservedSet
=
new
std
::
unordered_set
<
const
char
*
,
str_hash
,
str_eq
>
;
ReservedSet
->
insert
(
"common"
);
ReservedSet
->
insert
(
"common"
);
ReservedSet
->
insert
(
"partition"
);
ReservedSet
->
insert
(
"partition"
);
...
...
glslang/MachineIndependent/preprocessor/PpScanner.cpp
View file @
71e04d62
...
@@ -242,11 +242,11 @@ int TPpContext::tStringInput::scan(TPpToken* ppToken)
...
@@ -242,11 +242,11 @@ int TPpContext::tStringInput::scan(TPpToken* ppToken)
ppToken
->
ival
=
0
;
ppToken
->
ival
=
0
;
ppToken
->
space
=
false
;
ppToken
->
space
=
false
;
ch
=
pp
->
getChar
();
ch
=
getch
();
for
(;;)
{
for
(;;)
{
while
(
ch
==
' '
||
ch
==
'\t'
)
{
while
(
ch
==
' '
||
ch
==
'\t'
)
{
ppToken
->
space
=
true
;
ppToken
->
space
=
true
;
ch
=
pp
->
getChar
();
ch
=
getch
();
}
}
ppToken
->
loc
=
pp
->
parseContext
.
getCurrentLoc
();
ppToken
->
loc
=
pp
->
parseContext
.
getCurrentLoc
();
...
@@ -271,13 +271,13 @@ int TPpContext::tStringInput::scan(TPpToken* ppToken)
...
@@ -271,13 +271,13 @@ int TPpContext::tStringInput::scan(TPpToken* ppToken)
do
{
do
{
if
(
len
<
MaxTokenLength
)
{
if
(
len
<
MaxTokenLength
)
{
tokenText
[
len
++
]
=
(
char
)
ch
;
tokenText
[
len
++
]
=
(
char
)
ch
;
ch
=
pp
->
getChar
();
ch
=
getch
();
}
else
{
}
else
{
if
(
!
AlreadyComplained
)
{
if
(
!
AlreadyComplained
)
{
pp
->
parseContext
.
ppError
(
ppToken
->
loc
,
"name too long"
,
""
,
""
);
pp
->
parseContext
.
ppError
(
ppToken
->
loc
,
"name too long"
,
""
,
""
);
AlreadyComplained
=
1
;
AlreadyComplained
=
1
;
}
}
ch
=
pp
->
getChar
();
ch
=
getch
();
}
}
}
while
((
ch
>=
'a'
&&
ch
<=
'z'
)
||
}
while
((
ch
>=
'a'
&&
ch
<=
'z'
)
||
(
ch
>=
'A'
&&
ch
<=
'Z'
)
||
(
ch
>=
'A'
&&
ch
<=
'Z'
)
||
...
@@ -289,18 +289,18 @@ int TPpContext::tStringInput::scan(TPpToken* ppToken)
...
@@ -289,18 +289,18 @@ int TPpContext::tStringInput::scan(TPpToken* ppToken)
continue
;
continue
;
tokenText
[
len
]
=
'\0'
;
tokenText
[
len
]
=
'\0'
;
pp
->
ungetChar
();
ungetch
();
ppToken
->
atom
=
pp
->
LookUpAddString
(
tokenText
);
ppToken
->
atom
=
pp
->
LookUpAddString
(
tokenText
);
return
PpAtomIdentifier
;
return
PpAtomIdentifier
;
case
'0'
:
case
'0'
:
ppToken
->
name
[
len
++
]
=
(
char
)
ch
;
ppToken
->
name
[
len
++
]
=
(
char
)
ch
;
ch
=
pp
->
getChar
();
ch
=
getch
();
if
(
ch
==
'x'
||
ch
==
'X'
)
{
if
(
ch
==
'x'
||
ch
==
'X'
)
{
// must be hexidecimal
// must be hexidecimal
bool
isUnsigned
=
false
;
bool
isUnsigned
=
false
;
ppToken
->
name
[
len
++
]
=
(
char
)
ch
;
ppToken
->
name
[
len
++
]
=
(
char
)
ch
;
ch
=
pp
->
getChar
();
ch
=
getch
();
if
((
ch
>=
'0'
&&
ch
<=
'9'
)
||
if
((
ch
>=
'0'
&&
ch
<=
'9'
)
||
(
ch
>=
'A'
&&
ch
<=
'F'
)
||
(
ch
>=
'A'
&&
ch
<=
'F'
)
||
(
ch
>=
'a'
&&
ch
<=
'f'
))
{
(
ch
>=
'a'
&&
ch
<=
'f'
))
{
...
@@ -325,7 +325,7 @@ int TPpContext::tStringInput::scan(TPpToken* ppToken)
...
@@ -325,7 +325,7 @@ int TPpContext::tStringInput::scan(TPpToken* ppToken)
}
}
ival
=
0xffffffff
;
ival
=
0xffffffff
;
}
}
ch
=
pp
->
getChar
();
ch
=
getch
();
}
while
((
ch
>=
'0'
&&
ch
<=
'9'
)
||
}
while
((
ch
>=
'0'
&&
ch
<=
'9'
)
||
(
ch
>=
'A'
&&
ch
<=
'F'
)
||
(
ch
>=
'A'
&&
ch
<=
'F'
)
||
(
ch
>=
'a'
&&
ch
<=
'f'
));
(
ch
>=
'a'
&&
ch
<=
'f'
));
...
@@ -337,7 +337,7 @@ int TPpContext::tStringInput::scan(TPpToken* ppToken)
...
@@ -337,7 +337,7 @@ int TPpContext::tStringInput::scan(TPpToken* ppToken)
ppToken
->
name
[
len
++
]
=
(
char
)
ch
;
ppToken
->
name
[
len
++
]
=
(
char
)
ch
;
isUnsigned
=
true
;
isUnsigned
=
true
;
}
else
}
else
pp
->
ungetChar
();
ungetch
();
ppToken
->
name
[
len
]
=
'\0'
;
ppToken
->
name
[
len
]
=
'\0'
;
ppToken
->
ival
=
(
int
)
ival
;
ppToken
->
ival
=
(
int
)
ival
;
...
@@ -366,7 +366,7 @@ int TPpContext::tStringInput::scan(TPpToken* ppToken)
...
@@ -366,7 +366,7 @@ int TPpContext::tStringInput::scan(TPpToken* ppToken)
ival
=
(
ival
<<
3
)
|
ii
;
ival
=
(
ival
<<
3
)
|
ii
;
}
else
}
else
octalOverflow
=
true
;
octalOverflow
=
true
;
ch
=
pp
->
getChar
();
ch
=
getch
();
}
}
// could be part of a float...
// could be part of a float...
...
@@ -379,7 +379,7 @@ int TPpContext::tStringInput::scan(TPpToken* ppToken)
...
@@ -379,7 +379,7 @@ int TPpContext::tStringInput::scan(TPpToken* ppToken)
pp
->
parseContext
.
ppError
(
ppToken
->
loc
,
"numeric literal too long"
,
""
,
""
);
pp
->
parseContext
.
ppError
(
ppToken
->
loc
,
"numeric literal too long"
,
""
,
""
);
AlreadyComplained
=
1
;
AlreadyComplained
=
1
;
}
}
ch
=
pp
->
getChar
();
ch
=
getch
();
}
while
(
ch
>=
'0'
&&
ch
<=
'9'
);
}
while
(
ch
>=
'0'
&&
ch
<=
'9'
);
}
}
if
(
ch
==
'.'
||
ch
==
'e'
||
ch
==
'f'
||
ch
==
'E'
||
ch
==
'F'
||
ch
==
'l'
||
ch
==
'L'
)
if
(
ch
==
'.'
||
ch
==
'e'
||
ch
==
'f'
||
ch
==
'E'
||
ch
==
'F'
||
ch
==
'l'
||
ch
==
'L'
)
...
@@ -394,7 +394,7 @@ int TPpContext::tStringInput::scan(TPpToken* ppToken)
...
@@ -394,7 +394,7 @@ int TPpContext::tStringInput::scan(TPpToken* ppToken)
ppToken
->
name
[
len
++
]
=
(
char
)
ch
;
ppToken
->
name
[
len
++
]
=
(
char
)
ch
;
isUnsigned
=
true
;
isUnsigned
=
true
;
}
else
}
else
pp
->
ungetChar
();
ungetch
();
ppToken
->
name
[
len
]
=
'\0'
;
ppToken
->
name
[
len
]
=
'\0'
;
if
(
octalOverflow
)
if
(
octalOverflow
)
...
@@ -419,7 +419,7 @@ int TPpContext::tStringInput::scan(TPpToken* ppToken)
...
@@ -419,7 +419,7 @@ int TPpContext::tStringInput::scan(TPpToken* ppToken)
pp
->
parseContext
.
ppError
(
ppToken
->
loc
,
"numeric literal too long"
,
""
,
""
);
pp
->
parseContext
.
ppError
(
ppToken
->
loc
,
"numeric literal too long"
,
""
,
""
);
AlreadyComplained
=
1
;
AlreadyComplained
=
1
;
}
}
ch
=
pp
->
getChar
();
ch
=
getch
();
}
while
(
ch
>=
'0'
&&
ch
<=
'9'
);
}
while
(
ch
>=
'0'
&&
ch
<=
'9'
);
if
(
ch
==
'.'
||
ch
==
'e'
||
ch
==
'f'
||
ch
==
'E'
||
ch
==
'F'
||
ch
==
'l'
||
ch
==
'L'
)
{
if
(
ch
==
'.'
||
ch
==
'e'
||
ch
==
'f'
||
ch
==
'E'
||
ch
==
'F'
||
ch
==
'l'
||
ch
==
'L'
)
{
return
pp
->
lFloatConst
(
len
,
ch
,
ppToken
);
return
pp
->
lFloatConst
(
len
,
ch
,
ppToken
);
...
@@ -432,7 +432,7 @@ int TPpContext::tStringInput::scan(TPpToken* ppToken)
...
@@ -432,7 +432,7 @@ int TPpContext::tStringInput::scan(TPpToken* ppToken)
ppToken
->
name
[
len
++
]
=
(
char
)
ch
;
ppToken
->
name
[
len
++
]
=
(
char
)
ch
;
uint
=
true
;
uint
=
true
;
}
else
}
else
pp
->
ungetChar
();
ungetch
();
ppToken
->
name
[
len
]
=
'\0'
;
ppToken
->
name
[
len
]
=
'\0'
;
ival
=
0
;
ival
=
0
;
...
@@ -456,153 +456,153 @@ int TPpContext::tStringInput::scan(TPpToken* ppToken)
...
@@ -456,153 +456,153 @@ int TPpContext::tStringInput::scan(TPpToken* ppToken)
}
}
break
;
break
;
case
'-'
:
case
'-'
:
ch
=
pp
->
getChar
();
ch
=
getch
();
if
(
ch
==
'-'
)
{
if
(
ch
==
'-'
)
{
return
PpAtomDecrement
;
return
PpAtomDecrement
;
}
else
if
(
ch
==
'='
)
{
}
else
if
(
ch
==
'='
)
{
return
PpAtomSub
;
return
PpAtomSub
;
}
else
{
}
else
{
pp
->
ungetChar
();
ungetch
();
return
'-'
;
return
'-'
;
}
}
case
'+'
:
case
'+'
:
ch
=
pp
->
getChar
();
ch
=
getch
();
if
(
ch
==
'+'
)
{
if
(
ch
==
'+'
)
{
return
PpAtomIncrement
;
return
PpAtomIncrement
;
}
else
if
(
ch
==
'='
)
{
}
else
if
(
ch
==
'='
)
{
return
PpAtomAdd
;
return
PpAtomAdd
;
}
else
{
}
else
{
pp
->
ungetChar
();
ungetch
();
return
'+'
;
return
'+'
;
}
}
case
'*'
:
case
'*'
:
ch
=
pp
->
getChar
();
ch
=
getch
();
if
(
ch
==
'='
)
{
if
(
ch
==
'='
)
{
return
PpAtomMul
;
return
PpAtomMul
;
}
else
{
}
else
{
pp
->
ungetChar
();
ungetch
();
return
'*'
;
return
'*'
;
}
}
case
'%'
:
case
'%'
:
ch
=
pp
->
getChar
();
ch
=
getch
();
if
(
ch
==
'='
)
{
if
(
ch
==
'='
)
{
return
PpAtomMod
;
return
PpAtomMod
;
}
else
{
}
else
{
pp
->
ungetChar
();
ungetch
();
return
'%'
;
return
'%'
;
}
}
case
'^'
:
case
'^'
:
ch
=
pp
->
getChar
();
ch
=
getch
();
if
(
ch
==
'^'
)
{
if
(
ch
==
'^'
)
{
return
PpAtomXor
;
return
PpAtomXor
;
}
else
{
}
else
{
if
(
ch
==
'='
)
if
(
ch
==
'='
)
return
PpAtomXorAssign
;
return
PpAtomXorAssign
;
else
{
else
{
pp
->
ungetChar
();
ungetch
();
return
'^'
;
return
'^'
;
}
}
}
}
case
'='
:
case
'='
:
ch
=
pp
->
getChar
();
ch
=
getch
();
if
(
ch
==
'='
)
{
if
(
ch
==
'='
)
{
return
PpAtomEQ
;
return
PpAtomEQ
;
}
else
{
}
else
{
pp
->
ungetChar
();
ungetch
();
return
'='
;
return
'='
;
}
}
case
'!'
:
case
'!'
:
ch
=
pp
->
getChar
();
ch
=
getch
();
if
(
ch
==
'='
)
{
if
(
ch
==
'='
)
{
return
PpAtomNE
;
return
PpAtomNE
;
}
else
{
}
else
{
pp
->
ungetChar
();
ungetch
();
return
'!'
;
return
'!'
;
}
}
case
'|'
:
case
'|'
:
ch
=
pp
->
getChar
();
ch
=
getch
();
if
(
ch
==
'|'
)
{
if
(
ch
==
'|'
)
{
return
PpAtomOr
;
return
PpAtomOr
;
}
else
if
(
ch
==
'='
)
{
}
else
if
(
ch
==
'='
)
{
return
PpAtomOrAssign
;
return
PpAtomOrAssign
;
}
else
{
}
else
{
pp
->
ungetChar
();
ungetch
();
return
'|'
;
return
'|'
;
}
}
case
'&'
:
case
'&'
:
ch
=
pp
->
getChar
();
ch
=
getch
();
if
(
ch
==
'&'
)
{
if
(
ch
==
'&'
)
{
return
PpAtomAnd
;
return
PpAtomAnd
;
}
else
if
(
ch
==
'='
)
{
}
else
if
(
ch
==
'='
)
{
return
PpAtomAndAssign
;
return
PpAtomAndAssign
;
}
else
{
}
else
{
pp
->
ungetChar
();
ungetch
();
return
'&'
;
return
'&'
;
}
}
case
'<'
:
case
'<'
:
ch
=
pp
->
getChar
();
ch
=
getch
();
if
(
ch
==
'<'
)
{
if
(
ch
==
'<'
)
{
ch
=
pp
->
getChar
();
ch
=
getch
();
if
(
ch
==
'='
)
if
(
ch
==
'='
)
return
PpAtomLeftAssign
;
return
PpAtomLeftAssign
;
else
{
else
{
pp
->
ungetChar
();
ungetch
();
return
PpAtomLeft
;
return
PpAtomLeft
;
}
}
}
else
if
(
ch
==
'='
)
{
}
else
if
(
ch
==
'='
)
{
return
PpAtomLE
;
return
PpAtomLE
;
}
else
{
}
else
{
pp
->
ungetChar
();
ungetch
();
return
'<'
;
return
'<'
;
}
}
case
'>'
:
case
'>'
:
ch
=
pp
->
getChar
();
ch
=
getch
();
if
(
ch
==
'>'
)
{
if
(
ch
==
'>'
)
{
ch
=
pp
->
getChar
();
ch
=
getch
();
if
(
ch
==
'='
)
if
(
ch
==
'='
)
return
PpAtomRightAssign
;
return
PpAtomRightAssign
;
else
{
else
{
pp
->
ungetChar
();
ungetch
();
return
PpAtomRight
;
return
PpAtomRight
;
}
}
}
else
if
(
ch
==
'='
)
{
}
else
if
(
ch
==
'='
)
{
return
PpAtomGE
;
return
PpAtomGE
;
}
else
{
}
else
{
pp
->
ungetChar
();
ungetch
();
return
'>'
;
return
'>'
;
}
}
case
'.'
:
case
'.'
:
ch
=
pp
->
getChar
();
ch
=
getch
();
if
(
ch
>=
'0'
&&
ch
<=
'9'
)
{
if
(
ch
>=
'0'
&&
ch
<=
'9'
)
{
pp
->
ungetChar
();
ungetch
();
return
pp
->
lFloatConst
(
0
,
'.'
,
ppToken
);
return
pp
->
lFloatConst
(
0
,
'.'
,
ppToken
);
}
else
{
}
else
{
pp
->
ungetChar
();
ungetch
();
return
'.'
;
return
'.'
;
}
}
case
'/'
:
case
'/'
:
ch
=
pp
->
getChar
();
ch
=
getch
();
if
(
ch
==
'/'
)
{
if
(
ch
==
'/'
)
{
pp
->
inComment
=
true
;
pp
->
inComment
=
true
;
do
{
do
{
ch
=
pp
->
getChar
();
ch
=
getch
();
}
while
(
ch
!=
'\n'
&&
ch
!=
EndOfInput
);
}
while
(
ch
!=
'\n'
&&
ch
!=
EndOfInput
);
ppToken
->
space
=
true
;
ppToken
->
space
=
true
;
pp
->
inComment
=
false
;
pp
->
inComment
=
false
;
return
ch
;
return
ch
;
}
else
if
(
ch
==
'*'
)
{
}
else
if
(
ch
==
'*'
)
{
ch
=
pp
->
getChar
();
ch
=
getch
();
do
{
do
{
while
(
ch
!=
'*'
)
{
while
(
ch
!=
'*'
)
{
if
(
ch
==
EndOfInput
)
{
if
(
ch
==
EndOfInput
)
{
pp
->
parseContext
.
ppError
(
ppToken
->
loc
,
"End of input in comment"
,
"comment"
,
""
);
pp
->
parseContext
.
ppError
(
ppToken
->
loc
,
"End of input in comment"
,
"comment"
,
""
);
return
ch
;
return
ch
;
}
}
ch
=
pp
->
getChar
();
ch
=
getch
();
}
}
ch
=
pp
->
getChar
();
ch
=
getch
();
if
(
ch
==
EndOfInput
)
{
if
(
ch
==
EndOfInput
)
{
pp
->
parseContext
.
ppError
(
ppToken
->
loc
,
"End of input in comment"
,
"comment"
,
""
);
pp
->
parseContext
.
ppError
(
ppToken
->
loc
,
"End of input in comment"
,
"comment"
,
""
);
return
ch
;
return
ch
;
...
@@ -614,29 +614,29 @@ int TPpContext::tStringInput::scan(TPpToken* ppToken)
...
@@ -614,29 +614,29 @@ int TPpContext::tStringInput::scan(TPpToken* ppToken)
}
else
if
(
ch
==
'='
)
{
}
else
if
(
ch
==
'='
)
{
return
PpAtomDiv
;
return
PpAtomDiv
;
}
else
{
}
else
{
pp
->
ungetChar
();
ungetch
();
return
'/'
;
return
'/'
;
}
}
break
;
break
;
case
'"'
:
case
'"'
:
ch
=
pp
->
getChar
();
ch
=
getch
();
while
(
ch
!=
'"'
&&
ch
!=
'\n'
&&
ch
!=
EndOfInput
)
{
while
(
ch
!=
'"'
&&
ch
!=
'\n'
&&
ch
!=
EndOfInput
)
{
if
(
len
<
MaxTokenLength
)
{
if
(
len
<
MaxTokenLength
)
{
tokenText
[
len
]
=
(
char
)
ch
;
tokenText
[
len
]
=
(
char
)
ch
;
len
++
;
len
++
;
ch
=
pp
->
getChar
();
ch
=
getch
();
}
else
}
else
break
;
break
;
};
};
tokenText
[
len
]
=
'\0'
;
tokenText
[
len
]
=
'\0'
;
if
(
ch
!=
'"'
)
{
if
(
ch
!=
'"'
)
{
pp
->
ungetChar
();
ungetch
();
pp
->
parseContext
.
ppError
(
ppToken
->
loc
,
"End of line in string"
,
"string"
,
""
);
pp
->
parseContext
.
ppError
(
ppToken
->
loc
,
"End of line in string"
,
"string"
,
""
);
}
}
return
PpAtomConstString
;
return
PpAtomConstString
;
}
}
ch
=
pp
->
getChar
();
ch
=
getch
();
}
}
}
}
...
...
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