1 /* $OpenBSD: mdoc_validate.c,v 1.268 2017/09/12 18:20:32 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 (n->norm->Bl.width == NULL) 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 != NULL) 711 mandoc_vmsg(MANDOCERR_BL_SKIPW, mdoc->parse, 712 wa->line, wa->pos, "Bl -%s", 713 mdoc_argnames[mdoclt]); 714 n->norm->Bl.width = NULL; 715 break; 716 case LIST_bullet: 717 case LIST_dash: 718 case LIST_hyphen: 719 if (n->norm->Bl.width == NULL) 720 n->norm->Bl.width = "2n"; 721 break; 722 case LIST_enum: 723 if (n->norm->Bl.width == NULL) 724 n->norm->Bl.width = "3n"; 725 break; 726 default: 727 break; 728 } 729 } 730 731 static void 732 post_bd(POST_ARGS) 733 { 734 struct roff_node *n; 735 struct mdoc_argv *argv; 736 int i; 737 enum mdoc_disp dt; 738 739 n = mdoc->last; 740 for (i = 0; n->args && i < (int)n->args->argc; i++) { 741 argv = n->args->argv + i; 742 dt = DISP__NONE; 743 744 switch (argv->arg) { 745 case MDOC_Centred: 746 dt = DISP_centered; 747 break; 748 case MDOC_Ragged: 749 dt = DISP_ragged; 750 break; 751 case MDOC_Unfilled: 752 dt = DISP_unfilled; 753 break; 754 case MDOC_Filled: 755 dt = DISP_filled; 756 break; 757 case MDOC_Literal: 758 dt = DISP_literal; 759 break; 760 case MDOC_File: 761 mandoc_msg(MANDOCERR_BD_FILE, mdoc->parse, 762 n->line, n->pos, NULL); 763 break; 764 case MDOC_Offset: 765 if (0 == argv->sz) { 766 mandoc_msg(MANDOCERR_ARG_EMPTY, 767 mdoc->parse, argv->line, 768 argv->pos, "Bd -offset"); 769 break; 770 } 771 if (NULL != n->norm->Bd.offs) 772 mandoc_vmsg(MANDOCERR_ARG_REP, 773 mdoc->parse, argv->line, 774 argv->pos, "Bd -offset %s", 775 argv->value[0]); 776 rewrite_macro2len(mdoc, argv->value); 777 n->norm->Bd.offs = argv->value[0]; 778 break; 779 case MDOC_Compact: 780 if (n->norm->Bd.comp) 781 mandoc_msg(MANDOCERR_ARG_REP, 782 mdoc->parse, argv->line, 783 argv->pos, "Bd -compact"); 784 n->norm->Bd.comp = 1; 785 break; 786 default: 787 abort(); 788 } 789 if (DISP__NONE == dt) 790 continue; 791 792 if (DISP__NONE == n->norm->Bd.type) 793 n->norm->Bd.type = dt; 794 else 795 mandoc_vmsg(MANDOCERR_BD_REP, 796 mdoc->parse, n->line, n->pos, 797 "Bd -%s", mdoc_argnames[argv->arg]); 798 } 799 800 if (DISP__NONE == n->norm->Bd.type) { 801 mandoc_msg(MANDOCERR_BD_NOTYPE, mdoc->parse, 802 n->line, n->pos, "Bd"); 803 n->norm->Bd.type = DISP_ragged; 804 } 805 } 806 807 /* 808 * Stand-alone line macros. 809 */ 810 811 static void 812 post_an_norm(POST_ARGS) 813 { 814 struct roff_node *n; 815 struct mdoc_argv *argv; 816 size_t i; 817 818 n = mdoc->last; 819 if (n->args == NULL) 820 return; 821 822 for (i = 1; i < n->args->argc; i++) { 823 argv = n->args->argv + i; 824 mandoc_vmsg(MANDOCERR_AN_REP, 825 mdoc->parse, argv->line, argv->pos, 826 "An -%s", mdoc_argnames[argv->arg]); 827 } 828 829 argv = n->args->argv; 830 if (argv->arg == MDOC_Split) 831 n->norm->An.auth = AUTH_split; 832 else if (argv->arg == MDOC_Nosplit) 833 n->norm->An.auth = AUTH_nosplit; 834 else 835 abort(); 836 } 837 838 static void 839 post_eoln(POST_ARGS) 840 { 841 struct roff_node *n; 842 843 post_useless(mdoc); 844 n = mdoc->last; 845 if (n->child != NULL) 846 mandoc_vmsg(MANDOCERR_ARG_SKIP, mdoc->parse, n->line, 847 n->pos, "%s %s", roff_name[n->tok], n->child->string); 848 849 while (n->child != NULL) 850 roff_node_delete(mdoc, n->child); 851 852 roff_word_alloc(mdoc, n->line, n->pos, n->tok == MDOC_Bt ? 853 "is currently in beta test." : "currently under development."); 854 mdoc->last->flags |= NODE_EOS | NODE_NOSRC; 855 mdoc->last = n; 856 } 857 858 static int 859 build_list(struct roff_man *mdoc, int tok) 860 { 861 struct roff_node *n; 862 int ic; 863 864 n = mdoc->last->next; 865 for (ic = 1;; ic++) { 866 roff_elem_alloc(mdoc, n->line, n->pos, tok); 867 mdoc->last->flags |= NODE_NOSRC; 868 mdoc_node_relink(mdoc, n); 869 n = mdoc->last = mdoc->last->parent; 870 mdoc->next = ROFF_NEXT_SIBLING; 871 if (n->next == NULL) 872 return ic; 873 if (ic > 1 || n->next->next != NULL) { 874 roff_word_alloc(mdoc, n->line, n->pos, ","); 875 mdoc->last->flags |= NODE_DELIMC | NODE_NOSRC; 876 } 877 n = mdoc->last->next; 878 if (n->next == NULL) { 879 roff_word_alloc(mdoc, n->line, n->pos, "and"); 880 mdoc->last->flags |= NODE_NOSRC; 881 } 882 } 883 } 884 885 static void 886 post_ex(POST_ARGS) 887 { 888 struct roff_node *n; 889 int ic; 890 891 post_std(mdoc); 892 893 n = mdoc->last; 894 mdoc->next = ROFF_NEXT_CHILD; 895 roff_word_alloc(mdoc, n->line, n->pos, "The"); 896 mdoc->last->flags |= NODE_NOSRC; 897 898 if (mdoc->last->next != NULL) 899 ic = build_list(mdoc, MDOC_Nm); 900 else if (mdoc->meta.name != NULL) { 901 roff_elem_alloc(mdoc, n->line, n->pos, MDOC_Nm); 902 mdoc->last->flags |= NODE_NOSRC; 903 roff_word_alloc(mdoc, n->line, n->pos, mdoc->meta.name); 904 mdoc->last->flags |= NODE_NOSRC; 905 mdoc->last = mdoc->last->parent; 906 mdoc->next = ROFF_NEXT_SIBLING; 907 ic = 1; 908 } else { 909 mandoc_msg(MANDOCERR_EX_NONAME, mdoc->parse, 910 n->line, n->pos, "Ex"); 911 ic = 0; 912 } 913 914 roff_word_alloc(mdoc, n->line, n->pos, 915 ic > 1 ? "utilities exit\\~0" : "utility exits\\~0"); 916 mdoc->last->flags |= NODE_NOSRC; 917 roff_word_alloc(mdoc, n->line, n->pos, 918 "on success, and\\~>0 if an error occurs."); 919 mdoc->last->flags |= NODE_EOS | NODE_NOSRC; 920 mdoc->last = n; 921 } 922 923 static void 924 post_lb(POST_ARGS) 925 { 926 struct roff_node *n; 927 928 post_delim_nb(mdoc); 929 930 n = mdoc->last; 931 assert(n->child->type == ROFFT_TEXT); 932 mdoc->next = ROFF_NEXT_CHILD; 933 roff_word_alloc(mdoc, n->line, n->pos, "library"); 934 mdoc->last->flags = NODE_NOSRC; 935 roff_word_alloc(mdoc, n->line, n->pos, "\\(Lq"); 936 mdoc->last->flags = NODE_DELIMO | NODE_NOSRC; 937 mdoc->last = mdoc->last->next; 938 roff_word_alloc(mdoc, n->line, n->pos, "\\(Rq"); 939 mdoc->last->flags = NODE_DELIMC | NODE_NOSRC; 940 mdoc->last = n; 941 } 942 943 static void 944 post_rv(POST_ARGS) 945 { 946 struct roff_node *n; 947 int ic; 948 949 post_std(mdoc); 950 951 n = mdoc->last; 952 mdoc->next = ROFF_NEXT_CHILD; 953 if (n->child != NULL) { 954 roff_word_alloc(mdoc, n->line, n->pos, "The"); 955 mdoc->last->flags |= NODE_NOSRC; 956 ic = build_list(mdoc, MDOC_Fn); 957 roff_word_alloc(mdoc, n->line, n->pos, 958 ic > 1 ? "functions return" : "function returns"); 959 mdoc->last->flags |= NODE_NOSRC; 960 roff_word_alloc(mdoc, n->line, n->pos, 961 "the value\\~0 if successful;"); 962 } else 963 roff_word_alloc(mdoc, n->line, n->pos, "Upon successful " 964 "completion, the value\\~0 is returned;"); 965 mdoc->last->flags |= NODE_NOSRC; 966 967 roff_word_alloc(mdoc, n->line, n->pos, "otherwise " 968 "the value\\~\\-1 is returned and the global variable"); 969 mdoc->last->flags |= NODE_NOSRC; 970 roff_elem_alloc(mdoc, n->line, n->pos, MDOC_Va); 971 mdoc->last->flags |= NODE_NOSRC; 972 roff_word_alloc(mdoc, n->line, n->pos, "errno"); 973 mdoc->last->flags |= NODE_NOSRC; 974 mdoc->last = mdoc->last->parent; 975 mdoc->next = ROFF_NEXT_SIBLING; 976 roff_word_alloc(mdoc, n->line, n->pos, 977 "is set to indicate the error."); 978 mdoc->last->flags |= NODE_EOS | NODE_NOSRC; 979 mdoc->last = n; 980 } 981 982 static void 983 post_std(POST_ARGS) 984 { 985 struct roff_node *n; 986 987 post_delim(mdoc); 988 989 n = mdoc->last; 990 if (n->args && n->args->argc == 1) 991 if (n->args->argv[0].arg == MDOC_Std) 992 return; 993 994 mandoc_msg(MANDOCERR_ARG_STD, mdoc->parse, 995 n->line, n->pos, roff_name[n->tok]); 996 } 997 998 static void 999 post_st(POST_ARGS) 1000 { 1001 struct roff_node *n, *nch; 1002 const char *p; 1003 1004 n = mdoc->last; 1005 nch = n->child; 1006 assert(nch->type == ROFFT_TEXT); 1007 1008 if ((p = mdoc_a2st(nch->string)) == NULL) { 1009 mandoc_vmsg(MANDOCERR_ST_BAD, mdoc->parse, 1010 nch->line, nch->pos, "St %s", nch->string); 1011 roff_node_delete(mdoc, n); 1012 return; 1013 } 1014 1015 nch->flags |= NODE_NOPRT; 1016 mdoc->next = ROFF_NEXT_CHILD; 1017 roff_word_alloc(mdoc, nch->line, nch->pos, p); 1018 mdoc->last->flags |= NODE_NOSRC; 1019 mdoc->last= n; 1020 } 1021 1022 static void 1023 post_obsolete(POST_ARGS) 1024 { 1025 struct roff_node *n; 1026 1027 n = mdoc->last; 1028 if (n->type == ROFFT_ELEM || n->type == ROFFT_BLOCK) 1029 mandoc_msg(MANDOCERR_MACRO_OBS, mdoc->parse, 1030 n->line, n->pos, roff_name[n->tok]); 1031 } 1032 1033 static void 1034 post_useless(POST_ARGS) 1035 { 1036 struct roff_node *n; 1037 1038 n = mdoc->last; 1039 mandoc_msg(MANDOCERR_MACRO_USELESS, mdoc->parse, 1040 n->line, n->pos, roff_name[n->tok]); 1041 } 1042 1043 /* 1044 * Block macros. 1045 */ 1046 1047 static void 1048 post_bf(POST_ARGS) 1049 { 1050 struct roff_node *np, *nch; 1051 1052 /* 1053 * Unlike other data pointers, these are "housed" by the HEAD 1054 * element, which contains the goods. 1055 */ 1056 1057 np = mdoc->last; 1058 if (np->type != ROFFT_HEAD) 1059 return; 1060 1061 assert(np->parent->type == ROFFT_BLOCK); 1062 assert(np->parent->tok == MDOC_Bf); 1063 1064 /* Check the number of arguments. */ 1065 1066 nch = np->child; 1067 if (np->parent->args == NULL) { 1068 if (nch == NULL) { 1069 mandoc_msg(MANDOCERR_BF_NOFONT, mdoc->parse, 1070 np->line, np->pos, "Bf"); 1071 return; 1072 } 1073 nch = nch->next; 1074 } 1075 if (nch != NULL) 1076 mandoc_vmsg(MANDOCERR_ARG_EXCESS, mdoc->parse, 1077 nch->line, nch->pos, "Bf ... %s", nch->string); 1078 1079 /* Extract argument into data. */ 1080 1081 if (np->parent->args != NULL) { 1082 switch (np->parent->args->argv[0].arg) { 1083 case MDOC_Emphasis: 1084 np->norm->Bf.font = FONT_Em; 1085 break; 1086 case MDOC_Literal: 1087 np->norm->Bf.font = FONT_Li; 1088 break; 1089 case MDOC_Symbolic: 1090 np->norm->Bf.font = FONT_Sy; 1091 break; 1092 default: 1093 abort(); 1094 } 1095 return; 1096 } 1097 1098 /* Extract parameter into data. */ 1099 1100 if ( ! strcmp(np->child->string, "Em")) 1101 np->norm->Bf.font = FONT_Em; 1102 else if ( ! strcmp(np->child->string, "Li")) 1103 np->norm->Bf.font = FONT_Li; 1104 else if ( ! strcmp(np->child->string, "Sy")) 1105 np->norm->Bf.font = FONT_Sy; 1106 else 1107 mandoc_vmsg(MANDOCERR_BF_BADFONT, mdoc->parse, 1108 np->child->line, np->child->pos, 1109 "Bf %s", np->child->string); 1110 } 1111 1112 static void 1113 post_fname(POST_ARGS) 1114 { 1115 const struct roff_node *n; 1116 const char *cp; 1117 size_t pos; 1118 1119 n = mdoc->last->child; 1120 pos = strcspn(n->string, "()"); 1121 cp = n->string + pos; 1122 if ( ! (cp[0] == '\0' || (cp[0] == '(' && cp[1] == '*'))) 1123 mandoc_msg(MANDOCERR_FN_PAREN, mdoc->parse, 1124 n->line, n->pos + pos, n->string); 1125 } 1126 1127 static void 1128 post_fn(POST_ARGS) 1129 { 1130 1131 post_fname(mdoc); 1132 post_fa(mdoc); 1133 } 1134 1135 static void 1136 post_fo(POST_ARGS) 1137 { 1138 const struct roff_node *n; 1139 1140 n = mdoc->last; 1141 1142 if (n->type != ROFFT_HEAD) 1143 return; 1144 1145 if (n->child == NULL) { 1146 mandoc_msg(MANDOCERR_FO_NOHEAD, mdoc->parse, 1147 n->line, n->pos, "Fo"); 1148 return; 1149 } 1150 if (n->child != n->last) { 1151 mandoc_vmsg(MANDOCERR_ARG_EXCESS, mdoc->parse, 1152 n->child->next->line, n->child->next->pos, 1153 "Fo ... %s", n->child->next->string); 1154 while (n->child != n->last) 1155 roff_node_delete(mdoc, n->last); 1156 } else 1157 post_delim(mdoc); 1158 1159 post_fname(mdoc); 1160 } 1161 1162 static void 1163 post_fa(POST_ARGS) 1164 { 1165 const struct roff_node *n; 1166 const char *cp; 1167 1168 for (n = mdoc->last->child; n != NULL; n = n->next) { 1169 for (cp = n->string; *cp != '\0'; cp++) { 1170 /* Ignore callbacks and alterations. */ 1171 if (*cp == '(' || *cp == '{') 1172 break; 1173 if (*cp != ',') 1174 continue; 1175 mandoc_msg(MANDOCERR_FA_COMMA, mdoc->parse, 1176 n->line, n->pos + (cp - n->string), 1177 n->string); 1178 break; 1179 } 1180 } 1181 post_delim_nb(mdoc); 1182 } 1183 1184 static void 1185 post_nm(POST_ARGS) 1186 { 1187 struct roff_node *n; 1188 1189 n = mdoc->last; 1190 1191 if (n->sec == SEC_NAME && n->child != NULL && 1192 n->child->type == ROFFT_TEXT && mdoc->meta.msec != NULL) 1193 mandoc_xr_add(mdoc->meta.msec, n->child->string, -1, -1); 1194 1195 if (n->last != NULL && 1196 (n->last->tok == MDOC_Pp || 1197 n->last->tok == MDOC_Lp)) 1198 mdoc_node_relink(mdoc, n->last); 1199 1200 if (mdoc->meta.name == NULL) 1201 deroff(&mdoc->meta.name, n); 1202 1203 if (mdoc->meta.name == NULL || 1204 (mdoc->lastsec == SEC_NAME && n->child == NULL)) 1205 mandoc_msg(MANDOCERR_NM_NONAME, mdoc->parse, 1206 n->line, n->pos, "Nm"); 1207 1208 switch (n->type) { 1209 case ROFFT_ELEM: 1210 post_delim_nb(mdoc); 1211 break; 1212 case ROFFT_HEAD: 1213 post_delim(mdoc); 1214 break; 1215 default: 1216 return; 1217 } 1218 1219 if ((n->child != NULL && n->child->type == ROFFT_TEXT) || 1220 mdoc->meta.name == NULL) 1221 return; 1222 1223 mdoc->next = ROFF_NEXT_CHILD; 1224 roff_word_alloc(mdoc, n->line, n->pos, mdoc->meta.name); 1225 mdoc->last->flags |= NODE_NOSRC; 1226 mdoc->last = n; 1227 } 1228 1229 static void 1230 post_nd(POST_ARGS) 1231 { 1232 struct roff_node *n; 1233 1234 n = mdoc->last; 1235 1236 if (n->type != ROFFT_BODY) 1237 return; 1238 1239 if (n->sec != SEC_NAME) 1240 mandoc_msg(MANDOCERR_ND_LATE, mdoc->parse, 1241 n->line, n->pos, "Nd"); 1242 1243 if (n->child == NULL) 1244 mandoc_msg(MANDOCERR_ND_EMPTY, mdoc->parse, 1245 n->line, n->pos, "Nd"); 1246 else 1247 post_delim(mdoc); 1248 1249 post_hyph(mdoc); 1250 } 1251 1252 static void 1253 post_display(POST_ARGS) 1254 { 1255 struct roff_node *n, *np; 1256 1257 n = mdoc->last; 1258 switch (n->type) { 1259 case ROFFT_BODY: 1260 if (n->end != ENDBODY_NOT) { 1261 if (n->tok == MDOC_Bd && 1262 n->body->parent->args == NULL) 1263 roff_node_delete(mdoc, n); 1264 } else if (n->child == NULL) 1265 mandoc_msg(MANDOCERR_BLK_EMPTY, mdoc->parse, 1266 n->line, n->pos, roff_name[n->tok]); 1267 else if (n->tok == MDOC_D1) 1268 post_hyph(mdoc); 1269 break; 1270 case ROFFT_BLOCK: 1271 if (n->tok == MDOC_Bd) { 1272 if (n->args == NULL) { 1273 mandoc_msg(MANDOCERR_BD_NOARG, 1274 mdoc->parse, n->line, n->pos, "Bd"); 1275 mdoc->next = ROFF_NEXT_SIBLING; 1276 while (n->body->child != NULL) 1277 mdoc_node_relink(mdoc, 1278 n->body->child); 1279 roff_node_delete(mdoc, n); 1280 break; 1281 } 1282 post_bd(mdoc); 1283 post_prevpar(mdoc); 1284 } 1285 for (np = n->parent; np != NULL; np = np->parent) { 1286 if (np->type == ROFFT_BLOCK && np->tok == MDOC_Bd) { 1287 mandoc_vmsg(MANDOCERR_BD_NEST, 1288 mdoc->parse, n->line, n->pos, 1289 "%s in Bd", roff_name[n->tok]); 1290 break; 1291 } 1292 } 1293 break; 1294 default: 1295 break; 1296 } 1297 } 1298 1299 static void 1300 post_defaults(POST_ARGS) 1301 { 1302 struct roff_node *nn; 1303 1304 if (mdoc->last->child != NULL) { 1305 post_delim_nb(mdoc); 1306 return; 1307 } 1308 1309 /* 1310 * The `Ar' defaults to "file ..." if no value is provided as an 1311 * argument; the `Mt' and `Pa' macros use "~"; the `Li' just 1312 * gets an empty string. 1313 */ 1314 1315 nn = mdoc->last; 1316 switch (nn->tok) { 1317 case MDOC_Ar: 1318 mdoc->next = ROFF_NEXT_CHILD; 1319 roff_word_alloc(mdoc, nn->line, nn->pos, "file"); 1320 mdoc->last->flags |= NODE_NOSRC; 1321 roff_word_alloc(mdoc, nn->line, nn->pos, "..."); 1322 mdoc->last->flags |= NODE_NOSRC; 1323 break; 1324 case MDOC_Pa: 1325 case MDOC_Mt: 1326 mdoc->next = ROFF_NEXT_CHILD; 1327 roff_word_alloc(mdoc, nn->line, nn->pos, "~"); 1328 mdoc->last->flags |= NODE_NOSRC; 1329 break; 1330 default: 1331 abort(); 1332 } 1333 mdoc->last = nn; 1334 } 1335 1336 static void 1337 post_at(POST_ARGS) 1338 { 1339 struct roff_node *n, *nch; 1340 const char *att; 1341 1342 n = mdoc->last; 1343 nch = n->child; 1344 1345 /* 1346 * If we have a child, look it up in the standard keys. If a 1347 * key exist, use that instead of the child; if it doesn't, 1348 * prefix "AT&T UNIX " to the existing data. 1349 */ 1350 1351 att = NULL; 1352 if (nch != NULL && ((att = mdoc_a2att(nch->string)) == NULL)) 1353 mandoc_vmsg(MANDOCERR_AT_BAD, mdoc->parse, 1354 nch->line, nch->pos, "At %s", nch->string); 1355 1356 mdoc->next = ROFF_NEXT_CHILD; 1357 if (att != NULL) { 1358 roff_word_alloc(mdoc, nch->line, nch->pos, att); 1359 nch->flags |= NODE_NOPRT; 1360 } else 1361 roff_word_alloc(mdoc, n->line, n->pos, "AT&T UNIX"); 1362 mdoc->last->flags |= NODE_NOSRC; 1363 mdoc->last = n; 1364 } 1365 1366 static void 1367 post_an(POST_ARGS) 1368 { 1369 struct roff_node *np, *nch; 1370 1371 post_an_norm(mdoc); 1372 1373 np = mdoc->last; 1374 nch = np->child; 1375 if (np->norm->An.auth == AUTH__NONE) { 1376 if (nch == NULL) 1377 mandoc_msg(MANDOCERR_MACRO_EMPTY, mdoc->parse, 1378 np->line, np->pos, "An"); 1379 else 1380 post_delim_nb(mdoc); 1381 } else if (nch != NULL) 1382 mandoc_vmsg(MANDOCERR_ARG_EXCESS, mdoc->parse, 1383 nch->line, nch->pos, "An ... %s", nch->string); 1384 } 1385 1386 static void 1387 post_en(POST_ARGS) 1388 { 1389 1390 post_obsolete(mdoc); 1391 if (mdoc->last->type == ROFFT_BLOCK) 1392 mdoc->last->norm->Es = mdoc->last_es; 1393 } 1394 1395 static void 1396 post_es(POST_ARGS) 1397 { 1398 1399 post_obsolete(mdoc); 1400 mdoc->last_es = mdoc->last; 1401 } 1402 1403 static void 1404 post_xx(POST_ARGS) 1405 { 1406 struct roff_node *n; 1407 const char *os; 1408 char *v; 1409 1410 post_delim_nb(mdoc); 1411 1412 n = mdoc->last; 1413 switch (n->tok) { 1414 case MDOC_Bsx: 1415 os = "BSD/OS"; 1416 break; 1417 case MDOC_Dx: 1418 os = "DragonFly"; 1419 break; 1420 case MDOC_Fx: 1421 os = "FreeBSD"; 1422 break; 1423 case MDOC_Nx: 1424 os = "NetBSD"; 1425 if (n->child == NULL) 1426 break; 1427 v = n->child->string; 1428 if ((v[0] != '0' && v[0] != '1') || v[1] != '.' || 1429 v[2] < '0' || v[2] > '9' || 1430 v[3] < 'a' || v[3] > 'z' || v[4] != '\0') 1431 break; 1432 n->child->flags |= NODE_NOPRT; 1433 mdoc->next = ROFF_NEXT_CHILD; 1434 roff_word_alloc(mdoc, n->child->line, n->child->pos, v); 1435 v = mdoc->last->string; 1436 v[3] = toupper((unsigned char)v[3]); 1437 mdoc->last->flags |= NODE_NOSRC; 1438 mdoc->last = n; 1439 break; 1440 case MDOC_Ox: 1441 os = "OpenBSD"; 1442 break; 1443 case MDOC_Ux: 1444 os = "UNIX"; 1445 break; 1446 default: 1447 abort(); 1448 } 1449 mdoc->next = ROFF_NEXT_CHILD; 1450 roff_word_alloc(mdoc, n->line, n->pos, os); 1451 mdoc->last->flags |= NODE_NOSRC; 1452 mdoc->last = n; 1453 } 1454 1455 static void 1456 post_it(POST_ARGS) 1457 { 1458 struct roff_node *nbl, *nit, *nch; 1459 int i, cols; 1460 enum mdoc_list lt; 1461 1462 post_prevpar(mdoc); 1463 1464 nit = mdoc->last; 1465 if (nit->type != ROFFT_BLOCK) 1466 return; 1467 1468 nbl = nit->parent->parent; 1469 lt = nbl->norm->Bl.type; 1470 1471 switch (lt) { 1472 case LIST_tag: 1473 case LIST_hang: 1474 case LIST_ohang: 1475 case LIST_inset: 1476 case LIST_diag: 1477 if (nit->head->child == NULL) 1478 mandoc_vmsg(MANDOCERR_IT_NOHEAD, 1479 mdoc->parse, nit->line, nit->pos, 1480 "Bl -%s It", 1481 mdoc_argnames[nbl->args->argv[0].arg]); 1482 break; 1483 case LIST_bullet: 1484 case LIST_dash: 1485 case LIST_enum: 1486 case LIST_hyphen: 1487 if (nit->body == NULL || nit->body->child == NULL) 1488 mandoc_vmsg(MANDOCERR_IT_NOBODY, 1489 mdoc->parse, nit->line, nit->pos, 1490 "Bl -%s It", 1491 mdoc_argnames[nbl->args->argv[0].arg]); 1492 /* FALLTHROUGH */ 1493 case LIST_item: 1494 if ((nch = nit->head->child) != NULL) 1495 mandoc_vmsg(MANDOCERR_ARG_SKIP, mdoc->parse, 1496 nit->line, nit->pos, "It %s", 1497 nch->string == NULL ? roff_name[nch->tok] : 1498 nch->string); 1499 break; 1500 case LIST_column: 1501 cols = (int)nbl->norm->Bl.ncols; 1502 1503 assert(nit->head->child == NULL); 1504 1505 if (nit->head->next->child == NULL && 1506 nit->head->next->next == NULL) { 1507 mandoc_msg(MANDOCERR_MACRO_EMPTY, mdoc->parse, 1508 nit->line, nit->pos, "It"); 1509 roff_node_delete(mdoc, nit); 1510 break; 1511 } 1512 1513 i = 0; 1514 for (nch = nit->child; nch != NULL; nch = nch->next) { 1515 if (nch->type != ROFFT_BODY) 1516 continue; 1517 if (i++ && nch->flags & NODE_LINE) 1518 mandoc_msg(MANDOCERR_TA_LINE, mdoc->parse, 1519 nch->line, nch->pos, "Ta"); 1520 } 1521 if (i < cols || i > cols + 1) 1522 mandoc_vmsg(MANDOCERR_BL_COL, 1523 mdoc->parse, nit->line, nit->pos, 1524 "%d columns, %d cells", cols, i); 1525 else if (nit->head->next->child != NULL && 1526 nit->head->next->child->line > nit->line) 1527 mandoc_msg(MANDOCERR_IT_NOARG, mdoc->parse, 1528 nit->line, nit->pos, "Bl -column It"); 1529 break; 1530 default: 1531 abort(); 1532 } 1533 } 1534 1535 static void 1536 post_bl_block(POST_ARGS) 1537 { 1538 struct roff_node *n, *ni, *nc; 1539 1540 post_prevpar(mdoc); 1541 1542 n = mdoc->last; 1543 for (ni = n->body->child; ni != NULL; ni = ni->next) { 1544 if (ni->body == NULL) 1545 continue; 1546 nc = ni->body->last; 1547 while (nc != NULL) { 1548 switch (nc->tok) { 1549 case MDOC_Pp: 1550 case MDOC_Lp: 1551 case ROFF_br: 1552 break; 1553 default: 1554 nc = NULL; 1555 continue; 1556 } 1557 if (ni->next == NULL) { 1558 mandoc_msg(MANDOCERR_PAR_MOVE, 1559 mdoc->parse, nc->line, nc->pos, 1560 roff_name[nc->tok]); 1561 mdoc_node_relink(mdoc, nc); 1562 } else if (n->norm->Bl.comp == 0 && 1563 n->norm->Bl.type != LIST_column) { 1564 mandoc_vmsg(MANDOCERR_PAR_SKIP, 1565 mdoc->parse, nc->line, nc->pos, 1566 "%s before It", roff_name[nc->tok]); 1567 roff_node_delete(mdoc, nc); 1568 } else 1569 break; 1570 nc = ni->body->last; 1571 } 1572 } 1573 } 1574 1575 /* 1576 * If the argument of -offset or -width is a macro, 1577 * replace it with the associated default width. 1578 */ 1579 static void 1580 rewrite_macro2len(struct roff_man *mdoc, char **arg) 1581 { 1582 size_t width; 1583 enum roff_tok tok; 1584 1585 if (*arg == NULL) 1586 return; 1587 else if ( ! strcmp(*arg, "Ds")) 1588 width = 6; 1589 else if ((tok = roffhash_find(mdoc->mdocmac, *arg, 0)) == TOKEN_NONE) 1590 return; 1591 else 1592 width = macro2len(tok); 1593 1594 free(*arg); 1595 mandoc_asprintf(arg, "%zun", width); 1596 } 1597 1598 static void 1599 post_bl_head(POST_ARGS) 1600 { 1601 struct roff_node *nbl, *nh, *nch, *nnext; 1602 struct mdoc_argv *argv; 1603 int i, j; 1604 1605 post_bl_norm(mdoc); 1606 1607 nh = mdoc->last; 1608 if (nh->norm->Bl.type != LIST_column) { 1609 if ((nch = nh->child) == NULL) 1610 return; 1611 mandoc_vmsg(MANDOCERR_ARG_EXCESS, mdoc->parse, 1612 nch->line, nch->pos, "Bl ... %s", nch->string); 1613 while (nch != NULL) { 1614 roff_node_delete(mdoc, nch); 1615 nch = nh->child; 1616 } 1617 return; 1618 } 1619 1620 /* 1621 * Append old-style lists, where the column width specifiers 1622 * trail as macro parameters, to the new-style ("normal-form") 1623 * lists where they're argument values following -column. 1624 */ 1625 1626 if (nh->child == NULL) 1627 return; 1628 1629 nbl = nh->parent; 1630 for (j = 0; j < (int)nbl->args->argc; j++) 1631 if (nbl->args->argv[j].arg == MDOC_Column) 1632 break; 1633 1634 assert(j < (int)nbl->args->argc); 1635 1636 /* 1637 * Accommodate for new-style groff column syntax. Shuffle the 1638 * child nodes, all of which must be TEXT, as arguments for the 1639 * column field. Then, delete the head children. 1640 */ 1641 1642 argv = nbl->args->argv + j; 1643 i = argv->sz; 1644 for (nch = nh->child; nch != NULL; nch = nch->next) 1645 argv->sz++; 1646 argv->value = mandoc_reallocarray(argv->value, 1647 argv->sz, sizeof(char *)); 1648 1649 nh->norm->Bl.ncols = argv->sz; 1650 nh->norm->Bl.cols = (void *)argv->value; 1651 1652 for (nch = nh->child; nch != NULL; nch = nnext) { 1653 argv->value[i++] = nch->string; 1654 nch->string = NULL; 1655 nnext = nch->next; 1656 roff_node_delete(NULL, nch); 1657 } 1658 nh->child = NULL; 1659 } 1660 1661 static void 1662 post_bl(POST_ARGS) 1663 { 1664 struct roff_node *nparent, *nprev; /* of the Bl block */ 1665 struct roff_node *nblock, *nbody; /* of the Bl */ 1666 struct roff_node *nchild, *nnext; /* of the Bl body */ 1667 const char *prev_Er; 1668 int order; 1669 1670 nbody = mdoc->last; 1671 switch (nbody->type) { 1672 case ROFFT_BLOCK: 1673 post_bl_block(mdoc); 1674 return; 1675 case ROFFT_HEAD: 1676 post_bl_head(mdoc); 1677 return; 1678 case ROFFT_BODY: 1679 break; 1680 default: 1681 return; 1682 } 1683 if (nbody->end != ENDBODY_NOT) 1684 return; 1685 1686 nchild = nbody->child; 1687 if (nchild == NULL) { 1688 mandoc_msg(MANDOCERR_BLK_EMPTY, mdoc->parse, 1689 nbody->line, nbody->pos, "Bl"); 1690 return; 1691 } 1692 while (nchild != NULL) { 1693 nnext = nchild->next; 1694 if (nchild->tok == MDOC_It || 1695 (nchild->tok == MDOC_Sm && 1696 nnext != NULL && nnext->tok == MDOC_It)) { 1697 nchild = nnext; 1698 continue; 1699 } 1700 1701 /* 1702 * In .Bl -column, the first rows may be implicit, 1703 * that is, they may not start with .It macros. 1704 * Such rows may be followed by nodes generated on the 1705 * roff level, for example .TS, which cannot be moved 1706 * out of the list. In that case, wrap such roff nodes 1707 * into an implicit row. 1708 */ 1709 1710 if (nchild->prev != NULL) { 1711 mdoc->last = nchild; 1712 mdoc->next = ROFF_NEXT_SIBLING; 1713 roff_block_alloc(mdoc, nchild->line, 1714 nchild->pos, MDOC_It); 1715 roff_head_alloc(mdoc, nchild->line, 1716 nchild->pos, MDOC_It); 1717 mdoc->next = ROFF_NEXT_SIBLING; 1718 roff_body_alloc(mdoc, nchild->line, 1719 nchild->pos, MDOC_It); 1720 while (nchild->tok != MDOC_It) { 1721 mdoc_node_relink(mdoc, nchild); 1722 if ((nchild = nnext) == NULL) 1723 break; 1724 nnext = nchild->next; 1725 mdoc->next = ROFF_NEXT_SIBLING; 1726 } 1727 mdoc->last = nbody; 1728 continue; 1729 } 1730 1731 mandoc_msg(MANDOCERR_BL_MOVE, mdoc->parse, 1732 nchild->line, nchild->pos, roff_name[nchild->tok]); 1733 1734 /* 1735 * Move the node out of the Bl block. 1736 * First, collect all required node pointers. 1737 */ 1738 1739 nblock = nbody->parent; 1740 nprev = nblock->prev; 1741 nparent = nblock->parent; 1742 1743 /* 1744 * Unlink this child. 1745 */ 1746 1747 nbody->child = nnext; 1748 if (nnext == NULL) 1749 nbody->last = NULL; 1750 else 1751 nnext->prev = NULL; 1752 1753 /* 1754 * Relink this child. 1755 */ 1756 1757 nchild->parent = nparent; 1758 nchild->prev = nprev; 1759 nchild->next = nblock; 1760 1761 nblock->prev = nchild; 1762 if (nprev == NULL) 1763 nparent->child = nchild; 1764 else 1765 nprev->next = nchild; 1766 1767 nchild = nnext; 1768 } 1769 1770 if (mdoc->meta.os_e != MANDOC_OS_NETBSD) 1771 return; 1772 1773 prev_Er = NULL; 1774 for (nchild = nbody->child; nchild != NULL; nchild = nchild->next) { 1775 if (nchild->tok != MDOC_It) 1776 continue; 1777 if ((nnext = nchild->head->child) == NULL) 1778 continue; 1779 if (nnext->type == ROFFT_BLOCK) 1780 nnext = nnext->body->child; 1781 if (nnext == NULL || nnext->tok != MDOC_Er) 1782 continue; 1783 nnext = nnext->child; 1784 if (prev_Er != NULL) { 1785 order = strcmp(prev_Er, nnext->string); 1786 if (order > 0) 1787 mandoc_vmsg(MANDOCERR_ER_ORDER, 1788 mdoc->parse, nnext->line, nnext->pos, 1789 "Er %s %s (NetBSD)", 1790 prev_Er, nnext->string); 1791 else if (order == 0) 1792 mandoc_vmsg(MANDOCERR_ER_REP, 1793 mdoc->parse, nnext->line, nnext->pos, 1794 "Er %s (NetBSD)", prev_Er); 1795 } 1796 prev_Er = nnext->string; 1797 } 1798 } 1799 1800 static void 1801 post_bk(POST_ARGS) 1802 { 1803 struct roff_node *n; 1804 1805 n = mdoc->last; 1806 1807 if (n->type == ROFFT_BLOCK && n->body->child == NULL) { 1808 mandoc_msg(MANDOCERR_BLK_EMPTY, 1809 mdoc->parse, n->line, n->pos, "Bk"); 1810 roff_node_delete(mdoc, n); 1811 } 1812 } 1813 1814 static void 1815 post_sm(POST_ARGS) 1816 { 1817 struct roff_node *nch; 1818 1819 nch = mdoc->last->child; 1820 1821 if (nch == NULL) { 1822 mdoc->flags ^= MDOC_SMOFF; 1823 return; 1824 } 1825 1826 assert(nch->type == ROFFT_TEXT); 1827 1828 if ( ! strcmp(nch->string, "on")) { 1829 mdoc->flags &= ~MDOC_SMOFF; 1830 return; 1831 } 1832 if ( ! strcmp(nch->string, "off")) { 1833 mdoc->flags |= MDOC_SMOFF; 1834 return; 1835 } 1836 1837 mandoc_vmsg(MANDOCERR_SM_BAD, 1838 mdoc->parse, nch->line, nch->pos, 1839 "%s %s", roff_name[mdoc->last->tok], nch->string); 1840 mdoc_node_relink(mdoc, nch); 1841 return; 1842 } 1843 1844 static void 1845 post_root(POST_ARGS) 1846 { 1847 const char *openbsd_arch[] = { 1848 "alpha", "amd64", "arm64", "armv7", "hppa", "i386", 1849 "landisk", "loongson", "luna88k", "macppc", "mips64", 1850 "octeon", "sgi", "socppc", "sparc64", NULL 1851 }; 1852 const char *netbsd_arch[] = { 1853 "acorn26", "acorn32", "algor", "alpha", "amiga", 1854 "arc", "atari", 1855 "bebox", "cats", "cesfic", "cobalt", "dreamcast", 1856 "emips", "evbarm", "evbmips", "evbppc", "evbsh3", "evbsh5", 1857 "hp300", "hpcarm", "hpcmips", "hpcsh", "hppa", 1858 "i386", "ibmnws", "luna68k", 1859 "mac68k", "macppc", "mipsco", "mmeye", "mvme68k", "mvmeppc", 1860 "netwinder", "news68k", "newsmips", "next68k", 1861 "pc532", "playstation2", "pmax", "pmppc", "prep", 1862 "sandpoint", "sbmips", "sgimips", "shark", 1863 "sparc", "sparc64", "sun2", "sun3", 1864 "vax", "walnut", "x68k", "x86", "x86_64", "xen", NULL 1865 }; 1866 const char **arches[] = { NULL, netbsd_arch, openbsd_arch }; 1867 1868 struct roff_node *n; 1869 const char **arch; 1870 1871 /* Add missing prologue data. */ 1872 1873 if (mdoc->meta.date == NULL) 1874 mdoc->meta.date = mdoc->quick ? mandoc_strdup("") : 1875 mandoc_normdate(mdoc, NULL, 0, 0); 1876 1877 if (mdoc->meta.title == NULL) { 1878 mandoc_msg(MANDOCERR_DT_NOTITLE, 1879 mdoc->parse, 0, 0, "EOF"); 1880 mdoc->meta.title = mandoc_strdup("UNTITLED"); 1881 } 1882 1883 if (mdoc->meta.vol == NULL) 1884 mdoc->meta.vol = mandoc_strdup("LOCAL"); 1885 1886 if (mdoc->meta.os == NULL) { 1887 mandoc_msg(MANDOCERR_OS_MISSING, 1888 mdoc->parse, 0, 0, NULL); 1889 mdoc->meta.os = mandoc_strdup(""); 1890 } else if (mdoc->meta.os_e && 1891 (mdoc->meta.rcsids & (1 << mdoc->meta.os_e)) == 0) 1892 mandoc_msg(MANDOCERR_RCS_MISSING, mdoc->parse, 0, 0, 1893 mdoc->meta.os_e == MANDOC_OS_OPENBSD ? 1894 "(OpenBSD)" : "(NetBSD)"); 1895 1896 if (mdoc->meta.arch != NULL && 1897 (arch = arches[mdoc->meta.os_e]) != NULL) { 1898 while (*arch != NULL && strcmp(*arch, mdoc->meta.arch)) 1899 arch++; 1900 if (*arch == NULL) { 1901 n = mdoc->first->child; 1902 while (n->tok != MDOC_Dt || 1903 n->child == NULL || 1904 n->child->next == NULL || 1905 n->child->next->next == NULL) 1906 n = n->next; 1907 n = n->child->next->next; 1908 mandoc_vmsg(MANDOCERR_ARCH_BAD, 1909 mdoc->parse, n->line, n->pos, 1910 "Dt ... %s %s", mdoc->meta.arch, 1911 mdoc->meta.os_e == MANDOC_OS_OPENBSD ? 1912 "(OpenBSD)" : "(NetBSD)"); 1913 } 1914 } 1915 1916 /* Check that we begin with a proper `Sh'. */ 1917 1918 n = mdoc->first->child; 1919 while (n != NULL && n->tok >= MDOC_Dd && 1920 mdoc_macros[n->tok].flags & MDOC_PROLOGUE) 1921 n = n->next; 1922 1923 if (n == NULL) 1924 mandoc_msg(MANDOCERR_DOC_EMPTY, mdoc->parse, 0, 0, NULL); 1925 else if (n->tok != MDOC_Sh) 1926 mandoc_msg(MANDOCERR_SEC_BEFORE, mdoc->parse, 1927 n->line, n->pos, roff_name[n->tok]); 1928 } 1929 1930 static void 1931 post_rs(POST_ARGS) 1932 { 1933 struct roff_node *np, *nch, *next, *prev; 1934 int i, j; 1935 1936 np = mdoc->last; 1937 1938 if (np->type != ROFFT_BODY) 1939 return; 1940 1941 if (np->child == NULL) { 1942 mandoc_msg(MANDOCERR_RS_EMPTY, mdoc->parse, 1943 np->line, np->pos, "Rs"); 1944 return; 1945 } 1946 1947 /* 1948 * The full `Rs' block needs special handling to order the 1949 * sub-elements according to `rsord'. Pick through each element 1950 * and correctly order it. This is an insertion sort. 1951 */ 1952 1953 next = NULL; 1954 for (nch = np->child->next; nch != NULL; nch = next) { 1955 /* Determine order number of this child. */ 1956 for (i = 0; i < RSORD_MAX; i++) 1957 if (rsord[i] == nch->tok) 1958 break; 1959 1960 if (i == RSORD_MAX) { 1961 mandoc_msg(MANDOCERR_RS_BAD, mdoc->parse, 1962 nch->line, nch->pos, roff_name[nch->tok]); 1963 i = -1; 1964 } else if (nch->tok == MDOC__J || nch->tok == MDOC__B) 1965 np->norm->Rs.quote_T++; 1966 1967 /* 1968 * Remove this child from the chain. This somewhat 1969 * repeats roff_node_unlink(), but since we're 1970 * just re-ordering, there's no need for the 1971 * full unlink process. 1972 */ 1973 1974 if ((next = nch->next) != NULL) 1975 next->prev = nch->prev; 1976 1977 if ((prev = nch->prev) != NULL) 1978 prev->next = nch->next; 1979 1980 nch->prev = nch->next = NULL; 1981 1982 /* 1983 * Scan back until we reach a node that's 1984 * to be ordered before this child. 1985 */ 1986 1987 for ( ; prev ; prev = prev->prev) { 1988 /* Determine order of `prev'. */ 1989 for (j = 0; j < RSORD_MAX; j++) 1990 if (rsord[j] == prev->tok) 1991 break; 1992 if (j == RSORD_MAX) 1993 j = -1; 1994 1995 if (j <= i) 1996 break; 1997 } 1998 1999 /* 2000 * Set this child back into its correct place 2001 * in front of the `prev' node. 2002 */ 2003 2004 nch->prev = prev; 2005 2006 if (prev == NULL) { 2007 np->child->prev = nch; 2008 nch->next = np->child; 2009 np->child = nch; 2010 } else { 2011 if (prev->next) 2012 prev->next->prev = nch; 2013 nch->next = prev->next; 2014 prev->next = nch; 2015 } 2016 } 2017 } 2018 2019 /* 2020 * For some arguments of some macros, 2021 * convert all breakable hyphens into ASCII_HYPH. 2022 */ 2023 static void 2024 post_hyph(POST_ARGS) 2025 { 2026 struct roff_node *nch; 2027 char *cp; 2028 2029 for (nch = mdoc->last->child; nch != NULL; nch = nch->next) { 2030 if (nch->type != ROFFT_TEXT) 2031 continue; 2032 cp = nch->string; 2033 if (*cp == '\0') 2034 continue; 2035 while (*(++cp) != '\0') 2036 if (*cp == '-' && 2037 isalpha((unsigned char)cp[-1]) && 2038 isalpha((unsigned char)cp[1])) 2039 *cp = ASCII_HYPH; 2040 } 2041 } 2042 2043 static void 2044 post_ns(POST_ARGS) 2045 { 2046 struct roff_node *n; 2047 2048 n = mdoc->last; 2049 if (n->flags & NODE_LINE || 2050 (n->next != NULL && n->next->flags & NODE_DELIMC)) 2051 mandoc_msg(MANDOCERR_NS_SKIP, mdoc->parse, 2052 n->line, n->pos, NULL); 2053 } 2054 2055 static void 2056 post_sx(POST_ARGS) 2057 { 2058 post_delim(mdoc); 2059 post_hyph(mdoc); 2060 } 2061 2062 static void 2063 post_sh(POST_ARGS) 2064 { 2065 2066 post_ignpar(mdoc); 2067 2068 switch (mdoc->last->type) { 2069 case ROFFT_HEAD: 2070 post_sh_head(mdoc); 2071 break; 2072 case ROFFT_BODY: 2073 switch (mdoc->lastsec) { 2074 case SEC_NAME: 2075 post_sh_name(mdoc); 2076 break; 2077 case SEC_SEE_ALSO: 2078 post_sh_see_also(mdoc); 2079 break; 2080 case SEC_AUTHORS: 2081 post_sh_authors(mdoc); 2082 break; 2083 default: 2084 break; 2085 } 2086 break; 2087 default: 2088 break; 2089 } 2090 } 2091 2092 static void 2093 post_sh_name(POST_ARGS) 2094 { 2095 struct roff_node *n; 2096 int hasnm, hasnd; 2097 2098 hasnm = hasnd = 0; 2099 2100 for (n = mdoc->last->child; n != NULL; n = n->next) { 2101 switch (n->tok) { 2102 case MDOC_Nm: 2103 if (hasnm && n->child != NULL) 2104 mandoc_vmsg(MANDOCERR_NAMESEC_PUNCT, 2105 mdoc->parse, n->line, n->pos, 2106 "Nm %s", n->child->string); 2107 hasnm = 1; 2108 continue; 2109 case MDOC_Nd: 2110 hasnd = 1; 2111 if (n->next != NULL) 2112 mandoc_msg(MANDOCERR_NAMESEC_ND, 2113 mdoc->parse, n->line, n->pos, NULL); 2114 break; 2115 case TOKEN_NONE: 2116 if (n->type == ROFFT_TEXT && 2117 n->string[0] == ',' && n->string[1] == '\0' && 2118 n->next != NULL && n->next->tok == MDOC_Nm) { 2119 n = n->next; 2120 continue; 2121 } 2122 /* FALLTHROUGH */ 2123 default: 2124 mandoc_msg(MANDOCERR_NAMESEC_BAD, mdoc->parse, 2125 n->line, n->pos, roff_name[n->tok]); 2126 continue; 2127 } 2128 break; 2129 } 2130 2131 if ( ! hasnm) 2132 mandoc_msg(MANDOCERR_NAMESEC_NONM, mdoc->parse, 2133 mdoc->last->line, mdoc->last->pos, NULL); 2134 if ( ! hasnd) 2135 mandoc_msg(MANDOCERR_NAMESEC_NOND, mdoc->parse, 2136 mdoc->last->line, mdoc->last->pos, NULL); 2137 } 2138 2139 static void 2140 post_sh_see_also(POST_ARGS) 2141 { 2142 const struct roff_node *n; 2143 const char *name, *sec; 2144 const char *lastname, *lastsec, *lastpunct; 2145 int cmp; 2146 2147 n = mdoc->last->child; 2148 lastname = lastsec = lastpunct = NULL; 2149 while (n != NULL) { 2150 if (n->tok != MDOC_Xr || 2151 n->child == NULL || 2152 n->child->next == NULL) 2153 break; 2154 2155 /* Process one .Xr node. */ 2156 2157 name = n->child->string; 2158 sec = n->child->next->string; 2159 if (lastsec != NULL) { 2160 if (lastpunct[0] != ',' || lastpunct[1] != '\0') 2161 mandoc_vmsg(MANDOCERR_XR_PUNCT, 2162 mdoc->parse, n->line, n->pos, 2163 "%s before %s(%s)", lastpunct, 2164 name, sec); 2165 cmp = strcmp(lastsec, sec); 2166 if (cmp > 0) 2167 mandoc_vmsg(MANDOCERR_XR_ORDER, 2168 mdoc->parse, n->line, n->pos, 2169 "%s(%s) after %s(%s)", name, 2170 sec, lastname, lastsec); 2171 else if (cmp == 0 && 2172 strcasecmp(lastname, name) > 0) 2173 mandoc_vmsg(MANDOCERR_XR_ORDER, 2174 mdoc->parse, n->line, n->pos, 2175 "%s after %s", name, lastname); 2176 } 2177 lastname = name; 2178 lastsec = sec; 2179 2180 /* Process the following node. */ 2181 2182 n = n->next; 2183 if (n == NULL) 2184 break; 2185 if (n->tok == MDOC_Xr) { 2186 lastpunct = "none"; 2187 continue; 2188 } 2189 if (n->type != ROFFT_TEXT) 2190 break; 2191 for (name = n->string; *name != '\0'; name++) 2192 if (isalpha((const unsigned char)*name)) 2193 return; 2194 lastpunct = n->string; 2195 if (n->next == NULL || n->next->tok == MDOC_Rs) 2196 mandoc_vmsg(MANDOCERR_XR_PUNCT, mdoc->parse, 2197 n->line, n->pos, "%s after %s(%s)", 2198 lastpunct, lastname, lastsec); 2199 n = n->next; 2200 } 2201 } 2202 2203 static int 2204 child_an(const struct roff_node *n) 2205 { 2206 2207 for (n = n->child; n != NULL; n = n->next) 2208 if ((n->tok == MDOC_An && n->child != NULL) || child_an(n)) 2209 return 1; 2210 return 0; 2211 } 2212 2213 static void 2214 post_sh_authors(POST_ARGS) 2215 { 2216 2217 if ( ! child_an(mdoc->last)) 2218 mandoc_msg(MANDOCERR_AN_MISSING, mdoc->parse, 2219 mdoc->last->line, mdoc->last->pos, NULL); 2220 } 2221 2222 /* 2223 * Return an upper bound for the string distance (allowing 2224 * transpositions). Not a full Levenshtein implementation 2225 * because Levenshtein is quadratic in the string length 2226 * and this function is called for every standard name, 2227 * so the check for each custom name would be cubic. 2228 * The following crude heuristics is linear, resulting 2229 * in quadratic behaviour for checking one custom name, 2230 * which does not cause measurable slowdown. 2231 */ 2232 static int 2233 similar(const char *s1, const char *s2) 2234 { 2235 const int maxdist = 3; 2236 int dist = 0; 2237 2238 while (s1[0] != '\0' && s2[0] != '\0') { 2239 if (s1[0] == s2[0]) { 2240 s1++; 2241 s2++; 2242 continue; 2243 } 2244 if (++dist > maxdist) 2245 return INT_MAX; 2246 if (s1[1] == s2[1]) { /* replacement */ 2247 s1++; 2248 s2++; 2249 } else if (s1[0] == s2[1] && s1[1] == s2[0]) { 2250 s1 += 2; /* transposition */ 2251 s2 += 2; 2252 } else if (s1[0] == s2[1]) /* insertion */ 2253 s2++; 2254 else if (s1[1] == s2[0]) /* deletion */ 2255 s1++; 2256 else 2257 return INT_MAX; 2258 } 2259 dist += strlen(s1) + strlen(s2); 2260 return dist > maxdist ? INT_MAX : dist; 2261 } 2262 2263 static void 2264 post_sh_head(POST_ARGS) 2265 { 2266 struct roff_node *nch; 2267 const char *goodsec; 2268 const char *const *testsec; 2269 int dist, mindist; 2270 enum roff_sec sec; 2271 2272 /* 2273 * Process a new section. Sections are either "named" or 2274 * "custom". Custom sections are user-defined, while named ones 2275 * follow a conventional order and may only appear in certain 2276 * manual sections. 2277 */ 2278 2279 sec = mdoc->last->sec; 2280 2281 /* The NAME should be first. */ 2282 2283 if (sec != SEC_NAME && mdoc->lastnamed == SEC_NONE) 2284 mandoc_vmsg(MANDOCERR_NAMESEC_FIRST, mdoc->parse, 2285 mdoc->last->line, mdoc->last->pos, "Sh %s", 2286 sec != SEC_CUSTOM ? secnames[sec] : 2287 (nch = mdoc->last->child) == NULL ? "" : 2288 nch->type == ROFFT_TEXT ? nch->string : 2289 roff_name[nch->tok]); 2290 2291 /* The SYNOPSIS gets special attention in other areas. */ 2292 2293 if (sec == SEC_SYNOPSIS) { 2294 roff_setreg(mdoc->roff, "nS", 1, '='); 2295 mdoc->flags |= MDOC_SYNOPSIS; 2296 } else { 2297 roff_setreg(mdoc->roff, "nS", 0, '='); 2298 mdoc->flags &= ~MDOC_SYNOPSIS; 2299 } 2300 2301 /* Mark our last section. */ 2302 2303 mdoc->lastsec = sec; 2304 2305 /* We don't care about custom sections after this. */ 2306 2307 if (sec == SEC_CUSTOM) { 2308 if ((nch = mdoc->last->child) == NULL || 2309 nch->type != ROFFT_TEXT || nch->next != NULL) 2310 return; 2311 goodsec = NULL; 2312 mindist = INT_MAX; 2313 for (testsec = secnames + 1; *testsec != NULL; testsec++) { 2314 dist = similar(nch->string, *testsec); 2315 if (dist < mindist) { 2316 goodsec = *testsec; 2317 mindist = dist; 2318 } 2319 } 2320 if (goodsec != NULL) 2321 mandoc_vmsg(MANDOCERR_SEC_TYPO, mdoc->parse, 2322 nch->line, nch->pos, "Sh %s instead of %s", 2323 nch->string, goodsec); 2324 return; 2325 } 2326 2327 /* 2328 * Check whether our non-custom section is being repeated or is 2329 * out of order. 2330 */ 2331 2332 if (sec == mdoc->lastnamed) 2333 mandoc_vmsg(MANDOCERR_SEC_REP, mdoc->parse, 2334 mdoc->last->line, mdoc->last->pos, 2335 "Sh %s", secnames[sec]); 2336 2337 if (sec < mdoc->lastnamed) 2338 mandoc_vmsg(MANDOCERR_SEC_ORDER, mdoc->parse, 2339 mdoc->last->line, mdoc->last->pos, 2340 "Sh %s", secnames[sec]); 2341 2342 /* Mark the last named section. */ 2343 2344 mdoc->lastnamed = sec; 2345 2346 /* Check particular section/manual conventions. */ 2347 2348 if (mdoc->meta.msec == NULL) 2349 return; 2350 2351 goodsec = NULL; 2352 switch (sec) { 2353 case SEC_ERRORS: 2354 if (*mdoc->meta.msec == '4') 2355 break; 2356 goodsec = "2, 3, 4, 9"; 2357 /* FALLTHROUGH */ 2358 case SEC_RETURN_VALUES: 2359 case SEC_LIBRARY: 2360 if (*mdoc->meta.msec == '2') 2361 break; 2362 if (*mdoc->meta.msec == '3') 2363 break; 2364 if (NULL == goodsec) 2365 goodsec = "2, 3, 9"; 2366 /* FALLTHROUGH */ 2367 case SEC_CONTEXT: 2368 if (*mdoc->meta.msec == '9') 2369 break; 2370 if (NULL == goodsec) 2371 goodsec = "9"; 2372 mandoc_vmsg(MANDOCERR_SEC_MSEC, mdoc->parse, 2373 mdoc->last->line, mdoc->last->pos, 2374 "Sh %s for %s only", secnames[sec], goodsec); 2375 break; 2376 default: 2377 break; 2378 } 2379 } 2380 2381 static void 2382 post_xr(POST_ARGS) 2383 { 2384 struct roff_node *n, *nch; 2385 2386 n = mdoc->last; 2387 nch = n->child; 2388 if (nch->next == NULL) { 2389 mandoc_vmsg(MANDOCERR_XR_NOSEC, mdoc->parse, 2390 n->line, n->pos, "Xr %s", nch->string); 2391 } else { 2392 assert(nch->next == n->last); 2393 if(mandoc_xr_add(nch->next->string, nch->string, 2394 nch->line, nch->pos)) 2395 mandoc_vmsg(MANDOCERR_XR_SELF, mdoc->parse, 2396 nch->line, nch->pos, "Xr %s %s", 2397 nch->string, nch->next->string); 2398 } 2399 post_delim_nb(mdoc); 2400 } 2401 2402 static void 2403 post_ignpar(POST_ARGS) 2404 { 2405 struct roff_node *np; 2406 2407 switch (mdoc->last->type) { 2408 case ROFFT_BLOCK: 2409 post_prevpar(mdoc); 2410 return; 2411 case ROFFT_HEAD: 2412 post_delim(mdoc); 2413 post_hyph(mdoc); 2414 return; 2415 case ROFFT_BODY: 2416 break; 2417 default: 2418 return; 2419 } 2420 2421 if ((np = mdoc->last->child) != NULL) 2422 if (np->tok == MDOC_Pp || np->tok == MDOC_Lp) { 2423 mandoc_vmsg(MANDOCERR_PAR_SKIP, 2424 mdoc->parse, np->line, np->pos, 2425 "%s after %s", roff_name[np->tok], 2426 roff_name[mdoc->last->tok]); 2427 roff_node_delete(mdoc, np); 2428 } 2429 2430 if ((np = mdoc->last->last) != NULL) 2431 if (np->tok == MDOC_Pp || np->tok == MDOC_Lp) { 2432 mandoc_vmsg(MANDOCERR_PAR_SKIP, mdoc->parse, 2433 np->line, np->pos, "%s at the end of %s", 2434 roff_name[np->tok], 2435 roff_name[mdoc->last->tok]); 2436 roff_node_delete(mdoc, np); 2437 } 2438 } 2439 2440 static void 2441 post_prevpar(POST_ARGS) 2442 { 2443 struct roff_node *n; 2444 2445 n = mdoc->last; 2446 if (NULL == n->prev) 2447 return; 2448 if (n->type != ROFFT_ELEM && n->type != ROFFT_BLOCK) 2449 return; 2450 2451 /* 2452 * Don't allow prior `Lp' or `Pp' prior to a paragraph-type 2453 * block: `Lp', `Pp', or non-compact `Bd' or `Bl'. 2454 */ 2455 2456 if (n->prev->tok != MDOC_Pp && 2457 n->prev->tok != MDOC_Lp && 2458 n->prev->tok != ROFF_br) 2459 return; 2460 if (n->tok == MDOC_Bl && n->norm->Bl.comp) 2461 return; 2462 if (n->tok == MDOC_Bd && n->norm->Bd.comp) 2463 return; 2464 if (n->tok == MDOC_It && n->parent->norm->Bl.comp) 2465 return; 2466 2467 mandoc_vmsg(MANDOCERR_PAR_SKIP, mdoc->parse, 2468 n->prev->line, n->prev->pos, "%s before %s", 2469 roff_name[n->prev->tok], roff_name[n->tok]); 2470 roff_node_delete(mdoc, n->prev); 2471 } 2472 2473 static void 2474 post_par(POST_ARGS) 2475 { 2476 struct roff_node *np; 2477 2478 np = mdoc->last; 2479 if (np->tok != ROFF_br && np->tok != ROFF_sp) 2480 post_prevpar(mdoc); 2481 2482 if (np->tok == ROFF_sp) { 2483 if (np->child != NULL && np->child->next != NULL) 2484 mandoc_vmsg(MANDOCERR_ARG_EXCESS, mdoc->parse, 2485 np->child->next->line, np->child->next->pos, 2486 "sp ... %s", np->child->next->string); 2487 } else if (np->child != NULL) 2488 mandoc_vmsg(MANDOCERR_ARG_SKIP, 2489 mdoc->parse, np->line, np->pos, "%s %s", 2490 roff_name[np->tok], np->child->string); 2491 2492 if ((np = mdoc->last->prev) == NULL) { 2493 np = mdoc->last->parent; 2494 if (np->tok != MDOC_Sh && np->tok != MDOC_Ss) 2495 return; 2496 } else if (np->tok != MDOC_Pp && np->tok != MDOC_Lp && 2497 (mdoc->last->tok != ROFF_br || 2498 (np->tok != ROFF_sp && np->tok != ROFF_br))) 2499 return; 2500 2501 mandoc_vmsg(MANDOCERR_PAR_SKIP, mdoc->parse, 2502 mdoc->last->line, mdoc->last->pos, "%s after %s", 2503 roff_name[mdoc->last->tok], roff_name[np->tok]); 2504 roff_node_delete(mdoc, mdoc->last); 2505 } 2506 2507 static void 2508 post_dd(POST_ARGS) 2509 { 2510 struct roff_node *n; 2511 char *datestr; 2512 2513 n = mdoc->last; 2514 n->flags |= NODE_NOPRT; 2515 2516 if (mdoc->meta.date != NULL) { 2517 mandoc_msg(MANDOCERR_PROLOG_REP, mdoc->parse, 2518 n->line, n->pos, "Dd"); 2519 free(mdoc->meta.date); 2520 } else if (mdoc->flags & MDOC_PBODY) 2521 mandoc_msg(MANDOCERR_PROLOG_LATE, mdoc->parse, 2522 n->line, n->pos, "Dd"); 2523 else if (mdoc->meta.title != NULL) 2524 mandoc_msg(MANDOCERR_PROLOG_ORDER, mdoc->parse, 2525 n->line, n->pos, "Dd after Dt"); 2526 else if (mdoc->meta.os != NULL) 2527 mandoc_msg(MANDOCERR_PROLOG_ORDER, mdoc->parse, 2528 n->line, n->pos, "Dd after Os"); 2529 2530 if (n->child == NULL || n->child->string[0] == '\0') { 2531 mdoc->meta.date = mdoc->quick ? mandoc_strdup("") : 2532 mandoc_normdate(mdoc, NULL, n->line, n->pos); 2533 return; 2534 } 2535 2536 datestr = NULL; 2537 deroff(&datestr, n); 2538 if (mdoc->quick) 2539 mdoc->meta.date = datestr; 2540 else { 2541 mdoc->meta.date = mandoc_normdate(mdoc, 2542 datestr, n->line, n->pos); 2543 free(datestr); 2544 } 2545 } 2546 2547 static void 2548 post_dt(POST_ARGS) 2549 { 2550 struct roff_node *nn, *n; 2551 const char *cp; 2552 char *p; 2553 2554 n = mdoc->last; 2555 n->flags |= NODE_NOPRT; 2556 2557 if (mdoc->flags & MDOC_PBODY) { 2558 mandoc_msg(MANDOCERR_DT_LATE, mdoc->parse, 2559 n->line, n->pos, "Dt"); 2560 return; 2561 } 2562 2563 if (mdoc->meta.title != NULL) 2564 mandoc_msg(MANDOCERR_PROLOG_REP, mdoc->parse, 2565 n->line, n->pos, "Dt"); 2566 else if (mdoc->meta.os != NULL) 2567 mandoc_msg(MANDOCERR_PROLOG_ORDER, mdoc->parse, 2568 n->line, n->pos, "Dt after Os"); 2569 2570 free(mdoc->meta.title); 2571 free(mdoc->meta.msec); 2572 free(mdoc->meta.vol); 2573 free(mdoc->meta.arch); 2574 2575 mdoc->meta.title = NULL; 2576 mdoc->meta.msec = NULL; 2577 mdoc->meta.vol = NULL; 2578 mdoc->meta.arch = NULL; 2579 2580 /* Mandatory first argument: title. */ 2581 2582 nn = n->child; 2583 if (nn == NULL || *nn->string == '\0') { 2584 mandoc_msg(MANDOCERR_DT_NOTITLE, 2585 mdoc->parse, 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_vmsg(MANDOCERR_TITLE_CASE, 2595 mdoc->parse, nn->line, 2596 nn->pos + (p - nn->string), 2597 "Dt %s", nn->string); 2598 break; 2599 } 2600 } 2601 2602 /* Mandatory second argument: section. */ 2603 2604 if (nn != NULL) 2605 nn = nn->next; 2606 2607 if (nn == NULL) { 2608 mandoc_vmsg(MANDOCERR_MSEC_MISSING, 2609 mdoc->parse, n->line, n->pos, 2610 "Dt %s", mdoc->meta.title); 2611 mdoc->meta.vol = mandoc_strdup("LOCAL"); 2612 return; /* msec and arch remain NULL. */ 2613 } 2614 2615 mdoc->meta.msec = mandoc_strdup(nn->string); 2616 2617 /* Infer volume title from section number. */ 2618 2619 cp = mandoc_a2msec(nn->string); 2620 if (cp == NULL) { 2621 mandoc_vmsg(MANDOCERR_MSEC_BAD, mdoc->parse, 2622 nn->line, nn->pos, "Dt ... %s", nn->string); 2623 mdoc->meta.vol = mandoc_strdup(nn->string); 2624 } else 2625 mdoc->meta.vol = mandoc_strdup(cp); 2626 2627 /* Optional third argument: architecture. */ 2628 2629 if ((nn = nn->next) == NULL) 2630 return; 2631 2632 for (p = nn->string; *p != '\0'; p++) 2633 *p = tolower((unsigned char)*p); 2634 mdoc->meta.arch = mandoc_strdup(nn->string); 2635 2636 /* Ignore fourth and later arguments. */ 2637 2638 if ((nn = nn->next) != NULL) 2639 mandoc_vmsg(MANDOCERR_ARG_EXCESS, mdoc->parse, 2640 nn->line, nn->pos, "Dt ... %s", nn->string); 2641 } 2642 2643 static void 2644 post_bx(POST_ARGS) 2645 { 2646 struct roff_node *n, *nch; 2647 const char *macro; 2648 2649 post_delim_nb(mdoc); 2650 2651 n = mdoc->last; 2652 nch = n->child; 2653 2654 if (nch != NULL) { 2655 macro = !strcmp(nch->string, "Open") ? "Ox" : 2656 !strcmp(nch->string, "Net") ? "Nx" : 2657 !strcmp(nch->string, "Free") ? "Fx" : 2658 !strcmp(nch->string, "DragonFly") ? "Dx" : NULL; 2659 if (macro != NULL) 2660 mandoc_msg(MANDOCERR_BX, mdoc->parse, 2661 n->line, n->pos, macro); 2662 mdoc->last = nch; 2663 nch = nch->next; 2664 mdoc->next = ROFF_NEXT_SIBLING; 2665 roff_elem_alloc(mdoc, n->line, n->pos, MDOC_Ns); 2666 mdoc->last->flags |= NODE_NOSRC; 2667 mdoc->next = ROFF_NEXT_SIBLING; 2668 } else 2669 mdoc->next = ROFF_NEXT_CHILD; 2670 roff_word_alloc(mdoc, n->line, n->pos, "BSD"); 2671 mdoc->last->flags |= NODE_NOSRC; 2672 2673 if (nch == NULL) { 2674 mdoc->last = n; 2675 return; 2676 } 2677 2678 roff_elem_alloc(mdoc, n->line, n->pos, MDOC_Ns); 2679 mdoc->last->flags |= NODE_NOSRC; 2680 mdoc->next = ROFF_NEXT_SIBLING; 2681 roff_word_alloc(mdoc, n->line, n->pos, "-"); 2682 mdoc->last->flags |= NODE_NOSRC; 2683 roff_elem_alloc(mdoc, n->line, n->pos, MDOC_Ns); 2684 mdoc->last->flags |= NODE_NOSRC; 2685 mdoc->last = n; 2686 2687 /* 2688 * Make `Bx's second argument always start with an uppercase 2689 * letter. Groff checks if it's an "accepted" term, but we just 2690 * uppercase blindly. 2691 */ 2692 2693 *nch->string = (char)toupper((unsigned char)*nch->string); 2694 } 2695 2696 static void 2697 post_os(POST_ARGS) 2698 { 2699 #ifndef OSNAME 2700 struct utsname utsname; 2701 static char *defbuf; 2702 #endif 2703 struct roff_node *n; 2704 2705 n = mdoc->last; 2706 n->flags |= NODE_NOPRT; 2707 2708 if (mdoc->meta.os != NULL) 2709 mandoc_msg(MANDOCERR_PROLOG_REP, mdoc->parse, 2710 n->line, n->pos, "Os"); 2711 else if (mdoc->flags & MDOC_PBODY) 2712 mandoc_msg(MANDOCERR_PROLOG_LATE, mdoc->parse, 2713 n->line, n->pos, "Os"); 2714 2715 post_delim(mdoc); 2716 2717 /* 2718 * Set the operating system by way of the `Os' macro. 2719 * The order of precedence is: 2720 * 1. the argument of the `Os' macro, unless empty 2721 * 2. the -Ios=foo command line argument, if provided 2722 * 3. -DOSNAME="\"foo\"", if provided during compilation 2723 * 4. "sysname release" from uname(3) 2724 */ 2725 2726 free(mdoc->meta.os); 2727 mdoc->meta.os = NULL; 2728 deroff(&mdoc->meta.os, n); 2729 if (mdoc->meta.os) 2730 goto out; 2731 2732 if (mdoc->os_s != NULL) { 2733 mdoc->meta.os = mandoc_strdup(mdoc->os_s); 2734 goto out; 2735 } 2736 2737 #ifdef OSNAME 2738 mdoc->meta.os = mandoc_strdup(OSNAME); 2739 #else /*!OSNAME */ 2740 if (defbuf == NULL) { 2741 if (uname(&utsname) == -1) { 2742 mandoc_msg(MANDOCERR_OS_UNAME, mdoc->parse, 2743 n->line, n->pos, "Os"); 2744 defbuf = mandoc_strdup("UNKNOWN"); 2745 } else 2746 mandoc_asprintf(&defbuf, "%s %s", 2747 utsname.sysname, utsname.release); 2748 } 2749 mdoc->meta.os = mandoc_strdup(defbuf); 2750 #endif /*!OSNAME*/ 2751 2752 out: 2753 if (mdoc->meta.os_e == MANDOC_OS_OTHER) { 2754 if (strstr(mdoc->meta.os, "OpenBSD") != NULL) 2755 mdoc->meta.os_e = MANDOC_OS_OPENBSD; 2756 else if (strstr(mdoc->meta.os, "NetBSD") != NULL) 2757 mdoc->meta.os_e = MANDOC_OS_NETBSD; 2758 } 2759 2760 /* 2761 * This is the earliest point where we can check 2762 * Mdocdate conventions because we don't know 2763 * the operating system earlier. 2764 */ 2765 2766 if (n->child != NULL) 2767 mandoc_vmsg(MANDOCERR_OS_ARG, mdoc->parse, 2768 n->child->line, n->child->pos, 2769 "Os %s (%s)", n->child->string, 2770 mdoc->meta.os_e == MANDOC_OS_OPENBSD ? 2771 "OpenBSD" : "NetBSD"); 2772 2773 while (n->tok != MDOC_Dd) 2774 if ((n = n->prev) == NULL) 2775 return; 2776 if ((n = n->child) == NULL) 2777 return; 2778 if (strncmp(n->string, "$" "Mdocdate", 9)) { 2779 if (mdoc->meta.os_e == MANDOC_OS_OPENBSD) 2780 mandoc_vmsg(MANDOCERR_MDOCDATE_MISSING, 2781 mdoc->parse, n->line, n->pos, 2782 "Dd %s (OpenBSD)", n->string); 2783 } else { 2784 if (mdoc->meta.os_e == MANDOC_OS_NETBSD) 2785 mandoc_vmsg(MANDOCERR_MDOCDATE, 2786 mdoc->parse, n->line, n->pos, 2787 "Dd %s (NetBSD)", n->string); 2788 } 2789 } 2790 2791 enum roff_sec 2792 mdoc_a2sec(const char *p) 2793 { 2794 int i; 2795 2796 for (i = 0; i < (int)SEC__MAX; i++) 2797 if (secnames[i] && 0 == strcmp(p, secnames[i])) 2798 return (enum roff_sec)i; 2799 2800 return SEC_CUSTOM; 2801 } 2802 2803 static size_t 2804 macro2len(enum roff_tok macro) 2805 { 2806 2807 switch (macro) { 2808 case MDOC_Ad: 2809 return 12; 2810 case MDOC_Ao: 2811 return 12; 2812 case MDOC_An: 2813 return 12; 2814 case MDOC_Aq: 2815 return 12; 2816 case MDOC_Ar: 2817 return 12; 2818 case MDOC_Bo: 2819 return 12; 2820 case MDOC_Bq: 2821 return 12; 2822 case MDOC_Cd: 2823 return 12; 2824 case MDOC_Cm: 2825 return 10; 2826 case MDOC_Do: 2827 return 10; 2828 case MDOC_Dq: 2829 return 12; 2830 case MDOC_Dv: 2831 return 12; 2832 case MDOC_Eo: 2833 return 12; 2834 case MDOC_Em: 2835 return 10; 2836 case MDOC_Er: 2837 return 17; 2838 case MDOC_Ev: 2839 return 15; 2840 case MDOC_Fa: 2841 return 12; 2842 case MDOC_Fl: 2843 return 10; 2844 case MDOC_Fo: 2845 return 16; 2846 case MDOC_Fn: 2847 return 16; 2848 case MDOC_Ic: 2849 return 10; 2850 case MDOC_Li: 2851 return 16; 2852 case MDOC_Ms: 2853 return 6; 2854 case MDOC_Nm: 2855 return 10; 2856 case MDOC_No: 2857 return 12; 2858 case MDOC_Oo: 2859 return 10; 2860 case MDOC_Op: 2861 return 14; 2862 case MDOC_Pa: 2863 return 32; 2864 case MDOC_Pf: 2865 return 12; 2866 case MDOC_Po: 2867 return 12; 2868 case MDOC_Pq: 2869 return 12; 2870 case MDOC_Ql: 2871 return 16; 2872 case MDOC_Qo: 2873 return 12; 2874 case MDOC_So: 2875 return 12; 2876 case MDOC_Sq: 2877 return 12; 2878 case MDOC_Sy: 2879 return 6; 2880 case MDOC_Sx: 2881 return 16; 2882 case MDOC_Tn: 2883 return 10; 2884 case MDOC_Va: 2885 return 12; 2886 case MDOC_Vt: 2887 return 12; 2888 case MDOC_Xr: 2889 return 10; 2890 default: 2891 break; 2892 }; 2893 return 0; 2894 } 2895