/* codef8.c */
/*****************************************************************************/
/* SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only                     */
/*                                                                           */
/* AS-Portierung                                                             */
/*                                                                           */
/* Code Generator Fairchild F8                                               */
/*                                                                           */
/*****************************************************************************/

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

#include "codef8.h"

#define Flg_IO (1 << 8)
#define Flg_Code (1 << 9)
#define Flg_CMOS (1 << 10)

static IntType CodeIntType;
static Word MomCPUFlags;

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

static Boolean DecodeReg(const tStrComp *pArg, Byte *pResult)
{
  /* KU/KL/QU/QL are addressed via dedicated LR instructions! */
  static const char RegNames[][3] = { "J", "HU", "HL", "S", "I", "D", "" };
  int z;
  Boolean OK;

  for (z = 0; *RegNames[z]; z++)
    if (!as_strcasecmp(RegNames[z], pArg->str.p_str))
    {
      *pResult = z + 9;
      return True;
    }
  *pResult = EvalStrIntExpression(pArg, UInt4, &OK);
  return OK;
}

static Boolean DecodeAux(const char *pArg, Byte *pResult)
{
  if (strlen(pArg) != 2)
    return False;
  *pResult = 0;

  if (toupper(pArg[0]) == 'Q')
    *pResult |= 2;
  else if (toupper(pArg[0]) != 'K')
    return False;
  if (toupper(pArg[1]) == 'L')
    *pResult |= 1;
  else if (toupper(pArg[1]) != 'U')
    return False;
  return True;
}

static Boolean ArgPair(const char *pArg1, const char *pArg2)
{
  return !as_strcasecmp(ArgStr[1].str.p_str, pArg1) && !as_strcasecmp(ArgStr[2].str.p_str, pArg2);
}

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

static void DecodeFixed(Word Code)
{
  if (!ChkArgCnt(0, 0));
  else if ((Code & Flg_CMOS) && (!(MomCPUFlags & Flg_CMOS))) WrError(ErrNum_InstructionNotSupported);
  else
  {
    BAsmCode[0] = Code;
    CodeLen = 1;
  }
}

static void DecodeImm8(Word Code)
{
  Boolean IsIO = !!(Code & Flg_IO);

  if (ChkArgCnt(1, 1))
  {
    tEvalResult EvalResult;

    BAsmCode[1] = EvalStrIntExpressionWithResult(&ArgStr[1], IsIO ? UInt8 : Int8, &EvalResult);
    if (EvalResult.OK)
    {
      if (IsIO)
      {
        ChkSpace(SegIO, EvalResult.AddrSpaceMask);
        if (BAsmCode[1] < 4)
          WrStrErrorPos(ErrNum_NeedShortIO, &ArgStr[1]);
      }
      BAsmCode[0] = Lo(Code);
      CodeLen = 2;
    }
  }
}

static void DecodeImm4(Word Code)
{
  Boolean IsIO = !!(Code & Flg_IO);

  if (ChkArgCnt(1, 1))
  {
    tEvalResult EvalResult;

    BAsmCode[0] = Lo(Code) | EvalStrIntExpressionWithResult(&ArgStr[1], UInt4, &EvalResult);
    if (EvalResult.OK)
    {
      CodeLen = 1;
      if (IsIO)
        ChkSpace(SegIO, EvalResult.AddrSpaceMask);
    }
  }
}

static void DecodeImm3(Word Code)
{
  if (ChkArgCnt(1, 1))
  {
    Boolean OK;

    BAsmCode[0] = Code | EvalStrIntExpression(&ArgStr[1], UInt3, &OK);
    if (OK)
      CodeLen = 1;
  }
}

