=================================================================== RCS file: /cvs/mandoc/mdoc_validate.c,v retrieving revision 1.298 retrieving revision 1.350 diff -u -p -r1.298 -r1.350 --- mandoc/mdoc_validate.c 2015/10/20 02:01:32 1.298 +++ mandoc/mdoc_validate.c 2017/07/20 12:54:02 1.350 @@ -1,7 +1,7 @@ -/* $Id: mdoc_validate.c,v 1.298 2015/10/20 02:01:32 schwarze Exp $ */ +/* $Id: mdoc_validate.c,v 1.350 2017/07/20 12:54:02 schwarze Exp $ */ /* * Copyright (c) 2008-2012 Kristaps Dzonsons - * Copyright (c) 2010-2015 Ingo Schwarze + * Copyright (c) 2010-2017 Ingo Schwarze * Copyright (c) 2010 Joerg Sonnenberger * * Permission to use, copy, modify, and distribute this software for any @@ -33,6 +33,7 @@ #include "mandoc_aux.h" #include "mandoc.h" +#include "mandoc_xr.h" #include "roff.h" #include "mdoc.h" #include "libmandoc.h" @@ -41,7 +42,6 @@ /* FIXME: .Bl -diag can't have non-text children in HEAD. */ -#define PRE_ARGS struct roff_man *mdoc, struct roff_node *n #define POST_ARGS struct roff_man *mdoc enum check_ineq { @@ -50,34 +50,35 @@ enum check_ineq { CHECK_EQ }; -typedef void (*v_pre)(PRE_ARGS); typedef void (*v_post)(POST_ARGS); -struct valids { - v_pre pre; - v_post post; -}; - +static int build_list(struct roff_man *, int); static void check_text(struct roff_man *, int, int, char *); static void check_argv(struct roff_man *, struct roff_node *, struct mdoc_argv *); static void check_args(struct roff_man *, struct roff_node *); +static void check_toptext(struct roff_man *, int, int, const char *); static int child_an(const struct roff_node *); -static size_t macro2len(int); -static void rewrite_macro2len(char **); +static size_t macro2len(enum roff_tok); +static void rewrite_macro2len(struct roff_man *, char **); +static int similar(const char *, const char *); static void post_an(POST_ARGS); +static void post_an_norm(POST_ARGS); static void post_at(POST_ARGS); +static void post_bd(POST_ARGS); static void post_bf(POST_ARGS); static void post_bk(POST_ARGS); static void post_bl(POST_ARGS); static void post_bl_block(POST_ARGS); -static void post_bl_block_tag(POST_ARGS); static void post_bl_head(POST_ARGS); +static void post_bl_norm(POST_ARGS); static void post_bx(POST_ARGS); -static void post_d1(POST_ARGS); static void post_defaults(POST_ARGS); +static void post_display(POST_ARGS); static void post_dd(POST_ARGS); +static void post_delim(POST_ARGS); +static void post_delim_nb(POST_ARGS); static void post_dt(POST_ARGS); static void post_en(POST_ARGS); static void post_es(POST_ARGS); @@ -91,14 +92,16 @@ static void post_hyph(POST_ARGS); static void post_ignpar(POST_ARGS); static void post_it(POST_ARGS); static void post_lb(POST_ARGS); -static void post_literal(POST_ARGS); static void post_nd(POST_ARGS); static void post_nm(POST_ARGS); static void post_ns(POST_ARGS); +static void post_obsolete(POST_ARGS); static void post_os(POST_ARGS); static void post_par(POST_ARGS); +static void post_prevpar(POST_ARGS); static void post_root(POST_ARGS); static void post_rs(POST_ARGS); +static void post_rv(POST_ARGS); static void post_sh(POST_ARGS); static void post_sh_head(POST_ARGS); static void post_sh_name(POST_ARGS); @@ -106,144 +109,139 @@ static void post_sh_see_also(POST_ARGS); static void post_sh_authors(POST_ARGS); static void post_sm(POST_ARGS); static void post_st(POST_ARGS); +static void post_std(POST_ARGS); +static void post_sx(POST_ARGS); +static void post_useless(POST_ARGS); +static void post_xr(POST_ARGS); +static void post_xx(POST_ARGS); -static void pre_an(PRE_ARGS); -static void pre_bd(PRE_ARGS); -static void pre_bl(PRE_ARGS); -static void pre_display(PRE_ARGS); -static void pre_obsolete(PRE_ARGS); -static void pre_par(PRE_ARGS); -static void pre_std(PRE_ARGS); - -static const struct valids mdoc_valids[MDOC_MAX] = { - { NULL, NULL }, /* Ap */ - { NULL, post_dd }, /* Dd */ - { NULL, post_dt }, /* Dt */ - { NULL, post_os }, /* Os */ - { NULL, post_sh }, /* Sh */ - { NULL, post_ignpar }, /* Ss */ - { pre_par, post_par }, /* Pp */ - { pre_display, post_d1 }, /* D1 */ - { pre_display, post_literal }, /* Dl */ - { pre_bd, post_literal }, /* Bd */ - { NULL, NULL }, /* Ed */ - { pre_bl, post_bl }, /* Bl */ - { NULL, NULL }, /* El */ - { pre_par, post_it }, /* It */ - { NULL, NULL }, /* Ad */ - { pre_an, post_an }, /* An */ - { NULL, post_defaults }, /* Ar */ - { NULL, NULL }, /* Cd */ - { NULL, NULL }, /* Cm */ - { NULL, NULL }, /* Dv */ - { NULL, NULL }, /* Er */ - { NULL, NULL }, /* Ev */ - { pre_std, post_ex }, /* Ex */ - { NULL, post_fa }, /* Fa */ - { NULL, NULL }, /* Fd */ - { NULL, NULL }, /* Fl */ - { NULL, post_fn }, /* Fn */ - { NULL, NULL }, /* Ft */ - { NULL, NULL }, /* Ic */ - { NULL, NULL }, /* In */ - { NULL, post_defaults }, /* Li */ - { NULL, post_nd }, /* Nd */ - { NULL, post_nm }, /* Nm */ - { NULL, NULL }, /* Op */ - { pre_obsolete, NULL }, /* Ot */ - { NULL, post_defaults }, /* Pa */ - { pre_std, NULL }, /* Rv */ - { NULL, post_st }, /* St */ - { NULL, NULL }, /* Va */ - { NULL, NULL }, /* Vt */ - { NULL, NULL }, /* Xr */ - { NULL, NULL }, /* %A */ - { NULL, post_hyph }, /* %B */ /* FIXME: can be used outside Rs/Re. */ - { NULL, NULL }, /* %D */ - { NULL, NULL }, /* %I */ - { NULL, NULL }, /* %J */ - { NULL, post_hyph }, /* %N */ - { NULL, post_hyph }, /* %O */ - { NULL, NULL }, /* %P */ - { NULL, post_hyph }, /* %R */ - { NULL, post_hyph }, /* %T */ /* FIXME: can be used outside Rs/Re. */ - { NULL, NULL }, /* %V */ - { NULL, NULL }, /* Ac */ - { NULL, NULL }, /* Ao */ - { NULL, NULL }, /* Aq */ - { NULL, post_at }, /* At */ - { NULL, NULL }, /* Bc */ - { NULL, post_bf }, /* Bf */ - { NULL, NULL }, /* Bo */ - { NULL, NULL }, /* Bq */ - { NULL, NULL }, /* Bsx */ - { NULL, post_bx }, /* Bx */ - { pre_obsolete, NULL }, /* Db */ - { NULL, NULL }, /* Dc */ - { NULL, NULL }, /* Do */ - { NULL, NULL }, /* Dq */ - { NULL, NULL }, /* Ec */ - { NULL, NULL }, /* Ef */ - { NULL, NULL }, /* Em */ - { NULL, NULL }, /* Eo */ - { NULL, NULL }, /* Fx */ - { NULL, NULL }, /* Ms */ - { NULL, NULL }, /* No */ - { NULL, post_ns }, /* Ns */ - { NULL, NULL }, /* Nx */ - { NULL, NULL }, /* Ox */ - { NULL, NULL }, /* Pc */ - { NULL, NULL }, /* Pf */ - { NULL, NULL }, /* Po */ - { NULL, NULL }, /* Pq */ - { NULL, NULL }, /* Qc */ - { NULL, NULL }, /* Ql */ - { NULL, NULL }, /* Qo */ - { NULL, NULL }, /* Qq */ - { NULL, NULL }, /* Re */ - { NULL, post_rs }, /* Rs */ - { NULL, NULL }, /* Sc */ - { NULL, NULL }, /* So */ - { NULL, NULL }, /* Sq */ - { NULL, post_sm }, /* Sm */ - { NULL, post_hyph }, /* Sx */ - { NULL, NULL }, /* Sy */ - { NULL, NULL }, /* Tn */ - { NULL, NULL }, /* Ux */ - { NULL, NULL }, /* Xc */ - { NULL, NULL }, /* Xo */ - { NULL, post_fo }, /* Fo */ - { NULL, NULL }, /* Fc */ - { NULL, NULL }, /* Oo */ - { NULL, NULL }, /* Oc */ - { NULL, post_bk }, /* Bk */ - { NULL, NULL }, /* Ek */ - { NULL, post_eoln }, /* Bt */ - { NULL, NULL }, /* Hf */ - { pre_obsolete, NULL }, /* Fr */ - { NULL, post_eoln }, /* Ud */ - { NULL, post_lb }, /* Lb */ - { pre_par, post_par }, /* Lp */ - { NULL, NULL }, /* Lk */ - { NULL, post_defaults }, /* Mt */ - { NULL, NULL }, /* Brq */ - { NULL, NULL }, /* Bro */ - { NULL, NULL }, /* Brc */ - { NULL, NULL }, /* %C */ - { pre_obsolete, post_es }, /* Es */ - { pre_obsolete, post_en }, /* En */ - { NULL, NULL }, /* Dx */ - { NULL, NULL }, /* %Q */ - { NULL, post_par }, /* br */ - { NULL, post_par }, /* sp */ - { NULL, NULL }, /* %U */ - { NULL, NULL }, /* Ta */ - { NULL, NULL }, /* ll */ +static const v_post __mdoc_valids[MDOC_MAX - MDOC_Dd] = { + post_dd, /* Dd */ + post_dt, /* Dt */ + post_os, /* Os */ + post_sh, /* Sh */ + post_ignpar, /* Ss */ + post_par, /* Pp */ + post_display, /* D1 */ + post_display, /* Dl */ + post_display, /* Bd */ + NULL, /* Ed */ + post_bl, /* Bl */ + NULL, /* El */ + post_it, /* It */ + post_delim_nb, /* Ad */ + post_an, /* An */ + NULL, /* Ap */ + post_defaults, /* Ar */ + NULL, /* Cd */ + post_delim_nb, /* Cm */ + post_delim_nb, /* Dv */ + post_delim_nb, /* Er */ + post_delim_nb, /* Ev */ + post_ex, /* Ex */ + post_fa, /* Fa */ + NULL, /* Fd */ + post_delim_nb, /* Fl */ + post_fn, /* Fn */ + post_delim_nb, /* Ft */ + post_delim_nb, /* Ic */ + post_delim_nb, /* In */ + post_defaults, /* Li */ + post_nd, /* Nd */ + post_nm, /* Nm */ + post_delim_nb, /* Op */ + post_obsolete, /* Ot */ + post_defaults, /* Pa */ + post_rv, /* Rv */ + post_st, /* St */ + post_delim_nb, /* Va */ + post_delim_nb, /* Vt */ + post_xr, /* Xr */ + NULL, /* %A */ + post_hyph, /* %B */ /* FIXME: can be used outside Rs/Re. */ + NULL, /* %D */ + NULL, /* %I */ + NULL, /* %J */ + post_hyph, /* %N */ + post_hyph, /* %O */ + NULL, /* %P */ + post_hyph, /* %R */ + post_hyph, /* %T */ /* FIXME: can be used outside Rs/Re. */ + NULL, /* %V */ + NULL, /* Ac */ + post_delim_nb, /* Ao */ + post_delim_nb, /* Aq */ + post_at, /* At */ + NULL, /* Bc */ + post_bf, /* Bf */ + post_delim_nb, /* Bo */ + NULL, /* Bq */ + post_xx, /* Bsx */ + post_bx, /* Bx */ + post_obsolete, /* Db */ + NULL, /* Dc */ + NULL, /* Do */ + NULL, /* Dq */ + NULL, /* Ec */ + NULL, /* Ef */ + post_delim_nb, /* Em */ + NULL, /* Eo */ + post_xx, /* Fx */ + post_delim_nb, /* Ms */ + NULL, /* No */ + post_ns, /* Ns */ + post_xx, /* Nx */ + post_xx, /* Ox */ + NULL, /* Pc */ + NULL, /* Pf */ + post_delim_nb, /* Po */ + post_delim_nb, /* Pq */ + NULL, /* Qc */ + post_delim_nb, /* Ql */ + post_delim_nb, /* Qo */ + post_delim_nb, /* Qq */ + NULL, /* Re */ + post_rs, /* Rs */ + NULL, /* Sc */ + post_delim_nb, /* So */ + post_delim_nb, /* Sq */ + post_sm, /* Sm */ + post_sx, /* Sx */ + post_delim_nb, /* Sy */ + post_useless, /* Tn */ + post_xx, /* Ux */ + NULL, /* Xc */ + NULL, /* Xo */ + post_fo, /* Fo */ + NULL, /* Fc */ + post_delim_nb, /* Oo */ + NULL, /* Oc */ + post_bk, /* Bk */ + NULL, /* Ek */ + post_eoln, /* Bt */ + post_obsolete, /* Hf */ + post_obsolete, /* Fr */ + post_eoln, /* Ud */ + post_lb, /* Lb */ + post_par, /* Lp */ + post_delim_nb, /* Lk */ + post_defaults, /* Mt */ + post_delim_nb, /* Brq */ + post_delim_nb, /* Bro */ + NULL, /* Brc */ + NULL, /* %C */ + post_es, /* Es */ + post_en, /* En */ + post_xx, /* Dx */ + NULL, /* %Q */ + NULL, /* %U */ + NULL, /* Ta */ }; +static const v_post *const mdoc_valids = __mdoc_valids - MDOC_Dd; #define RSORD_MAX 14 /* Number of `Rs' blocks. */ -static const int rsord[RSORD_MAX] = { +static const enum roff_tok rsord[RSORD_MAX] = { MDOC__A, MDOC__T, MDOC__B, @@ -288,34 +286,10 @@ static const char * const secnames[SEC__MAX] = { void -mdoc_valid_pre(struct roff_man *mdoc, struct roff_node *n) -{ - v_pre p; - - switch (n->type) { - case ROFFT_TEXT: - if (n->sec != SEC_SYNOPSIS || n->parent->tok != MDOC_Fd) - check_text(mdoc, n->line, n->pos, n->string); - return; - case ROFFT_TBL: - case ROFFT_EQN: - case ROFFT_ROOT: - return; - default: - break; - } - - check_args(mdoc, n); - p = mdoc_valids[n->tok].pre; - if (*p) - (*p)(mdoc, n); -} - -void mdoc_node_validate(struct roff_man *mdoc) { struct roff_node *n; - v_post p; + const v_post *p; n = mdoc->last; mdoc->last = mdoc->last->child; @@ -331,6 +305,15 @@ mdoc_node_validate(struct roff_man *mdoc) mdoc->next = ROFF_NEXT_SIBLING; switch (n->type) { case ROFFT_TEXT: + if (n->sec != SEC_SYNOPSIS || + (n->parent->tok != MDOC_Cd && n->parent->tok != MDOC_Fd)) + check_text(mdoc, n->line, n->pos, n->string); + if (n->parent->tok == MDOC_It || + (n->parent->type == ROFFT_BODY && + (n->parent->tok == MDOC_Sh || + n->parent->tok == MDOC_Ss))) + check_toptext(mdoc, n->line, n->pos, n->string); + break; case ROFFT_EQN: case ROFFT_TBL: break; @@ -338,6 +321,7 @@ mdoc_node_validate(struct roff_man *mdoc) post_root(mdoc); break; default: + check_args(mdoc, mdoc->last); /* * Closing delimiters are not special at the @@ -346,13 +330,27 @@ mdoc_node_validate(struct roff_man *mdoc) */ if (n->child != NULL) - n->child->flags &= ~MDOC_DELIMC; + n->child->flags &= ~NODE_DELIMC; if (n->last != NULL) - n->last->flags &= ~MDOC_DELIMO; + n->last->flags &= ~NODE_DELIMO; /* Call the macro's postprocessor. */ - p = mdoc_valids[n->tok].post; + if (n->tok < ROFF_MAX) { + switch(n->tok) { + case ROFF_br: + case ROFF_sp: + post_par(mdoc); + break; + default: + roff_validate(mdoc); + break; + } + break; + } + + assert(n->tok >= MDOC_Dd && n->tok < MDOC_MAX); + p = mdoc_valids + n->tok; if (*p) (*p)(mdoc); if (mdoc->last == n) @@ -397,34 +395,174 @@ check_text(struct roff_man *mdoc, int ln, int pos, cha } static void -pre_display(PRE_ARGS) +check_toptext(struct roff_man *mdoc, int ln, int pos, const char *p) { - struct roff_node *node; + const char *cp, *cpr; - if (n->type != ROFFT_BLOCK) + if (*p == '\0') return; - for (node = mdoc->last->parent; node; node = node->parent) - if (node->type == ROFFT_BLOCK) - if (MDOC_Bd == node->tok) + if ((cp = strstr(p, "OpenBSD")) != NULL) + mandoc_msg(MANDOCERR_BX, mdoc->parse, + ln, pos + (cp - p), "Ox"); + if ((cp = strstr(p, "NetBSD")) != NULL) + mandoc_msg(MANDOCERR_BX, mdoc->parse, + ln, pos + (cp - p), "Nx"); + if ((cp = strstr(p, "FreeBSD")) != NULL) + mandoc_msg(MANDOCERR_BX, mdoc->parse, + ln, pos + (cp - p), "Fx"); + if ((cp = strstr(p, "DragonFly")) != NULL) + mandoc_msg(MANDOCERR_BX, mdoc->parse, + ln, pos + (cp - p), "Dx"); + + cp = p; + while ((cp = strstr(cp + 1, "()")) != NULL) { + for (cpr = cp - 1; cpr >= p; cpr--) + if (*cpr != '_' && !isalnum((unsigned char)*cpr)) break; + if ((cpr < p || *cpr == ' ') && cpr + 1 < cp) { + cpr++; + mandoc_vmsg(MANDOCERR_FUNC, mdoc->parse, + ln, pos + (cpr - p), + "%.*s()", (int)(cp - cpr), cpr); + } + } +} - if (node) - mandoc_vmsg(MANDOCERR_BD_NEST, - mdoc->parse, n->line, n->pos, - "%s in Bd", mdoc_macronames[n->tok]); +static void +post_delim(POST_ARGS) +{ + const struct roff_node *nch; + const char *lc; + enum mdelim delim; + enum roff_tok tok; + + tok = mdoc->last->tok; + nch = mdoc->last->last; + if (nch == NULL || nch->type != ROFFT_TEXT) + return; + lc = strchr(nch->string, '\0') - 1; + if (lc < nch->string) + return; + delim = mdoc_isdelim(lc); + if (delim == DELIM_NONE || delim == DELIM_OPEN) + return; + if (*lc == ')' && (tok == MDOC_Nd || tok == MDOC_Sh || + tok == MDOC_Ss || tok == MDOC_Fo)) + return; + + mandoc_vmsg(MANDOCERR_DELIM, mdoc->parse, + nch->line, nch->pos + (lc - nch->string), + "%s%s %s", roff_name[tok], + nch == mdoc->last->child ? "" : " ...", nch->string); } static void -pre_bl(PRE_ARGS) +post_delim_nb(POST_ARGS) { + const struct roff_node *nch; + const char *lc, *cp; + int nw; + enum mdelim delim; + enum roff_tok tok; + + /* + * Find candidates: at least two bytes, + * the last one a closing or middle delimiter. + */ + + tok = mdoc->last->tok; + nch = mdoc->last->last; + if (nch == NULL || nch->type != ROFFT_TEXT) + return; + lc = strchr(nch->string, '\0') - 1; + if (lc <= nch->string) + return; + delim = mdoc_isdelim(lc); + if (delim == DELIM_NONE || delim == DELIM_OPEN) + return; + + /* + * Reduce false positives by allowing various cases. + */ + + /* Escaped delimiters. */ + if (lc > nch->string + 1 && lc[-2] == '\\' && + (lc[-1] == '&' || lc[-1] == 'e')) + return; + + /* Specific byte sequences. */ + switch (*lc) { + case ')': + for (cp = lc; cp >= nch->string; cp--) + if (*cp == '(') + return; + break; + case '.': + if (lc > nch->string + 1 && lc[-2] == '.' && lc[-1] == '.') + return; + if (lc[-1] == '.') + return; + break; + case ';': + if (tok == MDOC_Vt) + return; + break; + case '?': + if (lc[-1] == '?') + return; + break; + case ']': + for (cp = lc; cp >= nch->string; cp--) + if (*cp == '[') + return; + break; + case '|': + if (lc == nch->string + 1 && lc[-1] == '|') + return; + default: + break; + } + + /* Exactly two non-alphanumeric bytes. */ + if (lc == nch->string + 1 && !isalnum((unsigned char)lc[-1])) + return; + + /* At least three alphabetic words with a sentence ending. */ + if (strchr("!.:?", *lc) != NULL && (tok == MDOC_Em || + tok == MDOC_Li || tok == MDOC_Po || tok == MDOC_Pq || + tok == MDOC_Sy)) { + nw = 0; + for (cp = lc - 1; cp >= nch->string; cp--) { + if (*cp == ' ') { + nw++; + if (cp > nch->string && cp[-1] == ',') + cp--; + } else if (isalpha((unsigned int)*cp)) { + if (nw > 1) + return; + } else + break; + } + } + + mandoc_vmsg(MANDOCERR_DELIM_NB, mdoc->parse, + nch->line, nch->pos + (lc - nch->string), + "%s%s %s", roff_name[tok], + nch == mdoc->last->child ? "" : " ...", nch->string); +} + +static void +post_bl_norm(POST_ARGS) +{ + struct roff_node *n; struct mdoc_argv *argv, *wa; int i; enum mdocargt mdoclt; enum mdoc_list lt; - if (n->type != ROFFT_BLOCK) - return; + n = mdoc->last->parent; + n->norm->Bl.type = LIST__NONE; /* * First figure out which kind of list to use: bind ourselves to @@ -494,7 +632,7 @@ pre_bl(PRE_ARGS) mdoc->parse, argv->line, argv->pos, "Bl -width %s", argv->value[0]); - rewrite_macro2len(argv->value); + rewrite_macro2len(mdoc, argv->value); n->norm->Bl.width = argv->value[0]; break; case MDOC_Offset: @@ -509,7 +647,7 @@ pre_bl(PRE_ARGS) mdoc->parse, argv->line, argv->pos, "Bl -offset %s", argv->value[0]); - rewrite_macro2len(argv->value); + rewrite_macro2len(mdoc, argv->value); n->norm->Bl.offs = argv->value[0]; break; default: @@ -550,6 +688,7 @@ pre_bl(PRE_ARGS) mandoc_msg(MANDOCERR_BL_NOTYPE, mdoc->parse, n->line, n->pos, "Bl"); n->norm->Bl.type = LIST_item; + mdoclt = MDOC_Item; } /* @@ -561,7 +700,7 @@ pre_bl(PRE_ARGS) switch (n->norm->Bl.type) { case LIST_tag: - if (NULL == n->norm->Bl.width) + if (n->norm->Bl.width == NULL) mandoc_msg(MANDOCERR_BL_NOWIDTH, mdoc->parse, n->line, n->pos, "Bl -tag"); break; @@ -570,39 +709,36 @@ pre_bl(PRE_ARGS) case LIST_ohang: case LIST_inset: case LIST_item: - if (n->norm->Bl.width) + if (n->norm->Bl.width != NULL) mandoc_vmsg(MANDOCERR_BL_SKIPW, mdoc->parse, wa->line, wa->pos, "Bl -%s", mdoc_argnames[mdoclt]); + n->norm->Bl.width = NULL; break; case LIST_bullet: case LIST_dash: case LIST_hyphen: - if (NULL == n->norm->Bl.width) + if (n->norm->Bl.width == NULL) n->norm->Bl.width = "2n"; break; case LIST_enum: - if (NULL == n->norm->Bl.width) + if (n->norm->Bl.width == NULL) n->norm->Bl.width = "3n"; break; default: break; } - pre_par(mdoc, n); } static void -pre_bd(PRE_ARGS) +post_bd(POST_ARGS) { + struct roff_node *n; struct mdoc_argv *argv; int i; enum mdoc_disp dt; - pre_display(mdoc, n); - - if (n->type != ROFFT_BLOCK) - return; - + n = mdoc->last; for (i = 0; n->args && i < (int)n->args->argc; i++) { argv = n->args->argv + i; dt = DISP__NONE; @@ -639,7 +775,7 @@ pre_bd(PRE_ARGS) mdoc->parse, argv->line, argv->pos, "Bd -offset %s", argv->value[0]); - rewrite_macro2len(argv->value); + rewrite_macro2len(mdoc, argv->value); n->norm->Bd.offs = argv->value[0]; break; case MDOC_Compact: @@ -668,15 +804,20 @@ pre_bd(PRE_ARGS) n->line, n->pos, "Bd"); n->norm->Bd.type = DISP_ragged; } - pre_par(mdoc, n); } +/* + * Stand-alone line macros. + */ + static void -pre_an(PRE_ARGS) +post_an_norm(POST_ARGS) { + struct roff_node *n; struct mdoc_argv *argv; size_t i; + n = mdoc->last; if (n->args == NULL) return; @@ -697,27 +838,228 @@ pre_an(PRE_ARGS) } static void -pre_std(PRE_ARGS) +post_eoln(POST_ARGS) { + struct roff_node *n; - if (n->args && 1 == n->args->argc) - if (MDOC_Std == n->args->argv[0].arg) + post_useless(mdoc); + n = mdoc->last; + if (n->child != NULL) + mandoc_vmsg(MANDOCERR_ARG_SKIP, mdoc->parse, n->line, + n->pos, "%s %s", roff_name[n->tok], n->child->string); + + while (n->child != NULL) + roff_node_delete(mdoc, n->child); + + roff_word_alloc(mdoc, n->line, n->pos, n->tok == MDOC_Bt ? + "is currently in beta test." : "currently under development."); + mdoc->last->flags |= NODE_EOS | NODE_NOSRC; + mdoc->last = n; +} + +static int +build_list(struct roff_man *mdoc, int tok) +{ + struct roff_node *n; + int ic; + + n = mdoc->last->next; + for (ic = 1;; ic++) { + roff_elem_alloc(mdoc, n->line, n->pos, tok); + mdoc->last->flags |= NODE_NOSRC; + mdoc_node_relink(mdoc, n); + n = mdoc->last = mdoc->last->parent; + mdoc->next = ROFF_NEXT_SIBLING; + if (n->next == NULL) + return ic; + if (ic > 1 || n->next->next != NULL) { + roff_word_alloc(mdoc, n->line, n->pos, ","); + mdoc->last->flags |= NODE_DELIMC | NODE_NOSRC; + } + n = mdoc->last->next; + if (n->next == NULL) { + roff_word_alloc(mdoc, n->line, n->pos, "and"); + mdoc->last->flags |= NODE_NOSRC; + } + } +} + +static void +post_ex(POST_ARGS) +{ + struct roff_node *n; + int ic; + + post_std(mdoc); + + n = mdoc->last; + mdoc->next = ROFF_NEXT_CHILD; + roff_word_alloc(mdoc, n->line, n->pos, "The"); + mdoc->last->flags |= NODE_NOSRC; + + if (mdoc->last->next != NULL) + ic = build_list(mdoc, MDOC_Nm); + else if (mdoc->meta.name != NULL) { + roff_elem_alloc(mdoc, n->line, n->pos, MDOC_Nm); + mdoc->last->flags |= NODE_NOSRC; + roff_word_alloc(mdoc, n->line, n->pos, mdoc->meta.name); + mdoc->last->flags |= NODE_NOSRC; + mdoc->last = mdoc->last->parent; + mdoc->next = ROFF_NEXT_SIBLING; + ic = 1; + } else { + mandoc_msg(MANDOCERR_EX_NONAME, mdoc->parse, + n->line, n->pos, "Ex"); + ic = 0; + } + + roff_word_alloc(mdoc, n->line, n->pos, + ic > 1 ? "utilities exit\\~0" : "utility exits\\~0"); + mdoc->last->flags |= NODE_NOSRC; + roff_word_alloc(mdoc, n->line, n->pos, + "on success, and\\~>0 if an error occurs."); + mdoc->last->flags |= NODE_EOS | NODE_NOSRC; + mdoc->last = n; +} + +static void +post_lb(POST_ARGS) +{ + struct roff_node *n; + const char *p; + + post_delim_nb(mdoc); + + n = mdoc->last; + assert(n->child->type == ROFFT_TEXT); + mdoc->next = ROFF_NEXT_CHILD; + + if ((p = mdoc_a2lib(n->child->string)) != NULL) { + n->child->flags |= NODE_NOPRT; + roff_word_alloc(mdoc, n->line, n->pos, p); + mdoc->last->flags = NODE_NOSRC; + mdoc->last = n; + return; + } + + mandoc_vmsg(MANDOCERR_LB_BAD, mdoc->parse, n->child->line, + n->child->pos, "Lb %s", n->child->string); + + roff_word_alloc(mdoc, n->line, n->pos, "library"); + mdoc->last->flags = NODE_NOSRC; + roff_word_alloc(mdoc, n->line, n->pos, "\\(Lq"); + mdoc->last->flags = NODE_DELIMO | NODE_NOSRC; + mdoc->last = mdoc->last->next; + roff_word_alloc(mdoc, n->line, n->pos, "\\(Rq"); + mdoc->last->flags = NODE_DELIMC | NODE_NOSRC; + mdoc->last = n; +} + +static void +post_rv(POST_ARGS) +{ + struct roff_node *n; + int ic; + + post_std(mdoc); + + n = mdoc->last; + mdoc->next = ROFF_NEXT_CHILD; + if (n->child != NULL) { + roff_word_alloc(mdoc, n->line, n->pos, "The"); + mdoc->last->flags |= NODE_NOSRC; + ic = build_list(mdoc, MDOC_Fn); + roff_word_alloc(mdoc, n->line, n->pos, + ic > 1 ? "functions return" : "function returns"); + mdoc->last->flags |= NODE_NOSRC; + roff_word_alloc(mdoc, n->line, n->pos, + "the value\\~0 if successful;"); + } else + roff_word_alloc(mdoc, n->line, n->pos, "Upon successful " + "completion, the value\\~0 is returned;"); + mdoc->last->flags |= NODE_NOSRC; + + roff_word_alloc(mdoc, n->line, n->pos, "otherwise " + "the value\\~\\-1 is returned and the global variable"); + mdoc->last->flags |= NODE_NOSRC; + roff_elem_alloc(mdoc, n->line, n->pos, MDOC_Va); + mdoc->last->flags |= NODE_NOSRC; + roff_word_alloc(mdoc, n->line, n->pos, "errno"); + mdoc->last->flags |= NODE_NOSRC; + mdoc->last = mdoc->last->parent; + mdoc->next = ROFF_NEXT_SIBLING; + roff_word_alloc(mdoc, n->line, n->pos, + "is set to indicate the error."); + mdoc->last->flags |= NODE_EOS | NODE_NOSRC; + mdoc->last = n; +} + +static void +post_std(POST_ARGS) +{ + struct roff_node *n; + + post_delim(mdoc); + + n = mdoc->last; + if (n->args && n->args->argc == 1) + if (n->args->argv[0].arg == MDOC_Std) return; mandoc_msg(MANDOCERR_ARG_STD, mdoc->parse, - n->line, n->pos, mdoc_macronames[n->tok]); + n->line, n->pos, roff_name[n->tok]); } static void -pre_obsolete(PRE_ARGS) +post_st(POST_ARGS) { + struct roff_node *n, *nch; + const char *p; + n = mdoc->last; + nch = n->child; + assert(nch->type == ROFFT_TEXT); + + if ((p = mdoc_a2st(nch->string)) == NULL) { + mandoc_vmsg(MANDOCERR_ST_BAD, mdoc->parse, + nch->line, nch->pos, "St %s", nch->string); + roff_node_delete(mdoc, n); + return; + } + + nch->flags |= NODE_NOPRT; + mdoc->next = ROFF_NEXT_CHILD; + roff_word_alloc(mdoc, nch->line, nch->pos, p); + mdoc->last->flags |= NODE_NOSRC; + mdoc->last= n; +} + +static void +post_obsolete(POST_ARGS) +{ + struct roff_node *n; + + n = mdoc->last; if (n->type == ROFFT_ELEM || n->type == ROFFT_BLOCK) mandoc_msg(MANDOCERR_MACRO_OBS, mdoc->parse, - n->line, n->pos, mdoc_macronames[n->tok]); + n->line, n->pos, roff_name[n->tok]); } static void +post_useless(POST_ARGS) +{ + struct roff_node *n; + + n = mdoc->last; + mandoc_msg(MANDOCERR_MACRO_USELESS, mdoc->parse, + n->line, n->pos, roff_name[n->tok]); +} + +/* + * Block macros. + */ + +static void post_bf(POST_ARGS) { struct roff_node *np, *nch; @@ -783,39 +1125,6 @@ post_bf(POST_ARGS) } static void -post_lb(POST_ARGS) -{ - struct roff_node *n; - const char *stdlibname; - char *libname; - - n = mdoc->last->child; - assert(n->type == ROFFT_TEXT); - - if (NULL == (stdlibname = mdoc_a2lib(n->string))) - mandoc_asprintf(&libname, - "library \\(Lq%s\\(Rq", n->string); - else - libname = mandoc_strdup(stdlibname); - - free(n->string); - n->string = libname; -} - -static void -post_eoln(POST_ARGS) -{ - const struct roff_node *n; - - n = mdoc->last; - if (n->child != NULL) - mandoc_vmsg(MANDOCERR_ARG_SKIP, - mdoc->parse, n->line, n->pos, - "%s %s", mdoc_macronames[n->tok], - n->child->string); -} - -static void post_fname(POST_ARGS) { const struct roff_node *n; @@ -828,6 +1137,8 @@ post_fname(POST_ARGS) if ( ! (cp[0] == '\0' || (cp[0] == '(' && cp[1] == '*'))) mandoc_msg(MANDOCERR_FN_PAREN, mdoc->parse, n->line, n->pos + pos, n->string); + if (n->sec == SEC_SYNOPSIS && mdoc->meta.msec != NULL) + mandoc_xr_add(mdoc->meta.msec, n->string, -1, -1); } static void @@ -859,7 +1170,8 @@ post_fo(POST_ARGS) "Fo ... %s", n->child->next->string); while (n->child != n->last) roff_node_delete(mdoc, n->last); - } + } else + post_delim(mdoc); post_fname(mdoc); } @@ -883,6 +1195,7 @@ post_fa(POST_ARGS) break; } } + post_delim_nb(mdoc); } static void @@ -892,40 +1205,47 @@ post_nm(POST_ARGS) n = mdoc->last; + if ((n->sec == SEC_NAME || n->sec == SEC_SYNOPSIS) && + n->child != NULL && n->child->type == ROFFT_TEXT && + mdoc->meta.msec != NULL) + mandoc_xr_add(mdoc->meta.msec, n->child->string, -1, -1); + if (n->last != NULL && (n->last->tok == MDOC_Pp || n->last->tok == MDOC_Lp)) mdoc_node_relink(mdoc, n->last); - if (mdoc->meta.name != NULL) - return; - - deroff(&mdoc->meta.name, n); - if (mdoc->meta.name == NULL) + deroff(&mdoc->meta.name, n); + + if (mdoc->meta.name == NULL || + (mdoc->lastsec == SEC_NAME && n->child == NULL)) mandoc_msg(MANDOCERR_NM_NONAME, mdoc->parse, n->line, n->pos, "Nm"); -} -static void -post_nd(POST_ARGS) -{ - struct roff_node *n; + switch (n->type) { + case ROFFT_ELEM: + post_delim_nb(mdoc); + break; + case ROFFT_HEAD: + post_delim(mdoc); + break; + default: + return; + } - n = mdoc->last; - - if (n->type != ROFFT_BODY) + if ((n->child != NULL && n->child->type == ROFFT_TEXT) || + mdoc->meta.name == NULL) return; - if (n->child == NULL) - mandoc_msg(MANDOCERR_ND_EMPTY, mdoc->parse, - n->line, n->pos, "Nd"); - - post_hyph(mdoc); + mdoc->next = ROFF_NEXT_CHILD; + roff_word_alloc(mdoc, n->line, n->pos, mdoc->meta.name); + mdoc->last->flags |= NODE_NOSRC; + mdoc->last = n; } static void -post_d1(POST_ARGS) +post_nd(POST_ARGS) { struct roff_node *n; @@ -934,26 +1254,64 @@ post_d1(POST_ARGS) if (n->type != ROFFT_BODY) return; + if (n->sec != SEC_NAME) + mandoc_msg(MANDOCERR_ND_LATE, mdoc->parse, + n->line, n->pos, "Nd"); + if (n->child == NULL) - mandoc_msg(MANDOCERR_BLK_EMPTY, mdoc->parse, - n->line, n->pos, "D1"); + mandoc_msg(MANDOCERR_ND_EMPTY, mdoc->parse, + n->line, n->pos, "Nd"); + else + post_delim(mdoc); post_hyph(mdoc); } static void -post_literal(POST_ARGS) +post_display(POST_ARGS) { - struct roff_node *n; + struct roff_node *n, *np; n = mdoc->last; - - if (n->type != ROFFT_BODY || n->end != ENDBODY_NOT) - return; - - if (n->child == NULL) - mandoc_msg(MANDOCERR_BLK_EMPTY, mdoc->parse, - n->line, n->pos, mdoc_macronames[n->tok]); + switch (n->type) { + case ROFFT_BODY: + if (n->end != ENDBODY_NOT) { + if (n->tok == MDOC_Bd && + n->body->parent->args == NULL) + roff_node_delete(mdoc, n); + } else if (n->child == NULL) + mandoc_msg(MANDOCERR_BLK_EMPTY, mdoc->parse, + n->line, n->pos, roff_name[n->tok]); + else if (n->tok == MDOC_D1) + post_hyph(mdoc); + break; + case ROFFT_BLOCK: + if (n->tok == MDOC_Bd) { + if (n->args == NULL) { + mandoc_msg(MANDOCERR_BD_NOARG, + mdoc->parse, n->line, n->pos, "Bd"); + mdoc->next = ROFF_NEXT_SIBLING; + while (n->body->child != NULL) + mdoc_node_relink(mdoc, + n->body->child); + roff_node_delete(mdoc, n); + break; + } + post_bd(mdoc); + post_prevpar(mdoc); + } + for (np = n->parent; np != NULL; np = np->parent) { + if (np->type == ROFFT_BLOCK && np->tok == MDOC_Bd) { + mandoc_vmsg(MANDOCERR_BD_NEST, + mdoc->parse, n->line, n->pos, + "%s in Bd", roff_name[n->tok]); + break; + } + } + break; + default: + break; + } } static void @@ -961,27 +1319,31 @@ post_defaults(POST_ARGS) { struct roff_node *nn; + if (mdoc->last->child != NULL) { + post_delim_nb(mdoc); + return; + } + /* * The `Ar' defaults to "file ..." if no value is provided as an * argument; the `Mt' and `Pa' macros use "~"; the `Li' just * gets an empty string. */ - if (mdoc->last->child != NULL) - return; - nn = mdoc->last; - switch (nn->tok) { case MDOC_Ar: mdoc->next = ROFF_NEXT_CHILD; roff_word_alloc(mdoc, nn->line, nn->pos, "file"); + mdoc->last->flags |= NODE_NOSRC; roff_word_alloc(mdoc, nn->line, nn->pos, "..."); + mdoc->last->flags |= NODE_NOSRC; break; case MDOC_Pa: case MDOC_Mt: mdoc->next = ROFF_NEXT_CHILD; roff_word_alloc(mdoc, nn->line, nn->pos, "~"); + mdoc->last->flags |= NODE_NOSRC; break; default: abort(); @@ -992,17 +1354,11 @@ post_defaults(POST_ARGS) static void post_at(POST_ARGS) { - struct roff_node *n; - const char *std_att; - char *att; + struct roff_node *n, *nch; + const char *att; n = mdoc->last; - if (n->child == NULL) { - mdoc->next = ROFF_NEXT_CHILD; - roff_word_alloc(mdoc, n->line, n->pos, "AT&T UNIX"); - mdoc->last = n; - return; - } + nch = n->child; /* * If we have a child, look it up in the standard keys. If a @@ -1010,17 +1366,19 @@ post_at(POST_ARGS) * prefix "AT&T UNIX " to the existing data. */ - n = n->child; - assert(n->type == ROFFT_TEXT); - if ((std_att = mdoc_a2att(n->string)) == NULL) { + att = NULL; + if (nch != NULL && ((att = mdoc_a2att(nch->string)) == NULL)) mandoc_vmsg(MANDOCERR_AT_BAD, mdoc->parse, - n->line, n->pos, "At %s", n->string); - mandoc_asprintf(&att, "AT&T UNIX %s", n->string); - } else - att = mandoc_strdup(std_att); + nch->line, nch->pos, "At %s", nch->string); - free(n->string); - n->string = att; + mdoc->next = ROFF_NEXT_CHILD; + if (att != NULL) { + roff_word_alloc(mdoc, nch->line, nch->pos, att); + nch->flags |= NODE_NOPRT; + } else + roff_word_alloc(mdoc, n->line, n->pos, "AT&T UNIX"); + mdoc->last->flags |= NODE_NOSRC; + mdoc->last = n; } static void @@ -1028,12 +1386,16 @@ post_an(POST_ARGS) { struct roff_node *np, *nch; + post_an_norm(mdoc); + np = mdoc->last; nch = np->child; if (np->norm->An.auth == AUTH__NONE) { if (nch == NULL) mandoc_msg(MANDOCERR_MACRO_EMPTY, mdoc->parse, np->line, np->pos, "An"); + else + post_delim_nb(mdoc); } else if (nch != NULL) mandoc_vmsg(MANDOCERR_ARG_EXCESS, mdoc->parse, nch->line, nch->pos, "An ... %s", nch->string); @@ -1043,6 +1405,7 @@ static void post_en(POST_ARGS) { + post_obsolete(mdoc); if (mdoc->last->type == ROFFT_BLOCK) mdoc->last->norm->Es = mdoc->last_es; } @@ -1051,16 +1414,71 @@ static void post_es(POST_ARGS) { + post_obsolete(mdoc); mdoc->last_es = mdoc->last; } static void +post_xx(POST_ARGS) +{ + struct roff_node *n; + const char *os; + char *v; + + post_delim_nb(mdoc); + + n = mdoc->last; + switch (n->tok) { + case MDOC_Bsx: + os = "BSD/OS"; + break; + case MDOC_Dx: + os = "DragonFly"; + break; + case MDOC_Fx: + os = "FreeBSD"; + break; + case MDOC_Nx: + os = "NetBSD"; + if (n->child == NULL) + break; + v = n->child->string; + if ((v[0] != '0' && v[0] != '1') || v[1] != '.' || + v[2] < '0' || v[2] > '9' || + v[3] < 'a' || v[3] > 'z' || v[4] != '\0') + break; + n->child->flags |= NODE_NOPRT; + mdoc->next = ROFF_NEXT_CHILD; + roff_word_alloc(mdoc, n->child->line, n->child->pos, v); + v = mdoc->last->string; + v[3] = toupper((unsigned char)v[3]); + mdoc->last->flags |= NODE_NOSRC; + mdoc->last = n; + break; + case MDOC_Ox: + os = "OpenBSD"; + break; + case MDOC_Ux: + os = "UNIX"; + break; + default: + abort(); + } + mdoc->next = ROFF_NEXT_CHILD; + roff_word_alloc(mdoc, n->line, n->pos, os); + mdoc->last->flags |= NODE_NOSRC; + mdoc->last = n; +} + +static void post_it(POST_ARGS) { struct roff_node *nbl, *nit, *nch; int i, cols; enum mdoc_list lt; + post_prevpar(mdoc); + nit = mdoc->last; if (nit->type != ROFFT_BLOCK) return; @@ -1091,25 +1509,41 @@ post_it(POST_ARGS) mdoc_argnames[nbl->args->argv[0].arg]); /* FALLTHROUGH */ case LIST_item: - if (nit->head->child != NULL) - mandoc_vmsg(MANDOCERR_ARG_SKIP, - mdoc->parse, nit->line, nit->pos, - "It %s", nit->head->child->string); + if ((nch = nit->head->child) != NULL) + mandoc_vmsg(MANDOCERR_ARG_SKIP, mdoc->parse, + nit->line, nit->pos, "It %s", + nch->string == NULL ? roff_name[nch->tok] : + nch->string); break; case LIST_column: cols = (int)nbl->norm->Bl.ncols; assert(nit->head->child == NULL); - i = 0; - for (nch = nit->child; nch != NULL; nch = nch->next) - if (nch->type == ROFFT_BODY) - i++; + if (nit->head->next->child == NULL && + nit->head->next->next == NULL) { + mandoc_msg(MANDOCERR_MACRO_EMPTY, mdoc->parse, + nit->line, nit->pos, "It"); + roff_node_delete(mdoc, nit); + break; + } + i = 0; + for (nch = nit->child; nch != NULL; nch = nch->next) { + if (nch->type != ROFFT_BODY) + continue; + if (i++ && nch->flags & NODE_LINE) + mandoc_msg(MANDOCERR_TA_LINE, mdoc->parse, + nch->line, nch->pos, "Ta"); + } if (i < cols || i > cols + 1) mandoc_vmsg(MANDOCERR_BL_COL, mdoc->parse, nit->line, nit->pos, "%d columns, %d cells", cols, i); + else if (nit->head->next->child != NULL && + nit->head->next->child->line > nit->line) + mandoc_msg(MANDOCERR_IT_NOARG, mdoc->parse, + nit->line, nit->pos, "Bl -column It"); break; default: abort(); @@ -1121,22 +1555,9 @@ post_bl_block(POST_ARGS) { struct roff_node *n, *ni, *nc; - /* - * These are fairly complicated, so we've broken them into two - * functions. post_bl_block_tag() is called when a -tag is - * specified, but no -width (it must be guessed). The second - * when a -width is specified (macro indicators must be - * rewritten into real lengths). - */ + post_prevpar(mdoc); n = mdoc->last; - - if (n->norm->Bl.type == LIST_tag && - n->norm->Bl.width == NULL) { - post_bl_block_tag(mdoc); - assert(n->norm->Bl.width != NULL); - } - for (ni = n->body->child; ni != NULL; ni = ni->next) { if (ni->body == NULL) continue; @@ -1145,7 +1566,7 @@ post_bl_block(POST_ARGS) switch (nc->tok) { case MDOC_Pp: case MDOC_Lp: - case MDOC_br: + case ROFF_br: break; default: nc = NULL; @@ -1154,14 +1575,13 @@ post_bl_block(POST_ARGS) if (ni->next == NULL) { mandoc_msg(MANDOCERR_PAR_MOVE, mdoc->parse, nc->line, nc->pos, - mdoc_macronames[nc->tok]); + roff_name[nc->tok]); mdoc_node_relink(mdoc, nc); } else if (n->norm->Bl.comp == 0 && n->norm->Bl.type != LIST_column) { mandoc_vmsg(MANDOCERR_PAR_SKIP, mdoc->parse, nc->line, nc->pos, - "%s before It", - mdoc_macronames[nc->tok]); + "%s before It", roff_name[nc->tok]); roff_node_delete(mdoc, nc); } else break; @@ -1174,17 +1594,17 @@ post_bl_block(POST_ARGS) * If the argument of -offset or -width is a macro, * replace it with the associated default width. */ -void -rewrite_macro2len(char **arg) +static void +rewrite_macro2len(struct roff_man *mdoc, char **arg) { size_t width; - int tok; + enum roff_tok tok; if (*arg == NULL) return; else if ( ! strcmp(*arg, "Ds")) width = 6; - else if ((tok = mdoc_hash_find(*arg)) == TOKEN_NONE) + else if ((tok = roffhash_find(mdoc->mdocmac, *arg, 0)) == TOKEN_NONE) return; else width = macro2len(tok); @@ -1194,79 +1614,15 @@ rewrite_macro2len(char **arg) } static void -post_bl_block_tag(POST_ARGS) -{ - struct roff_node *n, *nn; - size_t sz, ssz; - int i; - char buf[24]; - - /* - * Calculate the -width for a `Bl -tag' list if it hasn't been - * provided. Uses the first head macro. NOTE AGAIN: this is - * ONLY if the -width argument has NOT been provided. See - * rewrite_macro2len() for converting the -width string. - */ - - sz = 10; - n = mdoc->last; - - for (nn = n->body->child; nn != NULL; nn = nn->next) { - if (nn->tok != MDOC_It) - continue; - - assert(nn->type == ROFFT_BLOCK); - nn = nn->head->child; - - if (nn == NULL) - break; - - if (nn->type == ROFFT_TEXT) { - sz = strlen(nn->string) + 1; - break; - } - - if (0 != (ssz = macro2len(nn->tok))) - sz = ssz; - - break; - } - - /* Defaults to ten ens. */ - - (void)snprintf(buf, sizeof(buf), "%un", (unsigned int)sz); - - /* - * We have to dynamically add this to the macro's argument list. - * We're guaranteed that a MDOC_Width doesn't already exist. - */ - - assert(n->args != NULL); - i = (int)(n->args->argc)++; - - n->args->argv = mandoc_reallocarray(n->args->argv, - n->args->argc, sizeof(struct mdoc_argv)); - - n->args->argv[i].arg = MDOC_Width; - n->args->argv[i].line = n->line; - n->args->argv[i].pos = n->pos; - n->args->argv[i].sz = 1; - n->args->argv[i].value = mandoc_malloc(sizeof(char *)); - n->args->argv[i].value[0] = mandoc_strdup(buf); - - /* Set our width! */ - n->norm->Bl.width = n->args->argv[i].value[0]; -} - -static void post_bl_head(POST_ARGS) { struct roff_node *nbl, *nh, *nch, *nnext; struct mdoc_argv *argv; int i, j; - nh = mdoc->last; + post_bl_norm(mdoc); + nh = mdoc->last; if (nh->norm->Bl.type != LIST_column) { if ((nch = nh->child) == NULL) return; @@ -1303,7 +1659,8 @@ post_bl_head(POST_ARGS) argv = nbl->args->argv + j; i = argv->sz; - argv->sz += nh->nchild; + for (nch = nh->child; nch != NULL; nch = nch->next) + argv->sz++; argv->value = mandoc_reallocarray(argv->value, argv->sz, sizeof(char *)); @@ -1316,7 +1673,6 @@ post_bl_head(POST_ARGS) nnext = nch->next; roff_node_delete(NULL, nch); } - nh->nchild = 0; nh->child = NULL; } @@ -1326,6 +1682,8 @@ post_bl(POST_ARGS) struct roff_node *nparent, *nprev; /* of the Bl block */ struct roff_node *nblock, *nbody; /* of the Bl */ struct roff_node *nchild, *nnext; /* of the Bl body */ + const char *prev_Er; + int order; nbody = mdoc->last; switch (nbody->type) { @@ -1350,17 +1708,46 @@ post_bl(POST_ARGS) return; } while (nchild != NULL) { + nnext = nchild->next; if (nchild->tok == MDOC_It || (nchild->tok == MDOC_Sm && - nchild->next != NULL && - nchild->next->tok == MDOC_It)) { - nchild = nchild->next; + nnext != NULL && nnext->tok == MDOC_It)) { + nchild = nnext; continue; } + /* + * In .Bl -column, the first rows may be implicit, + * that is, they may not start with .It macros. + * Such rows may be followed by nodes generated on the + * roff level, for example .TS, which cannot be moved + * out of the list. In that case, wrap such roff nodes + * into an implicit row. + */ + + if (nchild->prev != NULL) { + mdoc->last = nchild; + mdoc->next = ROFF_NEXT_SIBLING; + roff_block_alloc(mdoc, nchild->line, + nchild->pos, MDOC_It); + roff_head_alloc(mdoc, nchild->line, + nchild->pos, MDOC_It); + mdoc->next = ROFF_NEXT_SIBLING; + roff_body_alloc(mdoc, nchild->line, + nchild->pos, MDOC_It); + while (nchild->tok != MDOC_It) { + mdoc_node_relink(mdoc, nchild); + if ((nchild = nnext) == NULL) + break; + nnext = nchild->next; + mdoc->next = ROFF_NEXT_SIBLING; + } + mdoc->last = nbody; + continue; + } + mandoc_msg(MANDOCERR_BL_MOVE, mdoc->parse, - nchild->line, nchild->pos, - mdoc_macronames[nchild->tok]); + nchild->line, nchild->pos, roff_name[nchild->tok]); /* * Move the node out of the Bl block. @@ -1370,21 +1757,16 @@ post_bl(POST_ARGS) nblock = nbody->parent; nprev = nblock->prev; nparent = nblock->parent; - nnext = nchild->next; /* * Unlink this child. */ - assert(nchild->prev == NULL); - if (--nbody->nchild == 0) { - nbody->child = NULL; + nbody->child = nnext; + if (nnext == NULL) nbody->last = NULL; - assert(nnext == NULL); - } else { - nbody->child = nnext; + else nnext->prev = NULL; - } /* * Relink this child. @@ -1395,7 +1777,6 @@ post_bl(POST_ARGS) nchild->next = nblock; nblock->prev = nchild; - nparent->nchild++; if (nprev == NULL) nparent->child = nchild; else @@ -1403,6 +1784,35 @@ post_bl(POST_ARGS) nchild = nnext; } + + if (mdoc->meta.os_e != MANDOC_OS_NETBSD) + return; + + prev_Er = NULL; + for (nchild = nbody->child; nchild != NULL; nchild = nchild->next) { + if (nchild->tok != MDOC_It) + continue; + if ((nnext = nchild->head->child) == NULL) + continue; + if (nnext->type == ROFFT_BLOCK) + nnext = nnext->body->child; + if (nnext == NULL || nnext->tok != MDOC_Er) + continue; + nnext = nnext->child; + if (prev_Er != NULL) { + order = strcmp(prev_Er, nnext->string); + if (order > 0) + mandoc_vmsg(MANDOCERR_ER_ORDER, + mdoc->parse, nnext->line, nnext->pos, + "Er %s %s (NetBSD)", + prev_Er, nnext->string); + else if (order == 0) + mandoc_vmsg(MANDOCERR_ER_REP, + mdoc->parse, nnext->line, nnext->pos, + "Er %s (NetBSD)", prev_Er); + } + prev_Er = nnext->string; + } } static void @@ -1444,7 +1854,7 @@ post_sm(POST_ARGS) mandoc_vmsg(MANDOCERR_SM_BAD, mdoc->parse, nch->line, nch->pos, - "%s %s", mdoc_macronames[mdoc->last->tok], nch->string); + "%s %s", roff_name[mdoc->last->tok], nch->string); mdoc_node_relink(mdoc, nch); return; } @@ -1452,14 +1862,35 @@ post_sm(POST_ARGS) static void post_root(POST_ARGS) { + const char *openbsd_arch[] = { + "alpha", "amd64", "arm64", "armv7", "hppa", "i386", + "landisk", "loongson", "luna88k", "macppc", "mips64", + "octeon", "sgi", "socppc", "sparc64", NULL + }; + const char *netbsd_arch[] = { + "acorn26", "acorn32", "algor", "alpha", "amiga", + "arc", "atari", + "bebox", "cats", "cesfic", "cobalt", "dreamcast", + "emips", "evbarm", "evbmips", "evbppc", "evbsh3", "evbsh5", + "hp300", "hpcarm", "hpcmips", "hpcsh", "hppa", + "i386", "ibmnws", "luna68k", + "mac68k", "macppc", "mipsco", "mmeye", "mvme68k", "mvmeppc", + "netwinder", "news68k", "newsmips", "next68k", + "pc532", "playstation2", "pmax", "pmppc", "prep", + "sandpoint", "sbmips", "sgimips", "shark", + "sparc", "sparc64", "sun2", "sun3", + "vax", "walnut", "x68k", "x86", "x86_64", "xen", NULL + }; + const char **arches[] = { NULL, netbsd_arch, openbsd_arch }; + struct roff_node *n; + const char **arch; /* Add missing prologue data. */ if (mdoc->meta.date == NULL) - mdoc->meta.date = mdoc->quick ? - mandoc_strdup("") : - mandoc_normdate(mdoc->parse, NULL, 0, 0); + mdoc->meta.date = mdoc->quick ? mandoc_strdup("") : + mandoc_normdate(mdoc, NULL, 0, 0); if (mdoc->meta.title == NULL) { mandoc_msg(MANDOCERR_DT_NOTITLE, @@ -1474,6 +1905,27 @@ post_root(POST_ARGS) mandoc_msg(MANDOCERR_OS_MISSING, mdoc->parse, 0, 0, NULL); mdoc->meta.os = mandoc_strdup(""); + } else if (mdoc->meta.os_e && + (mdoc->meta.rcsids & (1 << mdoc->meta.os_e)) == 0) + mandoc_msg(MANDOCERR_RCS_MISSING, mdoc->parse, 0, 0, + mdoc->meta.os_e == MANDOC_OS_OPENBSD ? + "(OpenBSD)" : "(NetBSD)"); + + if (mdoc->meta.arch != NULL && + (arch = arches[mdoc->meta.os_e]) != NULL) { + while (*arch != NULL && strcmp(*arch, mdoc->meta.arch)) + arch++; + if (*arch == NULL) { + n = mdoc->first->child; + while (n->tok != MDOC_Dt) + n = n->next; + n = n->child->next->next; + mandoc_vmsg(MANDOCERR_ARCH_BAD, + mdoc->parse, n->line, n->pos, + "Dt ... %s %s", mdoc->meta.arch, + mdoc->meta.os_e == MANDOC_OS_OPENBSD ? + "(OpenBSD)" : "(NetBSD)"); + } } /* Check that we begin with a proper `Sh'. */ @@ -1487,31 +1939,10 @@ post_root(POST_ARGS) mandoc_msg(MANDOCERR_DOC_EMPTY, mdoc->parse, 0, 0, NULL); else if (n->tok != MDOC_Sh) mandoc_msg(MANDOCERR_SEC_BEFORE, mdoc->parse, - n->line, n->pos, mdoc_macronames[n->tok]); + n->line, n->pos, roff_name[n->tok]); } static void -post_st(POST_ARGS) -{ - struct roff_node *n, *nch; - const char *p; - - n = mdoc->last; - nch = n->child; - - assert(nch->type == ROFFT_TEXT); - - if ((p = mdoc_a2st(nch->string)) == NULL) { - mandoc_vmsg(MANDOCERR_ST_BAD, mdoc->parse, - nch->line, nch->pos, "St %s", nch->string); - roff_node_delete(mdoc, n); - } else { - free(nch->string); - nch->string = mandoc_strdup(p); - } -} - -static void post_rs(POST_ARGS) { struct roff_node *np, *nch, *next, *prev; @@ -1542,9 +1973,8 @@ post_rs(POST_ARGS) break; if (i == RSORD_MAX) { - mandoc_msg(MANDOCERR_RS_BAD, - mdoc->parse, nch->line, nch->pos, - mdoc_macronames[nch->tok]); + mandoc_msg(MANDOCERR_RS_BAD, mdoc->parse, + nch->line, nch->pos, roff_name[nch->tok]); i = -1; } else if (nch->tok == MDOC__J || nch->tok == MDOC__B) np->norm->Rs.quote_T++; @@ -1628,13 +2058,23 @@ post_hyph(POST_ARGS) static void post_ns(POST_ARGS) { + struct roff_node *n; - if (mdoc->last->flags & MDOC_LINE) + n = mdoc->last; + if (n->flags & NODE_LINE || + (n->next != NULL && n->next->flags & NODE_DELIMC)) mandoc_msg(MANDOCERR_NS_SKIP, mdoc->parse, - mdoc->last->line, mdoc->last->pos, NULL); + n->line, n->pos, NULL); } static void +post_sx(POST_ARGS) +{ + post_delim(mdoc); + post_hyph(mdoc); +} + +static void post_sh(POST_ARGS) { @@ -1675,8 +2115,12 @@ post_sh_name(POST_ARGS) for (n = mdoc->last->child; n != NULL; n = n->next) { switch (n->tok) { case MDOC_Nm: + if (hasnm && n->child != NULL) + mandoc_vmsg(MANDOCERR_NAMESEC_PUNCT, + mdoc->parse, n->line, n->pos, + "Nm %s", n->child->string); hasnm = 1; - break; + continue; case MDOC_Nd: hasnd = 1; if (n->next != NULL) @@ -1684,14 +2128,19 @@ post_sh_name(POST_ARGS) mdoc->parse, n->line, n->pos, NULL); break; case TOKEN_NONE: - if (hasnm) - break; + if (n->type == ROFFT_TEXT && + n->string[0] == ',' && n->string[1] == '\0' && + n->next != NULL && n->next->tok == MDOC_Nm) { + n = n->next; + continue; + } /* FALLTHROUGH */ default: mandoc_msg(MANDOCERR_NAMESEC_BAD, mdoc->parse, - n->line, n->pos, mdoc_macronames[n->tok]); - break; + n->line, n->pos, roff_name[n->tok]); + continue; } + break; } if ( ! hasnm) @@ -1713,7 +2162,9 @@ post_sh_see_also(POST_ARGS) n = mdoc->last->child; lastname = lastsec = lastpunct = NULL; while (n != NULL) { - if (n->tok != MDOC_Xr || n->nchild < 2) + if (n->tok != MDOC_Xr || + n->child == NULL || + n->child->next == NULL) break; /* Process one .Xr node. */ @@ -1756,7 +2207,7 @@ post_sh_see_also(POST_ARGS) if (isalpha((const unsigned char)*name)) return; lastpunct = n->string; - if (n->next == NULL) + if (n->next == NULL || n->next->tok == MDOC_Rs) mandoc_vmsg(MANDOCERR_XR_PUNCT, mdoc->parse, n->line, n->pos, "%s after %s(%s)", lastpunct, lastname, lastsec); @@ -1769,7 +2220,7 @@ child_an(const struct roff_node *n) { for (n = n->child; n != NULL; n = n->next) - if ((n->tok == MDOC_An && n->nchild) || child_an(n)) + if ((n->tok == MDOC_An && n->child != NULL) || child_an(n)) return 1; return 0; } @@ -1783,11 +2234,55 @@ post_sh_authors(POST_ARGS) mdoc->last->line, mdoc->last->pos, NULL); } +/* + * Return an upper bound for the string distance (allowing + * transpositions). Not a full Levenshtein implementation + * because Levenshtein is quadratic in the string length + * and this function is called for every standard name, + * so the check for each custom name would be cubic. + * The following crude heuristics is linear, resulting + * in quadratic behaviour for checking one custom name, + * which does not cause measurable slowdown. + */ +static int +similar(const char *s1, const char *s2) +{ + const int maxdist = 3; + int dist = 0; + + while (s1[0] != '\0' && s2[0] != '\0') { + if (s1[0] == s2[0]) { + s1++; + s2++; + continue; + } + if (++dist > maxdist) + return INT_MAX; + if (s1[1] == s2[1]) { /* replacement */ + s1++; + s2++; + } else if (s1[0] == s2[1] && s1[1] == s2[0]) { + s1 += 2; /* transposition */ + s2 += 2; + } else if (s1[0] == s2[1]) /* insertion */ + s2++; + else if (s1[1] == s2[0]) /* deletion */ + s1++; + else + return INT_MAX; + } + dist += strlen(s1) + strlen(s2); + return dist > maxdist ? INT_MAX : dist; +} + static void post_sh_head(POST_ARGS) { - const char *goodsec; - enum roff_sec sec; + struct roff_node *nch; + const char *goodsec; + const char *const *testsec; + int dist, mindist; + enum roff_sec sec; /* * Process a new section. Sections are either "named" or @@ -1800,10 +2295,13 @@ post_sh_head(POST_ARGS) /* The NAME should be first. */ - if (SEC_NAME != sec && SEC_NONE == mdoc->lastnamed) + if (sec != SEC_NAME && mdoc->lastnamed == SEC_NONE) mandoc_vmsg(MANDOCERR_NAMESEC_FIRST, mdoc->parse, - mdoc->last->line, mdoc->last->pos, - "Sh %s", secnames[sec]); + mdoc->last->line, mdoc->last->pos, "Sh %s", + sec != SEC_CUSTOM ? secnames[sec] : + (nch = mdoc->last->child) == NULL ? "" : + nch->type == ROFFT_TEXT ? nch->string : + roff_name[nch->tok]); /* The SYNOPSIS gets special attention in other areas. */ @@ -1821,8 +2319,25 @@ post_sh_head(POST_ARGS) /* We don't care about custom sections after this. */ - if (sec == SEC_CUSTOM) + if (sec == SEC_CUSTOM) { + if ((nch = mdoc->last->child) == NULL || + nch->type != ROFFT_TEXT || nch->next != NULL) + return; + goodsec = NULL; + mindist = INT_MAX; + for (testsec = secnames + 1; *testsec != NULL; testsec++) { + dist = similar(nch->string, *testsec); + if (dist < mindist) { + goodsec = *testsec; + mindist = dist; + } + } + if (goodsec != NULL) + mandoc_vmsg(MANDOCERR_SEC_TYPO, mdoc->parse, + nch->line, nch->pos, "Sh %s instead of %s", + nch->string, goodsec); return; + } /* * Check whether our non-custom section is being repeated or is @@ -1879,12 +2394,37 @@ post_sh_head(POST_ARGS) } static void +post_xr(POST_ARGS) +{ + struct roff_node *n, *nch; + + n = mdoc->last; + nch = n->child; + if (nch->next == NULL) { + mandoc_vmsg(MANDOCERR_XR_NOSEC, mdoc->parse, + n->line, n->pos, "Xr %s", nch->string); + } else { + assert(nch->next == n->last); + if(mandoc_xr_add(nch->next->string, nch->string, + nch->line, nch->pos)) + mandoc_vmsg(MANDOCERR_XR_SELF, mdoc->parse, + nch->line, nch->pos, "Xr %s %s", + nch->string, nch->next->string); + } + post_delim_nb(mdoc); +} + +static void post_ignpar(POST_ARGS) { struct roff_node *np; switch (mdoc->last->type) { + case ROFFT_BLOCK: + post_prevpar(mdoc); + return; case ROFFT_HEAD: + post_delim(mdoc); post_hyph(mdoc); return; case ROFFT_BODY: @@ -1897,8 +2437,8 @@ post_ignpar(POST_ARGS) if (np->tok == MDOC_Pp || np->tok == MDOC_Lp) { mandoc_vmsg(MANDOCERR_PAR_SKIP, mdoc->parse, np->line, np->pos, - "%s after %s", mdoc_macronames[np->tok], - mdoc_macronames[mdoc->last->tok]); + "%s after %s", roff_name[np->tok], + roff_name[mdoc->last->tok]); roff_node_delete(mdoc, np); } @@ -1906,17 +2446,19 @@ post_ignpar(POST_ARGS) if (np->tok == MDOC_Pp || np->tok == MDOC_Lp) { mandoc_vmsg(MANDOCERR_PAR_SKIP, mdoc->parse, np->line, np->pos, "%s at the end of %s", - mdoc_macronames[np->tok], - mdoc_macronames[mdoc->last->tok]); + roff_name[np->tok], + roff_name[mdoc->last->tok]); roff_node_delete(mdoc, np); } } static void -pre_par(PRE_ARGS) +post_prevpar(POST_ARGS) { + struct roff_node *n; - if (NULL == mdoc->last) + n = mdoc->last; + if (NULL == n->prev) return; if (n->type != ROFFT_ELEM && n->type != ROFFT_BLOCK) return; @@ -1926,22 +2468,21 @@ pre_par(PRE_ARGS) * block: `Lp', `Pp', or non-compact `Bd' or `Bl'. */ - if (MDOC_Pp != mdoc->last->tok && - MDOC_Lp != mdoc->last->tok && - MDOC_br != mdoc->last->tok) + if (n->prev->tok != MDOC_Pp && + n->prev->tok != MDOC_Lp && + n->prev->tok != ROFF_br) return; - if (MDOC_Bl == n->tok && n->norm->Bl.comp) + if (n->tok == MDOC_Bl && n->norm->Bl.comp) return; - if (MDOC_Bd == n->tok && n->norm->Bd.comp) + if (n->tok == MDOC_Bd && n->norm->Bd.comp) return; - if (MDOC_It == n->tok && n->parent->norm->Bl.comp) + if (n->tok == MDOC_It && n->parent->norm->Bl.comp) return; mandoc_vmsg(MANDOCERR_PAR_SKIP, mdoc->parse, - mdoc->last->line, mdoc->last->pos, - "%s before %s", mdoc_macronames[mdoc->last->tok], - mdoc_macronames[n->tok]); - roff_node_delete(mdoc, mdoc->last); + n->prev->line, n->prev->pos, "%s before %s", + roff_name[n->prev->tok], roff_name[n->tok]); + roff_node_delete(mdoc, n->prev); } static void @@ -1950,30 +2491,31 @@ post_par(POST_ARGS) struct roff_node *np; np = mdoc->last; + if (np->tok != ROFF_br && np->tok != ROFF_sp) + post_prevpar(mdoc); - if (np->tok == MDOC_sp) { - if (np->nchild > 1) + if (np->tok == ROFF_sp) { + if (np->child != NULL && np->child->next != NULL) mandoc_vmsg(MANDOCERR_ARG_EXCESS, mdoc->parse, np->child->next->line, np->child->next->pos, "sp ... %s", np->child->next->string); } else if (np->child != NULL) mandoc_vmsg(MANDOCERR_ARG_SKIP, mdoc->parse, np->line, np->pos, "%s %s", - mdoc_macronames[np->tok], np->child->string); + roff_name[np->tok], np->child->string); if ((np = mdoc->last->prev) == NULL) { np = mdoc->last->parent; if (np->tok != MDOC_Sh && np->tok != MDOC_Ss) return; } else if (np->tok != MDOC_Pp && np->tok != MDOC_Lp && - (mdoc->last->tok != MDOC_br || - (np->tok != MDOC_sp && np->tok != MDOC_br))) + (mdoc->last->tok != ROFF_br || + (np->tok != ROFF_sp && np->tok != ROFF_br))) return; mandoc_vmsg(MANDOCERR_PAR_SKIP, mdoc->parse, - mdoc->last->line, mdoc->last->pos, - "%s after %s", mdoc_macronames[mdoc->last->tok], - mdoc_macronames[np->tok]); + mdoc->last->line, mdoc->last->pos, "%s after %s", + roff_name[mdoc->last->tok], roff_name[np->tok]); roff_node_delete(mdoc, mdoc->last); } @@ -1984,6 +2526,8 @@ post_dd(POST_ARGS) char *datestr; n = mdoc->last; + n->flags |= NODE_NOPRT; + if (mdoc->meta.date != NULL) { mandoc_msg(MANDOCERR_PROLOG_REP, mdoc->parse, n->line, n->pos, "Dd"); @@ -2000,8 +2544,8 @@ post_dd(POST_ARGS) if (n->child == NULL || n->child->string[0] == '\0') { mdoc->meta.date = mdoc->quick ? mandoc_strdup("") : - mandoc_normdate(mdoc->parse, NULL, n->line, n->pos); - goto out; + mandoc_normdate(mdoc, NULL, n->line, n->pos); + return; } datestr = NULL; @@ -2009,12 +2553,10 @@ post_dd(POST_ARGS) if (mdoc->quick) mdoc->meta.date = datestr; else { - mdoc->meta.date = mandoc_normdate(mdoc->parse, + mdoc->meta.date = mandoc_normdate(mdoc, datestr, n->line, n->pos); free(datestr); } -out: - roff_node_delete(mdoc, n); } static void @@ -2025,10 +2567,12 @@ post_dt(POST_ARGS) char *p; n = mdoc->last; + n->flags |= NODE_NOPRT; + if (mdoc->flags & MDOC_PBODY) { mandoc_msg(MANDOCERR_DT_LATE, mdoc->parse, n->line, n->pos, "Dt"); - goto out; + return; } if (mdoc->meta.title != NULL) @@ -2070,7 +2614,7 @@ post_dt(POST_ARGS) } } - /* Mandatory second argument: section. */ + /* Mandatory second argument: section. */ if (nn != NULL) nn = nn->next; @@ -2080,7 +2624,7 @@ post_dt(POST_ARGS) mdoc->parse, n->line, n->pos, "Dt %s", mdoc->meta.title); mdoc->meta.vol = mandoc_strdup("LOCAL"); - goto out; /* msec and arch remain NULL. */ + return; /* msec and arch remain NULL. */ } mdoc->meta.msec = mandoc_strdup(nn->string); @@ -2098,7 +2642,7 @@ post_dt(POST_ARGS) /* Optional third argument: architecture. */ if ((nn = nn->next) == NULL) - goto out; + return; for (p = nn->string; *p != '\0'; p++) *p = tolower((unsigned char)*p); @@ -2109,24 +2653,59 @@ post_dt(POST_ARGS) if ((nn = nn->next) != NULL) mandoc_vmsg(MANDOCERR_ARG_EXCESS, mdoc->parse, nn->line, nn->pos, "Dt ... %s", nn->string); - -out: - roff_node_delete(mdoc, n); } static void post_bx(POST_ARGS) { - struct roff_node *n; + struct roff_node *n, *nch; + const char *macro; + post_delim_nb(mdoc); + + n = mdoc->last; + nch = n->child; + + if (nch != NULL) { + macro = !strcmp(nch->string, "Open") ? "Ox" : + !strcmp(nch->string, "Net") ? "Nx" : + !strcmp(nch->string, "Free") ? "Fx" : + !strcmp(nch->string, "DragonFly") ? "Dx" : NULL; + if (macro != NULL) + mandoc_msg(MANDOCERR_BX, mdoc->parse, + n->line, n->pos, macro); + mdoc->last = nch; + nch = nch->next; + mdoc->next = ROFF_NEXT_SIBLING; + roff_elem_alloc(mdoc, n->line, n->pos, MDOC_Ns); + mdoc->last->flags |= NODE_NOSRC; + mdoc->next = ROFF_NEXT_SIBLING; + } else + mdoc->next = ROFF_NEXT_CHILD; + roff_word_alloc(mdoc, n->line, n->pos, "BSD"); + mdoc->last->flags |= NODE_NOSRC; + + if (nch == NULL) { + mdoc->last = n; + return; + } + + roff_elem_alloc(mdoc, n->line, n->pos, MDOC_Ns); + mdoc->last->flags |= NODE_NOSRC; + mdoc->next = ROFF_NEXT_SIBLING; + roff_word_alloc(mdoc, n->line, n->pos, "-"); + mdoc->last->flags |= NODE_NOSRC; + roff_elem_alloc(mdoc, n->line, n->pos, MDOC_Ns); + mdoc->last->flags |= NODE_NOSRC; + mdoc->last = n; + /* * Make `Bx's second argument always start with an uppercase * letter. Groff checks if it's an "accepted" term, but we just * uppercase blindly. */ - if ((n = mdoc->last->child) != NULL && (n = n->next) != NULL) - *n->string = (char)toupper((unsigned char)*n->string); + *nch->string = (char)toupper((unsigned char)*nch->string); } static void @@ -2139,6 +2718,8 @@ post_os(POST_ARGS) struct roff_node *n; n = mdoc->last; + n->flags |= NODE_NOPRT; + if (mdoc->meta.os != NULL) mandoc_msg(MANDOCERR_PROLOG_REP, mdoc->parse, n->line, n->pos, "Os"); @@ -2146,6 +2727,8 @@ post_os(POST_ARGS) mandoc_msg(MANDOCERR_PROLOG_LATE, mdoc->parse, n->line, n->pos, "Os"); + post_delim(mdoc); + /* * Set the operating system by way of the `Os' macro. * The order of precedence is: @@ -2161,8 +2744,8 @@ post_os(POST_ARGS) if (mdoc->meta.os) goto out; - if (mdoc->defos) { - mdoc->meta.os = mandoc_strdup(mdoc->defos); + if (mdoc->os_s != NULL) { + mdoc->meta.os = mandoc_strdup(mdoc->os_s); goto out; } @@ -2182,32 +2765,42 @@ post_os(POST_ARGS) #endif /*!OSNAME*/ out: - roff_node_delete(mdoc, n); -} + if (mdoc->meta.os_e == MANDOC_OS_OTHER) { + if (strstr(mdoc->meta.os, "OpenBSD") != NULL) + mdoc->meta.os_e = MANDOC_OS_OPENBSD; + else if (strstr(mdoc->meta.os, "NetBSD") != NULL) + mdoc->meta.os_e = MANDOC_OS_NETBSD; + } -/* - * If no argument is provided, - * fill in the name of the current manual page. - */ -static void -post_ex(POST_ARGS) -{ - struct roff_node *n; + /* + * This is the earliest point where we can check + * Mdocdate conventions because we don't know + * the operating system earlier. + */ - n = mdoc->last; - if (n->child != NULL) - return; + mandoc_vmsg(MANDOCERR_OS_ARG, mdoc->parse, + n->child->line, n->child->pos, + "Os %s (%s)", n->child->string, + mdoc->meta.os_e == MANDOC_OS_OPENBSD ? + "OpenBSD" : "NetBSD"); - if (mdoc->meta.name == NULL) { - mandoc_msg(MANDOCERR_EX_NONAME, mdoc->parse, - n->line, n->pos, "Ex"); + while (n->tok != MDOC_Dd) + if ((n = n->prev) == NULL) + return; + if ((n = n->child) == NULL) return; + if (strncmp(n->string, "$" "Mdocdate", 9)) { + if (mdoc->meta.os_e == MANDOC_OS_OPENBSD) + mandoc_vmsg(MANDOCERR_MDOCDATE_MISSING, + mdoc->parse, n->line, n->pos, + "Dd %s (OpenBSD)", n->string); + } else { + if (mdoc->meta.os_e == MANDOC_OS_NETBSD) + mandoc_vmsg(MANDOCERR_MDOCDATE, + mdoc->parse, n->line, n->pos, + "Dd %s (NetBSD)", n->string); } - - mdoc->next = ROFF_NEXT_CHILD; - roff_word_alloc(mdoc, n->line, n->pos, mdoc->meta.name); - mdoc->last = n; } enum roff_sec @@ -2223,7 +2816,7 @@ mdoc_a2sec(const char *p) } static size_t -macro2len(int macro) +macro2len(enum roff_tok macro) { switch (macro) {