/*
 * ACME - a crossassembler for producing 6502/65c02/65816 code.
 * Copyright (C) 1998 Marco Baye
 * Have a look at "acme.c" for further info
 */

/*
 * Mnemonics
 */

#include "mnemo.h"

/*
 * Branches to different checks according to current cpu
 */
static int FN_Mnemo_IsInvalid(int len) {
  switch(hCPU_Now) {

    case HCPU_6502:
    case HCPU_6510:
    case HCPU_65C02:
    case HCPU_65816:
    return(FN_Mnemo65_IsInvalid(len));

    case HCPU_Z80:
    return(FN_Mnemo80_IsInvalid(len));

    default:
    FN_Message(pseNotYet, EERROR);
    return(TRUE);
  }
}

/*
 * 65xx stuff
 */

/*
 * Test whether mnemonic is known by current 65xx cpu
 */
static int FN_Mnemo65_IsInvalid(int len) {
  if(len != 3) return(TRUE);
  MnemoItem.String[0] = StringItem.String[2] | 32;/* to lower case */
  MnemoItem.String[1] = StringItem.String[3] | 32;
  MnemoItem.String[2] = StringItem.String[4] | 32;
  MnemoItem.String[3] = 0;/* terminate (needed by item searcher) */

/* Look out for 6502 mnemonics */
  if(FN_Mnemo65_Check(HTYPE_6502MNEMO))  return(FALSE);
  if(hCPU_Now == HCPU_6502)              return(TRUE);

/* Look out for illegal 6510 mnemonics */
  if(hCPU_Now == HCPU_6510) return(!FN_Mnemo65_Check(HTYPE_6510MNEMO));

/* Look out for 65c02 mnemonics */
  if(FN_Mnemo65_Check(HTYPE_65C02MNEMO)) return(FALSE);
  if(hCPU_Now == HCPU_65C02)             return(TRUE);

/* Look out for 65816 mnemonics */
  return(!FN_Mnemo65_Check(HTYPE_65816MNEMO));
}

/*
 * Work routine
 */
static int FN_Mnemo65_Check(int type) {
  ListItem      *p;
  unsigned char  group,
                 value;

  /* search for list item, length = type + 3 chars */
  p = FN_Struct_Search(&MnemoItem, type, 4, 0);
  if(p == 0) return(FALSE);
  value = p->Data.Bytes.VALUE;/* get byte value */
  group = p->Data.Bytes.GROUP;/* get group */
  switch(group) {

    case  0:
    FN_Mnemo65_00(value);
    break;

    case  1:
    FN_Mnemo65_01(value);
    break;

    case  2:
    FN_Mnemo65_02(value);
    break;

    case  3:
    FN_Mnemo65_03_04(value, TRUE);
    break;

    case  4:
    FN_Mnemo65_03_04(value, FALSE);
    break;

    case  5:
    FN_Mnemo65_05_06_07(value, 5);
    break;

    case  6:
    FN_Mnemo65_05_06_07(value, 6);
    break;

    case  7:
    FN_Mnemo65_05_06_07(value, 7);
    break;

    case  8:
    FN_Mnemo65_08(value);
    break;

    case  9:
    FN_Mnemo65_09(value);
    break;

    case 10:
    FN_Mnemo65_10(value);
    break;

    default:FN_Message(pseUkGroup, ESERIOUS);
  }
  return(TRUE);
}

/*
 * Mnemonics using only implicit addressing.
 */
static void FN_Mnemo65_00(unsigned char opcode) {
  FN_Stream_PutCodeByte(opcode);
  FN_EnsureEOL();
}

/*
 * Mnemonics using only 8bit relative addressing (short branch instructions).
 */
static void FN_Mnemo65_01(unsigned char opcode) {
  Value v = FN_ALU_GetValue_Medium();

  if(ffValue & MVALUE_DEFINED) {
    if(fPCdefined) {
      v -= (PC_CPU + 2);
      if((v > 127) || (v < -128)) FN_Message(pseTooFar, EERROR);
    } else FN_Message(pseNoPC, ESERIOUS);
  }
  FN_Stream_PutCodeByte(opcode);
  FN_Stream_PutByte(v & 255);
  FN_EnsureEOL();
}

/*
 * Mnemonics using only 16bit relative addressing (BRL and PER).
 */
