/*  Copyright (C) 1987- Leon Bottou
 *
 *  This is free documentation; you can redistribute it and/or
 *  modify it under the terms of the GNU General Public License as
 *  published by the Free Software Foundation; either version 2 of
 *  the License, or (at your option) any later version.
 *
 *  The GNU General Public License's references to "object code"
 *  and "executables" are to be interpreted as the output of any
 *  document formatting or typesetting system, including
 *  intermediate and printed output.
 *
 *  This manual is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public
 *  License along with this manual. Otherwise check the web site
 *  of the Free Software Foundation at http://www.fsf.org.
 */
/* Z80 */

#include "cpu.h"

static int offset, offsetflag, indexflag;
static int arg, arg8flag, arg16flag;

#define isreg8(n)  (n>=0 && n<8)  /* registres a 8 bits  */
#define isreg16(n) (n>=8 && n<16) /* registres a 16 bits */
#define isspecialreg(b) (n>=16)   /* registres speciaux  */

#define IX 0xdd
#define IY 0xfd
#define HL 0xff

#define ACCUMULATOR  7
#define MREGISTER    6
#define HLREGISTER  10
#define MEMORY      -1
#define IMDATA      -2
#define SPBC        -4
#define SPDE        -5

#define CLRALL  indexflag=offsetflag=arg8flag=arg16flag=0

static char* needreg8 = "operates with reg8 or (HL),(IX+nn),(IY+nn)";
static char* needacc  = "operates on A only";
static char* badop    = "illegal addressing mode";


/*  GETREG prend un registre ou un mode d'addressage  */

static char* indexed[] =
{ "(_IX_)", "(_IY_)", "(_IX_+_?_)", "(_IY_+_?_)", "(_IX_-_?_)", "(_IY_-_?_)" };

static int getreg(char* oper)
{
  register struct result* r;
  register int i;
  char* oper2;

  for (i = 0; i < 6; i++)
  {
    if (filter(oper, indexed[i], &oper2))
    {
      offset = 0;

      if (i > 1)
      {
        r = parse(oper2);
        checktype(r, L_ABSOLUTE);
        offset = r->value;

        if (i > 3)
        {
          offset = -offset;
        }
      }

      if (indexflag || offsetflag)
      {
        crasm_error(badop);
      }

      offsetflag = TRUE;
      indexflag = i & 1 ? IY : IX;
      return MREGISTER;
    }
  }

  if (filter(oper, "(_HL_)"))
  {
    if (indexflag && indexflag != HL)
    {
      crasm_error(badop);
    }

    indexflag = HL;
    return MREGISTER;
  }

  if (filter(oper, "(_BC_)"))
  {
    return SPBC;
  }

  if (filter(oper, "(_DE_)"))
  {
    return SPDE;
  }

  if (filter(oper, "(_?_)", &oper2))
  {
    r = parse(oper2);
    checktype(r, L_ABSOLUTE);

    if (arg8flag || arg16flag)
    {
      crasm_error(badop);
    }

    arg16flag = TRUE;
    arg = r->value;
    return MEMORY;
  }

  r = parse(oper);

  if (r->type == L_ABSOLUTE)
  {
    if (arg8flag || arg16flag)
    {
      crasm_error(badop);
    }

    arg16flag = TRUE;
    arg = r->value;
    return IMDATA;
  }

  checktype(r, L_REGS);

  if (r->value != (1 << r->modifier))
  {
    crasm_error("illegal register list");
  }

  if (r->modifier == 12 || r->modifier == 13 || r->modifier == HLREGISTER)
  {
    switch (r->modifier)
    {
    case 12:
      i = IX;
      break;

    case 13:
      i = IY;
      break;

    default:
      i = HL;
      break;
    }

    if (indexflag && indexflag != i)
    {
      crasm_error(badop);
    }

    indexflag = i;
    return HLREGISTER;
  }

  return r->modifier;
}


