#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <sys/types.h>
#include <sys/stat.h>
#include "p88common.h"
#include "p88symboltable.h"

#define skip_space(p)	while (*(p)==' ' || *(p)=='\t') (p)++

static char *Err_Unexpect = "Unexpected token";
static char *Err_1stOperand = "1st operand is illegal";
static char *Err_2ndOperand = "2nd operand is illegal";

/* extern */
long bytecount;	/* bytes of machine code */
static long curline;	/* current line */


static int get_token(const char *p, int *lengthp)
{
	int x = 0;

	if (p[x] == 0 || p[x] == ';') {
		*lengthp = 0;
		return t_eol; /* End Of Line */
	}
	if (isalpha(p[x])) {
		++x;
		while (isalnum(p[x]))
			++x;
		*lengthp = x;
		return t_name;
	}
	if (isdigit(p[x]) || p[x] == '-' || p[x] == '+') {
		++x;
		while (isdigit(p[x]))
			++x;
		*lengthp = x;
		return t_number;
	}
	*lengthp = 1;
	return t_symbol;
}

static void namecpy(char *buf, const char *p, int n)
{
	int i, len;
	len = (n < MAX_ID_LENGTH) ? n : MAX_ID_LENGTH;
	for (i = 0; i < len; i++)
		buf[i] = p[i];
	buf[i] = 0;
}

static long get_literal(const char *lp, int bits)
{
	/* returns integer in specified bits including sign bit */
	/* returns -1 if error */
	long literal;
	unsigned long w, mask;

	literal = atol(lp);
	mask = ~0UL << (bits - 1);
	w = literal & mask;
	if (w != 0 && w != mask)
		return -1;
	return ((unsigned long)literal & ((1UL << bits) - 1));
}

static int get_indirect(const char *p, int *lengthp, int *regp)
{
	int length;
	long addr;
	const char *origp = p;
	char buf[MAX_ID_LENGTH+1];

	if ((*p != 'c' && *p != 'C') || *(p+1) != '(') {
		*lengthp = 0;
		return 0; /* not indirect */
	}
	p += 2;
	skip_space(p);
	if (get_token(p, &length) != t_name)
		return -1; /* error */
	namecpy(buf, p, length);
	if (check_id(buf, curline, &addr) != k_register)
		return -1; /* error */
	p += length;
	skip_space(p);
	if (*p++ != ')')
		return -1; /* error */
	*lengthp = p - origp;
	*regp = addr;
	return 1; /* success */
}

static operand get_operand(const char *p, int *lengthp)
{
	operand info = { 0 };
	int length, token, ch, knd;
	int x, reg;
	long literal, addr;
	const char *origp = p;
	char buf[MAX_ID_LENGTH+1];

	info.typ = t_none; /* initial value */
	skip_space(p);
	if ((x = get_indirect(p, &length, &reg)) != 0) {
		if (x == 1) { /* indirect found */
			info.typ = t_reg_indirect;
			info.reg = reg;
		}else if (x < 0) /* error */
			info.typ = t_error;
		*lengthp = (p - origp) + length;
		return info;
	}

	token = get_token(p, &length);
	if (token == t_eol)
		info.typ = t_error;
	else if (token == t_symbol) {
		ch = *p;
		p += length;
		if (ch == '#') {
			literal = get_literal(p, 8);
			if (literal < 0)
				info.typ = t_error;
			else {
				info.typ = t_value;
				info.val = literal;
			}
			if (*p == '-' || *p == '+')
				p++;
			while (isdigit(*p))
				p++;
		}else
			info.typ = t_error;
	}else { /* t_name or t_number */
		int isreg = NO;
		info.typ = t_mem_only;
		if (token == t_name) {
			namecpy(buf, p, length);
			knd = check_id(buf, curline, &addr);
			if (knd == k_register) {
				isreg = YES;
				info.typ = t_reg_only;
				info.reg = addr;
			}else if (knd == k_opcode)
				info.typ = t_error;
			else
				info.val = addr;
			/* null, k_label, or k_undef are allowed */
		}else { /* t_number */
			literal = get_literal(p, 16);
			if (literal < 0)
				info.typ = t_error;
			else
				info.val = literal;
		}
		p += length;
		skip_space(p);
		if (isreg == NO && *p == '+') {
			++p;
			skip_space(p);
			x = get_indirect(p, &length, &reg);
			if (x == 1) { /* indirect found */
				info.typ = t_mem_register;
				info.reg = reg;
			}else /* error */
				info.typ = t_error;
			p += length;
		}
	}

	*lengthp = p - origp;
	return info;
}

