/*
 * (c) copyright 1987 by the Vrije Universiteit, Amsterdam, The Netherlands.
 * See the copyright notice in the ACK home directory, in the file "Copyright".
 *
 */

#include "ip_spec.h"
#include <stdlib.h>
#include <stdio.h>
#include <stdarg.h>
#include <string.h>
#include "em_spec.h"
#include "em_flag.h"

#ifndef NORCSID
static char rcs_id[] = "$Id$";
#endif

/* This program reads the human readable interpreter specification
 and produces a efficient machine representation that can be
 translated by a C-compiler.
 */

#define NOTAB   600    /* The max no of interpreter specs */
#define ESCAP1  256
#define ESCAP2	257

struct opform intable[NOTAB];
struct opform *lastform = intable - 1;

int nerror = 0;
int atend = 0;
int line = 1;

extern char em_mnem[][4];
char esca1[] = "escape1";
char esca2[] = "escape2";
#define ename(no)       ((no)==ESCAP1?esca1:(no)==ESCAP2?esca2:em_mnem[(no)])

extern char em_flag[];

char *ident(void);
void check(int);
int decflag(char *);
void checkall(void);
void chkc(int, int, int, int);
void ckop(int, int, int, int);
int readchar(void);
void pushback(int);
void writeout(void);
void prx(register int, int, int);
void readin(void);
int getmnem(char *);
void error(char *str, ...);
void mess(char *str, ...);
void fatal(char *str, ...);



int main(int argc, char **argv)
{
	if (argc > 1)
	{
		if (freopen(argv[1], "r", stdin) == NULL)
		{
			fatal("Cannot open %s", argv[1]);
		}
	}
	if (argc > 2)
	{
		if (freopen(argv[2], "w", stdout) == NULL)
		{
			fatal("Cannot create %s", argv[2]);
		}
	}
	if (argc > 3)
	{
		fatal("%s [ file [ file ] ]", argv[0]);
	}
	atend = 0;
	readin();
	atend = 1;
	checkall();
	if (nerror == 0)
	{
		writeout();
	}
	exit(nerror);
}

void readin(void)
{
	register struct opform *nextform;
	char *firstid;

	for (nextform = intable; !feof(stdin) && nextform < &intable[NOTAB];)
	{
		firstid = ident();
		if (*firstid == '\n' || feof(stdin))
			continue;
		lastform = nextform;
		nextform->i_opcode = getmnem(firstid);
		nextform->i_flag = decflag(ident());
		switch (nextform->i_flag & OPTYPE)
		{
			case OPMINI:
			case OPSHORT:
				nextform->i_num = atoi(ident());
				break;
		}
		nextform->i_low = atoi(ident());
		if (*ident() != '\n')
		{
			int c;
			error("End of line expected");
			while ((c = readchar()) != '\n' && c != EOF)
				;
		}
		nextform++;
	}
	if (!feof(stdin))
		fatal("Internal table too small");
}

char *ident(void)
{
	/* skip spaces and tabs, anything up to space,tab or eof is
	 a identifier.
	 Anything from # to end-of-line is an end-of-line.
	 End-of-line is an identifier all by itself.
	 */

	static char array[200];
	register int c;
	register char *cc;

	do
	{
		c = readchar();
	} while (c == ' ' || c == '\t');
	for (cc = array; cc < &array[(sizeof array) - 1]; cc++)
	{
		if (c == '#')
		{
			do
			{
				c = readchar();
			} while (c != '\n' && c != EOF);
		}
		*cc = c;
		if (c == '\n' && cc == array)
			break;
		c = readchar();
		if (c == '\n')
		{
			pushback(c);
			break;
		}
		if (c == ' ' || c == '\t' || c == EOF)
			break;
	}
	*++cc = 0;
	return array;
}

int getmnem(char *str)
{
	char (*ptr)[4];

	for (ptr = em_mnem; *ptr <= &em_mnem[sp_lmnem - sp_fmnem][0]; ptr++)
	{
		if (strcmp(*ptr, str) == 0)
			return (ptr - em_mnem);
	}
	error("Illegal mnemonic");
	return 0;
}

void error(char *str, ...)
	/* VARARGS1 */
{
	if (!atend)
		fprintf(stderr, "line %d: ", line);

	va_list ap;
	va_start(ap, str);
	vfprintf(stderr, str, ap);
	va_end(ap);
	fprintf(stderr, "\n");
	nerror++;
}

void mess(char *str, ...)
	/* VARARGS1 */
{
	if (!atend)
		fprintf(stderr, "line %d: ", line);
	va_list ap;
	va_start(ap, str);
	vfprintf(stderr, str, ap);
	va_end(ap);
	fprintf(stderr, "\n");
}

void fatal(char *str, ...)
	/* VARARGS1 */
{
	va_list ap;
	va_start(ap, str);
	error(str, ap);
	va_end(ap);
	exit(1);
}

#define ILLGL   -1

void check(int val)
{
	if (val != ILLGL)
		error("Illegal flag combination");
}

