%{
#define YYERROR_VERBOSE
#define YYDEBUG 1

#include <stdlib.h>
#include <stdio.h>
#include <stdarg.h>
#include <inttypes.h>
#include <ctype.h>
#include <string.h>
#include <assert.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>

#include "util.h"
#include "parse.h"


#define LBLSTKSZ 64
static Node **lbls[LBLSTKSZ];
static size_t nlbls[LBLSTKSZ];
Stab *curscope;

/* the first time we see a label, we increment to 0 */
static int lbldepth = -1;

void yyerror(const char *s);
int yylex(void);

static Op binop(int);
static Node *mkpseudodecl(Srcloc, Type *);
static void installucons(Stab *, Type *);
static void setattrs(Node *, char **attrs, size_t);
static void setwith(Type *, Traitspec **, size_t);
static void mergeenv(Node *, Node *);
static void mangleautocall(Node *, char *);
static void addinit(Node *, Node *);
static void setuname(Type *);

%}

%token<tok> Tplus	/* + */
%token<tok> Tminus	/* - */
%token<tok> Tmul	/* * */
%token<tok> Tdiv	/* / */
%token<tok> Tinc	/* ++ */
%token<tok> Tdec	/* -- */
%token<tok> Tmod	/* % */
%token<tok> Tasn	/* = */
%token<tok> Taddeq	/* += */
%token<tok> Tsubeq	/* -= */
%token<tok> Tmuleq	/* *= */
%token<tok> Tdiveq	/* /= */
%token<tok> Tmodeq	/* %= */
%token<tok> Tboreq	/* |= */
%token<tok> Tbxoreq	/* ^= */
%token<tok> Tbandeq	/* &= */
%token<tok> Tbsleq	/* <<= */
%token<tok> Tbsreq	/* >>= */

%token<tok> Tbor	/* | */
%token<tok> Tbxor	/* ^ */
%token<tok> Tband	/* & */
%token<tok> Tbsl	/* << */
%token<tok> Tbsr	/* >> */
%token<tok> Tbnot	/* ~ */

%token<tok> Teq		/* == */
%token<tok> Tgt		/* > */
%token<tok> Tlt		/* < */
%token<tok> Tge		/* >= */
%token<tok> Tle		/* <= */
%token<tok> Tne		/* != */

%token<tok> Tlor	/* || */
%token<tok> Tland	/* && */
%token<tok> Tlnot	/* ! */

%token<tok> Tobrace	/* { */
%token<tok> Tcbrace	/* } */
%token<tok> Toparen	/* ( */
%token<tok> Tcparen	/* ) */
%token<tok> Tosqbrac	/* [ */
%token<tok> Tcsqbrac	/* ] */
%token<tok> Ttick	/* ` */
%token<tok> Tderef	/* # */
%token<tok> Tqmark	/* ? */

%token<tok> Ttype	/* type */
%token<tok> Tfor	/* for */
%token<tok> Twhile	/* while */
%token<tok> Tif		/* if */
%token<tok> Telse	/* else */
%token<tok> Telif	/* else */
%token<tok> Tmatch	/* match */
%token<tok> Tgoto	/* goto */
%token<tok> Tbreak	/* break */
%token<tok> Tcontinue	/* continue */
%token<tok> Tauto	/* auto */

%token<tok> Tintlit
%token<tok> Tstrlit
%token<tok> Tfloatlit
%token<tok> Tchrlit
%token<tok> Tboollit
%token<tok> Tvoidlit

%token<tok> Ttrait	/* trait */
%token<tok> Timpl	/* trait */
%token<tok> Tstruct	/* struct */
%token<tok> Tunion	/* union */
%token<tok> Ttyparam	/* @typename */

%token<tok> Tconst	/* const */
%token<tok> Tvar	/* var */
%token<tok> Tgeneric	/* generic */

%token<tok> Tgap	/* _ */
%token<tok> Tellipsis	/* ... */
%token<tok> Tendln	/* ; or \n */
%token<tok> Tendblk	/* ;; */
%token<tok> Tcolon	/* : */
%token<tok> Twith	/* :: */
%token<tok> Tdot	/* . */
%token<tok> Tcomma	/* , */
%token<tok> Tret	/* -> */
%token<tok> Tuse	/* use */
%token<tok> Tpkg	/* pkg */
%token<tok> Tattr	/* $attr */
%token<tok> Tsizeof	/* sizeof */

%token<tok> Tident

%start file

%type <ty> type structdef uniondef tupledef compoundtype functype funcsig
%type <ty> generictype
%type <tylist> typelist typarams optauxtypes
%type <traitspecs> traitspec traits
%type <traitspec> traitvar
%type <nodelist> traitlist

%type <tok> asnop cmpop addop mulop shiftop optident obrace

%type <tydef> tydef pkgtydef typeid
%type <trait> traitdef

%type<node> exprln retexpr goto continue break expr atomicexpr
%type<node> littok literal lorexpr landexpr borexpr strlit bandexpr
%type<node> cmpexpr addexpr mulexpr shiftexpr prefixexpr ternexpr
%type<node> postfixexpr funclit seqlit tuplit name block stmt label
%type<node> use fnparam declbody declcore typedeclcore structent
%type<node> arrayelt structelt tuphead ifstmt forstmt whilestmt
%type<node> matchstmt elifs optexprln loopcond optexpr match

%type <ucon> unionelt
%type <node> blkbody
%type <node> implstmt

