/*
 *  smoke16/src/smoke16as.tab.y - A Bison parser for the SMOKE-16 assembler.
 *  $Revision: 1.15 $
 *  Copyright 1997-2000, Benjamin Sittler
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 */

%{
#define YACC_VERSION \
   "parser $Revision: 1.15 $\n"
%}

%union {
  unsigned long ul;
  char *string;
}

%type <string> string symbolic symbol

%type <ul> insn immed4 immed8 offset4 vreg reg regs expr numexpr numterm numfact multichar

%type <string> SYMBOL

%type <ul> SECT_ID REG SREG NUMBER CHAR

%type <ul> OP_3REG JAL J CALL RET SHIFT NOT CMP B INT OP16

%token SYMBOL REG SREG NUMBER CHAR

%token LO HI LO16 HI16 ASSIGN

%token MOV LD ST LDS STS LDI
%token MOVB LDB STB LDBI
%token ADD ADDI SUB SUBI
%token CMP NOT SHIFT OR
%token OP_3REG
%token SETHI OP16 INT
%token JAL B J CALL RET
%token PUSH POP

%token _ALIGN _BYTE _WORD _LONG _ASCIIZ _ASCII
%token _STRING _ZSTRING SECT_ID _SPACE _GLOBAL _SET _FILE _LINE
%token _COMM _LCOMM
%%

lines: line lines
       | /* empty */
       ;

line:   maybelabel maybecmd eol
      | error eol
      ;

eol: '\n' | '#' pound '\n' ;

maybelabel:   /* empty */
              | symbol ':' maybelabel
                   { define($1, info[sect].offset); }
              | symbol '=' numexpr
                   { mkconst($1, $3); }
              ;

maybecmd: /* empty */ | cmd ;

cmd:     insn
            {
	      mkword($1);
              info[sect].offset += 2;
            }
       | pseudo
       | direc
       ;

symbolic:   symbol { $$ = $1; }
          | SECT_ID { if (! ($$ = strdup(info[$1].name))) perror("strdup"), cleanup(), exit(1); }
          ;

ld: LD | MOV;

st: ST | MOV;

ldb: LDB | MOVB;

stb: STB | MOVB;

ldbi: LDBI | MOVB;

addi: ADD | ADDI;

subi: SUB | SUBI;