int decflag(char *str)
{
	int type;
	int escape;
	int range;
	int wordm;
	int notzero;

	type = escape = range = wordm = notzero = ILLGL;
	while (*str)
		switch (*str++)
		{
			case 'm':
				check(type);
				type = OPMINI;
				break;
			case 's':
				check(type);
				type = OPSHORT;
				break;
			case '-':
				check(type);
				type = OPNO;
				break;
			case '1':
				check(type);
				type = OP8;
				break;
			case '2':
				check(type);
				type = OP16;
				break;
			case '4':
				check(type);
				type = OP32;
				break;
			case '8':
				check(type);
				type = OP64;
				break;
			case 'u':
				check(type);
				type = OP16U;
				break;
			case 'e':
				check(escape);
				escape = 0;
				break;
			case 'N':
				check(range);
				range = 2;
				break;
			case 'P':
				check(range);
				range = 1;
				break;
			case 'w':
				check(wordm);
				wordm = 0;
				break;
			case 'o':
				check(notzero);
				notzero = 0;
				break;
			default:
				error("Unknown flag");
		}
	if (type == ILLGL)
		error("Type must be specified");
	switch (type)
	{
		case OP64:
		case OP32:
			if (escape != ILLGL)
				error("Conflicting escapes");
			escape = ILLGL;
		case OP16:
		case OP16U:
		case OP8:
		case OPSHORT:
		case OPNO:
			if (notzero != ILLGL)
				mess("Improbable OPNZ");
			if (type == OPNO && range != ILLGL)
			{
				mess("No operand in range");
			}
	}
	if (escape != ILLGL)
		type |= OPESC;
	if (wordm != ILLGL)
		type |= OPWORD;
	switch (range)
	{
		case ILLGL:
			type |= OP_BOTH;
			if (type == OPMINI || type == OPSHORT)
				error("Minies and shorties must have P or N");
			break;
		case 1:
			type |= OP_POS;
			break;
		case 2:
			type |= OP_NEG;
			break;
	}
	if (notzero != ILLGL)
		type |= OPNZ;
	return type;
}

/* ----------- checking --------------*/

int ecodes[256], codes[256], lcodes[256];
char eflags[256], flags[256], lflags[256];
int elows[256], lows[256], llows[256];

#define NMNEM   (sp_lmnem-sp_fmnem+1)
#define MUST    1
#define MAY     2
#define FORB    3

char negc[NMNEM], zc[NMNEM], posc[NMNEM], lnegc[NMNEM], lposc[NMNEM];

void checkall(void)
{
	register int i, flag;
	register struct opform *next;
	int opc, low;

	for (i = 0; i < NMNEM; i++)
		negc[i] = zc[i] = posc[i] = 0;
	for (i = 0; i < 256; i++)
		lcodes[i] = codes[i] = ecodes[i] = -1;
	codes[254] = ESCAP1;
	codes[255] = ESCAP2;

	atend = 0;
	line = 0;
	for (next = intable; next <= lastform; next++)
	{
		line++;
		flag = next->i_flag & 0377;
		opc = next->i_opcode & 0377;
		low = next->i_low & 0377;
		chkc(flag, low, opc, low);
		switch (flag & OPTYPE)
		{
			case OPNO:
				zc[opc]++;
				break;
			case OPMINI:
			case OPSHORT:
				for (i = 1; i < ((next->i_num) & 0377); i++)
				{
					chkc(flag, low + i, opc, low);
				}
				if (!(em_flag[opc] & PAR_G) && (flag & OPRANGE) == OP_BOTH)
				{
					mess("Mini's and shorties should have P or N");
				}
				break;
			case OP8:
				error("OP8 is removed");
				break;
			case OP16:
				if (flag & OP_NEG)
					negc[opc]++;
				else if (flag & OP_POS)
					posc[opc]++;
				break;
			case OP32:
				if (flag & OP_NEG)
					lnegc[opc]++;
				else if (flag & OP_POS)
					lposc[opc]++;
				break;
			case OP16U:
				break;
			default:
				error("Illegal type");
				break;
		}
	}
	atend = 1;
	for (i = 0; i < 256; i++)
		if (codes[i] == -1)
		{
			mess("interpreter opcode %d not used", i);
		}
	for (opc = 0; opc < NMNEM; opc++)
	{
		switch (em_flag[opc] & EM_PAR)
		{
			case PAR_NO:
				ckop(opc, MUST, FORB, FORB);
				break;
			case PAR_C:
			case PAR_D:
			case PAR_F:
			case PAR_B:
				ckop(opc, FORB, MAY, MAY);
				break;
			case PAR_N:
			case PAR_G:
			case PAR_S:
			case PAR_Z:
			case PAR_O:
			case PAR_P:
				ckop(opc, FORB, MAY, FORB);
				break;
			case PAR_R:
				ckop(opc, FORB, MAY, FORB);
				break;
			case PAR_L:
				ckop(opc, FORB, MUST, MUST);
				break;
			case PAR_W:
				ckop(opc, MUST, MAY, FORB);
				break;
			default:
				error("Unknown instruction type of %s", ename(opc));
				break;
		}
	}
}

