/* code3201x.c */
/*****************************************************************************/
/* SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only                     */
/*                                                                           */
/* AS-Portierung                                                             */
/*                                                                           */
/* Codegenerator TMS3201x-Familie                                            */
/*                                                                           */
/*****************************************************************************/

#include "stdinc.h"
#include <string.h>
#include <ctype.h>

#include "bpemu.h"
#include "strutil.h"
#include "chunks.h"
#include "asmdef.h"
#include "asmsub.h"
#include "asmpars.h"
#include "asmitree.h"
#include "codepseudo.h"
#include "fourpseudo.h"
#include "codevars.h"
#include "errmsg.h"
#include "headids.h"

#include "code3201x.h"

typedef struct
{
  Word Code;
  Word AllowShifts;
} AdrShiftOrder;

typedef struct
{
  Word Code;
  Integer Min, Max;
  Word Mask;
} ImmOrder;


static Word AdrMode;
static Boolean AdrOK;

static CPUVar CPU32010, CPU32015;

static AdrShiftOrder *AdrShiftOrders;
static ImmOrder *ImmOrders;

/*----------------------------------------------------------------------------*/

static Word EvalARExpression(const tStrComp *pArg, Boolean *OK)
{
  *OK = True;
  if (!as_strcasecmp(pArg->str.p_str, "AR0"))
    return 0;
  if (!as_strcasecmp(pArg->str.p_str, "AR1"))
    return 1;
  return EvalStrIntExpression(pArg, UInt1, OK);
}

static void DecodeAdr(const tStrComp *pArg, int Aux, Boolean Must1)
{
  Byte h;
  char *p;
  char *Arg = pArg->str.p_str;

  AdrOK = False;

  if ((!strcmp(pArg->str.p_str, "*")) || (!strcmp(pArg->str.p_str, "*-")) || (!strcmp(pArg->str.p_str, "*+")))
  {
    AdrMode = 0x88;
    if (strlen(Arg) == 2)
      AdrMode += (pArg->str.p_str[1] == '+') ? 0x20 : 0x10;
    if (Aux <= ArgCnt)
    {
      h = EvalARExpression(&ArgStr[Aux], &AdrOK);
      if (AdrOK)
      {
        AdrMode &= 0xf7;
        AdrMode += h;
      }
    }
    else
      AdrOK = True;
  }
  else if (ChkArgCnt(1, Aux - 1))
  {
    tEvalResult EvalResult;

    h = 0;
    if ((strlen(pArg->str.p_str) > 3) && (!as_strncasecmp(pArg->str.p_str, "DAT", 3)))
    {
      AdrOK = True;
      for (p = pArg->str.p_str + 3; *p != '\0'; p++)
        if ((*p > '9') || (*p < '0'))
          AdrOK = False;
      if (AdrOK)
      {
        h = EvalStrIntExpressionOffsWithResult(pArg, 3, UInt8, &EvalResult);
        AdrOK = EvalResult.OK;
      }
    }
    if (!AdrOK)
    {
      h = EvalStrIntExpressionWithResult(pArg, Int8, &EvalResult);
      AdrOK = EvalResult.OK;
    }
    if (AdrOK)
    {
      if (Must1 && (h < 0x80) && !mFirstPassUnknown(EvalResult.Flags))
      {
        WrError(ErrNum_UnderRange);
        AdrOK = False;
      }
      else
      {
        AdrMode = h & 0x7f;
        ChkSpace(SegData, EvalResult.AddrSpaceMask);
      }
    }
  }
}

/*----------------------------------------------------------------------------*/

/* kein Argument */

static void DecodeFixed(Word Code)
{
  if (ChkArgCnt(0, 0))
  {
    CodeLen = 1;
    WAsmCode[0] = Code;
  }
}

/* Spruenge */

static void DecodeJmp(Word Code)
{
  if (ChkArgCnt(1, 1))
  {
    Boolean OK;

    WAsmCode[1] = EvalStrIntExpression(&ArgStr[1], UInt12, &OK);
    if (OK)
    {
      CodeLen = 2;
      WAsmCode[0] = Code;
    }
  }
}

/* nur Adresse */

static void DecodeAdrInst(Word Code)
{
  if (ChkArgCnt(1, 2))
  {
    DecodeAdr(&ArgStr[1], 2, Code & 1);
    if (AdrOK)
    {
      CodeLen = 1;
      WAsmCode[0] = (Code & 0xfffe) + AdrMode;
    }
  }
}

