/* codemic8.c */
/*****************************************************************************/
/* SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only                     */
/*                                                                           */
/* AS-Portierung                                                             */
/*                                                                           */
/* Codegenerator LatticeMico8                                                */
/*                                                                           */
/*****************************************************************************/

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

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

#include "codemic8.h"

/* define as needed by address space */

#define CodeAddrInt UInt12
#define DataAddrInt UInt5

typedef struct
{
  LongWord Code;
} FixedOrder;

typedef struct
{
  LongWord Code;
  Boolean MayImm;
} ALUOrder;

typedef struct
{
  LongWord Code;
  Byte Space;
} MemOrder;

static FixedOrder *FixedOrders, *ShortBranchOrders, *RegOrders, *LongBranchOrders;
static MemOrder *MemOrders;
static ALUOrder *ALUOrders;

static CPUVar CPUMico8_05, CPUMico8_V3, CPUMico8_V31;

/*--------------------------------------------------------------------------
 * Address Expression Parsing
 *--------------------------------------------------------------------------*/

/*!------------------------------------------------------------------------
 * \fn     IsWRegCore(const char *pArg, LongWord *pValue)
 * \brief  check whether argument is a CPU register
 * \param  pArg argument to check
 * \param  pValue register number if it is a register
 * \return True if it is a register
 * ------------------------------------------------------------------------ */

static Boolean IsWRegCore(const char *pArg, LongWord *pValue)
{
  char *p_end;

  if ((strlen(pArg) < 2) || (as_toupper(*pArg) != 'R'))
    return False;

  *pValue = strtoul(pArg + 1, &p_end, 10);
  if (*p_end)
    return False;

  return (*pValue < 32);
}

/*!------------------------------------------------------------------------
 * \fn     DissectReg_Mico8(char *pDest, size_t DestSize, tRegInt Value, tSymbolSize InpSize)
 * \brief  dissect register symbols - MICO8 variant
 * \param  pDest destination buffer
 * \param  DestSize destination buffer size
 * \param  Value numeric register value
 * \param  InpSize register size
 * ------------------------------------------------------------------------ */

static void DissectReg_Mico8(char *pDest, size_t DestSize, tRegInt Value, tSymbolSize InpSize)
{
  switch (InpSize)
  {
    case eSymbolSize8Bit:
      as_snprintf(pDest, DestSize, "R%u", (unsigned)Value);
      break;
    default:
      as_snprintf(pDest, DestSize, "%d-%u", (int)InpSize, (unsigned)Value);
  }
}

/*!------------------------------------------------------------------------
 * \fn     IsWReg(const tStrComp *pArg, LongWord *pValue, Boolean MustBeReg)
 * \brief  check whether argument is a CPU register or register alias
 * \param  pArg argument to check
 * \param  pValue register number if it is a register
 * \param  MustBeReg expecting register as arg?
 * \return register parse result
 * ------------------------------------------------------------------------ */

static tRegEvalResult IsWReg(const tStrComp *pArg, LongWord *pValue, Boolean MustBeReg)
{
  tRegDescr RegDescr;
  tEvalResult EvalResult;
  tRegEvalResult RegEvalResult;

  if (IsWRegCore(pArg->str.p_str, pValue))
    return eIsReg;

  RegEvalResult = EvalStrRegExpressionAsOperand(pArg, &RegDescr, &EvalResult, eSymbolSize8Bit, MustBeReg);
  *pValue = RegDescr.Reg;
  return RegEvalResult;
}

/*--------------------------------------------------------------------------
 * Code Handlers
 *--------------------------------------------------------------------------*/

static void DecodePort(Word Index)
{
  UNUSED(Index);

  code_equate_type(SegIO, UInt8);
}

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

  if (ChkArgCnt(0, 0))
  {
    DAsmCode[0] = pOrder->Code;
    CodeLen = 1;
  }
}

