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
33 * @(#) Copyright (c) 1980, 1993 The Regents of the University of California. All rights reserved.
34 * @(#)checknr.c 8.1 (Berkeley) 6/6/93
36 * $DragonFly: src/usr.bin/checknr/checknr.c,v 1.13 2008/11/11 01:02:40 pavalos Exp $
45 #define MAXSTK 100 /* Stack size */
46 #define MAXBR 100 /* Max number of bracket pairs known */
47 #define MAXCMDS 500 /* Max number of commands known */
49 static void addcmd(char *);
50 static void addmac(const char *);
51 static int binsrch(const char *, int *);
52 static void checkknown(const char *);
53 static void chkcmd(const char *);
54 static void complain(int);
55 static int eq(const char *, const char *);
56 static void nomatch(const char *);
58 static void process(FILE *);
59 static void prop(int);
60 static void usage(void);
63 * The stack on which we remember what we've seen so far.
66 int opno
; /* number of opening bracket */
67 int pl
; /* '+', '-', ' ' for \s, 1 for \f, 0 for .ft */
68 int parm
; /* parm to size, font, etc */
69 int lno
; /* line number the thing came in on */
74 * The kinds of opening and closing brackets.
80 /* A few bare bones troff commands */
82 {"sz", "sz"}, /* also \s */
84 {"ft", "ft"}, /* also \f */
114 /* The -me package */
123 /* Things needed by preprocessors */
131 * All commands known to nroff, plus macro packages.
132 * Used so we can complain about unrecognized commands.
134 char knowncmds
[MAXCMDS
][3] = {
135 "$c", "$f", "$h", "$p", "$s", "(b", "(c", "(d", "(f", "(l", "(q", "(t",
136 "(x", "(z", ")b", ")c", ")d", ")f", ")l", ")q", ")t", ")x", ")z", "++",
137 "+c", "1C", "1c", "2C", "2c", "@(", "@)", "@C", "@D", "@F", "@I", "@M",
138 "@c", "@e", "@f", "@h", "@m", "@n", "@o", "@p", "@r", "@t", "@z", "AB",
139 "AE", "AF", "AI", "AL", "AM", "AS", "AT", "AU", "AX", "B", "B1", "B2",
140 "BD", "BE", "BG", "BL", "BS", "BT", "BX", "C1", "C2", "CD", "CM", "CT",
141 "D", "DA", "DE", "DF", "DL", "DS", "DT", "EC", "EF", "EG", "EH", "EM",
142 "EN", "EQ", "EX", "FA", "FD", "FE", "FG", "FJ", "FK", "FL", "FN", "FO",
143 "FQ", "FS", "FV", "FX", "H", "HC", "HD", "HM", "HO", "HU", "I", "ID",
144 "IE", "IH", "IM", "IP", "IX", "IZ", "KD", "KE", "KF", "KQ", "KS", "LB",
145 "LC", "LD", "LE", "LG", "LI", "LP", "MC", "ME", "MF", "MH", "ML", "MR",
146 "MT", "ND", "NE", "NH", "NL", "NP", "NS", "OF", "OH", "OK", "OP", "P",
147 "P1", "PF", "PH", "PP", "PT", "PX", "PY", "QE", "QP", "QS", "R", "RA",
148 "RC", "RE", "RL", "RP", "RQ", "RS", "RT", "S", "S0", "S2", "S3", "SA",
149 "SG", "SH", "SK", "SM", "SP", "SY", "T&", "TA", "TB", "TC", "TD", "TE",
150 "TH", "TL", "TM", "TP", "TQ", "TR", "TS", "TX", "UL", "US", "UX", "VL",
151 "WC", "WH", "XA", "XD", "XE", "XF", "XK", "XP", "XS", "[", "[-", "[0",
152 "[1", "[2", "[3", "[4", "[5", "[<", "[>", "[]", "]", "]-", "]<", "]>",
153 "][", "ab", "ac", "ad", "af", "am", "ar", "as", "b", "ba", "bc", "bd",
154 "bi", "bl", "bp", "br", "bx", "c.", "c2", "cc", "ce", "cf", "ch", "cs",
155 "ct", "cu", "da", "de", "di", "dl", "dn", "ds", "dt", "dw", "dy", "ec",
156 "ef", "eh", "el", "em", "eo", "ep", "ev", "ex", "fc", "fi", "fl", "fo",
157 "fp", "ft", "fz", "hc", "he", "hl", "hp", "ht", "hw", "hx", "hy", "i",
158 "ie", "if", "ig", "in", "ip", "it", "ix", "lc", "lg", "li", "ll", "ln",
159 "lo", "lp", "ls", "lt", "m1", "m2", "m3", "m4", "mc", "mk", "mo", "n1",
160 "n2", "na", "ne", "nf", "nh", "nl", "nm", "nn", "np", "nr", "ns", "nx",
161 "of", "oh", "os", "pa", "pc", "pi", "pl", "pm", "pn", "po", "pp", "ps",
162 "q", "r", "rb", "rd", "re", "rm", "rn", "ro", "rr", "rs", "rt", "sb",
163 "sc", "sh", "sk", "so", "sp", "ss", "st", "sv", "sz", "ta", "tc", "th",
164 "ti", "tl", "tm", "tp", "tr", "u", "uf", "uh", "ul", "vs", "wh", "xp",
168 int lineno
; /* current line number in input file */
169 char line
[256]; /* the current line */
170 const char *cfilename
; /* name of current file */
171 int nfiles
; /* number of files to process */
172 int fflag
; /* -f: ignore \f */
173 int sflag
; /* -s: ignore \s */
174 int ncmds
; /* size of knowncmds */
177 * checknr: check an nroff/troff input file for matching macro calls.
178 * we also attempt to match size and font changes, but only the embedded
179 * kind. These must end in \s0 and \fP resp. Maybe more sophistication
180 * later but for now think of these restrictions as contributions to
181 * structured typesetting.
184 main(int argc
, char **argv
)
191 /* Figure out how many known commands there are */
193 while (ncmds
< MAXCMDS
&& knowncmds
[ncmds
][0] != '\0')
195 while (argc
> 1 && argv
[1][0] == '-') {
198 /* -a: add pairs of macros */
200 if ((strlen(argv
[1]) - 2) % 6 != 0)
202 /* look for empty macro slots */
204 while (i
< MAXBR
&& br
[i
].opbr
[0] != '\0')
207 errx(1, "Only %d known macro-pairs allowed",
210 for (cp
= argv
[1] + 3; cp
[-1]; cp
+= 6) {
211 strncpy(br
[i
].opbr
, cp
, 2);
212 strncpy(br
[i
].clbr
, cp
+ 3, 2);
214 * known pairs are also known cmds
222 /* -c: add known commands */
224 i
= strlen(argv
[1]) - 2;
227 for (cp
= argv
[1] + 3; cp
[-1]; cp
+= 3) {
228 if (cp
[2] && cp
[2] != '.')
236 /* -f: ignore font changes */
241 /* -s: ignore size changes */
254 for (i
= 1; i
< argc
; i
++) {
256 f
= fopen(cfilename
, "r");
258 warn("%s", cfilename
);
275 "usage: checknr [-fs] [-a.xx.yy.xx.yy...] [-c.xx.xx.xx...] "
284 char mac
[5]; /* The current macro or nroff command */
288 for (lineno
= 1; fgets(line
, sizeof(line
), f
); lineno
++) {
289 if (line
[0] == '.') {
291 * find and isolate the macro/command name.
293 strncpy(mac
, line
+ 1, 4);
294 if (isspace(mac
[0])) {
296 printf("Empty command\n");
297 } else if (isspace(mac
[1])) {
299 } else if (isspace(mac
[2])) {
301 } else if (mac
[0] != '\\' || mac
[1] != '\"') {
303 printf("Command too long\n");
307 * Is it a known command?
321 * At this point we process the line looking
324 for (i
= 0; line
[i
]; i
++) {
325 if (line
[i
] == '\\' && (i
== 0 || line
[i
-1] != '\\')) {
326 if (!sflag
&& line
[++i
] == 's') {
333 while (isdigit(line
[++i
]))
334 n
= 10 * n
+ line
[i
] - '0';
337 if (stk
[stktop
].opno
== SZ
) {
341 printf("unmatched \\s0\n");
344 stk
[++stktop
].opno
= SZ
;
346 stk
[stktop
].parm
= n
;
347 stk
[stktop
].lno
= lineno
;
349 } else if (!fflag
&& line
[i
] == 'f') {
352 if (stk
[stktop
].opno
== FT
) {
356 printf("unmatched \\fP\n");
359 stk
[++stktop
].opno
= FT
;
361 stk
[stktop
].parm
= n
;
362 stk
[stktop
].lno
= lineno
;
369 * We've hit the end and look at all this stuff that hasn't been
370 * matched yet! Complain, complain.
372 for (i
= stktop
; i
>= 0; i
--) {
381 printf("Unmatched ");
390 printf(".%s", br
[stk
[i
].opno
].opbr
);
391 else switch(stk
[i
].opno
) {
393 printf("\\s%c%d", stk
[i
].pl
, stk
[i
].parm
);
396 printf("\\f%c", stk
[i
].parm
);
399 printf("Bug: stk[%d].opno = %d = .%s, .%s",
400 i
, stk
[i
].opno
, br
[stk
[i
].opno
].opbr
,
401 br
[stk
[i
].opno
].clbr
);
406 chkcmd(const char *mac
)
411 * Check to see if it matches top of stack.
413 if (stktop
>= 0 && eq(mac
, br
[stk
[stktop
].opno
].clbr
))
414 stktop
--; /* OK. Pop & forget */
416 /* No. Maybe it's an opener */
417 for (i
= 0; br
[i
].opbr
[0] != '\0'; i
++) {
418 if (eq(mac
, br
[i
].opbr
)) {
419 /* Found. Push it. */
421 stk
[stktop
].opno
= i
;
423 stk
[stktop
].parm
= 0;
424 stk
[stktop
].lno
= lineno
;
428 * Maybe it's an unmatched closer.
429 * NOTE: this depends on the fact
430 * that none of the closers can be
433 if (eq(mac
, br
[i
].clbr
)) {
442 nomatch(const char *mac
)
447 * Look for a match further down on stack
448 * If we find one, it suggests that the stuff in
449 * between is supposed to match itself.
451 for (j
= stktop
; j
>= 0; j
--) {
452 if (eq(mac
, br
[stk
[j
].opno
].clbr
)) {
453 /* Found. Make a good diagnostic. */
454 if (j
== stktop
- 2) {
456 * Check for special case \fx..\fR and don't
459 if (stk
[j
+ 1].opno
== FT
&&
460 stk
[j
+ 1].parm
!= 'R' &&
461 stk
[j
+ 2].opno
== FT
&&
462 stk
[j
+ 2].parm
== 'R') {
467 * We have two unmatched frobs. Chances are
468 * they were intended to match, so we mention
473 printf(" does not match %d: ", stk
[j
+ 2].lno
);
477 for (i
= j
+ 1; i
<= stktop
; i
++) {
485 /* Didn't find one. Throw this away. */
487 printf("Unmatched .%s\n", mac
);
490 /* eq: are two strings equal? */
492 eq(const char *s1
, const char *s2
)
494 return (strcmp(s1
, s2
) == 0);
497 /* print the first part of an error message, given the line number */
502 printf("%s: ", cfilename
);
503 printf("%d: ", mylineno
);
507 checkknown(const char *mac
)
511 if (binsrch(mac
, NULL
) >= 0)
513 if (mac
[0] == '\\' && mac
[1] == '"') /* comments */
517 printf("Unknown command: .%s\n", mac
);
521 * We have a .de xx line in "line". Add xx to the list of known commands.
528 /* grab the macro being defined */
530 while (isspace(*mac
))
534 printf("illegal define: %s\n", myline
);
538 if (isspace(mac
[1]) || mac
[1] == '\\')
544 * Add mac to the list. We should really have some kind of tree
545 * structure here, but the loop below is reasonably fast.
548 addmac(const char *mac
)
552 if (ncmds
>= MAXCMDS
) {
553 errx(1, "Only %d known commands allowed", MAXCMDS
);
556 /* Don't try to add it if it's already in the table. */
557 if (binsrch(mac
, &slot
) >= 0) {
559 printf("binsrch(%s) -> already in table\n", mac
);
564 printf("binsrch(%s) -> %d\n", mac
, slot
);
566 for (i
= ncmds
- 1; i
>= slot
; i
--) {
567 strncpy(knowncmds
[i
+ 1], knowncmds
[i
], 2);
569 strncpy(knowncmds
[slot
], mac
, 2);
572 printf("after: %s %s %s %s %s, %d cmds\n", knowncmds
[slot
-2],
573 knowncmds
[slot
-1], knowncmds
[slot
], knowncmds
[slot
+1],
574 knowncmds
[slot
+2], ncmds
);
579 * Do a binary search in knowncmds for mac.
580 * If found, return the index. If not, return -1.
581 * Also, if not found, and if slot_ptr is not NULL,
582 * set *slot_ptr to where it should have been.
585 binsrch(const char *mac
, int *slot_ptr
)
587 const char *p
; /* pointer to current cmd in list */
588 int d
; /* difference if any */
589 int mid
; /* mid point in binary search */
590 int top
, bot
; /* boundaries of bin search, inclusive */
595 mid
= (top
+ bot
) / 2;
607 if (slot_ptr
!= NULL
)
608 *slot_ptr
= bot
; /* place it would have gone */