%{
#include "a.h"
%}
%union
{
       Sym     *sym;
       long    lval;
       double  dval;
       char    sval[8];
       Gen     gen;
}
%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 LTYPEF
%token  <lval>  LTYPEG LTYPEH LTYPEI LTYPEJ LTYPEK
%token  <lval>  LCONST LSP LSB LFP LPC LHI LLO LMREG
%token  <lval>  LTYPEX LREG LFREG LFCREG LR LM LF
%token  <lval>  LFCR LSCHED
%token  <dval>  LFCONST
%token  <sval>  LSCONST
%token  <sym>   LNAME LLAB LVAR
%type   <lval>  con expr pointer offset sreg
%type   <gen>   gen vgen lgen vlgen rel reg freg mreg fcreg
%type   <gen>   imm ximm ireg name oreg imr nireg fgen
%%
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;
       }
|       LSCHED ';'
       {
               nosched = $1;
       }
|       ';'
|       inst ';'
|       error ';'

inst:
/*
* Immed-type
*/
       LTYPE1 imr ',' sreg ',' reg
       {
               outcode($1, &$2, $4, &$6);
       }
|       LTYPE1 imr ',' reg
       {
               outcode($1, &$2, NREG, &$4);
       }
/*
* NOR
*/
|       LTYPE2 imr ',' sreg ',' imr
       {
               outcode($1, &$2, $4, &$6);
       }
|       LTYPE2 imr ',' imr
       {
               outcode($1, &$2, NREG, &$4);
       }
/*
* LOAD/STORE, but not MOVW
*/
|       LTYPE3 lgen ',' gen
       {
               if(!isreg(&$2) && !isreg(&$4))
                       print("one side must be register\n");
               outcode($1, &$2, NREG, &$4);
       }
/*
* SPECIAL
*/
|       LTYPE4 comma
       {
               outcode($1, &nullgen, NREG, &nullgen);
       }
/*
* MOVW
*/
|       LTYPE5 vlgen ',' vgen
       {
               if(!isreg(&$2) && !isreg(&$4))
                       print("one side must be register\n");
               outcode($1, &$2, NREG, &$4);
       }
/*
* MUL/DIV
*/
|       LTYPE6 reg ',' sreg comma
       {
               outcode($1, &$2, $4, &nullgen);
       }
|       LTYPE6 reg ',' sreg ',' reg
       {
               outcode($1, &$2, $4, &$6);
       }
/*
* JMP/JAL
*/
|       LTYPE7 comma rel
       {
               outcode($1, &nullgen, NREG, &$3);
       }
|       LTYPE7 comma nireg
       {
               outcode($1, &nullgen, NREG, &$3);
       }
|       LTYPE8 comma rel
       {
               outcode($1, &nullgen, NREG, &$3);
       }
|       LTYPE8 comma nireg
       {
               outcode($1, &nullgen, NREG, &$3);
       }
|       LTYPE8 sreg ',' nireg
       {
               outcode($1, &nullgen, $2, &$4);
       }
/*
* BEQ/BNE
*/
|       LTYPE9 gen ',' rel
       {
               if(!isreg(&$2))
                       print("left side must be register\n");
               outcode($1, &$2, NREG, &$4);
       }
|       LTYPE9 gen ',' sreg ',' rel
       {
               if(!isreg(&$2))
                       print("left side must be register\n");
               outcode($1, &$2, $4, &$6);
       }
/*
* B-other
*/
|       LTYPEA gen ',' rel
       {
               if(!isreg(&$2))
                       print("left side must be register\n");
               outcode($1, &$2, NREG, &$4);
       }
/*
* 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);
       }
/*
* floating-type
*/
|       LTYPED freg ',' freg
       {
               outcode($1, &$2, NREG, &$4);
       }
|       LTYPEE freg ',' freg
       {
               outcode($1, &$2, NREG, &$4);
       }
|       LTYPEE freg ',' LFREG ',' freg
       {
               outcode($1, &$2, $4, &$6);
       }
|       LTYPEF freg ',' LFREG comma
       {
               outcode($1, &$2, $4, &nullgen);
       }
