checknr.c revision 282438
11541Srgrimes/*
21541Srgrimes * Copyright (c) 1980, 1993
31541Srgrimes *	The Regents of the University of California.  All rights reserved.
41541Srgrimes *
51541Srgrimes * Redistribution and use in source and binary forms, with or without
61541Srgrimes * modification, are permitted provided that the following conditions
71541Srgrimes * are met:
81541Srgrimes * 1. Redistributions of source code must retain the above copyright
91541Srgrimes *    notice, this list of conditions and the following disclaimer.
101541Srgrimes * 2. Redistributions in binary form must reproduce the above copyright
111541Srgrimes *    notice, this list of conditions and the following disclaimer in the
121541Srgrimes *    documentation and/or other materials provided with the distribution.
131541Srgrimes * 4. Neither the name of the University nor the names of its contributors
141541Srgrimes *    may be used to endorse or promote products derived from this software
151541Srgrimes *    without specific prior written permission.
161541Srgrimes *
171541Srgrimes * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
181541Srgrimes * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
191541Srgrimes * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
201541Srgrimes * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
211541Srgrimes * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
221541Srgrimes * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
231541Srgrimes * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
241541Srgrimes * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
251541Srgrimes * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
261541Srgrimes * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
271541Srgrimes * SUCH DAMAGE.
281541Srgrimes */
291541Srgrimes
301541Srgrimes#ifndef lint
311541Srgrimesstatic const char copyright[] =
321541Srgrimes"@(#) Copyright (c) 1980, 1993\n\
3322521Sdyson	The Regents of the University of California.  All rights reserved.\n";
3429353Speter#endif /* not lint */
351541Srgrimes
3622521Sdyson#if 0
3722521Sdyson#ifndef lint
3822521Sdysonstatic char sccsid[] = "@(#)checknr.c	8.1 (Berkeley) 6/6/93";
3922521Sdyson#endif /* not lint */
4022521Sdyson#endif
4122521Sdyson
4222521Sdyson#include <sys/cdefs.h>
4322521Sdyson__FBSDID("$FreeBSD: head/usr.bin/checknr/checknr.c 282438 2015-05-04 22:18:58Z bapt $");
4422521Sdyson
4522521Sdyson/*
4622521Sdyson * checknr: check an nroff/troff input file for matching macro calls.
4722521Sdyson * we also attempt to match size and font changes, but only the embedded
4822521Sdyson * kind.  These must end in \s0 and \fP resp.  Maybe more sophistication
4922521Sdyson * later but for now think of these restrictions as contributions to
5022521Sdyson * structured typesetting.
5122521Sdyson */
5222521Sdyson#include <err.h>
5322521Sdyson#include <stdio.h>
5422521Sdyson#include <stdlib.h>
5522521Sdyson#include <string.h>
5622521Sdyson#include <ctype.h>
5722521Sdyson
5822521Sdyson#define MAXSTK	100	/* Stack size */
5922521Sdyson#define MAXBR	100	/* Max number of bracket pairs known */
6022521Sdyson#define MAXCMDS	500	/* Max number of commands known */
611541Srgrimes
621541Srgrimesstatic void addcmd(char *);
631541Srgrimesstatic void addmac(const char *);
641541Srgrimesstatic int binsrch(const char *);
651541Srgrimesstatic void checkknown(const char *);
661541Srgrimesstatic void chkcmd(const char *, const char *);
6722521Sdysonstatic void complain(int);
6828732Sphkstatic int eq(const char *, const char *);
6928732Sphkstatic void nomatch(const char *);
7028732Sphkstatic void pe(int);
7128732Sphkstatic void process(FILE *);
7228732Sphkstatic void prop(int);
7328732Sphkstatic void usage(void);
7428732Sphk
7528732Sphk/*
7628732Sphk * The stack on which we remember what we've seen so far.
7728732Sphk */
7828732Sphkstatic struct stkstr {
7928732Sphk	int opno;	/* number of opening bracket */
8022521Sdyson	int pl;		/* '+', '-', ' ' for \s, 1 for \f, 0 for .ft */
8122521Sdyson	int parm;	/* parm to size, font, etc */
8222521Sdyson	int lno;	/* line number */
831541Srgrimes} stk[MAXSTK];
841541Srgrimesstatic int stktop;
851541Srgrimes
861541Srgrimes/*
871541Srgrimes * The kinds of opening and closing brackets.
881541Srgrimes */
891541Srgrimesstatic struct brstr {
9022521Sdyson	const char *opbr;
9122521Sdyson	const char *clbr;
9222521Sdyson} br[MAXBR] = {
9322521Sdyson	/* A few bare bones troff commands */
9422521Sdyson#define SZ	0
9522521Sdyson	{"sz",	"sz"},	/* also \s */
9622521Sdyson#define FT	1
9722521Sdyson	{"ft",	"ft"},	/* also \f */
9822521Sdyson	/* the -mm package */
9922521Sdyson	{"AL",	"LE"},
10022521Sdyson	{"AS",	"AE"},
10122521Sdyson	{"BL",	"LE"},
10222521Sdyson	{"BS",	"BE"},
10322521Sdyson	{"DF",	"DE"},
10422521Sdyson	{"DL",	"LE"},
1051541Srgrimes	{"DS",	"DE"},
1061541Srgrimes	{"FS",	"FE"},
1071541Srgrimes	{"ML",	"LE"},
1081541Srgrimes	{"NS",	"NE"},
1091541Srgrimes	{"RL",	"LE"},
1101541Srgrimes	{"VL",	"LE"},
1111541Srgrimes	/* the -ms package */
11222521Sdyson	{"AB",	"AE"},
11322521Sdyson	{"BD",	"DE"},
11422521Sdyson	{"CD",	"DE"},
1151541Srgrimes	{"DS",	"DE"},
1161541Srgrimes	{"FS",	"FE"},
1171541Srgrimes	{"ID",	"DE"},
1181541Srgrimes	{"KF",	"KE"},
1191541Srgrimes	{"KS",	"KE"},
1201541Srgrimes	{"LD",	"DE"},
1211541Srgrimes	{"LG",	"NL"},
12222521Sdyson	{"QS",	"QE"},
12322521Sdyson	{"RS",	"RE"},
12422521Sdyson	{"SM",	"NL"},
1251541Srgrimes	{"XA",	"XE"},
1261541Srgrimes	{"XS",	"XE"},
1271541Srgrimes	/* The -me package */
1281541Srgrimes	{"(b",	")b"},
1291541Srgrimes	{"(c",	")c"},
1301541Srgrimes	{"(d",	")d"},
1311541Srgrimes	{"(f",	")f"},
13222521Sdyson	{"(l",	")l"},
13322521Sdyson	{"(q",	")q"},
13422521Sdyson	{"(x",	")x"},
1351541Srgrimes	{"(z",	")z"},
1361541Srgrimes	/* Things needed by preprocessors */
1371541Srgrimes	{"EQ",	"EN"},
1381541Srgrimes	{"TS",	"TE"},
1391541Srgrimes	/* Refer */
1401541Srgrimes	{"[",	"]"},
1411541Srgrimes	{0,	0}
14222521Sdyson};
14322521Sdyson
14422521Sdyson/*
1451541Srgrimes * All commands known to nroff, plus macro packages.
1461541Srgrimes * Used so we can complain about unrecognized commands.
1471541Srgrimes */
1481541Srgrimesstatic const char *knowncmds[MAXCMDS] = {
1491541Srgrimes"$c", "$f", "$h", "$p", "$s", "(b", "(c", "(d", "(f", "(l", "(q", "(t",
1501541Srgrimes"(x", "(z", ")b", ")c", ")d", ")f", ")l", ")q", ")t", ")x", ")z", "++",
1511541Srgrimes"+c", "1C", "1c", "2C", "2c", "@(", "@)", "@C", "@D", "@F", "@I", "@M",
15222521Sdyson"@c", "@e", "@f", "@h", "@m", "@n", "@o", "@p", "@r", "@t", "@z", "AB",
15322521Sdyson"AE", "AF", "AI", "AL", "AM", "AS", "AT", "AU", "AX", "B",  "B1", "B2",
15422521Sdyson"BD", "BE", "BG", "BL", "BS", "BT", "BX", "C1", "C2", "CD", "CM", "CT",
1551541Srgrimes"D",  "DA", "DE", "DF", "DL", "DS", "DT", "EC", "EF", "EG", "EH", "EM",
1561541Srgrimes"EN", "EQ", "EX", "FA", "FD", "FE", "FG", "FJ", "FK", "FL", "FN", "FO",
1571541Srgrimes"FQ", "FS", "FV", "FX", "H",  "HC", "HD", "HM", "HO", "HU", "I",  "ID",
1581541Srgrimes"IE", "IH", "IM", "IP", "IX", "IZ", "KD", "KE", "KF", "KQ", "KS", "LB",
1591541Srgrimes"LC", "LD", "LE", "LG", "LI", "LP", "MC", "ME", "MF", "MH", "ML", "MR",
1601541Srgrimes"MT", "ND", "NE", "NH", "NL", "NP", "NS", "OF", "OH", "OK", "OP", "P",
1611541Srgrimes"P1", "PF", "PH", "PP", "PT", "PX", "PY", "QE", "QP", "QS", "R",  "RA",
16222521Sdyson"RC", "RE", "RL", "RP", "RQ", "RS", "RT", "S",  "S0", "S2", "S3", "SA",
16322521Sdyson"SG", "SH", "SK", "SM", "SP", "SY", "T&", "TA", "TB", "TC", "TD", "TE",
16422521Sdyson"TH", "TL", "TM", "TP", "TQ", "TR", "TS", "TX", "UL", "US", "UX", "VL",
1651541Srgrimes"WC", "WH", "XA", "XD", "XE", "XF", "XK", "XP", "XS", "[",  "[-", "[0",
1661541Srgrimes"[1", "[2", "[3", "[4", "[5", "[<", "[>", "[]", "]",  "]-", "]<", "]>",
1671541Srgrimes"][", "ab", "ac", "ad", "af", "am", "ar", "as", "b",  "ba", "bc", "bd",
1681541Srgrimes"bi", "bl", "bp", "br", "bx", "c.", "c2", "cc", "ce", "cf", "ch",
1691541Srgrimes"chop", "cs", "ct", "cu", "da", "de", "di", "dl", "dn", "do", "ds",
1701541Srgrimes"dt", "dw", "dy", "ec", "ef", "eh", "el", "em", "eo", "ep", "ev",
1711541Srgrimes"evc", "ex", "fallback", "fc", "feature", "fi", "fl", "flig", "fo",
17222521Sdyson"fp", "ft", "ftr", "fz", "fzoom", "hc", "he", "hidechar", "hl", "hp",
17322521Sdyson"ht", "hw", "hx", "hy", "hylang", "i", "ie", "if", "ig", "in", "ip",
17422521Sdyson"it", "ix", "kern", "kernafter", "kernbefore", "kernpair", "lc", "lg",
1751541Srgrimes"lhang", "lc_ctype", "li", "ll", "ln", "lo", "lp", "ls", "lt", "m1",
1761541Srgrimes"m2", "m3", "m4", "mc", "mk", "mo", "n1", "n2", "na", "ne", "nf", "nh",
1771541Srgrimes"nl", "nm", "nn", "np", "nr", "ns", "nx", "of", "oh", "os", "pa",
1781541Srgrimes"papersize", "pc", "pi", "pl", "pm", "pn", "po", "pp", "ps", "q",
1791541Srgrimes"r",  "rb", "rd", "re", "recursionlimit", "return", "rhang", "rm",
1801541Srgrimes"rn", "ro", "rr", "rs", "rt", "sb", "sc", "sh", "shift", "sk", "so",
1811541Srgrimes"sp", "ss", "st", "sv", "sz", "ta", "tc", "th", "ti", "tl", "tm", "tp",
18222521Sdyson"tr", "track", "u",  "uf", "uh", "ul", "vs", "wh", "xflag", "xp", "yr",
18322521Sdyson0
18422521Sdyson};
18522521Sdyson
18622521Sdysonstatic int	lineno;		/* current line number in input file */
18722521Sdysonstatic const char *cfilename;	/* name of current file */
18822521Sdysonstatic int	nfiles;		/* number of files to process */
18922521Sdysonstatic int	fflag;		/* -f: ignore \f */
19022521Sdysonstatic int	sflag;		/* -s: ignore \s */
19122521Sdysonstatic int	ncmds;		/* size of knowncmds */
19222521Sdysonstatic int	slot;		/* slot in knowncmds found by binsrch */
19322521Sdyson
19422521Sdysonint
1951541Srgrimesmain(int argc, char **argv)
1961541Srgrimes{
19722521Sdyson	FILE *f;
1981541Srgrimes	int i;
1991541Srgrimes	char *cp;
2001541Srgrimes	char b1[4];
2011541Srgrimes
2021541Srgrimes	/* Figure out how many known commands there are */
2031541Srgrimes	while (knowncmds[ncmds])
20422521Sdyson		ncmds++;
20529353Speter	while (argc > 1 && argv[1][0] == '-') {
20622521Sdyson		switch(argv[1][1]) {
20729353Speter
2081541Srgrimes		/* -a: add pairs of macros */
20929353Speter		case 'a':
2101541Srgrimes			i = strlen(argv[1]) - 2;
2111541Srgrimes			if (i % 6 != 0)
2121541Srgrimes				usage();
2131541Srgrimes			/* look for empty macro slots */
21422521Sdyson			for (i=0; br[i].opbr; i++)
21522521Sdyson				;
21622521Sdyson			for (cp=argv[1]+3; cp[-1]; cp += 6) {
21722521Sdyson				br[i].opbr = strncpy(malloc(3), cp, 2);
21822521Sdyson				br[i].clbr = strncpy(malloc(3), cp+3, 2);
21922521Sdyson				addmac(br[i].opbr);	/* knows pairs are also known cmds */
22022521Sdyson				addmac(br[i].clbr);
22122521Sdyson				i++;
22222521Sdyson			}
22322521Sdyson			break;
22422521Sdyson
2251541Srgrimes		/* -c: add known commands */
2261541Srgrimes		case 'c':
2271541Srgrimes			i = strlen(argv[1]) - 2;
2281541Srgrimes			if (i % 3 != 0)
2291541Srgrimes				usage();
2301541Srgrimes			for (cp=argv[1]+3; cp[-1]; cp += 3) {
2311541Srgrimes				if (cp[2] && cp[2] != '.')
23222521Sdyson					usage();
23322521Sdyson				strncpy(b1, cp, 2);
23422521Sdyson				b1[2] = '\0';
2351541Srgrimes				addmac(b1);
2361541Srgrimes			}
2371541Srgrimes			break;
2381541Srgrimes
2391541Srgrimes		/* -f: ignore font changes */
2401541Srgrimes		case 'f':
2411541Srgrimes			fflag = 1;
24222521Sdyson			break;
24322521Sdyson
24422521Sdyson		/* -s: ignore size changes */
24522521Sdyson		case 's':
2461541Srgrimes			sflag = 1;
2471541Srgrimes			break;
2481541Srgrimes		default:
2491541Srgrimes			usage();
2501541Srgrimes		}
2511541Srgrimes		argc--; argv++;
2521541Srgrimes	}
25322521Sdyson
25422521Sdyson	nfiles = argc - 1;
25522521Sdyson
25622521Sdyson	if (nfiles > 0) {
2571541Srgrimes		for (i = 1; i < argc; i++) {
2581541Srgrimes			cfilename = argv[i];
2591541Srgrimes			f = fopen(cfilename, "r");
2601541Srgrimes			if (f == NULL)
2611541Srgrimes				warn("%s", cfilename);
2621541Srgrimes			else {
26322521Sdyson				process(f);
26422521Sdyson				fclose(f);
26522521Sdyson			}
26622521Sdyson		}
2679842Sdg	} else {
2689842Sdg		cfilename = "stdin";
2699842Sdg		process(stdin);
2709842Sdg	}
2719842Sdg	exit(0);
2721541Srgrimes}
27322521Sdyson
27422521Sdysonstatic void
27522521Sdysonusage(void)
27622521Sdyson{
27722521Sdyson	fprintf(stderr,
27822521Sdyson	"usage: checknr [-a.xx.yy.xx.yy...] [-c.xx.xx.xx...] [-s] [-f] file\n");
2791541Srgrimes	exit(1);
2801541Srgrimes}
2811541Srgrimes
2821541Srgrimesstatic void
2831541Srgrimesprocess(FILE *f)
2841541Srgrimes{
2851541Srgrimes	int i, n;
2861541Srgrimes	char mac[5];	/* The current macro or nroff command */
2871541Srgrimes	int pl;
28822521Sdyson	static char line[256];	/* the current line */
28922521Sdyson
29022521Sdyson	stktop = -1;
29122521Sdyson	for (lineno = 1; fgets(line, sizeof line, f); lineno++) {
2921541Srgrimes		if (line[0] == '.') {
2931541Srgrimes			/*
2941541Srgrimes			 * find and isolate the macro/command name.
2951541Srgrimes			 */
2961541Srgrimes			strncpy(mac, line+1, 4);
2971541Srgrimes			if (isspace(mac[0])) {
2981541Srgrimes				pe(lineno);
29922521Sdyson				printf("Empty command\n");
30022521Sdyson			} else if (isspace(mac[1])) {
30122521Sdyson				mac[1] = 0;
30222521Sdyson			} else if (isspace(mac[2])) {
3031541Srgrimes				mac[2] = 0;
3041541Srgrimes			} else if (mac[0] != '\\' || mac[1] != '\"') {
3051541Srgrimes				pe(lineno);
3061541Srgrimes				printf("Command too long\n");
3071541Srgrimes			}
3081541Srgrimes
30922521Sdyson			/*
31022521Sdyson			 * Is it a known command?
31122521Sdyson			 */
31222521Sdyson			checkknown(mac);
31322521Sdyson
31422521Sdyson			/*
31522521Sdyson			 * Should we add it?
31622521Sdyson			 */
3171541Srgrimes			if (eq(mac, "de"))
3181541Srgrimes				addcmd(line);
3191541Srgrimes
3201541Srgrimes			chkcmd(line, mac);
3211541Srgrimes		}
3221541Srgrimes
3231541Srgrimes		/*
3241541Srgrimes		 * At this point we process the line looking
32522521Sdyson		 * for \s and \f.
32622521Sdyson		 */
32722521Sdyson		for (i = 0; line[i]; i++)
3281541Srgrimes			if (line[i] == '\\' && (i == 0 || line[i-1] != '\\')) {
3291541Srgrimes				if (!sflag && line[++i] == 's') {
3301541Srgrimes					pl = line[++i];
3311541Srgrimes					if (isdigit(pl)) {
3323167Sdfr						n = pl - '0';
33322521Sdyson						pl = ' ';
33422521Sdyson					} else
3351541Srgrimes						n = 0;
3361541Srgrimes					while (isdigit(line[++i]))
33722521Sdyson						n = 10 * n + line[i] - '0';
33822521Sdyson					i--;
33922521Sdyson					if (n == 0) {
3401541Srgrimes						if (stk[stktop].opno == SZ) {
3411541Srgrimes							stktop--;
3421541Srgrimes						} else {
3431541Srgrimes							pe(lineno);
3441541Srgrimes							printf("unmatched \\s0\n");
3451541Srgrimes						}
34622521Sdyson					} else {
34722521Sdyson						stk[++stktop].opno = SZ;
34822521Sdyson						stk[stktop].pl = pl;
3491541Srgrimes						stk[stktop].parm = n;
3501541Srgrimes						stk[stktop].lno = lineno;
3511541Srgrimes					}
3521541Srgrimes				} else if (!fflag && line[i] == 'f') {
3531541Srgrimes					n = line[++i];
35422521Sdyson					if (n == 'P') {
35522521Sdyson						if (stk[stktop].opno == FT) {
35622521Sdyson							stktop--;
3571541Srgrimes						} else {
3581541Srgrimes							pe(lineno);
35922521Sdyson							printf("unmatched \\fP\n");
3601541Srgrimes						}
3611541Srgrimes					} else {
36222521Sdyson						stk[++stktop].opno = FT;
36322521Sdyson						stk[stktop].pl = 1;
36422521Sdyson						stk[stktop].parm = n;
3651541Srgrimes						stk[stktop].lno = lineno;
3661541Srgrimes					}
36722521Sdyson				}
3681541Srgrimes			}
3691541Srgrimes	}
37022521Sdyson	/*
37122521Sdyson	 * We've hit the end and look at all this stuff that hasn't been
37222521Sdyson	 * matched yet!  Complain, complain.
3731541Srgrimes	 */
3741541Srgrimes	for (i = stktop; i >= 0; i--) {
37522521Sdyson		complain(i);
37622521Sdyson	}
3771541Srgrimes}
3781541Srgrimes
37922521Sdysonstatic void
38022521Sdysoncomplain(int i)
38122521Sdyson{
3821541Srgrimes	pe(stk[i].lno);
3831541Srgrimes	printf("Unmatched ");
38422521Sdyson	prop(i);
38522521Sdyson	printf("\n");
3861541Srgrimes}
3871541Srgrimes
38822521Sdysonstatic void
38922521Sdysonprop(int i)
39022521Sdyson{
39122521Sdyson	if (stk[i].pl == 0)
3921541Srgrimes		printf(".%s", br[stk[i].opno].opbr);
3931541Srgrimes	else switch (stk[i].opno) {
3941541Srgrimes	case SZ:
3951541Srgrimes		printf("\\s%c%d", stk[i].pl, stk[i].parm);
3961541Srgrimes		break;
3971541Srgrimes	case FT:
39810551Sdyson		printf("\\f%c", stk[i].parm);
3991541Srgrimes		break;
4001541Srgrimes	default:
40122521Sdyson		printf("Bug: stk[%d].opno = %d = .%s, .%s",
40222521Sdyson			i, stk[i].opno, br[stk[i].opno].opbr,
40322521Sdyson			br[stk[i].opno].clbr);
4041541Srgrimes	}
4051541Srgrimes}
4061541Srgrimes
4071541Srgrimesstatic void
40822521Sdysonchkcmd(const char *line __unused, const char *mac)
40922521Sdyson{
41022521Sdyson	int i;
4111541Srgrimes
4121541Srgrimes	/*
4131541Srgrimes	 * Check to see if it matches top of stack.
4141541Srgrimes	 */
41522521Sdyson	if (stktop >= 0 && eq(mac, br[stk[stktop].opno].clbr))
41622521Sdyson		stktop--;	/* OK. Pop & forget */
41722521Sdyson	else {
4181541Srgrimes		/* No. Maybe it's an opener */
4191541Srgrimes		for (i=0; br[i].opbr; i++) {
4201541Srgrimes			if (eq(mac, br[i].opbr)) {
4211541Srgrimes				/* Found. Push it. */
42222521Sdyson				stktop++;
42322521Sdyson				stk[stktop].opno = i;
42422521Sdyson				stk[stktop].pl = 0;
4251541Srgrimes				stk[stktop].parm = 0;
4261541Srgrimes				stk[stktop].lno = lineno;
4271541Srgrimes				break;
42822521Sdyson			}
4291541Srgrimes			/*
4301541Srgrimes			 * Maybe it's an unmatched closer.
43122521Sdyson			 * NOTE: this depends on the fact
43222521Sdyson			 * that none of the closers can be
43322521Sdyson			 * openers too.
4341541Srgrimes			 */
4351541Srgrimes			if (eq(mac, br[i].clbr)) {
4361541Srgrimes				nomatch(mac);
4371541Srgrimes				break;
4381541Srgrimes			}
4391541Srgrimes		}
4401541Srgrimes	}
4411541Srgrimes}
44222521Sdyson
44322521Sdysonstatic void
44422521Sdysonnomatch(const char *mac)
4451541Srgrimes{
4461541Srgrimes	int i, j;
4471541Srgrimes
4481541Srgrimes	/*
4491541Srgrimes	 * Look for a match further down on stack
4501541Srgrimes	 * If we find one, it suggests that the stuff in
4511541Srgrimes	 * between is supposed to match itself.
45222521Sdyson	 */
45322521Sdyson	for (j=stktop; j>=0; j--)
45422521Sdyson		if (eq(mac,br[stk[j].opno].clbr)) {
4551541Srgrimes			/* Found.  Make a good diagnostic. */
4561541Srgrimes			if (j == stktop-2) {
4571541Srgrimes				/*
4581541Srgrimes				 * Check for special case \fx..\fR and don't
4591541Srgrimes				 * complain.
4601541Srgrimes				 */
4611541Srgrimes				if (stk[j+1].opno==FT && stk[j+1].parm!='R'
46222521Sdyson				 && stk[j+2].opno==FT && stk[j+2].parm=='R') {
46322521Sdyson					stktop = j -1;
46422521Sdyson					return;
4651541Srgrimes				}
4661541Srgrimes				/*
4671541Srgrimes				 * We have two unmatched frobs.  Chances are
4681541Srgrimes				 * they were intended to match, so we mention
4691541Srgrimes				 * them together.
47022521Sdyson				 */
47122521Sdyson				pe(stk[j+1].lno);
47222521Sdyson				prop(j+1);
4731541Srgrimes				printf(" does not match %d: ", stk[j+2].lno);
4741541Srgrimes				prop(j+2);
4751541Srgrimes				printf("\n");
4761541Srgrimes			} else for (i=j+1; i <= stktop; i++) {
4771541Srgrimes				complain(i);
4781541Srgrimes			}
47922521Sdyson			stktop = j-1;
48022521Sdyson			return;
48122521Sdyson		}
4821541Srgrimes	/* Didn't find one.  Throw this away. */
4831541Srgrimes	pe(lineno);
4841541Srgrimes	printf("Unmatched .%s\n", mac);
4851541Srgrimes}
4861541Srgrimes
4871541Srgrimes/* eq: are two strings equal? */
4881541Srgrimesstatic int
4891541Srgrimeseq(const char *s1, const char *s2)
49022521Sdyson{
49122521Sdyson	return (strcmp(s1, s2) == 0);
49222521Sdyson}
4931541Srgrimes
4941541Srgrimes/* print the first part of an error message, given the line number */
4951541Srgrimesstatic void
4961541Srgrimespe(int linen)
4971541Srgrimes{
4981541Srgrimes	if (nfiles > 1)
4991541Srgrimes		printf("%s: ", cfilename);
50010551Sdyson	printf("%d: ", linen);
50110551Sdyson}
50210551Sdyson
50310551Sdysonstatic void
50410551Sdysoncheckknown(const char *mac)
50512767Sdyson{
50610551Sdyson
50710551Sdyson	if (eq(mac, "."))
50810551Sdyson		return;
50910551Sdyson	if (binsrch(mac) >= 0)
51010551Sdyson		return;
51110551Sdyson	if (mac[0] == '\\' && mac[1] == '"')	/* comments */
51210551Sdyson		return;
51310551Sdyson
51412767Sdyson	pe(lineno);
51510551Sdyson	printf("Unknown command: .%s\n", mac);
51611704Sdyson}
51722521Sdyson
5181541Srgrimes/*
51922521Sdyson * We have a .de xx line in "line".  Add xx to the list of known commands.
5201541Srgrimes */
5211541Srgrimesstatic void
5221541Srgrimesaddcmd(char *line)
523{
524	char *mac;
525
526	/* grab the macro being defined */
527	mac = line+4;
528	while (isspace(*mac))
529		mac++;
530	if (*mac == 0) {
531		pe(lineno);
532		printf("illegal define: %s\n", line);
533		return;
534	}
535	mac[2] = 0;
536	if (isspace(mac[1]) || mac[1] == '\\')
537		mac[1] = 0;
538	if (ncmds >= MAXCMDS) {
539		printf("Only %d known commands allowed\n", MAXCMDS);
540		exit(1);
541	}
542	addmac(mac);
543}
544
545/*
546 * Add mac to the list.  We should really have some kind of tree
547 * structure here but this is a quick-and-dirty job and I just don't
548 * have time to mess with it.  (I wonder if this will come back to haunt
549 * me someday?)  Anyway, I claim that .de is fairly rare in user
550 * nroff programs, and the register loop below is pretty fast.
551 */
552static void
553addmac(const char *mac)
554{
555	const char **src, **dest, **loc;
556
557	if (binsrch(mac) >= 0){	/* it's OK to redefine something */
558#ifdef DEBUG
559		printf("binsrch(%s) -> already in table\n", mac);
560#endif
561		return;
562	}
563	/* binsrch sets slot as a side effect */
564#ifdef DEBUG
565printf("binsrch(%s) -> %d\n", mac, slot);
566#endif
567	loc = &knowncmds[slot];
568	src = &knowncmds[ncmds-1];
569	dest = src+1;
570	while (dest > loc)
571		*dest-- = *src--;
572	*loc = strcpy(malloc(3), mac);
573	ncmds++;
574#ifdef DEBUG
575	printf("after: %s %s %s %s %s, %d cmds\n",
576	    knowncmds[slot-2], knowncmds[slot-1], knowncmds[slot],
577	    knowncmds[slot+1], knowncmds[slot+2], ncmds);
578#endif
579}
580
581/*
582 * Do a binary search in knowncmds for mac.
583 * If found, return the index.  If not, return -1.
584 */
585static int
586binsrch(const char *mac)
587{
588	const char *p;	/* pointer to current cmd in list */
589	int d;		/* difference if any */
590	int mid;	/* mid point in binary search */
591	int top, bot;	/* boundaries of bin search, inclusive */
592
593	top = ncmds-1;
594	bot = 0;
595	while (top >= bot) {
596		mid = (top+bot)/2;
597		p = knowncmds[mid];
598		d = p[0] - mac[0];
599		if (d == 0)
600			d = p[1] - mac[1];
601		if (d == 0)
602			return (mid);
603		if (d < 0)
604			bot = mid + 1;
605		else
606			top = mid - 1;
607	}
608	slot = bot;	/* place it would have gone */
609	return (-1);
610}
611