%type <nodelist> arglist argdefs params matches
%type <nodelist> structbody structelts arrayelts
%type <nodelist> tupbody tuprest
%type <nodelist> decl decllist
%type <nodelist> traitbody implbody
%type <strlist> attrs

%type <uconlist> unionbody

/*
We want to bind more tightly to `Foo unaryexpr
than to binary operators around it. Eg:

	`Foo +bar

should be interpreted as

	(`Foo (+bar))

and not

	(`Foo) + (bar)

The default action of shifting does the right thing,
but warnings suck.
*/
%left Ttick
%left Tplus Tminus Tband

%union {
	struct {
		Srcloc loc;
		Node **nl;
		size_t nn;
	} nodelist;
	struct {
		char **str;
		size_t nstr;
	} strlist;
	struct {
		Srcloc loc;
		Ucon **ucl;
		size_t nucl;
	} uconlist;
	struct {
		Srcloc loc;
		Type **types;
		size_t ntypes;
	} tylist;
	struct { /* FIXME: unused */
		Srcloc loc;
		char *name;
		Type *type;
		Type **params;
		size_t nparams;
	} tydef;
	struct {
		Traitspec **spec;
		size_t nspec;
	} traitspecs;
	Traitspec *traitspec;
	Trait *trait;
	Node *node;
	Tok  *tok;
	Type *ty;
	Ucon *ucon;
}

%%

file	: toplev
	| file Tendln toplev
	;

toplev	: package
	| use {lappend(&file.uses, &file.nuses, $1);}
	| implstmt {
		lappend(&file.stmts, &file.nstmts, $1);
	}
	| traitdef {
		size_t i;
		puttrait(file.globls, $1->name, $1);
		for (i = 0; i < $1->nproto; i++)
			putdcl(file.globls, $1->proto[i]);
	}
	| tydef {
		puttype(file.globls, mkname($1.loc, $1.name), $1.type);
		installucons(file.globls, $1.type);
	}
	| decl {
		size_t i;
		Node *n, *d;

		for (i = 0; i < $1.nn; i++) {
			d = $1.nl[i];
			/* putdcl can merge, so we need to getdcl after */
			mangleautocall(d, declname(d));
			putdcl(file.globls, d);
			n = getdcl(file.globls, d->decl.name);
			lappend(&file.stmts, &file.nstmts, n);
			d->decl.isglobl = 1;
			if (d->decl.isinit)
				file.localinit = d;
			if (d->decl.isfini)
				file.localfini = d;
		}
	}
	| /* empty */
	;

decl	: attrs Tvar decllist traitspec {
		size_t i;

		for (i = 0; i < $3.nn; i++)
			setattrs($3.nl[i], $1.str, $1.nstr);
		$$ = $3;
	}
	| attrs Tconst decllist traitspec {
		size_t i;
		for (i = 0; i < $3.nn; i++) {
			setattrs($3.nl[i], $1.str, $1.nstr);
			$3.nl[i]->decl.isconst = 1;
		}
		$$ = $3;
	}
	| attrs Tgeneric decllist traitspec {
		size_t i;

		for (i = 0; i < $3.nn; i++) {
			setattrs($3.nl[i], $1.str, $1.nstr);
			setwith($3.nl[i]->decl.type, $4.spec, $4.nspec);
			$3.nl[i]->decl.isconst = 1;
			$3.nl[i]->decl.isgeneric = 1;
		}
		$$ = $3;
	}
        ;

attrs	: /* empty */ {$$.nstr = 0; $$.str = NULL;}
	| Tattr attrs {
		$$ = $2;
		lappend(&$$.str, &$$.nstr, strdup($1->id));
	}
	;

traitspec
	: Twith traits	{$$ = $2;}
	| /* nothing */ {$$.nspec = 0;}
	;

traits	: traitvar {
       		$$.spec = NULL;
       		$$.nspec = 0;
		lappend(&$$.spec, &$$.nspec, $1);
	}
	| traits listsep traitvar {
		$$ = $1;
		lappend(&$$.spec, &$$.nspec, $3);
	}
	;

traitvar
	: traitlist generictype {
		$$ = calloc(sizeof(Traitspec), 1);
		$$->trait = $1.nl;
		$$->ntrait = $1.nn;
		$$->param = $2;
		$$->aux = NULL;
	}
	| traitlist generictype Tret type {
		$$ = calloc(sizeof(Traitspec), 1);
		$$->trait = $1.nl;
		$$->ntrait = $1.nn;
		$$->param = $2;
		$$->aux = $4;
	}
	;

traitlist
	: name {
		$$.nl = 0;
		$$.nn = 0;
		lappend(&$$.nl, &$$.nn, $1);
	}
	| traitlist listsep name {
		lappend(&$$.nl, &$$.nn, $3);
	}
	;

decllist: declbody {
		$$.loc = $1->loc; $$.nl = NULL; $$.nn = 0;
		lappend(&$$.nl, &$$.nn, $1);
	}
	| declbody listsep decllist {
		linsert(&$3.nl, &$3.nn, 0, $1);
		$$=$3;
	}
	;

use	: Tuse Tident {$$ = mkuse($1->loc, $2->id, 0);}
	| Tuse Tstrlit {$$ = mkuse($1->loc, $2->strval.buf, 1);}
	;

optident: Tident      {$$ = $1;}
	| /* empty */ {$$ = NULL;}
	;