static int docode(int code)
{
  if (indexflag == IX || indexflag == IY || offsetflag)
  {
    insert8(indexflag);
  }

  if (code & 0xff00)
  {
    insert8(code >> 8);
  }
  else
  {
    insert8(code);
  }

  if (offsetflag)
  {
    insert8(offset);

    if (offset != (byte) offset)
    {
      crasm_warning("offset overflow");
    }
  }

  if (code & 0xff00)
  {
    insert8(code);
  }

  if (arg8flag)
  {
    insert8(arg);

    if (arg & 0xff00)
    {
      crasm_warning("argument overflow");
    }
  }
  else if (arg16flag)
  {
    insert16(arg);
  }

  return 0;
}

/* relative branchs */
static int relbranch(int code, char* label, char* mnemo, char* oper)
{
  byte d;
  int  dd;
  extern unsigned long pc;
  struct result* r;

  r = parse(oper);          /* operand search    */
  checktype(r, L_ABSOLUTE); /* is it a number?   */
  dd = r->value - pc - 2;   /* displacement calc */
  d = dd;                   /* -> signed byte    */
  insert8(code);            /* generate code     */
  insert8(dd);

  if (d != dd) /* test operand after */
  {
    crasm_error("too long branch");  /* if an error occurs during */
  }

  return 0; /* the first pass, branch is */
}           /* always too bytes long     */


/* IMPORTANT:                                                 *
*  You had better generate code before signaling errors about *
*  the operand values. So the first pass calculates right     *
*  forward references even when a ghost error occurs.         */

/* simple instruction */
static int simple(int code, char* label, char* mnemo, char* oper)
{
  if (code & 0xff00)
  {
    insert8(code >> 8);
  }

  insert8(code);

  if (oper) /* no operand! */
  {
    crasm_error("no operands allowed");
  }

  return 0;
}


static int im(int code, char* label, char* mnemo, char* oper)
{
  register struct result* r;
  r = parse(oper);
  checktype(r, L_ABSOLUTE);

  switch (r->value)
  {
  case 0:
    insert16(0x46ed);
    break;

  case 1:
    insert16(0x56ed);
    break;

  case 2:
    insert16(0x5eed);
    break;

  default:
    insert16(0);
    crasm_error("Bad interruption mode");
  }

  return 0;
}


static int rst(int code, char* label, char* mnemo, char* oper)
{
  register struct result* r;
  r = parse(oper);
  checktype(r, L_ABSOLUTE);

  if (!(r->value & 0xfff8))
  {
    r->value <<= 3;
  }

  insert8(0xc7 | r->value);

  if (r->value & 0xffc7)
  {
    crasm_error("Bad RST vector");
  }

  return 0;
}


static char* condcodes[] = { "NZ", "Z", "NC", "C", "PO", "PE", "P", "M", NULL };


static int searchcond(int maxcond, char* oper, char** pres)
{
  register char** q;
  char* ccode;

  if (filter(oper, "?_,_?", &ccode, pres))
  {
    for (q = condcodes; maxcond--; q++)
    {
      if (filter(ccode, *q))
      {
        return q - condcodes;
      }
    }

    crasm_error("Bad condition code");
  }

  *pres = oper;
  return -1;
}


static int jr(int code, char* label, char* mnemo, char* oper)
{
  char* operand2;
  code = 0x20 + (searchcond(4, oper, &operand2) << 3);
  return relbranch(code, label, mnemo, operand2);
}


static int ret(int code, char* label, char* mnemo, char* oper)
{
  register char** q;

  if (!oper || !*oper)
  {
    insert8(0xc9);
  }
  else
  {
    for (q = condcodes; *q; q++)
    {
      if (filter(oper, *q))
      {
        insert8(0xc0 + ((q - condcodes) << 3));
        return 0;
      }
    }

    crasm_error("Bad condition code");
  }

  return 0;
}


static int call(int code, char* label, char* mnemo, char* oper)
{
  char* operand2;
  register int cond;
  register struct result* r;

  cond = searchcond(8, oper, &operand2) << 3;
  r = parse(operand2);
  checktype(r, L_ABSOLUTE);

  if (cond >= 0)
  {
    insert8(0xc4 + cond);
  }
  else
  {
    insert8(0xcd);
  }

  insert16(r->value);
  return 0;
}