void chkc(int flag, int icode, int emc, int low)
{
	if (flag & OPESC)
	{
		if (ecodes[icode] != -1)
		{
			mess("Escaped opcode %d used by %s and %s", icode, ename(emc),
					ename(ecodes[icode]));
		}
		ecodes[icode] = emc;
		eflags[icode] = flag;
		elows[icode] = low;
	}
	else
		switch (flag & OPTYPE)
		{
			default:
				if (codes[icode] != -1)
				{
					mess("Opcode %d used by %s and %s", icode, ename(emc),
							ename(codes[icode]));
				}
				codes[icode] = emc;
				flags[icode] = flag;
				lows[icode] = low;
				break;
			case OP32:
			case OP64:
				if (lcodes[icode] != -1)
				{
					mess("Long opcode %d used by %s and %s", icode, ename(emc),
							ename(lcodes[icode]));
				}
				lcodes[icode] = emc;
				lflags[icode] = flag;
				llows[icode] = low;
				break;
		}
}

void ckop(int emc, int zf, int pf, int nf)
{
	if (zc[emc] > 1)
		mess("More then one OPNO for %s", ename(emc));
	if (posc[emc] > 1)
		mess("More then one OP16(pos) for %s", ename(emc));
	if (negc[emc] > 1)
		mess("More then one OP16(neg) for %s", ename(emc));
	if (lposc[emc] > 1)
		mess("More then one OP32(pos) for %s", ename(emc));
	if (lnegc[emc] > 1)
		mess("More then one OP32(neg) for %s", ename(emc));
	switch (zf)
	{
		case MUST:
			if (zc[emc] == 0)
				mess("No OPNO for %s", ename(emc));
			break;
		case FORB:
			if (zc[emc] == 1)
				mess("Forbidden OPNO for %s", ename(emc));
			break;
	}
	switch (pf)
	{
		case MUST:
			if (posc[emc] == 0)
				mess("No OP16(pos) for %s", ename(emc));
			break;
		case FORB:
			if (posc[emc] == 1)
				mess("Forbidden OP16(pos) for %s", ename(emc));
			break;
	}
	switch (nf)
	{
		case MUST:
			if (negc[emc] == 0)
				mess("No OP16(neg) for %s", ename(emc));
			break;
		case FORB:
			if (negc[emc] == 1)
				mess("Forbidden OP16(neg) for %s", ename(emc));
			break;
	}
}

static int pushchar;
static int pushf;

int readchar(void)
{
	int c;

	if (pushf)
	{
		pushf = 0;
		c = pushchar;
	}
	else
	{
		if (feof(stdin))
			return EOF;
		c = getc(stdin);
	}
	if (c == '\n')
		line++;
	return c;
}

void pushback(int c)
{
	if (pushf)
	{
		fatal("Double pushback");
	}
	pushf++;
	pushchar = c;
	if (c == '\n')
		line--;
}

void writeout(void)
{
	register int i;

	printf("DISPATCH1");
	for (i = 0; i < 256;)
	{
		if (!(i % 8))
			printf("\n%d", i);
		printf("\t%s", ename(codes[i]));
		if (i < 254)
		{
			prx(flags[i], lows[i], i);
		}
		i++;
	}

	printf("\nDISPATCH2");
	for (i = 0; i < 256;)
	{
		if (ecodes[i] != -1)
		{
			if (!(i % 8))
				printf("\n%d", i);
			printf("\t%s", ename(ecodes[i]));
			prx(eflags[i], elows[i], i);
		}
		else
			break;
		i++;
	}

	printf("\nDISPATCH3");
	i = 0;
	while (lcodes[i] != -1)
	{
		if (!(i % 8))
			printf("\n%d", i);
		printf("\t%s", ename(lcodes[i]));
		prx(lflags[i], llows[i], i);
		i++;
	}
	while (i++ % 8)
		putchar('\t');
	putchar('\n');
}

void prx(register int flg, int low, int opc)
{
	int arg = opc - low;

	putchar('.');
	switch (flg & OPTYPE)
	{
		case OPNO:
			putchar('z');
			break;
		case OP16U:
			putchar('u');
			break;
		case OP16:
			if (flg & OP_POS)
				putchar('p');
			else if (flg & OP_NEG)
				putchar('n');
			else
				putchar('l');
			if (flg & OPWORD)
				putchar('w');
			break;
		case OP32:
			if (flg & OP_POS)
				putchar('P');
			else if (flg & OP_NEG)
				putchar('N');
			else
				putchar('L');
			if (flg & OPWORD)
				putchar('w');
			break;
		case OPSHORT:
			if (flg & OPWORD)
				putchar('w');
			else
				putchar('s');
			/* fall through */
		case OPMINI:
			if (flg & OPNZ)
				arg++;
			if (flg & OP_NEG)
				arg = -arg - 1;
			printf("%d", arg);
			if ((flg & OPTYPE) == OPMINI && (flg & OPWORD))
				putchar('W');
	}
}
