Support for enums, arrays, encapsulated unions, signed/unsigned
qualifiers, UUIDs, include guards, the new ICOM_DEFINE1 macro, and some other improvements.
This commit is contained in:
parent
dec35a3575
commit
03c7d46806
|
@ -53,13 +53,13 @@ int is_void(type_t *t, var_t *v)
|
|||
return 0;
|
||||
}
|
||||
|
||||
static void write_pident(var_t *v)
|
||||
static void write_pident(FILE *h, var_t *v)
|
||||
{
|
||||
int c;
|
||||
for (c=0; c<v->ptr_level; c++) {
|
||||
fprintf(header, "*");
|
||||
fprintf(h, "*");
|
||||
}
|
||||
if (v->name) fprintf(header, "%s", v->name);
|
||||
if (v->name) fprintf(h, "%s", v->name);
|
||||
}
|
||||
|
||||
void write_name(FILE *h, var_t *v)
|
||||
|
@ -72,22 +72,66 @@ char* get_name(var_t *v)
|
|||
return v->name;
|
||||
}
|
||||
|
||||
static void write_array(FILE *h, expr_t *v)
|
||||
{
|
||||
if (!v) return;
|
||||
while (NEXT_LINK(v)) v = NEXT_LINK(v);
|
||||
fprintf(h, "[");
|
||||
while (v) {
|
||||
if (v->type == EXPR_NUM)
|
||||
fprintf(h, "%ld", v->u.lval); /* statically sized array */
|
||||
else
|
||||
fprintf(h, "1"); /* dynamically sized array */
|
||||
if (PREV_LINK(v))
|
||||
fprintf(h, ", ");
|
||||
v = PREV_LINK(v);
|
||||
}
|
||||
fprintf(h, "]");
|
||||
}
|
||||
|
||||
static void write_field(FILE *h, var_t *v)
|
||||
{
|
||||
if (!v) return;
|
||||
if (v->type) {
|
||||
indent(0);
|
||||
write_type(h, v->type, NULL, v->tname);
|
||||
if (get_name(v)) {
|
||||
fprintf(h, " ");
|
||||
write_pident(h, v);
|
||||
}
|
||||
write_array(h, v->array);
|
||||
fprintf(h, ";\n");
|
||||
}
|
||||
}
|
||||
|
||||
static void write_fields(FILE *h, var_t *v)
|
||||
{
|
||||
var_t *first = v;
|
||||
if (!v) return;
|
||||
while (NEXT_LINK(v)) v = NEXT_LINK(v);
|
||||
while (v) {
|
||||
write_field(h, v);
|
||||
if (v == first) break;
|
||||
v = PREV_LINK(v);
|
||||
}
|
||||
}
|
||||
|
||||
static void write_enums(FILE *h, var_t *v)
|
||||
{
|
||||
if (!v) return;
|
||||
while (NEXT_LINK(v)) v = NEXT_LINK(v);
|
||||
while (v) {
|
||||
if (v->type) {
|
||||
if (get_name(v)) {
|
||||
indent(0);
|
||||
write_type(h, v->type, NULL, v->tname);
|
||||
if (get_name(v)) {
|
||||
fprintf(header, " ");
|
||||
write_pident(v);
|
||||
}
|
||||
fprintf(header, ";\n");
|
||||
write_name(h, v);
|
||||
if (v->has_val)
|
||||
fprintf(h, " = %ld", v->lval);
|
||||
}
|
||||
if (PREV_LINK(v))
|
||||
fprintf(h, ",\n");
|
||||
v = PREV_LINK(v);
|
||||
}
|
||||
fprintf(h, "\n");
|
||||
}
|
||||
|
||||
void write_type(FILE *h, type_t *t, var_t *v, char *n)
|
||||
|
@ -98,25 +142,52 @@ void write_type(FILE *h, type_t *t, var_t *v, char *n)
|
|||
else {
|
||||
if (t->is_const) fprintf(h, "const ");
|
||||
if (t->type) {
|
||||
if (t->sign > 0) fprintf(h, "signed ");
|
||||
else if (t->sign < 0) fprintf(h, "unsigned ");
|
||||
switch (t->type) {
|
||||
case RPC_FC_BYTE:
|
||||
fprintf(h, "byte");
|
||||
if (t->ref) fprintf(h, t->ref->name);
|
||||
else fprintf(h, "byte");
|
||||
break;
|
||||
case RPC_FC_CHAR:
|
||||
fprintf(h, "char");
|
||||
if (t->ref) fprintf(h, t->ref->name);
|
||||
else fprintf(h, "char");
|
||||
break;
|
||||
case RPC_FC_WCHAR:
|
||||
fprintf(h, "wchar_t");
|
||||
break;
|
||||
case RPC_FC_USHORT:
|
||||
fprintf(h, "unsigned ");
|
||||
case RPC_FC_SHORT:
|
||||
if (t->ref) fprintf(h, t->ref->name);
|
||||
fprintf(h, "short");
|
||||
else fprintf(h, "short");
|
||||
break;
|
||||
case RPC_FC_ULONG:
|
||||
fprintf(h, "unsigned ");
|
||||
case RPC_FC_LONG:
|
||||
if (t->ref) fprintf(h, t->ref->name);
|
||||
else fprintf(h, "long");
|
||||
break;
|
||||
case RPC_FC_HYPER:
|
||||
if (t->ref) fprintf(h, t->ref->name);
|
||||
else fprintf(h, "__int64");
|
||||
break;
|
||||
case RPC_FC_FLOAT:
|
||||
fprintf(h, "float");
|
||||
break;
|
||||
case RPC_FC_DOUBLE:
|
||||
fprintf(h, "double");
|
||||
break;
|
||||
case RPC_FC_ENUM16:
|
||||
case RPC_FC_ENUM32:
|
||||
if (t->defined && !t->written) {
|
||||
if (t->name) fprintf(h, "enum %s {\n", t->name);
|
||||
else fprintf(h, "enum {\n");
|
||||
indentation++;
|
||||
write_enums(h, t->fields);
|
||||
indent(-1);
|
||||
fprintf(h, "}");
|
||||
}
|
||||
else fprintf(h, "enum %s", t->name);
|
||||
break;
|
||||
case RPC_FC_STRUCT:
|
||||
if (t->defined && !t->written) {
|
||||
if (t->name) fprintf(h, "struct %s {\n", t->name);
|
||||
|
@ -128,6 +199,24 @@ void write_type(FILE *h, type_t *t, var_t *v, char *n)
|
|||
}
|
||||
else fprintf(h, "struct %s", t->name);
|
||||
break;
|
||||
case RPC_FC_ENCAPSULATED_UNION:
|
||||
if (t->defined && !t->written) {
|
||||
var_t *d = t->fields;
|
||||
if (t->name) fprintf(h, "struct %s {\n", t->name);
|
||||
else fprintf(h, "struct {\n");
|
||||
indentation++;
|
||||
write_field(h, d);
|
||||
indent(0);
|
||||
fprintf(h, "union {\n");
|
||||
indentation++;
|
||||
write_fields(h, NEXT_LINK(d));
|
||||
indent(-1);
|
||||
fprintf(h, "} u;\n");
|
||||
indent(-1);
|
||||
fprintf(h, "}");
|
||||
}
|
||||
else fprintf(h, "struct %s", t->name);
|
||||
break;
|
||||
case RPC_FC_NON_ENCAPSULATED_UNION:
|
||||
if (t->defined && !t->written) {
|
||||
if (t->name) fprintf(h, "union %s {\n", t->name);
|
||||
|
@ -165,16 +254,25 @@ void write_typedef(type_t *type, var_t *names)
|
|||
write_type(header, type, NULL, tname);
|
||||
fprintf(header, " ");
|
||||
while (names) {
|
||||
write_pident(names);
|
||||
write_pident(header, names);
|
||||
if (PREV_LINK(names))
|
||||
fprintf(header, ", ");
|
||||
names = PREV_LINK(names);
|
||||
}
|
||||
fprintf(header, ";\n");
|
||||
fprintf(header, ";\n\n");
|
||||
}
|
||||
|
||||
/********** INTERFACES **********/
|
||||
|
||||
UUID *get_uuid(attr_t *a)
|
||||
{
|
||||
while (a) {
|
||||
if (a->type == ATTR_UUID) return a->u.pval;
|
||||
a = NEXT_LINK(a);
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
int is_object(attr_t *a)
|
||||
{
|
||||
while (a) {
|
||||
|
@ -364,12 +462,21 @@ static void write_method_proto(type_t *iface)
|
|||
|
||||
void write_forward(type_t *iface)
|
||||
{
|
||||
if (!iface->written) {
|
||||
if (is_object(iface->attrs) && !iface->written) {
|
||||
fprintf(header, "typedef struct %s %s;\n", iface->name, iface->name);
|
||||
iface->written = TRUE;
|
||||
}
|
||||
}
|
||||
|
||||
void write_guid(type_t *iface)
|
||||
{
|
||||
UUID *uuid = get_uuid(iface->attrs);
|
||||
if (!uuid) return;
|
||||
fprintf(header, "DEFINE_GUID(IID_%s, 0x%08lx, 0x%04x, 0x%04x, 0x%02x,0x%02x, 0x%02x,0x%02x,0x%02x,0x%02x,0x%02x,0x%02x);\n",
|
||||
iface->name, uuid->Data1, uuid->Data2, uuid->Data3, uuid->Data4[0], uuid->Data4[1],
|
||||
uuid->Data4[2], uuid->Data4[3], uuid->Data4[4], uuid->Data4[5], uuid->Data4[6], uuid->Data4[7]);
|
||||
}
|
||||
|
||||
void write_interface(type_t *iface)
|
||||
{
|
||||
if (!is_object(iface->attrs)) {
|
||||
|
@ -386,18 +493,19 @@ void write_interface(type_t *iface)
|
|||
fprintf(header, "/*****************************************************************************\n");
|
||||
fprintf(header, " * %s interface\n", iface->name);
|
||||
fprintf(header, " */\n");
|
||||
write_guid(iface);
|
||||
write_forward(iface);
|
||||
if (iface->ref)
|
||||
fprintf(header, "#define ICOM_INTERFACE %s\n", iface->name);
|
||||
fprintf(header, "#define ICOM_INTERFACE %s\n", iface->name);
|
||||
write_method_def(iface);
|
||||
fprintf(header, "#define %s_IMETHODS \\\n", iface->name);
|
||||
if (iface->ref)
|
||||
fprintf(header, " %s_IMETHODS \\\n", iface->ref->name);
|
||||
fprintf(header, " %s_METHODS \\\n", iface->name);
|
||||
if (iface->ref) {
|
||||
if (iface->ref)
|
||||
fprintf(header, "ICOM_DEFINE(%s,%s)\n", iface->name, iface->ref->name);
|
||||
fprintf(header, "#undef ICOM_INTERFACE\n");
|
||||
}
|
||||
else
|
||||
fprintf(header, "ICOM_DEFINE1(%s)\n", iface->name);
|
||||
fprintf(header, "#undef ICOM_INTERFACE\n");
|
||||
fprintf(header, "\n");
|
||||
write_method_macro(iface, iface->name);
|
||||
fprintf(header, "\n");
|
||||
|
|
|
@ -75,6 +75,26 @@ int import_stack_ptr = 0;
|
|||
|
||||
static void pop_import(void);
|
||||
|
||||
static UUID* parse_uuid(const char*u)
|
||||
{
|
||||
UUID* uuid = xmalloc(sizeof(UUID));
|
||||
char b[3];
|
||||
/* it would be nice to use UuidFromStringA */
|
||||
uuid->Data1 = strtol(u, NULL, 16);
|
||||
uuid->Data2 = strtol(u+9, NULL, 16);
|
||||
uuid->Data3 = strtol(u+14, NULL, 16);
|
||||
b[2] = 0;
|
||||
memcpy(b, u+19, 2); uuid->Data4[0] = strtol(b, NULL, 16);
|
||||
memcpy(b, u+21, 2); uuid->Data4[1] = strtol(b, NULL, 16);
|
||||
memcpy(b, u+24, 2); uuid->Data4[2] = strtol(b, NULL, 16);
|
||||
memcpy(b, u+26, 2); uuid->Data4[3] = strtol(b, NULL, 16);
|
||||
memcpy(b, u+28, 2); uuid->Data4[4] = strtol(b, NULL, 16);
|
||||
memcpy(b, u+30, 2); uuid->Data4[5] = strtol(b, NULL, 16);
|
||||
memcpy(b, u+32, 2); uuid->Data4[6] = strtol(b, NULL, 16);
|
||||
memcpy(b, u+34, 2); uuid->Data4[7] = strtol(b, NULL, 16);
|
||||
return uuid;
|
||||
}
|
||||
|
||||
%}
|
||||
|
||||
/*
|
||||
|
@ -94,9 +114,15 @@ static void pop_import(void);
|
|||
<QUOTE>\\\" addcchar(yytext[1]);
|
||||
<QUOTE>\\. addcchar('\\'); addcchar(yytext[1]);
|
||||
<QUOTE>. addcchar(yytext[0]);
|
||||
{uuid} return aUUID;
|
||||
{hex} return aNUM;
|
||||
{int} return aNUM;
|
||||
{uuid} {
|
||||
yylval.uuid = parse_uuid(yytext);
|
||||
return aUUID;
|
||||
}
|
||||
{hex} |
|
||||
{int} {
|
||||
yylval.num = strtol(yytext, NULL, 0);
|
||||
return aNUM;
|
||||
}
|
||||
{cident} return kw_token(yytext);
|
||||
\n
|
||||
{ws}
|
||||
|
|
|
@ -64,11 +64,16 @@
|
|||
static attr_t *make_attr(enum attr_type type);
|
||||
static attr_t *make_attrv(enum attr_type type, DWORD val);
|
||||
static attr_t *make_attrp(enum attr_type type, void *val);
|
||||
static expr_t *make_expr(enum expr_type type);
|
||||
static expr_t *make_exprl(enum expr_type type, long val);
|
||||
static expr_t *make_exprs(enum expr_type type, char *val);
|
||||
static expr_t *make_expr1(enum expr_type type, expr_t *expr);
|
||||
static expr_t *make_expr2(enum expr_type type, expr_t *exp1, expr_t *exp2);
|
||||
static type_t *make_type(BYTE type, type_t *ref);
|
||||
static typeref_t *make_tref(char *name, type_t *ref);
|
||||
static typeref_t *uniq_tref(typeref_t *ref);
|
||||
static type_t *type_ref(typeref_t *ref);
|
||||
static void set_type(var_t *v, typeref_t *ref);
|
||||
static void set_type(var_t *v, typeref_t *ref, expr_t *arr);
|
||||
static var_t *make_var(char *name);
|
||||
static func_t *make_func(var_t *def, var_t *args);
|
||||
|
||||
|
@ -83,16 +88,19 @@ static type_t *get_typev(BYTE type, var_t *name, int t);
|
|||
#define tsSTRUCT 2
|
||||
#define tsUNION 3
|
||||
|
||||
static type_t std_bool = { "boolean" };
|
||||
static type_t std_int = { "int" };
|
||||
|
||||
%}
|
||||
%union {
|
||||
attr_t *attr;
|
||||
expr_t *expr;
|
||||
type_t *type;
|
||||
typeref_t *tref;
|
||||
var_t *var;
|
||||
func_t *func;
|
||||
char *str;
|
||||
UUID *uuid;
|
||||
int num;
|
||||
}
|
||||
|
||||
|
@ -145,14 +153,15 @@ static type_t std_int = { "int" };
|
|||
%token tPOINTERTYPE
|
||||
|
||||
%type <attr> m_attributes attributes attrib_list attribute
|
||||
%type <type> inherit interface interfacedef
|
||||
%type <expr> aexprs aexpr_list aexpr array
|
||||
%type <type> inherit interface interfacedef lib_statements
|
||||
%type <type> base_type int_std
|
||||
%type <type> enumdef structdef typedef uniondef
|
||||
%type <tref> type
|
||||
%type <var> m_args no_args args arg
|
||||
%type <var> fields field
|
||||
%type <var> fields field s_field cases case enums enum_list enum constdef
|
||||
%type <var> m_ident t_ident ident p_ident pident pident_list
|
||||
%type <func> funcdef statements
|
||||
%type <func> funcdef int_statements
|
||||
%type <num> expr pointer_type
|
||||
|
||||
%left ','
|
||||
|
@ -167,22 +176,38 @@ static type_t std_int = { "int" };
|
|||
|
||||
%%
|
||||
|
||||
statements: { $$ = NULL; }
|
||||
| statements funcdef ';' { LINK($2, $1); $$ = $2; }
|
||||
| statements statement
|
||||
input: lib_statements { /* FIXME */ }
|
||||
;
|
||||
|
||||
lib_statements: { $$ = NULL; }
|
||||
| lib_statements import
|
||||
| lib_statements interface ';'
|
||||
| lib_statements interfacedef { LINK($2, $1); $$ = $2; }
|
||||
/* | lib_statements librarydef (when implemented) */
|
||||
| lib_statements statement
|
||||
;
|
||||
|
||||
/* we can't import from inside interfaces yet
|
||||
* (it's not entirely clear how Microsoft manages that yet,
|
||||
* but in MIDL you can derive a class from a base class and then
|
||||
* import the base class definition from inside the interface,
|
||||
* which I don't quite know how to pull off in yacc/bison yet) */
|
||||
int_statements: { $$ = NULL; }
|
||||
| int_statements funcdef ';' { LINK($2, $1); $$ = $2; }
|
||||
| int_statements statement
|
||||
;
|
||||
|
||||
statement: ';' {}
|
||||
| constdef {}
|
||||
| cppquote {}
|
||||
| enumdef ';' { if (!parse_only) { write_type(header, $1, NULL, NULL); fprintf(header, ";\n"); } }
|
||||
| enumdef ';' { if (!parse_only) { write_type(header, $1, NULL, NULL); fprintf(header, ";\n\n"); } }
|
||||
| externdef ';' {}
|
||||
| import {}
|
||||
| interface ';' {}
|
||||
| interfacedef {}
|
||||
| structdef ';' { if (!parse_only) { write_type(header, $1, NULL, NULL); fprintf(header, ";\n"); } }
|
||||
/* | import {} */
|
||||
/* | interface ';' {} */
|
||||
/* | interfacedef {} */
|
||||
| structdef ';' { if (!parse_only) { write_type(header, $1, NULL, NULL); fprintf(header, ";\n\n"); } }
|
||||
| typedef ';' {}
|
||||
| uniondef ';' { if (!parse_only) { write_type(header, $1, NULL, NULL); fprintf(header, ";\n"); } }
|
||||
| uniondef ';' { if (!parse_only) { write_type(header, $1, NULL, NULL); fprintf(header, ";\n\n"); } }
|
||||
;
|
||||
|
||||
cppquote: tCPPQUOTE '(' aSTRING ')' { if (!parse_only) fprintf(header, "%s\n", $3); }
|
||||
|
@ -204,40 +229,42 @@ args: arg
|
|||
|
||||
/* split into two rules to get bison to resolve a tVOID conflict */
|
||||
arg: attributes type pident array { $$ = $3;
|
||||
set_type($$, $2);
|
||||
$$->attrs = $1; /* FIXME: array */
|
||||
set_type($$, $2, $4);
|
||||
$$->attrs = $1;
|
||||
}
|
||||
| type pident array { $$ = $2;
|
||||
set_type($$, $1); /* FIXME: array */
|
||||
set_type($$, $1, $3);
|
||||
}
|
||||
;
|
||||
|
||||
aexprs:
|
||||
aexprs: { $$ = make_expr(EXPR_VOID); }
|
||||
| aexpr_list
|
||||
;
|
||||
|
||||
aexpr_list: aexpr
|
||||
| aexprs ',' aexpr
|
||||
| aexpr_list ',' aexpr { LINK($3, $1); $$ = $3; }
|
||||
;
|
||||
|
||||
aexpr: aNUM {}
|
||||
| aIDENTIFIER {}
|
||||
| aexpr '|' aexpr
|
||||
| aexpr '&' aexpr
|
||||
| aexpr '+' aexpr
|
||||
| aexpr '-' aexpr
|
||||
| aexpr '*' aexpr
|
||||
| aexpr '/' aexpr
|
||||
| '-' aexpr %prec NEG
|
||||
| '*' aexpr %prec PPTR
|
||||
| '(' type ')' aexpr %prec CAST
|
||||
| '(' aexpr ')'
|
||||
| tSIZEOF '(' type ')'
|
||||
aexpr: aNUM { $$ = make_exprl(EXPR_NUM, $1); }
|
||||
| aIDENTIFIER { $$ = make_exprs(EXPR_IDENTIFIER, $1); }
|
||||
| aexpr '|' aexpr { $$ = make_expr2(EXPR_OR , $1, $3); }
|
||||
| aexpr '&' aexpr { $$ = make_expr2(EXPR_AND, $1, $3); }
|
||||
| aexpr '+' aexpr { $$ = make_expr2(EXPR_ADD, $1, $3); }
|
||||
| aexpr '-' aexpr { $$ = make_expr2(EXPR_SUB, $1, $3); }
|
||||
| aexpr '*' aexpr { $$ = make_expr2(EXPR_MUL, $1, $3); }
|
||||
| aexpr '/' aexpr { $$ = make_expr2(EXPR_DIV, $1, $3); }
|
||||
| aexpr SHL aexpr { $$ = make_expr2(EXPR_SHL, $1, $3); }
|
||||
| aexpr SHR aexpr { $$ = make_expr2(EXPR_SHR, $1, $3); }
|
||||
| '-' aexpr %prec NEG { $$ = make_expr1(EXPR_NEG, $2); }
|
||||
| '*' aexpr %prec PPTR { $$ = make_expr1(EXPR_PPTR, $2); }
|
||||
| '(' type ')' aexpr %prec CAST { $$ = $4; /* FIXME */ free($2); }
|
||||
| '(' aexpr ')' { $$ = $2; }
|
||||
| tSIZEOF '(' type ')' { $$ = make_exprl(EXPR_NUM, 0); /* FIXME */ free($3); warning("can't do sizeof() yet\n"); }
|
||||
;
|
||||
|
||||
array:
|
||||
| '[' aexprs ']'
|
||||
| '[' '*' ']'
|
||||
array: { $$ = NULL; }
|
||||
| '[' aexprs ']' { $$ = $2; }
|
||||
| '[' '*' ']' { $$ = make_expr(EXPR_VOID); }
|
||||
;
|
||||
|
||||
m_attributes: { $$ = NULL; }
|
||||
|
@ -272,7 +299,7 @@ attribute:
|
|||
| tSTRING { $$ = make_attr(ATTR_STRING); }
|
||||
| tSWITCHIS '(' aexpr ')' { $$ = NULL; }
|
||||
| tSWITCHTYPE '(' type ')' { $$ = NULL; }
|
||||
| tUUID '(' aUUID ')' { $$ = NULL; }
|
||||
| tUUID '(' aUUID ')' { $$ = make_attrp(ATTR_UUID, $3); }
|
||||
| tV1ENUM { $$ = make_attr(ATTR_V1ENUM); }
|
||||
| tVERSION '(' version ')' { $$ = NULL; }
|
||||
| tWIREMARSHAL '(' type ')' { $$ = make_attrp(ATTR_WIREMARSHAL, type_ref($3)); }
|
||||
|
@ -283,31 +310,39 @@ callconv:
|
|||
| tSTDCALL
|
||||
;
|
||||
|
||||
cases:
|
||||
| cases case
|
||||
cases: { $$ = NULL; }
|
||||
| cases case { LINK($2, $1); $$ = $2; }
|
||||
;
|
||||
|
||||
case: tCASE expr ':' field
|
||||
| tDEFAULT ':' field
|
||||
case: tCASE expr ':' field { $$ = $4; }
|
||||
| tDEFAULT ':' field { $$ = $3; }
|
||||
;
|
||||
|
||||
constdef: tCONST type ident '=' expr
|
||||
constdef: tCONST type ident '=' expr { $$ = $3;
|
||||
set_type($$, $2, NULL);
|
||||
$$->has_val = TRUE;
|
||||
$$->lval = $5;
|
||||
}
|
||||
;
|
||||
|
||||
enums:
|
||||
| enum_list ','
|
||||
enums: { $$ = NULL; }
|
||||
| enum_list ',' { $$ = $1; }
|
||||
| enum_list
|
||||
;
|
||||
|
||||
enum_list: enum
|
||||
| enum_list ',' enum
|
||||
| enum_list ',' enum { LINK($3, $1); $$ = $3; }
|
||||
;
|
||||
|
||||
enum: ident '=' expr {}
|
||||
| ident {}
|
||||
enum: ident '=' expr { $$ = $1;
|
||||
$$->has_val = TRUE;
|
||||
$$->lval = $3;
|
||||
}
|
||||
| ident { $$ = $1; }
|
||||
;
|
||||
|
||||
enumdef: tENUM t_ident '{' enums '}' { $$ = get_typev(RPC_FC_SHORT /* FIXME */, $2, tsENUM);
|
||||
enumdef: tENUM t_ident '{' enums '}' { $$ = get_typev(RPC_FC_ENUM16, $2, tsENUM);
|
||||
$$->fields = $4;
|
||||
$$->defined = TRUE;
|
||||
}
|
||||
;
|
||||
|
@ -317,11 +352,12 @@ expr_list: expr {}
|
|||
;
|
||||
|
||||
expr: aNUM
|
||||
| aIDENTIFIER {}
|
||||
| expr '|' expr {}
|
||||
| expr SHL expr {}
|
||||
| expr SHR expr {}
|
||||
| '-' expr %prec NEG {}
|
||||
| aIDENTIFIER { $$ = 0; /* FIXME */ }
|
||||
| expr '|' expr { $$ = $1 | $3; }
|
||||
| expr '&' expr { $$ = $1 & $3; }
|
||||
| expr SHL expr { $$ = $1 << $3; }
|
||||
| expr SHR expr { $$ = $1 >> $3; }
|
||||
| '-' expr %prec NEG { $$ = -$2; }
|
||||
;
|
||||
|
||||
externdef: tEXTERN tCONST type ident
|
||||
|
@ -331,15 +367,18 @@ fields: { $$ = NULL; }
|
|||
| fields field { LINK($2, $1); $$ = $2; }
|
||||
;
|
||||
|
||||
field: m_attributes type pident array ';' { $$ = $3; set_type($$, $2); $$->attrs = $1; /* FIXME: array */ }
|
||||
field: s_field ';' { $$ = $1; }
|
||||
| m_attributes uniondef ';' { $$ = make_var(NULL); $$->type = $2; $$->attrs = $1; }
|
||||
| attributes ';' { $$ = make_var(NULL); $$->attrs = $1; }
|
||||
| ';' { $$ = NULL; }
|
||||
;
|
||||
|
||||
s_field: m_attributes type pident array { $$ = $3; set_type($$, $2, $4); $$->attrs = $1; }
|
||||
;
|
||||
|
||||
funcdef:
|
||||
m_attributes type callconv pident
|
||||
'(' m_args ')' { set_type($4, $2);
|
||||
'(' m_args ')' { set_type($4, $2, NULL);
|
||||
$4->attrs = $1;
|
||||
$$ = make_func($4, $6);
|
||||
}
|
||||
|
@ -358,15 +397,20 @@ ident: aIDENTIFIER { $$ = make_var($1); }
|
|||
;
|
||||
|
||||
base_type: tBYTE { $$ = make_type(RPC_FC_BYTE, NULL); }
|
||||
| tCHAR { $$ = make_type(RPC_FC_CHAR, NULL); }
|
||||
| tUNSIGNED tCHAR { $$ = make_type(RPC_FC_CHAR, NULL); }
|
||||
| tWCHAR { $$ = make_type(RPC_FC_WCHAR, NULL); }
|
||||
| int_std
|
||||
| tSIGNED int_std { $$ = $2; /* FIXME */ }
|
||||
| tUNSIGNED int_std { $$ = $2; /* FIXME */ }
|
||||
| tSIGNED int_std { $$ = $2; $$->sign = 1; }
|
||||
| tUNSIGNED int_std { $$ = $2; $$->sign = -1;
|
||||
switch ($$->type) {
|
||||
case RPC_FC_SMALL: $$->type = RPC_FC_USMALL; break;
|
||||
case RPC_FC_SHORT: $$->type = RPC_FC_USHORT; break;
|
||||
case RPC_FC_LONG: $$->type = RPC_FC_ULONG; break;
|
||||
default: break;
|
||||
}
|
||||
}
|
||||
| tFLOAT { $$ = make_type(RPC_FC_FLOAT, NULL); }
|
||||
| tDOUBLE { $$ = make_type(RPC_FC_DOUBLE, NULL); }
|
||||
| tBOOLEAN { $$ = make_type(RPC_FC_BYTE, NULL); /* ? */ }
|
||||
| tBOOLEAN { $$ = make_type(RPC_FC_BYTE, &std_bool); /* ? */ }
|
||||
;
|
||||
|
||||
m_int:
|
||||
|
@ -378,6 +422,7 @@ int_std: tINT { $$ = make_type(RPC_FC_LONG, &std_int); } /* win32 only */
|
|||
| tLONG m_int { $$ = make_type(RPC_FC_LONG, NULL); }
|
||||
| tHYPER m_int { $$ = make_type(RPC_FC_HYPER, NULL); }
|
||||
| tINT64 { $$ = make_type(RPC_FC_HYPER, NULL); }
|
||||
| tCHAR { $$ = make_type(RPC_FC_CHAR, NULL); }
|
||||
;
|
||||
|
||||
inherit: { $$ = NULL; }
|
||||
|
@ -389,7 +434,7 @@ interface: tINTERFACE aIDENTIFIER { $$ = get_type(RPC_FC_IP, $2, 0); if (!parse
|
|||
;
|
||||
|
||||
interfacedef: attributes interface inherit
|
||||
'{' statements '}' { $$ = $2;
|
||||
'{' int_statements '}' { $$ = $2;
|
||||
if ($$->defined) yyerror("multiple definition error\n");
|
||||
$$->ref = $3;
|
||||
$$->attrs = $1;
|
||||
|
@ -400,7 +445,7 @@ interfacedef: attributes interface inherit
|
|||
;
|
||||
|
||||
p_ident: '*' pident %prec PPTR { $$ = $2; $$->ptr_level++; }
|
||||
| tCONST p_ident { $$ = $2; }
|
||||
| tCONST p_ident { $$ = $2; /* FIXME */ }
|
||||
;
|
||||
|
||||
pident: ident
|
||||
|
@ -451,8 +496,9 @@ uniondef: tUNION t_ident '{' fields '}' { $$ = get_typev(RPC_FC_NON_ENCAPSULATE
|
|||
$$->defined = TRUE;
|
||||
}
|
||||
| tUNION t_ident
|
||||
tSWITCH '(' type ident ')'
|
||||
tSWITCH '(' s_field ')'
|
||||
m_ident '{' cases '}' { $$ = get_typev(RPC_FC_ENCAPSULATED_UNION, $2, tsUNION);
|
||||
LINK($5, $9); $$->fields = $5;
|
||||
$$->defined = TRUE;
|
||||
}
|
||||
;
|
||||
|
@ -469,6 +515,7 @@ static attr_t *make_attr(enum attr_type type)
|
|||
attr_t *a = xmalloc(sizeof(attr_t));
|
||||
a->type = type;
|
||||
a->u.ival = 0;
|
||||
INIT_LINK(a);
|
||||
return a;
|
||||
}
|
||||
|
||||
|
@ -477,6 +524,7 @@ static attr_t *make_attrv(enum attr_type type, DWORD val)
|
|||
attr_t *a = xmalloc(sizeof(attr_t));
|
||||
a->type = type;
|
||||
a->u.ival = val;
|
||||
INIT_LINK(a);
|
||||
return a;
|
||||
}
|
||||
|
||||
|
@ -485,9 +533,111 @@ static attr_t *make_attrp(enum attr_type type, void *val)
|
|||
attr_t *a = xmalloc(sizeof(attr_t));
|
||||
a->type = type;
|
||||
a->u.pval = val;
|
||||
INIT_LINK(a);
|
||||
return a;
|
||||
}
|
||||
|
||||
static expr_t *make_expr(enum expr_type type)
|
||||
{
|
||||
expr_t *e = xmalloc(sizeof(expr_t));
|
||||
e->type = type;
|
||||
e->ref = NULL;
|
||||
e->u.lval = 0;
|
||||
INIT_LINK(e);
|
||||
return e;
|
||||
}
|
||||
|
||||
static expr_t *make_exprl(enum expr_type type, long val)
|
||||
{
|
||||
expr_t *e = xmalloc(sizeof(expr_t));
|
||||
e->type = type;
|
||||
e->ref = NULL;
|
||||
e->u.lval = val;
|
||||
INIT_LINK(e);
|
||||
return e;
|
||||
}
|
||||
|
||||
static expr_t *make_exprs(enum expr_type type, char *val)
|
||||
{
|
||||
expr_t *e = xmalloc(sizeof(expr_t));
|
||||
/* FIXME: if type is EXPR_IDENTIFIER, we could check for match against const
|
||||
* declaration, and change to appropriate type and value if so */
|
||||
e->type = type;
|
||||
e->ref = NULL;
|
||||
e->u.sval = val;
|
||||
INIT_LINK(e);
|
||||
return e;
|
||||
}
|
||||
|
||||
static expr_t *make_expr1(enum expr_type type, expr_t *expr)
|
||||
{
|
||||
expr_t *e;
|
||||
/* check for compile-time optimization */
|
||||
if (expr->type == EXPR_NUM) {
|
||||
switch (type) {
|
||||
case EXPR_NEG:
|
||||
expr->u.lval = -expr->u.lval;
|
||||
return expr;
|
||||
default:
|
||||
}
|
||||
}
|
||||
e = xmalloc(sizeof(expr_t));
|
||||
e->type = type;
|
||||
e->ref = expr;
|
||||
e->u.lval = 0;
|
||||
INIT_LINK(e);
|
||||
return e;
|
||||
}
|
||||
|
||||
static expr_t *make_expr2(enum expr_type type, expr_t *expr1, expr_t *expr2)
|
||||
{
|
||||
expr_t *e;
|
||||
/* check for compile-time optimization */
|
||||
if (expr1->type == EXPR_NUM && expr2->type == EXPR_NUM) {
|
||||
switch (type) {
|
||||
case EXPR_ADD:
|
||||
expr1->u.lval += expr2->u.lval;
|
||||
free(expr2);
|
||||
return expr1;
|
||||
case EXPR_SUB:
|
||||
expr1->u.lval -= expr2->u.lval;
|
||||
free(expr2);
|
||||
return expr1;
|
||||
case EXPR_MUL:
|
||||
expr1->u.lval *= expr2->u.lval;
|
||||
free(expr2);
|
||||
return expr1;
|
||||
case EXPR_DIV:
|
||||
expr1->u.lval /= expr2->u.lval;
|
||||
free(expr2);
|
||||
return expr1;
|
||||
case EXPR_OR:
|
||||
expr1->u.lval |= expr2->u.lval;
|
||||
free(expr2);
|
||||
return expr1;
|
||||
case EXPR_AND:
|
||||
expr1->u.lval &= expr2->u.lval;
|
||||
free(expr2);
|
||||
return expr1;
|
||||
case EXPR_SHL:
|
||||
expr1->u.lval <<= expr2->u.lval;
|
||||
free(expr2);
|
||||
return expr1;
|
||||
case EXPR_SHR:
|
||||
expr1->u.lval >>= expr2->u.lval;
|
||||
free(expr2);
|
||||
return expr1;
|
||||
default:
|
||||
}
|
||||
}
|
||||
e = xmalloc(sizeof(expr_t));
|
||||
e->type = type;
|
||||
e->ref = expr1;
|
||||
e->u.ext = expr2;
|
||||
INIT_LINK(e);
|
||||
return e;
|
||||
}
|
||||
|
||||
static type_t *make_type(BYTE type, type_t *ref)
|
||||
{
|
||||
type_t *t = xmalloc(sizeof(type_t));
|
||||
|
@ -500,6 +650,7 @@ static type_t *make_type(BYTE type, type_t *ref)
|
|||
t->fields = NULL;
|
||||
t->ignore = parse_only;
|
||||
t->is_const = FALSE;
|
||||
t->sign = 0;
|
||||
t->defined = FALSE;
|
||||
t->written = FALSE;
|
||||
INIT_LINK(t);
|
||||
|
@ -536,12 +687,13 @@ static type_t *type_ref(typeref_t *ref)
|
|||
return t;
|
||||
}
|
||||
|
||||
static void set_type(var_t *v, typeref_t *ref)
|
||||
static void set_type(var_t *v, typeref_t *ref, expr_t *arr)
|
||||
{
|
||||
v->type = ref->ref;
|
||||
v->tname = ref->name;
|
||||
ref->name = NULL;
|
||||
free(ref);
|
||||
v->array = arr;
|
||||
}
|
||||
|
||||
static var_t *make_var(char *name)
|
||||
|
@ -552,6 +704,9 @@ static var_t *make_var(char *name)
|
|||
v->type = NULL;
|
||||
v->tname = NULL;
|
||||
v->attrs = NULL;
|
||||
v->array = NULL;
|
||||
v->has_val = FALSE;
|
||||
v->lval = 0;
|
||||
INIT_LINK(v);
|
||||
return v;
|
||||
}
|
||||
|
@ -574,7 +729,7 @@ struct rtype {
|
|||
struct rtype *next;
|
||||
};
|
||||
|
||||
struct rtype *first;
|
||||
struct rtype *first_type;
|
||||
|
||||
static type_t *reg_type(type_t *type, char *name, int t)
|
||||
{
|
||||
|
@ -587,8 +742,8 @@ static type_t *reg_type(type_t *type, char *name, int t)
|
|||
nt->name = name;
|
||||
nt->type = type;
|
||||
nt->t = t;
|
||||
nt->next = first;
|
||||
first = nt;
|
||||
nt->next = first_type;
|
||||
first_type = nt;
|
||||
return type;
|
||||
}
|
||||
|
||||
|
@ -623,7 +778,7 @@ static type_t *reg_types(type_t *type, var_t *names, int t)
|
|||
|
||||
static type_t *find_type(char *name, int t)
|
||||
{
|
||||
struct rtype *cur = first;
|
||||
struct rtype *cur = first_type;
|
||||
while (cur && (cur->t != t || strcmp(cur->name, name)))
|
||||
cur = cur->next;
|
||||
if (!cur) {
|
||||
|
@ -642,7 +797,7 @@ static type_t *find_type2(char *name, int t)
|
|||
|
||||
int is_type(const char *name)
|
||||
{
|
||||
struct rtype *cur = first;
|
||||
struct rtype *cur = first_type;
|
||||
while (cur && (cur->t || strcmp(cur->name, name)))
|
||||
cur = cur->next;
|
||||
if (cur) return TRUE;
|
||||
|
@ -654,7 +809,7 @@ static type_t *get_type(BYTE type, char *name, int t)
|
|||
struct rtype *cur = NULL;
|
||||
type_t *tp;
|
||||
if (name) {
|
||||
cur = first;
|
||||
cur = first_type;
|
||||
while (cur && (cur->t != t || strcmp(cur->name, name)))
|
||||
cur = cur->next;
|
||||
}
|
||||
|
|
|
@ -302,12 +302,13 @@ void finish_proxy(void)
|
|||
{
|
||||
if_list *lcur = if_first;
|
||||
if_list *cur;
|
||||
char *file_id = "XXX";
|
||||
int c;
|
||||
|
||||
if (!lcur) return;
|
||||
while (NEXT_LINK(lcur)) lcur = NEXT_LINK(lcur);
|
||||
|
||||
fprintf(proxy, "const CInterfaceProxyVtbl* _XXX_ProxyVtblList[] = {\n");
|
||||
fprintf(proxy, "const CInterfaceProxyVtbl* _%s_ProxyVtblList[] = {\n", file_id);
|
||||
cur = lcur;
|
||||
while (cur) {
|
||||
fprintf(proxy, " (CInterfaceProxyVtbl*)&%sProxyVtbl,\n", cur->iface->name);
|
||||
|
@ -317,7 +318,7 @@ void finish_proxy(void)
|
|||
fprintf(proxy, "};\n");
|
||||
fprintf(proxy, "\n");
|
||||
|
||||
fprintf(proxy, "const CInterfaceStubVtbl* _XXX_StubVtblList[] = {\n");
|
||||
fprintf(proxy, "const CInterfaceStubVtbl* _%s_StubVtblList[] = {\n", file_id);
|
||||
cur = lcur;
|
||||
while (cur) {
|
||||
fprintf(proxy, " (CInterfaceStubVtbl*)&%sStubVtbl,\n", cur->iface->name);
|
||||
|
@ -327,7 +328,7 @@ void finish_proxy(void)
|
|||
fprintf(proxy, "};\n");
|
||||
fprintf(proxy, "\n");
|
||||
|
||||
fprintf(proxy, "const PCInterfaceName _XXX_InterfaceNamesList[] = {\n");
|
||||
fprintf(proxy, "const PCInterfaceName _%s_InterfaceNamesList[] = {\n", file_id);
|
||||
cur = lcur;
|
||||
while (cur) {
|
||||
fprintf(proxy, " \"%s\",\n", cur->iface->name);
|
||||
|
@ -337,14 +338,14 @@ void finish_proxy(void)
|
|||
fprintf(proxy, "};\n");
|
||||
fprintf(proxy, "\n");
|
||||
|
||||
fprintf(proxy, "#define _XXX_CHECK_IID(n) IID_GENERIC_CHECK_IID(_XXX, pIID, n)\n");
|
||||
fprintf(proxy, "#define _%s_CHECK_IID(n) IID_GENERIC_CHECK_IID(_XXX, pIID, n)\n", file_id);
|
||||
fprintf(proxy, "\n");
|
||||
fprintf(proxy, "int __stdcall _XXX_IID_Lookup(const IID* pIID, int* pIndex)\n");
|
||||
fprintf(proxy, "int __stdcall _%s_IID_Lookup(const IID* pIID, int* pIndex)\n", file_id);
|
||||
fprintf(proxy, "{\n");
|
||||
cur = lcur;
|
||||
c = 0;
|
||||
while (cur) {
|
||||
fprintf(proxy, " if (!_XXX_CHECK_IID(%d)) {\n", c);
|
||||
fprintf(proxy, " if (!_%s_CHECK_IID(%d)) {\n", file_id, c);
|
||||
fprintf(proxy, " *pIndex = %d\n", c);
|
||||
fprintf(proxy, " return 1;\n");
|
||||
fprintf(proxy, " }\n");
|
||||
|
@ -355,12 +356,12 @@ void finish_proxy(void)
|
|||
fprintf(proxy, "}\n");
|
||||
fprintf(proxy, "\n");
|
||||
|
||||
fprintf(proxy, "const ExtendedProxyFileInfo XXX_ProxyFileInfo = {\n");
|
||||
fprintf(proxy, " (PCInterfaceProxyVtblList*)&_XXX_ProxyVtblList,\n");
|
||||
fprintf(proxy, " (PCInterfaceStubVtblList*)&_XXX_StubVtblList,\n");
|
||||
fprintf(proxy, " (const PCInterfaceName*)&_XXX_InterfaceNamesList,\n");
|
||||
fprintf(proxy, "const ExtendedProxyFileInfo %s_ProxyFileInfo = {\n", file_id);
|
||||
fprintf(proxy, " (PCInterfaceProxyVtblList*)&_%s_ProxyVtblList,\n", file_id);
|
||||
fprintf(proxy, " (PCInterfaceStubVtblList*)&_%s_StubVtblList,\n", file_id);
|
||||
fprintf(proxy, " (const PCInterfaceName*)&_%s_InterfaceNamesList,\n", file_id);
|
||||
fprintf(proxy, " 0,\n");
|
||||
fprintf(proxy, " &_XXX_IID_Lookup,\n");
|
||||
fprintf(proxy, " &_%s_IID_Lookup,\n", file_id);
|
||||
fprintf(proxy, " %d,\n", c);
|
||||
fprintf(proxy, " 1\n");
|
||||
fprintf(proxy, "};\n");
|
||||
|
|
|
@ -70,6 +70,7 @@ int no_preprocess = 0;
|
|||
|
||||
char *input_name;
|
||||
char *header_name;
|
||||
char *header_token;
|
||||
char *proxy_name;
|
||||
char *temp_name;
|
||||
|
||||
|
@ -85,6 +86,23 @@ int getopt (int argc, char *const *argv, const char *optstring);
|
|||
static void rm_tempfile(void);
|
||||
static void segvhandler(int sig);
|
||||
|
||||
static char *make_token(const char *name)
|
||||
{
|
||||
char *token;
|
||||
char *slash;
|
||||
int i;
|
||||
|
||||
slash = strrchr(name, '/');
|
||||
if (slash) name = slash + 1;
|
||||
|
||||
token = xstrdup(name);
|
||||
for (i=0; token[i]; i++) {
|
||||
if (!isalnum(token[i])) token[i] = '_';
|
||||
else token[i] = toupper(token[i]);
|
||||
}
|
||||
return token;
|
||||
}
|
||||
|
||||
int main(int argc,char *argv[])
|
||||
{
|
||||
extern char* optarg;
|
||||
|
@ -175,20 +193,29 @@ int main(int argc,char *argv[])
|
|||
|
||||
if(ret) exit(1);
|
||||
if(preprocess_only) exit(0);
|
||||
input_name = temp_name;
|
||||
if(!(yyin = fopen(temp_name, "r"))) {
|
||||
fprintf(stderr, "Could not open %s for input\n", temp_name);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
else {
|
||||
if(!(yyin = fopen(input_name, "r"))) {
|
||||
fprintf(stderr, "Could not open %s for input\n", input_name);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
if(!(yyin = fopen(input_name, "r"))) {
|
||||
fprintf(stderr, "Could not open %s for input\n", input_name);
|
||||
return 1;
|
||||
}
|
||||
header_token = make_token(header_name);
|
||||
|
||||
header = fopen(header_name, "w");
|
||||
fprintf(header, "/*** Autogenerated by WIDL %s - Do not edit ***/\n", WIDL_FULLVERSION);
|
||||
fprintf(header, "/*** Autogenerated by WIDL %s from %s - Do not edit ***/\n", WIDL_FULLVERSION, input_name);
|
||||
fprintf(header, "#ifndef __WIDL_%s\n", header_token);
|
||||
fprintf(header, "#define __WIDL_%s\n", header_token);
|
||||
|
||||
ret = yyparse();
|
||||
|
||||
finish_proxy();
|
||||
fprintf(header, "#endif /* __WIDL_%s */\n", header_token);
|
||||
fclose(header);
|
||||
fclose(yyin);
|
||||
|
||||
|
|
|
@ -26,6 +26,7 @@
|
|||
#include "wine/rpcfc.h"
|
||||
|
||||
typedef struct _attr_t attr_t;
|
||||
typedef struct _expr_t expr_t;
|
||||
typedef struct _type_t type_t;
|
||||
typedef struct _typeref_t typeref_t;
|
||||
typedef struct _var_t var_t;
|
||||
|
@ -57,10 +58,29 @@ enum attr_type
|
|||
ATTR_POINTERDEFAULT,
|
||||
ATTR_POINTERTYPE,
|
||||
ATTR_STRING,
|
||||
ATTR_UUID,
|
||||
ATTR_V1ENUM,
|
||||
ATTR_WIREMARSHAL,
|
||||
};
|
||||
|
||||
enum expr_type
|
||||
{
|
||||
EXPR_VOID,
|
||||
EXPR_NUM,
|
||||
EXPR_IDENTIFIER,
|
||||
EXPR_NEG,
|
||||
EXPR_PPTR,
|
||||
EXPR_CAST,
|
||||
EXPR_SHL,
|
||||
EXPR_SHR,
|
||||
EXPR_MUL,
|
||||
EXPR_DIV,
|
||||
EXPR_ADD,
|
||||
EXPR_SUB,
|
||||
EXPR_AND,
|
||||
EXPR_OR,
|
||||
};
|
||||
|
||||
struct _attr_t {
|
||||
enum attr_type type;
|
||||
union {
|
||||
|
@ -71,6 +91,18 @@ struct _attr_t {
|
|||
DECL_LINK(attr_t)
|
||||
};
|
||||
|
||||
struct _expr_t {
|
||||
enum expr_type type;
|
||||
expr_t *ref;
|
||||
union {
|
||||
long lval;
|
||||
char *sval;
|
||||
expr_t *ext;
|
||||
} u;
|
||||
/* parser-internal */
|
||||
DECL_LINK(expr_t)
|
||||
};
|
||||
|
||||
struct _type_t {
|
||||
char *name;
|
||||
BYTE type;
|
||||
|
@ -79,7 +111,7 @@ struct _type_t {
|
|||
attr_t *attrs;
|
||||
func_t *funcs;
|
||||
var_t *fields;
|
||||
int ignore, is_const;
|
||||
int ignore, is_const, sign;
|
||||
int defined, written;
|
||||
|
||||
/* parser-internal */
|
||||
|
@ -95,9 +127,12 @@ struct _typeref_t {
|
|||
struct _var_t {
|
||||
char *name;
|
||||
int ptr_level;
|
||||
expr_t *array;
|
||||
type_t *type;
|
||||
char *tname;
|
||||
attr_t *attrs;
|
||||
int has_val;
|
||||
long lval;
|
||||
|
||||
/* parser-internal */
|
||||
DECL_LINK(var_t)
|
||||
|
|
Loading…
Reference in New Issue