//***********************************************************************************************************
//**
//**	hp21asm - Machine Instruction Generation
//**
//**	All machine instruction encoding is in here.
//**
//**	2014 Nov: init / bh
//**
//***********************************************************************************************************
#include "util.h"

extern void			List_err();

extern char			*parsePtr;				// needed for undoing parsing
extern int			parse_try();
extern int			parse_arg();
extern int			parse_mod();
extern void			parse_space();
extern int			parse_comma();
extern int			parse_atEOT();

extern int			CurAddr;				// needed for page address checking


//* END Refs
//***********************************************************************************************************
//* Internals

//----------------------------------------------------
// Instruction table
// This table is a lookup table for the instructions as well as encoding the permissible micro-instruction sequences.
// As such, the ordering in the table for instructions permitted in sequences is important.
// Some instruction names occur twice in the table as they can occur twice in a sequence, with different opcodes.


#define OPD_NON			0					// operand formats for instructions
#define OPD_MEM			1
#define OPD_IO			2
#define OPD_IOC			3
#define OPD_CLF			4


typedef struct								// instruction definition for table
 {	char			*instr;
	int			seqGroup;
	int			seqOrder;
	int			operand;
	int			opcode;
 }
	INSTR_DEF;


INSTR_DEF			InstrTbl[] =				// table of all instructions with micro-instruction sequences encoded
	{	{ "NOP", 0, 0, OPD_NON,  000000 },

		{ "AND", 0, 0, OPD_MEM,  010000 },			// memory ref
		{ "XOR", 0, 0, OPD_MEM,  020000 },
		{ "IOR", 0, 0, OPD_MEM,  030000 },
		{ "JSB", 0, 0, OPD_MEM,  014000 },
		{ "JMP", 0, 0, OPD_MEM,  024000 },
		{ "ISZ", 0, 0, OPD_MEM,  034000 },
		{ "ADA", 0, 0, OPD_MEM,  040000 },
		{ "ADB", 0, 0, OPD_MEM,  044000 },
		{ "CPA", 0, 0, OPD_MEM,  050000 },
		{ "CPB", 0, 0, OPD_MEM,  054000 },
		{ "LDA", 0, 0, OPD_MEM,  060000 },
		{ "LDB", 0, 0, OPD_MEM,  064000 },
		{ "STA", 0, 0, OPD_MEM,  070000 },
		{ "STB", 0, 0, OPD_MEM,  074000 },

		{ "HLT", 0, 0, OPD_IOC, 0102000 },			// IO
		{ "STF", 0, 0, OPD_IO,  0102100 },
		{ "CLF", 0, 0, OPD_IO,  0103100 },
		{ "SFC", 0, 0, OPD_IO,  0102200 },
		{ "SFS", 0, 0, OPD_IO,  0102300 },
		{ "MIA", 0, 0, OPD_IOC, 0102400 },
		{ "MIB", 0, 0, OPD_IOC, 0106400 },
		{ "LIA", 0, 0, OPD_IOC, 0102500 },
		{ "LIB", 0, 0, OPD_IOC, 0106500 },
		{ "OTA", 0, 0, OPD_IOC, 0102600 },
		{ "OTB", 0, 0, OPD_IOC, 0106600 },
		{ "STC", 0, 0, OPD_IOC, 0102700 },
		{ "CLC", 0, 0, OPD_IOC, 0106700 },

		{ "STO", 0, 0, OPD_NON, 0102101 },
		{ "CLO", 0, 0, OPD_NON, 0103101 },
		{ "SOC", 0, 0, OPD_CLF, 0102201 },
		{ "SOS", 0, 0, OPD_CLF, 0102301 },

		{ "ALS", 1, 1, OPD_NON,  001000 },			// shift-rotate, A register, sequence group 1
		{ "ARS", 1, 1, OPD_NON,  001100 },
		{ "RAL", 1, 1, OPD_NON,  001200 },
		{ "RAR", 1, 1, OPD_NON,  001300 },
		{ "ALR", 1, 1, OPD_NON,  001400 },
		{ "ERA", 1, 1, OPD_NON,  001500 },
		{ "ELA", 1, 1, OPD_NON,  001600 },
		{ "ALF", 1, 1, OPD_NON,  001700 },
		{ "CLE", 1, 2, OPD_NON,  000040 },
		{ "SLA", 1, 3, OPD_NON,  000010 },
		{ "ALS", 1, 4, OPD_NON,  000020 },
		{ "ARS", 1, 4, OPD_NON,  000021 },
		{ "RAL", 1, 4, OPD_NON,  000022 },
		{ "RAR", 1, 4, OPD_NON,  000023 },
		{ "ALR", 1, 4, OPD_NON,  000024 },
		{ "ERA", 1, 4, OPD_NON,  000025 },
		{ "ELA", 1, 4, OPD_NON,  000026 },
		{ "ALF", 1, 4, OPD_NON,  000027 },

		{ "BLS", 2, 1, OPD_NON,  005000 },			// shift-rotate, B register, sequence group 2
		{ "BRS", 2, 1, OPD_NON,  005100 },
		{ "RBL", 2, 1, OPD_NON,  005200 },
		{ "RBR", 2, 1, OPD_NON,  005300 },
		{ "BLR", 2, 1, OPD_NON,  005400 },
		{ "ERB", 2, 1, OPD_NON,  005500 },
		{ "ELB", 2, 1, OPD_NON,  005600 },
		{ "BLF", 2, 1, OPD_NON,  005700 },
		{ "CLE", 2, 2, OPD_NON,  000040 },
		{ "SLB", 2, 3, OPD_NON,  004010 },
		{ "BLS", 2, 4, OPD_NON,  004020 },
		{ "BRS", 2, 4, OPD_NON,  004021 },
		{ "RBL", 2, 4, OPD_NON,  004022 },
		{ "RBR", 2, 4, OPD_NON,  004023 },
		{ "BLR", 2, 4, OPD_NON,  004024 },
		{ "ERB", 2, 4, OPD_NON,  004025 },
		{ "ELB", 2, 4, OPD_NON,  004026 },
		{ "BLF", 2, 4, OPD_NON,  004027 },

		{ "CLA", 3, 1, OPD_NON,  002400 },			// arithmetic-shift, A register, sequence group 3
		{ "CMA", 3, 1, OPD_NON,  003000 },
		{ "CCA", 3, 1, OPD_NON,  003400 },
		{ "SEZ", 3, 2, OPD_NON,  002040 },
		{ "CLE", 3, 3, OPD_NON,  002100 },
		{ "CME", 3, 3, OPD_NON,  002200 },
		{ "CCE", 3, 3, OPD_NON,  002300 },
		{ "SSA", 3, 4, OPD_NON,  002020 },
		{ "SLA", 3, 5, OPD_NON,  002010 },
		{ "INA", 3, 6, OPD_NON,  002004 },
		{ "SZA", 3, 7, OPD_NON,  002002 },
		{ "RSS", 3, 8, OPD_NON,  002001 },

		{ "CLB", 4, 1, OPD_NON,  006400 },			// arithmetic-shift, B register, sequence group 4
		{ "CMB", 4, 1, OPD_NON,  007000 },
		{ "CCB", 4, 1, OPD_NON,  007400 },
		{ "SEZ", 4, 2, OPD_NON,  006040 },
		{ "CLE", 4, 3, OPD_NON,  006100 },
		{ "CME", 4, 3, OPD_NON,  006200 },
		{ "CCE", 4, 3, OPD_NON,  006300 },
		{ "SSB", 4, 4, OPD_NON,  006020 },
		{ "SLB", 4, 5, OPD_NON,  006010 },
		{ "INB", 4, 6, OPD_NON,  006004 },
		{ "SZB", 4, 7, OPD_NON,  006002 },
		{ "RSS", 4, 8, OPD_NON,  006001 },

		{ NULL }
	};


