/* code2650.c */
/*****************************************************************************/
/* SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only                     */
/*                                                                           */
/* AS-Portierung                                                             */
/*                                                                           */
/* Codegenerator Signetics 2650                                              */
/*                                                                           */
/*****************************************************************************/

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

#include "nls.h"
#include "chunks.h"
#include "bpemu.h"
#include "strutil.h"

#include "asmdef.h"
#include "asmsub.h"
#include "asmpars.h"
#include "asmitree.h"
#include "codevars.h"
#include "headids.h"
#include "intpseudo.h"
#include "errmsg.h"
#include "onoff_common.h"

#include "code2650.h"

#define ADDR_INT UInt15

/*--------------------------------------------------------------------------*/
/* Local Variables */

static CPUVar CPU2650;
static Boolean splitted_arg;

/*--------------------------------------------------------------------------*/
/* Expression Parsers */

static Boolean DecodeReg(const char *pAsc, Byte *pRes)
{
  Boolean Result;

  Result = ((strlen(pAsc) == 2) && (as_toupper(pAsc[0]) == 'R') && (pAsc[1] >= '0') && (pAsc[1] <= '3'));
  if (Result)
    *pRes = pAsc[1] - '0';
  return Result;
}

/*!------------------------------------------------------------------------
 * \fn     DecodeCondition(const tStrComp *p_arg, Byte *p_ret)
 * \brief  decode condition code
 * \param  p_arg source argument
 * \param  p_ret binary encoded argument
 * \return true if success
 * ------------------------------------------------------------------------ */

static Boolean DecodeCondition(const tStrComp *p_arg, Byte *p_ret)
{
  const char *p_asc = p_arg->str.p_str;

  if (!as_strcasecmp(p_asc, "EQ") || !as_strcasecmp(p_asc, "Z"))
  {
    *p_ret = 0;
    return True;
  }
  else if (!as_strcasecmp(p_asc, "GT") || !as_strcasecmp(p_asc, "P"))
  {
    *p_ret = 1;
    return True;
  }
  else if (!as_strcasecmp(p_asc, "LT") || !as_strcasecmp(p_asc, "N"))
  {
    *p_ret = 2;
    return True;
  }
  else if ((!as_strcasecmp(p_asc, "ALWAYS")) || (!as_strcasecmp(p_asc, "UN")))
  {
    *p_ret = 3;
    return True;
  }
  else
  {
    Boolean ok;

    *p_ret = EvalStrIntExpression(p_arg, UInt2, &ok);
    return ok;
  }
}

/*!------------------------------------------------------------------------
 * \fn     page_rel_ok(Word dest, Word src, tSymbolFlags flags, Word *p_dist, Boolean is_branch)
 * \brief  check whether relative addressing in same 8K page is possible
 * \param  dest target address
 * \param  src current (source) address
 * \param  flags expression evaluation flags
 * \param  p_dist resulting distance
 * \param  is_branch code or data access?
 * \return True if distance is in range
 * ------------------------------------------------------------------------ */

#define PAGE_MASK 0x1fff

static Boolean page_rel_ok(Word dest, Word src, tSymbolFlags flags, Word *p_dist, Boolean is_branch)
{
  if (((src & ~PAGE_MASK) != (dest & ~PAGE_MASK)) && !mSymbolQuestionable(flags))
  {
    WrError(is_branch ? ErrNum_JmpTargOnDiffPage : ErrNum_TargOnDiffPage);
    return False;
  }

  *p_dist = (dest - src) & PAGE_MASK;
  if (((*p_dist < 0x1fc0) && (*p_dist > 0x3f)) && !mSymbolQuestionable(flags))
  {
    WrError(is_branch ? ErrNum_JmpDistTooBig : ErrNum_DistTooBig);
    return False;
  }

  return True;
}

/*!------------------------------------------------------------------------
 * \fn     Boolean page_abs_ok(Word dest, Word src, tSymbolFlags flags, Word *p_dest, Boolean is_branch)
 * \brief  check whether absolute address is in same 8K page
 * \param  dest target address
 * \param  src current (source) address
 * \param  flags expression evaluation flags
 * \param  p_dest resulting address in machine instruction
 * \param  is_branch code or data access?
 * \return True if distance is in range
 * ------------------------------------------------------------------------ */

static Boolean page_abs_ok(Word dest, Word src, tSymbolFlags flags, Word *p_dest, Boolean is_branch)
{
  if (((src & ~PAGE_MASK) != (dest & ~PAGE_MASK)) && !mSymbolQuestionable(flags))
  {
    WrError(is_branch ? ErrNum_JmpTargOnDiffPage : ErrNum_TargOnDiffPage);
    return False;
  }

  *p_dest = dest & PAGE_MASK;
  return True;
}

/*--------------------------------------------------------------------------*/
/* Instruction Decoders */

static void DecodeFixed(Word Index)
{
  if (ChkArgCnt(0, 0))
  {
    BAsmCode[0] = Index; CodeLen = 1;
  }
}

/*!------------------------------------------------------------------------
 * \fn     DecodeOneReg(Word Index)
 * \brief  decode instructions taking a single register as argument
 * \param  Index machine code of instruction when register 0 is used
 * ------------------------------------------------------------------------ */

static void DecodeOneReg(Word Index)
{
  Byte Reg;

  if (!ChkArgCnt(1, 1));
  else if (!DecodeReg(ArgStr[1].str.p_str, &Reg)) WrStrErrorPos(ErrNum_InvReg, &ArgStr[1]);
  else
  {
    BAsmCode[0] = Index | Reg;
    CodeLen = 1;
  }
}

/*!------------------------------------------------------------------------
 * \fn     DecodeOneReg_NoZero(Word Index)
 * \brief  decode instructions taking a single register except R0 as argument
 * \param  Index machine code of instruction register # is added to
 * ------------------------------------------------------------------------ */


static void DecodeOneReg_NoZero(Word Index)
{
  Byte Reg;

  if (!ChkArgCnt(1, 1));
  else if (!DecodeReg(ArgStr[1].str.p_str, &Reg)) WrStrErrorPos(ErrNum_InvReg, &ArgStr[1]);
  else if (!Reg) WrStrErrorPos(ErrNum_InvReg, &ArgStr[1]);
  else
  {
    BAsmCode[0] = Index | Reg;
    CodeLen = 1;
  }
}

/*!------------------------------------------------------------------------
 * \fn     DecodeLODZ(Word Index)
 * \brief  decode LODZ instruction
 * \param  Index machine code of instruction when register 0 is used
 * ------------------------------------------------------------------------ */

static void DecodeLODZ(Word Index)
{
  Byte Reg;

  if (!ChkArgCnt(1, 1));
  else if (!DecodeReg(ArgStr[1].str.p_str, &Reg)) WrStrErrorPos(ErrNum_InvReg, &ArgStr[1]);
  else
  {
    /* LODZ R0 shall be encoded as IORZ R0 */
    BAsmCode[0] = Reg ? (Index | Reg) : 0x60;
    CodeLen = 1;
  }
}

static void DecodeImm(Word Index)
{
  Boolean OK;

  if (ChkArgCnt(1, 1))
  {
    BAsmCode[1] = EvalStrIntExpression(&ArgStr[1], Int8, &OK);
    if (OK)
    {
      BAsmCode[0] = Index; CodeLen = 2;
    }
  }
}

static void DecodeRegImm(Word Index)
{
  Byte Reg;
  Boolean OK;

  if (!ChkArgCnt(2, 2));
  else if (!DecodeReg(ArgStr[1].str.p_str, &Reg)) WrStrErrorPos(ErrNum_InvReg, &ArgStr[1]);
  else
  {
    BAsmCode[1] = EvalStrIntExpression(&ArgStr[2], Int8, &OK);
    if (OK)
    {
      BAsmCode[0] = Index | Reg; CodeLen = 2;
    }
  }
}

