/* register.c */

#include "project.h"
#include <stdlib.h>
#include <string.h>

/* List of all registers */
static reg_t regs[] = {
	/* Normal register set  */
	{"zero", 0x0, R_NORMAL, 0},
	{  "xw", 0x1, R_NORMAL, 0},
	{  "yw", 0x2, R_NORMAL, 0},
	{  "zw", 0x3, R_NORMAL, 0},
	{  "r0", 0x4, R_NORMAL, 0},
	{  "r1", 0x5, R_NORMAL, 0},
	{  "r2", 0x6, R_NORMAL, 0},
	{  "r3", 0x7, R_NORMAL, 0},
	{  "r4", 0x8, R_NORMAL, 0},
	{  "r5", 0x9, R_NORMAL, 0},
	{  "r6", 0xa, R_NORMAL, 0},
	{  "r7", 0xb, R_NORMAL, 0},
	{  "c0", 0xc, R_NORMAL, 0},
	{  "c1", 0xd, R_NORMAL, 0},
	{  "cf", 0xe, R_NORMAL, 0},
	{  "pc", 0xf, R_NORMAL, 0},

	/* BCD register set     */
	{  "u0", 0x0, R_BCD,    0},
	{  "u1", 0x1, R_BCD,    0},
	{  "u2", 0x2, R_BCD,    0},
	{  "u3", 0x3, R_BCD,    0},
	{  "u4", 0x4, R_BCD,    0},
	{  "u5", 0x5, R_BCD,    0},
	{  "u6", 0x6, R_BCD,    0},
	{  "u7", 0x7, R_BCD,    0},
	{  "v0", 0x8, R_BCD,    0},
	{  "v1", 0x9, R_BCD,    0},
	{  "v2", 0xa, R_BCD,    0},
	{  "v3", 0xb, R_BCD,    0},
	{  "v4", 0xc, R_BCD,    0},
	{  "v5", 0xd, R_BCD,    0},
	{  "v6", 0xe, R_BCD,    0},
	{  "v7", 0xf, R_BCD,    0},

	/* Pseudoregister set   */
	{   "u", 0x0, R_PSEUDO, 0},
	{  "uh", 0x1, R_PSEUDO, 0},
	{  "ul", 0x2, R_PSEUDO, 0},
	{   "v", 0x3, R_PSEUDO, 0},
	{  "vh", 0x4, R_PSEUDO, 0},
	{  "vl", 0x5, R_PSEUDO, 0},

	/* End of list          */
	{          NULL          }
};

/* Given the name of a register, returns its reg_t*. */
reg_t* reg_by_name(char* name)
{
	reg_t* p = regs;
	while (p->name != NULL && strcmp(p->name, name) != 0) p++;
	if (p->name == NULL) return NULL;
	return p;
}

/* Given the code and type of a register, returns its reg_t*. */
reg_t* reg_by_code(unsigned code, reg_type_t type)
{
	reg_t* p = regs;
	while (p->name != NULL && (p->code != code || p->type != type)) p++;
	if (p->name == NULL) return NULL;
	return p;
}

/* Returns the value of a register. */
unsigned reg_read(reg_t* reg)
{
	switch (reg->type) {
	case R_NORMAL: case R_BCD: return reg->val;
	case R_PSEUDO: die("Cannot directly read pseudoregisters\n");
	}
}

/* Sets the value of a register. */
void reg_write(reg_t* reg, unsigned val)
{
	switch (reg->type) {
	case R_NORMAL: reg->val = val & 0xffffffff; break;
	case R_BCD:    reg->val = val & 0x0000000f; break;
	case R_PSEUDO: die("Cannot directly modify pseudoregisters\n");
	}
}

/* Initializes all registers from values embedded in the memory image. */
void reg_initialize(void)
{
	int i, j;
	reg_type_t type;
	reg_t* reg;
	label_t* label;
	char* buf = NULL;
	int buf_len = 0;
	unsigned val;

	/* Perform this step for both the normal and BCD register sets */
	for (j = 0; j < 2; j++) {
		/* Determine what register set to work with */
		switch (j) {
		case 0: label = label_by_name(".nreg"); type = R_NORMAL; break;
		case 1: label = label_by_name(".breg"); type = R_BCD;
		}

		/* Set initial value for entire register set */
		val = label != NULL ? mem_read(label->addr) : 0xdeadbeef;
		for (i = 0; i < 16; i++) {
			reg = reg_by_code(i, type);
			if (reg == NULL) continue;
			reg_write(reg, val);
		}

		/* Set initial value for individual registers */
		for (i = 0; i < 16; i++) {
			int reg_name_len;

			/* Get register */
			reg = reg_by_code(i, type);
			if (reg == NULL) continue;

			/* Get label name */
			reg_name_len = strlen(reg->name);
			if (reg_name_len + 2 > buf_len) {
				buf_len = reg_name_len + 2;
				buf = realloc(buf, buf_len);
				if (buf == NULL) die("Out of memory\n");
			}
			buf[0] = '.';
			memcpy(buf + 1, reg->name, reg_name_len + 1);

			/* Get label */
			label = label_by_name(buf);
			if (label == NULL) continue;

			/* Set register value */
			reg_write(reg, mem_read(label->addr));
		}
	}

	/* No memory leaks, please */
	free(buf);
}

/* EOF */

