/*+++*
 *  title:	keysubst.c
 *  abstract:	fill in a key-template file according to a JOVE wallchart
 *  author:	T.R.Hageman, Groningen, The Netherlands.
 *  created:	october 1989
 *  modified:	25-Jun-91, add ANSI prototypes.
 *  description:
 *	Usage: keysubst keybinding-file(s) <template-file
 *
 *	`keybinding-file' is a file containing the output from JOVEs
 *	"describe-bindings" command.
 *	`template file' is a file mixing text and "key templates".
 *
 *	A key template starts with a `key-code', which is a sequence
 *	of keys in `Hat' notation, i.e.
 *	 (ASCII 0-31)		^@ ^A-^Z ^[(=ESC) ^\ ^] ^^ ^_
 *	 (DEL)			^?
 *	 (function key xx)	^:xx
 *	 (literal character x)	^`x
 *	followed by an arbitrary number of fill characters (<,-,>)
 *	(for Left,Centered,Right justification, respectively),
 *	optionally followed by a continuation character (+ or \).
 *	The '+' expresses a preference for the first field,
 *	'\' for the second field.
 *
 *	The template is replaced by the name of the function bound
 *	to that key. If the name is too long to fit in a short field,
 *	the program attempts to shorten the name in some sensible way.
 *	If no binding exists, the field is filled with blanks.
 *
 *	Comment lines start with `#' in the first column.
 *
 *  bugs:
 *	If a field is continued with '\', it is assumed that there is
 *	only one more field to follow and that it has the same size as
 *	the first field.
 *
 *  example:
 *	bindings:
 *	  ^:ku		previous-line
 *	  ESC ^:ku	previous-window
 *	input:
 *	  UpArrow: ^:ku<<<<<<<<<<<<< ESC UpArrow: ^[^:ku-------------
 *	output:
 *	  UpArrow: previous-line     ESC UpArrow:   previous-window
 *---*/

#define NOT_JOVE
#include "jove.h"

RCS("$Id: keysubst.c,v 14.30.0.9 1993/12/13 01:57:29 tom Exp tom $")

/* #undef NULL /*  I'd rather have a warning than nothing at all... */
#include <ctype.h>
#include <stdio.h>

#ifdef DEBUG
#   define DPRINTF(x)	dprintf x
#   ifdef _STDARG_
#	include <stdarg.h>
#   else
#	include <varargs.h>
#   endif

int	debug;

void
DEFVARG( dprintf, (char *fmt, ...), (fmt, va_alist) const char	*fmt;)
{
	va_list	ap;

	if (!debug)
		return;
	va_begin(ap, fmt);
	vfprintf(stderr, fmt, ap);
	va_end(ap);
}
#else
#   define DPRINTF(x)
#endif /* DEBUG */

#undef NFKEYS
#define NFKEYS	128	/* we want many */

#undef MAPSIZE
#define MAPSIZE	(128 + NFKEYS)

#define VOIDKEY	(MAPSIZE-1)

#define NMAPS	4

typedef char	*keydef[MAPSIZE];

keydef		KeyTab[NMAPS];

char		*_pname;

char		line[BUFSIZ];
int		line_no;
const char	*file_name;

/*==============================*/
/*	support routines	*/
/*==============================*/

static char	Prefix[] = "prefix-";

#define ISPREFIX(key,map)	(!lmatch(Prefix, KeyTab[map][key]) ? 0 : \
				 KeyTab[map][key][sizeof(Prefix)-1] - '0')

int	numcomp __(( const char *_(s1), const char *_(s2) ));
int
numcomp(s1, s2)
register const char	*s1, *s2;
{
	register const char	*base = s1;

	while (*s1 == *s2++)
		if (*s1++ == '\0') {
			--s1;
			break;
		}
	return s1 - base;
}

int	lmatch __(( const char *_(s1), const char *_(s2) ));
int
lmatch(s1, s2)
register const char	*s1, *s2;
{
	if (!s1 || !s2)
		return 0;
	while (*s1)
		if (*s1++ != *s2++)
			return 0;
	return 1;
}

char *	copystr __(( const char *_(str) ));
char *
copystr(str)
const char	*str;
{
	register const char	*s;
	register char		*d, *base;

	for (s = str; *s++;)
		;
	base = d = malloc((size_t)(s - str));
	if (!base) {
		fprintf(stderr, "%s: [out of memory]\n", _pname);
		exit(2);
	}
	for (s = str; *d++ = *s++; )
		;
	if (d[-2] == '\n')		/* delete trailing newline */
		d[-2] = '\0';
	return base;
}

