/*
 * (c) copyright 1987 by the Vrije Universiteit, Amsterdam, The Netherlands.
 * See the copyright notice in the ACK home directory, in the file "Copyright".
 *
 * Author: Hans van Staveren
 */
#include <stdlib.h>
#include <stdio.h>
#include "param.h"
#include "types.h"
#include "tes.h"
#include "line.h"
#include "lookup.h"
#include "alloc.h"
#include "proinf.h"
#include <em_spec.h>
#include <em_pseu.h>
#include <em_flag.h>
#include <em_mes.h>
#include "ext.h"
#include "reg.h"
#include "getline.h"
#include "util.h"

static short tabval; /* temp store for shorts */
static offset tabval2; /* temp store for offsets */
static char string[IDL + 1]; /* temp store for names */

/*
 * The next constants are close to sp_cend for fast switches
 */
#define INST    256     /* instruction:         number in tabval */
#define PSEU    257     /* pseudo:              number in tabval */
#define ILBX    258     /* label:               number in tabval */
#define DLBX    259     /* symbol:              name in string[] */
#define CSTX1   260     /* short constant:      stored in tabval */
#define CSTX2   261     /* offset:              value in tabval2 */
#define VALX1   262     /* symbol+short:        in string[] and tabval */
#define VALX2   263     /* symbol+offset:       in string[] and tabval2 */
#define ATEOF   264     /* bumped into end of file */

#define readbyte getchar

/* Other external declarations */
extern void process(void);

/* Forward declarations */
static int table2(void);

static void tstinpro(void)
{
	if (prodepth == 0)
		error("This is not allowed outside a procedure");
}

short readshort(void)
{
	register int l_byte, h_byte;

	l_byte = readbyte();
	h_byte = readbyte();
	if (h_byte >= 128)
		h_byte -= 256;
	return l_byte | (h_byte * 256);
}

#ifdef LONGOFF
static offset readoffset(void)
{
	register long l;
	register int h_byte;

	l = readbyte();
	l |= ((unsigned) readbyte()) * 256;
	l |= readbyte() * 256L * 256L;
	h_byte = readbyte();
	if (h_byte >= 128)
		h_byte -= 256;
	return l | (h_byte * 256L * 256 * 256L);
}
#endif

static void draininput(void)
{
	/*
	 * called when MES ERR is encountered.
	 * Drain input in case it is a pipe.
	 */
	while (getchar() != EOF)
		;
}

static short getint(void)
{

	switch (table2())
	{
		default:
			error("int expected");
		case CSTX1:
			return (tabval);
	}
}

static sym_p getsym(int status)
{
	switch (table2())
	{
		default:
			error("symbol expected");
		case DLBX:
			return (symlookup(string, status, 0));
		case sp_pnam:
			return (symlookup(string, status, SYMPRO));
	}
}

static offset getoff(void)
{
	switch (table2())
	{
		default:
			error("offset expected");
		case CSTX1:
			return ((offset) tabval);
#ifdef LONGOFF
		case CSTX2:
			return (tabval2);
#endif
	}
}

static void make_string(int n)
{
	sprintf(string, ".%u", n);
}

static void inident(void)
{
	register int n;
	register char *p = string;
	register int c;

	n = getint();
	while (n--)
	{
		c = readbyte();
		if (p < &string[IDL])
			*p++ = c;
	}
	*p++ = 0;
}

static int table3(int n)
{
	switch (n)
	{
		case sp_ilb1:
			tabval = readbyte();
			return (ILBX);
		case sp_ilb2:
			tabval = readshort();
			return (ILBX);
		case sp_dlb1:
			make_string(readbyte());
			return (DLBX);
		case sp_dlb2:
			make_string(readshort());
			return (DLBX);
		case sp_dnam:
			inident();
			return (DLBX);
		case sp_pnam:
			inident();
			return (n);
		case sp_cst2:
			tabval = readshort();
			return (CSTX1);
#ifdef LONGOFF
		case sp_cst4:
			tabval2 = readoffset();
			return (CSTX2);
#endif
		case sp_doff:
			if (table2() != DLBX)
				error("symbol expected");
			switch (table2())
			{
				default:
					error("offset expected");
				case CSTX1:
					return (VALX1);
#ifdef LONGOFF
				case CSTX2:
					return (VALX2);
#endif
			}
		default:
			return (n);
	}
}