/* Adresse & schieben */

static void DecodeAdrShift(Word Index)
{
  Boolean HasSh;
  int AuxArgIndex;
  const AdrShiftOrder *pOrder = AdrShiftOrders + Index;

  if (ChkArgCnt(1, 3))
  {
    if (*ArgStr[1].str.p_str == '*')
    {
      switch (ArgCnt)
      {
        case 1:
          HasSh = False;
          AuxArgIndex = 3;
          break;
        case 2:
          if (!as_strncasecmp(ArgStr[2].str.p_str, "AR", 2))
          {
            HasSh = False;
            AuxArgIndex = 2;
          }
          else
          {
            HasSh = True;
            AuxArgIndex = 3;
          }
          break;
        default: /* 3 */
          HasSh = True;
          AuxArgIndex = 3;
      }
    }
    else
    {
      AuxArgIndex = 3;
      HasSh = (ArgCnt == 2);
    }
    DecodeAdr(&ArgStr[1], AuxArgIndex, False);
    if (AdrOK)
    {
      Boolean OK;
      Word AdrWord;

      if (!HasSh)
      {
        OK = True;
        AdrWord = 0;
      }
      else
      {
        tSymbolFlags Flags;

        AdrWord = EvalStrIntExpressionWithFlags(&ArgStr[2], Int4, &OK, &Flags);
        if (OK && mFirstPassUnknown(Flags))
          AdrWord = 0;
      }
      if (OK)
      {
        if ((pOrder->AllowShifts & (1 << AdrWord)) == 0) WrError(ErrNum_InvShiftArg);
        else
        {
          CodeLen = 1;
          WAsmCode[0] = pOrder->Code + AdrMode + (AdrWord << 8);
        }
      }
    }
  }
}

/* Ein/Ausgabe */

static void DecodeIN_OUT(Word Code)
{
  if (ChkArgCnt(2, 3))
  {
    DecodeAdr(&ArgStr[1], 3, False);
    if (AdrOK)
    {
      tEvalResult EvalResult;
      Word AdrWord = EvalStrIntExpressionWithResult(&ArgStr[2], UInt3, &EvalResult);
      if (EvalResult.OK)
      {
        ChkSpace(SegIO, EvalResult.AddrSpaceMask);
        CodeLen = 1;
        WAsmCode[0] = Code + AdrMode + (AdrWord << 8);
      }
    }
  }
}

/* konstantes Argument */

static void DecodeImm(Word Index)
{
  const ImmOrder *pOrder = ImmOrders + Index;

  if (ChkArgCnt(1, 1))
  {
    Boolean OK;
    tSymbolFlags Flags;
    LongInt AdrLong = EvalStrIntExpressionWithFlags(&ArgStr[1], Int32, &OK, &Flags);

    if (OK)
    {
      if (mFirstPassUnknown(Flags))
        AdrLong &= pOrder->Mask;
      if (AdrLong < pOrder->Min) WrError(ErrNum_UnderRange);
      else if (AdrLong > pOrder->Max) WrError(ErrNum_OverRange);
      else
      {
        CodeLen = 1;
        WAsmCode[0] = pOrder->Code + (AdrLong & pOrder->Mask);
      }
    }
  }
}

/* mit Hilfsregistern */

static void DecodeLARP(Word Code)
{
  UNUSED(Code);

  if (ChkArgCnt(1, 1))
  {
    Boolean OK;
    Word AdrWord = EvalARExpression(&ArgStr[1], &OK);
    if (OK)
    {
      CodeLen = 1;
      WAsmCode[0] = 0x6880 + AdrWord;
    }
  }
}

static void DecodeLAR_SAR(Word Code)
{
  if (ChkArgCnt(2, 3))
  {
    Boolean OK;
    Word AdrWord = EvalARExpression(&ArgStr[1], &OK);
    if (OK)
    {
      DecodeAdr(&ArgStr[2], 3, False);
      if (AdrOK)
      {
        CodeLen = 1;
        WAsmCode[0] = Code + AdrMode + (AdrWord << 8);
      }
    }
  }
}