static int gen_template(unsigned char *memp,
	int typ, int op, int r1, int r2, int store, long addr)
{
	int fbits1, fbits2, withaddr;

	switch (typ) {
	case t_reg_only:
		fbits1 = 1, fbits2 = 0, withaddr = NO;
		break;
	case t_mem_only:
		fbits1 = 2, fbits2 = 0, withaddr = YES;
		r2 = 0;
		break;
	case t_reg_indirect:
		fbits1 = 1, fbits2 = 2, withaddr = NO;
		break;
	case t_mem_register:
		fbits1 = 3, fbits2 = 2, withaddr = YES;
		break;
	default:
		return -1; /* error */
	}
	if (store)
		fbits2 |= 01;	/* STORE-type COPY operation: COPY mem,ax */
	memp[0] = (op << 4) | (fbits1 << 2) | r1;
	memp[1] = (fbits2 << 6) | (r2 << 4);
	if (withaddr) {
		memp[1] |= (addr >> 8) & 0x0f;
		memp[2] = addr;
		return 3;
	}
	return 2;
}

static const char *gen_code(int opcode, const char *lp,
	unsigned char *memoryp, int *gen)
{
	int length, blen;
	operand oprnd = { 0 }, oprnd2 = { 0 };
	unsigned char *memp;
	unsigned char tmppool[8]; /* used if pass == 1 */

	*gen = 0; /* for error */
	memp = (memoryp)
		? memoryp	/* pass2 */
		: tmppool;	/* pass1 */
	if (opcode == op_HALT || opcode == op_RTN) {
		memp[0] = opcode << 4;
		*gen = 1;
		return NULL;
	}

	oprnd = get_operand(lp, &length);
	if (oprnd.typ == t_error)
		return "Syntax error in 1st operand";
	lp += length;
	skip_space(lp);

	switch (opcode) {
	case op_ADD:
	case op_SUB:
	case op_MUL:
	case op_DIV:
	case op_CMP: /* 2 operands */
		if (oprnd.typ != t_reg_only)
			return "1st operand isn't register";
		/* fall to next case */
	case op_COPY:
		if (*lp++ != ',')
			return "',' is expected";
		skip_space(lp);
		oprnd2 = get_operand(lp, &length);
		if (oprnd2.typ == t_error)
			return "Syntax error in 2nd operand";
		lp += length;
		break;
	case op_JMP:
	case op_JNB:
	case op_JB:
	case op_CALL: /* only address is allowed */
		if (oprnd.typ != t_mem_only)
			return "Operand isn't address";
		break;
	case op_PUSH:
	case op_POP:
	case op_IN:
	case op_OUT:
	default: /* IO: only register is allowed */
		if (oprnd.typ != t_reg_only)
			return "Operand isn't register";
		break;
	}
	skip_space(lp);
	if (get_token(lp, &length) != t_eol)
		return Err_Unexpect;

	switch (opcode) {
	case op_COPY:
		if (oprnd.typ == t_reg_only) {
			/* LOAD operation */
			if (oprnd2.typ == t_value) {
				memp[0] = (opcode << 4) | oprnd.reg;
				memp[1] = oprnd2.val;
				*gen = 2;
				return NULL; /* success */
			}
			blen = gen_template(memp, oprnd2.typ, opcode,
				oprnd.reg, oprnd2.reg, 0, oprnd2.val);
			if (blen < 0)
				return Err_2ndOperand;
			*gen = blen;
			return NULL; /* success */
		}
		if (oprnd2.typ != t_reg_only)
			return "2nd operand isn't register";

		/* COPY(STORE) operation */
		switch (oprnd.typ) {
		case t_mem_only:
		case t_reg_indirect:
		case t_mem_register:
			blen = gen_template(memp, oprnd.typ, op_COPY,
				oprnd2.reg, oprnd.reg, 1, oprnd.val);
			if (blen < 0)
				return Err_1stOperand;
			*gen = blen;
			break;
		default:
			return Err_Unexpect;
		}
		break; /* success */
	case op_ADD:
	case op_SUB:
	case op_MUL:
	case op_DIV:
	case op_CMP:
		switch (oprnd2.typ) {
		case t_reg_only:
		case t_mem_only:
			blen = gen_template(memp, oprnd2.typ, opcode,
				oprnd.reg, oprnd2.reg, 0, oprnd2.val);
			if (blen < 0)
				return Err_2ndOperand;
			*gen = blen;
			break;
		case t_value:
			memp[0] = (opcode << 4) | oprnd.reg;
			memp[1] = oprnd2.val;
			*gen = 2;
			break;
		default:
			return Err_Unexpect;
		}
		break; /* success */
	case op_JMP:
	case op_JNB:
	case op_JB:
	case op_CALL:
		memp[0] = (opcode << 4) | ((oprnd.val >> 8) & 0x0f);
		memp[1] = oprnd.val;
		*gen = 2;
		break; /* success */
	case op_PUSH:
	case op_POP:
	case op_IN:
	case op_OUT:
		memp[0] = (opcode << 4) | (oprnd.reg & 3);
		*gen = 1;
		break; /* success */
	default:
		return "System Error: Code generation";
	}
	return NULL;
}