/*==============================*/
/*	function keys		*/
/*==============================*/

short	FkeyDef[NFKEYS];
int	AddFkeys = 1;

int	function_key __(( const char *_(s) ));
int
function_key(s)
const char	*s;
{
	register int	id = (s[0] << 8) | s[1];
	register int	i;

	for (i = 0; i < sizeof(FkeyDef)/sizeof(FkeyDef[0]) - 1; i++) {
		if (FkeyDef[i] == id)
			return i + 128;
		if (FkeyDef[i] == 0) {
			if (!AddFkeys)
				return VOIDKEY;
			FkeyDef[i] = id;
			return i + 128;
		}
	}
	fprintf(stderr, "Function key table overflow: \"%.2s\" ignored\n", s);
	return VOIDKEY;
}

void	fixup_function_keys __(( void ));
void
fixup_function_keys()
{
	int	i;

	for (i = 0;  i < NMAPS; i++)
		KeyTab[i][VOIDKEY] = NULL;
	AddFkeys = 0;
}

/*==============================*/
/*	wallchart parsing	*/
/*==============================*/

const char *	GetKey __(( const char *_(s), int *_(result) ));
const char *
GetKey(s, result)
register const char	*s;
int			*result;
{
	register int	c;

	switch (c = *s++) {
	case 'C':
		if (s[0] == '-')
			s++, c = *s++ ^ '@';
		break;
	case 'E':
		if (s[0] == 'S' && s[1] == 'C')
			c = '\33', s += 2;
		break;
	case 'S':
		if (s[0] == 'P' && s[1] == 'C')
			c = ' ', s += 2;
		break;
	case '^':
		if (s[0] == '?')
			s++, c = '\177';
		else if (s[0] == ':')
			c = function_key(++s), s += 2;
		break;
	}
	*result = c;
	return s;
}

#undef CHECK
#define CHECK(cond)	if (!(cond)) return 0;

int	GetKeySeq __(( char *_(line) ));
int
GetKeySeq(line)
char	*line;
{
	register const char	*s = line;
	int		key1, key2;
	register int	map,
			newmap;

	DPRINTF(("%.14s\n", s));
	for (map = 0; ; map = newmap) {
		switch (*s++) {
		case '{':
			if (s[0] != ' ' || s[1] == ',') {
 				CHECK(s = GetKey(s, &key1));
				CHECK(*s++ == ',');
				CHECK(s = GetKey(s, &key2));
				CHECK(*s++ == '}');
				key2 = -key2;
				break;
			}
			/* fall through to default */
		case '[':
			if (s[0] != ' ' || s[1] == '-') {
 				CHECK(s = GetKey(s, &key1));
				CHECK(*s++ == '-');
				CHECK(s = GetKey(s, &key2));
				CHECK(*s++ == ']');
				break;
			}
			/* fall through */
		default:
			s = GetKey(s - 1, &key1);
			key2 = key1;
		}
		CHECK(*s++ == ' ');
		if ((newmap = ISPREFIX(key1, map)) == 0)
			break;
	}
	while (*s++ == ' ') ;  --s;

	if (map && key1 == ' ' && key2 == key1 && lmatch(Prefix, s))
		return 1;	/* double occurrence of prefix-binding */

	KeyTab[map][key1] = copystr(s);
	if (key2 < 0)
		KeyTab[map][-key2] = KeyTab[map][key1];
	else while (key2 > key1)
		KeyTab[map][key2--] = KeyTab[map][key1];

	return 1;
}

int	eat_wallchart __(( const char *_(filename) ));
int
eat_wallchart(filename)
const char	*filename;
{
	int	notstdin = strcmp(filename, "-");
	FILE	*f = (notstdin) ? fopen(filename, "r") : stdin;

	if (!f) {
		fprintf(stderr, "%s: warning: can't read wallchart \"%s\"\n",
			_pname, filename);
		return notstdin;
	}
	DPRINTF(("**** eat_wallchart \"%s\"\n", (f != stdin) ? filename : "(stdin)"));
	line_no = 0;
	file_name = filename;
	while (fgets(line, sizeof line, f)) {
		line_no++;
		if (!GetKeySeq(line))
			fprintf(stderr, "%s:%d:[bad key-sequence] %s", file_name, line_no, line);
	}
	if (!notstdin)
		fclose(f);

	return notstdin;
}

/*==============================*/
/*	pending Formats		*/
/*==============================*/

#define NPENDINGS 40

