/*
 * File: compmess.c
 *
 * Program to compress a file of error messages, generating suitable
 * arrays to hold the compressed form, and also some code to expand
 * them again
 *
 * 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 <stdlib.h>

#define	MAXLETT		1000		/* Max length of 'lett' array */
#define	MAXLINE		80		/* Max input line length */
#define	MAXWORD		2000		/* Max length of 'word' array */

#define	SPACES		"                                            "

/* 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	do_listing(FILE *);
static	VOID	do_output(FILE *);
static	INT	getword(PUCHAR *, PUCHAR);
static	VOID	lit(PINT, PUCHAR, FILE *);
static	VOID	mess(PUCHAR, INT);
static	FILE	*openio(PUCHAR, PUCHAR);
static	INT	readn(FILE *);
static	VOID	squash(PUCHAR);
static	VOID	usage(VOID);

/* Local data */

static	INT	lett[MAXLETT];	/* Encoded words */
static	INT	next = 0;	/* Index of next free slot in 'lett' array */
static	INT	nmax = 0;	/* Highest message number seen */
static	INT	num = 0;	/* Next free slot in 'word' array */
static	INT	nummax = 0;	/* Pointer to last message in 'word' */
static	PUCHAR 	progname;	/* Program name */
static	INT	word[MAXWORD];	/* Message and word numbers */

const	UCHAR	intt[128] = { /* Array to map characters to 6-bit code */
	63,63,63,63,63,63,63,63,
	63,63,63,63,63,63,63,63,
	63,63,63,63,63,63,63,63,
	63,63,63,63,63,63,63,63,
	63,63,63,61,63,60,27,30,
	31,32,63,63,62,28,59,29,
	63,63,63,63,63,63,63,63,
	63,63,63,63,63,63,63,63,
	63, 1, 2, 3, 4, 5, 6, 7,
	 8, 9,10,11,12,13,14,15,
	16,17,18,19,20,21,22,23,
	24,25,26,63,63,63,63,63,
	63,33,34,35,36,37,38,39,
	40,41,42,43,44,45,46,47,
	48,49,50,51,52,53,54,55,
	56,57,58,63,63,63,63,63
};
const UCHAR outtt[64] = { /* Array to map 6-bit codes to characters */
	'?','A','B','C','D','E','F','G',
	'H','I','J','K','L','M','N','O',
	'P','Q','R','S','T','U','V','W',
	'X','Y','Z','&','-','/','\'','(',
	')','a','b','c','d','e','f','g',
	'h','i','j','k','l','m','n','o',
	'p','q','r','s','t','u','v','w',
	'x','y','z','.','%','#',',','?'
};


INT main(INT argc, PUCHAR argv[])
{	INT n;				/* Current message number */
	INT ch,i;
	PUCHAR ptr;			/* Pointer to next input word */
	FILE *infp,*outfp,*listfp;	/* I/O file pointers */
	UCHAR input[MAXLINE];		/* Input line buffer */
	UCHAR wk1[MAXLINE];		/* String work area */

	/* Generate clean version of program name, for messages */

	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");

	for(i = 0; i < MAXWORD; i++) word[i] = 0;
	lett[0] = 0;			/* Initialise terminator */

	/* Main loop - once for each message */

	while((n = readn(infp)) != 0) {
		if(n > nmax) nmax = n;	/* Update high water mark */
		if(num >= MAXWORD) {
			fputs("*** Overflow of 'word' array\n", stderr);
			exit(EXIT_FAILURE);
		}
		word[num] = n;		/* Store message number at start */
		nummax = num;		/* Store number of last message so far */

		for(ch = fgetc(infp); ch != '\n'; ch = fgetc(infp)) {
			if(ch != '"') continue;
					/* Scan for start of message */
			fscanf(infp, "%[^\"]", &input[0]);
					/* Read message within quotes */
			while(ch != '\n') ch = fgetc(infp);
					/* Skip trailing junk on line */
			break;
		}

		squash(&input[0]);	/* Squash multiple spaces */
		if((strlen(input) == 0) || (strcmp(input, " ") == 0))
			continue;	/* Ignore empty lines and messages */
		num++;			/* Point beyond message number */
		fprintf(listfp, "\n%3d", n);
					/* Output message number to listing */
		
		ptr = &input[0];
		while(getword(&ptr, &wk1[0])) {
					/* Get next word in message */
			if(strlen(wk1) != 0) {
				lit(&i, &wk1[0], listfp);
					/* Get word index to 'i' */
				if(num >= MAXWORD) {
					fputs("*** Overflow of 'word' array\n",
						stderr);
					exit(EXIT_FAILURE);
				}
				word[num++] = i | 0x8000;
					/* Store with continuation bit */
			}
		}
	}
	if(num >= MAXWORD) {
		fputs("*** Overflow of 'word' array\n", stderr);
		exit(EXIT_FAILURE);
	}
	word[num] = 0;			/* Store word list terminator */
	fputc('\n', listfp);
	fclose(infp);

	/* Generate listing of messages */

	do_listing(listfp);
	fclose(listfp);

	/* Generate actual output file */

	do_output(outfp);
	fclose(outfp);

#if	C
	fprintf(stderr, "%s: function 'message' generated\n", progname);
#endif
#if	IMP
	fprintf(stderr, "%s: routine 'message' generated\n", progname);
#endif

	return(EXIT_SUCCESS);
}


