/* $Id: parse.y,v 1.131 2012/02/17 14:25:56 vrsieh Exp $ 
 *
 * Copyright (C) 2007-2009 FAUcc Team <info@faumachine.org>.
 * This program is free software. You can redistribute it and/or modify it
 * under the terms of the GNU General Public License, either version 2 of
 * the License, or (at your option) any later version. See COPYING.
 */

%{
#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "scan.h"
#include "identifier.h"
#include "constraint.h"
#include "declaration.h"
#include "scope.h"
#include "stmt.h"
#include "expr.h"
%}

%union {
	int nil;
	unsigned long long integer;
	long double real;
	struct {
		unsigned int len;
		const char *string;
	} string;
	const char *identifier;

	int const_volatile;
	struct {
		enum {
			TYPE_SPEC_NONE,
			TYPE_SPEC_VOID,
			TYPE_SPEC_CHAR,
			TYPE_SPEC_INT,
			TYPE_SPEC_FLOAT,
			TYPE_SPEC_DOUBLE,
			TYPE_SPEC_STRUCT,
			TYPE_SPEC_UNION,
			TYPE_SPEC_ENUM,
			TYPE_SPEC_USER,
			TYPE_SPEC_ELIPSIS,
			TYPE_SPEC_VA_LIST,
		} type;
		const char *identifier;

		unsigned int mod_short;
		unsigned int mod_long;
		unsigned int mod_signed;
		unsigned int mod_unsigned;

		unsigned int mod_const;
		unsigned int mod_volatile;
		unsigned int attr_aligned;
		unsigned int attr_packed;

		enum type_storage storage;
		unsigned int mod_inline;
		unsigned int mod_noreturn;
	} type_spec_0;
	struct {
		struct type *type;

		enum type_storage storage;
		unsigned int mod_inline;
		unsigned int mod_noreturn;
	} type_spec_1;
	struct {
		int mod_noreturn;
		int attr_aligned;
		int attr_packed;
	} attrs;
	struct type *type;
	struct declaration *declarator;
	struct type *type_name;
	struct constraint *constraint;
	struct constraint_list *constraint_list;
	struct {
		struct declaration *first;
		struct declaration *last;
	} declarator_list;
	struct declaration *declaration;
	struct {
		struct declaration *first;
		struct declaration *last;
	} declaration_list;
	struct stmt *stmt;
	struct {
		struct stmt *first;
		struct stmt *last;
	} stmt_list;
	struct expr *expr;
}

%{
extern int line;
extern int column;

static struct scope *parse_scope;

static unsigned int enum_next;

void __attribute__((noreturn))
yyerror(const char *s)
{
	fprintf(stderr, "%d/%d: %s.\n", line, column, s);
	exit(1);
}

static void __attribute__((noreturn))
parse_unknown(const char *identifier)
{
	char msg[1024];

	sprintf(msg, "%s: unknown", identifier);
	yyerror(msg);
}
%}

%right '=' MUL_ASSIGN DIV_ASSIGN MOD_ASSIGN ADD_ASSIGN SUB_ASSIGN LEFT_ASSIGN RIGHT_ASSIGN AND_ASSIGN XOR_ASSIGN OR_ASSIGN
%right '?' ':'
%left OR_OP
%left AND_OP
%left '|'
%left '^'
%left '&'
%left EQ_OP NE_OP
%left '<' '>' LE_OP GE_OP
%left LEFT_OP RIGHT_OP
%left '+' '-'
%left '*' '/' '%'
%right CAST
%right SIZEOF BUILTIN_OFFSETOF BUILTIN_VA_ARG BUILTIN_CONSTANT_P
%right PTR_OP INC_OP DEC_OP

%token TYPEDEF EXTERN STATIC AUTO REGISTER
%token CHAR INT FLOAT DOUBLE VOID BUILTIN_VA_LIST
%token SHORT LONG SIGNED UNSIGNED CONST VOLATILE
%token ATTRIBUTE
%token STRUCT UNION ENUM
%token ELIPSIS
%token INLINE

%token CASE DEFAULT IF ELSE SWITCH WHILE DO FOR GOTO CONTINUE BREAK RETURN
%token ASM BUILTIN_VA_START BUILTIN_VA_END

%token <identifier> IDENTIFIER
%token <identifier> TYPE_NAME
%token <integer> INTEGER_LITERAL INTEGER_LITERAL_L INTEGER_LITERAL_LL
%token <integer> INTEGER_LITERAL_U INTEGER_LITERAL_UL INTEGER_LITERAL_ULL
%token <real> REAL_LITERAL
%token <string> STRING_LITERAL

%type <const_volatile> const_volatile_e
%type <type_spec_0> type_spec_mod
%type <type_spec_0> type_spec_type
%type <type_spec_0> type_spec_storage
%type <type_spec_0> type_spec_0
%type <type_spec_1> type_spec
%type <nil> enumerator_list_comma
%type <nil> enumerator_list
%type <nil> enumerator
%type <declarator_list> declarator_list_e
%type <declarator_list> declarator_list
%type <declarator> declarator
%type <declarator> declarator2
%type <declarator> declarator3
%type <declarator> declarator4
%type <type> abstract_declarator
%type <type> abstract_declarator2
%type <constraint_list> asm_constraint_list_e
%type <constraint_list> asm_constraint_list
%type <constraint> asm_constraint
%type <constraint_list> asm_change_list_e
%type <constraint_list> asm_change_list
%type <constraint> asm_change
%type <declaration_list> parameter_type_list_e
%type <declaration_list> parameter_type_list
%type <declaration_list> parameter_list
%type <declaration> parameter_declaration
%type <type_name> type_name
%type <integer> constant_primary_expr
%type <expr> primary_expr
%type <integer> constant_unary_expr
%type <expr> unary_expr
%type <expr> offsetof_member_designator
%type <integer> constant_multiplicative_expr
%type <expr> multiplicative_expr
%type <integer> constant_additive_expr
%type <expr> additive_expr
%type <integer> constant_shift_expr
%type <expr> shift_expr
%type <integer> constant_relational_expr
%type <expr> relational_expr
%type <integer> constant_equality_expr
%type <expr> equality_expr
%type <integer> constant_and_expr
%type <expr> and_expr
%type <integer> constant_exclusive_or_expr
%type <expr> exclusive_or_expr
%type <integer> constant_inclusive_or_expr
%type <expr> inclusive_or_expr
%type <expr> logical_and_expr
%type <expr> logical_or_expr
%type <integer> constant_conditional_expr
%type <expr> conditional_expr
%type <expr> assignment_expr
%type <expr> expr_list_e
%type <expr> expr_list
%type <expr> expr
%type <expr> expr_e
%type <integer> constant_expr
%type <expr> initializer_expr
%type <expr> initializer_expr_list
%type <attrs> attribute_e
%type <attrs> attribute
%type <attrs> attribute_element_list
%type <attrs> attribute_element
%type <integer> attribute_list
%type <stmt_list> statement_list
%type <stmt> statement
%type <stmt> compound_statement
%type <identifier> identifier_e
%type <identifier> identifier
%type <string> string

