/* code6804.c */
/*****************************************************************************/
/* SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only                     */
/*                                                                           */
/* AS-Portierung                                                             */
/*                                                                           */
/* AS-Codegenerator Motorola/ST 6804                                         */
/*                                                                           */
/*****************************************************************************/

#include "stdinc.h"

#include <string.h>

#include "bpemu.h"
#include "strutil.h"
#include "asmdef.h"
#include "asmsub.h"
#include "asmpars.h"
#include "asmitree.h"
#include "codepseudo.h"
#include "motpseudo.h"
#include "intpseudo.h"
#include "codevars.h"
#include "errmsg.h"
#include "headids.h"

#include "code6804.h"

typedef struct
{
  char *Name;
  LongInt Code;
} BaseOrder;

enum
{
  ModNone = -1,
  ModInd = 0,
  ModDir = 1,
  ModImm = 2
};

#define MModInd (1 << ModInd)
#define MModDir (1 << ModDir)
#define MModImm (1 << ModImm)

static ShortInt AdrMode;
static Byte AdrVal;

static CPUVar CPU6804;

static BaseOrder *FixedOrders;

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

static void DecodeAdr(const tStrComp *pArg, Boolean MayImm)
{
  Boolean OK;

  AdrMode = ModNone;

  if (!as_strcasecmp(pArg->str.p_str, "(X)"))
  {
    AdrMode = ModInd;
    AdrVal = 0x00;
    goto chk;
  }
  if (!as_strcasecmp(pArg->str.p_str, "(Y)"))
  {
    AdrMode = ModInd;
    AdrVal = 0x10;
    goto chk;
  }

  if (*pArg->str.p_str == '#')
  {
    AdrVal = EvalStrIntExpressionOffs(pArg, 1, Int8, &OK);
    if (OK)
      AdrMode = ModImm;
    goto chk;
  }

  AdrVal = EvalStrIntExpression(pArg, Int8, &OK);
  if (OK)
    AdrMode = ModDir;

chk:
  if ((AdrMode == ModImm) && (!MayImm))
  {
    WrError(ErrNum_InvAddrMode); AdrMode = ModNone;
  }
}

static Boolean IsShort(Byte Adr)
{
  return ((Adr & 0xfc) == 0x80);
}

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

/* Anweisungen ohne Argument */

static void DecodeFixed(Word Index)
{
  const BaseOrder *pOrder = FixedOrders + Index;

  if (ChkArgCnt(0, 0))
  {
    if ((pOrder->Code >> 16) != 0)
      CodeLen = 3;
    else
      CodeLen = 1 + Ord(Hi(pOrder->Code) != 0);
    if (CodeLen == 3)
      BAsmCode[0] = pOrder->Code >> 16;
    if (CodeLen >= 2)
      BAsmCode[CodeLen - 2] = Hi(pOrder->Code);
    BAsmCode[CodeLen - 1] = Lo(pOrder->Code);
  }
}

/* relative/absolute Spruenge */

static void DecodeRel(Word Code)
{
  if (ChkArgCnt(1, 1))
  {
    tEvalResult EvalResult;
    Integer AdrInt = EvalStrIntExpressionWithResult(&ArgStr[1], Int16, &EvalResult) - (EProgCounter() + 1);

    if (EvalResult.OK)
    {
      if (!mSymbolQuestionable(EvalResult.Flags) && ((AdrInt < -16) || (AdrInt > 15))) WrError(ErrNum_JmpDistTooBig);
      else
      {
        CodeLen = 1;
        BAsmCode[0] = Code + (AdrInt & 0x1f);
        ChkSpace(SegCode, EvalResult.AddrSpaceMask);
      }
    }
  }
}

