%{
#include "a.h"
%}
%union
{
       Sym     *sym;
       long    lval;
       double  dval;
       char    sval[8];
       Gen     gen;
}
%left   '|'
%left   '^'
%left   '&'
%left   '<' '>'
%left   '+' '-'
%left   '*' '/' '%'
%token  <lval>  LMOVW LMOVB LABS LLOGW LSHW LADDW LCMP LCROP
%token  <lval>  LBRA LFMOV LFCONV LFCMP LFADD LFMA LTRAP LXORW
%token  <lval>  LNOP LEND LRETT LWORD LTEXT LDATA LRETRN
%token  <lval>  LCONST LSP LSB LFP LPC LCREG LFLUSH
%token  <lval>  LREG LFREG LR LCR LF LFPSCR
%token  <lval>  LLR LCTR LSPR LSPREG LSEG LMSR LDCR
%token  <lval>  LSCHED LXLD LXST LXOP LXMV
%token  <lval>  LRLWM LMOVMW LMOVEM LMOVFL LMTFSB LMA LFMOVX
%token  <dval>  LFCONST
%token  <sval>  LSCONST
%token  <sym>   LNAME LLAB LVAR
%type   <lval>  con expr pointer offset sreg
%type   <gen>   addr rreg regaddr name creg freg xlreg lr ctr
%type   <gen>   imm ximm fimm rel psr lcr cbit fpscr fpscrf seg msr mask
%%
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:
/*
* load ints and bytes
*/
       LMOVW rreg ',' rreg
       {
               outcode($1, &$2, NREG, &$4);
       }
|       LMOVW addr ',' rreg
       {
               outcode($1, &$2, NREG, &$4);
       }
|       LMOVW regaddr ',' rreg
       {
               outcode($1, &$2, NREG, &$4);
       }
|       LMOVB rreg ',' rreg
       {
               outcode($1, &$2, NREG, &$4);
       }
|       LMOVB addr ',' rreg
       {
               outcode($1, &$2, NREG, &$4);
       }
|       LMOVB regaddr ',' rreg
       {
               outcode($1, &$2, NREG, &$4);
       }
/*
* load and store floats
*/
|       LFMOV addr ',' freg
       {
               outcode($1, &$2, NREG, &$4);
       }
|       LFMOV regaddr ',' freg
       {
               outcode($1, &$2, NREG, &$4);
       }
|       LFMOV fimm ',' freg
       {
               outcode($1, &$2, NREG, &$4);
       }
|       LFMOV freg ',' freg
       {
               outcode($1, &$2, NREG, &$4);
       }
|       LFMOV freg ',' addr
       {
               outcode($1, &$2, NREG, &$4);
       }
|       LFMOV freg ',' regaddr
       {
               outcode($1, &$2, NREG, &$4);
       }
/*
* load and store floats, indexed only
*/
|       LFMOVX regaddr ',' freg
       {
               outcode($1, &$2, NREG, &$4);
       }
|       LFMOVX freg ',' regaddr
       {
               outcode($1, &$2, NREG, &$4);
       }
/*
* store ints and bytes
*/
|       LMOVW rreg ',' addr
       {
               outcode($1, &$2, NREG, &$4);
       }
|       LMOVW rreg ',' regaddr
       {
               outcode($1, &$2, NREG, &$4);
       }
|       LMOVB rreg ',' addr
       {
               outcode($1, &$2, NREG, &$4);
       }
|       LMOVB rreg ',' regaddr
       {
               outcode($1, &$2, NREG, &$4);
       }
/*
* store floats
*/
|       LMOVW freg ',' addr
       {
               outcode($1, &$2, NREG, &$4);
       }
|       LMOVW freg ',' regaddr
       {
               outcode($1, &$2, NREG, &$4);
       }
/*
* floating point status
*/
|       LMOVW fpscr ',' freg
       {
               outcode($1, &$2, NREG, &$4);
       }
|       LMOVW freg ','  fpscr
       {
               outcode($1, &$2, NREG, &$4);
       }
|       LMOVW freg ',' imm ',' fpscr
       {
               outgcode($1, &$2, NREG, &$4, &$6);
       }
|       LMOVW fpscr ',' creg
       {
               outcode($1, &$2, NREG, &$4);
       }
|       LMOVW imm ',' fpscrf
       {
               outcode($1, &$2, NREG, &$4);
       }
|       LMTFSB imm ',' con
       {
               outcode($1, &$2, $4, &nullgen);
       }
/*
* field moves (mtcrf)
*/
|       LMOVW rreg ',' imm ',' lcr
       {
               outgcode($1, &$2, NREG, &$4, &$6);
       }
|       LMOVW rreg ',' creg
       {
               outcode($1, &$2, NREG, &$4);
       }