/*
 * Routine to output the listing file
 *
 */

static VOID do_listing(FILE *listfp)
{	UCHAR temp[MAXLINE];
	INT i;

	fputs("\n\n", listfp);
	for(i = 1; i <= nmax; i++) {
		mess(&temp[0], i);	/* Get message 'i' to 'temp' */
		if(strlen(temp) != 0) {
			fprintf(listfp, "%3d  %s\n", i, temp);
		}
	}
}


#if	C
/*
 * Routine to generate the main output file
 *
 * C version
 *
 */

static VOID do_output(FILE *outfp)
{	INT i, j;
	UINT k;
	UCHAR m[MAXLINE];

	fputs("/*\n * File: message.c\n *\n", outfp);
	fprintf(outfp,
		" * This file is generated automatically"
		" by the '%s' program\n",
		progname);
	fputs(" *\n * It should never be edited; rather,"
		" alter the message file then\n", outfp);
	fprintf(outfp, " * rerun '%s'\n *\n */\n\n", progname);
	fputs("#include <stdio.h>\n\n/", outfp);
	for(i = 1; i <= 70; i++) fputc('*', outfp);
	fputs("\n *       Outputs an error message stored in a"
		" compressed format       *\n",
		outfp);
	fputs(" *", outfp);
	for(i = 1; i <= 68; i++) fputc(' ', outfp);
	fputc('*', outfp);
	fputc('\n', outfp);
	for(i = 1; i <= nmax; i++) {
		mess(&m[0], i);		/* Get message 'i' to 'm' */
		k = strlen(m);
		if(k != 0) {		/* If message exists */
			fprintf(outfp, " *     %3d  %s", i, m);
			for(j = 1; j <= 58 - k; j++) fputc(' ', outfp);
			fputs("*\n", outfp);
		}
	}
	fputs(" *", outfp);
	for(i = 1; i <= 68; i++) fputc(' ', outfp);
	fputs("*\n *", outfp);
	for(i = 1; i <= 69; i++) fputc('*', outfp);
	fputs("/\n\n", outfp);
	next--;				/* Point to last used slot in 'lett' */
	fprintf(outfp, "#define\tMWORDMAX\t%d\n", num+1);
	fprintf(outfp, "#define\tDEFAULT\t\t%d\n\n", nummax+1);
	fputs("const unsigned char outtt[64] = {\n", outfp);
	fputs("\t'?','A','B','C','D','E','F','G',\n", outfp);
	fputs("\t    'H','I','J','K','L','M','N',\n", outfp);
	fputs("\t    'O','P','Q','R','S','T','U',\n", outfp);
	fputs("\t    'V','W','X','Y','Z','&','-',\n", outfp);
	fputs("\t    '/','\\'','(',')',\n", outfp);
	fputs("\t    'a','b','c','d','e','f','g',\n", outfp);
	fputs("\t    'h','i','j','k','l','m','n',\n", outfp);
	fputs("\t    'o','p','q','r','s','t','u',\n", outfp);
	fputs("\t    'v','w','x','y','z','.','%',\n", outfp);
	fputs("\t    '#',',','?'\n};\n\n", outfp);
	fputs("const unsigned short mword[MWORDMAX+1] = {\n", outfp);
	fputs("\t0,\n\t", outfp);

	for(i = 0; i <= num; i++) {
		fprintf(outfp, "0x%04x", word[i]);
		if(i != num) fputc(',', outfp);
		if(((i + 1) % 8) == 0) {
			fputc('\n', outfp);
			if(i != num)
				fputc('\t', outfp);
		}
	}
	if((num+1)%8 != 0) fputc('\n', outfp);
	fputs("};\n", outfp);

	fprintf(outfp, "\nconst int mlett[%d] = {\n\t0,\n\t", next+2);

	for(i = 0; i <= next; i++) {
		fprintf(outfp, "0x%08x", lett[i]);
		if(i != next) fputc(',', outfp);
		if(((i + 1) % 4) == 0) {
			fputc('\n', outfp);
			if(i != next)
				fputc('\t', outfp);
		}
	}
	if((next+1)%4 != 0) fputc('\n', outfp);
	fputs("};\n\n", outfp);

	fprintf(outfp, "void message(unsigned char *mes, int n)\n");
	fputs("{\tint i, j, k, q;\n", outfp);
	fputs("\tint m, sh;\n\n", outfp);
	fputs("\t*mes++ = \' \';\n", outfp);
	fputs("\t*mes = \'\\0\';\n", outfp);
	fputs("\tj = 0;\n", outfp);
	fputs("\tfor(i = 0; i < MWORDMAX+1; i++) {\n", outfp);
	fputs("\t\tif(n == mword[i]) {\n", outfp);
	fputs("\t\t\tj = 1;\n", outfp);
	fputs("\t\t\tbreak;\n", outfp);
	fputs("\t\t}\n", outfp);
	fputs("\t}\n\n", outfp);
	fputs("\tif(j == 0) {\n", outfp);
	fputs("\t\ti = DEFAULT;\n", outfp);
	fputs("\t\tj = 1;\n", outfp);
	fputs("\t}\n\n", outfp);
	fputs("\tfor(;;) {\n", outfp);
	fputs("\t\tk = mword[i+j];\n", outfp);
	fputs("\t\tif((k & 0x8000) == 0) break;\n", outfp);
	fputs("\t\tk &= 0x7fff;\n", outfp);
	fputs("\t\tif(j != 1) *mes++ = ' ';\n", outfp);
	fputs("\t\tdo {\n", outfp);
	fputs("\t\t\tm = mlett[k+1];\n", outfp);
	fputs("\t\t\tsh = 25;\n", outfp);
	fputs("\t\t\tdo {\n", outfp);
	fputs("\t\t\t\tq = (int) ((m >> sh) & 0x3f);\n", outfp);
	fputs("\t\t\t\tif(q != 0) *mes++ = outtt[q];\n", outfp);
	fputs("\t\t\t\tsh -= 6;\n", outfp);
	fputs("\t\t\t} while(sh >= 0);\n", outfp);
	fputs("\t\t\tk++;\n", outfp);
	fputs("\t\t} while((m & 1) != 0);\n", outfp);
	fputs("\t\tj++;\n", outfp);
	fputs("\t}\n", outfp);
	fputs("\t*mes = '\\0';\t\t\t/* Terminate string */\n", outfp);
	fputs("}\n", outfp);
	fputs("\n/*\n * End of file: message.c\n *\n */\n", outfp);
}
#endif