package : Tpkg optident Tasn pkgbody Tendblk {
		if (file.globls->name)
			lfatal($1->loc, "Package already declared\n");
		if ($2) {
			updatens(file.globls, $2->id);
		}
	}
	;

pkgbody : pkgitem
	| pkgbody Tendln pkgitem
	;

pkgitem : decl {
		size_t i;
		for (i = 0; i < $1.nn; i++) {
			$1.nl[i]->decl.vis = Visexport;
			$1.nl[i]->decl.isglobl = 1;
			putdcl(file.globls, $1.nl[i]);
			if ($1.nl[i]->decl.init)
				lappend(&file.stmts, &file.nstmts, $1.nl[i]);
		}
	}
	| pkgtydef {
		/* the type may only be null in a package context, so we
		can set the type when merging in this case.

		FIXME: clean up the fucking special cases. */
		if ($1.type)
			$1.type->vis = Visexport;
		puttype(file.globls, mkname($1.loc, $1.name), $1.type);
		installucons(file.globls, $1.type);
	}
	| traitdef {
		size_t i;
		$1->vis = Visexport;
		puttrait(file.globls, $1->name, $1);
		for (i = 0; i < $1->nproto; i++)
			putdcl(file.globls, $1->proto[i]);
	}
	| implstmt {
		$1->impl.vis = Visexport;
		lappend(&file.stmts, &file.nstmts, $1);
	}
	| /* empty */
	;

pkgtydef: attrs tydef {
		size_t i;
		$$ = $2;
		for (i = 0; i < $1.nstr; i++) {
			if (!strcmp($1.str[i], "pkglocal"))
				$$.type->ispkglocal = 1;
			else
				lfatal($$.loc, "invalid type attribute '%s'", $1.str[i]);
		}
	}
	;

declbody: declcore
	| declcore Tasn expr {
		$$ = $1;
		$1->decl.init = $3;
		mergeenv($1, $3);
	}
	;

declcore: name {$$ = mkdecl($1->loc, $1, mktyvar($1->loc));}
	| typedeclcore {$$ = $1;}
	;

typedeclcore
	: name Tcolon type {$$ = mkdecl($1->loc, $1, $3);}
	| Tcolon type {$$ = mkpseudodecl($1->loc, $2);}
	;

name	: Tident {$$ = mkname($1->loc, $1->id);}
	| Tident Tdot Tident {
		$$ = mknsname($3->loc, $1->id, $3->id);
	}
	;

implstmt
	: Timpl name type optauxtypes traitspec {
		size_t i;

		$$ = mkimplstmt($1->loc, $2, $3, $4.types, $4.ntypes, NULL, 0);
		$$->impl.isproto = 1;
		setwith($3, $5.spec, $5.nspec);
		for (i = 0; i < $4.ntypes; i++)
			setwith($4.types[i], $5.spec, $5.nspec);
	}
	| Timpl name type optauxtypes traitspec Tasn Tendln implbody Tendblk {
		size_t i;

		$$ = mkimplstmt($1->loc, $2, $3, $4.types, $4.ntypes, $8.nl, $8.nn);
		setwith($3, $5.spec, $5.nspec);
		for (i = 0; i < $4.ntypes; i++)
			setwith($4.types[i], $5.spec, $5.nspec);
	}
	;

implbody
	: optendlns {$$.nl = NULL; $$.nn = 0;}
	| implbody Tident Tasn exprln optendlns {
		Node *d;
		$$ = $1;
		d = mkdecl($2->loc, mkname($2->loc, $2->id), mktyvar($2->loc));
		d->decl.init = $4;
		d->decl.isconst = 1;
		d->decl.isglobl = 1;
		mergeenv(d, $4);
		lappend(&$$.nl, &$$.nn, d);
	}
	;

traitdef
	: Ttrait Tident generictype optauxtypes traitspec { /* trait prototype */
		size_t i;
		$$ = mktrait($1->loc,
			mkname($2->loc, $2->id), $3,
			$4.types, $4.ntypes,
			NULL, 0,
			1);
		setwith($3, $5.spec, $5.nspec);
		for (i = 0; i < $4.ntypes; i++)
			setwith($4.types[i], $5.spec, $5.nspec);
	}
	| Ttrait Tident generictype optauxtypes traitspec Tasn traitbody Tendblk /* trait definition */ {
		size_t i;
		$$ = mktrait($1->loc,
			mkname($2->loc, $2->id), $3,
			$4.types, $4.ntypes,
			$7.nl, $7.nn,
			0);
		for (i = 0; i < $7.nn; i++) {
			$7.nl[i]->decl.trait = $$;
			$7.nl[i]->decl.impls = mkht(tyhash, tyeq);
			$7.nl[i]->decl.isgeneric = 1;
		}
		setwith($3, $5.spec, $5.nspec);
		for (i = 0; i < $4.ntypes; i++)
			setwith($4.types[i], $5.spec, $5.nspec);
	}
	;

optauxtypes
	: Tret typelist {$$ = $2;}
	| /* empty */ { $$.types = NULL; $$.ntypes = 0; }
	;

traitbody
	: optendlns {$$.nl = NULL; $$.nn = 0;}
	| traitbody Tident Tcolon type optendlns {
		Node *d;
		$$ = $1;
		d = mkdecl($2->loc, mkname($2->loc, $2->id), $4);
		d->decl.isglobl = 1;
		d->decl.isgeneric = 1;
		d->decl.isconst = 1;
		lappend(&$$.nl, &$$.nn, d);
	}
	;


