/* codecp1600.c */
/*****************************************************************************/
/* SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only                     */
/*                                                                           */
/* AS-Portierung                                                             */
/*                                                                           */
/* Codegenerator GI CP-1600                                                  */
/*                                                                           */
/*****************************************************************************/

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

#include "nls.h"
#include "strutil.h"
#include "chunks.h"
#include "asmdef.h"
#include "asmsub.h"
#include "asmpars.h"
#include "asmitree.h"
#include "codevars.h"
#include "codepseudo.h"
#include "headids.h"
#include "errmsg.h"
#include "onoff_common.h"

#include "codecp1600.h"

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

static CPUVar CPUCP1600;

static Word Bits;
static Word Mask;
static Boolean PrefixedSDBD;

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

static Boolean DecReg(const char *pAsc, Word *reg, Boolean errMsg)
{
	if (strlen(pAsc) != 2)
	{
		if(errMsg) WrError(ErrNum_InvRegName);
		return False;
	}

	if (toupper(pAsc[0]) == 'R' && pAsc[1] >= '0' && pAsc[1] <= '7' )
	{
		*reg = pAsc[1] - '0';
		return True;
	}

	if (errMsg) WrError(ErrNum_InvRegName);
	return False;
}

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

static void DecodeRegAdrMVO(Word Index)
{
	Word reg;
	Word adr;
	Boolean OK;

	PrefixedSDBD = False;
		
	if (ChkArgCnt(2,2))
	{
		if (!DecReg(ArgStr[1].str.p_str, &reg, True)) return;

		adr = EvalStrIntExpression(&ArgStr[2], UInt16, &OK);
		if (!OK) return;

		if (adr & Mask)
		{
			WrError(ErrNum_OverRange);
			return;
		}

		WAsmCode[0] = Index | reg;
		WAsmCode[1] = adr;
		CodeLen = 2;
	}
}

static void DecodeRegRegMVO(Word Index)
{
	Word regs;
	Word regd;

	PrefixedSDBD = False;

	if (ChkArgCnt(2,2))
	{
		if(!DecReg(ArgStr[1].str.p_str, &regs, True)) return;

		if(!DecReg(ArgStr[2].str.p_str, &regd, True)) return;
		if (regd == 0 || regd == 7)
		{
			WrError(ErrNum_InvReg);
			return;
		}

		WAsmCode[0] = Index | (regd << 3) | regs;
		CodeLen = 1;
	}
}

static void DecodeRegImmMVO(Word Index)
{
	Word reg;
	LongInt val;
	Boolean OK;

	PrefixedSDBD = False;
		
	if (ChkArgCnt(2,2))
	{
		if (!DecReg(ArgStr[1].str.p_str, &reg, True)) return;

		val = EvalStrIntExpression(&ArgStr[2], Int32, &OK);
		if (!OK) return;
		if (!ChkRange(val, -32768, 65535)) return;

		if (val & Mask)
		{
			WrError(ErrNum_OverRange);
			return;
		}

		WAsmCode[0] = Index | reg;
		WAsmCode[1] = val;
		CodeLen = 2;
	}
}

static void DecodeAdrReg(Word Index)
{
	Word reg;
	Word adr;
	Boolean OK;

	PrefixedSDBD = False;
		
	if (ChkArgCnt(2,2))
	{
		adr = EvalStrIntExpression(&ArgStr[1], UInt16, &OK);
		if (!OK) return;

		if (adr & Mask)
		{
			WrError(ErrNum_OverRange);
			return;
		}

		if (!DecReg(ArgStr[2].str.p_str, &reg, True)) return;

		WAsmCode[0] = Index | reg;
		WAsmCode[1] = adr;
		CodeLen = 2;
	}
}

static void DecodeRegReg(Word Index)
{
	Word regs;
	Word regd;

	PrefixedSDBD = False;

	if (ChkArgCnt(2,2))
	{
		if(!DecReg(ArgStr[1].str.p_str, &regs, True)) return;
		if (Index & 0x0200)
		{
			if (regs == 0 || regs == 7)
			{
				WrError(ErrNum_InvReg);
				return;
			}
		}

		if(!DecReg(ArgStr[2].str.p_str, &regd, True)) return;

		WAsmCode[0] = Index | (regs << 3) | regd;
		CodeLen = 1;
	}
}