/*
* coprocessor branch
*/
|       LTYPEG comma rel
       {
               outcode($1, &nullgen, NREG, &$3);
       }
/*
* word
*/
|       LTYPEH comma ximm
       {
               outcode($1, &nullgen, NREG, &$3);
       }
/*
* NOP
*/
|       LTYPEI comma
       {
               outcode($1, &nullgen, NREG, &nullgen);
       }
|       LTYPEI ',' vgen
       {
               outcode($1, &nullgen, NREG, &$3);
       }
|       LTYPEI vgen comma
       {
               outcode($1, &$2, NREG, &nullgen);
       }
/*
* BREAK -- overloaded with CACHE opcode
*/
|       LTYPEJ comma
       {
               outcode($1, &nullgen, NREG, &nullgen);
       }
|       LTYPEJ vgen ',' vgen
       {
               outcode($1, &$2, NREG, &$4);
       }

comma:
|       ','

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;
       }

vlgen:
       lgen
|       fgen
|       mreg
|       fcreg
|       LHI
       {
               $$ = nullgen;
               $$.type = D_HI;
       }
|       LLO
       {
               $$ = nullgen;
               $$.type = D_LO;
       }

vgen:
       gen
|       fgen
|       mreg
|       fcreg
|       LHI
       {
               $$ = nullgen;
               $$.type = D_HI;
       }
|       LLO
       {
               $$ = nullgen;
               $$.type = D_LO;
       }

lgen:
       gen
|       ximm

fgen:
       freg

mreg:
       LMREG
       {
               $$ = nullgen;
               $$.type = D_MREG;
               $$.reg = $1;
       }
|       LM '(' con ')'
       {
               $$ = nullgen;
               $$.type = D_MREG;
               $$.reg = $3;
       }

fcreg:
       LFCREG
       {
               $$ = nullgen;
               $$.type = D_FCREG;
               $$.reg = $1;
       }
|       LFCR '(' con ')'
       {
               $$ = nullgen;
               $$.type = D_FCREG;
               $$.reg = $3;
       }

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

ximm:   '$' con
       {
               $$ = nullgen;
               $$.type = D_CONST;
               $$.offset = $2;
       }
|       '$' oreg
       {
               $$ = $2;
               $$.type = D_CONST;
       }
|       '$' '*' '$' oreg
       {
               $$ = $4;
               $$.type = D_OCONST;
       }
|       '$' LSCONST
       {
               $$ = nullgen;
               $$.type = D_SCONST;
               memcpy($$.sval, $2, sizeof($$.sval));
       }
|       '$' LFCONST
       {
               $$ = nullgen;
               $$.type = D_FCONST;
               $$.dval = $2;
       }
|       '$' '-' LFCONST
       {
               $$ = nullgen;
               $$.type = D_FCONST;
               $$.dval = -$3;
       }

nireg:
       ireg
|       con ireg
       {
               if($1 != 0)
                       yyerror("offset must be zero");
               $$ = $2;
       }
|       name
       {
               $$ = $1;
               if($1.name != D_EXTERN && $1.name != D_STATIC) {
               }
       }

ireg:
       '(' sreg ')'
       {
               $$ = nullgen;
               $$.type = D_OREG;
               $$.reg = $2;
               $$.offset = 0;
       }

gen:
       reg
|       con
       {
               $$ = nullgen;
               $$.type = D_OREG;
               $$.offset = $1;
       }
|       oreg

oreg:
       name
|       name '(' sreg ')'
       {
               $$ = $1;
               $$.type = D_OREG;
               $$.reg = $3;
       }
|       '(' sreg ')'
       {
               $$ = nullgen;
               $$.type = D_OREG;
               $$.reg = $2;
               $$.offset = 0;
       }
|       con '(' sreg ')'
       {
               $$ = nullgen;
               $$.type = D_OREG;
               $$.reg = $3;
               $$.offset = $1;
       }

imr:
       reg
|       imm

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

reg:
       sreg
       {
               $$ = nullgen;
               $$.type = D_REG;
               $$.reg = $1;
       }

sreg:
       LREG
|       LR '(' con ')'
       {
               if($$ < 0 || $$ >= NREG)
                       print("register value out of range\n");
               $$ = $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;
       }