/*	This file must be included in the file "read_emk.c".
	It takes care of the reading of compact EM code.
*/

#include <ctype.h>

/* get16, get32: read a signed constant
*/
PRIVATE int
get16()
{
	register int l_byte, h_byte;

	l_byte = getbyte();
	h_byte = getbyte();
	if (h_byte >= 128) h_byte -= 256;
	return l_byte | (h_byte << 8);
}

PRIVATE arith
get32()
{
	register arith l;
	register int h_byte;

	l = getbyte();
	l |= ((unsigned) getbyte() << 8);
	l |= ((arith) getbyte() << 16);
	h_byte = getbyte();
	if (h_byte >= 128) h_byte -= 256;
	return l | ((arith) h_byte << 24);
}

PRIVATE struct string *getstring();

/* getarg : read an argument of any type, and check it against "typset"
   if neccesary. Return a pointer to the argument.
*/
PRIVATE struct e_args *
getarg(typset)
{
	register struct e_args *ap = argentry();
	register int i = getbyte();
#ifdef CHECKING
	int argtyp;
#endif CHECKING

	ap->em_next = 0;
	switch(i) {
	default:
		if (i < sp_fcst0+sp_ncst0 && i >= sp_fcst0) { /* A cst */
			ap->em_cst = i - sp_zcst0;
			ap->em_argtype = cst_ptyp;
			i = sp_cst2;
		}
		break;

	case sp_dlb1:	/* Numeric data label encoded in one byte */
		ap->em_dlb = getbyte();
		ap->em_noff = 0;
		ap->em_argtype = nof_ptyp;
		break;

	case sp_dlb2:	/* Numeric data label encoded in two bytes */
		ap->em_dlb = get16();
		ap->em_noff = 0;
		ap->em_argtype = nof_ptyp;
#ifdef CHECKING
		if (ap->em_dlb < 0) {
			xerror("Illegal data label");
			break;
		}
#endif CHECKING
		break;

	case sp_ilb1:	/* Instruction label encoded in one byte */
		ap->em_ilb = getbyte();
		ap->em_argtype = ilb_ptyp;
		break;

	case sp_ilb2:	/* Instruction label encoded in two bytes */
		ap->em_ilb = get16();
		ap->em_argtype = ilb_ptyp;
#ifdef CHECKING
		if (ap->em_ilb < 0) {
			xerror("Illegal instruction label");
			break;
		}
#endif CHECKING
		break;

	case sp_cst2:	/* A cst encoded in two bytes */
		ap->em_cst = get16();
		ap->em_argtype = cst_ptyp;
		break;

	case sp_cst4:	/* A cst encoded in four bytes */
		ap->em_cst = get32();
		ap->em_argtype = cst_ptyp;
		break;

	case sp_pnam:	/* A procedure name */
	{
		register struct string *p;

		p = getstring(1);
#ifdef CHECKING
		if (state & INSTRING) {
			xerror("Procedure name too long");
		}
#endif CHECKING
		ap->em_pnam = p->str;
		ap->em_argtype = pro_ptyp;
		break;
	}

	case sp_dnam:	/* A Non-numeric data label */
	{
		register struct string *p;

		p = getstring(1);
#ifdef CHECKING
		if (state & INSTRING) {
			xerror("Data label too long");
		}
#endif CHECKING
		ap->em_dnam = p->str;
		ap->em_soff = 0;
		ap->em_argtype = sof_ptyp;
		break;
	}

	case sp_doff:	/* A data label + offset */
	{
		register struct e_args *ap1;

		ap1 = getarg(lab_ptyp);
		*ap = *ap1;
		i_emargs--;
		ap1 = getarg(cst_ptyp);
		if (ap->em_argtype == sof_ptyp) {	/* non-numeric label */
			ap->em_soff = ap1->em_cst;
		}
		else	ap->em_noff = ap1->em_cst;
		i_emargs--;
		break;
	}

	case sp_icon:	/* An integer constant */
	case sp_ucon:	/* An unsigned constant */
	case sp_fcon:	/* A floating constant */
	{
		register struct string *p;

		ap->em_size = getarg(cst_ptyp)->em_cst;
		i_emargs--;
		p = getstring(0);
#ifdef CHECKING
		if (state & INSTRING) {
			xerror("Numeric constant too long");
		}
#endif CHECKING
		ap->em_argtype = ptyp(i);
		ap->em_str = p->str;
		break;
	}

	case sp_scon:	/* A string constant */
	{
		register struct string *p;

		p = getstring(0);
		ap->em_argtype = str_ptyp;
		ap->em_str = p->str;
		ap->em_size = p->length;
		break;
	}
	}
#ifdef CHECKING
	argtyp = i;
	if (EM_error) return 0;

	if (i == EOF) {
		xfatal("Unexpected EOF");
		return 0;
	}

	if ((i -= sp_fspec) < 0 || i >= 16) {
		xerror("Illegal byte");
		return 0;
	}

	if ((typset & (1 << i)) == 0) {
		xerror("Bad argument type");
	}
	if (argtyp == sp_cend) return 0;
#else not CHECKING
	if (i == sp_cend) return 0;
#endif CHECKING
	return ap;
}

