V10/ncurses/screen/tic.c

Compare this file to the similar file:
Show the results in this format:

/*	@(#) tic.c: 1.1 10/15/83	(1.15	2/23/83)	*/

#ifdef pdp11
	/* Has to be this small to fit, even split I/D, with several use='s */
# define CAPSIZ	2048
#else
# define CAPSIZ	4096
#endif

#define MAXHOP	32	/* max number of use= indirections */

#define COMMENT	'#'
#define SEPARATE	','
#define NUMBER	'#'
#define STRING	'='
#define CANCEL	'@'

#include <stdio.h>
#include <ctype.h>
#include <sys/types.h>
#include <sys/stat.h>

#include "../local/uparm.h"
#ifndef E_TERMINFO
#define E_TERMINFO "terminfo.src"
#endif

#ifndef termpath
#define termpath(name) "/usr/lib/terminfo/name"
#endif

/*
 * L_ctermid is only defined on USG.
 * We use it here since we don't include curses.h.
 */
#ifdef L_ctermid
#define index strchr
#endif

/*
 * compile: program to compile a source terminfo file into object files
 */

static	char *tbuf;
static	int hopcount;	/* detect infinite loops in terminfo, init 0 */
long	starttime;
int	verbose;
char	*terminfo;
char	*tskip();
char	*tgetstr();
char	*tdecode();
char	*getenv();

char *sourcefile = E_TERMINFO;

main(argc, argv)
char **argv;
{
	int i;

	time(&starttime);
	while (argc > 1 && argv[1][0] == '-') {
		switch(argv[1][1]) {
		case 'v':
			if (argv[1][2])
				verbose = argv[1][2] - '0';
			else
				verbose++;
			break;
		default:
			fprintf(stderr, "Usage: compile [-v] [files...]\n");
			exit(1);
		}
		argc--; argv++;
	}

	terminfo = getenv("TERMINFO");

	if (argc == 1) {
		compfile(stdin, "stdin");
	}
	else for (i=1; i<argc; i++) {
		compfile(fopen(argv[i], "r"), argv[i]);
	}
}

/*
 * Compile a file.  This is very similar to the
 * code in tgetstr but it passes through the whole file.
 */
compfile(tf, fname)
	FILE *tf;
	char *fname;
{
	register char *cp;
	register int c;
	register int i = 0, cnt = 0;
	char bp[CAPSIZ];
	char ibuf[CAPSIZ];
	char *cp2;

	if (tf == NULL) {
		perror(fname);
		return (-1);
	}
	ibuf[0] = 0;
	for (;;) {
		tbuf = bp;
		strcpy(bp, ibuf);
		for (;;) {
			if (fgets(ibuf, sizeof ibuf, tf) == NULL) {
				fclose(tf);
				if (tnchkuse(fname))
					store(bp);
				return 0;
			}
			/* comment or blank line */
			if (ibuf[0] == COMMENT || ibuf[0] == '\n')
				continue;
			cp = &ibuf[strlen(ibuf)-3];
			/* Allow and ignore old style backslashes */
			if (*cp == SEPARATE && cp[1] == '\\')
				cp[1] = 0;
			cp[2] = 0;	/* get rid of newline */
			/* lines with leading white space are continuation */
			if (!isspace(ibuf[0]) && *bp)
				break;
			if (strlen(bp) + strlen(ibuf) >= CAPSIZ) {
				fprintf(stdout, "Terminfo entry too long:\n");
				fprintf(stdout, "%s", bp);
			}
			else {
				cp = ibuf;
				while (isspace(*cp))
					cp++;
				strcat(bp, cp);
			}
		}

		/*
		 * We have it, now do something with it.
		 */
		if (tnchkuse(fname))
			store(bp);
	}
}

/*
 * Get an entry for terminal name in buffer bp,
 * from the terminfo file.  Parse is very rudimentary;
 * we just notice escaped newlines.
 */