tydef	: Ttype typeid traitspec {$$ = $2;}
	| Ttype typeid traitspec Tasn type {
		$$ = $2;
		if ($$.nparams == 0)
			$$.type = mktyname($2.loc, mkname($2.loc, $2.name), $5);
		else
			$$.type = mktygeneric($2.loc, mkname($2.loc, $2.name), $2.params, $2.nparams, $5);
		setuname($$.type);
		setwith($$.type, $3.spec, $3.nspec);
	}
	;

typeid	: Tident {
		$$.loc = $1->loc;
		$$.name = $1->id;
		$$.params = NULL;
		$$.type = NULL;
	}
	| Tident Toparen typarams Tcparen {
		$$.loc = $1->loc;
		$$.name = $1->id;
		$$.params = $3.types;
		$$.nparams = $3.ntypes;
		$$.type = NULL;
	}
	;

typarams
	: generictype {
		$$.types = NULL; $$.ntypes = 0;
		lappend(&$$.types, &$$.ntypes, $1);
	}
	| typarams listsep generictype {
		lappend(&$$.types, &$$.ntypes, $3);
	}
	;

type	: structdef
	| tupledef
	| uniondef
	| compoundtype
	| generictype
	| Tellipsis {$$ = mktype($1->loc, Tyvalist);}
	;

generictype
	: Ttyparam {$$ = mktyparam($1->loc, $1->id);}
	;

compoundtype
	: functype   				{$$ = $1;}
	| type Tosqbrac Tcolon Tcsqbrac		{$$ = mktyslice($2->loc, $1);}
	| type Tosqbrac expr Tcsqbrac		{$$ = mktyarray($2->loc, $1, $3);}
	| type Tosqbrac Tellipsis Tcsqbrac 	{$$ = mktyarray($2->loc, $1, NULL);}
	| name Toparen typelist Tcparen 	{$$ = mktyunres($1->loc, $1, $3.types, $3.ntypes);}
	| type Tderef				{$$ = mktyptr($2->loc, $1);}
	| Tvoidlit				{$$ = mktyunres($1->loc, mkname($1->loc, $1->id), NULL, 0);}
	| name					{$$ = mktyunres($1->loc, $1, NULL, 0);}
	;

functype: Toparen funcsig Tcparen {$$ = $2;}
	;

funcsig : argdefs Tret type
	{$$ = mktyfunc($2->loc, $1.nl, $1.nn, $3);}
	;

argdefs : typedeclcore {
		$$.loc = $1->loc;
		$$.nl = NULL;
		$$.nn = 0; lappend(&$$.nl, &$$.nn, $1);
	}
	| argdefs listsep typedeclcore {lappend(&$$.nl, &$$.nn, $3);}
	| /* empty */ {
		$$.loc.line = 0;
		$$.loc.file = 0;
		$$.nl = NULL;
		$$.nn = 0;
	}
	;

tupledef: Toparen typelist Tcparen
	{$$ = mktytuple($1->loc, $2.types, $2.ntypes);}
	;

typelist: type {
		$$.types = NULL; $$.ntypes = 0;
		lappend(&$$.types, &$$.ntypes, $1);
	}
	| typelist listsep type
	{lappend(&$$.types, &$$.ntypes, $3);}
	;

structdef
	: Tstruct structbody Tendblk
	{$$ = mktystruct($1->loc, $2.nl, $2.nn);}
	;

structbody
	: structent {
		if ($1) {
			$$.nl = NULL;
			$$.nn = 0;
			lappend(&$$.nl, &$$.nn, $1);
		}
	}
	| structbody structent {
		if ($2)
			lappend(&$$.nl, &$$.nn, $2);
	}
	;

structent
	: declcore Tendln {$$ = $1;}
	| Tendln {$$ = NULL;}
	;

uniondef
	: Tunion unionbody Tendblk
	{$$ = mktyunion($1->loc, $2.ucl, $2.nucl);}
	;

unionbody
	: unionelt {
		$$.ucl = NULL;
		$$.nucl = 0;
		if ($1)
			lappend(&$$.ucl, &$$.nucl, $1);
	 }
	| unionbody unionelt {
		if ($2)
			lappend(&$$.ucl, &$$.nucl, $2);
	}
	;

unionelt /* nb: the ucon union type gets filled in when we have context */
	: Ttick name type Tendln {$$ = mkucon($2->loc, $2, NULL, $3);}
	| Ttick name Tendln {$$ = mkucon($2->loc, $2, NULL, NULL);}
	| Tendln {$$ = NULL;}
	;

goto	: Tgoto Tident {
		Node *lbl;

		lbl = mklbl($2->loc, "");
		lbl->expr.args[0]->lit.lblname = strdup($2->id);
		$$ = mkexpr($1->loc, Ojmp, lbl, NULL);
	}
	;

retexpr : Tret expr {$$ = mkexpr($1->loc, Oret, $2, NULL);}
	| expr
	;

optexpr : expr {$$ = $1;}
	| /* empty */ {$$ = NULL;}
	;

loopcond: exprln {$$ = $1;}
	| Tendln {$$ = mkboollit($1->loc, 1);}
	;

optexprln
	: exprln {$$ = $1;}
	| Tendln {$$ = NULL;}
	;

exprln	: expr Tendln
	;

