// Inferno utils/5a/a.y
// http://code.google.com/p/inferno-os/source/browse/utils/5a/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/arm"
)
%}

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

%left	'|'
%left	'^'
%left	'&'
%left	'<' '>'
%left	'+' '-'
%left	'*' '/' '%'
%token	<lval>	LTYPE1 LTYPE2 LTYPE3 LTYPE4 LTYPE5
%token	<lval>	LTYPE6 LTYPE7 LTYPE8 LTYPE9 LTYPEA
%token	<lval>	LTYPEB LTYPEC LTYPED LTYPEE
%token	<lval>	LTYPEG LTYPEH LTYPEI LTYPEJ LTYPEK
%token	<lval>	LTYPEL LTYPEM LTYPEN LTYPEBX LTYPEPLD
%token	<lval>	LCONST LSP LSB LFP LPC
%token	<lval>	LTYPEX LTYPEPC LTYPEF LR LREG LF LFREG LC LCREG LPSR LFCR
%token	<lval>	LCOND LS LAT LGLOBL
%token	<dval>	LFCONST
%token	<sval>	LSCONST
%token	<sym>	LNAME LLAB LVAR
%type	<lval>	con expr oexpr pointer offset sreg spreg creg
%type	<lval>	rcon cond reglist
%type	<addr>	gen rel reg regreg freg shift fcon frcon textsize
%type	<addr>	imm ximm name oreg ireg nireg ioreg imsr
%%
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", $1.Labelname)
		}
		$1.Type = LLAB;
		$1.Value = int64(asm.PC)
	}
	line
|	LNAME '=' expr ';'
	{
		$1.Type = LVAR;
		$1.Value = int64($3);
	}
|	LVAR '=' expr ';'
	{
		if $1.Value != int64($3) {
			yyerror("redeclaration of %s", $1.Name)
		}
		$1.Value = int64($3);
	}
|	';'
|	inst ';'
|	error ';'

inst:
/*
 * ADD
 */
	LTYPE1 cond imsr ',' spreg ',' reg
	{
		outcode($1, $2, &$3, $5, &$7);
	}
|	LTYPE1 cond imsr ',' spreg ','
	{
		outcode($1, $2, &$3, $5, &nullgen);
	}
|	LTYPE1 cond imsr ',' reg
	{
		outcode($1, $2, &$3, 0, &$5);
	}
/*
 * MVN
 */
|	LTYPE2 cond imsr ',' reg
	{
		outcode($1, $2, &$3, 0, &$5);
	}
/*
 * MOVW
 */
|	LTYPE3 cond gen ',' gen
	{
		outcode($1, $2, &$3, 0, &$5);
	}
/*
 * B/BL
 */
|	LTYPE4 cond comma rel
	{
		outcode($1, $2, &nullgen, 0, &$4);
	}
|	LTYPE4 cond comma nireg
	{
		outcode($1, $2, &nullgen, 0, &$4);
	}
/*
 * BX
 */
|	LTYPEBX comma ireg
	{
		outcode($1, Always, &nullgen, 0, &$3);
	}
/*
 * BEQ
 */
|	LTYPE5 comma rel
	{
		outcode($1, Always, &nullgen, 0, &$3);
	}
/*
 * SWI
 */
|	LTYPE6 cond comma gen
	{
		outcode($1, $2, &nullgen, 0, &$4);
	}
/*
 * CMP
 */
|	LTYPE7 cond imsr ',' spreg comma
	{
		outcode($1, $2, &$3, $5, &nullgen);
	}
/*
 * MOVM
 */
|	LTYPE8 cond ioreg ',' '[' reglist ']'
	{
		var g obj.Addr

		g = nullgen;
		g.Type = obj.TYPE_REGLIST;
		g.Offset = int64($6);
		outcode($1, $2, &$3, 0, &g);
	}