|       LMOVW rreg ',' lcr
       {
               outcode($1, &$2, NREG, &$4);
       }
/*
* integer operations
* logical instructions
* shift instructions
* unary instructions
*/
|       LADDW rreg ',' sreg ',' rreg
       {
               outcode($1, &$2, $4, &$6);
       }
|       LADDW imm ',' sreg ',' rreg
       {
               outcode($1, &$2, $4, &$6);
       }
|       LADDW rreg ',' imm ',' rreg
       {
               outgcode($1, &$2, NREG, &$4, &$6);
       }
|       LADDW rreg ',' rreg
       {
               outcode($1, &$2, NREG, &$4);
       }
|       LADDW imm ',' rreg
       {
               outcode($1, &$2, NREG, &$4);
       }
|       LLOGW rreg ',' sreg ',' rreg
       {
               outcode($1, &$2, $4, &$6);
       }
|       LLOGW rreg ',' rreg
       {
               outcode($1, &$2, NREG, &$4);
       }
|       LSHW rreg ',' sreg ',' rreg
       {
               outcode($1, &$2, $4, &$6);
       }
|       LSHW rreg ',' rreg
       {
               outcode($1, &$2, NREG, &$4);
       }
|       LSHW imm ',' sreg ',' rreg
       {
               outcode($1, &$2, $4, &$6);
       }
|       LSHW imm ',' rreg
       {
               outcode($1, &$2, NREG, &$4);
       }
|       LABS rreg ',' rreg
       {
               outcode($1, &$2, NREG, &$4);
       }
|       LABS rreg
       {
               outcode($1, &$2, NREG, &$2);
       }
/*
* multiply-accumulate
*/
|       LMA rreg ',' sreg ',' rreg
       {
               outcode($1, &$2, $4, &$6);
       }
/*
* move immediate: macro for cau+or, addi, addis, and other combinations
*/
|       LMOVW imm ',' rreg
       {
               outcode($1, &$2, NREG, &$4);
       }
|       LMOVW ximm ',' rreg
       {
               outcode($1, &$2, NREG, &$4);
       }
/*
* condition register operations
*/
|       LCROP cbit ',' cbit
       {
               outcode($1, &$2, $4.reg, &$4);
       }
|       LCROP cbit ',' con ',' cbit
       {
               outcode($1, &$2, $4, &$6);
       }
/*
* condition register moves
* move from machine state register
*/
|       LMOVW creg ',' creg
       {
               outcode($1, &$2, NREG, &$4);
       }
|       LMOVW psr ',' creg
       {
               outcode($1, &$2, NREG, &$4);
       }
|       LMOVW lcr ',' rreg
       {
               outcode($1, &$2, NREG, &$4);
       }
|       LMOVW psr ',' rreg
       {
               outcode($1, &$2, NREG, &$4);
       }
|       LMOVW seg ',' rreg
       {
               int r;
               r = $2.offset;
               $2.offset = 0;
               outcode($1, &$2, r, &$4);
       }
|       LMOVW rreg ',' seg
       {
               int r;
               r = $4.offset;
               $4.offset = 0;
               outcode($1, &$2, r, &$4);
       }
|       LMOVW xlreg ',' rreg
       {
               outcode($1, &$2, NREG, &$4);
       }
|       LMOVW rreg ',' xlreg
       {
               outcode($1, &$2, NREG, &$4);
       }
|       LMOVW creg ',' psr
       {
               outcode($1, &$2, NREG, &$4);
       }
|       LMOVW rreg ',' psr
       {
               outcode($1, &$2, NREG, &$4);
       }
/*
* branch, branch conditional
* branch conditional register
* branch conditional to count register
*/
|       LBRA rel
       {
               outcode($1, &nullgen, NREG, &$2);
       }
|       LBRA addr
       {
               outcode($1, &nullgen, NREG, &$2);
       }
|       LBRA '(' xlreg ')'
       {
               outcode($1, &nullgen, NREG, &$3);
       }
|       LBRA ',' rel
       {
               outcode($1, &nullgen, NREG, &$3);
       }
|       LBRA ',' addr
       {
               outcode($1, &nullgen, NREG, &$3);
       }
|       LBRA ',' '(' xlreg ')'
       {
               outcode($1, &nullgen, NREG, &$4);
       }
|       LBRA creg ',' rel
       {
               outcode($1, &$2, NREG, &$4);
       }
|       LBRA creg ',' addr
       {
               outcode($1, &$2, NREG, &$4);
       }
|       LBRA creg ',' '(' xlreg ')'
       {
               outcode($1, &$2, NREG, &$5);
       }
|       LBRA con ',' rel
       {
               outcode($1, &nullgen, $2, &$4);
       }
|       LBRA con ',' addr
       {
               outcode($1, &nullgen, $2, &$4);
       }