expr	: ternexpr asnop expr
	{$$ = mkexpr($1->loc, binop($2->type), $1, $3, NULL);}
	| ternexpr
	;

asnop	: Tasn
	| Taddeq        /* += */
	| Tsubeq        /* -= */
	| Tmuleq        /* *= */
	| Tdiveq        /* /= */
	| Tmodeq        /* %= */
	| Tboreq        /* |= */
	| Tbxoreq       /* ^= */
	| Tbandeq       /* &= */
	| Tbsleq        /* <<= */
	| Tbsreq        /* >>= */
	;

ternexpr
	: lorexpr
	| lorexpr Tqmark lorexpr Tcolon lorexpr
	{$$ = mkexpr($1->loc, Otern, $1, $3, $5, NULL);}
	;

lorexpr : lorexpr Tlor landexpr
	{$$ = mkexpr($1->loc, binop($2->type), $1, $3, NULL);}
	| landexpr
	;

landexpr: landexpr Tland cmpexpr
	{$$ = mkexpr($1->loc, binop($2->type), $1, $3, NULL);}
	| cmpexpr
	;

cmpexpr : cmpexpr cmpop borexpr
	{$$ = mkexpr($1->loc, binop($2->type), $1, $3, NULL);}
	| borexpr
	;

cmpop	: Teq | Tgt | Tlt | Tge | Tle | Tne ;


borexpr : borexpr Tbor bandexpr
	{$$ = mkexpr($1->loc, binop($2->type), $1, $3, NULL);}
	| borexpr Tbxor bandexpr
	{$$ = mkexpr($1->loc, binop($2->type), $1, $3, NULL);}
	| bandexpr
	;

bandexpr: bandexpr Tband addexpr
	{$$ = mkexpr($1->loc, binop($2->type), $1, $3, NULL);}
	| addexpr
	;

addexpr : addexpr addop mulexpr
	{$$ = mkexpr($1->loc, binop($2->type), $1, $3, NULL);}
	| mulexpr
	;

addop	: Tplus | Tminus ;

mulexpr : mulexpr mulop shiftexpr
	{$$ = mkexpr($1->loc, binop($2->type), $1, $3, NULL);}
	| shiftexpr
	;

mulop	: Tmul | Tdiv | Tmod
	;

shiftexpr
	: shiftexpr shiftop prefixexpr
	{$$ = mkexpr($1->loc, binop($2->type), $1, $3, NULL);}
	| prefixexpr
	;

shiftop : Tbsl | Tbsr;

prefixexpr
	: Tauto prefixexpr	{$$ = mkexpr($1->loc, Oauto, $2, NULL);}
	| Tinc prefixexpr	{$$ = mkexpr($1->loc, Opreinc, $2, NULL);}
	| Tdec prefixexpr	{$$ = mkexpr($1->loc, Opredec, $2, NULL);}
	| Tband prefixexpr	{$$ = mkexpr($1->loc, Oaddr, $2, NULL);}
	| Tlnot prefixexpr	{$$ = mkexpr($1->loc, Olnot, $2, NULL);}
	| Tbnot prefixexpr	{$$ = mkexpr($1->loc, Obnot, $2, NULL);}
	| Tminus prefixexpr	{$$ = mkexpr($1->loc, Oneg, $2, NULL);}
	| Tplus prefixexpr	{$$ = $2;} /* positive is a nop */
	| Ttick name prefixexpr	{$$ = mkexpr($1->loc, Oucon, $2, $3, NULL);}
	| Ttick name 		{$$ = mkexpr($1->loc, Oucon, $2, NULL);}
	| postfixexpr
	;

postfixexpr
	: postfixexpr Tdot Tident
	{$$ = mkexpr($1->loc, Omemb, $1, mkname($3->loc, $3->id), NULL);}
	| postfixexpr Tdot Tintlit
	{$$ = mkexpr($1->loc, Otupmemb, $1, mkint($3->loc, $3->intval), NULL);}
	| postfixexpr Tinc
	{$$ = mkexpr($1->loc, Opostinc, $1, NULL);}
	| postfixexpr Tdec
	{$$ = mkexpr($1->loc, Opostdec, $1, NULL);}
	| postfixexpr Tosqbrac expr Tcsqbrac
	{$$ = mkexpr($1->loc, Oidx, $1, $3, NULL);}
	| postfixexpr Tosqbrac optexpr Tcolon optexpr Tcsqbrac
	{$$ = mksliceexpr($1->loc, $1, $3, $5);}
	| postfixexpr Tderef
	{$$ = mkexpr($1->loc, Oderef, $1, NULL);}
	| postfixexpr Toparen arglist Tcparen
	{$$ = mkcall($1->loc, $1, $3.nl, $3.nn);}
	| atomicexpr
	;

arglist : expr
	{$$.nl = NULL; $$.nn = 0; lappend(&$$.nl, &$$.nn, $1);}
	| arglist listsep expr
	{lappend(&$$.nl, &$$.nn, $3);}
	| /* empty */
	{$$.nl = NULL; $$.nn = 0;}
	;

atomicexpr
	: Tident
	{$$ = mkexpr($1->loc, Ovar, mkname($1->loc, $1->id), NULL);}
	| Tgap
	{$$ = mkexpr($1->loc, Ogap, NULL);}
	| literal
	| Toparen expr Tcparen
	{$$ = $2;}
	| Toparen expr Tcolon type Tcparen {
		$$ = mkexpr($1->loc, Ocast, $2, NULL);
		$$->expr.type = $4;
	}
	| Tsizeof Toparen type Tcparen {
		$$ = mkexpr($1->loc, Osize, mkpseudodecl($1->loc, $3), NULL);
	}
	| Timpl Toparen name listsep type Tcparen {
		$$ = mkexpr($1->loc, Ovar, $3, NULL);
		$$->expr.param = $5;
	}
	;

