Mon Jul 12 21:43:44 2021 UTC ()
lint: reorder grammar rules in the same way as in C99

The code coverage before and after this change is exactly the same,
except of course for cgram.y and cgram.c.

No functional change.


(rillig)
diff -r1.318 -r1.319 src/usr.bin/xlint/lint1/cgram.y

cvs diff -r1.318 -r1.319 src/usr.bin/xlint/lint1/cgram.y (expand / switch to context diff)
--- src/usr.bin/xlint/lint1/cgram.y 2021/07/11 21:07:44 1.318
+++ src/usr.bin/xlint/lint1/cgram.y 2021/07/12 21:43:44 1.319
@@ -1,5 +1,5 @@
 %{
-/* $NetBSD: cgram.y,v 1.318 2021/07/11 21:07:44 rillig Exp $ */
+/* $NetBSD: cgram.y,v 1.319 2021/07/12 21:43:44 rillig Exp $ */
 
 /*
  * Copyright (c) 1996 Christopher G. Demetriou.  All Rights Reserved.
@@ -35,7 +35,7 @@
 
 #include <sys/cdefs.h>
 #if defined(__RCSID) && !defined(lint)
-__RCSID("$NetBSD: cgram.y,v 1.318 2021/07/11 21:07:44 rillig Exp $");
+__RCSID("$NetBSD: cgram.y,v 1.319 2021/07/12 21:43:44 rillig Exp $");
 #endif
 
 #include <limits.h>
@@ -363,161 +363,375 @@
 	| translation_unit
 	;
 
-translation_unit:		/* C99 6.9 */
-	  external_declaration
-	| translation_unit external_declaration
+identifier_sym:			/* helper for struct/union/enum */
+	  identifier {
+		$$ = getsym($1);
+	  }
 	;
 
-external_declaration:		/* C99 6.9 */
-	  asm_statement
-	| function_definition {
-		global_clean_up_decl(false);
-		clear_warning_flags();
+/* K&R ???, C90 ???, C99 6.4.2.1, C11 ???, C18 ??? */
+identifier:
+	  T_NAME {
+		$$ = $1;
+		cgram_debug("name '%s'", $$->sb_name);
 	  }
-	| top_level_declaration {
-		global_clean_up_decl(false);
-		clear_warning_flags();
+	| T_TYPENAME {
+		$$ = $1;
+		cgram_debug("typename '%s'", $$->sb_name);
 	  }
 	;
 
-/*
- * On the top level, lint allows several forms of declarations that it doesn't
- * allow in functions.  For example, a single ';' is an empty declaration and
- * is supported by some compilers, but in a function it would be an empty
- * statement, not a declaration.  This makes a difference in C90 mode, where
- * a statement must not be followed by a declaration.
- *
- * See 'declaration' for all other declarations.
- */
-top_level_declaration:		/* C99 6.9 calls this 'declaration' */
-	  T_SEMI {
-		if (sflag) {
-			/* empty declaration */
-			error(0);
-		} else if (!tflag) {
-			/* empty declaration */
-			warning(0);
-		}
+/* see C99 6.4.5, string literals are joined by 5.1.1.2 */
+string:
+	  T_STRING
+	| T_STRING string2 {
+		$$ = cat_strings($1, $2);
 	  }
-	| begin_type end_type notype_init_decls T_SEMI {
-		if (sflag) {
-			/* old style declaration; add 'int' */
-			error(1);
-		} else if (!tflag) {
-			/* old style declaration; add 'int' */
-			warning(1);
+	;
+
+/* see C99 6.4.5, string literals are joined by 5.1.1.2 */
+string2:
+	  T_STRING {
+		if (tflag) {
+			/* concatenated strings are illegal in traditional C */
+			warning(219);
 		}
+		$$ = $1;
 	  }
-	| begin_type_declmods end_type T_SEMI {
-		if (dcs->d_scl == TYPEDEF) {
-			/* typedef declares no type name */
-			warning(72);
-		} else {
-			/* empty declaration */
-			warning(2);
-		}
+	| string2 T_STRING {
+		$$ = cat_strings($1, $2);
 	  }
-	| begin_type_declmods end_type notype_init_decls T_SEMI
-	| begin_type_declaration_specifiers end_type T_SEMI {
-		if (dcs->d_scl == TYPEDEF) {
-			/* typedef declares no type name */
-			warning(72);
-		} else if (!dcs->d_nonempty_decl) {
-			/* empty declaration */
-			warning(2);
-		}
+	;
+
+/* K&R 7.1, C90 ???, C99 6.5.1, C11 6.5.1, C18 6.5.1 */
+primary_expression:
+	  T_NAME {
+		/* XXX really necessary? */
+		if (yychar < 0)
+			yychar = yylex();
+		$$ = new_name_node(getsym($1), yychar);
 	  }
-	| begin_type_declaration_specifiers end_type type_init_decls T_SEMI
-	| error T_SEMI {
-		global_clean_up();
+	| T_CON {
+		$$ = expr_new_constant(gettyp($1->v_tspec), $1);
 	  }
-	| error T_RBRACE {
-		global_clean_up();
+	| string {
+		$$ = new_string_node($1);
 	  }
+	| T_LPAREN expr T_RPAREN {
+		if ($2 != NULL)
+			$2->tn_parenthesized = true;
+		$$ = $2;
+	  }
+	| generic_selection
+	/* GCC primary-expression, see c_parser_postfix_expression */
+	| T_BUILTIN_OFFSETOF T_LPAREN type_name T_COMMA identifier T_RPAREN {
+		symtyp = FMEMBER;
+		$$ = build_offsetof($3, getsym($5));
+	  }
 	;
 
-function_definition:		/* C99 6.9.1 */
-	  func_decl {
-		if ($1->s_type->t_tspec != FUNC) {
-			/* syntax error '%s' */
-			error(249, yytext);
-			YYERROR;
-		}
-		if ($1->s_type->t_typedef) {
-			/* ()-less function definition */
-			error(64);
-			YYERROR;
-		}
-		funcdef($1);
-		block_level++;
-		begin_declaration_level(ARG);
-		if (lwarn == LWARN_NONE)
-			$1->s_used = true;
-	  } arg_declaration_list_opt {
-		end_declaration_level();
-		block_level--;
-		check_func_lint_directives();
-		check_func_old_style_arguments();
-		begin_control_statement(CS_FUNCTION_BODY);
-	  } compound_statement {
-		funcend();
-		end_control_statement(CS_FUNCTION_BODY);
+/* K&R ---, C90 ---, C99 ---, C11 6.5.1.1, C18 6.5.1.1 */
+generic_selection:
+	  T_GENERIC T_LPAREN assignment_expression T_COMMA
+	    generic_assoc_list T_RPAREN {
+	  	/* generic selection requires C11 or later */
+	  	c11ism(345);
+		$$ = build_generic_selection($3, $5);
 	  }
 	;
 
-func_decl:
-	  begin_type end_type notype_decl {
+/* K&R ---, C90 ---, C99 ---, C11 6.5.1.1, C18 6.5.1.1 */
+generic_assoc_list:
+	  generic_association
+	| generic_assoc_list T_COMMA generic_association {
+		$3->ga_prev = $1;
 		$$ = $3;
 	  }
-	| begin_type_declmods end_type notype_decl {
-		$$ = $3;
+	;
+
+/* K&R ---, C90 ---, C99 ---, C11 6.5.1.1, C18 6.5.1.1 */
+generic_association:
+	  type_name T_COLON assignment_expression {
+		$$ = getblk(sizeof(*$$));
+		$$->ga_arg = $1;
+		$$->ga_result = $3;
 	  }
-	| begin_type_declaration_specifiers end_type type_decl {
-		$$ = $3;
+	| T_DEFAULT T_COLON assignment_expression {
+		$$ = getblk(sizeof(*$$));
+		$$->ga_arg = NULL;
+		$$->ga_result = $3;
 	  }
 	;
 
-arg_declaration_list_opt:	/* C99 6.9.1p13 example 1 */
-	  /* empty */
-	| arg_declaration_list
+/* K&R 7.1, C90 ???, C99 6.5.2, C11 6.5.2, C18 6.5.2 */
+postfix_expression:
+	  primary_expression
+	| postfix_expression T_LBRACK expr T_RBRACK {
+		$$ = build(INDIR, build(PLUS, $1, $3), NULL);
+	  }
+	| postfix_expression T_LPAREN T_RPAREN {
+		$$ = new_function_call_node($1, NULL);
+	  }
+	| postfix_expression T_LPAREN argument_expression_list T_RPAREN {
+		$$ = new_function_call_node($1, $3);
+	  }
+	| postfix_expression point_or_arrow T_NAME {
+		if ($1 != NULL) {
+			sym_t	*msym;
+			/*
+			 * XXX struct_or_union_member should be integrated
+			 * in build()
+			 */
+			if ($2 == ARROW) {
+				/*
+				 * must do this before struct_or_union_member
+				 * is called
+				 */
+				$1 = cconv($1);
+			}
+			msym = struct_or_union_member($1, $2, getsym($3));
+			$$ = build($2, $1, new_name_node(msym, 0));
+		} else {
+			$$ = NULL;
+		}
+	  }
+	| postfix_expression T_INCDEC {
+		$$ = build($2 == INC ? INCAFT : DECAFT, $1, NULL);
+	  }
+	| T_LPAREN type_name T_RPAREN {	/* C99 6.5.2.5 "Compound literals" */
+		sym_t *tmp = mktempsym($2);
+		begin_initialization(tmp);
+		cgram_declare(tmp, true, NULL);
+	  } init_lbrace initializer_list comma_opt init_rbrace {
+		if (!Sflag)
+			 /* compound literals are a C9X/GCC extension */
+			 gnuism(319);
+		$$ = new_name_node(*current_initsym(), 0);
+		end_initialization();
+	  }
+	| T_LPAREN compound_statement_lbrace gcc_statement_expr_list {
+		block_level--;
+		mem_block_level--;
+		begin_initialization(mktempsym(dup_type($3->tn_type)));
+		mem_block_level++;
+		block_level++;
+		/* ({ }) is a GCC extension */
+		gnuism(320);
+	  } compound_statement_rbrace T_RPAREN {
+		$$ = new_name_node(*current_initsym(), 0);
+		end_initialization();
+	  }
 	;
 
-arg_declaration_list:		/* C99 6.9.1p13 example 1 */
-	  arg_declaration
-	| arg_declaration_list arg_declaration
-	/* XXX or better "arg_declaration error" ? */
-	| error
+comma_opt:			/* helper for 'postfix_expression' */
+	  /* empty */
+	| T_COMMA
 	;
 
 /*
- * "arg_declaration" is separated from "declaration" because it
- * needs other error handling.
+ * The inner part of a GCC statement-expression of the form ({ ... }).
+ *
+ * https://gcc.gnu.org/onlinedocs/gcc/Statement-Exprs.html
  */
-arg_declaration:
-	  begin_type_declmods end_type T_SEMI {
-		/* empty declaration */
-		warning(2);
+gcc_statement_expr_list:
+	  gcc_statement_expr_item
+	| gcc_statement_expr_list gcc_statement_expr_item {
+		$$ = $2;
 	  }
-	| begin_type_declmods end_type notype_init_decls T_SEMI
-	| begin_type_declaration_specifiers end_type T_SEMI {
-		if (!dcs->d_nonempty_decl) {
-			/* empty declaration */
-			warning(2);
+	;
+
+gcc_statement_expr_item:
+	  declaration {
+		clear_warning_flags();
+		$$ = NULL;
+	  }
+	| non_expr_statement {
+		$$ = expr_zalloc_tnode();
+		$$->tn_type = gettyp(VOID);
+	  }
+	| expr T_SEMI {
+		if ($1 == NULL) {	/* in case of syntax errors */
+			$$ = expr_zalloc_tnode();
+			$$->tn_type = gettyp(VOID);
 		} else {
-			/* '%s' declared in argument declaration list */
-			warning(3, type_name(dcs->d_type));
+			/* XXX: do that only on the last name */
+			if ($1->tn_op == NAME)
+				$1->tn_sym->s_used = true;
+			$$ = $1;
+			expr($1, false, false, false, false);
+			seen_fallthrough = false;
 		}
 	  }
-	| begin_type_declaration_specifiers end_type type_init_decls T_SEMI {
-		if (dcs->d_nonempty_decl) {
-			/* '%s' declared in argument declaration list */
-			warning(3, type_name(dcs->d_type));
+	;
+
+point_or_arrow:			/* helper for 'postfix_expression' */
+	  T_POINT {
+		symtyp = FMEMBER;
+		$$ = POINT;
+	  }
+	| T_ARROW {
+		symtyp = FMEMBER;
+		$$ = ARROW;
+	  }
+	;
+
+/* K&R 7.1, C90 ???, C99 6.5.2, C11 6.5.2, C18 6.5.2 */
+argument_expression_list:
+	  expr %prec T_COMMA {
+		$$ = new_function_argument_node(NULL, $1);
+	  }
+	| argument_expression_list T_COMMA expr {
+		$$ = new_function_argument_node($1, $3);
+	  }
+	;
+
+/* K&R 7.2, C90 ???, C99 6.5.3, C11 6.5.3, C18 6.5.3 */
+unary_expression:
+	  postfix_expression
+	| T_INCDEC unary_expression {
+		$$ = build($1 == INC ? INCBEF : DECBEF, $2, NULL);
+	  }
+	| T_AMPER cast_expression {
+		$$ = build(ADDR, $2, NULL);
+	  }
+	| T_ASTERISK cast_expression {
+		$$ = build(INDIR, $2, NULL);
+	  }
+	| T_ADDITIVE cast_expression {
+		if (tflag && $1 == PLUS) {
+			/* unary + is illegal in traditional C */
+			warning(100);
 		}
+		$$ = build($1 == PLUS ? UPLUS : UMINUS, $2, NULL);
 	  }
-	| begin_type_declmods error
-	| begin_type_declaration_specifiers error
+	| T_COMPLEMENT cast_expression {
+		$$ = build(COMPL, $2, NULL);
+	  }
+	| T_LOGNOT cast_expression {
+		$$ = build(NOT, $2, NULL);
+	  }
+	| T_REAL cast_expression {	/* GCC c_parser_unary_expression */
+		$$ = build(REAL, $2, NULL);
+	  }
+	| T_IMAG cast_expression {	/* GCC c_parser_unary_expression */
+		$$ = build(IMAG, $2, NULL);
+	  }
+	| T_EXTENSION cast_expression {	/* GCC c_parser_unary_expression */
+		$$ = $2;
+	  }
+	| T_SIZEOF unary_expression {
+		$$ = $2 == NULL ? NULL : build_sizeof($2->tn_type);
+		if ($$ != NULL)
+			check_expr_misc($2, false, false, false, false, false, true);
+	  }
+	| T_SIZEOF T_LPAREN type_name T_RPAREN {
+		$$ = build_sizeof($3);
+	  }
+	/* K&R ---, C90 ---, C99 ---, C11 6.5.3, C18 6.5.3 */
+	| T_ALIGNOF T_LPAREN type_name T_RPAREN {
+		$$ = build_alignof($3);
+	  }
 	;
 
+/* The rule 'unary_operator' is inlined into unary_expression. */
+
+/* K&R 7.2, C90 ???, C99 6.5.4, C11 6.5.4, C18 6.5.4 */
+cast_expression:
+	  unary_expression
+	| T_LPAREN type_name T_RPAREN cast_expression {
+		$$ = cast($4, $2);
+	  }
+	;
+
+expr_opt:
+	  /* empty */ {
+		$$ = NULL;
+	  }
+	| expr
+	;
+
+/* 'expression' also implements 'multiplicative_expression'. */
+/* 'expression' also implements 'additive_expression'. */
+/* 'expression' also implements 'shift_expression'. */
+/* 'expression' also implements 'relational_expression'. */
+/* 'expression' also implements 'equality_expression'. */
+/* 'expression' also implements 'AND_expression'. */
+/* 'expression' also implements 'exclusive_OR_expression'. */
+/* 'expression' also implements 'inclusive_OR_expression'. */
+/* 'expression' also implements 'logical_AND_expression'. */
+/* 'expression' also implements 'logical_OR_expression'. */
+/* 'expression' also implements 'conditional_expression'. */
+/* 'expression' also implements 'assignment_expression'. */
+/* TODO: rename to 'expression' */
+/* K&R ???, C90 ???, C99 6.5.5 to 6.5.17, C11 ???, C18 ??? */
+expr:
+	  expr T_ASTERISK expr {
+		$$ = build(MULT, $1, $3);
+	  }
+	| expr T_MULTIPLICATIVE expr {
+		$$ = build($2, $1, $3);
+	  }
+	| expr T_ADDITIVE expr {
+		$$ = build($2, $1, $3);
+	  }
+	| expr T_SHIFT expr {
+		$$ = build($2, $1, $3);
+	  }
+	| expr T_RELATIONAL expr {
+		$$ = build($2, $1, $3);
+	  }
+	| expr T_EQUALITY expr {
+		$$ = build($2, $1, $3);
+	  }
+	| expr T_AMPER expr {
+		$$ = build(BITAND, $1, $3);
+	  }
+	| expr T_BITXOR expr {
+		$$ = build(BITXOR, $1, $3);
+	  }
+	| expr T_BITOR expr {
+		$$ = build(BITOR, $1, $3);
+	  }
+	| expr T_LOGAND expr {
+		$$ = build(LOGAND, $1, $3);
+	  }
+	| expr T_LOGOR expr {
+		$$ = build(LOGOR, $1, $3);
+	  }
+	| expr T_QUEST expr T_COLON expr {
+		$$ = build(QUEST, $1, build(COLON, $3, $5));
+	  }
+	| expr T_ASSIGN expr {
+		$$ = build(ASSIGN, $1, $3);
+	  }
+	| expr T_OPASSIGN expr {
+		$$ = build($2, $1, $3);
+	  }
+	| expr T_COMMA expr {
+		$$ = build(COMMA, $1, $3);
+	  }
+	| cast_expression
+	;
+
+/* K&R ???, C90 ???, C99 6.5.16, C11 ???, C18 ??? */
+assignment_expression:
+	  expr %prec T_ASSIGN
+	;
+
+constant_expr_list_opt:		/* helper for gcc_attribute */
+	  /* empty */
+	| constant_expr_list
+	;
+
+constant_expr_list:		/* helper for gcc_attribute */
+	  constant_expr
+	| constant_expr_list T_COMMA constant_expr
+	;
+
+constant_expr:			/* C99 6.6 */
+	  expr %prec T_ASSIGN
+	;
+
 declaration:			/* C99 6.7 */
 	  begin_type_declmods end_type T_SEMI {
 		if (dcs->d_scl == TYPEDEF) {
@@ -542,18 +756,6 @@
 	| error T_SEMI
 	;
 
-begin_type:
-	  /* empty */ {
-		begin_type();
-	  }
-	;
-
-end_type:
-	  /* empty */ {
-		end_type();
-	  }
-	;
-
 begin_type_declaration_specifiers:	/* see C99 6.7 */
 	  begin_type_typespec {
 		add_type($1);
@@ -568,7 +770,7 @@
 	  }
 	;
 
-begin_type_declmods:
+begin_type_declmods:		/* see C99 6.7 */
 	  begin_type T_QUAL {
 		add_qualifier($2);
 	  }
@@ -578,14 +780,21 @@
 	| begin_type_declmods declmod
 	;
 
-declmod:
-	  T_QUAL {
-		add_qualifier($1);
+begin_type_noclass_declspecs:
+	  begin_type_typespec {
+		add_type($1);
 	  }
-	| T_SCLASS {
-		add_storage_class($1);
+	| type_attribute begin_type_noclass_declspecs
+	| begin_type_noclass_declmods type_specifier {
+		add_type($2);
 	  }
-	| type_attribute_list
+	| begin_type_noclass_declspecs T_QUAL {
+		add_qualifier($2);
+	  }
+	| begin_type_noclass_declspecs notype_type_specifier {
+		add_type($2);
+	  }
+	| begin_type_noclass_declspecs type_attribute
 	;
 
 begin_type_typespec:
@@ -597,6 +806,25 @@
 	  }
 	;
 
+begin_type_noclass_declmods:
+	  begin_type T_QUAL {
+		add_qualifier($2);
+	  }
+	| begin_type_noclass_declmods T_QUAL {
+		add_qualifier($2);
+	  }
+	;
+
+declmod:
+	  T_QUAL {
+		add_qualifier($1);
+	  }
+	| T_SCLASS {
+		add_storage_class($1);
+	  }
+	| type_attribute_list
+	;
+
 type_attribute_list:
 	  type_attribute
 	| type_attribute_list type_attribute
@@ -620,6 +848,23 @@
 	| T_NORETURN
 	;
 
+align_as:			/* See alignment-specifier in C11 6.7.5 */
+	  type_specifier
+	| constant_expr
+	;
+
+begin_type:
+	  /* empty */ {
+		begin_type();
+	  }
+	;
+
+end_type:
+	  /* empty */ {
+		end_type();
+	  }
+	;
+
 type_specifier:			/* C99 6.7.2 */
 	  notype_type_specifier
 	| T_TYPENAME {
@@ -627,7 +872,7 @@
 	  }
 	;
 
-notype_type_specifier:
+notype_type_specifier:		/* see C99 6.7.2 */
 	  T_TYPE {
 		$$ = gettyp($1);
 	  }
@@ -682,19 +927,19 @@
 	  }
 	;
 
-braced_struct_declaration_list:
+braced_struct_declaration_list:	/* see C99 6.7.2.1 */
 	  struct_declaration_lbrace struct_declaration_list_with_rbrace {
 		$$ = $2;
 	  }
 	;
 
-struct_declaration_lbrace:
+struct_declaration_lbrace:	/* see C99 6.7.2.1 */
 	  T_LBRACE {
 		symtyp = FVFT;
 	  }
 	;
 
-struct_declaration_list_with_rbrace:
+struct_declaration_list_with_rbrace:	/* see C99 6.7.2.1 */
 	  struct_declaration_list T_SEMI T_RBRACE
 	| struct_declaration_list T_RBRACE {
 		if (sflag) {
@@ -711,14 +956,14 @@
 	  }
 	;
 
-struct_declaration_list:
+struct_declaration_list:	/* C99 6.7.2.1 */
 	  struct_declaration
 	| struct_declaration_list T_SEMI struct_declaration {
 		$$ = lnklst($1, $3);
 	  }
 	;
 
-struct_declaration:
+struct_declaration:		/* C99 6.7.2.1 */
 	  begin_type_noclass_declmods end_type {
 		/* too late, i know, but getsym() compensates it */
 		symtyp = FMEMBER;
@@ -758,32 +1003,7 @@
 	  }
 	;
 
-begin_type_noclass_declspecs:
-	  begin_type_typespec {
-		add_type($1);
-	  }
-	| type_attribute begin_type_noclass_declspecs
-	| begin_type_noclass_declmods type_specifier {
-		add_type($2);
-	  }
-	| begin_type_noclass_declspecs T_QUAL {
-		add_qualifier($2);
-	  }
-	| begin_type_noclass_declspecs notype_type_specifier {
-		add_type($2);
-	  }
-	| begin_type_noclass_declspecs type_attribute
-	;
-
-begin_type_noclass_declmods:
-	  begin_type T_QUAL {
-		add_qualifier($2);
-	  }
-	| begin_type_noclass_declmods T_QUAL {
-		add_qualifier($2);
-	  }
-	;
-
+/* TODO: rename 'decls' to 'declarators', everywhere. */
 notype_member_decls:
 	  notype_member_decl {
 		$$ = declarator_1_struct_union($1);
@@ -830,7 +1050,7 @@
 	  }
 	;
 
-enum_specifier:		/* C99 6.7.2.2 */
+enum_specifier:			/* C99 6.7.2.2 */
 	  enum identifier_sym {
 		$$ = mktag($2, ENUM, false, false);
 	  }
@@ -850,27 +1070,27 @@
 	  }
 	;
 
-enum:
+enum:				/* helper for C99 6.7.2.2 */
 	  T_ENUM {
 		symtyp = FTAG;
 		begin_declaration_level(CTCONST);
 	  }
 	;
 
-enum_declaration:
+enum_declaration:		/* helper for C99 6.7.2.2 */
 	  enum_decl_lbrace enums_with_opt_comma T_RBRACE {
 		$$ = $2;
 	  }
 	;
 
-enum_decl_lbrace:
+enum_decl_lbrace:		/* helper for C99 6.7.2.2 */
 	  T_LBRACE {
 		symtyp = FVFT;
 		enumval = 0;
 	  }
 	;
 
-enums_with_opt_comma:
+enums_with_opt_comma:		/* helper for C99 6.7.2.2 */
 	  enumerator_list
 	| enumerator_list T_COMMA {
 		if (sflag) {
@@ -884,7 +1104,7 @@
 	  }
 	;
 
-enumerator_list:
+enumerator_list:		/* C99 6.7.2.2 */
 	  enumerator
 	| enumerator_list T_COMMA enumerator {
 		$$ = lnklst($1, $3);
@@ -903,7 +1123,50 @@
 	  }
 	;
 
+type_qualifier:			/* C99 6.7.3 */
+	  T_QUAL {
+		$$ = xcalloc(1, sizeof(*$$));
+		if ($1 == CONST) {
+			$$->p_const = true;
+		} else if ($1 == VOLATILE) {
+			$$->p_volatile = true;
+		} else {
+			lint_assert($1 == RESTRICT || $1 == THREAD);
+		}
+	  }
+	;
 
+pointer:			/* C99 6.7.5 */
+	  asterisk type_qualifier_list_opt {
+		$$ = merge_qualified_pointer($1, $2);
+	  }
+	| asterisk type_qualifier_list_opt pointer {
+		$$ = merge_qualified_pointer($1, $2);
+		$$ = merge_qualified_pointer($$, $3);
+	  }
+	;
+
+asterisk:			/* helper for 'pointer' */
+	  T_ASTERISK {
+		$$ = xcalloc(1, sizeof(*$$));
+		$$->p_pointer = true;
+	  }
+	;
+
+type_qualifier_list_opt:	/* see C99 6.7.5 */
+	  /* empty */ {
+		$$ = NULL;
+	  }
+	| type_qualifier_list
+	;
+
+type_qualifier_list:		/* C99 6.7.5 */
+	  type_qualifier
+	| type_qualifier_list type_qualifier {
+		$$ = merge_qualified_pointer($1, $2);
+	  }
+	;
+
 /*
  * For an explanation of 'notype' in the following rules, see the Bison
  * manual, section 7.1 "Semantic Info in Token Kinds".
@@ -1023,16 +1286,11 @@
 	  }
 	;
 
-array_size:
-	  type_qualifier_list_opt T_SCLASS constant_expr {
-		/* C11 6.7.6.3p7 */
-		if ($2 != STATIC)
-			yyerror("Bad attribute");
-		/* static array size is a C11 extension */
-		c11ism(343);
-		$$ = $3;
+notype_param_decl:
+	  direct_notype_param_decl
+	| pointer direct_notype_param_decl {
+		$$ = add_pointer($2, $1);
 	  }
-	| constant_expr
 	;
 
 direct_param_decl:
@@ -1058,13 +1316,6 @@
 	  }
 	;
 
-notype_param_decl:
-	  direct_notype_param_decl
-	| pointer direct_notype_param_decl {
-		$$ = add_pointer($2, $1);
-	  }
-	;
-
 direct_notype_param_decl:
 	  identifier {
 		$$ = declarator_name(getsym($1));
@@ -1085,77 +1336,113 @@
 	  }
 	;
 
-pointer:			/* C99 6.7.5 */
-	  asterisk type_qualifier_list_opt {
-		$$ = merge_qualified_pointer($1, $2);
+param_list:
+	  id_list_lparen identifier_list T_RPAREN {
+		$$ = $2;
 	  }
-	| asterisk type_qualifier_list_opt pointer {
-		$$ = merge_qualified_pointer($1, $2);
-		$$ = merge_qualified_pointer($$, $3);
-	  }
+	| abstract_decl_param_list
 	;
 
-asterisk:
-	  T_ASTERISK {
-		$$ = xcalloc(1, sizeof(*$$));
-		$$->p_pointer = true;
+id_list_lparen:
+	  T_LPAREN {
+		block_level++;
+		begin_declaration_level(PROTO_ARG);
 	  }
 	;
 
-type_qualifier_list_opt:
-	  /* empty */ {
-		$$ = NULL;
+array_size:
+	  type_qualifier_list_opt T_SCLASS constant_expr {
+		/* C11 6.7.6.3p7 */
+		if ($2 != STATIC)
+			yyerror("Bad attribute");
+		/* static array size is a C11 extension */
+		c11ism(343);
+		$$ = $3;
 	  }
-	| type_qualifier_list
+	| constant_expr
 	;
 
-type_qualifier_list:		/* C99 6.7.5 */
-	  type_qualifier
-	| type_qualifier_list type_qualifier {
-		$$ = merge_qualified_pointer($1, $2);
+identifier_list:		/* C99 6.7.5 */
+	  T_NAME {
+		$$ = old_style_function_name(getsym($1));
 	  }
+	| identifier_list T_COMMA T_NAME {
+		$$ = lnklst($1, old_style_function_name(getsym($3)));
+	  }
+	| identifier_list error
 	;
 
-type_qualifier:
-	  T_QUAL {
-		$$ = xcalloc(1, sizeof(*$$));
-		if ($1 == CONST) {
-			$$->p_const = true;
-		} else if ($1 == VOLATILE) {
-			$$->p_volatile = true;
-		} else {
-			lint_assert($1 == RESTRICT || $1 == THREAD);
-		}
+/* XXX: C99 requires an additional specifier-qualifier-list. */
+type_name:			/* C99 6.7.6 */
+	  {
+		begin_declaration_level(ABSTRACT);
+	  } abstract_declaration {
+		end_declaration_level();
+		$$ = $2->s_type;
 	  }
 	;
 
-align_as:			/* See alignment-specifier in C11 6.7.5 */
-	  type_specifier
-	| constant_expr
-	;
-
-param_list:
-	  id_list_lparen identifier_list T_RPAREN {
-		$$ = $2;
+abstract_declaration:
+	  begin_type_noclass_declmods end_type {
+		$$ = declare_1_abstract(abstract_name());
 	  }
-	| abstract_decl_param_list
+	| begin_type_noclass_declspecs end_type {
+		$$ = declare_1_abstract(abstract_name());
+	  }
+	| begin_type_noclass_declmods end_type abstract_declarator {
+		$$ = declare_1_abstract($3);
+	  }
+	| begin_type_noclass_declspecs end_type abstract_declarator {
+		$$ = declare_1_abstract($3);
+	  }
 	;
 
-id_list_lparen:
-	  T_LPAREN {
-		block_level++;
-		begin_declaration_level(PROTO_ARG);
+abstract_declarator:		/* C99 6.7.6 */
+	  pointer {
+		$$ = add_pointer(abstract_name(), $1);
 	  }
+	| direct_abstract_declarator
+	| pointer direct_abstract_declarator {
+		$$ = add_pointer($2, $1);
+	  }
+	| T_TYPEOF cast_expression {	/* GCC extension */
+		$$ = mktempsym($2->tn_type);
+	  }
 	;
 
-identifier_list:
-	  T_NAME {
-		$$ = old_style_function_name(getsym($1));
+direct_abstract_declarator:	/* C99 6.7.6 */
+	  T_LPAREN abstract_declarator T_RPAREN {
+		$$ = $2;
 	  }
-	| identifier_list T_COMMA T_NAME {
-		$$ = lnklst($1, old_style_function_name(getsym($3)));
+	| T_LBRACK T_RBRACK {
+		$$ = add_array(abstract_name(), false, 0);
 	  }
-	| identifier_list error
+	| T_LBRACK array_size T_RBRACK {
+		$$ = add_array(abstract_name(), true, to_int_constant($2, false));
+	  }
+	| type_attribute direct_abstract_declarator {
+		$$ = $2;
+	  }
+	| direct_abstract_declarator T_LBRACK T_RBRACK {
+		$$ = add_array($1, false, 0);
+	  }
+	| direct_abstract_declarator T_LBRACK T_ASTERISK T_RBRACK { /* C99 */
+		$$ = add_array($1, false, 0);
+	  }
+	| direct_abstract_declarator T_LBRACK array_size T_RBRACK {
+		$$ = add_array($1, true, to_int_constant($3, false));
+	  }
+	| abstract_decl_param_list asm_or_symbolrename_opt {
+		$$ = add_function(symbolrename(abstract_name(), $2), $1);
+		end_declaration_level();
+		block_level--;
+	  }
+	| direct_abstract_declarator abstract_decl_param_list asm_or_symbolrename_opt {
+		$$ = add_function(symbolrename($1, $3), $2);
+		end_declaration_level();
+		block_level--;
+	  }
+	| direct_abstract_declarator type_attribute_list
 	;
 
 abstract_decl_param_list:
@@ -1197,6 +1484,7 @@
 	  }
 	;
 
+/* XXX: C99 6.7.5 defines the same name, but it looks different. */
 parameter_type_list:
 	  parameter_declaration
 	| parameter_type_list T_COMMA parameter_declaration {
@@ -1233,19 +1521,6 @@
 	  }
 	;
 
-asm_or_symbolrename_opt:		/* expect only one */
-	  /* empty */ {
-		$$ = NULL;
-	  }
-	| T_ASM T_LPAREN T_STRING T_RPAREN {
-		freeyyv(&$3, T_STRING);
-		$$ = NULL;
-	  }
-	| T_SYMBOLRENAME T_LPAREN T_NAME T_RPAREN {
-		$$ = $3;
-	  }
-	;
-
 initializer:			/* C99 6.7.8 "Initialization" */
 	  expr %prec T_COMMA {
 		init_expr($1);
@@ -1262,7 +1537,7 @@
 	| initializer_list T_COMMA initializer_list_item
 	;
 
-initializer_list_item:
+initializer_list_item:		/* helper */
 	  designation initializer
 	| initializer
 	;
@@ -1309,91 +1584,37 @@
 	  }
 	;
 
-init_lbrace:
+init_lbrace:			/* helper */
 	  T_LBRACE {
 		init_lbrace();
 	  }
 	;
 
-init_rbrace:
+init_rbrace:			/* helper */
 	  T_RBRACE {
 		init_rbrace();
 	  }
 	;
 
-type_name:			/* C99 6.7.6 */
-	  {
-		begin_declaration_level(ABSTRACT);
-	  } abstract_declaration {
-		end_declaration_level();
-		$$ = $2->s_type;
+asm_or_symbolrename_opt:	/* GCC extensions */
+	  /* empty */ {
+		$$ = NULL;
 	  }
-	;
-
-abstract_declaration:
-	  begin_type_noclass_declmods end_type {
-		$$ = declare_1_abstract(abstract_name());
+	| T_ASM T_LPAREN T_STRING T_RPAREN {
+		freeyyv(&$3, T_STRING);
+		$$ = NULL;
 	  }
-	| begin_type_noclass_declspecs end_type {
-		$$ = declare_1_abstract(abstract_name());
+	| T_SYMBOLRENAME T_LPAREN T_NAME T_RPAREN {
+		$$ = $3;
 	  }
-	| begin_type_noclass_declmods end_type abstract_declarator {
-		$$ = declare_1_abstract($3);
-	  }
-	| begin_type_noclass_declspecs end_type abstract_declarator {
-		$$ = declare_1_abstract($3);
-	  }
 	;
 
-abstract_declarator:		/* C99 6.7.6 */
-	  pointer {
-		$$ = add_pointer(abstract_name(), $1);
-	  }
-	| direct_abstract_declarator
-	| pointer direct_abstract_declarator {
-		$$ = add_pointer($2, $1);
-	  }
-	| T_TYPEOF cast_expression {	/* GCC extension */
-		$$ = mktempsym($2->tn_type);
-	  }
+statement:			/* C99 6.8 */
+	  expression_statement
+	| non_expr_statement
 	;
 
-direct_abstract_declarator:		/* C99 6.7.6 */
-	  T_LPAREN abstract_declarator T_RPAREN {
-		$$ = $2;
-	  }
-	| T_LBRACK T_RBRACK {
-		$$ = add_array(abstract_name(), false, 0);
-	  }
-	| T_LBRACK array_size T_RBRACK {
-		$$ = add_array(abstract_name(), true, to_int_constant($2, false));
-	  }
-	| type_attribute direct_abstract_declarator {
-		$$ = $2;
-	  }
-	| direct_abstract_declarator T_LBRACK T_RBRACK {
-		$$ = add_array($1, false, 0);
-	  }
-	| direct_abstract_declarator T_LBRACK T_ASTERISK T_RBRACK { /* C99 */
-		$$ = add_array($1, false, 0);
-	  }
-	| direct_abstract_declarator T_LBRACK array_size T_RBRACK {
-		$$ = add_array($1, true, to_int_constant($3, false));
-	  }
-	| abstract_decl_param_list asm_or_symbolrename_opt {
-		$$ = add_function(symbolrename(abstract_name(), $2), $1);
-		end_declaration_level();
-		block_level--;
-	  }
-	| direct_abstract_declarator abstract_decl_param_list asm_or_symbolrename_opt {
-		$$ = add_function(symbolrename($1, $3), $2);
-		end_declaration_level();
-		block_level--;
-	  }
-	| direct_abstract_declarator type_attribute_list
-	;
-
-non_expr_statement:
+non_expr_statement:		/* helper for C99 6.8 */
 	  type_attribute T_SEMI
 	| labeled_statement
 	| compound_statement
@@ -1405,11 +1626,6 @@
 	| asm_statement
 	;
 
-statement:			/* C99 6.8 */
-	  expression_statement
-	| non_expr_statement
-	;
-
 labeled_statement:		/* C99 6.8.1 */
 	  label type_attribute_opt statement
 	;
@@ -1534,12 +1750,6 @@
 	  }
 	;
 
-do_statement:			/* C99 6.8.5 */
-	  do statement {
-		clear_warning_flags();
-	  }
-	;
-
 iteration_statement:		/* C99 6.8.5 */
 	  while_expr statement {
 		clear_warning_flags();
@@ -1571,20 +1781,26 @@
 	  }
 	;
 
-while_expr:
+while_expr:			/* see C99 6.8.5 */
 	  T_WHILE T_LPAREN expr T_RPAREN {
 		while1($3);
 		clear_warning_flags();
 	  }
 	;
 
+do_statement:			/* see C99 6.8.5 */
+	  do statement {
+		clear_warning_flags();
+	  }
+	;
+
 do:				/* see C99 6.8.5 */
 	  T_DO {
 		do1();
 	  }
 	;
 
-do_while_expr:
+do_while_expr:			/* see C99 6.8.5 */
 	  T_WHILE T_LPAREN expr T_RPAREN T_SEMI {
 		$$ = $3;
 	  }
@@ -1632,13 +1848,13 @@
 	  }
 	;
 
-goto:
+goto:				/* see C99 6.8.6 */
 	  T_GOTO {
 		symtyp = FLABEL;
 	  }
 	;
 
-asm_statement:
+asm_statement:			/* GCC extension */
 	  T_ASM T_LPAREN read_until_rparen T_SEMI {
 		setasm();
 	  }
@@ -1648,359 +1864,165 @@
 	| T_ASM error
 	;
 
-read_until_rparen:
+read_until_rparen:		/* helper for 'asm_statement' */
 	  /* empty */ {
 		ignore_up_to_rparen();
 	  }
 	;
 
-constant_expr_list_opt:
-	  /* empty */
-	| constant_expr_list
+translation_unit:		/* C99 6.9 */
+	  external_declaration
+	| translation_unit external_declaration
 	;
 
-constant_expr_list:
-	  constant_expr
-	| constant_expr_list T_COMMA constant_expr
-	;
-
-constant_expr:			/* C99 6.6 */
-	  expr %prec T_ASSIGN
-	;
-
-expr_opt:
-	  /* empty */ {
-		$$ = NULL;
+external_declaration:		/* C99 6.9 */
+	  asm_statement
+	| function_definition {
+		global_clean_up_decl(false);
+		clear_warning_flags();
 	  }
-	| expr
-	;
-
-expr:				/* C99 6.5 */
-	  expr T_ASTERISK expr {
-		$$ = build(MULT, $1, $3);
+	| top_level_declaration {
+		global_clean_up_decl(false);
+		clear_warning_flags();
 	  }
-	| expr T_MULTIPLICATIVE expr {
-		$$ = build($2, $1, $3);
-	  }
-	| expr T_ADDITIVE expr {
-		$$ = build($2, $1, $3);
-	  }
-	| expr T_SHIFT expr {
-		$$ = build($2, $1, $3);
-	  }
-	| expr T_RELATIONAL expr {
-		$$ = build($2, $1, $3);
-	  }
-	| expr T_EQUALITY expr {
-		$$ = build($2, $1, $3);
-	  }
-	| expr T_AMPER expr {
-		$$ = build(BITAND, $1, $3);
-	  }
-	| expr T_BITXOR expr {
-		$$ = build(BITXOR, $1, $3);
-	  }
-	| expr T_BITOR expr {
-		$$ = build(BITOR, $1, $3);
-	  }
-	| expr T_LOGAND expr {
-		$$ = build(LOGAND, $1, $3);
-	  }
-	| expr T_LOGOR expr {
-		$$ = build(LOGOR, $1, $3);
-	  }
-	| expr T_QUEST expr T_COLON expr {
-		$$ = build(QUEST, $1, build(COLON, $3, $5));
-	  }
-	| expr T_ASSIGN expr {
-		$$ = build(ASSIGN, $1, $3);
-	  }
-	| expr T_OPASSIGN expr {
-		$$ = build($2, $1, $3);
-	  }
-	| expr T_COMMA expr {
-		$$ = build(COMMA, $1, $3);
-	  }
-	| cast_expression
 	;
 
-assignment_expression:		/* C99 6.5.16 */
-	  expr %prec T_ASSIGN
-	;
-
-/* K&R 7.1, C90 ???, C99 6.5.1, C11 6.5.1, C18 6.5.1 */
-primary_expression:
-	  T_NAME {
-		/* XXX really necessary? */
-		if (yychar < 0)
-			yychar = yylex();
-		$$ = new_name_node(getsym($1), yychar);
+/*
+ * On the top level, lint allows several forms of declarations that it doesn't
+ * allow in functions.  For example, a single ';' is an empty declaration and
+ * is supported by some compilers, but in a function it would be an empty
+ * statement, not a declaration.  This makes a difference in C90 mode, where
+ * a statement must not be followed by a declaration.
+ *
+ * See 'declaration' for all other declarations.
+ */
+top_level_declaration:		/* C99 6.9 calls this 'declaration' */
+	  T_SEMI {
+		if (sflag) {
+			/* empty declaration */
+			error(0);
+		} else if (!tflag) {
+			/* empty declaration */
+			warning(0);
+		}
 	  }
-	| T_CON {
-		$$ = expr_new_constant(gettyp($1->v_tspec), $1);
+	| begin_type end_type notype_init_decls T_SEMI {
+		if (sflag) {
+			/* old style declaration; add 'int' */
+			error(1);
+		} else if (!tflag) {
+			/* old style declaration; add 'int' */
+			warning(1);
+		}
 	  }
-	| string {
-		$$ = new_string_node($1);
+	| begin_type_declmods end_type T_SEMI {
+		if (dcs->d_scl == TYPEDEF) {
+			/* typedef declares no type name */
+			warning(72);
+		} else {
+			/* empty declaration */
+			warning(2);
+		}
 	  }
-	| T_LPAREN expr T_RPAREN {
-		if ($2 != NULL)
-			$2->tn_parenthesized = true;
-		$$ = $2;
+	| begin_type_declmods end_type notype_init_decls T_SEMI
+	| begin_type_declaration_specifiers end_type T_SEMI {
+		if (dcs->d_scl == TYPEDEF) {
+			/* typedef declares no type name */
+			warning(72);
+		} else if (!dcs->d_nonempty_decl) {
+			/* empty declaration */
+			warning(2);
+		}
 	  }
-	| generic_selection
-	/* GCC primary-expression, see c_parser_postfix_expression */
-	| T_BUILTIN_OFFSETOF T_LPAREN type_name T_COMMA identifier T_RPAREN {
-		symtyp = FMEMBER;
-		$$ = build_offsetof($3, getsym($5));
+	| begin_type_declaration_specifiers end_type type_init_decls T_SEMI
+	| error T_SEMI {
+		global_clean_up();
 	  }
-	;
-
-/* K&R ---, C90 ---, C99 ---, C11 6.5.1.1, C18 6.5.1.1 */
-generic_selection:
-	  T_GENERIC T_LPAREN assignment_expression T_COMMA
-	    generic_assoc_list T_RPAREN {
-	  	/* generic selection requires C11 or later */
-	  	c11ism(345);
-		$$ = build_generic_selection($3, $5);
+	| error T_RBRACE {
+		global_clean_up();
 	  }
 	;
 
-/* K&R ---, C90 ---, C99 ---, C11 6.5.1.1, C18 6.5.1.1 */
-generic_assoc_list:
-	  generic_association
-	| generic_assoc_list T_COMMA generic_association {
-		$3->ga_prev = $1;
-		$$ = $3;
-	  }
-	;
-
-/* K&R ---, C90 ---, C99 ---, C11 6.5.1.1, C18 6.5.1.1 */
-generic_association:
-	  type_name T_COLON assignment_expression {
-		$$ = getblk(sizeof(*$$));
-		$$->ga_arg = $1;
-		$$->ga_result = $3;
-	  }
-	| T_DEFAULT T_COLON assignment_expression {
-		$$ = getblk(sizeof(*$$));
-		$$->ga_arg = NULL;
-		$$->ga_result = $3;
-	  }
-	;
-
-/* K&R 7.1, C90 ???, C99 6.5.2, C11 6.5.2, C18 6.5.2 */
-postfix_expression:
-	  primary_expression
-	| postfix_expression T_LBRACK expr T_RBRACK {
-		$$ = build(INDIR, build(PLUS, $1, $3), NULL);
-	  }
-	| postfix_expression T_LPAREN T_RPAREN {
-		$$ = new_function_call_node($1, NULL);
-	  }
-	| postfix_expression T_LPAREN argument_expression_list T_RPAREN {
-		$$ = new_function_call_node($1, $3);
-	  }
-	| postfix_expression point_or_arrow T_NAME {
-		if ($1 != NULL) {
-			sym_t	*msym;
-			/*
-			 * XXX struct_or_union_member should be integrated
-			 * in build()
-			 */
-			if ($2 == ARROW) {
-				/*
-				 * must do this before struct_or_union_member
-				 * is called
-				 */
-				$1 = cconv($1);
-			}
-			msym = struct_or_union_member($1, $2, getsym($3));
-			$$ = build($2, $1, new_name_node(msym, 0));
-		} else {
-			$$ = NULL;
+function_definition:		/* C99 6.9.1 */
+	  func_decl {
+		if ($1->s_type->t_tspec != FUNC) {
+			/* syntax error '%s' */
+			error(249, yytext);
+			YYERROR;
 		}
-	  }
-	| postfix_expression T_INCDEC {
-		$$ = build($2 == INC ? INCAFT : DECAFT, $1, NULL);
-	  }
-	| T_LPAREN type_name T_RPAREN {	/* C99 6.5.2.5 "Compound literals" */
-		sym_t *tmp = mktempsym($2);
-		begin_initialization(tmp);
-		cgram_declare(tmp, true, NULL);
-	  } init_lbrace initializer_list comma_opt init_rbrace {
-		if (!Sflag)
-			 /* compound literals are a C9X/GCC extension */
-			 gnuism(319);
-		$$ = new_name_node(*current_initsym(), 0);
-		end_initialization();
-	  }
-	| T_LPAREN compound_statement_lbrace gcc_statement_expr_list {
-		block_level--;
-		mem_block_level--;
-		begin_initialization(mktempsym(dup_type($3->tn_type)));
-		mem_block_level++;
+		if ($1->s_type->t_typedef) {
+			/* ()-less function definition */
+			error(64);
+			YYERROR;
+		}
+		funcdef($1);
 		block_level++;
-		/* ({ }) is a GCC extension */
-		gnuism(320);
-	  } compound_statement_rbrace T_RPAREN {
-		$$ = new_name_node(*current_initsym(), 0);
-		end_initialization();
+		begin_declaration_level(ARG);
+		if (lwarn == LWARN_NONE)
+			$1->s_used = true;
+	  } arg_declaration_list_opt {
+		end_declaration_level();
+		block_level--;
+		check_func_lint_directives();
+		check_func_old_style_arguments();
+		begin_control_statement(CS_FUNCTION_BODY);
+	  } compound_statement {
+		funcend();
+		end_control_statement(CS_FUNCTION_BODY);
 	  }
 	;
 
-/* K&R 7.1, C90 ???, C99 6.5.2, C11 6.5.2, C18 6.5.2 */
-argument_expression_list:
-	  expr %prec T_COMMA {
-		$$ = new_function_argument_node(NULL, $1);
+func_decl:
+	  begin_type end_type notype_decl {
+		$$ = $3;
 	  }
-	| argument_expression_list T_COMMA expr {
-		$$ = new_function_argument_node($1, $3);
+	| begin_type_declmods end_type notype_decl {
+		$$ = $3;
 	  }
+	| begin_type_declaration_specifiers end_type type_decl {
+		$$ = $3;
+	  }
 	;
 
-/* K&R 7.2, C90 ???, C99 6.5.3, C11 6.5.3, C18 6.5.3 */
-unary_expression:
-	  postfix_expression
-	| T_INCDEC unary_expression {
-		$$ = build($1 == INC ? INCBEF : DECBEF, $2, NULL);
-	  }
-	| T_AMPER cast_expression {
-		$$ = build(ADDR, $2, NULL);
-	  }
-	| T_ASTERISK cast_expression {
-		$$ = build(INDIR, $2, NULL);
-	  }
-	| T_ADDITIVE cast_expression {
-		if (tflag && $1 == PLUS) {
-			/* unary + is illegal in traditional C */
-			warning(100);
-		}
-		$$ = build($1 == PLUS ? UPLUS : UMINUS, $2, NULL);
-	  }
-	| T_COMPLEMENT cast_expression {
-		$$ = build(COMPL, $2, NULL);
-	  }
-	| T_LOGNOT cast_expression {
-		$$ = build(NOT, $2, NULL);
-	  }
-	| T_REAL cast_expression {	/* GCC c_parser_unary_expression */
-		$$ = build(REAL, $2, NULL);
-	  }
-	| T_IMAG cast_expression {	/* GCC c_parser_unary_expression */
-		$$ = build(IMAG, $2, NULL);
-	  }
-	| T_EXTENSION cast_expression {	/* GCC c_parser_unary_expression */
-		$$ = $2;
-	  }
-	| T_SIZEOF unary_expression {
-		$$ = $2 == NULL ? NULL : build_sizeof($2->tn_type);
-		if ($$ != NULL)
-			check_expr_misc($2, false, false, false, false, false, true);
-	  }
-	| T_SIZEOF T_LPAREN type_name T_RPAREN {
-		$$ = build_sizeof($3);
-	  }
-	/* K&R ---, C90 ---, C99 ---, C11 6.5.3, C18 6.5.3 */
-	| T_ALIGNOF T_LPAREN type_name T_RPAREN {
-		$$ = build_alignof($3);
-	  }
+arg_declaration_list_opt:	/* C99 6.9.1p13 example 1 */
+	  /* empty */
+	| arg_declaration_list
 	;
 
-/* K&R 7.2, C90 ???, C99 6.5.4, C11 6.5.4, C18 6.5.4 */
-cast_expression:
-	  unary_expression
-	| T_LPAREN type_name T_RPAREN cast_expression {
-		$$ = cast($4, $2);
-	  }
+arg_declaration_list:		/* C99 6.9.1p13 example 1 */
+	  arg_declaration
+	| arg_declaration_list arg_declaration
+	/* XXX or better "arg_declaration error" ? */
+	| error
 	;
 
 /*
- * The inner part of a GCC statement-expression of the form ({ ... }).
- *
- * https://gcc.gnu.org/onlinedocs/gcc/Statement-Exprs.html
+ * "arg_declaration" is separated from "declaration" because it
+ * needs other error handling.
  */
-gcc_statement_expr_list:
-	  gcc_statement_expr_item
-	| gcc_statement_expr_list gcc_statement_expr_item {
-		$$ = $2;
+arg_declaration:
+	  begin_type_declmods end_type T_SEMI {
+		/* empty declaration */
+		warning(2);
 	  }
-	;
-
-gcc_statement_expr_item:
-	  declaration {
-		clear_warning_flags();
-		$$ = NULL;
-	  }
-	| non_expr_statement {
-		$$ = expr_zalloc_tnode();
-		$$->tn_type = gettyp(VOID);
-	  }
-	| expr T_SEMI {
-		if ($1 == NULL) {	/* in case of syntax errors */
-			$$ = expr_zalloc_tnode();
-			$$->tn_type = gettyp(VOID);
+	| begin_type_declmods end_type notype_init_decls T_SEMI
+	| begin_type_declaration_specifiers end_type T_SEMI {
+		if (!dcs->d_nonempty_decl) {
+			/* empty declaration */
+			warning(2);
 		} else {
-			/* XXX: do that only on the last name */
-			if ($1->tn_op == NAME)
-				$1->tn_sym->s_used = true;
-			$$ = $1;
-			expr($1, false, false, false, false);
-			seen_fallthrough = false;
+			/* '%s' declared in argument declaration list */
+			warning(3, type_name(dcs->d_type));
 		}
 	  }
-	;
-
-string:
-	  T_STRING
-	| T_STRING string2 {
-		$$ = cat_strings($1, $2);
-	  }
-	;
-
-string2:
-	  T_STRING {
-		if (tflag) {
-			/* concatenated strings are illegal in traditional C */
-			warning(219);
+	| begin_type_declaration_specifiers end_type type_init_decls T_SEMI {
+		if (dcs->d_nonempty_decl) {
+			/* '%s' declared in argument declaration list */
+			warning(3, type_name(dcs->d_type));
 		}
-		$$ = $1;
 	  }
-	| string2 T_STRING {
-		$$ = cat_strings($1, $2);
-	  }
-	;
-
-point_or_arrow:
-	  T_POINT {
-		symtyp = FMEMBER;
-		$$ = POINT;
-	  }
-	| T_ARROW {
-		symtyp = FMEMBER;
-		$$ = ARROW;
-	  }
-	;
-
-identifier_sym:
-	  identifier {
-		$$ = getsym($1);
-	  }
-	;
-
-identifier:			/* C99 6.4.2.1 */
-	  T_NAME {
-		$$ = $1;
-		cgram_debug("name '%s'", $$->sb_name);
-	  }
-	| T_TYPENAME {
-		$$ = $1;
-		cgram_debug("typename '%s'", $$->sb_name);
-	  }
-	;
-
-comma_opt:
-	  /* empty */
-	| T_COMMA
+	| begin_type_declmods error
+	| begin_type_declaration_specifiers error
 	;
 
 gcc_attribute_spec_list: