/* codecop8.c */
/*****************************************************************************/
/* SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only                     */
/*                                                                           */
/* AS-Portierung                                                             */
/*                                                                           */
/* Codegeneratormodul COP8-Familie                                           */
/*                                                                           */
/*****************************************************************************/

#include "stdinc.h"

#include <string.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 "intpseudo.h"
#include "natpseudo.h"
#include "codevars.h"
#include "errmsg.h"
#include "headids.h"

#include "codecop8.h"

#define ModNone (-1)
#define ModAcc 0
#define MModAcc (1 << ModAcc)
#define ModBInd 1
#define MModBInd (1 << ModBInd)
#define ModBInc 2
#define MModBInc (1 << ModBInc)
#define ModBDec 3
#define MModBDec (1 << ModBDec)
#define ModXInd 4
#define MModXInd (1 << ModXInd)
#define ModXInc 5
#define MModXInc (1 << ModXInc)
#define ModXDec 6
#define MModXDec (1 << ModXDec)
#define ModDir 7
#define MModDir (1 << ModDir)
#define ModImm 8
#define MModImm (1 << ModImm)

#define DirPrefix 0xbd
#define BReg 0xfe

#define BitOrderCnt 3

static CPUVar CPUCOP87L84;

typedef struct
{
  ShortInt Mode;
  Byte Val;
  tSymbolFlags ValSymFlags;
} tAdrResult;

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