static void FN_Mnemo65_02(unsigned char opcode) {
  Value v = FN_ALU_GetValue_Medium();

  if(ffValue & MVALUE_DEFINED) {
    if(fPCdefined) {
      v -= (PC_CPU + 3);
      if((v > 32767) || (v < -32768)) FN_Message(pseTooFar, EERROR);
    } else FN_Message(pseNoPC, ESERIOUS);
  }
  FN_Stream_PutCodeByte(opcode);
  FN_Stream_PutByte(v & 255);
  FN_Stream_PutByte((v >> 8) & 255);
  FN_EnsureEOL();
}

/*
 * The main accumulator stuff (ADC, AND, CMP, EOR, LDA, ORA, SBC, STA).
 * Group 4 means "without immediate addressing mode" (applies to STA only).
 */
static void FN_Mnemo65_03_04(unsigned char base, int ImmFlag) {
  char  offset = 0;/* offset, added to base to make opcode */
  int   pf     = FN_Mnemo_GetPostfix();/* read length info (skips spaces) */
  Value v      = FN_Mnemo_GetArgument();

  switch(hAM_Now) {

    case HAM_IMM:/* #$ff or #$ffff (depending on accu length) = offset $09 */
    if(ImmFlag) {
      if(hCPU_Now == HCPU_65816) {
        /* 65816 knows 16bit immediate addressing */
        if(pf == 0) {/* if no postfix given, use flag */
          if(fCPU_LongA) pf = 2; else pf = 1;
        }
        FN_Stream_PutCodeByte(base + 9);
        switch(FN_Mnemo_DoChecks(pf, v, ffValue, MAYBE_1_2__)) {

          case 1:
          FN_Stream_PutByte(v);
          break;

          case 2:
          FN_Stream_PutByte(v & 255);
          FN_Stream_PutByte((v >> 8) & 255);
        }
      } else {
        /* other 65xx only know 8bit immediate addressing */
        if(FN_Mnemo_DoChecks(pf, v, ffValue, MAYBE_1____)) {
          FN_Stream_PutCodeByte(base + 9);
          FN_Stream_PutByte(v);
        }
      }
    } else FN_Message(pseCombCA, EERROR);/* kill "STA #..." */
    break;

    case HAM_ABSX:/* $ff,x, $ffff,x $ffffff,x = offsets $15, $1d, $1f */
    offset = 16;/* adjust offset and go on (no break here) */
    /*FALLTHROUGH*/

    case HAM_ABS:/*  $ff,   $ffff,  $ffffff   = offsets $05, $0d, $0f */
    switch(FN_Mnemo_DoChecks(pf, v, ffValue, MAYBE_1_2_3)) {

      case 1:
      FN_Stream_PutCodeByte(base + offset + 5);
      FN_Stream_PutByte(v);
      break;

      case 2:
      FN_Stream_PutCodeByte(base + offset + 13);
      FN_Stream_PutByte(v & 255);
      FN_Stream_PutByte(v >> 8);
      break;

      case 3:
      FN_Stream_PutCodeByte(base + offset + 15);
      FN_Stream_PutByte(v & 255);
      FN_Stream_PutByte((v >> 8) & 255);
      FN_Stream_PutByte(v >> 16);
    }
    break;

    case HAM_ABSY:/* $ffff,y = offset $19 */
    if(FN_Mnemo_DoChecks(pf, v, ffValue, MAYBE___2__)) {
      FN_Stream_PutCodeByte(base + 25);
      FN_Stream_PutByte(v & 255);
      FN_Stream_PutByte(v >> 8);
    }
    break;

    case HAM_ABSS:/* $ff,s = offset $03 */
    if(FN_Mnemo_DoChecks(pf, v, ffValue, MAYBE_1____)) {
      FN_Stream_PutCodeByte(base + 3);
      FN_Stream_PutByte(v);
    }
    break;

    case HAM_XIND:/* ($ff,x) = offset $01 */
    if(FN_Mnemo_DoChecks(pf, v, ffValue, MAYBE_1____)) {
      FN_Stream_PutCodeByte(base + 1);
      FN_Stream_PutByte(v);
    }
    break;

    case HAM_IND:/*  ($ff)   = offset $1a */
    offset = 9;/* adjust offset and go on (no break here) */
    /*FALLTHROUGH*/

    case HAM_INDY:/* ($ff),y = offset $11 */
    if(FN_Mnemo_DoChecks(pf, v, ffValue, MAYBE_1____)) {
      FN_Stream_PutCodeByte(base + offset + 17);
      FN_Stream_PutByte(v);
    }
    break;

    case HAM_INDLY:/* [$ff],y = offset $17 */
    offset = 16;/* adjust offset and go on (no break here) */
    /*FALLTHROUGH*/

    case HAM_INDL:/*  [$ff]   = offset $07 */
    if(FN_Mnemo_DoChecks(pf, v, ffValue, MAYBE_1____)) {
      FN_Stream_PutCodeByte(base + offset + 7);
      FN_Stream_PutByte(v);
    }
    break;

    case HAM_SINDY:/* ($ff,s),y = offset $13 */
    if(FN_Mnemo_DoChecks(pf, v, ffValue, MAYBE_1____)) {
      FN_Stream_PutCodeByte(base + 19);
      FN_Stream_PutByte(v);
    }
    break;

    default:

#ifdef FDEBUG
    printf("addressing mode: %d", hAM_Now);
#endif

    FN_Message(pseCombCA, EERROR);
  }
}

