Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
W
wine-winehq
Project
Project
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
Registry
Registry
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
wine
wine-winehq
Commits
33c891e6
Commit
33c891e6
authored
Apr 25, 2008
by
Rob Shearman
Committed by
Alexandre Julliard
Apr 25, 2008
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
widl: Add support for string literals and wide-string literals in expressions.
parent
957dd4b1
Hide whitespace changes
Inline
Side-by-side
Showing
5 changed files
with
84 additions
and
58 deletions
+84
-58
expr.c
tools/widl/expr.c
+18
-0
parser.l
tools/widl/parser.l
+11
-4
parser.y
tools/widl/parser.y
+29
-24
widltypes.h
tools/widl/widltypes.h
+4
-4
write_msft.c
tools/widl/write_msft.c
+22
-26
No files found.
tools/widl/expr.c
View file @
33c891e6
...
...
@@ -411,6 +411,16 @@ static struct expression_type resolve_expression(const struct expr_loc *expr_loc
result
.
is_temporary
=
FALSE
;
result
.
type
=
find_type
(
"int"
,
0
);
break
;
case
EXPR_STRLIT
:
result
.
is_variable
=
FALSE
;
result
.
is_temporary
=
TRUE
;
result
.
type
=
make_type
(
RPC_FC_RP
,
find_type
(
"char"
,
0
));
break
;
case
EXPR_WSTRLIT
:
result
.
is_variable
=
FALSE
;
result
.
is_temporary
=
TRUE
;
result
.
type
=
make_type
(
RPC_FC_RP
,
find_type
(
"wchar_t"
,
0
));
break
;
case
EXPR_DOUBLE
:
result
.
is_variable
=
FALSE
;
result
.
is_temporary
=
FALSE
;
...
...
@@ -596,6 +606,12 @@ void write_expr(FILE *h, const expr_t *e, int brackets,
}
fprintf
(
h
,
"%s"
,
e
->
u
.
sval
);
break
;
case
EXPR_STRLIT
:
fprintf
(
h
,
"
\"
%s
\"
"
,
e
->
u
.
sval
);
break
;
case
EXPR_WSTRLIT
:
fprintf
(
h
,
"L
\"
%s
\"
"
,
e
->
u
.
sval
);
break
;
case
EXPR_LOGNOT
:
fprintf
(
h
,
"!"
);
write_expr
(
h
,
e
->
ref
,
1
,
toplevel
,
toplevel_prefix
,
cont_type
);
...
...
@@ -743,6 +759,8 @@ int compare_expr(const expr_t *a, const expr_t *b)
case
EXPR_DOUBLE
:
return
a
->
u
.
dval
-
b
->
u
.
dval
;
case
EXPR_IDENTIFIER
:
case
EXPR_STRLIT
:
case
EXPR_WSTRLIT
:
return
strcmp
(
a
->
u
.
sval
,
b
->
u
.
sval
);
case
EXPR_COND
:
ret
=
compare_expr
(
a
->
ref
,
b
->
ref
);
...
...
tools/widl/parser.l
View file @
33c891e6
...
...
@@ -32,6 +32,7 @@ uuid {hexd}{8}-{hexd}{4}-{hexd}{4}-{hexd}{4}-{hexd}{12}
double [0-9]+\.[0-9]+([eE][+-]?[0-9]+)*
%x QUOTE
%x WSTRQUOTE
%x ATTR
%x PP_LINE
...
...
@@ -133,10 +134,16 @@ UUID *parse_uuid(const char *u)
parser_lval.str = get_buffered_cstring();
return aSTRING;
}
<QUOTE>\\\\ |
<QUOTE>\\\" addcchar(yytext[1]);
<QUOTE>\\. addcchar('\\'); addcchar(yytext[1]);
<QUOTE>. addcchar(yytext[0]);
<INITIAL,ATTR>L\" yy_push_state(WSTRQUOTE);
<WSTRQUOTE>\" {
yy_pop_state();
parser_lval.str = get_buffered_cstring();
return aWSTRING;
}
<QUOTE,WSTRQUOTE>\\\\ |
<QUOTE,WSTRQUOTE>\\\" addcchar(yytext[1]);
<QUOTE,WSTRQUOTE>\\. addcchar('\\'); addcchar(yytext[1]);
<QUOTE,WSTRQUOTE>. addcchar(yytext[0]);
<INITIAL,ATTR>\[ yy_push_state(ATTR); return '[';
<ATTR>\] yy_pop_state(); return ']';
<ATTR>{cident} return attr_token(yytext);
...
...
tools/widl/parser.y
View file @
33c891e6
...
...
@@ -187,7 +187,7 @@ static statement_list_t *append_statement(statement_list_t *list, statement_t *s
%token <str> aKNOWNTYPE
%token <num> aNUM aHEXNUM
%token <dbl> aDOUBLE
%token <str> aSTRING
%token <str> aSTRING
aWSTRING
%token <uuid> aUUID
%token aEOF
%token SHL SHR
...
...
@@ -278,8 +278,8 @@ static statement_list_t *append_statement(statement_list_t *list, statement_t *s
%type <attr> attribute
%type <attr_list> m_attributes attributes attrib_list
%type <str_list> str_list
%type <expr> m_expr expr expr_const array
%type <expr_list> m_exprs /* exprs expr_list */ expr_list_const
%type <expr> m_expr expr expr_const
expr_int_const
array
%type <expr_list> m_exprs /* exprs expr_list */ expr_list_
int_
const
%type <ifinfo> interfacehdr
%type <type> inherit interface interfacedef interfacedec
%type <type> dispinterface dispinterfacehdr dispinterfacedef
...
...
@@ -492,31 +492,29 @@ attribute: { $$ = NULL; }
| tBINDABLE { $$ = make_attr(ATTR_BINDABLE); }
| tBROADCAST { $$ = make_attr(ATTR_BROADCAST); }
| tCALLAS '(' ident ')' { $$ = make_attrp(ATTR_CALLAS, $3); }
| tCASE '(' expr_list_
const ')'
{ $$ = make_attrp(ATTR_CASE, $3); }
| tCASE '(' expr_list_
int_const ')'
{ $$ = make_attrp(ATTR_CASE, $3); }
| tCONTEXTHANDLE { $$ = make_attrv(ATTR_CONTEXTHANDLE, 0); }
| tCONTEXTHANDLENOSERIALIZE { $$ = make_attrv(ATTR_CONTEXTHANDLE, 0); /* RPC_CONTEXT_HANDLE_DONT_SERIALIZE */ }
| tCONTEXTHANDLESERIALIZE { $$ = make_attrv(ATTR_CONTEXTHANDLE, 0); /* RPC_CONTEXT_HANDLE_SERIALIZE */ }
| tCONTROL { $$ = make_attr(ATTR_CONTROL); }
| tDEFAULT { $$ = make_attr(ATTR_DEFAULT); }
| tDEFAULTCOLLELEM { $$ = make_attr(ATTR_DEFAULTCOLLELEM); }
| tDEFAULTVALUE '(' expr_const ')' { $$ = make_attrp(ATTR_DEFAULTVALUE_EXPR, $3); }
| tDEFAULTVALUE '(' aSTRING ')' { $$ = make_attrp(ATTR_DEFAULTVALUE_STRING, $3); }
| tDEFAULTVALUE '(' expr_const ')' { $$ = make_attrp(ATTR_DEFAULTVALUE, $3); }
| tDEFAULTVTABLE { $$ = make_attr(ATTR_DEFAULTVTABLE); }
| tDISPLAYBIND { $$ = make_attr(ATTR_DISPLAYBIND); }
| tDLLNAME '(' aSTRING ')' { $$ = make_attrp(ATTR_DLLNAME, $3); }
| tDUAL { $$ = make_attr(ATTR_DUAL); }
| tENDPOINT '(' str_list ')' { $$ = make_attrp(ATTR_ENDPOINT, $3); }
| tENTRY '(' aSTRING ')' { $$ = make_attrp(ATTR_ENTRY_STRING, $3); }
| tENTRY '(' expr_const ')' { $$ = make_attrp(ATTR_ENTRY_ORDINAL, $3); }
| tENTRY '(' expr_const ')' { $$ = make_attrp(ATTR_ENTRY, $3); }
| tEXPLICITHANDLE { $$ = make_attr(ATTR_EXPLICIT_HANDLE); }
| tHANDLE { $$ = make_attr(ATTR_HANDLE); }
| tHELPCONTEXT '(' expr_const ')' { $$ = make_attrp(ATTR_HELPCONTEXT, $3); }
| tHELPCONTEXT '(' expr_
int_
const ')' { $$ = make_attrp(ATTR_HELPCONTEXT, $3); }
| tHELPFILE '(' aSTRING ')' { $$ = make_attrp(ATTR_HELPFILE, $3); }
| tHELPSTRING '(' aSTRING ')' { $$ = make_attrp(ATTR_HELPSTRING, $3); }
| tHELPSTRINGCONTEXT '(' expr_const ')' { $$ = make_attrp(ATTR_HELPSTRINGCONTEXT, $3); }
| tHELPSTRINGCONTEXT '(' expr_
int_
const ')' { $$ = make_attrp(ATTR_HELPSTRINGCONTEXT, $3); }
| tHELPSTRINGDLL '(' aSTRING ')' { $$ = make_attrp(ATTR_HELPSTRINGDLL, $3); }
| tHIDDEN { $$ = make_attr(ATTR_HIDDEN); }
| tID '(' expr_const ')' { $$ = make_attrp(ATTR_ID, $3); }
| tID '(' expr_
int_
const ')' { $$ = make_attrp(ATTR_ID, $3); }
| tIDEMPOTENT { $$ = make_attr(ATTR_IDEMPOTENT); }
| tIIDIS '(' expr ')' { $$ = make_attrp(ATTR_IIDIS, $3); }
| tIMMEDIATEBIND { $$ = make_attr(ATTR_IMMEDIATEBIND); }
...
...
@@ -524,7 +522,7 @@ attribute: { $$ = NULL; }
| tIN { $$ = make_attr(ATTR_IN); }
| tINPUTSYNC { $$ = make_attr(ATTR_INPUTSYNC); }
| tLENGTHIS '(' m_exprs ')' { $$ = make_attrp(ATTR_LENGTHIS, $3); }
| tLCID '(' expr_const ')' { $$ = make_attrp(ATTR_LIBLCID, $3); }
| tLCID '(' expr_
int_
const ')' { $$ = make_attrp(ATTR_LIBLCID, $3); }
| tLOCAL { $$ = make_attr(ATTR_LOCAL); }
| tNONBROWSABLE { $$ = make_attr(ATTR_NONBROWSABLE); }
| tNONCREATABLE { $$ = make_attr(ATTR_NONCREATABLE); }
...
...
@@ -539,9 +537,10 @@ attribute: { $$ = NULL; }
| tPROPPUT { $$ = make_attr(ATTR_PROPPUT); }
| tPROPPUTREF { $$ = make_attr(ATTR_PROPPUTREF); }
| tPUBLIC { $$ = make_attr(ATTR_PUBLIC); }
| tRANGE '(' expr_const ',' expr_const ')' { expr_list_t *list = append_expr( NULL, $3 );
list = append_expr( list, $5 );
$$ = make_attrp(ATTR_RANGE, list); }
| tRANGE '(' expr_int_const ',' expr_int_const ')'
{ expr_list_t *list = append_expr( NULL, $3 );
list = append_expr( list, $5 );
$$ = make_attrp(ATTR_RANGE, list); }
| tREADONLY { $$ = make_attr(ATTR_READONLY); }
| tREQUESTEDIT { $$ = make_attr(ATTR_REQUESTEDIT); }
| tRESTRICTED { $$ = make_attr(ATTR_RESTRICTED); }
...
...
@@ -578,7 +577,7 @@ cases: { $$ = NULL; }
| cases case { $$ = append_var( $1, $2 ); }
;
case: tCASE expr_const ':' union_field { attr_t *a = make_attrp(ATTR_CASE, append_expr( NULL, $2 ));
case: tCASE expr_
int_
const ':' union_field { attr_t *a = make_attrp(ATTR_CASE, append_expr( NULL, $2 ));
$$ = $4; if (!$$) $$ = make_var(NULL);
$$->attrs = append_attr( $$->attrs, a );
}
...
...
@@ -612,7 +611,7 @@ enum_list: enum { if (!$1->eval)
}
;
enum: ident '=' expr_
const
{ $$ = reg_const($1);
enum: ident '=' expr_
int_const
{ $$ = reg_const($1);
$$->eval = $3;
$$->type = make_int(0);
}
...
...
@@ -654,6 +653,8 @@ expr: aNUM { $$ = make_exprl(EXPR_NUM, $1); }
| tFALSE { $$ = make_exprl(EXPR_TRUEFALSE, 0); }
| tNULL { $$ = make_exprl(EXPR_NUM, 0); }
| tTRUE { $$ = make_exprl(EXPR_TRUEFALSE, 1); }
| aSTRING { $$ = make_exprs(EXPR_STRLIT, $1); }
| aWSTRING { $$ = make_exprs(EXPR_WSTRLIT, $1); }
| aIDENTIFIER { $$ = make_exprs(EXPR_IDENTIFIER, $1); }
| expr '?' expr ':' expr { $$ = make_expr3(EXPR_COND, $1, $3, $5); }
| expr LOGICALOR expr { $$ = make_expr2(EXPR_LOGOR, $1, $3); }
...
...
@@ -688,12 +689,18 @@ expr: aNUM { $$ = make_exprl(EXPR_NUM, $1); }
| '(' expr ')' { $$ = $2; }
;
expr_list_
const: expr_const
{ $$ = append_expr( NULL, $1 ); }
| expr_list_
const ',' expr_const
{ $$ = append_expr( $1, $3 ); }
expr_list_
int_const: expr_int_const
{ $$ = append_expr( NULL, $1 ); }
| expr_list_
int_const ',' expr_int_const
{ $$ = append_expr( $1, $3 ); }
;
expr_const: expr { $$ = $1;
expr_
int_
const: expr { $$ = $1;
if (!$$->is_const)
error_loc("expression is not an integer constant\n");
}
;
expr_const: expr { $$ = $1;
if (!$$->is_const && $$->type != EXPR_STRLIT && $$->type != EXPR_WSTRLIT)
error_loc("expression is not constant\n");
}
;
...
...
@@ -2047,16 +2054,14 @@ struct allowed_attr allowed_attr[] =
/* ATTR_CONTROL */ { 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, "control" },
/* ATTR_DEFAULT */ { 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, "default" },
/* ATTR_DEFAULTCOLLELEM */ { 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, "defaultcollelem" },
/* ATTR_DEFAULTVALUE_EXPR */ { 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, "defaultvalue" },
/* ATTR_DEFAULTVALUE_STRING */ { 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, "defaultvalue" },
/* ATTR_DEFAULTVALUE */ { 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, "defaultvalue" },
/* ATTR_DEFAULTVTABLE */ { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, "defaultvtable" },
/* ATTR_DISPINTERFACE */ { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, NULL },
/* ATTR_DISPLAYBIND */ { 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, "displaybind" },
/* ATTR_DLLNAME */ { 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, "dllname" },
/* ATTR_DUAL */ { 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, "dual" },
/* ATTR_ENDPOINT */ { 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, "endpoint" },
/* ATTR_ENTRY_ORDINAL */ { 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, "entry" },
/* ATTR_ENTRY_STRING */ { 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, "entry" },
/* ATTR_ENTRY */ { 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, "entry" },
/* ATTR_EXPLICIT_HANDLE */ { 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, "explicit_handle" },
/* ATTR_HANDLE */ { 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, "handle" },
/* ATTR_HELPCONTEXT */ { 0, 0, 1, 1, 0, 1, 0, 1, 1, 1, 1, "helpcontext" },
...
...
tools/widl/widltypes.h
View file @
33c891e6
...
...
@@ -82,16 +82,14 @@ enum attr_type
ATTR_CONTROL
,
ATTR_DEFAULT
,
ATTR_DEFAULTCOLLELEM
,
ATTR_DEFAULTVALUE_EXPR
,
ATTR_DEFAULTVALUE_STRING
,
ATTR_DEFAULTVALUE
,
ATTR_DEFAULTVTABLE
,
ATTR_DISPINTERFACE
,
ATTR_DISPLAYBIND
,
ATTR_DLLNAME
,
ATTR_DUAL
,
ATTR_ENDPOINT
,
ATTR_ENTRY_ORDINAL
,
ATTR_ENTRY_STRING
,
ATTR_ENTRY
,
ATTR_EXPLICIT_HANDLE
,
ATTR_HANDLE
,
ATTR_HELPCONTEXT
,
...
...
@@ -180,6 +178,8 @@ enum expr_type
EXPR_LESSEQL
,
EXPR_LOGNOT
,
EXPR_POS
,
EXPR_STRLIT
,
EXPR_WSTRLIT
,
};
enum
type_kind
...
...
tools/widl/write_msft.c
View file @
33c891e6
...
...
@@ -1183,7 +1183,7 @@ static unsigned long get_ulong_val(unsigned long val, int vt)
return
val
;
}
static
void
write_value
(
msft_typelib_t
*
typelib
,
int
*
out
,
int
vt
,
void
*
value
)
static
void
write_value
(
msft_typelib_t
*
typelib
,
int
*
out
,
int
vt
,
const
void
*
value
)
{
switch
(
vt
)
{
case
VT_I2
:
...
...
@@ -1199,7 +1199,7 @@ static void write_value(msft_typelib_t* typelib, int *out, int vt, void *value)
case
VT_HRESULT
:
case
VT_PTR
:
{
const
unsigned
long
lv
=
get_ulong_val
(
*
(
unsigned
long
*
)
value
,
vt
);
const
unsigned
long
lv
=
get_ulong_val
(
*
(
const
unsigned
long
*
)
value
,
vt
);
if
((
lv
&
0x3ffffff
)
==
lv
)
{
*
out
=
0x80000000
;
*
out
|=
vt
<<
26
;
...
...
@@ -1215,7 +1215,7 @@ static void write_value(msft_typelib_t* typelib, int *out, int vt, void *value)
}
case
VT_BSTR
:
{
c
har
*
s
=
(
char
*
)
value
;
c
onst
char
*
s
=
(
const
char
*
)
value
;
int
len
=
strlen
(
s
),
seg_len
=
(
len
+
6
+
3
)
&
~
0x3
;
int
offset
=
ctl2_alloc_segment
(
typelib
,
MSFT_SEG_CUSTDATA
,
seg_len
,
0
);
*
((
unsigned
short
*
)
&
typelib
->
typelib_segment_data
[
MSFT_SEG_CUSTDATA
][
offset
])
=
vt
;
...
...
@@ -1305,7 +1305,7 @@ static HRESULT add_func_desc(msft_typeinfo_t* typeinfo, const func_t *func, int
{
num_params
++
;
if
(
arg
->
attrs
)
LIST_FOR_EACH_ENTRY
(
attr
,
arg
->
attrs
,
const
attr_t
,
entry
)
{
if
(
attr
->
type
==
ATTR_DEFAULTVALUE
_EXPR
||
attr
->
type
==
ATTR_DEFAULTVALUE_STRING
)
if
(
attr
->
type
==
ATTR_DEFAULTVALUE
)
num_defaults
++
;
else
if
(
attr
->
type
==
ATTR_OPTIONAL
)
num_optional
++
;
...
...
@@ -1329,14 +1329,14 @@ static HRESULT add_func_desc(msft_typeinfo_t* typeinfo, const func_t *func, int
case
ATTR_DISPLAYBIND
:
funcflags
|=
0x10
;
/* FUNCFLAG_FDISPLAYBIND */
break
;
case
ATTR_ENTRY
_ORDINAL
:
case
ATTR_ENTRY
:
extra_attr
=
max
(
extra_attr
,
3
);
entry
=
expr
->
cval
;
entry_is_ord
=
1
;
break
;
case
ATTR_ENTRY_STRING
:
extra_attr
=
max
(
extra_attr
,
3
)
;
entry
=
ctl2_alloc_string
(
typeinfo
->
typelib
,
attr
->
u
.
pval
);
if
(
expr
->
type
==
EXPR_STRLIT
||
expr
->
type
==
EXPR_WSTRLIT
)
entry
=
ctl2_alloc_string
(
typeinfo
->
typelib
,
attr
->
u
.
pval
)
;
else
{
entry
=
expr
->
cval
;
entry_is_ord
=
1
;
}
break
;
case
ATTR_HELPCONTEXT
:
extra_attr
=
max
(
extra_attr
,
1
);
...
...
@@ -1477,7 +1477,7 @@ static HRESULT add_func_desc(msft_typeinfo_t* typeinfo, const func_t *func, int
encode_var
(
typeinfo
->
typelib
,
arg
->
type
,
arg
,
paramdata
,
NULL
,
NULL
,
&
decoded_size
);
if
(
arg
->
attrs
)
LIST_FOR_EACH_ENTRY
(
attr
,
arg
->
attrs
,
const
attr_t
,
entry
)
{
switch
(
attr
->
type
)
{
case
ATTR_DEFAULTVALUE
_EXPR
:
case
ATTR_DEFAULTVALUE
:
{
int
vt
;
expr_t
*
expr
=
(
expr_t
*
)
attr
->
u
.
pval
;
...
...
@@ -1486,21 +1486,17 @@ static HRESULT add_func_desc(msft_typeinfo_t* typeinfo, const func_t *func, int
else
vt
=
get_type_vt
(
arg
->
type
);
paramflags
|=
0x30
;
/* PARAMFLAG_FHASDEFAULT | PARAMFLAG_FOPT */
chat
(
"default value %ld
\n
"
,
expr
->
cval
);
write_value
(
typeinfo
->
typelib
,
defaultdata
,
vt
,
&
expr
->
cval
);
break
;
}
case
ATTR_DEFAULTVALUE_STRING
:
{
char
*
s
=
(
char
*
)
attr
->
u
.
pval
;
int
vt
;
if
(
arg
->
type
->
type
==
RPC_FC_ENUM16
)
vt
=
VT_INT
;
if
(
expr
->
type
==
EXPR_STRLIT
||
expr
->
type
==
EXPR_WSTRLIT
)
{
if
(
vt
!=
VT_BSTR
)
error
(
"string default value applied to non-string type
\n
"
);
chat
(
"default value '%s'
\n
"
,
expr
->
u
.
sval
);
write_value
(
typeinfo
->
typelib
,
defaultdata
,
vt
,
expr
->
u
.
sval
);
}
else
vt
=
get_type_vt
(
arg
->
type
);
paramflags
|=
0x30
;
/* PARAMFLAG_FHASDEFAULT | PARAMFLAG_FOPT */
chat
(
"default value '%s'
\n
"
,
s
);
write_value
(
typeinfo
->
typelib
,
defaultdata
,
vt
,
s
);
{
chat
(
"default value %ld
\n
"
,
expr
->
cval
);
write_value
(
typeinfo
->
typelib
,
defaultdata
,
vt
,
&
expr
->
cval
);
}
break
;
}
case
ATTR_IN
:
...
...
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