// Inferno utils/6a/a.y
// http://code.google.com/p/inferno-os/source/browse/utils/6a/a.y
//
//	Copyright © 1994-1999 Lucent Technologies Inc.	All rights reserved.
//	Portions Copyright © 1995-1997 C H Forsyth (forsyth@terzarima.net)
//	Portions Copyright © 1997-1999 Vita Nuova Limited
//	Portions Copyright © 2000-2007 Vita Nuova Holdings Limited (www.vitanuova.com)
//	Portions Copyright © 2004,2006 Bruce Ellis
//	Portions Copyright © 2005-2007 C H Forsyth (forsyth@terzarima.net)
//	Revisions Copyright © 2000-2007 Lucent Technologies Inc. and others
//	Portions Copyright © 2009 The Go Authors.  All rights reserved.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

%{
package main

import (
	"cmd/internal/asm"
	"cmd/internal/obj"
	"cmd/internal/obj/x86"
)
%}

%union {
	sym *asm.Sym
	lval int64
	dval float64
	sval string
	addr obj.Addr
	addr2 Addr2
}

%left	'|'
%left	'^'
%left	'&'
%left	'<' '>'
%left	'+' '-'
%left	'*' '/' '%'
%token	<lval>	LTYPE0 LTYPE1 LTYPE2 LTYPE3 LTYPE4
%token	<lval>	LTYPEC LTYPED LTYPEN LTYPER LTYPET LTYPEG LTYPEPC
%token	<lval>	LTYPES LTYPEM LTYPEI LTYPEXC LTYPEX LTYPERT LTYPEF
%token	<lval>	LCONST LFP LPC LSB
%token	<lval>	LBREG LLREG LSREG LFREG LMREG LXREG
%token	<dval>	LFCONST
%token	<sval>	LSCONST LSP
%token	<sym>	LNAME LLAB LVAR
%type	<lval>	con expr pointer offset
%type	<addr>	mem imm textsize reg nam rel rem rim rom omem nmem
%type	<addr2>	nonnon nonrel nonrem rimnon rimrem remrim
%type	<addr2>	spec3 spec4 spec5 spec6 spec7 spec8 spec9
%type	<addr2>	spec10 spec12 spec13
%%
prog:
|	prog 
	{
		stmtline = asm.Lineno;
	}
	line

line:
	LNAME ':'
	{
		$1 = asm.LabelLookup($1);
		if $1.Type == LLAB && $1.Value != int64(asm.PC) {
			yyerror("redeclaration of %s (%s)", $1.Labelname, $1.Name);
		}
		$1.Type = LLAB;
		$1.Value = int64(asm.PC)
	}
	line
|	';'
|	inst ';'
|	error ';'

inst:
	LNAME '=' expr
	{
		$1.Type = LVAR;
		$1.Value = $3;
	}
|	LVAR '=' expr
	{
		if $1.Value != $3 {
			yyerror("redeclaration of %s", $1.Name);
		}
		$1.Value = $3;
	}
|	LTYPE0 nonnon	{ outcode(int($1), &$2); }
|	LTYPE1 nonrem	{ outcode(int($1), &$2); }
|	LTYPE2 rimnon	{ outcode(int($1), &$2); }
|	LTYPE3 rimrem	{ outcode(int($1), &$2); }
|	LTYPE4 remrim	{ outcode(int($1), &$2); }
|	LTYPER nonrel	{ outcode(int($1), &$2); }
|	spec1
|	spec2
|	LTYPEC spec3	{ outcode(int($1), &$2); }
|	LTYPEN spec4	{ outcode(int($1), &$2); }
|	LTYPES spec5	{ outcode(int($1), &$2); }
|	LTYPEM spec6	{ outcode(int($1), &$2); }
|	LTYPEI spec7	{ outcode(int($1), &$2); }
|	LTYPEXC spec8	{ outcode(int($1), &$2); }
|	LTYPEX spec9	{ outcode(int($1), &$2); }
|	LTYPERT spec10	{ outcode(int($1), &$2); }
|	spec11
|	LTYPEPC spec12	{ outcode(int($1), &$2); }
|	LTYPEF spec13	{ outcode(int($1), &$2); }

nonnon:
	{
		$$.from = nullgen;
		$$.to = nullgen;
	}
|	','
	{
		$$.from = nullgen;
		$$.to = nullgen;
	}

rimrem:
	rim ',' rem
	{
		$$.from = $1;
		$$.to = $3;
	}