static void DecodeLARK(Word Code)
{
  UNUSED(Code);

  if (ChkArgCnt(2, 2))
  {
    Boolean OK;
    Word AdrWord = EvalARExpression(&ArgStr[1], &OK);
    if (OK)
    {
      WAsmCode[0] = EvalStrIntExpression(&ArgStr[2], Int8, &OK);
      if (OK)
      {
        CodeLen = 1;
        WAsmCode[0] = Lo(WAsmCode[0]) + 0x7000 + (AdrWord << 8);
      }
    }
  }
}

static void DecodePORT(Word Code)
{
  UNUSED(Code);

  code_equate_type(SegIO, UInt3);
}

static void DecodeDATA_3201x(Word Code)
{
  UNUSED(Code);

  DecodeDATA(Int16, Int16);
}

/*----------------------------------------------------------------------------*/

static void AddFixed(const char *NName, Word NCode)
{
  AddInstTable(InstTable, NName, NCode, DecodeFixed);
}

static void AddJmp(const char *NName, Word NCode)
{
  AddInstTable(InstTable, NName, NCode, DecodeJmp);
}

static void AddAdr(const char *NName, Word NCode, Word NMust1)
{
  AddInstTable(InstTable, NName, NCode | NMust1, DecodeAdrInst);
}

static void AddAdrShift(const char *NName, Word NCode, Word NAllow)
{
  order_array_rsv_end(AdrShiftOrders, AdrShiftOrder);
  AdrShiftOrders[InstrZ].Code = NCode;
  AdrShiftOrders[InstrZ].AllowShifts = NAllow;
  AddInstTable(InstTable, NName, InstrZ++, DecodeAdrShift);
}

static void AddImm(const char *NName, Word NCode, Integer NMin, Integer NMax, Word NMask)
{
  order_array_rsv_end(ImmOrders, ImmOrder);
  ImmOrders[InstrZ].Code = NCode;
  ImmOrders[InstrZ].Min = NMin;
  ImmOrders[InstrZ].Max = NMax;
  ImmOrders[InstrZ].Mask = NMask;
  AddInstTable(InstTable, NName, InstrZ++, DecodeImm);
}

static void InitFields(void)
{
  InstTable = CreateInstTable(203);

  add_null_pseudo(InstTable);

  AddInstTable(InstTable, "IN", 0x4000, DecodeIN_OUT);
  AddInstTable(InstTable, "OUT", 0x4800, DecodeIN_OUT);
  AddInstTable(InstTable, "LARP", 0, DecodeLARP);
  AddInstTable(InstTable, "LAR", 0x3800, DecodeLAR_SAR);
  AddInstTable(InstTable, "SAR", 0x3000, DecodeLAR_SAR);
  AddInstTable(InstTable, "LARK", 0, DecodeLARK);
  AddInstTable(InstTable, "PORT", 0, DecodePORT);
  AddInstTable(InstTable, "RES", 0, DecodeRES);
  AddInstTable(InstTable, "DATA", 0, DecodeDATA_3201x);

  AddFixed("ABS"   , 0x7f88);  AddFixed("APAC"  , 0x7f8f);
  AddFixed("CALA"  , 0x7f8c);  AddFixed("DINT"  , 0x7f81);
  AddFixed("EINT"  , 0x7f82);  AddFixed("NOP"   , 0x7f80);
  AddFixed("PAC"   , 0x7f8e);  AddFixed("POP"   , 0x7f9d);
  AddFixed("PUSH"  , 0x7f9c);  AddFixed("RET"   , 0x7f8d);
  AddFixed("ROVM"  , 0x7f8a);  AddFixed("SOVM"  , 0x7f8b);
  AddFixed("SPAC"  , 0x7f90);  AddFixed("ZAC"   , 0x7f89);

  AddJmp("B"     , 0xf900);  AddJmp("BANZ"  , 0xf400);
  AddJmp("BGEZ"  , 0xfd00);  AddJmp("BGZ"   , 0xfc00);
  AddJmp("BIOZ"  , 0xf600);  AddJmp("BLEZ"  , 0xfb00);
  AddJmp("BLZ"   , 0xfa00);  AddJmp("BNZ"   , 0xfe00);
  AddJmp("BV"    , 0xf500);  AddJmp("BZ"    , 0xff00);
  AddJmp("CALL"  , 0xf800);

  AddAdr("ADDH"  , 0x6000, False);  AddAdr("ADDS"  , 0x6100, False);
  AddAdr("AND"   , 0x7900, False);  AddAdr("DMOV"  , 0x6900, False);
  AddAdr("LDP"   , 0x6f00, False);  AddAdr("LST"   , 0x7b00, False);
  AddAdr("LT"    , 0x6a00, False);  AddAdr("LTA"   , 0x6c00, False);
  AddAdr("LTD"   , 0x6b00, False);  AddAdr("MAR"   , 0x6800, False);
  AddAdr("MPY"   , 0x6d00, False);  AddAdr("OR"    , 0x7a00, False);
  AddAdr("SST"   , 0x7c00, True );  AddAdr("SUBC"  , 0x6400, False);
  AddAdr("SUBH"  , 0x6200, False);  AddAdr("SUBS"  , 0x6300, False);
  AddAdr("TBLR"  , 0x6700, False);  AddAdr("TBLW"  , 0x7d00, False);
  AddAdr("XOR"   , 0x7800, False);  AddAdr("ZALH"  , 0x6500, False);
  AddAdr("ZALS"  , 0x6600, False);

  InstrZ = 0;
  AddAdrShift("ADD"   , 0x0000, 0xffff);
  AddAdrShift("LAC"   , 0x2000, 0xffff);
  AddAdrShift("SACH"  , 0x5800, 0x0013);
  AddAdrShift("SACL"  , 0x5000, 0x0001);
  AddAdrShift("SUB"   , 0x1000, 0xffff);

  InstrZ = 0;
  AddImm("LACK", 0x7e00,     0,  255,   0xff);
  AddImm("LDPK", 0x6e00,     0,    1,    0x1);
  AddImm("MPYK", 0x8000, -4096, 4095, 0x1fff);
}

