/*
 * File: genps.c
 *
 * Program to generate phrase structure tables for the IMP80 compiler
 *
 * Copyright (c) 2022, Bob Eager
 *
 */

/*
 * 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 */

#include <ctype.h>
#include <stdio.h>
#include <string.h>
#include <time.h>
#include <stdlib.h>
#include <libgen.h>
#include "typedefs.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 */

/* Forward references */

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

/* 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	*ifp;			/* File pointer for input file */
static	INT	kk[MAXDWORD+1];		/* Dictionary subsidiary array */
static	FILE	*lfp;			/* File pointer for listing file */
static	INT	nbip;			/* Number of BIPs */
static	INT	nidflag = 0;		/* Count of phrases not in dictionary */
static	FILE	*ofp;			/* File pointer for output file */
static	PCHAR	progname;		/* Program name */
static	INT	ss;			/* Table root */
static	INT	symbol[MAXSYMBOL+1];	/* Final form of main table */


INT main(INT argc, char *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 */
	CHAR s_now[26];			/* string date and time */

	progname = basename(argv[0]);

	if(argc != 4) putusage();

	/* open all files now */
	ifp = openio(argv[1], "r");
	ofp = openio(argv[2], "w");
	lfp = openio(argv[3], "w");

	/* get date and time for code and listing */
	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 */

	/* Get number of built in phrases */
	dnum = MINDWORD;
	nbip = readn();

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

	/* main loop */

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

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

		/* Dictionary definition */
		if(ch == 'D') {
			ch = fgetc(ifp);	/* skip the '(' */
			readstring(')');
			if(dnum < MINDWORD + nbip) {	/* BIP - list it */
				fprintf(lfp, "%6d  ", dnum+1);
				printstr(cc, 0);
				fputc('\n', lfp);
			}
			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", lfp);
		}
		fprintf(lfp, "\n\n%c%c", ch, fgetc(ifp));
		readstring(')');		/* read the phrase name */
		printstr(cc, 0);		/* copy to listing file */
		fputc(')', lfp);		/* finish it off */

		/* get offset in 'dword' of dict entry, and store in 'i' */
		lookup(dword, dlett, MINDWORD+1+nbip, dnum, TRUE, &i,
			MAXDLETT, MAXDWORD, 'd');
		kk[i] = asl;
		def = asl++;
		alt = asl++;

		/* loop to handle all alternates for current phrase */		
		for(;;) {
			ch = fgetc(ifp);
			fputc(ch, lfp);
			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(')', lfp);
					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('\'', lfp);
					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(INT 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", lfp);

	for(fp = lfp;;) {
#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(INT 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(INT 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(INT 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(INT 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 == ofp) break;
		fp = ofp;
	}

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

	return(EXIT_SUCCESS);
}


/*
 * 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;

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

nxt:		(*i)++;
	}

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


/*
 * Open a file in a specified mode. Does not return if there is a failure,
 * but outputs an error message and exits.
 *
 * Inputs:
 *	file	name of file to be opened
 *	mode	mode in which to open file
 *
 * Outputs:
 *	file pointer (if successful)
 *
 */

static FILE *openio(PCHAR file, PCHAR 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);
}


/*
 * List the contents of the literal dictionary.
 *
 * Inputs:
 *	none
 *
 * Outputs:
 *	none
 *
 */

static VOID pcdict(VOID)
{	INT k;

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

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


/*
 * List the contents of the main table.
 *
 * Inputs:
 *	none
 *
 * Outputs:
 *	none
 *
 */

static VOID pddict(VOID)
{	fputs("\n\n\nKey to main table (SYMBOL)\n\n", lfp);

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


/*
 * Outputs, to the listing file, the string stored in 'cs',
 * starting at 'ptr'. Handles 'underlined' characters and generates
 * '%' as required.
 *
 * Inputs:
 *	cs	string for output
 *	ptr	starting point in string
 *
 * Outputs:
 *	none
 *
 */

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

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

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


/*
 * Read a number from the input, and return its value.
 *
 * Inputs:
 *	none
 *
 * Outputs:
 *	value read
 *
 */

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

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

	return(res);
}


/*
 * Read a string from the input file into 'cc', stopping when the character
 * 'terminator' is encountered. 'Underlined' characters are generated as
 * required.
 *
 * Inputs:
 *	terminator	terminating character
 *
 * Outputs:
 *	'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(ifp);
		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)
{	(*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(INT i = 0; i <= cc[0]; i++) {
		lett[*next+i] = cc[i];
	}
	*next += (cc[0]+1);
}


/*
 * Output brief usage information, then exit.
 *
 * Inputs:
 *	none
 *
 * Outputs:
 *	none
 *
 */

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

	exit(EXIT_SUCCESS);
}

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