static int jp(int code, char* label, char* mnemo, char* oper)
{
  char* operand2;
  register int cond;
  register struct result* r;

  CLRALL;

  if (filter(oper, "(_?_)", &operand2))
  {
    if (getreg(operand2) == HLREGISTER)
    {
      docode(0xe9);
    }
    else
    {
      crasm_error("illegal indirection");
    }
  }
  else
  {
    cond = searchcond(8, oper, &operand2) << 3;
    r = parse(operand2);
    checktype(r, L_ABSOLUTE);

    if (cond >= 0)
    {
      insert8(0xc2 + cond);
    }
    else
    {
      insert8(0xc3);
    }

    insert16(r->value);
  }

  return 0;
}


static int ex(int code, char* label, char* mnemo, char* oper)
{
  char* oper2;

  CLRALL;

  if (filter(oper, "DE_,_HL") || filter(oper, "HL_,_DE"))
  {
    return docode(0xeb);
  }

  if (filter(oper, "(_SP_)_,_?", &oper2) ||
      filter(oper, "?_,_(_SP_)", &oper2))
  {
    if (getreg(oper2) == HLREGISTER)
    {
      return docode(0xe3);
    }
  }

  /* EX AF,AF'
     ' is a special filter char. So, if there is one in OPER,
     then OPER is OPER+SPACES+REMARK... We have to manage with  */

  if (filter(oper, "AF_,_AF'_?", &oper2) ||
      filter(oper, "AF'_,_AF_?", &oper2))
  {
    if (oper2 == NULL || !*oper2 || *oper2 == ';')
    {
      return docode(0x08);
    }
  }

  crasm_error("Illegal EX instruction");
  return 0;
}


static int inout(int code, char* label, char* mnemo, char* oper)
{
  char* op1;
  char* op2;
  register int reg;

  CLRALL;

  if ((!(code & 8)) ?
      filter(oper, "(?)_,_?", &op1, &op2)
      : filter(oper, "?_,_(?)", &op2, &op1))
  {
    reg = getreg(op1);

    if (reg == IMDATA)
    {
      reg = getreg(op2);

      if (reg != ACCUMULATOR)
      {
        crasm_error(needacc);
      }

      arg8flag = TRUE;
      docode(code);
    }
    else if (reg == 1)
    {
      reg = getreg(op2);

      if (!isreg8(reg) || reg == 6)
      {
        crasm_error("operates on reg8 only");
      }

      reg <<= 3;

      if (!(code & 8))
      {
        reg += 1;
      }

      docode(0xed40 + reg);
    }
    else
    {
      crasm_error("port is (C) or (nn)");
    }
  }
  else
  {
    crasm_error(badop);
  }

  return 0;
}


static int bitop(int code, char* label, char* mnemo, char* oper)
{
  register struct result* r;
  char* oper1;
  char* oper2;
  register int bit;
  register int reg;

  CLRALL;

  if (!filter(oper, "?_,_?", &oper1, &oper2))
  {
    crasm_error("two operands required");
  }

  r = parse(oper1);
  checktype(r, L_ABSOLUTE);
  bit = r->value;

  if (bit < 0 || bit > 7)
  {
    crasm_error("illegal bit number");
  }

  reg = getreg(oper2);

  if (! isreg8(reg))
  {
    crasm_error(needreg8);
  }

  docode(code + reg + (bit << 3));
  return 0;
}


static int onreg8(int code, char* label, char* mnemo, char* oper)
{
  register int reg;

  CLRALL;
  reg = getreg(oper);

  if (!isreg8(reg))
  {
    crasm_error(needreg8);
  }

  return docode(code + reg);
}


static int logical(int code, char* label, char* mnemo, char* oper)
{
  register int reg;
  char* op1;
  char* op2;

  CLRALL;

  if (filter(oper, "?_,_?", &op1, &op2))
  {
    if (getreg(op1) != ACCUMULATOR)
    {
      crasm_error(needacc);
    }

    oper = op2;
    crasm_warning("One operand only. but i understand!");
  }

  reg = getreg(oper);

  if (reg == IMDATA)
  {
    reg = 0x46;
    arg8flag = TRUE;
  }

  return docode(code + reg);
}


