/* $Id$ */
/*
 * (c) copyright 1987 by the Vrije Universiteit, Amsterdam, The Netherlands.
 * See the copyright notice in the ACK home directory, in the file "Copyright".
 */
/*	This file is ment to be included in the file read_em.c.
	It contains the part that takes care of the reading of human readable
	EM-code.
*/

#include <ctype.h>
#include <string.h>
#include <stdio.h>
#include "ack_string.h"

/* #define XXX_YYY	*//* only for early debugging */

#ifdef XXX_YYY
#define out(str)	(fwrite(stdout, 1, str, strlen(str)))
#else
#define out(s)
#endif

#define fit16i(x)	((x) >= -32768L && (x) <= 32767L)

#define HSIZE	256	/* Size of hashtable for mnemonics */

static int hashtab[HSIZE];	/* The hashtable for mnemonics */

static int argnum;		/* Number of arguments */

#define COMMENTSTARTER	';'

/* Forward declarations */
PRIVATE void gettyp(int, register struct e_arg *);
PRIVATE int getexpr(register int, register struct e_arg *);

/* External definitions */
extern char em_mnem[][4];
extern char em_pseu[][4];

/* inithash, pre_hash, hash: Simple hashtable mechanism
*/
PRIVATE int hash(register char *s)
{
	register int h = 0;

	while (*s) {
		h <<= 1;
		h += *s++;
	}
	return h;
}

PRIVATE void pre_hash(int i, char *s)
{
	register int h;

	assert(i != 0);
	h = hash(s);

	for (;;) {
		h++;
		if (h >= HSIZE) h %= HSIZE;
		if (hashtab[h] == 0) {
			hashtab[h] = i;
			return;
		}
	}
	/*NOTREACHED*/
}



PRIVATE void inithash(void)
{
	register int i;

	/* Enter instructions ... */
	for (i = sp_fmnem; i <= sp_lmnem; i++) {
		pre_hash(i, em_mnem[i - sp_fmnem]);
	}

	/* and pseudos ... */
	for (i = sp_fpseu; i <= sp_lpseu; i++) {
		pre_hash(i, em_pseu[i - sp_fpseu]);
	}
}

/* nospace: skip until we find a non-space character. Also skip
	comments.
*/
PRIVATE int nospace(void)
{
	register int c;

	do	c = getbyte();
	while (isspace(c) && c != '\n');

	if (c == COMMENTSTARTER) {
		do	c = getbyte();
		while (c != '\n' && c != EOF);
	}

	return c;
}

/* syntax: Put an error message in EM_error and skip to the end of the line
*/
PRIVATE void syntax(char *s)
{
	register int c;

	xerror(s);
	state = 0;
	while ((c = getbyte()) != '\n' && c != EOF) /* nothing */ ;
	ungetbyte(c);
}

/* checkeol: check that we have a complete line (except maybe for spaces)
*/
PRIVATE void checkeol(void)
{

	if (nospace() != '\n') {
		syntax("end of line expected");
		nospace();
	}
}

/* getescape: read a '\' escape sequence
*/
PRIVATE int getescape(void)
{
	register int c, j, r;

	if ((c = getbyte()) >= '0' && c <= '7') { /* numeric escape */
		r = c - '0';

		for (j = 0; j < 2; j++) {
			if ((c = getbyte()) < '0' || c > '7') {
				ungetbyte(c);
				return r;
			}
			r <<= 3;
			r += c - '0';
		}

		return r;
	}

	switch(c) {
	case 'b':	return '\b';
	case 'f':	return '\f';
	case 'n':	return '\n';
	case 'r':	return '\r';
	case 't':	return '\t';
	}

	return c;
}

/* getname: Read a string of characters representing an identifier
*/
PRIVATE struct string *getname(void)
{
	register char *p;
	register struct string *s;
	register int c;

	s = &string;
	p = s->str;
	if (!p) {
		s->maxlen = 256;
		s->str = p = Malloc(256);
	}
	c = getbyte();

	if (!(isalpha(c) || c == '_')) {
		ungetbyte(c);
		syntax("Letter expected");
		return s;
	}

	while (isalnum(c) || c == '_') {
		if (p >= &(s->str[s->maxlen])) {
			int df = p - s->str;
			s->str = Realloc(s->str, (s->maxlen += 256));
			p = s->str + df;
		}
		*p++ = c;
		c = getbyte();
	}

	ungetbyte(c);
	*p = '\0';
	s->length = p - s->str;
	return s;
}

/* getstring: read a string of characters between quotes
*/
PRIVATE struct string *getstring(int isident)
{
	register char *p;
	struct string *s;
	register int c;
	static int termc;

	s = &string;
	p = s->str;
	if (!p) {
		s->maxlen = 256;
		s->str = p = Malloc(256);
	}

	termc = getbyte();
	/* assert(termc == '"' || termc == '\''); */
	/* This assertion does not work. Compiler error messages.
	   The trouble lies in the ", it terminates the string
	   created in the assertion macro
	*/

	for (;;) {
		if ((c = getbyte()) == '\n' || c == EOF) {
			ungetbyte(c);
			syntax("non-terminated string");
			break;
		}

		if (c == termc) {
			if (termc == '"') *p++ = '\0';
			break;
		}

		if (c == '\\') c = getescape();

		if (p >= &(s->str[s->maxlen])) {
			int df = p - s->str;
			s->str = Realloc(s->str, (s->maxlen += 256));
			p = s->str + df;
		}

		*p++ = c;	
	}
	*p = '\0';

	s->length = p - s->str;
	return s;
}




