%{
#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include "corewar.h"
#include "asm.h"

/*
 * I apologize for how bad this stuff is.  It's the first Bison/Yacc program
 *   I've ever written and the first Flex program I've ever written.
 */

static char  *errstr_end;
static stmt_t  *newprog;
unsigned  asm_line_number;
static char  name_str[STR_MAXLEN], author_str[STR_MAXLEN],
             address_str[STR_MAXLEN], *prog_fname;

#define  INT_BAD  (1 << ((sizeof(int)*8)-1))

static stmt_t  *stail(stmt_t *s), *newstmt(), *stcat(stmt_t *s1, stmt_t *s2);
static ptree_t  *newptree();
static aval_t  pass1(stmt_t *s), pass2(stmt_t *s, core_el_t *listing);
static aval_t  mk_aval(int ival);
static int  fetchaddr(ptree_t *pt, aval_t curaddr);
static int  strfetch(char *str, aval_t curaddr, unsigned line);
static void  clearstmt(stmt_t *s);
static void  clearpt(ptree_t *pt);
static void  yyerror(char *str);
%}

%token tt_op
%token tt_label
%token tt_id
%token tt_progid
%token tt_oparen
%token tt_cparen
%token tt_am
%token tt_comma
%token tt_name
%token tt_author
%token tt_address
%token tt_eoln
%token tt_nogood
%token tt_add
%token tt_sub
%token tt_mul
%token tt_div

%%
program	:	program line tt_eoln	{
		$$.stmt = (newprog = stcat($1.stmt, $2.stmt)); }
	| error tt_eoln {
		$$.stmt = newstmt();
		/*
		 * OK, this is a LAME error message, but it only happens when something
		 *   goes wrong and none of the other, useful error messages cover it.
		 */
		strcat(cwerrstr, "Invalid assembly code.");
		asm_error_loc(asm_line_number);
		YYABORT; }
	|	{ $$.stmt = newstmt(); }
;

line	: oplab			{ $$.stmt = $1.stmt; }
	| oplab tt_op arg tt_comma arg {
		stmt_t  *temp = $1.stmt;
		$$.stmt = $1.stmt;
		temp->op = $2.op;
		temp->a = $3.argtop;
		temp->b = $5.argtop; }
	| oplab tt_op arg {
		stmt_t  *temp = $1.stmt;
		$$.stmt = $1.stmt;
		temp->op = $2.op;
		temp->a = $3.argtop; }
	| oplab tt_op {
		stmt_t  *temp = $1.stmt;
		$$.stmt = $1.stmt;
		temp->op = $2.op; }
	| tt_author {
		$$.stmt = newstmt();
		strncpy(author_str, yytext, STR_MAXLEN - 1); }
	| tt_address {
		$$.stmt = newstmt();
		strncpy(address_str, yytext, STR_MAXLEN - 1); }
	| tt_name {
		$$.stmt = newstmt();
		strncpy(name_str, yytext, STR_MAXLEN - 1); }
	| tt_id {
		sprintf(cwerrstr, "Id \"%s\" must be preceded by an operator.", yytext);
		asm_error_loc(asm_line_number);
		YYABORT; }
	| oplab tt_op arg bad_newarg {
		sprintf(cwerrstr, "A \",\" is needed between arguments.");
		asm_error_loc(asm_line_number);
		YYABORT; }
;

bad_newarg : tt_am | tt_id ;

oplab	: tt_label	{
		char  *tcp;

		$$.stmt = newstmt();
		strcat(tcp = ($$.stmt)->str, yytext);
		while ((*tcp != ':') && (*tcp != '\0'))
			++tcp;
		*tcp = '\0';  /* Strip off the colon. */ }
	|		{
		$$.stmt = newstmt(); }
;

arg	: tt_am expr	{
		$$.argtop = (argtop_t *)tmalloc(sizeof(argtop_t));
		($$.argtop)->mode = $1.am;
		($$.argtop)->pt = $2.ptree; }
	| expr	{
		$$.argtop = (argtop_t *)tmalloc(sizeof(argtop_t));
		($$.argtop)->mode = am_dir;
		($$.argtop)->pt = $1.ptree; }
  | tt_nogood  {
		asm_error_loc(asm_line_number);
		YYABORT; }