remrim:
	rem ',' rim
	{
		$$.from = $1;
		$$.to = $3;
	}

rimnon:
	rim ','
	{
		$$.from = $1;
		$$.to = nullgen;
	}
|	rim
	{
		$$.from = $1;
		$$.to = nullgen;
	}

nonrem:
	',' rem
	{
		$$.from = nullgen;
		$$.to = $2;
	}
|	rem
	{
		$$.from = nullgen;
		$$.to = $1;
	}

nonrel:
	',' rel
	{
		$$.from = nullgen;
		$$.to = $2;
	}
|	rel
	{
		$$.from = nullgen;
		$$.to = $1;
	}
|	imm ',' rel
	{
		$$.from = $1;
		$$.to = $3;
	}

spec1:	/* DATA */
	LTYPED nam '/' con ',' imm
	{
		var a Addr2
		a.from = $2
		a.to = $6
		outcode(obj.ADATA, &a)
		if asm.Pass > 1 {
			lastpc.From3 = new(obj.Addr)
			lastpc.From3.Type = obj.TYPE_CONST
			lastpc.From3.Offset = $4
		}
	}

spec2:	/* TEXT */
	LTYPET mem ',' '$' textsize
	{
		asm.Settext($2.Sym);
		outcode(obj.ATEXT, &Addr2{from: $2, to: $5})
		if asm.Pass > 1 {
			lastpc.From3 = new(obj.Addr)
		}	
	}
|	LTYPET mem ',' con ',' '$' textsize
	{
		asm.Settext($2.Sym);
		outcode(obj.ATEXT, &Addr2{from: $2, to: $7})
		if asm.Pass > 1 {
			lastpc.From3 = new(obj.Addr)
			lastpc.From3.Type = obj.TYPE_CONST
			lastpc.From3.Offset = $4
		}
	}

spec11:	/* GLOBL */
	LTYPEG mem ',' imm
	{
		asm.Settext($2.Sym)
		outcode(obj.AGLOBL, &Addr2{from: $2, to: $4})
		if asm.Pass > 1 {
			lastpc.From3 = new(obj.Addr)
		}	
	}
|	LTYPEG mem ',' con ',' imm
	{
		asm.Settext($2.Sym)
		outcode(obj.AGLOBL, &Addr2{from: $2, to: $6})
		if asm.Pass > 1 {
			lastpc.From3 = new(obj.Addr)
			lastpc.From3.Type = obj.TYPE_CONST
			lastpc.From3.Offset = $4
		}
	}

spec3:	/* JMP/CALL */
	',' rom
	{
		$$.from = nullgen;
		$$.to = $2;
	}
|	rom
	{
		$$.from = nullgen;
		$$.to = $1;
	}

spec4:	/* NOP */
	nonnon
|	nonrem

spec5:	/* SHL/SHR */
	rim ',' rem
	{
		$$.from = $1;
		$$.to = $3;
	}
|	rim ',' rem ':' LLREG
	{
		$$.from = $1;
		$$.to = $3;
		if $$.from.Index != obj.TYPE_NONE {
			yyerror("dp shift with lhs index");
		}
		$$.from.Index = int16($5);
	}

spec6:	/* MOVW/MOVL */
	rim ',' rem
	{
		$$.from = $1;
		$$.to = $3;
	}
|	rim ',' rem ':' LSREG
	{
		$$.from = $1;
		$$.to = $3;
		if $$.to.Index != obj.TYPE_NONE {
			yyerror("dp move with lhs index");
		}
		$$.to.Index = int16($5);
	}

spec7:
	rim ','
	{
		$$.from = $1;
		$$.to = nullgen;
	}
|	rim
	{
		$$.from = $1;
		$$.to = nullgen;
	}
|	rim ',' rem
	{
		$$.from = $1;
		$$.to = $3;
	}

spec8:	/* CMPPS/CMPPD */
	rem ',' reg ',' con
	{
		$$.from = $1;
		$$.from3 = $3;
		$$.to.Type = obj.TYPE_MEM; // to give library something to do
		$$.to.Offset = $5;
	}

spec9:	/* shufl */
	imm ',' rem ',' reg
	{
		$$.from = $1;
		$$.from3 = $3;
		$$.to = $5;
	}

spec10:	/* RET/RETF */
	{
		$$.from = nullgen;
		$$.to = nullgen;
	}
|	imm
	{
		$$.from = $1;
		$$.to = nullgen;
	}