tupbody : tuphead tuprest
	{$$ = $2;
	 linsert(&$$.nl, &$$.nn, 0, $1);}
	;

tuphead : expr listsep {$$ = $1;}
	;

tuprest : /*empty */
	{$$.nl = NULL; $$.nn = 0;}
	| expr {
		$$.nl = NULL; $$.nn = 0;
		lappend(&$$.nl, &$$.nn, $1);
	}
	| tuprest listsep expr {lappend(&$$.nl, &$$.nn, $3);}
	;

literal : funclit       {$$ = mkexpr($1->loc, Olit, $1, NULL);}
	| littok        {$$ = mkexpr($1->loc, Olit, $1, NULL);}
	| seqlit        {$$ = $1;}
	| tuplit        {$$ = $1;}
	;

tuplit	: Toparen tupbody Tcparen
	{$$ = mkexprl($1->loc, Otup, $2.nl, $2.nn);}

littok	: strlit	{$$ = $1;}
	| Tchrlit       {$$ = mkchar($1->loc, $1->chrval);}
	| Tfloatlit     {$$ = mkfloat($1->loc, $1->fltval);}
	| Tboollit      {$$ = mkbool($1->loc, !strcmp($1->id, "true"));}
	| Tvoidlit      {$$ = mkvoid($1->loc);}
	| Tintlit {
		$$ = mkint($1->loc, $1->intval);
		if ($1->inttype)
			$$->lit.type = mktype($1->loc, $1->inttype);
	}
	;

strlit : Tstrlit { $$ = mkstr($1->loc, $1->strval); }
	| strlit Tstrlit {
		Str merged;

		merged.len = $1->lit.strval.len + $2->strval.len;
		merged.buf = malloc(merged.len);
		memcpy(merged.buf, $1->lit.strval.buf, $1->lit.strval.len);
		memcpy(merged.buf + $1->lit.strval.len, $2->strval.buf, $2->strval.len);
		$$ = mkstr($1->loc, merged);
	}
	;

obrace	: Tobrace {
		assert(lbldepth < LBLSTKSZ);
		lbldepth++;
	}
	;

funclit : obrace params traitspec Tendln blkbody Tcbrace {
		size_t i;
		Node *fn, *lit;

		for (i = 0; i < $2.nn; i++)
			setwith($2.nl[i]->decl.type, $3.spec, $3.nspec);
		$$ = mkfunc($1->loc, $2.nl, $2.nn, mktyvar($4->loc), $5);
		fn = $$->lit.fnval;
		for (i = 0; i < nlbls[lbldepth]; i++) {
			lit = lbls[lbldepth][i]->expr.args[0];
			putlbl(fn->func.scope, lit->lit.lblname, lbls[lbldepth][i]);
		}
		lfree(&lbls[lbldepth], &nlbls[lbldepth]);
		assert(lbldepth >= 0);
		lbldepth--;
	}
	| obrace params Tret type traitspec Tendln blkbody Tcbrace {
		size_t i;
		Node *fn, *lit;

		setwith($4, $5.spec, $5.nspec);
		for (i = 0; i < $2.nn; i++)
			setwith($2.nl[i]->decl.type, $5.spec, $5.nspec);
		$$ = mkfunc($1->loc, $2.nl, $2.nn, $4, $7);
		fn = $$->lit.fnval;
		for (i = 0; i < nlbls[lbldepth]; i++) {
			lit = lbls[lbldepth][i]->expr.args[0];
			putlbl(fn->func.scope, lit->lit.lblname, lbls[lbldepth][i]);
		}
		lfree(&lbls[lbldepth], &nlbls[lbldepth]);
		assert(lbldepth >= 0);
		lbldepth--;
	}
	;

params	: fnparam {
		$$.nl = NULL;
		$$.nn = 0;
		lappend(&$$.nl, &$$.nn, $1);
	}
	| params listsep fnparam {lappend(&$$.nl, &$$.nn, $3);}
	| /* empty */ {$$.nl = NULL; $$.nn = 0;}
	;

fnparam : declcore {$$ = $1;}
	| Tgap { $$ = mkpseudodecl($1->loc, mktyvar($1->loc)); }
	| Tgap Tcolon type { $$ = mkpseudodecl($1->loc, $3); }
	;

seqlit	: Tosqbrac optendlns arrayelts optcomma Tcsqbrac
	{$$ = mkexprl($1->loc, Oarr, $3.nl, $3.nn);}
	| Tosqbrac optendlns structelts optcomma Tcsqbrac
	{$$ = mkexprl($1->loc, Ostruct, $3.nl, $3.nn);}
	| Tosqbrac optendlns optcomma Tcsqbrac /* [] is the empty array. */
	{$$ = mkexprl($1->loc, Oarr, NULL, 0);}
	;

