/* codeol40.c */
/*****************************************************************************/
/* SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only                     */
/*                                                                           */
/* AS-Portierung                                                             */
/*                                                                           */
/* Codegenerator OKI OLMS-40-Familie                                         */
/*                                                                           */
/*****************************************************************************/

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

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

#include "codeol40.h"

typedef enum
{
  ModA = 0,
  ModDPL = 1,
  ModDPH = 2,
  ModW = 3,
  ModX = 4,
  ModY = 5,
  ModZ = 6,
  ModM = 7,
  ModImm = 10,
  ModPP = 11,
  ModT = 12,
  ModTL = 13,
  ModTH = 14,
  ModPPI = 15,
  ModNone = 0x7f
} tAdrMode;

#define MModA (1 << ModA)
#define MModDPL (1 << ModDPL)
#define MModDPH (1 << ModDPH)
#define MModW (1 << ModW)
#define MModX (1 << ModX)
#define MModY (1 << ModY)
#define MModZ (1 << ModZ)
#define MModM (1 << ModM)
#define MModImm (1 << ModImm)
#define MModPP (1 << ModPP)
#define MModT (1 << ModT)
#define MModTL (1 << ModTL)
#define MModTH (1 << ModTH)
#define MModPPI (1 << ModPPI)

static CPUVar CPU5840, CPU5842, CPU58421, CPU58422, CPU5847;
static IntType CodeIntType, DataIntType, OpSizeType;
static tAdrMode AdrMode;
static Byte AdrVal;

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

typedef struct
{
  const char *pAsc;
  tAdrMode AdrMode;
} tNotation;

static void DecodeAdr(const tStrComp *pArg, Word Mask)
{
  static const tNotation Notations[] =
  {
    { "A",    ModA    },
    { "M",    ModM    },
    { "(DP)", ModM    },
    { "W",    ModW    },
    { "X",    ModX    },
    { "Y",    ModY    },
    { "Z",    ModZ    },
    { "DPL",  ModDPL  },
    { "DPH",  ModDPH  },
    { "PP",   ModPP   },
    { "T",    ModT    },
    { "TL",   ModTL   },
    { "TH",   ModTH   },
    { "(PP)", ModPPI  },
    { NULL,   ModNone },
  };
  const tNotation *pNot;
  Boolean OK;

  if (MomCPU != CPU5840)
    Mask &= ~(MModX | MModY | MModZ | MModTL | MModTH);
  if (MomCPU  == CPU5847)
    Mask &= ~(MModW);

  for (pNot = Notations; pNot->pAsc; pNot++)
    if (!as_strcasecmp(pArg->str.p_str, pNot->pAsc))
    {
      AdrMode = pNot->AdrMode;
      goto AdrFound;
    }

  AdrVal = EvalStrIntExpressionOffs(pArg, !!(0[pArg->str.p_str] == '#'), OpSizeType, &OK);
  if (OK)
  {
    AdrMode = ModImm;
    AdrVal &= IntTypeDefs[OpSizeType].Mask;
  }

AdrFound:

  if ((AdrMode != ModNone) && (!(Mask & (1 << AdrMode))))
  {
    WrError(ErrNum_InvAddrMode);
    AdrMode = ModNone; AdrCnt = 0;
  }
}

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

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

  if (ChkArgCnt(2, 2))
  {
    DecodeAdr(&ArgStr[1], MModA | MModDPL | MModDPH | MModW | MModX | MModY | MModZ | MModPP | MModT);
    switch (AdrMode)
    {
      case ModA:
        DecodeAdr(&ArgStr[2], MModImm | MModW | MModX | MModY | MModZ | MModM | MModDPL | MModTL | MModTH);
        switch (AdrMode)
        {
          case ModW:
          case ModX:
          case ModY:
          case ModZ:
            BAsmCode[CodeLen++] = 0x84 | (AdrMode - ModW);
            break;
          case ModM:
            BAsmCode[CodeLen++] = 0x94;
            break;
          case ModDPL:
            BAsmCode[CodeLen++] = 0x55;
            break;
          case ModImm:
            BAsmCode[CodeLen++] = 0x10 | AdrVal;
            break;
          case ModTL:
          case ModTH:
            BAsmCode[CodeLen++] = 0x6b - (AdrMode - ModTL);
            break;
          default:
            break;
        }
        break;
      case ModDPL:
        DecodeAdr(&ArgStr[2], MModImm | MModA);
        switch (AdrMode)
        {
          case ModImm:
            BAsmCode[CodeLen++] = 0x20 | AdrVal;
            break;
          case ModA:
            BAsmCode[CodeLen++] = 0x54;
            break;
          default:
            break;
        }
        break;
      case ModDPH:
        DecodeAdr(&ArgStr[2], MModImm);
        switch (AdrMode)
        {
          case ModImm:
            BAsmCode[CodeLen++] = 0x60 | AdrVal;
            break;
          default:
            break;
        }
        break;
      case ModW:
      case ModX:
      case ModY:
      case ModZ:
      {
        tAdrMode HMode = AdrMode;

        DecodeAdr(&ArgStr[2], MModA);
        switch (AdrMode)
        {
          case ModA:
            BAsmCode[CodeLen++] = 0x80 | (HMode - ModW);
            break;
          default:
            break;
        }
        break;
      }
      case ModPP:
        DecodeAdr(&ArgStr[2], MModA);
        switch (AdrMode)
        {
          case ModA:
            BAsmCode[CodeLen++] = 0x58;
            break;
          default:
            break;
        }
        break;
      case ModT:
        OpSizeType = Int8;
        DecodeAdr(&ArgStr[2], MModImm);
        switch (AdrMode)
        {
          case ModImm:
            if (MomCPU == CPU5840)
            {
              BAsmCode[CodeLen++] = 0x68;
              BAsmCode[CodeLen++] = AdrVal;
            }
            else if (AdrVal) WrError(ErrNum_OverRange);
            else
              BAsmCode[CodeLen++] = 0x68;
            break;
          default:
            break;
        }
        break;
      default:
        break;
    }
  }
}