static void DecodeImm16(Word Code)
{
  Boolean IsCode = !!(Code & Flg_Code);

  if (ChkArgCnt(1, 1))
  {
    tEvalResult EvalResult;
    Word Arg = EvalStrIntExpressionWithResult(&ArgStr[1], IsCode ? CodeIntType : Int16, &EvalResult);

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

static void DecodeOneReg(Word Code)
{
  if (ChkArgCnt(1, 1) && DecodeReg(&ArgStr[1], &BAsmCode[0]))
  {
    BAsmCode[0] |= Code;
    CodeLen = 1;
  }
}

static void DecodeBranchCore(const tStrComp *pArg, Word Code)
{
  Boolean OK;
  LongInt Dist;
  tSymbolFlags Flags;

  Dist = EvalStrIntExpressionWithFlags(pArg, UInt16, &OK, &Flags) - (EProgCounter() + 1);
  if (OK)
  {
    if (!mSymbolQuestionable(Flags) && ((Dist < -128) || (Dist > 127))) WrStrErrorPos(ErrNum_JmpDistTooBig, pArg);
    else
    {
      BAsmCode[0] = Code;
      BAsmCode[1] = Dist & 0xff;
      CodeLen = 2;
    }
  }
}

static void DecodeBranch(Word Code)
{
  if (ChkArgCnt(1, 1))
    DecodeBranchCore(&ArgStr[1], Code);
}

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

    /* BT does not include OVF flag in its condition mask, only values 0..7! */

    Code |= EvalStrIntExpression(&ArgStr[1], (Code == 0x90) ? UInt4 : UInt3, &OK);
    if (OK)
      DecodeBranchCore(&ArgStr[2], Code);
  }
}

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

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

  if (!as_strcasecmp(ArgStr[1].str.p_str, "A"))
  {
    if (DecodeAux(ArgStr[2].str.p_str, &BAsmCode[0]))
    {
      CodeLen = 1;
    }
    else if (!as_strcasecmp(ArgStr[2].str.p_str, "IS"))
    {
      BAsmCode[0] = 0x0a;
      CodeLen = 1;
    }
    else if (DecodeReg(&ArgStr[2], &BAsmCode[0]))
    {
      BAsmCode[0] |= 0x40;
      CodeLen = 1;
    }
  }
  else if (!as_strcasecmp(ArgStr[2].str.p_str, "A"))
  {
    if (DecodeAux(ArgStr[1].str.p_str, &BAsmCode[0]))
    {
      BAsmCode[0] |= 4;
      CodeLen = 1;
    }
    else if (!as_strcasecmp(ArgStr[1].str.p_str, "IS"))
      BAsmCode[CodeLen++] = 0x0b;
    else if (DecodeReg(&ArgStr[1], &BAsmCode[0]))
    {
      BAsmCode[0] |= 0x50;
      CodeLen = 1;
    }
  }
  else if (ArgPair("DC", "Q"))
    BAsmCode[CodeLen++] = 0x0f;
  else if (ArgPair("DC", "H"))
    BAsmCode[CodeLen++] = 0x10;
  else if (ArgPair("Q", "DC"))
    BAsmCode[CodeLen++] = 0x0e;
  else if (ArgPair("H", "DC"))
    BAsmCode[CodeLen++] = 0x11;
  else if (ArgPair("K", "P"))
    BAsmCode[CodeLen++] = 0x08;
  else if (ArgPair("P", "K"))
    BAsmCode[CodeLen++] = 0x09;
  else if (ArgPair("P0", "Q"))
    BAsmCode[CodeLen++] = 0x0d;
  else if (ArgPair("J", "W"))
    BAsmCode[CodeLen++] = 0x1e;
  else if (ArgPair("W", "J"))
    BAsmCode[CodeLen++] = 0x1d;
  else
    WrStrErrorPos(ErrNum_InvAddrMode, &ArgStr[1]);
}

static void DecodeShift(Word Code)
{
  if (!ArgCnt)
    BAsmCode[CodeLen++] = Code;
  else if (ChkArgCnt(1,1))
  {
    Boolean OK;
    Byte Cnt;
    tSymbolFlags Flags;

    Cnt = EvalStrIntExpressionWithFlags(&ArgStr[1], UInt3, &OK, &Flags);
    if (OK)
    {
      if (Cnt == 4)
        BAsmCode[CodeLen++] = Code + 2;
      else if (mFirstPassUnknown(Flags) || (Cnt == 1))
        BAsmCode[CodeLen++] = Code;
      else
        WrStrErrorPos(ErrNum_OverRange, &ArgStr[1]);
    }
  }
}

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

  code_equate_type(SegIO, UInt8);
}

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

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

static void AddImm8(const char *NName, Word NCode)
{
  AddInstTable(InstTable, NName, NCode, DecodeImm8);
}

static void AddImm4(const char *NName, Word NCode)
{
  AddInstTable(InstTable, NName, NCode, DecodeImm4);
}

static void AddImm3(const char *NName, Word NCode)
{
  AddInstTable(InstTable, NName, NCode, DecodeImm3);
}

static void AddImm16(const char *NName, Word NCode)
{
  AddInstTable(InstTable, NName, NCode, DecodeImm16);
}

static void AddOneReg(const char *NName, Word NCode)
{
  AddInstTable(InstTable, NName, NCode, DecodeOneReg);
}

static void AddBranch(const char *NName, Word NCode)
{
  AddInstTable(InstTable, NName, NCode, DecodeBranch);
}