static int table1(void)
{
	register int n;

	n = readbyte();
	if (n == EOF)
		return (ATEOF);
	if ((n <= sp_lmnem) && (n >= sp_fmnem))
	{
		tabval = n;
		return (INST);
	}
	if ((n <= sp_lpseu) && (n >= sp_fpseu))
	{
		tabval = n;
		return (PSEU);
	}
	if ((n < sp_filb0 + sp_nilb0) && (n >= sp_filb0))
	{
		tabval = n - sp_filb0;
		return (ILBX);
	}
	return (table3(n));
}

static int table2(void)
{
	register int n;

	n = readbyte();
	if ((n < sp_fcst0 + sp_ncst0) && (n >= sp_fcst0))
	{
		tabval = n - sp_zcst0;
		return (CSTX1);
	}
	return (table3(n));
}

static void argstring(offset length, register argb_p abp)
{
	while (length--)
	{
		if (abp->ab_index == NARGBYTES)
			abp = abp->ab_next = newargb();
		abp->ab_contents[abp->ab_index++] = readbyte();
	}
}

static line_p arglist(int n)
{
	line_p lnp;
	register arg_p ap, *app;
	bool moretocome;
	offset length;

	/*
	 * creates an arglist with n elements
	 * if n == 0 the arglist is variable and terminated by sp_cend
	 */

	lnp = newline(OPLIST);
	app = &lnp->l_a.la_arg;
	moretocome = TRUE;
	do
	{
		switch (table2())
		{
			default:
				error("unknown byte in arglist");
			case CSTX1:
				tabval2 = (offset) tabval;
			case CSTX2:
				*app = ap = newarg(ARGOFF);
				ap->a_a.a_offset = tabval2;
				app = &ap->a_next;
				break;
			case ILBX:
				tstinpro();
				*app = ap = newarg(ARGNUM);
				ap->a_a.a_np = numlookup((unsigned) tabval);
				ap->a_a.a_np->n_flags |= NUMDATA;
				app = &ap->a_next;
				break;
			case DLBX:
				*app = ap = newarg(ARGSYM);
				ap->a_a.a_sp = symlookup(string, OCCURRING, 0);
				app = &ap->a_next;
				break;
			case sp_pnam:
				*app = ap = newarg(ARGSYM);
				ap->a_a.a_sp = symlookup(string, OCCURRING, SYMPRO);
				app = &ap->a_next;
				break;
			case VALX1:
				tabval2 = (offset) tabval;
			case VALX2:
				*app = ap = newarg(ARGVAL);
				ap->a_a.a_val.av_sp = symlookup(string, OCCURRING, 0);
				ap->a_a.a_val.av_offset = tabval2;
				app = &ap->a_next;
				break;
			case sp_scon:
				*app = ap = newarg(ARGSTR);
				length = getoff();
				argstring(length, &ap->a_a.a_string);
				app = &ap->a_next;
				break;
			case sp_icon:
				*app = ap = newarg(ARGICN);
				goto casecon;
			case sp_ucon:
				*app = ap = newarg(ARGUCN);
				goto casecon;
			case sp_fcon:
				*app = ap = newarg(ARGFCN);
				casecon: length = getint();
				ap->a_a.a_con.ac_length = (short) length;
				argstring(getoff(), &ap->a_a.a_con.ac_con);
				app = &ap->a_next;
				break;
			case sp_cend:
				moretocome = FALSE;
		}
		if (n && (--n) == 0)
			moretocome = FALSE;
	} while (moretocome);
	return (lnp);
}