/*!------------------------------------------------------------------------
 * \fn     void DecodeRegAbs(Word code)
 * \brief  handle instruction with register & absolute address
 * \param  code instruction machine code
 * ------------------------------------------------------------------------ */

static void DecodeRegAbs(Word code)
{
  Byte dest_reg;

  if (!ChkArgCnt(2, 4));
  else if (!DecodeReg(ArgStr[1].str.p_str, &dest_reg)) WrStrErrorPos(ErrNum_InvReg, &ArgStr[1]);
  else
  {
    Boolean ok, ind_flag = *ArgStr[2].str.p_str == '*';
    Word abs_val;
    tSymbolFlags flags;
    Byte index_reg;

    abs_val = EvalStrIntExpressionOffsWithFlags(&ArgStr[2], ind_flag, ADDR_INT, &ok, &flags);
    if (ok && page_abs_ok(abs_val, EProgCounter() + 3, flags, &abs_val, False))
    {
      BAsmCode[0] = code;
      BAsmCode[1] = Hi(abs_val);
      BAsmCode[2] = Lo(abs_val);
      if (ind_flag)
        BAsmCode[1] |= 0x80;
      if (ArgCnt == 2)
      {
        BAsmCode[0] |= dest_reg;
        CodeLen = 3;
      }
      else
      {
        if (!DecodeReg(ArgStr[3].str.p_str, &index_reg)) WrStrErrorPos(ErrNum_InvReg, &ArgStr[3]);
        else if (dest_reg != 0) WrError(ErrNum_InvAddrMode);
        else
        {
          BAsmCode[0] |= index_reg;
          if (ArgCnt == 3)
          {
            BAsmCode[1] |= 0x60;
            CodeLen = 3;
          }
          else if (!strcmp(ArgStr[4].str.p_str, "-"))
          {
            BAsmCode[1] |= 0x40;
            CodeLen = 3;
          }
          else if (!strcmp(ArgStr[4].str.p_str, "+"))
          {
            BAsmCode[1] |= 0x20;
            CodeLen = 3;
          }
          else
            WrError(ErrNum_InvAddrMode);
        }
      }
    }
  }
}

/*!------------------------------------------------------------------------
 * \fn     DecodeRegRel(Word code)
 * \brief  handle instructions with register & relative argument
 * \param  code instruction machine code
 * ------------------------------------------------------------------------ */

static void DecodeRegRel(Word code)
{
  Byte reg;

  if (!ChkArgCnt(2, 2));
  else if (!DecodeReg(ArgStr[1].str.p_str, &reg)) WrStrErrorPos(ErrNum_InvReg, &ArgStr[1]);
  else
  {
    Word dest, dist;
    tSymbolFlags flags;
    Boolean ind_flag, ok;

    BAsmCode[0] = code | reg;
    ind_flag = *ArgStr[2].str.p_str == '*';
    dest = EvalStrIntExpressionOffsWithFlags(&ArgStr[2], ind_flag, ADDR_INT, &ok, &flags);
    if (ok && page_rel_ok(dest, EProgCounter() + 2, flags, &dist, False))
    {
      BAsmCode[1] = dist & 0x7f;
      if (ind_flag)
        BAsmCode[1] |= 0x80;
      CodeLen = 2;
    }
  }
}

static void DecodeCondAbs(Word Index)
{
  Byte Cond;
  Word Address;
  Boolean OK, IndFlag;

  if (ChkArgCnt(2, 2)
   && DecodeCondition(&ArgStr[1], &Cond))
  {
    IndFlag = *ArgStr[2].str.p_str == '*';
    Address = EvalStrIntExpressionOffs(&ArgStr[2], IndFlag, ADDR_INT, &OK);
    if (OK)
    {
      BAsmCode[0] = Index | Cond;
      BAsmCode[1] = Hi(Address);
      if (IndFlag)
        BAsmCode[1] |= 0x80;
      BAsmCode[2] = Lo(Address);
      CodeLen = 3;
    }
  }
}