static void DecodeALU(Word Index)
{
  ALUOrder *pOrder = ALUOrders + Index;
  LongWord Src, DReg;

  if (ChkArgCnt(2, 2)
   && IsWReg(&ArgStr[1], &DReg, True))
    switch (IsWReg(&ArgStr[2], &Src, True))
    {
      case eIsReg:
        DAsmCode[0] = pOrder->Code | (DReg << 8) | (Src << 3);
        CodeLen = 1;
        break;
      case eIsNoReg:
        if (!pOrder->MayImm) WrStrErrorPos(ErrNum_InvAddrMode, &ArgStr[2]);
        else
        {
          Boolean OK;

          Src = EvalStrIntExpression(&ArgStr[2], Int8, &OK);
          if (OK)
          {
            DAsmCode[0] = pOrder->Code | (1 << 13) | (DReg << 8) | (Src & 0xff);
           CodeLen = 1;
          }
        }
        break;
      default:
        break;
    }
}

static void DecodeALUI(Word Index)
{
  ALUOrder *pOrder = ALUOrders + Index;
  LongWord Src, DReg;
  Boolean OK;

  if (ChkArgCnt(2, 2)
   && IsWReg(&ArgStr[1], &DReg, True))
  {
    Src = EvalStrIntExpression(&ArgStr[2], Int8, &OK);
    if (OK)
    {
      DAsmCode[0] = pOrder->Code | (1 << 13) | (DReg << 8) | (Src & 0xff);
      CodeLen = 1;
    }
  }
}

static void DecodeShortBranch(Word Index)
{
  FixedOrder *pOrder = ShortBranchOrders + Index;
  LongInt Dest;
  Boolean OK;
  tSymbolFlags Flags;

  if (ChkArgCnt(1, 1))
  {
    Dest = EvalStrIntExpressionWithFlags(&ArgStr[1], CodeAddrInt, &OK, &Flags);
    if (OK)
    {
      Dest -= EProgCounter();
      if (((Dest < -512) || (Dest > 511)) && !mSymbolQuestionable(Flags)) WrError(ErrNum_JmpDistTooBig);
      else
      {
        DAsmCode[0] = pOrder->Code | (Dest & 0x3ff);
        CodeLen = 1;
      }
    }
  }
}

static void DecodeLongBranch(Word Index)
{
  FixedOrder *pOrder = LongBranchOrders + Index;
  LongInt Dest;
  Boolean OK;
  tSymbolFlags Flags;

  if (ChkArgCnt(1, 1))
  {
    Dest = EvalStrIntExpressionWithFlags(&ArgStr[1], CodeAddrInt, &OK, &Flags);
    if (OK)
    {
      Dest -= EProgCounter();
      if (((Dest < -2048) || (Dest > 2047)) && !mSymbolQuestionable(Flags)) WrError(ErrNum_JmpDistTooBig);
      else
      {
        DAsmCode[0] = pOrder->Code | (Dest & 0xfff);
        CodeLen = 1;
      }
    }
  }
}

static void DecodeMem(Word Index)
{
  MemOrder *pOrder = MemOrders + Index;
  LongWord DReg, Src;

  if (ChkArgCnt(2, 2)
   && IsWReg(&ArgStr[1], &DReg, True))
    switch (IsWReg(&ArgStr[2], &Src, False))
    {
      case eIsReg:
        DAsmCode[0] = pOrder->Code | (DReg << 8) | ((Src & 0x1f) << 3) | 2;
        CodeLen = 1;
        break;
      case eIsNoReg:
      {
        tEvalResult EvalResult;

        Src = EvalStrIntExpressionWithResult(&ArgStr[2], DataAddrInt, &EvalResult);
        if (EvalResult.OK)
        {
          ChkSpace(pOrder->Space, EvalResult.AddrSpaceMask);
          DAsmCode[0] = pOrder->Code | (DReg << 8) | ((Src & 0x1f) << 3);
          CodeLen = 1;
        }
        break;
      }
      default:
        break;
    }
}