offset aoff(register arg_p ap, int n)
{

	while (n > 0)
	{
		if (ap != (arg_p) 0)
			ap = ap->a_next;
		n--;
	}
	if (ap == (arg_p) 0)
		error("too few parameters");
	if (ap->a_typ != ARGOFF)
		error("offset expected");
	return (ap->a_a.a_offset);
}

static int inpseudo(short n)
{
	register line_p lnp, head, tail;
	short n1, n2;
	proinf savearea;
#ifdef PSEUBETWEEN
	static int pcount = 0;

	if (pcount++ >= PSEUBETWEEN && prodepth == 0)
	{
		process();
		pcount = 0;
	}
#endif

	switch (n)
	{
		default:
			error("unknown pseudo");
		case ps_bss:
		case ps_hol:
			lnp = arglist(3);
			break;
		case ps_rom:
		case ps_con:
			lnp = arglist(0);
			break;
		case ps_ina:
		case ps_inp:
		case ps_exa:
		case ps_exp:
			lnp = newline(OPSYMBOL);
			lnp->l_a.la_sp = getsym(NOTHING);
			break;
		case ps_exc:
			n1 = getint();
			n2 = getint();
			if (n1 != 0 && n2 != 0)
			{
				tail = curpro.lastline;
				while (--n2)
					tail = tail->l_next;
				head = tail;
				while (n1--)
					head = head->l_next;
				lnp = tail->l_next;
				tail->l_next = head->l_next;
				head->l_next = curpro.lastline;
				curpro.lastline = lnp;
			}
			lnp = newline(OPNO);
			break;
		case ps_mes:
			lnp = arglist(0);
			switch ((int) aoff(lnp->l_a.la_arg, 0))
			{
				case ms_err:
					draininput();
					exit(-1);
				case ms_opt:
					nflag = TRUE;
					break;
				case ms_emx:
					wordsize = aoff(lnp->l_a.la_arg, 1);
					pointersize = aoff(lnp->l_a.la_arg, 2);
#ifndef LONGOFF
					if (wordsize>2)
					error("This optimizer cannot handle wordsize>2");
#endif
					break;
				case ms_gto:
					curpro.gtoproc = 1;
					/* Treat as empty mes ms_reg */
				case ms_reg:
					tstinpro();
					regvar(lnp->l_a.la_arg->a_next);
					oldline(lnp);
					lnp = newline(OPNO);
					n = ps_exc; /* kludge to force out this line */
					break;
				case ms_tes:
					tstinpro();
					oldline(lnp);
					lnp = newline(OPNO);
					n = ps_exc; /* kludge to force out this line */
					break;
			}
			break;
		case ps_pro:
			if (prodepth > 0)
				savearea = curpro;
			else
				process();
			curpro.symbol = getsym(DEFINING);
			switch (table2())
			{
				case sp_cend:
					curpro.localbytes = (offset) -1;
					break;
				case CSTX1:
					tabval2 = (offset) tabval;
				case CSTX2:
					curpro.localbytes = tabval2;
					break;
				default:
					error("bad second arg of PRO");
			}
			prodepth++;
			curpro.gtoproc = 0;
			if (prodepth > 1)
			{
				register int i;

				curpro.lastline = (line_p) 0;
				curpro.freg = (reg_p) 0;
				for (i = 0; i < NNUMHASH; i++)
					curpro.numhash[i] = (num_p) 0;
				getlines();
				curpro = savearea;
				prodepth--;
			}
			return (0);
		case ps_end:
			if (prodepth == 0)
				error("END misplaced");
			switch (table2())
			{
				case sp_cend:
					if (curpro.localbytes == (offset) -1)
						error("bytes for locals still unknown");
					break;
				case CSTX1:
					tabval2 = (offset) tabval;
				case CSTX2:
					if (curpro.localbytes != (offset) -1
							&& curpro.localbytes != tabval2)
						error("inconsistency in number of bytes for locals");
					curpro.localbytes = tabval2;
					break;
			}
			process();
			curpro.symbol = (sym_p) 0;
			if (prodepth == 1)
			{
				prodepth = 0;
#ifdef PSEUBETWEEN
				pcount = 0;
#endif
				return (0);
			}
			else
				return (1);
	}
	lnp->l_instr = n;
	lnp->l_next = curpro.lastline;
	curpro.lastline = lnp;
	return (0);
}

