+ assert(j < (int)np->args->argc);
+
+ /*
+ * Accommodate for new-style groff column syntax. Shuffle the
+ * child nodes, all of which must be TEXT, as arguments for the
+ * column field. Then, delete the head children.
+ */
+
+ argv = np->args->argv + j;
+ i = argv->sz;
+ argv->sz += mdoc->last->nchild;
+ argv->value = mandoc_reallocarray(argv->value,
+ argv->sz, sizeof(char *));
+
+ mdoc->last->norm->Bl.ncols = argv->sz;
+ mdoc->last->norm->Bl.cols = (void *)argv->value;
+
+ for (nn = mdoc->last->child; nn; i++) {
+ argv->value[i] = nn->string;
+ nn->string = NULL;
+ nnp = nn;
+ nn = nn->next;
+ mdoc_node_delete(NULL, nnp);
+ }
+
+ mdoc->last->nchild = 0;
+ mdoc->last->child = NULL;
+
+ return(1);
+}
+
+static int
+post_bl(POST_ARGS)
+{
+ struct mdoc_node *nparent, *nprev; /* of the Bl block */
+ struct mdoc_node *nblock, *nbody; /* of the Bl */
+ struct mdoc_node *nchild, *nnext; /* of the Bl body */
+
+ nbody = mdoc->last;
+ switch (nbody->type) {
+ case MDOC_BLOCK:
+ return(post_bl_block(mdoc));
+ case MDOC_HEAD:
+ return(post_bl_head(mdoc));
+ case MDOC_BODY:
+ break;
+ default:
+ return(1);
+ }
+
+ bwarn_ge1(mdoc);
+
+ nchild = nbody->child;
+ while (NULL != nchild) {
+ if (MDOC_It == nchild->tok || MDOC_Sm == nchild->tok) {
+ nchild = nchild->next;
+ continue;
+ }
+
+ mandoc_msg(MANDOCERR_BL_MOVE, mdoc->parse,
+ nchild->line, nchild->pos,
+ mdoc_macronames[nchild->tok]);
+
+ /*
+ * Move the node out of the Bl block.
+ * First, collect all required node pointers.
+ */
+
+ nblock = nbody->parent;
+ nprev = nblock->prev;
+ nparent = nblock->parent;
+ nnext = nchild->next;
+
+ /*
+ * Unlink this child.
+ */
+
+ assert(NULL == nchild->prev);
+ if (0 == --nbody->nchild) {
+ nbody->child = NULL;
+ nbody->last = NULL;
+ assert(NULL == nnext);
+ } else {
+ nbody->child = nnext;
+ nnext->prev = NULL;
+ }
+
+ /*
+ * Relink this child.
+ */
+
+ nchild->parent = nparent;
+ nchild->prev = nprev;
+ nchild->next = nblock;
+
+ nblock->prev = nchild;
+ nparent->nchild++;
+ if (NULL == nprev)
+ nparent->child = nchild;
+ else
+ nprev->next = nchild;
+
+ nchild = nnext;
+ }
+
+ return(1);
+}
+
+static int
+post_bk(POST_ARGS)
+{
+
+ hwarn_eq0(mdoc);
+ bwarn_ge1(mdoc);
+ return(1);
+}
+
+static int
+ebool(struct mdoc *mdoc)
+{
+ struct mdoc_node *nch;
+ enum mdoct tok;
+
+ tok = mdoc->last->tok;
+ nch = mdoc->last->child;
+
+ if (NULL == nch) {
+ if (MDOC_Sm == tok)
+ mdoc->flags ^= MDOC_SMOFF;
+ return(1);
+ }
+
+ check_count(mdoc, MDOC_ELEM, CHECK_WARN, CHECK_LT, 2);
+
+ assert(MDOC_TEXT == nch->type);
+
+ if (0 == strcmp(nch->string, "on")) {
+ if (MDOC_Sm == tok)
+ mdoc->flags &= ~MDOC_SMOFF;
+ return(1);
+ }
+ if (0 == strcmp(nch->string, "off")) {
+ if (MDOC_Sm == tok)
+ mdoc->flags |= MDOC_SMOFF;
+ return(1);
+ }
+
+ mandoc_vmsg(MANDOCERR_SM_BAD,
+ mdoc->parse, nch->line, nch->pos,
+ "%s %s", mdoc_macronames[tok], nch->string);
+ return(mdoc_node_relink(mdoc, nch));
+}
+
+static int
+post_root(POST_ARGS)
+{
+ struct mdoc_node *n;
+
+ /* Add missing prologue data. */
+
+ if (mdoc->meta.date == NULL)
+ mdoc->meta.date = mdoc->quick ?
+ mandoc_strdup("") :
+ mandoc_normdate(mdoc->parse, NULL, 0, 0);
+
+ if (mdoc->meta.title == NULL) {
+ mandoc_msg(MANDOCERR_DT_NOTITLE,
+ mdoc->parse, 0, 0, "EOF");
+ mdoc->meta.title = mandoc_strdup("UNTITLED");
+ }
+
+ if (mdoc->meta.vol == NULL)
+ mdoc->meta.vol = mandoc_strdup("LOCAL");
+
+ if (mdoc->meta.os == NULL) {
+ mandoc_msg(MANDOCERR_OS_MISSING,
+ mdoc->parse, 0, 0, NULL);
+ mdoc->meta.os = mandoc_strdup("");
+ }
+
+ /* Check that we begin with a proper `Sh'. */
+
+ n = mdoc->first->child;
+ while (n != NULL && mdoc_macros[n->tok].flags & MDOC_PROLOGUE)
+ n = n->next;
+
+ if (n == NULL)
+ 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]);
+
+ return(1);
+}
+
+static int
+post_st(POST_ARGS)
+{
+ struct mdoc_node *n, *nch;
+ const char *p;
+
+ n = mdoc->last;
+ nch = n->child;
+
+ if (NULL == nch) {
+ mandoc_msg(MANDOCERR_MACRO_EMPTY, mdoc->parse,
+ n->line, n->pos, mdoc_macronames[n->tok]);
+ mdoc_node_delete(mdoc, n);
+ return(1);
+ }
+
+ assert(MDOC_TEXT == nch->type);
+
+ if (NULL == (p = mdoc_a2st(nch->string))) {
+ mandoc_vmsg(MANDOCERR_ST_BAD, mdoc->parse,
+ nch->line, nch->pos, "St %s", nch->string);
+ mdoc_node_delete(mdoc, n);
+ } else {
+ free(nch->string);
+ nch->string = mandoc_strdup(p);
+ }
+
+ return(1);
+}
+
+static int
+post_rs(POST_ARGS)
+{
+ struct mdoc_node *nn, *next, *prev;
+ int i, j;
+
+ switch (mdoc->last->type) {
+ case MDOC_HEAD:
+ check_count(mdoc, MDOC_HEAD, CHECK_WARN, CHECK_EQ, 0);
+ return(1);
+ case MDOC_BODY:
+ if (mdoc->last->child)
+ break;
+ check_count(mdoc, MDOC_BODY, CHECK_WARN, CHECK_GT, 0);
+ return(1);
+ default:
+ return(1);
+ }
+
+ /*
+ * The full `Rs' block needs special handling to order the
+ * sub-elements according to `rsord'. Pick through each element
+ * and correctly order it. This is an insertion sort.
+ */
+
+ next = NULL;
+ for (nn = mdoc->last->child->next; nn; nn = next) {
+ /* Determine order of `nn'. */
+ for (i = 0; i < RSORD_MAX; i++)
+ if (rsord[i] == nn->tok)
+ break;
+
+ if (i == RSORD_MAX) {
+ mandoc_msg(MANDOCERR_RS_BAD,
+ mdoc->parse, nn->line, nn->pos,
+ mdoc_macronames[nn->tok]);
+ i = -1;
+ } else if (MDOC__J == nn->tok || MDOC__B == nn->tok)
+ mdoc->last->norm->Rs.quote_T++;
+
+ /*
+ * Remove `nn' from the chain. This somewhat
+ * repeats mdoc_node_unlink(), but since we're
+ * just re-ordering, there's no need for the
+ * full unlink process.
+ */
+
+ if (NULL != (next = nn->next))
+ next->prev = nn->prev;
+
+ if (NULL != (prev = nn->prev))
+ prev->next = nn->next;
+
+ nn->prev = nn->next = NULL;
+
+ /*
+ * Scan back until we reach a node that's
+ * ordered before `nn'.
+ */
+
+ for ( ; prev ; prev = prev->prev) {
+ /* Determine order of `prev'. */
+ for (j = 0; j < RSORD_MAX; j++)
+ if (rsord[j] == prev->tok)
+ break;
+ if (j == RSORD_MAX)
+ j = -1;
+
+ if (j <= i)
+ break;
+ }
+
+ /*
+ * Set `nn' back into its correct place in front
+ * of the `prev' node.
+ */
+
+ nn->prev = prev;
+
+ if (prev) {
+ if (prev->next)
+ prev->next->prev = nn;
+ nn->next = prev->next;
+ prev->next = nn;
+ } else {
+ mdoc->last->child->prev = nn;
+ nn->next = mdoc->last->child;
+ mdoc->last->child = nn;
+ }
+ }
+
+ return(1);
+}
+
+/*
+ * For some arguments of some macros,
+ * convert all breakable hyphens into ASCII_HYPH.
+ */
+static int
+post_hyph(POST_ARGS)
+{
+ struct mdoc_node *n, *nch;
+ char *cp;
+
+ n = mdoc->last;
+ switch (n->type) {
+ case MDOC_HEAD:
+ if (MDOC_Sh == n->tok || MDOC_Ss == n->tok)
+ break;
+ return(1);
+ case MDOC_BODY:
+ if (MDOC_D1 == n->tok || MDOC_Nd == n->tok)
+ break;
+ return(1);
+ case MDOC_ELEM:
+ break;
+ default:
+ return(1);
+ }
+
+ for (nch = n->child; nch; nch = nch->next) {
+ if (MDOC_TEXT != nch->type)
+ continue;
+ cp = nch->string;
+ if ('\0' == *cp)
+ continue;
+ while ('\0' != *(++cp))
+ if ('-' == *cp &&
+ isalpha((unsigned char)cp[-1]) &&
+ isalpha((unsigned char)cp[1]))
+ *cp = ASCII_HYPH;
+ }
+ return(1);
+}
+
+static int
+post_hyphtext(POST_ARGS)
+{
+
+ ewarn_ge1(mdoc);
+ return(post_hyph(mdoc));
+}
+
+static int
+post_ns(POST_ARGS)
+{
+
+ if (MDOC_LINE & mdoc->last->flags)
+ mandoc_msg(MANDOCERR_NS_SKIP, mdoc->parse,
+ mdoc->last->line, mdoc->last->pos, NULL);
+ return(1);
+}
+
+static int
+post_sh(POST_ARGS)
+{
+
+ post_ignpar(mdoc);
+
+ switch (mdoc->last->type) {
+ case MDOC_HEAD:
+ return(post_sh_head(mdoc));
+ case MDOC_BODY:
+ switch (mdoc->lastsec) {
+ case SEC_NAME:
+ return(post_sh_name(mdoc));
+ case SEC_SEE_ALSO:
+ return(post_sh_see_also(mdoc));
+ case SEC_AUTHORS:
+ return(post_sh_authors(mdoc));
+ default:
+ break;
+ }
+ break;
+ default:
+ break;
+ }
+
+ return(1);
+}
+
+static int
+post_sh_name(POST_ARGS)
+{
+ struct mdoc_node *n;
+
+ /*
+ * Warn if the NAME section doesn't contain the `Nm' and `Nd'
+ * macros (can have multiple `Nm' and one `Nd'). Note that the
+ * children of the BODY declaration can also be "text".
+ */
+
+ if (NULL == (n = mdoc->last->child)) {
+ mandoc_msg(MANDOCERR_NAMESEC_BAD, mdoc->parse,
+ mdoc->last->line, mdoc->last->pos, "empty");
+ return(1);
+ }
+
+ for ( ; n && n->next; n = n->next) {
+ if (MDOC_ELEM == n->type && MDOC_Nm == n->tok)
+ continue;
+ if (MDOC_TEXT == n->type)
+ continue;
+ mandoc_msg(MANDOCERR_NAMESEC_BAD, mdoc->parse,
+ n->line, n->pos, mdoc_macronames[n->tok]);
+ }
+
+ assert(n);
+ if (MDOC_BLOCK == n->type && MDOC_Nd == n->tok)
+ return(1);
+
+ mandoc_msg(MANDOCERR_NAMESEC_BAD, mdoc->parse,
+ n->line, n->pos, mdoc_macronames[n->tok]);
+ return(1);
+}
+
+static int
+post_sh_see_also(POST_ARGS)
+{
+ const struct mdoc_node *n;
+ const char *name, *sec;
+ const char *lastname, *lastsec, *lastpunct;
+ int cmp;
+
+ n = mdoc->last->child;
+ lastname = lastsec = lastpunct = NULL;
+ while (n != NULL) {
+ if (n->tok != MDOC_Xr || n->nchild < 2)
+ break;
+
+ /* Process one .Xr node. */
+
+ name = n->child->string;
+ sec = n->child->next->string;
+ if (lastsec != NULL) {
+ if (lastpunct[0] != ',' || lastpunct[1] != '\0')
+ mandoc_vmsg(MANDOCERR_XR_PUNCT,
+ mdoc->parse, n->line, n->pos,
+ "%s before %s(%s)", lastpunct,
+ name, sec);
+ cmp = strcmp(lastsec, sec);
+ if (cmp > 0)
+ mandoc_vmsg(MANDOCERR_XR_ORDER,
+ mdoc->parse, n->line, n->pos,
+ "%s(%s) after %s(%s)", name,
+ sec, lastname, lastsec);
+ else if (cmp == 0 &&
+ strcasecmp(lastname, name) > 0)
+ mandoc_vmsg(MANDOCERR_XR_ORDER,
+ mdoc->parse, n->line, n->pos,
+ "%s after %s", name, lastname);
+ }
+ lastname = name;
+ lastsec = sec;
+
+ /* Process the following node. */
+
+ n = n->next;
+ if (n == NULL)
+ break;
+ if (n->tok == MDOC_Xr) {
+ lastpunct = "none";
+ continue;
+ }
+ if (n->type != MDOC_TEXT)
+ break;
+ for (name = n->string; *name != '\0'; name++)
+ if (isalpha((const unsigned char)*name))
+ return(1);
+ lastpunct = n->string;
+ if (n->next == NULL)
+ mandoc_vmsg(MANDOCERR_XR_PUNCT, mdoc->parse,
+ n->line, n->pos, "%s after %s(%s)",
+ lastpunct, lastname, lastsec);
+ n = n->next;
+ }
+ return(1);
+}
+
+static int
+child_an(const struct mdoc_node *n)
+{
+
+ for (n = n->child; n != NULL; n = n->next)
+ if ((n->tok == MDOC_An && n->nchild) || child_an(n))
+ return(1);
+ return(0);
+}
+
+static int
+post_sh_authors(POST_ARGS)
+{
+
+ if ( ! child_an(mdoc->last))
+ mandoc_msg(MANDOCERR_AN_MISSING, mdoc->parse,
+ mdoc->last->line, mdoc->last->pos, NULL);
+ return(1);
+}
+
+static int
+post_sh_head(POST_ARGS)
+{
+ struct mdoc_node *n;
+ const char *goodsec;
+ char *secname;
+ enum mdoc_sec sec;
+
+ /*
+ * Process a new section. Sections are either "named" or
+ * "custom". Custom sections are user-defined, while named ones
+ * follow a conventional order and may only appear in certain
+ * manual sections.
+ */
+
+ secname = NULL;
+ sec = SEC_CUSTOM;
+ mdoc_deroff(&secname, mdoc->last);
+ sec = NULL == secname ? SEC_CUSTOM : a2sec(secname);
+
+ /* The NAME should be first. */
+
+ if (SEC_NAME != sec && SEC_NONE == mdoc->lastnamed)
+ mandoc_vmsg(MANDOCERR_NAMESEC_FIRST, mdoc->parse,
+ mdoc->last->line, mdoc->last->pos,
+ "Sh %s", secname);
+
+ /* The SYNOPSIS gets special attention in other areas. */
+
+ if (SEC_SYNOPSIS == sec) {
+ roff_setreg(mdoc->roff, "nS", 1, '=');
+ mdoc->flags |= MDOC_SYNOPSIS;
+ } else {
+ roff_setreg(mdoc->roff, "nS", 0, '=');
+ mdoc->flags &= ~MDOC_SYNOPSIS;
+ }
+
+ /* Mark our last section. */
+
+ mdoc->lastsec = sec;
+
+ /*
+ * Set the section attribute for the current HEAD, for its
+ * parent BLOCK, and for the HEAD children; the latter can
+ * only be TEXT nodes, so no recursion is needed.
+ * For other blocks and elements, including .Sh BODY, this is
+ * done when allocating the node data structures, but for .Sh
+ * BLOCK and HEAD, the section is still unknown at that time.
+ */
+
+ mdoc->last->parent->sec = sec;
+ mdoc->last->sec = sec;
+ for (n = mdoc->last->child; n; n = n->next)
+ n->sec = sec;
+
+ /* We don't care about custom sections after this. */
+
+ if (SEC_CUSTOM == sec) {
+ free(secname);
+ return(1);
+ }
+
+ /*
+ * Check whether our non-custom section is being repeated or is
+ * out of order.
+ */
+
+ if (sec == mdoc->lastnamed)
+ mandoc_vmsg(MANDOCERR_SEC_REP, mdoc->parse,
+ mdoc->last->line, mdoc->last->pos,
+ "Sh %s", secname);
+
+ if (sec < mdoc->lastnamed)
+ mandoc_vmsg(MANDOCERR_SEC_ORDER, mdoc->parse,
+ mdoc->last->line, mdoc->last->pos,
+ "Sh %s", secname);
+
+ /* Mark the last named section. */
+
+ mdoc->lastnamed = sec;
+
+ /* Check particular section/manual conventions. */
+
+ if (mdoc->meta.msec == NULL) {
+ free(secname);
+ return(1);
+ }
+
+ goodsec = NULL;
+ switch (sec) {
+ case SEC_ERRORS:
+ if (*mdoc->meta.msec == '4')
+ break;
+ goodsec = "2, 3, 4, 9";
+ /* FALLTHROUGH */
+ case SEC_RETURN_VALUES:
+ /* FALLTHROUGH */
+ case SEC_LIBRARY:
+ if (*mdoc->meta.msec == '2')
+ break;
+ if (*mdoc->meta.msec == '3')
+ break;
+ if (NULL == goodsec)
+ goodsec = "2, 3, 9";
+ /* FALLTHROUGH */
+ case SEC_CONTEXT:
+ if (*mdoc->meta.msec == '9')
+ break;
+ if (NULL == goodsec)
+ goodsec = "9";
+ mandoc_vmsg(MANDOCERR_SEC_MSEC, mdoc->parse,
+ mdoc->last->line, mdoc->last->pos,
+ "Sh %s for %s only", secname, goodsec);
+ break;
+ default:
+ break;
+ }
+
+ free(secname);
+ return(1);
+}
+
+static int
+post_ignpar(POST_ARGS)
+{
+ struct mdoc_node *np;
+
+ hwarn_ge1(mdoc);
+ post_hyph(mdoc);
+
+ if (MDOC_BODY != mdoc->last->type)
+ return(1);
+
+ if (NULL != (np = mdoc->last->child))
+ if (MDOC_Pp == np->tok || MDOC_Lp == np->tok) {
+ mandoc_vmsg(MANDOCERR_PAR_SKIP,
+ mdoc->parse, np->line, np->pos,
+ "%s after %s", mdoc_macronames[np->tok],
+ mdoc_macronames[mdoc->last->tok]);
+ mdoc_node_delete(mdoc, np);
+ }
+
+ if (NULL != (np = mdoc->last->last))
+ if (MDOC_Pp == np->tok || MDOC_Lp == np->tok) {
+ 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]);
+ mdoc_node_delete(mdoc, np);
+ }
+
+ return(1);
+}
+
+static int
+pre_par(PRE_ARGS)
+{
+
+ if (NULL == mdoc->last)
+ return(1);
+ if (MDOC_ELEM != n->type && MDOC_BLOCK != n->type)
+ return(1);
+
+ /*
+ * Don't allow prior `Lp' or `Pp' prior to a paragraph-type
+ * 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)
+ return(1);
+ if (MDOC_Bl == n->tok && n->norm->Bl.comp)
+ return(1);
+ if (MDOC_Bd == n->tok && n->norm->Bd.comp)
+ return(1);
+ if (MDOC_It == n->tok && n->parent->norm->Bl.comp)
+ return(1);
+
+ 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]);
+ mdoc_node_delete(mdoc, mdoc->last);