insn:    OP_3REG REG ',' REG ',' REG
             { $$ = $1 | (0xf00UL & ($6<<8)) | (0xf0UL & ($2<<4)) | (0xfUL & $4); }
       | OP_3REG REG ',' REG
             { $$ = $1 | (0xf00UL & ($4<<8)) | (0xf0UL & ($4<<4)) | (0xfUL & $2); } 
       | ADD REG ',' REG ',' REG
             { $$ = 0x0000UL | (0xf00UL & ($6<<8)) | (0xf0UL & ($2<<4)) | (0xfUL & $4); }
       | ADD REG ',' REG
             { $$ = 0x0000UL | (0xf00UL & ($4<<8)) | (0xf0UL & ($4<<4)) | (0xfUL & $2); } 
       | addi immed4 ',' REG
             { $$ = 0xfe00UL | (0xf0UL & ($4<<4)) | (0xfUL & $2); }
       | SUB REG ',' REG ',' REG
             { $$ = 0x1000UL | (0xf00UL & ($6<<8)) | (0xf0UL & ($2<<4)) | (0xfUL & $4); }
       | SUB REG ',' REG
             { $$ = 0x1000UL | (0xf00UL & ($4<<8)) | (0xf0UL & ($4<<4)) | (0xfUL & $2); } 
       | subi immed4 ',' REG
             { $$ = 0xfd00UL | (0xf0UL & ($4<<4)) | (0xfUL & $2); }
       | OR REG ',' REG ',' REG
             { $$ = 0x5000UL | (0xf00UL & ($6<<8)) | (0xf0UL & ($2<<4)) | (0xfUL & $4); }
       | OR REG ',' REG
             { $$ = 0x5000UL | (0xf00UL & ($4<<8)) | (0xf0UL & ($4<<4)) | (0xfUL & $2); } 
       | SETHI immed8 ',' REG
             { $$ = 0x7000UL | (0xf00UL & ($4<<8)) | (0xffUL & $2); }
       | st REG ',' REG
             { $$ = 0x5000UL | (0xf00UL & ($4<<8)) | (0xfUL & $2); } 
       | JAL regs ',' REG
             { $$ = $1 | (0xf00UL & ($4<<8)) | (0xffUL & $2); }
       | J regs
             { $$ = $1 | (0xffUL & $2); }
       | CALL regs
             { $$ = $1 | 0xe00UL | (0xffUL & $2); }
       | RET
             { $$ = $1 | 0xeUL; }
       | SHIFT immed4 ',' REG
             { $$ = $1 | (0xf00UL & ($4<<8)) | (0xf0UL & ($4<<4)) | (0xfUL & $2); }
       | SHIFT REG ',' immed4 ',' REG
             { $$ = $1 | (0xf00UL & ($6<<8)) | (0xf0UL & ($2<<4)) | (0xfUL & $4); }
       | ld '[' reg ']' ',' REG
             { $$ = 0x6000UL | (0xf00UL & ($6<<8)) | (0xfUL & $3); }
       | ld '[' REG '+' offset4 ']' ',' REG
             { $$ = 0x6000UL | (0xf00UL & ($8<<8)) | (0xf0UL & ($5<<4)) | (0xfUL & $3); }
       | st REG ',' '[' reg ']'
             { $$ = 0xd000UL | (0xf00UL & ($2<<8)) | (0xfUL & $5); }
       | st REG ',' '[' REG '+' offset4 ']'
             { $$ = 0xd000UL | (0xf00UL & ($2<<8)) | (0xf0UL & ($7<<4)) | (0xfUL & $5); }
       | NOT REG ',' REG
             { $$ = $1 | (0xf0UL & ($4<<4)) | (0xfUL & $2); }
       | NOT REG
             { $$ = $1 | (0xf0UL & ($2<<4)) | (0xfUL & $2); }
       | CMP REG ',' REG
             { $$ = $1 | (0xf0UL & ($2<<4)) | (0xfUL & $4); }
       | ldb '[' reg ']' ',' REG
             { $$ = 0xf200UL | (0xf0UL & ($6<<4)) | (0xfUL & $3); }
       | stb REG ',' '[' reg ']'
             { $$ = 0xf300UL | (0xf0UL & ($2<<4)) | (0xfUL & $5); }
       | ldbi immed8 ',' REG
             { $$ = 0xe000UL | (0xf00UL & ($4<<8)) | (0xffUL & $2); }
       | ld SREG ',' REG
             { $$ = 0xf400UL | (0xf0UL & ($4<<4)) | (0xfUL & $2); }
       | LDS SREG ',' REG
             { $$ = 0xf400UL | (0xf0UL & ($4<<4)) | (0xfUL & $2); }
       | st REG ',' SREG
             { $$ = 0xf500UL | (0xf0UL & ($2<<4)) | (0xfUL & $4); }
       | STS REG ',' SREG
             { $$ = 0xf500UL | (0xf0UL & ($2<<4)) | (0xfUL & $4); }
       | B symbolic
             { $$ = $1; mkdisp($2, info[sect].offset+1, 0); }
       | B symbolic '+' numexpr
             { $$ = $1; mkdisp($2, info[sect].offset+1, $4); }
       | B symbolic '-' numexpr
             { $$ = $1; mkdisp($2, info[sect].offset+1, -$4); }
       | B numexpr
             {
	       char *s;

	       if (! (s = strdup(info[SECT_ABS].name))) perror("strdup"), cleanup(), exit(1);
	       $$ = $1;
	       mkdisp(s, info[sect].offset+1, $2);
	     }
       | INT numexpr
             { $$ = $1 | $2;
	       if ($2 != ((0x7fUL) & $2))
		 yyerror("must be a number from 0 to 127");
	     }
       | OP16
             { $$ = $1; }
       ;