static void DecodeImmReg(Word Index)
{
	LongInt val;
	Word regd;
	Boolean OK;
	Boolean prefixed = PrefixedSDBD;
	
	PrefixedSDBD = False;

	if (ChkArgCnt(2,2))
	{
		val = EvalStrIntExpression(&ArgStr[1], Int32, &OK);
		if (!OK) return;
		if (!ChkRange(val, -32768, 65535)) return;

		if(!DecReg(ArgStr[2].str.p_str, &regd, True)) return;

		if (prefixed)
		{
			WAsmCode[0] = Index | regd;
			WAsmCode[1] = val & 0x00FF;
			WAsmCode[2] = val >> 8;
			CodeLen = 3;
		}
		else
		{
			if (val & Mask)
			{
				WAsmCode[0] = 0x0001; /* SDBD */
				WAsmCode[1] = Index | regd;
				WAsmCode[2] = val & 0x00FF;
				WAsmCode[3] = val >> 8;
				CodeLen = 4;
			}
			else
			{
				WAsmCode[0] = Index | regd;
				WAsmCode[1] = val;
				CodeLen = 2;
			}
		}
	}
}

static void DecodeRegDup(Word Index)
{
	Word reg;

	PrefixedSDBD = False;

	if (ChkArgCnt(1,1))
	{
		if(!DecReg(ArgStr[1].str.p_str, &reg, True)) return;

		WAsmCode[0] = Index | (reg << 3) | reg;
		CodeLen = 1;
	}
}

static void DecodeBranch(Word Index)
{
	Word cond = 0;
	Word adr;
	Boolean OK;
	Word PC = EProgCounter();

	PrefixedSDBD = False;

	if (Index & 0x0010)
	{
		/* BEXT */
		if (!ChkArgCnt(2,2)) return;

		cond = EvalStrIntExpression(&ArgStr[2], UInt4, &OK);
	}
	else
	{
		if (!ChkArgCnt(1,1)) return;

		cond = 0;
	}

	adr = EvalStrIntExpression(&ArgStr[1], UInt16, &OK);
	if (!OK) return;

	if (adr >= PC + 2)
	{
		WAsmCode[0] = Index | cond;
		WAsmCode[1] = adr - (PC + 2);
	}
	else
	{
		WAsmCode[0] = Index | 0x0020 | cond;
		WAsmCode[1] = PC + 2 - adr - 1;
	}

	if (WAsmCode[1] & Mask)
	{
		WrError(ErrNum_JmpDistTooBig);
		return;
	}

	CodeLen = 2;
}

static void DecodeNOPP(Word Index)
{
	PrefixedSDBD = False;

	if (ChkArgCnt(0, 0))
	{
		WAsmCode[0] = Index;
		WAsmCode[1] = 0;
		CodeLen = 2;
	}
}

static void DecodeReg(Word Index)
{
	Word reg;

	PrefixedSDBD = False;

	if (ChkArgCnt(1,1))
	{
		if(!DecReg(ArgStr[1].str.p_str, &reg, True)) return;

		if (Index == 0x0030)
		{
			/* GSWD */
			if (reg > 3)
			{
				WrError(ErrNum_InvReg);
				return;
			}
		}

		WAsmCode[0] = Index | reg;
		CodeLen = 1;
	}
}

static void DecodeShift(Word Index)
{
	Word reg;
	Word bit = 0;

	PrefixedSDBD = False;

	if (ChkArgCnt(1,2))
	{
		if(!DecReg(ArgStr[1].str.p_str, &reg, True)) return;
		if (reg > 3)
		{
			WrError(ErrNum_InvReg);
			return;
		}

		if (ArgCnt == 2)
		{
			Word val;
			Boolean OK;
			
			val = EvalStrIntExpression(&ArgStr[2], UInt16, &OK);
			if (!OK) return;

			if (val == 1)
			{
				bit = 0x0000;
			}
			else if (val == 2)
			{
				bit = 0x0004;
			}
			else
			{
				WrError(ErrNum_InvShiftArg);
				return;
			}
		}

		WAsmCode[0] = Index | bit | reg;
		CodeLen = 1;
	}
}