#if	IMP
/*
 * Routine to generate the main output file
 *
 * IMP version
 *
 */

static VOID do_output(FILE *outfp)
{	INT i,j,k;
	UCHAR m[MAXLINE];

	fputs("!\n! File: message.imp\n!\n", outfp);
	fprintf(outfp,
		"! This file is generated automatically by the '%s' program\n",
		progname);
	fputs("!\n! It should never be edited; rather,"
		" alter the message file then\n",
		outfp);
	fprintf(outfp, "! rerun '%s'\n!\n", progname);
	fprintf(outfp,
		"%%string(%d)%%function message(%%integer n)\n!", MAXLINE);
	for(i = 1; i <= 71; i++) fputc('*', outfp);
	fputs("\n!*       Outputs an error message stored in a"
		" compressed format        *\n",
		outfp);
	fputc('!', outfp);
	fputc('*', outfp);
	for(i = 1; i <= 69; i++) fputc(' ', outfp);
	fputc('*', outfp);
	fputc('\n', outfp);
	for(i = 1; i <= nmax; i++) {
		mess(&m[0], i);		/* Get message 'i' to 'm' */
		k = strlen(m);
		if(k != 0) {		/* If message exists */
			fprintf(outfp,"!*     %3d  %s", i, m);
			for(j = 1; j <= 59 - k; j++) fputc(' ', outfp);
			fputs("*\n", outfp);
		}
	}
	fputc('!', outfp);
	fputc('*', outfp);
	for(i = 1; i <= 69; i++) fputc(' ', outfp);
	fputc('*', outfp);
	fputc('\n', outfp);
	fputc('!', outfp);
	for(i = 1; i <= 71; i++) fputc('*', outfp);
	fputs("\n!\n", outfp);
	next--;				/* Point to last used slot in 'lett' */
	fputs("%constantbyteintegerarray outtt(0:63) = "
		    "'?','A','B','C','D','E','F','G',\n", outfp);
	fputs(SPACES"'H','I','J','K','L','M','N',\n", outfp);
	fputs(SPACES"'O','P','Q','R','S','T','U',\n", outfp);
	fputs(SPACES"'V','W','X','Y','Z','&','-',\n", outfp);
	fputs(SPACES"'/','''','(',')',\n", outfp);
	fputs(SPACES"'a','b','c','d','e','f','g',\n", outfp);
	fputs(SPACES"'h','i','j','k','l','m','n',\n", outfp);
	fputs(SPACES"'o','p','q','r','s','t','u',\n", outfp);
	fputs(SPACES"'v','w','x','y','z','.','%',\n", outfp);
	fputs(SPACES"'#',',','?'\n!\n", outfp);
	fprintf(outfp,
		"%%constantinteger mwordmax = %d, default = %d\n",
		num+1, nummax+1);
	fputs("%constanthalfintegerarray mword(0:mwordmax) = 0,%c\n         ",
		outfp);

	for(i = 0; i <= num; i++) {
		fprintf(outfp, "x'%04x'", word[i]);
		if(i != num) fputc(',', outfp);
		if(((i + 1) % 8) == 0) {
			fputc('\n', outfp);
			if(i != num)
				fputs("         ", outfp);
		}
	}
	if((num+1)%8 != 0) fputc('\n', outfp);

	fprintf(outfp,
		"!\n%%constantintegerarray mlett(0:%d) = 0,%%c\n        ",
		next+1);

	for(i = 0; i <= next; i++) {
		fprintf(outfp, "x'%08x'", lett[i]);
		if(i != next) fputc(',', outfp);
		if(((i + 1) % 4) == 0) {
			fputc('\n', outfp);
			if(i != next)
				fputs("        ", outfp);
		}
	}
	if((next+1)%4 != 0) fputc('\n', outfp);
	fputs("!\n", outfp);
	fputs("%integer i,j,k,m,q,s\n", outfp);
	fprintf(outfp, "%%string(%d) omess\n!\n", MAXLINE);
	fputs("      omess = \" \"\n", outfp);
	fputs("      %for i = 1,1,mwordmax - 1 %cycle\n", outfp);
	fputs("         -> found %if n = mword(i)\n", outfp);
	fputs("      %repeat\n", outfp);
	fputs("      i = default\n", outfp);
	fputs("!\nfound:\n!\n", outfp);
	fputs("      j = 1\n", outfp);
	fputs("      %cycle\n", outfp);
	fputs("         k = mword(i+j)\n", outfp);
	fputs("         %if k & x'8000' = 0 %then %exit\n", outfp);
	fputs("         k = k & x'7fff'\n", outfp);
	fputs("         omess = omess.\" \" %unless j = 1\n", outfp);
	fputs("         %until m & 1 = 0 %cycle\n", outfp);
	fputs("            m = mlett(k); s = 25\n", outfp);
	fputs("            %until s < 0 %cycle\n", outfp);
	fputs("               q = m >> s & 63\n", outfp);
	fputs("               %if q \\= 0 %then omess = "
		"omess.tostring(outtt(q))\n", outfp);
	fputs("               s = s - 6\n", outfp);
	fputs("            %repeat\n", outfp);
	fputs("            k = k + 1\n", outfp);
	fputs("         %repeat\n", outfp);
	fputs("         j = j + 1\n", outfp);
	fputs("      %repeat\n", outfp);
	fputs("   %result = omess\n", outfp);
	fputs("%end;   ! of mess\n", outfp);
	fputs("!\n! End of file: message.imp\n!\n", outfp);
}
#endif