%start file

%%

constant_primary_expr
	: INTEGER_LITERAL
		{ $$ = $1; }
	| '(' constant_expr ')'
		{ $$ = $2; }
	;

primary_expr
	: INTEGER_LITERAL
		{ $$ = expr_new();
		  $$->type = EXPR_INTEGER;
		  $$->type_name = type_int();
		  $$->integer = $1; }
	| INTEGER_LITERAL_L
		{ $$ = expr_new();
		  $$->type = EXPR_INTEGER;
		  $$->type_name = type_long_int();
		  $$->integer = $1; }
	| INTEGER_LITERAL_LL
		{ $$ = expr_new();
		  $$->type = EXPR_INTEGER;
		  $$->type_name = type_long_long_int();
		  $$->integer = $1; }
	| INTEGER_LITERAL_U
		{ $$ = expr_new();
		  $$->type = EXPR_INTEGER;
		  $$->type_name = type_unsigned_int();
		  $$->integer = $1; }
	| INTEGER_LITERAL_UL
		{ $$ = expr_new();
		  $$->type = EXPR_INTEGER;
		  $$->type_name = type_unsigned_long_int();
		  $$->integer = $1; }
	| INTEGER_LITERAL_ULL
		{ $$ = expr_new();
		  $$->type = EXPR_INTEGER;
		  $$->type_name = type_unsigned_long_long_int();
		  $$->integer = $1; }
	/* | REAL_LITERAL_F - FIXME */
	| REAL_LITERAL
		{ $$ = expr_new();
		  $$->type = EXPR_REAL;
		  $$->type_name = type_double();
		  $$->real = $1; }
	/* | REAL_LITERAL_L - FIXME */
	| string
		{ $$ = expr_new();
		  $$->type = EXPR_STRING;
		  $$->string_len = $1.len;
		  $$->string = $1.string; }
	| IDENTIFIER
		{ int ret;

		  $$ = expr_new();
		  $$->type = EXPR_IDENTIFIER;
		  ret = scope_lookup_current($1, &$$->declaration);
		  if (ret < 0) {
		  	parse_unknown($1);
		  }
		  assert(0 <= ret); }
	| '(' expr ')'
		{ $$ = $2; }
	| primary_expr '[' expr ']'
		{ $$ = expr_new();
		  $$->type = EXPR_ARRAY;
		  $$->expr0 = $1;
		  $$->expr1 = $3; }
	| primary_expr '(' expr_list_e ')'
		{ $$ = expr_new();
		  $$->type = EXPR_FUNC;
		  $$->expr0 = $1;
		  $$->expr1 = $3; }
	| primary_expr '.' identifier
		{ $$ = expr_new();
		  $$->type = EXPR_DOT;
		  $$->expr0 = $1;
		  $$->member = $3; }
	| primary_expr PTR_OP identifier
		{ $$ = expr_new();
		  $$->type = EXPR_ARROW;
		  $$->expr0 = $1;
		  $$->member = $3; }
	;

constant_unary_expr
	: constant_primary_expr
		{ $$ = $1; }
	| '-' constant_unary_expr
		{ $$ = -$2; }
	| '~' constant_unary_expr
		{ $$ = ~$2; }
	/* | SIZEOF conditional_expr		%prec SIZEOF
		{ $$ = 0; } */
		/* FIXME */
	| SIZEOF '(' type_name ')'		%prec SIZEOF
		{ $$ = type_sizeof(scope_current, $3); }
	;

unary_expr
	: primary_expr
		{ $$ = $1; }
	| primary_expr INC_OP
		{ $$ = expr_new();
		  $$->type = EXPR_POST_INC;
		  $$->expr0 = $1; }
	| primary_expr DEC_OP
		{ $$ = expr_new();
		  $$->type = EXPR_POST_DEC;
		  $$->expr0 = $1; }
	| '*' unary_expr
		{ $$ = expr_new();
		  $$->type = EXPR_STAR;
		  $$->expr0 = $2; }
	| '&' unary_expr
		{ $$ = expr_new();
		  $$->type = EXPR_AMPHERSAND;
		  $$->expr0 = $2; }
	| '+' unary_expr /* Correct? FIXME */
		{ $$ = $2; }
	| '-' unary_expr
		{ $$ = expr_new();
		  $$->type = EXPR_NEG;
		  $$->expr0 = $2; }
	| '!' unary_expr
		{ $$ = expr_new();
		  $$->type = EXPR_NOT;
		  $$->expr0 = $2; }
	| '~' unary_expr
		{ $$ = expr_new();
		  $$->type = EXPR_INV;
		  $$->expr0 = $2; }
	| INC_OP unary_expr
		{ $$ = expr_new();
		  $$->type = EXPR_PRE_INC;
		  $$->expr0 = $2; }
	| DEC_OP unary_expr
		{ $$ = expr_new();
		  $$->type = EXPR_PRE_DEC;
		  $$->expr0 = $2; }
	| '(' type_name ')' unary_expr			%prec CAST
		{ $$ = expr_new();
		  $$->type = EXPR_TYPE_CONVERSION;
		  $$->type_name = $2;
		  $$->expr0 = $4; }
	| SIZEOF unary_expr				%prec SIZEOF
		{ $$ = expr_new();
		  $$->type = EXPR_SIZEOF_EXPR;
		  $$->expr0 = $2; }
	| SIZEOF '(' type_name ')'			%prec SIZEOF
		{ $$ = expr_new();
		  $$->type = EXPR_SIZEOF_TYPE;
		  $$->type_name = $3; }
	| BUILTIN_OFFSETOF '(' type_name ',' offsetof_member_designator ')'
		{ $$ = expr_new();
		  $$->type = EXPR_BUILTIN_OFFSETOF;
		  $$->type_name = $3;
		  $$->expr0 = $5; }
	| BUILTIN_VA_ARG '(' identifier ',' type_name ')'
		{ int ret;

		  $$ = expr_new();
		  $$->type = EXPR_BUILTIN_VA_ARG;
		  $$->type_name = $5;
		  $$->expr0 = expr_new();
		  $$->expr0->type = EXPR_IDENTIFIER;
		  ret = scope_lookup_current($3, &$$->expr0->declaration);
		  assert(0 <= ret); }
	| BUILTIN_CONSTANT_P '(' expr ')'
		{ $$ = expr_new();
		  $$->type = EXPR_BUILTIN_CONSTANT_P;
		  $$->expr0 = $3; }
	;

offsetof_member_designator
	: identifier
		{ $$ = expr_new();
		  $$->type = EXPR_ARROW;
		  $$->member = $1; }
	| offsetof_member_designator '.' identifier
		{ $$ = expr_new();
		  $$->type = EXPR_DOT;
		  $$->member = $3;
		  $$->expr0 = $1; }
	| offsetof_member_designator '[' expr ']'
		{ $$ = expr_new();
		  $$->type = EXPR_ARRAY;
		  $$->expr0 = $1;
		  $$->expr1 = $3; }
	;