/*
 * Various mnemonics with different addressing modes.
 * Group 5 isn't affected by different immediate addressing,
 * Group 6 means accumulator-related,
 * Group 7 means index-register-related.
 */
static void FN_Mnemo65_05_06_07(unsigned char row, unsigned char group) {
  int   bits,
        code1,
        code2,
        column = 0,
        pf     = FN_Mnemo_GetPostfix();/* read length info (skips spaces) */
  Value v      = FN_Mnemo_GetArgument();

  switch(hAM_Now) {

    case HAM_IMP:/* column 0 */
    FN_Mnemo65_567(0, row);/* output code byte */
    break;

    case HAM_IMM:/* #$ff or #$ffff (depending on register length), column 1 */
    if(FN_Mnemo65_567(1, row)) {;/* output code byte */
      /* check CPU and group (5 = don't care, 6 = accu, 7 = index registers) */
      if((hCPU_Now == HCPU_65816) && (group != 5)) {
        /* 65816 knows 16bit immediate addressing of some commands */
        if(pf == 0) {/* if no postfix given, use relevant switch */
          pf = 1;
          if(group == 6) {
            if(fCPU_LongA) pf = 2;/* group 6 depends on accu flag*/
          } else {
            if(fCPU_LongR) pf = 2;/* group 6 depends on register flag */
          }
        }
        switch(FN_Mnemo_DoChecks(pf, v, ffValue, MAYBE_1_2__)) {

          case 1:
          FN_Stream_PutByte(v);
          break;

          case 2:
          FN_Stream_PutByte(v & 255);
          FN_Stream_PutByte(v >> 8);
        }
      } else {
        /* only 8bit immediate addressing */
        if(FN_Mnemo_DoChecks(pf, v, ffValue, MAYBE_1____)) {
          FN_Stream_PutByte(v);
        }
      }
    }
    break;

    case HAM_ABSY:/* $ff,y  or  $ffff,y, column 6(8bit) or 7(16bit) */
    column += 2;/* adjust column and go on (no break here) */
    /*FALLTHROUGH*/

    case HAM_ABSX:/* $ff,x  or  $ffff,x, column 4(8bit) or 5(16bit) */
    column += 2;/* adjust column and go on (no break here) */
    /*FALLTHROUGH*/

    case HAM_ABS: /* $ff    or  $ffff,   column 2(8bit) or 3(16bit) */
    column += 2;/* adjust column and go on (no break here) */
    code1 = pGroup5Table[column + (row << 3)];
    code2 = pGroup5Table[column + (row << 3) + 1];
    bits = MAYBE______;
    if(code1) bits |= MAYBE_1____;/*  8 bit */
    if(code2) bits |= MAYBE___2__;/* 16 bit */
    switch(FN_Mnemo_DoChecks(pf, v, ffValue, bits)) {

      case 1:
      FN_Stream_PutCodeByte(code1);
      FN_Stream_PutByte(v);
      break;

      case 2:
      FN_Stream_PutCodeByte(code2);
      FN_Stream_PutByte(v & 255);
      FN_Stream_PutByte(v >> 8);
    }
    break;

    default:
    FN_Message(pseCombCA, EERROR);
  }
}

/*
 * Utility routine for groups 5, 6 and 7
 */
static int FN_Mnemo65_567(int x, int y) {
  char opcode = pGroup5Table[x + (y << 3)];

  if(opcode) {
    FN_Stream_PutCodeByte(opcode);
    return(TRUE);
  }
  FN_Message(pseCombCA, EERROR);
  return(FALSE);
}

