/*
 *      do9.c	-	MC6809 specific processing
 *
 *	From Motorola BBS.
 *	See ../doc/ChangeLog to examine and/or add change descriptions.
 */

#define PAGE2	0x10
#define PAGE3	0x11
#define IPBYTE	0x9F	/* extended indirect postbyte */
#define SWI     0x3F

/* register names */

#define RD	0
#define RX	1
#define RY	2
#define RU	3
#define RS	4
#define RPC	5
#define RA	8
#define RB	9
#define RCC	10
#define RDP	11
#define RPCR	12

/* convert tfr/exg reg number into psh/pul format */
int     regs[] = { 6,16,32,64,64,128,0,0,2,4,1,8,0};
int     rcycl[]= { 2,2, 2, 2, 2, 2,  0,0,1,1,1,1,0};

/* addressing modes */
#define IMMED   0       /* immediate */
#define IND     1       /* indexed */
#define INDIR   2       /* indirect */
#define OTHER   3       /* NOTA */

/*
 *      localinit --- machine specific initialization
 */
void
localinit(void)
{
}

/*
 *      do_op --- process mnemonic
 *
 *	Called with the base opcode and it's class. Optr points to
 *	the beginning of the operand field.
 */
void
do_op(int opcode, int class)
{
        char    *skip_white(char *ptr);
	int     dist;   /* relative branch distance */
	int     src,dst;/* source and destination registers */
	int     pbyte;  /* postbyte value */
	int     amode;  /* indicated addressing mode */
	int	j;

	if ( class == INH ) { /* inherent addressing */
			emit(opcode);
			return;
		}
	else if ( class == P2INH ) { /* Page 2 inherent */
			emit(PAGE2);
			emit(opcode);
			return;
		}
	else if ( class == P3INH ) { /* Page 3 inherent */
			emit(PAGE3);
			emit(opcode);
			return;
		}
	amode = set_mode();     /* pickup indicated addressing mode */

	switch(class){
		case GEN:                       /* general addressing */
			do_gen(opcode,amode);
			return;
		case IMM:                       /* immediate addressing */
			if( amode != IMMED ){
				error("Immediate Operand Required");
				return;
				}
			Optr++;
			eval();
			emit(opcode);
			emit(lobyte(Result));
			return;
		case REL:                       /* short relative branches */
			if( eval() )
				dist = Result - (Pc+2);
			else dist = -2;
			emit(opcode);
			if( (dist >127 || dist <-128) && Pass==2){
				error("Branch out of Range");
				dist = -2;
				}
			emit(lobyte(dist));
			return;
		case P2REL:                     /* long relative branches */
			eval();
			dist = Result - (Pc+4);
			emit(PAGE2);
			emit(opcode);
			eword(dist);
			return;
		case P1REL:                     /* lbra and lbsr */
			eval();
			dist = Result - (Pc+3);
			emit(opcode);
			eword(dist);
			return;
		case NOIMM:
			if( amode == IMMED ){
				error("Immediate Addressing Illegal");
				return;
				}
			do_gen(opcode,amode);
			return;
		case P2GEN:
			emit(PAGE2);
			if( amode == IMMED ){
				emit(opcode);
				Optr++;
				eval();
				eword(Result);
				return;
				}
			do_gen(opcode,amode);
			return;
		case P3GEN:
			emit(PAGE3);
			if( amode == IMMED ){
				emit(opcode);
				Optr++;
				eval();
				eword(Result);
				return;
				}
			do_gen(opcode,amode);
			return;
		case RTOR:                      /* tfr and exg */
			emit(opcode);
			src = regnum();
			while(alpha(*Optr))Optr++;
			if(src==ERR){
				error("Register Name Required");
				emit(0);
				return;
				}
			if(*Optr++ != ','){
				error("Missing comma");
				emit(0);
				return;
				}
			Optr = skip_white(Optr);
			dst = regnum();
			while(alpha(*Optr))Optr++;
			if(dst==ERR){
				error("Two Register Names Required");
				emit(0);
				return;
				}
			if( src==RPCR || dst==RPCR){
				error("PCR illegal here");
				emit(0);
				return;
				}
			if( src >=8 && dst <=5 ){
				error("Register Size Mismatch");
				src = dst = 0;
				}
			else if(opcode == 30 && src <=5 && dst >=8) {
				error("Register Size Mismatch");
				src = dst = 0;
				}
			emit( (src<<4)+dst );
			return;
		case INDEXED:                   /* indexed addressing only */
			if( *Optr == '#'){
				Optr++;         /* kludge city */
				amode = IND;
				}
			if( amode != IND ){
				error("Indexed Addressing Required");
				return;
				}
			do_indexed(opcode);
			return;
		case RLIST:                     /* pushes and pulls */
			if( regnum() == ERR ) {
				error("Register List Required");
				return;
				}
			emit(opcode);
			pbyte = 0;
			do{
                                Optr = skip_white(Optr);
				j = regnum();
				if( j == ERR || j==RPCR)
					error("Illegal Register Name");
				else if(j==RS && (opcode==52))
					error("Can't Push S on S");
				else if(j==RU && (opcode==54))
					error("Can't Push U on U");
				else if(j==RS && (opcode==53))
					error("Can't Pull S from S");
				else if(j==RU && (opcode==55))
					error("Can't Pull U from U");
				else{
					pbyte |= regs[j];
					Cycles += rcycl[j];
					}
				while(alpha(*Optr)) Optr++;
			}while( *Optr++ == ',' );
			emit(lobyte(pbyte));
			return;
		case P2NOIMM:
			if( amode == IMMED )
				error("Immediate Addressing Illegal");
			else{
				emit(PAGE2);
				do_gen(opcode,amode);
				}
			return;
		case LONGIMM:
			if( amode == IMMED ){
				emit(opcode);
				Optr++;
				eval();
				eword(Result);
				}
			else
				do_gen(opcode,amode);
			return;
		case GRP2:
			if( amode == IND ){
				do_indexed(opcode+0x60);
				return;
				}
			else if( amode == INDIR){
				Optr++;
				emit(opcode + 0x60);
				emit(IPBYTE);
				eval();
				eword(Result);
				Cycles += 7;
				if(*Optr == ']'){
					Optr++;
					return;
					}
				error("Missing ']'");
				return;
				}
			eval();
			if(Force_word){
				emit(opcode+0x70);
				eword(Result);
				Cycles += 3;
				return;
				}
			if(Force_byte){
				emit(opcode);
				emit(lobyte(Result));
				Cycles += 2;
				return;
				}
			if(Result>=0 && Result <=0xFF){
				emit(opcode);
				emit(lobyte(Result));
				Cycles += 2;
				return;
				}
			else {
				emit(opcode+0x70);
				eword(Result);
				Cycles += 3;
				return;
				}
		case SYS:                       /* system call */
			emit(SWI);
			eval();
			emit(lobyte(Result));
			return;
		default:
			fatal("Error in Mnemonic table");
		}
}


