/*

   Copyright (c) 2015-2016, John Forecast

   Permission is hereby granted, free of charge, to any person obtaining a
   copy of this software and associated documentation files (the "Software"),
   to deal in the Software without restriction, including without limitation
   the rights to use, copy, modify, merge, publish, distribute, sublicense,
   and/or sell copies of the Software, and to permit persons to whom the
   Software is furnished to do so, subject to the following conditions:

   The above copyright notice and this permission notice shall be included in
   all copies or substantial portions of the Software.

   THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
   IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
   FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
   JOHN FORECAST BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
   IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
   CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

   Except as contained in this notice, the name of John Forecast shall not
   be used in advertising or otherwise to promote the sale, use or other dealings
   in this Software without prior written authorization from John Forecast.

*/

/* cdc1700_sym.c: symbolic assembler input for "deposit" command
 */

#include "cdc1700_defs.h"
#include <ctype.h>

extern UNIT cpu_unit;

extern uint16 doADDinternal(uint16, uint16);

/*
 * Symbol tables
 */
#define I_DATA          0x10000         /* Data transmission */
#define I_ARITH         0x20000         /* Arithmetic */
#define I_LOG           0x30000         /* Logical */
#define I_JUMP          0x40000         /* Jumps */
#define I_REG           0x50000         /* Register reference */
#define I_SKIP          0x60000         /* Skip */
#define I_INTER         0x70000         /* Inter-register */
#define I_SHIFT         0x80000         /* Shift */
#define I_MASK          0xF0000

#define I_DMASK         0xFFFF

/*
 * Modifiers for I_REG addressing.
 */
#define I_NONE          0x000000        /* No argument expected */
#define I_REL           0x100000        /* 8-bit relative address */
#define I_ABS           0x200000        /* 8-bit absolute value */
#define I_SIGNED        0x300000        /* 8-bit signed value */
#define I_MASK2         0x300000

#define I_NOARG         I_REG + I_NONE

static const char *opcode[] = {
  "ADQ", "LDQ", "RAO", "LDA",
  "EOR", "AND", "SUB", "ADD",
  "SPA", "STA", "RTJ", "STQ",
  "DVI", "MUI", "JMP", "SLS",
  "SAZ", "SAN", "SAP", "SAM",
  "SQZ", "SQN", "SQP", "SQM",
  "SWS", "SWN", "SOV", "SNO",
  "SPE", "SNP", "SPF", "SNF",
  "INP", "OUT", "EIN", "IIN",
  "ECA", "DCA", "SPB", "CPB",
  "AAM", "AAQ", "AAB", "CLR",
  "TCM", "TCQ", "TCB", "TCA",
  "EAM", "EAQ", "EAB", "SET",
  "TRM", "TRQ", "TRB", "TRA",
  "LAM", "LAQ", "LAB", "CAM",
  "CAQ", "CAB", "INA", "ENA",
  "NOP", "ENQ", "INQ", "EXI",
  "QRS", "ARS", "LRS", "QLS", 
  "ALS", "LLS", "ECA", "DCA",
  NULL
};

