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