/*
 *      do_gen --- process general addressing mode stuff
 */
void
do_gen(int op, int mode)
{
	if( mode == IMMED){
		Optr++;
		emit(op);
		eval();
		emit(lobyte(Result));
		return;
		}
	else if( mode == IND ){
		do_indexed(op+0x20);
		return;
		}
	else if( mode == INDIR){
		Optr++;
		emit(op+0x20);
		emit(IPBYTE);
		eval();
		eword(Result);
		Cycles += 7;
		if(*Optr == ']'){
			Optr++;
			return;
			}
		error("Missing ']'");
		return;
		}
	else if( mode == OTHER){
		eval();
		if(Force_word){
			emit(op+0x30);
			eword(Result);
			Cycles += 3;
			return;
			}
		if(Force_byte){
			emit(op+0x10);
			emit(lobyte(Result));
			Cycles += 2;
			return;
			}
		if(Result>=0 && Result <=0xFF){
			emit(op+0x10);
			emit(lobyte(Result));
			Cycles += 2;
			return;
			}
		else {
			emit(op+0x30);
			eword(Result);
			Cycles += 3;
			return;
			}
		}
	else {
		error("Unknown Addressing Mode");
		return;
		}
}

/*
 *      do_indexed --- handle all weird stuff for indexed addressing
 */
