/* codesx20.c */
/*****************************************************************************/
/* SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only                     */
/*                                                                           */
/* AS-Portierung                                                             */
/*                                                                           */
/* Code Generator Parallax SX20                                              */
/*                                                                           */
/*****************************************************************************/

#include "stdinc.h"

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

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

#include "codef8.h"

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

static CPUVar CPUSX20, CPUSX28;

static LongInt Reg_FSR, Reg_STATUS;

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

static Boolean DecodeRegLinear(const tStrComp *pArg, int Offset, Word *pResult, tEvalResult *pEvalResult)
{
  *pResult = EvalStrIntExpressionOffsWithResult(pArg, Offset, UInt8, pEvalResult);
  if (pEvalResult->OK)
    ChkSpace(SegData, pEvalResult->AddrSpaceMask);
  return pEvalResult->OK;
}

static Word Lin2PagedRegAddr(const tStrComp *pArg, Word LinAddr, tSymbolFlags Flags)
{
  if (!mFirstPassUnknown(Flags) && (LinAddr & 0x10))
  {
    if ((Reg_FSR & 0xf0) != (LinAddr & 0xf0))
      WrStrErrorPos(ErrNum_InAccPage, pArg);
  }
  return LinAddr & 0x1f;
}

static Boolean DecodeReg(const tStrComp *pArg, int Offset, Word *pResult)
{
  tEvalResult EvalResult;

  if (!DecodeRegLinear(pArg, Offset, pResult, &EvalResult))
    return False;
  *pResult = Lin2PagedRegAddr(pArg, *pResult, EvalResult.Flags);
  return True;
}

static Boolean DecodeRegAppendix(tStrComp *pArg, const char *pAppendix, Word *pResult)
{
  int ArgLen = strlen(pArg->str.p_str), AppLen = strlen(pAppendix);
  Boolean Result = False;

  if ((ArgLen > AppLen) && !as_strcasecmp(pArg->str.p_str + (ArgLen - AppLen), pAppendix))
  {
    char Save = pArg->str.p_str[ArgLen - AppLen];

    pArg->str.p_str[ArgLen - AppLen] = '\0';
    pArg->Pos.Len -= AppLen;
    Result = DecodeReg(pArg, 0, pResult);
    pArg->str.p_str[ArgLen - AppLen] = Save;
    pArg->Pos.Len += AppLen;
  }
  return Result;
}

/* NOTE: bit symbols have bit position in lower three bits, opposed
   to machine coding.  Done this way because incrementing makes more
   sense this way. */

static Word AssembleBitSymbol(Word RegAddr, Word BitPos)
{
  return ((RegAddr & 0xff) << 3) | (BitPos & 7);
}

static void SplitBitSymbol(Word BitSymbol, Word *pRegAddr, Word *pBitPos)
{
  *pRegAddr = (BitSymbol >> 3) & 0xff;
  *pBitPos = BitSymbol & 7;
}

/*!------------------------------------------------------------------------
 * \fn     DissectBit_SX20(char *pDest, size_t DestSize, LargeWord Inp)
 * \brief  dissect compact storage of bit into readable form for listing
 * \param  pDest destination for ASCII representation
 * \param  DestSize destination buffer size
 * \param  Inp compact storage
 * ------------------------------------------------------------------------ */

static void DissectBit_SX20(char *pDest, size_t DestSize, LargeWord Inp)
{
  Word BitPos, Address;

  SplitBitSymbol(Inp, &Address, &BitPos);

  as_snprintf(pDest, DestSize, "$%x.%u", (unsigned)Address, (unsigned)BitPos);
}

/*!------------------------------------------------------------------------
 * \fn     DecodeBitSymbol(const tStrComp *pArg, Word *pBitSymbol)
 * \brief  decode a bit expression and return in internal format
 * \param  pArg bit spec in source code
 * \param  pBitSymbol returns bit in internal format
 * \return True if success
 * ------------------------------------------------------------------------ */