/*!------------------------------------------------------------------------
 * \fn     DecodeCondRel(Word code)
 * \brief  decode relative branches
 * \param  code machine code
 * ------------------------------------------------------------------------ */

static void DecodeCondRel(Word code)
{
  Byte cond;

  if (ChkArgCnt(2, 2)
   && DecodeCondition(&ArgStr[1], &cond))
  {
    Boolean ind_flag, ok;
    tSymbolFlags flags;
    Word dist, dest;

    BAsmCode[0] = code | cond;
    ind_flag = *ArgStr[2].str.p_str == '*';
    dest = EvalStrIntExpressionOffsWithFlags(&ArgStr[2], ind_flag, ADDR_INT, &ok, &flags);
    if (ok && page_rel_ok(dest, EProgCounter() + 2, flags, &dist, True))
    {
      BAsmCode[1] = dist & 0x7f;
      if (ind_flag)
        BAsmCode[1] |= 0x80;
      CodeLen = 2;
    }
  }
}

static void DecodeRegAbs2(Word Index)
{
  Byte Reg;
  Word AbsVal;
  Boolean IndFlag, OK;

  if (!ChkArgCnt(2, 2));
  else if (!DecodeReg(ArgStr[1].str.p_str, &Reg)) WrStrErrorPos(ErrNum_InvReg, &ArgStr[1]);
  else
  {
    BAsmCode[0] = Index | Reg;
    IndFlag = *ArgStr[2].str.p_str == '*';
    AbsVal = EvalStrIntExpressionOffs(&ArgStr[2], IndFlag, ADDR_INT, &OK);
    if (OK)
    {
      BAsmCode[1] = Hi(AbsVal);
      if (IndFlag)
        BAsmCode[1] |= 0x80;
      BAsmCode[2] = Lo(AbsVal);
      CodeLen = 3;
    }
  }
}

static void DecodeBrAbs(Word Index)
{
  Byte Reg = 3;
  Word AbsVal;
  Boolean IndFlag, OK;

  if (!ChkArgCnt(1, 2));
  else if ((ArgCnt == 2) && (!DecodeReg(ArgStr[2].str.p_str, &Reg))) WrStrErrorPos(ErrNum_InvReg, &ArgStr[2]);
  else if (Reg != 3) WrError(ErrNum_InvAddrMode);
  else
  {
    BAsmCode[0] = Index | Reg;
    IndFlag = *ArgStr[1].str.p_str == '*';
    AbsVal = EvalStrIntExpressionOffs(&ArgStr[1], IndFlag, ADDR_INT, &OK);
    if (OK)
    {
      BAsmCode[1] = Hi(AbsVal);
      if (IndFlag)
        BAsmCode[1] |= 0x80;
      BAsmCode[2] = Lo(AbsVal);
      CodeLen = 3;
    }
  }
}

static void DecodeCond(Word Index)
{
  Byte Cond;

  if (ChkArgCnt(1, 1)
   && DecodeCondition(&ArgStr[1], &Cond))
  {
    BAsmCode[0] = Index | Cond;
    CodeLen = 1;
  }
}

/*!------------------------------------------------------------------------
 * \fn     DecodeZero(Word code)
 * \brief  decode zero page branch instructions
 * \param  code machine code
 * ------------------------------------------------------------------------ */

static void DecodeZero(Word code)
{
  if (ChkArgCnt(1, 1))
  {
    Boolean ind_flag, ok;
    tSymbolFlags flags;
    Word dest, dist;

    BAsmCode[0] = code;
    ind_flag = *ArgStr[1].str.p_str == '*';
    dest = EvalStrIntExpressionOffsWithFlags(&ArgStr[1], ind_flag, ADDR_INT, &ok, &flags);
    if (ok && page_rel_ok(dest, 0x0000, flags, &dist, True))
    {
      BAsmCode[1] = dist & 0x7f;
      if (ind_flag)
        BAsmCode[1] |= 0x80;
      CodeLen = 2;
    }
  }
}

