/* -*- fundamental -*- */

%{
#include "com.h"
#include "symtab.h"

#define YYERROR_VERBOSE

static int nohdr = 0;

static Type *set_type(Type *, Type *);
void yyerror(const char *, ...);
int yylex(void);
%}

%union
{
	Var	*var;
	Type	*type;
	char	*name;
	int	num;
}

%token STRUCT TYPEDEF UNSIGNED SIGNED NOTHDR GENERATE
%token STATIC CONST EXTERN ELLIPSIS VOLATILE
%token <name> IDENT
%token <type> TYPE
%token <num> NUM
%token <name> STRING

%left '-' '+'
%left '*' '/'

%type <type> type struct qual_type unsigned
%type <var> type_name varlist typedef c_ident c_id_list type_list
%type <name> op_ident
%type <num> const

%%

sfile	:	/* */
	|	sfile actions
	;

actions	:	nothdr
	|	header_thing
	|	generate
	;

nothdr	:	NOTHDR '{' { nohdr++; } header_things { nohdr--; } '}'
	;

type_list:	STRING			{ $$ = mkvar(NULL, $1); }
	|	type_list ',' STRING	{ $$ = add($1, mkvar(NULL, $3)); }
	;

/* Cause code to be generated for types */
generate:	GENERATE type_list ';'
			{
				Var *v = $2;
				for(; v != NULL; v = v->next)
				{
					Var *tv = lookup(global, v->name);
					if (tv)
						tv->type->demand = 0;
					else
						yyerror("Type %s unknown", v->name);
				}
			}
	;

/* things we're likely to find in a header file */
header_thing:	typedefs
	|	prototype
	|	vars
	;

header_things:	header_thing
	|	header_things header_thing
	;

/* List of typedef statements */
typedefs:	typedef		{ if ($1 != NULL) global = addl(global, $1); }
	|	typedefs typedef			
			{
				if ($2 != NULL)
					global = addl(global, $2);
			}
	;

/* A single typedef */
typedef	:	TYPEDEF type c_id_list ';'
			{
				Var *v, *l = NULL;
				for(v = $3; v != NULL; v = v->next)
				{
					Var *td;
					v->type = set_type(v->type, $2);
					td = mktypedef(v->type, v->name);
					td->type->nohdr = nohdr;
					td->type->demand = nohdr;
					l = add(l, td);
				}
				$$ = l;
			}
	|	struct ';'			{ $$ = NULL; }
	;

/* A list of type-ident pairs */
varlist:	/* */				{ $$ = NULL; }
	|	varlist type_name		{ $$ = $2 == NULL ? NULL : addl($1, $2); }
	;

/* A type with a list of names */
type_name:	type c_id_list ';'
			{
				Var *v, *n, *l = NULL;
				for(v = $2; v != NULL; v = n) {
					n = v->next;
					v->type = set_type(v->type, $1);
					l = add(l, v);
				}
				$$ = l;
			}
	;

const	: NUM			{ $$ = $1; }
	| const '+' const	{ $$ = $1 + $3; }
	| const '-' const	{ $$ = $1 - $3; }
	| const '*' const	{ $$ = $1 * $3; }
	| const '/' const	{ $$ = $1 / $3; }
	| '(' const ')'		{ $$ = $2; }
	;
	
/*
 * A comma-separated list of C idents
 * The hacks in here are for things like "int foo, bar[10];" and
 * "char goop, **poot;"
 */
c_ident	:	IDENT				{ $$ = mkvar(NULL, $1); }
	|	'*' c_ident			{ $$ = mkvar(mkpointer($2->type), $2->name); }
	|	c_ident '[' const ']'		{ $$ = mkvar(mkcarray($1->type, $3), $1->name); }
	|	c_ident '[' ']'			{ $$ = mkvar(mkarray($1->type), $1->name); }
	; 