static Boolean DecodeBitSymbol(const tStrComp *pArg, Word *pBitSymbol, tEvalResult *pEvalResult)
{
  char *pSplit;

  pSplit = strchr(pArg->str.p_str, '.');
  if (!pSplit)
  {
    *pBitSymbol = EvalStrIntExpressionWithResult(pArg, UInt11, pEvalResult);
    if (pEvalResult->OK)
      ChkSpace(SegBData, pEvalResult->AddrSpaceMask);
    return pEvalResult->OK;
  }
  else
  {
    tStrComp RegArg, BitArg;
    Boolean BitOK, RegOK;
    Word BitPos, RegAddr;

    StrCompSplitRef(&RegArg, &BitArg, pArg, pSplit);
    BitPos = EvalStrIntExpression(&BitArg, UInt3, &BitOK);
    RegOK = DecodeRegLinear(&RegArg, 0, &RegAddr, pEvalResult);
    *pBitSymbol = AssembleBitSymbol(RegAddr, BitPos);
    return BitOK && RegOK;
  }
}

static Boolean DecodeRegBitPacked(const tStrComp *pArg, Word *pPackedResult)
{
  Word BitSymbol, RegAddr, BitPos;
  tEvalResult EvalResult;

  if (!DecodeBitSymbol(pArg, &BitSymbol, &EvalResult))
    return False;
  SplitBitSymbol(BitSymbol, &RegAddr, &BitPos);
  *pPackedResult = (BitPos << 5) | Lin2PagedRegAddr(pArg, RegAddr, EvalResult.Flags);
  return True;
}

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

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

static void DecodeOneReg(Word Code)
{
  Word Reg;

  if (ChkArgCnt(1, 1)
   && DecodeReg(&ArgStr[1], 0, &Reg))
    WAsmCode[CodeLen++] = Code | Reg;
}

static void DecodeNOT(Word Code)
{
  if (!ChkArgCnt(1, 1))
    return;

  if (!as_strcasecmp(ArgStr[1].str.p_str, "W"))
    WAsmCode[CodeLen++] = 0xfff;
  else
    DecodeOneReg(Code);
}