immed4:   numexpr
             {
	       $$ = 0xfUL & $1;
	       if($$ != $1)
		 yyerror("must be a number from 0 to 15");
	     }
       ;

offset4:   numexpr
             {
	       $$ = 0xfUL & ($1 >> 1);
	       if(($$ << 1) != $1)
		 yyerror("must be an even number from 0 to 30");
	     }
       ;

immed8:  LO '(' symbolic ')'
             { $$ = 0; mklo($3, info[sect].offset+1, 0); }
       | LO '(' symbolic '+' numexpr ')'
             { $$ = 0; mklo($3, info[sect].offset+1, $5); }
       | LO '(' symbolic '-' numexpr ')'
             { $$ = 0; mklo($3, info[sect].offset+1, -$5); }
       | HI '(' symbolic ')'
             { $$ = 0; mkhi($3, info[sect].offset+1, 0); }
       | HI '(' symbolic '+' numexpr ')'
             { $$ = 0; mkhi($3, info[sect].offset+1, $5); }
       | HI '(' symbolic '-' numexpr ')'
             { $$ = 0; mkhi($3, info[sect].offset+1, -$5); }
       | numexpr
             {
	       $$ = to_u8($1);
	     }
       ;

string:   CHAR
            {
	      char buf[2] = { '\0', '\0' };

	      *buf = $1;
	      if (! ($$ = strdup(buf))) perror("strdup"), cleanup(), exit(1);
	    }
        | string CHAR
            {
	      int len;

	      len = strlen($1);
	      $1[len++] = $2;
	      if (! ($$ = (char *) realloc($1, len + 1))) perror("realloc"), cleanup(), exit(1);
	      $$[len] = '\0';
	    }
        ;

symbol: SYMBOL { $$ = $1; }
      | '"' string '"' { $$ = $2; }
      ;

pound: NUMBER '"' string '"' pound_flags
           {
#ifdef DEBUG_POUND
	     fprintf(stderr, "well-formed pound\n");
#endif
	     lineno = $1;
             if(infile)
	       free(infile);
	     infile = $3;
	   }
       | NUMBER '"' string '"' pound_flags '#' pound_junk
           {
#ifdef DEBUG_POUND
	     fprintf(stderr, "well-formed pound pound\n");
#endif
	     lineno = $1;
             if(infile)
	       free(infile);
	     infile = $3;
	   }
       | /* empty */
            {
#ifdef DEBUG_POUND
	      fprintf(stderr, "pound\n");
#endif
	    }
       | '#' pound_junk
            {
#ifdef DEBUG_POUND
	      fprintf(stderr, "double pound\n");
#endif
	    }
       | '"' string '"' pound_junk
            {
#ifdef DEBUG_POUND
	      fprintf(stderr, "pound string\n");
#endif
	      free($2);
	    }
       | NUMBER
            {
#ifdef DEBUG_POUND
	      fprintf(stderr, "pound number\n");
#endif
	    }
       | NUMBER '#' pound_junk
            {
#ifdef DEBUG_POUND
	      fprintf(stderr, "pound number pound\n");
#endif
	    }
       | NUMBER NUMBER pound_junk
            {
#ifdef DEBUG_POUND
	      fprintf(stderr, "pound number number\n");
#endif
	    }
       | NUMBER '"' string '"' pound_flags '"' string '"' pound_junk
            {
#ifdef DEBUG_POUND
	      fprintf(stderr, "pound number string string\n");
#endif
	      free($3);
	      free($7);
	    }
       ;

pound_flags: NUMBER pound_flags { } | /* empty */ ;

pound_junk_item:   '#'
                 | '"' string '"' { free($2); }
                 | NUMBER { }
                 ;

pound_junk: pound_junk_item pound_junk | /* empty */ ;

ldi: LDI | MOV;