c_id_list:	c_ident				{ $$ = $1; }
	|	c_id_list ',' c_ident		{ $$ = add($1, $3); }
	;

/* Optional ident for C struct syntax */
op_ident:	/* */				{ $$ = NULL; }
	|	IDENT				{ $$ = $1; }
	;

/* Cope with C structure syntax */
struct	:	STRUCT op_ident '{' varlist '}'
			{
				$$ = mkstruct($4);
				if ($2 != NULL)
				{
					Var *v = mkvar($$, $2);
					structures = add(structures, v);
				}
			}
	|	STRUCT IDENT
			{
				Var *v = lookup(structures, $2);

				if (v != NULL)
					$$ = v->type;
				else
				{
					$$ = mkstruct(NULL);
					$$->norep = 1;
				}
			}
	;

/* Cruftyness to handle "long int" and "long double" */
qual_type:	TYPE TYPE
			{
				Type *t1 = realtype($1);
				Type *t2 = realtype($2);
				$$ = NULL;

				if (t1 != long_type && t1 != short_type)
				{
					yyerror("Can only qualify with \"long\" or \"short\"");
					YYERROR;
				}
				else if (t2->tclass != Scalar)
				{
					yyerror("Can only qualify scalar types");
					YYERROR;
				}
				else
				{
					switch(t2->i.scalar_i.size)
					{
					case Float:
						if (t1 == long_type)
						{
							$$ = double_type;
							break;
						}
					case Long:
						if (t1 == short_type)
						{
							$$ = short_type;
							break;
						}
						if (t1 == long_type)
						{
							$$ = longlong_type;
							break;
						}

					default:	$$ = $2;
					}
				}
			}
	;

/* Cope with "unsigned int" */
unsigned:	UNSIGNED TYPE			{ $$ = $2; }
	|	UNSIGNED qual_type		{ $$ = $2; }
	|	UNSIGNED			{ $$ = Ulong_type; }
	;

/* Main rule for all types */
type	: 	TYPE				{ $$ = $1; }
        |       SIGNED TYPE                     { $$ = $2; }
	|	qual_type
	|	unsigned
			{
				Type *t, *rt;

				rt = realtype($1);
				if (rt->tclass != Scalar)
				{
					yyerror("only scalars can be unsigned");
					YYERROR;
					t = $1;
				}
				else
				{
					switch(rt->i.scalar_i.size)
					{
					case Char: t = Uchar_type; break;
					case Short: t = Ushort_type; break;
					case Long: t = Ulong_type; break;
					case LongLong: t = Ulonglong_type; break;
					default:
						yyerror("only integral types can be unsigned");
						YYERROR;
						break;
					}
				}
				$$ = t;
			}
	|	struct				{ $$ = $1; }
	;

/* Parse prototypes, without semantic action */

/* Storage class for functions.  We're an accepting lot  */
sclass	:	/* */
	|	sclass EXTERN
	|	sclass CONST
	|	sclass VOLATILE
	|	sclass STATIC
	;

prototype:	sclass type IDENT '(' comma_proto_list ')' ';'
	;

/* Variables */
vars	:	sclass type_name /* ';' -- type_name expects it */

/* Optional constant */
opconst:	/* */
	|	CONST
	;

/* A prototype argument */
proto	:	opconst type
	|	opconst type IDENT
	|	ELLIPSIS
	;

/* A list of parameters */
comma_proto_list:	/* */
	|	proto
	|	comma_proto_list ',' proto
	;
%%

static Type *set_type(Type *tt, Type *to)
{
	if (tt == NULL)
		return to;

	switch(tt->tclass)
	{
	case Array:
		tt->i.array_i = set_type(tt->i.array_i, to);
		break;

	case CArray:
		tt->i.carray_i.type = set_type(tt->i.carray_i.type, to);
		break;

	case Pointer:
		tt->i.pointer_i = set_type(tt->i.pointer_i, to);
		break;

	default:
		abort();
	}

	return tt;
}
