/* This subroutine provides the interface for the yacc yyparse subroutine.
 * It process the operand and generates the object code.
 *
 * $Log: proper.c,v $
 * Revision 1.2  1997/06/04 01:16:37  david
 * ANSI-fied the program.
 * Fixed to restart FLEX lexical analyser properly.
 * Now checks yyparse gramar analyzer for problems.
 *
 */

#include <stdio.h>
#include <ctype.h>
#include "xasm.h"
#include "propery.h"

static char *RcsId = "$Id: proper.c,v 1.2 1997/06/04 01:16:37 david Exp $";

#define FindOff(x) (offset-loc-( setoff+(strlen(opptr1->opcode[x])>>1) ))
#define Check   (loc+setoff+(strlen(opptr1->opcode[REL])>>1)+check)

char fwdref;

static char *ptr; /* Pointer used by yacc parser (yyparse) */
static BYTE push, tfr;    /* Special Operands for TFR, EXG, PSH, PUL  */
static struct table *opptr1; /* Point to appropriate opcode table entry */
static WORD opercode; /* Operand Code (up to 2 bytes) */
static char setoff; /* Non-zero if offset (operand code) used */
static BYTE postbyte;
static char opermode; /* Mode of Operand */
static char objcode[128]; /* Object code generated */

#include "propery.c"

/* opptr = Pointer to appropriate opcode table entry */
char *proper( char *label, char *opcode, char *operand, struct table *opptr )
{
static char *special[]={"exg","tfr","pshs","puls","pshu","pulu", NULL };
int sptr, optr;
char spc = ':'; /* Special character for tfr or exg */
char temp[5]; /* Holds up to 4 characters + NULL */
	
fwdref = 0; /* No forward reference found yet */
opercode = 0; setoff = 0;/* Initialize operand object code */
postbyte = 0; opermode = 0;
*objcode = '\0'; /* Reset Object Code generated */

push = 0; tfr = 0;  /* Initialize special operands */
ptr = operand; /* Initialize parser to first of operand */
opptr1 = opptr;

/* Pre-Process Special operation codes */
if ( operand != NULL )
   if ( *operand != '\0' )
      for ( sptr = 0; special[sptr] != NULL; sptr++ )
	 { /* Check for special instruction handling */
	 if ( sptr >= 2 )
	    spc = '{'; /* Special character for pshs */
	 if ( sptr >= 4 )
	    spc = '}'; /* Special character for pshu */
         if ( strcmp( special[sptr], opcode ) == 0 )
	    { /* Make room for special character */
	    for ( optr = strlen( operand ) + 1; optr > 0; optr-- )
		operand[optr] = operand[optr - 1];
	    operand[0] = spc; /* Insert special character */
	    opermode = IMM; /* Set IMMEDIATE mode */
	    break; /* Get out of loop */
	    }
	 } /* end of 'for' loop */

/* Initialize Lexical Parsing */
yyrestart( NULL );

if ( yyparse() ) /* Call the yacc parser for the current operand */
   {
   fprintf( stderr, "Error in grammar.  Exiting.\n" );
   exit( 1 );
   }

if ( sptr <= 1 ) /* Post-Process EXG or TFR operands */
   opercode =  tfr;
if ( sptr >= 2 && sptr <= 5 ) /* Post-Process PSH or PUL operands */
   opercode = push;
if ( opptr != NULL )
   sprintf( objcode, "%s", opptr->opcode[opermode] );
if ( opermode == IND )
   {
   sprintf( temp, "%02x", postbyte );
   strcat( objcode, temp );
   }
if ( setoff )
   {
   if ( setoff == 1 )
      sprintf( temp, "%02x", opercode );
    else
      sprintf( temp, "%04x", opercode );
   strcat( objcode, temp );
   }
if ( opptr1 != NULL )
   locinc += (strlen( objcode ) >> 1);
return( objcode );
}