constant_multiplicative_expr
	: constant_unary_expr
		{ $$ = $1; }
	| constant_multiplicative_expr '*' constant_unary_expr
		{ $$ = $1 * $3; }
	| constant_multiplicative_expr '/' constant_unary_expr
		{ $$ = $1 / $3; }
	| constant_multiplicative_expr '%' constant_unary_expr
		{ $$ = $1 % $3; }

multiplicative_expr
	: unary_expr
		{ $$ = $1; }
	| multiplicative_expr '*' unary_expr
		{ $$ = expr_new();
		  $$->type = EXPR_MUL;
		  $$->expr0 = $1;
		  $$->expr1 = $3; }
	| multiplicative_expr '/' unary_expr
		{ $$ = expr_new();
		  $$->type = EXPR_DIV;
		  $$->expr0 = $1;
		  $$->expr1 = $3; }
	| multiplicative_expr '%' unary_expr
		{ $$ = expr_new();
		  $$->type = EXPR_MOD;
		  $$->expr0 = $1;
		  $$->expr1 = $3; }
	;

constant_additive_expr
	: constant_multiplicative_expr
		{ $$ = $1; }
	| constant_additive_expr '+' constant_multiplicative_expr
		{ $$ = $1 + $3; }
	| constant_additive_expr '-' constant_multiplicative_expr
		{ $$ = $1 - $3; }
	;

additive_expr
	: multiplicative_expr
		{ $$ = $1; }
	| additive_expr '+' multiplicative_expr
		{ $$ = expr_new();
		  $$->type = EXPR_ADD;
		  $$->expr0 = $1;
		  $$->expr1 = $3; }
	| additive_expr '-' multiplicative_expr
		{ $$ = expr_new();
		  $$->type = EXPR_SUB;
		  $$->expr0 = $1;
		  $$->expr1 = $3; }
	;

constant_shift_expr
	: constant_additive_expr
		{ $$ = $1; }
	| constant_shift_expr LEFT_OP constant_additive_expr
		{ $$ = $1 << $3; }
	| constant_shift_expr RIGHT_OP constant_additive_expr
		{ $$ = $1 >> $3; }
	;

shift_expr
	: additive_expr
		{ $$ = $1; }
	| shift_expr LEFT_OP additive_expr
		{ $$ = expr_new();
		  $$->type = EXPR_LEFT;
		  $$->expr0 = $1;
		  $$->expr1 = $3; }
	| shift_expr RIGHT_OP additive_expr
		{ $$ = expr_new();
		  $$->type = EXPR_RIGHT;
		  $$->expr0 = $1;
		  $$->expr1 = $3; }
	;

constant_relational_expr
	: constant_shift_expr
		{ $$ = $1; }
	| constant_relational_expr '<' constant_shift_expr
		{ $$ = $1 < $3; }
	| constant_relational_expr '>' constant_shift_expr
		{ $$ = $1 > $3; }
	| constant_relational_expr LE_OP constant_shift_expr
		{ $$ = $1 <= $3; }
	| constant_relational_expr GE_OP constant_shift_expr
		{ $$ = $1 >= $3; }
	;

relational_expr
	: shift_expr
		{ $$ = $1; }
	| relational_expr '<' shift_expr
		{ $$ = expr_new();
		  $$->type = EXPR_LESS;
		  $$->expr0 = $1;
		  $$->expr1 = $3; }
	| relational_expr '>' shift_expr
		{ $$ = expr_new();
		  $$->type = EXPR_GREATER;
		  $$->expr0 = $1;
		  $$->expr1 = $3; }
	| relational_expr LE_OP shift_expr
		{ $$ = expr_new();
		  $$->type = EXPR_LESS_EQUAL;
		  $$->expr0 = $1;
		  $$->expr1 = $3; }
	| relational_expr GE_OP shift_expr
		{ $$ = expr_new();
		  $$->type = EXPR_GREATER_EQUAL;
		  $$->expr0 = $1;
		  $$->expr1 = $3; }
	;

constant_equality_expr
	: constant_relational_expr
		{ $$ = $1; }
	| constant_equality_expr EQ_OP constant_relational_expr
		{ $$ = $1 == $3; }
	| constant_equality_expr NE_OP constant_relational_expr
		{ $$ = $1 != $3; }
	;

equality_expr
	: relational_expr
		{ $$ = $1; }
	| equality_expr EQ_OP relational_expr
		{ $$ = expr_new();
		  $$->type = EXPR_EQUAL;
		  $$->expr0 = $1;
		  $$->expr1 = $3; }
	| equality_expr NE_OP relational_expr
		{ $$ = expr_new();
		  $$->type = EXPR_NOT_EQUAL;
		  $$->expr0 = $1;
		  $$->expr1 = $3; }
	;

constant_and_expr
	: constant_equality_expr
		{ $$ = $1; }
	| constant_and_expr '&' constant_equality_expr
		{ $$ = $1 & $3; }
	;

and_expr
	: equality_expr
		{ $$ = $1; }
	| and_expr '&' equality_expr
		{ $$ = expr_new();
		  $$->type = EXPR_AND;
		  $$->expr0 = $1;
		  $$->expr1 = $3; }
	;

constant_exclusive_or_expr
	: constant_and_expr
		{ $$ = $1; }
	| constant_exclusive_or_expr '^' constant_and_expr
		{ $$ = $1 ^ $3; }
	;

exclusive_or_expr
	: and_expr
		{ $$ = $1; }
	| exclusive_or_expr '^' and_expr
		{ $$ = expr_new();
		  $$->type = EXPR_XOR;
		  $$->expr0 = $1;
		  $$->expr1 = $3; }
	;

constant_inclusive_or_expr
	: constant_exclusive_or_expr
		{ $$ = $1; }
	| constant_inclusive_or_expr '|' constant_exclusive_or_expr
		{ $$ = $1 | $3; }
	;

inclusive_or_expr
	: exclusive_or_expr
		{ $$ = $1; }
	| inclusive_or_expr '|' exclusive_or_expr
		{ $$ = expr_new();
		  $$->type = EXPR_OR;
		  $$->expr0 = $1;
		  $$->expr1 = $3; }
	;

logical_and_expr
	: inclusive_or_expr
		{ $$ = $1; }
	| logical_and_expr AND_OP inclusive_or_expr
		{ $$ = expr_new();
		  $$->type = EXPR_SHORT_AND;
		  $$->expr0 = $1;
		  $$->expr1 = $3; }
	;

logical_or_expr
	: logical_and_expr
		{ $$ = $1; }
	| logical_or_expr OR_OP logical_and_expr
		{ $$ = expr_new();
		  $$->type = EXPR_SHORT_OR;
		  $$->expr0 = $1;
		  $$->expr1 = $3; }
	;

