]> git.cameronkatri.com Git - mandoc.git/blob - man_validate.c
better error reporting for .br .fi .nf with arguments
[mandoc.git] / man_validate.c
1 /* $OpenBSD$ */
2 /*
3 * Copyright (c) 2008, 2009, 2010, 2011 Kristaps Dzonsons <kristaps@bsd.lv>
4 * Copyright (c) 2010, 2012, 2013, 2014 Ingo Schwarze <schwarze@openbsd.org>
5 *
6 * Permission to use, copy, modify, and distribute this software for any
7 * purpose with or without fee is hereby granted, provided that the above
8 * copyright notice and this permission notice appear in all copies.
9 *
10 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17 */
18 #include "config.h"
19
20 #include <sys/types.h>
21
22 #include <assert.h>
23 #include <ctype.h>
24 #include <errno.h>
25 #include <limits.h>
26 #include <stdarg.h>
27 #include <stdlib.h>
28 #include <string.h>
29 #include <time.h>
30
31 #include "man.h"
32 #include "mandoc.h"
33 #include "mandoc_aux.h"
34 #include "libman.h"
35 #include "libmandoc.h"
36
37 #define CHKARGS struct man *man, struct man_node *n
38
39 typedef void (*v_check)(CHKARGS);
40
41 static void check_eq2(CHKARGS);
42 static void check_le1(CHKARGS);
43 static void check_le5(CHKARGS);
44 static void check_par(CHKARGS);
45 static void check_part(CHKARGS);
46 static void check_root(CHKARGS);
47 static void check_text(CHKARGS);
48
49 static void post_AT(CHKARGS);
50 static void post_IP(CHKARGS);
51 static void post_vs(CHKARGS);
52 static void post_fi(CHKARGS);
53 static void post_ft(CHKARGS);
54 static void post_nf(CHKARGS);
55 static void post_TH(CHKARGS);
56 static void post_UC(CHKARGS);
57 static void post_UR(CHKARGS);
58
59 static v_check man_valids[MAN_MAX] = {
60 post_vs, /* br */
61 post_TH, /* TH */
62 NULL, /* SH */
63 NULL, /* SS */
64 NULL, /* TP */
65 check_par, /* LP */
66 check_par, /* PP */
67 check_par, /* P */
68 post_IP, /* IP */
69 NULL, /* HP */
70 NULL, /* SM */
71 NULL, /* SB */
72 NULL, /* BI */
73 NULL, /* IB */
74 NULL, /* BR */
75 NULL, /* RB */
76 NULL, /* R */
77 NULL, /* B */
78 NULL, /* I */
79 NULL, /* IR */
80 NULL, /* RI */
81 post_vs, /* sp */
82 post_nf, /* nf */
83 post_fi, /* fi */
84 NULL, /* RE */
85 check_part, /* RS */
86 NULL, /* DT */
87 post_UC, /* UC */
88 check_le1, /* PD */
89 post_AT, /* AT */
90 NULL, /* in */
91 post_ft, /* ft */
92 check_eq2, /* OP */
93 post_nf, /* EX */
94 post_fi, /* EE */
95 post_UR, /* UR */
96 NULL, /* UE */
97 NULL, /* ll */
98 };
99
100
101 void
102 man_valid_post(struct man *man)
103 {
104 struct man_node *n;
105 v_check *cp;
106
107 n = man->last;
108 if (n->flags & MAN_VALID)
109 return;
110 n->flags |= MAN_VALID;
111
112 switch (n->type) {
113 case MAN_TEXT:
114 check_text(man, n);
115 break;
116 case MAN_ROOT:
117 check_root(man, n);
118 break;
119 case MAN_EQN:
120 /* FALLTHROUGH */
121 case MAN_TBL:
122 break;
123 default:
124 cp = man_valids + n->tok;
125 if (*cp)
126 (*cp)(man, n);
127 break;
128 }
129 }
130
131 static void
132 check_root(CHKARGS)
133 {
134
135 assert((man->flags & (MAN_BLINE | MAN_ELINE)) == 0);
136
137 if (NULL == man->first->child)
138 mandoc_msg(MANDOCERR_DOC_EMPTY, man->parse,
139 n->line, n->pos, NULL);
140 else
141 man->meta.hasbody = 1;
142
143 if (NULL == man->meta.title) {
144 mandoc_msg(MANDOCERR_TH_NOTITLE, man->parse,
145 n->line, n->pos, NULL);
146
147 /*
148 * If a title hasn't been set, do so now (by
149 * implication, date and section also aren't set).
150 */
151
152 man->meta.title = mandoc_strdup("");
153 man->meta.msec = mandoc_strdup("");
154 man->meta.date = man->quick ? mandoc_strdup("") :
155 mandoc_normdate(man->parse, NULL, n->line, n->pos);
156 }
157 }
158
159 static void
160 check_text(CHKARGS)
161 {
162 char *cp, *p;
163
164 if (MAN_LITERAL & man->flags)
165 return;
166
167 cp = n->string;
168 for (p = cp; NULL != (p = strchr(p, '\t')); p++)
169 mandoc_msg(MANDOCERR_FI_TAB, man->parse,
170 n->line, n->pos + (p - cp), NULL);
171 }
172
173 #define INEQ_DEFINE(x, ineq, name) \
174 static void \
175 check_##name(CHKARGS) \
176 { \
177 if (n->nchild ineq (x)) \
178 return; \
179 mandoc_vmsg(MANDOCERR_ARGCOUNT, man->parse, n->line, n->pos, \
180 "line arguments %s %d (have %d)", \
181 #ineq, (x), n->nchild); \
182 }
183
184 INEQ_DEFINE(2, ==, eq2)
185 INEQ_DEFINE(1, <=, le1)
186 INEQ_DEFINE(5, <=, le5)
187
188 static void
189 post_UR(CHKARGS)
190 {
191
192 if (MAN_HEAD == n->type && 1 != n->nchild)
193 mandoc_vmsg(MANDOCERR_ARGCOUNT, man->parse, n->line,
194 n->pos, "line arguments eq 1 (have %d)", n->nchild);
195 check_part(man, n);
196 }
197
198 static void
199 post_ft(CHKARGS)
200 {
201 char *cp;
202 int ok;
203
204 if (0 == n->nchild)
205 return;
206
207 ok = 0;
208 cp = n->child->string;
209 switch (*cp) {
210 case '1':
211 /* FALLTHROUGH */
212 case '2':
213 /* FALLTHROUGH */
214 case '3':
215 /* FALLTHROUGH */
216 case '4':
217 /* FALLTHROUGH */
218 case 'I':
219 /* FALLTHROUGH */
220 case 'P':
221 /* FALLTHROUGH */
222 case 'R':
223 if ('\0' == cp[1])
224 ok = 1;
225 break;
226 case 'B':
227 if ('\0' == cp[1] || ('I' == cp[1] && '\0' == cp[2]))
228 ok = 1;
229 break;
230 case 'C':
231 if ('W' == cp[1] && '\0' == cp[2])
232 ok = 1;
233 break;
234 default:
235 break;
236 }
237
238 if (0 == ok) {
239 mandoc_vmsg(MANDOCERR_FT_BAD, man->parse,
240 n->line, n->pos, "ft %s", cp);
241 *cp = '\0';
242 }
243
244 if (1 < n->nchild)
245 mandoc_vmsg(MANDOCERR_ARGCOUNT, man->parse, n->line,
246 n->pos, "want one child (have %d)", n->nchild);
247 }
248
249 static void
250 check_part(CHKARGS)
251 {
252
253 if (n->type == MAN_BODY && n->child == NULL)
254 mandoc_msg(MANDOCERR_BLK_EMPTY, man->parse,
255 n->line, n->pos, man_macronames[n->tok]);
256 }
257
258 static void
259 check_par(CHKARGS)
260 {
261
262 switch (n->type) {
263 case MAN_BLOCK:
264 if (0 == n->body->nchild)
265 man_node_delete(man, n);
266 break;
267 case MAN_BODY:
268 if (0 == n->nchild)
269 mandoc_vmsg(MANDOCERR_PAR_SKIP,
270 man->parse, n->line, n->pos,
271 "%s empty", man_macronames[n->tok]);
272 break;
273 case MAN_HEAD:
274 if (n->nchild)
275 mandoc_vmsg(MANDOCERR_ARG_SKIP,
276 man->parse, n->line, n->pos,
277 "%s %s%s", man_macronames[n->tok],
278 n->child->string,
279 n->nchild > 1 ? " ..." : "");
280 break;
281 default:
282 break;
283 }
284 }
285
286 static void
287 post_IP(CHKARGS)
288 {
289
290 switch (n->type) {
291 case MAN_BLOCK:
292 if (0 == n->head->nchild && 0 == n->body->nchild)
293 man_node_delete(man, n);
294 break;
295 case MAN_BODY:
296 if (0 == n->parent->head->nchild && 0 == n->nchild)
297 mandoc_vmsg(MANDOCERR_PAR_SKIP,
298 man->parse, n->line, n->pos,
299 "%s empty", man_macronames[n->tok]);
300 break;
301 default:
302 break;
303 }
304 }
305
306 static void
307 post_TH(CHKARGS)
308 {
309 struct man_node *nb;
310 const char *p;
311
312 check_le5(man, n);
313
314 free(man->meta.title);
315 free(man->meta.vol);
316 free(man->meta.source);
317 free(man->meta.msec);
318 free(man->meta.date);
319
320 man->meta.title = man->meta.vol = man->meta.date =
321 man->meta.msec = man->meta.source = NULL;
322
323 nb = n;
324
325 /* ->TITLE<- MSEC DATE SOURCE VOL */
326
327 n = n->child;
328 if (n && n->string) {
329 for (p = n->string; '\0' != *p; p++) {
330 /* Only warn about this once... */
331 if (isalpha((unsigned char)*p) &&
332 ! isupper((unsigned char)*p)) {
333 mandoc_vmsg(MANDOCERR_TITLE_CASE,
334 man->parse, n->line,
335 n->pos + (p - n->string),
336 "TH %s", n->string);
337 break;
338 }
339 }
340 man->meta.title = mandoc_strdup(n->string);
341 } else {
342 man->meta.title = mandoc_strdup("");
343 mandoc_msg(MANDOCERR_TH_NOTITLE, man->parse,
344 nb->line, nb->pos, "TH");
345 }
346
347 /* TITLE ->MSEC<- DATE SOURCE VOL */
348
349 if (n)
350 n = n->next;
351 if (n && n->string)
352 man->meta.msec = mandoc_strdup(n->string);
353 else {
354 man->meta.msec = mandoc_strdup("");
355 mandoc_vmsg(MANDOCERR_MSEC_MISSING, man->parse,
356 nb->line, nb->pos, "TH %s", man->meta.title);
357 }
358
359 /* TITLE MSEC ->DATE<- SOURCE VOL */
360
361 if (n)
362 n = n->next;
363 if (n && n->string && '\0' != n->string[0]) {
364 man->meta.date = man->quick ?
365 mandoc_strdup(n->string) :
366 mandoc_normdate(man->parse, n->string,
367 n->line, n->pos);
368 } else {
369 man->meta.date = mandoc_strdup("");
370 mandoc_msg(MANDOCERR_DATE_MISSING, man->parse,
371 n ? n->line : nb->line,
372 n ? n->pos : nb->pos, "TH");
373 }
374
375 /* TITLE MSEC DATE ->SOURCE<- VOL */
376
377 if (n && (n = n->next))
378 man->meta.source = mandoc_strdup(n->string);
379 else if (man->defos != NULL)
380 man->meta.source = mandoc_strdup(man->defos);
381
382 /* TITLE MSEC DATE SOURCE ->VOL<- */
383 /* If missing, use the default VOL name for MSEC. */
384
385 if (n && (n = n->next))
386 man->meta.vol = mandoc_strdup(n->string);
387 else if ('\0' != man->meta.msec[0] &&
388 (NULL != (p = mandoc_a2msec(man->meta.msec))))
389 man->meta.vol = mandoc_strdup(p);
390
391 /*
392 * Remove the `TH' node after we've processed it for our
393 * meta-data.
394 */
395 man_node_delete(man, man->last);
396 }
397
398 static void
399 post_nf(CHKARGS)
400 {
401
402 if (man->flags & MAN_LITERAL)
403 mandoc_msg(MANDOCERR_NF_SKIP, man->parse,
404 n->line, n->pos, "nf");
405
406 man->flags |= MAN_LITERAL;
407 }
408
409 static void
410 post_fi(CHKARGS)
411 {
412
413 if ( ! (MAN_LITERAL & man->flags))
414 mandoc_msg(MANDOCERR_FI_SKIP, man->parse,
415 n->line, n->pos, "fi");
416
417 man->flags &= ~MAN_LITERAL;
418 }
419
420 static void
421 post_UC(CHKARGS)
422 {
423 static const char * const bsd_versions[] = {
424 "3rd Berkeley Distribution",
425 "4th Berkeley Distribution",
426 "4.2 Berkeley Distribution",
427 "4.3 Berkeley Distribution",
428 "4.4 Berkeley Distribution",
429 };
430
431 const char *p, *s;
432
433 n = n->child;
434
435 if (NULL == n || MAN_TEXT != n->type)
436 p = bsd_versions[0];
437 else {
438 s = n->string;
439 if (0 == strcmp(s, "3"))
440 p = bsd_versions[0];
441 else if (0 == strcmp(s, "4"))
442 p = bsd_versions[1];
443 else if (0 == strcmp(s, "5"))
444 p = bsd_versions[2];
445 else if (0 == strcmp(s, "6"))
446 p = bsd_versions[3];
447 else if (0 == strcmp(s, "7"))
448 p = bsd_versions[4];
449 else
450 p = bsd_versions[0];
451 }
452
453 free(man->meta.source);
454 man->meta.source = mandoc_strdup(p);
455 }
456
457 static void
458 post_AT(CHKARGS)
459 {
460 static const char * const unix_versions[] = {
461 "7th Edition",
462 "System III",
463 "System V",
464 "System V Release 2",
465 };
466
467 const char *p, *s;
468 struct man_node *nn;
469
470 n = n->child;
471
472 if (NULL == n || MAN_TEXT != n->type)
473 p = unix_versions[0];
474 else {
475 s = n->string;
476 if (0 == strcmp(s, "3"))
477 p = unix_versions[0];
478 else if (0 == strcmp(s, "4"))
479 p = unix_versions[1];
480 else if (0 == strcmp(s, "5")) {
481 nn = n->next;
482 if (nn && MAN_TEXT == nn->type && nn->string[0])
483 p = unix_versions[3];
484 else
485 p = unix_versions[2];
486 } else
487 p = unix_versions[0];
488 }
489
490 free(man->meta.source);
491 man->meta.source = mandoc_strdup(p);
492 }
493
494 static void
495 post_vs(CHKARGS)
496 {
497
498 if (n->tok == MAN_sp)
499 check_le1(man, n);
500
501 if (NULL != n->prev)
502 return;
503
504 switch (n->parent->tok) {
505 case MAN_SH:
506 /* FALLTHROUGH */
507 case MAN_SS:
508 mandoc_vmsg(MANDOCERR_PAR_SKIP, man->parse, n->line, n->pos,
509 "%s after %s", man_macronames[n->tok],
510 man_macronames[n->parent->tok]);
511 /* FALLTHROUGH */
512 case MAN_MAX:
513 /*
514 * Don't warn about this because it occurs in pod2man
515 * and would cause considerable (unfixable) warnage.
516 */
517 man_node_delete(man, n);
518 break;
519 default:
520 break;
521 }
522 }