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