constant_conditional_expr
	: constant_inclusive_or_expr
		{ $$ = $1; }
	| constant_inclusive_or_expr '?' constant_conditional_expr ':' constant_conditional_expr
		{ $$ = $1 ? $3 : $5; }
	;

conditional_expr
	: logical_or_expr
		{ $$ = $1; }
	| logical_or_expr '?' conditional_expr ':' conditional_expr
		{ $$ = expr_new();
		  $$->type = EXPR_CONDITION;
		  $$->expr0 = $1;
		  $$->expr1 = $3;
		  $$->expr2 = $5; }
	;

assignment_expr
	: conditional_expr
		{ $$ = $1; }
	| unary_expr '=' assignment_expr
		{ $$ = expr_new();
		  $$->type = EXPR_ASSIGN;
		  $$->expr0 = $1;
		  $$->expr1 = $3; }
	| unary_expr MUL_ASSIGN assignment_expr
		{ $$ = expr_new();
		  $$->type = EXPR_MUL_ASSIGN;
		  $$->expr0 = $1;
		  $$->expr1 = $3; }
	| unary_expr DIV_ASSIGN assignment_expr
		{ $$ = expr_new();
		  $$->type = EXPR_DIV_ASSIGN;
		  $$->expr0 = $1;
		  $$->expr1 = $3; }
	| unary_expr MOD_ASSIGN assignment_expr
		{ $$ = expr_new();
		  $$->type = EXPR_MOD_ASSIGN;
		  $$->expr0 = $1;
		  $$->expr1 = $3; }
	| unary_expr ADD_ASSIGN assignment_expr
		{ $$ = expr_new();
		  $$->type = EXPR_ADD_ASSIGN;
		  $$->expr0 = $1;
		  $$->expr1 = $3; }
	| unary_expr SUB_ASSIGN assignment_expr
		{ $$ = expr_new();
		  $$->type = EXPR_SUB_ASSIGN;
		  $$->expr0 = $1;
		  $$->expr1 = $3; }
	| unary_expr LEFT_ASSIGN assignment_expr
		{ $$ = expr_new();
		  $$->type = EXPR_LEFT_ASSIGN;
		  $$->expr0 = $1;
		  $$->expr1 = $3; }
	| unary_expr RIGHT_ASSIGN assignment_expr
		{ $$ = expr_new();
		  $$->type = EXPR_RIGHT_ASSIGN;
		  $$->expr0 = $1;
		  $$->expr1 = $3; }
	| unary_expr AND_ASSIGN assignment_expr
		{ $$ = expr_new();
		  $$->type = EXPR_AND_ASSIGN;
		  $$->expr0 = $1;
		  $$->expr1 = $3; }
	| unary_expr XOR_ASSIGN assignment_expr
		{ $$ = expr_new();
		  $$->type = EXPR_XOR_ASSIGN;
		  $$->expr0 = $1;
		  $$->expr1 = $3; }
	| unary_expr OR_ASSIGN assignment_expr
		{ $$ = expr_new();
		  $$->type = EXPR_OR_ASSIGN;
		  $$->expr0 = $1;
		  $$->expr1 = $3; }
	;

expr_list_e
	: /* lambda */
		{ $$ = expr_new();
		  $$->type = EXPR_LIST;
		  $$->first = NULL;
		  $$->last = NULL; }
	| expr_list
		{ $$ = $1; }
	;

expr_list
	: assignment_expr
		{ $$ = expr_new();
		  $$->type = EXPR_LIST;
		  $1->prev = NULL;
		  $1->next = NULL;
		  $$->first = $1;
		  $$->last = $1; }
	| expr_list ',' assignment_expr
		{ $3->prev = $1->last;
		  $3->next = NULL;
		  if ($3->prev) {
		  	$3->prev->next = $3;
		  } else {
			$1->first = $3;
		  }
		  $1->last = $3;
		  $$ = $1; }
	;

expr
	: assignment_expr
		{ $$ = $1; }
	| expr ',' assignment_expr
		{ if ($1->type == EXPR_LIST) {
			/* Add last to first. */
			$3->prev = $1->last;
			$3->next = NULL;
			$3->prev->next = $3;
			$1->last = $3;
			$$ = $1;
		  } else {
			/* Create new list. */
			$$ = expr_new();
			$$->type = EXPR_LIST;
			$1->prev = NULL;
			$1->next = $3;
			$3->prev = $1;
			$3->next = NULL;
			$$->first = $1;
			$$->last = $3;
		  }}
	;

expr_e
	: /* lambda */
		{ $$ = NULL; }
	| expr
		{ $$ = $1; }
	;

constant_expr
	: constant_conditional_expr
		{ $$ = $1; }
	;

initializer_expr
	: assignment_expr
		{ $$ = $1; }
	| '{' initializer_expr_list '}'
		{ $$ = $2; }
	| '{' initializer_expr_list ',' '}'
		{ $$ = $2; }
	;

initializer_expr_list
	: initializer_expr
		{ $$ = expr_new();
		  $$->type = EXPR_BRACES;
		  $1->prev = NULL;
		  $1->next = NULL;
		  $$->first = $1;
		  $$->last = $1; }
	| initializer_expr_list ',' initializer_expr
		{ $3->prev = $1->last;
		  $3->next = NULL;
		  $3->prev->next = $3;
		  $1->last = $3;
		  $$ = $1; }
	;

attribute_e
	: /* lambda */
		{ memset(&$$, 0, sizeof($$)); }
	| attribute_e attribute
		{ memset(&$$, 0, sizeof $$);
		  $$.mod_noreturn = $1.mod_noreturn | $2.mod_noreturn;
		  if ($1.attr_aligned < $2.attr_aligned) {
			  $$.attr_aligned = $2.attr_aligned;
		  } else {
			  $$.attr_aligned = $1.attr_aligned;
		  }
		  $$.attr_packed = $1.attr_packed | $2.attr_packed; }
	;
attribute
	: ATTRIBUTE '(' '(' attribute_element_list ')' ')'
		{ memcpy(&$$, &$4, sizeof $$); }
	;
attribute_element_list
	: attribute_element
		{ memcpy(&$$, &$1, sizeof $$); }
	| attribute_element_list ',' attribute_element
		{ $$.mod_noreturn = $1.mod_noreturn | $3.mod_noreturn;
		  if ($1.attr_aligned < $3.attr_aligned) {
			  $$.attr_aligned = $3.attr_aligned;
		  } else {
			  $$.attr_aligned = $1.attr_aligned;
		  }
		  $$.attr_packed = $1.attr_packed | $3.attr_packed; }
	;
