/*
 * File: genps.c
 *
 * Program to generate phrase structure tables for the IMP80 compiler
 *
 * Bob Eager   August 2002
 *
 */

/*
 * Compile time options
 *
 * Define exactly one target language name to be 1; all the rest should be 0.
 *
 */

#define	C	1			/* C language */
#define	IMP	0			/* IMP80 language */


#ifdef	__IBMC__
#pragma	strings(readonly)
#endif

#include <ctype.h>
#include <stdio.h>
#include <string.h>
#include <time.h>
#include <stdlib.h>

#define	FALSE		0
#define	TRUE		1

#define	MAXCC		300		/* Size of 'cc' array */
#define	MAXCLETT	1000		/* Size of 'clett' array */
#define	MAXCWORD	200		/* Size of 'cword' array */
#define	MAXDLETT	1000		/* Size of 'dlett' array */
#define	MAXDWORD	1200		/* Size of 'dword' array */
#define	MAXSYMBOL	3000		/* Size of 'symbol' array */
#define	MINDWORD	1000		/* Starting point of 'dword' array */
#define	MINSYMBOL	1300		/* Starting point of 'symbol' array */

/* Type definitions */

typedef	void		VOID, *PVOID;
typedef	int		BOOL, *PBOOL;
typedef	char		CHAR, *PCHAR;
typedef	unsigned char	UCHAR, *PUCHAR;
typedef	int		INT, *PINT;
typedef	unsigned int	UINT, *PUINT;
typedef	long		LONG, *PLONG;
typedef	unsigned long	ULONG, *PULONG;

/* Forward references */

static	VOID	lookup(INT [], INT[], INT, INT, INT, PINT, INT, INT, CHAR);
static	FILE	*openio(PUCHAR, PUCHAR);
static	VOID	pcdict(VOID);
static	VOID	pddict(VOID);
static	VOID	printstr(INT [], INT);
static	int	readn(VOID);
static	VOID	readstring(INT);
static	VOID	record(INT [], INT[], PINT, PINT, INT, INT, CHAR);
static	VOID	usage(VOID);

/* Local data */

static	INT	cc[MAXCC+1];		/* Input string buffer */
static	INT	clett[MAXCLETT+1];	/* Literal dictionary word list */
static	INT	cnext = 0;		/* Points to next free slot in 'clett' */
static	INT	cnum = 0;		/* Number of items in literal dictionary */
static	INT	cword[MAXCWORD+1];	/* List of literal dictionary word pointers */
static	INT	dlett[MAXDLETT+1];	/* List of dictionary words */
static	INT	dnext = 0;		/* Points to next free slot in 'dlett' */
static	INT	dnum;			/* Number of items in dictionary + 1000 */
static	INT	dword[MAXDWORD+1];	/* List of dictionary word pointers */
static	FILE	*infp;			/* File pointer for input file */
static	INT	kk[MAXDWORD+1];		/* Dictionary subsidiary array */
static	FILE	*listfp;		/* File pointer for listing file */
static	INT	nbip;			/* Number of BIPs */
static	INT	nidflag = 0;		/* Count of phrases not in dictionary */
static	FILE	*outfp;			/* File pointer for output file */
static	PUCHAR	progname;		/* Program name */
static	INT	ss;			/* Table root */
static	INT	symbol[MAXSYMBOL+1];	/* Final form of main table */