tgetent(bp, name, fname)
	char *bp, *name, *fname;
{
	register char *cp;
	register int c;
	register int i = 0, cnt = 0;
	char ibuf[CAPSIZ];
	char *cp2;
	FILE *tf;

	ibuf[0] = 0;
	tf = fopen(fname, "r");
	if (tf == NULL)
		return (-1);
	tbuf = bp;
	for (;;) {
		strcpy(bp, ibuf);
		for (;;) {
			if (fgets(ibuf, sizeof ibuf, tf) == NULL) {
				fclose(tf);
				if (tnamatch(name))
					return(tnchkuse(fname));
				return 0;
			}
			if (ibuf[0] == COMMENT) /* comment */
				continue;
			cp = &ibuf[strlen(ibuf)-3];
			/* Allow and ignore old style backslashes */
			if (*cp == SEPARATE && cp[1] == '\\')
				cp[1] = 0;
			cp[2] = 0;	/* get rid of newline */
			/* lines with leading white space are continuation */
			if (!isspace(ibuf[0]) && *bp)
				break;
			if (strlen(bp) + strlen(ibuf) >= CAPSIZ) {
				fprintf(stdout, "Terminfo entry too long:\n");
				fprintf(stdout, "%s", bp);
			}
			else {
				cp = ibuf;
				while (isspace(*cp))
					cp++;
				strcat(bp, cp);
			}
		}

		/*
		 * The real work for the match.
		 */
		if (tnamatch(name)) {
			fclose(tf);
			return(tnchkuse(fname));
		}
	}
}

/*
 * tnchkuse: check the last entry, see if it's use=xxx. If so,
 * recursively find xxx and append that entry (minus the names)
 * to take the place of the use=xxx entry. This allows terminfo
 * entries to say "like an HP2621 but doesn't turn on the labels".
 * Note that this works because of the left to right scan.
 */
tnchkuse(fname)
char *fname;
{
	register char *p, *q;
	char tcname[16];	/* name of similar terminal */
	char tcbuf[CAPSIZ];
	char restbuf[CAPSIZ];
	char *holdtbuf = tbuf;
	char *beg_use, *beg_next;
	char *strchr();
	int l;

	p = tbuf;
	if (++hopcount > MAXHOP) {
		fprintf(stdout, "Infinite use= loop '%s'\n", tbuf);
		return (0);
	}
	for (;;) {
		p = strchr(p, 'u');
		if (p == NULL) {
			tbuf = holdtbuf;
			return 1;
		}
		beg_use = p;
		if (*++p != 's' || *++p != 'e' || *++p != '=')
			continue;
		strncpy(tcname, ++p, sizeof tcname);
		q = strchr(tcname, SEPARATE);
		if (q)
			*q = 0;
		/* try local file ... */
		if (tgetent(tcbuf, tcname, fname) != 1)	{
			/* ... and master */
			if (tgetent(tcbuf, tcname, E_TERMINFO) != 1) {
				printf("Cannot find term %s\n", tcname);
				return(0);
			}
		}

		/* Find the end of the use= spec */
		for(beg_next=beg_use;
		    *beg_next && *beg_next!=SEPARATE;
		    beg_next++)
			;
		beg_next++;
		while (isspace(*beg_next++))
			;
		beg_next--;
		
		/* Now shuffle string around. */
		strcpy(restbuf, beg_next);
		p = strchr(tcbuf, SEPARATE);
		if (p == NULL)
			p = tcbuf;
		else
			p++;
		strcpy(beg_use, p);
		p = strchr(beg_use, '\0');
		strcpy(p, restbuf);
	}
}

/*
 * Tnamatch deals with name matching.  The first field of the terminfo
 * entry is a sequence of names separated by |'s, so we compare
 * against each such name.  The normal : terminator after the last
 * name (before the first field) stops us.
 */
tnamatch(np)
	char *np;
{
	register char *Np, *Bp;

/* printf("tnamatch, np '%s', tbuf '%s'\n", np, tbuf); */
	Bp = tbuf;
	if (*Bp == COMMENT)
		return(0);
	for (;;) {
		for (Np = np; *Np && *Bp == *Np; Bp++, Np++)
			;
		if (*Np == 0 && (*Bp == '|' || *Bp == SEPARATE || *Bp == 0))
			return (1);
		while (*Bp && *Bp != SEPARATE && *Bp != '|')
			Bp++;
		if (*Bp == 0 || *Bp == SEPARATE)
			return (0);
		Bp++;
	}
}

/*
 * Skip to the next SEPARATE delimited field.
 */
static char *
tskip(bp)
	register char *bp;
{

	while (*bp && *bp != SEPARATE)
		bp++;
	if (*bp == 0)
		return bp;
	bp++;
	while (isspace(*bp) || *bp == SEPARATE)
		bp++;
	return (bp);
}

/*
 * Return the (numeric) option id.
 * Numeric options look like
 *	li#80
 * i.e. the option string is separated from the numeric value by
 * a # character.  If the option is not found we return -1.
 * Note that we handle octal numbers beginning with 0.
 */