static void DecodeMOV(Word Code)
{
  Word Reg;
  Boolean OK;

  UNUSED(Code);

  if (!ChkArgCnt(2, 2))
    return;

  if (!as_strcasecmp(ArgStr[1].str.p_str, "W"))
  {
    if (*ArgStr[2].str.p_str == '#')
    {
      Reg = EvalStrIntExpressionOffs(&ArgStr[2], 1, Int8, &OK);
      if (OK)
        WAsmCode[CodeLen++] = 0xc00 | (Reg & 0xff);
    }
    else if (!as_strcasecmp(ArgStr[2].str.p_str, "M"))
      WAsmCode[CodeLen++] = 0x042;
    else if (!strncmp(ArgStr[2].str.p_str, "/", 1) && DecodeReg(&ArgStr[2], 1, &Reg))
      WAsmCode[CodeLen++] = 0x240 | Reg;
    else if (!strncmp(ArgStr[2].str.p_str, "--", 2) && DecodeReg(&ArgStr[2], 2, &Reg))
      WAsmCode[CodeLen++] = 0x0c0 | Reg;
    else if (!strncmp(ArgStr[2].str.p_str, "++", 2) && DecodeReg(&ArgStr[2], 2, &Reg))
      WAsmCode[CodeLen++] = 0x280 | Reg;
    else if (!strncmp(ArgStr[2].str.p_str, "<<", 2) && DecodeReg(&ArgStr[2], 2, &Reg))
      WAsmCode[CodeLen++] = 0x340 | Reg;
    else if (!strncmp(ArgStr[2].str.p_str, ">>", 2) && DecodeReg(&ArgStr[2], 2, &Reg))
      WAsmCode[CodeLen++] = 0x300 | Reg;
    else if (!strncmp(ArgStr[2].str.p_str, "<>", 2) && DecodeReg(&ArgStr[2], 2, &Reg))
      WAsmCode[CodeLen++] = 0x380 | Reg;
    else if (DecodeRegAppendix(&ArgStr[2], "-W", &Reg))
      WAsmCode[CodeLen++] = 0x080 | Reg;
    else if (DecodeReg(&ArgStr[2], 0, &Reg))
      WAsmCode[CodeLen++] = 0x200 | Reg;
  }
  else if (!as_strcasecmp(ArgStr[2].str.p_str, "W"))
  {
    if (!as_strcasecmp(ArgStr[1].str.p_str, "!OPTION"))
      WAsmCode[CodeLen++] = 0x002;
    else if (!as_strcasecmp(ArgStr[1].str.p_str, "M"))
      WAsmCode[CodeLen++] = 0x003;
    else if (!strncmp(ArgStr[1].str.p_str, "!", 1) && DecodeReg(&ArgStr[1], 1, &Reg))
    {
      if (ChkRange(Reg, 0, 7))
        WAsmCode[CodeLen++] = 0x000 | Reg;
    }
    else if (DecodeReg(&ArgStr[1], 0, &Reg))
      WAsmCode[CodeLen++] = 0x020 | Reg;
  }
  else if (!as_strcasecmp(ArgStr[1].str.p_str, "M"))
  {
    if (*ArgStr[2].str.p_str == '#')
    {
      Reg = EvalStrIntExpressionOffs(&ArgStr[2], 1, Int4, &OK);
      if (OK)
        WAsmCode[CodeLen++] = 0x050 | (Reg & 0xf);
    }
    else
      WrStrErrorPos(ErrNum_InvAddrMode, &ArgStr[2]);
  }
  else
    WrError(ErrNum_InvAddrMode);
}

static void DecodeMOVSZ(Word Code)
{
  Word Reg;

  UNUSED(Code);

  if (!ChkArgCnt(2, 2))
    return;

  if (!as_strcasecmp(ArgStr[1].str.p_str, "W"))
  {
    if (!strncmp(ArgStr[2].str.p_str, "--", 2) && DecodeReg(&ArgStr[2], 2, &Reg))
      WAsmCode[CodeLen++] = 0x2c0 | Reg;
    else if (!strncmp(ArgStr[2].str.p_str, "++", 2) && DecodeReg(&ArgStr[2], 2, &Reg))
      WAsmCode[CodeLen++] = 0x3c0 | Reg;
    else
      WrStrErrorPos(ErrNum_InvAddrMode, &ArgStr[2]);
  }
  else
    WrStrErrorPos(ErrNum_InvAddrMode, &ArgStr[1]);
}

static void DecodeLogic(Word Code)
{
  Word Arg;

  if (!ChkArgCnt(2, 2))
    return;

  if (!as_strcasecmp(ArgStr[1].str.p_str, "W"))
  {
    if (*ArgStr[2].str.p_str == '#')
    {
      Word ImmCode = Code & 0x0f00;

      if (ImmCode)
      {
        Boolean OK;

        Arg = EvalStrIntExpressionOffs(&ArgStr[2], 1, Int8, &OK);
        if (OK)
          WAsmCode[CodeLen++] = ImmCode | (Arg & 0xff);
      }
      else
        WrStrErrorPos(ErrNum_InvAddrMode, &ArgStr[2]);
    }
    else if (DecodeReg(&ArgStr[2], 0, &Arg))
      WAsmCode[CodeLen++] = (Lo(Code) << 4) | Arg;
  }
  else if (!as_strcasecmp(ArgStr[2].str.p_str, "W"))
  {
    if (DecodeReg(&ArgStr[1], 0, &Arg))
      WAsmCode[CodeLen++] = (Lo(Code) << 4) | 0x20 | Arg;
  }
  else
    WrError(ErrNum_InvAddrMode);
}

