%{
#include "a.h"
%}
%union
{
       Sym     *sym;
       vlong   lval;
       double  dval;
       char    sval[NSNAME];
       Gen     gen;
}
%left   '|'
%left   '^'
%left   '&'
%left   '<' '>'
%left   '+' '-'
%left   '*' '/' '%'
%token  <lval>  LTYPE0 LTYPE1 LTYPE2 LTYPE3 LTYPE4 LTYPE5
%token  <lval>  LTYPE6 LTYPE7 LTYPE8 LTYPE9 LTYPEA
%token  <lval>  LTYPEB LTYPEC LTYPED LTYPEE LTYPEF
%token  <lval>  LTYPEG LTYPEH LTYPEI LTYPEJ LTYPEK
%token  <lval>  LTYPEL LTYPEM LTYPEN LTYPEO LTYPEP LTYPEQ
%token  <lval>  LTYPER LTYPES LTYPET LTYPEU LTYPEV LTYPEW LTYPEX LTYPEY LTYPEZ
%token  <lval>  LMOVK LDMB LSTXR
%token  <lval>  LCONST LSP LSB LFP LPC
%token  <lval>  LR LREG LF LFREG LV LVREG LC LCREG LFCR LFCSEL
%token  <lval>  LCOND LS LAT LEXT LSPR LSPREG LVTYPE
%token  <dval>  LFCONST
%token  <sval>  LSCONST
%token  <sym>   LNAME LLAB LVAR
%type   <lval>  con expr pointer offset sreg spreg
%type   <lval>  scon indexreg vset vreglist
%type   <gen>   gen rel reg freg vreg shift fcon frcon extreg vlane vgen
%type   <gen>   imm ximm name oreg nireg ioreg imsr spr cond sysarg
%%
prog:
|       prog line

line:
       LLAB ':'
       {
               if($1->value != pc)
                       yyerror("redeclaration of %s", $1->name);
               $1->value = pc;
       }
       line
|       LNAME ':'
       {
               $1->type = LLAB;
               $1->value = pc;
       }
       line
|       LNAME '=' expr ';'
       {
               $1->type = LVAR;
               $1->value = $3;
       }
|       LVAR '=' expr ';'
       {
               if($1->value != $3)
                       yyerror("redeclaration of %s", $1->name);
               $1->value = $3;
       }
|       ';'
|       inst ';'
|       error ';'

inst:
/*
* ERET
*/
       LTYPE0 comma
       {
               outcode($1, &nullgen, NREG, &nullgen);
       }
/*
* ADD
*/
|       LTYPE1 imsr ',' spreg ',' reg
       {
               outcode($1, &$2, $4, &$6);
       }
|       LTYPE1 imsr ',' spreg ','
       {
               outcode($1, &$2, $4, &nullgen);
       }
|       LTYPE1 imsr ',' reg
       {
               outcode($1, &$2, NREG, &$4);
       }
/*
* CLS
*/
|       LTYPE2 imsr ',' reg
       {
               outcode($1, &$2, NREG, &$4);
       }
/*
* MOV
*/
|       LTYPE3 gen ',' gen
       {
               outcode($1, &$2, NREG, &$4);
       }
/*
* MOVK
*/
|       LMOVK imm ',' reg
       {
               outcode($1, &$2, NREG, &$4);
       }
|       LMOVK imm '<' '<' con ',' reg
       {
               Gen g;
               g = nullgen;
               g.type = D_CONST;
               g.offset = $5;
               outcode4($1, &$2, NREG, &g, &$7);
       }
/*
* B/BL
*/
|       LTYPE4 comma rel
       {
               outcode($1, &nullgen, NREG, &$3);
       }
|       LTYPE4 comma nireg
       {
               outcode($1, &nullgen, NREG, &$3);
       }
/*
* BEQ
*/
|       LTYPE5 comma rel
       {
               outcode($1, &nullgen, NREG, &$3);
       }
/*
* SVC
*/
|       LTYPE6 comma gen
       {
               outcode($1, &nullgen, NREG, &$3);
       }
|       LTYPE6
       {
               outcode($1, &nullgen, NREG, &nullgen);
       }
/*
* CMP
*/
|       LTYPE7 imsr ',' spreg comma
       {
               outcode($1, &$2, $4, &nullgen);
       }
/*
* CBZ
*/
|       LTYPE8 reg ',' rel
       {
               outcode($1, &$2, NREG, &$4);
       }
/*
* CSET
*/
|       LTYPER cond ',' reg
       {
               outcode($1, &$2, NREG, &$4);
       }