static ShortInt DecodeAdr(const tStrComp *pArg, Word Mask, tAdrResult *pResult)
{
  static const char ModStrings[ModXDec + 1][5] =
  {
    "A", "[B]", "[B+]", "[B-]",
    "[X]", "[X+]", "[X-]"
  };

  int z;
  tEvalResult EvalResult;

  pResult->Mode = ModNone;

   /* indirekt/Akku */

  for (z = ModAcc; z <= ModXDec; z++)
    if (!as_strcasecmp(pArg->str.p_str, ModStrings[z]))
    {
      pResult->Mode = z;
      goto chk;
    }

  /* immediate */

  if (*pArg->str.p_str == '#')
  {
    pResult->Val = EvalStrIntExpressionOffsWithResult(pArg, 1, Int8, &EvalResult);
    if (EvalResult.OK)
    {
      pResult->Mode = ModImm;
      pResult->ValSymFlags = EvalResult.Flags;
    }
    goto chk;
  }

  /* direkt */

  pResult->Val = EvalStrIntExpressionWithResult(pArg, Int8, &EvalResult);
  if (EvalResult.OK)
  {
    pResult->Mode = ModDir;
    pResult->ValSymFlags = EvalResult.Flags;
    ChkSpace(SegData, EvalResult.AddrSpaceMask);
  }

chk:
  if ((pResult->Mode != ModNone) && !(Mask & (1 << pResult->Mode)))
  {
    pResult->Mode = ModNone; WrError(ErrNum_InvAddrMode);
  }
  return pResult->Mode;
}

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

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

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

  if (ChkArgCnt(2, 2))
  {
    tAdrResult DestResult;

    switch (DecodeAdr(&ArgStr[1], MModAcc | MModDir | MModBInd | MModBInc | MModBDec, &DestResult))
    {
      case ModAcc:
      {
        tAdrResult SrcResult;

        switch(DecodeAdr(&ArgStr[2], MModDir | MModImm | MModBInd | MModXInd | MModBInc | MModXInc | MModBDec | MModXDec, &SrcResult))
        {
          case ModDir:
            BAsmCode[0] = 0x9d;
            BAsmCode[1] = SrcResult.Val;
            CodeLen = 2;
            break;
          case ModImm:
            BAsmCode[0] = 0x98;
            BAsmCode[1] = SrcResult.Val;
            CodeLen = 2;
            break;
          case ModBInd:
            BAsmCode[0] = 0xae;
            CodeLen = 1;
            break;
          case ModXInd:
            BAsmCode[0] = 0xbe;
            CodeLen = 1;
            break;
          case ModBInc:
            BAsmCode[0] = 0xaa;
            CodeLen = 1;
            break;
          case ModXInc:
            BAsmCode[0] = 0xba;
            CodeLen = 1;
            break;
          case ModBDec:
            BAsmCode[0] = 0xab;
            CodeLen = 1;
            break;
          case ModXDec:
            BAsmCode[0] = 0xbb;
            CodeLen = 1;
            break;
        }
        break;
      }
      case ModDir:
      {
        tAdrResult SrcResult;

        if (DecodeAdr(&ArgStr[2], MModImm, &SrcResult) == ModImm)
        {
          if (DestResult.Val == BReg)
          {
            if (SrcResult.Val <= 15)
            {
              BAsmCode[0] = 0x5f - SrcResult.Val;
              CodeLen = 1;
            }
            else
            {
              BAsmCode[0] = 0x9f;
              BAsmCode[1] = SrcResult.Val;
              CodeLen = 2;
            }
          }
          else if (DestResult.Val >= 0xf0)
          {
            BAsmCode[0] = DestResult.Val - 0x20;
            BAsmCode[1] = SrcResult.Val;
            CodeLen = 2;
          }
          else
          {
            BAsmCode[0] = 0xbc;
            BAsmCode[1] = DestResult.Val;
            BAsmCode[2] = SrcResult.Val;
            CodeLen = 3;
          }
        }
        break;
      }
      case ModBInd:
      {
        tAdrResult SrcResult;

        if (DecodeAdr(&ArgStr[2], MModImm, &SrcResult) != ModNone)
        {
          BAsmCode[0] = 0x9e;
          BAsmCode[1] = SrcResult.Val;
          CodeLen = 2;
        }
        break;
      }
      case ModBInc:
      {
        tAdrResult SrcResult;

        if (DecodeAdr(&ArgStr[2], MModImm, &SrcResult) != ModNone)
        {
          BAsmCode[0] = 0x9a;
          BAsmCode[1] = SrcResult.Val;
          CodeLen = 2;
        }
        break;
      }
      case ModBDec:
      {
        tAdrResult SrcResult;

        if (DecodeAdr(&ArgStr[2], MModImm, &SrcResult) != ModNone)
        {
          BAsmCode[0] = 0x9b;
          BAsmCode[1] = SrcResult.Val;
          CodeLen = 2;
        }
        break;
      }
    }
  }
}

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

  if (ChkArgCnt(2, 2))
  {
    tStrComp *pAccArg, *pMemArg;
    tAdrResult DestResult;

    if (as_strcasecmp(ArgStr[1].str.p_str, "A"))
    {
      pAccArg = &ArgStr[2];
      pMemArg = &ArgStr[1];
    }
    else
    {
      pAccArg = &ArgStr[1];
      pMemArg = &ArgStr[2];
    }
    if (DecodeAdr(pAccArg, MModAcc, &DestResult) != ModNone)
    {
      tAdrResult SrcResult;

      switch (DecodeAdr(pMemArg, MModDir | MModBInd | MModXInd | MModBInc | MModXInc | MModBDec | MModXDec, &SrcResult))
      {
        case ModDir:
          BAsmCode[0] = 0x9c;
          BAsmCode[1] = SrcResult.Val;
          CodeLen = 2;
          break;
        case ModBInd:
          BAsmCode[0] = 0xa6;
          CodeLen = 1;
          break;
        case ModBInc:
          BAsmCode[0] = 0xa2;
          CodeLen = 1;
          break;
        case ModBDec:
          BAsmCode[0] = 0xa3;
          CodeLen = 1;
          break;
        case ModXInd:
          BAsmCode[0] = 0xb6;
          CodeLen = 1;
          break;
        case ModXInc:
          BAsmCode[0] = 0xb2;
          CodeLen = 1;
          break;
        case ModXDec:
          BAsmCode[0] = 0xb3;
          CodeLen = 1;
          break;
      }
    }
  }
}

