2 * Copyright (c) 1980, 1993
3 * The Regents of the University of California. All rights reserved.
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
8 * 1. Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright
11 * notice, this list of conditions and the following disclaimer in the
12 * documentation and/or other materials provided with the distribution.
13 * 3. All advertising materials mentioning features or use of this software
14 * must display the following acknowledgement:
15 * This product includes software developed by the University of
16 * California, Berkeley and its contributors.
17 * 4. Neither the name of the University nor the names of its contributors
18 * may be used to endorse or promote products derived from this software
19 * without specific prior written permission.
21 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
22 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
23 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
24 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
25 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
26 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
27 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
28 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
29 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
30 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
35 static const char copyright[] =
36 "@(#) Copyright (c) 1980, 1993\n\
37 The Regents of the University of California. All rights reserved.\n";
42 static char sccsid[] = "@(#)checknr.c 8.1 (Berkeley) 6/6/93";
46 #include <sys/cdefs.h>
47 __FBSDID("$FreeBSD$");
50 * checknr: check an nroff/troff input file for matching macro calls.
51 * we also attempt to match size and font changes, but only the embedded
52 * kind. These must end in \s0 and \fP resp. Maybe more sophistication
53 * later but for now think of these restrictions as contributions to
54 * structured typesetting.
62 #define MAXSTK 100 /* Stack size */
63 #define MAXBR 100 /* Max number of bracket pairs known */
64 #define MAXCMDS 500 /* Max number of commands known */
67 void addmac(const char *);
68 int binsrch(const char *);
69 void checkknown(const char *);
70 void chkcmd(const char *, const char *);
72 int eq(const char *, const char *);
73 void nomatch(const char *);
77 static void usage(void);
80 * The stack on which we remember what we've seen so far.
83 int opno; /* number of opening bracket */
84 int pl; /* '+', '-', ' ' for \s, 1 for \f, 0 for .ft */
85 int parm; /* parm to size, font, etc */
86 int lno; /* line number the thing came in */
91 * The kinds of opening and closing brackets.
97 /* A few bare bones troff commands */
99 {"sz", "sz"}, /* also \s */
101 {"ft", "ft"}, /* also \f */
102 /* the -mm package */
115 /* the -ms package */
131 /* The -me package */
140 /* Things needed by preprocessors */
149 * All commands known to nroff, plus macro packages.
150 * Used so we can complain about unrecognized commands.
152 const char *knowncmds[MAXCMDS] = {
153 "$c", "$f", "$h", "$p", "$s", "(b", "(c", "(d", "(f", "(l", "(q", "(t",
154 "(x", "(z", ")b", ")c", ")d", ")f", ")l", ")q", ")t", ")x", ")z", "++",
155 "+c", "1C", "1c", "2C", "2c", "@(", "@)", "@C", "@D", "@F", "@I", "@M",
156 "@c", "@e", "@f", "@h", "@m", "@n", "@o", "@p", "@r", "@t", "@z", "AB",
157 "AE", "AF", "AI", "AL", "AM", "AS", "AT", "AU", "AX", "B", "B1", "B2",
158 "BD", "BE", "BG", "BL", "BS", "BT", "BX", "C1", "C2", "CD", "CM", "CT",
159 "D", "DA", "DE", "DF", "DL", "DS", "DT", "EC", "EF", "EG", "EH", "EM",
160 "EN", "EQ", "EX", "FA", "FD", "FE", "FG", "FJ", "FK", "FL", "FN", "FO",
161 "FQ", "FS", "FV", "FX", "H", "HC", "HD", "HM", "HO", "HU", "I", "ID",
162 "IE", "IH", "IM", "IP", "IX", "IZ", "KD", "KE", "KF", "KQ", "KS", "LB",
163 "LC", "LD", "LE", "LG", "LI", "LP", "MC", "ME", "MF", "MH", "ML", "MR",
164 "MT", "ND", "NE", "NH", "NL", "NP", "NS", "OF", "OH", "OK", "OP", "P",
165 "P1", "PF", "PH", "PP", "PT", "PX", "PY", "QE", "QP", "QS", "R", "RA",
166 "RC", "RE", "RL", "RP", "RQ", "RS", "RT", "S", "S0", "S2", "S3", "SA",
167 "SG", "SH", "SK", "SM", "SP", "SY", "T&", "TA", "TB", "TC", "TD", "TE",
168 "TH", "TL", "TM", "TP", "TQ", "TR", "TS", "TX", "UL", "US", "UX", "VL",
169 "WC", "WH", "XA", "XD", "XE", "XF", "XK", "XP", "XS", "[", "[-", "[0",
170 "[1", "[2", "[3", "[4", "[5", "[<", "[>", "[]", "]", "]-", "]<", "]>",
171 "][", "ab", "ac", "ad", "af", "am", "ar", "as", "b", "ba", "bc", "bd",
172 "bi", "bl", "bp", "br", "bx", "c.", "c2", "cc", "ce", "cf", "ch", "cs",
173 "ct", "cu", "da", "de", "di", "dl", "dn", "ds", "dt", "dw", "dy", "ec",
174 "ef", "eh", "el", "em", "eo", "ep", "ev", "ex", "fc", "fi", "fl", "fo",
175 "fp", "ft", "fz", "hc", "he", "hl", "hp", "ht", "hw", "hx", "hy", "i",
176 "ie", "if", "ig", "in", "ip", "it", "ix", "lc", "lg", "li", "ll", "ln",
177 "lo", "lp", "ls", "lt", "m1", "m2", "m3", "m4", "mc", "mk", "mo", "n1",
178 "n2", "na", "ne", "nf", "nh", "nl", "nm", "nn", "np", "nr", "ns", "nx",
179 "of", "oh", "os", "pa", "pc", "pi", "pl", "pm", "pn", "po", "pp", "ps",
180 "q", "r", "rb", "rd", "re", "rm", "rn", "ro", "rr", "rs", "rt", "sb",
181 "sc", "sh", "sk", "so", "sp", "ss", "st", "sv", "sz", "ta", "tc", "th",
182 "ti", "tl", "tm", "tp", "tr", "u", "uf", "uh", "ul", "vs", "wh", "xp",
186 int lineno; /* current line number in input file */
187 const char *cfilename; /* name of current file */
188 int nfiles; /* number of files to process */
189 int fflag; /* -f: ignore \f */
190 int sflag; /* -s: ignore \s */
191 int ncmds; /* size of knowncmds */
192 int slot; /* slot in knowncmds found by binsrch */
195 main(int argc, char **argv)
202 /* Figure out how many known commands there are */
203 while (knowncmds[ncmds])
205 while (argc > 1 && argv[1][0] == '-') {
208 /* -a: add pairs of macros */
210 i = strlen(argv[1]) - 2;
213 /* look for empty macro slots */
214 for (i=0; br[i].opbr; i++)
216 for (cp=argv[1]+3; cp[-1]; cp += 6) {
217 br[i].opbr = strncpy(malloc(3), cp, 2);
218 br[i].clbr = strncpy(malloc(3), cp+3, 2);
219 addmac(br[i].opbr); /* knows pairs are also known cmds */
225 /* -c: add known commands */
227 i = strlen(argv[1]) - 2;
230 for (cp=argv[1]+3; cp[-1]; cp += 3) {
231 if (cp[2] && cp[2] != '.')
239 /* -f: ignore font changes */
244 /* -s: ignore size changes */
257 for (i=1; i<argc; i++) {
259 f = fopen(cfilename, "r");
261 warn("%s", cfilename);
278 "usage: checknr [-a.xx.yy.xx.yy...] [-c.xx.xx.xx...] [-s] [-f] file\n");
286 char mac[5]; /* The current macro or nroff command */
288 static char line[256]; /* the current line */
291 for (lineno = 1; fgets(line, sizeof line, f); lineno++) {
292 if (line[0] == '.') {
294 * find and isolate the macro/command name.
296 strncpy(mac, line+1, 4);
297 if (isspace(mac[0])) {
299 printf("Empty command\n");
300 } else if (isspace(mac[1])) {
302 } else if (isspace(mac[2])) {
304 } else if (mac[0] != '\\' || mac[1] != '\"') {
306 printf("Command too long\n");
310 * Is it a known command?
324 * At this point we process the line looking
327 for (i=0; line[i]; i++)
328 if (line[i]=='\\' && (i==0 || line[i-1]!='\\')) {
329 if (!sflag && line[++i]=='s') {
336 while (isdigit(line[++i]))
337 n = 10 * n + line[i] - '0';
340 if (stk[stktop].opno == SZ) {
344 printf("unmatched \\s0\n");
347 stk[++stktop].opno = SZ;
349 stk[stktop].parm = n;
350 stk[stktop].lno = lineno;
352 } else if (!fflag && line[i]=='f') {
355 if (stk[stktop].opno == FT) {
359 printf("unmatched \\fP\n");
362 stk[++stktop].opno = FT;
364 stk[stktop].parm = n;
365 stk[stktop].lno = lineno;
371 * We've hit the end and look at all this stuff that hasn't been
372 * matched yet! Complain, complain.
374 for (i=stktop; i>=0; i--) {
383 printf("Unmatched ");
392 printf(".%s", br[stk[i].opno].opbr);
393 else switch(stk[i].opno) {
395 printf("\\s%c%d", stk[i].pl, stk[i].parm);
398 printf("\\f%c", stk[i].parm);
401 printf("Bug: stk[%d].opno = %d = .%s, .%s",
402 i, stk[i].opno, br[stk[i].opno].opbr, br[stk[i].opno].clbr);
407 chkcmd(const char *line __unused, const char *mac)
412 * Check to see if it matches top of stack.
414 if (stktop >= 0 && eq(mac, br[stk[stktop].opno].clbr))
415 stktop--; /* OK. Pop & forget */
417 /* No. Maybe it's an opener */
418 for (i=0; br[i].opbr; i++) {
419 if (eq(mac, br[i].opbr)) {
420 /* Found. Push it. */
422 stk[stktop].opno = i;
424 stk[stktop].parm = 0;
425 stk[stktop].lno = lineno;
429 * Maybe it's an unmatched closer.
430 * NOTE: this depends on the fact
431 * that none of the closers can be
434 if (eq(mac, br[i].clbr)) {
443 nomatch(const char *mac)
448 * Look for a match further down on stack
449 * If we find one, it suggests that the stuff in
450 * between is supposed to match itself.
452 for (j=stktop; j>=0; j--)
453 if (eq(mac,br[stk[j].opno].clbr)) {
454 /* Found. Make a good diagnostic. */
457 * Check for special case \fx..\fR and don't
460 if (stk[j+1].opno==FT && stk[j+1].parm!='R'
461 && stk[j+2].opno==FT && stk[j+2].parm=='R') {
466 * We have two unmatched frobs. Chances are
467 * they were intended to match, so we mention
472 printf(" does not match %d: ", stk[j+2].lno);
475 } else for (i=j+1; i <= stktop; i++) {
481 /* Didn't find one. Throw this away. */
483 printf("Unmatched .%s\n", mac);
486 /* eq: are two strings equal? */
488 eq(const char *s1, const char *s2)
490 return (strcmp(s1, s2) == 0);
493 /* print the first part of an error message, given the line number */
498 printf("%s: ", cfilename);
499 printf("%d: ", linen);
503 checkknown(const char *mac)
508 if (binsrch(mac) >= 0)
510 if (mac[0] == '\\' && mac[1] == '"') /* comments */
514 printf("Unknown command: .%s\n", mac);
518 * We have a .de xx line in "line". Add xx to the list of known commands.
525 /* grab the macro being defined */
527 while (isspace(*mac))
531 printf("illegal define: %s\n", line);
535 if (isspace(mac[1]) || mac[1] == '\\')
537 if (ncmds >= MAXCMDS) {
538 printf("Only %d known commands allowed\n", MAXCMDS);
545 * Add mac to the list. We should really have some kind of tree
546 * structure here but this is a quick-and-dirty job and I just don't
547 * have time to mess with it. (I wonder if this will come back to haunt
548 * me someday?) Anyway, I claim that .de is fairly rare in user
549 * nroff programs, and the register loop below is pretty fast.
552 addmac(const char *mac)
554 const char **src, **dest, **loc;
556 if (binsrch(mac) >= 0){ /* it's OK to redefine something */
558 printf("binsrch(%s) -> already in table\n", mac);
562 /* binsrch sets slot as a side effect */
564 printf("binsrch(%s) -> %d\n", mac, slot);
566 loc = &knowncmds[slot];
567 src = &knowncmds[ncmds-1];
571 *loc = strcpy(malloc(3), mac);
574 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);
579 * Do a binary search in knowncmds for mac.
580 * If found, return the index. If not, return -1.
583 binsrch(const char *mac)
585 const char *p; /* pointer to current cmd in list */
586 int d; /* difference if any */
587 int mid; /* mid point in binary search */
588 int top, bot; /* boundaries of bin search, inclusive */
605 slot = bot; /* place it would have gone */