Zapple-II/z80as/as1.c

687 lines
12 KiB
C

/*
* Z-80 assembler.
* Assemble one line of input.
* Knows all the dirt.
*/
#include "as.h"
#define OPDJNZ 0x10 /* Opcode: djnz */
#define OPADD 0x80 /* Opcode: add */
#define OPDAD 0x09 /* Opcode: dad */
#define OPADC 0x88 /* Opcode: adc */
#define OPADCW 0x4A /* Opcode: adc hl */
#define OPSBCW 0x42 /* Opcode: sbc hl */
#define OPSUBI 0xC6 /* Opcode: make immediate */
#define OPXCHG 0xEB /* Opcode: xchg */
#define OPXTHL 0xE3 /* Opcode: xthl */
#define OPEXAF 0x08 /* Opcode: ex af,af' */
#define OPRST 0xC7 /* Opcode: rst 0 */
#define OPINCRP 0x03 /* Opcode: inc rp */
#define OPDECRP 0x0B /* Opcode: dec rp */
#define OPINC 0x04 /* Opcode: inc */
#define OPIIN 0x40 /* Opcode: indirect in */
#define OPIOUT 0x41 /* Opcode: indirect out */
#define OPIN 0xDB /* Opcode: in */
#define OPIM 0x46 /* Opcode: im */
#define OPPCHL 0xE9 /* Opcode: jp (hl) */
#define OPJP 0xC3 /* Opcode: jp cc base */
#define OPJR 0x20 /* Opcode: jr cc base */
#define OPRET 0xC0 /* Opcode: ret cc base */
/*
* Assemble one line.
* The line in in "ib", the "ip"
* scans along it. The code is written
* right out, and also stashed in the
* "cb" for the listing.
*/
asmline()
{
register SYM *sp;
register int c;
register int opcode;
register int disp;
register int reg;
register int srcreg;
register int cc;
register VALUE value;
register int delim;
register SYM *sp1;
char id[NCPS];
char id1[NCPS];
ADDR a1;
ADDR a2;
laddr = dot;
lmode = SLIST;
loop:
if ((c=getnb())=='\n' || c==';')
return;
if (isalpha(c) == 0)
qerr();
getid(id, c);
if ((c=getnb()) == ':') {
sp = lookup(id, uhash, 1);
if (pass == 0) {
if ((sp->s_type&TMMODE) != TNEW
&& (sp->s_type&TMASG) == 0)
sp->s_type |= TMMDF;
sp->s_type &= ~TMMODE;
sp->s_type |= TUSER;
sp->s_value = dot;
} else {
if ((sp->s_type&TMMDF) != 0)
err('m');
if (sp->s_value != dot)
err('p');
}
lmode = ALIST;
goto loop;
}
/*
* If the first token is an
* id and not an operation code,
* assume that it is the name in front
* of an "equ" assembler directive.
*/
if ((sp=lookup(id, phash, 0)) == NULL) {
getid(id1, c);
if ((sp1=lookup(id1, phash, 0)) == NULL
|| (sp1->s_type&TMMODE) != TEQU) {
err('o');
return;
}
getaddr(&a1);
istuser(&a1);
sp = lookup(id, uhash, 1);
if ((sp->s_type&TMMODE) != TNEW
&& (sp->s_type&TMASG) == 0)
err('m');
sp->s_type &= ~TMMODE;
sp->s_type |= TUSER|TMASG;
sp->s_value = a1.a_value;
laddr = a1.a_value;
lmode = ALIST;
goto loop;
}
unget(c);
lmode = BLIST;
opcode = sp->s_value;
switch (sp->s_type&TMMODE) {
case TORG:
getaddr(&a1);
istuser(&a1);
lmode = ALIST;
laddr = dot = a1.a_value;
break;
case TDEFB:
do {
getaddr(&a1);
istuser(&a1);
outab(a1.a_value);
} while ((c=getnb()) == ',');
unget(c);
break;
case TDEFW:
lmode = WLIST;
do {
getaddr(&a1);
istuser(&a1);
outaw(a1.a_value);
} while ((c=getnb()) == ',');
unget(c);
break;
case TDEFM:
if ((delim=getnb()) == '\n')
qerr();
while ((c=get()) != delim) {
if (c == '\n')
qerr();
outab(c);
}
break;
case TDEFS:
laddr = dot;
lmode = ALIST;
getaddr(&a1);
istuser(&a1);
dot += a1.a_value;
break;
case TNOP:
if ((opcode&0xFF00) != 0)
outab(opcode >> 8);
outab(opcode);
break;
case TRST:
getaddr(&a1);
istuser(&a1);
if (a1.a_value < 8) {
outab(OPRST|(a1.a_value<<3));
break;
}
aerr();
break;
case TREL:
getaddr(&a1);
if ((cc=ccfetch(&a1)) >= 0) {
if (opcode==OPDJNZ || cc>=CPO)
aerr();
opcode = OPJR | (cc<<3);
comma();
getaddr(&a1);
}
istuser(&a1);
disp = a1.a_value-dot-2;
if (disp<-128 || disp>127)
aerr();
outab(opcode);
outab(disp);
break;
case TRET:
unget(c = getnb());
if (c!='\n' && c!=';') {
getaddr(&a1);
if ((cc=ccfetch(&a1)) < 0)
aerr();
opcode = OPRET | (cc<<3);
}
outab(opcode);
break;
case TJMP:
getaddr(&a1);
if ((cc=ccfetch(&a1)) >= 0) {
opcode = (opcode&0x00C6) | (cc<<3);
comma();
getaddr(&a1);
}
if ((a1.a_type&TMMODE) == TBR) {
reg = a1.a_type&TMREG;
if (reg==M || reg==IX || reg==IY) {
if (opcode != OPJP)
aerr();
outop(OPPCHL, &a1);
break;
}
}
istuser(&a1);
outab(opcode);
outaw(a1.a_value);
break;
case TPUSH:
getaddr(&a1);
if ((a1.a_type&TMMODE) == TWR) {
reg = a1.a_type&TMREG;
switch (reg) {
case IX:
outab(0xDD);
reg = HL;
break;
case IY:
outab(0xFD);
reg = HL;
break;
case AF:
reg = SP;
break;
case SP:
case AFPRIME:
aerr();
}
outab(opcode|(reg<<4));
break;
}
aerr();
break;
case TIM:
getaddr(&a1);
istuser(&a1);
if ((value=a1.a_value) > 2)
aerr();
else if (value != 0)
++value;
outab(0xED);
outab(OPIM|(value<<3));
break;
case TIO:
getaddr(opcode==OPIN ? &a1 : &a2);
comma();
getaddr(opcode==OPIN ? &a2 : &a1);
if (a1.a_type==(TBR|A) && a2.a_type==(TUSER|TMINDIR)) {
outab(opcode);
outab(a2.a_value);
break;
}
if ((a1.a_type&TMMODE)==TBR && a2.a_type==(TBR|TMINDIR|C)) {
reg = a1.a_type&TMREG;
if (reg==M || reg==IX || reg==IY)
aerr();
outab(0xED);
if (opcode == OPIN)
opcode = OPIIN; else
opcode = OPIOUT;
outab(opcode|(reg<<3));
break;
}
aerr();
break;
case TBIT:
getaddr(&a1);
comma();
getaddr(&a2);
if ((a1.a_type&TMMODE) == TUSER
&& a1.a_value < 8
&& (a2.a_type&TMMODE) == TBR) {
if ((reg=a2.a_type&TMREG)==IX || reg==IY)
reg = M;
outop(opcode|(a1.a_value<<3)|reg, &a2);
break;
}
aerr();
break;
case TSHR:
getaddr(&a1);
if ((a1.a_type&TMMODE) == TBR) {
if ((reg=a1.a_type&TMREG)==IX || reg==IY)
reg = M;
outop(opcode|reg, &a1);
break;
}
aerr();
case TINC:
getaddr(&a1);
if ((a1.a_type&TMMODE) == TWR) {
reg = a1.a_type&TMREG;
switch (reg) {
case IX:
outab(0xDD);
reg = HL;
break;
case IY:
outab(0xFD);
reg = HL;
break;
case AF:
case AFPRIME:
aerr();
}
if (opcode == OPINC)
opcode = OPINCRP; else
opcode = OPDECRP;
outab(opcode|(reg<<4));
break;
}
if ((a1.a_type&TMMODE) == TBR) {
if ((reg=a1.a_type&TMREG)==IX || reg==IY)
reg = M;
outop(opcode|(reg<<3), &a1);
break;
}
aerr();
break;
case TEX:
getaddr(&a1);
comma();
getaddr(&a2);
if (a1.a_type==(TWR|AF) && a2.a_type==(TWR|AFPRIME)) {
outab(OPEXAF);
break;
}
if (a1.a_type == (TWR|DE))
opcode = OPXCHG;
else if (a1.a_type == (TWR|TMINDIR|SP))
opcode = OPXTHL;
else
aerr();
if (a2.a_type == (TWR|HL))
outab(opcode);
else if (a2.a_type == (TWR|IX)) {
outab(0xDD);
outab(opcode);
} else if (a2.a_type == (TWR|IY)) {
outab(0xFD);
outab(opcode);
} else
aerr();
break;
case TSUB:
getaddr(&a1);
if (a1.a_type == TUSER) {
outab(opcode | OPSUBI);
outab(a1.a_value);
break;
}
if ((a1.a_type&TMMODE) == TBR) {
if ((reg=a1.a_type&TMREG)==IX || reg==IY)
reg = M;
outop(opcode|reg, &a1);
break;
}
aerr();
break;
case TADD:
getaddr(&a1);
comma();
getaddr(&a2);
if (a1.a_type == (TBR|A)) {
if (a2.a_type == TUSER) {
outab(opcode | OPSUBI);
outab(a2.a_value);
break;
}
if ((a2.a_type&TMMODE) == TBR) {
if ((reg=a2.a_type&TMREG)==IX || reg==IY)
reg = M;
outop(opcode|reg, &a1);
break;
}
}
if ((a1.a_type&TMMODE) == TWR) {
switch(reg = a1.a_type&TMREG) {
case IX:
if (opcode != OPADD)
aerr();
outab(0xDD);
opcode = OPDAD;
srcreg = IX;
break;
case IY:
if (opcode != OPADD)
aerr();
outab(0xFD);
opcode = OPDAD;
srcreg = IY;
break;
case HL:
if (opcode == OPADD)
opcode = OPDAD;
else {
outab(0xED);
if (opcode == OPADC)
opcode = OPADCW;
else
opcode = OPSBCW;
}
srcreg = HL;
break;
default:
aerr();
}
if ((a2.a_type&TMMODE) == TWR) {
reg = a2.a_type&TMREG;
if (reg==BC || reg==DE || reg==SP) {
outab(opcode|(reg<<4));
break;
}
if (reg == srcreg) {
outab(opcode|(HL<<4));
break;
}
}
}
aerr();
break;
case TLD:
asmld();
break;
default:
err('o');
}
goto loop;
}
/*
* Handle the dreaded "ld" instruction,
* with its dozens of forms, formats and special
* encodings. The "getldaddr" routine performs most
* of the special stuff for index registers and for
* indexing. This layer just screens out the many
* cases, and emits the correct bytes.
*/
asmld()
{
int mdst;
int rdst;
int msrc;
int rsrc;
ADDR *indexap;
ADDR dst;
ADDR src;
ADDR *getldaddr();
indexap = NULL;
indexap = getldaddr(&dst, &mdst, &rdst, indexap);
comma();
indexap = getldaddr(&src, &msrc, &rsrc, indexap);
if (dst.a_type == (TBR|A)) {
if (msrc == TSR) {
if (rsrc == I)
outaw(0x57ED); /* ld a,i */
else
outaw(0x5FED); /* ld a,r */
return;
}
if (msrc == (TMINDIR|TUSER)) {
outab(0x3A); /* lda */
outaw(src.a_value);
return;
}
if (msrc == (TMINDIR|TWR)) {
if (rsrc==BC || rsrc==DE) {
outab(0x0A|(rsrc<<4)); /* ldax */
return;
}
}
}
if (src.a_type == (TBR|A)) {
if (mdst == TSR) {
if (rdst == I)
outaw(0x47ED); /* ld i,a */
else
outaw(0x4FED); /* ld r,a */
return;
}
if (mdst == (TMINDIR|TUSER)) {
outab(0x32); /* sta */
outaw(dst.a_value);
return;
}
if (mdst == (TMINDIR|TWR)) {
if (rdst==BC || rdst==DE) {
outab(0x02|(rdst<<4)); /* stax */
return;
}
}
}
if (dst.a_type==(TWR|SP) && msrc==TWR) {
if (rsrc == HL) {
outab(0xF9); /* sphl */
return;
}
}
if (msrc == TUSER) {
if (mdst == TBR) {
outab(0x06|(rdst<<3)); /* mvi */
if (indexap != NULL)
outab(indexap->a_value);
outab(src.a_value);
return;
}
if (mdst == TWR) {
outab(0x01|(rdst<<4)); /* lxi */
outaw(src.a_value);
return;
}
}
if (mdst==TWR && msrc==(TMINDIR|TUSER)) {
if (rdst == HL)
outab(0x2A); /* lhld */
else
outaw(0x4BED|(rdst<<12)); /* ld rp,(ppqq) */
outaw(src.a_value);
return;
}
if (mdst==(TMINDIR|TUSER) && msrc==TWR) {
if (rsrc == HL)
outab(0x22); /* shld */
else
outaw(0x43ED|(rsrc<<12)); /* ld (ppqq),rp */
outaw(dst.a_value);
return;
}
if (mdst==TBR && msrc==TBR && (rdst!=M || rsrc!=M)) {
outab(0x40|(rdst<<3)|rsrc);
if (indexap != NULL)
outab(indexap->a_value);
return;
}
aerr();
}
/*
* Read in addresses for "ld"
* instructions. Split off the mode
* and the register name. Adjust the register
* name to correctly deal with the index registers
* and for indexed addressing modes. Return the address
* pointer "ap" if indexing is required, otherwise just
* pass the "iap" through.
*/
ADDR *
getldaddr(ap, modep, regp, iap)
ADDR *ap;
int *modep;
int *regp;
ADDR *iap;
{
register int mode;
register int reg;
getaddr(ap);
mode = ap->a_type&TMMODE;
reg = ap->a_type&TMREG;
switch (ap->a_type) {
case TBR|IX:
outab(0xDD);
reg = M;
iap = ap;
break;
case TBR|IY:
outab(0xFD);
reg = M;
iap = ap;
break;
case TWR|IX:
outab(0xDD);
reg = HL;
break;
case TWR|IY:
outab(0xFD);
reg == HL;
break;
case TWR|AF:
case TWR|AFPRIME:
aerr();
reg = HL;
}
*modep = mode;
*regp = reg;
return (iap);
}
/*
* Output an opcode, surrounded
* by the index prefix bytes and the
* index displacement byte. Look at
* the address mode to see if the bytes
* are needed.
*/
outop(op, ap)
register int op;
register ADDR *ap;
{
register int needisp;
needisp = 0;
if (ap->a_type == (TBR|IX)) {
outab(0xDD);
needisp = 1;
} else if (ap->a_type == (TBR|IY)) {
outab(0xFD);
needisp = 1;
}
if ((op&0xFF00) != 0) {
outab(op>>8);
if (needisp != 0) {
outab(ap->a_value);
needisp = 0;
}
}
outab(op);
if (needisp != 0)
outab(ap->a_value);
}
/*
* The next character
* in the input must be a comma
* or it is a fatal error.
*/
comma()
{
if (getnb() != ',')
qerr();
}
/*
* Check if the mode of
* an ADDR is TUSER. If not, give
* an error.
*/
istuser(ap)
register ADDR *ap;
{
if ((ap->a_type&TMMODE) != TUSER)
aerr();
}
/*
* Try to interpret an "ADDR"
* as a condition code name. Return
* the condition, or "-1" if it cannot
* be interpreted as a condition. The
* "c" condition is a pain.
*/
ccfetch(ap)
register ADDR *ap;
{
if (ap->a_type == (TBR|C))
return (CC);
if ((ap->a_type&TMMODE) == TCC)
return (ap->a_type&TMREG);
return (-1);
}