static void DecodeAcc(Word Code)
{
  if (ChkArgCnt(1, 1))
  {
    tAdrResult DestResult;

    if (DecodeAdr(&ArgStr[1], MModAcc, &DestResult) == ModAcc)
    {
      BAsmCode[0] = Code;
      CodeLen = 1;
    }
  }
}

static void DecodeAccMem(Word Code)
{
  if (ChkArgCnt(2, 2))
  {
    tAdrResult DestResult;

    if (DecodeAdr(&ArgStr[1], MModAcc, &DestResult) != ModNone)
    {
      tAdrResult SrcResult;

      switch (DecodeAdr(&ArgStr[2], MModDir | MModImm | MModBInd, &SrcResult))
      {
        case ModBInd:
          BAsmCode[0] = Code;
          CodeLen = 1;
          break;
        case ModImm:
          BAsmCode[0] = Code + 0x10;
          BAsmCode[1] = SrcResult.Val;
          CodeLen = 2;
          break;
        case ModDir:
          BAsmCode[0] = DirPrefix;
          BAsmCode[1] = SrcResult.Val;
          BAsmCode[2] = Code;
          CodeLen = 3;
          break;
      }
    }
  }
}

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

  if (ChkArgCnt(2, 2))
  {
    tAdrResult DestResult;

    if (DecodeAdr(&ArgStr[1], MModAcc, &DestResult) != ModNone)
    {
      tAdrResult SrcResult;

      if (DecodeAdr(&ArgStr[2], MModImm, &SrcResult) == ModImm)
      {
        BAsmCode[0] = 0x60;
        BAsmCode[1] = SrcResult.Val;
        CodeLen = 2;
      }
    }
  }
}

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

  if (ChkArgCnt(2, 2))
  {
    tAdrResult DestResult;

    switch (DecodeAdr(&ArgStr[1], MModAcc | MModDir, &DestResult))
    {
      case ModAcc:
      {
        tAdrResult SrcResult;

        switch (DecodeAdr(&ArgStr[2], MModDir | MModBInd | MModImm, &SrcResult))
        {
          case ModDir:
            BAsmCode[0] = DirPrefix;
            BAsmCode[1] = SrcResult.Val;
            BAsmCode[2] = 0x82;
            CodeLen = 3;
            break;
          case ModBInd:
            BAsmCode[0] = 0x82;
            CodeLen = 1;
            break;
          case ModImm:
            BAsmCode[0] = 0x92;
            BAsmCode[1] = SrcResult.Val;
            CodeLen = 2;
            break;
        }
        break;
      }
      case ModDir:
      {
        tAdrResult SrcResult;

        BAsmCode[1] = DestResult.Val;
        if (DecodeAdr(&ArgStr[2], MModImm, &SrcResult) == ModImm)
        {
          BAsmCode[0] = 0xa9;
          BAsmCode[2] = SrcResult.Val;
          CodeLen = 3;
        }
        break;
      }
    }
  }
}

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

  if (ChkArgCnt(2, 2))
  {
    tAdrResult DestResult;

    switch (DecodeAdr(&ArgStr[1], MModAcc, &DestResult))
    {
      case ModAcc:
      {
        tAdrResult SrcResult;

        switch (DecodeAdr(&ArgStr[2], MModDir | MModBInd | MModImm, &SrcResult))
        {
          case ModDir:
            BAsmCode[0] = DirPrefix;
            BAsmCode[1] = SrcResult.Val;
            BAsmCode[2] = 0xb9;
            CodeLen = 3;
            break;
          case ModBInd:
            BAsmCode[0] = 0xb9;
            CodeLen = 1;
            break;
          case ModImm:
            BAsmCode[0] = 0x99;
            BAsmCode[1] = SrcResult.Val;
            CodeLen = 2;
            break;
        }
        break;
      }
    }
  }
}

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

  if (!ChkArgCnt(1, 1));
  else if (*ArgStr[1].str.p_str != '#') WrError(ErrNum_InvAddrMode);
  else
  {
    Boolean OK;

    BAsmCode[0] = EvalStrIntExpressionOffs(&ArgStr[1], 1, UInt4, &OK);
    if (OK)
    {
      BAsmCode[0] += 0x40;
      CodeLen = 1;
    }
  }
}