static void DecodeOptionalImm(Word Index)
{
	Word bit = 0;

	PrefixedSDBD = False;

	if (ChkArgCnt(0, 1))
	{
		if (ArgCnt == 0)
		{
			bit = 0x0000;
		}
		else{
			Word val;
			Boolean OK;

			val = EvalStrIntExpression(&ArgStr[1], UInt16, &OK);
			if (!OK) return;

			if (val == 1)
			{
				bit = 0x0000;
			}
			else if (val == 2)
			{
				bit = 0x0001;
			}
			else
			{
				WrError(ErrNum_InvShiftArg);
				return;
			}
		}
		WAsmCode[0] = Index | bit;
		CodeLen = 1;
	}
}

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

	if (Index == 0x0001) PrefixedSDBD = True;
	else PrefixedSDBD = False;
}

static void DecodeJump(Word Index)
{
	Word adr;
	Boolean OK;
	Word reg;

	PrefixedSDBD = False;

	if (Index & 0x0300)
	{
		/* J/JD/JE */
		if (!ChkArgCnt(1, 1)) return;

		reg = 0;

		adr = EvalStrIntExpression(&ArgStr[1], UInt16, &OK);		
	}
	else
	{
		/* JSR/JSRD/JSRE */
		if (!ChkArgCnt(2, 2)) return;

		if (!DecReg(ArgStr[1].str.p_str, &reg, True)) return;
		if (reg < 4 || reg > 6)
		{
			WrError(ErrNum_InvReg);
			return;
		}
		reg = (reg - 4) << 8;

		adr = EvalStrIntExpression(&ArgStr[2], UInt16, &OK);
	}
	if (!OK) return;

	WAsmCode[0] = 0x0004;
	WAsmCode[1] = Index | reg | ((adr & 0xFC00) >> 8);
	WAsmCode[2] = adr & 0x03FF;
	CodeLen = 3;
}

static void DecodeJR(Word Index)
{
	Word reg;

	PrefixedSDBD = False;

	if (ChkArgCnt(1,1))
	{
		if(!DecReg(ArgStr[1].str.p_str, &reg, True)) return;

		WAsmCode[0] = Index | (reg << 3);
		CodeLen = 1;
	}
}

static void DecodeRES(Word Index)
{
	Word Size;
	Boolean OK;
	tSymbolFlags Flags;
	int i;

	PrefixedSDBD = False;

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

	Size = EvalStrIntExpressionWithFlags(&ArgStr[1], Int16, &OK, &Flags);
	if (!OK) return;
	if (mFirstPassUnknown(Flags))
	{
		WrStrErrorPos(ErrNum_FirstPassCalc, &ArgStr[1]);
		return;
	}

	if (Index)
	{
		for (i = 0; i < Size; i++)
		{
			WAsmCode[i] = 0;
		}
	}
	else
	{
		DontPrint = True;
	}
	CodeLen = Size;
	BookKeeping();
}

static void PutByte(Word value, int *p_half)
{
	if (*p_half & 1)
	{
		WAsmCode[CodeLen - 1] |= value << 8;
	}
	else
	{
		WAsmCode[CodeLen++] = value & 0xFF;
	}
	if (Packing)
		(*p_half)++;
}