PRIVATE int offsetted(int argtyp, arith *ap)
{
	register int c;

	if ((c = nospace()) == '+' || c == '-') {
		struct e_arg dummy;

		gettyp(cst_ptyp, &dummy);
		if (c == '-') *ap = -(dummy.ema_cst);
		else *ap = dummy.ema_cst;
		return sp_doff;
	}
	else	*ap = 0;

	ungetbyte(c);
	return argtyp;
}

PRIVATE int getnumber(register int c, register struct e_arg *ap)
{
	char str[256 + 1];
	register char *p = str;
	int n;
	int expsign;

	ap->ema_argtype = cst_ptyp;
	expsign = 0;

	if (c == '+' || c == '-') {
		if (c == '-') *p++ = c;
		c = getbyte();
	}

	/* Soak up any whitespace (to allow "- 4" to be considered a number). */
	
	while (isspace(c))
		c = getbyte();
		
	if (! isdigit(c)) {
		ungetbyte(c);
		syntax("digit expected");
		return sp_cst4;
	}

	n = sp_cst4;

	for (;;) {
		if (p >= &(str[256])) {
			syntax("number too long");
			return sp_cst4;
		}

		*p++ = c;

		if ((c = getbyte()) == '.' || c == 'e' || c == 'E') {
			expsign = c != '.';
			n = sp_fcon;
			continue;
		}

		if (expsign) {
			expsign = 0;
			if (c == '+' || c == '-') continue;
		}

		if (! isdigit(c)) break;
	}

	ungetbyte(c);
	*p = '\0';
	c = nospace();

	if (n == sp_fcon && c != 'F') {
		ungetbyte(c);
		syntax("'F' expected");
		return n;
	}	

	if (c == 'I' || c == 'U' || c == 'F') {
		struct e_arg dummy;

		strcpy(string.str, str);
		ap->ema_string = string.str;
		gettyp(cst_ptyp, &dummy);
		ap->ema_szoroff = dummy.ema_cst;

		switch(c) {
		case 'I':
			ap->ema_argtype = ico_ptyp;
			return sp_icon;
		case 'U':
			ap->ema_argtype = uco_ptyp;
			return sp_ucon;
		case 'F':
			ap->ema_argtype = fco_ptyp;
			return sp_fcon;
		}
		assert(0);
	}

	ungetbyte(c);
	ap->ema_cst = (arith) str2long(str, 10);
	return sp_cst4;
}


PRIVATE int getfactor(register int c, register struct e_arg *ap)
{
	if (c == '(') {
		if (getexpr(nospace(), ap) != sp_cst4) {
			syntax("expression expected");
		}
		else if ((c = nospace()) != ')') {
			ungetbyte(c);
			syntax("')' expected");
		}
		return sp_cst4;
	}
	return getnumber(c, ap);
}

PRIVATE int getterm(register int c, register struct e_arg *ap)
{
	arith left;

	if ((c = getfactor(c, ap)) != sp_cst4) return c;

	for (;;) {
		if ((c = nospace()) != '*' && c != '/' && c != '%') {
			ungetbyte(c);
			break;
		}

		left = ap->ema_cst;
		if (getfactor(nospace(), ap) != sp_cst4) {
			syntax("factor expected");
			break;
		}

		if (c == '*') ap->ema_cst *= left;
		else if (c == '/') ap->ema_cst = left / ap->ema_cst;
		else	ap->ema_cst = left % ap->ema_cst;
	}
	return sp_cst4;
}

PRIVATE int getexpr(register int c, register struct e_arg *ap)
{
	arith left;

	if ((c = getterm(c, ap)) != sp_cst4) return c;

	for (;;) {
		if ((c = nospace()) != '+' && c != '-') {
			ungetbyte(c);
			break;
		}

		left = ap->ema_cst;
		if (getterm(nospace(), ap) != sp_cst4) {
			syntax("term expected");
			break;
		}

		if (c == '+') ap->ema_cst += left;
		else ap->ema_cst = left - ap->ema_cst;
	}
	return sp_cst4;
}

PRIVATE int get15u(void)
{
	struct e_arg dummy;

	if (getnumber(getbyte(), &dummy) != sp_cst4) {
		syntax("integer expected");
	}
	else check((dummy.ema_cst & ~077777) == 0);
	return (int) (dummy.ema_cst);
}

