
/*
   This software is copyright 1989 by John Dunning.  See the file
   'COPYLEFT.JRD' for the full copyright notice.
*/

/* opcode decoder */

#include "util.h"
#include "parse.h"
#include "gen.h"
#include "eval.h"
#include "global.h"

/* opcode classes */

/* opcode is the compete instr */
#define	CLIMM	0
/* register inst */
#define CLREG	1
/* general addressing */
#define CLEA	2
/* subset addressing */
#define	CLSA	3
/* subset + accum */
#define CLRSA	4
/* branch */
#define CLBR	5

#define CLJSR	6
#define CLJMP	7

/* defs for operand types */

#define OPIMM	0x0001		/* immediate */
#define	OPABS	0x0002		/* absolute, long */
#define OPABSS	0x0004		/* absolute, short */
#define OPABSX	0x0008		/* long,x */
#define OPABSSX	0x0010		/* short,x */
#define OPABSY	0x0020		/* long,y */
#define OPABSSY	0x0040		/* short,y */
#define OPINDX	0x0080		/* (foo,x) */
#define OPINDY	0x0100		/* (foo),y */
#define OPACCUM	0x0200		/* A */
#define OPINDL	0x0400		/* (addr) */
#define OPBR	0x0800		/* like abs, but different so we can
				   tell what to generate */
/* the opcode table */

#ifndef old

struct op_elt 
	{
	char * name;		/* opcode name */
	char basecode;		/* basic opcode */
	int valid;		/* a bit mask of valid operand types */
	};

struct op_elt optab[] =
	{
	{"LDA", 0xAD, 
		OPIMM | OPABS | OPABSS | OPABSX | OPABSSX |
		OPABSY | OPINDX | OPINDY},
	{"STA", 0x8D,
		OPABS | OPABSS | OPABSX | OPABSSX |
		OPABSY | OPINDX | OPINDY},
	{"JMP", 0x4C, OPABS | OPINDL},
	{"JSR", 0x20, OPABS},
	{"RTS", 0x60, 0},
	{"LDX", 0xAE, 
		OPIMM | OPABS | OPABSS | OPABSY | OPABSSY},
	{"STX", 0x8E, 
		OPABS | OPABSS | OPABSY | OPABSSY},
	{"LDY", 0xAC, 
		OPIMM | OPABS | OPABSS | OPABSY | OPABSSY},
	{"STY", 0x8C, 
		OPABS | OPABSS | OPABSSX},
	{"AND", 0x2D, 
		OPIMM | OPABS | OPABSS | OPABSX | OPABSSX |
		OPABSY | OPINDX | OPINDY},
	{"BIT", 0x2C, OPABS | OPABSS},
	{"CMP", 0xCD, 
		OPIMM | OPABS | OPABSS | OPABSX | OPABSSX |
		OPABSY | OPINDX | OPINDY},
	{"CPX", 0xEC, OPIMM | OPABS | OPABSS},
	{"CPY", 0xCC, OPIMM | OPABS | OPABSS},
	{"ADC", 0x6D, 
		OPIMM | OPABS | OPABSS | OPABSX | OPABSSX |
		OPABSY | OPINDX | OPINDY},
	{"SBC", 0xED, 
		OPIMM | OPABS | OPABSS | OPABSX | OPABSSX |
		OPABSY | OPINDX | OPINDY},
	{"ORA", 0x0D, 
		OPIMM | OPABS | OPABSS | OPABSX | OPABSSX |
		OPABSY | OPINDX | OPINDY},
	{"EOR", 0x4D, 
		OPIMM | OPABS | OPABSS | OPABSX | OPABSSX |
		OPABSY | OPINDX | OPINDY},
	{"INC", 0xEE, OPABS | OPABSS | OPABSX | OPABSSX},
	{"DEC", 0xCE, OPABS | OPABSS | OPABSX | OPABSSX},
	{"INX", 0xE8, 0},
	{"DEX", 0xCA, 0},
	{"INY", 0xC8, 0},
	{"DEY", 0x88, 0},
	{"ASL", 0x0E, 
		OPABS | OPABSS | OPABSX | OPABSSX | 
		OPACCUM},
	{"ROL", 0x2E, 
		OPABS | OPABSS | OPABSX | OPABSSX |
		OPACCUM},
	{"LSR", 0x4E, 
		OPABS | OPABSS | OPABSX | OPABSSX |
		OPACCUM},
	{"ROR", 0x6E, 
		OPABS | OPABSS | OPABSX | OPABSSX |
		OPACCUM},
	{"TAX", 0xAA, 0},
	{"TXA", 0x8A, 0},
	{"TAY", 0xA8, 0},
	{"TYA", 0x98, 0},
	{"TSX", 0xBA, 0},
	{"TXS", 0x9A, 0},
	{"CLV", 0xB8, 0},
	{"CLD", 0xD8, 0},
	{"SED", 0xF8, 0},
	{"CLI", 0x58, 0},
	{"SEI", 0x78, 0},
	{"CLC", 0x18, 0},
	{"SEC", 0x38, 0},
	{"BPL", 0x10, OPBR},
	{"BMI", 0x30, OPBR},
	{"BVC", 0x50, OPBR},
	{"BVS", 0x70, OPBR},
	{"BCC", 0x90, OPBR},
	{"BCS", 0xB0, OPBR},
	{"BNE", 0xD0, OPBR},
	{"BEQ", 0xF0, OPBR},
	{"BRK", 0x00, 0},
	{"RTI", 0x40, 0},
	{"PHP", 0x08, 0},
	{"PLP", 0x28, 0},
	{"PHA", 0x48, 0},
	{"PLA", 0x68, 0},
	{"NOP", 0xEA, 0},
	{0, 0, 0}
	};