INT main(INT argc, PUCHAR argv[])
{	INT asl = MINSYMBOL;		/* Main table free pointer */
	INT ch;				/* Character temporary */
	INT i;
	INT alt;
	INT def;
	INT phrase = 0;
	FILE *fp;			/* Temporary I/O file pointer */
	time_t now;			/* Binary date and time */
	UCHAR s_now[26];		/* String date and time */
	PUCHAR ptr;			/* Miscellaneous pointer */

	progname = argv[0];
	ptr = strrchr(progname, '\\');
	if(ptr != (PUCHAR) NULL) progname = ++ptr;
	for(i = 0; progname[i] != '\0'; i++)
		progname[i] = (UCHAR) tolower(progname[i]);

	if(argc != 4) usage();

	infp = openio(argv[1], "r");
	outfp = openio(argv[2], "w");
	listfp = openio(argv[3], "w");

	now = time((time_t *) NULL);	/* Get binary date and time */
	strcpy(s_now, ctime(&now));	/* Copy string version */
	s_now[24] = '\0';		/* Truncate to remove newline */

	dnum = MINDWORD;
	nbip = readn();			/* Get number of BIPs */

	fprintf(listfp, "\nIMP80 phrase structure generated on %s\n\n", s_now);
	fputs("Built in phrases\n\n", listfp);

	/* Main loop */

	for(;;) {
		ch = fgetc(infp);
		if(isalpha(ch)) ch = toupper(ch);

		/* Comment */

		if(ch == '!') {
			while(ch != '\n') ch = fgetc(infp);
					/* Skip rest of line */
			continue;
		}

		/* Dictionary definition */

		if(ch == 'D') {
			ch = fgetc(infp);
					/* Skip the '(' */
			readstring(')');
			if(dnum < MINDWORD + nbip) {	/* BIP - list it */
				fprintf(listfp, "%6d  ", dnum+1);
				printstr(cc, 0);
				fputc('\n', listfp);
			}
			record(dword, dlett, &dnum, &dnext, MAXDLETT,
				MAXDWORD, 'd');
			continue;
		}
		if((ch != 'P') && (ch != 'E')) continue;
					/* Ignore others */
		if(ch == 'E') break;	/* Finished */

		/* Phrase definition */

		if(!phrase) {
			phrase++;
			fputs("\f\n\nPhrase definitions\n\n", listfp);
		}
		fprintf(listfp, "\n\n%c%c", ch, fgetc(infp));
		readstring(')');	/* Read the phrase name */
		printstr(cc, 0);		/* Copy to listing file */
		fputc(')', listfp);	/* Finish it off */
		lookup(dword, dlett, MINDWORD+1+nbip, dnum, TRUE, &i,
			MAXDLETT, MAXDWORD, 'd');
				/* Get offset in 'dword' of dict entry, */
				/* and store in 'i' */
		kk[i] = asl;
		def = asl++;
		alt = asl++;

		/* Loop to handle all alternates for current phrase */
		
		for(;;) {
			ch = fgetc(infp);
			fputc(ch, listfp);
			switch(ch) {
				case ',':	/* End of alternate */
					symbol[alt] = asl;
					alt = asl++;
					break;

				case '0':	/* Always success alternate */
					symbol[asl++] = MINDWORD;
					break;

				case '*':	/* Repeated phrase */
					symbol[asl++] = MINDWORD - 1;
					break;

				case '(':	/* Dictionary item */
					readstring(')');
						/* Read the name */
					printstr(cc, 0);
					fputc(')', listfp);
					lookup(dword, dlett, MINDWORD+1,
						dnum, FALSE, &i, MAXDLETT,
						MAXDWORD, 'd');
					symbol[asl++] = i;
					break;

				case '\'':	/* Literal */
					readstring('\'');
						/* Read the literal */
					printstr(cc, 0);
					fputc('\'', listfp);
					lookup(cword, clett, 1,
						cnum, TRUE, &i, MAXCLETT,
						MAXCWORD, 'c');
					symbol[asl++] = i;
					break;

				case ';':	/* End of alternates */
					symbol[alt] = asl;
					symbol[def] = asl;
					goto nexts;
			}
		}
nexts:;
	}

	pcdict();
	pddict();

	for(i = MINSYMBOL; i < asl; i++) {
		if((1 <= symbol[i]) && (symbol[i] <= cnum)) {
			symbol[i] = cword[symbol[i]];
		}
		if((1001 + nbip <= symbol[i]) && (symbol[i] <= dnum)) {
			symbol[i] = kk[symbol[i]];
		}
	}

	ss = kk[dnum];
	fputs("\n\f\n", listfp);

	for(fp = listfp;;) {
#if	C
		fprintf(fp, "/*\n * File: %s\n *\n * Produced by GENPS"
				" from %s on %s\n *\n */\n\n",
				argv[2], argv[1], s_now);
		fprintf(fp, "const\tunsigned char\tclett[%d] = {", cnext);

		for(i = 0; i <= cnext - 1; i++) {
			fprintf(fp, "%4d%s", clett[i],
				(i == cnext - 1) ? "\n};" : ",");
			if((i != cnext -1) && (i%14 == 0)) fputc('\n', fp);
		}
		fprintf(fp, "\n\n");
		fprintf(fp, "const\tshort\tx_symbol[%d] = {", asl - MINSYMBOL);

		for(i = MINSYMBOL; i < asl; i++) {
			fprintf(fp, "%5d%s", symbol[i],
				(i == asl - 1) ? "\n};" : ",");
			if((i != asl - 1) && ((i-MINSYMBOL+1)%10 == 1))
				fputc('\n', fp);
		}
		fprintf(fp, "\n\nconst\tshort\t*symbol = &x_symbol[-%d];\n",
				MINSYMBOL);

		fprintf(fp, "\n#define\tSS\t\t%d\n\n", ss);
		fprintf(fp, "/*\n * End of file: %s\n *\n */\n", argv[2]);
#endif
#if	IMP
		fprintf(fp, "!\n! Produced by GENPS from %s on %s\n!\n",
			argv[1], s_now);
		fprintf(fp, "%%constantbyteintegerarray clett(0:%d) = ",
				cnext-1);

		for(i = 0; i <= cnext - 1; i++) {
			fprintf(fp, "%4d%c", clett[i],
				(i == cnext - 1) ? ';' : ',');
			if((i != cnext -1) && (i%14 == 0)) fputc('\n', fp);
		}
		fprintf(fp, "\n!\n");
		fprintf(fp, "%%constantintegerarray symbol(%d:%d) = ",
			MINSYMBOL, asl - 1);

		for(i = MINSYMBOL; i < asl; i++) {
			fprintf(fp, "%5d%c", symbol[i],
				(i == asl - 1) ? ';' : ',');
			if((i != asl - 1) && ((i-MINSYMBOL+1)%10 == 1))
				fputc('\n', fp);
		}
		fprintf(fp, "\n!\n%%constantinteger ss = %d\n!\n", ss);
#endif

		if(nidflag != 0) {
			fprintf(stderr, "%s: %d phrase%s not found in"
				" dictionary\n",
				progname, nidflag, nidflag == 1 ? "" : "s");
			exit(EXIT_FAILURE);
		}

		if(fp == outfp) break;
		fp = outfp;
	}

	fprintf(stderr, "%s: no errors\n", progname);

	return(EXIT_SUCCESS);
}