;

expr	: factor	{ $$.ptree = $1.ptree; }
	| noid_expr tt_add factor	{
		$$.ptree = newptree();
		($$.ptree)->parsetype = pt_add;
		($$.ptree)->left = $1.ptree;
		($$.ptree)->right = $3.ptree; }
	| noid_expr tt_sub factor  {
		$$.ptree = newptree();
		($$.ptree)->parsetype = pt_sub;
		($$.ptree)->left = $1.ptree;
		($$.ptree)->right = $3.ptree; }
  | tt_progid {
		$$.ptree = newptree();
		($$.ptree)->parsetype = pt_end;
		($$.ptree)->val = AVAL_PROGID;
	}
;

noid_expr	: factor	{ $$.ptree = $1.ptree; }
	| noid_expr tt_add factor	{
		$$.ptree = newptree();
		($$.ptree)->parsetype = pt_add;
		($$.ptree)->left = $1.ptree;
		($$.ptree)->right = $3.ptree; }
	| noid_expr tt_sub factor  {
		$$.ptree = newptree();
		($$.ptree)->parsetype = pt_sub;
		($$.ptree)->left = $1.ptree;
		($$.ptree)->right = $3.ptree; }
  | tt_progid {
		sprintf(cwerrstr, "The \"id\" value cannot be used in expressions.\n");
		asm_error_loc(asm_line_number);
		YYABORT;	}
;

factor	: unit	{ $$.ptree = $1.ptree; }
	| factor tt_mul unit	{
		$$.ptree = newptree();
		($$.ptree)->parsetype = pt_mul;
		($$.ptree)->left = $1.ptree;
		($$.ptree)->right = $3.ptree; }
	| factor tt_div unit	{
		$$.ptree = newptree();
		($$.ptree)->parsetype = pt_div;
		($$.ptree)->left = $1.ptree;
		($$.ptree)->right = $3.ptree; }
;

unit	: tt_id	{
		$$.ptree = newptree();
		($$.ptree)->parsetype = pt_end;
		($$.ptree)->val = $1.val;
		strcpy(($$.ptree)->str, yytext); }
	| tt_sub tt_id {
		$$.ptree = newptree();
		($$.ptree)->parsetype = pt_uminus;
		($$.ptree)->left = newptree();
		($$.ptree)->left->parsetype = pt_end;
		($$.ptree)->left->val = $2.val;
		strcpy(($$.ptree)->left->str, yytext); }
	| tt_oparen noid_expr tt_cparen	{ $$.ptree = $2.ptree; }
;

%%

unsigned  assemble(program_t *prog, char *fname)  {
	stmt_t  *s;
	aval_t  plen;
	unsigned  i;
	FILE  *infile;
	static int  firstparse = TRUE;

#if  YYDEBUG
	yydebug = 1;
#endif
	cwerrstr[0] = '\0';
	prog_fname = fname;
	newprog = NULL;
	strcpy(name_str, "x Unknown");
	strcpy(address_str, "x Unknown");
	strcpy(author_str, "x Unknown");
	strcpy(prog->pname, "Unknown");
	strcpy(prog->fname, fname);
	asm_line_number = 1;
	errstr_end = NULL;
	infile = fopen(fname, "r");
	if (infile == NULL)  {
		sprintf(cwerrstr, "Cannot open file \"%s\".", fname);
		return(FALSE);
	}
	if (firstparse)  {
		firstparse = FALSE;
		yyin = infile;
	} else
		yyrestart(infile);
	if (yyparse())  {
		fclose(infile);
		clearstmt(newprog);
		if (errstr_end != NULL)
			*errstr_end = '\0';
		return(FALSE);
	}
	fclose(infile);

	sscanf(address_str, "%*s%s", prog->retaddr);

	for (i = 0;  !isspace(author_str[i]) && author_str[i];  ++i);
	while (isspace(author_str[i]) && author_str[i])
		++i;
	strcpy(prog->author, author_str + i);

	for (i = 0;  !isspace(name_str[i]) && name_str[i];  ++i);
	while (isspace(name_str[i]) && name_str[i])
		++i;
	strcpy(prog->pname, name_str + i);

	s = newprog;
	if ((plen = pass1(s)) == AVAL_NOGOOD)  {
		clearstmt(newprog);
		if (errstr_end != NULL)
			*errstr_end = '\0';
		return(FALSE);
	}
	if (plen > maxprogsize)  {
		clearstmt(newprog);
		sprintf(cwerrstr, "Program is %d words long.  "
						"This exceeds the maximum allowed of %d words.",
						plen, maxprogsize);
		return(FALSE);
	}
	prog->proglen = plen;
	prog->listing = (core_el_t *)tmalloc(plen * sizeof(core_el_t));
	if ((prog->startaddr = pass2(s, prog->listing)) == AVAL_NOGOOD)  {
		clearstmt(newprog);
		if (errstr_end != NULL)
			*errstr_end = '\0';
		return(FALSE);
	}
	clearstmt(newprog);
	return(TRUE);
}