static void DecodeINCDEC(Word IsDEC)
{
  if (ChkArgCnt(1, 1))
  {
    DecodeAdr(&ArgStr[1], MModA | MModW | MModX | MModY | MModZ | MModDPL | MModM
                       | ((IsDEC && (MomCPU == CPU5840)) ? MModDPH : 0));
    switch (AdrMode)
    {
      case ModA:
        BAsmCode[CodeLen++] = IsDEC ? 0x0f : 0x01;
        break;
      case ModW:
      case ModX:
      case ModY:
      case ModZ:
        BAsmCode[CodeLen++] = (IsDEC ? 0x8c : 0x88) | (AdrMode - ModW);
        break;
      case ModM:
        BAsmCode[CodeLen++] = IsDEC ? 0x5c : 0x5d;
        break;
      case ModDPL:
        BAsmCode[CodeLen++] = IsDEC ? 0x56 : 0x57;
        break;
      case ModDPH:
        BAsmCode[CodeLen++] = 0x5f;
        break;
      default:
        break;
    }
  }
}

static void DecodeBit(Word Code)
{
  if (ArgCnt == 1)
  {
    if (!as_strcasecmp(ArgStr[1].str.p_str, "C"))
      BAsmCode[CodeLen++] = Hi(Code);
    else
      WrError(ErrNum_InvAddrMode);
  }
  else if (ChkArgCnt(1, 2))
  {
    Boolean OK;
    unsigned BitNo = EvalStrIntExpression(&ArgStr[2], UInt2, &OK);

    Code = Lo(Code);

    if (OK)
    {
      DecodeAdr(&ArgStr[1], ((Code == 2) ? MModA : MModPPI) | MModM);
      switch (AdrMode)
      {
        case ModA:
          BAsmCode[CodeLen++] = 0xa0 | BitNo;
          break;
        case ModM:
          if (Code == 2)
            BAsmCode[CodeLen++] = 0xa4 | BitNo;
          else
            BAsmCode[CodeLen++] = 0xb8 | (Code << 2) | BitNo;
          break;
        case ModPPI:
          BAsmCode[CodeLen++] = 0xb0 | (Code << 2) | BitNo;
          break;
        default:
          break;
      }
    }
  }
}

