1259412Sluigi/*
2260368Sluigi * Copyright (c) 1980, 1993
3259412Sluigi *	The Regents of the University of California.  All rights reserved.
4259412Sluigi *
5259412Sluigi * Redistribution and use in source and binary forms, with or without
6259412Sluigi * modification, are permitted provided that the following conditions
7259412Sluigi * are met:
8259412Sluigi * 1. Redistributions of source code must retain the above copyright
9259412Sluigi *    notice, this list of conditions and the following disclaimer.
10259412Sluigi * 2. Redistributions in binary form must reproduce the above copyright
11259412Sluigi *    notice, this list of conditions and the following disclaimer in the
12259412Sluigi *    documentation and/or other materials provided with the distribution.
13259412Sluigi * 4. Neither the name of the University nor the names of its contributors
14259412Sluigi *    may be used to endorse or promote products derived from this software
15259412Sluigi *    without specific prior written permission.
16259412Sluigi *
17259412Sluigi * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
18259412Sluigi * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19259412Sluigi * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
20259412Sluigi * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
21259412Sluigi * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
22259412Sluigi * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
23259412Sluigi * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
24259412Sluigi * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25259412Sluigi * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
26259412Sluigi * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
27285349Sluigi * SUCH DAMAGE.
28285349Sluigi */
29259412Sluigi
30259412Sluigi#ifndef lint
31259412Sluigistatic const char copyright[] =
32259412Sluigi"@(#) Copyright (c) 1980, 1993\n\
33259412Sluigi	The Regents of the University of California.  All rights reserved.\n";
34261909Sluigi#endif /* not lint */
35259412Sluigi
36259412Sluigi#if 0
37261909Sluigi#ifndef lint
38259412Sluigistatic char sccsid[] = "@(#)checknr.c	8.1 (Berkeley) 6/6/93";
39259412Sluigi#endif /* not lint */
40259412Sluigi#endif
41259412Sluigi
42259412Sluigi#include <sys/cdefs.h>
43259412Sluigi__FBSDID("$FreeBSD$");
44259412Sluigi
45259412Sluigi/*
46259412Sluigi * checknr: check an nroff/troff input file for matching macro calls.
47259412Sluigi * we also attempt to match size and font changes, but only the embedded
48259412Sluigi * kind.  These must end in \s0 and \fP resp.  Maybe more sophistication
49259412Sluigi * later but for now think of these restrictions as contributions to
50259412Sluigi * structured typesetting.
51259412Sluigi */
52259412Sluigi#include <err.h>
53259412Sluigi#include <stdio.h>
54259412Sluigi#include <stdlib.h>
55270063Sluigi#include <string.h>
56270063Sluigi#include <ctype.h>
57270063Sluigi
58259412Sluigi#define MAXSTK	100	/* Stack size */
59261909Sluigi#define MAXBR	100	/* Max number of bracket pairs known */
60261909Sluigi#define MAXCMDS	500	/* Max number of commands known */
61259412Sluigi
62259412Sluigistatic void addcmd(char *);
63259412Sluigistatic void addmac(const char *);
64259412Sluigistatic int binsrch(const char *);
65259412Sluigistatic void checkknown(const char *);
66259412Sluigistatic void chkcmd(const char *, const char *);
67259412Sluigistatic void complain(int);
68259412Sluigistatic int eq(const char *, const char *);
69267180Sluigistatic void nomatch(const char *);
70267180Sluigistatic void pe(int);
71261909Sluigistatic void process(FILE *);
72261909Sluigistatic void prop(int);
73261909Sluigistatic void usage(void);
74261909Sluigi
75261909Sluigi/*
76261909Sluigi * The stack on which we remember what we've seen so far.
77261909Sluigi */
78261909Sluigistatic struct stkstr {
79261909Sluigi	int opno;	/* number of opening bracket */
80261909Sluigi	int pl;		/* '+', '-', ' ' for \s, 1 for \f, 0 for .ft */
81261909Sluigi	int parm;	/* parm to size, font, etc */
82261909Sluigi	int lno;	/* line number */
83261909Sluigi} stk[MAXSTK];
84261909Sluigistatic int stktop;
85261909Sluigi
86261909Sluigi/*
87261909Sluigi * The kinds of opening and closing brackets.
88261909Sluigi */
89267180Sluigistatic struct brstr {
90267180Sluigi	const char *opbr;
91261909Sluigi	const char *clbr;
92261909Sluigi} br[MAXBR] = {
93261909Sluigi	/* A few bare bones troff commands */
94261909Sluigi#define SZ	0
95261909Sluigi	{"sz",	"sz"},	/* also \s */
96261909Sluigi#define FT	1
97261909Sluigi	{"ft",	"ft"},	/* also \f */
98261909Sluigi	/* the -mm package */
99270063Sluigi	{"AL",	"LE"},
100261909Sluigi	{"AS",	"AE"},
101261909Sluigi	{"BL",	"LE"},
102261909Sluigi	{"BS",	"BE"},
103261909Sluigi	{"DF",	"DE"},
104261909Sluigi	{"DL",	"LE"},
105261909Sluigi	{"DS",	"DE"},
106261909Sluigi	{"FS",	"FE"},
107261909Sluigi	{"ML",	"LE"},
108267180Sluigi	{"NS",	"NE"},
109267180Sluigi	{"RL",	"LE"},
110261909Sluigi	{"VL",	"LE"},
111261909Sluigi	/* the -ms package */
112262238Sluigi	{"AB",	"AE"},
113261909Sluigi	{"BD",	"DE"},
114261909Sluigi	{"CD",	"DE"},
115261909Sluigi	{"DS",	"DE"},
116261909Sluigi	{"FS",	"FE"},
117261909Sluigi	{"ID",	"DE"},
118261909Sluigi	{"KF",	"KE"},
119261909Sluigi	{"KS",	"KE"},
120261909Sluigi	{"LD",	"DE"},
121261909Sluigi	{"LG",	"NL"},
122262238Sluigi	{"QS",	"QE"},
123262238Sluigi	{"RS",	"RE"},
124262238Sluigi	{"SM",	"NL"},
125262238Sluigi	{"XA",	"XE"},
126262238Sluigi	{"XS",	"XE"},
127262238Sluigi	/* The -me package */
128262238Sluigi	{"(b",	")b"},
129261909Sluigi	{"(c",	")c"},
130261909Sluigi	{"(d",	")d"},
131267180Sluigi	{"(f",	")f"},
132267180Sluigi	{"(l",	")l"},
133261909Sluigi	{"(q",	")q"},
134261909Sluigi	{"(x",	")x"},
135261909Sluigi	{"(z",	")z"},
136261909Sluigi	/* Things needed by preprocessors */
137261909Sluigi	{"EQ",	"EN"},
138261909Sluigi	{"TS",	"TE"},
139261909Sluigi	/* Refer */
140261909Sluigi	{"[",	"]"},
141261909Sluigi	{0,	0}
142261909Sluigi};
143261909Sluigi
144261909Sluigi/*
145261909Sluigi * All commands known to nroff, plus macro packages.
146261909Sluigi * Used so we can complain about unrecognized commands.
147261909Sluigi */
148259412Sluigistatic const char *knowncmds[MAXCMDS] = {
149259412Sluigi"$c", "$f", "$h", "$p", "$s", "(b", "(c", "(d", "(f", "(l", "(q", "(t",
150259412Sluigi"(x", "(z", ")b", ")c", ")d", ")f", ")l", ")q", ")t", ")x", ")z", "++",
151259412Sluigi"+c", "1C", "1c", "2C", "2c", "@(", "@)", "@C", "@D", "@F", "@I", "@M",
152259412Sluigi"@c", "@e", "@f", "@h", "@m", "@n", "@o", "@p", "@r", "@t", "@z", "AB",
153285349Sluigi"AE", "AF", "AI", "AL", "AM", "AS", "AT", "AU", "AX", "B",  "B1", "B2",
154259412Sluigi"BD", "BE", "BG", "BL", "BS", "BT", "BX", "C1", "C2", "CD", "CM", "CT",
155285349Sluigi"D",  "DA", "DE", "DF", "DL", "DS", "DT", "EC", "EF", "EG", "EH", "EM",
156259412Sluigi"EN", "EQ", "EX", "FA", "FD", "FE", "FG", "FJ", "FK", "FL", "FN", "FO",
157259412Sluigi"FQ", "FS", "FV", "FX", "H",  "HC", "HD", "HM", "HO", "HU", "I",  "ID",
158259412Sluigi"IE", "IH", "IM", "IP", "IX", "IZ", "KD", "KE", "KF", "KQ", "KS", "LB",
159259412Sluigi"LC", "LD", "LE", "LG", "LI", "LP", "MC", "ME", "MF", "MH", "ML", "MR",
160259412Sluigi"MT", "ND", "NE", "NH", "NL", "NP", "NS", "OF", "OH", "OK", "OP", "P",
161259412Sluigi"P1", "PF", "PH", "PP", "PT", "PX", "PY", "QE", "QP", "QS", "R",  "RA",
162259412Sluigi"RC", "RE", "RL", "RP", "RQ", "RS", "RT", "S",  "S0", "S2", "S3", "SA",
163259412Sluigi"SG", "SH", "SK", "SM", "SP", "SY", "T&", "TA", "TB", "TC", "TD", "TE",
164259412Sluigi"TH", "TL", "TM", "TP", "TQ", "TR", "TS", "TX", "UL", "US", "UX", "VL",
165259412Sluigi"WC", "WH", "XA", "XD", "XE", "XF", "XK", "XP", "XS", "[",  "[-", "[0",
166259412Sluigi"[1", "[2", "[3", "[4", "[5", "[<", "[>", "[]", "]",  "]-", "]<", "]>",
167259412Sluigi"][", "ab", "ac", "ad", "af", "am", "ar", "as", "b",  "ba", "bc", "bd",
168259412Sluigi"bi", "bl", "bp", "br", "bx", "c.", "c2", "cc", "ce", "cf", "ch", "cs",
169259412Sluigi"ct", "cu", "da", "de", "di", "dl", "dn", "ds", "dt", "dw", "dy", "ec",
170259412Sluigi"ef", "eh", "el", "em", "eo", "ep", "ev", "ex", "fc", "fi", "fl", "fo",
171259412Sluigi"fp", "ft", "fz", "hc", "he", "hl", "hp", "ht", "hw", "hx", "hy", "i",
172259412Sluigi"ie", "if", "ig", "in", "ip", "it", "ix", "lc", "lg", "li", "ll", "ln",
173259412Sluigi"lo", "lp", "ls", "lt", "m1", "m2", "m3", "m4", "mc", "mk", "mo", "n1",
174259412Sluigi"n2", "na", "ne", "nf", "nh", "nl", "nm", "nn", "np", "nr", "ns", "nx",
175259412Sluigi"of", "oh", "os", "pa", "pc", "pi", "pl", "pm", "pn", "po", "pp", "ps",
176259412Sluigi"q",  "r",  "rb", "rd", "re", "rm", "rn", "ro", "rr", "rs", "rt", "sb",
177260368Sluigi"sc", "sh", "sk", "so", "sp", "ss", "st", "sv", "sz", "ta", "tc", "th",
178259412Sluigi"ti", "tl", "tm", "tp", "tr", "u",  "uf", "uh", "ul", "vs", "wh", "xp",
179259412Sluigi"yr", 0
180259412Sluigi};
181259412Sluigi
182261909Sluigistatic int	lineno;		/* current line number in input file */
183259412Sluigistatic const char *cfilename;	/* name of current file */
184259412Sluigistatic int	nfiles;		/* number of files to process */
185260368Sluigistatic int	fflag;		/* -f: ignore \f */
186259412Sluigistatic int	sflag;		/* -s: ignore \s */
187260368Sluigistatic int	ncmds;		/* size of knowncmds */
188285349Sluigistatic int	slot;		/* slot in knowncmds found by binsrch */
189260368Sluigi
190259412Sluigiint
191260368Sluigimain(int argc, char **argv)
192260368Sluigi{
193259412Sluigi	FILE *f;
194260368Sluigi	int i;
195259412Sluigi	char *cp;
196259412Sluigi	char b1[4];
197259412Sluigi
198260368Sluigi	/* Figure out how many known commands there are */
199261909Sluigi	while (knowncmds[ncmds])
200261909Sluigi		ncmds++;
201259412Sluigi	while (argc > 1 && argv[1][0] == '-') {
202259412Sluigi		switch(argv[1][1]) {
203259412Sluigi
204259412Sluigi		/* -a: add pairs of macros */
205259412Sluigi		case 'a':
206259412Sluigi			i = strlen(argv[1]) - 2;
207259412Sluigi			if (i % 6 != 0)
208259412Sluigi				usage();
209275358Shselasky			/* look for empty macro slots */
210259412Sluigi			for (i=0; br[i].opbr; i++)
211259412Sluigi				;
212259412Sluigi			for (cp=argv[1]+3; cp[-1]; cp += 6) {
213259412Sluigi				br[i].opbr = strncpy(malloc(3), cp, 2);
214259412Sluigi				br[i].clbr = strncpy(malloc(3), cp+3, 2);
215259412Sluigi				addmac(br[i].opbr);	/* knows pairs are also known cmds */
216259412Sluigi				addmac(br[i].clbr);
217259412Sluigi				i++;
218259412Sluigi			}
219259412Sluigi			break;
220259412Sluigi
221267180Sluigi		/* -c: add known commands */
222267180Sluigi		case 'c':
223267180Sluigi			i = strlen(argv[1]) - 2;
224267180Sluigi			if (i % 3 != 0)
225267180Sluigi				usage();
226259412Sluigi			for (cp=argv[1]+3; cp[-1]; cp += 3) {
227270063Sluigi				if (cp[2] && cp[2] != '.')
228267180Sluigi					usage();
229270063Sluigi				strncpy(b1, cp, 2);
230267180Sluigi				b1[2] = '\0';
231267180Sluigi				addmac(b1);
232267180Sluigi			}
233267180Sluigi			break;
234267180Sluigi
235267180Sluigi		/* -f: ignore font changes */
236267180Sluigi		case 'f':
237267180Sluigi			fflag = 1;
238267180Sluigi			break;
239267180Sluigi
240267180Sluigi		/* -s: ignore size changes */
241267180Sluigi		case 's':
242267180Sluigi			sflag = 1;
243267180Sluigi			break;
244270063Sluigi		default:
245275358Shselasky			usage();
246259412Sluigi		}
247259412Sluigi		argc--; argv++;
248260368Sluigi	}
249259412Sluigi
250259412Sluigi	nfiles = argc - 1;
251259412Sluigi
252260368Sluigi	if (nfiles > 0) {
253267170Sluigi		for (i=1; i<argc; i++) {
254267170Sluigi			cfilename = argv[i];
255267170Sluigi			f = fopen(cfilename, "r");
256267170Sluigi			if (f == NULL)
257267170Sluigi				warn("%s", cfilename);
258267170Sluigi			else {
259267170Sluigi				process(f);
260267170Sluigi				fclose(f);
261259412Sluigi			}
262259412Sluigi		}
263259412Sluigi	} else {
264259412Sluigi		cfilename = "stdin";
265259412Sluigi		process(stdin);
266259412Sluigi	}
267259412Sluigi	exit(0);
268267180Sluigi}
269259412Sluigi
270259412Sluigistatic void
271259412Sluigiusage(void)
272260368Sluigi{
273259412Sluigi	fprintf(stderr,
274259412Sluigi	"usage: checknr [-a.xx.yy.xx.yy...] [-c.xx.xx.xx...] [-s] [-f] file\n");
275259412Sluigi	exit(1);
276267180Sluigi}
277261909Sluigi
278261909Sluigistatic void
279259412Sluigiprocess(FILE *f)
280259412Sluigi{
281260368Sluigi	int i, n;
282267180Sluigi	char mac[5];	/* The current macro or nroff command */
283270063Sluigi	int pl;
284259412Sluigi	static char line[256];	/* the current line */
285259412Sluigi
286261909Sluigi	stktop = -1;
287270063Sluigi	for (lineno = 1; fgets(line, sizeof line, f); lineno++) {
288261909Sluigi		if (line[0] == '.') {
289259412Sluigi			/*
290259412Sluigi			 * find and isolate the macro/command name.
291259412Sluigi			 */
292267180Sluigi			strncpy(mac, line+1, 4);
293267180Sluigi			if (isspace(mac[0])) {
294259412Sluigi				pe(lineno);
295259412Sluigi				printf("Empty command\n");
296259412Sluigi			} else if (isspace(mac[1])) {
297259412Sluigi				mac[1] = 0;
298260368Sluigi			} else if (isspace(mac[2])) {
299267180Sluigi				mac[2] = 0;
300267180Sluigi			} else if (mac[0] != '\\' || mac[1] != '\"') {
301259412Sluigi				pe(lineno);
302259412Sluigi				printf("Command too long\n");
303259412Sluigi			}
304259412Sluigi
305260368Sluigi			/*
306267180Sluigi			 * Is it a known command?
307267180Sluigi			 */
308259412Sluigi			checkknown(mac);
309259412Sluigi
310259412Sluigi			/*
311259412Sluigi			 * Should we add it?
312259412Sluigi			 */
313260368Sluigi			if (eq(mac, "de"))
314267180Sluigi				addcmd(line);
315267180Sluigi
316259412Sluigi			chkcmd(line, mac);
317259412Sluigi		}
318259412Sluigi
319259412Sluigi		/*
320270063Sluigi		 * At this point we process the line looking
321270063Sluigi		 * for \s and \f.
322270063Sluigi		 */
323270063Sluigi		for (i=0; line[i]; i++)
324270063Sluigi			if (line[i]=='\\' && (i==0 || line[i-1]!='\\')) {
325260368Sluigi				if (!sflag && line[++i]=='s') {
326270063Sluigi					pl = line[++i];
327270063Sluigi					if (isdigit(pl)) {
328270063Sluigi						n = pl - '0';
329270063Sluigi						pl = ' ';
330270063Sluigi					} else
331270063Sluigi						n = 0;
332259412Sluigi					while (isdigit(line[++i]))
333270063Sluigi						n = 10 * n + line[i] - '0';
334270063Sluigi					i--;
335270063Sluigi					if (n == 0) {
336270063Sluigi						if (stk[stktop].opno == SZ) {
337270063Sluigi							stktop--;
338270063Sluigi						} else {
339270063Sluigi							pe(lineno);
340270063Sluigi							printf("unmatched \\s0\n");
341270063Sluigi						}
342270063Sluigi					} else {
343270063Sluigi						stk[++stktop].opno = SZ;
344270063Sluigi						stk[stktop].pl = pl;
345270063Sluigi						stk[stktop].parm = n;
346270063Sluigi						stk[stktop].lno = lineno;
347270063Sluigi					}
348270063Sluigi				} else if (!fflag && line[i]=='f') {
349270063Sluigi					n = line[++i];
350270063Sluigi					if (n == 'P') {
351270063Sluigi						if (stk[stktop].opno == FT) {
352270063Sluigi							stktop--;
353270063Sluigi						} else {
354270063Sluigi							pe(lineno);
355270063Sluigi							printf("unmatched \\fP\n");
356270063Sluigi						}
357270063Sluigi					} else {
358270063Sluigi						stk[++stktop].opno = FT;
359270063Sluigi						stk[stktop].pl = 1;
360270063Sluigi						stk[stktop].parm = n;
361270063Sluigi						stk[stktop].lno = lineno;
362270063Sluigi					}
363270063Sluigi				}
364270063Sluigi			}
365270063Sluigi	}
366270063Sluigi	/*
367270063Sluigi	 * We've hit the end and look at all this stuff that hasn't been
368270063Sluigi	 * matched yet!  Complain, complain.
369270063Sluigi	 */
370270063Sluigi	for (i=stktop; i>=0; i--) {
371270063Sluigi		complain(i);
372270063Sluigi	}
373270063Sluigi}
374270063Sluigi
375270063Sluigistatic void
376270063Sluigicomplain(int i)
377270063Sluigi{
378270063Sluigi	pe(stk[i].lno);
379270063Sluigi	printf("Unmatched ");
380270063Sluigi	prop(i);
381270063Sluigi	printf("\n");
382270063Sluigi}
383270063Sluigi
384270063Sluigistatic void
385270063Sluigiprop(int i)
386270063Sluigi{
387270063Sluigi	if (stk[i].pl == 0)
388270063Sluigi		printf(".%s", br[stk[i].opno].opbr);
389270063Sluigi	else switch(stk[i].opno) {
390270063Sluigi	case SZ:
391270063Sluigi		printf("\\s%c%d", stk[i].pl, stk[i].parm);
392270063Sluigi		break;
393270063Sluigi	case FT:
394270063Sluigi		printf("\\f%c", stk[i].parm);
395270063Sluigi		break;
396270063Sluigi	default:
397270063Sluigi		printf("Bug: stk[%d].opno = %d = .%s, .%s",
398270063Sluigi			i, stk[i].opno, br[stk[i].opno].opbr, br[stk[i].opno].clbr);
399270063Sluigi	}
400270063Sluigi}
401270063Sluigi
402270063Sluigistatic void
403270063Sluigichkcmd(const char *line __unused, const char *mac)
404270063Sluigi{
405270063Sluigi	int i;
406270063Sluigi
407270063Sluigi	/*
408270063Sluigi	 * Check to see if it matches top of stack.
409270063Sluigi	 */
410270063Sluigi	if (stktop >= 0 && eq(mac, br[stk[stktop].opno].clbr))
411270063Sluigi		stktop--;	/* OK. Pop & forget */
412270063Sluigi	else {
413270063Sluigi		/* No. Maybe it's an opener */
414270063Sluigi		for (i=0; br[i].opbr; i++) {
415270063Sluigi			if (eq(mac, br[i].opbr)) {
416270063Sluigi				/* Found. Push it. */
417270063Sluigi				stktop++;
418270063Sluigi				stk[stktop].opno = i;
419270063Sluigi				stk[stktop].pl = 0;
420270063Sluigi				stk[stktop].parm = 0;
421270063Sluigi				stk[stktop].lno = lineno;
422270063Sluigi				break;
423270063Sluigi			}
424270063Sluigi			/*
425270063Sluigi			 * Maybe it's an unmatched closer.
426270063Sluigi			 * NOTE: this depends on the fact
427270063Sluigi			 * that none of the closers can be
428270063Sluigi			 * openers too.
429270063Sluigi			 */
430270063Sluigi			if (eq(mac, br[i].clbr)) {
431270063Sluigi				nomatch(mac);
432270063Sluigi				break;
433270063Sluigi			}
434270063Sluigi		}
435270063Sluigi	}
436270063Sluigi}
437270063Sluigi
438270063Sluigistatic void
439270063Sluiginomatch(const char *mac)
440270063Sluigi{
441270063Sluigi	int i, j;
442270063Sluigi
443270063Sluigi	/*
444270063Sluigi	 * Look for a match further down on stack
445270063Sluigi	 * If we find one, it suggests that the stuff in
446270063Sluigi	 * between is supposed to match itself.
447270063Sluigi	 */
448270063Sluigi	for (j=stktop; j>=0; j--)
449270063Sluigi		if (eq(mac,br[stk[j].opno].clbr)) {
450270063Sluigi			/* Found.  Make a good diagnostic. */
451259412Sluigi			if (j == stktop-2) {
452259412Sluigi				/*
453259412Sluigi				 * Check for special case \fx..\fR and don't
454259412Sluigi				 * complain.
455259412Sluigi				 */
456259412Sluigi				if (stk[j+1].opno==FT && stk[j+1].parm!='R'
457259412Sluigi				 && stk[j+2].opno==FT && stk[j+2].parm=='R') {
458259412Sluigi					stktop = j -1;
459259412Sluigi					return;
460259412Sluigi				}
461260368Sluigi				/*
462259412Sluigi				 * We have two unmatched frobs.  Chances are
463259412Sluigi				 * they were intended to match, so we mention
464259412Sluigi				 * them together.
465259412Sluigi				 */
466259412Sluigi				pe(stk[j+1].lno);
467261909Sluigi				prop(j+1);
468261909Sluigi				printf(" does not match %d: ", stk[j+2].lno);
469261909Sluigi				prop(j+2);
470261909Sluigi				printf("\n");
471274354Sluigi			} else for (i=j+1; i <= stktop; i++) {
472274354Sluigi				complain(i);
473259412Sluigi			}
474259412Sluigi			stktop = j-1;
475259412Sluigi			return;
476259412Sluigi		}
477259412Sluigi	/* Didn't find one.  Throw this away. */
478259412Sluigi	pe(lineno);
479259412Sluigi	printf("Unmatched .%s\n", mac);
480259412Sluigi}
481259412Sluigi
482259412Sluigi/* eq: are two strings equal? */
483259412Sluigistatic int
484261909Sluigieq(const char *s1, const char *s2)
485261909Sluigi{
486261909Sluigi	return (strcmp(s1, s2) == 0);
487259412Sluigi}
488259412Sluigi
489259412Sluigi/* print the first part of an error message, given the line number */
490259412Sluigistatic void
491259412Sluigipe(int linen)
492260368Sluigi{
493259412Sluigi	if (nfiles > 1)
494259412Sluigi		printf("%s: ", cfilename);
495259412Sluigi	printf("%d: ", linen);
496259412Sluigi}
497259412Sluigi
498259412Sluigistatic void
499285349Sluigicheckknown(const char *mac)
500259412Sluigi{
501259412Sluigi
502259412Sluigi	if (eq(mac, "."))
503259412Sluigi		return;
504259412Sluigi	if (binsrch(mac) >= 0)
505259412Sluigi		return;
506259412Sluigi	if (mac[0] == '\\' && mac[1] == '"')	/* comments */
507259412Sluigi		return;
508259412Sluigi
509285349Sluigi	pe(lineno);
510259412Sluigi	printf("Unknown command: .%s\n", mac);
511259412Sluigi}
512259412Sluigi
513259412Sluigi/*
514259412Sluigi * We have a .de xx line in "line".  Add xx to the list of known commands.
515259412Sluigi */
516259412Sluigistatic void
517259412Sluigiaddcmd(char *line)
518259412Sluigi{
519259412Sluigi	char *mac;
520259412Sluigi
521259412Sluigi	/* grab the macro being defined */
522259412Sluigi	mac = line+4;
523259412Sluigi	while (isspace(*mac))
524259412Sluigi		mac++;
525259412Sluigi	if (*mac == 0) {
526259412Sluigi		pe(lineno);
527259412Sluigi		printf("illegal define: %s\n", line);
528259412Sluigi		return;
529259412Sluigi	}
530259412Sluigi	mac[2] = 0;
531259412Sluigi	if (isspace(mac[1]) || mac[1] == '\\')
532259412Sluigi		mac[1] = 0;
533259412Sluigi	if (ncmds >= MAXCMDS) {
534259412Sluigi		printf("Only %d known commands allowed\n", MAXCMDS);
535259412Sluigi		exit(1);
536259412Sluigi	}
537259412Sluigi	addmac(mac);
538259412Sluigi}
539259412Sluigi
540259412Sluigi/*
541259412Sluigi * Add mac to the list.  We should really have some kind of tree
542259412Sluigi * structure here but this is a quick-and-dirty job and I just don't
543259412Sluigi * have time to mess with it.  (I wonder if this will come back to haunt
544259412Sluigi * me someday?)  Anyway, I claim that .de is fairly rare in user
545259412Sluigi * nroff programs, and the register loop below is pretty fast.
546259412Sluigi */
547259412Sluigistatic void
548259412Sluigiaddmac(const char *mac)
549259412Sluigi{
550259412Sluigi	const char **src, **dest, **loc;
551259412Sluigi
552259412Sluigi	if (binsrch(mac) >= 0){	/* it's OK to redefine something */
553259412Sluigi#ifdef DEBUG
554259412Sluigi		printf("binsrch(%s) -> already in table\n", mac);
555259412Sluigi#endif
556259412Sluigi		return;
557259412Sluigi	}
558259412Sluigi	/* binsrch sets slot as a side effect */
559259412Sluigi#ifdef DEBUG
560261909Sluigiprintf("binsrch(%s) -> %d\n", mac, slot);
561261909Sluigi#endif
562261909Sluigi	loc = &knowncmds[slot];
563259412Sluigi	src = &knowncmds[ncmds-1];
564259412Sluigi	dest = src+1;
565259412Sluigi	while (dest > loc)
566259412Sluigi		*dest-- = *src--;
567259412Sluigi	*loc = strcpy(malloc(3), mac);
568259412Sluigi	ncmds++;
569259412Sluigi#ifdef DEBUG
570259412Sluigiprintf("after: %s %s %s %s %s, %d cmds\n", knowncmds[slot-2], knowncmds[slot-1], knowncmds[slot], knowncmds[slot+1], knowncmds[slot+2], ncmds);
571259412Sluigi#endif
572259412Sluigi}
573259412Sluigi
574285349Sluigi/*
575285349Sluigi * Do a binary search in knowncmds for mac.
576285349Sluigi * If found, return the index.  If not, return -1.
577285349Sluigi */
578259412Sluigistatic int
579285359Sluigibinsrch(const char *mac)
580259412Sluigi{
581259412Sluigi	const char *p;	/* pointer to current cmd in list */
582259412Sluigi	int d;		/* difference if any */
583259412Sluigi	int mid;	/* mid point in binary search */
584259412Sluigi	int top, bot;	/* boundaries of bin search, inclusive */
585259412Sluigi
586259412Sluigi	top = ncmds-1;
587259412Sluigi	bot = 0;
588259412Sluigi	while (top >= bot) {
589259412Sluigi		mid = (top+bot)/2;
590259412Sluigi		p = knowncmds[mid];
591259412Sluigi		d = p[0] - mac[0];
592259412Sluigi		if (d == 0)
593259412Sluigi			d = p[1] - mac[1];
594259412Sluigi		if (d == 0)
595259412Sluigi			return mid;
596285359Sluigi		if (d < 0)
597259412Sluigi			bot = mid + 1;
598259412Sluigi		else
599259412Sluigi			top = mid - 1;
600259412Sluigi	}
601259412Sluigi	slot = bot;	/* place it would have gone */
602259412Sluigi	return -1;
603259412Sluigi}
604285349Sluigi