/*
* CSEL/CINC/CNEG/CINV
*/
|       LTYPES cond ',' reg ',' reg ',' reg
       {
               outcode4($1, &$2, $6.reg, &$4, &$8);
       }
|       LTYPES cond ',' reg ',' reg
       {
               outcode($1, &$2, $4.reg, &$6);
       }
/*
* TBZ
*/
|       LTYPET imm ',' reg ',' rel
       {
               outcode($1, &$2, $4.reg, &$6);
       }
/*
* CCMN
*/
|       LTYPEU cond ',' imsr ',' reg ',' imm comma
       {
               outcode4($1, &$2, $6.reg, &$4, &$8);
       }
/*
* ADR
*/
|       LTYPEV rel ',' reg
       {
               outcode($1, &$2, NREG, &$4);
       }
|       LTYPEV '$' name ',' reg
       {
               outcode($1, &$3, NREG, &$5);
       }
/*
* BFM/BFI
*/
|       LTYPEY imm ',' imm ',' spreg ',' reg
       {
               outcode4($1, &$2, $6, &$4, &$8);
       }
/*
* EXTR
*/
|       LTYPEP imm ',' reg ',' spreg ',' reg
       {
               outcode4($1, &$2, $6, &$4, &$8);
       }
/*
* RET/RETURN
*/
|       LTYPEA comma
       {
               outcode($1, &nullgen, NREG, &nullgen);
       }
|       LTYPEA reg
       {
               outcode($1, &nullgen, NREG, &$2);
       }
/*
* NOP
*/
|       LTYPEQ comma
       {
               outcode($1, &nullgen, NREG, &nullgen);
       }
|       LTYPEQ reg comma
       {
               outcode($1, &$2, NREG, &nullgen);
       }
|       LTYPEQ freg comma
       {
               outcode($1, &$2, NREG, &nullgen);
       }
|       LTYPEQ ',' reg
       {
               outcode($1, &nullgen, NREG, &$3);
       }
|       LTYPEQ ',' freg
       {
               outcode($1, &nullgen, NREG, &$3);
       }
/*
* TEXT/GLOBL
*/
|       LTYPEB name ',' imm
       {
               outcode($1, &$2, NREG, &$4);
       }
|       LTYPEB name ',' con ',' imm
       {
               outcode($1, &$2, $4, &$6);
       }
/*
* DATA
*/
|       LTYPEC name '/' con ',' ximm
       {
               outcode($1, &$2, $4, &$6);
       }
/*
* CASE
*/
|       LTYPED reg ',' reg
       {
               outcode($1, &$2, NREG, &$4);
       }
/*
* word
*/
|       LTYPEH comma ximm
       {
               outcode($1, &nullgen, NREG, &$3);
       }
/*
* floating-point
*/
|       LTYPEI freg ',' freg
       {
               outcode($1, &$2, NREG, &$4);
       }
/*
* FADDD
*/
|       LTYPEK frcon ',' freg
       {
               outcode($1, &$2, NREG, &$4);
       }
|       LTYPEK frcon ',' freg ',' freg
       {
               outcode($1, &$2, $4.reg, &$6);
       }
/*
* FCMP
*/
|       LTYPEL frcon ',' freg comma
       {
               outcode($1, &$2, $4.reg, &nullgen);
       }
/*
* FCCMP
*/
|       LTYPEF cond ',' freg ',' freg ',' imm comma
       {
               outcode4($1, &$2, $6.reg, &$4, &$8);
       }
/*
* FMULA
*/
|       LTYPE9 freg ',' freg ', ' freg ',' freg comma
       {
               outcode4($1, &$2, $4.reg, &$6, &$8);
       }
/*
* FCSEL
*/
|       LFCSEL cond ',' freg ',' freg ',' freg
       {
               outcode4($1, &$2, $6.reg, &$4, &$8);
       }
/*
* SIMD
*/
|       LTYPEW vgen ',' vgen
       {
               outcode($1, &$2, NREG, &$4);
       }
|       LTYPEW vgen ',' vgen ',' vgen
       {
               outcode($1, &$2, $4.reg, &$6);
       }
/*
* MOVP/MOVNP
*/
|       LTYPEJ gen ',' sreg ',' gen
       {
               outcode($1, &$2, $4, &$6);
       }
/*
* MADD Rn,Rm,Ra,Rd
*/
|       LTYPEM reg ',' reg ',' sreg ',' reg
       {
               outcode4($1, &$2, $6, &$4, &$8);
       }
/*
* SYS/SYSL
*/
|       LTYPEN sysarg
       {
               outcode($1, &$2, NREG, &nullgen);
       }