static void CodeFixed(Word Code)
{
  if (ChkArgCnt(0, 0)
   && (ChkExactCPUMask(Hi(Code), CPU5840) >= 0))
    BAsmCode[CodeLen++] = Lo(Code);
}

static void CodeTHB(Word Code)
{
  if (ChkArgCnt(1, 1)
   && (ChkExactCPUMask(Hi(Code), CPU5840) >= 0))
  {
    Boolean OK = True;
    Word ImmVal = ArgCnt ? EvalStrIntExpression(&ArgStr[1], UInt1, &OK) : 0;

    if (OK)
    {
      if ((MomCPU == CPU5840) || (MomCPU == CPU5842))
        BAsmCode[CodeLen++] = Lo(Code) | (ImmVal & 1);
      else if (ImmVal) WrError(ErrNum_OverRange);
      else
        BAsmCode[CodeLen++] = Lo(Code);
    }
  }
}

static void CodeImm2(Word Code)
{
  if (ChkArgCnt(1, 1)
   && (ChkExactCPUMask(Hi(Code), CPU5840) >= 0))
  {
    Boolean OK;
    Word ImmVal = EvalStrIntExpression(&ArgStr[1], UInt2, &OK);

    if (OK)
      BAsmCode[CodeLen++] = Lo(Code) | (ImmVal & 3);
  }
}

static void CodeImm4(Word Code)
{
  if (ChkArgCnt(1, 1)
   && (ChkExactCPUMask(Hi(Code), CPU5840) >= 0))
  {
    Boolean OK;
    Word ImmVal = EvalStrIntExpression(&ArgStr[1], Int4, &OK);

    if (OK)
      BAsmCode[CodeLen++] = Lo(Code) | (ImmVal & 15);
  }
}

static void CodeLTI(Word Code)
{
  if (ChkArgCnt(0, 1)
   && (ChkExactCPUMask(Hi(Code), CPU5840) >= 0))
  {
    Boolean OK = True;
    Word ImmVal = ArgCnt ? EvalStrIntExpression(&ArgStr[1], Int8, &OK) : 0;

    if (OK)
    {
      /* LTI can only load 0 into timer on smaller devices */

      if (MomCPU == CPU5840)
      {
        BAsmCode[CodeLen++] = Lo(Code);
        BAsmCode[CodeLen++] = Lo(ImmVal);
      }
      else if (ImmVal != 0) WrError(ErrNum_OverRange);
      else
        BAsmCode[CodeLen++] = Lo(Code);
    }
  }
}

static void CodeJC(Word Code)
{
  if (ChkArgCnt(1, 1)
   && (ChkExactCPUMask(Hi(Code), CPU5840) >= 0))
  {
    tEvalResult EvalResult;
    Word Address = EvalStrIntExpressionWithResult(&ArgStr[1], CodeIntType, &EvalResult);
    if (EvalResult.OK && ChkSamePage(EProgCounter() + 1, Address, 6, EvalResult.Flags))
    {
      ChkSpace(SegCode, EvalResult.AddrSpaceMask);
      BAsmCode[CodeLen++] = Lo(Code) | (Address & 0x3f);
    }
  }
}