static aval_t  pass1(stmt_t *s)  {
	aval_t  cur_addr = 0;

	while (s != NULL)  {
		s->addr = cur_addr;
		if (op_realop(s->op))
			++cur_addr;
		s = s->next;
	}
	return(cur_addr);
}


static aval_t  pass2(stmt_t *s, core_el_t *listing)  {
	aval_t  start = AVAL_NOGOOD, cur_addr = 0;
#define  ARG_OPTIONAL  0x8000
	static unsigned  invalid_modes[][2] = {
		{ARG_OPTIONAL|(1<<am_dir)|(1<<am_ind), (1<<am_dir)|(1<<am_ind)}, /* dat */
		{0, 1<<am_imm}, /* mov */
		{0, 1<<am_imm}, /* add */
		{0, 1<<am_imm}, /* sub */
		{1<<am_imm, ARG_OPTIONAL}, /* jmp */
		{1<<am_imm, 0}, /* jmz */
		{1<<am_imm, 0}, /* jmn */
		{1<<am_imm, 0}, /* djn */
		{0, 1<<am_imm}, /* cmp */
		{1<<am_imm, ARG_OPTIONAL}, /* spl */
		{0, 1<<am_imm}};  /* slt */

	while (s != NULL)  {
		switch(s->op)  {
		case op_equ:
			if (!s->str[0])  {
				sprintf(cwerrstr, "An \"EQU\" pseudo-op must have a label.");
				asm_error_loc(s->line);
				return(AVAL_NOGOOD);
			}
			if (s->b != NULL)  {
				sprintf(cwerrstr, "An \"EQU\" pseudo-op can only have one argument.");
				asm_error_loc(s->line);
				return(AVAL_NOGOOD);
			}
			if (s->a == NULL)  {
				sprintf(cwerrstr, "An \"EQU\" pseudo-op must have an argument.");
				asm_error_loc(s->line);
				return(AVAL_NOGOOD);
			}
			if (s->a->mode != am_dir)  {
				sprintf(cwerrstr, "The argument of an \"EQU\" pseudo-op must be "
								"direct mode.");
				asm_error_loc(s->line);
				return(AVAL_NOGOOD);
			}
			break;
		case op_end:
			if (s->b != NULL)  {
				sprintf(cwerrstr, "An \"END\" pseudo-op can only have one argument.");
				asm_error_loc(s->line);
				return(AVAL_NOGOOD);
			}
			if (s->a == NULL)
				start = 0;
			else  {
				if (s->a->mode != am_dir)  {
					sprintf(cwerrstr, "The argument of an \"END\" pseudo-op must be "
									"direct mode.");
					asm_error_loc(s->line);
					return(AVAL_NOGOOD);
				}
				start = mk_aval(fetchaddr(s->a->pt, 0));
				if (start == AVAL_NOGOOD)
					return(AVAL_NOGOOD);
			}
			break;
		case op_nogood:
			break;
		default:
			if (start != AVAL_NOGOOD)  {
				sprintf(cwerrstr, "Program continued after \"END\" pseudo-op.");
				asm_error_loc(s->line);
				return(AVAL_NOGOOD);
			}
			if (s->a == NULL)  {
				if ((invalid_modes[s->op][0] & ARG_OPTIONAL) ||
						(invalid_modes[s->op][1] & ARG_OPTIONAL))
					sprintf(cwerrstr, "The \"%s\" operator needs at least one argument.",
									opnames[s->op]);
				else
					sprintf(cwerrstr, "The \"%s\" operator needs two arguments.",
									opnames[s->op]);
				asm_error_loc(s->line);
				return(AVAL_NOGOOD);
			}
			if ((s->b == NULL) && (invalid_modes[s->op][0] & ARG_OPTIONAL))  {
				s->b = s->a;
				s->a = NULL;
			}
			if (s->a == NULL)  {
				s->a = (argtop_t *)tmalloc(sizeof(argtop_t));
				s->a->mode = am_imm;
				s->a->pt = NULL;
				listing[cur_addr].val1 = 0;
			} else  {
				if (amode_restrict && (invalid_modes[s->op][0] & (1 << s->a->mode)))  {
					sprintf(cwerrstr, "Operator \"%s\" cannot use %s addressing "
									"with argument A.", opnames[s->op], amodenames[s->a->mode]);
					asm_error_loc(s->line);
					return(AVAL_NOGOOD);
				}
				if ((listing[cur_addr].val1 = mk_aval(fetchaddr(s->a->pt, cur_addr)))
						== AVAL_NOGOOD)
					return(AVAL_NOGOOD);
			}
			if (s->b == NULL)  {
				if (!(invalid_modes[s->op][1] & ARG_OPTIONAL))  {
					sprintf(cwerrstr, "The \"%s\" operator needs two arguments.",
									opnames[s->op]);
					asm_error_loc(s->line);
					return(AVAL_NOGOOD);
				}
				s->b = (argtop_t *)tmalloc(sizeof(argtop_t));
				s->b->mode = am_imm;
				s->b->pt = NULL;
				listing[cur_addr].val2 = 0;
			} else  {
				if (amode_restrict && (invalid_modes[s->op][1] & (1 << s->b->mode)))  {
					sprintf(cwerrstr, "Operator \"%s\" cannot use %s addressing "
									"with argument B.", opnames[s->op], amodenames[s->b->mode]);
					asm_error_loc(s->line);
					return(AVAL_NOGOOD);
				}
				if ((listing[cur_addr].val2 = mk_aval(fetchaddr(s->b->pt, cur_addr)))
						== AVAL_NOGOOD)
					return(AVAL_NOGOOD);
			}
			listing[cur_addr].index = JTINDEX(s->op, s->a->mode, s->b->mode);
			listing[cur_addr].instr = jmptable[listing[cur_addr].index];
			++cur_addr;
			break;
		}
		s = s->next;
	}
	if (start == AVAL_NOGOOD)
		return(0);
	return(start);
}