static int stackop(int code, char* label, char* mnemo, char* oper)
{
  register int reg;

  CLRALL;
  reg = getreg(oper);

  if (reg == 19)
  {
    reg = 11;
  }
  else if (reg == 11)
  {
    reg = 19;
  }

  if (!isreg16(reg))
  {
    crasm_error("Operates with AF,BC,DE,HL,IX,IY");
  }

  return docode(code + (reg << 4));
}


static int arith(int code, char* label, char* mnemo, char* oper)
{
  register int reg;
  char* op1;
  char* op2;

  CLRALL;

  if (!filter(oper, "?_,_?", &op1, &op2))
  {
    crasm_error(badop);
  }

  reg = getreg(op1);

  if (reg == ACCUMULATOR)
  {
    logical(code, label, mnemo, op2);
  }
  else if (reg == HLREGISTER)
  {
    if (indexflag != HL && code != 0x80)
    {
      crasm_error("illegal index operation");
    }

    switch (code)
    {
    case 0x80:
      code = 0x09 - 0x80;
      break;

    case 0x88:
      code = 0xed4a - 0x80;
      break;

    case 0x98:
      code = 0xed42 - 0x80;
      break;
    }

    reg = getreg(op2);

    if (!isreg16(reg))
    {
      crasm_error(badop);
    }

    docode(code + (reg << 4));
  }
  else
  {
    crasm_error(badop);
  }

  return 0;
}


static int incr(int code, char* label, char* mnemo, char* oper)
{
  register int reg;

  CLRALL;
  reg = getreg(oper);

  if (isreg8(reg))
  {
    code >>= 8;
    docode((reg << 3) + code);
  }
  else if (isreg16(reg))
  {
    code &= 0xff;
    reg -= 8;
    docode(code + (reg << 4));
  }
  else
  {
    crasm_error(badop);
  }

  return 0;
}


static int ld(int code, char* label, char* mnemo, char* oper)
{
  register int reg1;
  register int reg2;
  char* op1;
  char* op2;

  if (!filter(oper, "?_,_?", &op1, &op2))
  {
    crasm_error(badop);
  }

  CLRALL;
  reg1 = getreg(op1);
  reg2 = getreg(op2);

  if (isreg8(reg1))
  {
    if (isreg8(reg2))
    {
      if (reg1 == reg2 && reg1 == MREGISTER)
      {
        crasm_error(badop);
      }

      return docode(0x40 + (reg1 << 3) + reg2);
    }

    if (reg2 == IMDATA)
    {
      arg8flag = TRUE;
      return docode(0x06 + (reg1 << 3));
    }

    if (reg1 == ACCUMULATOR)
    {
      if (reg2 == MEMORY)
      {
        return docode(0x3a);
      }

      if (reg2 == SPBC)
      {
        return docode(0x0a);
      }

      if (reg2 == SPDE)
      {
        return docode(0x1a);
      }

      if (reg2 == 17)
      {
        return docode(0xed57);
      }

      if (reg2 == 18)
      {
        return docode(0xed5f);
      }
    }
  }

  if (isreg16(reg1))
  {
    if (reg2 == IMDATA)
    {
      return docode(0x01 + ((reg1 - 8) << 4));
    }

    if (reg2 == MEMORY)
    {
      if (reg1 == HLREGISTER)
      {
        return docode(0x2a);
      }
      else
      {
        return docode(0xed4b + ((reg1 - 8) << 4));
      }
    }

    if (reg2 == HLREGISTER && reg1 == 11)
    {
      return docode(0xf9);
    }
  }

  if (reg1 == MEMORY)
  {
    if (reg2 == ACCUMULATOR)
    {
      return docode(0x32);
    }

    if (reg2 == HLREGISTER)
    {
      return docode(0x22);
    }

    if (isreg16(reg2))
    {
      return docode(0xed43 + ((reg2 - 8) << 4));
    }
  }

  if (reg2 == ACCUMULATOR)
  {
    if (reg1 == SPBC)
    {
      return docode(0x02);
    }

    if (reg1 == SPDE)
    {
      return docode(0x12);
    }

    if (reg1 == 17)
    {
      return docode(0xed47);
    }

    if (reg1 == 18)
    {
      return docode(0xed4f);
    }
  }

  crasm_error(badop);
  return 0;
}


startmnemos(mz80) /* Z80 mnemonics */