typedef struct {		/* to remember a continuation */
	int	key,
		map;
	char	*tail;
} pending;

int	Npending;
pending	PendTab[NPENDINGS];

void	MakePending __(( int _(key), int _(map), const char *_(tail) ));
void
MakePending(key, map, tail)
int		key, map;
const char	*tail;
{
	register pending *p;

	if (Npending == NPENDINGS) {
		fprintf(stderr,
			"warning: too many pending continuations, max = %d\n",
			NPENDINGS);
		return;
	}
	for (p = &PendTab[0]; p->tail; p++)	/* find empty slot */
		;
	DPRINTF(("[MakePending (%d) map %d, key %d, tail %s]\n", Npending, map, key, tail));
	Npending++;
	p->map = map;
	p->key = key;
	p->tail = copystr(tail);
}

char	*Pending __(( int _(key), int _(map) ));
char *
Pending(key, map)
{
	register pending *p;
	register int	count = Npending;
	char		*tail;

	if (count == 0)
		return NULL;

	for (p = &PendTab[0]; p <= &PendTab[NPENDINGS]; p++) {
		if (tail = p->tail) {
			if (key == p->key && map == p->map) {
				Npending--;
				p->tail = NULL;
				return tail;
			}
			if (--count == 0)
				break;
		}
	}
	return NULL;
}

/*==============================*/
/*	Shortening		*/
/*==============================*/

const char * const ShortTab[] = {
	"backward",	"back",
	"beginning",	"begin",
	"buffer",	"buf",
	"buffers",	"bufs",
	"character",	"char",
	"characters",	"chars",
	"current",	"curr",
	"delete",	"del",
	"error",	"err",
	"execute",	"exec",
	"expression",	"expr",
	"forward",	"forw",
	"keyboard",	"kbd",
	"modified",	"mod",
	"paragraph",	"par",
	"position",	"pos",
	"previous",	"prev",
	"region",	"reg",
	"sentence",	"sent",
	"window",	"wind",
	"windows",	"wind",
	NULL
};

char	ShortBuf[128];

int	Shorten __(( const char *_(name) ));
int
Shorten(name)
const char	*name;
{
	register const char	*s = name,
				* const *tp,
				*te;
	register char		*d = ShortBuf;
	register int		n;

	while (*s) {
		for (tp = ShortTab;  te = *tp++;  tp++) {
			if (*te < *s)
				continue;
			if (*te > *s) {		/* assume alphabetic order */
				te = NULL;
				break;
			}
			if (te[n = numcomp(s, te)])
				continue;	/* exact match? */
			if (!isalnum(s[n]))
				break;		/* match */
		}
		if (te) {			/* match */
			if (te = *tp) {		/* get abbrev */
				while (*d++ = *te++);  --d;
				s += n;
				if (*s)
					*d++ = *s++;
			}
		} else {			/* the original */
			while (isalnum(*s))
				*d++ = *s++;
			if (*s)
				*d++ = *s++;
		}
	}
	*d = '\0';

	return (d - ShortBuf);
}


/*==============================*/
/*	output processing	*/
/*==============================*/

void	Pad __(( char *_(buf), int _(len) ));
void
Pad(buf, len)
register char	*buf;
register int	len;
{
	while (--len >= 0)
		*buf++ = ' ';
}

void	DoFormat __((char *_(buf), int _(key), int _(map),
		     int _(field), int _(just), int _(cont) ));
void
DoFormat(buf, key, map, field, just, cont)
char	*buf;
{
	char		*s;
	register char	*p, *q;
	int		len, padlen;

	if (!(s = Pending(key, map)))
		s = KeyTab[map][key];

	len = strlen(s);
	q = p = s;
	if (cont != '\\')
		q += len;	/* preference for current */

	DPRINTF(("[Initial: (field,len,p,q) (%d,%d,%d,%d) %s]\n", field, len, (int)(p - s), (int)(q - s), s));

	if (len > field) {
		int short_len = Shorten(s); /* try short version first */

		if (short_len < len) {
			len = short_len;
			p = q = s = ShortBuf;
			if (cont != '\\')
				q += len;
		}
		if (len > field && cont) {
			for (;;) {
				while (isalnum(*p))
					p++;
				if ((p - s) >= field)
					break;
				q = p;
				p++;
				if (cont == '\\' && len - (p - s) <= field)
					break;
			}
		}
	}
	len = ((q - s) > field) ? field : (q - s);
	DPRINTF(("[final: (field,len,p,q) (%d,%d,%d,%d) %s]\n", field, len, (int)(p - s), (int)(q - s), s));
	if (*q) {
		if (*q == '-')
			q++;
		if (*q)
			MakePending(key, map, q);
	}
	switch(just) {
	case '>':
		padlen = (field - len);
		break;
	case '-':
		padlen = (field - len) / 2;
		break;
	case '<':
		padlen = 0;
	}
	Pad(buf, padlen);
	strncpy(buf + padlen, s, len);
	Pad(buf + padlen + len, field - padlen - len);

	if (s != ShortBuf && s != KeyTab[map][key])
		free(s);	/* it was Pending, so cleanup */
}

