ALLOC(ptr, sizeof (type)); \
*(ptr) = (type) { __VA_ARGS__ }; \
} while (0)
+
+/*
+ * With the postprocessing performed by fix-yytname.awk, all the symbol
+ * name strings can be used directly in error messages and there is no
+ * need for any string processing.
+ */
+#define yytnamerr(a, b) cdecl__strlcpy(a, b, (a) ? INT_MAX : 0)
%}
%code requires {
uintmax_t uintval;
unsigned spectype;
_Bool boolval;
- char *strval;
struct cdecl_declspec *declspec;
struct cdecl_declarator *declarator;
struct cdecl *decl;
+ struct parse_item *item;
}
%{
struct cdecl_declspec *p;
while (x) {
p = x->next;
- free(x->ident);
free(x);
x = p;
}
switch (x->type) {
case CDECL_DECL_NULL:
- break;
+ x = NULL;
case CDECL_DECL_IDENT:
- free(x->u.ident);
+ case CDECL_DECL_ARRAY:
break;
case CDECL_DECL_POINTER:
free_declspec(x->u.pointer.qualifiers);
break;
- case CDECL_DECL_ARRAY:
- free(x->u.array.vla);
- break;
case CDECL_DECL_FUNCTION:
free_decl(x->u.function.parameters);
break;
* Alter an abstract declarator (type name) to declare an identifier instead,
* used by the English parser rules to reduce "identifier as type" sequences.
*/
-static struct cdecl *insert_identifier(struct cdecl *decl, char *ident)
+static struct cdecl *insert_identifier(struct cdecl *decl, struct parse_item *ident)
{
- struct cdecl_declarator *d = decl->declarators;
+ struct cdecl_declarator *d, **p = &decl->declarators;
- while (d->child)
- d = d->child;
-
- d->type = CDECL_DECL_IDENT;
- d->u.ident = ident;
+ while ((d = *p)->child)
+ p = &d->child;
+ *p = &ident->u.declarator;
return decl;
}
+
+static struct cdecl_declarator *nulldecl(void)
+{
+ static const struct cdecl_declarator nulldecl = {0};
+ return (void *)&nulldecl;
+}
+#define NULLDECL (nulldecl())
+
%}
-%destructor { free($$); } <strval>
+%destructor { free($$); } <item>
%destructor { free_declspec($$); } <declspec>
%destructor { free_declarator($$); } <declarator>
%destructor { free_decl($$); } <decl>
/* Magic tokens */
%token T_LEX_ERROR
-%token T_ENGLISH
-%token <strval> T_IDENT "identifier"
-%token <uintval> T_UINT "integer constant"
+%token <item> T_IDENT "identifier"
+%token <uintval> T_UINT "integer constant"
%token T_SEMICOLON ";"
%token T_ASTERISK "*"
%token T_AS "as"
%token T_VLA "variable-length"
-%type <strval> vla_ident
+%type <item> vla_ident
%type <uintval> array_length
%type <boolval> varargs
%type <spectype> declspec_simple qualifier_simple
%type <declspec> declspecs declspecs_noid
%type <declarator> direct_declarator declarator pointer array parens postfix
%type <declarator> direct_declarator_ish declarator_ish parameter_type_list
-%type <decl> declaration declarators declarator_wrap
-%type <decl> parameter
+%type <decl> cdecl declaration declarators declarator_wrap parameter
-%type <strval> english_vla
+%type <item> english_vla
%type <declspec> storage_func_specs post_specs
%type <declspec> type_qual_spec type_qual_specs typedef_name_qual
%type <declarator> english_declarator english_array english_function
%type <declarator> english_parameter_list null_decl
-%type <decl> english english_declaration
-%type <decl> english_parameter
+%type <decl> english english_declaration english_parameter
-/*
- * Harmless shift/reduce conflicts in english_parameter. See comments below
- * for more details.
- */
-%expect 2
+/* Precedence declaration to avoid conflict in english_parameter; see below. */
+%right T_TYPE
+%right T_IDENT
%%
-input: T_ENGLISH english {
- *out = $2;
-} | declaration {
- *out = $1;
-};
+input: cdecl { *out = $1; }
+cdecl: english | declaration
semi: | T_SEMICOLON
-
declaration: declspecs declarators semi {
$$ = $2;
$$->specifiers = $1;
| T_COMPLEX
| T_IMAGINARY
-typespec_tagged: T_STRUCT | T_UNION | T_ENUM
+typespec_tagged: T_STRUCT | T_UNION | T_ENUM | { $$ = CDECL_TYPE_IDENT; }
qualifier_simple: T_CONST
| T_RESTRICT
}
typespec: typespec_noid | typespec_tagged T_IDENT {
- ALLOC_STRUCT($$, struct cdecl_declspec,
- .type = $1,
- .ident = $2);
-} | T_IDENT {
- ALLOC_STRUCT($$, struct cdecl_declspec,
- .type = CDECL_TYPE_IDENT,
- .ident = $1);
+ /* Compiler should be able to elide this assignment. */
+ $2->u.declspec.ident = $2->u.declarator.u.ident;
+
+ $$ = &$2->u.declspec;
+ $$->type = $1;
}
declspec_noid: declspec_notype | typespec_noid
vla_ident: T_IDENT | T_ASTERISK {
- ALLOC($$, sizeof "");
- *$$ = 0;
+ if (!($$ = cdecl__alloc_item(1)))
+ YYERROR;
+ *$$->s = 0;
}
array: T_LBRACKET array_length T_RBRACKET {
.type = CDECL_DECL_ARRAY,
.u.array.length = $2);
} | T_LBRACKET vla_ident T_RBRACKET {
- ALLOC_STRUCT($$, struct cdecl_declarator,
- .type = CDECL_DECL_ARRAY,
- .u.array.vla = $2);
+ $$ = &$2->u.declarator;
+ $$->type = CDECL_DECL_ARRAY;
+ $$->u.array.vla = $$->u.ident;
+ $$->u.array.length = 0;
}
parameter: declspecs declarator {
postfix: array | parens
direct_declarator_ish: {
- ALLOC_STRUCT($$, struct cdecl_declarator,
- .type = CDECL_DECL_NULL);
+ $$ = NULLDECL;
} | direct_declarator_ish postfix {
$$ = $2;
$$->child = $1;
}
direct_declarator: {
- ALLOC_STRUCT($$, struct cdecl_declarator,
- .type = CDECL_DECL_NULL);
+ $$ = NULLDECL;
} | T_IDENT {
- ALLOC_STRUCT($$, struct cdecl_declarator,
- .type = CDECL_DECL_IDENT,
- .u.ident = $1);
+ $$ = &$1->u.declarator;
} | direct_declarator postfix {
$$ = $2;
$$->child = $1;
$$ = $2;
}
-storage_func_specs: { $$ = NULL; } | declspec_simple storage_func_specs {
+/*
+ * We use a precedence declaration to prefer shifting an identifier
+ * over reducing this empty rule; see below.
+ */
+storage_func_specs: %prec T_TYPE { $$ = NULL; }
+storage_func_specs: declspec_simple storage_func_specs {
ALLOC_STRUCT($$, struct cdecl_declspec,
.type = $1,
.next = $2);
}
english_declarator: {
- ALLOC_STRUCT($$, struct cdecl_declarator,
- .type = CDECL_DECL_NULL);
+ $$ = NULLDECL;
} | english_declarator qualifiers T_POINTER T_TO {
ALLOC_STRUCT($$, struct cdecl_declarator,
.type = CDECL_DECL_POINTER,
}
typedef_name_qual: T_IDENT qualifiers {
- ALLOC_STRUCT($$, struct cdecl_declspec,
- .type = CDECL_TYPE_IDENT,
- .ident = $1,
- .next = $2);
+ /* Compiler should be able to elide this assignment. */
+ $1->u.declspec.ident = $1->u.declarator.u.ident;
+
+ $$ = &$1->u.declspec;
+ $$->type = CDECL_TYPE_IDENT;
+ $$->next = $2;
}
null_decl: {
- ALLOC_STRUCT($$, struct cdecl_declarator,
- .type = CDECL_DECL_NULL);
+ $$ = NULLDECL;
}
/*
- * There is a small shift/reduce conflict here. An unadorned identifier
- * as the first thing in the parameter might be a typedef name deep in the
- * first english_declaration (thus empty storage_func_specs and empty
- * english_declarator need to be reduced) or it might be the identifier
- * before the "as" (thus the identifier should be shifted).
+ * There is a shift/reduce conflict here when an identifier appears as the
+ * first token. The conflict is between shifting T_IDENT, or reducing the
+ * empty production for storage_func_specs (cf. english_declaration).
+ *
+ * - In either case, if we reduce, we won't match T_IDENT T_AS since the
+ * stack now has the extra storage_func_specs nonterminal symbol.
+ * - And if we shift, we won't match english_declaration since it is
+ * too late to add storage_func_specs to the stack.
*
- * The typedef name conflict is the only issue, so treating it as a special
- * case makes the shift harmless.
+ * The only valid input affected by the conflict is a simple type names,
+ * possibly followed by qualifiers. So the conflict is adequately resolved
+ * by shifting, so long as we have a special-case reduction to handle this.
*/
english_parameter: english_declaration | typedef_name_qual null_decl {
ALLOC_STRUCT($$, struct cdecl,
}
english_array: T_VLA T_ARRAY english_vla T_OF {
- ALLOC_STRUCT($$, struct cdecl_declarator,
- .type = CDECL_DECL_ARRAY,
- .u.array.vla = $3);
+ $$ = &$3->u.declarator;
+ $$->type = CDECL_DECL_ARRAY;
+ $$->u.array.vla = $$->u.ident;
+ $$->u.array.length = 0;
} | T_ARRAY array_length T_OF {
ALLOC_STRUCT($$, struct cdecl_declarator,
.type = CDECL_DECL_ARRAY,
}
english_vla: T_IDENT | {
- ALLOC($$, sizeof "");
- *$$ = 0;
+ if (!($$ = cdecl__alloc_item(1)))
+ YYERROR;
+ *$$->s = 0;
}
%%