]> git.cameronkatri.com Git - mandoc.git/blob - mdoc_validate.c
move man(7) validation into the dedicated validation phase, too
[mandoc.git] / mdoc_validate.c
1 /* $Id: mdoc_validate.c,v 1.299 2015/10/21 23:51:11 schwarze Exp $ */
2 /*
3 * Copyright (c) 2008-2012 Kristaps Dzonsons <kristaps@bsd.lv>
4 * Copyright (c) 2010-2015 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 "roff.h"
37 #include "mdoc.h"
38 #include "libmandoc.h"
39 #include "roff_int.h"
40 #include "libmdoc.h"
41
42 /* FIXME: .Bl -diag can't have non-text children in HEAD. */
43
44 #define POST_ARGS struct roff_man *mdoc
45
46 enum check_ineq {
47 CHECK_LT,
48 CHECK_GT,
49 CHECK_EQ
50 };
51
52 typedef void (*v_post)(POST_ARGS);
53
54 static void check_text(struct roff_man *, int, int, char *);
55 static void check_argv(struct roff_man *,
56 struct roff_node *, struct mdoc_argv *);
57 static void check_args(struct roff_man *, struct roff_node *);
58 static int child_an(const struct roff_node *);
59 static size_t macro2len(int);
60 static void rewrite_macro2len(char **);
61
62 static void post_an(POST_ARGS);
63 static void post_an_norm(POST_ARGS);
64 static void post_at(POST_ARGS);
65 static void post_bd(POST_ARGS);
66 static void post_bf(POST_ARGS);
67 static void post_bk(POST_ARGS);
68 static void post_bl(POST_ARGS);
69 static void post_bl_block(POST_ARGS);
70 static void post_bl_block_tag(POST_ARGS);
71 static void post_bl_head(POST_ARGS);
72 static void post_bl_norm(POST_ARGS);
73 static void post_bx(POST_ARGS);
74 static void post_defaults(POST_ARGS);
75 static void post_display(POST_ARGS);
76 static void post_dd(POST_ARGS);
77 static void post_dt(POST_ARGS);
78 static void post_en(POST_ARGS);
79 static void post_es(POST_ARGS);
80 static void post_eoln(POST_ARGS);
81 static void post_ex(POST_ARGS);
82 static void post_fa(POST_ARGS);
83 static void post_fn(POST_ARGS);
84 static void post_fname(POST_ARGS);
85 static void post_fo(POST_ARGS);
86 static void post_hyph(POST_ARGS);
87 static void post_ignpar(POST_ARGS);
88 static void post_it(POST_ARGS);
89 static void post_lb(POST_ARGS);
90 static void post_nd(POST_ARGS);
91 static void post_nm(POST_ARGS);
92 static void post_ns(POST_ARGS);
93 static void post_obsolete(POST_ARGS);
94 static void post_os(POST_ARGS);
95 static void post_par(POST_ARGS);
96 static void post_prevpar(POST_ARGS);
97 static void post_root(POST_ARGS);
98 static void post_rs(POST_ARGS);
99 static void post_sh(POST_ARGS);
100 static void post_sh_head(POST_ARGS);
101 static void post_sh_name(POST_ARGS);
102 static void post_sh_see_also(POST_ARGS);
103 static void post_sh_authors(POST_ARGS);
104 static void post_sm(POST_ARGS);
105 static void post_st(POST_ARGS);
106 static void post_std(POST_ARGS);
107
108 static v_post mdoc_valids[MDOC_MAX] = {
109 NULL, /* Ap */
110 post_dd, /* Dd */
111 post_dt, /* Dt */
112 post_os, /* Os */
113 post_sh, /* Sh */
114 post_ignpar, /* Ss */
115 post_par, /* Pp */
116 post_display, /* D1 */
117 post_display, /* Dl */
118 post_display, /* Bd */
119 NULL, /* Ed */
120 post_bl, /* Bl */
121 NULL, /* El */
122 post_it, /* It */
123 NULL, /* Ad */
124 post_an, /* An */
125 post_defaults, /* Ar */
126 NULL, /* Cd */
127 NULL, /* Cm */
128 NULL, /* Dv */
129 NULL, /* Er */
130 NULL, /* Ev */
131 post_ex, /* Ex */
132 post_fa, /* Fa */
133 NULL, /* Fd */
134 NULL, /* Fl */
135 post_fn, /* Fn */
136 NULL, /* Ft */
137 NULL, /* Ic */
138 NULL, /* In */
139 post_defaults, /* Li */
140 post_nd, /* Nd */
141 post_nm, /* Nm */
142 NULL, /* Op */
143 post_obsolete, /* Ot */
144 post_defaults, /* Pa */
145 post_std, /* Rv */
146 post_st, /* St */
147 NULL, /* Va */
148 NULL, /* Vt */
149 NULL, /* Xr */
150 NULL, /* %A */
151 post_hyph, /* %B */ /* FIXME: can be used outside Rs/Re. */
152 NULL, /* %D */
153 NULL, /* %I */
154 NULL, /* %J */
155 post_hyph, /* %N */
156 post_hyph, /* %O */
157 NULL, /* %P */
158 post_hyph, /* %R */
159 post_hyph, /* %T */ /* FIXME: can be used outside Rs/Re. */
160 NULL, /* %V */
161 NULL, /* Ac */
162 NULL, /* Ao */
163 NULL, /* Aq */
164 post_at, /* At */
165 NULL, /* Bc */
166 post_bf, /* Bf */
167 NULL, /* Bo */
168 NULL, /* Bq */
169 NULL, /* Bsx */
170 post_bx, /* Bx */
171 post_obsolete, /* Db */
172 NULL, /* Dc */
173 NULL, /* Do */
174 NULL, /* Dq */
175 NULL, /* Ec */
176 NULL, /* Ef */
177 NULL, /* Em */
178 NULL, /* Eo */
179 NULL, /* Fx */
180 NULL, /* Ms */
181 NULL, /* No */
182 post_ns, /* Ns */
183 NULL, /* Nx */
184 NULL, /* Ox */
185 NULL, /* Pc */
186 NULL, /* Pf */
187 NULL, /* Po */
188 NULL, /* Pq */
189 NULL, /* Qc */
190 NULL, /* Ql */
191 NULL, /* Qo */
192 NULL, /* Qq */
193 NULL, /* Re */
194 post_rs, /* Rs */
195 NULL, /* Sc */
196 NULL, /* So */
197 NULL, /* Sq */
198 post_sm, /* Sm */
199 post_hyph, /* Sx */
200 NULL, /* Sy */
201 NULL, /* Tn */
202 NULL, /* Ux */
203 NULL, /* Xc */
204 NULL, /* Xo */
205 post_fo, /* Fo */
206 NULL, /* Fc */
207 NULL, /* Oo */
208 NULL, /* Oc */
209 post_bk, /* Bk */
210 NULL, /* Ek */
211 post_eoln, /* Bt */
212 NULL, /* Hf */
213 post_obsolete, /* Fr */
214 post_eoln, /* Ud */
215 post_lb, /* Lb */
216 post_par, /* Lp */
217 NULL, /* Lk */
218 post_defaults, /* Mt */
219 NULL, /* Brq */
220 NULL, /* Bro */
221 NULL, /* Brc */
222 NULL, /* %C */
223 post_es, /* Es */
224 post_en, /* En */
225 NULL, /* Dx */
226 NULL, /* %Q */
227 post_par, /* br */
228 post_par, /* sp */
229 NULL, /* %U */
230 NULL, /* Ta */
231 NULL, /* ll */
232 };
233
234 #define RSORD_MAX 14 /* Number of `Rs' blocks. */
235
236 static const int rsord[RSORD_MAX] = {
237 MDOC__A,
238 MDOC__T,
239 MDOC__B,
240 MDOC__I,
241 MDOC__J,
242 MDOC__R,
243 MDOC__N,
244 MDOC__V,
245 MDOC__U,
246 MDOC__P,
247 MDOC__Q,
248 MDOC__C,
249 MDOC__D,
250 MDOC__O
251 };
252
253 static const char * const secnames[SEC__MAX] = {
254 NULL,
255 "NAME",
256 "LIBRARY",
257 "SYNOPSIS",
258 "DESCRIPTION",
259 "CONTEXT",
260 "IMPLEMENTATION NOTES",
261 "RETURN VALUES",
262 "ENVIRONMENT",
263 "FILES",
264 "EXIT STATUS",
265 "EXAMPLES",
266 "DIAGNOSTICS",
267 "COMPATIBILITY",
268 "ERRORS",
269 "SEE ALSO",
270 "STANDARDS",
271 "HISTORY",
272 "AUTHORS",
273 "CAVEATS",
274 "BUGS",
275 "SECURITY CONSIDERATIONS",
276 NULL
277 };
278
279
280 void
281 mdoc_node_validate(struct roff_man *mdoc)
282 {
283 struct roff_node *n;
284 v_post *p;
285
286 n = mdoc->last;
287 mdoc->last = mdoc->last->child;
288 while (mdoc->last != NULL) {
289 mdoc_node_validate(mdoc);
290 if (mdoc->last == n)
291 mdoc->last = mdoc->last->child;
292 else
293 mdoc->last = mdoc->last->next;
294 }
295
296 mdoc->last = n;
297 mdoc->next = ROFF_NEXT_SIBLING;
298 switch (n->type) {
299 case ROFFT_TEXT:
300 if (n->sec != SEC_SYNOPSIS || n->parent->tok != MDOC_Fd)
301 check_text(mdoc, n->line, n->pos, n->string);
302 break;
303 case ROFFT_EQN:
304 case ROFFT_TBL:
305 break;
306 case ROFFT_ROOT:
307 post_root(mdoc);
308 break;
309 default:
310 check_args(mdoc, mdoc->last);
311
312 /*
313 * Closing delimiters are not special at the
314 * beginning of a block, opening delimiters
315 * are not special at the end.
316 */
317
318 if (n->child != NULL)
319 n->child->flags &= ~MDOC_DELIMC;
320 if (n->last != NULL)
321 n->last->flags &= ~MDOC_DELIMO;
322
323 /* Call the macro's postprocessor. */
324
325 p = mdoc_valids + n->tok;
326 if (*p)
327 (*p)(mdoc);
328 if (mdoc->last == n)
329 mdoc_state(mdoc, n);
330 break;
331 }
332 }
333
334 static void
335 check_args(struct roff_man *mdoc, struct roff_node *n)
336 {
337 int i;
338
339 if (NULL == n->args)
340 return;
341
342 assert(n->args->argc);
343 for (i = 0; i < (int)n->args->argc; i++)
344 check_argv(mdoc, n, &n->args->argv[i]);
345 }
346
347 static void
348 check_argv(struct roff_man *mdoc, struct roff_node *n, struct mdoc_argv *v)
349 {
350 int i;
351
352 for (i = 0; i < (int)v->sz; i++)
353 check_text(mdoc, v->line, v->pos, v->value[i]);
354 }
355
356 static void
357 check_text(struct roff_man *mdoc, int ln, int pos, char *p)
358 {
359 char *cp;
360
361 if (MDOC_LITERAL & mdoc->flags)
362 return;
363
364 for (cp = p; NULL != (p = strchr(p, '\t')); p++)
365 mandoc_msg(MANDOCERR_FI_TAB, mdoc->parse,
366 ln, pos + (int)(p - cp), NULL);
367 }
368
369 static void
370 post_bl_norm(POST_ARGS)
371 {
372 struct roff_node *n;
373 struct mdoc_argv *argv, *wa;
374 int i;
375 enum mdocargt mdoclt;
376 enum mdoc_list lt;
377
378 n = mdoc->last->parent;
379 n->norm->Bl.type = LIST__NONE;
380
381 /*
382 * First figure out which kind of list to use: bind ourselves to
383 * the first mentioned list type and warn about any remaining
384 * ones. If we find no list type, we default to LIST_item.
385 */
386
387 wa = (n->args == NULL) ? NULL : n->args->argv;
388 mdoclt = MDOC_ARG_MAX;
389 for (i = 0; n->args && i < (int)n->args->argc; i++) {
390 argv = n->args->argv + i;
391 lt = LIST__NONE;
392 switch (argv->arg) {
393 /* Set list types. */
394 case MDOC_Bullet:
395 lt = LIST_bullet;
396 break;
397 case MDOC_Dash:
398 lt = LIST_dash;
399 break;
400 case MDOC_Enum:
401 lt = LIST_enum;
402 break;
403 case MDOC_Hyphen:
404 lt = LIST_hyphen;
405 break;
406 case MDOC_Item:
407 lt = LIST_item;
408 break;
409 case MDOC_Tag:
410 lt = LIST_tag;
411 break;
412 case MDOC_Diag:
413 lt = LIST_diag;
414 break;
415 case MDOC_Hang:
416 lt = LIST_hang;
417 break;
418 case MDOC_Ohang:
419 lt = LIST_ohang;
420 break;
421 case MDOC_Inset:
422 lt = LIST_inset;
423 break;
424 case MDOC_Column:
425 lt = LIST_column;
426 break;
427 /* Set list arguments. */
428 case MDOC_Compact:
429 if (n->norm->Bl.comp)
430 mandoc_msg(MANDOCERR_ARG_REP,
431 mdoc->parse, argv->line,
432 argv->pos, "Bl -compact");
433 n->norm->Bl.comp = 1;
434 break;
435 case MDOC_Width:
436 wa = argv;
437 if (0 == argv->sz) {
438 mandoc_msg(MANDOCERR_ARG_EMPTY,
439 mdoc->parse, argv->line,
440 argv->pos, "Bl -width");
441 n->norm->Bl.width = "0n";
442 break;
443 }
444 if (NULL != n->norm->Bl.width)
445 mandoc_vmsg(MANDOCERR_ARG_REP,
446 mdoc->parse, argv->line,
447 argv->pos, "Bl -width %s",
448 argv->value[0]);
449 rewrite_macro2len(argv->value);
450 n->norm->Bl.width = argv->value[0];
451 break;
452 case MDOC_Offset:
453 if (0 == argv->sz) {
454 mandoc_msg(MANDOCERR_ARG_EMPTY,
455 mdoc->parse, argv->line,
456 argv->pos, "Bl -offset");
457 break;
458 }
459 if (NULL != n->norm->Bl.offs)
460 mandoc_vmsg(MANDOCERR_ARG_REP,
461 mdoc->parse, argv->line,
462 argv->pos, "Bl -offset %s",
463 argv->value[0]);
464 rewrite_macro2len(argv->value);
465 n->norm->Bl.offs = argv->value[0];
466 break;
467 default:
468 continue;
469 }
470 if (LIST__NONE == lt)
471 continue;
472 mdoclt = argv->arg;
473
474 /* Check: multiple list types. */
475
476 if (LIST__NONE != n->norm->Bl.type) {
477 mandoc_vmsg(MANDOCERR_BL_REP,
478 mdoc->parse, n->line, n->pos,
479 "Bl -%s", mdoc_argnames[argv->arg]);
480 continue;
481 }
482
483 /* The list type should come first. */
484
485 if (n->norm->Bl.width ||
486 n->norm->Bl.offs ||
487 n->norm->Bl.comp)
488 mandoc_vmsg(MANDOCERR_BL_LATETYPE,
489 mdoc->parse, n->line, n->pos, "Bl -%s",
490 mdoc_argnames[n->args->argv[0].arg]);
491
492 n->norm->Bl.type = lt;
493 if (LIST_column == lt) {
494 n->norm->Bl.ncols = argv->sz;
495 n->norm->Bl.cols = (void *)argv->value;
496 }
497 }
498
499 /* Allow lists to default to LIST_item. */
500
501 if (LIST__NONE == n->norm->Bl.type) {
502 mandoc_msg(MANDOCERR_BL_NOTYPE, mdoc->parse,
503 n->line, n->pos, "Bl");
504 n->norm->Bl.type = LIST_item;
505 }
506
507 /*
508 * Validate the width field. Some list types don't need width
509 * types and should be warned about them. Others should have it
510 * and must also be warned. Yet others have a default and need
511 * no warning.
512 */
513
514 switch (n->norm->Bl.type) {
515 case LIST_tag:
516 if (NULL == n->norm->Bl.width)
517 mandoc_msg(MANDOCERR_BL_NOWIDTH, mdoc->parse,
518 n->line, n->pos, "Bl -tag");
519 break;
520 case LIST_column:
521 case LIST_diag:
522 case LIST_ohang:
523 case LIST_inset:
524 case LIST_item:
525 if (n->norm->Bl.width)
526 mandoc_vmsg(MANDOCERR_BL_SKIPW, mdoc->parse,
527 wa->line, wa->pos, "Bl -%s",
528 mdoc_argnames[mdoclt]);
529 break;
530 case LIST_bullet:
531 case LIST_dash:
532 case LIST_hyphen:
533 if (NULL == n->norm->Bl.width)
534 n->norm->Bl.width = "2n";
535 break;
536 case LIST_enum:
537 if (NULL == n->norm->Bl.width)
538 n->norm->Bl.width = "3n";
539 break;
540 default:
541 break;
542 }
543 }
544
545 static void
546 post_bd(POST_ARGS)
547 {
548 struct roff_node *n;
549 struct mdoc_argv *argv;
550 int i;
551 enum mdoc_disp dt;
552
553 n = mdoc->last;
554 for (i = 0; n->args && i < (int)n->args->argc; i++) {
555 argv = n->args->argv + i;
556 dt = DISP__NONE;
557
558 switch (argv->arg) {
559 case MDOC_Centred:
560 dt = DISP_centered;
561 break;
562 case MDOC_Ragged:
563 dt = DISP_ragged;
564 break;
565 case MDOC_Unfilled:
566 dt = DISP_unfilled;
567 break;
568 case MDOC_Filled:
569 dt = DISP_filled;
570 break;
571 case MDOC_Literal:
572 dt = DISP_literal;
573 break;
574 case MDOC_File:
575 mandoc_msg(MANDOCERR_BD_FILE, mdoc->parse,
576 n->line, n->pos, NULL);
577 break;
578 case MDOC_Offset:
579 if (0 == argv->sz) {
580 mandoc_msg(MANDOCERR_ARG_EMPTY,
581 mdoc->parse, argv->line,
582 argv->pos, "Bd -offset");
583 break;
584 }
585 if (NULL != n->norm->Bd.offs)
586 mandoc_vmsg(MANDOCERR_ARG_REP,
587 mdoc->parse, argv->line,
588 argv->pos, "Bd -offset %s",
589 argv->value[0]);
590 rewrite_macro2len(argv->value);
591 n->norm->Bd.offs = argv->value[0];
592 break;
593 case MDOC_Compact:
594 if (n->norm->Bd.comp)
595 mandoc_msg(MANDOCERR_ARG_REP,
596 mdoc->parse, argv->line,
597 argv->pos, "Bd -compact");
598 n->norm->Bd.comp = 1;
599 break;
600 default:
601 abort();
602 }
603 if (DISP__NONE == dt)
604 continue;
605
606 if (DISP__NONE == n->norm->Bd.type)
607 n->norm->Bd.type = dt;
608 else
609 mandoc_vmsg(MANDOCERR_BD_REP,
610 mdoc->parse, n->line, n->pos,
611 "Bd -%s", mdoc_argnames[argv->arg]);
612 }
613
614 if (DISP__NONE == n->norm->Bd.type) {
615 mandoc_msg(MANDOCERR_BD_NOTYPE, mdoc->parse,
616 n->line, n->pos, "Bd");
617 n->norm->Bd.type = DISP_ragged;
618 }
619 }
620
621 static void
622 post_an_norm(POST_ARGS)
623 {
624 struct roff_node *n;
625 struct mdoc_argv *argv;
626 size_t i;
627
628 n = mdoc->last;
629 if (n->args == NULL)
630 return;
631
632 for (i = 1; i < n->args->argc; i++) {
633 argv = n->args->argv + i;
634 mandoc_vmsg(MANDOCERR_AN_REP,
635 mdoc->parse, argv->line, argv->pos,
636 "An -%s", mdoc_argnames[argv->arg]);
637 }
638
639 argv = n->args->argv;
640 if (argv->arg == MDOC_Split)
641 n->norm->An.auth = AUTH_split;
642 else if (argv->arg == MDOC_Nosplit)
643 n->norm->An.auth = AUTH_nosplit;
644 else
645 abort();
646 }
647
648 static void
649 post_std(POST_ARGS)
650 {
651 struct roff_node *n;
652
653 n = mdoc->last;
654 if (n->args && n->args->argc == 1)
655 if (n->args->argv[0].arg == MDOC_Std)
656 return;
657
658 mandoc_msg(MANDOCERR_ARG_STD, mdoc->parse,
659 n->line, n->pos, mdoc_macronames[n->tok]);
660 }
661
662 static void
663 post_obsolete(POST_ARGS)
664 {
665 struct roff_node *n;
666
667 n = mdoc->last;
668 if (n->type == ROFFT_ELEM || n->type == ROFFT_BLOCK)
669 mandoc_msg(MANDOCERR_MACRO_OBS, mdoc->parse,
670 n->line, n->pos, mdoc_macronames[n->tok]);
671 }
672
673 static void
674 post_bf(POST_ARGS)
675 {
676 struct roff_node *np, *nch;
677
678 /*
679 * Unlike other data pointers, these are "housed" by the HEAD
680 * element, which contains the goods.
681 */
682
683 np = mdoc->last;
684 if (np->type != ROFFT_HEAD)
685 return;
686
687 assert(np->parent->type == ROFFT_BLOCK);
688 assert(np->parent->tok == MDOC_Bf);
689
690 /* Check the number of arguments. */
691
692 nch = np->child;
693 if (np->parent->args == NULL) {
694 if (nch == NULL) {
695 mandoc_msg(MANDOCERR_BF_NOFONT, mdoc->parse,
696 np->line, np->pos, "Bf");
697 return;
698 }
699 nch = nch->next;
700 }
701 if (nch != NULL)
702 mandoc_vmsg(MANDOCERR_ARG_EXCESS, mdoc->parse,
703 nch->line, nch->pos, "Bf ... %s", nch->string);
704
705 /* Extract argument into data. */
706
707 if (np->parent->args != NULL) {
708 switch (np->parent->args->argv[0].arg) {
709 case MDOC_Emphasis:
710 np->norm->Bf.font = FONT_Em;
711 break;
712 case MDOC_Literal:
713 np->norm->Bf.font = FONT_Li;
714 break;
715 case MDOC_Symbolic:
716 np->norm->Bf.font = FONT_Sy;
717 break;
718 default:
719 abort();
720 }
721 return;
722 }
723
724 /* Extract parameter into data. */
725
726 if ( ! strcmp(np->child->string, "Em"))
727 np->norm->Bf.font = FONT_Em;
728 else if ( ! strcmp(np->child->string, "Li"))
729 np->norm->Bf.font = FONT_Li;
730 else if ( ! strcmp(np->child->string, "Sy"))
731 np->norm->Bf.font = FONT_Sy;
732 else
733 mandoc_vmsg(MANDOCERR_BF_BADFONT, mdoc->parse,
734 np->child->line, np->child->pos,
735 "Bf %s", np->child->string);
736 }
737
738 static void
739 post_lb(POST_ARGS)
740 {
741 struct roff_node *n;
742 const char *stdlibname;
743 char *libname;
744
745 n = mdoc->last->child;
746 assert(n->type == ROFFT_TEXT);
747
748 if (NULL == (stdlibname = mdoc_a2lib(n->string)))
749 mandoc_asprintf(&libname,
750 "library \\(Lq%s\\(Rq", n->string);
751 else
752 libname = mandoc_strdup(stdlibname);
753
754 free(n->string);
755 n->string = libname;
756 }
757
758 static void
759 post_eoln(POST_ARGS)
760 {
761 const struct roff_node *n;
762
763 n = mdoc->last;
764 if (n->child != NULL)
765 mandoc_vmsg(MANDOCERR_ARG_SKIP,
766 mdoc->parse, n->line, n->pos,
767 "%s %s", mdoc_macronames[n->tok],
768 n->child->string);
769 }
770
771 static void
772 post_fname(POST_ARGS)
773 {
774 const struct roff_node *n;
775 const char *cp;
776 size_t pos;
777
778 n = mdoc->last->child;
779 pos = strcspn(n->string, "()");
780 cp = n->string + pos;
781 if ( ! (cp[0] == '\0' || (cp[0] == '(' && cp[1] == '*')))
782 mandoc_msg(MANDOCERR_FN_PAREN, mdoc->parse,
783 n->line, n->pos + pos, n->string);
784 }
785
786 static void
787 post_fn(POST_ARGS)
788 {
789
790 post_fname(mdoc);
791 post_fa(mdoc);
792 }
793
794 static void
795 post_fo(POST_ARGS)
796 {
797 const struct roff_node *n;
798
799 n = mdoc->last;
800
801 if (n->type != ROFFT_HEAD)
802 return;
803
804 if (n->child == NULL) {
805 mandoc_msg(MANDOCERR_FO_NOHEAD, mdoc->parse,
806 n->line, n->pos, "Fo");
807 return;
808 }
809 if (n->child != n->last) {
810 mandoc_vmsg(MANDOCERR_ARG_EXCESS, mdoc->parse,
811 n->child->next->line, n->child->next->pos,
812 "Fo ... %s", n->child->next->string);
813 while (n->child != n->last)
814 roff_node_delete(mdoc, n->last);
815 }
816
817 post_fname(mdoc);
818 }
819
820 static void
821 post_fa(POST_ARGS)
822 {
823 const struct roff_node *n;
824 const char *cp;
825
826 for (n = mdoc->last->child; n != NULL; n = n->next) {
827 for (cp = n->string; *cp != '\0'; cp++) {
828 /* Ignore callbacks and alterations. */
829 if (*cp == '(' || *cp == '{')
830 break;
831 if (*cp != ',')
832 continue;
833 mandoc_msg(MANDOCERR_FA_COMMA, mdoc->parse,
834 n->line, n->pos + (cp - n->string),
835 n->string);
836 break;
837 }
838 }
839 }
840
841 static void
842 post_nm(POST_ARGS)
843 {
844 struct roff_node *n;
845
846 n = mdoc->last;
847
848 if (n->last != NULL &&
849 (n->last->tok == MDOC_Pp ||
850 n->last->tok == MDOC_Lp))
851 mdoc_node_relink(mdoc, n->last);
852
853 if (mdoc->meta.name != NULL)
854 return;
855
856 deroff(&mdoc->meta.name, n);
857
858 if (mdoc->meta.name == NULL)
859 mandoc_msg(MANDOCERR_NM_NONAME, mdoc->parse,
860 n->line, n->pos, "Nm");
861 }
862
863 static void
864 post_nd(POST_ARGS)
865 {
866 struct roff_node *n;
867
868 n = mdoc->last;
869
870 if (n->type != ROFFT_BODY)
871 return;
872
873 if (n->child == NULL)
874 mandoc_msg(MANDOCERR_ND_EMPTY, mdoc->parse,
875 n->line, n->pos, "Nd");
876
877 post_hyph(mdoc);
878 }
879
880 static void
881 post_display(POST_ARGS)
882 {
883 struct roff_node *n, *np;
884
885 n = mdoc->last;
886 switch (n->type) {
887 case ROFFT_BODY:
888 if (n->end != ENDBODY_NOT)
889 break;
890 if (n->child == NULL)
891 mandoc_msg(MANDOCERR_BLK_EMPTY, mdoc->parse,
892 n->line, n->pos, mdoc_macronames[n->tok]);
893 else if (n->tok == MDOC_D1)
894 post_hyph(mdoc);
895 break;
896 case ROFFT_BLOCK:
897 if (n->tok == MDOC_Bd) {
898 post_bd(mdoc);
899 post_prevpar(mdoc);
900 }
901 for (np = n->parent; np != NULL; np = np->parent) {
902 if (np->type == ROFFT_BLOCK && np->tok == MDOC_Bd) {
903 mandoc_vmsg(MANDOCERR_BD_NEST,
904 mdoc->parse, n->line, n->pos,
905 "%s in Bd", mdoc_macronames[n->tok]);
906 break;
907 }
908 }
909 break;
910 default:
911 break;
912 }
913 }
914
915 static void
916 post_defaults(POST_ARGS)
917 {
918 struct roff_node *nn;
919
920 /*
921 * The `Ar' defaults to "file ..." if no value is provided as an
922 * argument; the `Mt' and `Pa' macros use "~"; the `Li' just
923 * gets an empty string.
924 */
925
926 if (mdoc->last->child != NULL)
927 return;
928
929 nn = mdoc->last;
930
931 switch (nn->tok) {
932 case MDOC_Ar:
933 mdoc->next = ROFF_NEXT_CHILD;
934 roff_word_alloc(mdoc, nn->line, nn->pos, "file");
935 roff_word_alloc(mdoc, nn->line, nn->pos, "...");
936 break;
937 case MDOC_Pa:
938 case MDOC_Mt:
939 mdoc->next = ROFF_NEXT_CHILD;
940 roff_word_alloc(mdoc, nn->line, nn->pos, "~");
941 break;
942 default:
943 abort();
944 }
945 mdoc->last = nn;
946 }
947
948 static void
949 post_at(POST_ARGS)
950 {
951 struct roff_node *n;
952 const char *std_att;
953 char *att;
954
955 n = mdoc->last;
956 if (n->child == NULL) {
957 mdoc->next = ROFF_NEXT_CHILD;
958 roff_word_alloc(mdoc, n->line, n->pos, "AT&T UNIX");
959 mdoc->last = n;
960 return;
961 }
962
963 /*
964 * If we have a child, look it up in the standard keys. If a
965 * key exist, use that instead of the child; if it doesn't,
966 * prefix "AT&T UNIX " to the existing data.
967 */
968
969 n = n->child;
970 assert(n->type == ROFFT_TEXT);
971 if ((std_att = mdoc_a2att(n->string)) == NULL) {
972 mandoc_vmsg(MANDOCERR_AT_BAD, mdoc->parse,
973 n->line, n->pos, "At %s", n->string);
974 mandoc_asprintf(&att, "AT&T UNIX %s", n->string);
975 } else
976 att = mandoc_strdup(std_att);
977
978 free(n->string);
979 n->string = att;
980 }
981
982 static void
983 post_an(POST_ARGS)
984 {
985 struct roff_node *np, *nch;
986
987 post_an_norm(mdoc);
988
989 np = mdoc->last;
990 nch = np->child;
991 if (np->norm->An.auth == AUTH__NONE) {
992 if (nch == NULL)
993 mandoc_msg(MANDOCERR_MACRO_EMPTY, mdoc->parse,
994 np->line, np->pos, "An");
995 } else if (nch != NULL)
996 mandoc_vmsg(MANDOCERR_ARG_EXCESS, mdoc->parse,
997 nch->line, nch->pos, "An ... %s", nch->string);
998 }
999
1000 static void
1001 post_en(POST_ARGS)
1002 {
1003
1004 post_obsolete(mdoc);
1005 if (mdoc->last->type == ROFFT_BLOCK)
1006 mdoc->last->norm->Es = mdoc->last_es;
1007 }
1008
1009 static void
1010 post_es(POST_ARGS)
1011 {
1012
1013 post_obsolete(mdoc);
1014 mdoc->last_es = mdoc->last;
1015 }
1016
1017 static void
1018 post_it(POST_ARGS)
1019 {
1020 struct roff_node *nbl, *nit, *nch;
1021 int i, cols;
1022 enum mdoc_list lt;
1023
1024 post_prevpar(mdoc);
1025
1026 nit = mdoc->last;
1027 if (nit->type != ROFFT_BLOCK)
1028 return;
1029
1030 nbl = nit->parent->parent;
1031 lt = nbl->norm->Bl.type;
1032
1033 switch (lt) {
1034 case LIST_tag:
1035 case LIST_hang:
1036 case LIST_ohang:
1037 case LIST_inset:
1038 case LIST_diag:
1039 if (nit->head->child == NULL)
1040 mandoc_vmsg(MANDOCERR_IT_NOHEAD,
1041 mdoc->parse, nit->line, nit->pos,
1042 "Bl -%s It",
1043 mdoc_argnames[nbl->args->argv[0].arg]);
1044 break;
1045 case LIST_bullet:
1046 case LIST_dash:
1047 case LIST_enum:
1048 case LIST_hyphen:
1049 if (nit->body == NULL || nit->body->child == NULL)
1050 mandoc_vmsg(MANDOCERR_IT_NOBODY,
1051 mdoc->parse, nit->line, nit->pos,
1052 "Bl -%s It",
1053 mdoc_argnames[nbl->args->argv[0].arg]);
1054 /* FALLTHROUGH */
1055 case LIST_item:
1056 if (nit->head->child != NULL)
1057 mandoc_vmsg(MANDOCERR_ARG_SKIP,
1058 mdoc->parse, nit->line, nit->pos,
1059 "It %s", nit->head->child->string);
1060 break;
1061 case LIST_column:
1062 cols = (int)nbl->norm->Bl.ncols;
1063
1064 assert(nit->head->child == NULL);
1065
1066 i = 0;
1067 for (nch = nit->child; nch != NULL; nch = nch->next)
1068 if (nch->type == ROFFT_BODY)
1069 i++;
1070
1071 if (i < cols || i > cols + 1)
1072 mandoc_vmsg(MANDOCERR_BL_COL,
1073 mdoc->parse, nit->line, nit->pos,
1074 "%d columns, %d cells", cols, i);
1075 break;
1076 default:
1077 abort();
1078 }
1079 }
1080
1081 static void
1082 post_bl_block(POST_ARGS)
1083 {
1084 struct roff_node *n, *ni, *nc;
1085
1086 post_prevpar(mdoc);
1087
1088 /*
1089 * These are fairly complicated, so we've broken them into two
1090 * functions. post_bl_block_tag() is called when a -tag is
1091 * specified, but no -width (it must be guessed). The second
1092 * when a -width is specified (macro indicators must be
1093 * rewritten into real lengths).
1094 */
1095
1096 n = mdoc->last;
1097
1098 if (n->norm->Bl.type == LIST_tag &&
1099 n->norm->Bl.width == NULL) {
1100 post_bl_block_tag(mdoc);
1101 assert(n->norm->Bl.width != NULL);
1102 }
1103
1104 for (ni = n->body->child; ni != NULL; ni = ni->next) {
1105 if (ni->body == NULL)
1106 continue;
1107 nc = ni->body->last;
1108 while (nc != NULL) {
1109 switch (nc->tok) {
1110 case MDOC_Pp:
1111 case MDOC_Lp:
1112 case MDOC_br:
1113 break;
1114 default:
1115 nc = NULL;
1116 continue;
1117 }
1118 if (ni->next == NULL) {
1119 mandoc_msg(MANDOCERR_PAR_MOVE,
1120 mdoc->parse, nc->line, nc->pos,
1121 mdoc_macronames[nc->tok]);
1122 mdoc_node_relink(mdoc, nc);
1123 } else if (n->norm->Bl.comp == 0 &&
1124 n->norm->Bl.type != LIST_column) {
1125 mandoc_vmsg(MANDOCERR_PAR_SKIP,
1126 mdoc->parse, nc->line, nc->pos,
1127 "%s before It",
1128 mdoc_macronames[nc->tok]);
1129 roff_node_delete(mdoc, nc);
1130 } else
1131 break;
1132 nc = ni->body->last;
1133 }
1134 }
1135 }
1136
1137 /*
1138 * If the argument of -offset or -width is a macro,
1139 * replace it with the associated default width.
1140 */
1141 void
1142 rewrite_macro2len(char **arg)
1143 {
1144 size_t width;
1145 int tok;
1146
1147 if (*arg == NULL)
1148 return;
1149 else if ( ! strcmp(*arg, "Ds"))
1150 width = 6;
1151 else if ((tok = mdoc_hash_find(*arg)) == TOKEN_NONE)
1152 return;
1153 else
1154 width = macro2len(tok);
1155
1156 free(*arg);
1157 mandoc_asprintf(arg, "%zun", width);
1158 }
1159
1160 static void
1161 post_bl_block_tag(POST_ARGS)
1162 {
1163 struct roff_node *n, *nn;
1164 size_t sz, ssz;
1165 int i;
1166 char buf[24];
1167
1168 /*
1169 * Calculate the -width for a `Bl -tag' list if it hasn't been
1170 * provided. Uses the first head macro. NOTE AGAIN: this is
1171 * ONLY if the -width argument has NOT been provided. See
1172 * rewrite_macro2len() for converting the -width string.
1173 */
1174
1175 sz = 10;
1176 n = mdoc->last;
1177
1178 for (nn = n->body->child; nn != NULL; nn = nn->next) {
1179 if (nn->tok != MDOC_It)
1180 continue;
1181
1182 assert(nn->type == ROFFT_BLOCK);
1183 nn = nn->head->child;
1184
1185 if (nn == NULL)
1186 break;
1187
1188 if (nn->type == ROFFT_TEXT) {
1189 sz = strlen(nn->string) + 1;
1190 break;
1191 }
1192
1193 if (0 != (ssz = macro2len(nn->tok)))
1194 sz = ssz;
1195
1196 break;
1197 }
1198
1199 /* Defaults to ten ens. */
1200
1201 (void)snprintf(buf, sizeof(buf), "%un", (unsigned int)sz);
1202
1203 /*
1204 * We have to dynamically add this to the macro's argument list.
1205 * We're guaranteed that a MDOC_Width doesn't already exist.
1206 */
1207
1208 assert(n->args != NULL);
1209 i = (int)(n->args->argc)++;
1210
1211 n->args->argv = mandoc_reallocarray(n->args->argv,
1212 n->args->argc, sizeof(struct mdoc_argv));
1213
1214 n->args->argv[i].arg = MDOC_Width;
1215 n->args->argv[i].line = n->line;
1216 n->args->argv[i].pos = n->pos;
1217 n->args->argv[i].sz = 1;
1218 n->args->argv[i].value = mandoc_malloc(sizeof(char *));
1219 n->args->argv[i].value[0] = mandoc_strdup(buf);
1220
1221 /* Set our width! */
1222 n->norm->Bl.width = n->args->argv[i].value[0];
1223 }
1224
1225 static void
1226 post_bl_head(POST_ARGS)
1227 {
1228 struct roff_node *nbl, *nh, *nch, *nnext;
1229 struct mdoc_argv *argv;
1230 int i, j;
1231
1232 post_bl_norm(mdoc);
1233
1234 nh = mdoc->last;
1235 if (nh->norm->Bl.type != LIST_column) {
1236 if ((nch = nh->child) == NULL)
1237 return;
1238 mandoc_vmsg(MANDOCERR_ARG_EXCESS, mdoc->parse,
1239 nch->line, nch->pos, "Bl ... %s", nch->string);
1240 while (nch != NULL) {
1241 roff_node_delete(mdoc, nch);
1242 nch = nh->child;
1243 }
1244 return;
1245 }
1246
1247 /*
1248 * Append old-style lists, where the column width specifiers
1249 * trail as macro parameters, to the new-style ("normal-form")
1250 * lists where they're argument values following -column.
1251 */
1252
1253 if (nh->child == NULL)
1254 return;
1255
1256 nbl = nh->parent;
1257 for (j = 0; j < (int)nbl->args->argc; j++)
1258 if (nbl->args->argv[j].arg == MDOC_Column)
1259 break;
1260
1261 assert(j < (int)nbl->args->argc);
1262
1263 /*
1264 * Accommodate for new-style groff column syntax. Shuffle the
1265 * child nodes, all of which must be TEXT, as arguments for the
1266 * column field. Then, delete the head children.
1267 */
1268
1269 argv = nbl->args->argv + j;
1270 i = argv->sz;
1271 argv->sz += nh->nchild;
1272 argv->value = mandoc_reallocarray(argv->value,
1273 argv->sz, sizeof(char *));
1274
1275 nh->norm->Bl.ncols = argv->sz;
1276 nh->norm->Bl.cols = (void *)argv->value;
1277
1278 for (nch = nh->child; nch != NULL; nch = nnext) {
1279 argv->value[i++] = nch->string;
1280 nch->string = NULL;
1281 nnext = nch->next;
1282 roff_node_delete(NULL, nch);
1283 }
1284 nh->nchild = 0;
1285 nh->child = NULL;
1286 }
1287
1288 static void
1289 post_bl(POST_ARGS)
1290 {
1291 struct roff_node *nparent, *nprev; /* of the Bl block */
1292 struct roff_node *nblock, *nbody; /* of the Bl */
1293 struct roff_node *nchild, *nnext; /* of the Bl body */
1294
1295 nbody = mdoc->last;
1296 switch (nbody->type) {
1297 case ROFFT_BLOCK:
1298 post_bl_block(mdoc);
1299 return;
1300 case ROFFT_HEAD:
1301 post_bl_head(mdoc);
1302 return;
1303 case ROFFT_BODY:
1304 break;
1305 default:
1306 return;
1307 }
1308 if (nbody->end != ENDBODY_NOT)
1309 return;
1310
1311 nchild = nbody->child;
1312 if (nchild == NULL) {
1313 mandoc_msg(MANDOCERR_BLK_EMPTY, mdoc->parse,
1314 nbody->line, nbody->pos, "Bl");
1315 return;
1316 }
1317 while (nchild != NULL) {
1318 if (nchild->tok == MDOC_It ||
1319 (nchild->tok == MDOC_Sm &&
1320 nchild->next != NULL &&
1321 nchild->next->tok == MDOC_It)) {
1322 nchild = nchild->next;
1323 continue;
1324 }
1325
1326 mandoc_msg(MANDOCERR_BL_MOVE, mdoc->parse,
1327 nchild->line, nchild->pos,
1328 mdoc_macronames[nchild->tok]);
1329
1330 /*
1331 * Move the node out of the Bl block.
1332 * First, collect all required node pointers.
1333 */
1334
1335 nblock = nbody->parent;
1336 nprev = nblock->prev;
1337 nparent = nblock->parent;
1338 nnext = nchild->next;
1339
1340 /*
1341 * Unlink this child.
1342 */
1343
1344 assert(nchild->prev == NULL);
1345 if (--nbody->nchild == 0) {
1346 nbody->child = NULL;
1347 nbody->last = NULL;
1348 assert(nnext == NULL);
1349 } else {
1350 nbody->child = nnext;
1351 nnext->prev = NULL;
1352 }
1353
1354 /*
1355 * Relink this child.
1356 */
1357
1358 nchild->parent = nparent;
1359 nchild->prev = nprev;
1360 nchild->next = nblock;
1361
1362 nblock->prev = nchild;
1363 nparent->nchild++;
1364 if (nprev == NULL)
1365 nparent->child = nchild;
1366 else
1367 nprev->next = nchild;
1368
1369 nchild = nnext;
1370 }
1371 }
1372
1373 static void
1374 post_bk(POST_ARGS)
1375 {
1376 struct roff_node *n;
1377
1378 n = mdoc->last;
1379
1380 if (n->type == ROFFT_BLOCK && n->body->child == NULL) {
1381 mandoc_msg(MANDOCERR_BLK_EMPTY,
1382 mdoc->parse, n->line, n->pos, "Bk");
1383 roff_node_delete(mdoc, n);
1384 }
1385 }
1386
1387 static void
1388 post_sm(POST_ARGS)
1389 {
1390 struct roff_node *nch;
1391
1392 nch = mdoc->last->child;
1393
1394 if (nch == NULL) {
1395 mdoc->flags ^= MDOC_SMOFF;
1396 return;
1397 }
1398
1399 assert(nch->type == ROFFT_TEXT);
1400
1401 if ( ! strcmp(nch->string, "on")) {
1402 mdoc->flags &= ~MDOC_SMOFF;
1403 return;
1404 }
1405 if ( ! strcmp(nch->string, "off")) {
1406 mdoc->flags |= MDOC_SMOFF;
1407 return;
1408 }
1409
1410 mandoc_vmsg(MANDOCERR_SM_BAD,
1411 mdoc->parse, nch->line, nch->pos,
1412 "%s %s", mdoc_macronames[mdoc->last->tok], nch->string);
1413 mdoc_node_relink(mdoc, nch);
1414 return;
1415 }
1416
1417 static void
1418 post_root(POST_ARGS)
1419 {
1420 struct roff_node *n;
1421
1422 /* Add missing prologue data. */
1423
1424 if (mdoc->meta.date == NULL)
1425 mdoc->meta.date = mdoc->quick ?
1426 mandoc_strdup("") :
1427 mandoc_normdate(mdoc->parse, NULL, 0, 0);
1428
1429 if (mdoc->meta.title == NULL) {
1430 mandoc_msg(MANDOCERR_DT_NOTITLE,
1431 mdoc->parse, 0, 0, "EOF");
1432 mdoc->meta.title = mandoc_strdup("UNTITLED");
1433 }
1434
1435 if (mdoc->meta.vol == NULL)
1436 mdoc->meta.vol = mandoc_strdup("LOCAL");
1437
1438 if (mdoc->meta.os == NULL) {
1439 mandoc_msg(MANDOCERR_OS_MISSING,
1440 mdoc->parse, 0, 0, NULL);
1441 mdoc->meta.os = mandoc_strdup("");
1442 }
1443
1444 /* Check that we begin with a proper `Sh'. */
1445
1446 n = mdoc->first->child;
1447 while (n != NULL && n->tok != TOKEN_NONE &&
1448 mdoc_macros[n->tok].flags & MDOC_PROLOGUE)
1449 n = n->next;
1450
1451 if (n == NULL)
1452 mandoc_msg(MANDOCERR_DOC_EMPTY, mdoc->parse, 0, 0, NULL);
1453 else if (n->tok != MDOC_Sh)
1454 mandoc_msg(MANDOCERR_SEC_BEFORE, mdoc->parse,
1455 n->line, n->pos, mdoc_macronames[n->tok]);
1456 }
1457
1458 static void
1459 post_st(POST_ARGS)
1460 {
1461 struct roff_node *n, *nch;
1462 const char *p;
1463
1464 n = mdoc->last;
1465 nch = n->child;
1466
1467 assert(nch->type == ROFFT_TEXT);
1468
1469 if ((p = mdoc_a2st(nch->string)) == NULL) {
1470 mandoc_vmsg(MANDOCERR_ST_BAD, mdoc->parse,
1471 nch->line, nch->pos, "St %s", nch->string);
1472 roff_node_delete(mdoc, n);
1473 } else {
1474 free(nch->string);
1475 nch->string = mandoc_strdup(p);
1476 }
1477 }
1478
1479 static void
1480 post_rs(POST_ARGS)
1481 {
1482 struct roff_node *np, *nch, *next, *prev;
1483 int i, j;
1484
1485 np = mdoc->last;
1486
1487 if (np->type != ROFFT_BODY)
1488 return;
1489
1490 if (np->child == NULL) {
1491 mandoc_msg(MANDOCERR_RS_EMPTY, mdoc->parse,
1492 np->line, np->pos, "Rs");
1493 return;
1494 }
1495
1496 /*
1497 * The full `Rs' block needs special handling to order the
1498 * sub-elements according to `rsord'. Pick through each element
1499 * and correctly order it. This is an insertion sort.
1500 */
1501
1502 next = NULL;
1503 for (nch = np->child->next; nch != NULL; nch = next) {
1504 /* Determine order number of this child. */
1505 for (i = 0; i < RSORD_MAX; i++)
1506 if (rsord[i] == nch->tok)
1507 break;
1508
1509 if (i == RSORD_MAX) {
1510 mandoc_msg(MANDOCERR_RS_BAD,
1511 mdoc->parse, nch->line, nch->pos,
1512 mdoc_macronames[nch->tok]);
1513 i = -1;
1514 } else if (nch->tok == MDOC__J || nch->tok == MDOC__B)
1515 np->norm->Rs.quote_T++;
1516
1517 /*
1518 * Remove this child from the chain. This somewhat
1519 * repeats roff_node_unlink(), but since we're
1520 * just re-ordering, there's no need for the
1521 * full unlink process.
1522 */
1523
1524 if ((next = nch->next) != NULL)
1525 next->prev = nch->prev;
1526
1527 if ((prev = nch->prev) != NULL)
1528 prev->next = nch->next;
1529
1530 nch->prev = nch->next = NULL;
1531
1532 /*
1533 * Scan back until we reach a node that's
1534 * to be ordered before this child.
1535 */
1536
1537 for ( ; prev ; prev = prev->prev) {
1538 /* Determine order of `prev'. */
1539 for (j = 0; j < RSORD_MAX; j++)
1540 if (rsord[j] == prev->tok)
1541 break;
1542 if (j == RSORD_MAX)
1543 j = -1;
1544
1545 if (j <= i)
1546 break;
1547 }
1548
1549 /*
1550 * Set this child back into its correct place
1551 * in front of the `prev' node.
1552 */
1553
1554 nch->prev = prev;
1555
1556 if (prev == NULL) {
1557 np->child->prev = nch;
1558 nch->next = np->child;
1559 np->child = nch;
1560 } else {
1561 if (prev->next)
1562 prev->next->prev = nch;
1563 nch->next = prev->next;
1564 prev->next = nch;
1565 }
1566 }
1567 }
1568
1569 /*
1570 * For some arguments of some macros,
1571 * convert all breakable hyphens into ASCII_HYPH.
1572 */
1573 static void
1574 post_hyph(POST_ARGS)
1575 {
1576 struct roff_node *nch;
1577 char *cp;
1578
1579 for (nch = mdoc->last->child; nch != NULL; nch = nch->next) {
1580 if (nch->type != ROFFT_TEXT)
1581 continue;
1582 cp = nch->string;
1583 if (*cp == '\0')
1584 continue;
1585 while (*(++cp) != '\0')
1586 if (*cp == '-' &&
1587 isalpha((unsigned char)cp[-1]) &&
1588 isalpha((unsigned char)cp[1]))
1589 *cp = ASCII_HYPH;
1590 }
1591 }
1592
1593 static void
1594 post_ns(POST_ARGS)
1595 {
1596
1597 if (mdoc->last->flags & MDOC_LINE)
1598 mandoc_msg(MANDOCERR_NS_SKIP, mdoc->parse,
1599 mdoc->last->line, mdoc->last->pos, NULL);
1600 }
1601
1602 static void
1603 post_sh(POST_ARGS)
1604 {
1605
1606 post_ignpar(mdoc);
1607
1608 switch (mdoc->last->type) {
1609 case ROFFT_HEAD:
1610 post_sh_head(mdoc);
1611 break;
1612 case ROFFT_BODY:
1613 switch (mdoc->lastsec) {
1614 case SEC_NAME:
1615 post_sh_name(mdoc);
1616 break;
1617 case SEC_SEE_ALSO:
1618 post_sh_see_also(mdoc);
1619 break;
1620 case SEC_AUTHORS:
1621 post_sh_authors(mdoc);
1622 break;
1623 default:
1624 break;
1625 }
1626 break;
1627 default:
1628 break;
1629 }
1630 }
1631
1632 static void
1633 post_sh_name(POST_ARGS)
1634 {
1635 struct roff_node *n;
1636 int hasnm, hasnd;
1637
1638 hasnm = hasnd = 0;
1639
1640 for (n = mdoc->last->child; n != NULL; n = n->next) {
1641 switch (n->tok) {
1642 case MDOC_Nm:
1643 hasnm = 1;
1644 break;
1645 case MDOC_Nd:
1646 hasnd = 1;
1647 if (n->next != NULL)
1648 mandoc_msg(MANDOCERR_NAMESEC_ND,
1649 mdoc->parse, n->line, n->pos, NULL);
1650 break;
1651 case TOKEN_NONE:
1652 if (hasnm)
1653 break;
1654 /* FALLTHROUGH */
1655 default:
1656 mandoc_msg(MANDOCERR_NAMESEC_BAD, mdoc->parse,
1657 n->line, n->pos, mdoc_macronames[n->tok]);
1658 break;
1659 }
1660 }
1661
1662 if ( ! hasnm)
1663 mandoc_msg(MANDOCERR_NAMESEC_NONM, mdoc->parse,
1664 mdoc->last->line, mdoc->last->pos, NULL);
1665 if ( ! hasnd)
1666 mandoc_msg(MANDOCERR_NAMESEC_NOND, mdoc->parse,
1667 mdoc->last->line, mdoc->last->pos, NULL);
1668 }
1669
1670 static void
1671 post_sh_see_also(POST_ARGS)
1672 {
1673 const struct roff_node *n;
1674 const char *name, *sec;
1675 const char *lastname, *lastsec, *lastpunct;
1676 int cmp;
1677
1678 n = mdoc->last->child;
1679 lastname = lastsec = lastpunct = NULL;
1680 while (n != NULL) {
1681 if (n->tok != MDOC_Xr || n->nchild < 2)
1682 break;
1683
1684 /* Process one .Xr node. */
1685
1686 name = n->child->string;
1687 sec = n->child->next->string;
1688 if (lastsec != NULL) {
1689 if (lastpunct[0] != ',' || lastpunct[1] != '\0')
1690 mandoc_vmsg(MANDOCERR_XR_PUNCT,
1691 mdoc->parse, n->line, n->pos,
1692 "%s before %s(%s)", lastpunct,
1693 name, sec);
1694 cmp = strcmp(lastsec, sec);
1695 if (cmp > 0)
1696 mandoc_vmsg(MANDOCERR_XR_ORDER,
1697 mdoc->parse, n->line, n->pos,
1698 "%s(%s) after %s(%s)", name,
1699 sec, lastname, lastsec);
1700 else if (cmp == 0 &&
1701 strcasecmp(lastname, name) > 0)
1702 mandoc_vmsg(MANDOCERR_XR_ORDER,
1703 mdoc->parse, n->line, n->pos,
1704 "%s after %s", name, lastname);
1705 }
1706 lastname = name;
1707 lastsec = sec;
1708
1709 /* Process the following node. */
1710
1711 n = n->next;
1712 if (n == NULL)
1713 break;
1714 if (n->tok == MDOC_Xr) {
1715 lastpunct = "none";
1716 continue;
1717 }
1718 if (n->type != ROFFT_TEXT)
1719 break;
1720 for (name = n->string; *name != '\0'; name++)
1721 if (isalpha((const unsigned char)*name))
1722 return;
1723 lastpunct = n->string;
1724 if (n->next == NULL)
1725 mandoc_vmsg(MANDOCERR_XR_PUNCT, mdoc->parse,
1726 n->line, n->pos, "%s after %s(%s)",
1727 lastpunct, lastname, lastsec);
1728 n = n->next;
1729 }
1730 }
1731
1732 static int
1733 child_an(const struct roff_node *n)
1734 {
1735
1736 for (n = n->child; n != NULL; n = n->next)
1737 if ((n->tok == MDOC_An && n->nchild) || child_an(n))
1738 return 1;
1739 return 0;
1740 }
1741
1742 static void
1743 post_sh_authors(POST_ARGS)
1744 {
1745
1746 if ( ! child_an(mdoc->last))
1747 mandoc_msg(MANDOCERR_AN_MISSING, mdoc->parse,
1748 mdoc->last->line, mdoc->last->pos, NULL);
1749 }
1750
1751 static void
1752 post_sh_head(POST_ARGS)
1753 {
1754 const char *goodsec;
1755 enum roff_sec sec;
1756
1757 /*
1758 * Process a new section. Sections are either "named" or
1759 * "custom". Custom sections are user-defined, while named ones
1760 * follow a conventional order and may only appear in certain
1761 * manual sections.
1762 */
1763
1764 sec = mdoc->last->sec;
1765
1766 /* The NAME should be first. */
1767
1768 if (SEC_NAME != sec && SEC_NONE == mdoc->lastnamed)
1769 mandoc_vmsg(MANDOCERR_NAMESEC_FIRST, mdoc->parse,
1770 mdoc->last->line, mdoc->last->pos,
1771 "Sh %s", secnames[sec]);
1772
1773 /* The SYNOPSIS gets special attention in other areas. */
1774
1775 if (sec == SEC_SYNOPSIS) {
1776 roff_setreg(mdoc->roff, "nS", 1, '=');
1777 mdoc->flags |= MDOC_SYNOPSIS;
1778 } else {
1779 roff_setreg(mdoc->roff, "nS", 0, '=');
1780 mdoc->flags &= ~MDOC_SYNOPSIS;
1781 }
1782
1783 /* Mark our last section. */
1784
1785 mdoc->lastsec = sec;
1786
1787 /* We don't care about custom sections after this. */
1788
1789 if (sec == SEC_CUSTOM)
1790 return;
1791
1792 /*
1793 * Check whether our non-custom section is being repeated or is
1794 * out of order.
1795 */
1796
1797 if (sec == mdoc->lastnamed)
1798 mandoc_vmsg(MANDOCERR_SEC_REP, mdoc->parse,
1799 mdoc->last->line, mdoc->last->pos,
1800 "Sh %s", secnames[sec]);
1801
1802 if (sec < mdoc->lastnamed)
1803 mandoc_vmsg(MANDOCERR_SEC_ORDER, mdoc->parse,
1804 mdoc->last->line, mdoc->last->pos,
1805 "Sh %s", secnames[sec]);
1806
1807 /* Mark the last named section. */
1808
1809 mdoc->lastnamed = sec;
1810
1811 /* Check particular section/manual conventions. */
1812
1813 if (mdoc->meta.msec == NULL)
1814 return;
1815
1816 goodsec = NULL;
1817 switch (sec) {
1818 case SEC_ERRORS:
1819 if (*mdoc->meta.msec == '4')
1820 break;
1821 goodsec = "2, 3, 4, 9";
1822 /* FALLTHROUGH */
1823 case SEC_RETURN_VALUES:
1824 case SEC_LIBRARY:
1825 if (*mdoc->meta.msec == '2')
1826 break;
1827 if (*mdoc->meta.msec == '3')
1828 break;
1829 if (NULL == goodsec)
1830 goodsec = "2, 3, 9";
1831 /* FALLTHROUGH */
1832 case SEC_CONTEXT:
1833 if (*mdoc->meta.msec == '9')
1834 break;
1835 if (NULL == goodsec)
1836 goodsec = "9";
1837 mandoc_vmsg(MANDOCERR_SEC_MSEC, mdoc->parse,
1838 mdoc->last->line, mdoc->last->pos,
1839 "Sh %s for %s only", secnames[sec], goodsec);
1840 break;
1841 default:
1842 break;
1843 }
1844 }
1845
1846 static void
1847 post_ignpar(POST_ARGS)
1848 {
1849 struct roff_node *np;
1850
1851 switch (mdoc->last->type) {
1852 case ROFFT_HEAD:
1853 post_hyph(mdoc);
1854 return;
1855 case ROFFT_BODY:
1856 break;
1857 default:
1858 return;
1859 }
1860
1861 if ((np = mdoc->last->child) != NULL)
1862 if (np->tok == MDOC_Pp || np->tok == MDOC_Lp) {
1863 mandoc_vmsg(MANDOCERR_PAR_SKIP,
1864 mdoc->parse, np->line, np->pos,
1865 "%s after %s", mdoc_macronames[np->tok],
1866 mdoc_macronames[mdoc->last->tok]);
1867 roff_node_delete(mdoc, np);
1868 }
1869
1870 if ((np = mdoc->last->last) != NULL)
1871 if (np->tok == MDOC_Pp || np->tok == MDOC_Lp) {
1872 mandoc_vmsg(MANDOCERR_PAR_SKIP, mdoc->parse,
1873 np->line, np->pos, "%s at the end of %s",
1874 mdoc_macronames[np->tok],
1875 mdoc_macronames[mdoc->last->tok]);
1876 roff_node_delete(mdoc, np);
1877 }
1878 }
1879
1880 static void
1881 post_prevpar(POST_ARGS)
1882 {
1883 struct roff_node *n;
1884
1885 n = mdoc->last;
1886 if (NULL == n->prev)
1887 return;
1888 if (n->type != ROFFT_ELEM && n->type != ROFFT_BLOCK)
1889 return;
1890
1891 /*
1892 * Don't allow prior `Lp' or `Pp' prior to a paragraph-type
1893 * block: `Lp', `Pp', or non-compact `Bd' or `Bl'.
1894 */
1895
1896 if (n->prev->tok != MDOC_Pp &&
1897 n->prev->tok != MDOC_Lp &&
1898 n->prev->tok != MDOC_br)
1899 return;
1900 if (n->tok == MDOC_Bl && n->norm->Bl.comp)
1901 return;
1902 if (n->tok == MDOC_Bd && n->norm->Bd.comp)
1903 return;
1904 if (n->tok == MDOC_It && n->parent->norm->Bl.comp)
1905 return;
1906
1907 mandoc_vmsg(MANDOCERR_PAR_SKIP, mdoc->parse,
1908 n->prev->line, n->prev->pos,
1909 "%s before %s", mdoc_macronames[n->prev->tok],
1910 mdoc_macronames[n->tok]);
1911 roff_node_delete(mdoc, n->prev);
1912 }
1913
1914 static void
1915 post_par(POST_ARGS)
1916 {
1917 struct roff_node *np;
1918
1919 np = mdoc->last;
1920 if (np->tok != MDOC_br && np->tok != MDOC_sp)
1921 post_prevpar(mdoc);
1922
1923 if (np->tok == MDOC_sp) {
1924 if (np->nchild > 1)
1925 mandoc_vmsg(MANDOCERR_ARG_EXCESS, mdoc->parse,
1926 np->child->next->line, np->child->next->pos,
1927 "sp ... %s", np->child->next->string);
1928 } else if (np->child != NULL)
1929 mandoc_vmsg(MANDOCERR_ARG_SKIP,
1930 mdoc->parse, np->line, np->pos, "%s %s",
1931 mdoc_macronames[np->tok], np->child->string);
1932
1933 if ((np = mdoc->last->prev) == NULL) {
1934 np = mdoc->last->parent;
1935 if (np->tok != MDOC_Sh && np->tok != MDOC_Ss)
1936 return;
1937 } else if (np->tok != MDOC_Pp && np->tok != MDOC_Lp &&
1938 (mdoc->last->tok != MDOC_br ||
1939 (np->tok != MDOC_sp && np->tok != MDOC_br)))
1940 return;
1941
1942 mandoc_vmsg(MANDOCERR_PAR_SKIP, mdoc->parse,
1943 mdoc->last->line, mdoc->last->pos,
1944 "%s after %s", mdoc_macronames[mdoc->last->tok],
1945 mdoc_macronames[np->tok]);
1946 roff_node_delete(mdoc, mdoc->last);
1947 }
1948
1949 static void
1950 post_dd(POST_ARGS)
1951 {
1952 struct roff_node *n;
1953 char *datestr;
1954
1955 n = mdoc->last;
1956 if (mdoc->meta.date != NULL) {
1957 mandoc_msg(MANDOCERR_PROLOG_REP, mdoc->parse,
1958 n->line, n->pos, "Dd");
1959 free(mdoc->meta.date);
1960 } else if (mdoc->flags & MDOC_PBODY)
1961 mandoc_msg(MANDOCERR_PROLOG_LATE, mdoc->parse,
1962 n->line, n->pos, "Dd");
1963 else if (mdoc->meta.title != NULL)
1964 mandoc_msg(MANDOCERR_PROLOG_ORDER, mdoc->parse,
1965 n->line, n->pos, "Dd after Dt");
1966 else if (mdoc->meta.os != NULL)
1967 mandoc_msg(MANDOCERR_PROLOG_ORDER, mdoc->parse,
1968 n->line, n->pos, "Dd after Os");
1969
1970 if (n->child == NULL || n->child->string[0] == '\0') {
1971 mdoc->meta.date = mdoc->quick ? mandoc_strdup("") :
1972 mandoc_normdate(mdoc->parse, NULL, n->line, n->pos);
1973 goto out;
1974 }
1975
1976 datestr = NULL;
1977 deroff(&datestr, n);
1978 if (mdoc->quick)
1979 mdoc->meta.date = datestr;
1980 else {
1981 mdoc->meta.date = mandoc_normdate(mdoc->parse,
1982 datestr, n->line, n->pos);
1983 free(datestr);
1984 }
1985 out:
1986 roff_node_delete(mdoc, n);
1987 }
1988
1989 static void
1990 post_dt(POST_ARGS)
1991 {
1992 struct roff_node *nn, *n;
1993 const char *cp;
1994 char *p;
1995
1996 n = mdoc->last;
1997 if (mdoc->flags & MDOC_PBODY) {
1998 mandoc_msg(MANDOCERR_DT_LATE, mdoc->parse,
1999 n->line, n->pos, "Dt");
2000 goto out;
2001 }
2002
2003 if (mdoc->meta.title != NULL)
2004 mandoc_msg(MANDOCERR_PROLOG_REP, mdoc->parse,
2005 n->line, n->pos, "Dt");
2006 else if (mdoc->meta.os != NULL)
2007 mandoc_msg(MANDOCERR_PROLOG_ORDER, mdoc->parse,
2008 n->line, n->pos, "Dt after Os");
2009
2010 free(mdoc->meta.title);
2011 free(mdoc->meta.msec);
2012 free(mdoc->meta.vol);
2013 free(mdoc->meta.arch);
2014
2015 mdoc->meta.title = NULL;
2016 mdoc->meta.msec = NULL;
2017 mdoc->meta.vol = NULL;
2018 mdoc->meta.arch = NULL;
2019
2020 /* Mandatory first argument: title. */
2021
2022 nn = n->child;
2023 if (nn == NULL || *nn->string == '\0') {
2024 mandoc_msg(MANDOCERR_DT_NOTITLE,
2025 mdoc->parse, n->line, n->pos, "Dt");
2026 mdoc->meta.title = mandoc_strdup("UNTITLED");
2027 } else {
2028 mdoc->meta.title = mandoc_strdup(nn->string);
2029
2030 /* Check that all characters are uppercase. */
2031
2032 for (p = nn->string; *p != '\0'; p++)
2033 if (islower((unsigned char)*p)) {
2034 mandoc_vmsg(MANDOCERR_TITLE_CASE,
2035 mdoc->parse, nn->line,
2036 nn->pos + (p - nn->string),
2037 "Dt %s", nn->string);
2038 break;
2039 }
2040 }
2041
2042 /* Mandatory second argument: section. */
2043
2044 if (nn != NULL)
2045 nn = nn->next;
2046
2047 if (nn == NULL) {
2048 mandoc_vmsg(MANDOCERR_MSEC_MISSING,
2049 mdoc->parse, n->line, n->pos,
2050 "Dt %s", mdoc->meta.title);
2051 mdoc->meta.vol = mandoc_strdup("LOCAL");
2052 goto out; /* msec and arch remain NULL. */
2053 }
2054
2055 mdoc->meta.msec = mandoc_strdup(nn->string);
2056
2057 /* Infer volume title from section number. */
2058
2059 cp = mandoc_a2msec(nn->string);
2060 if (cp == NULL) {
2061 mandoc_vmsg(MANDOCERR_MSEC_BAD, mdoc->parse,
2062 nn->line, nn->pos, "Dt ... %s", nn->string);
2063 mdoc->meta.vol = mandoc_strdup(nn->string);
2064 } else
2065 mdoc->meta.vol = mandoc_strdup(cp);
2066
2067 /* Optional third argument: architecture. */
2068
2069 if ((nn = nn->next) == NULL)
2070 goto out;
2071
2072 for (p = nn->string; *p != '\0'; p++)
2073 *p = tolower((unsigned char)*p);
2074 mdoc->meta.arch = mandoc_strdup(nn->string);
2075
2076 /* Ignore fourth and later arguments. */
2077
2078 if ((nn = nn->next) != NULL)
2079 mandoc_vmsg(MANDOCERR_ARG_EXCESS, mdoc->parse,
2080 nn->line, nn->pos, "Dt ... %s", nn->string);
2081
2082 out:
2083 roff_node_delete(mdoc, n);
2084 }
2085
2086 static void
2087 post_bx(POST_ARGS)
2088 {
2089 struct roff_node *n;
2090
2091 /*
2092 * Make `Bx's second argument always start with an uppercase
2093 * letter. Groff checks if it's an "accepted" term, but we just
2094 * uppercase blindly.
2095 */
2096
2097 if ((n = mdoc->last->child) != NULL && (n = n->next) != NULL)
2098 *n->string = (char)toupper((unsigned char)*n->string);
2099 }
2100
2101 static void
2102 post_os(POST_ARGS)
2103 {
2104 #ifndef OSNAME
2105 struct utsname utsname;
2106 static char *defbuf;
2107 #endif
2108 struct roff_node *n;
2109
2110 n = mdoc->last;
2111 if (mdoc->meta.os != NULL)
2112 mandoc_msg(MANDOCERR_PROLOG_REP, mdoc->parse,
2113 n->line, n->pos, "Os");
2114 else if (mdoc->flags & MDOC_PBODY)
2115 mandoc_msg(MANDOCERR_PROLOG_LATE, mdoc->parse,
2116 n->line, n->pos, "Os");
2117
2118 /*
2119 * Set the operating system by way of the `Os' macro.
2120 * The order of precedence is:
2121 * 1. the argument of the `Os' macro, unless empty
2122 * 2. the -Ios=foo command line argument, if provided
2123 * 3. -DOSNAME="\"foo\"", if provided during compilation
2124 * 4. "sysname release" from uname(3)
2125 */
2126
2127 free(mdoc->meta.os);
2128 mdoc->meta.os = NULL;
2129 deroff(&mdoc->meta.os, n);
2130 if (mdoc->meta.os)
2131 goto out;
2132
2133 if (mdoc->defos) {
2134 mdoc->meta.os = mandoc_strdup(mdoc->defos);
2135 goto out;
2136 }
2137
2138 #ifdef OSNAME
2139 mdoc->meta.os = mandoc_strdup(OSNAME);
2140 #else /*!OSNAME */
2141 if (defbuf == NULL) {
2142 if (uname(&utsname) == -1) {
2143 mandoc_msg(MANDOCERR_OS_UNAME, mdoc->parse,
2144 n->line, n->pos, "Os");
2145 defbuf = mandoc_strdup("UNKNOWN");
2146 } else
2147 mandoc_asprintf(&defbuf, "%s %s",
2148 utsname.sysname, utsname.release);
2149 }
2150 mdoc->meta.os = mandoc_strdup(defbuf);
2151 #endif /*!OSNAME*/
2152
2153 out:
2154 roff_node_delete(mdoc, n);
2155 }
2156
2157 /*
2158 * If no argument is provided,
2159 * fill in the name of the current manual page.
2160 */
2161 static void
2162 post_ex(POST_ARGS)
2163 {
2164 struct roff_node *n;
2165
2166 post_std(mdoc);
2167
2168 n = mdoc->last;
2169 if (n->child != NULL)
2170 return;
2171
2172 if (mdoc->meta.name == NULL) {
2173 mandoc_msg(MANDOCERR_EX_NONAME, mdoc->parse,
2174 n->line, n->pos, "Ex");
2175 return;
2176 }
2177
2178 mdoc->next = ROFF_NEXT_CHILD;
2179 roff_word_alloc(mdoc, n->line, n->pos, mdoc->meta.name);
2180 mdoc->last = n;
2181 }
2182
2183 enum roff_sec
2184 mdoc_a2sec(const char *p)
2185 {
2186 int i;
2187
2188 for (i = 0; i < (int)SEC__MAX; i++)
2189 if (secnames[i] && 0 == strcmp(p, secnames[i]))
2190 return (enum roff_sec)i;
2191
2192 return SEC_CUSTOM;
2193 }
2194
2195 static size_t
2196 macro2len(int macro)
2197 {
2198
2199 switch (macro) {
2200 case MDOC_Ad:
2201 return 12;
2202 case MDOC_Ao:
2203 return 12;
2204 case MDOC_An:
2205 return 12;
2206 case MDOC_Aq:
2207 return 12;
2208 case MDOC_Ar:
2209 return 12;
2210 case MDOC_Bo:
2211 return 12;
2212 case MDOC_Bq:
2213 return 12;
2214 case MDOC_Cd:
2215 return 12;
2216 case MDOC_Cm:
2217 return 10;
2218 case MDOC_Do:
2219 return 10;
2220 case MDOC_Dq:
2221 return 12;
2222 case MDOC_Dv:
2223 return 12;
2224 case MDOC_Eo:
2225 return 12;
2226 case MDOC_Em:
2227 return 10;
2228 case MDOC_Er:
2229 return 17;
2230 case MDOC_Ev:
2231 return 15;
2232 case MDOC_Fa:
2233 return 12;
2234 case MDOC_Fl:
2235 return 10;
2236 case MDOC_Fo:
2237 return 16;
2238 case MDOC_Fn:
2239 return 16;
2240 case MDOC_Ic:
2241 return 10;
2242 case MDOC_Li:
2243 return 16;
2244 case MDOC_Ms:
2245 return 6;
2246 case MDOC_Nm:
2247 return 10;
2248 case MDOC_No:
2249 return 12;
2250 case MDOC_Oo:
2251 return 10;
2252 case MDOC_Op:
2253 return 14;
2254 case MDOC_Pa:
2255 return 32;
2256 case MDOC_Pf:
2257 return 12;
2258 case MDOC_Po:
2259 return 12;
2260 case MDOC_Pq:
2261 return 12;
2262 case MDOC_Ql:
2263 return 16;
2264 case MDOC_Qo:
2265 return 12;
2266 case MDOC_So:
2267 return 12;
2268 case MDOC_Sq:
2269 return 12;
2270 case MDOC_Sy:
2271 return 6;
2272 case MDOC_Sx:
2273 return 16;
2274 case MDOC_Tn:
2275 return 10;
2276 case MDOC_Va:
2277 return 12;
2278 case MDOC_Vt:
2279 return 12;
2280 case MDOC_Xr:
2281 return 10;
2282 default:
2283 break;
2284 };
2285 return 0;
2286 }