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