%{
#include "asm.h"
union {
	uvlong	l;
	double	d;
} u;
%}

%union
{
	Inst*	inst;
	Addr*	addr;
	vlong	ival;
	double	fval;
	String*	string;
	Sym*	sym;
	List*	list;
}

%left	'|'
%left	'^'
%left	'&'
%left	'<' '>'
%left	'+' '-'
%left	'*' '/' '%'

%type<inst>	label ilist inst
%type<ival>	con expr heapid
%type<addr>	addr raddr mem roff
%type<list>	elist
%type<string>	ptrs
%token<ival>	TOKI0 TOKI1 TOKI2 TOKI3 TCONST
%token		TOKSB TOKFP TOKHEAP TOKDB TOKDW TOKDL TOKDF TOKDS TOKVAR
%token		TOKEXT TOKMOD TOKLINK TOKENTRY TOKARRAY TOKINDIR TOKAPOP TOKLDTS TOKEXCS TOKEXC TOKETAB TOKSRC
%token<sym>	TID
%token<fval>	TFCONST
%token<string>	TSTRING

%%
prog	: ilist
	{
		assem($1);
	}
	;

ilist	:
	{ $$ = nil; }
	| ilist label
	{
		if($2 != nil) {
			$2->link = $1;
			$$ = $2;
		}
		else
			$$ = $1;
	}
	;

label	: TID ':' inst
	{
		$3->sym = $1;
		$$ = $3;
	}
	| TOKHEAP heapid ',' expr ptrs
	{
		heap($2, $4, $5);
		$$ = nil;
	}
	| data
	{
		$$ = nil;
	}
	| inst
	;

heapid	: '$' expr
	{
		$$ = $2;
	}
	| TID
	{
		$1->value = heapid++;
		$$ = $1->value;
	}
	;

ptrs	:
	{ $$ = nil; }
	| ',' TSTRING
	{
		$$ = $2;
	}
	;

elist	: expr
	{
		$$ = newi($1, nil);
	}
	| elist ',' expr
	{
		$$ = newi($3, $1);
	}
	;

inst	: TOKI3 addr ',' addr
	{
		$$ = ai($1);
		$$->src = $2;
		$$->dst = $4;
	}
	| TOKI3 addr ',' raddr ',' addr
	{
		$$ = ai($1);
		$$->src = $2;
		$$->reg = $4;
		$$->dst = $6;
	}
	| TOKI2 addr ',' addr
	{
		$$ = ai($1);
		$$->src = $2;
		$$->dst = $4;
	}
	| TOKI1 addr
	{
		$$ = ai($1);
		$$->dst = $2;
	}
	| TOKI0
	{
		$$ = ai($1);
	}
	;

data	: TOKDB expr ',' elist
	{
		data(DEFB, $2, $4);
	}
	| TOKDW expr ',' elist
	{
		data(DEFW, $2, $4);
	}
	| TOKDL expr ',' elist
	{
		data(DEFL, $2, $4);
	}
	| TOKDF expr ',' TCONST
	{
		data(DEFF, $2, newi(dtocanon((double)$4), nil));
	}
	| TOKDF expr ',' TFCONST
	{
		data(DEFF, $2, newi(dtocanon($4), nil));
	}
	| TOKDF expr ',' TID
	{
		if(strcmp($4->name, "Inf") == 0 || strcmp($4->name, "Infinity") == 0) {
			u.l = (uvlong)0x7ff00000<<32;
			data(DEFF, $2, newi(dtocanon(u.d), nil));
		} else if(strcmp($4->name, "NaN") == 0) {
			u.l = ((uvlong)0x7fffffff<<32) | (uvlong)0xffffffffUL;
			data(DEFF, $2, newi(dtocanon(u.d), nil));
		} else
			diag("bad value for real: %s", $4->name);
	}
	| TOKDF expr ',' '-' TCONST
	{
		data(DEFF, $2, newi(dtocanon(-(double)$5), nil));
	}
	| TOKDF expr ',' '-' TFCONST
	{
		data(DEFF, $2, newi(dtocanon(-$5), nil));
	}
	| TOKDF expr ',' '-' TID
	{
		if(strcmp($5->name, "Inf") == 0 || strcmp($5->name, "Infinity") == 0) {
			u.l = (uvlong)0xfff00000<<32;
			data(DEFF, $2, newi(dtocanon(u.d), nil));
		} else
			diag("bad value for real: %s", $5->name);
	}
	| TOKDS expr ',' TSTRING
	{
		data(DEFS, $2, news($4, nil));
	}
	| TOKVAR TID ',' expr
	{
		if($2->ds != 0)
			diag("%s declared twice", $2->name);
		$2->ds = $4;
		$2->value = dseg;
		dseg += $4;
	}
	| TOKEXT expr ',' expr ',' TSTRING
	{
		ext($2, $4, $6);
	}
	| TOKLINK expr ',' expr ',' expr ',' TSTRING
	{
		mklink($2, $4, $6, $8);
	}
	| TOKMOD TID
	{
		if(module != nil)
			diag("this module already defined as %s", $2->name);
		else
			module = $2;
	}
	| TOKENTRY expr ',' expr
	{
		if(pcentry >= 0)
			diag("this module already has entry point %d, %d" , pcentry, dentry);
		pcentry = $2;
		dentry = $4;
	}
	| TOKARRAY expr ',' heapid ',' expr
	{
		data(DEFA, $2, newa($4, $6));
	}
	| TOKINDIR expr ',' expr
	{
		data(DIND, $2, newa($4, 0));
	}
	| TOKAPOP
	{
		data(DAPOP, 0, newa(0, 0));
	}
	| TOKLDTS TID ',' expr
	{
		ldts($4);
	}
	| TOKEXCS expr
	{
		excs($2);
	}
	| TOKEXC expr ',' expr ',' expr ',' expr ',' expr ',' expr
	{
		exc($2, $4, $6, $8, $10, $12);
	}
	| TOKETAB TSTRING ',' expr
	{
		etab($2, $4);
	}
	| TOKETAB '*' ',' expr
	{
		etab(nil, $4);
	}
	| TOKSRC TSTRING
	{
		source($2);
	}
	;