void
do_indexed(int op)
{
	int     pbyte;
	int     j,k;
	int     predec,pstinc;
	int	byte,word;
	char	*p;

	Cycles += 2;    /* indexed is always 2+ base cycle count */
	predec=0;
	pstinc=0;
	pbyte=128;
	byte=0;
	word=0;
	emit(op);
	j=regnum();
	if(j==RA){
		Cycles++;
		abd_index(pbyte+6);
		return;
		}
	if(j==RB){
		Cycles++;
		abd_index(pbyte+5);
		return;
		}
	if(j==RD){
		Cycles += 4;
		abd_index(pbyte+11);
		return;
		}
	if(*Optr=='<') {
		byte=1;
		Optr++;
		}
	else if(*Optr=='>') {
		word=1;
		Optr++;
		}
	if(*Optr=='['){
		pbyte |= 0x10;    /* set indirect bit */
		Optr++;
		if( !any((char)']',Optr))
			error("Missing ']'");
		Cycles += 3;    /* indirection takes this much longer */
		}
	Force_word = NO;	/* setup in case eval() not called */
	Force_byte = NO;
	Result = 0;
	if( *Optr != ',' ) {		/* decide whether to call eval() */
		p = Optr;
		while( *p == '-' ) p++;
		if( regnum() == ERR ) {
			eval();
			if( *Optr == ',' ) Optr++;
			else error("Missing comma");
			}
		}
	else Optr++;
	if( byte ) Force_byte++;
	if( word ) Force_word++;
	if( Force_byte && Force_word ) {
		error("Can't force both byte and word");
		Force_byte = 0;
		}
	while(*Optr=='-'){
		predec++;
		Optr++;
		}
	j=regnum();
	while( alpha(*Optr) )Optr++;
	while(*Optr=='+'){
		pstinc++;
		Optr++;
		}
	if(j==RPC || j==RPCR){
		if( pstinc || predec ){
			error("Auto Inc/Dec Illegal on PC");
			return;
			}
		if(j==RPC){
			if(Force_word){
				emit(pbyte+13);
				eword(Result);
				Cycles += 5;
				return;
				}
			if(Force_byte){
				emit(pbyte+12);
				emit(lobyte(Result));
				Cycles++;
				return;
				}
			if(Result>=-128 && Result <=127){
				emit(pbyte+12);
				emit(lobyte(Result));
				Cycles++;
				return;
				}
			else {
				emit(pbyte+13);
				eword(Result);
				Cycles += 5;
				return;
				}
			}
		/* PCR addressing */
		if(Force_word){
			emit(pbyte+13);
			eword(Result-(Pc+2));
			Cycles += 5;
			return;
			}
		if(Force_byte){
			emit(pbyte+12);
			emit(lobyte(Result-(Pc+1)));
			Cycles++;
			return;
			}
		k=Result-(Pc+2);
		if( k >= -128 && k <= 127){
			emit(pbyte+12);
			emit(lobyte(Result-(Pc+1)));
			Cycles++;
			return;
			}
		else{
			emit(pbyte+13);
			eword(Result-(Pc+2));
			Cycles += 5;
			return;
			}
		}
	if(predec || pstinc){
		if(Result != 0){
			error("Offset must be Zero");
			return;
			}
		if(predec>2 || pstinc>2){
			error("Auto Inc/Dec by 1 or 2 only");
			return;
			}
		if((predec==1 && (pbyte&0x10) != 0) ||
		   (pstinc==1 && (pbyte&0x10) != 0)){
			error("No Auto Inc/Dec by 1 for Indirect");
			return;
			}
		if(predec && pstinc){
			error("Can't do both predecrement & postincrement!");
			return;
			}
		if(predec)
			pbyte += predec+1;
		else if(pstinc)
			pbyte += pstinc-1;
		pbyte += rtype(j);
		emit(pbyte);
		Cycles += 1 + predec + pstinc;
		return;
		}
	pbyte += rtype(j);
	if(Force_word){
		emit(pbyte+0x09);
		eword(Result);
		Cycles += 4;
		return;
		}
	if(Force_byte){
		emit(pbyte+0x08);
		emit(lobyte(Result));
		Cycles++;
		return;
		}
	if(Result==0){
		emit(pbyte+0x04);
		return;
		}
	if((Result >= -16) && (Result <= 15) && ((pbyte&16)==0)){
		pbyte &= 127;
		pbyte += Result&31;
		emit(pbyte);
		Cycles++;
		return;
		}
	if(Result >= -128 && Result <= 127){
		emit(pbyte+0x08);
		emit(lobyte(Result));
		Cycles++;
		return;
		}
	emit(pbyte+0x09);
	eword(Result);
	Cycles += 4;
	return;
}