attribute_element
	: identifier
		{ memset(&$$, 0, sizeof $$);
		  $$.mod_noreturn = (strcmp($1, "noreturn") == 0)
		  		| (strcmp($1, "__noreturn__") == 0);
		  $$.attr_packed = (strcmp($1, "packed") == 0)
		  		|  (strcmp($1, "__packed__") == 0); }
	| CONST
		{ memset(&$$, 0, sizeof $$); }
	| identifier '(' attribute_list ')'
		{ memset(&$$, 0, sizeof $$);
		  if (strcmp($1, "aligned") == 0
		   || strcmp($1, "__aligned__") == 0) {
		  	$$.attr_aligned = $3;
		  }
		}
	;
attribute_list
	: INTEGER_LITERAL
		{ $$ = $1; }
	| string
		{ $$ = 0; } /* FIXME */
	| identifier
		{ $$ = 0; } /* FIXME */
	| identifier '(' type_spec ')'
		{ $$ = 0; } /* FIXME */
	| attribute_list ',' INTEGER_LITERAL
		{ $$ = 0; } /* FIXME */
	| attribute_list ',' string
		{ $$ = 0; } /* FIXME */
	| attribute_list ',' identifier
		{ $$ = 0; } /* FIXME */
	;

volatile_e
	: /* lambda */
	| VOLATILE
	;

const_volatile_e
	: /* lambda */
		{ $$ = 0; }
	| const_volatile_e VOLATILE
		{ $$ = $1 | 1; }
	| const_volatile_e CONST
		{ $$ = $1 | 2; }
	;

type_spec_mod
	: SHORT
		{ memset(&$$, 0, sizeof $$);
		  $$.mod_short = 1; }
	| LONG
		{ memset(&$$, 0, sizeof $$);
		  $$.mod_long = 1; }
	| SIGNED
		{ memset(&$$, 0, sizeof $$);
		  $$.mod_signed = 1; }
	| UNSIGNED
		{ memset(&$$, 0, sizeof $$);
		  $$.mod_unsigned = 1; }
	| CONST
		{ memset(&$$, 0, sizeof $$);
		  $$.mod_const = 1; }
	| VOLATILE
		{ memset(&$$, 0, sizeof $$);
		  $$.mod_volatile = 1; }
	| INLINE
		{ memset(&$$, 0, sizeof $$);
		  $$.mod_inline = 1; }
	| attribute
		/* FIXME */
		{ memset(&$$, 0, sizeof $$);
		  $$.mod_noreturn = $1.mod_noreturn;
		  $$.attr_aligned = $1.attr_aligned;
		  $$.attr_packed = $1.attr_packed; }
	;
type_spec_storage
	: EXTERN
		{ memset(&$$, 0, sizeof $$);
		  $$.storage = STORAGE_EXTERN; }
	| TYPEDEF
		{ memset(&$$, 0, sizeof $$);
		  $$.storage = STORAGE_TYPEDEF; }
	| AUTO
		{ memset(&$$, 0, sizeof $$);
		  $$.storage = STORAGE_AUTO; }
	| STATIC
		{ memset(&$$, 0, sizeof $$);
		  $$.storage = STORAGE_STATIC; }
	| REGISTER
		{ memset(&$$, 0, sizeof $$);
		  $$.storage = STORAGE_REGISTER; }
	;
type_spec_type
	: VOID
		{ memset(&$$, 0, sizeof $$);
		  $$.type = TYPE_SPEC_VOID; }
	| CHAR
		{ memset(&$$, 0, sizeof $$);
		  $$.type = TYPE_SPEC_CHAR; }
	| INT
		{ memset(&$$, 0, sizeof $$);
		  $$.type = TYPE_SPEC_INT; }
	| FLOAT
		{ memset(&$$, 0, sizeof $$);
		  $$.type = TYPE_SPEC_FLOAT; }
	| DOUBLE
		{ memset(&$$, 0, sizeof $$);
		  $$.type = TYPE_SPEC_DOUBLE; }
	| STRUCT identifier_e '{'
		{ scope_struct_begin($2); }
	  declaration_list '}'
		{ memset(&$$, 0, sizeof $$);
		  $$.type = TYPE_SPEC_STRUCT;
		  $$.identifier = $2;
		  scope_struct_end(); }
	| STRUCT identifier
		{ memset(&$$, 0, sizeof $$);
		  $$.type = TYPE_SPEC_STRUCT;
		  $$.identifier = $2; }
	| UNION identifier_e '{'
		{ scope_union_begin($2); }
	  declaration_list '}'
		{ memset(&$$, 0, sizeof $$);
		  $$.type = TYPE_SPEC_UNION;
		  $$.identifier = $2;
		  scope_union_end(); }
	| UNION identifier
		{ memset(&$$, 0, sizeof $$);
		  $$.type = TYPE_SPEC_UNION;
		  $$.identifier = $2; }
	| ENUM identifier_e '{'
		{ scope_enum_begin($2);
		  enum_next = 0; }
	  enumerator_list_comma '}'
		{ memset(&$$, 0, sizeof $$);
		  $$.type = TYPE_SPEC_ENUM;
		  $$.identifier = $2;
		  scope_enum_end(); }
	| ENUM identifier
		{ memset(&$$, 0, sizeof $$);
		  $$.type = TYPE_SPEC_ENUM;
		  $$.identifier = $2; }
	| BUILTIN_VA_LIST
		{ memset(&$$, 0, sizeof $$);
		  $$.type = TYPE_SPEC_VA_LIST; }
	| TYPE_NAME
		{ memset(&$$, 0, sizeof $$);
		  $$.type = TYPE_SPEC_USER;
		  $$.identifier = $1; }
	;
type_spec_0
	: type_spec_mod
		{ $$ = $1; }
	| type_spec_storage
		{ $$ = $1; }
	| type_spec_type
		{ $$ = $1; }
	| type_spec_0 type_spec_mod
		{ $$ = $1;
		  $$.mod_short += $2.mod_short;
		  $$.mod_long += $2.mod_long;
		  $$.mod_signed += $2.mod_signed;
		  $$.mod_unsigned += $2.mod_unsigned;
		  $$.mod_const += $2.mod_const;
		  $$.mod_volatile += $2.mod_volatile;
		  if ($$.attr_packed < $2.attr_packed) {
			  $$.attr_packed = $2.attr_packed;
		  }
		  $$.mod_inline += $2.mod_inline;
		  $$.mod_noreturn += $2.mod_noreturn; }
	| type_spec_0 type_spec_storage
		{ $$ = $1;
		  $$.storage = $2.storage; }
	| type_spec_0 type_spec_type
		{ $$ = $1;
		  $$.type = $2.type;
		  $$.identifier = $2.identifier; }
	;