|       LBRA con ',' '(' xlreg ')'
       {
               outcode($1, &nullgen, $2, &$5);
       }
|       LBRA con ',' con ',' rel
       {
               Gen g;
               g = nullgen;
               g.type = D_CONST;
               g.offset = $2;
               outcode($1, &g, $4, &$6);
       }
|       LBRA con ',' con ',' addr
       {
               Gen g;
               g = nullgen;
               g.type = D_CONST;
               g.offset = $2;
               outcode($1, &g, $4, &$6);
       }
|       LBRA con ',' con ',' '(' xlreg ')'
       {
               Gen g;
               g = nullgen;
               g.type = D_CONST;
               g.offset = $2;
               outcode($1, &g, $4, &$7);
       }
/*
* conditional trap
*/
|       LTRAP rreg ',' sreg
       {
               outcode($1, &$2, $4, &nullgen);
       }
|       LTRAP imm ',' sreg
       {
               outcode($1, &$2, $4, &nullgen);
       }
|       LTRAP rreg comma
       {
               outcode($1, &$2, NREG, &nullgen);
       }
|       LTRAP comma
       {
               outcode($1, &nullgen, NREG, &nullgen);
       }
/*
* floating point operate
*/
|       LFCONV freg ',' freg
       {
               outcode($1, &$2, NREG, &$4);
       }
|       LFADD freg ',' freg
       {
               outcode($1, &$2, NREG, &$4);
       }
|       LFADD freg ',' freg ',' freg
       {
               outcode($1, &$2, $4.reg, &$6);
       }
|       LFMA freg ',' freg ',' freg ',' freg
       {
               outgcode($1, &$2, $4.reg, &$6, &$8);
       }
|       LFCMP freg ',' freg
       {
               outcode($1, &$2, NREG, &$4);
       }
|       LFCMP freg ',' freg ',' creg
       {
               outcode($1, &$2, $6.reg, &$4);
       }
/*
* CMP
*/
|       LCMP rreg ',' rreg
       {
               outcode($1, &$2, NREG, &$4);
       }
|       LCMP rreg ',' imm
       {
               outcode($1, &$2, NREG, &$4);
       }
|       LCMP rreg ',' rreg ',' creg
       {
               outcode($1, &$2, $6.reg, &$4);
       }
|       LCMP rreg ',' imm ',' creg
       {
               outcode($1, &$2, $6.reg, &$4);
       }
/*
* rotate and mask
*/
|       LRLWM  imm ',' rreg ',' imm ',' rreg
       {
               outgcode($1, &$2, $4.reg, &$6, &$8);
       }
|       LRLWM  imm ',' rreg ',' mask ',' rreg
       {
               outgcode($1, &$2, $4.reg, &$6, &$8);
       }
|       LRLWM  rreg ',' rreg ',' imm ',' rreg
       {
               outgcode($1, &$2, $4.reg, &$6, &$8);
       }
|       LRLWM  rreg ',' rreg ',' mask ',' rreg
       {
               outgcode($1, &$2, $4.reg, &$6, &$8);
       }
/*
* load/store multiple
*/
|       LMOVMW addr ',' rreg
       {
               outcode($1, &$2, NREG, &$4);
       }
|       LMOVMW rreg ',' addr
       {
               outcode($1, &$2, NREG, &$4);
       }
/*
* various indexed load/store
* indexed unary (eg, cache clear)
*/
|       LXLD regaddr ',' rreg
       {
               outcode($1, &$2, NREG, &$4);
       }
|       LXLD regaddr ',' imm ',' rreg
       {
               outgcode($1, &$2, NREG, &$4, &$6);
       }
|       LXST rreg ',' regaddr
       {
               outcode($1, &$2, NREG, &$4);
       }
|       LXST rreg ',' imm ',' regaddr
       {
               outgcode($1, &$2, NREG, &$4, &$6);
       }
|       LXMV regaddr ',' rreg
       {
               outcode($1, &$2, NREG, &$4);
       }
|       LXMV rreg ',' regaddr
       {
               outcode($1, &$2, NREG, &$4);
       }
|       LXOP regaddr
       {
               outcode($1, &$2, NREG, &nullgen);
       }
/*
* NOP
*/
|       LNOP comma
       {
               outcode($1, &nullgen, NREG, &nullgen);
       }
|       LNOP rreg comma
       {
               outcode($1, &$2, NREG, &nullgen);
       }
|       LNOP freg comma
       {
               outcode($1, &$2, NREG, &nullgen);
       }
|       LNOP ',' rreg
       {
               outcode($1, &nullgen, NREG, &$3);
       }
|       LNOP ',' freg
       {
               outcode($1, &nullgen, NREG, &$3);
       }