arrayelts
	: arrayelt {
		$$.nl = NULL;
		$$.nn = 0;
		if ($1->expr.idx)
			lappend(&$$.nl, &$$.nn, $1);
		else
			lappend(&$$.nl, &$$.nn, mkidxinit($1->loc, mkintlit($1->loc, 0), $1));
	}
	| arrayelts listsep arrayelt {
		if ($3->expr.idx)
			lappend(&$$.nl, &$$.nn, $3);
		else
			lappend(&$$.nl, &$$.nn, mkidxinit($3->loc, mkintlit($3->loc, $$.nn), $3));
	}
	;

arrayelt: expr optendlns {$$ = $1;}
	| Tdot Tosqbrac expr Tcsqbrac Tasn expr optendlns {
		$$ = mkidxinit($1->loc, $3, $6);
	}
	;

structelts
	: structelt {
		$$.nl = NULL;
		$$.nn = 0;
		lappend(&$$.nl, &$$.nn, $1);
	}
	| structelts listsep structelt {
		lappend(&$$.nl, &$$.nn, $3);
	}
	;

structelt: Tdot Tident Tasn expr optendlns {
		$$ = $4;
		mkidxinit($2->loc, mkname($2->loc, $2->id), $4);
	}
	;

listsep	: Tcomma optendlns
	;

optcomma: Tcomma optendlns
	| /* empty */
	;

optendlns 
	: /* empty */
	| optendlns Tendln
	;

stmt	: goto
	| break
	| continue
	| retexpr
	| label
	| ifstmt
	| forstmt
	| whilestmt
	| matchstmt
	| /* empty */ {$$ = NULL;}
	;

break	: Tbreak
	{$$ = mkexpr($1->loc, Obreak, NULL);}
	;

continue	: Tcontinue
	{$$ = mkexpr($1->loc, Ocontinue, NULL);}
	;

forstmt : Tfor optexprln loopcond optexprln block
	{$$ = mkloopstmt($1->loc, $2, $3, $4, $5);}
	| Tfor expr Tcolon exprln block
	{$$ = mkiterstmt($1->loc, $2, $4, $5);}
	| Tfor decl Tendln loopcond optexprln block {
		if ($2.nn != 1)
			lfatal($1->loc, "only one declaration is allowed in for loop");
		$$ = mkloopstmt($1->loc, $2.nl[0], $4, $5, $6);
		putdcl($$->loopstmt.scope, $2.nl[0]);
	}
	;

whilestmt
	: Twhile exprln block
	{$$ = mkloopstmt($1->loc, NULL, $2, NULL, $3);}
	;

ifstmt	: Tif exprln blkbody elifs
	{$$ = mkifstmt($1->loc, $2, $3, $4);}
	;

elifs	: Telif exprln blkbody elifs
	{$$ = mkifstmt($1->loc, $2, $3, $4);}
	| Telse block
	{$$ = $2;}
	| Tendblk
	{$$ = NULL;}
	;

matchstmt: Tmatch exprln optendlns Tbor matches Tendblk
	{$$ = mkmatchstmt($1->loc, $2, $5.nl, $5.nn);}
	 ;

matches : match {
		$$.nl = NULL;
		$$.nn = 0;
		lappend(&$$.nl, &$$.nn, $1);
	}
	| matches Tbor match {
		lappend(&$$.nl, &$$.nn, $3);
	}
	;

match	: expr Tcolon blkbody Tendln {$$ = mkmatch($1->loc, $1, $3);}
	;

block	: blkbody Tendblk
	;

blkbody : decl {
		size_t i;

		$$ = mkblock($1.loc, mkstab(0));
		for (i = 0; i < $1.nn; i++) {
			putdcl($$->block.scope, $1.nl[i]);
			addinit($$, $1.nl[i]);
		}
	}
	| stmt {
		$$ = mkblock(curloc, mkstab(0));
		if ($1)
			lappend(&$$->block.stmts, &$$->block.nstmts, $1);
	}
	| tydef {
		$$ = mkblock(curloc, mkstab(0));
		puttype($$->block.scope, mkname($1.loc, $1.name), $1.type);
		installucons($$->block.scope, $1.type);
	}
	| blkbody Tendln stmt {
		if ($3)
			lappend(&$1->block.stmts, &$1->block.nstmts, $3);
		$$ = $1;
	}
	| blkbody Tendln decl {
		size_t i;
		for (i = 0; i < $3.nn; i++){
			putdcl($$->block.scope, $3.nl[i]);
			addinit($$, $3.nl[i]);
		}
	}
	| blkbody Tendln tydef {
		puttype($$->block.scope, mkname($3.loc, $3.name), $3.type);
		installucons($$->block.scope, $3.type);
	}
	;

label	: Tcolon Tident {
		char buf[512];
		genlblstr(buf, sizeof buf, $2->id);
		$$ = mklbl($2->loc, buf);
		$$->expr.args[0]->lit.lblname = strdup($2->id);
		lappend(&lbls[lbldepth], &nlbls[lbldepth], $$);
	}
	;

%%

static void
addinit(Node *blk, Node *dcl)
{
	Node *n, *u;
	if (!dcl->decl.init) {
		n = mkexpr(dcl->loc, Ovar, dcl->decl.name, NULL);
		u = mkexpr(n->loc, Oundef, n, NULL);
		n->expr.did = dcl->decl.did;
		lappend(&blk->block.stmts, &blk->block.nstmts, u);
	}
	lappend(&blk->block.stmts, &blk->block.nstmts, dcl);
}

