Home | History | Annotate | Line # | Download | only in checknr
checknr.c revision 1.3
      1  1.1      cgd /*
      2  1.1      cgd  * Copyright (c) 1980 The Regents of the University of California.
      3  1.1      cgd  * All rights reserved.
      4  1.1      cgd  *
      5  1.1      cgd  * Redistribution and use in source and binary forms, with or without
      6  1.1      cgd  * modification, are permitted provided that the following conditions
      7  1.1      cgd  * are met:
      8  1.1      cgd  * 1. Redistributions of source code must retain the above copyright
      9  1.1      cgd  *    notice, this list of conditions and the following disclaimer.
     10  1.1      cgd  * 2. Redistributions in binary form must reproduce the above copyright
     11  1.1      cgd  *    notice, this list of conditions and the following disclaimer in the
     12  1.1      cgd  *    documentation and/or other materials provided with the distribution.
     13  1.1      cgd  * 3. All advertising materials mentioning features or use of this software
     14  1.1      cgd  *    must display the following acknowledgement:
     15  1.1      cgd  *	This product includes software developed by the University of
     16  1.1      cgd  *	California, Berkeley and its contributors.
     17  1.1      cgd  * 4. Neither the name of the University nor the names of its contributors
     18  1.1      cgd  *    may be used to endorse or promote products derived from this software
     19  1.1      cgd  *    without specific prior written permission.
     20  1.1      cgd  *
     21  1.1      cgd  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
     22  1.1      cgd  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
     23  1.1      cgd  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
     24  1.1      cgd  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
     25  1.1      cgd  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
     26  1.1      cgd  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
     27  1.1      cgd  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
     28  1.1      cgd  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
     29  1.1      cgd  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
     30  1.1      cgd  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
     31  1.1      cgd  * SUCH DAMAGE.
     32  1.1      cgd  */
     33  1.1      cgd 
     34  1.1      cgd #ifndef lint
     35  1.1      cgd char copyright[] =
     36  1.1      cgd "@(#) Copyright (c) 1980 The Regents of the University of California.\n\
     37  1.1      cgd  All rights reserved.\n";
     38  1.1      cgd #endif /* not lint */
     39  1.1      cgd 
     40  1.1      cgd #ifndef lint
     41  1.2  mycroft /*static char sccsid[] = "from: @(#)checknr.c	5.4 (Berkeley) 6/1/90";*/
     42  1.3      cgd static char rcsid[] = "$Id: checknr.c,v 1.3 1994/12/24 15:57:02 cgd Exp $";
     43  1.1      cgd #endif /* not lint */
     44  1.1      cgd 
     45  1.1      cgd /*
     46  1.1      cgd  * checknr: check an nroff/troff input file for matching macro calls.
     47  1.1      cgd  * we also attempt to match size and font changes, but only the embedded
     48  1.1      cgd  * kind.  These must end in \s0 and \fP resp.  Maybe more sophistication
     49  1.1      cgd  * later but for now think of these restrictions as contributions to
     50  1.1      cgd  * structured typesetting.
     51  1.1      cgd  */
     52  1.1      cgd #include <stdio.h>
     53  1.3      cgd #include <string.h>
     54  1.1      cgd #include <ctype.h>
     55  1.1      cgd 
     56  1.1      cgd #define MAXSTK	100	/* Stack size */
     57  1.1      cgd #define MAXBR	100	/* Max number of bracket pairs known */
     58  1.1      cgd #define MAXCMDS	500	/* Max number of commands known */
     59  1.1      cgd 
     60  1.1      cgd /*
     61  1.1      cgd  * The stack on which we remember what we've seen so far.
     62  1.1      cgd  */
     63  1.1      cgd struct stkstr {
     64  1.1      cgd 	int opno;	/* number of opening bracket */
     65  1.1      cgd 	int pl;		/* '+', '-', ' ' for \s, 1 for \f, 0 for .ft */
     66  1.1      cgd 	int parm;	/* parm to size, font, etc */
     67  1.1      cgd 	int lno;	/* line number the thing came in in */
     68  1.1      cgd } stk[MAXSTK];
     69  1.1      cgd int stktop;
     70  1.1      cgd 
     71  1.1      cgd /*
     72  1.1      cgd  * The kinds of opening and closing brackets.
     73  1.1      cgd  */
     74  1.1      cgd struct brstr {
     75  1.1      cgd 	char *opbr;
     76  1.1      cgd 	char *clbr;
     77  1.1      cgd } br[MAXBR] = {
     78  1.1      cgd 	/* A few bare bones troff commands */
     79  1.1      cgd #define SZ	0
     80  1.1      cgd 	"sz",	"sz",	/* also \s */
     81  1.1      cgd #define FT	1
     82  1.1      cgd 	"ft",	"ft",	/* also \f */
     83  1.1      cgd 	/* the -mm package */
     84  1.1      cgd 	"AL",	"LE",
     85  1.1      cgd 	"AS",	"AE",
     86  1.1      cgd 	"BL",	"LE",
     87  1.1      cgd 	"BS",	"BE",
     88  1.1      cgd 	"DF",	"DE",
     89  1.1      cgd 	"DL",	"LE",
     90  1.1      cgd 	"DS",	"DE",
     91  1.1      cgd 	"FS",	"FE",
     92  1.1      cgd 	"ML",	"LE",
     93  1.1      cgd 	"NS",	"NE",
     94  1.1      cgd 	"RL",	"LE",
     95  1.1      cgd 	"VL",	"LE",
     96  1.1      cgd 	/* the -ms package */
     97  1.1      cgd 	"AB",	"AE",
     98  1.1      cgd 	"BD",	"DE",
     99  1.1      cgd 	"CD",	"DE",
    100  1.1      cgd 	"DS",	"DE",
    101  1.1      cgd 	"FS",	"FE",
    102  1.1      cgd 	"ID",	"DE",
    103  1.1      cgd 	"KF",	"KE",
    104  1.1      cgd 	"KS",	"KE",
    105  1.1      cgd 	"LD",	"DE",
    106  1.1      cgd 	"LG",	"NL",
    107  1.1      cgd 	"QS",	"QE",
    108  1.1      cgd 	"RS",	"RE",
    109  1.1      cgd 	"SM",	"NL",
    110  1.1      cgd 	"XA",	"XE",
    111  1.1      cgd 	"XS",	"XE",
    112  1.1      cgd 	/* The -me package */
    113  1.1      cgd 	"(b",	")b",
    114  1.1      cgd 	"(c",	")c",
    115  1.1      cgd 	"(d",	")d",
    116  1.1      cgd 	"(f",	")f",
    117  1.1      cgd 	"(l",	")l",
    118  1.1      cgd 	"(q",	")q",
    119  1.1      cgd 	"(x",	")x",
    120  1.1      cgd 	"(z",	")z",
    121  1.1      cgd 	/* Things needed by preprocessors */
    122  1.1      cgd 	"EQ",	"EN",
    123  1.1      cgd 	"TS",	"TE",
    124  1.1      cgd 	/* Refer */
    125  1.1      cgd 	"[",	"]",
    126  1.1      cgd 	0,	0
    127  1.1      cgd };
    128  1.1      cgd 
    129  1.1      cgd /*
    130  1.1      cgd  * All commands known to nroff, plus macro packages.
    131  1.1      cgd  * Used so we can complain about unrecognized commands.
    132  1.1      cgd  */
    133  1.1      cgd char *knowncmds[MAXCMDS] = {
    134  1.1      cgd "$c", "$f", "$h", "$p", "$s", "(b", "(c", "(d", "(f", "(l", "(q", "(t",
    135  1.1      cgd "(x", "(z", ")b", ")c", ")d", ")f", ")l", ")q", ")t", ")x", ")z", "++",
    136  1.1      cgd "+c", "1C", "1c", "2C", "2c", "@(", "@)", "@C", "@D", "@F", "@I", "@M",
    137  1.1      cgd "@c", "@e", "@f", "@h", "@m", "@n", "@o", "@p", "@r", "@t", "@z", "AB",
    138  1.1      cgd "AE", "AF", "AI", "AL", "AM", "AS", "AT", "AU", "AX", "B",  "B1", "B2",
    139  1.1      cgd "BD", "BE", "BG", "BL", "BS", "BT", "BX", "C1", "C2", "CD", "CM", "CT",
    140  1.1      cgd "D",  "DA", "DE", "DF", "DL", "DS", "DT", "EC", "EF", "EG", "EH", "EM",
    141  1.1      cgd "EN", "EQ", "EX", "FA", "FD", "FE", "FG", "FJ", "FK", "FL", "FN", "FO",
    142  1.1      cgd "FQ", "FS", "FV", "FX", "H",  "HC", "HD", "HM", "HO", "HU", "I",  "ID",
    143  1.1      cgd "IE", "IH", "IM", "IP", "IX", "IZ", "KD", "KE", "KF", "KQ", "KS", "LB",
    144  1.1      cgd "LC", "LD", "LE", "LG", "LI", "LP", "MC", "ME", "MF", "MH", "ML", "MR",
    145  1.1      cgd "MT", "ND", "NE", "NH", "NL", "NP", "NS", "OF", "OH", "OK", "OP", "P",
    146  1.1      cgd "P1", "PF", "PH", "PP", "PT", "PX", "PY", "QE", "QP", "QS", "R",  "RA",
    147  1.1      cgd "RC", "RE", "RL", "RP", "RQ", "RS", "RT", "S",  "S0", "S2", "S3", "SA",
    148  1.1      cgd "SG", "SH", "SK", "SM", "SP", "SY", "T&", "TA", "TB", "TC", "TD", "TE",
    149  1.1      cgd "TH", "TL", "TM", "TP", "TQ", "TR", "TS", "TX", "UL", "US", "UX", "VL",
    150  1.1      cgd "WC", "WH", "XA", "XD", "XE", "XF", "XK", "XP", "XS", "[",  "[-", "[0",
    151  1.1      cgd "[1", "[2", "[3", "[4", "[5", "[<", "[>", "[]", "]",  "]-", "]<", "]>",
    152  1.1      cgd "][", "ab", "ac", "ad", "af", "am", "ar", "as", "b",  "ba", "bc", "bd",
    153  1.1      cgd "bi", "bl", "bp", "br", "bx", "c.", "c2", "cc", "ce", "cf", "ch", "cs",
    154  1.1      cgd "ct", "cu", "da", "de", "di", "dl", "dn", "ds", "dt", "dw", "dy", "ec",
    155  1.1      cgd "ef", "eh", "el", "em", "eo", "ep", "ev", "ex", "fc", "fi", "fl", "fo",
    156  1.1      cgd "fp", "ft", "fz", "hc", "he", "hl", "hp", "ht", "hw", "hx", "hy", "i",
    157  1.1      cgd "ie", "if", "ig", "in", "ip", "it", "ix", "lc", "lg", "li", "ll", "ln",
    158  1.1      cgd "lo", "lp", "ls", "lt", "m1", "m2", "m3", "m4", "mc", "mk", "mo", "n1",
    159  1.1      cgd "n2", "na", "ne", "nf", "nh", "nl", "nm", "nn", "np", "nr", "ns", "nx",
    160  1.1      cgd "of", "oh", "os", "pa", "pc", "pi", "pl", "pm", "pn", "po", "pp", "ps",
    161  1.1      cgd "q",  "r",  "rb", "rd", "re", "rm", "rn", "ro", "rr", "rs", "rt", "sb",
    162  1.1      cgd "sc", "sh", "sk", "so", "sp", "ss", "st", "sv", "sz", "ta", "tc", "th",
    163  1.1      cgd "ti", "tl", "tm", "tp", "tr", "u",  "uf", "uh", "ul", "vs", "wh", "xp",
    164  1.1      cgd "yr", 0
    165  1.1      cgd };
    166  1.1      cgd 
    167  1.1      cgd int	lineno;		/* current line number in input file */
    168  1.1      cgd char	line[256];	/* the current line */
    169  1.1      cgd char	*cfilename;	/* name of current file */
    170  1.1      cgd int	nfiles;		/* number of files to process */
    171  1.1      cgd int	fflag;		/* -f: ignore \f */
    172  1.1      cgd int	sflag;		/* -s: ignore \s */
    173  1.1      cgd int	ncmds;		/* size of knowncmds */
    174  1.1      cgd int	slot;		/* slot in knowncmds found by binsrch */
    175  1.1      cgd 
    176  1.1      cgd char	*malloc();
    177  1.1      cgd 
    178  1.1      cgd main(argc, argv)
    179  1.1      cgd int argc;
    180  1.1      cgd char **argv;
    181  1.1      cgd {
    182  1.1      cgd 	FILE *f;
    183  1.1      cgd 	int i;
    184  1.1      cgd 	char *cp;
    185  1.1      cgd 	char b1[4];
    186  1.1      cgd 
    187  1.1      cgd 	/* Figure out how many known commands there are */
    188  1.1      cgd 	while (knowncmds[ncmds])
    189  1.1      cgd 		ncmds++;
    190  1.1      cgd 	while (argc > 1 && argv[1][0] == '-') {
    191  1.1      cgd 		switch(argv[1][1]) {
    192  1.1      cgd 
    193  1.1      cgd 		/* -a: add pairs of macros */
    194  1.1      cgd 		case 'a':
    195  1.1      cgd 			i = strlen(argv[1]) - 2;
    196  1.1      cgd 			if (i % 6 != 0)
    197  1.1      cgd 				usage();
    198  1.1      cgd 			/* look for empty macro slots */
    199  1.1      cgd 			for (i=0; br[i].opbr; i++)
    200  1.1      cgd 				;
    201  1.1      cgd 			for (cp=argv[1]+3; cp[-1]; cp += 6) {
    202  1.1      cgd 				br[i].opbr = malloc(3);
    203  1.1      cgd 				strncpy(br[i].opbr, cp, 2);
    204  1.1      cgd 				br[i].clbr = malloc(3);
    205  1.1      cgd 				strncpy(br[i].clbr, cp+3, 2);
    206  1.1      cgd 				addmac(br[i].opbr);	/* knows pairs are also known cmds */
    207  1.1      cgd 				addmac(br[i].clbr);
    208  1.1      cgd 				i++;
    209  1.1      cgd 			}
    210  1.1      cgd 			break;
    211  1.1      cgd 
    212  1.1      cgd 		/* -c: add known commands */
    213  1.1      cgd 		case 'c':
    214  1.1      cgd 			i = strlen(argv[1]) - 2;
    215  1.1      cgd 			if (i % 3 != 0)
    216  1.1      cgd 				usage();
    217  1.1      cgd 			for (cp=argv[1]+3; cp[-1]; cp += 3) {
    218  1.1      cgd 				if (cp[2] && cp[2] != '.')
    219  1.1      cgd 					usage();
    220  1.1      cgd 				strncpy(b1, cp, 2);
    221  1.1      cgd 				addmac(b1);
    222  1.1      cgd 			}
    223  1.1      cgd 			break;
    224  1.1      cgd 
    225  1.1      cgd 		/* -f: ignore font changes */
    226  1.1      cgd 		case 'f':
    227  1.1      cgd 			fflag = 1;
    228  1.1      cgd 			break;
    229  1.1      cgd 
    230  1.1      cgd 		/* -s: ignore size changes */
    231  1.1      cgd 		case 's':
    232  1.1      cgd 			sflag = 1;
    233  1.1      cgd 			break;
    234  1.1      cgd 		default:
    235  1.1      cgd 			usage();
    236  1.1      cgd 		}
    237  1.1      cgd 		argc--; argv++;
    238  1.1      cgd 	}
    239  1.1      cgd 
    240  1.1      cgd 	nfiles = argc - 1;
    241  1.1      cgd 
    242  1.1      cgd 	if (nfiles > 0) {
    243  1.1      cgd 		for (i=1; i<argc; i++) {
    244  1.1      cgd 			cfilename = argv[i];
    245  1.1      cgd 			f = fopen(cfilename, "r");
    246  1.1      cgd 			if (f == NULL)
    247  1.1      cgd 				perror(cfilename);
    248  1.1      cgd 			else
    249  1.1      cgd 				process(f);
    250  1.1      cgd 		}
    251  1.1      cgd 	} else {
    252  1.1      cgd 		cfilename = "stdin";
    253  1.1      cgd 		process(stdin);
    254  1.1      cgd 	}
    255  1.1      cgd 	exit(0);
    256  1.1      cgd }
    257  1.1      cgd 
    258  1.1      cgd usage()
    259  1.1      cgd {
    260  1.1      cgd 	printf("Usage: checknr -s -f -a.xx.yy.xx.yy... -c.xx.xx.xx...\n");
    261  1.1      cgd 	exit(1);
    262  1.1      cgd }
    263  1.1      cgd 
    264  1.1      cgd process(f)
    265  1.1      cgd FILE *f;
    266  1.1      cgd {
    267  1.1      cgd 	register int i, n;
    268  1.1      cgd 	char mac[5];	/* The current macro or nroff command */
    269  1.1      cgd 	int pl;
    270  1.1      cgd 
    271  1.1      cgd 	stktop = -1;
    272  1.1      cgd 	for (lineno = 1; fgets(line, sizeof line, f); lineno++) {
    273  1.1      cgd 		if (line[0] == '.') {
    274  1.1      cgd 			/*
    275  1.1      cgd 			 * find and isolate the macro/command name.
    276  1.1      cgd 			 */
    277  1.1      cgd 			strncpy(mac, line+1, 4);
    278  1.1      cgd 			if (isspace(mac[0])) {
    279  1.1      cgd 				pe(lineno);
    280  1.1      cgd 				printf("Empty command\n");
    281  1.1      cgd 			} else if (isspace(mac[1])) {
    282  1.1      cgd 				mac[1] = 0;
    283  1.1      cgd 			} else if (isspace(mac[2])) {
    284  1.1      cgd 				mac[2] = 0;
    285  1.1      cgd 			} else if (mac[0] != '\\' || mac[1] != '\"') {
    286  1.1      cgd 				pe(lineno);
    287  1.1      cgd 				printf("Command too long\n");
    288  1.1      cgd 			}
    289  1.1      cgd 
    290  1.1      cgd 			/*
    291  1.1      cgd 			 * Is it a known command?
    292  1.1      cgd 			 */
    293  1.1      cgd 			checkknown(mac);
    294  1.1      cgd 
    295  1.1      cgd 			/*
    296  1.1      cgd 			 * Should we add it?
    297  1.1      cgd 			 */
    298  1.1      cgd 			if (eq(mac, "de"))
    299  1.1      cgd 				addcmd(line);
    300  1.1      cgd 
    301  1.1      cgd 			chkcmd(line, mac);
    302  1.1      cgd 		}
    303  1.1      cgd 
    304  1.1      cgd 		/*
    305  1.1      cgd 		 * At this point we process the line looking
    306  1.1      cgd 		 * for \s and \f.
    307  1.1      cgd 		 */
    308  1.1      cgd 		for (i=0; line[i]; i++)
    309  1.1      cgd 			if (line[i]=='\\' && (i==0 || line[i-1]!='\\')) {
    310  1.1      cgd 				if (!sflag && line[++i]=='s') {
    311  1.1      cgd 					pl = line[++i];
    312  1.1      cgd 					if (isdigit(pl)) {
    313  1.1      cgd 						n = pl - '0';
    314  1.1      cgd 						pl = ' ';
    315  1.1      cgd 					} else
    316  1.1      cgd 						n = 0;
    317  1.1      cgd 					while (isdigit(line[++i]))
    318  1.1      cgd 						n = 10 * n + line[i] - '0';
    319  1.1      cgd 					i--;
    320  1.1      cgd 					if (n == 0) {
    321  1.1      cgd 						if (stk[stktop].opno == SZ) {
    322  1.1      cgd 							stktop--;
    323  1.1      cgd 						} else {
    324  1.1      cgd 							pe(lineno);
    325  1.1      cgd 							printf("unmatched \\s0\n");
    326  1.1      cgd 						}
    327  1.1      cgd 					} else {
    328  1.1      cgd 						stk[++stktop].opno = SZ;
    329  1.1      cgd 						stk[stktop].pl = pl;
    330  1.1      cgd 						stk[stktop].parm = n;
    331  1.1      cgd 						stk[stktop].lno = lineno;
    332  1.1      cgd 					}
    333  1.1      cgd 				} else if (!fflag && line[i]=='f') {
    334  1.1      cgd 					n = line[++i];
    335  1.1      cgd 					if (n == 'P') {
    336  1.1      cgd 						if (stk[stktop].opno == FT) {
    337  1.1      cgd 							stktop--;
    338  1.1      cgd 						} else {
    339  1.1      cgd 							pe(lineno);
    340  1.1      cgd 							printf("unmatched \\fP\n");
    341  1.1      cgd 						}
    342  1.1      cgd 					} else {
    343  1.1      cgd 						stk[++stktop].opno = FT;
    344  1.1      cgd 						stk[stktop].pl = 1;
    345  1.1      cgd 						stk[stktop].parm = n;
    346  1.1      cgd 						stk[stktop].lno = lineno;
    347  1.1      cgd 					}
    348  1.1      cgd 				}
    349  1.1      cgd 			}
    350  1.1      cgd 	}
    351  1.1      cgd 	/*
    352  1.1      cgd 	 * We've hit the end and look at all this stuff that hasn't been
    353  1.1      cgd 	 * matched yet!  Complain, complain.
    354  1.1      cgd 	 */
    355  1.1      cgd 	for (i=stktop; i>=0; i--) {
    356  1.1      cgd 		complain(i);
    357  1.1      cgd 	}
    358  1.1      cgd }
    359  1.1      cgd 
    360  1.1      cgd complain(i)
    361  1.1      cgd {
    362  1.1      cgd 	pe(stk[i].lno);
    363  1.1      cgd 	printf("Unmatched ");
    364  1.1      cgd 	prop(i);
    365  1.1      cgd 	printf("\n");
    366  1.1      cgd }
    367  1.1      cgd 
    368  1.1      cgd prop(i)
    369  1.1      cgd {
    370  1.1      cgd 	if (stk[i].pl == 0)
    371  1.1      cgd 		printf(".%s", br[stk[i].opno].opbr);
    372  1.1      cgd 	else switch(stk[i].opno) {
    373  1.1      cgd 	case SZ:
    374  1.1      cgd 		printf("\\s%c%d", stk[i].pl, stk[i].parm);
    375  1.1      cgd 		break;
    376  1.1      cgd 	case FT:
    377  1.1      cgd 		printf("\\f%c", stk[i].parm);
    378  1.1      cgd 		break;
    379  1.1      cgd 	default:
    380  1.1      cgd 		printf("Bug: stk[%d].opno = %d = .%s, .%s",
    381  1.1      cgd 			i, stk[i].opno, br[stk[i].opno].opbr, br[stk[i].opno].clbr);
    382  1.1      cgd 	}
    383  1.1      cgd }
    384  1.1      cgd 
    385  1.1      cgd chkcmd(line, mac)
    386  1.1      cgd char *line;
    387  1.1      cgd char *mac;
    388  1.1      cgd {
    389  1.1      cgd 	register int i, n;
    390  1.1      cgd 
    391  1.1      cgd 	/*
    392  1.1      cgd 	 * Check to see if it matches top of stack.
    393  1.1      cgd 	 */
    394  1.1      cgd 	if (stktop >= 0 && eq(mac, br[stk[stktop].opno].clbr))
    395  1.1      cgd 		stktop--;	/* OK. Pop & forget */
    396  1.1      cgd 	else {
    397  1.1      cgd 		/* No. Maybe it's an opener */
    398  1.1      cgd 		for (i=0; br[i].opbr; i++) {
    399  1.1      cgd 			if (eq(mac, br[i].opbr)) {
    400  1.1      cgd 				/* Found. Push it. */
    401  1.1      cgd 				stktop++;
    402  1.1      cgd 				stk[stktop].opno = i;
    403  1.1      cgd 				stk[stktop].pl = 0;
    404  1.1      cgd 				stk[stktop].parm = 0;
    405  1.1      cgd 				stk[stktop].lno = lineno;
    406  1.1      cgd 				break;
    407  1.1      cgd 			}
    408  1.1      cgd 			/*
    409  1.1      cgd 			 * Maybe it's an unmatched closer.
    410  1.1      cgd 			 * NOTE: this depends on the fact
    411  1.1      cgd 			 * that none of the closers can be
    412  1.1      cgd 			 * openers too.
    413  1.1      cgd 			 */
    414  1.1      cgd 			if (eq(mac, br[i].clbr)) {
    415  1.1      cgd 				nomatch(mac);
    416  1.1      cgd 				break;
    417  1.1      cgd 			}
    418  1.1      cgd 		}
    419  1.1      cgd 	}
    420  1.1      cgd }
    421  1.1      cgd 
    422  1.1      cgd nomatch(mac)
    423  1.1      cgd char *mac;
    424  1.1      cgd {
    425  1.1      cgd 	register int i, j;
    426  1.1      cgd 
    427  1.1      cgd 	/*
    428  1.1      cgd 	 * Look for a match further down on stack
    429  1.1      cgd 	 * If we find one, it suggests that the stuff in
    430  1.1      cgd 	 * between is supposed to match itself.
    431  1.1      cgd 	 */
    432  1.1      cgd 	for (j=stktop; j>=0; j--)
    433  1.1      cgd 		if (eq(mac,br[stk[j].opno].clbr)) {
    434  1.1      cgd 			/* Found.  Make a good diagnostic. */
    435  1.1      cgd 			if (j == stktop-2) {
    436  1.1      cgd 				/*
    437  1.1      cgd 				 * Check for special case \fx..\fR and don't
    438  1.1      cgd 				 * complain.
    439  1.1      cgd 				 */
    440  1.1      cgd 				if (stk[j+1].opno==FT && stk[j+1].parm!='R'
    441  1.1      cgd 				 && stk[j+2].opno==FT && stk[j+2].parm=='R') {
    442  1.1      cgd 					stktop = j -1;
    443  1.1      cgd 					return;
    444  1.1      cgd 				}
    445  1.1      cgd 				/*
    446  1.1      cgd 				 * We have two unmatched frobs.  Chances are
    447  1.1      cgd 				 * they were intended to match, so we mention
    448  1.1      cgd 				 * them together.
    449  1.1      cgd 				 */
    450  1.1      cgd 				pe(stk[j+1].lno);
    451  1.1      cgd 				prop(j+1);
    452  1.1      cgd 				printf(" does not match %d: ", stk[j+2].lno);
    453  1.1      cgd 				prop(j+2);
    454  1.1      cgd 				printf("\n");
    455  1.1      cgd 			} else for (i=j+1; i <= stktop; i++) {
    456  1.1      cgd 				complain(i);
    457  1.1      cgd 			}
    458  1.1      cgd 			stktop = j-1;
    459  1.1      cgd 			return;
    460  1.1      cgd 		}
    461  1.1      cgd 	/* Didn't find one.  Throw this away. */
    462  1.1      cgd 	pe(lineno);
    463  1.1      cgd 	printf("Unmatched .%s\n", mac);
    464  1.1      cgd }
    465  1.1      cgd 
    466  1.1      cgd /* eq: are two strings equal? */
    467  1.1      cgd eq(s1, s2)
    468  1.1      cgd char *s1, *s2;
    469  1.1      cgd {
    470  1.1      cgd 	return (strcmp(s1, s2) == 0);
    471  1.1      cgd }
    472  1.1      cgd 
    473  1.1      cgd /* print the first part of an error message, given the line number */
    474  1.1      cgd pe(lineno)
    475  1.1      cgd int lineno;
    476  1.1      cgd {
    477  1.1      cgd 	if (nfiles > 1)
    478  1.1      cgd 		printf("%s: ", cfilename);
    479  1.1      cgd 	printf("%d: ", lineno);
    480  1.1      cgd }
    481  1.1      cgd 
    482  1.1      cgd checkknown(mac)
    483  1.1      cgd char *mac;
    484  1.1      cgd {
    485  1.1      cgd 
    486  1.1      cgd 	if (eq(mac, "."))
    487  1.1      cgd 		return;
    488  1.1      cgd 	if (binsrch(mac) >= 0)
    489  1.1      cgd 		return;
    490  1.1      cgd 	if (mac[0] == '\\' && mac[1] == '"')	/* comments */
    491  1.1      cgd 		return;
    492  1.1      cgd 
    493  1.1      cgd 	pe(lineno);
    494  1.1      cgd 	printf("Unknown command: .%s\n", mac);
    495  1.1      cgd }
    496  1.1      cgd 
    497  1.1      cgd /*
    498  1.1      cgd  * We have a .de xx line in "line".  Add xx to the list of known commands.
    499  1.1      cgd  */
    500  1.1      cgd addcmd(line)
    501  1.1      cgd char *line;
    502  1.1      cgd {
    503  1.1      cgd 	char *mac;
    504  1.1      cgd 
    505  1.1      cgd 	/* grab the macro being defined */
    506  1.1      cgd 	mac = line+4;
    507  1.1      cgd 	while (isspace(*mac))
    508  1.1      cgd 		mac++;
    509  1.1      cgd 	if (*mac == 0) {
    510  1.1      cgd 		pe(lineno);
    511  1.1      cgd 		printf("illegal define: %s\n", line);
    512  1.1      cgd 		return;
    513  1.1      cgd 	}
    514  1.1      cgd 	mac[2] = 0;
    515  1.1      cgd 	if (isspace(mac[1]) || mac[1] == '\\')
    516  1.1      cgd 		mac[1] = 0;
    517  1.1      cgd 	if (ncmds >= MAXCMDS) {
    518  1.1      cgd 		printf("Only %d known commands allowed\n", MAXCMDS);
    519  1.1      cgd 		exit(1);
    520  1.1      cgd 	}
    521  1.1      cgd 	addmac(mac);
    522  1.1      cgd }
    523  1.1      cgd 
    524  1.1      cgd /*
    525  1.1      cgd  * Add mac to the list.  We should really have some kind of tree
    526  1.1      cgd  * structure here but this is a quick-and-dirty job and I just don't
    527  1.1      cgd  * have time to mess with it.  (I wonder if this will come back to haunt
    528  1.1      cgd  * me someday?)  Anyway, I claim that .de is fairly rare in user
    529  1.1      cgd  * nroff programs, and the register loop below is pretty fast.
    530  1.1      cgd  */
    531  1.1      cgd addmac(mac)
    532  1.1      cgd char *mac;
    533  1.1      cgd {
    534  1.1      cgd 	register char **src, **dest, **loc;
    535  1.1      cgd 
    536  1.1      cgd 	if (binsrch(mac) >= 0){	/* it's OK to redefine something */
    537  1.1      cgd #ifdef DEBUG
    538  1.1      cgd 		printf("binsrch(%s) -> already in table\n", mac);
    539  1.1      cgd #endif DEBUG
    540  1.1      cgd 		return;
    541  1.1      cgd 	}
    542  1.1      cgd 	/* binsrch sets slot as a side effect */
    543  1.1      cgd #ifdef DEBUG
    544  1.1      cgd printf("binsrch(%s) -> %d\n", mac, slot);
    545  1.1      cgd #endif
    546  1.1      cgd 	loc = &knowncmds[slot];
    547  1.1      cgd 	src = &knowncmds[ncmds-1];
    548  1.1      cgd 	dest = src+1;
    549  1.1      cgd 	while (dest > loc)
    550  1.1      cgd 		*dest-- = *src--;
    551  1.1      cgd 	*loc = malloc(3);
    552  1.1      cgd 	strcpy(*loc, mac);
    553  1.1      cgd 	ncmds++;
    554  1.1      cgd #ifdef DEBUG
    555  1.1      cgd printf("after: %s %s %s %s %s, %d cmds\n", knowncmds[slot-2], knowncmds[slot-1], knowncmds[slot], knowncmds[slot+1], knowncmds[slot+2], ncmds);
    556  1.1      cgd #endif
    557  1.1      cgd }
    558  1.1      cgd 
    559  1.1      cgd /*
    560  1.1      cgd  * Do a binary search in knowncmds for mac.
    561  1.1      cgd  * If found, return the index.  If not, return -1.
    562  1.1      cgd  */
    563  1.1      cgd binsrch(mac)
    564  1.1      cgd char *mac;
    565  1.1      cgd {
    566  1.1      cgd 	register char *p;	/* pointer to current cmd in list */
    567  1.1      cgd 	register int d;		/* difference if any */
    568  1.1      cgd 	register int mid;	/* mid point in binary search */
    569  1.1      cgd 	register int top, bot;	/* boundaries of bin search, inclusive */
    570  1.1      cgd 
    571  1.1      cgd 	top = ncmds-1;
    572  1.1      cgd 	bot = 0;
    573  1.1      cgd 	while (top >= bot) {
    574  1.1      cgd 		mid = (top+bot)/2;
    575  1.1      cgd 		p = knowncmds[mid];
    576  1.1      cgd 		d = p[0] - mac[0];
    577  1.1      cgd 		if (d == 0)
    578  1.1      cgd 			d = p[1] - mac[1];
    579  1.1      cgd 		if (d == 0)
    580  1.1      cgd 			return mid;
    581  1.1      cgd 		if (d < 0)
    582  1.1      cgd 			bot = mid + 1;
    583  1.1      cgd 		else
    584  1.1      cgd 			top = mid - 1;
    585  1.1      cgd 	}
    586  1.1      cgd 	slot = bot;	/* place it would have gone */
    587  1.1      cgd 	return -1;
    588  1.1      cgd }
    589