static void DecodeBit(Word Code)
{
  if (ChkArgCnt(2, 2))
  {
    Boolean OK;

    Byte HReg = EvalStrIntExpression(&ArgStr[1], UInt3, &OK);
    if (OK)
    {
      tAdrResult DestResult;

      switch (DecodeAdr(&ArgStr[2], MModDir | MModBInd, &DestResult))
      {
        case ModDir:
          BAsmCode[0] = DirPrefix;
          BAsmCode[1] = DestResult.Val;
          BAsmCode[2] = Code + HReg;
          CodeLen = 3;
          break;
        case ModBInd:
          BAsmCode[0] = Code + HReg;
          CodeLen = 1;
          break;
      }
    }
  }
}

static void DecodeJMP_JSR(Word Code)
{
  if (ChkArgCnt(1, 1))
  {
    tEvalResult EvalResult;
    Word AdrWord = EvalStrIntExpressionWithResult(&ArgStr[1], UInt16, &EvalResult);

    if (EvalResult.OK && ChkSamePage(EProgCounter() + 2, AdrWord, 12, EvalResult.Flags))
    {
      ChkSpace(SegCode, EvalResult.AddrSpaceMask);
      BAsmCode[0] = 0x20 + Code + ((AdrWord >> 8) & 15);
      BAsmCode[1] = Lo(AdrWord);
      CodeLen = 2;
    }
  }
}

static void DecodeJMPL_JSRL(Word Code)
{
  if (ChkArgCnt(1, 1))
  {
    tEvalResult EvalResult;
    Word AdrWord = EvalStrIntExpressionWithResult(&ArgStr[1], UInt16, &EvalResult);

    if (EvalResult.OK)
    {
      ChkSpace(SegCode, EvalResult.AddrSpaceMask);
      BAsmCode[0] = Code;
      BAsmCode[1] = Hi(AdrWord);
      BAsmCode[2] = Lo(AdrWord);
      CodeLen = 3;
    }
  }
}

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

  if (ChkArgCnt(1, 1))
  {
    Boolean OK;
    tSymbolFlags Flags;

    Integer AdrInt = EvalStrIntExpressionWithFlags(&ArgStr[1], UInt16, &OK, &Flags) - (EProgCounter() + 1);
    if (OK)
    {
      if (AdrInt == 0)
      {
        BAsmCode[0] = NOPCode;
        CodeLen = 1;
        WrError(ErrNum_DistNull);
      }
      else if (((AdrInt > 31) || (AdrInt < -32)) && !mSymbolQuestionable(Flags)) WrError(ErrNum_JmpDistTooBig);
      else
      {
        BAsmCode[0] = AdrInt & 0xff;
        CodeLen = 1;
      }
    }
  }
  return;
}

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

  if (ChkArgCnt(1, 1))
  {
    tAdrResult DestResult;

    if (DecodeAdr(&ArgStr[1], MModDir, &DestResult) == ModDir)
    {
      if (mFirstPassUnknown(DestResult.ValSymFlags))
        DestResult.Val |= 0xf0;
      if (DestResult.Val < 0xf0) WrError(ErrNum_UnderRange);
      else
      {
        BAsmCode[0] = DestResult.Val - 0x30;
        CodeLen = 1;
      }
    }
  }
}

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

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

static void AddAcc(const char *NName, Byte NCode)
{
  AddInstTable(InstTable, NName, NCode, DecodeAcc);
}

static void AddAccMem(const char *NName, Byte NCode)
{
  AddInstTable(InstTable, NName, NCode, DecodeAccMem);
}