/*
 * Function to extract the next word from the string 's' and place
 * it in 'word'. Returns zero if no words left, otherwise returns 1.
 *
 */

static INT getword(PUCHAR *s, PUCHAR word)
{	UCHAR ch;
	PUCHAR p = *s;			/* Get working copy of pointer */

	if(*p == '\0') return(0);	/* String empty */

	for(;;) {			/* Lose leading spaces */
		ch = *p++;
		if(ch != ' ') break;
	}

	while((ch != ' ') && (ch != '\0')) {
		*word++ = ch;
		ch = *p++;
	}
	if(ch == '\0') p--;		/* Back off to null terminator */
	*word = '\0';			/* Add terminator */

	/* 'p' now points beyond the space. */

	*s = p;				/* Copy back pointer */
	return(1);			/* A word has been read */
}


/*
 * This routine searches for the word 'txt' in the current word list.
 * If found, it returns the word index in 'p'.
 * If not found, it adds the word to the word list and again returns
 * the word index in 'p'.
 *
 */

static VOID lit(PINT p, PUCHAR txt, FILE *listfp)
{	INT ch = 0;
	INT i,j;
	INT l = 0;
	UINT txtlen = strlen(txt);
	INT w = 0;
	INT sh = 25;

	while(ch < txtlen) {
		i = txt[ch++];		/* Get next character in word */
		i = intt[i];		/* Convert to 6-bit code */
		w = w | (i << sh);
		sh -= 6;
		if(sh >= 0) continue;
		if(ch < txtlen) w |= 1;
		if(next+l >= MAXLETT) {
			fputs("Overflow of 'lett' array\n", stderr);
			exit(EXIT_FAILURE);
		}
		lett[next+l] = w;
		w = 0;
		sh = 25;
		l++;
	}

	if(sh != 25) {
		if(next+l >= MAXLETT) {
			fputs("Overflow of 'lett' array\n", stderr);
			exit(EXIT_FAILURE);
		}
		lett[next+l] = w;
		l++;
	}
					/* Store any remainder */
	for(i = 0; i < next; i++) {
		for(j = 0; j < l; j++) {
			if(lett[i+j] != lett[next+j]) goto fail;
        	}
		goto found;
	fail:;
	}

	*p = next;
	next += l;
	fprintf(listfp, " word entered");
	return;

found:
	*p = i;
	fprintf(listfp, " word found  ");
}