/*
 * Routine to look up the item in 'cc' in the tables specified by
 * 'word' and 'lett'.
 *
 */

static VOID lookup(INT word[], INT lett[], INT first, INT last, INT insert,
			PINT i, INT maxlett, INT maxword, CHAR atype)
{	INT j, k;

	*i = first;
	while(*i <= last) {
		j = word[*i];
		for(k = 0; k <= lett[j]; k++)
			if(lett[j+k] != cc[k]) goto nxt;
		return;

nxt:		(*i)++;
	}

	if(insert == FALSE) {
		fprintf(listfp,
		"\n**************** Phrase not in dictionary ***********   ");
		printstr(cc, 0);
		nidflag++;
		return;
	}
	record(word, lett, &cnum, &cnext, maxlett, maxword, atype);
						/* Insert into C dictionary */
	*i = cnum;
}


/*
 * Function to open a file in a specified mode
 * Does not return if there is a failure, but outputs an error
 * message and exits
 *
 */

static FILE *openio(PUCHAR file, PUCHAR mode)
{	FILE *fp;

	fp = fopen(file, mode);
	if(fp == (FILE *) NULL) {
		fprintf(stderr, "%s: cannot open '%s'\n", progname, file);
		exit(EXIT_FAILURE);
	}
	return(fp);
}