/*
 * Mnemonics using "8bit, 8bit" addressing. Only applies to "MVN" and "MVP".
 */
static void FN_Mnemo65_08(unsigned char opcode) {
  Value b2,
        b1 = FN_ALU_GetValue_Medium();

  if(FN_Stream_Comma()) {
    b2 = FN_ALU_GetValue_Medium();
    FN_Stream_PutCodeByte(opcode);
    FN_Stream_PutByte(b2);/* changed syntax to "opcode, source, target" */
    FN_Stream_PutByte(b1);
    FN_EnsureEOL();
  } else FN_Message(pseSyntax, EERROR);
}

/*
 * Mnemonics using only the "($..)" addressing mode. Only applies to PEI.
 */
static void FN_Mnemo65_09(unsigned char opcode) {
  Value v = FN_Mnemo_GetArgument();

  switch(hAM_Now) {

    case HAM_IND:
    FN_Stream_PutCodeByte(opcode);
    FN_Stream_PutByte(v);
    break;

    default:
    FN_Message(pseCombCA, EERROR);
  }
}

/*
 * The jump instructions.
 */
static void FN_Mnemo65_10(unsigned char column) {
  char  code2,
        code3;
  int   bits,
        offset = 0,
        pf     = FN_Mnemo_GetPostfix();/* read length info (skips spaces) */
  Value v      = FN_Mnemo_GetArgument();

  switch(hAM_Now) {

    case HAM_ABS:/* $ffff  or  $ffffff, row 0(16bit) or 3(24bit) */
    code2 = pGroup0Table[column];
    code3 = pGroup0Table[column + 12];
    bits = MAYBE______;
    if(code2) bits  = MAYBE___2__;/* 16 bit */
    if(code3) bits |= MAYBE_____3;/* 24 bit */
    switch(FN_Mnemo_DoChecks(pf, v, ffValue, bits)) {

      case 2:
      FN_Stream_PutCodeByte(code2);
      FN_Stream_PutByte(v & 255);
      FN_Stream_PutByte(v >> 8);
      break;

      case 3:
      FN_Stream_PutCodeByte(code3);
      FN_Stream_PutByte(v & 255);
      FN_Stream_PutByte((v >> 8) & 255);
      FN_Stream_PutByte(v >> 16);
    }
    break;

    case HAM_INDL:/* [$ffff],   row 4 */
    offset += 8;/* adjust offset and go on (no break here) */
    /*FALLTHROUGH*/

    case HAM_XIND:/* ($ffff,x), row 2 */
    offset += 4;/* adjust offset and go on (no break here) */
    /*FALLTHROUGH*/

    case HAM_IND: /* ($ffff),   row 1 */
    offset += 4;/* adjust offset and go on (no break here) */
    if(FN_Mnemo_DoChecks(pf, v, ffValue, MAYBE___2__)) {
      code2 = pGroup0Table[column + offset];
      if(code2) {
        FN_Stream_PutCodeByte(code2);
      } else FN_Message(pseCombCA, EERROR);
    }
    break;

    default:FN_Message(pseCombCA, EERROR);
  }
}

/*
 * Service routine to read optional info about parameter length.
 */
static int FN_Mnemo_GetPostfix() {
  char byte,
       pf = 0;

  if(GotByte == '+') {
    byte = FN_GetByte();
    /* The following values must relate to "MVALUE_SIZE" */
    if(byte == '1') pf = 1;
    if(byte == '2') pf = 2;
    if(byte == '3') pf = 3;
    if(pf) FN_GetByte();
    else   FN_Message(pseUkPF, EERROR);
  }
  SKIPSPACE;
  return(pf);
}

/*
 * Address mode parsing
 */

/*
 * This routine returns the command's argument (using the valueparser). The
 * addressing mode will be stored in hAM_Now.
 */