regs:   reg
         {
	   $$ = $1;
	 }
      | REG '+' REG
         {
	   $$ = (0x0fUL & $1) | (0xf0UL & ($3<<4));
	 }
      | symbolic '+' REG
         {
	   char *s;

	   if (! (s=strdup($1))) perror("strdup"), cleanup(), exit(1);
	   mkhi(s, info[sect].offset+1, 0);
	   mkword(0x7700UL);
	   info[sect].offset += 2;
	   mklo($1, info[sect].offset+1, 0);
	   mkword(0xe700UL);
	   info[sect].offset += 2;
	   $$ = 7 | (0xf0UL & ($3<<4));
	 }
      | symbolic '+' numexpr '+' REG
         {
	   char *s;

	   if (! (s=strdup($1))) perror("strdup"), cleanup(), exit(1);
	   mkhi(s, info[sect].offset+1, $3);
	   mkword(0x7700UL);
	   info[sect].offset += 2;
	   mklo($1, info[sect].offset+1, $3);
	   mkword(0xe700UL);
	   info[sect].offset += 2;
	   $$ = 7 | (0xf0UL & ($5<<4));
	 }
      | symbolic '-' numexpr '+' REG
         {
	   char *s;

	   if (! (s=strdup($1))) perror("strdup"), cleanup(), exit(1);
	   mkhi(s, info[sect].offset+1, -$3);
	   mkword(0x7700UL);
	   info[sect].offset += 2;
	   mklo($1, info[sect].offset+1, -$3);
	   mkword(0xe700UL);
	   info[sect].offset += 2;
	   $$ = 7 | (0xf0UL & ($5<<4));
	 }
     | numexpr '+' REG
         {
	   $1 = to_u16($1);
	   mkword(0x7700UL | (($1 & 0xff00UL) >> 8));
	   info[sect].offset += 2;
	   mkword(0xe700UL | ($1 & 0xffUL));
	   info[sect].offset += 2;
	   $$ = 7 | (0xf0UL & ($3<<4));
	 }
      ;

reg:   REG
         {
	   $$ = $1;
	 }
     | vreg
         {
	   $$ = $1;
	 }
     ;

vreg:  symbolic
         {
	   char *s;

	   if (! (s=strdup($1))) perror("strdup"), cleanup(), exit(1);
	   mkhi(s, info[sect].offset+1, 0);
	   mkword(0x7700UL);
	   info[sect].offset += 2;
	   mklo($1, info[sect].offset+1, 0);
	   mkword(0xe700UL);
	   info[sect].offset += 2;
	   $$ = 7;
	 }
     | symbolic '+' numexpr
         {
	   char *s;

	   if (! (s=strdup($1))) perror("strdup"), cleanup(), exit(1);
	   mkhi(s, info[sect].offset+1, $3);
	   mkword(0x7700UL);
	   info[sect].offset += 2;
	   mklo($1, info[sect].offset+1, $3);
	   mkword(0xe700UL);
	   info[sect].offset += 2;
	   $$ = 7;
	 }
     | symbolic '-' numexpr
         {
	   char *s;

	   if (! (s=strdup($1))) perror("strdup"), cleanup(), exit(1);
	   mkhi(s, info[sect].offset+1, -$3);
	   mkword(0x7700UL);
	   info[sect].offset += 2;
	   mklo($1, info[sect].offset+1, -$3);
	   mkword(0xe700UL);
	   info[sect].offset += 2;
	   $$ = 7;
	 }
     | numexpr
         {
	   $1 = to_u16($1);
	   mkword(0x7700UL | (($1 & 0xff00UL) >> 8));
	   info[sect].offset += 2;
	   mkword(0xe700UL | ($1 & 0xffUL));
	   info[sect].offset += 2;
	   $$ = 7;
	 }
     ;