static void  yyerror(char *str)  {
	if (!cwerrstr[0])  {
		if (str[0] == '\n')  {
			sprintf(cwerrstr, "Error near end of line: %s.", str);
			asm_error_loc(asm_line_number - 1);
		} else  {
			sprintf(cwerrstr, "Error near string \"%s\": %s.",
							yytext, str);
			asm_error_loc(asm_line_number);
		}
	}
}


void  asm_error_loc(unsigned line)  {
	char  tempbuf[200];
	FILE  *f;
	int  c, cline = 1;
	char  *cindex;

	if (errstr_end != NULL)
		return;
	sprintf(tempbuf, "\nError around line %d:\n", line);
	strcat(cwerrstr, tempbuf);
	for (cindex = cwerrstr;  *cindex;  ++cindex);
	f = fopen(prog_fname, "r");
	while (cline < line)  {
		c = getc(f);
		if (c == '\n')
			++cline;
		if (c == EOF)
			break;
	}
	c = getc(f);
	while ((cindex < cwerrstr+sizeof(cwerrstr)-1) &&
				 (c != '\n') && (c != EOF) && (isprint(c) || isspace(c)))  {
		*(cindex++) = c;
		c = getc(f);
	}
	fclose(f);
	*cindex = '\0';
	errstr_end = cindex;
}


static stmt_t  *stail(stmt_t *s)  {
	if (s->next == NULL)
		return(s);
	return(stail(s->next));
}


static stmt_t  *newstmt()  {
	stmt_t  *ns = (stmt_t *)tmalloc(sizeof(stmt_t));

	ns->line = asm_line_number;
	ns->op = op_nogood;
	ns->a = ns->b = NULL;
	ns->marked = FALSE;
	ns->next = NULL;
	ns->str[0] = '\0';
	return(ns);
}