void getlines(void)
{
	register line_p lnp;
	register int instr;

	for (;;)
	{
		linecount++;
		switch (table1())
		{
			default:
				error("unknown instruction byte");
				/* NOTREACHED */

			case ATEOF:
				if (prodepth != 0)
					error("procedure unterminated at eof");
				process();
				return;
			case INST:
				tstinpro();
				instr = tabval;
				break;
			case DLBX:
				lnp = newline(OPSYMBOL);
				lnp->l_instr = ps_sym;
				lnp->l_a.la_sp = symlookup(string, DEFINING, 0);
				lnp->l_next = curpro.lastline;
				curpro.lastline = lnp;
				continue;
			case ILBX:
				tstinpro();
				lnp = newline(OPNUMLAB);
				lnp->l_instr = op_lab;
				lnp->l_a.la_np = numlookup((unsigned) tabval);
				if (lnp->l_a.la_np->n_line != (line_p) 0)
					error("label %u multiple defined", (unsigned) tabval);
				lnp->l_a.la_np->n_line = lnp;
				lnp->l_next = curpro.lastline;
				curpro.lastline = lnp;
				continue;
			case PSEU:
				if (inpseudo(tabval))
					return;
				continue;
		}

		/*
		 * Now we have an instruction number in instr
		 * There might be an operand, look for it
		 */

		if ((em_flag[instr - sp_fmnem] & EM_PAR) == PAR_NO)
		{
			lnp = newline(OPNO);
		}
		else
			switch (table2())
			{
				default:
					error("unknown offset byte");
				case sp_cend:
					lnp = newline(OPNO);
					break;
				case CSTX1:
					if ((em_flag[instr - sp_fmnem] & EM_PAR) != PAR_B)
					{
						if (CANMINI(tabval))
							lnp = newline(tabval + Z_OPMINI);
						else
						{
							lnp = newline(OPSHORT);
							lnp->l_a.la_short = tabval;
						}
					}
					else
					{
						lnp = newline(OPNUMLAB);
						lnp->l_a.la_np = numlookup((unsigned) tabval);
					}
					break;
#ifdef LONGOFF
				case CSTX2:
					lnp = newline(OPOFFSET);
					lnp->l_a.la_offset = tabval2;
					break;
#endif
				case ILBX:
					tstinpro();
					lnp = newline(OPNUMLAB);
					lnp->l_a.la_np = numlookup((unsigned) tabval);
					break;
				case DLBX:
					lnp = newline(OPSYMBOL);
					lnp->l_a.la_sp = symlookup(string, OCCURRING, 0);
					break;
				case sp_pnam:
					lnp = newline(OPSYMBOL);
					lnp->l_a.la_sp = symlookup(string, OCCURRING, SYMPRO);
					break;
				case VALX1:
					lnp = newline(OPSVAL);
					lnp->l_a.la_sval.lasv_sp = symlookup(string, OCCURRING, 0);
					lnp->l_a.la_sval.lasv_short = tabval;
					break;
#ifdef LONGOFF
				case VALX2:
					lnp = newline(OPLVAL);
					lnp->l_a.la_lval.lalv_sp = symlookup(string, OCCURRING, 0);
					lnp->l_a.la_lval.lalv_offset = tabval2;
					break;
#endif
			}
		lnp->l_instr = instr;
		lnp->l_next = curpro.lastline;
		curpro.lastline = lnp;
	}
}