static void DecodeCLR(Word Code)
{
  Word Arg;

  UNUSED(Code);

  if (!ChkArgCnt(1, 1))
    return;

  if (!as_strcasecmp(ArgStr[1].str.p_str, "W"))
    WAsmCode[CodeLen++] = 0x040;
  else if (!as_strcasecmp(ArgStr[1].str.p_str, "!WDT"))
    WAsmCode[CodeLen++] = 0x004;
  else if (DecodeReg(&ArgStr[1], 0, &Arg))
    WAsmCode[CodeLen++] = 0x060 | Arg;
}

static void DecodeSUB(Word Code)
{
  Word Arg;

  if (!ChkArgCnt(2, 2))
    return;

  if (!as_strcasecmp(ArgStr[2].str.p_str, "W"))
  {
    if (DecodeReg(&ArgStr[1], 0, &Arg))
      WAsmCode[CodeLen++] = (Lo(Code) << 4) | 0x20 | Arg;
  }
  else
    WrError(ErrNum_InvAddrMode);
}

static void DecodeBit(Word Code)
{
  Word BitArg;

  if (!ChkArgCnt(1, 1))
    return;

  if (DecodeRegBitPacked(&ArgStr[1], &BitArg))
    WAsmCode[CodeLen++] = Code | BitArg;
}

static void DecodeJMP_CALL(Word Code)
{
  Word Addr;
  Boolean IsCall = (Code == 0x900);
  tEvalResult EvalResult;

  if (!ChkArgCnt(1, 1))
    return;

  if (!IsCall)
  {
    if (!as_strcasecmp(ArgStr[1].str.p_str, "W"))
    {
      WAsmCode[CodeLen++] = 0x022;
      return;
    }
    else if (!as_strcasecmp(ArgStr[1].str.p_str, "PC+W"))
    {
      WAsmCode[CodeLen++] = 0x1e2;
      return;
    }
  }

  Addr = EvalStrIntExpressionWithResult(&ArgStr[1], UInt11, &EvalResult);
  if (!EvalResult.OK)
    return;

  if (!mFirstPassUnknown(EvalResult.Flags) && IsCall && (Addr & 0x100)) WrStrErrorPos(ErrNum_NotFromThisAddress, &ArgStr[1]);
  else
  {
    if (!mFirstPassUnknown(EvalResult.Flags) && ((Reg_STATUS & 0xe0) != ((Addr >> 4) & 0xe0))) WrStrErrorPos(ErrNum_InAccPage, &ArgStr[1]);
    WAsmCode[CodeLen++] = Code | (Addr & 0x1ff);
    ChkSpace(SegCode, EvalResult.AddrSpaceMask);
  }
}

static void DecodeRETW(Word Code)
{
  Boolean OK;
  Word Arg;

  if (!ChkArgCnt(1, 1))
    return;

  Arg = EvalStrIntExpression(&ArgStr[1], Int8, &OK);
  if (OK)
    WAsmCode[CodeLen++] = Code | Arg;
}

static void DecodeBANK(Word Code)
{
  Word Arg;
  tEvalResult EvalResult;

  if (!ChkArgCnt(1, 1))
    return;

  Arg = EvalStrIntExpressionWithResult(&ArgStr[1], UInt8, &EvalResult);
  if (EvalResult.OK)
  {
    WAsmCode[CodeLen++] = Code | ((Arg >> 5) & 7);
    ChkSpace(SegData, EvalResult.AddrSpaceMask);
  }
}

static void DecodePAGE(Word Code)
{
  tEvalResult EvalResult;
  Word Arg;

  if (!ChkArgCnt(1, 1))
    return;

  Arg = EvalStrIntExpressionWithResult(&ArgStr[1], UInt12, &EvalResult);
  if (EvalResult.OK)
  {
    WAsmCode[CodeLen++] = Code | ((Arg >> 9) & 7);
    ChkSpace(SegCode, EvalResult.AddrSpaceMask);
  }
}

