xref: /netbsd-src/usr.bin/checknr/checknr.c (revision af153cbd78db98f22949db4c58c342ed0a569ff3)
1*af153cbdSmrg /*	$NetBSD: checknr.c,v 1.25 2021/04/13 01:38:04 mrg Exp $	*/
2e91ed174Sglass 
361f28255Scgd /*
4e91ed174Sglass  * Copyright (c) 1980, 1993
5e91ed174Sglass  *	The Regents of the University of California.  All rights reserved.
661f28255Scgd  *
761f28255Scgd  * Redistribution and use in source and binary forms, with or without
861f28255Scgd  * modification, are permitted provided that the following conditions
961f28255Scgd  * are met:
1061f28255Scgd  * 1. Redistributions of source code must retain the above copyright
1161f28255Scgd  *    notice, this list of conditions and the following disclaimer.
1261f28255Scgd  * 2. Redistributions in binary form must reproduce the above copyright
1361f28255Scgd  *    notice, this list of conditions and the following disclaimer in the
1461f28255Scgd  *    documentation and/or other materials provided with the distribution.
1589aaa1bbSagc  * 3. Neither the name of the University nor the names of its contributors
1661f28255Scgd  *    may be used to endorse or promote products derived from this software
1761f28255Scgd  *    without specific prior written permission.
1861f28255Scgd  *
1961f28255Scgd  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
2061f28255Scgd  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
2161f28255Scgd  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
2261f28255Scgd  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
2361f28255Scgd  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
2461f28255Scgd  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
2561f28255Scgd  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
2661f28255Scgd  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
2761f28255Scgd  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
2861f28255Scgd  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
2961f28255Scgd  * SUCH DAMAGE.
3061f28255Scgd  */
3161f28255Scgd 
325c1befb6Slukem #include <sys/cdefs.h>
3361f28255Scgd #ifndef lint
3498e5374cSlukem __COPYRIGHT("@(#) Copyright (c) 1980, 1993\
3598e5374cSlukem  The Regents of the University of California.  All rights reserved.");
3661f28255Scgd #endif /* not lint */
3761f28255Scgd 
3861f28255Scgd #ifndef lint
39e91ed174Sglass #if 0
40e91ed174Sglass static char sccsid[] = "@(#)checknr.c	8.1 (Berkeley) 6/6/93";
41e91ed174Sglass #else
42*af153cbdSmrg __RCSID("$NetBSD: checknr.c,v 1.25 2021/04/13 01:38:04 mrg Exp $");
43e91ed174Sglass #endif
4461f28255Scgd #endif /* not lint */
4561f28255Scgd 
4661f28255Scgd /*
4761f28255Scgd  * checknr: check an nroff/troff input file for matching macro calls.
4861f28255Scgd  * we also attempt to match size and font changes, but only the embedded
4961f28255Scgd  * kind.  These must end in \s0 and \fP resp.  Maybe more sophistication
5061f28255Scgd  * later but for now think of these restrictions as contributions to
5161f28255Scgd  * structured typesetting.
5261f28255Scgd  */
5361f28255Scgd #include <ctype.h>
545ca972d9Sxtraeme #include <err.h>
555c1befb6Slukem #include <stdio.h>
565c1befb6Slukem #include <stdlib.h>
575c1befb6Slukem #include <string.h>
5861f28255Scgd 
5961f28255Scgd #define MAXSTK	100	/* Stack size */
6061f28255Scgd #define MAXBR	100	/* Max number of bracket pairs known */
6161f28255Scgd #define MAXCMDS	500	/* Max number of commands known */
6261f28255Scgd 
6361f28255Scgd /*
6461f28255Scgd  * The stack on which we remember what we've seen so far.
6561f28255Scgd  */
6606eef689Sdholland static struct stkstr {
6761f28255Scgd 	int opno;	/* number of opening bracket */
6861f28255Scgd 	int pl;		/* '+', '-', ' ' for \s, 1 for \f, 0 for .ft */
6961f28255Scgd 	int parm;	/* parm to size, font, etc */
7061f28255Scgd 	int lno;	/* line number the thing came in in */
7161f28255Scgd } stk[MAXSTK];
7206eef689Sdholland static int stktop;
7361f28255Scgd 
7461f28255Scgd /*
7561f28255Scgd  * The kinds of opening and closing brackets.
7661f28255Scgd  */
7706eef689Sdholland static struct brstr {
78beced5eaSdholland 	const char *opbr;
79beced5eaSdholland 	const char *clbr;
8061f28255Scgd } br[MAXBR] = {
8161f28255Scgd 	/* A few bare bones troff commands */
8261f28255Scgd #define SZ	0
835c1befb6Slukem 	{ "sz",	"sz"},	/* also \s */
8461f28255Scgd #define FT	1
855c1befb6Slukem 	{ "ft",	"ft"},	/* also \f */
8661f28255Scgd 	/* the -mm package */
875c1befb6Slukem 	{"AL",	"LE"},
885c1befb6Slukem 	{"AS",	"AE"},
895c1befb6Slukem 	{"BL",	"LE"},
905c1befb6Slukem 	{"BS",	"BE"},
915c1befb6Slukem 	{"DF",	"DE"},
925c1befb6Slukem 	{"DL",	"LE"},
935c1befb6Slukem 	{"DS",	"DE"},
945c1befb6Slukem 	{"FS",	"FE"},
955c1befb6Slukem 	{"ML",	"LE"},
965c1befb6Slukem 	{"NS",	"NE"},
975c1befb6Slukem 	{"RL",	"LE"},
985c1befb6Slukem 	{"VL",	"LE"},
9961f28255Scgd 	/* the -ms package */
1005c1befb6Slukem 	{"AB",	"AE"},
1015c1befb6Slukem 	{"BD",	"DE"},
1025c1befb6Slukem 	{"CD",	"DE"},
1035c1befb6Slukem 	{"DS",	"DE"},
1045c1befb6Slukem 	{"FS",	"FE"},
1055c1befb6Slukem 	{"ID",	"DE"},
1065c1befb6Slukem 	{"KF",	"KE"},
1075c1befb6Slukem 	{"KS",	"KE"},
1085c1befb6Slukem 	{"LD",	"DE"},
1095c1befb6Slukem 	{"LG",	"NL"},
1105c1befb6Slukem 	{"QS",	"QE"},
1115c1befb6Slukem 	{"RS",	"RE"},
1125c1befb6Slukem 	{"SM",	"NL"},
1135c1befb6Slukem 	{"XA",	"XE"},
1145c1befb6Slukem 	{"XS",	"XE"},
11561f28255Scgd 	/* The -me package */
1165c1befb6Slukem 	{"(b",	")b"},
1175c1befb6Slukem 	{"(c",	")c"},
1185c1befb6Slukem 	{"(d",	")d"},
1195c1befb6Slukem 	{"(f",	")f"},
1205c1befb6Slukem 	{"(l",	")l"},
1215c1befb6Slukem 	{"(q",	")q"},
1225c1befb6Slukem 	{"(x",	")x"},
1235c1befb6Slukem 	{"(z",	")z"},
1249cf6e5c4Swiz 	/* The -mdoc package */
1259cf6e5c4Swiz 	{"Ao",  "Ac"},
1269cf6e5c4Swiz 	{"Bd",  "Ed"},
1279cf6e5c4Swiz 	{"Bk",  "Ek"},
1289cf6e5c4Swiz 	{"Bo",  "Bc"},
1299cf6e5c4Swiz 	{"Do",  "Dc"},
1309cf6e5c4Swiz 	{"Fo",  "Fc"},
1319cf6e5c4Swiz 	{"Oo",  "Oc"},
1329cf6e5c4Swiz 	{"Po",  "Pc"},
1339cf6e5c4Swiz 	{"Qo",  "Qc"},
1349cf6e5c4Swiz 	{"Rs",  "Re"},
1359cf6e5c4Swiz 	{"So",  "Sc"},
1369cf6e5c4Swiz 	{"Xo",  "Xc"},
13761f28255Scgd 	/* Things needed by preprocessors */
1385c1befb6Slukem 	{"EQ",	"EN"},
1395c1befb6Slukem 	{"TS",	"TE"},
14061f28255Scgd 	/* Refer */
1415c1befb6Slukem 	{"[",	"]"},
142bd02b4a3Swiz 	{0,	0}
14361f28255Scgd };
14461f28255Scgd 
14561f28255Scgd /*
14661f28255Scgd  * All commands known to nroff, plus macro packages.
14761f28255Scgd  * Used so we can complain about unrecognized commands.
14861f28255Scgd  */
14906eef689Sdholland static const char *knowncmds[MAXCMDS] = {
1509cf6e5c4Swiz "$c", "$f", "$h", "$p", "$s", "%A", "%B", "%C", "%D", "%I", "%J", "%N",
1519cf6e5c4Swiz "%O", "%P", "%Q", "%R", "%T", "%V", "(b", "(c", "(d", "(f", "(l", "(q",
1529cf6e5c4Swiz "(t", "(x", "(z", ")b", ")c", ")d", ")f", ")l", ")q", ")t", ")x",
1539cf6e5c4Swiz ")z", "++", "+c", "1C", "1c", "2C", "2c", "@(", "@)", "@C", "@D",
1549cf6e5c4Swiz "@F", "@I", "@M", "@c", "@e", "@f", "@h", "@m", "@n", "@o", "@p",
1559cf6e5c4Swiz "@r", "@t", "@z", "AB", "AE", "AF", "AI", "AL", "AM", "AS", "AT",
1569cf6e5c4Swiz "AU", "AX", "Ac", "Ad", "An", "Ao", "Ap", "Aq", "Ar", "At", "B" ,  "B1",
1579cf6e5c4Swiz "B2", "BD", "BE", "BG", "BL", "BS", "BT", "BX", "Bc", "Bd", "Bf",
1589cf6e5c4Swiz "Bk", "Bl", "Bo", "Bq", "Bsx", "Bx", "C1", "C2", "CD", "CM", "CT",
1599cf6e5c4Swiz "Cd", "Cm", "D" , "D1", "DA", "DE", "DF", "DL", "DS", "DT", "Db", "Dc",
1609cf6e5c4Swiz "Dd", "Dl", "Do", "Dq", "Dt", "Dv", "EC", "EF", "EG", "EH", "EM",
1619cf6e5c4Swiz "EN", "EQ", "EX", "Ec", "Ed", "Ef", "Ek", "El", "Em", "Eo", "Er",
1629cf6e5c4Swiz "Ev", "FA", "FD", "FE", "FG", "FJ", "FK", "FL", "FN", "FO", "FQ",
1639cf6e5c4Swiz "FS", "FV", "FX", "Fa", "Fc", "Fd", "Fl", "Fn", "Fo", "Ft", "Fx",
1649cf6e5c4Swiz "H" , "HC", "HD", "HM", "HO", "HU", "I" , "ID", "IE", "IH", "IM",
165ec34356aSwiz "IP", "IX", "IZ", "Ic", "In", "It", "KD", "KE", "KF", "KQ", "KS", "LB",
1669cf6e5c4Swiz "LC", "LD", "LE", "LG", "LI", "LP", "Lb", "Li", "MC", "ME", "MF",
1679cf6e5c4Swiz "MH", "ML", "MR", "MT", "ND", "NE", "NH", "NL", "NP", "NS", "Nd",
1689cf6e5c4Swiz "Nm", "No", "Ns", "Nx", "OF", "OH", "OK", "OP", "Oc", "Oo", "Op",
1699cf6e5c4Swiz "Os", "Ot", "Ox", "P" , "P1", "PF", "PH", "PP", "PT", "PX", "PY",
1709cf6e5c4Swiz "Pa", "Pc", "Pf", "Po", "Pp", "Pq", "QE", "QP", "QS", "Qc", "Ql",
1719cf6e5c4Swiz "Qo", "Qq", "R" , "RA", "RC", "RE", "RL", "RP", "RQ", "RS", "RT",
1729cf6e5c4Swiz "Re", "Rs", "S" , "S0", "S2", "S3", "SA", "SG", "SH", "SK", "SM",
1739cf6e5c4Swiz "SP", "SY", "Sc", "Sh", "Sm", "So", "Sq", "Ss", "St", "Sx", "Sy",
1749cf6e5c4Swiz "T&", "TA", "TB", "TC", "TD", "TE", "TH", "TL", "TM", "TP", "TQ",
1759cf6e5c4Swiz "TR", "TS", "TX", "Tn", "UL", "US", "UX", "Ud", "Ux", "VL", "Va", "Vt",
1769cf6e5c4Swiz "WC", "WH", "XA", "XD", "XE", "XF", "XK", "XP", "XS", "Xc", "Xo",
1779cf6e5c4Swiz "Xr", "[" , "[-", "[0", "[1", "[2", "[3", "[4", "[5", "[<", "[>",
1789cf6e5c4Swiz "[]", "\\{", "\\}", "]" , "]-", "]<", "]>", "][", "ab", "ac", "ad", "af", "am",
1799cf6e5c4Swiz "ar", "as", "b" , "ba", "bc", "bd", "bi", "bl", "bp", "br", "bx",
1809cf6e5c4Swiz "c.", "c2", "cc", "ce", "cf", "ch", "cs", "ct", "cu", "da", "de",
1819cf6e5c4Swiz "di", "dl", "dn", "ds", "dt", "dw", "dy", "ec", "ef", "eh", "el",
1829cf6e5c4Swiz "em", "eo", "ep", "ev", "ex", "fc", "fi", "fl", "fo", "fp", "ft",
1839cf6e5c4Swiz "fz", "hc", "he", "hl", "hp", "ht", "hw", "hx", "hy", "i" , "ie",
1849cf6e5c4Swiz "if", "ig", "in", "ip", "it", "ix", "lc", "lg", "li", "ll", "ln",
1859cf6e5c4Swiz "lo", "lp", "ls", "lt", "m1", "m2", "m3", "m4", "mc", "mk", "mo",
1869cf6e5c4Swiz "n1", "n2", "na", "ne", "nf", "nh", "nl", "nm", "nn", "np", "nr",
1879cf6e5c4Swiz "ns", "nx", "of", "oh", "os", "pa", "pc", "pi", "pl", "pm", "pn",
1889cf6e5c4Swiz "po", "pp", "ps", "q" , "r" , "rb", "rd", "re", "rm", "rn", "ro",
1899cf6e5c4Swiz "rr", "rs", "rt", "sb", "sc", "sh", "sk", "so", "sp", "ss", "st",
1909cf6e5c4Swiz "sv", "sz", "ta", "tc", "th", "ti", "tl", "tm", "tp", "tr", "u",
1919cf6e5c4Swiz "uf", "uh", "ul", "vs", "wh", "xp", "yr", 0
19261f28255Scgd };
19361f28255Scgd 
19406eef689Sdholland static int lineno;		/* current line number in input file */
19506eef689Sdholland static const char *cfilename;	/* name of current file */
19606eef689Sdholland static int nfiles;		/* number of files to process */
19706eef689Sdholland static int fflag;		/* -f: ignore \f */
19806eef689Sdholland static int sflag;		/* -s: ignore \s */
19906eef689Sdholland static int ncmds;		/* size of knowncmds */
20006eef689Sdholland static int slot;		/* slot in knowncmds found by binsrch */
20161f28255Scgd 
20206eef689Sdholland static void addcmd(char *);
20306eef689Sdholland static void addmac(const char *);
20406eef689Sdholland static int binsrch(const char *);
20506eef689Sdholland static void checkknown(const char *);
20606eef689Sdholland static void chkcmd(const char *);
20706eef689Sdholland static void complain(int);
208beced5eaSdholland static int eq(const char *, const char *);
20906eef689Sdholland static void nomatch(const char *);
21006eef689Sdholland static void pe(int);
21106eef689Sdholland static void process(FILE *);
21206eef689Sdholland static void prop(int);
2132c7fa373Sjoerg static void usage(void) __dead;
21461f28255Scgd 
2155c1befb6Slukem int
main(int argc,char ** argv)216105896cdSwiz main(int argc, char **argv)
21761f28255Scgd {
21861f28255Scgd 	FILE *f;
21961f28255Scgd 	int i;
22061f28255Scgd 	char *cp;
22161f28255Scgd 	char b1[4];
22261f28255Scgd 
22361f28255Scgd 	/* Figure out how many known commands there are */
22461f28255Scgd 	while (knowncmds[ncmds])
22561f28255Scgd 		ncmds++;
22661f28255Scgd 	while (argc > 1 && argv[1][0] == '-') {
22761f28255Scgd 		switch(argv[1][1]) {
22861f28255Scgd 
22961f28255Scgd 		/* -a: add pairs of macros */
23061f28255Scgd 		case 'a':
23161f28255Scgd 			i = strlen(argv[1]) - 2;
23261f28255Scgd 			if (i % 6 != 0)
23361f28255Scgd 				usage();
23461f28255Scgd 			/* look for empty macro slots */
23561f28255Scgd 			for (i=0; br[i].opbr; i++)
23661f28255Scgd 				;
23761f28255Scgd 			for (cp=argv[1]+3; cp[-1]; cp += 6) {
238beced5eaSdholland 				char *tmp;
239beced5eaSdholland 
240bd02b4a3Swiz 				if (i >= MAXBR)
241bd02b4a3Swiz 					errx(1, "too many pairs");
242beced5eaSdholland 				if ((tmp = malloc(3)) == NULL)
243bd02b4a3Swiz 					err(1, "malloc");
244beced5eaSdholland 				strlcpy(tmp, cp, 3);
245beced5eaSdholland 				br[i].opbr = tmp;
246beced5eaSdholland 				if ((tmp = malloc(3)) == NULL)
247bd02b4a3Swiz 					err(1, "malloc");
248beced5eaSdholland 				strlcpy(tmp, cp+3, 3);
249beced5eaSdholland 				br[i].clbr = tmp;
25061f28255Scgd 				addmac(br[i].opbr);	/* knows pairs are also known cmds */
25161f28255Scgd 				addmac(br[i].clbr);
25261f28255Scgd 				i++;
25361f28255Scgd 			}
25461f28255Scgd 			break;
25561f28255Scgd 
25661f28255Scgd 		/* -c: add known commands */
25761f28255Scgd 		case 'c':
25861f28255Scgd 			i = strlen(argv[1]) - 2;
25961f28255Scgd 			if (i % 3 != 0)
26061f28255Scgd 				usage();
26161f28255Scgd 			for (cp=argv[1]+3; cp[-1]; cp += 3) {
26261f28255Scgd 				if (cp[2] && cp[2] != '.')
26361f28255Scgd 					usage();
26461f28255Scgd 				strncpy(b1, cp, 2);
26561f28255Scgd 				addmac(b1);
26661f28255Scgd 			}
26761f28255Scgd 			break;
26861f28255Scgd 
26961f28255Scgd 		/* -f: ignore font changes */
27061f28255Scgd 		case 'f':
27161f28255Scgd 			fflag = 1;
27261f28255Scgd 			break;
27361f28255Scgd 
27461f28255Scgd 		/* -s: ignore size changes */
27561f28255Scgd 		case 's':
27661f28255Scgd 			sflag = 1;
27761f28255Scgd 			break;
27861f28255Scgd 		default:
27961f28255Scgd 			usage();
28061f28255Scgd 		}
28161f28255Scgd 		argc--; argv++;
28261f28255Scgd 	}
28361f28255Scgd 
28461f28255Scgd 	nfiles = argc - 1;
28561f28255Scgd 
28661f28255Scgd 	if (nfiles > 0) {
28761f28255Scgd 		for (i=1; i<argc; i++) {
28861f28255Scgd 			cfilename = argv[i];
28961f28255Scgd 			f = fopen(cfilename, "r");
29061f28255Scgd 			if (f == NULL)
29161f28255Scgd 				perror(cfilename);
292de2f2d87Swiz 			else {
29361f28255Scgd 				process(f);
2949cf6e5c4Swiz 				fclose(f);
29561f28255Scgd 			}
296de2f2d87Swiz 		}
29761f28255Scgd 	} else {
29861f28255Scgd 		cfilename = "stdin";
29961f28255Scgd 		process(stdin);
30061f28255Scgd 	}
30161f28255Scgd 	exit(0);
30261f28255Scgd }
30361f28255Scgd 
30406eef689Sdholland static void
usage(void)305105896cdSwiz usage(void)
30661f28255Scgd {
30755759098Swiz 	(void)fprintf(stderr,
30855759098Swiz 	    "usage: %s [-fs] [-a.xx.yy.xx.yy...] [-c.xx.xx.xx...] file\n",
30955759098Swiz 	    getprogname());
31061f28255Scgd 	exit(1);
31161f28255Scgd }
31261f28255Scgd 
31306eef689Sdholland static void
process(FILE * f)314105896cdSwiz process(FILE *f)
31561f28255Scgd {
3165c1befb6Slukem 	int i, n;
3171bbe14d8Swiz 	char line[256];	/* the current line */
31861f28255Scgd 	char mac[5];	/* The current macro or nroff command */
31961f28255Scgd 	int pl;
32061f28255Scgd 
32161f28255Scgd 	stktop = -1;
32261f28255Scgd 	for (lineno = 1; fgets(line, sizeof line, f); lineno++) {
32361f28255Scgd 		if (line[0] == '.') {
32461f28255Scgd 			/*
32561f28255Scgd 			 * find and isolate the macro/command name.
32661f28255Scgd 			 */
32761f28255Scgd 			strncpy(mac, line+1, 4);
328*af153cbdSmrg 			mac[4] = '\0';
329bd5b624cSchristos 			if (isspace((unsigned char)mac[0])) {
33061f28255Scgd 				pe(lineno);
33161f28255Scgd 				printf("Empty command\n");
332bd5b624cSchristos 			} else if (isspace((unsigned char)mac[1])) {
33361f28255Scgd 				mac[1] = 0;
334bd5b624cSchristos 			} else if (isspace((unsigned char)mac[2])) {
33561f28255Scgd 				mac[2] = 0;
33661f28255Scgd 			} else if (mac[0] != '\\' || mac[1] != '\"') {
33761f28255Scgd 				pe(lineno);
33861f28255Scgd 				printf("Command too long\n");
33961f28255Scgd 			}
34061f28255Scgd 
34161f28255Scgd 			/*
34261f28255Scgd 			 * Is it a known command?
34361f28255Scgd 			 */
34461f28255Scgd 			checkknown(mac);
34561f28255Scgd 
34661f28255Scgd 			/*
34761f28255Scgd 			 * Should we add it?
34861f28255Scgd 			 */
34961f28255Scgd 			if (eq(mac, "de"))
35061f28255Scgd 				addcmd(line);
35161f28255Scgd 
352043c758bSdholland 			chkcmd(mac);
35361f28255Scgd 		}
35461f28255Scgd 
35561f28255Scgd 		/*
35661f28255Scgd 		 * At this point we process the line looking
35761f28255Scgd 		 * for \s and \f.
35861f28255Scgd 		 */
35961f28255Scgd 		for (i=0; line[i]; i++)
36061f28255Scgd 			if (line[i]=='\\' && (i==0 || line[i-1]!='\\')) {
36161f28255Scgd 				if (!sflag && line[++i]=='s') {
36261f28255Scgd 					pl = line[++i];
363bd5b624cSchristos 					if (isdigit((unsigned char)pl)) {
36461f28255Scgd 						n = pl - '0';
36561f28255Scgd 						pl = ' ';
36661f28255Scgd 					} else
36761f28255Scgd 						n = 0;
368bd5b624cSchristos 					while (isdigit((unsigned char)line[++i]))
36961f28255Scgd 						n = 10 * n + line[i] - '0';
37061f28255Scgd 					i--;
37161f28255Scgd 					if (n == 0) {
372095e4da2Schristos 						if (stktop >= 0 &&
373095e4da2Schristos 						    stk[stktop].opno == SZ) {
37461f28255Scgd 							stktop--;
37561f28255Scgd 						} else {
37661f28255Scgd 							pe(lineno);
37761f28255Scgd 							printf("unmatched \\s0\n");
37861f28255Scgd 						}
37961f28255Scgd 					} else {
38061f28255Scgd 						stk[++stktop].opno = SZ;
38161f28255Scgd 						stk[stktop].pl = pl;
38261f28255Scgd 						stk[stktop].parm = n;
38361f28255Scgd 						stk[stktop].lno = lineno;
38461f28255Scgd 					}
38561f28255Scgd 				} else if (!fflag && line[i]=='f') {
38661f28255Scgd 					n = line[++i];
38761f28255Scgd 					if (n == 'P') {
3884d5030b7Schristos 						if (stktop >= 0 &&
3894d5030b7Schristos 						    stk[stktop].opno == FT) {
39061f28255Scgd 							stktop--;
39161f28255Scgd 						} else {
39261f28255Scgd 							pe(lineno);
39361f28255Scgd 							printf("unmatched \\fP\n");
39461f28255Scgd 						}
39561f28255Scgd 					} else {
39661f28255Scgd 						stk[++stktop].opno = FT;
39761f28255Scgd 						stk[stktop].pl = 1;
39861f28255Scgd 						stk[stktop].parm = n;
39961f28255Scgd 						stk[stktop].lno = lineno;
40061f28255Scgd 					}
40161f28255Scgd 				}
40261f28255Scgd 			}
40361f28255Scgd 	}
40461f28255Scgd 	/*
40561f28255Scgd 	 * We've hit the end and look at all this stuff that hasn't been
40661f28255Scgd 	 * matched yet!  Complain, complain.
40761f28255Scgd 	 */
40861f28255Scgd 	for (i=stktop; i>=0; i--) {
40961f28255Scgd 		complain(i);
41061f28255Scgd 	}
41161f28255Scgd }
41261f28255Scgd 
41306eef689Sdholland static void
complain(int i)414105896cdSwiz complain(int i)
41561f28255Scgd {
41661f28255Scgd 	pe(stk[i].lno);
41761f28255Scgd 	printf("Unmatched ");
41861f28255Scgd 	prop(i);
41961f28255Scgd 	printf("\n");
42061f28255Scgd }
42161f28255Scgd 
42206eef689Sdholland static void
prop(int i)423105896cdSwiz prop(int i)
42461f28255Scgd {
42561f28255Scgd 	if (stk[i].pl == 0)
42661f28255Scgd 		printf(".%s", br[stk[i].opno].opbr);
42761f28255Scgd 	else switch(stk[i].opno) {
42861f28255Scgd 	case SZ:
42961f28255Scgd 		printf("\\s%c%d", stk[i].pl, stk[i].parm);
43061f28255Scgd 		break;
43161f28255Scgd 	case FT:
43261f28255Scgd 		printf("\\f%c", stk[i].parm);
43361f28255Scgd 		break;
43461f28255Scgd 	default:
43561f28255Scgd 		printf("Bug: stk[%d].opno = %d = .%s, .%s",
4365c1befb6Slukem 			i, stk[i].opno, br[stk[i].opno].opbr,
4375c1befb6Slukem 			br[stk[i].opno].clbr);
43861f28255Scgd 	}
43961f28255Scgd }
44061f28255Scgd 
44106eef689Sdholland static void
chkcmd(const char * mac)442043c758bSdholland chkcmd(const char *mac)
44361f28255Scgd {
4445c1befb6Slukem 	int i;
44561f28255Scgd 
44661f28255Scgd 	/*
44761f28255Scgd 	 * Check to see if it matches top of stack.
44861f28255Scgd 	 */
44961f28255Scgd 	if (stktop >= 0 && eq(mac, br[stk[stktop].opno].clbr))
45061f28255Scgd 		stktop--;	/* OK. Pop & forget */
45161f28255Scgd 	else {
45261f28255Scgd 		/* No. Maybe it's an opener */
45361f28255Scgd 		for (i=0; br[i].opbr; i++) {
45461f28255Scgd 			if (eq(mac, br[i].opbr)) {
45561f28255Scgd 				/* Found. Push it. */
45661f28255Scgd 				stktop++;
45761f28255Scgd 				stk[stktop].opno = i;
45861f28255Scgd 				stk[stktop].pl = 0;
45961f28255Scgd 				stk[stktop].parm = 0;
46061f28255Scgd 				stk[stktop].lno = lineno;
46161f28255Scgd 				break;
46261f28255Scgd 			}
46361f28255Scgd 			/*
46461f28255Scgd 			 * Maybe it's an unmatched closer.
46561f28255Scgd 			 * NOTE: this depends on the fact
46661f28255Scgd 			 * that none of the closers can be
46761f28255Scgd 			 * openers too.
46861f28255Scgd 			 */
46961f28255Scgd 			if (eq(mac, br[i].clbr)) {
47061f28255Scgd 				nomatch(mac);
47161f28255Scgd 				break;
47261f28255Scgd 			}
47361f28255Scgd 		}
47461f28255Scgd 	}
47561f28255Scgd }
47661f28255Scgd 
47706eef689Sdholland static void
nomatch(const char * mac)478043c758bSdholland nomatch(const char *mac)
47961f28255Scgd {
4805c1befb6Slukem 	int i, j;
48161f28255Scgd 
48261f28255Scgd 	/*
48361f28255Scgd 	 * Look for a match further down on stack
48461f28255Scgd 	 * If we find one, it suggests that the stuff in
48561f28255Scgd 	 * between is supposed to match itself.
48661f28255Scgd 	 */
48761f28255Scgd 	for (j=stktop; j>=0; j--)
48861f28255Scgd 		if (eq(mac,br[stk[j].opno].clbr)) {
48961f28255Scgd 			/* Found.  Make a good diagnostic. */
49061f28255Scgd 			if (j == stktop-2) {
49161f28255Scgd 				/*
49261f28255Scgd 				 * Check for special case \fx..\fR and don't
49361f28255Scgd 				 * complain.
49461f28255Scgd 				 */
49561f28255Scgd 				if (stk[j+1].opno==FT && stk[j+1].parm!='R'
49661f28255Scgd 				 && stk[j+2].opno==FT && stk[j+2].parm=='R') {
49761f28255Scgd 					stktop = j -1;
49861f28255Scgd 					return;
49961f28255Scgd 				}
50061f28255Scgd 				/*
50161f28255Scgd 				 * We have two unmatched frobs.  Chances are
50261f28255Scgd 				 * they were intended to match, so we mention
50361f28255Scgd 				 * them together.
50461f28255Scgd 				 */
50561f28255Scgd 				pe(stk[j+1].lno);
50661f28255Scgd 				prop(j+1);
50761f28255Scgd 				printf(" does not match %d: ", stk[j+2].lno);
50861f28255Scgd 				prop(j+2);
50961f28255Scgd 				printf("\n");
51061f28255Scgd 			} else for (i=j+1; i <= stktop; i++) {
51161f28255Scgd 				complain(i);
51261f28255Scgd 			}
51361f28255Scgd 			stktop = j-1;
51461f28255Scgd 			return;
51561f28255Scgd 		}
51661f28255Scgd 	/* Didn't find one.  Throw this away. */
51761f28255Scgd 	pe(lineno);
51861f28255Scgd 	printf("Unmatched .%s\n", mac);
51961f28255Scgd }
52061f28255Scgd 
52161f28255Scgd /* eq: are two strings equal? */
522beced5eaSdholland static int
eq(const char * s1,const char * s2)523beced5eaSdholland eq(const char *s1, const char *s2)
52461f28255Scgd {
525beced5eaSdholland 	return strcmp(s1, s2) == 0;
52661f28255Scgd }
52761f28255Scgd 
52861f28255Scgd /* print the first part of an error message, given the line number */
52906eef689Sdholland static void
pe(int pelineno)5301bbe14d8Swiz pe(int pelineno)
53161f28255Scgd {
53261f28255Scgd 	if (nfiles > 1)
53361f28255Scgd 		printf("%s: ", cfilename);
5341bbe14d8Swiz 	printf("%d: ", pelineno);
53561f28255Scgd }
53661f28255Scgd 
53706eef689Sdholland static void
checkknown(const char * mac)538043c758bSdholland checkknown(const char *mac)
53961f28255Scgd {
54061f28255Scgd 
54161f28255Scgd 	if (eq(mac, "."))
54261f28255Scgd 		return;
54361f28255Scgd 	if (binsrch(mac) >= 0)
54461f28255Scgd 		return;
54561f28255Scgd 	if (mac[0] == '\\' && mac[1] == '"')	/* comments */
54661f28255Scgd 		return;
54761f28255Scgd 
54861f28255Scgd 	pe(lineno);
54961f28255Scgd 	printf("Unknown command: .%s\n", mac);
55061f28255Scgd }
55161f28255Scgd 
55261f28255Scgd /*
55361f28255Scgd  * We have a .de xx line in "line".  Add xx to the list of known commands.
55461f28255Scgd  */
55506eef689Sdholland static void
addcmd(char * line)556105896cdSwiz addcmd(char *line)
55761f28255Scgd {
55861f28255Scgd 	char *mac;
55961f28255Scgd 
56061f28255Scgd 	/* grab the macro being defined */
56161f28255Scgd 	mac = line+4;
562bd5b624cSchristos 	while (isspace((unsigned char)*mac))
56361f28255Scgd 		mac++;
56461f28255Scgd 	if (*mac == 0) {
56561f28255Scgd 		pe(lineno);
56661f28255Scgd 		printf("illegal define: %s\n", line);
56761f28255Scgd 		return;
56861f28255Scgd 	}
56961f28255Scgd 	mac[2] = 0;
570bd5b624cSchristos 	if (isspace((unsigned char)mac[1]) || mac[1] == '\\')
57161f28255Scgd 		mac[1] = 0;
57261f28255Scgd 	if (ncmds >= MAXCMDS) {
57361f28255Scgd 		printf("Only %d known commands allowed\n", MAXCMDS);
57461f28255Scgd 		exit(1);
57561f28255Scgd 	}
57661f28255Scgd 	addmac(mac);
57761f28255Scgd }
57861f28255Scgd 
57961f28255Scgd /*
58061f28255Scgd  * Add mac to the list.  We should really have some kind of tree
58161f28255Scgd  * structure here but this is a quick-and-dirty job and I just don't
58261f28255Scgd  * have time to mess with it.  (I wonder if this will come back to haunt
58361f28255Scgd  * me someday?)  Anyway, I claim that .de is fairly rare in user
58461f28255Scgd  * nroff programs, and the register loop below is pretty fast.
58561f28255Scgd  */
58606eef689Sdholland static void
addmac(const char * mac)587beced5eaSdholland addmac(const char *mac)
58861f28255Scgd {
589beced5eaSdholland 	const char **src, **dest, **loc;
59061f28255Scgd 
59161f28255Scgd 	if (binsrch(mac) >= 0){	/* it's OK to redefine something */
59261f28255Scgd #ifdef DEBUG
59361f28255Scgd 		printf("binsrch(%s) -> already in table\n", mac);
594d594ce93Scgd #endif /* DEBUG */
59561f28255Scgd 		return;
59661f28255Scgd 	}
59761f28255Scgd 	/* binsrch sets slot as a side effect */
59861f28255Scgd #ifdef DEBUG
59961f28255Scgd 	printf("binsrch(%s) -> %d\n", mac, slot);
60061f28255Scgd #endif
60161f28255Scgd 	loc = &knowncmds[slot];
60261f28255Scgd 	src = &knowncmds[ncmds-1];
60361f28255Scgd 	dest = src+1;
60461f28255Scgd 	while (dest > loc)
60561f28255Scgd 		*dest-- = *src--;
606bd02b4a3Swiz 	if ((*loc = strdup(mac)) == NULL)
607bd02b4a3Swiz 		err(1, "strdup");
60861f28255Scgd 	ncmds++;
60961f28255Scgd #ifdef DEBUG
6101bbe14d8Swiz 	printf("after: %s %s %s %s %s, %d cmds\n", knowncmds[slot-2],
6111bbe14d8Swiz 	    knowncmds[slot-1], knowncmds[slot], knowncmds[slot+1],
6121bbe14d8Swiz 	    knowncmds[slot+2], ncmds);
61361f28255Scgd #endif
61461f28255Scgd }
61561f28255Scgd 
61661f28255Scgd /*
61761f28255Scgd  * Do a binary search in knowncmds for mac.
61861f28255Scgd  * If found, return the index.  If not, return -1.
61961f28255Scgd  */
62006eef689Sdholland static int
binsrch(const char * mac)621beced5eaSdholland binsrch(const char *mac)
62261f28255Scgd {
623beced5eaSdholland 	const char *p;	/* pointer to current cmd in list */
6245c1befb6Slukem 	int d;		/* difference if any */
6255c1befb6Slukem 	int mid;	/* mid point in binary search */
6265c1befb6Slukem 	int top, bot;	/* boundaries of bin search, inclusive */
62761f28255Scgd 
62861f28255Scgd 	top = ncmds-1;
62961f28255Scgd 	bot = 0;
63061f28255Scgd 	while (top >= bot) {
63161f28255Scgd 		mid = (top+bot)/2;
63261f28255Scgd 		p = knowncmds[mid];
63361f28255Scgd 		d = p[0] - mac[0];
63461f28255Scgd 		if (d == 0)
63561f28255Scgd 			d = p[1] - mac[1];
63661f28255Scgd 		if (d == 0)
63761f28255Scgd 			return mid;
63861f28255Scgd 		if (d < 0)
63961f28255Scgd 			bot = mid + 1;
64061f28255Scgd 		else
64161f28255Scgd 			top = mid - 1;
64261f28255Scgd 	}
64361f28255Scgd 	slot = bot;	/* place it would have gone */
64461f28255Scgd 	return -1;
64561f28255Scgd }
646