static const int32 opc_val[] = {
  OPC_ADQ + I_ARITH, OPC_LDQ + I_DATA, OPC_RAO + I_ARITH, OPC_LDA + I_DATA,
  OPC_EOR + I_LOG, OPC_AND + I_LOG, OPC_SUB + I_ARITH, OPC_ADD + I_ARITH,
  OPC_SPA + I_DATA, OPC_STA + I_DATA, OPC_RTJ + I_JUMP, OPC_STQ + I_DATA,
  OPC_DVI + I_ARITH, OPC_MUI + I_ARITH, OPC_JMP + I_JUMP, OPC_SLS + I_NOARG,
  OPC_SAZ + I_SKIP, OPC_SAN + I_SKIP, OPC_SAP + I_SKIP, OPC_SAM + I_SKIP,
  OPC_SQZ + I_SKIP, OPC_SQN + I_SKIP, OPC_SQP + I_SKIP, OPC_SQM + I_SKIP,
  OPC_SWS + I_SKIP, OPC_SWN + I_SKIP, OPC_SOV + I_SKIP, OPC_SNO + I_SKIP,
  OPC_SPE + I_SKIP, OPC_SNP + I_SKIP, OPC_SPF + I_SKIP, OPC_SNF + I_SKIP,
  OPC_INP + I_REG + I_REL, OPC_OUT + I_REG + I_REL, OPC_EIN + I_NOARG, OPC_IIN + I_NOARG,
  OPC_ECA + I_NOARG, OPC_DCA + I_NOARG, OPC_SPB + I_NOARG, OPC_CPB + I_NOARG,
  OPC_AAM + I_INTER, OPC_AAQ + I_INTER, OPC_AAB + I_INTER, OPC_CLR + I_INTER,
  OPC_TCM + I_INTER, OPC_TCQ + I_INTER, OPC_TCB + I_INTER, OPC_TCA + I_INTER,
  OPC_EAM + I_INTER, OPC_EAQ + I_INTER, OPC_EAB + I_INTER, OPC_SET + I_INTER,
  OPC_TRM + I_INTER, OPC_TRQ + I_INTER, OPC_TRB + I_INTER, OPC_TRA + I_INTER,
  OPC_LAM + I_INTER, OPC_LAQ + I_INTER, OPC_LAB + I_INTER, OPC_CAM + I_INTER,
  OPC_CAQ + I_INTER, OPC_CAB + I_INTER, OPC_INA + I_REG + I_SIGNED, OPC_ENA + I_REG + I_SIGNED,
  OPC_NOP + I_NOARG, OPC_ENQ + I_REG + I_SIGNED, OPC_INQ + I_REG + I_SIGNED, OPC_EXI + I_REG + I_ABS,
  OPC_QRS + I_SHIFT, OPC_ARS + I_SHIFT, OPC_LRS + I_SHIFT, OPC_QLS + I_SHIFT,
  OPC_ALS + I_SHIFT, OPC_LLS + I_SHIFT, OPC_ECA + I_NOARG, OPC_DCA + I_NOARG
};

/*
 * Register (and pseudo-register) names.
 */
static const char *regname[] = {
  "A", "Q", "M", "I", "B",
  NULL
};

/*
 * Usage value for each usage type (0 means invalid).
 */
static uint16 instIndex[] = {
  0x0000, MOD_I1, 0x0000, MOD_I2, MOD_I1 | MOD_I2
};

static uint16 instInter[] = {
  MOD_D_A, MOD_D_Q, MOD_D_M, 0x0000, 0x0000
};

#define NEXTSYMBOL(mchar)            \
  cptr = get_glyph(cptr, gbuf, mchar); \
  for (j = 0; (regname[j] != NULL) && (strcmp(regname[j], gbuf) != 0); j++); \
  if (regname[j] == NULL) return SCPE_ARG

