/* code4004.c */
/*****************************************************************************/
/* SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only                     */
/*                                                                           */
/* AS-Portierung                                                             */
/*                                                                           */
/* Codegenerator Intel 4004                                                  */
/*                                                                           */
/*****************************************************************************/

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

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

#include "code4004.h"

/*---------------------------------------------------------------------------*/
/* Variablen */

static CPUVar CPU4004, CPU4040;

/*---------------------------------------------------------------------------*/
/* Parser */

/*!------------------------------------------------------------------------
 * \fn     RegVal(const char *pInp, int l)
 * \brief  decode numeric part of register name - either single hex digit or two dec digits
 * \param  pInp numberic value from argument
 * \param  length of numberic value from argument
 * \return register number or 0xff if invalid
 * ------------------------------------------------------------------------ */

static Byte RegVal(const char *pInp, int l)
{
  switch (l)
  {
    case 1:
    {
      char ch = as_toupper(*pInp);
      if ((ch >='0') && (ch <= '9'))
        return ch - '0';
      else if ((ch >='A') && (ch <= 'F'))
        return ch - 'A' + 10;
      else
        return 0xff;
    }
    case 2:
    {
      unsigned Acc = 0, z;

      for (z = 0; z < 2; z++)
      {
        if (!isdigit(pInp[z]))
          return 0xff;
        Acc = (Acc * 10) + (pInp[z] - '0');
      }
      return (Acc <= 15) ? Acc : 0xff;
    }
    default:
      return 0xff;
  }
}

/*!------------------------------------------------------------------------
 * \fn     DecodeRegCore(const char *pArg, Byte *pValue, int l)
 * \brief  decode 4 bit register
 * \param  pArg potential register argument
 * \param  pValue register # if it's a register
 * \param  l length of argument
 * \return True if it's a register
 * ------------------------------------------------------------------------ */

static Boolean DecodeRegCore(const char *pAsc, Byte *pErg, int l)
{
  if ((l < 2) || (l > 3) || (as_toupper(*pAsc) != 'R'))
    return False;

  *pErg = RegVal(pAsc + 1, l - 1);
  return (*pErg != 0xff);
}

/*!------------------------------------------------------------------------
 * \fn     DecodeRRegCore(const char *pArg, Byte *pValue)
 * \brief  decode 8 bit register pair
 * \param  pArg ASCII argument of potential register
 * \param  pValue register # if it's a register
 * \return True if it's a register
 * ------------------------------------------------------------------------ */

static Boolean DecodeRRegCore(const char *pArg, Byte *pValue)
{
  Byte UpperValue;
  const char *pPair;
  int l;

  l = strlen(pArg);

  /* syntax RnP: */

  if ((l >= 3) && (l <= 4)
   && (as_toupper(pArg[l -1]) == 'P')
   && DecodeRegCore(pArg, pValue, l - 1))
  {
    if (*pValue > 7)
      return False;
    *pValue <<= 1;
    return True;
  }

  /* syntax RnRn+1 */

  if ((l < 4) || (l > 6) || (as_toupper(*pArg) != 'R'))
    return False;

  for (pPair = pArg + 1; *pPair; pPair++)
    if (as_toupper(*pPair) == 'R')
      break;
  if ((!*pPair) || (pPair - pArg < 2) || (pPair - pArg >= l - 1))
    return False;

  *pValue = RegVal(pArg + 1, pPair - pArg - 1);
  UpperValue = RegVal(pPair + 1, l - (pPair - pArg + 1));
  return (*pValue != 0xff) && (UpperValue != 0xff) && Odd(UpperValue) && (*pValue + 1 == UpperValue);
}

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

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

/*!------------------------------------------------------------------------
 * \fn     DecodeReg(const tStrComp *pArg, Byte *pValue)
 * \brief  decode 4 bit register, including register aliases
 * \param  pArg potential register argument
 * \param  pValue register # if it's a register
 * \return True if it's a register
 * ------------------------------------------------------------------------ */