/*
* word
*/
|       LWORD imm comma
       {
               outcode($1, &$2, NREG, &nullgen);
       }
|       LWORD ximm comma
       {
               outcode($1, &$2, NREG, &nullgen);
       }
/*
* END
*/
|       LEND comma
       {
               outcode($1, &nullgen, NREG, &nullgen);
       }
/*
* TEXT/GLOBL
*/
|       LTEXT name ',' imm
       {
               outcode($1, &$2, NREG, &$4);
       }
|       LTEXT name ',' con ',' imm
       {
               outcode($1, &$2, $4, &$6);
       }
|       LTEXT name ',' imm ':' imm
       {
               outgcode($1, &$2, NREG, &$6, &$4);
       }
|       LTEXT name ',' con ',' imm ':' imm
       {
               outgcode($1, &$2, $4, &$8, &$6);
       }
/*
* DATA
*/
|       LDATA name '/' con ',' imm
       {
               outcode($1, &$2, $4, &$6);
       }
|       LDATA name '/' con ',' ximm
       {
               outcode($1, &$2, $4, &$6);
       }
|       LDATA name '/' con ',' fimm
       {
               outcode($1, &$2, $4, &$6);
       }
/*
* RETURN
*/
|       LRETRN  comma
       {
               outcode($1, &nullgen, NREG, &nullgen);
       }

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

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

xlreg:
       lr
|       ctr

lr:
       LLR
       {
               $$ = nullgen;
               $$.type = D_SPR;
               $$.offset = $1;
       }

lcr:
       LCR
       {
               $$ = nullgen;
               $$.type = D_CREG;
               $$.reg = NREG;  /* whole register */
       }

ctr:
       LCTR
       {
               $$ = nullgen;
               $$.type = D_SPR;
               $$.offset = $1;
       }

msr:
       LMSR
       {
               $$ = nullgen;
               $$.type = D_MSR;
       }

psr:
       LSPREG
       {
               $$ = nullgen;
               $$.type = D_SPR;
               $$.offset = $1;
       }
|       LSPR '(' con ')'
       {
               $$ = nullgen;
               $$.type = $1;
               $$.offset = $3;
       }
|       LDCR '(' con ')'
       {
               $$ = nullgen;
               $$.type = $1;
               $$.offset = $3;
       }
|       LDCR '(' sreg ')'
       {
               $$ = nullgen;
               $$.type = $1;
               $$.reg = $3;
               $$.offset = 0;
       }
|       msr

seg:
       LSEG '(' con ')'
       {
               if($3 < 0 || $3 > 15)
                       yyerror("segment register number out of range");
               $$ = nullgen;
               $$.type = D_SREG;
               $$.reg = $3;
               $$.offset = NREG;
       }
|       LSEG '(' sreg ')'
       {
               $$ = nullgen;
               $$.type = D_SREG;
               $$.reg = NREG;
               $$.offset = $3;
       }

fpscr:
       LFPSCR
       {
               $$ = nullgen;
               $$.type = D_FPSCR;
               $$.reg = NREG;
       }

fpscrf:
       LFPSCR '(' con ')'
       {
               $$ = nullgen;
               $$.type = D_FPSCR;
               $$.reg = $3;
       }

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

creg:
       LCREG
       {
               $$ = nullgen;
               $$.type = D_CREG;
               $$.reg = $1;
       }
|       LCR '(' con ')'
       {
               $$ = nullgen;
               $$.type = D_CREG;
               $$.reg = $3;
       }


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

mask:
       con ',' con
       {
               int mb, me;
               ulong v;

               $$ = nullgen;
               $$.type = D_CONST;
               mb = $1;
               me = $3;
               if(mb < 0 || mb > 31 || me < 0 || me > 31){
                       yyerror("illegal mask start/end value(s)");
                       mb = me = 0;
               }
               if(mb <= me)
                       v = ((ulong)~0L>>mb) & (~0L<<(31-me));
               else
                       v = ~(((ulong)~0L>>(me+1)) & (~0L<<(31-(mb-1))));
               $$.offset = v;
       }

ximm:
       '$' addr
       {
               $$ = $2;
               $$.type = D_CONST;
       }
|       '$' LSCONST
       {
               $$ = nullgen;
               $$.type = D_SCONST;
               memcpy($$.sval, $2, sizeof($$.sval));
       }

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

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

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

regaddr:
       '(' sreg ')'
       {
               $$ = nullgen;
               $$.type = D_OREG;
               $$.reg = $2;
               $$.offset = 0;
       }
|       '(' sreg '+' sreg ')'
       {
               $$ = nullgen;
               $$.type = D_OREG;
               $$.reg = $2;
               $$.xreg = $4;
               $$.offset = 0;
       }

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

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

comma:
|       ','

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