t_stat parse_sym(CONST char *cptr, t_addr addr, UNIT *uptr, t_value *val, int32 sw)
{
  int32 i, j, l, rdx;
  t_bool neg;
  t_value temp;
  t_stat r;
  char gbuf[CBUFSIZE], mode;
  const char *cptr2;

  while (isspace(*cptr))
    cptr++;

  if ((sw & SWMASK('A')) || ((*cptr == '\'') && cptr++)) {
    /* ASCII character */
    if (cptr[0] == 0)
      return SCPE_ARG;
    val[0] = (t_value)cptr[0] | 0200;
    return SCPE_OK;
  }

  if ((sw & SWMASK('C')) || ((*cptr == '"') && cptr++)) {
    /* Packed ASCII characters (2 to a word) */
    if (cptr[0] == 0)

    val[0] = (((t_value)cptr[0] | 0200) << 8) | ((t_value)cptr[1] | 0200);
    return SCPE_OK;
  }

  cptr = get_glyph(cptr, gbuf, 0);
  l = strlen(gbuf);
  if ((gbuf[l - 1] == '*') || (gbuf[l - 1] == '-') || (gbuf[l - 1] == '+')) {
    mode = gbuf[l - 1];
    gbuf[l - 1] = '\0';
  } else mode = 0;

  for (i = 0; (opcode[i] != NULL) && (strcmp(opcode[i], gbuf) != 0); i++);
  if (opcode[i] == NULL)
    return SCPE_ARG;

  val[0] = opc_val[i] & I_DMASK;
  while (isspace(*cptr))
    cptr++;

  neg = FALSE;
  rdx = 10;

  switch (opc_val[i] & I_MASK) {
    case I_DATA:
    case I_ARITH:
    case I_LOG:
      if (*cptr == '=') {
        cptr++;

        if (*cptr == '-') {
          neg = TRUE;
          cptr++;
        }

        if (*cptr == '$') {
          rdx = 16;
          cptr++;
        }
        temp = get_uint(cptr, rdx, MAXNEG, &r);
        if (r != SCPE_OK)
          return r;
        if (neg) {
          if (temp > MAXPOS)
            return SCPE_ARG;
          temp = (~temp) & 0xFFFF;
        }

        if ((mode == '*') || (mode == '-'))
          return SCPE_ARG;

        /*
         * Constant addressing mode always occupies 2 words.
         */
        val[1] = temp;
        return -1;
      }
      /* FALLTHROUGH */

    case I_JUMP:
      if (*cptr == '(') {
        cptr++;

        if (*cptr == '$') {
          rdx = 16;
          cptr++;
        }
        temp = strtotv(cptr, &cptr2, rdx);
        if ((cptr == cptr2) || (*cptr2++ != ')'))
          return SCPE_ARG;
        cptr = (char *)cptr2;
        val[0] |= MOD_IN;
      } else {
        if (*cptr == '$') {
          rdx = 16;
          cptr++;
        }
        temp = strtotv(cptr, &cptr2, rdx);
        if (cptr == cptr2)
          return SCPE_ARG;
        cptr = (char *)cptr2;
      }

      if (mode == '*') {
        temp = doADDinternal(temp, ~addr);
        if (CANEXTEND8(temp))
          temp &= 0xFF;
        val[0] |= MOD_RE;
      }

      if ((mode == '-') && ((temp & 0xFF00) != 0))
        return SCPE_ARG;

      /*
       * Check for indexing modifier
       */
      if (*cptr++ == ',') {
        NEXTSYMBOL(0);
        if (instIndex[j] == 0)
          return SCPE_ARG;

        val[0] |= instIndex[j];
      }

      if (((temp & 0xFF00) != 0) || (mode == '+')) {
        val[1] = temp;
        return -1;
      }
      val[0] |= temp;
      return SCPE_OK;

    case I_REG:
      switch (opc_val[i] & I_MASK2) {
        case I_NONE:
          return SCPE_OK;

        case I_REL:
        case I_SIGNED:
          if (*cptr == '-') {
            neg = TRUE;
            cptr++;
          }
          if (*cptr == '$') {
            rdx = 16;
            cptr++;
          }
          temp = get_uint(cptr, rdx, 127, &r);
          if (r != SCPE_OK)
            return r;
          if (neg)
            temp = (~temp) & 0xFF;
          val[0] |= temp;
          return SCPE_OK;

        case I_ABS:
          if (*cptr == '$') {
            rdx = 16;
            cptr++;
          }
          temp = get_uint(cptr, rdx, 255, &r);
          if (r != SCPE_OK)
            return r;
          val[0] |= temp;
          return SCPE_OK;
      }
      break;

    case I_SKIP:
      if (*cptr == '$') {
        rdx = 16;
        cptr++;
      }
      temp = get_uint(cptr, rdx, 15, &r);
      if (r != SCPE_OK)
        return r;
      val[0] |= temp;
      return SCPE_OK;

    case I_INTER:
      if (*cptr != 0) {
        do {
          NEXTSYMBOL(',');
          if (instInter[j] == 0)
            return SCPE_ARG;
          val[0] |= instInter[j];
        } while (*cptr != 0);
      }
      return SCPE_OK;

    case I_SHIFT:
      if (*cptr == '$') {
        rdx = 16;
        cptr++;
      }
      temp = get_uint(cptr, rdx, 31, &r);
      if (r != SCPE_OK)
        return r;
      val[0] |= temp;
      return SCPE_OK;
  }
  return SCPE_ARG;
}