static Boolean DecodeReg(const tStrComp *pArg, Byte *pValue)
{
  tRegDescr RegDescr;
  tEvalResult EvalResult;
  tRegEvalResult RegEvalResult;

  if (DecodeRegCore(pArg->str.p_str, pValue, strlen(pArg->str.p_str)))
    return True;

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

/*!------------------------------------------------------------------------
 * \fn     DecodeRReg(const tStrComp *pArg, Byte *pValue)
 * \brief  decode 8 bit register pair, including register aliases
 * \param  pArg potential register argument
 * \param  pValue register # if it's a register
 * \return True if it's a register
 * ------------------------------------------------------------------------ */

static Boolean DecodeRReg(const tStrComp *pArg, Byte *pValue)
{
  tRegDescr RegDescr;
  tEvalResult EvalResult;
  tRegEvalResult RegEvalResult;

  if (DecodeRRegCore(pArg->str.p_str, pValue))
    return True;

  RegEvalResult = EvalStrRegExpressionAsOperand(pArg, &RegDescr, &EvalResult, eSymbolSize16Bit, True);
  *pValue = RegDescr.Reg;
  return (RegEvalResult == eIsReg);
}

/*---------------------------------------------------------------------------*/
/* Hilfsdekoder */

static void DecodeFixed(Word Code)
{
  CPUVar MinCPU = CPU4004 + (CPUVar)Hi(Code);

  if (ChkArgCnt(0, 0)
   && ChkMinCPU(MinCPU))
  {
    BAsmCode[0] = Lo(Code);
    CodeLen = 1;
  }
}

static void DecodeOneReg(Word Code)
{
  Byte Erg;

  if (!ChkArgCnt(1, 1));
  else if (DecodeReg(&ArgStr[1], &Erg))
  {
    BAsmCode[0] = Lo(Code) + Erg;
    CodeLen = 1;
  }
}

static void DecodeOneRReg(Word Code)
{
  Byte Erg;

  if (!ChkArgCnt(1, 1));
  else if (DecodeRReg(&ArgStr[1], &Erg))
  {
    BAsmCode[0] = Lo(Code) + Erg;
    CodeLen = 1;
  }
}

static void DecodeAccReg(Word Code)
{
  Byte Erg;

  if (!ChkArgCnt(1, 2));
  else if ((ArgCnt == 2) && (as_strcasecmp(ArgStr[1].str.p_str, "A"))) WrError(ErrNum_InvAddrMode);
  else if (DecodeReg(&ArgStr[ArgCnt], &Erg))
  {
    BAsmCode[0] = Lo(Code) + Erg;
    CodeLen = 1;
  }
}

static void DecodeImm4(Word Code)
{
  Boolean OK;

  if (ChkArgCnt(1, 1))
  {
    BAsmCode[0] = EvalStrIntExpression(&ArgStr[1], UInt4, &OK);
    if (OK)
    {
      BAsmCode[0] += Lo(Code);
      CodeLen = 1;
    }
  }
}

static void DecodeFullJmp(Word Index)
{
  Word Adr;
  Boolean OK;

  if (ChkArgCnt(1, 1))
  {
    Adr = EvalStrIntExpression(&ArgStr[1], UInt12, &OK);
    if (OK)
    {
      BAsmCode[0] = 0x40 + (Index << 4) + Hi(Adr);
      BAsmCode[1] = Lo(Adr);
      CodeLen = 2;
    }
  }
}

static void DecodeISZ(Word Index)
{
  Word Adr;
  Boolean OK;
  tSymbolFlags Flags;
  Byte Erg;
  UNUSED(Index);

  if (!ChkArgCnt(2, 2));
  else if (DecodeReg(&ArgStr[1], &Erg))
  {
    Adr = EvalStrIntExpressionWithFlags(&ArgStr[2], UInt12, &OK, &Flags);
    if (OK && ChkSamePage(EProgCounter() + 1, Adr, 8, Flags))
    {
      BAsmCode[0] = 0x70 + Erg;
      BAsmCode[1] = Lo(Adr);
      CodeLen = 2;
    }
  }
}

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

  if (ChkArgCnt(2, 2))
  {
    Boolean OK = True;
    char *pCond;

    BAsmCode[0] = 0;
    for (pCond = ArgStr[1].str.p_str; *pCond; pCond++)
      switch (as_toupper(*pCond))
      {
        case 'Z': BAsmCode[0] |= 4; break;
        case 'C': BAsmCode[0] |= 2; break;
        case 'T': BAsmCode[0] |= 1; break;
        case 'N': BAsmCode[0] |= 8; break;
        default: OK = False;
      }
    if (!OK)
      BAsmCode[0] = EvalStrIntExpression(&ArgStr[1], UInt4, &OK);

    if (OK)
    {
      Word AdrInt;
      tSymbolFlags Flags;

      AdrInt = EvalStrIntExpressionWithFlags(&ArgStr[2], UInt12, &OK, &Flags);
      if (OK)
      {
        if (!mSymbolQuestionable(Flags) && (Hi(EProgCounter() + 2) != Hi(AdrInt))) WrError(ErrNum_JmpDistTooBig);
        else
        {
          BAsmCode[0] |= 0x10;
          BAsmCode[1] = Lo(AdrInt);
          CodeLen = 2;
        }
      }
    }
  }
}