static void DecodeJSR_JMP(Word Code)
{
  if (ChkArgCnt(1, 1))
  {
    tEvalResult EvalResult;
    Word AdrInt = EvalStrIntExpressionWithResult(&ArgStr[1], UInt12, &EvalResult);

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

/* AKKU-Operationen */

static void DecodeALU(Word Code)
{
  if (ChkArgCnt(1, 1))
  {
    DecodeAdr(&ArgStr[1], True);
    switch (AdrMode)
    {
      case ModInd:
        CodeLen = 1;
        BAsmCode[0] = 0xe0 + AdrVal + Code;
        break;
      case ModDir:
        CodeLen = 2;
        BAsmCode[0] = 0xf8 + Code;
        BAsmCode[1] = AdrVal;
        break;
      case ModImm:
        CodeLen = 2;
        BAsmCode[0] = 0xe8 + Code;
        BAsmCode[1] = AdrVal;
        break;
    }
  }
}

/* Datentransfer */

static void DecodeLDA_STA(Word Code)
{
  if (ChkArgCnt(1, 1))
  {
    DecodeAdr(&ArgStr[1], !Code);
    switch (AdrMode)
    {
      case ModInd:
        CodeLen = 1;
        BAsmCode[0] = 0xe0 + Code + AdrVal;
        break;
      case ModDir:
        if (IsShort(AdrVal))
        {
          CodeLen = 1;
          BAsmCode[0] = 0xac + (Code << 4) + (AdrVal & 3);
        }
        else
        {
          CodeLen = 2;
          BAsmCode[0] = 0xf8 + Code;
          BAsmCode[1] = AdrVal;
        }
        break;
      case ModImm:
        CodeLen = 2;
        BAsmCode[0] = 0xe8 + Code;
        BAsmCode[1] = AdrVal;
        break;
    }
  }
}

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

    BAsmCode[2] = EvalStrIntExpressionOffs(&ArgStr[1], 1, Int8, &OK);
    if (OK)
    {
      CodeLen = 3;
      BAsmCode[0] = 0xb0;
      BAsmCode[1] = Code;
    }
  }
}

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

  if (!ChkArgCnt(2, 2));
  else if (*ArgStr[2].str.p_str != '#') WrError(ErrNum_InvAddrMode);
  else
  {
    tEvalResult EvalResult;

    BAsmCode[1] = EvalStrIntExpressionWithResult(&ArgStr[1], Int8, &EvalResult);
    if (EvalResult.OK)
    {
      ChkSpace(SegData, EvalResult.AddrSpaceMask);
      BAsmCode[2] = EvalStrIntExpressionOffsWithResult(&ArgStr[2], 1, Int8, &EvalResult);
      if (EvalResult.OK)
      {
        BAsmCode[0] = 0xb0;
        CodeLen = 3;
      }
    }
  }
}

/* Read/Modify/Write-Operationen */

static void DecodeINC_DEC(Word Code)
{
  if (ChkArgCnt(1, 1))
  {
    DecodeAdr(&ArgStr[1], False);
    switch (AdrMode)
    {
      case ModInd:
        CodeLen = 1;
        BAsmCode[0] = 0xe6 + Code + AdrVal;
        break;
      case ModDir:
        if (IsShort(AdrVal))
        {
          CodeLen = 1;
          BAsmCode[0] = 0xa8 + (Code << 4) + (AdrVal & 3);
        }
        else
        {
          CodeLen = 2;
          BAsmCode[0] = 0xfe + Code;
          BAsmCode[1] = AdrVal;
        }
        break;
    }
  }
}

/* Bitbefehle */

static void DecodeBSET_BCLR(Word Code)
{
  if (ChkArgCnt(2, 2))
  {
    tEvalResult EvalResult;
    Byte Bit = EvalStrIntExpressionWithResult(&ArgStr[1], UInt3, &EvalResult);
    if (EvalResult.OK)
    {
      BAsmCode[0] = Code + Bit;
      BAsmCode[1] = EvalStrIntExpressionWithResult(&ArgStr[2], Int8, &EvalResult);
      if (EvalResult.OK)
      {
        CodeLen = 2;
        ChkSpace(SegData, EvalResult.AddrSpaceMask);
      }
    }
  }
}

static void DecodeBRSET_BRCLR(Word Code)
{
  if (ChkArgCnt(3, 3))
  {
    tEvalResult EvalResult;
    Byte Bit = EvalStrIntExpressionWithResult(&ArgStr[1], UInt3, &EvalResult);

    if (EvalResult.OK)
    {
      BAsmCode[0] = Code + Bit;
      BAsmCode[1] = EvalStrIntExpressionWithResult(&ArgStr[2], Int8, &EvalResult);
      if (EvalResult.OK)
      {
        Integer AdrInt;

        ChkSpace(SegData, EvalResult.AddrSpaceMask);
        AdrInt = EvalStrIntExpressionWithResult(&ArgStr[3], Int16, &EvalResult) - (EProgCounter() + 3);
        if (EvalResult.OK)
        {
          if (!mSymbolQuestionable(EvalResult.Flags) && ((AdrInt < -128) || (AdrInt > 127))) WrError(ErrNum_JmpDistTooBig);
          else
          {
            ChkSpace(SegCode, EvalResult.AddrSpaceMask);
            BAsmCode[2] = AdrInt & 0xff;
            CodeLen = 3;
          }
        }
      }
    }
  }
}

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

  code_equate_type(SegData, UInt8);
}

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