/*
 *      abd_index --- a,b or d indexed
 */

abd_index(int pbyte)
{
	int     k;

	Optr++;
	if( *Optr++ != ',' )
		error("Missing comma");
	k=regnum();
	pbyte += rtype(k);
	emit(pbyte);
	return;
}

/*
 *      rtype --- return register type in post-byte format
 */
rtype(int r)
{
	switch(r){
	case RX:        return(0x00);
	case RY:        return(0x20);
	case RU:        return(0x40);
	case RS:        return(0x60);
		}
	error("Illegal Register for Indexed");
	return(0);
}

/*
 *      set_mode --- determine addressing mode from operand field
 */
set_mode(void)
{
	register char *p;
	int k;

	if( *Operand == '#' )
		return(IMMED);          /* immediate addressing */
	p = Operand;
	while( !delim(*p) ) {           /* any , before break */
		if( *p == ',')
			return(IND);    /* indexed addressing */
		p++;
		}
	p = Operand;
	if( *p == '>' || *p == '<' ) p++;
	if( *p == '[' ) p++;
	while( *p == '-' ) p++;
	k = regnum();
	if( k==RS || k==RU || k==RX || k==RY || k==RPC || k==RPCR )
		 return(IND);  /* indexed addressing */
	if( *Operand == '[')
		return(INDIR);          /* indirect addressing */
	return(OTHER);                  /* NOTA */
}

/*
 *      regnum --- return register number of *Optr
 */
regnum(void)
{
	if( head(Optr,"D" ))return(RD);
	if( head(Optr,"d" ))return(RD);
	if( head(Optr,"X" ))return(RX);
	if( head(Optr,"x" ))return(RX);
	if( head(Optr,"Y" ))return(RY);
	if( head(Optr,"y" ))return(RY);
	if( head(Optr,"U" ))return(RU);
	if( head(Optr,"u" ))return(RU);
	if( head(Optr,"S" ))return(RS);
	if( head(Optr,"s" ))return(RS);
	if( head(Optr,"PC" ))return(RPC);
	if( head(Optr,"pc" ))return(RPC);
	if( head(Optr,"PCR" ))return(RPCR);
	if( head(Optr,"pcr" ))return(RPCR);
	if( head(Optr,"A" ))return(RA);
	if( head(Optr,"a" ))return(RA);
	if( head(Optr,"B" ))return(RB);
	if( head(Optr,"b" ))return(RB);
	if( head(Optr,"CC" ))return(RCC);
	if( head(Optr,"cc" ))return(RCC);
	if( head(Optr,"DP" ))return(RDP);
	if( head(Optr,"dp" ))return(RDP);
	return(ERR);
}