|       LTYPEN reg ',' sysarg
       {
               outcode($1, &$4, $2.reg, &nullgen);
       }
|       LTYPEO sysarg ',' reg
       {
               outcode($1, &$2, NREG, &$4);
       }
/*
* DMB, HINT
*/
|       LDMB imm
       {
               outcode($1, &$2, NREG, &nullgen);
       }
/*
* STXR
*/
|       LSTXR reg ',' gen ',' sreg
       {
               outcode($1, &$2, $6, &$4);
       }
/*
* END
*/
|       LTYPEE comma
       {
               outcode($1, &nullgen, NREG, &nullgen);
       }

cond:
       LCOND
       {
               $$ = nullgen;
               $$.type = D_COND;
               $$.reg = $1;
       }

comma:
|       ',' comma

sysarg:
       con ',' con ',' con ',' con
       {
               $$ = nullgen;
               $$.type = D_CONST;
               $$.offset = SYSARG4($1, $3, $5, $7);
       }
|       imm

rel:
       con '(' LPC ')'
       {
               $$ = nullgen;
               $$.type = D_BRANCH;
               $$.offset = $1 + pc;
       }
|       LNAME offset
       {
               $$ = nullgen;
               if(pass == 2)
                       yyerror("undefined label: %s", $1->name);
               $$.type = D_BRANCH;
               $$.sym = $1;
               $$.offset = $2;
       }
|       LLAB offset
       {
               $$ = nullgen;
               $$.type = D_BRANCH;
               $$.sym = $1;
               $$.offset = $1->value + $2;
       }

ximm:   '$' con
       {
               $$ = nullgen;
               $$.type = D_CONST;
               $$.offset = $2;
       }
|       '$' oreg
       {
               $$ = $2;
               $$.type = D_CONST;
       }
|       '$' '*' '$' oreg
       {
               $$ = $4;
               $$.type = D_OCONST;
       }
|       '$' LSCONST
       {
               $$ = nullgen;
               $$.type = D_SCONST;
               memmove($$.sval, $2, sizeof($$.sval));
       }
|       fcon

fcon:
       '$' LFCONST
       {
               $$ = nullgen;
               $$.type = D_FCONST;
               $$.dval = $2;
       }
|       '$' '-' LFCONST
       {
               $$ = nullgen;
               $$.type = D_FCONST;
               $$.dval = -$3;
       }

gen:
       reg
|       ximm
|       LFCR
       {
               $$ = nullgen;
               $$.type = D_SPR;
               $$.offset = $1;
       }
|       con
       {
               $$ = nullgen;
               $$.type = D_OREG;
               $$.offset = $1;
       }
|       oreg
|       freg
|       vreg
|       spr

nireg:
       '(' sreg ')'
       {
               $$ = nullgen;
               $$.type = D_OREG;
               $$.reg = $2;
               $$.offset = 0;
       }
|       name
       {
               $$ = $1;
               if($1.name != D_EXTERN && $1.name != D_STATIC) {
               }
       }

oreg:
       name
|       name '(' sreg ')'
       {
               $$ = $1;
               $$.type = D_OREG;
               $$.reg = $3;
       }
|       ioreg

ioreg:
       '(' sreg ')'
       {
               $$ = nullgen;
               $$.type = D_OREG;
               $$.reg = $2;
               $$.offset = 0;
       }
|       con '(' sreg ')'
       {
               $$ = nullgen;
               $$.type = D_OREG;
               $$.reg = $3;
               $$.offset = $1;
       }
|       con '(' sreg ')' '!'
       {
               $$ = nullgen;
               $$.type = D_XPRE;
               $$.reg = $3;
               $$.offset = $1;
       }
|       '(' sreg ')' con '!'
       {
               $$ = nullgen;
               $$.type = D_XPOST;
               $$.reg = $2;
               $$.offset = $4;
       }
|       '(' sreg ')' '(' indexreg ')'
       {
               $$ = nullgen;
               $$.type = D_ROFF;
               $$.reg = $2;
               $$.xreg = $5 & 0x1f;
               $$.offset = $5;
       }
|       '(' sreg ')' '[' indexreg ']'
       {
               $$ = nullgen;
               $$.type = D_ROFF;
               $$.reg = $2;
               $$.xreg = $5 & 0x1f;
               $$.offset = $5 | (1<<16);
       }

imsr:
       imm
|       shift
|       extreg

imm:    '$' con
       {
               $$ = nullgen;
               $$.type = D_CONST;
               $$.offset = $2;
       }