static void DecodeWORD(Word Index)
{
	int c;
	int b = 0;
	TempResult t;

	PrefixedSDBD = False;

	as_tempres_ini(&t);
	if (ChkArgCnt(1, ArgCntMax))
	{
		Boolean OK = True;
		tStrComp *pArg;

		forallargs (pArg, OK)
		{
			EvalStrExpression(pArg, &t);
			if (mFirstPassUnknown(t.Flags) && t.Typ == TempInt) t.Contents.Int &= 65535;

			switch (t.Typ)
			{
			case TempInt:
				switch (Index)
				{
				case 0x0000: /* WORD */
					if (ChkRange(t.Contents.Int, -32768, 65535))
					{
						WAsmCode[CodeLen++] = t.Contents.Int;
					}
					b = 0;
					break;
				case 0x0001: /* BYTE */
					if (ChkRange(t.Contents.Int, -32768, 65535))
					{
						WAsmCode[CodeLen++] = t.Contents.Int & 0x00FF;
						WAsmCode[CodeLen++] = (t.Contents.Int >> 8) & 0x00FF;
					}
					b = 0;
					break;
				case 0x0002: /* TEXT */
					if (ChkRange(t.Contents.Int, 0, 255))
						PutByte(t.Contents.Int & 0xff, &b);
					break;
				default:
					OK = False;
				}
				break;
			case TempFloat:
				WrStrErrorPos(ErrNum_StringOrIntButFloat, pArg);
				OK = False;
				break;
			case TempString:
				if (Index != 0x0002)
				{
					OK = False;
					break;
				}
				if (as_chartrans_xlate_nonz_dynstr(CurrTransTable->p_table, &t.Contents.str, pArg))
					OK = False;
				else
					for (c = 0; c < (int)t.Contents.str.len; c++)
						PutByte(t.Contents.str.p_str[c], &b);
				break;
			default:
				OK = False;
			}
		}
		if (!OK) CodeLen = 0;
	}
	as_tempres_free(&t);
}

static void DecodeBITS(Word Index)
{
	Word val;
	Boolean OK;

	UNUSED(Index);

	PrefixedSDBD = False;

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

	val = EvalStrIntExpression(&ArgStr[1], Int16, &OK);
	if (!OK) return;
	if (ChkRange(val, 10, 16))
	{
		Bits = val;
		Mask = ~((1 << val) - 1);
	}
}

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

  add_null_pseudo(InstTable);

	/* Data Access Instructions */
	AddRegAdrMVO("MVO", 0x0240);
	AddRegRegMVO("MVO@", 0x0240);
	AddRegImmMVO("MVOI", 0x0278);
	AddAdrReg("MVI", 0x0280);
	AddRegReg("MVI@", 0x0280);
	AddImmReg("MVII", 0x02B8);
	AddAdrReg("ADD", 0x02C0);
	AddRegReg("ADD@", 0x02C0);
	AddImmReg("ADDI", 0x02F8);
	AddAdrReg("SUB", 0x300);
	AddRegReg("SUB@", 0x0300);
	AddImmReg("SUBI", 0x0338);
	AddAdrReg("CMP", 0x0340);
	AddRegReg("CMP@", 0x0340);
	AddImmReg("CMPI", 0x0378);
	AddAdrReg("AND", 0x0380);
	AddRegReg("AND@", 0x0380);
	AddImmReg("ANDI", 0x03B8);
	AddAdrReg("XOR", 0x03C0);
	AddRegReg("XOR@", 0x03C0);
	AddImmReg("XORI", 0x03F8);

	/* Conditional Branch Instructions */
	AddBranch("B", 0x0200);
	AddBranch("BC", 0x0201);
	AddBranch("BLGT", 0x0201);
	AddBranch("BNC", 0x0209);
	AddBranch("BLLT", 0x0209);
	AddBranch("BOV", 0x0202);
	AddBranch("BNOV", 0x020A);
	AddBranch("BPL", 0x0203);
	AddBranch("BMI", 0x020B);
	AddBranch("BZE", 0x0204);
	AddBranch("BEQ", 0x0204);
	AddBranch("BNZE", 0x020C);
	AddBranch("BNEQ", 0x020C);
	AddBranch("BLT", 0x0205);
	AddBranch("BGE", 0x020D);
	AddBranch("BLE", 0x0206);
	AddBranch("BGT", 0x020E);
	AddBranch("BUSC", 0x0207);
	AddBranch("BESC", 0x020F);
	AddBranch("BEXT", 0x0210);
	AddNOPP("NOPP", 0x0208);

	/* Register to Register */
	AddRegReg("MOVR", 0x0080);
	AddRegReg("ADDR", 0x00C0);
	AddRegReg("SUBR", 0x0100);
	AddRegReg("CMPR", 0x0140);
	AddRegReg("ANDR", 0x0180);
	AddRegReg("XORR", 0x01C0);

	/* Register Shift */
	AddShift("SWAP", 0x0040);
	AddShift("SLL", 0x0048);
	AddShift("RLC", 0x0050);
	AddShift("SLLC", 0x0058);
	AddShift("SLR", 0x0060);
	AddShift("SAR", 0x0068);
	AddShift("RRC", 0x0070);
	AddShift("SARC", 0x0078);

	/* Single Register */
	AddReg("INCR", 0x0008);
	AddReg("DECR", 0x0010);
	AddReg("COMR", 0x0018);
	AddReg("NEGR", 0x0020);
	AddReg("ADCR", 0x0028);
	AddReg("GSWD", 0x0030);
	/* AddFixed("NOP", 0x0034);	*/
	AddOptionalImm("NOP", 0x0034);
	/* AddFixed("SIN", 0x0036);	*/
	AddOptionalImm("SIN", 0x0036);
	AddReg("RSWD", 0x0038);

	/* Internal Control */
	AddFixed("HLT", 0x0000);
	AddFixed("SDBD", 0x0001);
	AddFixed("EIS", 0x0002);
	AddFixed("DIS", 0x0003);
	AddFixed("TCI", 0x0005);
	AddFixed("CLRC", 0x0006);
	AddFixed("SETC", 0x0007);

	/* Jump / Jump and Save Return */
	AddJump("J", 0x0300); /* Second Word */
	AddJump("JE", 0x0301); /* Second Word */
	AddJump("JD", 0x0302); /* Second Word */
	AddJump("JSR", 0x0000); /* Second Word */
	AddJump("JSRE", 0x0001); /* Second Word */
	AddJump("JSRD", 0x0002); /* Second Word */

	/* Alias */
	AddRegDup("TSTR", 0x0080);
	AddRegDup("CLRR", 0x01C0);
	AddJR("JR", 0x0087);
	AddReg("PSHR", 0x0270);
	AddReg("PULR", 0x02B0);

	/* Pseudo Instructions */
	AddRES("RES", 0x0000); /* Flag Word */
	AddRES("ZERO", 0x0001); /* Flag Word */
	AddWORD("WORD", 0x0000); /* Flag Word */
	AddWORD("BYTE", 0x0001); /* Flag Word */
	AddWORD("TEXT", 0x0002); /* Flag Word */
	AddInstTable(InstTable, "BITS", 0x0000, DecodeBITS);
}

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

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

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