tgetnum(id)
	char *id;
{
	register int i, base;
	register char *bp = tbuf;
	int idl = strlen(id);
	int sign = 1;

	for (;;) {
		bp = tskip(bp);
		if (*bp == 0)
			return (-1);
		if (strncmp(id, bp, idl))
			continue;
		bp += idl;
		if (*bp == CANCEL)
			return(-1);
		if (*bp != NUMBER && *bp != STRING)
			continue;
		bp++;
		if (*bp == '-') {
			sign = -1;
			bp++;
		}
		base = 10;
		if (*bp == '0')
			base = 8;
		i = 0;
		while (isdigit(*bp))
			i *= base, i += *bp++ - '0';
		i *= sign;
		return (i);
	}
}

/*
 * Handle a flag option.
 * Flag options are given "naked", i.e. followed by a : or the end
 * of the buffer.  Return 1 if we find the option, or 0 if it is
 * not given.
 */
tgetflag(id)
	char *id;
{
	register char *bp = tbuf;
	int idl = strlen(id);

	for (;;) {
		bp = tskip(bp);
		if (!*bp)
			return (0);
		if (strncmp(bp, id, idl) == 0) {
			bp += idl;
			if (!*bp || *bp == SEPARATE)
				return (1);
			else if (*bp == CANCEL)
				return(0);
		}
	}
}

/*
 * Get a string valued option.
 * These are given as
 *	cl=^Z
 * Much decoding is done on the strings, and the strings are
 * placed in area, which is a ref parameter which is updated.
 * No checking on area overflow.
 */
char *
tgetstr(id, area)
	char *id, **area;
{
	register char *bp = tbuf;
	int idl = strlen(id);

	for (;;) {
		bp = tskip(bp);
		if (!*bp)
			return (0);
		if (strncmp(id, bp, idl))
			continue;
		bp += idl;
		if (*bp == CANCEL)
			return(0);
		if (*bp != STRING)
			continue;
		bp++;
		return (tdecode(bp, area));
	}
}

/*
 * Tdecode does the grung work to decode the
 * string capability escapes.
 */
static char *
tdecode(str, area)
	register char *str;
	char **area;
{
	register char *cp;
	register int c;
	register char *dp;
	int i;

	cp = *area;
	while ((c = *str++) && c != SEPARATE) {
		switch (c) {

		case '^':
			c = *str++ & 037;
			break;

		case '\\':
			/*
			 * \x escapes understood:
			 *	\e	escape
			 *	\E	escape
			 *	\^	^
			 *	\\	\
			 *	\,	,
			 *	\:	:
			 *	\l	linefeed
			 *	\n	newline (=linefeed)
			 *	\r	return
			 *	\t	tab
			 *	\b	backspace
			 *	\f	formfeed
			 *	\s	space
			 *	\0	null
			 *	\###	octal ###
			 */
			dp = "e\033E\033^^\\\\,,::l\012n\nr\rt\tb\bf\fs ";
			c = *str++;
nextc:
			if (*dp++ == c) {
				c = *dp++;
				break;
			}
			dp++;
			if (*dp)
				goto nextc;
			if (isdigit(c)) {
				c -= '0', i = 2;
				do {
					if (!isdigit(*str))
						break;
					c <<= 3;
					c |= *str++ - '0';
				} while (--i);
				if (c == 0)
					c = 0200;	/* don't term. str. */
			}
			break;
		}
		*cp++ = c;
	}
	*cp++ = 0;
	str = *area;
	*area = cp;
	return (str);
}

extern char *boolnames[], *numnames[], *strnames[];
extern char *boolcodes[], *numcodes[], *strcodes[];

char *malloc();
char *tgetstr();

#define TIMAGNUM 0432