type_spec
	: type_spec_0
		{ struct declaration *dion;
		  struct type *type;
		  int ret;

		  switch ($1.type) {
		  case TYPE_SPEC_VOID:
		  	if ($1.mod_signed
			 || $1.mod_unsigned
			 || $1.mod_short
			 || $1.mod_long) {
				yyerror("Bad type modifier");
			}
		  	type = type_void();
			break;
		  case TYPE_SPEC_CHAR:
		  	if ($1.mod_short
			 || $1.mod_long
			 || 1 < $1.mod_signed
			 || 1 < $1.mod_unsigned
			 || ($1.mod_signed && $1.mod_unsigned)) {
				yyerror("Bad type modifier");
			}
		  	if ($1.mod_signed) {
				type = type_signed_char();
			} else if ($1.mod_unsigned) {
				type = type_unsigned_char();
			} else {
				type = type_char();
			}
			break;
		  case TYPE_SPEC_NONE:
		  case TYPE_SPEC_INT:
		  	if (1 < $1.mod_signed
			 || 1 < $1.mod_unsigned
			 || 1 < $1.mod_short
			 || 2 < $1.mod_long
			 || ($1.mod_signed && $1.mod_unsigned)
		  	 || ($1.mod_short && $1.mod_long)) {
				yyerror("Bad type modifier");
			}
			if ($1.mod_unsigned) {
				if ($1.mod_short) {
					type = type_unsigned_short_int();
				} else if ($1.mod_long == 0) {
					type = type_unsigned_int();
				} else if ($1.mod_long == 1) {
					type = type_unsigned_long_int();
				} else if ($1.mod_long == 2) {
					type = type_unsigned_long_long_int();
				} else {
					assert(0);
				}
			} else {
				if ($1.mod_short) {
					type = type_short_int();
				} else if ($1.mod_long == 0) {
					type = type_int();
				} else if ($1.mod_long == 1) {
					type = type_long_int();
				} else if ($1.mod_long == 2) {
					type = type_long_long_int();
				} else {
					assert(0);
				}
			}
			break;
		  case TYPE_SPEC_FLOAT:
		  	if ($1.mod_signed
			 || $1.mod_unsigned
			 || $1.mod_short
			 || $1.mod_long) {
				yyerror("Bad type modifier");
			}
		  	type = type_float();
			break;
		  case TYPE_SPEC_DOUBLE:
		  	if ($1.mod_signed
			 || $1.mod_unsigned
			 || $1.mod_short
			 || 1 < $1.mod_long) {
				yyerror("Bad type modifier");
			}
		  	if ($1.mod_long == 0) {
				type = type_double();
			} else {
				type = type_long_double();
			}
			break;
		  case TYPE_SPEC_STRUCT:
		  	if ($1.mod_signed
			 || $1.mod_unsigned
			 || $1.mod_short
			 || $1.mod_long) {
				yyerror("Bad type modifier");
			}
		  	type = type_new();
			type->type = TYPE_STRUCT;
			type->identifier = $1.identifier;
			break;
		  case TYPE_SPEC_UNION:
		  	if ($1.mod_signed
			 || $1.mod_unsigned
			 || $1.mod_short
			 || $1.mod_long) {
				yyerror("Bad type modifier");
			}
		  	type = type_new();
			type->type = TYPE_UNION;
			type->identifier = $1.identifier;
			break;
		  case TYPE_SPEC_ENUM:
		  	if ($1.mod_signed
			 || $1.mod_unsigned
			 || $1.mod_short
			 || $1.mod_long) {
				yyerror("Bad type modifier");
			}
		  	type = type_int();
			break;
		  case TYPE_SPEC_USER:
		  	if ($1.mod_signed
			 || $1.mod_unsigned
			 || $1.mod_short
			 || $1.mod_long) {
				yyerror("Bad type modifier");
			}
		  	ret = scope_lookup_current($1.identifier, &dion);
			assert(0 <= ret);
			type = dion->type_name;
			break;
		  case TYPE_SPEC_VA_LIST:
		  	if ($1.mod_signed
			 || $1.mod_unsigned
			 || $1.mod_short
			 || $1.mod_long) {
				yyerror("Bad type modifier");
			}
		  	type = type_new();
			type->type = TYPE_VA_LIST;
			break;
		  case TYPE_SPEC_ELIPSIS:
		  	if ($1.mod_signed
			 || $1.mod_unsigned
			 || $1.mod_short
			 || $1.mod_long) {
				yyerror("Bad type modifier");
			}
		  	type = type_new();
			type->type = TYPE_ELIPSIS;
			break;
		  default:
		  	assert(0);
		  }
		  if ($1.mod_const) {
		  	type = type_const(type);
		  }
		  if ($1.mod_volatile) {
		  	type = type_volatile(type);
		  }
		  if ($1.attr_aligned) {
		  	type = type_aligned(scope_current, type,
					$1.attr_aligned);
		  }
		  if ($1.attr_packed) {
		  	type = type_packed(scope_current, type);
		  }
		  $$.type = type;
		  $$.storage = $1.storage;
		  $$.mod_inline = $1.mod_inline;
		  $$.mod_noreturn = $1.mod_noreturn;
		}
	;

enumerator_list_comma
	: enumerator_list
	| enumerator_list ','
	;

enumerator_list
	: enumerator
	| enumerator_list ',' enumerator
	;

enumerator
	: IDENTIFIER
		{ scope_enum_add($1, enum_next++); }
	| IDENTIFIER '=' constant_expr
		{ enum_next = $3;
		  scope_enum_add($1, enum_next++); }
	; 

declarator_list_e
	: /* lambda */
		{ $$.first = NULL;
		  $$.last = NULL; }
	| declarator_list
		{ $$ = $1; }
	;

declarator_list
	: declarator
		{ $1->prev = NULL;
		  $1->next = NULL;
		  $$.first = $1;
		  $$.last = $1; }
	| declarator_list ',' declarator
		{ $3->prev = $1.last;
		  $3->next = NULL;
		  $3->prev->next = $3;
		  $1.last = $3;
		  $$.first = $1.first;
		  $$.last = $1.last; }
	;

declarator
	: declarator2 attribute_e
		{ $$ = $1;
		  $$->attr_aligned = $2.attr_aligned;
		  $$->attr_noreturn = $2.mod_noreturn; }
	| declarator2 attribute_e '=' initializer_expr attribute_e
		{ $$ = $1;
		  $$->attr_aligned = $2.attr_aligned;
		  $$->attr_noreturn = $2.mod_noreturn;
		  declaration_initializer_set($$, $4); }
	;

declarator2
	: ':' constant_expr
		{ $$ = declaration_identifier(identifier_tmp());
		  $$->nbits = expr_integer($2); };
	| declarator3
		{ $$ = $1; }
	| declarator3 ASM '(' string ')'
		{ $$ = $1;
		  $$->regname = $4.string; }
	| declarator3 ':' constant_expr
		{ $$ = $1;
		  $$->nbits = expr_integer($3); }
	;

declarator3
	: declarator4
		{ $$ = $1; }
	| '*' const_volatile_e declarator3
		{ $$ = $3;
		  $$->type_name = type_add_pointer($$->type_name, $2); }
	;