static void DecodeMemI(Word Index)
{
  MemOrder *pOrder = MemOrders + Index;
  LongWord DReg, SReg;

  if (ChkArgCnt(2, 2)
   && IsWReg(&ArgStr[1], &DReg, True)
   && IsWReg(&ArgStr[2], &SReg, True))
  {
    DAsmCode[0] = pOrder->Code | (DReg << 8) | (SReg << 3) | 2;
    CodeLen = 1;
  }
}

static void DecodeReg(Word Index)
{
  FixedOrder *pOrder = RegOrders + Index;
  LongWord Reg = 0;

  if (!ChkArgCnt(1, 1));
  else if (IsWReg(&ArgStr[1], &Reg, True))
  {
    DAsmCode[0] = pOrder->Code | (Reg << 8);
    CodeLen = 1;
  }
}

/*--------------------------------------------------------------------------
 * Instruction Table Handling
 *--------------------------------------------------------------------------*/

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

static void AddALU(const char *NName, const char *NImmName, LongWord NCode)
{
  order_array_rsv_end(ALUOrders, ALUOrder);
  ALUOrders[InstrZ].Code = NCode;
  AddInstTable(InstTable, NName, InstrZ, DecodeALU);
  ALUOrders[InstrZ].MayImm = NImmName != NULL;
  if (ALUOrders[InstrZ].MayImm)
    AddInstTable(InstTable, NImmName, InstrZ, DecodeALUI);
  InstrZ++;
}

static void AddShortBranch(const char *NName, LongWord NCode)
{
  order_array_rsv_end(ShortBranchOrders, FixedOrder);
  ShortBranchOrders[InstrZ].Code = NCode;
  AddInstTable(InstTable, NName, InstrZ++, DecodeShortBranch);
}

static void AddLongBranch(const char *NName, LongWord NCode)
{
  order_array_rsv_end(LongBranchOrders, FixedOrder);
  LongBranchOrders[InstrZ].Code = NCode;
  AddInstTable(InstTable, NName, InstrZ++, DecodeLongBranch);
}

static void AddMem(const char *NName, const char *NImmName, LongWord NCode, Byte NSpace)
{
  order_array_rsv_end(MemOrders, MemOrder);
  MemOrders[InstrZ].Code = NCode;
  MemOrders[InstrZ].Space = NSpace;
  AddInstTable(InstTable, NName, InstrZ, DecodeMem);
  AddInstTable(InstTable, NImmName, InstrZ, DecodeMemI);
  InstrZ++;
}