spec12:	/* asm.PCDATA */
	rim ',' rim
	{
		if $1.Type != obj.TYPE_CONST || $3.Type != obj.TYPE_CONST {
			yyerror("arguments to asm.PCDATA must be integer constants");
		}
		$$.from = $1;
		$$.to = $3;
	}

spec13:	/* FUNCDATA */
	rim ',' rim
	{
		if $1.Type != obj.TYPE_CONST {
			yyerror("index for FUNCDATA must be integer constant");
		}
		if $3.Type != obj.TYPE_MEM || ($3.Name != obj.NAME_EXTERN && $3.Name != obj.NAME_STATIC) {
			yyerror("value for FUNCDATA must be symbol reference");
		}
		$$.from = $1;
		$$.to = $3;
	}

rem:
	reg
|	mem

rom:
	rel
|	nmem
|	'*' reg
	{
		$$ = $2;
	}
|	'*' omem
	{
		$$ = $2;
	}
|	reg
|	omem

rim:
	rem
|	imm

rel:
	con '(' LPC ')'
	{
		$$ = nullgen;
		$$.Type = obj.TYPE_BRANCH;
		$$.Offset = $1 + int64(asm.PC);
	}
|	LNAME offset
	{
		$1 = asm.LabelLookup($1);
		$$ = nullgen;
		if asm.Pass == 2 && $1.Type != LLAB {
			yyerror("undefined label: %s", $1.Labelname);
		}
		$$.Type = obj.TYPE_BRANCH;
		$$.Offset = $1.Value + $2;
	}

reg:
	LBREG
	{
		$$ = nullgen;
		$$.Type = obj.TYPE_REG
		$$.Reg = int16($1);
	}
|	LFREG
	{
		$$ = nullgen;
		$$.Type = obj.TYPE_REG
		$$.Reg = int16($1);
	}
|	LLREG
	{
		$$ = nullgen;
		$$.Type = obj.TYPE_REG
		$$.Reg = int16($1);
	}
|	LMREG
	{
		$$ = nullgen;
		$$.Type = obj.TYPE_REG
		$$.Reg = int16($1);
	}
|	LSP
	{
		$$ = nullgen;
		$$.Type = obj.TYPE_REG
		$$.Reg = x86.REG_SP;
	}
|	LSREG
	{
		$$ = nullgen;
		$$.Type = obj.TYPE_REG
		$$.Reg = int16($1);
	}
|	LXREG
	{
		$$ = nullgen;
		$$.Type = obj.TYPE_REG
		$$.Reg = int16($1);
	}

imm:
	'$' con
	{
		$$ = nullgen;
		$$.Type = obj.TYPE_CONST;
		$$.Offset = $2;
	}
|	'$' nam
	{
		$$ = $2;
		$$.Type = obj.TYPE_ADDR;
		/*
		if($2.Type == x86.D_AUTO || $2.Type == x86.D_PARAM)
			yyerror("constant cannot be automatic: %s",
				$2.sym.Name);
		 */
	}
|	'$' LSCONST
	{
		$$ = nullgen;
		$$.Type = obj.TYPE_SCONST;
		$$.Val = ($2+"\x00\x00\x00\x00\x00\x00\x00\x00")[:8]
	}
|	'$' LFCONST
	{
		$$ = nullgen;
		$$.Type = obj.TYPE_FCONST;
		$$.Val = $2;
	}
|	'$' '(' LFCONST ')'
	{
		$$ = nullgen;
		$$.Type = obj.TYPE_FCONST;
		$$.Val = $3;
	}
|	'$' '(' '-' LFCONST ')'
	{
		$$ = nullgen;
		$$.Type = obj.TYPE_FCONST;
		$$.Val = -$4;
	}
|	'$' '-' LFCONST
	{
		$$ = nullgen;
		$$.Type = obj.TYPE_FCONST;
		$$.Val = -$3;
	}

mem:
	omem
|	nmem

omem:
	con
	{
		$$ = nullgen;
		$$.Type = obj.TYPE_MEM
		$$.Offset = $1;
	}
|	con '(' LLREG ')'
	{
		$$ = nullgen;
		$$.Type = obj.TYPE_MEM
		$$.Reg = int16($3)
		$$.Offset = $1;
	}
|	con '(' LSP ')'
	{
		$$ = nullgen;
		$$.Type = obj.TYPE_MEM
		$$.Reg = x86.REG_SP
		$$.Offset = $1;
	}
|	con '(' LSREG ')'
	{
		$$ = nullgen;
		$$.Type = obj.TYPE_MEM
		$$.Reg = int16($3)
		$$.Offset = $1;
	}
