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. Neither the name of the University nor the names of its contributors
14 * may be used to endorse or promote products derived from this software
15 * without specific prior written permission.
17 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
18 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
20 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
21 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
22 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
23 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
24 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
26 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
29 * @(#) Copyright (c) 1980, 1993 The Regents of the University of California. All rights reserved.
30 * @(#)checknr.c 8.1 (Berkeley) 6/6/93
32 * $DragonFly: src/usr.bin/checknr/checknr.c,v 1.13 2008/11/11 01:02:40 pavalos Exp $
41 #define MAXSTK 100 /* Stack size */
42 #define MAXBR 100 /* Max number of bracket pairs known */
43 #define MAXCMDS 500 /* Max number of commands known */
45 static void addcmd(char *);
46 static void addmac(const char *);
47 static int binsrch(const char *, int *);
48 static void checkknown(const char *);
49 static void chkcmd(const char *);
50 static void complain(int);
51 static int eq(const char *, const char *);
52 static void nomatch(const char *);
54 static void process(FILE *);
55 static void prop(int);
56 static void usage(void);
59 * The stack on which we remember what we've seen so far.
62 int opno
; /* number of opening bracket */
63 int pl
; /* '+', '-', ' ' for \s, 1 for \f, 0 for .ft */
64 int parm
; /* parm to size, font, etc */
65 int lno
; /* line number the thing came in on */
70 * The kinds of opening and closing brackets.
76 /* A few bare bones troff commands */
78 {"sz", "sz"}, /* also \s */
80 {"ft", "ft"}, /* also \f */
110 /* The -me package */
119 /* Things needed by preprocessors */
127 * All commands known to nroff, plus macro packages.
128 * Used so we can complain about unrecognized commands.
130 char knowncmds
[MAXCMDS
][3] = {
131 "$c", "$f", "$h", "$p", "$s", "(b", "(c", "(d", "(f", "(l", "(q", "(t",
132 "(x", "(z", ")b", ")c", ")d", ")f", ")l", ")q", ")t", ")x", ")z", "++",
133 "+c", "1C", "1c", "2C", "2c", "@(", "@)", "@C", "@D", "@F", "@I", "@M",
134 "@c", "@e", "@f", "@h", "@m", "@n", "@o", "@p", "@r", "@t", "@z", "AB",
135 "AE", "AF", "AI", "AL", "AM", "AS", "AT", "AU", "AX", "B", "B1", "B2",
136 "BD", "BE", "BG", "BL", "BS", "BT", "BX", "C1", "C2", "CD", "CM", "CT",
137 "D", "DA", "DE", "DF", "DL", "DS", "DT", "EC", "EF", "EG", "EH", "EM",
138 "EN", "EQ", "EX", "FA", "FD", "FE", "FG", "FJ", "FK", "FL", "FN", "FO",
139 "FQ", "FS", "FV", "FX", "H", "HC", "HD", "HM", "HO", "HU", "I", "ID",
140 "IE", "IH", "IM", "IP", "IX", "IZ", "KD", "KE", "KF", "KQ", "KS", "LB",
141 "LC", "LD", "LE", "LG", "LI", "LP", "MC", "ME", "MF", "MH", "ML", "MR",
142 "MT", "ND", "NE", "NH", "NL", "NP", "NS", "OF", "OH", "OK", "OP", "P",
143 "P1", "PF", "PH", "PP", "PT", "PX", "PY", "QE", "QP", "QS", "R", "RA",
144 "RC", "RE", "RL", "RP", "RQ", "RS", "RT", "S", "S0", "S2", "S3", "SA",
145 "SG", "SH", "SK", "SM", "SP", "SY", "T&", "TA", "TB", "TC", "TD", "TE",
146 "TH", "TL", "TM", "TP", "TQ", "TR", "TS", "TX", "UL", "US", "UX", "VL",
147 "WC", "WH", "XA", "XD", "XE", "XF", "XK", "XP", "XS", "[", "[-", "[0",
148 "[1", "[2", "[3", "[4", "[5", "[<", "[>", "[]", "]", "]-", "]<", "]>",
149 "][", "ab", "ac", "ad", "af", "am", "ar", "as", "b", "ba", "bc", "bd",
150 "bi", "bl", "bp", "br", "bx", "c.", "c2", "cc", "ce", "cf", "ch", "cs",
151 "ct", "cu", "da", "de", "di", "dl", "dn", "ds", "dt", "dw", "dy", "ec",
152 "ef", "eh", "el", "em", "eo", "ep", "ev", "ex", "fc", "fi", "fl", "fo",
153 "fp", "ft", "fz", "hc", "he", "hl", "hp", "ht", "hw", "hx", "hy", "i",
154 "ie", "if", "ig", "in", "ip", "it", "ix", "lc", "lg", "li", "ll", "ln",
155 "lo", "lp", "ls", "lt", "m1", "m2", "m3", "m4", "mc", "mk", "mo", "n1",
156 "n2", "na", "ne", "nf", "nh", "nl", "nm", "nn", "np", "nr", "ns", "nx",
157 "of", "oh", "os", "pa", "pc", "pi", "pl", "pm", "pn", "po", "pp", "ps",
158 "q", "r", "rb", "rd", "re", "rm", "rn", "ro", "rr", "rs", "rt", "sb",
159 "sc", "sh", "sk", "so", "sp", "ss", "st", "sv", "sz", "ta", "tc", "th",
160 "ti", "tl", "tm", "tp", "tr", "u", "uf", "uh", "ul", "vs", "wh", "xp",
164 int lineno
; /* current line number in input file */
165 char line
[256]; /* the current line */
166 const char *cfilename
; /* name of current file */
167 int nfiles
; /* number of files to process */
168 int fflag
; /* -f: ignore \f */
169 int sflag
; /* -s: ignore \s */
170 int ncmds
; /* size of knowncmds */
173 * checknr: check an nroff/troff input file for matching macro calls.
174 * we also attempt to match size and font changes, but only the embedded
175 * kind. These must end in \s0 and \fP resp. Maybe more sophistication
176 * later but for now think of these restrictions as contributions to
177 * structured typesetting.
180 main(int argc
, char **argv
)
187 /* Figure out how many known commands there are */
189 while (ncmds
< MAXCMDS
&& knowncmds
[ncmds
][0] != '\0')
191 while (argc
> 1 && argv
[1][0] == '-') {
194 /* -a: add pairs of macros */
196 if ((strlen(argv
[1]) - 2) % 6 != 0)
198 /* look for empty macro slots */
200 while (i
< MAXBR
&& br
[i
].opbr
[0] != '\0')
203 errx(1, "Only %d known macro-pairs allowed",
206 for (cp
= argv
[1] + 3; cp
[-1]; cp
+= 6) {
207 strncpy(br
[i
].opbr
, cp
, 2);
208 strncpy(br
[i
].clbr
, cp
+ 3, 2);
210 * known pairs are also known cmds
218 /* -c: add known commands */
220 i
= strlen(argv
[1]) - 2;
223 for (cp
= argv
[1] + 3; cp
[-1]; cp
+= 3) {
224 if (cp
[2] && cp
[2] != '.')
232 /* -f: ignore font changes */
237 /* -s: ignore size changes */
250 for (i
= 1; i
< argc
; i
++) {
252 f
= fopen(cfilename
, "r");
254 warn("%s", cfilename
);
271 "usage: checknr [-fs] [-a.xx.yy.xx.yy...] [-c.xx.xx.xx...] "
280 char mac
[5]; /* The current macro or nroff command */
284 for (lineno
= 1; fgets(line
, sizeof(line
), f
); lineno
++) {
285 if (line
[0] == '.') {
287 * find and isolate the macro/command name.
289 strncpy(mac
, line
+ 1, 4);
290 if (isspace(mac
[0])) {
292 printf("Empty command\n");
293 } else if (isspace(mac
[1])) {
295 } else if (isspace(mac
[2])) {
297 } else if (mac
[0] != '\\' || mac
[1] != '\"') {
299 printf("Command too long\n");
303 * Is it a known command?
317 * At this point we process the line looking
320 for (i
= 0; line
[i
]; i
++) {
321 if (line
[i
] == '\\' && (i
== 0 || line
[i
-1] != '\\')) {
322 if (!sflag
&& line
[++i
] == 's') {
329 while (isdigit(line
[++i
]))
330 n
= 10 * n
+ line
[i
] - '0';
333 if (stk
[stktop
].opno
== SZ
) {
337 printf("unmatched \\s0\n");
340 stk
[++stktop
].opno
= SZ
;
342 stk
[stktop
].parm
= n
;
343 stk
[stktop
].lno
= lineno
;
345 } else if (!fflag
&& line
[i
] == 'f') {
348 if (stk
[stktop
].opno
== FT
) {
352 printf("unmatched \\fP\n");
355 stk
[++stktop
].opno
= FT
;
357 stk
[stktop
].parm
= n
;
358 stk
[stktop
].lno
= lineno
;
365 * We've hit the end and look at all this stuff that hasn't been
366 * matched yet! Complain, complain.
368 for (i
= stktop
; i
>= 0; i
--) {
377 printf("Unmatched ");
386 printf(".%s", br
[stk
[i
].opno
].opbr
);
387 else switch(stk
[i
].opno
) {
389 printf("\\s%c%d", stk
[i
].pl
, stk
[i
].parm
);
392 printf("\\f%c", stk
[i
].parm
);
395 printf("Bug: stk[%d].opno = %d = .%s, .%s",
396 i
, stk
[i
].opno
, br
[stk
[i
].opno
].opbr
,
397 br
[stk
[i
].opno
].clbr
);
402 chkcmd(const char *mac
)
407 * Check to see if it matches top of stack.
409 if (stktop
>= 0 && eq(mac
, br
[stk
[stktop
].opno
].clbr
))
410 stktop
--; /* OK. Pop & forget */
412 /* No. Maybe it's an opener */
413 for (i
= 0; br
[i
].opbr
[0] != '\0'; i
++) {
414 if (eq(mac
, br
[i
].opbr
)) {
415 /* Found. Push it. */
417 stk
[stktop
].opno
= i
;
419 stk
[stktop
].parm
= 0;
420 stk
[stktop
].lno
= lineno
;
424 * Maybe it's an unmatched closer.
425 * NOTE: this depends on the fact
426 * that none of the closers can be
429 if (eq(mac
, br
[i
].clbr
)) {
438 nomatch(const char *mac
)
443 * Look for a match further down on stack
444 * If we find one, it suggests that the stuff in
445 * between is supposed to match itself.
447 for (j
= stktop
; j
>= 0; j
--) {
448 if (eq(mac
, br
[stk
[j
].opno
].clbr
)) {
449 /* Found. Make a good diagnostic. */
450 if (j
== stktop
- 2) {
452 * Check for special case \fx..\fR and don't
455 if (stk
[j
+ 1].opno
== FT
&&
456 stk
[j
+ 1].parm
!= 'R' &&
457 stk
[j
+ 2].opno
== FT
&&
458 stk
[j
+ 2].parm
== 'R') {
463 * We have two unmatched frobs. Chances are
464 * they were intended to match, so we mention
469 printf(" does not match %d: ", stk
[j
+ 2].lno
);
473 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: ", mylineno
);
503 checkknown(const char *mac
)
507 if (binsrch(mac
, NULL
) >= 0)
509 if (mac
[0] == '\\' && mac
[1] == '"') /* comments */
513 printf("Unknown command: .%s\n", mac
);
517 * We have a .de xx line in "line". Add xx to the list of known commands.
524 /* grab the macro being defined */
526 while (isspace(*mac
))
530 printf("illegal define: %s\n", myline
);
534 if (isspace(mac
[1]) || mac
[1] == '\\')
540 * Add mac to the list. We should really have some kind of tree
541 * structure here, but the loop below is reasonably fast.
544 addmac(const char *mac
)
548 if (ncmds
>= MAXCMDS
) {
549 errx(1, "Only %d known commands allowed", MAXCMDS
);
552 /* Don't try to add it if it's already in the table. */
553 if (binsrch(mac
, &slot
) >= 0) {
555 printf("binsrch(%s) -> already in table\n", mac
);
560 printf("binsrch(%s) -> %d\n", mac
, slot
);
562 for (i
= ncmds
- 1; i
>= slot
; i
--) {
563 strncpy(knowncmds
[i
+ 1], knowncmds
[i
], 2);
565 strncpy(knowncmds
[slot
], mac
, 2);
568 printf("after: %s %s %s %s %s, %d cmds\n", knowncmds
[slot
-2],
569 knowncmds
[slot
-1], knowncmds
[slot
], knowncmds
[slot
+1],
570 knowncmds
[slot
+2], ncmds
);
575 * Do a binary search in knowncmds for mac.
576 * If found, return the index. If not, return -1.
577 * Also, if not found, and if slot_ptr is not NULL,
578 * set *slot_ptr to where it should have been.
581 binsrch(const char *mac
, int *slot_ptr
)
583 const char *p
; /* pointer to current cmd in list */
584 int d
; /* difference if any */
585 int mid
; /* mid point in binary search */
586 int top
, bot
; /* boundaries of bin search, inclusive */
591 mid
= (top
+ bot
) / 2;
603 if (slot_ptr
!= NULL
)
604 *slot_ptr
= bot
; /* place it would have gone */