static Boolean IsDef_CP1600(void)
{
	return False;
}

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

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

	TurnWords = True;
	/* SetIntConstMode(eIntConstModeIBM); */
	NativeIntConstModeMask = (1ul << eIntFormatCOct) | (1ul << eIntFormatIBMXHex) | (1ul << eIntFormatDefRadix);
	OtherIntConstModeMask = eIntFormatMaskC | eIntFormatMaskIntel | eIntFormatMaskMoto | eIntFormatMaskIBM;
	SetIntConstModeByMask( NativeIntConstModeMask | (RelaxedMode ? OtherIntConstModeMask : 0));

	pDescr = FindFamilyByName("CP1600");
	PCSymbol = "*";
	HeaderID = pDescr->Id;
	NOPCode = 0x0034;
	DivideChars = ",";
	HasAttrs = False;

	ValidSegs = (1 << SegCode);
	Grans[SegCode] = 2;
	ListGrans[SegCode] = 2;
	SegInits[SegCode] = 0;
	SegLimits[SegCode] = 0xffff;

	Bits = 16;
	Mask = 0x0000;
	PrefixedSDBD = False;

  onoff_packing_add(True);

	MakeCode = MakeCode_CP1600;
	IsDef = IsDef_CP1600;
	SwitchFrom = SwitchFrom_CP1600;
	InitFields();
}

void codecp1600_init(void)
{
	CPUCP1600 = AddCPU("CP-1600", SwitchTo_CP1600);

	AddCopyright("GI CP-1600-Generator (C) 2021 Haruo Asano");
}