#else
/* old way */

struct op_elt 
	{
	char name[4];		/* opcode name */
	char basecode;		/* basic opcode */
	int valid;		/* a bit mask of valid operand types */
	};

struct op_elt optab[] =
	{
	{'L', 'D', 'A', '\0', 0xAD, 
		OPIMM | OPABS | OPABSS | OPABSX | OPABSSX |
		OPABSY | OPINDX | OPINDY},
	{'S', 'T', 'A', '\0', 0x8D,
		OPABS | OPABSS | OPABSX | OPABSSX |
		OPABSY | OPINDX | OPINDY},
	{'L', 'D', 'X', '\0', 0xAE, 
		OPIMM | OPABS | OPABSS | OPABSY | OPABSSY},
	{'S', 'T', 'X', '\0', 0x8E, 
		OPABS | OPABSS | OPABSY | OPABSSY},
	{'L', 'D', 'Y', '\0', 0xAC, 
		OPIMM | OPABS | OPABSS | OPABSY | OPABSSY},
	{'S', 'T', 'Y', '\0', 0x8C, 
		OPABS | OPABSS | OPABSSX},
	{'A', 'N', 'D', '\0', 0x2D, 
		OPIMM | OPABS | OPABSS | OPABSX | OPABSSX |
		OPABSY | OPINDX | OPINDY},
	{'B', 'I', 'T', '\0', 0x2C, OPABS | OPABSS},
	{'C', 'M', 'P', '\0', 0xCD, 
		OPIMM | OPABS | OPABSS | OPABSX | OPABSSX |
		OPABSY | OPINDX | OPINDY},
	{'C', 'P', 'X', '\0', 0xEC, OPIMM | OPABS | OPABSS},
	{'C', 'P', 'Y', '\0', 0xCC, OPIMM | OPABS | OPABSS},
	{'A', 'D', 'C', '\0', 0x6D, 
		OPIMM | OPABS | OPABSS | OPABSX | OPABSSX |
		OPABSY | OPINDX | OPINDY},
	{'S', 'B', 'C', '\0', 0xED, 
		OPIMM | OPABS | OPABSS | OPABSX | OPABSSX |
		OPABSY | OPINDX | OPINDY},
	{'O', 'R', 'A', '\0', 0x0D, 
		OPIMM | OPABS | OPABSS | OPABSX | OPABSSX |
		OPABSY | OPINDX | OPINDY},
	{'E', 'O', 'R', '\0', 0x4D, 
		OPIMM | OPABS | OPABSS | OPABSX | OPABSSX |
		OPABSY | OPINDX | OPINDY},
	{'I', 'N', 'C', '\0', 0xEE, OPABS | OPABSS | OPABSX | OPABSSX},
	{'D', 'E', 'C', '\0', 0xCE, OPABS | OPABSS | OPABSX | OPABSSX},
	{'I', 'N', 'X', '\0', 0xE8, 0},
	{'D', 'E', 'X', '\0', 0xCA, 0},
	{'I', 'N', 'Y', '\0', 0xC8, 0},
	{'D', 'E', 'Y', '\0', 0x88, 0},
	{'A', 'S', 'L', '\0', 0x0E, 
		OPABS | OPABSS | OPABSX | OPABSSX | 
		OPACCUM},
	{'R', 'O', 'L', '\0', 0x2E, 
		OPABS | OPABSS | OPABSX | OPABSSX |
		OPACCUM},
	{'L', 'S', 'R', '\0', 0x4E, 
		OPABS | OPABSS | OPABSX | OPABSSX |
		OPACCUM},
	{'R', 'O', 'R', '\0', 0x6E, 
		OPABS | OPABSS | OPABSX | OPABSSX |
		OPACCUM},
	{'T', 'A', 'X', '\0', 0xAA, 0},
	{'T', 'X', 'A', '\0', 0x8A, 0},
	{'T', 'A', 'Y', '\0', 0xA8, 0},
	{'T', 'Y', 'A', '\0', 0x98, 0},
	{'T', 'S', 'X', '\0', 0xBA, 0},
	{'T', 'X', 'S', '\0', 0x9A, 0},
	{'C', 'L', 'V', '\0', 0xB8, 0},
	{'C', 'L', 'D', '\0', 0xD8, 0},
	{'S', 'E', 'D', '\0', 0xF8, 0},
	{'C', 'L', 'I', '\0', 0x58, 0},
	{'S', 'E', 'I', '\0', 0x78, 0},
	{'C', 'L', 'C', '\0', 0x18, 0},
	{'S', 'E', 'C', '\0', 0x38, 0},
	{'B', 'P', 'L', '\0', 0x10, OPBR},
	{'B', 'M', 'I', '\0', 0x30, OPBR},
	{'B', 'V', 'C', '\0', 0x50, OPBR},
	{'B', 'V', 'S', '\0', 0x70, OPBR},
	{'B', 'C', 'C', '\0', 0x90, OPBR},
	{'B', 'C', 'S', '\0', 0xB0, OPBR},
	{'B', 'N', 'E', '\0', 0xD0, OPBR},
	{'B', 'E', 'Q', '\0', 0xF0, OPBR},
	{'J', 'M', 'P', '\0', 0x4C, OPABS | OPINDL},
	{'J', 'S', 'R', '\0', 0x20, OPABS},
	{'R', 'T', 'S', '\0', 0x60, 0},
	{'B', 'R', 'K', '\0', 0x00, 0},
	{'R', 'T', 'I', '\0', 0x40, 0},
	{'P', 'H', 'P', '\0', 0x08, 0},
	{'P', 'L', 'P', '\0', 0x28, 0},
	{'P', 'H', 'A', '\0', 0x48, 0},
	{'P', 'L', 'A', '\0', 0x68, 0},
	{'N', 'O', 'P', '\0', 0xEA, 0},
	0
	};