#ifdef CHECKING
/* checkident: check that a string indeed represents an identifier
*/
PRIVATE int
checkident(s)
	register struct string *s;
{
	register char *p;
	register int n;

	p = s->str;
	if (!isascii(*p) || (!isalpha(*p) && *p != '_')) {
		return 0;
	}
	p++;
	for (n = s->length; --n > 0; p++) {
		if (!isascii(*p) || (!isalnum(*p) && *p != '_')) {
			return 0;
		}
	}
	return 1;
}
#endif CHECKING

/* getstring: read a string from the input, but at most STRSIZ characters
	of it. The next characters will be read another time around
*/
/*ARGSUSED*/
PRIVATE struct string *
getstring(isident)
{
	register char *p;
	register int n;
	register struct string *s;

	if (!(state & INSTRING)) {	/* Not reading a string yet */
		struct e_args *ap = getarg(cst_ptyp);
					/* Read length of string */
		i_emargs--;
		strleft = ap->em_cst;
#ifdef CHECKING
		if (strleft < 0) {
			xerror("Negative length in string");
			return 0;
		}
#endif CHECKING
	}

	s = stringentry();

	if (strleft <= STRSIZ) {	/* Handle the whole string */
		n = strleft;
		state &= ~INSTRING;
	}
	else {				/* Handle STRSIZ characters of it, and
					   indicate that there is more
					*/
	 	n = STRSIZ;
		strleft -= STRSIZ;
		state |= INSTRING;
	}

	s->length = n;
	p = s->str;
	while (--n >= 0) {
		*p++ = getbyte();
	}
	*p++ = '\0';

#ifdef CHECKING
	if (isident) {
		if (!checkident(s)) {
			xerror("Illegal identifier");
		}
	}
#endif CHECKING
	return s;
}

/* gethead: read the start of an EM-line
*/
PRIVATE struct e_instr *
gethead()
{
	register int i;
	register struct e_instr *p = &emhead;

	EM_lineno++;

	if ((i = getbyte()) < sp_fmnem+sp_nmnem && i >= sp_fmnem) {
		/* A mnemonic */
		p->em_type = EM_MNEM;
		p->em_opcode = i;
		return p;
	}

	if (i < sp_fpseu+sp_npseu && i >= sp_fpseu) {	/* A pseudo */
		if (i == ps_mes) {
			p->em_type = EM_STARTMES;
		}
		else	p->em_type = EM_PSEU;
		p->em_opcode = i;
		return p;
	}

	if (i < sp_filb0+sp_nilb0 && i >= sp_filb0) {	/* Instruction label */
		p->em_type = EM_DEFILB;
		p->em_deflb = i - sp_filb0;
		return p;
	}

	switch(i) {
	case sp_ilb1:	/* Instruction label */
		p->em_type = EM_DEFILB;
		p->em_deflb = getbyte();
		break;

	case sp_ilb2:	/* Instruction label */
		p->em_type = EM_DEFILB;
		p->em_deflb = get16();
#ifdef CHECKING
		if (p->em_deflb < 0) {
			xerror("Illegal instruction label definition");
		}
#endif CHECKING
		break;

	case sp_dlb1:	/* Numeric data label */
		p->em_type = EM_DEFDLB;
		p->em_deflb = getbyte();
		break;

	case sp_dlb2:	/* Numeric data label */
		p->em_type = EM_DEFDLB;
		p->em_deflb = get16();
#ifdef CHECKING
		if (p->em_deflb < 0) {
			xerror("Illegal data label definition");
		}
#endif CHECKING
		break;

	case sp_dnam:	/* Non-numeric data label */
	{
		struct string *s;

		p->em_type = EM_DEFDNAM;
		if (!(s = getstring(1))) {
			p->em_type = EM_ERROR;
		}
		else	p->em_defdnam = s->str;
		break;
	}		

	case EOF:	/* End of file */
		return 0;

	default:
		xerror("Unknown opcode");
		break;
	}

	return p;
}