/*!------------------------------------------------------------------------
 * \fn     check_no_splitted_arg(Word code)
 * \brief  complain if pseudo op got derived by splitting off argument
 * ------------------------------------------------------------------------ */

static void check_no_splitted_arg(Word code)
{
  UNUSED(code);
  if (splitted_arg)
    WrStrErrorPos(ErrNum_InvArg, &ArgStr[1]);
}

/*--------------------------------------------------------------------------*/
/* Code Table Handling */

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

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

static void AddImm(const char *pName, Word Code)
{
  AddInstTable(InstTable, pName, Code, DecodeImm);
}

static void AddRegImm(const char *pName, Word Code)
{
  AddInstTable(InstTable, pName, Code, DecodeRegImm);
}

static void AddRegAbs(const char *pName, Word Code)
{
  AddInstTable(InstTable, pName, Code, DecodeRegAbs);
}

static void AddRegRel(const char *pName, Word Code)
{
  AddInstTable(InstTable, pName, Code, DecodeRegRel);
}

static void AddCondAbs(const char *pName, Word Code)
{
  AddInstTable(InstTable, pName, Code, DecodeCondAbs);
}

static void AddCondRel(const char *pName, Word Code)
{
  AddInstTable(InstTable, pName, Code, DecodeCondRel);
}

static void AddRegAbs2(const char *pName, Word Code)
{
  AddInstTable(InstTable, pName, Code, DecodeRegAbs2);
}

static void AddBrAbs(const char *pName, Word Code)
{
  AddInstTable(InstTable, pName, Code, DecodeBrAbs);
}

static void AddCond(const char *pName, Word Code)
{
  AddInstTable(InstTable, pName, Code, DecodeCond);
}