static void AddFixed(const char *NName, LongInt NCode)
{
  order_array_rsv_end(FixedOrders, BaseOrder);
  FixedOrders[InstrZ].Code = NCode;
  AddInstTable(InstTable, NName, InstrZ++, DecodeFixed);
}

static void AddRel(const char *NName, LongInt NCode)
{
  AddInstTable(InstTable, NName, NCode, DecodeRel);
}

static void AddALU(const char *NName, LongInt NCode)
{
  AddInstTable(InstTable, NName, NCode, DecodeALU);
}

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

  add_null_pseudo(InstTable);

  AddInstTable(InstTable, "JMP", 0x90, DecodeJSR_JMP);
  AddInstTable(InstTable, "JSR", 0x80, DecodeJSR_JMP);
  AddInstTable(InstTable, "LDA", 0, DecodeLDA_STA);
  AddInstTable(InstTable, "STA", 1, DecodeLDA_STA);
  AddInstTable(InstTable, "LDXI", 0x80, DecodeLDXI_LDYI);
  AddInstTable(InstTable, "LDYI", 0x81, DecodeLDXI_LDYI);
  AddInstTable(InstTable, "MVI", 0, DecodeMVI);
  AddInstTable(InstTable, "INC", 0, DecodeINC_DEC);
  AddInstTable(InstTable, "DEC", 1, DecodeINC_DEC);
  AddInstTable(InstTable, "BSET", 0xd8, DecodeBSET_BCLR);
  AddInstTable(InstTable, "BCLR", 0xd0, DecodeBSET_BCLR);
  AddInstTable(InstTable, "BRSET", 0xc8, DecodeBRSET_BRCLR);
  AddInstTable(InstTable, "BRCLR", 0xc0, DecodeBRSET_BRCLR);
  AddInstTable(InstTable, "SFR", 0, DecodeSFR);

  InstrZ = 0;
  AddFixed("CLRA", 0x00fbff);
  AddFixed("CLRX", 0xb08000);
  AddFixed("CLRY", 0xb08100);
  AddFixed("COMA", 0x0000b4);
  AddFixed("ROLA", 0x0000b5);
  AddFixed("ASLA", 0x00faff);
  AddFixed("INCA", 0x00feff);
  AddFixed("INCX", 0x0000a8);
  AddFixed("INCY", 0x0000a9);
  AddFixed("DECA", 0x00ffff);
  AddFixed("DECX", 0x0000b8);
  AddFixed("DECY", 0x0000b9);
  AddFixed("TAX" , 0x0000bc);
  AddFixed("TAY" , 0x0000bd);
  AddFixed("TXA" , 0x0000ac);
  AddFixed("TYA" , 0x0000ad);
  AddFixed("RTS" , 0x0000b3);
  AddFixed("RTI" , 0x0000b2);
  AddFixed("NOP" , 0x000020);

  AddRel("BCC", 0x40);
  AddRel("BHS", 0x40);
  AddRel("BCS", 0x60);
  AddRel("BLO", 0x60);
  AddRel("BNE", 0x00);
  AddRel("BEQ", 0x20);

  AddALU("ADD", 0x02);
  AddALU("SUB", 0x03);
  AddALU("CMP", 0x04);
  AddALU("AND", 0x05);

  add_moto8_pseudo(InstTable, e_moto_pseudo_flags_be | e_moto_pseudo_flags_ds);
  AddInstTable(InstTable, "DB", eIntPseudoFlag_BigEndian | eIntPseudoFlag_AllowInt | eIntPseudoFlag_AllowString | eIntPseudoFlag_MotoRep, DecodeIntelDB);
  AddInstTable(InstTable, "DW", eIntPseudoFlag_BigEndian | eIntPseudoFlag_AllowInt | eIntPseudoFlag_AllowString | eIntPseudoFlag_MotoRep, DecodeIntelDW);
}

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

  order_array_free(FixedOrders);
}

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

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

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

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

static void SwitchTo_6804(void)
{
  const TFamilyDescr *p_descr = FindFamilyByName("6804");

  TurnWords = False;
  SetIntConstMode(eIntConstModeMoto);

  PCSymbol = "PC";
  HeaderID = p_descr->Id;
  NOPCode = 0x20;
  DivideChars = ",";
  HasAttrs = False;

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

  MakeCode = MakeCode_6804;
  IsDef = IsDef_6804;
  SwitchFrom = SwitchFrom_6804;
  InitFields();
}

void code6804_init(void)
{
  CPU6804 = AddCPU("6804", SwitchTo_6804);
}