declarator4
	: IDENTIFIER
		{ $$ = declaration_identifier($1); }
	| '(' declarator3 ')'
		{ $$ = $2; }
	| declarator4 '[' ']'
		{ $$ = $1;
		  $$->type_name = type_add_array($$->type_name, NULL); }
	| declarator4 '[' constant_expr ']'
		{ $$ = $1;
		  $$->type_name = type_add_array(
				$$->type_name, expr_integer($3)); }
	| declarator4 '('
		  { scope_parameter_begin(); }
	  parameter_type_list_e ')'
		{ $$ = $1;
		  $$->type_name = type_add_function(
		  		$$->type_name, scope_current);
		  scope_parameter_end($4.first, $4.last); }
	;

parameter_type_list_e
	: /* lambda */
		{ $$.first = NULL;
		  $$.last = NULL; }
	| parameter_type_list
		{ if ($1.first
		   && $1.first == $1.last
		   && type_is_void($1.first->type_name)) {
			$$.first = NULL;
			$$.last = NULL;
		  } else {
			$$ = $1;
		  }}
	;

parameter_type_list
	: parameter_list
		{ $$ = $1; }
	| parameter_list ',' ELIPSIS
		{ struct declaration *dion;

		  dion = declaration_identifier(NULL);
		  dion->storage = STORAGE_PARAM;
		  dion->mod_inline = 0;
		  dion->attr_aligned = 0;
		  dion->attr_noreturn = 0;
		  dion->type_name = type_new();
		  dion->type_name->type = TYPE_ELIPSIS;

		  dion->prev = $1.last;
		  dion->next = NULL;
		  dion->prev->next = dion;
		  $1.last = dion;

		  $$.first = $1.first;
		  $$.last = $1.last; }
	;

parameter_list
	: parameter_declaration
		{ $1->prev = NULL;
		  $1->next = NULL;
		  $$.first = $1;
		  $$.last = $1; }
	| parameter_list ',' parameter_declaration
		{ $3->prev = $1.last;
		  $3->next = NULL;
		  $3->prev->next = $3;
		  $1.last = $3;
		  $$.first = $1.first;
		  $$.last = $1.last; }
	;

parameter_declaration
	: type_spec declarator
		{ $2->storage = STORAGE_PARAM;
		  $2->mod_inline = 0;
		  $2->attr_aligned = 0;
		  $2->attr_noreturn = 0;
		  $2->type_name = type_add_user($2->type_name, $1.type);
		  if (type_is_array($2->type_name)) {
		  	$2->type_name = type_amphersand(
					type_array_access($2->type_name));
		  }
		  $$ = $2; }
	| type_name
		{ $$ = declaration_identifier(NULL);
		  $$->storage = STORAGE_PARAM;
		  $$->mod_inline = 0;
		  $$->attr_aligned = 0;
		  $$->attr_noreturn = 0;
		  $$->type_name = $1;
		  if (type_is_array($$->type_name)) {
		  	$$->type_name = type_amphersand(
					type_array_access($$->type_name));
		  } }
	;

type_name
	: type_spec
		{ $$ = $1.type; }
	| type_spec abstract_declarator
		{ $$ = type_add_user($2, $1.type); }
	;

abstract_declarator
	: '*' const_volatile_e
		{ struct type *dor;

		  dor = type_type_spec();
		  $$ = type_add_pointer(dor, $2); }
	| '*' const_volatile_e abstract_declarator
		{ $$ = type_add_pointer($3, $2); }
	| abstract_declarator2
		{ $$ = $1; }
	;

abstract_declarator2
	: '(' abstract_declarator ')'
		{ $$ = $2; }
	| '[' ']'
		{ struct type *dor;

		  dor = type_type_spec();
		  $$ = type_add_array(dor, NULL); }
	| '[' constant_expr ']'
		{ struct type *dor;

		  dor = type_type_spec();
		  $$ = type_add_array(dor, expr_integer($2)); }
	| '('
		{ scope_parameter_begin(); }
	  parameter_type_list_e ')'
		{ struct type *dor;

		  dor = type_type_spec();
		  $$ = type_add_function(dor, scope_current);
		  scope_parameter_end($3.first, $3.last); }
	| abstract_declarator2 '[' ']'
		{ $$ = type_add_array($1, NULL); }
	| abstract_declarator2 '[' constant_expr ']'
		{ $$ = type_add_array($1, expr_integer($3)); }
	| abstract_declarator2 '('
		{ scope_parameter_begin(); }
	  parameter_type_list_e ')'
		{ $$ = type_add_function($1, scope_current);
		  scope_parameter_end($4.first, $4.last); }
	;

declaration_list
	: /* lambda */
	| declaration_list declaration
	;

declaration
	: type_spec declarator_list_e ';'
		{ struct declaration *dor;

		  for (dor = $2.first; dor; ) {
			struct declaration *next;

			next = dor->next;

			dor->storage = $1.storage;
			dor->mod_inline += $1.mod_inline;
			dor->attr_noreturn += $1.mod_noreturn;
			dor->type_name = type_add_user(dor->type_name, $1.type);
			scope_declaration_append(scope_current, dor);

			dor = next;
		  }
		}
	;

asm_constraint_list_e
	: /* lambda */
		{ $$ = constraint_list_new();
		  $$->first = NULL;
		  $$->last = NULL; }
	| asm_constraint_list
		{ $$ = $1; }
	;

asm_constraint_list
	: asm_constraint
		{ $$ = constraint_list_new();
		  $1->prev = NULL;
		  $1->next = NULL;
		  $$->first = $1;
		  $$->last = $1; }
	| asm_constraint_list ',' asm_constraint
		{ $3->prev = $1->last;
		  $3->next = NULL;
		  $3->prev->next = $3;
		  $1->last = $3;
		  $$ = $1; }
	;

asm_constraint
	: string '(' expr ')'
		{ $$ = constraint_new();
		  $$->string = $1.string;
		  $$->expr = $3;
		}
	;

asm_change_list_e
	: /* lambda */
		{ $$ = constraint_list_new(); }
	| asm_change_list
		{ $$ = $1; }
	;

asm_change_list
	: asm_change
		{ $$ = constraint_list_new();
		  $1->prev = NULL;
		  $1->next = NULL;
		  $$->first = $1;
		  $$->last = $1; }
	| asm_change_list ',' asm_change
		{ $3->prev = $1->last;
		  $3->next = NULL;
		  $3->prev->next = $3;
		  $1->last = $3;
		  $$ = $1; }
	;

asm_change
	: string
		{ $$ = constraint_new();
		  $$->string = $1.string;
		  $$->expr = NULL; }
	;

statement_list
	: /* lambda */
		{ $$.first = NULL;
		  $$.last = NULL; }
	| statement_list statement
		{ $2->prev = $1.last;
		  $2->next = NULL;
		  if ($2->prev) {
			$2->prev->next = $2;
		  } else {
			$1.first = $2;
		  }
		  $1.last = $2;
		  $$.first = $1.first;
		  $$.last = $1.last; }
	;