raddr	: '$' expr
	{
		$$ = aa($2);
		$$->mode = AXIMM;
		if($$->val > 0x7FFF || $$->val < -0x8000)
			diag("immediate %d too large for middle operand", $$->val);
	}
	| roff
	{
		if($1->mode == AMP)
			$1->mode = AXINM;
		else
			$1->mode = AXINF;
		if($1->mode == AXINM && (ulong)$1->val > 0xFFFF)
			diag("register offset %d(mp) too large", $1->val);
		if($1->mode == AXINF && (ulong)$1->val > 0xFFFF)
			diag("register offset %d(fp) too large", $1->val);
		$$ = $1;
	}
	;

addr	: '$' expr
	{
		$$ = aa($2);
		$$->mode = AIMM;
	}
	| TID
	{
		$$ = aa(0);
		$$->sym = $1;
	}
	| mem
	;

mem	: '*' roff
	{
		$2->mode |= AIND;
		$$ = $2;
	}
	| expr '(' roff ')'
	{
		$3->mode |= AIND;
		if($3->val & 3)
			diag("indirect offset must be word size");
		if($3->mode == (AMP|AIND) && ((ulong)$3->val > 0xFFFF || (ulong)$1 > 0xFFFF))
			diag("indirect offset %d(%d(mp)) too large", $1, $3->val);
		if($3->mode == (AFP|AIND) && ((ulong)$3->val > 0xFFFF || (ulong)$1 > 0xFFFF))
			diag("indirect offset %d(%d(fp)) too large", $1, $3->val);
		$3->off = $3->val;
		$3->val = $1;
		$$ = $3;
	}
	| roff
	;

roff	: expr '(' TOKSB ')'
	{
		$$ = aa($1);
		$$->mode = AMP;
	}
	| expr '(' TOKFP ')'
	{
		$$ = aa($1);
		$$->mode = AFP;
	}
	;

con	: TCONST
	| TID
	{
		$$ = $1->value;
	}
	| '-' con
	{
		$$ = -$2;
	}
	| '+' con
	{
		$$ = $2;
	}
	| '~' con
	{
		$$ = ~$2;
	}
	| '(' expr ')'
	{
		$$ = $2;
	}
	;

expr:	con
	| expr '+' expr
	{
		$$ = $1 + $3;
	}
	| expr '-' expr
	{
		$$ = $1 - $3;
	}
	| expr '*' expr
	{
		$$ = $1 * $3;
	}
	| expr '/' expr
	{
		$$ = $1 / $3;
	}
	| expr '%' expr
	{
		$$ = $1 % $3;
	}
	| expr '<' '<' expr
	{
		$$ = $1 << $4;
	}
	| expr '>' '>' expr
	{
		$$ = $1 >> $4;
	}
	| expr '&' expr
	{
		$$ = $1 & $3;
	}
	| expr '^' expr
	{
		$$ = $1 ^ $3;
	}
	| expr '|' expr
	{
		$$ = $1 | $3;
	}
	;