static stmt_t  *stcat(stmt_t *s1, stmt_t *s2)  {
	stail(s1)->next = s2;
	return(s1);
}


static ptree_t  *newptree()  {
	ptree_t  *npt;

	npt = (ptree_t *)tmalloc(sizeof(ptree_t));
	npt->line = asm_line_number;
	npt->str[0] = '\0';
	npt->left = npt->right = NULL;
	return(npt);
}


static int  fetchaddr(ptree_t *pt, aval_t curaddr)  {
	int  l, r, c;

	if (pt->left != NULL)  {
		l = fetchaddr(pt->left, curaddr);
		if (l == INT_BAD)
			return(INT_BAD);
	}
	if (pt->right != NULL)  {
		r = fetchaddr(pt->right, curaddr);
		if (r == INT_BAD)
			return(INT_BAD);
	}
	switch(pt->parsetype)  {
	case pt_uminus:
		return(-l);
		break;
	case pt_add:
		c = l + r;
		if (((l < 0) && (r < 0) && (c > 0)) ||
				((l > 0) && (r > 0) && (c < 0)) ||
				(c == INT_BAD))  {
			sprintf(cwerrstr, "Additive overflow.\n");
			asm_error_loc(pt->line);
			return(INT_BAD);
		}
		return(c);
		break;
	case pt_sub:
		c = l - r;
		if (((l < 0) && (r > 0) && (c > 0)) ||
				((l > 0) && (r < 0) && (c < 0)) ||
				(c == INT_BAD))  {
			sprintf(cwerrstr, "Subtractive overflow.\n");
			asm_error_loc(pt->line);
			return(INT_BAD);
		}
		return(c);
		break;
	case pt_mul:
		c = l * r;
		if (((r != 0) && (c / r != l)) || (c == INT_BAD))  {
			sprintf(cwerrstr, "Multiplicative overflow.\n");
			asm_error_loc(pt->line);
			return(INT_BAD);
		}
		return(c);
		break;
	case pt_div:
		if (r == 0)  {
			sprintf(cwerrstr, "Division by zero.");
			asm_error_loc(pt->line);
			return(AVAL_NOGOOD);
		}
		return(mk_aval(l / r));
		break;
	case pt_end:
		if (pt->val != AVAL_NOGOOD)
			return(pt->val);
		return(strfetch(pt->str, curaddr, pt->line));
		break;
	}
}


static int  strfetch(char *str, aval_t curaddr, unsigned line)  {
	stmt_t  *s = newprog;
	int  result;

	while (s != NULL)  {
		if (!strcmp(str, s->str))  {
			if (s->op != op_equ)
				return(s->addr - curaddr);
			else  {
				if (s->marked)  {
					sprintf(cwerrstr, "Recursive \"EQU\" definition of label \"%s\".",
									str);
					asm_error_loc(s->line);
					return(INT_BAD);
				}
				s->marked = TRUE;
				result = fetchaddr(s->a->pt, curaddr);
				s->marked = FALSE;
				return(result);
			}
		}
		s = s->next;
	}
	sprintf(cwerrstr, "Unknown label \"%s\".", str);
	asm_error_loc(line);
	return(INT_BAD);
}


static void  clearstmt(stmt_t *s)  {
	if (s != NULL)  {
		clearstmt(s->next);
		if (s->a != NULL)  {
			clearpt(s->a->pt);
			free(s->a);
		}
		if (s->b != NULL)  {
			clearpt(s->b->pt);
			free(s->b);
		}
		free(s);
	}
}


static void  clearpt(ptree_t *pt)  {
	if (pt != NULL)  {
		clearpt(pt->left);
		clearpt(pt->right);
		free(pt);
	}
}


/* I'm always kinda hazy on how strict C compilers are about modulus of
 *   negative numbers so I do things extra careful here.
 */
static aval_t  mk_aval(int ival)  {
	if (ival == INT_BAD)
		return(AVAL_NOGOOD);
	if (ival < 0)  {
		ival = -ival % coresize;
		if (ival == 0)
			return(0);
		else
			return(coresize - ival);
	} else
		return(ival % coresize);
}

