#include <string.h>
#include <ctype.h>
#include <stdio.h>
#include "as.h"
#include "arg_type.h"


process_label() {}


process_mnemonic( m)
char *m;
{
	for ( ; *m != '\0'; m++)
		if ( *m == '.')
			*m = '_';
}


process_operand( str, op)
register char *str;
register struct t_operand *op;
{
	char *glob_lbl(), *strchr();

	op->type = 0;

	switch ( *str) {
	  case '#' : op->type = IS_IMMEDIATE;
		     op->expr = str+1;

		     if ( *(op->expr) != '$') {			/*  #1  */
			op->val = atoi( str+1);
			if ( 1 <= op->val  &&  op->val <= 8 ) {
				op->type = IS_QUICK;
				op->lbl = NULL;
			}
		     }
		     else 
			if ( arg_type( str+1) == STRING) {	/*  #$1+$2  */
				op->lbl = op->expr;
				*(op->lbl+2) = '\0';
				op->expr = op->lbl+3;
		     	}
		     	else					/*  #$1  */
				op->lbl = NULL;
		     break;

	  case '(' : if ( strchr( str+1, ',') == NULL)
			if ( is_reg( str+1)) {
				op->reg = reg_val( str+1);

				if ( *(str+4) == '+')		/*  (sp)+  */
					op->type = IS_INCR;
				else				/*  (a0)  */
					op->type = IS_IND_REG;
			}
			else {
				op->type = IS_IND_MEM;
				op->expr = str+1;
				for ( str++; *++str != ')';)
					;
				*str = '\0';

				if ( *(op->expr) == '$')
					if ( arg_type( op->expr) == STRING) {
								/*  ($1+$2)  */
						op->lbl = op->expr;
						if ( strlen( op->lbl) == 2) 
							op->expr = "0";
						else {
							*(op->lbl+2) = '\0';
							op->expr = op->lbl+3;
						}
		     			}
		     			else			/*  ($1)  */
						op->lbl = NULL;
				else if ( isdigit( *(op->expr))) /*  (1)  */
					op->lbl = NULL;
				else {				/*  (.trppc) */
					op->lbl = glob_lbl( op->expr);
					op->expr = "0";
				}
			}
		     else
			if ( *(str+1) == '[') {
				op->type = IS_IND_IND;
				op->expr = str+2;
				for ( str += 2; *++str != ',';)
					;
				*str++ = '\0';
				for ( ; *str == ' '; str++)
					;
				op->reg = reg_val( str);

				for ( ; *str++ != ']';)
					;
				if ( *str == ')')	/*  ([$1, a6])  */
					op->expr2 = 0;
				else {			/*  ([8,a6],8)  */
					for ( ; *str++ != ',';)
						;
					for ( ; *str == ' '; str++)
						;
					op->expr2 = atoi( str);
				}
			}
			else {
				op->expr = str+1;
				for ( str++; *++str != ',';)
					;
				*str++ = '\0';
				for ( ; *str == ' '; str++)
					;
				op->reg = reg_val( str);
				if ( *(str+2) == ')') {		/*  (4, a0)  */
					int i = atoi(op->expr);
					if ((*(op->expr) == '-' ||
					     isdigit(*(op->expr))) &&
					    i <= 32767 && i >= -32768) {
						op->type = IS_IND_REG_DISPL;
					}
					else	op->type = IS_IND_REG_EDISPL;
				}
				else {			 /*  (0, sp, d0.l*1)  */
					op->type = IS_3_OPS;
					for ( str++; *++str != ',';)
						;
					for ( ; *str == ' '; str++)
						;
					op->reg2 = reg_val( str);

					for ( ; *str++ != '*';)
						;
					op->scale = atoi( str);
				}
			}
		     break;

	  case '-' : op->type = IS_DECR;			/*  -(sp)  */
		     op->reg = reg_val( str+2);
		     break;

	  case '$' : op->type = IS_GLOB_LBL;			/*  $1  */
		     op->lbl = str;
		     op->expr ="0";
		     break;

	  default  : if ( is_reg( str)) {
			op->reg = reg_val( str);
			if ( *(str+2) == ':') {			/*  d2:d1  */
				op->type = IS_REG_PAIR;
				op->reg2 = reg_val( str+3);
			}
			else					/*  a6  */
				op->type = ( *str == 'd' ? IS_D_REG : IS_A_REG);
		     }
		     else if ( isdigit( *str)) {		/*  1f  */
			op->type = IS_LOC_LBL;
			op->lbl = str;
			op->expr = "0";
			*(str+1) ='\0';
		     }
		     else {					/*  .strhp  */
			op->type = IS_GLOB_LBL;
			op->lbl = glob_lbl( str);
			*(str+1) ='\0';
			op->expr = "0";
		     }
	}
}


int reg_val( reg)
char *reg;
{
	return( *reg == 's' ? 7 : atoi( reg+1));
}

int is_reg( str)
register char *str;
{
	switch ( *str) {
	  case 'a' :
	  case 'd' : return( isdigit( *(str+1)));

	  case 's' : return( *(str+1) == 'p');

	  default  : return( 0);
	}
}


char *glob_lbl( lbl)
char *lbl;
{
	char *gl, *Malloc();

	gl = Malloc( strlen( lbl) + 3);
	sprintf( gl, "\"%s\"", lbl);
	return( gl);
}


/******************************************************************************/