statement
	: ';'
		{ $$ = stmt_null(); }
	| IDENTIFIER ':' statement
		{ $$ = stmt_label(scope_function_label_get($1), $3); }
	| CASE constant_expr ':' statement
		{ $$ = stmt_new();
		  $$->type = STMT_CASE;
		  $$->expr0 = expr_integer($2);
		  $$->stmt0 = $4; }
	| DEFAULT ':' statement
		{ $$ = stmt_new();
		  $$->type = STMT_DEFAULT;
		  $$->stmt0 = $3; }
	| expr ';'
		{ $$ = stmt_expr($1); }
	| IF '(' expr ')' statement
		{ $$ = stmt_if($3, $5, NULL); }
	| IF '(' expr ')' statement ELSE statement
		{ $$ = stmt_if($3, $5, $7); }
	| SWITCH '(' expr ')' '{'
		{ scope_block_begin(); }
	  compound_statement '}'
		{ scope_block_end($7);
		  $$ = stmt_new();
		  $$->type = STMT_SWITCH;
		  $$->expr0 = $3;
		  $$->stmt0 = $7; }
	| WHILE '(' expr ')' statement
		{ $$ = stmt_new();
		  $$->type = STMT_WHILE;
		  $$->expr0 = $3;
		  $$->stmt0 = $5; }
	| DO statement WHILE '(' expr ')' ';'
		{ $$ = stmt_new();
		  $$->type = STMT_DO_WHILE;
		  $$->expr0 = $5;
		  $$->stmt0 = $2; }
	| FOR '(' expr_e ';' expr_e ';' expr_e ')' statement
		{ $$ = stmt_new();
		  $$->type = STMT_FOR;
		  $$->expr0 = $3;
		  $$->expr1 = $5;
		  $$->expr2 = $7;
		  $$->stmt0 = $9; }
	| GOTO IDENTIFIER ';'
		{ $$ = stmt_goto(scope_function_label_get($2)); }
	| CONTINUE ';'
		{ $$ = stmt_new();
		  $$->type = STMT_CONTINUE; }
	| BREAK ';'
		{ $$ = stmt_new();
		  $$->type = STMT_BREAK; }
	| RETURN ';'
		{ $$ = stmt_new();
		  $$->type = STMT_RETURN;
		  $$->expr0 = NULL; }
	| RETURN expr ';'
		{ $$ = stmt_new();
		  $$->type = STMT_RETURN;
		  $$->expr0 = $2; }
	| ASM volatile_e '(' string ')' ';'
		{ $$ = stmt_new();
		  $$->type = STMT_ASM;
		  $$->code_len = $4.len;
		  $$->code = $4.string;
		  $$->output = constraint_list_new();
		  $$->input = constraint_list_new();
		  $$->change = constraint_list_new(); }
	| ASM volatile_e '(' string ':' asm_constraint_list_e ')' ';'
		{ $$ = stmt_new();
		  $$->type = STMT_ASM;
		  $$->code_len = $4.len;
		  $$->code = $4.string;
		  $$->output = $6;
		  $$->input = constraint_list_new();
		  $$->change = constraint_list_new(); }
	| ASM volatile_e '(' string ':' asm_constraint_list_e ':' asm_constraint_list_e ')' ';'
		{ $$ = stmt_new();
		  $$->type = STMT_ASM;
		  $$->code_len = $4.len;
		  $$->code = $4.string;
		  $$->output = $6;
		  $$->input = $8;
		  $$->change = constraint_list_new(); }
	| ASM volatile_e '(' string ':' asm_constraint_list_e ':' asm_constraint_list_e ':' asm_change_list_e ')' ';'
		{ $$ = stmt_new();
		  $$->type = STMT_ASM;
		  $$->code_len = $4.len;
		  $$->code = $4.string;
		  $$->output = $6;
		  $$->input = $8;
		  $$->change = $10; }
	| BUILTIN_VA_START '(' identifier ',' identifier ')'
		{ int ret;
		
		  $$ = stmt_new();
		  $$->type = STMT_VA_START;
		  $$->expr0 = expr_new();
		  $$->expr0->type = EXPR_IDENTIFIER;
		  ret = scope_lookup_current($3, &$$->expr0->declaration);
		  if (ret < 0) {
		  	parse_unknown($3);
		  }
		  assert(0 <= ret);
		  $$->expr1 = expr_new();
		  $$->expr1->type = EXPR_IDENTIFIER;
		  ret = scope_lookup_current($5, &$$->expr1->declaration);
		  if (ret < 0) {
		  	parse_unknown($5);
		  }
		  assert(0 <= ret); }
	| BUILTIN_VA_END '(' identifier ')'
		{ int ret;
		
		  $$ = stmt_new();
		  $$->type = STMT_VA_END;
		  $$->expr0 = expr_new();
		  $$->expr0->type = EXPR_IDENTIFIER;
		  ret = scope_lookup_current($3, &$$->expr0->declaration);
		  if (ret < 0) {
		  	parse_unknown($3);
		  }
		  assert(0 <= ret); }
	| '{'
		{ scope_block_begin(); }
	  compound_statement '}'
		{ scope_block_end($3);
		  $$ = $3; }
	;

compound_statement
	: declaration_list statement_list
		{ $$ = stmt_new();
		  $$->type = STMT_BLOCK;
		  $$->stmt_first = $2.first;
		  $$->stmt_last = $2.last; }
	;

external_definition_list
	: /* lambda */
	| external_definition_list external_definition
	;

external_definition
	: type_spec declarator '{'
		{ assert(! $2->attr_noreturn);
		  $2->storage = $1.storage;
		  $2->mod_inline += $1.mod_inline;
		  $2->attr_noreturn += $1.mod_noreturn;
		  scope_function_begin($1.type, $2); }
	  compound_statement '}'
		{ scope_function_end($5); }
	| declaration
	| ASM '(' string ')' ';'
		{ scope_asm_add($3.len, $3.string); }
	;

file
	: /* lambda */
		{ scope_file_begin(); }
	  external_definition_list
		{ scope_file_end(&parse_scope); }
	;

identifier_e
	: /* lambda */
		{ $$ = identifier_tmp(); }
	| identifier
		{ $$ = $1; }
	;
identifier
	: IDENTIFIER
		{ $$ = $1; }
	| TYPE_NAME
		{ $$ = $1; }
	;

string
	: STRING_LITERAL
		{ $$.len = $1.len;
		  $$.string = $1.string; }
	| string STRING_LITERAL
		{ char *str;
		
		  str = malloc($1.len + $2.len + 1);
		  assert(str);
		  memcpy(str, $1.string, $1.len);
		  memcpy(str + $1.len, $2.string, $2.len);
		  str[$1.len + $2.len] = '\0';
		  free($1.string);
		  free($2.string);
		  $$.len = $1.len + $2.len;
		  $$.string = str; }
	;
%%

struct scope *
parse(const char *in)
{
	int ret;

	yyin = fopen(in, "r");
	assert(yyin);

	yyparse();

	ret = fclose(yyin);
	assert(0 <= ret);

	return parse_scope;
}