#endif

/* valid addressing modes, per opclass */
int mvalid[] = 
	{
	0,					/* climm, no args */
	0,					/* clreg, not used? */
	OPIMM | OPABS | OPABSS | OPABSX | 
		OPABSSX | OPABSY | OPABSSY |
		OPINDX | OPINDY,		/* general addressing */
	OPIMM | OPABS | OPABSS | OPABSX | OPABSSX,	/* subset */
	OPIMM | OPABS | OPABSS | OPABSX | OPABSSX | OPACCUM,  /* sub + a */
	OPABS,					/* branch, abs */
	OPABS,					/* jsr, abs */
	OPABS | OPINDL				/* jmp, abs or ind */
	};


/* given a name, return T if it's an opcode, and if so, return
   its code and flags */
int opcode_p(name, code, valid)
char * name;
int * code;
int * valid;
{
  int i;

  if (strlen(name) != 3)
	return(0);		/* don't bother */

  for (i = 0 ; ; i++)
	{
	if (optab[i].name[0] == '\0')
		return(0);

	if (string_equal(optab[i].name, name))
		{
		*code = optab[i].basecode;
		*valid = optab[i].valid;
		return(1);
		}
	}
}


/* handed an opcode and opflags, finish generating this instruction */

void assemble_op(basecode, valid, args)
int basecode;
int valid;
{
  int operand, optype, expr_flags;
  struct sym * rel_to_sym;

/* later, leave a slot for relocatable stuff, ptr to sym this
   word is relative to, or something */

/*  set_label();  */
  gen_label();
  operand = optype = 0;
  if (valid)
	optype = decode_operand(valid, &operand, &expr_flags, &rel_to_sym,
				 args);

/* now have operand etc */
  switch (optype)
	{
	case 0:			/* no operand */
		{
		genlit(basecode);
		break;
		}
	case OPIMM:
		{
		/* bit of a kludge here... gen code ^ 0x04, but if bit 0 is
		    clear, clear bit 3 as well */
		if (basecode & 0x01)
			genlit(basecode ^ 0x04);
		    else
			genlit((basecode ^ 0x04) & 0xF7);
		genbyte(operand, expr_flags, rel_to_sym);
		break;
		}
	case OPABS:
		{
		genlit(basecode);
		genword(operand, expr_flags, rel_to_sym);
		break;
		}
	case OPBR:
		{
		genlit(basecode);
/*		genlit(operand - pc - 1);	/* should range check this... */
		genbranch(rel_to_sym, operand);
		break;
		}
	case OPABSS:
		{
		genlit(basecode ^ 0x08);
		/* rel is an error here... */
		genbyte(operand, expr_flags, rel_to_sym);
		break;
		}
	case OPABSX:			/* xxxx,x */
		{
		genlit(basecode ^ 0x10);
		genword(operand, expr_flags, rel_to_sym);
		break;
		}
	case OPABSSX:
		{
		genlit(basecode ^ 0x18);
		/* rel is an error... */
		genbyte(operand, expr_flags, rel_to_sym);
		break;
		}
	case OPABSY:			/* xxxx,y */
		{
		genlit(basecode ^ 0x14);
		genword(operand, expr_flags, rel_to_sym);
		break;
		}
	case OPABSSY:
		{
		genlit(basecode ^ 0x18);
		genbyte(operand, expr_flags, rel_to_sym);
		break;
		}
	case OPINDX:			/* (xx,x) */
		{
		genlit(basecode ^ 0x0C);
		genbyte(operand, expr_flags, rel_to_sym);
		break;
		}
	case OPINDY:				/* (xx),y */
		{
		genlit(basecode ^ 0x1C);
		genbyte(operand, expr_flags, rel_to_sym);
		break;
		}
	case OPACCUM:		/* A */
		{
		genlit(basecode ^ 0x04);
		break;
		}
	case OPINDL:
		{
		genlit(basecode ^ 0x20);
		genword(operand, expr_flags, rel_to_sym);
		break;
		}
	}

}