static void
mangleautocall(Node *n, char *fn)
{
	char name[1024];
	char *p, *s;

	if (strcmp(fn, "__init__") == 0)
		n->decl.isinit = 1;
	else if (strcmp(fn, "__fini__") == 0)
		n->decl.isfini = 1;
	else
		return;

	bprintf(name, sizeof name, "%s$%s", file.files[0], fn);
	if ((s = strrchr(name, '/')) != NULL)
		s++;
	else
		s = name;
	for(p = s; *p; p++)
		if (!isalnum(*p) && *p != '_')
			*p = '$';
	n->decl.vis = Vishidden;
	n->decl.name->name.name = strdup(s);
}

static void
setuname(Type *ty)
{
	size_t i;

	if (ty->sub[0]->type != Tyunion)
		return;
	for (i = 0; i < ty->sub[0]->nmemb; i++)
		ty->sub[0]->udecls[i]->utype = ty;
}

static Node *
mkpseudodecl(Srcloc l, Type *t)
{
	static int nextpseudoid;
	char buf[128];
	Node *d;

	bprintf(buf, 128, ".pdecl%d", nextpseudoid++);
	d = mkdecl(l, mkname(l, buf), t);
	d->decl.env = NULL;
	return d;
}

static void
setattrs(Node *dcl, char **attrs, size_t nattrs)
{
	size_t i;

	for (i = 0; i < nattrs; i++) {
		if (!strcmp(attrs[i], "extern"))
			dcl->decl.isextern = 1;
		else if (!strcmp(attrs[i], "$noret"))
			dcl->decl.isnoret = 1;
		else if (!strcmp(attrs[i], "pkglocal"))
			dcl->decl.ispkglocal = 1;
	}
}

static void
setwith(Type *ty, Traitspec **ts, size_t nts)
{
	size_t i, j;

	if (!ty)
		return;
	for (i = 0; i < nts; i++) {
		switch (ty->type) {
		case Typaram:
			assert(ts[i]->param->type == Typaram);
			if (streq(ty->pname, ts[i]->param->pname))
				lappend(&ty->spec, &ty->nspec, ts[i]);

			break;
		case Tyname:
		case Tyunres:
			for (j = 0; j < ty->ngparam; j++)
				setwith(ty->gparam[j], ts, nts);
			for (j = 0; j < ty->narg; j++)
				setwith(ty->arg[j], ts, nts);
			break;
		case Tystruct:
			for (j = 0; j < ty->nmemb; j++)
				setwith(ty->sdecls[j]->decl.type, ts, nts);
			break;
		case Tyunion:
			for (j = 0; j < ty->nmemb; j++)
				setwith(ty->udecls[j]->etype, ts, nts);
			break;
		case Typtr:
		case Tyarray:
		case Tyslice:
		case Tyfunc:
		case Tytuple:
			for (j = 0; j < ty->nsub; j++)
				setwith(ty->sub[j], ts, nts);
			break;
		default:
			break;
		}
	}
}

static void
mergeenv(Node *dcl, Node *init)
{
	Node *f;

	if (init->type != Nexpr || exprop(init) != Olit)
		return;
	if (init->expr.args[0]->lit.littype != Lfunc)
		return;
	f = init->expr.args[0]->lit.fnval;
	if (!dcl->decl.env)
		dcl->decl.env = mkenv();
	bindtype(dcl->decl.env, f->func.type);
}


static void
installucons(Stab *st, Type *t)
{
	Type *b;
	size_t i;

	if (!t)
		return;
	b = tybase(t);
	switch (b->type) {
	case Tystruct:
		for (i = 0; i < b->nmemb; i++)
			installucons(st, b->sdecls[i]->decl.type);
		break;
	case Tyunion:
		for (i = 0; i < b->nmemb; i++) {
			if (!b->udecls[i]->utype)
				b->udecls[i]->utype = b;
			b->udecls[i]->id = i;
			putucon(st, b->udecls[i]);
		}
		break;
	default:
		break;
	}
}


static Op
binop(int tt)
{
	Op o;

	o = Obad;
	switch (tt) {
	case Tplus:	o = Oadd;	break;
	case Tminus:	o = Osub;	break;
	case Tmul:	o = Omul;	break;
	case Tdiv:	o = Odiv;	break;
	case Tmod:	o = Omod;	break;
	case Tasn:	o = Oasn;	break;
	case Taddeq:	o = Oaddeq;	break;
	case Tsubeq:	o = Osubeq;	break;
	case Tmuleq:	o = Omuleq;	break;
	case Tdiveq:	o = Odiveq;	break;
	case Tmodeq:	o = Omodeq;	break;
	case Tboreq:	o = Oboreq;	break;
	case Tbxoreq:	o = Obxoreq;	break;
	case Tbandeq:	o = Obandeq;	break;
	case Tbsleq:	o = Obsleq;	break;
	case Tbsreq:	o = Obsreq;	break;
	case Tbor:	o = Obor;	break;
	case Tbxor:	o = Obxor;	break;
	case Tband:	o = Oband;	break;
	case Tbsl:	o = Obsl;	break;
	case Tbsr:	o = Obsr;	break;
	case Teq:	o = Oeq;	break;
	case Tgt:	o = Ogt;	break;
	case Tlt:	o = Olt;	break;
	case Tge:	o = Oge;	break;
	case Tle:	o = Ole;	break;
	case Tne:	o = One;	break;
	case Tlor:	o = Olor;	break;
	case Tland:	o = Oland;	break;
	default:
		die("Unimplemented binop\n");
		break;
	}
	return o;
}