static void CodeJMP(Word Code)
{
  if (ChkArgCnt(1, 1)
   && (ChkExactCPUMask(Hi(Code), CPU5840) >= 0))
  {
    tEvalResult EvalResult;
    Word Address= EvalStrIntExpressionWithResult(&ArgStr[1], CodeIntType, &EvalResult);
    if (EvalResult.OK)
    {
      ChkSpace(SegCode, EvalResult.AddrSpaceMask);
      BAsmCode[CodeLen++] = Lo(Code) | (Hi(Address) & 0x07);
      BAsmCode[CodeLen++] = Lo(Address);
    }
  }
}

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

  DecodeDATA(Int8, Int4);
}

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

  code_equate_segment(SegData);
}

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

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

  add_null_pseudo(InstTable);

  AddInstTable(InstTable, "LD", 0, DecodeLD);
  AddInstTable(InstTable, "INC", 0, DecodeINCDEC);
  AddInstTable(InstTable, "DEC", 1, DecodeINCDEC);
  AddInstTable(InstTable, "BSET", 0x4000, DecodeBit);
  AddInstTable(InstTable, "BCLR", 0x4101, DecodeBit);
  AddInstTable(InstTable, "BTST", 0x4202, DecodeBit);

  AddInstTable(InstTable, "CLA",    0x1f10, CodeFixed);
  AddInstTable(InstTable, "CLL",    0x1f20, CodeFixed);
  AddInstTable(InstTable, "CLH",    0x1f60, CodeFixed);
  AddInstTable(InstTable, "L",      0x1f94, CodeFixed);
  AddInstTable(InstTable, "LAL",    0x1f55, CodeFixed);
  AddInstTable(InstTable, "LLA",    0x1f54, CodeFixed);
  AddInstTable(InstTable, "LAW",    0x0f84, CodeFixed);
  AddInstTable(InstTable, "LAX",    0x0185, CodeFixed);
  AddInstTable(InstTable, "LAY",    0x0186, CodeFixed);
  AddInstTable(InstTable, "LAZ",    0x0187, CodeFixed);
  AddInstTable(InstTable, "SI",     0x1f90, CodeFixed);
  AddInstTable(InstTable, "LWA",    0x0f80, CodeFixed);
  AddInstTable(InstTable, "LXA",    0x0181, CodeFixed);
  AddInstTable(InstTable, "LYA",    0x0182, CodeFixed);
  AddInstTable(InstTable, "LZA",    0x0183, CodeFixed);
  AddInstTable(InstTable, "LPA",    0x1f58, CodeFixed);
  AddInstTable(InstTable, "RTH",    0x016a, CodeFixed);
  AddInstTable(InstTable, "RTL",    0x016b, CodeFixed);
  AddInstTable(InstTable, "XA",     0x0149, CodeFixed);
  AddInstTable(InstTable, "XL",     0x014a, CodeFixed);
  AddInstTable(InstTable, "XCH",    0x0148, CodeFixed);
  AddInstTable(InstTable, "X",      0x1f98, CodeFixed);
  AddInstTable(InstTable, "XAX",    0x014b, CodeFixed);
  AddInstTable(InstTable, "INA",    0x0f01, CodeFixed);
  AddInstTable(InstTable, "INL",    0x0f57, CodeFixed);
  AddInstTable(InstTable, "INM",    0x1f5d, CodeFixed);
  AddInstTable(InstTable, "INW",    0x0f88, CodeFixed);
  AddInstTable(InstTable, "INX",    0x0189, CodeFixed);
  AddInstTable(InstTable, "INY",    0x018a, CodeFixed);
  AddInstTable(InstTable, "INZ",    0x018b, CodeFixed);
  AddInstTable(InstTable, "DCA",    0x0f0f, CodeFixed);
  AddInstTable(InstTable, "DCL",    0x0f56, CodeFixed);
  AddInstTable(InstTable, "DCM",    0x1f5c, CodeFixed);
  AddInstTable(InstTable, "DCW",    0x018c, CodeFixed);
  AddInstTable(InstTable, "DCX",    0x018d, CodeFixed);
  AddInstTable(InstTable, "DCY",    0x018e, CodeFixed);
  AddInstTable(InstTable, "DCZ",    0x018f, CodeFixed);
  AddInstTable(InstTable, "DCH",    0x015f, CodeFixed);
  AddInstTable(InstTable, "CAO",    0x1f50, CodeFixed);
  AddInstTable(InstTable, "AND",    0x0144, CodeFixed);
  AddInstTable(InstTable, "OR",     0x0145, CodeFixed);
  AddInstTable(InstTable, "EOR",    0x0146, CodeFixed);
  AddInstTable(InstTable, "RAL",    0x1f47, CodeFixed);
  AddInstTable(InstTable, "AC",     0x1f4c, CodeFixed);
  AddInstTable(InstTable, "ACS",    0x014d, CodeFixed);
  AddInstTable(InstTable, "AS",     0x1f4e, CodeFixed);
  AddInstTable(InstTable, "DAS",    0x1f5a, CodeFixed);
  AddInstTable(InstTable, "CM",     0x1f5e, CodeFixed);
  AddInstTable(InstTable, "AWS",    0x019c, CodeFixed);
  AddInstTable(InstTable, "AXS",    0x019d, CodeFixed);
  AddInstTable(InstTable, "AYS",    0x019e, CodeFixed);
  AddInstTable(InstTable, "AZS",    0x019f, CodeFixed);
  AddInstTable(InstTable, "TI",     0x01af, CodeFixed);
  AddInstTable(InstTable, "TTM",    0x1fae, CodeFixed);
  AddInstTable(InstTable, "TC",     0x1f42, CodeFixed);
  AddInstTable(InstTable, "SC",     0x1f40, CodeFixed);
  AddInstTable(InstTable, "RC",     0x1f41, CodeFixed);
  AddInstTable(InstTable, "JA",     0x1f43, CodeFixed);
  AddInstTable(InstTable, "RT",     0x1f59, CodeFixed);
  AddInstTable(InstTable, "OBS",    0x0170, CodeFixed);
  AddInstTable(InstTable, "OTD",    0x1f71, CodeFixed);
  AddInstTable(InstTable, "OA",     0x1f72, CodeFixed);
  AddInstTable(InstTable, "OB",     0x1f73, CodeFixed);
  AddInstTable(InstTable, "OP",     0x1f74, CodeFixed);
  AddInstTable(InstTable, "OAB",    0x0175, CodeFixed);
  AddInstTable(InstTable, "OPM",    0x1f76, CodeFixed);
  AddInstTable(InstTable, "IA",     0x1f7a, CodeFixed);
  AddInstTable(InstTable, "IB",     0x1f7b, CodeFixed);
  AddInstTable(InstTable, "IK",     0x0f7c, CodeFixed);
  AddInstTable(InstTable, "IAB",    0x017d, CodeFixed);
  AddInstTable(InstTable, "EI",     0x0153, CodeFixed);
  AddInstTable(InstTable, "DI",     0x0152, CodeFixed);
  AddInstTable(InstTable, "ET",     0x016f, CodeFixed);
  AddInstTable(InstTable, "DT",     0x016e, CodeFixed);
  AddInstTable(InstTable, "ECT",    0x017f, CodeFixed);
  AddInstTable(InstTable, "DCT",    0x017e, CodeFixed);
  AddInstTable(InstTable, "HLT",    0x016d, CodeFixed);
  AddInstTable(InstTable, "EXP",    0x0169, CodeFixed);
  AddInstTable(InstTable, "NOP",    0x1f00, CodeFixed);

  AddInstTable(InstTable, "THB",    0x0fac, CodeTHB);

  AddInstTable(InstTable, "LM",     0x0194, CodeImm2);
  AddInstTable(InstTable, "SMI",    0x0190, CodeImm2);
  AddInstTable(InstTable, "XM",     0x0198, CodeImm2);
  AddInstTable(InstTable, "SPB",    0x01b0, CodeImm2);
  AddInstTable(InstTable, "RPB",    0x01b4, CodeImm2);
  AddInstTable(InstTable, "SMB",    0x1fb8, CodeImm2);
  AddInstTable(InstTable, "RMB",    0x1fbc, CodeImm2);
  AddInstTable(InstTable, "TAB",    0x1fa0, CodeImm2);
  AddInstTable(InstTable, "TMB",    0x1fa4, CodeImm2);
  AddInstTable(InstTable, "TKB",    0x01a8, CodeImm2);

  AddInstTable(InstTable, "LAI",    0x1f10, CodeImm4);
  AddInstTable(InstTable, "LLI",    0x1f20, CodeImm4);
  AddInstTable(InstTable, "LHI",    0x1f60, CodeImm4);
  AddInstTable(InstTable, "AIS",    0x1f00, CodeImm4);

  AddInstTable(InstTable, "LTI",    0x1f68, CodeLTI);

  AddInstTable(InstTable, "JC",     0x1fc0, CodeJC);

  AddInstTable(InstTable, "J",      0x1f30, CodeJMP);
  AddInstTable(InstTable, "CAL",    0x1f38, CodeJMP);

  AddInstTable(InstTable, "RES", 0, DecodeRES);
  AddInstTable(InstTable, "DATA", 0, DecodeDATA_OLMS40);
  AddInstTable(InstTable, "SFR", 0, DecodeSFR);
}

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

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