static void DecodeMODE(Word Code)
{
  tEvalResult EvalResult;
  Word Arg;

  if (!ChkArgCnt(1, 1))
    return;

  Arg = EvalStrIntExpressionWithResult(&ArgStr[1], Int4, &EvalResult);
  if (EvalResult.OK)
  {
    WAsmCode[CodeLen++] = Code | Arg;
    ChkSpace(SegCode, EvalResult.AddrSpaceMask);
  }
}

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

  if (!ChkArgCnt(0, 0))
    return;

  WAsmCode[CodeLen++] = EProgCounter() & 1 ? 0x702 : 0x602;
}

static void DecodeSFR(Word Code)
{
  UNUSED(Code);
  code_equate_type(SegData, UInt8);
}

static void DecodeBIT(Word Code)
{
  Word BitSymbol;
  tEvalResult EvalResult;

  UNUSED(Code);
  if (ChkArgCnt(1, 1)
   && DecodeBitSymbol(&ArgStr[1], &BitSymbol, &EvalResult))
  {
    *ListLine = '=';
    DissectBit_SX20(ListLine + 1, STRINGSIZE - 3, BitSymbol);
    PushLocHandle(-1);
    EnterIntSymbol(&LabPart, BitSymbol, SegBData, False);
    PopLocHandle();
    /* TODO: MakeUseList? */
  }
}

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

  DecodeDATA(Int12, Int8);
}

static void DecodeZERO(Word Code)
{
  Word Size;
  Boolean ValOK;
  tSymbolFlags Flags;

  UNUSED(Code);

  if (ChkArgCnt(1, 1))
  {
    Size = EvalStrIntExpressionWithFlags(&ArgStr[1], Int16, &ValOK, &Flags);
    if (mFirstPassUnknown(Flags)) WrError(ErrNum_FirstPassCalc);
    if (ValOK && !mFirstPassUnknown(Flags))
    {
      if (SetMaxCodeLen(Size << 1)) WrError(ErrNum_CodeOverflow);
      else
      {
        CodeLen = Size;
        memset(WAsmCode, 0, 2 * Size);
      }
    }
  }
}

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

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

  add_null_pseudo(InstTable);

  AddInstTable(InstTable, "NOP"  , NOPCode, DecodeFixed);
  AddInstTable(InstTable, "RET"  , 0x00c, DecodeFixed);
  AddInstTable(InstTable, "RETP" , 0x00d, DecodeFixed);
  AddInstTable(InstTable, "RETI" , 0x00e, DecodeFixed);
  AddInstTable(InstTable, "RETIW", 0x00f, DecodeFixed);
  AddInstTable(InstTable, "IREAD", 0x041, DecodeFixed);
  AddInstTable(InstTable, "SLEEP", 0x003, DecodeFixed);
  AddInstTable(InstTable, "CLC"  , 0x403, DecodeFixed);
  AddInstTable(InstTable, "CLZ"  , 0x443, DecodeFixed);
  AddInstTable(InstTable, "SEC"  , 0x503, DecodeFixed);
  AddInstTable(InstTable, "SEZ"  , 0x543, DecodeFixed);
  AddInstTable(InstTable, "SC"   , 0x703, DecodeFixed);
  AddInstTable(InstTable, "SZ"   , 0x743, DecodeFixed);

  AddInstTable(InstTable, "DEC"  , 0x0e0, DecodeOneReg);
  AddInstTable(InstTable, "INC"  , 0x2a0, DecodeOneReg);
  AddInstTable(InstTable, "DECSZ", 0x2e0, DecodeOneReg);
  AddInstTable(InstTable, "INCSZ", 0x3e0, DecodeOneReg);
  AddInstTable(InstTable, "RL"   , 0x360, DecodeOneReg);
  AddInstTable(InstTable, "RR"   , 0x320, DecodeOneReg);
  AddInstTable(InstTable, "SWAP" , 0x3a0, DecodeOneReg);
  AddInstTable(InstTable, "TEST" , 0x220, DecodeOneReg);
  AddInstTable(InstTable, "NOT"  , 0x260, DecodeNOT);

  AddInstTable(InstTable, "MOV"  , 0, DecodeMOV);
  AddInstTable(InstTable, "MOVSZ", 0, DecodeMOVSZ);

  AddInstTable(InstTable, "AND"  , 0x0e14, DecodeLogic);
  AddInstTable(InstTable, "OR"   , 0x0d10, DecodeLogic);
  AddInstTable(InstTable, "XOR"  , 0x0f18, DecodeLogic);
  AddInstTable(InstTable, "ADD"  , 0x001c, DecodeLogic);
  AddInstTable(InstTable, "SUB"  , 0x000a, DecodeSUB);
  AddInstTable(InstTable, "CLR"  , 0, DecodeCLR);

  AddInstTable(InstTable, "CLRB" , 0x0400, DecodeBit);
  AddInstTable(InstTable, "SB"   , 0x0700, DecodeBit);
  AddInstTable(InstTable, "SETB" , 0x0500, DecodeBit);
  AddInstTable(InstTable, "SNB"  , 0x0600, DecodeBit);

  AddInstTable(InstTable, "CALL" , 0x0900, DecodeJMP_CALL);
  AddInstTable(InstTable, "JMP"  , 0x0a00, DecodeJMP_CALL);
  AddInstTable(InstTable, "SKIP" , 0     , DecodeSKIP);

  AddInstTable(InstTable, "RETW" , 0x0800, DecodeRETW);
  AddInstTable(InstTable, "BANK" , 0x0800, DecodeBANK);
  AddInstTable(InstTable, "PAGE" , 0x0010, DecodePAGE);
  AddInstTable(InstTable, "MODE" , 0x0050, DecodeMODE);

  AddInstTable(InstTable, "SFR"  , 0, DecodeSFR);
  AddInstTable(InstTable, "BIT"  , 0, DecodeBIT);
  AddInstTable(InstTable, "DATA" , 0, DecodeDATA_SX20);
  AddInstTable(InstTable, "ZERO" , 0, DecodeZERO);
  AddInstTable(InstTable, "RES"  , 0, DecodeRES);
}

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

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

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

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