static Value FN_Mnemo_GetArgument() {
  Value  v;
  char   First;

  hAM_Now = HAM_ABS;
  SKIPSPACE;
  if(GotByte == '[') {
    FN_GetByte();/* proceed with next char */
    v = FN_ALU_GetValue_Medium();
    if(GotByte == ']') hAM_Now |= HAM_INDL | FN_Mnemo_GetIndex(TRUE);
    else               FN_Message(pseSyntax, EERROR);
  } else {
    if(GotByte == '#') {
      FN_GetByte();/* proceed with next char */
      hAM_Now |= HAM_IMM;
    }
    First = GotByte;
    v = FN_ALU_GetValue_Liberal(FALSE);/* liberal, to allow for "(...," */
    if(ffValue & MVALUE_NONE) {
      hAM_Now |= HAM_IMP;
    } else {
      if((ffValue & MVALUE_DEFINED) == 0) FN_NeedValue();
    }
    /* check for index in parentheses */
    if(nKlammer) {
      /* in case there are still open parentheses, read internal index */
      hAM_Now |= (FN_Mnemo_GetIndex(FALSE) << 2);
      if(GotByte == ')') FN_GetByte();/* proceed with next char */
      else FN_Message(pseSyntax, EERROR);
    }
    /* check for indirect addressing */
    if((First == '(') && (VP_Work == FALSE)) hAM_Now |= HAM_IND;
    /* check for external index */
    hAM_Now |= FN_Mnemo_GetIndex(FALSE);
  }
  /* ensure end of line */
  FN_EnsureEOL();
  return(v);
}

/*
 * Utility routine for parsing indices.
 */
static int FN_Mnemo_GetIndex(int next) {
  int am = HAM__;

  if(next) FN_GetByte();
  if(!FN_Stream_Comma()) return(am);
  switch(GotByte) {

    case 's':
    case 'S':
    am = HAM_S;
    break;

    case 'x':
    case 'X':
    am = HAM_X;
    break;

    case 'y':
    case 'Y':
    am = HAM_Y;
    break;

    default:
    FN_Message(pseSyntax, EERROR);
  }
  if(am != HAM__) NEXTANDSKIPSPACE;
  return(am);
}

/*
 * Utility routine for comparing postfix, argument value, argument size,
 * "unsure"-flag and possible addressing modes. Returns number of parameter
 * bytes to send. If it returns zero, an error occurred (and has already been
 * delivered).
 */
static int FN_Mnemo_DoChecks(int pf, Value v, int ff, int am) {
  int c;

/*
 *      pf      Postfix (none, 8b, 16b, 24b)
 *      v       Value of parameter
 *      ff      Flags of value (ffValue)
 *      am      Adressing modes (%0010 = 8b, %0100 = 16b, %1000 = 24b)
 *              Must have trailing zero for this conversion: "1 << pf"
 *      c       Return value, number of parameter bytes to send (0 = error)
 */
  if(am != MAYBE______) {
    if(pf) {
      /* Postfix given */
      if(am & (1 << pf)) {
        /* Command allows this postfix */
        if(v < (1 << (pf << 3))) return(pf);/* if value fits, return postfix */
        /* else error and return zero */
        FN_Message(psePFExceeded, EERROR);
        return(0);
      } else {
        /* Command doesn't allow this postfix */
        FN_Message(pseCombCP, EERROR);
        return(0);
      }
    } else {
      /* no postfix given, so use value's size */
      c = ff & MVALUE_SIZE;
      if(c) {
        /* value's size is defined, so return this or bigger size */
        if((c < 2) && (am & MAYBE_1____)) return(1);/*  8 bit */
        if((c < 3) && (am & MAYBE___2__)) return(2);/* 16 bit */
        if(am & MAYBE_____3)              return(3);/* 24 bit */
        /* else error and return zero */
        FN_Message(pseTooBig, EERROR);
        return(0);
      } else {
        /* Value's size is undefined */
        if(ff & MVALUE_UNSURE) {
          /* Value is unsure, so use default size */
          c = 1;
          if(am & MAYBE_____3) c = 3;
          if(am & MAYBE___2__) c = 2;
          if((ff & MVALUE_DEFINED) && (v || (c != 1))) {
            if(v < (1 << ((c - 1) << 3))) FN_Message(pseHighbyteZero, EWARNING);
          }
          return(c);
        } else {
          /* Value is sure, so act upon its size */
          if((v <   256) && (am & MAYBE_1____)) return(1);
          if((v < 65536) && (am & MAYBE___2__)) return(2);
          if(                am & MAYBE_____3 ) return(3);
          FN_Message(pseTooBig, EERROR);
        }
      }
    }
  } else FN_Message(pseCombCA, EERROR);
  return(c);
}

/*
 * Z80 stuff
 */

static int FN_Mnemo80_IsInvalid(int len) {
  FN_Message(pseNotYet, EERROR);
  FN_SkipRest();
  return(TRUE);
}