static void DeinitFields(void)
{
  DestroyInstTable(InstTable);

  order_array_free(AdrShiftOrders);
  order_array_free(ImmOrders);
}

/*----------------------------------------------------------------------------*/

/*!------------------------------------------------------------------------
 * \fn     InternSymbol_3201X(char *pArg, TempResult *pResult)
 * \brief  parse for built-in symbols
 * \param  pArg source argument
 * \param  pResult possible result
 * ------------------------------------------------------------------------ */

static void InternSymbol_3201X(char *pArg, TempResult *pResult)
{
  if ((strlen(pArg) == 3)
   && (as_toupper(pArg[0]) == 'P')
   && (as_toupper(pArg[1]) == 'A')
   && ((pArg[2] >= '0') && (pArg[2] <= '7')))
  {
    as_tempres_set_int(pResult, pArg[2] - '0');
    pResult->AddrSpaceMask |= 1 << SegIO;
  }
}

static void MakeCode_3201X(void)
{
  if (!LookupInstTable(InstTable, OpPart.str.p_str))
    WrStrErrorPos(ErrNum_UnknownInstruction, &OpPart);
}

static Boolean IsDef_3201X(void)
{
  return (Memo("PORT"));
}

static void SwitchFrom_3201X(void)
{
  DeinitFields();
}

static void SwitchTo_3201X(void)
{
  const TFamilyDescr *p_descr = FindFamilyByName("TMS3201x");

  TurnWords = False;
  SetIntConstMode(eIntConstModeIntel);

  PCSymbol = "$";
  HeaderID = p_descr->Id;
  NOPCode = 0x7f80;
  DivideChars = ",";
  HasAttrs = False;

  ValidSegs = (1 << SegCode)|(1 << SegData)|(1 << SegIO);
  Grans[SegCode] = 2; ListGrans[SegCode] = 2; SegInits[SegCode] = 0;
  SegLimits[SegCode] = 0xfff;
  Grans[SegData] = 2; ListGrans[SegData] = 2; SegInits[SegData] = 0;
  SegLimits[SegData] = (MomCPU == CPU32010) ? 0x8f : 0xff;
  Grans[SegIO  ] = 2; ListGrans[SegIO  ] = 2; SegInits[SegIO  ] = 0;
  SegLimits[SegIO  ] = 7;

  MakeCode = MakeCode_3201X;
  IsDef = IsDef_3201X;
  InternSymbol = InternSymbol_3201X;
  SwitchFrom = SwitchFrom_3201X;
  InitFields();
}

void code3201x_init(void)
{
  CPU32010 = AddCPU("32010", SwitchTo_3201X);
  CPU32015 = AddCPU("32015", SwitchTo_3201X);
}