regs("a", 7) /* registers */
regs("b", 0)
regs("c", 1)
regs("d", 2)
regs("e", 3)
regs("h", 4)
regs("l", 5)

regs("bc",  8)
regs("de",  9)
regs("hl",  10)
regs("sp",  11)
regs("ix",  12)
regs("iy",  13)

regs("f",  16)
regs("i",  17)
regs("r",  18)
regs("af", 19)

mnemo("ccf",   simple,   0x3f)    /* instructions without oper */
mnemo("cpd",   simple,   0xeda9)
mnemo("cpdr",  simple,   0xed89)
mnemo("cpi",   simple,   0xeda1)
mnemo("cpir",  simple,   0xedb1)
mnemo("cpl",   simple,   0x2f)
mnemo("daa",   simple,   0x27)
mnemo("exx",   simple,   0xd9)
mnemo("halt",  simple,   0x76)
mnemo("ind",   simple,   0xedaa)
mnemo("indr",  simple,   0xedba)
mnemo("ini",   simple,   0xeda2)
mnemo("inir",  simple,   0xedb2)
mnemo("di",    simple,   0xf3)
mnemo("ei",    simple,   0xfb)
mnemo("ldd",   simple,   0xeda8)
mnemo("lddr",  simple,   0xedb8)
mnemo("ldi",   simple,   0xeda0)
mnemo("ldir",  simple,   0xedb0)
mnemo("neg",   simple,   0xed44)
mnemo("nop",   simple,   0x00)
mnemo("otdr",  simple,   0xedbb)
mnemo("otir",  simple,   0xedb3)
mnemo("outd",  simple,   0xedab)
mnemo("outi",  simple,   0xeda3)
mnemo("reti",  simple,   0xed4d)
mnemo("retn",  simple,   0xed45)
mnemo("rla",   simple,   0x17)
mnemo("rlca",  simple,   0x07)
mnemo("rld",   simple,   0xed6f)
mnemo("rra",   simple,   0x1f)
mnemo("rrca",  simple,   0x0f)
mnemo("rrd",   simple,   0xed67)
mnemo("scf",   simple,   0x37)

mnemo("im",    im,       0)       /* Specials */
mnemo("rst",   rst,      0)
mnemo("djnz",  relbranch,0x10)
mnemo("jr",    jr,       0)
mnemo("jp",    jp,       0)
mnemo("call",  call,     0)
mnemo("ret",   ret,      0)
mnemo("ex",    ex,       0)
mnemo("in",    inout,    0xdb)
mnemo("out",   inout,    0xd3)


mnemo("bit",   bitop,    0xcb40)  /* Quasi regulars */
mnemo("set",   bitop,    0xcbc0)
mnemo("res",   bitop,    0xcb80)

mnemo("sla",   onreg8,   0xcb20)
mnemo("sra",   onreg8,   0xcb28)
mnemo("srl",   onreg8,   0xcb38)
mnemo("rl",    onreg8,   0xcb10)
mnemo("rr",    onreg8,   0xcb18)
mnemo("rlc",   onreg8,   0xcb00)
mnemo("rrc",   onreg8,   0xcb08)

mnemo("or",    logical,  0xb0)
mnemo("and",   logical,  0xa0)
mnemo("xor",   logical,  0xa8)
mnemo("sub",   logical,  0x90)
mnemo("cp",    logical,  0xb8)

mnemo("push",  stackop,  0xc5 - 0x80)
mnemo("pop",   stackop,  0xc1 - 0x80)

mnemo("adc",   arith,    0x88)   /* no index */
mnemo("sbc",   arith,    0x98)   /* no index */
mnemo("add",   arith,    0x80)   /* index ok */

mnemo("inc",   incr,   0x0403)   /*  (*8)  */
mnemo("dec",   incr,   0x050b)

mnemo("ld",    ld,   0)        /* heavy magic !! */
endmnemos


void initz80(int code)
{
  setflag(F_CODE_HEX);  /* Intel hex by default */
  setflag(F_ADDR16);    /* 16 bits address      */
  setflag(F_LOHI);      /* LSB first            */
  clrflag(F_RELATIF);   /* no translatable code */
  bindvocabulary(mz80); /* add Z80 mnemos       */
}
