+ return(mdoc_err(mdoc, "macro expects either argument or parameters"));
+}
+
+
+static int
+post_ex(struct mdoc *mdoc)
+{
+
+ assert(MDOC_ELEM == mdoc->last->type);
+ assert(MDOC_Ex == mdoc->last->tok);
+
+ if (0 == mdoc->last->data.elem.argc) {
+ if (mdoc->last->child)
+ return(1);
+ return(mdoc_err(mdoc, "macro expects `%s' or a single child",
+ mdoc_argnames[MDOC_Std]));
+ }
+ if (mdoc->last->child)
+ return(mdoc_err(mdoc, "macro expects `%s' or a single child",
+ mdoc_argnames[MDOC_Std]));
+ if (1 != mdoc->last->data.elem.argc)
+ return(mdoc_err(mdoc, "macro expects `%s' or a single child",
+ mdoc_argnames[MDOC_Std]));
+ if (MDOC_Std != mdoc->last->data.elem.argv[0].arg)
+ return(mdoc_err(mdoc, "macro expects `%s' or a single child",
+ mdoc_argnames[MDOC_Std]));
+ return(1);
+}
+
+
+/* Warn if `Bl' type-specific syntax isn't reflected in items. */
+static int
+post_it(struct mdoc *mdoc)
+{
+ int type, sv;
+#define TYPE_NONE (0)
+#define TYPE_BODY (1)
+#define TYPE_HEAD (2)
+#define TYPE_OHEAD (3)
+ size_t i, argc;
+ struct mdoc_node *n;
+
+ if (MDOC_BLOCK != mdoc->last->type)
+ return(1);
+
+ assert(MDOC_It == mdoc->last->tok);
+
+ n = mdoc->last->parent;
+ assert(n);
+ assert(MDOC_Bl == n->tok);
+
+ n = n->parent;
+ assert(MDOC_BLOCK == n->type);
+ assert(MDOC_Bl == n->tok);
+
+ argc = n->data.block.argc;
+ type = TYPE_NONE;
+ sv = -1;
+
+ /* Some types require block-head, some not. */
+
+ /* LINTED */
+ for (i = 0; TYPE_NONE == type && i < argc; i++)
+ switch (n->data.block.argv[(int)i].arg) {
+ case (MDOC_Tag):
+ /* FALLTHROUGH */
+ case (MDOC_Diag):
+ /* FALLTHROUGH */
+ case (MDOC_Hang):
+ /* FALLTHROUGH */
+ case (MDOC_Ohang):
+ /* FALLTHROUGH */
+ case (MDOC_Inset):
+ type = TYPE_HEAD;
+ sv = n->data.block.argv[(int)i].arg;
+ break;
+ case (MDOC_Bullet):
+ /* FALLTHROUGH */
+ case (MDOC_Dash):
+ /* FALLTHROUGH */
+ case (MDOC_Enum):
+ /* FALLTHROUGH */
+ case (MDOC_Hyphen):
+ /* FALLTHROUGH */
+ case (MDOC_Item):
+ type = TYPE_BODY;
+ sv = n->data.block.argv[(int)i].arg;
+ break;
+ case (MDOC_Column):
+ type = TYPE_OHEAD;
+ sv = n->data.block.argv[(int)i].arg;
+ break;
+ default:
+ break;
+ }
+
+ assert(TYPE_NONE != type);
+
+ n = mdoc->last->data.block.head;
+ assert(n);
+
+ if (TYPE_HEAD == type) {
+ if (NULL == n->child)
+ if ( ! mdoc_warn(mdoc, WARN_SYNTAX, "macro suggests line parameters"))
+ return(0);
+
+ n = mdoc->last->data.block.body;
+ assert(n);
+ if (NULL == n->child)
+ if ( ! mdoc_warn(mdoc, WARN_SYNTAX, "macro suggests body children"))
+ return(0);
+
+ } else if (TYPE_BODY == type) {
+ if (n->child)
+ if ( ! mdoc_warn(mdoc, WARN_SYNTAX, "macro suggests no line parameters"))
+ return(0);
+
+ n = mdoc->last->data.block.body;
+ assert(n);
+ if (NULL == n->child)
+ if ( ! mdoc_warn(mdoc, WARN_SYNTAX, "macro suggests body children"))
+ return(0);
+ } else {
+ if (NULL == n->child)
+ if ( ! mdoc_warn(mdoc, WARN_SYNTAX, "macro suggests line parameters"))
+ return(0);
+
+ n = mdoc->last->data.block.body;
+ assert(n);
+ if (n->child)
+ if ( ! mdoc_warn(mdoc, WARN_SYNTAX, "macro suggests no body children"))
+ return(0);
+ }
+
+ if (MDOC_Column != sv)
+ return(1);
+
+ /* Make sure the number of columns is sane. */
+
+ argc = mdoc->last->parent->parent->data.block.argv->sz;
+ n = mdoc->last->data.block.head->child;
+
+ for (i = 0; n; n = n->next)
+ i++;
+
+ if (i == argc)
+ return(1);
+ return(mdoc_err(mdoc, "expected %zu list columns, have %zu", argc, i));
+#undef TYPE_NONE
+#undef TYPE_BODY
+#undef TYPE_HEAD
+#undef TYPE_OHEAD
+}
+
+
+static int
+post_bl(struct mdoc *mdoc)
+{
+ struct mdoc_node *n;
+
+ if (MDOC_BODY != mdoc->last->type)
+ return(1);
+ assert(MDOC_Bl == mdoc->last->tok);
+
+ /* LINTED */
+ for (n = mdoc->last->child; n; n = n->next) {
+ if (MDOC_BLOCK == n->type)
+ if (MDOC_It == n->tok)
+ continue;
+ break;
+ }
+ if (NULL == n)
+ return(1);
+ return(mdoc_nerr(mdoc, n, "invalid child of parent macro `Bl'"));
+}
+
+
+static int
+ebool(struct mdoc *mdoc)
+{
+ struct mdoc_node *n;
+
+ assert(MDOC_ELEM == mdoc->last->type);
+ /* LINTED */
+ for (n = mdoc->last->child; n; n = n->next) {
+ if (MDOC_TEXT != n->type)
+ break;
+ if (xstrcmp(n->data.text.string, "on"))
+ continue;
+ if (xstrcmp(n->data.text.string, "off"))
+ continue;
+ break;
+ }
+ if (NULL == n)
+ return(1);
+ return(mdoc_nerr(mdoc, n, "expected boolean value"));
+}
+
+
+static int
+post_root(struct mdoc *mdoc)
+{
+
+ if (NULL == mdoc->first->child)
+ return(mdoc_err(mdoc, "document has no data"));
+ if (SEC_PROLOGUE == mdoc->lastnamed)
+ return(mdoc_err(mdoc, "document has incomplete prologue"));
+ if (MDOC_BLOCK != mdoc->first->child->type)
+ return(mdoc_err(mdoc, "document expects `%s' macro after prologue", mdoc_macronames[MDOC_Sh]));
+ if (MDOC_Sh != mdoc->first->child->tok)
+ return(mdoc_err(mdoc, "document expects `%s' macro after prologue", mdoc_macronames[MDOC_Sh]));
+ return(1);
+}
+
+
+static int
+post_sh(struct mdoc *mdoc)
+{
+
+ if (MDOC_HEAD == mdoc->last->type)
+ return(post_sh_head(mdoc));
+ if (MDOC_BODY == mdoc->last->type)
+ return(post_sh_body(mdoc));
+ return(1);
+}
+
+
+static int
+post_sh_body(struct mdoc *mdoc)
+{
+ struct mdoc_node *n;
+
+ assert(MDOC_Sh == mdoc->last->tok);
+ if (SEC_NAME != mdoc->lastnamed)
+ return(1);
+
+ if (NULL == (n = mdoc->last->child))
+ return(mdoc_err(mdoc, "section NAME must contain %s as the first body child", mdoc_macronames[MDOC_Nm]));
+ if (MDOC_ELEM != n->type || MDOC_Nm != n->tok)
+ return(mdoc_err(mdoc, "section NAME must contain %s as the first body child", mdoc_macronames[MDOC_Nm]));
+ if (NULL == (n = n->next))
+ return(mdoc_err(mdoc, "section NAME must contain %s as the second body child", mdoc_macronames[MDOC_Nd]));
+ if (MDOC_ELEM != n->type || MDOC_Nd != n->tok)
+ return(mdoc_err(mdoc, "section NAME must contain %s as the second body child", mdoc_macronames[MDOC_Nd]));
+ if (NULL == (n = n->next))
+ return(1);
+
+ return(mdoc_warn(mdoc, WARN_SYNTAX, "section NAME usually limited to %s and %s body children",
+ mdoc_macronames[MDOC_Nm], mdoc_macronames[MDOC_Nd]));
+}
+
+
+static int
+post_sh_head(struct mdoc *mdoc)
+{
+ char buf[64];
+ enum mdoc_sec sec;
+
+ assert(MDOC_Sh == mdoc->last->tok);
+
+ if ( ! xstrlcats(buf, mdoc->last->child, 64))
+ return(mdoc_err(mdoc, "macro parameters too long"));
+
+ sec = mdoc_atosec(buf);
+
+ if (SEC_BODY == mdoc->lastnamed && SEC_NAME != sec)
+ return(mdoc_err(mdoc, "section NAME must be first"));
+ if (SEC_CUSTOM == sec)
+ return(1);
+ if (sec == mdoc->lastnamed)
+ return(mdoc_warn(mdoc, WARN_SYNTAX, "section repeated"));
+ if (sec < mdoc->lastnamed)
+ return(mdoc_warn(mdoc, WARN_SYNTAX, "section out of conventional order"));
+
+ return(1);
+}
+
+
+int
+mdoc_valid_pre(struct mdoc *mdoc, struct mdoc_node *node)
+{
+ v_pre *p;
+
+ if (MDOC_TEXT == node->type)
+ return(1);
+ assert(MDOC_ROOT != node->type);
+
+ if (NULL == mdoc_valids[node->tok].pre)
+ return(1);
+ for (p = mdoc_valids[node->tok].pre; *p; p++)
+ if ( ! (*p)(mdoc, node))
+ return(0);
+ return(1);