PRIVATE void gettyp(int typset, register struct e_arg *ap)
{
	register int c, t;
	register int argtyp;

	if ((c = nospace()) == '\n') {
		ungetbyte(c);
		out("newline\n");
		argtyp = sp_cend;
	}
	else if (isdigit(c) || c == '+' || c == '-' || c == '(') {
		out("expr\n");
		argtyp = getexpr(c, ap);
		if (argtyp == sp_cst4 && fit16i(ap->ema_cst)) argtyp = sp_cst2;
	}
	else if (isalpha(c) || c == '_') {
		out("name\n");
		ungetbyte(c);
		ap->ema_dnam = getname()->str;
		ap->ema_argtype = sof_ptyp;
		argtyp = offsetted(sp_dnam, &(ap->ema_szoroff));
	}
	else if (c == '.') {
		out(".label\n");
		ap->ema_dlb = get15u();
		ap->ema_argtype = nof_ptyp;
		argtyp = offsetted(sp_dlb2, &(ap->ema_szoroff));
	}
	else if (c == '*') {
		out("*label\n");
		ap->ema_ilb = get15u();
		ap->ema_argtype = ilb_ptyp;
		argtyp = sp_ilb2;
	}
	else if (c == '$') {
		out("$name\n");
		ap->ema_pnam = getname()->str;
		ap->ema_argtype = pro_ptyp;
		argtyp = sp_pnam;
	}
	else if (c == '"' || c == '\'') {
		register struct string *s;

		out("string\n");
		ungetbyte(c);
		s = getstring(0);
		ap->ema_string = s->str;
		ap->ema_szoroff = s->length;	
		ap->ema_argtype = str_ptyp;
		argtyp = sp_scon;
	}
	else if (c == '?') {
		out("?\n");
		argtyp = sp_cend;
		ap->ema_argtype = 0;
	}
	else {
		/* c != '\n', so "ungetbyte" not neccesary */
		syntax("operand expected");
		return;
	}

	t = argtyp - sp_fspec;
	assert(t >= 0 && t < 16);
	if ((typset & (1 << t)) == 0) {
		syntax("Bad argument type");
		return;
	}

	if (argtyp == sp_cend) {
		ap->ema_argtype = 0;
	}
}

PRIVATE void getarg(int typset, struct e_arg *ap)
{
	register int c;

	if (argnum != 1) {
		if ((c = nospace()) != ',') {
			if (c != '\n') {
				syntax("comma expected");
				return;
			}
			ungetbyte(c);
		}
	}
	argnum++;
	gettyp(typset, ap);
}

/* getmnem: We found the start of either an instruction or a pseudo.
	get the rest of it
*/
PRIVATE void getmnem(int c, register struct e_instr *p)
{
	register int h;
	int i;
	register struct string *s;

	ungetbyte(c);
	s = getname();
	h = hash(s->str);

	for (;;) {
		h++;
		if (h >= HSIZE) h %= HSIZE;
		if ((i = hashtab[h]) == 0) {
			syntax("bad mnemonic");
			return;
		}
		else if (i <= sp_lmnem) {
			assert(i >= sp_fmnem);
			if (strcmp(s->str, em_mnem[i - sp_fmnem]) != 0) {
				continue;
			}
			p->em_type = EM_MNEM;
			p->em_opcode = i;
			break;
		}
		assert(i <= sp_lpseu && i >= sp_fpseu);
		if (strcmp(s->str, em_pseu[i - sp_fpseu]) != 0) {
			continue;
		}
		if (i == ps_mes) {
			p->em_type = EM_STARTMES;
			break;
		}
		p->em_opcode = i;
		p->em_type = EM_PSEU;
		break;
	}
}

PRIVATE void line_line(void)
{
	static char filebuf[256 + 1];
	char *btscpy();
	struct e_arg dummy;

	gettyp(ptyp(sp_cst2), &dummy);
	EM_lineno = dummy.ema_cst;
	gettyp(str_ptyp, &dummy);
	btscpy(filebuf, dummy.ema_string, (int) dummy.ema_szoroff);
	EM_filename = filebuf;
}

PRIVATE void getlabel(int c, register struct e_instr *p)
{

	ungetbyte(c);
	gettyp(lab_ptyp|ptyp(sp_cst2), &(p->em_arg));
	switch(p->em_argtype) {
	case cst_ptyp:
		p->em_type = EM_DEFILB;
		p->em_argtype = ilb_ptyp;
		p->em_ilb = p->em_cst;
		break;
	case sof_ptyp:
		p->em_type = EM_DEFDNAM;
		break;
	case nof_ptyp:
		p->em_type = EM_DEFDLB;
		break;
	}
	checkeol();
}

PRIVATE void gethead(register struct e_instr *p)
{
	register int c;

	argnum = 1;
	for (;;) {	
		EM_lineno++;
		c = getbyte();
		if (c == COMMENTSTARTER) {
			do	c = getbyte();
			while (c != '\n' && c != EOF);
		}
		if (c == EOF) {
			p->em_type = EM_EOF;
			return;
		}
		if (c == '\n') continue;
		if (isspace(c)) {
			c = nospace();
			if (isalpha(c) || c == '_') {
				getmnem(c, p);
				return;
			}
			ungetbyte(c);
		}
		else if (c == '#') line_line();
		else {
			getlabel(c, p);
			return;
		}
		checkeol();
		if (p->em_type == EM_ERROR || p->em_type == EM_FATAL) return;
	}
	/*NOTREACHED*/
}