/* Form a string in the objcode buffer */
void formstr( char *string )
{
char temp[3];
int i, offset;
	
if ( (i = strlen( string )) >= sizeof( objcode ) - 1 )
   {
   xerror( "formstr: String too long." );
   return;
   }
   
if ( opptr1 != NULL && i >= 1 && i <= 2 ) /* Special..Select Addr Mode */
   {
   offset = *string;
   if ( i == 2 )
      offset = (offset * 256) + *(string+1);
   i = (i == 1 ) ? DIR : EXT; /* Switch to Dir or EXT Mode */
   formde( offset, i );
   return;
   }
locinc += i;
for ( ; i > 0; i--, string++ )
   {
   sprintf( temp, "%02x", *string );
   strcat( objcode, temp );
   }
}

/*
   NAME: formind
   This subroutine forms the post byte from the information from yyparse.
*/
void formind( long offset, int AccReg, int AddReg, int incdec, int ind, int force )
{
if ( opptr1 == NULL )
   {
   xerror( "formind: Invalid Operand for this OpCode.\n" );
   return;
   }
   
opermode = IND; /* Initilalize operand mode indicator */

/* Don't care in PC offset & Extended Indirect mode, so do anyway */
if ( AddReg >= X && AddReg <= S )
   postbyte |= ((AddReg - X) << 5); /* Shift AddReg left 5 bits */
      
if ( ind )
   postbyte |= 0x90; /* OR in INDIRECT & Special Bit 0x80 */
   
if ( AddReg == PCR )
   {
   setoff = 2;
   opercode = (FindOff(IND)-1) & 0xffff; /* 1, Because of postbyte */
   if ( (fwdref && force != 1) || force == 2 || (force == 0 &&
      (opercode > 0x007f && opercode < 0xff80)) )
      {
      postbyte |= 0x8d; /* OR in 16 bit PCR Offset & Special Bit 0x80 */
      setoff = 2; /* Use 16 bit length */
      opercode = (FindOff(IND)-1) & 0xffff; /* 1, Because of postbyte */
      }
    else
      {
      postbyte |= 0x8c; /* OR in 8 bit PCR Offset & Special Bit 0x80 */
      setoff = 1; /* Use 8 bit length */
      opercode = (FindOff(IND)-1) & 0xff; /* 1, Because of postbyte */
      }
   return;
   }
   
if ( incdec )
   {
   postbyte |= 0x80;
   switch( incdec )
      {
      case P : /* Form ",R+" */
	 postbyte |= 0;
	 break;
      case PP : /* Form ",R++" */
         postbyte |= 1;
         break;
      case M : /* Form ",-R" */
         postbyte |= 2;
         break;
      case MM : /* Form ",--R" */
         postbyte |= 3;
         break;
      default :
	 xerror( "formop: Invalid INC/DEC" );
	 break;
      }
   return;
   }
   
if ( AccReg )
   {
   switch( AccReg )
      {
      case A :
         postbyte |= 0x86; /* Acc A Offset */
	 break;
      case B :
         postbyte |= 0x85; /* Acc B Offset */
	 break;
      case D :
         postbyte |= 0x8b; /* Acc D Offset */
         break;
      default :
         fprintf( stderr, "Invalid Accumulator (%d)\n", AccReg );
         break;
      }
   return;
   }

/* Generate postbyte and offset (if any) */
if ( !ind && offset != -1 && !force && !fwdref )
   /* Can we select 5 Bit Offset Mode? */
   if ( (offset & 0xffff) <= 15 || (offset & 0xffff) >= 0xfff0 )
      {
      postbyte |= (offset & 0x1f);
      return;
      }

/* Select 0 offset? */
if ( offset == -1 && !force && !fwdref )
   postbyte |= 0x84;
 else /* Nope. Select 8 or 16 bit mode */
   if ( (fwdref && force != 1) || force == 2 || (force == 0 &&
      (offset > 0x007f || offset >= 0xff80)) )
      {
      opercode = offset & 0xffff;
      setoff = 2;
      postbyte |= 0x89; /* Select 16 bit offset mode */
      }
    else
      {
      opercode = offset & 0xff;
      setoff = 1;
      postbyte |= 0x88; /* Select 8 bit offset mode */
      }
}