|	con '(' LLREG '*' con ')'
	{
		$$ = nullgen;
		$$.Type = obj.TYPE_MEM
		$$.Offset = $1;
		$$.Index = int16($3);
		$$.Scale = int16($5);
		checkscale($$.Scale);
	}
|	con '(' LLREG ')' '(' LLREG '*' con ')'
	{
		$$ = nullgen;
		$$.Type = obj.TYPE_MEM
		$$.Reg = int16($3)
		$$.Offset = $1;
		$$.Index = int16($6);
		$$.Scale = int16($8);
		checkscale($$.Scale);
	}
|	con '(' LLREG ')' '(' LSREG '*' con ')'
	{
		$$ = nullgen;
		$$.Type = obj.TYPE_MEM
		$$.Reg = int16($3)
		$$.Offset = $1;
		$$.Index = int16($6);
		$$.Scale = int16($8);
		checkscale($$.Scale);
	}
|	'(' LLREG ')'
	{
		$$ = nullgen;
		$$.Type = obj.TYPE_MEM
		$$.Reg = int16($2)
	}
|	'(' LSP ')'
	{
		$$ = nullgen;
		$$.Type = obj.TYPE_MEM
		$$.Reg = x86.REG_SP
	}
|	'(' LLREG '*' con ')'
	{
		$$ = nullgen;
		$$.Type = obj.TYPE_MEM
		$$.Index = int16($2);
		$$.Scale = int16($4);
		checkscale($$.Scale);
	}
|	'(' LLREG ')' '(' LLREG '*' con ')'
	{
		$$ = nullgen;
		$$.Type = obj.TYPE_MEM
		$$.Reg = int16($2)
		$$.Index = int16($5);
		$$.Scale = int16($7);
		checkscale($$.Scale);
	}

nmem:
	nam
	{
		$$ = $1;
	}
|	nam '(' LLREG '*' con ')'
	{
		$$ = $1;
		$$.Index = int16($3);
		$$.Scale = int16($5);
		checkscale($$.Scale);
	}

nam:
	LNAME offset '(' pointer ')'
	{
		$$ = nullgen;
		$$.Type = obj.TYPE_MEM
		$$.Name = int8($4)
		$$.Sym = obj.Linklookup(asm.Ctxt, $1.Name, 0);
		$$.Offset = $2;
	}
|	LNAME '<' '>' offset '(' LSB ')'
	{
		$$ = nullgen;
		$$.Type = obj.TYPE_MEM
		$$.Name = obj.NAME_STATIC
		$$.Sym = obj.Linklookup(asm.Ctxt, $1.Name, 1);
		$$.Offset = $4;
	}

offset:
	{
		$$ = 0;
	}
|	'+' con
	{
		$$ = $2;
	}
|	'-' con
	{
		$$ = -$2;
	}

pointer:
	LSB
|	LSP
	{
		$$ = obj.NAME_AUTO;
	}
|	LFP

con:
	LCONST
|	LVAR
	{
		$$ = $1.Value;
	}
|	'-' con
	{
		$$ = -$2;
	}
|	'+' con
	{
		$$ = $2;
	}
|	'~' con
	{
		$$ = ^$2;
	}
|	'(' expr ')'
	{
		$$ = $2;
	}

textsize:
	LCONST
	{
		$$ = nullgen;
		$$.Type = obj.TYPE_TEXTSIZE;
		$$.Offset = $1;
		$$.Val = int32(obj.ArgsSizeUnknown);
	}
|	'-' LCONST
	{
		$$ = nullgen;
		$$.Type = obj.TYPE_TEXTSIZE;
		$$.Offset = -$2;
		$$.Val = int32(obj.ArgsSizeUnknown);
	}
|	LCONST '-' LCONST
	{
		$$ = nullgen;
		$$.Type = obj.TYPE_TEXTSIZE;
		$$.Offset = $1;
		$$.Val = int32($3);
	}
|	'-' LCONST '-' LCONST
	{
		$$ = nullgen;
		$$.Type = obj.TYPE_TEXTSIZE;
		$$.Offset = -$2;
		$$.Val = int32($4);
	}

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 << uint($4);
	}
|	expr '>' '>' expr
	{
		$$ = $1 >> uint($4);
	}
|	expr '&' expr
	{
		$$ = $1 & $3;
	}
|	expr '^' expr
	{
		$$ = $1 ^ $3;
	}
|	expr '|' expr
	{
		$$ = $1 | $3;
	}