reg:
       sreg
       {
               $$ = nullgen;
               $$.type = D_REG;
               $$.reg = $1;
       }
|       LSP
       {
               $$ = nullgen;
               $$.type = D_SP;
               $$.reg = REGSP;
       }

shift:
       sreg '<' '<' scon
       {
               $$ = nullgen;
               $$.type = D_SHIFT;
               $$.offset = ($1 << 16) | ($4 << 10) | (0 << 22);
       }
|       sreg '>' '>' scon
       {
               $$ = nullgen;
               $$.type = D_SHIFT;
               $$.offset = (($1&0x1F) << 16) | ($4 << 10) | (1 << 22);
       }
|       sreg '-' '>' scon
       {
               $$ = nullgen;
               $$.type = D_SHIFT;
               $$.offset = ($1 << 16) | ($4 << 10) | (2 << 22);
       }
|       sreg LAT '>' scon
       {
               $$ = nullgen;
               $$.type = D_SHIFT;
               $$.offset = ($1 << 16) | ($4 << 10) | (3 << 22);
       }

extreg:
       sreg
       {
               $$ = nullgen;
               $$.type = D_REG;
               $$.reg = $1;
       }
|       sreg LEXT
       {
               $$ = nullgen;
               $$.type = D_EXTREG;
               $$.reg = $1;
               $$.offset = ($1 << 16) | ($2 << 13);
       }
|       sreg LEXT '<' '<' con
       {
               if($5 < 0 || $5 > 4)
                       yyerror("shift value out of range");
               $$ = nullgen;
               $$.type = D_EXTREG;
               $$.reg = $1;
               $$.offset = ($1 << 16) | ($2 << 13) | ($5 << 10);
       }

indexreg:
       sreg
       {
               $$ = (3 << 8) | $1;
       }
|       sreg LEXT
       {
               $$ = ($2 << 8) | $1;
       }

scon:
       con
       {
               if($$ < 0 || $$ >= 64)
                       yyerror("shift value out of range");
               $$ = $1&0x3F;
       }

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

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

spr:
       LSPREG
       {
               $$ = nullgen;
               $$.type = D_SPR;
               $$.offset = $1;
       }
|       LSPR '(' con ')'
       {
               $$ = nullgen;
               $$.type = $1;
               $$.offset = $3;
       }

frcon:
       freg
|       fcon

freg:
       LFREG
       {
               $$ = nullgen;
               $$.type = D_FREG;
               $$.reg = $1;
       }
|       LF '(' con ')'
       {
               $$ = nullgen;
               $$.type = D_FREG;
               $$.reg = $3;
       }

vgen:
       oreg
|       vreg
|       vlane
|       vset
       {
               $$ = nullgen;
               $$.type = D_VSET;
               $$.offset = $1;
       }

vlane:
       vreg '[' con ']'
       {
               $$.type = D_VLANE;
               $$.offset = $3;
       }
|       vset '[' con ']'
       {
               $$.type = D_VLANE;
               $$.offset = $3;
       }

vset:
       '{' vreglist '}'
       {
               $$ = $2;
       }

vreglist:
       vreg
       {
               $$ = 1 << $1.reg;
       }
|       vreg '-' vreg
       {
               int i;
               $$=0;
               for(i=$1.reg; i<=$3.reg; i++)
                       $$ |= 1<<i;
               for(i=$3.reg; i<=$1.reg; i++)
                       $$ |= 1<<i;
       }
|       vreg comma vreglist
       {
               $$ = (1<<$1.reg) | $3;
       }

vreg:
       LVREG
       {
               $$ = nullgen;
               $$.type = D_VREG;
               $$.reg = $1;
               /* TO DO: slice */
       }
|       LV '(' con ')'
       {
               $$ = nullgen;
               $$.type = D_VREG;
               $$.reg = $3;
       }

name:
       con '(' pointer ')'
       {
               $$ = nullgen;
               $$.type = D_OREG;
               $$.name = $3;
               $$.sym = S;
               $$.offset = $1;
       }
|       LNAME offset '(' pointer ')'
       {
               $$ = nullgen;
               $$.type = D_OREG;
               $$.name = $4;
               $$.sym = $1;
               $$.offset = $2;
       }
|       LNAME '<' '>' offset '(' LSB ')'
       {
               $$ = nullgen;
               $$.type = D_OREG;
               $$.name = D_STATIC;
               $$.sym = $1;
               $$.offset = $4;
       }

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

pointer:
       LSB
|       LSP
|       LFP

con:
       LCONST
|       LVAR
       {
               $$ = $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;
       }