char	*DoHat __(( char *_(base) ));
char *
DoHat(base)
char	*base;
{
	register char	*s = base;
	register int	c;
	register int	key, map;
	int		newmap;

	newmap = 0;
	while((c = *s++) == '^') {
		c = *s++;
		c = toupper(c);
		if (('@' <= c && c < '`') || c == '?')
			key = c ^ '@';
		else if (c == ':')
			key = function_key(s), s += 2;
		else if (c == '`')
			key = *s++;
		else {
			s--;
			break;
		}
		map = newmap;
		if (map >= 0 && !(newmap = ISPREFIX(key, map)))
			newmap = -1;		/* unknown key sequence */
	}
	s--;
	DPRINTF(("[Matched \"%.*s}{%6.6s\" map %d, key %d, newmap %d\n", (int)(s - base), base, s, map, key, newmap));
	if (s > base) {				/* we found a key sequence */
		int	justify;
		int	continuation = 0;
		char	*where = s;		/* remember */

		if (*s == '<' || *s == '-' || *s == '>')
			justify = *s++;
		while (*s++ == justify);  --s;	/* skip fill chars */
		if (*s == '+' || *s == '\\')
			continuation = *s++;
		if (where == s) {		/* at least one directive */
			s = base;
		} else if (map < 0 || !KeyTab[map][key]) {
			Pad(base, (int)(s - base));
		} else {
			DoFormat(base, key, map, (int)(s - base), justify, continuation);
		}
	}
	if (*s == '^')				/* standalone "hat" */
		s++;
	return s;
}

void	gobble_input __(( void ));
void
gobble_input()
{
	register char	*s;

	while (fgets(line, sizeof line, stdin)) {
		if (*(s = line) == '#')
			continue;
		while (*s) {
			if (*s++ == '^')
				s = DoHat(s - 1);
		}
		if (*--s == '\n') {
			while (--s >= line && (*s == ' ' || *s == '\t'))
				;/* remove trailing blanks */
			++s;
			*s++ = '\n';
			*s = '\0';
		}
		fputs(line, stdout);
	}
}

char	inbuf[BUFSIZ], outbuf[BUFSIZ];

void
main(argc, argv)
int	argc;
char	*argv[];
{
	_pname = *argv++;
	setbuf(stdin, inbuf);
	setbuf(stdout, outbuf);
#ifdef DEBUG
	if (argc > 1 && strcmp(*argv, "-d") == 0) {
		debug++;
		argc--, argv++;
	}
#endif
	if (--argc == 0) {
		fprintf(stderr, "usage: %s wall-chart-files <template-file\n", _pname);
		fprintf(stderr, "   or: %s [wall-chart-files] - template-file <wall-chart-file\n", _pname);
		exit(EXIT_FAILURE);
	}
	while (eat_wallchart(*argv++) && --argc) ;
	if (argc) {
		if (--argc == 0) {
			fprintf(stderr, "%s: template file missing.\n", _pname);
			exit(EXIT_FAILURE);
		}
		if (freopen(*argv, "r", stdin) == NULL) {
			fprintf(stderr, "%s: cannot read template file \"%s\"\n", _pname, *argv);
			exit(EXIT_FAILURE);
		}
	}
	DPRINTF(("**** template file \"%s\"\n", (*argv) ? *argv : "(stdin)"));
	fixup_function_keys();
	gobble_input();
	exit(EXIT_SUCCESS);
}

/*======================================================================
 * $Log: keysubst.c,v $
 * Revision 14.30.0.9  1993/12/13  01:57:29  tom
 * (main): fix indirection bug in error message.
 *
 * Revision 14.30  1993/01/26  18:43:18  tom
 * cleanup whitespace.
 *
 * Revision 14.28  1992/10/22  01:20:00  tom
 * convert to "port{ansi,defs}.h" conventions.
 *
 * Revision 14.26  1992/08/26  23:57:07  tom
 * add RCS directives.
 *
 */