|	LTYPE8 cond '[' reglist ']' ',' ioreg
	{
		var g obj.Addr

		g = nullgen;
		g.Type = obj.TYPE_REGLIST;
		g.Offset = int64($4);
		outcode($1, $2, &g, 0, &$7);
	}
/*
 * SWAP
 */
|	LTYPE9 cond reg ',' ireg ',' reg
	{
		outcode($1, $2, &$5, int32($3.Reg), &$7);
	}
|	LTYPE9 cond reg ',' ireg comma
	{
		outcode($1, $2, &$5, int32($3.Reg), &$3);
	}
|	LTYPE9 cond comma ireg ',' reg
	{
		outcode($1, $2, &$4, int32($6.Reg), &$6);
	}
/*
 * RET
 */
|	LTYPEA cond comma
	{
		outcode($1, $2, &nullgen, 0, &nullgen);
	}
/*
 * TEXT
 */
|	LTYPEB name ',' '$' textsize
	{
		asm.Settext($2.Sym);
		outcode($1, Always, &$2, 0, &$5);
		if asm.Pass > 1 {
			lastpc.From3 = new(obj.Addr)
		}
	}
|	LTYPEB name ',' con ',' '$' textsize
	{
		asm.Settext($2.Sym);
		outcode($1, Always, &$2, 0, &$7);
		if asm.Pass > 1 {
			lastpc.From3 = new(obj.Addr)
			lastpc.From3.Type = obj.TYPE_CONST;
			lastpc.From3.Offset = int64($4)
		}
	}
/*
 * GLOBL
 */
|	LGLOBL name ',' imm
	{
		asm.Settext($2.Sym)
		outcode($1, Always, &$2, 0, &$4)
		if asm.Pass > 1 {
			lastpc.From3 = new(obj.Addr)
		}
	}
|	LGLOBL name ',' con ',' imm
	{
		asm.Settext($2.Sym)
		outcode($1, Always, &$2, 0, &$6)
		if asm.Pass > 1 {
			lastpc.From3 = new(obj.Addr)
			lastpc.From3.Type = obj.TYPE_CONST
			lastpc.From3.Offset = int64($4)
		}
	}

/*
 * DATA
 */
|	LTYPEC name '/' con ',' ximm
	{
		outcode($1, Always, &$2, 0, &$6)
		if asm.Pass > 1 {
			lastpc.From3 = new(obj.Addr)
			lastpc.From3.Type = obj.TYPE_CONST
			lastpc.From3.Offset = int64($4)
		}
	}
/*
 * CASE
 */
|	LTYPED cond reg comma
	{
		outcode($1, $2, &$3, 0, &nullgen);
	}
/*
 * word
 */
|	LTYPEH comma ximm
	{
		outcode($1, Always, &nullgen, 0, &$3);
	}
/*
 * floating-point coprocessor
 */
|	LTYPEI cond freg ',' freg
	{
		outcode($1, $2, &$3, 0, &$5);
	}
|	LTYPEK cond frcon ',' freg
	{
		outcode($1, $2, &$3, 0, &$5);
	}
|	LTYPEK cond frcon ',' LFREG ',' freg
	{
		outcode($1, $2, &$3, $5, &$7);
	}
|	LTYPEL cond freg ',' freg comma
	{
		outcode($1, $2, &$3, int32($5.Reg), &nullgen);
	}
/*
 * MCR MRC
 */
|	LTYPEJ cond con ',' expr ',' spreg ',' creg ',' creg oexpr
	{
		var g obj.Addr

		g = nullgen;
		g.Type = obj.TYPE_CONST;
		g.Offset = int64(
			(0xe << 24) |		/* opcode */
			($1 << 20) |		/* MCR/MRC */
			(($2^C_SCOND_XOR) << 28) |		/* scond */
			(($3 & 15) << 8) |	/* coprocessor number */
			(($5 & 7) << 21) |	/* coprocessor operation */
			(($7 & 15) << 12) |	/* arm register */
			(($9 & 15) << 16) |	/* Crn */
			(($11 & 15) << 0) |	/* Crm */
			(($12 & 7) << 5) |	/* coprocessor information */
			(1<<4));			/* must be set */
		outcode(AMRC, Always, &nullgen, 0, &g);
	}