/* Form Immediate operand code */
void formimm( int offset )
{
if ( opptr1 == NULL )
   {
   xerror( "formimm: Invalid Operand for this OpCode." );
   return;
   }
   
opermode = IMM;
setoff = opptr1->irlen; /* Set operand code length */
switch( setoff )
   {
   case 1 : /* 1 byte */
      opercode = offset & 0xff;
      break;
   case 2 : /* 2 bytes */
      opercode = offset &0xffff;
      break;
   default :
      fprintf( stderr, "formimm: Invalid operand code length.\n" );
      break;
   }
}

/* Form immediate string */
void formims( char *string )
{
if ( opptr1 == NULL )
   {
   xerror( "formimm: Invalid Operand for this OpCode." );
   return;
   }
   
opermode = IMM;
setoff = opptr1->irlen; /* Set operand code length */
switch( setoff )
   {
   case 1 : /* 1 byte */
      opercode = *string  & 0xff;
      break;
   case 2 : /* 2 bytes */
      opercode = (*string * 256 + *(string+1)) & 0xffff;
      break;
   default :
      fprintf( stderr, "formimm: Invalid operand code length.\n" );
      break;
   }
}

/* Form DIRect or EXTended operand code */
void formde( int offset, int mode )
{
if ( opptr1 == NULL ) /* This is for psuedops */
   {
   setoff = 2;
   opercode = offset & 0xffff;
   return;
   }
   
while( 1 ) /* Go around until mode is selected */
   switch( mode )
      {
      case 0 : /* Program Chooses...or select Relative Mode */
         if ( *(opptr1->opcode[DIR]) != '\0' )
	    if ( (offset >> 8) == dp && !fwdref )
               { /* Choose DIR addressing mode */
               mode = opermode = DIR;
	       continue;
	       }

	 if ( *(opptr1->opcode[EXT]) != '\0' )
            { /* Choose EXT unless DP set == to highbyte of offset */
	    mode = opermode = EXT;
	    continue;
	    }

	 if ( *(opptr1->opcode[REL]) != '\0' )
	    {
            setoff = opptr1->irlen; /* Set to length of operand */
	    opermode = REL;
	    if ( setoff == 1 )
	       opercode = FindOff(REL) & 0xff;
	     else
	       opercode = FindOff(REL) & 0xffff;
	    { /* Now check if it worked */
	    WORD check;
	    
	    check = opercode;
	    if ( (setoff == 1) && (opercode & 0x80) )
		  check |= 0xff00;

	    if ( (Check & 0xffff) != (offset & 0xffff ) )
	       xerror( "Relative Branch Offset Error." );
	    }
	    return;
	    }
	 fprintf( stderr, "formde: Invalid Addressing mode.\n" );
         return;
      case DIR: /* 1 Byte */
         opercode = offset & 0xff;
         setoff = 1; /* Set to 1 byte */
	 opermode = DIR;
         return; /* Get out of whole subroutine */
      case EXT : /* 2 Bytes */
         opercode = offset & 0xffff;
         setoff = 2; /* Set to 2 bytes */
	 opermode = EXT;
         return;
      default :
         fprintf( stderr, "formde: Invalid mode (%d).\n", mode );
         return;
      } /* End of switch */
}

void formpsh( int reg )
{
static int list[]={PCR,128,UR,64,S,64,Y,32,X,16,DP,8,D,6,B,4,A,2,CC,1,0};
int ptr;
	
opermode = IMM; setoff = 1;
for ( ptr = 0; list[ptr] != 0; ptr += 2 )
   if ( list[ptr] == reg )
      {
      push |= list[ ptr + 1 ];
      break;
      }
}

/* formtfr - This subroutine is called from yacc when it recognizes two
   valid registers. It forms the operand byte and puts it in 'tfr'. */
void formtfr( int reg1, int reg2 )
{
opermode = IMM; setoff = 1;
tfr = (gtfrreg( reg1 ) << 4) + gtfrreg( reg2 );
}

/* gtfrreg - Looks up the appropriate code for each register */
int gtfrreg( int reg )
{
static int list[16]={D,X,Y,UR,S,PCR,0,0,A,B,CC,DP,0,0,0,0};
int ptr;

for ( ptr = 0; list[ptr] != reg; ptr++ )
   ;
return( ptr );
}