static void AddReg(const char *NName, LongWord NCode)
{
  order_array_rsv_end(RegOrders, FixedOrder);
  RegOrders[InstrZ].Code = NCode;
  AddInstTable(InstTable, NName, InstrZ++, DecodeReg);
}

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

  add_null_pseudo(InstTable);

  InstrZ = 0;
  AddFixed("CLRC"  , 0x2c000);
  AddFixed("SETC"  , 0x2c001);
  AddFixed("CLRZ"  , 0x2c002);
  AddFixed("SETZ"  , 0x2c003);
  AddFixed("CLRI"  , 0x2c004);
  AddFixed("SETI"  , 0x2c005);
  if (MomCPU == CPUMico8_05)
  {
    AddFixed("RET"   , 0x3a000);
    AddFixed("IRET"  , 0x3a001);
  }
  else if (MomCPU == CPUMico8_V3)
  {
    AddFixed("RET"   , 0x38000);
    AddFixed("IRET"  , 0x39000);
  }
  else if (MomCPU == CPUMico8_V31)
  {
    AddFixed("RET"   , 0x39000);
    AddFixed("IRET"  , 0x3a000);
  }
  AddFixed("NOP"   , 0x10000);

  InstrZ = 0;
  AddALU("ADD"    , "ADDI"  ,   2UL << 14);
  AddALU("ADDC"   , "ADDIC" ,   3UL << 14);
  AddALU("SUB"    , "SUBI"  ,   0UL << 14);
  AddALU("SUBC"   , "SUBIC" ,   1UL << 14);
  AddALU("MOV"    , "MOVI"  ,   4UL << 14);
  AddALU("AND"    , "ANDI"  ,   5UL << 14);
  AddALU("OR"     , "ORI"   ,   6UL << 14);
  AddALU("XOR"    , "XORI"  ,   7UL << 14);
  AddALU("CMP"    , "CMPI"  ,   8UL << 14);
  AddALU("TEST"   , "TESTI" ,   9UL << 14);
  AddALU("ROR"    , NULL    , (10UL << 14) | 0); /* Note: The User guide (Feb '08) differs  */
  AddALU("ROL"    , NULL    , (10UL << 14) | 1); /* from the actual implementation in */
  AddALU("RORC"   , NULL    , (10UL << 14) | 2); /* decoding the last 3 bits of the Rotate */
  AddALU("ROLC"   , NULL    , (10UL << 14) | 3); /* instructions. These values are correct. */

  InstrZ = 0;
  AddReg("INC"    , (2UL << 14)  | (1UL << 13) | 1);
  AddReg("DEC"    , (0UL << 14)  | (1UL << 13) | 1);

  InstrZ = 0;
  if (MomCPU != CPUMico8_V31)
  {
    AddShortBranch("BZ"    , 0x32000);
    AddShortBranch("BNZ"   , 0x32400);
    AddShortBranch("BC"    , 0x32800);
    AddShortBranch("BNC"   , 0x32c00);
    AddShortBranch("CALLZ" , 0x36000);
    AddShortBranch("CALLNZ", 0x36400);
    AddShortBranch("CALLC" , 0x36800);
    AddShortBranch("CALLNC", 0x36c00);
  }

  /* AcQ/MA: a group for unconditional branches, which can support
   *         larger branches then the conditional branches (not supported
   *         in the earliest versions of the Mico8 processor). The branch
   *         range is +2047 to -2048 instead of +511 to -512. */
  InstrZ = 0;
  if (MomCPU != CPUMico8_05)
  {
    if (MomCPU == CPUMico8_V31)
    {
      AddLongBranch("BZ"    , 0x30000);
      AddLongBranch("BNZ"   , 0x31000);
      AddLongBranch("BC"    , 0x32000);
      AddLongBranch("BNC"   , 0x33000);
      AddLongBranch("CALLZ" , 0x34000);
      AddLongBranch("CALLNZ", 0x35000);
      AddLongBranch("CALLC" , 0x36000);
      AddLongBranch("CALLNC", 0x37000);
      AddLongBranch("CALL"  , 0x38000);
      AddLongBranch("B"     , 0x3b000);
    }
    else
    {
      AddLongBranch("B"     , 0x33000);
      AddLongBranch("CALL"  , 0x37000);
    }
  }

  InstrZ = 0;
  if (MomCPU == CPUMico8_V31)
  {
    AddMem("INP"    , "INPI"   , (23UL << 13) | 1, SegIO);
    AddMem("IMPORT" , "IMPORTI", (23UL << 13) | 1, SegIO);
    AddMem("OUTP"   , "OUTPI"  , (23UL << 13) | 0, SegIO);
    AddMem("EXPORT" , "EXPORTI", (23UL << 13) | 0, SegIO);
    AddMem("LSP"    , "LSPI"   , (23UL << 13) | 5, SegData);
    AddMem("SSP"    , "SSPI"   , (23UL << 13) | 4, SegData);
  }
  else
  {
    if (MomCPU == CPUMico8_V3)
    {
      AddMem("INP"    , "INPI"   , (15UL << 14) | 1, SegIO);
      AddMem("OUTP"   , "OUTPI"  , (15UL << 14) | 0, SegIO);
    }
    AddMem("IMPORT" , "IMPORTI", (15UL << 14) | 1, SegIO);
    AddMem("EXPORT" , "EXPORTI", (15UL << 14) | 0, SegIO);
    AddMem("LSP"    , "LSPI"   , (15UL << 14) | 5, SegData);
    AddMem("SSP"    , "SSPI"   , (15UL << 14) | 4, SegData);
  }

  AddInstTable(InstTable, "REG", 0, CodeREG);
  AddInstTable(InstTable, "PORT", 0, DecodePort);
  AddIntelPseudo(InstTable, eIntPseudoFlag_BigEndian);
}