/* passed a bit mask of legal optypes, try to make sense out of the arg
   vector, and return a 16-bit operand value, and the type that it is.
   the value and relative flag are returned by reference; the type is
   returned as the function value;
*/
int decode_operand(valid, operand, expr_flags, rel_to_sym, args)
int valid;			/* mask of valid types */
int * operand;
int * expr_flags;
struct sym ** rel_to_sym;
char * args[];
{
  int val;
  int e_flags;			/* flags from eval */
  char indexed_p;		/* if operand is indexed */
  char indirect_p;		/* if operand is indirect */
  int type;			/* optype we figure out */
  char * arg0;
  char * arg1;
  int short_p;

  arg0 = args[0];
  arg1 = args[1];

/* make sure there's an operand */
  if (!arg0)
	{
	barf("operand required");
	return(0);
	};

/* special case check for accum */
  if (string_equal(arg0, "A"))
	{
	return(OPACCUM);
	}

/* first check for come kind of indirect operand */
  if (arg0[0] == '(')
	{
	if (arg1)
		{
		indirect_p = toupper(arg1[0]);

		/* later, be smarter.  this should work for now... */
		if (indirect_p == 'Y')
			type = OPINDY;
		    else
			type = OPINDX;
		val = eval(arg0 + 1, &e_flags, rel_to_sym);
		*operand = val;
		*expr_flags = e_flags;
		}
	    else		/* must be (addr) */
		{
		type = OPINDL;
		val = eval(arg0 + 1, &e_flags, rel_to_sym);
		*operand = val;
		*expr_flags = e_flags;
		}
	return(type);
	}
    else		/* first arg doesn't start with a paren */
	{
	if ((arg0[0] == '#') || (arg0[0] == '='))	/* immediate? */
		{
		val = eval(arg0 + 1, &e_flags, rel_to_sym);	/* get expr value */
		/* make sure it's 8 bits... later */

		*operand = val & 0xFF;
		*expr_flags = e_flags;
		return(OPIMM);
		}
	    else		/* not immediate */
		{
		val = eval(arg0, &e_flags, rel_to_sym);		/* get expr val */
		/* see if indexed by anything */
		if (arg1)
			{
			if (string_equal(arg1, "Y") || string_equal(arg1, "X"))
				{
				indirect_p = toupper(arg1[0]);
				/* later, figure out whether to use short
				   addressing; for now just do it the easy 
				   way */

				short_p = (((val & 0xFF00) == 0) &
					   (!(e_flags & E_UNDEF)));
				if (indirect_p == 'X')
					{
					if (short_p && (valid & OPABSSX))
						type = OPABSSX;
					    else
						type = OPABSX;
					}
				    else
					{
					if (short_p && (valid & OPABSSY))
						type = OPABSSY;
					    else
						type = OPABSY;
					}
				*operand = val;
				*expr_flags = e_flags;
				return(type);
				}
			    else
				{
				/* arg1 there but not x or y?  bogus */
				barf("too many operands");
				return(0);
				}
			}		/* env of 'arg1' */
		    else
			{		/* no arg1, must be absolute */
			/* later, check for page 0. */
			*operand = val;
			*expr_flags = e_flags;
			if (valid & OPBR)
				return(OPBR);
			    else
				if ((valid & OPABSS) && 
				    ((val & 0xFF00) == 0) &&
				    (!(e_flags & E_REL)) && 
				    (!(e_flags & E_UNDEF)))
					{
/*	printf("line %d %X would have been short\n", line_nbr, val); */
					return(OPABSS);
					}
				    else
					return(OPABS);
			}
		}			/* end of 'not immed' */
	}				/* end of 'not indirect' */		
}