int mode_reg( eaddr)
register struct t_operand *eaddr;
{
	switch ( eaddr->type) {
	  case IS_A_REG         : return( 0x08 | eaddr->reg);

	  case IS_D_REG         : return( 0x00 | eaddr->reg);

	  case IS_IND_REG       : return( 0x10 | eaddr->reg);

	  case IS_INCR          : return( 0x18 | eaddr->reg);

	  case IS_DECR          : return( 0x20 | eaddr->reg);

	  case IS_IND_MEM       : return( 0x39);

	  case IS_IND_IND       : return( 0x30 | eaddr->reg);

	  case IS_IND_REG_DISPL : return( 0x28 | eaddr->reg);

	  case IS_IND_REG_EDISPL: return( 0x30 | eaddr->reg);

	  case IS_GLOB_LBL      : return( 0x39);

	  case IS_3_OPS         : if ( isdigit( *(eaddr->expr)) &&
				                    atoi( eaddr->expr) < 128)
					return( 0x30 | eaddr->reg);
				  else
					fprintf( stderr, "FOUT in IS_3_OPS\n");
				  break;

	  case IS_QUICK		:
	  case IS_IMMEDIATE	: return( 0x3c);

	  default		: fprintf( stderr,
					   "mode_reg(), wrong operand %d\n",
					   eaddr->type);
				  abort();
				  break;
	}
}


code_extension( eaddr)
register struct t_operand *eaddr;
{

	switch ( eaddr->type) {
	  case IS_IND_MEM       : if ( eaddr->lbl == NULL) 
					@text4( %$( eaddr->expr));
				  else
					@reloc4( %$( eaddr->lbl),
						 %$( eaddr->expr),
						 ABSOLUTE);
				  break;

	  case IS_IND_IND       : if ( eaddr->expr2 == 0) {
				  	@text2( 0x161);
					@text2( %$(eaddr->expr));
				  }
				  else {
				  	@text2( 0x162);
					@text2( %$(eaddr->expr));
					@text2( %d(eaddr->expr2));
				  }
				  break;

	  case IS_IND_REG_DISPL : @text2( %$( eaddr->expr));
				  break;

	  case IS_IND_REG_EDISPL :@text2(0x0170);
				  @text4( %$( eaddr->expr));
				  break;

	  case IS_GLOB_LBL      : @reloc4( %$(eaddr->lbl),
					   %$(eaddr->expr),
					   ABSOLUTE);
				  break;

	  case IS_3_OPS         : if ( isdigit( *(eaddr->expr)) &&
				                    atoi( eaddr->expr) < 128) {

					@text2( %d( 0x0800 |
				  	  	  ( eaddr->reg2 << 12) |
					  	  ( two_log( eaddr->scale)<<9) |
					  	  atoi( eaddr->expr)));
				  }
				  else
					fprintf( stderr, "FOUT in IS_3_OPS\n");
				  break;

	  case IS_QUICK		:
	  case IS_IMMEDIATE	: if ( eaddr->lbl != NULL)
					@reloc4( %$(eaddr->lbl),
						 %$(eaddr->expr),
						 ABSOLUTE);
				  else
					@text4( %$(eaddr->expr));
	}
}


int reg_mode( eaddr)
struct t_operand *eaddr;
{
	int mr;

	mr = mode_reg( eaddr);
	return( ((mr & 0x7) << 3) | ((mr & 0x38) >> 3));
}


code_opcode( opcode, field1, field2, eaddr)
int opcode, field1, field2;
struct t_operand *eaddr;
{
	@text2( %d(((opcode & 0xf) << 12) | ((field1 & 0x7) << 9) |
		   ((field2 & 0x7) << 6) | (mode_reg(eaddr) & 0x3f)));
}


/* The attempts to optimize the instruction size when it cannot be done
   at code-expander generation time is actually quite dangerous, because
   it may not happen between references to and definitions of (corresponding)
   local labels. The reason for this is that these offsets are computed
   at code-expander generation time. Unfortunately, no warning is given,
   so this has to be checked by hand.
*/
code_instr( opcode, field1, field2, eaddr)
int opcode, field1, field2;
struct t_operand *eaddr;
{
	if (eaddr->type == IS_IND_REG_EDISPL) {
		@__instr_code(%d(((opcode & 0xf) << 12) | ((field1 & 0x7) << 9) |
			        ((field2 & 0x7) << 6)),
			      %d(eaddr->reg), %$(eaddr->expr));
	}
	else {
		code_opcode( opcode, field1, field2, eaddr);
		code_extension( eaddr);
	}
}


code_move( size, src, dst)
int size;
struct t_operand *src, *dst;
{
	if (src->type == IS_IND_REG_EDISPL) {
		if (dst->type == IS_IND_REG_EDISPL) {
			@__moveXX(%d( ((size & 0x3) << 12)),
				 %d(dst->reg), %$(dst->expr),
				 %d(src->reg), %$(src->expr));
		}
		else {
			@__instr_code(%d( ((size & 0x3) << 12)|((reg_mode( dst) & 0x3f) << 6)),
				 %d(src->reg), %$(src->expr));
		}
	}
	else if (dst->type == IS_IND_REG_EDISPL) {
		@__move_X(%d( ((size & 0x3) << 12) | (mode_reg( src) & 0x3f)),
			 %d(dst->reg), %$(dst->expr));
	}
	else {
		@text2( %d( ((size & 0x3) << 12) | ((reg_mode( dst) & 0x3f) << 6) |
		    	     		   (mode_reg( src) & 0x3f)));
		code_extension( src);
		code_extension( dst);
	}
}


code_dist4( dst)
struct t_operand *dst;
{
	@reloc4( %$(dst->lbl), %$(dst->expr) + 4, PC_REL);
}


int two_log( nr)
register int nr;
{
	register int log;

	for ( log = 0; nr >= 2; nr >>= 1)
		log++;

	return( log);
}