pseudo:   ldi symbolic ',' REG
            {
	      char *s;
	      
	      if (! (s=strdup($2))) perror("strdup"), cleanup(), exit(1);
	      mkhi(s, info[sect].offset+1, 0);
	      mkword(0x7000UL | (0xf00UL & ($4<<8)));
	      info[sect].offset += 2;
	      mklo($2, info[sect].offset+1, 0);
	      mkword(0xe000UL | (0xf00UL & ($4<<8)));
	      info[sect].offset += 2;
	    }
        | ldi symbolic '+' numexpr ',' REG
            {
	      char *s;
	      
	      if (! (s=strdup($2))) perror("strdup"), cleanup(), exit(1);
	      mkhi(s, info[sect].offset+1, $4);
	      mkword(0x7000UL | (0xf00UL & ($6<<8)));
	      info[sect].offset += 2;
	      mklo($2, info[sect].offset+1, $4);
	      mkword(0xe000UL | (0xf00UL & ($6<<8)));
	      info[sect].offset += 2;
	    }
        | ldi symbolic '-' numexpr ',' REG
            {
	      char *s;
	      
	      if (! (s=strdup($2))) perror("strdup"), cleanup(), exit(1);
	      mkhi(s, info[sect].offset+1, -$4);
	      mkword(0x7000UL | (0xf00UL & ($6<<8)));
	      info[sect].offset += 2;
	      mklo($2, info[sect].offset+1, -$4);
	      mkword(0xe000UL | (0xf00UL & ($6<<8)));
	      info[sect].offset += 2;
	    }
        | ldi numexpr ',' REG
            {
	      $2 = to_u16($2);
	      mkword(0x7000UL | (0xf00UL & ($4<<8)) | ($2 >> 8));
	      info[sect].offset += 2;
	      mkword(0xe000UL | (0xf00UL & ($4<<8)) | ($2 & 0xffUL));
	      info[sect].offset += 2;
	    }
        | PUSH reg
            {
              mkword(0xfdd2UL);
              info[sect].offset += 2;
              mkword(0xd01dUL | ($2 << 8)); 
              info[sect].offset += 2;
	    }
        | PUSH
            {
              mkword(0xfdd2UL);
              info[sect].offset += 2;
	    }
        | POP REG
            {
              mkword(0x601dUL | ($2 << 8)); 
              info[sect].offset += 2;
              mkword(0xfed2UL);
              info[sect].offset += 2;
	    }
        | POP
            {
              mkword(0xfed2UL);
              info[sect].offset += 2;
	    }
        ;

direc:   _ALIGN numexpr ',' numexpr
           {
	     if ($2 > info[sect].align)
	       info[sect].align = $2;
             while (info[sect].offset != ((info[sect].offset >> $2) << $2)) {
	       mkbyte($4);
	       info[sect].offset++;
	     }
           }
       | _ALIGN numexpr
           {
	     if ($2 > info[sect].align)
	       info[sect].align = $2;
             while (info[sect].offset != ((info[sect].offset >> $2) << $2)) {
	       mkbyte(0);
	       info[sect].offset++;
	     }
           }
       | _ASCII { encoding = ENCODING_PORTABLE; } '"' chars '"' { encoding = ENCODING_NATIVE; }
       | _ASCIIZ { encoding = ENCODING_PORTABLE; } '"' chars '"'
           { 
	     encoding = ENCODING_NATIVE;
	     mkbyte(0);
	     info[sect].offset++;
	   } ;
       | _STRING { encoding = xencoding; } '"' chars '"' { encoding = ENCODING_NATIVE; }
       | _ZSTRING { encoding = xencoding; } '"' chars '"'
           { 
	     encoding = ENCODING_NATIVE;
	     mkbyte(0);
	     info[sect].offset++;
	   } ;
       | _FILE '"' string '"'
           {
             if(infile)
	       free(infile);
	     infile = $3;
	   }
       | _LINE NUMBER
           { lineno = $2; }                       
       | SECT_ID
           { sect = $1; }
       | _SPACE numexpr ',' numexpr
           {
             unsigned long i;
             for (i = 0; i < $2; i++) {
	       mkbyte($4);
	       info[sect].offset++;
	     }
           }
       | _COMM symbol ',' numexpr
           {
	     mkcomm($2, $4);
           }
       | _LCOMM symbol ',' numexpr
           {
	     mklcomm($2, $4);
           }
       | _SPACE numexpr
           {
             unsigned long i;
             for (i = 0; i < $2; i++) {
	       mkbyte(0);
               info[sect].offset++;
	     }
           }
       | _BYTE bytes
       | _WORD words
       | _LONG longs
       | _GLOBAL globals
       | _SET symbol ',' numexpr
           { mkconst($2, $4); }
       ;