/*
 * Routine to return a string corresponding to message 'n'.
 * The string is stored in the character array 'mes'.
 *
 */

static VOID mess(PUCHAR mes, INT n)
{	INT i, j, k, q;
	INT m, sh;

	j = 0;
	for(i = 0; i < num; i++) {
		if(n == word[i]) {
			j = 1;
			break;
		}
	}
	if(j == 0) {
		*mes = '\0';
		return;
	}

	for(;;) {
		k = word[i+j];
		if((k & 0x8000) == 0) break;
		k &= 0x7fff;
		if(j != 1) *mes++ = ' ';
		do {
			m = lett[k];
			sh = 25;
			do {
				q = (int) ((m >> sh) & 0x3f);
				if(q != 0) *mes++ = outtt[q];
				sh -= 6;
			} while(sh >= 0);
			k++;
		} while((m & 1) != 0);
		j++;
	}
	*mes = '\0';			/* Terminate string */
}


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


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

static INT readn(FILE *fp)
{	INT res;
	INT ch = '0';

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

	return(res);
}


/*
 * Routine to convert multiple spaces to single spaces in the string 's'.
 *
 */

static VOID squash(PUCHAR s)
{	UCHAR ch;
	UCHAR lastch = 'x';
	INT i;
	INT j = 0;

	for(i = 0; s[i] != '\0'; i++) {
		ch = s[i];
		if(!((ch == ' ') && (lastch == ' '))) {
			s[j++] = ch;
			lastch = ch;
		}
	}
	s[j] = '\0';
} 


/*
 * 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: compmess.c
 *
 */