static void AddBit(const char *NName, Byte NCode)
{
  AddInstTable(InstTable, NName, NCode, DecodeBit);
}

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

  add_null_pseudo(InstTable);

  AddInstTable(InstTable, "LD", 0, DecodeLD);
  AddInstTable(InstTable, "X", 0, DecodeX);
  AddInstTable(InstTable, "ANDSZ", 0, DecodeANDSZ);
  AddInstTable(InstTable, "IFEQ", 0, DecodeIFEQ);
  AddInstTable(InstTable, "IFNE", 0, DecodeIFNE);
  AddInstTable(InstTable, "IFBNE", 0, DecodeIFBNE);
  AddInstTable(InstTable, "JMP", 0, DecodeJMP_JSR);
  AddInstTable(InstTable, "JSR", 0x10, DecodeJMP_JSR);
  AddInstTable(InstTable, "JMPL", 0xac, DecodeJMPL_JSRL);
  AddInstTable(InstTable, "JSRL", 0xad, DecodeJMPL_JSRL);
  AddInstTable(InstTable, "JP", 0, DecodeJP);
  AddInstTable(InstTable, "DRSZ", 0, DecodeDRSZ);

  AddFixed("LAID" , 0xa4);  AddFixed("SC"   , 0xa1);  AddFixed("RC"   , 0xa0);
  AddFixed("IFC"  , 0x88);  AddFixed("IFNC" , 0x89);  AddFixed("VIS"  , 0xb4);
  AddFixed("JID"  , 0xa5);  AddFixed("RET"  , 0x8e);  AddFixed("RETSK", 0x8d);
  AddFixed("RETI" , 0x8f);  AddFixed("INTR" , 0x00);  AddFixed("NOP"  , 0xb8);
  AddFixed("RPND" , 0xb5);

  AddAcc("CLR"  , 0x64);  AddAcc("INC"  , 0x8a);  AddAcc("DEC"  , 0x8b);
  AddAcc("DCOR" , 0x66);  AddAcc("RRC"  , 0xb0);  AddAcc("RLC"  , 0xa8);
  AddAcc("SWAP" , 0x65);  AddAcc("POP"  , 0x8c);  AddAcc("PUSH" , 0x67);

  AddAccMem("ADD"  , 0x84);  AddAccMem("ADC"  , 0x80);  AddAccMem("SUBC" , 0x81);
  AddAccMem("AND"  , 0x85);  AddAccMem("OR"   , 0x87);  AddAccMem("XOR"  , 0x86);
  AddAccMem("IFGT" , 0x83);

  AddBit("IFBIT", 0x70); AddBit("SBIT", 0x78); AddBit("RBIT", 0x68);

  AddNatPseudo(InstTable);
  AddIntelPseudo(InstTable, eIntPseudoFlag_LittleEndian);
}

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

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

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

static Boolean IsDef_COP8(void)
{
  return (Memo("SFR"));
}

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

static void SwitchTo_COP8(void)
{
  const TFamilyDescr *p_descr = FindFamilyByName("COP8");

  TurnWords = False;
  SetIntConstMode(eIntConstModeC);

  PCSymbol = ".";
  HeaderID = p_descr->Id;
  NOPCode = 0xb8;
  DivideChars = ",";
  HasAttrs = False;

  ValidSegs = (1 << SegCode) | (1 << SegData);
  Grans[SegCode] = 1;
  ListGrans[SegCode] = 1;
  SegInits[SegCode]  =0;
  SegLimits[SegCode] = 0x1fff;
  Grans[SegData] = 1;
  ListGrans[SegData] = 1;
  SegInits[SegData] = 0;
  SegLimits[SegData] = 0xff;

  MakeCode = MakeCode_COP8;
  IsDef = IsDef_COP8;
  SwitchFrom = SwitchFrom_COP8;
  InitFields();
}

void codecop8_init(void)
{
  CPUCOP87L84 = AddCPU("COP87L84", SwitchTo_COP8);
}