/*
 * Routine to list contents of literal dictionary 
 *
 */

static VOID pcdict(VOID)
{	INT i, j, k;

	fputs("\n\f\n\nKey to literal dictionary (CLETT)\n\n", listfp);

	for(j = 1; j <= cnum; j++) {
		k = cword[j];
		fprintf(listfp, "%4d  ", k);
		printstr(clett, k);
		for(i = 0; i < 17 - ss; i++) fputc(' ', listfp);
		if(j%3 == 0) fputc('\n', listfp);
	}
}


/*
 * Routine to list contents of main table
 *
 */

static VOID pddict(VOID)
{	INT i, j;

	fputs("\n\n\nKey to main table (SYMBOL)\n\n", listfp);

	for(j = 1001; j <= dnum; j++) {
		if(j <= (1000+nbip)) {
			fprintf(listfp, "%4d  ", j);
		} else {
			fprintf(listfp, "%4d  ", kk[j]);
		}
		printstr(dlett, dword[j]);
		for(i = 0; i < 17 - ss; i++) fputc(' ', listfp);
		if(((j-1000)%3) == 0)	fputc('\n', listfp);
	}
}


/*
 * Outputs, to the listing file, the string stored in 'cc',
 * starting at 'ptr'. Handles underlined characters and generates
 * '%' as required.
 *
 */

static VOID printstr(INT cc[], INT ptr)
{	INT ch;
	INT i, k;
	INT del;

	ss = cc[ptr];
	k = ss;
	del = 0;

	for(i = 1; i <= k; i++) {
		ch = cc[ptr+i];
		if((del == 0) && (ch > 128)) {
			ss++;
			del = 1;
			fputc('%', listfp);
		}
		ch &= 0x7f;
		fputc(ch, listfp);
	}
}


/*
 * Function to read a number from input 'fp' and return its value.
 *
 */

static INT readn(VOID)
{	INT res;
	INT ch = '0';

	for(res = 0; isdigit(ch); ch = fgetc(infp)) {
		res = res*10 + (ch - '0');
	}

	return(res);
}


/*
 * Routine to read a string from the input file into 'cc', stopping
 * when the character 'terminator' is encountered. Underlined characters
 * are generated as required.
 *
 * On exit, 'cc' contains the string, with the length in cc[0].
 *
 */

static VOID readstring(INT terminator)
{	INT under = 0;
	INT ch;
	INT count = 0;

	for(;;) {
		ch = fgetc(infp);
		if(ch == '%') {
			under = 128;
			continue;
		}
		if(isalpha(ch)) {
			ch = toupper(ch);
			ch += under;
		} else under = 0;
		if(ch == ' ') continue;	/* Ignore spaces */
		if(ch == terminator) {
			cc[0] = count;
			break;
		}
		cc[++count] = ch;
	}
}


/*
 * Records the item in 'cc' in the tables specified by 'word' and 'lett'.
 * 'word' holds the offset in 'lett' where the item starts.
 * Updates 'next', the table free pointer, by adding the length of the item.
 * Updates 'num', the number of dictionary items, by adding one.
 *
 */

static VOID record(INT word[], INT lett[], PINT num, PINT next, INT maxlett,
			INT maxword, CHAR atype)
{	INT i;

	(*num)++;
	if(*num >= maxword) {
		fprintf(stderr, "%s: %cword array overflow\n", progname, atype);
		exit(EXIT_FAILURE);
	}

	word[*num] = *next;

	if((*next + cc[0] + 1) >= maxlett) {
		fprintf(stderr, "%s: %clett array overflow\n", progname, atype);
		exit(EXIT_FAILURE);
	}

	for(i = 0; i <= cc[0]; i++) {
		lett[*next+i] = cc[i];
	}
	*next += (cc[0]+1);
}


/*
 * Routine to output brief usage information, then exit.
 *
 */

static VOID usage(VOID)
{	fprintf(stderr, "Usage: %s input output listing\n", progname);
	exit(EXIT_SUCCESS);
}

/*
 * End of file: genps.c
 *
 */