/*
 * MULL r1,r2,(hi,lo)
 */
|	LTYPEM cond reg ',' reg ',' regreg
	{
		outcode($1, $2, &$3, int32($5.Reg), &$7);
	}
/*
 * MULA r1,r2,r3,r4: (r1*r2+r3) & 0xffffffff . r4
 * MULAW{T,B} r1,r2,r3,r4
 */
|	LTYPEN cond reg ',' reg ',' reg ',' spreg
	{
		$7.Type = obj.TYPE_REGREG2;
		$7.Offset = int64($9);
		outcode($1, $2, &$3, int32($5.Reg), &$7);
	}
/*
 * PLD
 */
|	LTYPEPLD oreg
	{
		outcode($1, Always, &$2, 0, &nullgen);
	}
/*
 * PCDATA
 */
|	LTYPEPC gen ',' gen
	{
		if $2.Type != obj.TYPE_CONST || $4.Type != obj.TYPE_CONST {
			yyerror("arguments to PCDATA must be integer constants")
		}
		outcode($1, Always, &$2, 0, &$4);
	}
/*
 * FUNCDATA
 */
|	LTYPEF gen ',' gen
	{
		if $2.Type != obj.TYPE_CONST {
			yyerror("index for FUNCDATA must be integer constant")
		}
		if $4.Type != obj.NAME_EXTERN && $4.Type != obj.NAME_STATIC && $4.Type != obj.TYPE_MEM {
			yyerror("value for FUNCDATA must be symbol reference")
		}
 		outcode($1, Always, &$2, 0, &$4);
	}
/*
 * END
 */
|	LTYPEE comma
	{
		outcode($1, Always, &nullgen, 0, &nullgen);
	}

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

cond:
	{
		$$ = Always;
	}
|	cond LCOND
	{
		$$ = ($1 & ^ C_SCOND) | $2;
	}
|	cond LS
	{
		$$ = $1 | $2;
	}

comma:
|	',' comma