globals:   global
         | global ',' globals
         ;

global:   symbol
             { mkglobal($1); }
        ;

byte:   numexpr
           {
	     mkbyte($1);
	     info[sect].offset++;
           }
      ;

bytes: byte maybebytes ;
maybebytes: /* empty */ | ',' bytes ;

char:   CHAR
           {
	     mkbyte($1);
	     info[sect].offset++;
           }
      ;

chars:   char maybechars ;
maybechars: /* empty */ | chars ;

multichar: CHAR
              {
		$$ = $1;
	      }
         | multichar CHAR
              {
		$$ = ($1 << 8) | $2;
	      }
         ;

words:  word maybewords ;
word:   expr 
           {
	     mkword($1);
             info[sect].offset += 2; 
           }
      ;
maybewords: /* empty */ | ',' words ;

longs:  long maybelongs ;
long:   numexpr 
           {
	     mkword($1 >> 16);
             info[sect].offset += 2; 
	     mkword($1 & 0xffffLU);
             info[sect].offset += 2; 
           }
      ;
maybelongs: /* empty */ | ',' longs ;

expr:  symbolic '+' numexpr
           {
	     char *s;

	     if (! (s = strdup($1))) perror("strdup"), cleanup(), exit(1);
	     $$ = 0;
	     mkhi(s, info[sect].offset, $3);
	     mklo($1, info[sect].offset+1, $3);
	   }
     | symbolic '-' numexpr
           {
	     char *s;

	     if (! (s = strdup($1))) perror("strdup"), cleanup(), exit(1);
	     $$ = 0;
	     mkhi(s, info[sect].offset, -$3);
	     mklo($1, info[sect].offset+1, -$3);
	   }
     | symbolic
           {
	     char *s;

	     if (! (s = strdup($1))) perror("strdup"), cleanup(), exit(1);
	     $$ = 0;
	     mkhi(s, info[sect].offset, 0);
	     mklo($1, info[sect].offset+1, 0);
	   }
     | numexpr
           { $$ = $1; }
     ;

numexpr:   numexpr '+' numterm
              { $$ = $1 + $3; }
         | numexpr '-' numterm
              { $$ = $1 - $3; }
         | numexpr '<' numterm
              { $$ = $1 << $3; }
         | numexpr '>' numterm
              { $$ = $1 >> $3; }
         | numexpr '<' '<' numterm
              { $$ = $1 << $4; }
         | numexpr '>' '>' numterm
              { $$ = $1 >> $4; }
         | numterm
              { $$ = $1; }
         ;

numterm:   numterm '*' numfact
              { $$ = $1 * $3; }
         | numfact
              { $$ = $1; }
         | numterm '/' numfact
              { $$ = $3 ? ($1 / $3) : (yyerror("division by zero"), 0); }
         | numterm '%' numfact
              { $$ = $1 % $3; }
         | numterm '&' numfact
              { $$ = $1 & $3; }
         | numterm '!' numfact
              { $$ = $1 & ~$3; }
         | numterm '|' numfact
              { $$ = $1 | $3; }
         | numterm '^' numfact
              { $$ = $1 ^ $3; }
         ;

numfact:   NUMBER
              { $$ = $1; }
         | '\'' multichar '\''
              { $$ = $2; }
         | '-' numfact
              { $$ = - $2; }
         | '+' numfact
              { $$ = $2; }
         | '~' numfact
              { $$ = ~$2; }
         | '(' numexpr ')'
              { $$ = $2; }
         | LO '(' numexpr ')'
              { $$ = $3 & 0xffUL; }
         | HI '(' numexpr ')'
              { $$ = $3 >> 8; }
         | LO16 '(' numexpr ')'
              { $$ = $3 & 0xffffUL; }
         | HI16 '(' numexpr ')'
              { $$ = $3 >> 16; }
         ;