store(cap)
char *cap;
{
	register char *cp;
	register int i;
	register char **pp, **np;
	char tcpbuf[1024];
	char *tcp = tcpbuf;
	char *tname = cap;
	char *tnp;
	char tnbuf[256], names[256];
	char fnbuf[64], lnbuf[64];
	char strtab[4096];
	register char *strtabptr;
	FILE *fd;
	int sname, sbool;

	while (*cap != SEPARATE)	/* skip over names */
		cap++;
	*cap = 0;
	strcpy(tnbuf, tname);
	strcpy(names, tname);
	*cap = SEPARATE;

	for (tnp=tnbuf; *tnp && *tnp != '|' && *tnp != SEPARATE; tnp++)
		;
	if (*tnp)
		*tnp++ = 0;
	if (terminfo) {
		strcpy(fnbuf, terminfo);
		strcat(fnbuf, "/");
	} else {
		strcpy(fnbuf, termpath(/));
	}
	strcat(fnbuf, tnbuf);
	checkon(fnbuf);
	if (verbose)
		printf("create '%s'\n", fnbuf);
	fd = fopen(fnbuf, "w");
	if (fd == NULL) {
		perror(fnbuf);
		return;
	}

	putsh(TIMAGNUM, fd);
	sname = strlen(names)+1;
	putsh(sname, fd);
	sbool = listlen(boolcodes);
	putsh(sbool, fd);
	putsh(listlen(numcodes), fd);
	putsh(listlen(strcodes), fd);
	putsh(0, fd);			/* length of string table */

	/* Write out various terminal names to file, null terminated. */
	for (cp=names; *cp; cp++)
		putc(*cp, fd);
	putc(0, fd);

	/* Write out the booleans: flag */
	for (pp=boolnames, np=boolcodes; *np; pp++,np++) {
		i = tgetflag(*pp);
		putc(i, fd);
		if (verbose > 2)
			printf("bool cap %s code %s val %d\n", *pp, *np, i);
	}
	if ((sname + sbool) & 1)
		putc(0, fd);

	/* Numbers: highbyte, lowbyte.  0377,0377 means -1 (missing) */
	for (pp=numnames, np=numcodes;   *np; pp++,np++) {
		i = tgetnum(*pp);
		putsh(i, fd);
		if (verbose > 1)
			printf("num cap %s code %s val %d\n", *pp, *np, i);
	}

	/* Strings: offset into string table.  If cap is missing, -1 is used */
	strtabptr = strtab;
	for (pp=strnames, np=strcodes;   *np; pp++,np++) {
		cp = tgetstr(*pp, &tcp);
		if (verbose > 3)
			if (cp)
				printf("str %s code %s val %s\n", *pp, *np, cp);
			else
				printf("str %s code %s val NULL\n", *pp, *np);
		if (cp) {
			putsh(strtabptr-strtab, fd);
			while (*strtabptr++ = *cp++)
				;
		} else {
			putsh(-1, fd);
		}
	}
	fwrite(strtab, 1, strtabptr-strtab, fd);
	fseek(fd, 10L, 0);	/* Back to string table size in header */
	putsh(strtabptr-strtab, fd);
	fclose(fd);
	hopcount = 0;

	while (*tnp) {
		i = 0;
		for (tname=tnp; *tnp && *tnp != '|' && *tnp != SEPARATE; tnp++)
			if (isspace(*tnp))
				i = 1;
		if (*tnp)
			*tnp++ = 0;
		if (i)
			continue;
		if (terminfo) {
			strcpy(lnbuf, terminfo);
			strcat(lnbuf, "/");
		} else {
			strcpy(lnbuf, termpath(/));
		}
		strcat(lnbuf, tname);
		checkon(lnbuf);
		link(fnbuf, lnbuf);
		if (verbose)
			printf("link '%s' '%s'\n", fnbuf, lnbuf);
	}
}

/*
 * Write a short out to the file in machine-independent format.
 */
putsh(val, fd)
register val;
FILE *fd;
{
	if (val != -1) {
		putc(val&0377, fd);
		putc((val>>8)&0377, fd);
	} else {
		/* Write -1 as two 0377's. */
		putc(0377, fd);
		putc(0377, fd);
	}
}

listlen(list)
register char **list;
{
	register int rv = 0;

	while (*list) {
		list++;
		rv++;
	}
	return rv;
}

/*
 * Do various processing on a file name we are about to create.
 * If it already exists, and it's older than we started, unlink it.
 * Also insert a / after the 2nd char of the tail, and make sure
 * that directory exists.
 */
checkon(fn)
char *fn;
{
	struct stat stbuf;
	char *fp, *cp;
	char nbuf[64];
	char cmdbuf[64];

	/* Find last / */
	for (cp=fn; *cp; cp++)
		if (*cp == '/')
			fp = cp;
	if (cp-fp > 2) {
		cp = fp+2;
		strcpy(nbuf, fp+1);
		*cp = 0;
		if (stat(fn, &stbuf) < 0) {
			sprintf(cmdbuf, "mkdir %s", fn);
			if (verbose)
				printf("%s\n", cmdbuf);
			system(cmdbuf);
		}
		*cp++ = '/';
		strcpy(cp, nbuf);
	}
	else
		printf("%s: terminal name too short\n", fp+1);
	if (stat(fn, &stbuf) < 0)
		return;
	if (stbuf.st_mtime < starttime) {
		if (verbose > 1)
			printf("unlink %s\n", fn);
		unlink(fn);
	}
}