static void AddGenBranch(const char *NName, Word NCode)
{
  AddInstTable(InstTable, NName, NCode, DecodeGenBranch);
}

static void AddShift(const char *NName, Word NCode)
{
  AddInstTable(InstTable, NName, NCode, DecodeShift);
}

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

  add_null_pseudo(InstTable);

  AddFixed("ADC", 0x8e);
  AddFixed("AM" , 0x88);
  AddFixed("AMD", 0x89);
  AddFixed("CLR", 0x70);
  AddFixed("CM" , 0x8d);
  AddFixed("COM", 0x18);
  AddFixed("DI" , 0x1a);
  AddFixed("EI" , 0x1b);
  AddFixed("HET", Flg_CMOS | 0x2e); /* guess */
  AddFixed("HAL", Flg_CMOS | 0x2f); /* guess */
  AddFixed("INC", 0x1f);
  AddFixed("LM" , 0x16);
  AddFixed("LNK", 0x19);
  AddFixed("NM" , 0x8a);
  AddFixed("NOP", NOPCode);
  AddFixed("OM" , 0x8b);
  AddFixed("PK" , 0x0c);
  AddFixed("POP", 0x1c);
  AddFixed("ST" , 0x17);
  AddFixed("XDC", 0x2c);
  AddFixed("XM" , 0x8c);

  AddImm8("AI" , 0x24);
  AddImm8("CI" , 0x25);
  AddImm8("LI" , 0x20);
  AddImm8("NI" , 0x21);
  AddImm8("IN" , Flg_IO | 0x26);
  AddImm8("OI" , 0x22);
  AddImm8("OUT", Flg_IO | 0x27);
  AddImm8("XI" , 0x23);

  AddImm4("LIS" , 0x70);
  AddImm4("INS" , Flg_IO | 0xa0);
  AddImm4("OUTS", Flg_IO | 0xb0);

  AddImm3("LISL", 0x68);
  AddImm3("LISU", 0x60);

  AddImm16("DCI", 0x2a);
  AddImm16("JMP", Flg_Code | 0x29);
  AddImm16("PI" , Flg_Code | 0x28);

  AddOneReg("AS"  , 0xc0);
  AddOneReg("ASD" , 0xd0);
  AddOneReg("DS"  , 0x30);
  AddOneReg("NS"  , 0xf0);
  AddOneReg("XS"  , 0xe0);

  AddBranch("BC"  , 0x82);
  AddBranch("BM"  , 0x91);
  AddBranch("BNC" , 0x92);
  AddBranch("BNO" , 0x98);
  AddBranch("BNZ" , 0x94);
  AddBranch("BP"  , 0x81);
  AddBranch("BR"  , 0x90);
  AddBranch("BR7" , 0x8f);
  AddBranch("BZ"  , 0x84);
  AddGenBranch("BF", 0x90);
  AddGenBranch("BT", 0x80);

  AddShift("SL", 0x13);
  AddShift("SR", 0x12);

  AddInstTable(InstTable, "LR", 0, DecodeLR);
  AddInstTable(InstTable, "PORT", 0, DecodePORT);

  /* DS is a machine instruction on F8, so we cannot use AddIntelPseudo(): */

  AddInstTable(InstTable, "DN", eIntPseudoFlag_BigEndian | eIntPseudoFlag_AllowInt, DecodeIntelDN);
  AddInstTable(InstTable, "DB", eIntPseudoFlag_BigEndian | eIntPseudoFlag_AllowInt | eIntPseudoFlag_AllowString, DecodeIntelDB);
  AddInstTable(InstTable, "DW", eIntPseudoFlag_BigEndian | eIntPseudoFlag_AllowInt | eIntPseudoFlag_AllowString | eIntPseudoFlag_AllowFloat, DecodeIntelDW);
  AddInstTable(InstTable, "DD", eIntPseudoFlag_BigEndian | eIntPseudoFlag_AllowInt | eIntPseudoFlag_AllowString | eIntPseudoFlag_AllowFloat, DecodeIntelDD);
  AddInstTable(InstTable, "DQ", eIntPseudoFlag_BigEndian | eIntPseudoFlag_AllowInt | eIntPseudoFlag_AllowString | eIntPseudoFlag_AllowFloat, DecodeIntelDQ);
  AddInstTable(InstTable, "DT", eIntPseudoFlag_BigEndian | eIntPseudoFlag_AllowInt | eIntPseudoFlag_AllowString | eIntPseudoFlag_AllowFloat, DecodeIntelDT);
  AddInstTable(InstTable, "DO", eIntPseudoFlag_BigEndian | eIntPseudoFlag_AllowInt | eIntPseudoFlag_AllowString | eIntPseudoFlag_AllowFloat, DecodeIntelDO);
}

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

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

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

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