static void  MakeCode_OLMS40(void)
{
  OpSizeType = Int4;

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


static Boolean IsDef_OLMS40(void)
{
  return False;
}

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

static void SwitchTo_OLMS40(void)
{
  const TFamilyDescr *pDescr;

  pDescr = FindFamilyByName("OLMS-40");

  TurnWords = False;
  SetIntConstMode(eIntConstModeIntel);

  PCSymbol = "$";
  HeaderID = pDescr->Id;
  NOPCode = 0x00;
  DivideChars = ",";
  HasAttrs = False;

  ValidSegs = (1 << SegCode) | (1 << SegData);
  Grans[SegCode] = 1; ListGrans[SegCode] = 1; SegInits[SegCode] = 0;
  Grans[SegData] = 1; ListGrans[SegData] = 1; SegInits[SegCode] = 0;
  if (MomCPU == CPU5840)
  {
    CodeIntType = UInt11;
    DataIntType = UInt7;
    SegLimits[SegData] = IntTypeDefs[DataIntType].Max;
    SegLimits[SegCode] = IntTypeDefs[CodeIntType].Max;
  }
  else if (MomCPU == CPU5842)
  {
    CodeIntType = UInt10;
    DataIntType = UInt5;
    SegLimits[SegData] = IntTypeDefs[DataIntType].Max;
    SegLimits[SegCode] = 767;
  }
  else if ((MomCPU == CPU58421) || (MomCPU == CPU58422))
  {
    CodeIntType = UInt11;
    DataIntType = UInt6;
    SegLimits[SegData] = 39;
    SegLimits[SegCode] = 1535;
  }
  else if (MomCPU == CPU5847)
  {
    CodeIntType = UInt11;
    DataIntType = UInt7;
    SegLimits[SegData] = 95;
    SegLimits[SegCode] = 1535;
  }

  MakeCode = MakeCode_OLMS40;
  IsDef = IsDef_OLMS40;
  SwitchFrom = SwitchFrom_OLMS40; InitFields();

}

void codeolms40_init(void)
{
  CPU5840  = AddCPU("MSM5840" , SwitchTo_OLMS40);
  CPU5842  = AddCPU("MSM5842" , SwitchTo_OLMS40);
  CPU58421 = AddCPU("MSM58421", SwitchTo_OLMS40);
  CPU58422 = AddCPU("MSM58422", SwitchTo_OLMS40);
  CPU5847  = AddCPU("MSM5847" , SwitchTo_OLMS40);
}