static void DecodeFIM(Word Index)
{
  Boolean OK;
  UNUSED(Index);

  if (!ChkArgCnt(2, 2));
  else if (DecodeRReg(&ArgStr[1], BAsmCode))
  {
    BAsmCode[1] = EvalStrIntExpression(&ArgStr[2], Int8, &OK);
    if (OK)
    {
      BAsmCode[0] |= 0x20;
      CodeLen = 2;
    }
  }
}

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

  DecodeDATA(Int8, Int4);
}

/*---------------------------------------------------------------------------*/
/* Codetabellenverwaltung */

static void AddFixed(const char *NName, Word NCode, CPUVar NMin)
{
  NCode |= ((Word)(NMin - CPU4004)) << 8;
  AddInstTable(InstTable, NName, NCode, DecodeFixed);
}

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

static void AddOneRReg(const char *NName, Byte NCode)
{
  AddInstTable(InstTable, NName, NCode, DecodeOneRReg);
}

static void AddAccReg(const char *NName, Byte NCode)
{
  AddInstTable(InstTable, NName, NCode, DecodeAccReg);
}

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

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

  add_null_pseudo(InstTable);

  AddFixed("NOP" , 0x00, CPU4004); AddFixed("WRM" , 0xe0, CPU4004);
  AddFixed("WMP" , 0xe1, CPU4004); AddFixed("WRR" , 0xe2, CPU4004);
  AddFixed("WPM" , 0xe3, CPU4004); AddFixed("WR0" , 0xe4, CPU4004);
  AddFixed("WR1" , 0xe5, CPU4004); AddFixed("WR2" , 0xe6, CPU4004);
  AddFixed("WR3" , 0xe7, CPU4004); AddFixed("SBM" , 0xe8, CPU4004);
  AddFixed("RDM" , 0xe9, CPU4004); AddFixed("RDR" , 0xea, CPU4004);
  AddFixed("ADM" , 0xeb, CPU4004); AddFixed("RD0" , 0xec, CPU4004);
  AddFixed("RD1" , 0xed, CPU4004); AddFixed("RD2" , 0xee, CPU4004);
  AddFixed("RD3" , 0xef, CPU4004); AddFixed("CLB" , 0xf0, CPU4004);
  AddFixed("CLC" , 0xf1, CPU4004); AddFixed("IAC" , 0xf2, CPU4004);
  AddFixed("CMC" , 0xf3, CPU4004); AddFixed("CMA" , 0xf4, CPU4004);
  AddFixed("RAL" , 0xf5, CPU4004); AddFixed("RAR" , 0xf6, CPU4004);
  AddFixed("TCC" , 0xf7, CPU4004); AddFixed("DAC" , 0xf8, CPU4004);
  AddFixed("TCS" , 0xf9, CPU4004); AddFixed("STC" , 0xfa, CPU4004);
  AddFixed("DAA" , 0xfb, CPU4004); AddFixed("KBP" , 0xfc, CPU4004);
  AddFixed("DCL" , 0xfd, CPU4004); AddFixed("AD0" , 0xec, CPU4004);
  AddFixed("AD1" , 0xed, CPU4004); AddFixed("AD2" , 0xee, CPU4004);
  AddFixed("AD3" , 0xef, CPU4004);

  AddFixed("HLT" , 0x01, CPU4040); AddFixed("BBS" , 0x02, CPU4040);
  AddFixed("LCR" , 0x03, CPU4040); AddFixed("OR4" , 0x04, CPU4040);
  AddFixed("OR5" , 0x05, CPU4040); AddFixed("AN6" , 0x06, CPU4040);
  AddFixed("AN7" , 0x07, CPU4040); AddFixed("DB0" , 0x08, CPU4040);
  AddFixed("DB1" , 0x09, CPU4040); AddFixed("SB0" , 0x0a, CPU4040);
  AddFixed("SB1" , 0x0b, CPU4040); AddFixed("EIN" , 0x0c, CPU4040);
  AddFixed("DIN" , 0x0d, CPU4040); AddFixed("RPM" , 0x0e, CPU4040);

  AddOneReg("INC" , 0x60);

  AddOneRReg("SRC" , 0x21);
  AddOneRReg("FIN" , 0x30);
  AddOneRReg("JIN" , 0x31);

  AddAccReg("ADD" , 0x80); AddAccReg("SUB" , 0x90);
  AddAccReg("LD"  , 0xa0); AddAccReg("XCH" , 0xb0);

  AddImm4("BBL" , 0xc0); AddImm4("LDM" , 0xd0);

  AddInstTable(InstTable,"JCN", 0, DecodeJCN);
  AddInstTable(InstTable,"JCM", 0, DecodeJCN);
  AddInstTable(InstTable,"JUN", 0, DecodeFullJmp);
  AddInstTable(InstTable,"JMS", 1, DecodeFullJmp);
  AddInstTable(InstTable,"ISZ", 0, DecodeISZ);
  AddInstTable(InstTable,"FIM", 0, DecodeFIM);

  AddInstTable(InstTable, "DS", 0, DecodeRES);
  AddInstTable(InstTable, "DATA", 0, DecodeDATA_4004);
  AddInstTable(InstTable, "REG", 0, CodeREG);
}

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