static Boolean IsDef_SX20(void)
{
  return Memo("SFR") || Memo("BIT");
}

static void SwitchTo_SX20(void)
{
  const TFamilyDescr *pDescr = FindFamilyByName("SX20");
  static const as_assume_rec_t ASSUMESX20s[] =
  {
    { "FSR"   , &Reg_FSR   , 0,  0xff, 0, NULL },
    { "STATUS", &Reg_STATUS, 0,  0xff, 0, NULL },
  };

  TurnWords = False;
  SetIntConstMode(eIntConstModeMoto);

  PCSymbol = "*";
  HeaderID = pDescr->Id;
  NOPCode = 0x000;
  DivideChars = ",";
  HasAttrs = False;
  PageIsOccupied = True;

  ValidSegs = (1 << SegCode) + (1 << SegData);
  Grans[SegCode] = 2; ListGrans[SegCode] = 2; SegInits[SegCode] = 0;
  SegLimits[SegCode] = 2047;
  Grans[SegData] = 1; ListGrans[SegData] = 1; SegInits[SegCode] = 0;
  SegLimits[SegData] = 0xff;

  MakeCode = MakeCode_SX20;
  IsDef = IsDef_SX20;
  SwitchFrom = SwitchFrom_SX20;
  DissectBit = DissectBit_SX20;
  InitFields();

  assume_set(ASSUMESX20s, as_array_size(ASSUMESX20s));
}

void codesx20_init(void)
{
  CPUSX20 = AddCPU("SX20", SwitchTo_SX20);
  CPUSX28 = AddCPU("SX28", SwitchTo_SX20);
}