/* extern */
int analyze(const char *const *prog, long lines, int pass, unsigned char *core)
{
	int errcount = 0;
	int lastopcode = -1;
	const char *errmsg;

	bytecount = 0;
	for (curline = 0; curline < lines; curline++) {
		int length, token, knd, gen;
		long info, literal;
		char buf[MAX_ID_LENGTH+1];
		const char *lp;
		unsigned char *memp;

		lp = prog[curline];
		if (*lp == ';' || *lp == 0) /* end of line */
			continue;

		/* label */
		if (*lp != ' ' && *lp != '\t') {
			token = get_token(lp, &length);
			if (token != t_name) {
				errmsg = Err_Unexpect;
				goto ErrContinue;
			}
			if (pass == 1) {
				namecpy(buf, lp, length);
				knd = def_label(buf, bytecount);
				if (knd != null && knd != k_undef) {
					errmsg = (knd == k_label)
					? "Duplicated label definitionl"
					: Err_Unexpect;
					goto ErrContinue;
				}
			}
			lp += length;
		}
		skip_space(lp);

		/* op-code or data literal */
		token = get_token(lp, &length);
		switch (token) {
		case t_eol:
			continue; /* continue loop */
		case t_symbol:
			errmsg = Err_Unexpect;
			goto ErrContinue;
		case t_name:
			namecpy(buf, lp, length);
			knd = check_id(buf, curline, &info);
			lp += length;
			skip_space(lp);
			switch (knd) {
			case k_opcode:
				memp = (pass == 1) ? NULL : &core[bytecount];
				errmsg = gen_code(info, lp, memp, &gen);
				lastopcode = info;
				if (errmsg)
					goto ErrContinue;
				bytecount += gen;
				break;
			case k_directive: /* Directive RES */
				token = get_token(lp, &length);
				if (token != t_number
				|| (literal = get_literal(lp, 13)) < 0) {
					/* Unsigned 12bit number */
					errmsg = "Number is expected";
					goto ErrContinue;
				}
				if (pass == 2) {
					long i;
					for (i = 0; i < literal; i++)
						core[bytecount++] = 0;
				}else
					bytecount += literal;
				lp += length;
				goto CheckEOL;
			case null:
			case k_undef:
			case k_label:
				if (pass == 2) {
					core[bytecount] = info >> 8;
					core[bytecount+1] = info;
				}
				bytecount += 2;
				goto CheckEOL;
			default:
				errmsg = "Opcode is expected";
				goto ErrContinue;
			}
			break;
		case t_number:
			literal = get_literal(lp, 16);
			if (literal < 0) {
				errmsg = "Value exceeds the range";
				goto ErrContinue;
			}
			if (pass == 2) {
				core[bytecount] = literal >> 8;
				core[bytecount+1] = literal;
			}
			bytecount += 2;
			lp += length;
			goto CheckEOL;
		}

		continue;
CheckEOL:
		skip_space(lp);
		token = get_token(lp, &length);
		if (token == t_eol)
		    continue; /* OK */
		errmsg = "End of line is expected";
ErrContinue:
		errcount++;
		fprintf(stderr, "*pass=%d, %s\n", pass, errmsg);
		fprintf(stderr, "%7ld>%s\n", curline+1, prog[curline]);
	}
	if (errcount == 0 && lastopcode != op_HALT) {
		if (pass == 2)
			core[bytecount] = op_HALT << 4;
		bytecount++;
	}
	return errcount;
}