/*---------------------------------------------------------------------------*/
/* Callbacks */

/*!------------------------------------------------------------------------
 * \fn     InternSymbol_4004
 * \brief  Built-in symbols for 4004
 * \param  pArg source argument
 * \param  pResult buffer for result
 * ------------------------------------------------------------------------ */

static void InternSymbol_4004(char *pArg, TempResult *pResult)
{
  Byte RegValue;

  if (DecodeRegCore(pArg, &RegValue, strlen(pArg)))
  {
    pResult->Typ = TempReg;
    pResult->DataSize = eSymbolSize8Bit;
    pResult->Contents.RegDescr.Reg = RegValue;
    pResult->Contents.RegDescr.Dissect = DissectReg_4004;
    pResult->Contents.RegDescr.compare = NULL;
  }
  else if (DecodeRRegCore(pArg, &RegValue))
  {
    pResult->Typ = TempReg;
    pResult->DataSize = eSymbolSize16Bit;
    pResult->Contents.RegDescr.Reg = RegValue;
    pResult->Contents.RegDescr.Dissect = DissectReg_4004;
    pResult->Contents.RegDescr.compare = NULL;
  }
}

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

static Boolean IsDef_4004(void)
{
  return Memo("REG");
}

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

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

  FoundDescr = FindFamilyByName("4004/4040");

  TurnWords = False;
  SetIntConstMode(eIntConstModeIntel);

  PCSymbol = "$";
  HeaderID = FoundDescr->Id;
  NOPCode = 0x00;
  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_4004;
  IsDef = IsDef_4004;
  InternSymbol = InternSymbol_4004;
  DissectReg = DissectReg_4004;
  SwitchFrom = SwitchFrom_4004;

  InitFields();
}

/*---------------------------------------------------------------------------*/
/* Initialisierung */

void code4004_init(void)
{
  CPU4004 = AddCPU("4004", SwitchTo_4004);
  CPU4040 = AddCPU("4040", SwitchTo_4004);
}
