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