]> git.cameronkatri.com Git - mandoc.git/blob - mdoc_validate.c
Introduce the concept of nodes that are semantically transparent:
[mandoc.git] / mdoc_validate.c
1 /* $Id: mdoc_validate.c,v 1.378 2020/02/27 01:43:52 schwarze Exp $ */
2 /*
3 * Copyright (c) 2008-2012 Kristaps Dzonsons <kristaps@bsd.lv>
4 * Copyright (c) 2010-2020 Ingo Schwarze <schwarze@openbsd.org>
5 * Copyright (c) 2010 Joerg Sonnenberger <joerg@netbsd.org>
6 *
7 * Permission to use, copy, modify, and distribute this software for any
8 * purpose with or without fee is hereby granted, provided that the above
9 * copyright notice and this permission notice appear in all copies.
10 *
11 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHORS DISCLAIM ALL WARRANTIES
12 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
13 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR
14 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
15 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
16 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
17 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
18 */
19 #include "config.h"
20
21 #include <sys/types.h>
22 #ifndef OSNAME
23 #include <sys/utsname.h>
24 #endif
25
26 #include <assert.h>
27 #include <ctype.h>
28 #include <limits.h>
29 #include <stdio.h>
30 #include <stdlib.h>
31 #include <string.h>
32 #include <time.h>
33
34 #include "mandoc_aux.h"
35 #include "mandoc.h"
36 #include "mandoc_xr.h"
37 #include "roff.h"
38 #include "mdoc.h"
39 #include "libmandoc.h"
40 #include "roff_int.h"
41 #include "libmdoc.h"
42
43 /* FIXME: .Bl -diag can't have non-text children in HEAD. */
44
45 #define POST_ARGS struct roff_man *mdoc
46
47 enum check_ineq {
48 CHECK_LT,
49 CHECK_GT,
50 CHECK_EQ
51 };
52
53 typedef void (*v_post)(POST_ARGS);
54
55 static int build_list(struct roff_man *, int);
56 static void check_argv(struct roff_man *,
57 struct roff_node *, struct mdoc_argv *);
58 static void check_args(struct roff_man *, struct roff_node *);
59 static void check_text(struct roff_man *, int, int, char *);
60 static void check_text_em(struct roff_man *, int, int, char *);
61 static void check_toptext(struct roff_man *, int, int, const char *);
62 static int child_an(const struct roff_node *);
63 static size_t macro2len(enum roff_tok);
64 static void rewrite_macro2len(struct roff_man *, char **);
65 static int similar(const char *, const char *);
66
67 static void post_abort(POST_ARGS) __attribute__((__noreturn__));
68 static void post_an(POST_ARGS);
69 static void post_an_norm(POST_ARGS);
70 static void post_at(POST_ARGS);
71 static void post_bd(POST_ARGS);
72 static void post_bf(POST_ARGS);
73 static void post_bk(POST_ARGS);
74 static void post_bl(POST_ARGS);
75 static void post_bl_block(POST_ARGS);
76 static void post_bl_head(POST_ARGS);
77 static void post_bl_norm(POST_ARGS);
78 static void post_bx(POST_ARGS);
79 static void post_defaults(POST_ARGS);
80 static void post_display(POST_ARGS);
81 static void post_dd(POST_ARGS);
82 static void post_delim(POST_ARGS);
83 static void post_delim_nb(POST_ARGS);
84 static void post_dt(POST_ARGS);
85 static void post_en(POST_ARGS);
86 static void post_es(POST_ARGS);
87 static void post_eoln(POST_ARGS);
88 static void post_ex(POST_ARGS);
89 static void post_fa(POST_ARGS);
90 static void post_fn(POST_ARGS);
91 static void post_fname(POST_ARGS);
92 static void post_fo(POST_ARGS);
93 static void post_hyph(POST_ARGS);
94 static void post_ignpar(POST_ARGS);
95 static void post_it(POST_ARGS);
96 static void post_lb(POST_ARGS);
97 static void post_nd(POST_ARGS);
98 static void post_nm(POST_ARGS);
99 static void post_ns(POST_ARGS);
100 static void post_obsolete(POST_ARGS);
101 static void post_os(POST_ARGS);
102 static void post_par(POST_ARGS);
103 static void post_prevpar(POST_ARGS);
104 static void post_root(POST_ARGS);
105 static void post_rs(POST_ARGS);
106 static void post_rv(POST_ARGS);
107 static void post_sh(POST_ARGS);
108 static void post_sh_head(POST_ARGS);
109 static void post_sh_name(POST_ARGS);
110 static void post_sh_see_also(POST_ARGS);
111 static void post_sh_authors(POST_ARGS);
112 static void post_sm(POST_ARGS);
113 static void post_st(POST_ARGS);
114 static void post_std(POST_ARGS);
115 static void post_sx(POST_ARGS);
116 static void post_tg(POST_ARGS);
117 static void post_useless(POST_ARGS);
118 static void post_xr(POST_ARGS);
119 static void post_xx(POST_ARGS);
120
121 static const v_post mdoc_valids[MDOC_MAX - MDOC_Dd] = {
122 post_dd, /* Dd */
123 post_dt, /* Dt */
124 post_os, /* Os */
125 post_sh, /* Sh */
126 post_ignpar, /* Ss */
127 post_par, /* Pp */
128 post_display, /* D1 */
129 post_display, /* Dl */
130 post_display, /* Bd */
131 NULL, /* Ed */
132 post_bl, /* Bl */
133 NULL, /* El */
134 post_it, /* It */
135 post_delim_nb, /* Ad */
136 post_an, /* An */
137 NULL, /* Ap */
138 post_defaults, /* Ar */
139 NULL, /* Cd */
140 post_delim_nb, /* Cm */
141 post_delim_nb, /* Dv */
142 post_delim_nb, /* Er */
143 post_delim_nb, /* Ev */
144 post_ex, /* Ex */
145 post_fa, /* Fa */
146 NULL, /* Fd */
147 post_delim_nb, /* Fl */
148 post_fn, /* Fn */
149 post_delim_nb, /* Ft */
150 post_delim_nb, /* Ic */
151 post_delim_nb, /* In */
152 post_defaults, /* Li */
153 post_nd, /* Nd */
154 post_nm, /* Nm */
155 post_delim_nb, /* Op */
156 post_abort, /* Ot */
157 post_defaults, /* Pa */
158 post_rv, /* Rv */
159 post_st, /* St */
160 post_delim_nb, /* Va */
161 post_delim_nb, /* Vt */
162 post_xr, /* Xr */
163 NULL, /* %A */
164 post_hyph, /* %B */ /* FIXME: can be used outside Rs/Re. */
165 NULL, /* %D */
166 NULL, /* %I */
167 NULL, /* %J */
168 post_hyph, /* %N */
169 post_hyph, /* %O */
170 NULL, /* %P */
171 post_hyph, /* %R */
172 post_hyph, /* %T */ /* FIXME: can be used outside Rs/Re. */
173 NULL, /* %V */
174 NULL, /* Ac */
175 NULL, /* Ao */
176 post_delim_nb, /* Aq */
177 post_at, /* At */
178 NULL, /* Bc */
179 post_bf, /* Bf */
180 NULL, /* Bo */
181 NULL, /* Bq */
182 post_xx, /* Bsx */
183 post_bx, /* Bx */
184 post_obsolete, /* Db */
185 NULL, /* Dc */
186 NULL, /* Do */
187 NULL, /* Dq */
188 NULL, /* Ec */
189 NULL, /* Ef */
190 post_delim_nb, /* Em */
191 NULL, /* Eo */
192 post_xx, /* Fx */
193 post_delim_nb, /* Ms */
194 NULL, /* No */
195 post_ns, /* Ns */
196 post_xx, /* Nx */
197 post_xx, /* Ox */
198 NULL, /* Pc */
199 NULL, /* Pf */
200 NULL, /* Po */
201 post_delim_nb, /* Pq */
202 NULL, /* Qc */
203 post_delim_nb, /* Ql */
204 NULL, /* Qo */
205 post_delim_nb, /* Qq */
206 NULL, /* Re */
207 post_rs, /* Rs */
208 NULL, /* Sc */
209 NULL, /* So */
210 post_delim_nb, /* Sq */
211 post_sm, /* Sm */
212 post_sx, /* Sx */
213 post_delim_nb, /* Sy */
214 post_useless, /* Tn */
215 post_xx, /* Ux */
216 NULL, /* Xc */
217 NULL, /* Xo */
218 post_fo, /* Fo */
219 NULL, /* Fc */
220 NULL, /* Oo */
221 NULL, /* Oc */
222 post_bk, /* Bk */
223 NULL, /* Ek */
224 post_eoln, /* Bt */
225 post_obsolete, /* Hf */
226 post_obsolete, /* Fr */
227 post_eoln, /* Ud */
228 post_lb, /* Lb */
229 post_abort, /* Lp */
230 post_delim_nb, /* Lk */
231 post_defaults, /* Mt */
232 post_delim_nb, /* Brq */
233 NULL, /* Bro */
234 NULL, /* Brc */
235 NULL, /* %C */
236 post_es, /* Es */
237 post_en, /* En */
238 post_xx, /* Dx */
239 NULL, /* %Q */
240 NULL, /* %U */
241 NULL, /* Ta */
242 post_tg, /* Tg */
243 };
244
245 #define RSORD_MAX 14 /* Number of `Rs' blocks. */
246
247 static const enum roff_tok rsord[RSORD_MAX] = {
248 MDOC__A,
249 MDOC__T,
250 MDOC__B,
251 MDOC__I,
252 MDOC__J,
253 MDOC__R,
254 MDOC__N,
255 MDOC__V,
256 MDOC__U,
257 MDOC__P,
258 MDOC__Q,
259 MDOC__C,
260 MDOC__D,
261 MDOC__O
262 };
263
264 static const char * const secnames[SEC__MAX] = {
265 NULL,
266 "NAME",
267 "LIBRARY",
268 "SYNOPSIS",
269 "DESCRIPTION",
270 "CONTEXT",
271 "IMPLEMENTATION NOTES",
272 "RETURN VALUES",
273 "ENVIRONMENT",
274 "FILES",
275 "EXIT STATUS",
276 "EXAMPLES",
277 "DIAGNOSTICS",
278 "COMPATIBILITY",
279 "ERRORS",
280 "SEE ALSO",
281 "STANDARDS",
282 "HISTORY",
283 "AUTHORS",
284 "CAVEATS",
285 "BUGS",
286 "SECURITY CONSIDERATIONS",
287 NULL
288 };
289
290
291 /* Validate the subtree rooted at mdoc->last. */
292 void
293 mdoc_validate(struct roff_man *mdoc)
294 {
295 struct roff_node *n, *np;
296 const v_post *p;
297
298 /*
299 * Translate obsolete macros to modern macros first
300 * such that later code does not need to look
301 * for the obsolete versions.
302 */
303
304 n = mdoc->last;
305 switch (n->tok) {
306 case MDOC_Lp:
307 n->tok = MDOC_Pp;
308 break;
309 case MDOC_Ot:
310 post_obsolete(mdoc);
311 n->tok = MDOC_Ft;
312 break;
313 default:
314 break;
315 }
316
317 /*
318 * Iterate over all children, recursing into each one
319 * in turn, depth-first.
320 */
321
322 mdoc->last = mdoc->last->child;
323 while (mdoc->last != NULL) {
324 mdoc_validate(mdoc);
325 if (mdoc->last == n)
326 mdoc->last = mdoc->last->child;
327 else
328 mdoc->last = mdoc->last->next;
329 }
330
331 /* Finally validate the macro itself. */
332
333 mdoc->last = n;
334 mdoc->next = ROFF_NEXT_SIBLING;
335 switch (n->type) {
336 case ROFFT_TEXT:
337 np = n->parent;
338 if (n->sec != SEC_SYNOPSIS ||
339 (np->tok != MDOC_Cd && np->tok != MDOC_Fd))
340 check_text(mdoc, n->line, n->pos, n->string);
341 if ((n->flags & NODE_NOFILL) == 0 &&
342 (np->tok != MDOC_It || np->type != ROFFT_HEAD ||
343 np->parent->parent->norm->Bl.type != LIST_diag))
344 check_text_em(mdoc, n->line, n->pos, n->string);
345 if (np->tok == MDOC_It || (np->type == ROFFT_BODY &&
346 (np->tok == MDOC_Sh || np->tok == MDOC_Ss)))
347 check_toptext(mdoc, n->line, n->pos, n->string);
348 break;
349 case ROFFT_COMMENT:
350 case ROFFT_EQN:
351 case ROFFT_TBL:
352 break;
353 case ROFFT_ROOT:
354 post_root(mdoc);
355 break;
356 default:
357 check_args(mdoc, mdoc->last);
358
359 /*
360 * Closing delimiters are not special at the
361 * beginning of a block, opening delimiters
362 * are not special at the end.
363 */
364
365 if (n->child != NULL)
366 n->child->flags &= ~NODE_DELIMC;
367 if (n->last != NULL)
368 n->last->flags &= ~NODE_DELIMO;
369
370 /* Call the macro's postprocessor. */
371
372 if (n->tok < ROFF_MAX) {
373 roff_validate(mdoc);
374 break;
375 }
376
377 assert(n->tok >= MDOC_Dd && n->tok < MDOC_MAX);
378 p = mdoc_valids + (n->tok - MDOC_Dd);
379 if (*p)
380 (*p)(mdoc);
381 if (mdoc->last == n)
382 mdoc_state(mdoc, n);
383 break;
384 }
385 }
386
387 static void
388 check_args(struct roff_man *mdoc, struct roff_node *n)
389 {
390 int i;
391
392 if (NULL == n->args)
393 return;
394
395 assert(n->args->argc);
396 for (i = 0; i < (int)n->args->argc; i++)
397 check_argv(mdoc, n, &n->args->argv[i]);
398 }
399
400 static void
401 check_argv(struct roff_man *mdoc, struct roff_node *n, struct mdoc_argv *v)
402 {
403 int i;
404
405 for (i = 0; i < (int)v->sz; i++)
406 check_text(mdoc, v->line, v->pos, v->value[i]);
407 }
408
409 static void
410 check_text(struct roff_man *mdoc, int ln, int pos, char *p)
411 {
412 char *cp;
413
414 if (mdoc->last->flags & NODE_NOFILL)
415 return;
416
417 for (cp = p; NULL != (p = strchr(p, '\t')); p++)
418 mandoc_msg(MANDOCERR_FI_TAB, ln, pos + (int)(p - cp), NULL);
419 }
420
421 static void
422 check_text_em(struct roff_man *mdoc, int ln, int pos, char *p)
423 {
424 const struct roff_node *np, *nn;
425 char *cp;
426
427 np = mdoc->last->prev;
428 nn = mdoc->last->next;
429
430 /* Look for em-dashes wrongly encoded as "--". */
431
432 for (cp = p; *cp != '\0'; cp++) {
433 if (cp[0] != '-' || cp[1] != '-')
434 continue;
435 cp++;
436
437 /* Skip input sequences of more than two '-'. */
438
439 if (cp[1] == '-') {
440 while (cp[1] == '-')
441 cp++;
442 continue;
443 }
444
445 /* Skip "--" directly attached to something else. */
446
447 if ((cp - p > 1 && cp[-2] != ' ') ||
448 (cp[1] != '\0' && cp[1] != ' '))
449 continue;
450
451 /* Require a letter right before or right afterwards. */
452
453 if ((cp - p > 2 ?
454 isalpha((unsigned char)cp[-3]) :
455 np != NULL &&
456 np->type == ROFFT_TEXT &&
457 *np->string != '\0' &&
458 isalpha((unsigned char)np->string[
459 strlen(np->string) - 1])) ||
460 (cp[1] != '\0' && cp[2] != '\0' ?
461 isalpha((unsigned char)cp[2]) :
462 nn != NULL &&
463 nn->type == ROFFT_TEXT &&
464 isalpha((unsigned char)*nn->string))) {
465 mandoc_msg(MANDOCERR_DASHDASH,
466 ln, pos + (int)(cp - p) - 1, NULL);
467 break;
468 }
469 }
470 }
471
472 static void
473 check_toptext(struct roff_man *mdoc, int ln, int pos, const char *p)
474 {
475 const char *cp, *cpr;
476
477 if (*p == '\0')
478 return;
479
480 if ((cp = strstr(p, "OpenBSD")) != NULL)
481 mandoc_msg(MANDOCERR_BX, ln, pos + (int)(cp - p), "Ox");
482 if ((cp = strstr(p, "NetBSD")) != NULL)
483 mandoc_msg(MANDOCERR_BX, ln, pos + (int)(cp - p), "Nx");
484 if ((cp = strstr(p, "FreeBSD")) != NULL)
485 mandoc_msg(MANDOCERR_BX, ln, pos + (int)(cp - p), "Fx");
486 if ((cp = strstr(p, "DragonFly")) != NULL)
487 mandoc_msg(MANDOCERR_BX, ln, pos + (int)(cp - p), "Dx");
488
489 cp = p;
490 while ((cp = strstr(cp + 1, "()")) != NULL) {
491 for (cpr = cp - 1; cpr >= p; cpr--)
492 if (*cpr != '_' && !isalnum((unsigned char)*cpr))
493 break;
494 if ((cpr < p || *cpr == ' ') && cpr + 1 < cp) {
495 cpr++;
496 mandoc_msg(MANDOCERR_FUNC, ln, pos + (int)(cpr - p),
497 "%.*s()", (int)(cp - cpr), cpr);
498 }
499 }
500 }
501
502 static void
503 post_abort(POST_ARGS)
504 {
505 abort();
506 }
507
508 static void
509 post_delim(POST_ARGS)
510 {
511 const struct roff_node *nch;
512 const char *lc;
513 enum mdelim delim;
514 enum roff_tok tok;
515
516 tok = mdoc->last->tok;
517 nch = mdoc->last->last;
518 if (nch == NULL || nch->type != ROFFT_TEXT)
519 return;
520 lc = strchr(nch->string, '\0') - 1;
521 if (lc < nch->string)
522 return;
523 delim = mdoc_isdelim(lc);
524 if (delim == DELIM_NONE || delim == DELIM_OPEN)
525 return;
526 if (*lc == ')' && (tok == MDOC_Nd || tok == MDOC_Sh ||
527 tok == MDOC_Ss || tok == MDOC_Fo))
528 return;
529
530 mandoc_msg(MANDOCERR_DELIM, nch->line,
531 nch->pos + (int)(lc - nch->string), "%s%s %s", roff_name[tok],
532 nch == mdoc->last->child ? "" : " ...", nch->string);
533 }
534
535 static void
536 post_delim_nb(POST_ARGS)
537 {
538 const struct roff_node *nch;
539 const char *lc, *cp;
540 int nw;
541 enum mdelim delim;
542 enum roff_tok tok;
543
544 /*
545 * Find candidates: at least two bytes,
546 * the last one a closing or middle delimiter.
547 */
548
549 tok = mdoc->last->tok;
550 nch = mdoc->last->last;
551 if (nch == NULL || nch->type != ROFFT_TEXT)
552 return;
553 lc = strchr(nch->string, '\0') - 1;
554 if (lc <= nch->string)
555 return;
556 delim = mdoc_isdelim(lc);
557 if (delim == DELIM_NONE || delim == DELIM_OPEN)
558 return;
559
560 /*
561 * Reduce false positives by allowing various cases.
562 */
563
564 /* Escaped delimiters. */
565 if (lc > nch->string + 1 && lc[-2] == '\\' &&
566 (lc[-1] == '&' || lc[-1] == 'e'))
567 return;
568
569 /* Specific byte sequences. */
570 switch (*lc) {
571 case ')':
572 for (cp = lc; cp >= nch->string; cp--)
573 if (*cp == '(')
574 return;
575 break;
576 case '.':
577 if (lc > nch->string + 1 && lc[-2] == '.' && lc[-1] == '.')
578 return;
579 if (lc[-1] == '.')
580 return;
581 break;
582 case ';':
583 if (tok == MDOC_Vt)
584 return;
585 break;
586 case '?':
587 if (lc[-1] == '?')
588 return;
589 break;
590 case ']':
591 for (cp = lc; cp >= nch->string; cp--)
592 if (*cp == '[')
593 return;
594 break;
595 case '|':
596 if (lc == nch->string + 1 && lc[-1] == '|')
597 return;
598 default:
599 break;
600 }
601
602 /* Exactly two non-alphanumeric bytes. */
603 if (lc == nch->string + 1 && !isalnum((unsigned char)lc[-1]))
604 return;
605
606 /* At least three alphabetic words with a sentence ending. */
607 if (strchr("!.:?", *lc) != NULL && (tok == MDOC_Em ||
608 tok == MDOC_Li || tok == MDOC_Pq || tok == MDOC_Sy)) {
609 nw = 0;
610 for (cp = lc - 1; cp >= nch->string; cp--) {
611 if (*cp == ' ') {
612 nw++;
613 if (cp > nch->string && cp[-1] == ',')
614 cp--;
615 } else if (isalpha((unsigned int)*cp)) {
616 if (nw > 1)
617 return;
618 } else
619 break;
620 }
621 }
622
623 mandoc_msg(MANDOCERR_DELIM_NB, nch->line,
624 nch->pos + (int)(lc - nch->string), "%s%s %s", roff_name[tok],
625 nch == mdoc->last->child ? "" : " ...", nch->string);
626 }
627
628 static void
629 post_bl_norm(POST_ARGS)
630 {
631 struct roff_node *n;
632 struct mdoc_argv *argv, *wa;
633 int i;
634 enum mdocargt mdoclt;
635 enum mdoc_list lt;
636
637 n = mdoc->last->parent;
638 n->norm->Bl.type = LIST__NONE;
639
640 /*
641 * First figure out which kind of list to use: bind ourselves to
642 * the first mentioned list type and warn about any remaining
643 * ones. If we find no list type, we default to LIST_item.
644 */
645
646 wa = (n->args == NULL) ? NULL : n->args->argv;
647 mdoclt = MDOC_ARG_MAX;
648 for (i = 0; n->args && i < (int)n->args->argc; i++) {
649 argv = n->args->argv + i;
650 lt = LIST__NONE;
651 switch (argv->arg) {
652 /* Set list types. */
653 case MDOC_Bullet:
654 lt = LIST_bullet;
655 break;
656 case MDOC_Dash:
657 lt = LIST_dash;
658 break;
659 case MDOC_Enum:
660 lt = LIST_enum;
661 break;
662 case MDOC_Hyphen:
663 lt = LIST_hyphen;
664 break;
665 case MDOC_Item:
666 lt = LIST_item;
667 break;
668 case MDOC_Tag:
669 lt = LIST_tag;
670 break;
671 case MDOC_Diag:
672 lt = LIST_diag;
673 break;
674 case MDOC_Hang:
675 lt = LIST_hang;
676 break;
677 case MDOC_Ohang:
678 lt = LIST_ohang;
679 break;
680 case MDOC_Inset:
681 lt = LIST_inset;
682 break;
683 case MDOC_Column:
684 lt = LIST_column;
685 break;
686 /* Set list arguments. */
687 case MDOC_Compact:
688 if (n->norm->Bl.comp)
689 mandoc_msg(MANDOCERR_ARG_REP,
690 argv->line, argv->pos, "Bl -compact");
691 n->norm->Bl.comp = 1;
692 break;
693 case MDOC_Width:
694 wa = argv;
695 if (0 == argv->sz) {
696 mandoc_msg(MANDOCERR_ARG_EMPTY,
697 argv->line, argv->pos, "Bl -width");
698 n->norm->Bl.width = "0n";
699 break;
700 }
701 if (NULL != n->norm->Bl.width)
702 mandoc_msg(MANDOCERR_ARG_REP,
703 argv->line, argv->pos,
704 "Bl -width %s", argv->value[0]);
705 rewrite_macro2len(mdoc, argv->value);
706 n->norm->Bl.width = argv->value[0];
707 break;
708 case MDOC_Offset:
709 if (0 == argv->sz) {
710 mandoc_msg(MANDOCERR_ARG_EMPTY,
711 argv->line, argv->pos, "Bl -offset");
712 break;
713 }
714 if (NULL != n->norm->Bl.offs)
715 mandoc_msg(MANDOCERR_ARG_REP,
716 argv->line, argv->pos,
717 "Bl -offset %s", argv->value[0]);
718 rewrite_macro2len(mdoc, argv->value);
719 n->norm->Bl.offs = argv->value[0];
720 break;
721 default:
722 continue;
723 }
724 if (LIST__NONE == lt)
725 continue;
726 mdoclt = argv->arg;
727
728 /* Check: multiple list types. */
729
730 if (LIST__NONE != n->norm->Bl.type) {
731 mandoc_msg(MANDOCERR_BL_REP, n->line, n->pos,
732 "Bl -%s", mdoc_argnames[argv->arg]);
733 continue;
734 }
735
736 /* The list type should come first. */
737
738 if (n->norm->Bl.width ||
739 n->norm->Bl.offs ||
740 n->norm->Bl.comp)
741 mandoc_msg(MANDOCERR_BL_LATETYPE,
742 n->line, n->pos, "Bl -%s",
743 mdoc_argnames[n->args->argv[0].arg]);
744
745 n->norm->Bl.type = lt;
746 if (LIST_column == lt) {
747 n->norm->Bl.ncols = argv->sz;
748 n->norm->Bl.cols = (void *)argv->value;
749 }
750 }
751
752 /* Allow lists to default to LIST_item. */
753
754 if (LIST__NONE == n->norm->Bl.type) {
755 mandoc_msg(MANDOCERR_BL_NOTYPE, n->line, n->pos, "Bl");
756 n->norm->Bl.type = LIST_item;
757 mdoclt = MDOC_Item;
758 }
759
760 /*
761 * Validate the width field. Some list types don't need width
762 * types and should be warned about them. Others should have it
763 * and must also be warned. Yet others have a default and need
764 * no warning.
765 */
766
767 switch (n->norm->Bl.type) {
768 case LIST_tag:
769 if (n->norm->Bl.width == NULL)
770 mandoc_msg(MANDOCERR_BL_NOWIDTH,
771 n->line, n->pos, "Bl -tag");
772 break;
773 case LIST_column:
774 case LIST_diag:
775 case LIST_ohang:
776 case LIST_inset:
777 case LIST_item:
778 if (n->norm->Bl.width != NULL)
779 mandoc_msg(MANDOCERR_BL_SKIPW, wa->line, wa->pos,
780 "Bl -%s", mdoc_argnames[mdoclt]);
781 n->norm->Bl.width = NULL;
782 break;
783 case LIST_bullet:
784 case LIST_dash:
785 case LIST_hyphen:
786 if (n->norm->Bl.width == NULL)
787 n->norm->Bl.width = "2n";
788 break;
789 case LIST_enum:
790 if (n->norm->Bl.width == NULL)
791 n->norm->Bl.width = "3n";
792 break;
793 default:
794 break;
795 }
796 }
797
798 static void
799 post_bd(POST_ARGS)
800 {
801 struct roff_node *n;
802 struct mdoc_argv *argv;
803 int i;
804 enum mdoc_disp dt;
805
806 n = mdoc->last;
807 for (i = 0; n->args && i < (int)n->args->argc; i++) {
808 argv = n->args->argv + i;
809 dt = DISP__NONE;
810
811 switch (argv->arg) {
812 case MDOC_Centred:
813 dt = DISP_centered;
814 break;
815 case MDOC_Ragged:
816 dt = DISP_ragged;
817 break;
818 case MDOC_Unfilled:
819 dt = DISP_unfilled;
820 break;
821 case MDOC_Filled:
822 dt = DISP_filled;
823 break;
824 case MDOC_Literal:
825 dt = DISP_literal;
826 break;
827 case MDOC_File:
828 mandoc_msg(MANDOCERR_BD_FILE, n->line, n->pos, NULL);
829 break;
830 case MDOC_Offset:
831 if (0 == argv->sz) {
832 mandoc_msg(MANDOCERR_ARG_EMPTY,
833 argv->line, argv->pos, "Bd -offset");
834 break;
835 }
836 if (NULL != n->norm->Bd.offs)
837 mandoc_msg(MANDOCERR_ARG_REP,
838 argv->line, argv->pos,
839 "Bd -offset %s", argv->value[0]);
840 rewrite_macro2len(mdoc, argv->value);
841 n->norm->Bd.offs = argv->value[0];
842 break;
843 case MDOC_Compact:
844 if (n->norm->Bd.comp)
845 mandoc_msg(MANDOCERR_ARG_REP,
846 argv->line, argv->pos, "Bd -compact");
847 n->norm->Bd.comp = 1;
848 break;
849 default:
850 abort();
851 }
852 if (DISP__NONE == dt)
853 continue;
854
855 if (DISP__NONE == n->norm->Bd.type)
856 n->norm->Bd.type = dt;
857 else
858 mandoc_msg(MANDOCERR_BD_REP, n->line, n->pos,
859 "Bd -%s", mdoc_argnames[argv->arg]);
860 }
861
862 if (DISP__NONE == n->norm->Bd.type) {
863 mandoc_msg(MANDOCERR_BD_NOTYPE, n->line, n->pos, "Bd");
864 n->norm->Bd.type = DISP_ragged;
865 }
866 }
867
868 /*
869 * Stand-alone line macros.
870 */
871
872 static void
873 post_an_norm(POST_ARGS)
874 {
875 struct roff_node *n;
876 struct mdoc_argv *argv;
877 size_t i;
878
879 n = mdoc->last;
880 if (n->args == NULL)
881 return;
882
883 for (i = 1; i < n->args->argc; i++) {
884 argv = n->args->argv + i;
885 mandoc_msg(MANDOCERR_AN_REP, argv->line, argv->pos,
886 "An -%s", mdoc_argnames[argv->arg]);
887 }
888
889 argv = n->args->argv;
890 if (argv->arg == MDOC_Split)
891 n->norm->An.auth = AUTH_split;
892 else if (argv->arg == MDOC_Nosplit)
893 n->norm->An.auth = AUTH_nosplit;
894 else
895 abort();
896 }
897
898 static void
899 post_eoln(POST_ARGS)
900 {
901 struct roff_node *n;
902
903 post_useless(mdoc);
904 n = mdoc->last;
905 if (n->child != NULL)
906 mandoc_msg(MANDOCERR_ARG_SKIP, n->line,
907 n->pos, "%s %s", roff_name[n->tok], n->child->string);
908
909 while (n->child != NULL)
910 roff_node_delete(mdoc, n->child);
911
912 roff_word_alloc(mdoc, n->line, n->pos, n->tok == MDOC_Bt ?
913 "is currently in beta test." : "currently under development.");
914 mdoc->last->flags |= NODE_EOS | NODE_NOSRC;
915 mdoc->last = n;
916 }
917
918 static int
919 build_list(struct roff_man *mdoc, int tok)
920 {
921 struct roff_node *n;
922 int ic;
923
924 n = mdoc->last->next;
925 for (ic = 1;; ic++) {
926 roff_elem_alloc(mdoc, n->line, n->pos, tok);
927 mdoc->last->flags |= NODE_NOSRC;
928 roff_node_relink(mdoc, n);
929 n = mdoc->last = mdoc->last->parent;
930 mdoc->next = ROFF_NEXT_SIBLING;
931 if (n->next == NULL)
932 return ic;
933 if (ic > 1 || n->next->next != NULL) {
934 roff_word_alloc(mdoc, n->line, n->pos, ",");
935 mdoc->last->flags |= NODE_DELIMC | NODE_NOSRC;
936 }
937 n = mdoc->last->next;
938 if (n->next == NULL) {
939 roff_word_alloc(mdoc, n->line, n->pos, "and");
940 mdoc->last->flags |= NODE_NOSRC;
941 }
942 }
943 }
944
945 static void
946 post_ex(POST_ARGS)
947 {
948 struct roff_node *n;
949 int ic;
950
951 post_std(mdoc);
952
953 n = mdoc->last;
954 mdoc->next = ROFF_NEXT_CHILD;
955 roff_word_alloc(mdoc, n->line, n->pos, "The");
956 mdoc->last->flags |= NODE_NOSRC;
957
958 if (mdoc->last->next != NULL)
959 ic = build_list(mdoc, MDOC_Nm);
960 else if (mdoc->meta.name != NULL) {
961 roff_elem_alloc(mdoc, n->line, n->pos, MDOC_Nm);
962 mdoc->last->flags |= NODE_NOSRC;
963 roff_word_alloc(mdoc, n->line, n->pos, mdoc->meta.name);
964 mdoc->last->flags |= NODE_NOSRC;
965 mdoc->last = mdoc->last->parent;
966 mdoc->next = ROFF_NEXT_SIBLING;
967 ic = 1;
968 } else {
969 mandoc_msg(MANDOCERR_EX_NONAME, n->line, n->pos, "Ex");
970 ic = 0;
971 }
972
973 roff_word_alloc(mdoc, n->line, n->pos,
974 ic > 1 ? "utilities exit\\~0" : "utility exits\\~0");
975 mdoc->last->flags |= NODE_NOSRC;
976 roff_word_alloc(mdoc, n->line, n->pos,
977 "on success, and\\~>0 if an error occurs.");
978 mdoc->last->flags |= NODE_EOS | NODE_NOSRC;
979 mdoc->last = n;
980 }
981
982 static void
983 post_lb(POST_ARGS)
984 {
985 struct roff_node *n;
986 const char *p;
987
988 post_delim_nb(mdoc);
989
990 n = mdoc->last;
991 assert(n->child->type == ROFFT_TEXT);
992 mdoc->next = ROFF_NEXT_CHILD;
993
994 if ((p = mdoc_a2lib(n->child->string)) != NULL) {
995 n->child->flags |= NODE_NOPRT;
996 roff_word_alloc(mdoc, n->line, n->pos, p);
997 mdoc->last->flags = NODE_NOSRC;
998 mdoc->last = n;
999 return;
1000 }
1001
1002 mandoc_msg(MANDOCERR_LB_BAD, n->child->line,
1003 n->child->pos, "Lb %s", n->child->string);
1004
1005 roff_word_alloc(mdoc, n->line, n->pos, "library");
1006 mdoc->last->flags = NODE_NOSRC;
1007 roff_word_alloc(mdoc, n->line, n->pos, "\\(lq");
1008 mdoc->last->flags = NODE_DELIMO | NODE_NOSRC;
1009 mdoc->last = mdoc->last->next;
1010 roff_word_alloc(mdoc, n->line, n->pos, "\\(rq");
1011 mdoc->last->flags = NODE_DELIMC | NODE_NOSRC;
1012 mdoc->last = n;
1013 }
1014
1015 static void
1016 post_rv(POST_ARGS)
1017 {
1018 struct roff_node *n;
1019 int ic;
1020
1021 post_std(mdoc);
1022
1023 n = mdoc->last;
1024 mdoc->next = ROFF_NEXT_CHILD;
1025 if (n->child != NULL) {
1026 roff_word_alloc(mdoc, n->line, n->pos, "The");
1027 mdoc->last->flags |= NODE_NOSRC;
1028 ic = build_list(mdoc, MDOC_Fn);
1029 roff_word_alloc(mdoc, n->line, n->pos,
1030 ic > 1 ? "functions return" : "function returns");
1031 mdoc->last->flags |= NODE_NOSRC;
1032 roff_word_alloc(mdoc, n->line, n->pos,
1033 "the value\\~0 if successful;");
1034 } else
1035 roff_word_alloc(mdoc, n->line, n->pos, "Upon successful "
1036 "completion, the value\\~0 is returned;");
1037 mdoc->last->flags |= NODE_NOSRC;
1038
1039 roff_word_alloc(mdoc, n->line, n->pos, "otherwise "
1040 "the value\\~\\-1 is returned and the global variable");
1041 mdoc->last->flags |= NODE_NOSRC;
1042 roff_elem_alloc(mdoc, n->line, n->pos, MDOC_Va);
1043 mdoc->last->flags |= NODE_NOSRC;
1044 roff_word_alloc(mdoc, n->line, n->pos, "errno");
1045 mdoc->last->flags |= NODE_NOSRC;
1046 mdoc->last = mdoc->last->parent;
1047 mdoc->next = ROFF_NEXT_SIBLING;
1048 roff_word_alloc(mdoc, n->line, n->pos,
1049 "is set to indicate the error.");
1050 mdoc->last->flags |= NODE_EOS | NODE_NOSRC;
1051 mdoc->last = n;
1052 }
1053
1054 static void
1055 post_std(POST_ARGS)
1056 {
1057 struct roff_node *n;
1058
1059 post_delim(mdoc);
1060
1061 n = mdoc->last;
1062 if (n->args && n->args->argc == 1)
1063 if (n->args->argv[0].arg == MDOC_Std)
1064 return;
1065
1066 mandoc_msg(MANDOCERR_ARG_STD, n->line, n->pos,
1067 "%s", roff_name[n->tok]);
1068 }
1069
1070 static void
1071 post_st(POST_ARGS)
1072 {
1073 struct roff_node *n, *nch;
1074 const char *p;
1075
1076 n = mdoc->last;
1077 nch = n->child;
1078 assert(nch->type == ROFFT_TEXT);
1079
1080 if ((p = mdoc_a2st(nch->string)) == NULL) {
1081 mandoc_msg(MANDOCERR_ST_BAD,
1082 nch->line, nch->pos, "St %s", nch->string);
1083 roff_node_delete(mdoc, n);
1084 return;
1085 }
1086
1087 nch->flags |= NODE_NOPRT;
1088 mdoc->next = ROFF_NEXT_CHILD;
1089 roff_word_alloc(mdoc, nch->line, nch->pos, p);
1090 mdoc->last->flags |= NODE_NOSRC;
1091 mdoc->last= n;
1092 }
1093
1094 static void
1095 post_tg(POST_ARGS)
1096 {
1097 struct roff_node *n, *nch;
1098 size_t len;
1099
1100 n = mdoc->last;
1101 nch = n->child;
1102 if (nch == NULL && n->next != NULL &&
1103 n->next->child->type == ROFFT_TEXT) {
1104 mdoc->next = ROFF_NEXT_CHILD;
1105 roff_word_alloc(mdoc, n->line, n->pos, n->next->child->string);
1106 nch = mdoc->last;
1107 nch->flags |= NODE_NOSRC;
1108 mdoc->last = n;
1109 }
1110 if (nch == NULL || *nch->string == '\0') {
1111 mandoc_msg(MANDOCERR_MACRO_EMPTY, n->line, n->pos, "Tg");
1112 roff_node_delete(mdoc, n);
1113 return;
1114 }
1115 len = strcspn(nch->string, " \t");
1116 if (nch->string[len] != '\0')
1117 mandoc_msg(MANDOCERR_TG_SPC, nch->line, nch->pos + len + 1,
1118 "Tg %s", nch->string);
1119 if (nch->next != NULL) {
1120 mandoc_msg(MANDOCERR_ARG_EXCESS, nch->next->line,
1121 nch->next->pos, "Tg ... %s", nch->next->string);
1122 while (nch->next != NULL)
1123 roff_node_delete(mdoc, nch->next);
1124 }
1125 if (nch->string[len] != '\0')
1126 roff_node_delete(mdoc, n);
1127 }
1128
1129 static void
1130 post_obsolete(POST_ARGS)
1131 {
1132 struct roff_node *n;
1133
1134 n = mdoc->last;
1135 if (n->type == ROFFT_ELEM || n->type == ROFFT_BLOCK)
1136 mandoc_msg(MANDOCERR_MACRO_OBS, n->line, n->pos,
1137 "%s", roff_name[n->tok]);
1138 }
1139
1140 static void
1141 post_useless(POST_ARGS)
1142 {
1143 struct roff_node *n;
1144
1145 n = mdoc->last;
1146 mandoc_msg(MANDOCERR_MACRO_USELESS, n->line, n->pos,
1147 "%s", roff_name[n->tok]);
1148 }
1149
1150 /*
1151 * Block macros.
1152 */
1153
1154 static void
1155 post_bf(POST_ARGS)
1156 {
1157 struct roff_node *np, *nch;
1158
1159 /*
1160 * Unlike other data pointers, these are "housed" by the HEAD
1161 * element, which contains the goods.
1162 */
1163
1164 np = mdoc->last;
1165 if (np->type != ROFFT_HEAD)
1166 return;
1167
1168 assert(np->parent->type == ROFFT_BLOCK);
1169 assert(np->parent->tok == MDOC_Bf);
1170
1171 /* Check the number of arguments. */
1172
1173 nch = np->child;
1174 if (np->parent->args == NULL) {
1175 if (nch == NULL) {
1176 mandoc_msg(MANDOCERR_BF_NOFONT,
1177 np->line, np->pos, "Bf");
1178 return;
1179 }
1180 nch = nch->next;
1181 }
1182 if (nch != NULL)
1183 mandoc_msg(MANDOCERR_ARG_EXCESS,
1184 nch->line, nch->pos, "Bf ... %s", nch->string);
1185
1186 /* Extract argument into data. */
1187
1188 if (np->parent->args != NULL) {
1189 switch (np->parent->args->argv[0].arg) {
1190 case MDOC_Emphasis:
1191 np->norm->Bf.font = FONT_Em;
1192 break;
1193 case MDOC_Literal:
1194 np->norm->Bf.font = FONT_Li;
1195 break;
1196 case MDOC_Symbolic:
1197 np->norm->Bf.font = FONT_Sy;
1198 break;
1199 default:
1200 abort();
1201 }
1202 return;
1203 }
1204
1205 /* Extract parameter into data. */
1206
1207 if ( ! strcmp(np->child->string, "Em"))
1208 np->norm->Bf.font = FONT_Em;
1209 else if ( ! strcmp(np->child->string, "Li"))
1210 np->norm->Bf.font = FONT_Li;
1211 else if ( ! strcmp(np->child->string, "Sy"))
1212 np->norm->Bf.font = FONT_Sy;
1213 else
1214 mandoc_msg(MANDOCERR_BF_BADFONT, np->child->line,
1215 np->child->pos, "Bf %s", np->child->string);
1216 }
1217
1218 static void
1219 post_fname(POST_ARGS)
1220 {
1221 const struct roff_node *n;
1222 const char *cp;
1223 size_t pos;
1224
1225 n = mdoc->last->child;
1226 cp = n->string;
1227 if (*cp == '(') {
1228 if (cp[strlen(cp + 1)] == ')')
1229 return;
1230 pos = 0;
1231 } else {
1232 pos = strcspn(cp, "()");
1233 if (cp[pos] == '\0')
1234 return;
1235 }
1236 mandoc_msg(MANDOCERR_FN_PAREN, n->line, n->pos + pos, "%s", cp);
1237 }
1238
1239 static void
1240 post_fn(POST_ARGS)
1241 {
1242
1243 post_fname(mdoc);
1244 post_fa(mdoc);
1245 }
1246
1247 static void
1248 post_fo(POST_ARGS)
1249 {
1250 const struct roff_node *n;
1251
1252 n = mdoc->last;
1253
1254 if (n->type != ROFFT_HEAD)
1255 return;
1256
1257 if (n->child == NULL) {
1258 mandoc_msg(MANDOCERR_FO_NOHEAD, n->line, n->pos, "Fo");
1259 return;
1260 }
1261 if (n->child != n->last) {
1262 mandoc_msg(MANDOCERR_ARG_EXCESS,
1263 n->child->next->line, n->child->next->pos,
1264 "Fo ... %s", n->child->next->string);
1265 while (n->child != n->last)
1266 roff_node_delete(mdoc, n->last);
1267 } else
1268 post_delim(mdoc);
1269
1270 post_fname(mdoc);
1271 }
1272
1273 static void
1274 post_fa(POST_ARGS)
1275 {
1276 const struct roff_node *n;
1277 const char *cp;
1278
1279 for (n = mdoc->last->child; n != NULL; n = n->next) {
1280 for (cp = n->string; *cp != '\0'; cp++) {
1281 /* Ignore callbacks and alterations. */
1282 if (*cp == '(' || *cp == '{')
1283 break;
1284 if (*cp != ',')
1285 continue;
1286 mandoc_msg(MANDOCERR_FA_COMMA, n->line,
1287 n->pos + (int)(cp - n->string), "%s", n->string);
1288 break;
1289 }
1290 }
1291 post_delim_nb(mdoc);
1292 }
1293
1294 static void
1295 post_nm(POST_ARGS)
1296 {
1297 struct roff_node *n;
1298
1299 n = mdoc->last;
1300
1301 if (n->sec == SEC_NAME && n->child != NULL &&
1302 n->child->type == ROFFT_TEXT && mdoc->meta.msec != NULL)
1303 mandoc_xr_add(mdoc->meta.msec, n->child->string, -1, -1);
1304
1305 if (n->last != NULL && n->last->tok == MDOC_Pp)
1306 roff_node_relink(mdoc, n->last);
1307
1308 if (mdoc->meta.name == NULL)
1309 deroff(&mdoc->meta.name, n);
1310
1311 if (mdoc->meta.name == NULL ||
1312 (mdoc->lastsec == SEC_NAME && n->child == NULL))
1313 mandoc_msg(MANDOCERR_NM_NONAME, n->line, n->pos, "Nm");
1314
1315 switch (n->type) {
1316 case ROFFT_ELEM:
1317 post_delim_nb(mdoc);
1318 break;
1319 case ROFFT_HEAD:
1320 post_delim(mdoc);
1321 break;
1322 default:
1323 return;
1324 }
1325
1326 if ((n->child != NULL && n->child->type == ROFFT_TEXT) ||
1327 mdoc->meta.name == NULL)
1328 return;
1329
1330 mdoc->next = ROFF_NEXT_CHILD;
1331 roff_word_alloc(mdoc, n->line, n->pos, mdoc->meta.name);
1332 mdoc->last->flags |= NODE_NOSRC;
1333 mdoc->last = n;
1334 }
1335
1336 static void
1337 post_nd(POST_ARGS)
1338 {
1339 struct roff_node *n;
1340
1341 n = mdoc->last;
1342
1343 if (n->type != ROFFT_BODY)
1344 return;
1345
1346 if (n->sec != SEC_NAME)
1347 mandoc_msg(MANDOCERR_ND_LATE, n->line, n->pos, "Nd");
1348
1349 if (n->child == NULL)
1350 mandoc_msg(MANDOCERR_ND_EMPTY, n->line, n->pos, "Nd");
1351 else
1352 post_delim(mdoc);
1353
1354 post_hyph(mdoc);
1355 }
1356
1357 static void
1358 post_display(POST_ARGS)
1359 {
1360 struct roff_node *n, *np;
1361
1362 n = mdoc->last;
1363 switch (n->type) {
1364 case ROFFT_BODY:
1365 if (n->end != ENDBODY_NOT) {
1366 if (n->tok == MDOC_Bd &&
1367 n->body->parent->args == NULL)
1368 roff_node_delete(mdoc, n);
1369 } else if (n->child == NULL)
1370 mandoc_msg(MANDOCERR_BLK_EMPTY, n->line, n->pos,
1371 "%s", roff_name[n->tok]);
1372 else if (n->tok == MDOC_D1)
1373 post_hyph(mdoc);
1374 break;
1375 case ROFFT_BLOCK:
1376 if (n->tok == MDOC_Bd) {
1377 if (n->args == NULL) {
1378 mandoc_msg(MANDOCERR_BD_NOARG,
1379 n->line, n->pos, "Bd");
1380 mdoc->next = ROFF_NEXT_SIBLING;
1381 while (n->body->child != NULL)
1382 roff_node_relink(mdoc,
1383 n->body->child);
1384 roff_node_delete(mdoc, n);
1385 break;
1386 }
1387 post_bd(mdoc);
1388 post_prevpar(mdoc);
1389 }
1390 for (np = n->parent; np != NULL; np = np->parent) {
1391 if (np->type == ROFFT_BLOCK && np->tok == MDOC_Bd) {
1392 mandoc_msg(MANDOCERR_BD_NEST, n->line,
1393 n->pos, "%s in Bd", roff_name[n->tok]);
1394 break;
1395 }
1396 }
1397 break;
1398 default:
1399 break;
1400 }
1401 }
1402
1403 static void
1404 post_defaults(POST_ARGS)
1405 {
1406 struct roff_node *nn;
1407
1408 if (mdoc->last->child != NULL) {
1409 post_delim_nb(mdoc);
1410 return;
1411 }
1412
1413 /*
1414 * The `Ar' defaults to "file ..." if no value is provided as an
1415 * argument; the `Mt' and `Pa' macros use "~"; the `Li' just
1416 * gets an empty string.
1417 */
1418
1419 nn = mdoc->last;
1420 switch (nn->tok) {
1421 case MDOC_Ar:
1422 mdoc->next = ROFF_NEXT_CHILD;
1423 roff_word_alloc(mdoc, nn->line, nn->pos, "file");
1424 mdoc->last->flags |= NODE_NOSRC;
1425 roff_word_alloc(mdoc, nn->line, nn->pos, "...");
1426 mdoc->last->flags |= NODE_NOSRC;
1427 break;
1428 case MDOC_Pa:
1429 case MDOC_Mt:
1430 mdoc->next = ROFF_NEXT_CHILD;
1431 roff_word_alloc(mdoc, nn->line, nn->pos, "~");
1432 mdoc->last->flags |= NODE_NOSRC;
1433 break;
1434 default:
1435 abort();
1436 }
1437 mdoc->last = nn;
1438 }
1439
1440 static void
1441 post_at(POST_ARGS)
1442 {
1443 struct roff_node *n, *nch;
1444 const char *att;
1445
1446 n = mdoc->last;
1447 nch = n->child;
1448
1449 /*
1450 * If we have a child, look it up in the standard keys. If a
1451 * key exist, use that instead of the child; if it doesn't,
1452 * prefix "AT&T UNIX " to the existing data.
1453 */
1454
1455 att = NULL;
1456 if (nch != NULL && ((att = mdoc_a2att(nch->string)) == NULL))
1457 mandoc_msg(MANDOCERR_AT_BAD,
1458 nch->line, nch->pos, "At %s", nch->string);
1459
1460 mdoc->next = ROFF_NEXT_CHILD;
1461 if (att != NULL) {
1462 roff_word_alloc(mdoc, nch->line, nch->pos, att);
1463 nch->flags |= NODE_NOPRT;
1464 } else
1465 roff_word_alloc(mdoc, n->line, n->pos, "AT&T UNIX");
1466 mdoc->last->flags |= NODE_NOSRC;
1467 mdoc->last = n;
1468 }
1469
1470 static void
1471 post_an(POST_ARGS)
1472 {
1473 struct roff_node *np, *nch;
1474
1475 post_an_norm(mdoc);
1476
1477 np = mdoc->last;
1478 nch = np->child;
1479 if (np->norm->An.auth == AUTH__NONE) {
1480 if (nch == NULL)
1481 mandoc_msg(MANDOCERR_MACRO_EMPTY,
1482 np->line, np->pos, "An");
1483 else
1484 post_delim_nb(mdoc);
1485 } else if (nch != NULL)
1486 mandoc_msg(MANDOCERR_ARG_EXCESS,
1487 nch->line, nch->pos, "An ... %s", nch->string);
1488 }
1489
1490 static void
1491 post_en(POST_ARGS)
1492 {
1493
1494 post_obsolete(mdoc);
1495 if (mdoc->last->type == ROFFT_BLOCK)
1496 mdoc->last->norm->Es = mdoc->last_es;
1497 }
1498
1499 static void
1500 post_es(POST_ARGS)
1501 {
1502
1503 post_obsolete(mdoc);
1504 mdoc->last_es = mdoc->last;
1505 }
1506
1507 static void
1508 post_xx(POST_ARGS)
1509 {
1510 struct roff_node *n;
1511 const char *os;
1512 char *v;
1513
1514 post_delim_nb(mdoc);
1515
1516 n = mdoc->last;
1517 switch (n->tok) {
1518 case MDOC_Bsx:
1519 os = "BSD/OS";
1520 break;
1521 case MDOC_Dx:
1522 os = "DragonFly";
1523 break;
1524 case MDOC_Fx:
1525 os = "FreeBSD";
1526 break;
1527 case MDOC_Nx:
1528 os = "NetBSD";
1529 if (n->child == NULL)
1530 break;
1531 v = n->child->string;
1532 if ((v[0] != '0' && v[0] != '1') || v[1] != '.' ||
1533 v[2] < '0' || v[2] > '9' ||
1534 v[3] < 'a' || v[3] > 'z' || v[4] != '\0')
1535 break;
1536 n->child->flags |= NODE_NOPRT;
1537 mdoc->next = ROFF_NEXT_CHILD;
1538 roff_word_alloc(mdoc, n->child->line, n->child->pos, v);
1539 v = mdoc->last->string;
1540 v[3] = toupper((unsigned char)v[3]);
1541 mdoc->last->flags |= NODE_NOSRC;
1542 mdoc->last = n;
1543 break;
1544 case MDOC_Ox:
1545 os = "OpenBSD";
1546 break;
1547 case MDOC_Ux:
1548 os = "UNIX";
1549 break;
1550 default:
1551 abort();
1552 }
1553 mdoc->next = ROFF_NEXT_CHILD;
1554 roff_word_alloc(mdoc, n->line, n->pos, os);
1555 mdoc->last->flags |= NODE_NOSRC;
1556 mdoc->last = n;
1557 }
1558
1559 static void
1560 post_it(POST_ARGS)
1561 {
1562 struct roff_node *nbl, *nit, *nch;
1563 int i, cols;
1564 enum mdoc_list lt;
1565
1566 post_prevpar(mdoc);
1567
1568 nit = mdoc->last;
1569 if (nit->type != ROFFT_BLOCK)
1570 return;
1571
1572 nbl = nit->parent->parent;
1573 lt = nbl->norm->Bl.type;
1574
1575 switch (lt) {
1576 case LIST_tag:
1577 case LIST_hang:
1578 case LIST_ohang:
1579 case LIST_inset:
1580 case LIST_diag:
1581 if (nit->head->child == NULL)
1582 mandoc_msg(MANDOCERR_IT_NOHEAD,
1583 nit->line, nit->pos, "Bl -%s It",
1584 mdoc_argnames[nbl->args->argv[0].arg]);
1585 break;
1586 case LIST_bullet:
1587 case LIST_dash:
1588 case LIST_enum:
1589 case LIST_hyphen:
1590 if (nit->body == NULL || nit->body->child == NULL)
1591 mandoc_msg(MANDOCERR_IT_NOBODY,
1592 nit->line, nit->pos, "Bl -%s It",
1593 mdoc_argnames[nbl->args->argv[0].arg]);
1594 /* FALLTHROUGH */
1595 case LIST_item:
1596 if ((nch = nit->head->child) != NULL)
1597 mandoc_msg(MANDOCERR_ARG_SKIP,
1598 nit->line, nit->pos, "It %s",
1599 nch->string == NULL ? roff_name[nch->tok] :
1600 nch->string);
1601 break;
1602 case LIST_column:
1603 cols = (int)nbl->norm->Bl.ncols;
1604
1605 assert(nit->head->child == NULL);
1606
1607 if (nit->head->next->child == NULL &&
1608 nit->head->next->next == NULL) {
1609 mandoc_msg(MANDOCERR_MACRO_EMPTY,
1610 nit->line, nit->pos, "It");
1611 roff_node_delete(mdoc, nit);
1612 break;
1613 }
1614
1615 i = 0;
1616 for (nch = nit->child; nch != NULL; nch = nch->next) {
1617 if (nch->type != ROFFT_BODY)
1618 continue;
1619 if (i++ && nch->flags & NODE_LINE)
1620 mandoc_msg(MANDOCERR_TA_LINE,
1621 nch->line, nch->pos, "Ta");
1622 }
1623 if (i < cols || i > cols + 1)
1624 mandoc_msg(MANDOCERR_BL_COL, nit->line, nit->pos,
1625 "%d columns, %d cells", cols, i);
1626 else if (nit->head->next->child != NULL &&
1627 nit->head->next->child->flags & NODE_LINE)
1628 mandoc_msg(MANDOCERR_IT_NOARG,
1629 nit->line, nit->pos, "Bl -column It");
1630 break;
1631 default:
1632 abort();
1633 }
1634 }
1635
1636 static void
1637 post_bl_block(POST_ARGS)
1638 {
1639 struct roff_node *n, *ni, *nc;
1640
1641 post_prevpar(mdoc);
1642
1643 n = mdoc->last;
1644 for (ni = n->body->child; ni != NULL; ni = ni->next) {
1645 if (ni->body == NULL)
1646 continue;
1647 nc = ni->body->last;
1648 while (nc != NULL) {
1649 switch (nc->tok) {
1650 case MDOC_Pp:
1651 case ROFF_br:
1652 break;
1653 default:
1654 nc = NULL;
1655 continue;
1656 }
1657 if (ni->next == NULL) {
1658 mandoc_msg(MANDOCERR_PAR_MOVE, nc->line,
1659 nc->pos, "%s", roff_name[nc->tok]);
1660 roff_node_relink(mdoc, nc);
1661 } else if (n->norm->Bl.comp == 0 &&
1662 n->norm->Bl.type != LIST_column) {
1663 mandoc_msg(MANDOCERR_PAR_SKIP,
1664 nc->line, nc->pos,
1665 "%s before It", roff_name[nc->tok]);
1666 roff_node_delete(mdoc, nc);
1667 } else
1668 break;
1669 nc = ni->body->last;
1670 }
1671 }
1672 }
1673
1674 /*
1675 * If the argument of -offset or -width is a macro,
1676 * replace it with the associated default width.
1677 */
1678 static void
1679 rewrite_macro2len(struct roff_man *mdoc, char **arg)
1680 {
1681 size_t width;
1682 enum roff_tok tok;
1683
1684 if (*arg == NULL)
1685 return;
1686 else if ( ! strcmp(*arg, "Ds"))
1687 width = 6;
1688 else if ((tok = roffhash_find(mdoc->mdocmac, *arg, 0)) == TOKEN_NONE)
1689 return;
1690 else
1691 width = macro2len(tok);
1692
1693 free(*arg);
1694 mandoc_asprintf(arg, "%zun", width);
1695 }
1696
1697 static void
1698 post_bl_head(POST_ARGS)
1699 {
1700 struct roff_node *nbl, *nh, *nch, *nnext;
1701 struct mdoc_argv *argv;
1702 int i, j;
1703
1704 post_bl_norm(mdoc);
1705
1706 nh = mdoc->last;
1707 if (nh->norm->Bl.type != LIST_column) {
1708 if ((nch = nh->child) == NULL)
1709 return;
1710 mandoc_msg(MANDOCERR_ARG_EXCESS,
1711 nch->line, nch->pos, "Bl ... %s", nch->string);
1712 while (nch != NULL) {
1713 roff_node_delete(mdoc, nch);
1714 nch = nh->child;
1715 }
1716 return;
1717 }
1718
1719 /*
1720 * Append old-style lists, where the column width specifiers
1721 * trail as macro parameters, to the new-style ("normal-form")
1722 * lists where they're argument values following -column.
1723 */
1724
1725 if (nh->child == NULL)
1726 return;
1727
1728 nbl = nh->parent;
1729 for (j = 0; j < (int)nbl->args->argc; j++)
1730 if (nbl->args->argv[j].arg == MDOC_Column)
1731 break;
1732
1733 assert(j < (int)nbl->args->argc);
1734
1735 /*
1736 * Accommodate for new-style groff column syntax. Shuffle the
1737 * child nodes, all of which must be TEXT, as arguments for the
1738 * column field. Then, delete the head children.
1739 */
1740
1741 argv = nbl->args->argv + j;
1742 i = argv->sz;
1743 for (nch = nh->child; nch != NULL; nch = nch->next)
1744 argv->sz++;
1745 argv->value = mandoc_reallocarray(argv->value,
1746 argv->sz, sizeof(char *));
1747
1748 nh->norm->Bl.ncols = argv->sz;
1749 nh->norm->Bl.cols = (void *)argv->value;
1750
1751 for (nch = nh->child; nch != NULL; nch = nnext) {
1752 argv->value[i++] = nch->string;
1753 nch->string = NULL;
1754 nnext = nch->next;
1755 roff_node_delete(NULL, nch);
1756 }
1757 nh->child = NULL;
1758 }
1759
1760 static void
1761 post_bl(POST_ARGS)
1762 {
1763 struct roff_node *nbody; /* of the Bl */
1764 struct roff_node *nchild, *nnext; /* of the Bl body */
1765 const char *prev_Er;
1766 int order;
1767
1768 nbody = mdoc->last;
1769 switch (nbody->type) {
1770 case ROFFT_BLOCK:
1771 post_bl_block(mdoc);
1772 return;
1773 case ROFFT_HEAD:
1774 post_bl_head(mdoc);
1775 return;
1776 case ROFFT_BODY:
1777 break;
1778 default:
1779 return;
1780 }
1781 if (nbody->end != ENDBODY_NOT)
1782 return;
1783
1784 /*
1785 * Up to the first item, move nodes before the list,
1786 * but leave transparent nodes where they are
1787 * if they precede an item.
1788 * The next non-transparent node is kept in nchild.
1789 * It only needs to be updated after a non-transparent
1790 * node was moved out, and at the very beginning
1791 * when no node at all was moved yet.
1792 */
1793
1794 nchild = mdoc->last;
1795 for (;;) {
1796 if (nchild == mdoc->last)
1797 nchild = roff_node_child(nbody);
1798 if (nchild == NULL) {
1799 mdoc->last = nbody;
1800 mandoc_msg(MANDOCERR_BLK_EMPTY,
1801 nbody->line, nbody->pos, "Bl");
1802 return;
1803 }
1804 if (nchild->tok == MDOC_It) {
1805 mdoc->last = nbody;
1806 break;
1807 }
1808 mandoc_msg(MANDOCERR_BL_MOVE, nbody->child->line,
1809 nbody->child->pos, "%s", roff_name[nbody->child->tok]);
1810 if (nbody->parent->prev == NULL) {
1811 mdoc->last = nbody->parent->parent;
1812 mdoc->next = ROFF_NEXT_CHILD;
1813 } else {
1814 mdoc->last = nbody->parent->prev;
1815 mdoc->next = ROFF_NEXT_SIBLING;
1816 }
1817 roff_node_relink(mdoc, nbody->child);
1818 }
1819
1820 /*
1821 * We have reached the first item,
1822 * so moving nodes out is no longer possible.
1823 * But in .Bl -column, the first rows may be implicit,
1824 * that is, they may not start with .It macros.
1825 * Such rows may be followed by nodes generated on the
1826 * roff level, for example .TS.
1827 * Wrap such roff nodes into an implicit row.
1828 */
1829
1830 while (nchild != NULL) {
1831 if (nchild->tok == MDOC_It) {
1832 nchild = roff_node_next(nchild);
1833 continue;
1834 }
1835 nnext = nchild->next;
1836 mdoc->last = nchild->prev;
1837 mdoc->next = ROFF_NEXT_SIBLING;
1838 roff_block_alloc(mdoc, nchild->line, nchild->pos, MDOC_It);
1839 roff_head_alloc(mdoc, nchild->line, nchild->pos, MDOC_It);
1840 mdoc->next = ROFF_NEXT_SIBLING;
1841 roff_body_alloc(mdoc, nchild->line, nchild->pos, MDOC_It);
1842 while (nchild->tok != MDOC_It) {
1843 roff_node_relink(mdoc, nchild);
1844 if (nnext == NULL)
1845 break;
1846 nchild = nnext;
1847 nnext = nchild->next;
1848 mdoc->next = ROFF_NEXT_SIBLING;
1849 }
1850 mdoc->last = nbody;
1851 }
1852
1853 if (mdoc->meta.os_e != MANDOC_OS_NETBSD)
1854 return;
1855
1856 prev_Er = NULL;
1857 for (nchild = nbody->child; nchild != NULL; nchild = nchild->next) {
1858 if (nchild->tok != MDOC_It)
1859 continue;
1860 if ((nnext = nchild->head->child) == NULL)
1861 continue;
1862 if (nnext->type == ROFFT_BLOCK)
1863 nnext = nnext->body->child;
1864 if (nnext == NULL || nnext->tok != MDOC_Er)
1865 continue;
1866 nnext = nnext->child;
1867 if (prev_Er != NULL) {
1868 order = strcmp(prev_Er, nnext->string);
1869 if (order > 0)
1870 mandoc_msg(MANDOCERR_ER_ORDER,
1871 nnext->line, nnext->pos,
1872 "Er %s %s (NetBSD)",
1873 prev_Er, nnext->string);
1874 else if (order == 0)
1875 mandoc_msg(MANDOCERR_ER_REP,
1876 nnext->line, nnext->pos,
1877 "Er %s (NetBSD)", prev_Er);
1878 }
1879 prev_Er = nnext->string;
1880 }
1881 }
1882
1883 static void
1884 post_bk(POST_ARGS)
1885 {
1886 struct roff_node *n;
1887
1888 n = mdoc->last;
1889
1890 if (n->type == ROFFT_BLOCK && n->body->child == NULL) {
1891 mandoc_msg(MANDOCERR_BLK_EMPTY, n->line, n->pos, "Bk");
1892 roff_node_delete(mdoc, n);
1893 }
1894 }
1895
1896 static void
1897 post_sm(POST_ARGS)
1898 {
1899 struct roff_node *nch;
1900
1901 nch = mdoc->last->child;
1902
1903 if (nch == NULL) {
1904 mdoc->flags ^= MDOC_SMOFF;
1905 return;
1906 }
1907
1908 assert(nch->type == ROFFT_TEXT);
1909
1910 if ( ! strcmp(nch->string, "on")) {
1911 mdoc->flags &= ~MDOC_SMOFF;
1912 return;
1913 }
1914 if ( ! strcmp(nch->string, "off")) {
1915 mdoc->flags |= MDOC_SMOFF;
1916 return;
1917 }
1918
1919 mandoc_msg(MANDOCERR_SM_BAD, nch->line, nch->pos,
1920 "%s %s", roff_name[mdoc->last->tok], nch->string);
1921 roff_node_relink(mdoc, nch);
1922 return;
1923 }
1924
1925 static void
1926 post_root(POST_ARGS)
1927 {
1928 struct roff_node *n;
1929
1930 /* Add missing prologue data. */
1931
1932 if (mdoc->meta.date == NULL)
1933 mdoc->meta.date = mandoc_normdate(NULL, NULL);
1934
1935 if (mdoc->meta.title == NULL) {
1936 mandoc_msg(MANDOCERR_DT_NOTITLE, 0, 0, "EOF");
1937 mdoc->meta.title = mandoc_strdup("UNTITLED");
1938 }
1939
1940 if (mdoc->meta.vol == NULL)
1941 mdoc->meta.vol = mandoc_strdup("LOCAL");
1942
1943 if (mdoc->meta.os == NULL) {
1944 mandoc_msg(MANDOCERR_OS_MISSING, 0, 0, NULL);
1945 mdoc->meta.os = mandoc_strdup("");
1946 } else if (mdoc->meta.os_e &&
1947 (mdoc->meta.rcsids & (1 << mdoc->meta.os_e)) == 0)
1948 mandoc_msg(MANDOCERR_RCS_MISSING, 0, 0,
1949 mdoc->meta.os_e == MANDOC_OS_OPENBSD ?
1950 "(OpenBSD)" : "(NetBSD)");
1951
1952 if (mdoc->meta.arch != NULL &&
1953 arch_valid(mdoc->meta.arch, mdoc->meta.os_e) == 0) {
1954 n = mdoc->meta.first->child;
1955 while (n->tok != MDOC_Dt ||
1956 n->child == NULL ||
1957 n->child->next == NULL ||
1958 n->child->next->next == NULL)
1959 n = n->next;
1960 n = n->child->next->next;
1961 mandoc_msg(MANDOCERR_ARCH_BAD, n->line, n->pos,
1962 "Dt ... %s %s", mdoc->meta.arch,
1963 mdoc->meta.os_e == MANDOC_OS_OPENBSD ?
1964 "(OpenBSD)" : "(NetBSD)");
1965 }
1966
1967 /* Check that we begin with a proper `Sh'. */
1968
1969 n = mdoc->meta.first->child;
1970 while (n != NULL &&
1971 (n->type == ROFFT_COMMENT ||
1972 (n->tok >= MDOC_Dd &&
1973 mdoc_macro(n->tok)->flags & MDOC_PROLOGUE)))
1974 n = n->next;
1975
1976 if (n == NULL)
1977 mandoc_msg(MANDOCERR_DOC_EMPTY, 0, 0, NULL);
1978 else if (n->tok != MDOC_Sh)
1979 mandoc_msg(MANDOCERR_SEC_BEFORE, n->line, n->pos,
1980 "%s", roff_name[n->tok]);
1981 }
1982
1983 static void
1984 post_rs(POST_ARGS)
1985 {
1986 struct roff_node *np, *nch, *next, *prev;
1987 int i, j;
1988
1989 np = mdoc->last;
1990
1991 if (np->type != ROFFT_BODY)
1992 return;
1993
1994 if (np->child == NULL) {
1995 mandoc_msg(MANDOCERR_RS_EMPTY, np->line, np->pos, "Rs");
1996 return;
1997 }
1998
1999 /*
2000 * The full `Rs' block needs special handling to order the
2001 * sub-elements according to `rsord'. Pick through each element
2002 * and correctly order it. This is an insertion sort.
2003 */
2004
2005 next = NULL;
2006 for (nch = np->child->next; nch != NULL; nch = next) {
2007 /* Determine order number of this child. */
2008 for (i = 0; i < RSORD_MAX; i++)
2009 if (rsord[i] == nch->tok)
2010 break;
2011
2012 if (i == RSORD_MAX) {
2013 mandoc_msg(MANDOCERR_RS_BAD, nch->line, nch->pos,
2014 "%s", roff_name[nch->tok]);
2015 i = -1;
2016 } else if (nch->tok == MDOC__J || nch->tok == MDOC__B)
2017 np->norm->Rs.quote_T++;
2018
2019 /*
2020 * Remove this child from the chain. This somewhat
2021 * repeats roff_node_unlink(), but since we're
2022 * just re-ordering, there's no need for the
2023 * full unlink process.
2024 */
2025
2026 if ((next = nch->next) != NULL)
2027 next->prev = nch->prev;
2028
2029 if ((prev = nch->prev) != NULL)
2030 prev->next = nch->next;
2031
2032 nch->prev = nch->next = NULL;
2033
2034 /*
2035 * Scan back until we reach a node that's
2036 * to be ordered before this child.
2037 */
2038
2039 for ( ; prev ; prev = prev->prev) {
2040 /* Determine order of `prev'. */
2041 for (j = 0; j < RSORD_MAX; j++)
2042 if (rsord[j] == prev->tok)
2043 break;
2044 if (j == RSORD_MAX)
2045 j = -1;
2046
2047 if (j <= i)
2048 break;
2049 }
2050
2051 /*
2052 * Set this child back into its correct place
2053 * in front of the `prev' node.
2054 */
2055
2056 nch->prev = prev;
2057
2058 if (prev == NULL) {
2059 np->child->prev = nch;
2060 nch->next = np->child;
2061 np->child = nch;
2062 } else {
2063 if (prev->next)
2064 prev->next->prev = nch;
2065 nch->next = prev->next;
2066 prev->next = nch;
2067 }
2068 }
2069 }
2070
2071 /*
2072 * For some arguments of some macros,
2073 * convert all breakable hyphens into ASCII_HYPH.
2074 */
2075 static void
2076 post_hyph(POST_ARGS)
2077 {
2078 struct roff_node *nch;
2079 char *cp;
2080
2081 for (nch = mdoc->last->child; nch != NULL; nch = nch->next) {
2082 if (nch->type != ROFFT_TEXT)
2083 continue;
2084 cp = nch->string;
2085 if (*cp == '\0')
2086 continue;
2087 while (*(++cp) != '\0')
2088 if (*cp == '-' &&
2089 isalpha((unsigned char)cp[-1]) &&
2090 isalpha((unsigned char)cp[1]))
2091 *cp = ASCII_HYPH;
2092 }
2093 }
2094
2095 static void
2096 post_ns(POST_ARGS)
2097 {
2098 struct roff_node *n;
2099
2100 n = mdoc->last;
2101 if (n->flags & NODE_LINE ||
2102 (n->next != NULL && n->next->flags & NODE_DELIMC))
2103 mandoc_msg(MANDOCERR_NS_SKIP, n->line, n->pos, NULL);
2104 }
2105
2106 static void
2107 post_sx(POST_ARGS)
2108 {
2109 post_delim(mdoc);
2110 post_hyph(mdoc);
2111 }
2112
2113 static void
2114 post_sh(POST_ARGS)
2115 {
2116
2117 post_ignpar(mdoc);
2118
2119 switch (mdoc->last->type) {
2120 case ROFFT_HEAD:
2121 post_sh_head(mdoc);
2122 break;
2123 case ROFFT_BODY:
2124 switch (mdoc->lastsec) {
2125 case SEC_NAME:
2126 post_sh_name(mdoc);
2127 break;
2128 case SEC_SEE_ALSO:
2129 post_sh_see_also(mdoc);
2130 break;
2131 case SEC_AUTHORS:
2132 post_sh_authors(mdoc);
2133 break;
2134 default:
2135 break;
2136 }
2137 break;
2138 default:
2139 break;
2140 }
2141 }
2142
2143 static void
2144 post_sh_name(POST_ARGS)
2145 {
2146 struct roff_node *n;
2147 int hasnm, hasnd;
2148
2149 hasnm = hasnd = 0;
2150
2151 for (n = mdoc->last->child; n != NULL; n = n->next) {
2152 switch (n->tok) {
2153 case MDOC_Nm:
2154 if (hasnm && n->child != NULL)
2155 mandoc_msg(MANDOCERR_NAMESEC_PUNCT,
2156 n->line, n->pos,
2157 "Nm %s", n->child->string);
2158 hasnm = 1;
2159 continue;
2160 case MDOC_Nd:
2161 hasnd = 1;
2162 if (n->next != NULL)
2163 mandoc_msg(MANDOCERR_NAMESEC_ND,
2164 n->line, n->pos, NULL);
2165 break;
2166 case TOKEN_NONE:
2167 if (n->type == ROFFT_TEXT &&
2168 n->string[0] == ',' && n->string[1] == '\0' &&
2169 n->next != NULL && n->next->tok == MDOC_Nm) {
2170 n = n->next;
2171 continue;
2172 }
2173 /* FALLTHROUGH */
2174 default:
2175 mandoc_msg(MANDOCERR_NAMESEC_BAD,
2176 n->line, n->pos, "%s", roff_name[n->tok]);
2177 continue;
2178 }
2179 break;
2180 }
2181
2182 if ( ! hasnm)
2183 mandoc_msg(MANDOCERR_NAMESEC_NONM,
2184 mdoc->last->line, mdoc->last->pos, NULL);
2185 if ( ! hasnd)
2186 mandoc_msg(MANDOCERR_NAMESEC_NOND,
2187 mdoc->last->line, mdoc->last->pos, NULL);
2188 }
2189
2190 static void
2191 post_sh_see_also(POST_ARGS)
2192 {
2193 const struct roff_node *n;
2194 const char *name, *sec;
2195 const char *lastname, *lastsec, *lastpunct;
2196 int cmp;
2197
2198 n = mdoc->last->child;
2199 lastname = lastsec = lastpunct = NULL;
2200 while (n != NULL) {
2201 if (n->tok != MDOC_Xr ||
2202 n->child == NULL ||
2203 n->child->next == NULL)
2204 break;
2205
2206 /* Process one .Xr node. */
2207
2208 name = n->child->string;
2209 sec = n->child->next->string;
2210 if (lastsec != NULL) {
2211 if (lastpunct[0] != ',' || lastpunct[1] != '\0')
2212 mandoc_msg(MANDOCERR_XR_PUNCT, n->line,
2213 n->pos, "%s before %s(%s)",
2214 lastpunct, name, sec);
2215 cmp = strcmp(lastsec, sec);
2216 if (cmp > 0)
2217 mandoc_msg(MANDOCERR_XR_ORDER, n->line,
2218 n->pos, "%s(%s) after %s(%s)",
2219 name, sec, lastname, lastsec);
2220 else if (cmp == 0 &&
2221 strcasecmp(lastname, name) > 0)
2222 mandoc_msg(MANDOCERR_XR_ORDER, n->line,
2223 n->pos, "%s after %s", name, lastname);
2224 }
2225 lastname = name;
2226 lastsec = sec;
2227
2228 /* Process the following node. */
2229
2230 n = n->next;
2231 if (n == NULL)
2232 break;
2233 if (n->tok == MDOC_Xr) {
2234 lastpunct = "none";
2235 continue;
2236 }
2237 if (n->type != ROFFT_TEXT)
2238 break;
2239 for (name = n->string; *name != '\0'; name++)
2240 if (isalpha((const unsigned char)*name))
2241 return;
2242 lastpunct = n->string;
2243 if (n->next == NULL || n->next->tok == MDOC_Rs)
2244 mandoc_msg(MANDOCERR_XR_PUNCT, n->line,
2245 n->pos, "%s after %s(%s)",
2246 lastpunct, lastname, lastsec);
2247 n = n->next;
2248 }
2249 }
2250
2251 static int
2252 child_an(const struct roff_node *n)
2253 {
2254
2255 for (n = n->child; n != NULL; n = n->next)
2256 if ((n->tok == MDOC_An && n->child != NULL) || child_an(n))
2257 return 1;
2258 return 0;
2259 }
2260
2261 static void
2262 post_sh_authors(POST_ARGS)
2263 {
2264
2265 if ( ! child_an(mdoc->last))
2266 mandoc_msg(MANDOCERR_AN_MISSING,
2267 mdoc->last->line, mdoc->last->pos, NULL);
2268 }
2269
2270 /*
2271 * Return an upper bound for the string distance (allowing
2272 * transpositions). Not a full Levenshtein implementation
2273 * because Levenshtein is quadratic in the string length
2274 * and this function is called for every standard name,
2275 * so the check for each custom name would be cubic.
2276 * The following crude heuristics is linear, resulting
2277 * in quadratic behaviour for checking one custom name,
2278 * which does not cause measurable slowdown.
2279 */
2280 static int
2281 similar(const char *s1, const char *s2)
2282 {
2283 const int maxdist = 3;
2284 int dist = 0;
2285
2286 while (s1[0] != '\0' && s2[0] != '\0') {
2287 if (s1[0] == s2[0]) {
2288 s1++;
2289 s2++;
2290 continue;
2291 }
2292 if (++dist > maxdist)
2293 return INT_MAX;
2294 if (s1[1] == s2[1]) { /* replacement */
2295 s1++;
2296 s2++;
2297 } else if (s1[0] == s2[1] && s1[1] == s2[0]) {
2298 s1 += 2; /* transposition */
2299 s2 += 2;
2300 } else if (s1[0] == s2[1]) /* insertion */
2301 s2++;
2302 else if (s1[1] == s2[0]) /* deletion */
2303 s1++;
2304 else
2305 return INT_MAX;
2306 }
2307 dist += strlen(s1) + strlen(s2);
2308 return dist > maxdist ? INT_MAX : dist;
2309 }
2310
2311 static void
2312 post_sh_head(POST_ARGS)
2313 {
2314 struct roff_node *nch;
2315 const char *goodsec;
2316 const char *const *testsec;
2317 int dist, mindist;
2318 enum roff_sec sec;
2319
2320 /*
2321 * Process a new section. Sections are either "named" or
2322 * "custom". Custom sections are user-defined, while named ones
2323 * follow a conventional order and may only appear in certain
2324 * manual sections.
2325 */
2326
2327 sec = mdoc->last->sec;
2328
2329 /* The NAME should be first. */
2330
2331 if (sec != SEC_NAME && mdoc->lastnamed == SEC_NONE)
2332 mandoc_msg(MANDOCERR_NAMESEC_FIRST,
2333 mdoc->last->line, mdoc->last->pos, "Sh %s",
2334 sec != SEC_CUSTOM ? secnames[sec] :
2335 (nch = mdoc->last->child) == NULL ? "" :
2336 nch->type == ROFFT_TEXT ? nch->string :
2337 roff_name[nch->tok]);
2338
2339 /* The SYNOPSIS gets special attention in other areas. */
2340
2341 if (sec == SEC_SYNOPSIS) {
2342 roff_setreg(mdoc->roff, "nS", 1, '=');
2343 mdoc->flags |= MDOC_SYNOPSIS;
2344 } else {
2345 roff_setreg(mdoc->roff, "nS", 0, '=');
2346 mdoc->flags &= ~MDOC_SYNOPSIS;
2347 }
2348
2349 /* Mark our last section. */
2350
2351 mdoc->lastsec = sec;
2352
2353 /* We don't care about custom sections after this. */
2354
2355 if (sec == SEC_CUSTOM) {
2356 if ((nch = mdoc->last->child) == NULL ||
2357 nch->type != ROFFT_TEXT || nch->next != NULL)
2358 return;
2359 goodsec = NULL;
2360 mindist = INT_MAX;
2361 for (testsec = secnames + 1; *testsec != NULL; testsec++) {
2362 dist = similar(nch->string, *testsec);
2363 if (dist < mindist) {
2364 goodsec = *testsec;
2365 mindist = dist;
2366 }
2367 }
2368 if (goodsec != NULL)
2369 mandoc_msg(MANDOCERR_SEC_TYPO, nch->line, nch->pos,
2370 "Sh %s instead of %s", nch->string, goodsec);
2371 return;
2372 }
2373
2374 /*
2375 * Check whether our non-custom section is being repeated or is
2376 * out of order.
2377 */
2378
2379 if (sec == mdoc->lastnamed)
2380 mandoc_msg(MANDOCERR_SEC_REP, mdoc->last->line,
2381 mdoc->last->pos, "Sh %s", secnames[sec]);
2382
2383 if (sec < mdoc->lastnamed)
2384 mandoc_msg(MANDOCERR_SEC_ORDER, mdoc->last->line,
2385 mdoc->last->pos, "Sh %s", secnames[sec]);
2386
2387 /* Mark the last named section. */
2388
2389 mdoc->lastnamed = sec;
2390
2391 /* Check particular section/manual conventions. */
2392
2393 if (mdoc->meta.msec == NULL)
2394 return;
2395
2396 goodsec = NULL;
2397 switch (sec) {
2398 case SEC_ERRORS:
2399 if (*mdoc->meta.msec == '4')
2400 break;
2401 goodsec = "2, 3, 4, 9";
2402 /* FALLTHROUGH */
2403 case SEC_RETURN_VALUES:
2404 case SEC_LIBRARY:
2405 if (*mdoc->meta.msec == '2')
2406 break;
2407 if (*mdoc->meta.msec == '3')
2408 break;
2409 if (NULL == goodsec)
2410 goodsec = "2, 3, 9";
2411 /* FALLTHROUGH */
2412 case SEC_CONTEXT:
2413 if (*mdoc->meta.msec == '9')
2414 break;
2415 if (NULL == goodsec)
2416 goodsec = "9";
2417 mandoc_msg(MANDOCERR_SEC_MSEC,
2418 mdoc->last->line, mdoc->last->pos,
2419 "Sh %s for %s only", secnames[sec], goodsec);
2420 break;
2421 default:
2422 break;
2423 }
2424 }
2425
2426 static void
2427 post_xr(POST_ARGS)
2428 {
2429 struct roff_node *n, *nch;
2430
2431 n = mdoc->last;
2432 nch = n->child;
2433 if (nch->next == NULL) {
2434 mandoc_msg(MANDOCERR_XR_NOSEC,
2435 n->line, n->pos, "Xr %s", nch->string);
2436 } else {
2437 assert(nch->next == n->last);
2438 if(mandoc_xr_add(nch->next->string, nch->string,
2439 nch->line, nch->pos))
2440 mandoc_msg(MANDOCERR_XR_SELF,
2441 nch->line, nch->pos, "Xr %s %s",
2442 nch->string, nch->next->string);
2443 }
2444 post_delim_nb(mdoc);
2445 }
2446
2447 static void
2448 post_ignpar(POST_ARGS)
2449 {
2450 struct roff_node *np;
2451
2452 switch (mdoc->last->type) {
2453 case ROFFT_BLOCK:
2454 post_prevpar(mdoc);
2455 return;
2456 case ROFFT_HEAD:
2457 post_delim(mdoc);
2458 post_hyph(mdoc);
2459 return;
2460 case ROFFT_BODY:
2461 break;
2462 default:
2463 return;
2464 }
2465
2466 if ((np = mdoc->last->child) != NULL)
2467 if (np->tok == MDOC_Pp ||
2468 np->tok == ROFF_br || np->tok == ROFF_sp) {
2469 mandoc_msg(MANDOCERR_PAR_SKIP, np->line, np->pos,
2470 "%s after %s", roff_name[np->tok],
2471 roff_name[mdoc->last->tok]);
2472 roff_node_delete(mdoc, np);
2473 }
2474
2475 if ((np = mdoc->last->last) != NULL)
2476 if (np->tok == MDOC_Pp || np->tok == ROFF_br) {
2477 mandoc_msg(MANDOCERR_PAR_SKIP, np->line, np->pos,
2478 "%s at the end of %s", roff_name[np->tok],
2479 roff_name[mdoc->last->tok]);
2480 roff_node_delete(mdoc, np);
2481 }
2482 }
2483
2484 static void
2485 post_prevpar(POST_ARGS)
2486 {
2487 struct roff_node *n, *np;
2488
2489 n = mdoc->last;
2490 if (n->type != ROFFT_ELEM && n->type != ROFFT_BLOCK)
2491 return;
2492 if ((np = roff_node_prev(n)) == NULL)
2493 return;
2494
2495 /*
2496 * Don't allow `Pp' prior to a paragraph-type
2497 * block: `Pp' or non-compact `Bd' or `Bl'.
2498 */
2499
2500 if (np->tok != MDOC_Pp && np->tok != ROFF_br)
2501 return;
2502 if (n->tok == MDOC_Bl && n->norm->Bl.comp)
2503 return;
2504 if (n->tok == MDOC_Bd && n->norm->Bd.comp)
2505 return;
2506 if (n->tok == MDOC_It && n->parent->norm->Bl.comp)
2507 return;
2508
2509 mandoc_msg(MANDOCERR_PAR_SKIP, np->line, np->pos,
2510 "%s before %s", roff_name[np->tok], roff_name[n->tok]);
2511 roff_node_delete(mdoc, np);
2512 }
2513
2514 static void
2515 post_par(POST_ARGS)
2516 {
2517 struct roff_node *np;
2518
2519 post_prevpar(mdoc);
2520
2521 np = mdoc->last;
2522 if (np->child != NULL)
2523 mandoc_msg(MANDOCERR_ARG_SKIP, np->line, np->pos,
2524 "%s %s", roff_name[np->tok], np->child->string);
2525 }
2526
2527 static void
2528 post_dd(POST_ARGS)
2529 {
2530 struct roff_node *n;
2531
2532 n = mdoc->last;
2533 n->flags |= NODE_NOPRT;
2534
2535 if (mdoc->meta.date != NULL) {
2536 mandoc_msg(MANDOCERR_PROLOG_REP, n->line, n->pos, "Dd");
2537 free(mdoc->meta.date);
2538 } else if (mdoc->flags & MDOC_PBODY)
2539 mandoc_msg(MANDOCERR_PROLOG_LATE, n->line, n->pos, "Dd");
2540 else if (mdoc->meta.title != NULL)
2541 mandoc_msg(MANDOCERR_PROLOG_ORDER,
2542 n->line, n->pos, "Dd after Dt");
2543 else if (mdoc->meta.os != NULL)
2544 mandoc_msg(MANDOCERR_PROLOG_ORDER,
2545 n->line, n->pos, "Dd after Os");
2546
2547 if (mdoc->quick && n != NULL)
2548 mdoc->meta.date = mandoc_strdup("");
2549 else
2550 mdoc->meta.date = mandoc_normdate(n->child, n);
2551 }
2552
2553 static void
2554 post_dt(POST_ARGS)
2555 {
2556 struct roff_node *nn, *n;
2557 const char *cp;
2558 char *p;
2559
2560 n = mdoc->last;
2561 n->flags |= NODE_NOPRT;
2562
2563 if (mdoc->flags & MDOC_PBODY) {
2564 mandoc_msg(MANDOCERR_DT_LATE, n->line, n->pos, "Dt");
2565 return;
2566 }
2567
2568 if (mdoc->meta.title != NULL)
2569 mandoc_msg(MANDOCERR_PROLOG_REP, n->line, n->pos, "Dt");
2570 else if (mdoc->meta.os != NULL)
2571 mandoc_msg(MANDOCERR_PROLOG_ORDER,
2572 n->line, n->pos, "Dt after Os");
2573
2574 free(mdoc->meta.title);
2575 free(mdoc->meta.msec);
2576 free(mdoc->meta.vol);
2577 free(mdoc->meta.arch);
2578
2579 mdoc->meta.title = NULL;
2580 mdoc->meta.msec = NULL;
2581 mdoc->meta.vol = NULL;
2582 mdoc->meta.arch = NULL;
2583
2584 /* Mandatory first argument: title. */
2585
2586 nn = n->child;
2587 if (nn == NULL || *nn->string == '\0') {
2588 mandoc_msg(MANDOCERR_DT_NOTITLE, n->line, n->pos, "Dt");
2589 mdoc->meta.title = mandoc_strdup("UNTITLED");
2590 } else {
2591 mdoc->meta.title = mandoc_strdup(nn->string);
2592
2593 /* Check that all characters are uppercase. */
2594
2595 for (p = nn->string; *p != '\0'; p++)
2596 if (islower((unsigned char)*p)) {
2597 mandoc_msg(MANDOCERR_TITLE_CASE, nn->line,
2598 nn->pos + (int)(p - nn->string),
2599 "Dt %s", nn->string);
2600 break;
2601 }
2602 }
2603
2604 /* Mandatory second argument: section. */
2605
2606 if (nn != NULL)
2607 nn = nn->next;
2608
2609 if (nn == NULL) {
2610 mandoc_msg(MANDOCERR_MSEC_MISSING, n->line, n->pos,
2611 "Dt %s", mdoc->meta.title);
2612 mdoc->meta.vol = mandoc_strdup("LOCAL");
2613 return; /* msec and arch remain NULL. */
2614 }
2615
2616 mdoc->meta.msec = mandoc_strdup(nn->string);
2617
2618 /* Infer volume title from section number. */
2619
2620 cp = mandoc_a2msec(nn->string);
2621 if (cp == NULL) {
2622 mandoc_msg(MANDOCERR_MSEC_BAD,
2623 nn->line, nn->pos, "Dt ... %s", nn->string);
2624 mdoc->meta.vol = mandoc_strdup(nn->string);
2625 } else
2626 mdoc->meta.vol = mandoc_strdup(cp);
2627
2628 /* Optional third argument: architecture. */
2629
2630 if ((nn = nn->next) == NULL)
2631 return;
2632
2633 for (p = nn->string; *p != '\0'; p++)
2634 *p = tolower((unsigned char)*p);
2635 mdoc->meta.arch = mandoc_strdup(nn->string);
2636
2637 /* Ignore fourth and later arguments. */
2638
2639 if ((nn = nn->next) != NULL)
2640 mandoc_msg(MANDOCERR_ARG_EXCESS,
2641 nn->line, nn->pos, "Dt ... %s", nn->string);
2642 }
2643
2644 static void
2645 post_bx(POST_ARGS)
2646 {
2647 struct roff_node *n, *nch;
2648 const char *macro;
2649
2650 post_delim_nb(mdoc);
2651
2652 n = mdoc->last;
2653 nch = n->child;
2654
2655 if (nch != NULL) {
2656 macro = !strcmp(nch->string, "Open") ? "Ox" :
2657 !strcmp(nch->string, "Net") ? "Nx" :
2658 !strcmp(nch->string, "Free") ? "Fx" :
2659 !strcmp(nch->string, "DragonFly") ? "Dx" : NULL;
2660 if (macro != NULL)
2661 mandoc_msg(MANDOCERR_BX,
2662 n->line, n->pos, "%s", macro);
2663 mdoc->last = nch;
2664 nch = nch->next;
2665 mdoc->next = ROFF_NEXT_SIBLING;
2666 roff_elem_alloc(mdoc, n->line, n->pos, MDOC_Ns);
2667 mdoc->last->flags |= NODE_NOSRC;
2668 mdoc->next = ROFF_NEXT_SIBLING;
2669 } else
2670 mdoc->next = ROFF_NEXT_CHILD;
2671 roff_word_alloc(mdoc, n->line, n->pos, "BSD");
2672 mdoc->last->flags |= NODE_NOSRC;
2673
2674 if (nch == NULL) {
2675 mdoc->last = n;
2676 return;
2677 }
2678
2679 roff_elem_alloc(mdoc, n->line, n->pos, MDOC_Ns);
2680 mdoc->last->flags |= NODE_NOSRC;
2681 mdoc->next = ROFF_NEXT_SIBLING;
2682 roff_word_alloc(mdoc, n->line, n->pos, "-");
2683 mdoc->last->flags |= NODE_NOSRC;
2684 roff_elem_alloc(mdoc, n->line, n->pos, MDOC_Ns);
2685 mdoc->last->flags |= NODE_NOSRC;
2686 mdoc->last = n;
2687
2688 /*
2689 * Make `Bx's second argument always start with an uppercase
2690 * letter. Groff checks if it's an "accepted" term, but we just
2691 * uppercase blindly.
2692 */
2693
2694 *nch->string = (char)toupper((unsigned char)*nch->string);
2695 }
2696
2697 static void
2698 post_os(POST_ARGS)
2699 {
2700 #ifndef OSNAME
2701 struct utsname utsname;
2702 static char *defbuf;
2703 #endif
2704 struct roff_node *n;
2705
2706 n = mdoc->last;
2707 n->flags |= NODE_NOPRT;
2708
2709 if (mdoc->meta.os != NULL)
2710 mandoc_msg(MANDOCERR_PROLOG_REP, n->line, n->pos, "Os");
2711 else if (mdoc->flags & MDOC_PBODY)
2712 mandoc_msg(MANDOCERR_PROLOG_LATE, n->line, n->pos, "Os");
2713
2714 post_delim(mdoc);
2715
2716 /*
2717 * Set the operating system by way of the `Os' macro.
2718 * The order of precedence is:
2719 * 1. the argument of the `Os' macro, unless empty
2720 * 2. the -Ios=foo command line argument, if provided
2721 * 3. -DOSNAME="\"foo\"", if provided during compilation
2722 * 4. "sysname release" from uname(3)
2723 */
2724
2725 free(mdoc->meta.os);
2726 mdoc->meta.os = NULL;
2727 deroff(&mdoc->meta.os, n);
2728 if (mdoc->meta.os)
2729 goto out;
2730
2731 if (mdoc->os_s != NULL) {
2732 mdoc->meta.os = mandoc_strdup(mdoc->os_s);
2733 goto out;
2734 }
2735
2736 #ifdef OSNAME
2737 mdoc->meta.os = mandoc_strdup(OSNAME);
2738 #else /*!OSNAME */
2739 if (defbuf == NULL) {
2740 if (uname(&utsname) == -1) {
2741 mandoc_msg(MANDOCERR_OS_UNAME, n->line, n->pos, "Os");
2742 defbuf = mandoc_strdup("UNKNOWN");
2743 } else
2744 mandoc_asprintf(&defbuf, "%s %s",
2745 utsname.sysname, utsname.release);
2746 }
2747 mdoc->meta.os = mandoc_strdup(defbuf);
2748 #endif /*!OSNAME*/
2749
2750 out:
2751 if (mdoc->meta.os_e == MANDOC_OS_OTHER) {
2752 if (strstr(mdoc->meta.os, "OpenBSD") != NULL)
2753 mdoc->meta.os_e = MANDOC_OS_OPENBSD;
2754 else if (strstr(mdoc->meta.os, "NetBSD") != NULL)
2755 mdoc->meta.os_e = MANDOC_OS_NETBSD;
2756 }
2757
2758 /*
2759 * This is the earliest point where we can check
2760 * Mdocdate conventions because we don't know
2761 * the operating system earlier.
2762 */
2763
2764 if (n->child != NULL)
2765 mandoc_msg(MANDOCERR_OS_ARG, n->child->line, n->child->pos,
2766 "Os %s (%s)", n->child->string,
2767 mdoc->meta.os_e == MANDOC_OS_OPENBSD ?
2768 "OpenBSD" : "NetBSD");
2769
2770 while (n->tok != MDOC_Dd)
2771 if ((n = n->prev) == NULL)
2772 return;
2773 if ((n = n->child) == NULL)
2774 return;
2775 if (strncmp(n->string, "$" "Mdocdate", 9)) {
2776 if (mdoc->meta.os_e == MANDOC_OS_OPENBSD)
2777 mandoc_msg(MANDOCERR_MDOCDATE_MISSING, n->line,
2778 n->pos, "Dd %s (OpenBSD)", n->string);
2779 } else {
2780 if (mdoc->meta.os_e == MANDOC_OS_NETBSD)
2781 mandoc_msg(MANDOCERR_MDOCDATE, n->line,
2782 n->pos, "Dd %s (NetBSD)", n->string);
2783 }
2784 }
2785
2786 enum roff_sec
2787 mdoc_a2sec(const char *p)
2788 {
2789 int i;
2790
2791 for (i = 0; i < (int)SEC__MAX; i++)
2792 if (secnames[i] && 0 == strcmp(p, secnames[i]))
2793 return (enum roff_sec)i;
2794
2795 return SEC_CUSTOM;
2796 }
2797
2798 static size_t
2799 macro2len(enum roff_tok macro)
2800 {
2801
2802 switch (macro) {
2803 case MDOC_Ad:
2804 return 12;
2805 case MDOC_Ao:
2806 return 12;
2807 case MDOC_An:
2808 return 12;
2809 case MDOC_Aq:
2810 return 12;
2811 case MDOC_Ar:
2812 return 12;
2813 case MDOC_Bo:
2814 return 12;
2815 case MDOC_Bq:
2816 return 12;
2817 case MDOC_Cd:
2818 return 12;
2819 case MDOC_Cm:
2820 return 10;
2821 case MDOC_Do:
2822 return 10;
2823 case MDOC_Dq:
2824 return 12;
2825 case MDOC_Dv:
2826 return 12;
2827 case MDOC_Eo:
2828 return 12;
2829 case MDOC_Em:
2830 return 10;
2831 case MDOC_Er:
2832 return 17;
2833 case MDOC_Ev:
2834 return 15;
2835 case MDOC_Fa:
2836 return 12;
2837 case MDOC_Fl:
2838 return 10;
2839 case MDOC_Fo:
2840 return 16;
2841 case MDOC_Fn:
2842 return 16;
2843 case MDOC_Ic:
2844 return 10;
2845 case MDOC_Li:
2846 return 16;
2847 case MDOC_Ms:
2848 return 6;
2849 case MDOC_Nm:
2850 return 10;
2851 case MDOC_No:
2852 return 12;
2853 case MDOC_Oo:
2854 return 10;
2855 case MDOC_Op:
2856 return 14;
2857 case MDOC_Pa:
2858 return 32;
2859 case MDOC_Pf:
2860 return 12;
2861 case MDOC_Po:
2862 return 12;
2863 case MDOC_Pq:
2864 return 12;
2865 case MDOC_Ql:
2866 return 16;
2867 case MDOC_Qo:
2868 return 12;
2869 case MDOC_So:
2870 return 12;
2871 case MDOC_Sq:
2872 return 12;
2873 case MDOC_Sy:
2874 return 6;
2875 case MDOC_Sx:
2876 return 16;
2877 case MDOC_Tn:
2878 return 10;
2879 case MDOC_Va:
2880 return 12;
2881 case MDOC_Vt:
2882 return 12;
2883 case MDOC_Xr:
2884 return 10;
2885 default:
2886 break;
2887 };
2888 return 0;
2889 }