1 /* Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T */
2 /* All Rights Reserved */
6 * Copyright (c) 1980 Regents of the University of California.
7 * All rights reserved. The Berkeley software License Agreement
8 * specifies the terms and conditions for redistribution.
12 * Copyright 2004 Sun Microsystems, Inc. All rights reserved.
13 * Use is subject to license terms.
16 #pragma ident "%Z%%M% %I% %E% SMI"
19 * checknr: check an nroff/troff input file for matching macro calls.
20 * we also attempt to match size and font changes, but only the embedded
21 * kind. These must end in \s0 and \fP resp. Maybe more sophistication
22 * later but for now think of these restrictions as contributions to
23 * structured typesetting.
32 #define MAXSTK 100 /* Stack size */
34 #define MAXBR 100 /* Max number of bracket pairs known */
35 #define MAXCMDS 500 /* Max number of commands known */
38 * The stack on which we remember what we've seen so far.
40 static struct stkstr
{
41 int opno
; /* number of opening bracket */
42 int pl
; /* '+', '-', ' ' for \s, 1 for \f, 0 for .ft */
43 int parm
; /* parm to size, font, etc */
44 int lno
; /* line number the thing came in in */
49 * The kinds of opening and closing brackets.
55 /* A few bare bones troff commands */
57 "sz", "sz", /* also \s */
59 "ft", "ft", /* also \f */
98 /* Things needed by preprocessors */
107 * All commands known to nroff, plus macro packages.
108 * Used so we can complain about unrecognized commands.
110 static char *knowncmds
[MAXCMDS
] = {
111 "$c", "$f", "$h", "$p", "$s", "(b", "(c", "(d", "(f", "(l", "(q", "(t",
112 "(x", "(z", ")b", ")c", ")d", ")f", ")l", ")q", ")t", ")x", ")z", "++",
113 "+c", "1C", "1c", "2C", "2c", "@(", "@)", "@C", "@D", "@F", "@I", "@M",
114 "@c", "@e", "@f", "@h", "@m", "@n", "@o", "@p", "@r", "@t", "@z", "AB",
115 "AE", "AF", "AI", "AL", "AM", "AS", "AT", "AU", "AX", "B", "B1", "B2",
116 "BD", "BE", "BG", "BL", "BS", "BT", "BX", "C1", "C2", "CD", "CM", "CT",
117 "D", "DA", "DE", "DF", "DL", "DS", "DT", "EC", "EF", "EG", "EH", "EM",
118 "EN", "EQ", "EX", "FA", "FD", "FE", "FG", "FJ", "FK", "FL", "FN", "FO",
119 "FQ", "FS", "FV", "FX", "H", "HC", "HD", "HM", "HO", "HU", "I", "ID",
120 "IE", "IH", "IM", "IP", "IX", "IZ", "KD", "KE", "KF", "KQ", "KS", "LB",
121 "LC", "LD", "LE", "LG", "LI", "LP", "MC", "ME", "MF", "MH", "ML", "MR",
122 "MT", "ND", "NE", "NH", "NL", "NP", "NS", "OF", "OH", "OK", "OP", "P",
123 "P1", "PF", "PH", "PP", "PT", "PX", "PY", "QE", "QP", "QS", "R", "RA",
124 "RC", "RE", "RL", "RP", "RQ", "RS", "RT", "S", "S0", "S2", "S3", "SA",
125 "SG", "SH", "SK", "SM", "SP", "SY", "T&", "TA", "TB", "TC", "TD", "TE",
126 "TH", "TL", "TM", "TP", "TQ", "TR", "TS", "TX", "UL", "US", "UX", "VL",
127 "WC", "WH", "XA", "XD", "XE", "XF", "XK", "XP", "XS", "[", "[-", "[0",
128 "[1", "[2", "[3", "[4", "[5", "[<", "[>", "[]", "]", "]-", "]<", "]>",
129 "][", "ab", "ac", "ad", "af", "am", "ar", "as", "b", "ba", "bc", "bd",
130 "bi", "bl", "bp", "br", "bx", "c.", "c2", "cc", "ce", "cf", "ch", "cs",
131 "ct", "cu", "da", "de", "di", "dl", "dn", "ds", "dt", "dw", "dy", "ec",
132 "ef", "eh", "el", "em", "eo", "ep", "ev", "ex", "fc", "fi", "fl", "fo",
133 "fp", "ft", "fz", "hc", "he", "hl", "hp", "ht", "hw", "hx", "hy", "i",
134 "ie", "if", "ig", "in", "ip", "it", "ix", "lc", "lg", "li", "ll", "ln",
135 "lo", "lp", "ls", "lt", "m1", "m2", "m3", "m4", "mc", "mk", "mo", "n1",
136 "n2", "na", "ne", "nf", "nh", "nl", "nm", "nn", "np", "nr", "ns", "nx",
137 "of", "oh", "os", "pa", "pc", "pi", "pl", "pm", "pn", "po", "pp", "ps",
138 "q", "r", "rb", "rd", "re", "rm", "rn", "ro", "rr", "rs", "rt", "sb",
139 "sc", "sh", "sk", "so", "sp", "ss", "st", "sv", "sz", "ta", "tc", "th",
140 "ti", "tl", "tm", "tp", "tr", "u", "uf", "uh", "ul", "vs", "wh", "xp",
144 static int lineno
; /* current line number in input file */
145 static char line
[256]; /* the current line */
146 static char *cfilename
; /* name of current file */
147 static int nfiles
; /* number of files to process */
148 static int fflag
; /* -f: ignore \f */
149 static int sflag
; /* -s: ignore \s */
150 static int ncmds
; /* size of knowncmds */
151 static int slot
; /* slot in knowncmds found by binsrch */
153 static void growstk();
155 static void process(FILE *f
);
156 static void complain(int i
);
157 static void prop(int i
);
158 static void chkcmd(char *line
, char *mac
);
159 static void nomatch(char *mac
);
160 static int eq(char *s1
, char *s2
);
161 static void pe(int lineno
);
162 static void checkknown(char *mac
);
163 static void addcmd(char *line
);
164 static void addmac(char *mac
);
165 static int binsrch(char *mac
);
171 if (stktop
>= maxstk
) {
173 stk
= (struct stkstr
*)realloc(stk
,
174 sizeof (struct stkstr
) * maxstk
);
188 (void) setlocale(LC_ALL
, "");
189 #if !defined(TEXT_DOMAIN)
190 #define TEXT_DOMAIN "SYS_TEST"
192 (void) textdomain(TEXT_DOMAIN
);
193 stk
= (struct stkstr
*)calloc(sizeof (struct stkstr
), 100);
195 /* Figure out how many known commands there are */
196 while (knowncmds
[ncmds
])
198 while (argc
> 1 && argv
[1][0] == '-') {
199 switch (argv
[1][1]) {
201 /* -a: add pairs of macros */
203 i
= strlen(argv
[1]) - 2;
206 /* look for empty macro slots */
207 for (i
= 0; br
[i
].opbr
; i
++)
209 for (cp
= argv
[1]+3; cp
[-1]; cp
+= 6) {
210 br
[i
].opbr
= malloc(3);
211 (void) strncpy(br
[i
].opbr
, cp
, 2);
212 br
[i
].clbr
= malloc(3);
213 (void) strncpy(br
[i
].clbr
, cp
+3, 2);
214 /* knows pairs are also known cmds */
221 /* -c: add known commands */
223 i
= strlen(argv
[1]) - 2;
226 for (cp
= argv
[1]+3; cp
[-1]; cp
+= 3) {
227 if (cp
[2] && cp
[2] != '.')
229 (void) strncpy(b1
, cp
, 2);
234 /* -f: ignore font changes */
239 /* -s: ignore size changes */
252 for (i
= 1; i
< argc
; i
++) {
254 f
= fopen(cfilename
, "r");
272 (void) printf(gettext("Usage: \
273 checknr [ -fs ] [ -a.xx.yy.xx.yy...] [-c.xx.xx.xx...] [ filename .. ]\n"));
281 char mac
[5]; /* The current macro or nroff command */
285 for (lineno
= 1; fgets(line
, sizeof (line
), f
); lineno
++) {
286 if (line
[0] == '.') {
288 * find and isolate the macro/command name.
290 (void) strncpy(mac
, line
+1, 4);
291 if (isspace(mac
[0])) {
293 (void) printf(gettext("Empty command\n"));
294 } else if (isspace(mac
[1])) {
296 } else if (isspace(mac
[2])) {
298 } else if (mac
[0] != '\\' || mac
[1] != '\"') {
300 (void) printf(gettext("Command too long\n"));
304 * Is it a known command?
318 * At this point we process the line looking
321 for (i
= 0; line
[i
]; i
++)
322 if (line
[i
] == '\\' && (i
== 0 || line
[i
-1] != '\\')) {
323 if (!sflag
&& line
[++i
] == 's') {
330 while (isdigit(line
[++i
]))
331 n
= 10 * n
+ line
[i
] - '0';
334 if (stk
[stktop
].opno
== SZ
) {
339 gettext("unmatched \\s0\n"));
343 stk
[stktop
].opno
= SZ
;
345 stk
[stktop
].parm
= n
;
346 stk
[stktop
].lno
= lineno
;
348 } else if (!fflag
&& line
[i
] == 'f') {
351 if (stk
[stktop
].opno
== FT
) {
356 gettext("unmatched \\fP\n"));
360 stk
[stktop
].opno
= FT
;
362 stk
[stktop
].parm
= n
;
363 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 (void) printf(gettext("Unmatched "));
390 (void) printf(".%s", br
[stk
[i
].opno
].opbr
);
391 else switch (stk
[i
].opno
) {
393 (void) printf("\\s%c%d", stk
[i
].pl
, stk
[i
].parm
);
396 (void) printf("\\f%c", stk
[i
].parm
);
399 (void) printf(gettext("Bug: stk[%d].opno = %d = .%s, .%s"),
400 i
, stk
[i
].opno
, br
[stk
[i
].opno
].opbr
,
401 br
[stk
[i
].opno
].clbr
);
407 chkcmd(char *line
, 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
)) {
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
&&
461 stk
[j
+1].parm
!= 'R' &&
462 stk
[j
+2].opno
== FT
&&
463 stk
[j
+2].parm
== 'R') {
468 * We have two unmatched frobs. Chances are
469 * they were intended to match, so we mention
474 (void) printf(gettext(" does not match %d: "),
478 } else for (i
= j
+1; i
<= stktop
; i
++) {
484 /* Didn't find one. Throw this away. */
486 (void) printf(gettext("Unmatched .%s\n"), mac
);
489 /* eq: are two strings equal? */
491 eq(char *s1
, char *s2
)
493 return (strcmp(s1
, s2
) == 0);
496 /* print the first part of an error message, given the line number */
501 (void) printf("%s: ", cfilename
);
502 (void) printf("%d: ", lineno
);
506 checkknown(char *mac
)
511 if (binsrch(mac
) >= 0)
513 if (mac
[0] == '\\' && mac
[1] == '"') /* comments */
517 (void) printf(gettext("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 (void) printf(gettext("illegal define: %s\n"), line
);
538 if (isspace(mac
[1]) || mac
[1] == '\\')
540 if (ncmds
>= MAXCMDS
) {
541 (void) printf(gettext("Only %d known commands allowed\n"),
549 * Add mac to the list. We should really have some kind of tree
550 * structure here but this is a quick-and-dirty job and I just don't
551 * have time to mess with it. (I wonder if this will come back to haunt
552 * me someday?) Anyway, I claim that .de is fairly rare in user
553 * nroff programs, and the loop below is pretty fast.
558 char **src
, **dest
, **loc
;
560 if (binsrch(mac
) >= 0) { /* it's OK to redefine something */
562 (void) printf("binsrch(%s) -> already in table\n", mac
);
566 /* binsrch sets slot as a side effect */
568 printf("binsrch(%s) -> %d\n", mac
, slot
);
570 loc
= &knowncmds
[slot
];
571 src
= &knowncmds
[ncmds
-1];
576 (void) strcpy(*loc
, mac
);
579 (void) printf("after: %s %s %s %s %s, %d cmds\n",
580 knowncmds
[slot
-2], knowncmds
[slot
-1], knowncmds
[slot
],
581 knowncmds
[slot
+1], knowncmds
[slot
+2], ncmds
);
586 * Do a binary search in knowncmds for mac.
587 * If found, return the index. If not, return -1.
592 char *p
; /* pointer to current cmd in list */
593 int d
; /* difference if any */
594 int mid
; /* mid point in binary search */
595 int top
, bot
; /* boundaries of bin search, inclusive */
612 slot
= bot
; /* place it would have gone */