rel:
	con '(' LPC ')'
	{
		$$ = nullgen;
		$$.Type = obj.TYPE_BRANCH;
		$$.Offset = int64($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 + int64($2);
	}

ximm:	'$' con
	{
		$$ = nullgen;
		$$.Type = obj.TYPE_CONST;
		$$.Offset = int64($2);
	}
|	'$' oreg
	{
		$$ = $2;
		$$.Type = obj.TYPE_ADDR;
	}
|	'$' LSCONST
	{
		$$ = nullgen;
		$$.Type = obj.TYPE_SCONST;
		$$.Val = $2
	}
|	fcon

fcon:
	'$' LFCONST
	{
		$$ = nullgen;
		$$.Type = obj.TYPE_FCONST;
		$$.Val = $2;
	}
|	'$' '-' LFCONST
	{
		$$ = nullgen;
		$$.Type = obj.TYPE_FCONST;
		$$.Val = -$3;
	}

reglist:
	spreg
	{
		$$ = 1 << uint($1&15);
	}
|	spreg '-' spreg
	{
		$$=0;
		for i:=$1; i<=$3; i++ {
			$$ |= 1<<uint(i&15)
		}
		for i:=$3; i<=$1; i++ {
			$$ |= 1<<uint(i&15)
		}
	}
|	spreg comma reglist
	{
		$$ = (1<<uint($1&15)) | $3;
	}

gen:
	reg
|	ximm
|	shift
|	shift '(' spreg ')'
	{
		$$ = $1;
		$$.Reg = int16($3);
	}
|	LPSR
	{
		$$ = nullgen;
		$$.Type = obj.TYPE_REG
		$$.Reg = int16($1);
	}
|	LFCR
	{
		$$ = nullgen;
		$$.Type = obj.TYPE_REG
		$$.Reg = int16($1);
	}
|	con
	{
		$$ = nullgen;
		$$.Type = obj.TYPE_MEM;
		$$.Offset = int64($1);
	}
|	oreg
|	freg

nireg:
	ireg
|	name
	{
		$$ = $1;
		if($1.Name != obj.NAME_EXTERN && $1.Name != obj.NAME_STATIC) {
		}
	}

ireg:
	'(' spreg ')'
	{
		$$ = nullgen;
		$$.Type = obj.TYPE_MEM;
		$$.Reg = int16($2);
		$$.Offset = 0;
	}

ioreg:
	ireg
|	con '(' sreg ')'
	{
		$$ = nullgen;
		$$.Type = obj.TYPE_MEM;
		$$.Reg = int16($3);
		$$.Offset = int64($1);
	}

oreg:
	name
|	name '(' sreg ')'
	{
		$$ = $1;
		$$.Type = obj.TYPE_MEM;
		$$.Reg = int16($3);
	}
|	ioreg

imsr:
	reg
|	imm
|	shift

imm:	'$' con
	{
		$$ = nullgen;
		$$.Type = obj.TYPE_CONST;
		$$.Offset = int64($2);
	}

reg:
	spreg
	{
		$$ = nullgen;
		$$.Type = obj.TYPE_REG;
		$$.Reg = int16($1);
	}

regreg:
	'(' spreg ',' spreg ')'
	{
		$$ = nullgen;
		$$.Type = obj.TYPE_REGREG;
		$$.Reg = int16($2);
		$$.Offset = int64($4);
	}

shift:
	spreg '<' '<' rcon
	{
		$$ = nullgen;
		$$.Type = obj.TYPE_SHIFT;
		$$.Offset = int64($1&15) | int64($4) | (0 << 5);
	}
|	spreg '>' '>' rcon
	{
		$$ = nullgen;
		$$.Type = obj.TYPE_SHIFT;
		$$.Offset = int64($1&15) | int64($4) | (1 << 5);
	}
|	spreg '-' '>' rcon
	{
		$$ = nullgen;
		$$.Type = obj.TYPE_SHIFT;
		$$.Offset = int64($1&15) | int64($4) | (2 << 5);
	}
|	spreg LAT '>' rcon
	{
		$$ = nullgen;
		$$.Type = obj.TYPE_SHIFT;
		$$.Offset = int64($1&15) | int64($4) | (3 << 5);
	}

rcon:
	spreg
	{
		if $$ < REG_R0 || $$ > REG_R15 {
			print("register value out of range\n")
		}
		$$ = (($1&15) << 8) | (1 << 4);
	}
|	con
	{
		if $$ < 0 || $$ >= 32 {
			print("shift value out of range\n")
		}
		$$ = ($1&31) << 7;
	}

sreg:
	LREG
|	LPC
	{
		$$ = REGPC;
	}
|	LR '(' expr ')'
	{
		if $3 < 0 || $3 >= NREG {
			print("register value out of range\n")
		}
		$$ = REG_R0 + $3;
	}

spreg:
	sreg
|	LSP
	{
		$$ = REGSP;
	}

creg:
	LCREG
|	LC '(' expr ')'
	{
		if $3 < 0 || $3 >= NREG {
			print("register value out of range\n")
		}
		$$ = $3; // TODO(rsc): REG_C0+$3
	}

frcon:
	freg
|	fcon

freg:
	LFREG
	{
		$$ = nullgen;
		$$.Type = obj.TYPE_REG;
		$$.Reg = int16($1);
	}
|	LF '(' con ')'
	{
		$$ = nullgen;
		$$.Type = obj.TYPE_REG;
		$$.Reg = int16(REG_F0 + $3);
	}

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

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

pointer:
	LSB
|	LSP
|	LFP

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

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