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