static void AddZero(const char *pName, Word Code)
{
  AddInstTable(InstTable, pName, Code, DecodeZero);
}

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

  AddFixed("NOP", 0xc0);
  AddFixed("HALT", 0x40);
  AddFixed("LPSL", 0x93);
  AddFixed("LPSU", 0x92);
  AddFixed("SPSL", 0x13);
  AddFixed("SPSU", 0x12);

  AddOneReg("ADDZ", 0x80);
  /* ANDZ R0 is not allowed and decodes as HALT */
  AddInstTable(InstTable, "ANDZ", 0x40, DecodeOneReg_NoZero);
  AddOneReg("COMZ", 0xe0);
  AddOneReg("DAR", 0x94);
  AddOneReg("EORZ", 0x20);
  AddOneReg("IORZ", 0x60);
  AddInstTable(InstTable, "LODZ", 0x00, DecodeLODZ);
  AddOneReg("REDC", 0x30);
  AddOneReg("REDD", 0x70);
  AddOneReg("RRL", 0xd0);
  AddOneReg("RRR", 0x50);
  /* STRZ R0 is not allowed and decodes as NOP */
  AddInstTable(InstTable, "STRZ", 0xc0, DecodeOneReg_NoZero);
  AddOneReg("SUBZ", 0xa0);
  AddOneReg("WRTC", 0xb0);
  AddOneReg("WRTD", 0xf0);

  AddImm("CPSL", 0x75);
  AddImm("CPSU", 0x74);
  AddImm("PPSL", 0x77);
  AddImm("PPSU", 0x76);
  AddImm("TPSL", 0xb5);
  AddImm("TPSU", 0xb4);

  AddRegImm("ADDI", 0x84);
  AddRegImm("ANDI", 0x44);
  AddRegImm("COMI", 0xe4);
  AddRegImm("EORI", 0x24);
  AddRegImm("IORI", 0x64);
  AddRegImm("LODI", 0x04);
  AddRegImm("REDE", 0x54);
  AddRegImm("SUBI", 0xa4);
  AddRegImm("TMI", 0xf4);
  AddRegImm("WRTE", 0xd4);

  AddRegAbs("ADDA", 0x8c);
  AddRegAbs("ANDA", 0x4c);
  AddRegAbs("COMA", 0xec);
  AddRegAbs("EORA", 0x2c);
  AddRegAbs("IORA", 0x6c);
  AddRegAbs("LODA", 0x0c);
  AddRegAbs("STRA", 0xcc);
  AddRegAbs("SUBA", 0xac);

  AddRegRel("ADDR", 0x88);
  AddRegRel("ANDR", 0x48);
  AddRegRel("BDRR", 0xf8);
  AddRegRel("BIRR", 0xd8);
  AddRegRel("BRNR", 0x58);
  AddRegRel("BSNR", 0x78);
  AddRegRel("COMR", 0xe8);
  AddRegRel("EORR", 0x28);
  AddRegRel("IORR", 0x68);
  AddRegRel("LODR", 0x08);
  AddRegRel("STRR", 0xc8);
  AddRegRel("SUBR", 0xa8);

  AddCondAbs("BCFA", 0x9c);
  AddCondAbs("BCTA", 0x1c);
  AddCondAbs("BSFA", 0xbc);
  AddCondAbs("BSTA", 0x3c);

  AddCondRel("BCFR", 0x98);
  AddCondRel("BCTR", 0x18);
  AddCondRel("BSFR", 0xb8);
  AddCondRel("BSTR", 0x38);

  AddRegAbs2("BDRA", 0xfc);
  AddRegAbs2("BIRA", 0xdc);
  AddRegAbs2("BRNA", 0x5c);
  AddRegAbs2("BSNA", 0x7c);

  AddBrAbs("BSXA", 0xbf);
  AddBrAbs("BXA", 0x9f);

  AddCond("RETC", 0x14);
  AddCond("RETE", 0x34);

  AddZero("ZBRR", 0x9b);
  AddZero("ZBSR", 0xbb);

  inst_table_set_prefix_proc(InstTable, check_no_splitted_arg, 0);
  AddInstTable(InstTable, "RES", 1, DecodeIntelDS);
  AddInstTable(InstTable, "ACON", eIntPseudoFlag_BigEndian | eIntPseudoFlag_AllowInt | eIntPseudoFlag_AllowString, DecodeIntelDW);
  AddIntelPseudo(InstTable, eIntPseudoFlag_DynEndian);
}

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

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

static void MakeCode_2650(void)
{
  char *pPos;

  /* Nullanweisung */

  if ((*OpPart.str.p_str == '\0') && (ArgCnt == 0))
    return;

  /* try to split off first (register) operand from instruction */

  pPos = strchr(OpPart.str.p_str, ',');
  splitted_arg = !!pPos;
  if (splitted_arg)
  {
    InsertArg(1, strlen(OpPart.str.p_str));
    StrCompSplitRight(&OpPart, &ArgStr[1], pPos);
  }

  /* alles aus der Tabelle */

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

static Boolean IsDef_2650(void)
{
  return FALSE;
}

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

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

  TurnWords = False;
  SetIntConstMode(eIntConstModeMoto);

  pDescr = FindFamilyByName("2650");
  PCSymbol = "$"; HeaderID = pDescr->Id; NOPCode = 0xc0;
  DivideChars = ","; HasAttrs = False;

  ValidSegs = (1 << SegCode);
  Grans[SegCode] = 1; ListGrans[SegCode] = 1; SegInits[SegCode] = 0;
  SegLimits[SegCode] = IntTypeDefs[ADDR_INT].Max;

  MakeCode = MakeCode_2650; IsDef = IsDef_2650;
  SwitchFrom = SwitchFrom_2650; InitFields();

  onoff_bigendian_add();
}

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

void code2650_init(void)
{
  CPU2650 = AddCPU("2650", SwitchTo_2650);
}