static void DeinitFields(void)
{
  DestroyInstTable(InstTable);
  order_array_free(FixedOrders);
  order_array_free(ALUOrders);
  order_array_free(LongBranchOrders);
  order_array_free(ShortBranchOrders);
  order_array_free(MemOrders);
  order_array_free(RegOrders);
}

/*--------------------------------------------------------------------------
 * Semipublic Functions
 *--------------------------------------------------------------------------*/

/*!------------------------------------------------------------------------
 * \fn     InternSymbol_Mico8(char *pArg, TempResult *pResult)
 * \brief  handle built-in (register) symbols for MICO8
 * \param  pArg source argument
 * \param  pResult result buffer
 * ------------------------------------------------------------------------ */

static void InternSymbol_Mico8(char *pArg, TempResult *pResult)
{
  LongWord RegNum;

  if (IsWRegCore(pArg, &RegNum))
  {
    pResult->Typ = TempReg;
    pResult->DataSize = eSymbolSize8Bit;
    pResult->Contents.RegDescr.Reg = RegNum;
    pResult->Contents.RegDescr.Dissect = DissectReg_Mico8;
    pResult->Contents.RegDescr.compare = NULL;
  }
}

static Boolean IsDef_Mico8(void)
{
   return (Memo("REG")) || (Memo("PORT"));
}

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

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

static void SwitchTo_Mico8(void)
{
   const TFamilyDescr *FoundDescr;

   FoundDescr = FindFamilyByName("Mico8");

   TurnWords = True;
   SetIntConstMode(eIntConstModeC);

   PCSymbol = "$"; HeaderID = FoundDescr->Id;

   /* NOP = mov R0,R0 */

   NOPCode = 0x10000;
   DivideChars = ","; HasAttrs = False;

   ValidSegs = (1 << SegCode) | (1 << SegData) | (1 << SegXData) | (1 << SegIO);
   Grans[SegCode] = 4; ListGrans[SegCode] = 4; SegInits[SegCode] = 0;
   SegLimits[SegCode] = IntTypeDefs[CodeAddrInt].Max;
   Grans[SegData] = 1; ListGrans[SegData] = 1; SegInits[SegData] = 0;
   SegLimits[SegData] = IntTypeDefs[DataAddrInt].Max;
   Grans[SegXData] = 1; ListGrans[SegXData] = 1; SegInits[SegXData] = 0;
   SegLimits[SegXData] = 0xff;
   Grans[SegIO] = 1; ListGrans[SegIO] = 1; SegInits[SegIO] = 0;
   SegLimits[SegIO] = 0xff;

   MakeCode = MakeCode_Mico8;
   IsDef = IsDef_Mico8;
   InternSymbol = InternSymbol_Mico8;
   DissectReg = DissectReg_Mico8;
   SwitchFrom = SwitchFrom_Mico8; InitFields();
}

/*--------------------------------------------------------------------------
 * Initialization
 *--------------------------------------------------------------------------*/

void codemico8_init(void)
{
   CPUMico8_05  = AddCPU("Mico8_05" , SwitchTo_Mico8);
   CPUMico8_V3  = AddCPU("Mico8_V3" , SwitchTo_Mico8);
   CPUMico8_V31 = AddCPU("Mico8_V31", SwitchTo_Mico8);
}