// END Internals
//***********************************************************************************************************
// Interface

int
Mch2100_instr( ret_opcode )
	int			*ret_opcode;
 {
	char			*savePtr;
	INSTR_DEF		*id, *prev;
	int			opcode, arg;

	savePtr = parsePtr;							// mark beginning of source micro-instruction sequence
	prev = NULL;
	opcode = 0;

	for( id=InstrTbl; id->instr; ++id )					// search for instr in table
	 {	if( prev && id->seqOrder==prev->seqOrder )			// if in a sequence, skip over entries at same position
			continue;
		if( !parse_try(id->instr) )					// no match here, onto next
			continue;
		if( prev && id->seqGroup!=prev->seqGroup )			// check if sequence order is OK
		 {	id = prev + 1;						// no, restart search after previous match
			prev = NULL;						// new sequence
			opcode = 0;						// reset opcode
			parsePtr = savePtr;					// restart instruction parsing
			continue;						// continue searching
		 }
										// sequencing is good
		prev = id;							// keep as previous so we know sequence position
		opcode |= id->opcode;						// build opcode

		if( !parse_comma() )						// if another micro-instruction continue search
			break;							// otherwise get out
	 }

	if( !id->instr )							// if end of list there was no match
	 {	List_err( "Unknown instruction or invalid sequence" );
		return( FALSE );
	 }

	if( !parse_atEOT() )							// check for proper end of instruction
	 {	List_err( "Syntax error" );
		return( FALSE );
	 }

	//-----------------------
	// operand processing

	switch( id->operand )							// select operand type
	 {	case OPD_MEM:							// parse memory-reference operand
			arg = parse_arg();
			if( arg < 02000 )
				opcode |= arg;					// base page
			else if( (arg & 0776000) == (CurAddr & 0776000) )
				opcode |= 02000 | (arg & 01777);		// current page
			else
				List_err( "Operand address unreachable, outside current & base page" );

			if( parse_mod('I') ) opcode |= 0100000;			// check for indirect
			break;

		case OPD_IO:							// parse select-code operand
		case OPD_IOC:
			arg = parse_arg();
			if( arg <= 077 )
				opcode |= arg;
			else
				List_err( "Select code > 63" );

			if( id->operand==OPD_IOC && parse_mod('C') )		// check for clear-flag modifier
				opcode |= 001000;
			break;

		case OPD_CLF:							// parse solitary clear-flag operand
			savePtr = parsePtr;					// awkward because optional operand may be confused with comment
			parse_space();
			if( parse_try("C") )
				opcode |= 001000;
			else
				parsePtr = savePtr;
			break;
	 }

	*ret_opcode = opcode;
	return( TRUE );
 }


//* EOF
//***********************************************************************************************************
