1 /* $OpenBSD: mdoc_validate.c,v 1.284 2018/12/31 08:38:17 schwarze Exp $ */ 2 /* 3 * Copyright (c) 2008-2012 Kristaps Dzonsons <kristaps@bsd.lv> 4 * Copyright (c) 2010-2018 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); 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->line > nit->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 const char *openbsd_arch[] = { 1887 "alpha", "amd64", "arm64", "armv7", "hppa", "i386", 1888 "landisk", "loongson", "luna88k", "macppc", "mips64", 1889 "octeon", "sgi", "socppc", "sparc64", NULL 1890 }; 1891 const char *netbsd_arch[] = { 1892 "acorn26", "acorn32", "algor", "alpha", "amiga", 1893 "arc", "atari", 1894 "bebox", "cats", "cesfic", "cobalt", "dreamcast", 1895 "emips", "evbarm", "evbmips", "evbppc", "evbsh3", "evbsh5", 1896 "hp300", "hpcarm", "hpcmips", "hpcsh", "hppa", 1897 "i386", "ibmnws", "luna68k", 1898 "mac68k", "macppc", "mipsco", "mmeye", "mvme68k", "mvmeppc", 1899 "netwinder", "news68k", "newsmips", "next68k", 1900 "pc532", "playstation2", "pmax", "pmppc", "prep", 1901 "sandpoint", "sbmips", "sgimips", "shark", 1902 "sparc", "sparc64", "sun2", "sun3", 1903 "vax", "walnut", "x68k", "x86", "x86_64", "xen", NULL 1904 }; 1905 const char **arches[] = { NULL, netbsd_arch, openbsd_arch }; 1906 1907 struct roff_node *n; 1908 const char **arch; 1909 1910 /* Add missing prologue data. */ 1911 1912 if (mdoc->meta.date == NULL) 1913 mdoc->meta.date = mdoc->quick ? mandoc_strdup("") : 1914 mandoc_normdate(mdoc, NULL, 0, 0); 1915 1916 if (mdoc->meta.title == NULL) { 1917 mandoc_msg(MANDOCERR_DT_NOTITLE, 0, 0, "EOF"); 1918 mdoc->meta.title = mandoc_strdup("UNTITLED"); 1919 } 1920 1921 if (mdoc->meta.vol == NULL) 1922 mdoc->meta.vol = mandoc_strdup("LOCAL"); 1923 1924 if (mdoc->meta.os == NULL) { 1925 mandoc_msg(MANDOCERR_OS_MISSING, 0, 0, NULL); 1926 mdoc->meta.os = mandoc_strdup(""); 1927 } else if (mdoc->meta.os_e && 1928 (mdoc->meta.rcsids & (1 << mdoc->meta.os_e)) == 0) 1929 mandoc_msg(MANDOCERR_RCS_MISSING, 0, 0, 1930 mdoc->meta.os_e == MANDOC_OS_OPENBSD ? 1931 "(OpenBSD)" : "(NetBSD)"); 1932 1933 if (mdoc->meta.arch != NULL && 1934 (arch = arches[mdoc->meta.os_e]) != NULL) { 1935 while (*arch != NULL && strcmp(*arch, mdoc->meta.arch)) 1936 arch++; 1937 if (*arch == NULL) { 1938 n = mdoc->meta.first->child; 1939 while (n->tok != MDOC_Dt || 1940 n->child == NULL || 1941 n->child->next == NULL || 1942 n->child->next->next == NULL) 1943 n = n->next; 1944 n = n->child->next->next; 1945 mandoc_msg(MANDOCERR_ARCH_BAD, n->line, n->pos, 1946 "Dt ... %s %s", mdoc->meta.arch, 1947 mdoc->meta.os_e == MANDOC_OS_OPENBSD ? 1948 "(OpenBSD)" : "(NetBSD)"); 1949 } 1950 } 1951 1952 /* Check that we begin with a proper `Sh'. */ 1953 1954 n = mdoc->meta.first->child; 1955 while (n != NULL && 1956 (n->type == ROFFT_COMMENT || 1957 (n->tok >= MDOC_Dd && 1958 mdoc_macro(n->tok)->flags & MDOC_PROLOGUE))) 1959 n = n->next; 1960 1961 if (n == NULL) 1962 mandoc_msg(MANDOCERR_DOC_EMPTY, 0, 0, NULL); 1963 else if (n->tok != MDOC_Sh) 1964 mandoc_msg(MANDOCERR_SEC_BEFORE, n->line, n->pos, 1965 "%s", roff_name[n->tok]); 1966 } 1967 1968 static void 1969 post_rs(POST_ARGS) 1970 { 1971 struct roff_node *np, *nch, *next, *prev; 1972 int i, j; 1973 1974 np = mdoc->last; 1975 1976 if (np->type != ROFFT_BODY) 1977 return; 1978 1979 if (np->child == NULL) { 1980 mandoc_msg(MANDOCERR_RS_EMPTY, np->line, np->pos, "Rs"); 1981 return; 1982 } 1983 1984 /* 1985 * The full `Rs' block needs special handling to order the 1986 * sub-elements according to `rsord'. Pick through each element 1987 * and correctly order it. This is an insertion sort. 1988 */ 1989 1990 next = NULL; 1991 for (nch = np->child->next; nch != NULL; nch = next) { 1992 /* Determine order number of this child. */ 1993 for (i = 0; i < RSORD_MAX; i++) 1994 if (rsord[i] == nch->tok) 1995 break; 1996 1997 if (i == RSORD_MAX) { 1998 mandoc_msg(MANDOCERR_RS_BAD, nch->line, nch->pos, 1999 "%s", roff_name[nch->tok]); 2000 i = -1; 2001 } else if (nch->tok == MDOC__J || nch->tok == MDOC__B) 2002 np->norm->Rs.quote_T++; 2003 2004 /* 2005 * Remove this child from the chain. This somewhat 2006 * repeats roff_node_unlink(), but since we're 2007 * just re-ordering, there's no need for the 2008 * full unlink process. 2009 */ 2010 2011 if ((next = nch->next) != NULL) 2012 next->prev = nch->prev; 2013 2014 if ((prev = nch->prev) != NULL) 2015 prev->next = nch->next; 2016 2017 nch->prev = nch->next = NULL; 2018 2019 /* 2020 * Scan back until we reach a node that's 2021 * to be ordered before this child. 2022 */ 2023 2024 for ( ; prev ; prev = prev->prev) { 2025 /* Determine order of `prev'. */ 2026 for (j = 0; j < RSORD_MAX; j++) 2027 if (rsord[j] == prev->tok) 2028 break; 2029 if (j == RSORD_MAX) 2030 j = -1; 2031 2032 if (j <= i) 2033 break; 2034 } 2035 2036 /* 2037 * Set this child back into its correct place 2038 * in front of the `prev' node. 2039 */ 2040 2041 nch->prev = prev; 2042 2043 if (prev == NULL) { 2044 np->child->prev = nch; 2045 nch->next = np->child; 2046 np->child = nch; 2047 } else { 2048 if (prev->next) 2049 prev->next->prev = nch; 2050 nch->next = prev->next; 2051 prev->next = nch; 2052 } 2053 } 2054 } 2055 2056 /* 2057 * For some arguments of some macros, 2058 * convert all breakable hyphens into ASCII_HYPH. 2059 */ 2060 static void 2061 post_hyph(POST_ARGS) 2062 { 2063 struct roff_node *nch; 2064 char *cp; 2065 2066 for (nch = mdoc->last->child; nch != NULL; nch = nch->next) { 2067 if (nch->type != ROFFT_TEXT) 2068 continue; 2069 cp = nch->string; 2070 if (*cp == '\0') 2071 continue; 2072 while (*(++cp) != '\0') 2073 if (*cp == '-' && 2074 isalpha((unsigned char)cp[-1]) && 2075 isalpha((unsigned char)cp[1])) 2076 *cp = ASCII_HYPH; 2077 } 2078 } 2079 2080 static void 2081 post_ns(POST_ARGS) 2082 { 2083 struct roff_node *n; 2084 2085 n = mdoc->last; 2086 if (n->flags & NODE_LINE || 2087 (n->next != NULL && n->next->flags & NODE_DELIMC)) 2088 mandoc_msg(MANDOCERR_NS_SKIP, n->line, n->pos, NULL); 2089 } 2090 2091 static void 2092 post_sx(POST_ARGS) 2093 { 2094 post_delim(mdoc); 2095 post_hyph(mdoc); 2096 } 2097 2098 static void 2099 post_sh(POST_ARGS) 2100 { 2101 2102 post_ignpar(mdoc); 2103 2104 switch (mdoc->last->type) { 2105 case ROFFT_HEAD: 2106 post_sh_head(mdoc); 2107 break; 2108 case ROFFT_BODY: 2109 switch (mdoc->lastsec) { 2110 case SEC_NAME: 2111 post_sh_name(mdoc); 2112 break; 2113 case SEC_SEE_ALSO: 2114 post_sh_see_also(mdoc); 2115 break; 2116 case SEC_AUTHORS: 2117 post_sh_authors(mdoc); 2118 break; 2119 default: 2120 break; 2121 } 2122 break; 2123 default: 2124 break; 2125 } 2126 } 2127 2128 static void 2129 post_sh_name(POST_ARGS) 2130 { 2131 struct roff_node *n; 2132 int hasnm, hasnd; 2133 2134 hasnm = hasnd = 0; 2135 2136 for (n = mdoc->last->child; n != NULL; n = n->next) { 2137 switch (n->tok) { 2138 case MDOC_Nm: 2139 if (hasnm && n->child != NULL) 2140 mandoc_msg(MANDOCERR_NAMESEC_PUNCT, 2141 n->line, n->pos, 2142 "Nm %s", n->child->string); 2143 hasnm = 1; 2144 continue; 2145 case MDOC_Nd: 2146 hasnd = 1; 2147 if (n->next != NULL) 2148 mandoc_msg(MANDOCERR_NAMESEC_ND, 2149 n->line, n->pos, NULL); 2150 break; 2151 case TOKEN_NONE: 2152 if (n->type == ROFFT_TEXT && 2153 n->string[0] == ',' && n->string[1] == '\0' && 2154 n->next != NULL && n->next->tok == MDOC_Nm) { 2155 n = n->next; 2156 continue; 2157 } 2158 /* FALLTHROUGH */ 2159 default: 2160 mandoc_msg(MANDOCERR_NAMESEC_BAD, 2161 n->line, n->pos, "%s", roff_name[n->tok]); 2162 continue; 2163 } 2164 break; 2165 } 2166 2167 if ( ! hasnm) 2168 mandoc_msg(MANDOCERR_NAMESEC_NONM, 2169 mdoc->last->line, mdoc->last->pos, NULL); 2170 if ( ! hasnd) 2171 mandoc_msg(MANDOCERR_NAMESEC_NOND, 2172 mdoc->last->line, mdoc->last->pos, NULL); 2173 } 2174 2175 static void 2176 post_sh_see_also(POST_ARGS) 2177 { 2178 const struct roff_node *n; 2179 const char *name, *sec; 2180 const char *lastname, *lastsec, *lastpunct; 2181 int cmp; 2182 2183 n = mdoc->last->child; 2184 lastname = lastsec = lastpunct = NULL; 2185 while (n != NULL) { 2186 if (n->tok != MDOC_Xr || 2187 n->child == NULL || 2188 n->child->next == NULL) 2189 break; 2190 2191 /* Process one .Xr node. */ 2192 2193 name = n->child->string; 2194 sec = n->child->next->string; 2195 if (lastsec != NULL) { 2196 if (lastpunct[0] != ',' || lastpunct[1] != '\0') 2197 mandoc_msg(MANDOCERR_XR_PUNCT, n->line, 2198 n->pos, "%s before %s(%s)", 2199 lastpunct, name, sec); 2200 cmp = strcmp(lastsec, sec); 2201 if (cmp > 0) 2202 mandoc_msg(MANDOCERR_XR_ORDER, n->line, 2203 n->pos, "%s(%s) after %s(%s)", 2204 name, sec, lastname, lastsec); 2205 else if (cmp == 0 && 2206 strcasecmp(lastname, name) > 0) 2207 mandoc_msg(MANDOCERR_XR_ORDER, n->line, 2208 n->pos, "%s after %s", name, lastname); 2209 } 2210 lastname = name; 2211 lastsec = sec; 2212 2213 /* Process the following node. */ 2214 2215 n = n->next; 2216 if (n == NULL) 2217 break; 2218 if (n->tok == MDOC_Xr) { 2219 lastpunct = "none"; 2220 continue; 2221 } 2222 if (n->type != ROFFT_TEXT) 2223 break; 2224 for (name = n->string; *name != '\0'; name++) 2225 if (isalpha((const unsigned char)*name)) 2226 return; 2227 lastpunct = n->string; 2228 if (n->next == NULL || n->next->tok == MDOC_Rs) 2229 mandoc_msg(MANDOCERR_XR_PUNCT, n->line, 2230 n->pos, "%s after %s(%s)", 2231 lastpunct, lastname, lastsec); 2232 n = n->next; 2233 } 2234 } 2235 2236 static int 2237 child_an(const struct roff_node *n) 2238 { 2239 2240 for (n = n->child; n != NULL; n = n->next) 2241 if ((n->tok == MDOC_An && n->child != NULL) || child_an(n)) 2242 return 1; 2243 return 0; 2244 } 2245 2246 static void 2247 post_sh_authors(POST_ARGS) 2248 { 2249 2250 if ( ! child_an(mdoc->last)) 2251 mandoc_msg(MANDOCERR_AN_MISSING, 2252 mdoc->last->line, mdoc->last->pos, NULL); 2253 } 2254 2255 /* 2256 * Return an upper bound for the string distance (allowing 2257 * transpositions). Not a full Levenshtein implementation 2258 * because Levenshtein is quadratic in the string length 2259 * and this function is called for every standard name, 2260 * so the check for each custom name would be cubic. 2261 * The following crude heuristics is linear, resulting 2262 * in quadratic behaviour for checking one custom name, 2263 * which does not cause measurable slowdown. 2264 */ 2265 static int 2266 similar(const char *s1, const char *s2) 2267 { 2268 const int maxdist = 3; 2269 int dist = 0; 2270 2271 while (s1[0] != '\0' && s2[0] != '\0') { 2272 if (s1[0] == s2[0]) { 2273 s1++; 2274 s2++; 2275 continue; 2276 } 2277 if (++dist > maxdist) 2278 return INT_MAX; 2279 if (s1[1] == s2[1]) { /* replacement */ 2280 s1++; 2281 s2++; 2282 } else if (s1[0] == s2[1] && s1[1] == s2[0]) { 2283 s1 += 2; /* transposition */ 2284 s2 += 2; 2285 } else if (s1[0] == s2[1]) /* insertion */ 2286 s2++; 2287 else if (s1[1] == s2[0]) /* deletion */ 2288 s1++; 2289 else 2290 return INT_MAX; 2291 } 2292 dist += strlen(s1) + strlen(s2); 2293 return dist > maxdist ? INT_MAX : dist; 2294 } 2295 2296 static void 2297 post_sh_head(POST_ARGS) 2298 { 2299 struct roff_node *nch; 2300 const char *goodsec; 2301 const char *const *testsec; 2302 int dist, mindist; 2303 enum roff_sec sec; 2304 2305 /* 2306 * Process a new section. Sections are either "named" or 2307 * "custom". Custom sections are user-defined, while named ones 2308 * follow a conventional order and may only appear in certain 2309 * manual sections. 2310 */ 2311 2312 sec = mdoc->last->sec; 2313 2314 /* The NAME should be first. */ 2315 2316 if (sec != SEC_NAME && mdoc->lastnamed == SEC_NONE) 2317 mandoc_msg(MANDOCERR_NAMESEC_FIRST, 2318 mdoc->last->line, mdoc->last->pos, "Sh %s", 2319 sec != SEC_CUSTOM ? secnames[sec] : 2320 (nch = mdoc->last->child) == NULL ? "" : 2321 nch->type == ROFFT_TEXT ? nch->string : 2322 roff_name[nch->tok]); 2323 2324 /* The SYNOPSIS gets special attention in other areas. */ 2325 2326 if (sec == SEC_SYNOPSIS) { 2327 roff_setreg(mdoc->roff, "nS", 1, '='); 2328 mdoc->flags |= MDOC_SYNOPSIS; 2329 } else { 2330 roff_setreg(mdoc->roff, "nS", 0, '='); 2331 mdoc->flags &= ~MDOC_SYNOPSIS; 2332 } 2333 2334 /* Mark our last section. */ 2335 2336 mdoc->lastsec = sec; 2337 2338 /* We don't care about custom sections after this. */ 2339 2340 if (sec == SEC_CUSTOM) { 2341 if ((nch = mdoc->last->child) == NULL || 2342 nch->type != ROFFT_TEXT || nch->next != NULL) 2343 return; 2344 goodsec = NULL; 2345 mindist = INT_MAX; 2346 for (testsec = secnames + 1; *testsec != NULL; testsec++) { 2347 dist = similar(nch->string, *testsec); 2348 if (dist < mindist) { 2349 goodsec = *testsec; 2350 mindist = dist; 2351 } 2352 } 2353 if (goodsec != NULL) 2354 mandoc_msg(MANDOCERR_SEC_TYPO, nch->line, nch->pos, 2355 "Sh %s instead of %s", nch->string, goodsec); 2356 return; 2357 } 2358 2359 /* 2360 * Check whether our non-custom section is being repeated or is 2361 * out of order. 2362 */ 2363 2364 if (sec == mdoc->lastnamed) 2365 mandoc_msg(MANDOCERR_SEC_REP, mdoc->last->line, 2366 mdoc->last->pos, "Sh %s", secnames[sec]); 2367 2368 if (sec < mdoc->lastnamed) 2369 mandoc_msg(MANDOCERR_SEC_ORDER, mdoc->last->line, 2370 mdoc->last->pos, "Sh %s", secnames[sec]); 2371 2372 /* Mark the last named section. */ 2373 2374 mdoc->lastnamed = sec; 2375 2376 /* Check particular section/manual conventions. */ 2377 2378 if (mdoc->meta.msec == NULL) 2379 return; 2380 2381 goodsec = NULL; 2382 switch (sec) { 2383 case SEC_ERRORS: 2384 if (*mdoc->meta.msec == '4') 2385 break; 2386 goodsec = "2, 3, 4, 9"; 2387 /* FALLTHROUGH */ 2388 case SEC_RETURN_VALUES: 2389 case SEC_LIBRARY: 2390 if (*mdoc->meta.msec == '2') 2391 break; 2392 if (*mdoc->meta.msec == '3') 2393 break; 2394 if (NULL == goodsec) 2395 goodsec = "2, 3, 9"; 2396 /* FALLTHROUGH */ 2397 case SEC_CONTEXT: 2398 if (*mdoc->meta.msec == '9') 2399 break; 2400 if (NULL == goodsec) 2401 goodsec = "9"; 2402 mandoc_msg(MANDOCERR_SEC_MSEC, 2403 mdoc->last->line, mdoc->last->pos, 2404 "Sh %s for %s only", secnames[sec], goodsec); 2405 break; 2406 default: 2407 break; 2408 } 2409 } 2410 2411 static void 2412 post_xr(POST_ARGS) 2413 { 2414 struct roff_node *n, *nch; 2415 2416 n = mdoc->last; 2417 nch = n->child; 2418 if (nch->next == NULL) { 2419 mandoc_msg(MANDOCERR_XR_NOSEC, 2420 n->line, n->pos, "Xr %s", nch->string); 2421 } else { 2422 assert(nch->next == n->last); 2423 if(mandoc_xr_add(nch->next->string, nch->string, 2424 nch->line, nch->pos)) 2425 mandoc_msg(MANDOCERR_XR_SELF, 2426 nch->line, nch->pos, "Xr %s %s", 2427 nch->string, nch->next->string); 2428 } 2429 post_delim_nb(mdoc); 2430 } 2431 2432 static void 2433 post_ignpar(POST_ARGS) 2434 { 2435 struct roff_node *np; 2436 2437 switch (mdoc->last->type) { 2438 case ROFFT_BLOCK: 2439 post_prevpar(mdoc); 2440 return; 2441 case ROFFT_HEAD: 2442 post_delim(mdoc); 2443 post_hyph(mdoc); 2444 return; 2445 case ROFFT_BODY: 2446 break; 2447 default: 2448 return; 2449 } 2450 2451 if ((np = mdoc->last->child) != NULL) 2452 if (np->tok == MDOC_Pp || 2453 np->tok == ROFF_br || np->tok == ROFF_sp) { 2454 mandoc_msg(MANDOCERR_PAR_SKIP, np->line, np->pos, 2455 "%s after %s", roff_name[np->tok], 2456 roff_name[mdoc->last->tok]); 2457 roff_node_delete(mdoc, np); 2458 } 2459 2460 if ((np = mdoc->last->last) != NULL) 2461 if (np->tok == MDOC_Pp || np->tok == ROFF_br) { 2462 mandoc_msg(MANDOCERR_PAR_SKIP, np->line, np->pos, 2463 "%s at the end of %s", roff_name[np->tok], 2464 roff_name[mdoc->last->tok]); 2465 roff_node_delete(mdoc, np); 2466 } 2467 } 2468 2469 static void 2470 post_prevpar(POST_ARGS) 2471 { 2472 struct roff_node *n; 2473 2474 n = mdoc->last; 2475 if (NULL == n->prev) 2476 return; 2477 if (n->type != ROFFT_ELEM && n->type != ROFFT_BLOCK) 2478 return; 2479 2480 /* 2481 * Don't allow `Pp' prior to a paragraph-type 2482 * block: `Pp' or non-compact `Bd' or `Bl'. 2483 */ 2484 2485 if (n->prev->tok != MDOC_Pp && n->prev->tok != ROFF_br) 2486 return; 2487 if (n->tok == MDOC_Bl && n->norm->Bl.comp) 2488 return; 2489 if (n->tok == MDOC_Bd && n->norm->Bd.comp) 2490 return; 2491 if (n->tok == MDOC_It && n->parent->norm->Bl.comp) 2492 return; 2493 2494 mandoc_msg(MANDOCERR_PAR_SKIP, n->prev->line, n->prev->pos, 2495 "%s before %s", roff_name[n->prev->tok], roff_name[n->tok]); 2496 roff_node_delete(mdoc, n->prev); 2497 } 2498 2499 static void 2500 post_par(POST_ARGS) 2501 { 2502 struct roff_node *np; 2503 2504 post_prevpar(mdoc); 2505 2506 np = mdoc->last; 2507 if (np->child != NULL) 2508 mandoc_msg(MANDOCERR_ARG_SKIP, np->line, np->pos, 2509 "%s %s", roff_name[np->tok], np->child->string); 2510 } 2511 2512 static void 2513 post_dd(POST_ARGS) 2514 { 2515 struct roff_node *n; 2516 char *datestr; 2517 2518 n = mdoc->last; 2519 n->flags |= NODE_NOPRT; 2520 2521 if (mdoc->meta.date != NULL) { 2522 mandoc_msg(MANDOCERR_PROLOG_REP, n->line, n->pos, "Dd"); 2523 free(mdoc->meta.date); 2524 } else if (mdoc->flags & MDOC_PBODY) 2525 mandoc_msg(MANDOCERR_PROLOG_LATE, n->line, n->pos, "Dd"); 2526 else if (mdoc->meta.title != NULL) 2527 mandoc_msg(MANDOCERR_PROLOG_ORDER, 2528 n->line, n->pos, "Dd after Dt"); 2529 else if (mdoc->meta.os != NULL) 2530 mandoc_msg(MANDOCERR_PROLOG_ORDER, 2531 n->line, n->pos, "Dd after Os"); 2532 2533 if (n->child == NULL || n->child->string[0] == '\0') { 2534 mdoc->meta.date = mdoc->quick ? mandoc_strdup("") : 2535 mandoc_normdate(mdoc, NULL, n->line, n->pos); 2536 return; 2537 } 2538 2539 datestr = NULL; 2540 deroff(&datestr, n); 2541 if (mdoc->quick) 2542 mdoc->meta.date = datestr; 2543 else { 2544 mdoc->meta.date = mandoc_normdate(mdoc, 2545 datestr, n->line, n->pos); 2546 free(datestr); 2547 } 2548 } 2549 2550 static void 2551 post_dt(POST_ARGS) 2552 { 2553 struct roff_node *nn, *n; 2554 const char *cp; 2555 char *p; 2556 2557 n = mdoc->last; 2558 n->flags |= NODE_NOPRT; 2559 2560 if (mdoc->flags & MDOC_PBODY) { 2561 mandoc_msg(MANDOCERR_DT_LATE, n->line, n->pos, "Dt"); 2562 return; 2563 } 2564 2565 if (mdoc->meta.title != NULL) 2566 mandoc_msg(MANDOCERR_PROLOG_REP, n->line, n->pos, "Dt"); 2567 else if (mdoc->meta.os != NULL) 2568 mandoc_msg(MANDOCERR_PROLOG_ORDER, 2569 n->line, n->pos, "Dt after Os"); 2570 2571 free(mdoc->meta.title); 2572 free(mdoc->meta.msec); 2573 free(mdoc->meta.vol); 2574 free(mdoc->meta.arch); 2575 2576 mdoc->meta.title = NULL; 2577 mdoc->meta.msec = NULL; 2578 mdoc->meta.vol = NULL; 2579 mdoc->meta.arch = NULL; 2580 2581 /* Mandatory first argument: title. */ 2582 2583 nn = n->child; 2584 if (nn == NULL || *nn->string == '\0') { 2585 mandoc_msg(MANDOCERR_DT_NOTITLE, n->line, n->pos, "Dt"); 2586 mdoc->meta.title = mandoc_strdup("UNTITLED"); 2587 } else { 2588 mdoc->meta.title = mandoc_strdup(nn->string); 2589 2590 /* Check that all characters are uppercase. */ 2591 2592 for (p = nn->string; *p != '\0'; p++) 2593 if (islower((unsigned char)*p)) { 2594 mandoc_msg(MANDOCERR_TITLE_CASE, nn->line, 2595 nn->pos + (int)(p - nn->string), 2596 "Dt %s", nn->string); 2597 break; 2598 } 2599 } 2600 2601 /* Mandatory second argument: section. */ 2602 2603 if (nn != NULL) 2604 nn = nn->next; 2605 2606 if (nn == NULL) { 2607 mandoc_msg(MANDOCERR_MSEC_MISSING, n->line, n->pos, 2608 "Dt %s", mdoc->meta.title); 2609 mdoc->meta.vol = mandoc_strdup("LOCAL"); 2610 return; /* msec and arch remain NULL. */ 2611 } 2612 2613 mdoc->meta.msec = mandoc_strdup(nn->string); 2614 2615 /* Infer volume title from section number. */ 2616 2617 cp = mandoc_a2msec(nn->string); 2618 if (cp == NULL) { 2619 mandoc_msg(MANDOCERR_MSEC_BAD, 2620 nn->line, nn->pos, "Dt ... %s", nn->string); 2621 mdoc->meta.vol = mandoc_strdup(nn->string); 2622 } else 2623 mdoc->meta.vol = mandoc_strdup(cp); 2624 2625 /* Optional third argument: architecture. */ 2626 2627 if ((nn = nn->next) == NULL) 2628 return; 2629 2630 for (p = nn->string; *p != '\0'; p++) 2631 *p = tolower((unsigned char)*p); 2632 mdoc->meta.arch = mandoc_strdup(nn->string); 2633 2634 /* Ignore fourth and later arguments. */ 2635 2636 if ((nn = nn->next) != NULL) 2637 mandoc_msg(MANDOCERR_ARG_EXCESS, 2638 nn->line, nn->pos, "Dt ... %s", nn->string); 2639 } 2640 2641 static void 2642 post_bx(POST_ARGS) 2643 { 2644 struct roff_node *n, *nch; 2645 const char *macro; 2646 2647 post_delim_nb(mdoc); 2648 2649 n = mdoc->last; 2650 nch = n->child; 2651 2652 if (nch != NULL) { 2653 macro = !strcmp(nch->string, "Open") ? "Ox" : 2654 !strcmp(nch->string, "Net") ? "Nx" : 2655 !strcmp(nch->string, "Free") ? "Fx" : 2656 !strcmp(nch->string, "DragonFly") ? "Dx" : NULL; 2657 if (macro != NULL) 2658 mandoc_msg(MANDOCERR_BX, 2659 n->line, n->pos, "%s", macro); 2660 mdoc->last = nch; 2661 nch = nch->next; 2662 mdoc->next = ROFF_NEXT_SIBLING; 2663 roff_elem_alloc(mdoc, n->line, n->pos, MDOC_Ns); 2664 mdoc->last->flags |= NODE_NOSRC; 2665 mdoc->next = ROFF_NEXT_SIBLING; 2666 } else 2667 mdoc->next = ROFF_NEXT_CHILD; 2668 roff_word_alloc(mdoc, n->line, n->pos, "BSD"); 2669 mdoc->last->flags |= NODE_NOSRC; 2670 2671 if (nch == NULL) { 2672 mdoc->last = n; 2673 return; 2674 } 2675 2676 roff_elem_alloc(mdoc, n->line, n->pos, MDOC_Ns); 2677 mdoc->last->flags |= NODE_NOSRC; 2678 mdoc->next = ROFF_NEXT_SIBLING; 2679 roff_word_alloc(mdoc, n->line, n->pos, "-"); 2680 mdoc->last->flags |= NODE_NOSRC; 2681 roff_elem_alloc(mdoc, n->line, n->pos, MDOC_Ns); 2682 mdoc->last->flags |= NODE_NOSRC; 2683 mdoc->last = n; 2684 2685 /* 2686 * Make `Bx's second argument always start with an uppercase 2687 * letter. Groff checks if it's an "accepted" term, but we just 2688 * uppercase blindly. 2689 */ 2690 2691 *nch->string = (char)toupper((unsigned char)*nch->string); 2692 } 2693 2694 static void 2695 post_os(POST_ARGS) 2696 { 2697 #ifndef OSNAME 2698 struct utsname utsname; 2699 static char *defbuf; 2700 #endif 2701 struct roff_node *n; 2702 2703 n = mdoc->last; 2704 n->flags |= NODE_NOPRT; 2705 2706 if (mdoc->meta.os != NULL) 2707 mandoc_msg(MANDOCERR_PROLOG_REP, n->line, n->pos, "Os"); 2708 else if (mdoc->flags & MDOC_PBODY) 2709 mandoc_msg(MANDOCERR_PROLOG_LATE, n->line, n->pos, "Os"); 2710 2711 post_delim(mdoc); 2712 2713 /* 2714 * Set the operating system by way of the `Os' macro. 2715 * The order of precedence is: 2716 * 1. the argument of the `Os' macro, unless empty 2717 * 2. the -Ios=foo command line argument, if provided 2718 * 3. -DOSNAME="\"foo\"", if provided during compilation 2719 * 4. "sysname release" from uname(3) 2720 */ 2721 2722 free(mdoc->meta.os); 2723 mdoc->meta.os = NULL; 2724 deroff(&mdoc->meta.os, n); 2725 if (mdoc->meta.os) 2726 goto out; 2727 2728 if (mdoc->os_s != NULL) { 2729 mdoc->meta.os = mandoc_strdup(mdoc->os_s); 2730 goto out; 2731 } 2732 2733 #ifdef OSNAME 2734 mdoc->meta.os = mandoc_strdup(OSNAME); 2735 #else /*!OSNAME */ 2736 if (defbuf == NULL) { 2737 if (uname(&utsname) == -1) { 2738 mandoc_msg(MANDOCERR_OS_UNAME, n->line, n->pos, "Os"); 2739 defbuf = mandoc_strdup("UNKNOWN"); 2740 } else 2741 mandoc_asprintf(&defbuf, "%s %s", 2742 utsname.sysname, utsname.release); 2743 } 2744 mdoc->meta.os = mandoc_strdup(defbuf); 2745 #endif /*!OSNAME*/ 2746 2747 out: 2748 if (mdoc->meta.os_e == MANDOC_OS_OTHER) { 2749 if (strstr(mdoc->meta.os, "OpenBSD") != NULL) 2750 mdoc->meta.os_e = MANDOC_OS_OPENBSD; 2751 else if (strstr(mdoc->meta.os, "NetBSD") != NULL) 2752 mdoc->meta.os_e = MANDOC_OS_NETBSD; 2753 } 2754 2755 /* 2756 * This is the earliest point where we can check 2757 * Mdocdate conventions because we don't know 2758 * the operating system earlier. 2759 */ 2760 2761 if (n->child != NULL) 2762 mandoc_msg(MANDOCERR_OS_ARG, n->child->line, n->child->pos, 2763 "Os %s (%s)", n->child->string, 2764 mdoc->meta.os_e == MANDOC_OS_OPENBSD ? 2765 "OpenBSD" : "NetBSD"); 2766 2767 while (n->tok != MDOC_Dd) 2768 if ((n = n->prev) == NULL) 2769 return; 2770 if ((n = n->child) == NULL) 2771 return; 2772 if (strncmp(n->string, "$" "Mdocdate", 9)) { 2773 if (mdoc->meta.os_e == MANDOC_OS_OPENBSD) 2774 mandoc_msg(MANDOCERR_MDOCDATE_MISSING, n->line, 2775 n->pos, "Dd %s (OpenBSD)", n->string); 2776 } else { 2777 if (mdoc->meta.os_e == MANDOC_OS_NETBSD) 2778 mandoc_msg(MANDOCERR_MDOCDATE, n->line, 2779 n->pos, "Dd %s (NetBSD)", n->string); 2780 } 2781 } 2782 2783 enum roff_sec 2784 mdoc_a2sec(const char *p) 2785 { 2786 int i; 2787 2788 for (i = 0; i < (int)SEC__MAX; i++) 2789 if (secnames[i] && 0 == strcmp(p, secnames[i])) 2790 return (enum roff_sec)i; 2791 2792 return SEC_CUSTOM; 2793 } 2794 2795 static size_t 2796 macro2len(enum roff_tok macro) 2797 { 2798 2799 switch (macro) { 2800 case MDOC_Ad: 2801 return 12; 2802 case MDOC_Ao: 2803 return 12; 2804 case MDOC_An: 2805 return 12; 2806 case MDOC_Aq: 2807 return 12; 2808 case MDOC_Ar: 2809 return 12; 2810 case MDOC_Bo: 2811 return 12; 2812 case MDOC_Bq: 2813 return 12; 2814 case MDOC_Cd: 2815 return 12; 2816 case MDOC_Cm: 2817 return 10; 2818 case MDOC_Do: 2819 return 10; 2820 case MDOC_Dq: 2821 return 12; 2822 case MDOC_Dv: 2823 return 12; 2824 case MDOC_Eo: 2825 return 12; 2826 case MDOC_Em: 2827 return 10; 2828 case MDOC_Er: 2829 return 17; 2830 case MDOC_Ev: 2831 return 15; 2832 case MDOC_Fa: 2833 return 12; 2834 case MDOC_Fl: 2835 return 10; 2836 case MDOC_Fo: 2837 return 16; 2838 case MDOC_Fn: 2839 return 16; 2840 case MDOC_Ic: 2841 return 10; 2842 case MDOC_Li: 2843 return 16; 2844 case MDOC_Ms: 2845 return 6; 2846 case MDOC_Nm: 2847 return 10; 2848 case MDOC_No: 2849 return 12; 2850 case MDOC_Oo: 2851 return 10; 2852 case MDOC_Op: 2853 return 14; 2854 case MDOC_Pa: 2855 return 32; 2856 case MDOC_Pf: 2857 return 12; 2858 case MDOC_Po: 2859 return 12; 2860 case MDOC_Pq: 2861 return 12; 2862 case MDOC_Ql: 2863 return 16; 2864 case MDOC_Qo: 2865 return 12; 2866 case MDOC_So: 2867 return 12; 2868 case MDOC_Sq: 2869 return 12; 2870 case MDOC_Sy: 2871 return 6; 2872 case MDOC_Sx: 2873 return 16; 2874 case MDOC_Tn: 2875 return 10; 2876 case MDOC_Va: 2877 return 12; 2878 case MDOC_Vt: 2879 return 12; 2880 case MDOC_Xr: 2881 return 10; 2882 default: 2883 break; 2884 }; 2885 return 0; 2886 } 2887