static Boolean IsDef_F8(void)
{
  return Memo("PORT");
}

static void SwitchTo_F8(void)
{
  const TFamilyDescr *Descr;

  TurnWords = False;
  SetIntConstMode(eIntConstModeIntel);

  Descr = FindFamilyByName("F8");
  PCSymbol = "$";
  HeaderID = Descr->Id;
  NOPCode = 0x2b;
  DivideChars = ",";
  HasAttrs = False;

  ValidSegs = (1 << SegCode) | (1 << SegData) | (1 << SegIO);
  Grans[SegCode] = 1; ListGrans[SegCode] = 1; SegLimits[SegCode] = IntTypeDefs[CodeIntType].Max;
  Grans[SegData] = 1; ListGrans[SegData] = 1; SegLimits[SegData] = 0x3f;
  Grans[SegIO  ] = 1; ListGrans[SegIO  ] = 1; SegLimits[SegIO  ] = 0xff;

  MakeCode = MakeCode_F8;
  SwitchFrom = SwitchFrom_F8;
  IsDef = IsDef_F8;
  InitFields();
}

static void SwitchTo_F8_12(void)
{
  CodeIntType = UInt12;
  MomCPUFlags = 0;
  SwitchTo_F8();
}

static void SwitchTo_F8_16(void)
{
  CodeIntType = UInt16;
  MomCPUFlags = 0;
  SwitchTo_F8();
}

static void SwitchTo_F8_12_CMOS(void)
{
  CodeIntType = UInt12;
  MomCPUFlags = Flg_CMOS;
  SwitchTo_F8();
}

void codef8_init(void)
{
  (void)AddCPU("F3850"    , SwitchTo_F8_16);
  (void)AddCPU("MK3850"   , SwitchTo_F8_16);
  (void)AddCPU("MK3870"   , SwitchTo_F8_12);
  (void)AddCPU("MK3870/10", SwitchTo_F8_12);
  (void)AddCPU("MK3870/12", SwitchTo_F8_12);
  (void)AddCPU("MK3870/20", SwitchTo_F8_12); /* == MK3870 */
  (void)AddCPU("MK3870/22", SwitchTo_F8_12); /* == MK3876 */
  (void)AddCPU("MK3870/30", SwitchTo_F8_12);
  (void)AddCPU("MK3870/32", SwitchTo_F8_12);
  (void)AddCPU("MK3870/40", SwitchTo_F8_12);
  (void)AddCPU("MK3870/42", SwitchTo_F8_12); /* == MK3872 */
  (void)AddCPU("MK3872"   , SwitchTo_F8_12);
  (void)AddCPU("MK3873"   , SwitchTo_F8_12);
  (void)AddCPU("MK3873/10", SwitchTo_F8_12);
  (void)AddCPU("MK3873/12", SwitchTo_F8_12);
  (void)AddCPU("MK3873/20", SwitchTo_F8_12); /* == MK3873 */
  (void)AddCPU("MK3873/22", SwitchTo_F8_12);
  (void)AddCPU("MK3874"   , SwitchTo_F8_12);
  (void)AddCPU("MK3875"   , SwitchTo_F8_12);
  (void)AddCPU("MK3875/22", SwitchTo_F8_12); /* == MK3876 with standby */
  (void)AddCPU("MK3875/42", SwitchTo_F8_12); /* == MK3872 with standby */
  (void)AddCPU("MK3876"   , SwitchTo_F8_12);
  (void)AddCPU("MK38P70/02",SwitchTo_F8_12); /* == MK3874, MK974xx */
  (void)AddCPU("MK38CP70/02", SwitchTo_F8_12_CMOS);
  (void)AddCPU("MK38C70"  , SwitchTo_F8_12_CMOS);
  (void)AddCPU("MK38C70/10",SwitchTo_F8_12);
  (void)AddCPU("MK38C70/20",SwitchTo_F8_12); /* == MK38C70 */
  (void)AddCPU("MK97400"  , SwitchTo_F8_12);
  (void)AddCPU("MK97410"  , SwitchTo_F8_12);
  (void)AddCPU("MK97500"  , SwitchTo_F8_16);
  (void)AddCPU("MK97501"  , SwitchTo_F8_16);
  (void)AddCPU("MK97503"  , SwitchTo_F8_16);
}
