]> git.cameronkatri.com Git - mandoc.git/blobdiff - validate.c
Initial separation of tree/mdocml.1.
[mandoc.git] / validate.c
index 585bf357303efbe372ee3a5ea175f4cb3e89ab8d..d7413fa5dca16dbf9f4e8aaa08b297e9dc60aa46 100644 (file)
@@ -1,4 +1,4 @@
-/* $Id: validate.c,v 1.23 2009/01/09 15:15:31 kristaps Exp $ */
+/* $Id: validate.c,v 1.35 2009/01/16 15:58:50 kristaps Exp $ */
 /*
  * Copyright (c) 2008 Kristaps Dzonsons <kristaps@kth.se>
  *
@@ -27,123 +27,179 @@ typedef   int     (*v_post)(struct mdoc *);
 
 
 struct valids {
-       v_pre    pre;
+       v_pre   *pre;
        v_post  *post;
 };
 
+static int     pre_check_parent(struct mdoc *, struct mdoc_node *, 
+                       int, enum mdoc_type);
+static int     pre_check_msecs(struct mdoc *, struct mdoc_node *, 
+                       int, enum mdoc_msec *);
 
 static int     pre_display(struct mdoc *, struct mdoc_node *);
+static int     pre_sh(struct mdoc *, struct mdoc_node *);
+static int     pre_ss(struct mdoc *, struct mdoc_node *);
+static int     pre_bd(struct mdoc *, struct mdoc_node *);
+static int     pre_bl(struct mdoc *, struct mdoc_node *);
+static int     pre_it(struct mdoc *, struct mdoc_node *);
+static int     pre_cd(struct mdoc *, struct mdoc_node *);
+static int     pre_er(struct mdoc *, struct mdoc_node *);
+static int     pre_ex(struct mdoc *, struct mdoc_node *);
+static int     pre_an(struct mdoc *, struct mdoc_node *);
 static int     pre_prologue(struct mdoc *, struct mdoc_node *);
 static int     pre_prologue(struct mdoc *, struct mdoc_node *);
 static int     pre_prologue(struct mdoc *, struct mdoc_node *);
-static int     post_headchild_err_ge1(struct mdoc *);
-static int     post_elemchild_err_ge1(struct mdoc *);
-static int     post_elemchild_warn_eq0(struct mdoc *);
-static int     post_bodychild_warn_ge1(struct mdoc *);
-static int     post_sh(struct mdoc *);
 
-static v_post  posts_sh[] = { post_headchild_err_ge1, 
-                       post_bodychild_warn_ge1, post_sh, NULL };
-static v_post  posts_ss[] = { post_headchild_err_ge1, NULL };
-static v_post  posts_pp[] = { post_elemchild_warn_eq0, NULL };
-static v_post  posts_dd[] = { post_elemchild_err_ge1, NULL };
-static v_post  posts_display[] = { post_headchild_err_ge1, NULL };
+static int     head_err_ge1(struct mdoc *);
+static int     head_warn_ge1(struct mdoc *);
+static int     head_err_eq0(struct mdoc *);
+static int     elem_err_eq0(struct mdoc *);
+static int     elem_err_le1(struct mdoc *);
+static int     elem_err_eq1(struct mdoc *);
+static int     elem_err_ge1(struct mdoc *);
+static int     elem_warn_eq0(struct mdoc *);
+static int     body_warn_ge1(struct mdoc *);
+static int     body_err_eq0(struct mdoc *);
+static int     elem_warn_ge1(struct mdoc *);
+static int     elem_bool(struct mdoc *);
+static int     post_sh(struct mdoc *);
+static int     post_bl(struct mdoc *);
+static int     post_it(struct mdoc *);
+static int     post_ex(struct mdoc *);
+static int     post_an(struct mdoc *);
+
+static v_pre   pres_prologue[] = { pre_prologue, NULL };
+static v_pre   pres_d1[] = { pre_display, NULL };
+static v_pre   pres_bd[] = { pre_display, pre_bd, NULL };
+static v_pre   pres_bl[] = { pre_bl, NULL };
+static v_pre   pres_it[] = { pre_it, NULL };
+static v_pre   pres_ss[] = { pre_ss, NULL };
+static v_pre   pres_sh[] = { pre_sh, NULL };
+static v_pre   pres_cd[] = { pre_cd, NULL };
+static v_pre   pres_er[] = { pre_er, NULL };
+static v_pre   pres_ex[] = { pre_ex, NULL };
+static v_pre   pres_an[] = { pre_an, NULL };
+
+static v_post  posts_bool[] = { elem_err_eq1, elem_bool, NULL };
+static v_post  posts_bd[] = { head_err_eq0, body_warn_ge1, NULL };
+static v_post  posts_text[] = { elem_err_ge1, NULL };
+static v_post  posts_wtext[] = { elem_warn_ge1, NULL };
+static v_post  posts_notext[] = { elem_err_eq0, NULL };
+static v_post  posts_wline[] = { head_warn_ge1, body_err_eq0, NULL };
+static v_post  posts_sh[] = { head_err_ge1, 
+                       body_warn_ge1, post_sh, NULL };
+static v_post  posts_bl[] = { head_err_eq0, 
+                       body_warn_ge1, post_bl, NULL };
+static v_post  posts_it[] = { post_it, NULL };
+static v_post  posts_ss[] = { head_err_ge1, NULL };
+static v_post  posts_pp[] = { elem_warn_eq0, NULL };
+static v_post  posts_d1[] = { head_err_ge1, NULL };
+static v_post  posts_ex[] = { elem_err_le1, post_ex, NULL };
+static v_post  posts_an[] = { post_an, NULL };
 
 
 const  struct valids mdoc_valids[MDOC_MAX] = {
        { NULL, NULL }, /* \" */
-       { pre_prologue, posts_dd }, /* Dd */
-       { pre_prologue, NULL }, /* Dt */
-       { pre_prologue, NULL }, /* Os */
-       { NULL, posts_sh }, /* Sh */ /* FIXME: preceding Pp. */
-       { NULL, posts_ss }, /* Ss */ /* FIXME: preceding Pp. */
-       { NULL, posts_pp }, /* Pp */ /* FIXME: proceeding... */
-       { pre_display, posts_display }, /* D1 */
-       { pre_display, posts_display }, /* Dl */
-       { pre_display, NULL }, /* Bd */ /* FIXME: preceding Pp. */
+       { pres_prologue, posts_text }, /* Dd */
+       { pres_prologue, NULL }, /* Dt */
+       { pres_prologue, NULL }, /* Os */
+       /* FIXME: preceding Pp. */ 
+       /* FIXME: NAME section internal ordering. */
+       { pres_sh, posts_sh }, /* Sh */ 
+       /* FIXME: preceding Pp. */
+       { pres_ss, posts_ss }, /* Ss */ 
+       /* FIXME: proceeding Pp */
+       { NULL, posts_pp }, /* Pp */ 
+       { pres_d1, posts_d1 }, /* D1 */
+       { pres_d1, posts_d1 }, /* Dl */
+        /* FIXME: preceding Pp. */
+       { pres_bd, posts_bd }, /* Bd */
        { NULL, NULL }, /* Ed */
-       { NULL, NULL }, /* Bl */ /* FIXME: preceding Pp. */
+       /* FIXME: preceding Pp. */
+       { pres_bl, posts_bl }, /* Bl */ 
        { NULL, NULL }, /* El */
-       { NULL, NULL }, /* It */
-       { NULL, NULL }, /* Ad */ 
-       { NULL, NULL }, /* An */
+       { pres_it, posts_it }, /* It */
+       { NULL, posts_text }, /* Ad */ 
+       { pres_an, posts_an }, /* An */ 
        { NULL, NULL }, /* Ar */
-       { NULL, NULL }, /* Cd */
+       { pres_cd, posts_text }, /* Cd */ 
        { NULL, NULL }, /* Cm */
-       { NULL, NULL }, /* Dv */ 
-       { NULL, NULL }, /* Er */ 
-       { NULL, NULL }, /* Ev */ 
-       { NULL, NULL }, /* Ex */
-       { NULL, NULL }, /* Fa */ 
-       { NULL, NULL }, /* Fd */ 
+       { NULL, posts_text }, /* Dv */ 
+       { pres_er, posts_text }, /* Er */ 
+       { NULL, posts_text }, /* Ev */ 
+       { pres_ex, posts_ex }, /* Ex */ 
+       { NULL, posts_text }, /* Fa */ 
+       /* FIXME: only in SYNOPSIS section. */
+       { NULL, NULL }, /* Fd */
        { NULL, NULL }, /* Fl */
-       { NULL, NULL }, /* Fn */ 
+       { NULL, posts_text }, /* Fn */ 
        { NULL, NULL }, /* Ft */ 
-       { NULL, NULL }, /* Ic */ 
-       { NULL, NULL }, /* In */ 
-       { NULL, NULL }, /* Li */
-       { NULL, NULL }, /* Nd */ 
-       { NULL, NULL }, /* Nm */ 
-       { NULL, NULL }, /* Op */
+       { NULL, posts_text }, /* Ic */ 
+       { NULL, posts_wtext }, /* In */ 
+       { NULL, posts_text }, /* Li */
+       { NULL, posts_wtext }, /* Nd */
+       /* FIXME: check that name must be set/provided. */
+       { NULL, NULL }, /* Nm */
+       { NULL, posts_wline }, /* Op */
        { NULL, NULL }, /* Ot */
        { NULL, NULL }, /* Pa */
-       { NULL, NULL }, /* Rv */
-       { NULL, NULL }, /* St */
-       { NULL, NULL }, /* Va */
-       { NULL, NULL }, /* Vt */ 
-       { NULL, NULL }, /* Xr */
-       { NULL, NULL }, /* %A */
-       { NULL, NULL }, /* %B */
-       { NULL, NULL }, /* %D */
-       { NULL, NULL }, /* %I */
-       { NULL, NULL }, /* %J */
-       { NULL, NULL }, /* %N */
-       { NULL, NULL }, /* %O */
-       { NULL, NULL }, /* %P */
-       { NULL, NULL }, /* %R */
-       { NULL, NULL }, /* %T */
-       { NULL, NULL }, /* %V */
+       { NULL, posts_notext }, /* Rv */ /* FIXME: -std required */
+       { NULL, posts_notext }, /* St */ /* FIXME: arg required */
+       { NULL, posts_text }, /* Va */
+       { NULL, posts_text }, /* Vt */ 
+       { NULL, NULL }, /* Xr */ /* FIXME: valid arguments */
+       { NULL, posts_text }, /* %A */
+       { NULL, posts_text }, /* %B */
+       { NULL, posts_text }, /* %D */
+       { NULL, posts_text }, /* %I */
+       { NULL, posts_text }, /* %J */
+       { NULL, posts_text }, /* %N */
+       { NULL, posts_text }, /* %O */
+       { NULL, posts_text }, /* %P */
+       { NULL, posts_text }, /* %R */
+       { NULL, posts_text }, /* %T */
+       { NULL, posts_text }, /* %V */
        { NULL, NULL }, /* Ac */
        { NULL, NULL }, /* Ao */
-       { NULL, NULL }, /* Aq */
-       { NULL, NULL }, /* At */ /* FIXME */
+       { NULL, posts_wline }, /* Aq */
+       { NULL, NULL }, /* At */ /* FIXME: valid arguments */
        { NULL, NULL }, /* Bc */
        { NULL, NULL }, /* Bf */ 
        { NULL, NULL }, /* Bo */
-       { NULL, NULL }, /* Bq */
+       { NULL, posts_wline }, /* Bq */
        { NULL, NULL }, /* Bsx */
        { NULL, NULL }, /* Bx */
-       { NULL, NULL }, /* Db */
+       { NULL, posts_bool }, /* Db */
        { NULL, NULL }, /* Dc */
        { NULL, NULL }, /* Do */
-       { NULL, NULL }, /* Dq */
+       { NULL, posts_wline }, /* Dq */
        { NULL, NULL }, /* Ec */
        { NULL, NULL }, /* Ef */ /* -symbolic, etc. */
-       { NULL, NULL }, /* Em */ 
+       { NULL, posts_text }, /* Em */ 
        { NULL, NULL }, /* Eo */
        { NULL, NULL }, /* Fx */
-       { NULL, NULL }, /* Ms */
-       { NULL, NULL }, /* No */
-       { NULL, NULL }, /* Ns */
+       { NULL, posts_text }, /* Ms */ /* FIXME: which symbols? */
+       { NULL, posts_notext }, /* No */
+       { NULL, posts_notext }, /* Ns */
        { NULL, NULL }, /* Nx */
        { NULL, NULL }, /* Ox */
        { NULL, NULL }, /* Pc */
-       { NULL, NULL }, /* Pf */ /* 2 or more arguments */
+       { NULL, NULL }, /* Pf */
        { NULL, NULL }, /* Po */
-       { NULL, NULL }, /* Pq */ /* FIXME: ignore following Sh/Ss */
+       { NULL, posts_wline }, /* Pq */ /* FIXME: ignore following Sh/Ss */
        { NULL, NULL }, /* Qc */
-       { NULL, NULL }, /* Ql */
+       { NULL, posts_wline }, /* Ql */
        { NULL, NULL }, /* Qo */
-       { NULL, NULL }, /* Qq */
+       { NULL, posts_wline }, /* Qq */
        { NULL, NULL }, /* Re */
        { NULL, NULL }, /* Rs */
        { NULL, NULL }, /* Sc */
        { NULL, NULL }, /* So */
-       { NULL, NULL }, /* Sq */
-       { NULL, NULL }, /* Sm */
-       { NULL, NULL }, /* Sx */
-       { NULL, NULL }, /* Sy */
-       { NULL, NULL }, /* Tn */
+       { NULL, posts_wline }, /* Sq */
+       { NULL, posts_bool }, /* Sm */ 
+       { NULL, posts_text }, /* Sx */
+       { NULL, posts_text }, /* Sy */
+       { NULL, posts_text }, /* Tn */
        { NULL, NULL }, /* Ux */
        { NULL, NULL }, /* Xc */
        { NULL, NULL }, /* Xo */
@@ -153,57 +209,173 @@ const    struct valids mdoc_valids[MDOC_MAX] = {
        { NULL, NULL }, /* Oc */
        { NULL, NULL }, /* Bk */
        { NULL, NULL }, /* Ek */
-       { NULL, NULL }, /* Bt */
+       { NULL, posts_notext }, /* Bt */
        { NULL, NULL }, /* Hf */
        { NULL, NULL }, /* Fr */
-       { NULL, NULL }, /* Ud */
+       { NULL, posts_notext }, /* Ud */
 };
 
 
 static int
-post_bodychild_warn_ge1(struct mdoc *mdoc)
+pre_check_msecs(struct mdoc *mdoc, struct mdoc_node *node, 
+               int sz, enum mdoc_msec *msecs)
+{
+       int              i;
+
+       for (i = 0; i < sz; i++)
+               if (msecs[i] == mdoc->meta.msec)
+                       return(1);
+       return(mdoc_nwarn(mdoc, node, WARN_COMPAT,
+                               "macro is not appropriate for this manual section"));
+}
+
+
+static int
+pre_check_parent(struct mdoc *mdoc, struct mdoc_node *node, 
+               int tok, enum mdoc_type type)
+{
+
+       if (type != node->parent->type) 
+               return(mdoc_nerr(mdoc, node, "invalid macro parent class %s, expected %s", 
+                                       mdoc_type2a(node->parent->type),
+                                       mdoc_type2a(type)));
+       if (MDOC_ROOT != type && tok != node->parent->tok)
+               return(mdoc_nerr(mdoc, node, "invalid macro parent `%s', expected `%s'", 
+                                       mdoc_macronames[node->parent->tok],
+                                       mdoc_macronames[tok]));
+       return(1);
+}
+
+
+static int
+body_err_eq0(struct mdoc *mdoc)
+{
+
+       if (MDOC_BODY != mdoc->last->type)
+               return(1);
+       if (NULL == mdoc->last->child)
+               return(1);
+       return(mdoc_warn(mdoc, WARN_SYNTAX, "macro suggests no body children"));
+}
+
+
+static int
+body_warn_ge1(struct mdoc *mdoc)
 {
 
        if (MDOC_BODY != mdoc->last->type)
                return(1);
        if (mdoc->last->child)
                return(1);
+       return(mdoc_warn(mdoc, WARN_SYNTAX, "macro suggests one or more body children"));
+}
+
+
+static int
+elem_warn_eq0(struct mdoc *mdoc)
+{
 
-       return(mdoc_warn(mdoc, WARN_ARGS_GE1));
+       assert(MDOC_ELEM == mdoc->last->type);
+       if (NULL == mdoc->last->child)
+               return(1);
+       return(mdoc_pwarn(mdoc, mdoc->last->child->line,
+                       mdoc->last->child->pos, WARN_SYNTAX, "macro suggests no parameters"));
 }
 
 
 static int
-post_elemchild_warn_eq0(struct mdoc *mdoc)
+elem_warn_ge1(struct mdoc *mdoc)
+{
+
+       assert(MDOC_ELEM == mdoc->last->type);
+       if (mdoc->last->child)
+               return(1);
+       return(mdoc_warn(mdoc, WARN_SYNTAX, "macro suggests one or more parameters"));
+}
+
+
+static int
+elem_err_eq1(struct mdoc *mdoc)
+{
+
+       assert(MDOC_ELEM == mdoc->last->type);
+       if (NULL == mdoc->last->child)
+               return(mdoc_err(mdoc, "macro expects one parameter"));
+       if (mdoc->last->child->next)
+               return(mdoc_err(mdoc, "macro expects one parameter"));
+       return(1);
+}
+
+
+static int
+elem_err_le1(struct mdoc *mdoc)
+{
+
+       assert(MDOC_ELEM == mdoc->last->type);
+       if (NULL == mdoc->last->child)
+               return(1);
+       if (NULL == mdoc->last->child->next)
+               return(1);
+       return(mdoc_err(mdoc, "macro expects one or fewer parameters"));
+}
+
+
+static int
+elem_err_eq0(struct mdoc *mdoc)
 {
 
        assert(MDOC_ELEM == mdoc->last->type);
        if (NULL == mdoc->last->child)
                return(1);
-       return(mdoc_warn(mdoc, WARN_ARGS_EQ0));
+       return(mdoc_err(mdoc, "macro expects no parameters"));
 }
 
 
 static int
-post_elemchild_err_ge1(struct mdoc *mdoc)
+elem_err_ge1(struct mdoc *mdoc)
 {
 
        assert(MDOC_ELEM == mdoc->last->type);
        if (mdoc->last->child)
                return(1);
-       return(mdoc_err(mdoc, ERR_ARGS_GE1));
+       return(mdoc_err(mdoc, "macro expects one or more parameters"));
+}
+
+
+static int
+head_err_eq0(struct mdoc *mdoc)
+{
+
+       if (MDOC_HEAD != mdoc->last->type)
+               return(1);
+       if (NULL == mdoc->last->child)
+               return(1);
+       return(mdoc_perr(mdoc, mdoc->last->child->line,
+                       mdoc->last->child->pos, "macro expects no parameters"));
+}
+
+
+static int
+head_warn_ge1(struct mdoc *mdoc)
+{
+
+       if (MDOC_HEAD != mdoc->last->type)
+               return(1);
+       if (mdoc->last->child)
+               return(1);
+       return(mdoc_warn(mdoc, WARN_SYNTAX, "macro suggests one or more parameters"));
 }
 
 
 static int
-post_headchild_err_ge1(struct mdoc *mdoc)
+head_err_ge1(struct mdoc *mdoc)
 {
 
        if (MDOC_HEAD != mdoc->last->type)
                return(1);
        if (mdoc->last->child)
                return(1);
-       return(mdoc_err(mdoc, ERR_ARGS_GE1));
+       return(mdoc_err(mdoc, "macro expects one or more parameters"));
 }
 
 
@@ -212,13 +384,209 @@ pre_display(struct mdoc *mdoc, struct mdoc_node *node)
 {
        struct mdoc_node *n;
 
-       for (n = mdoc->last; n; n = n->parent) 
+       if (MDOC_BLOCK != node->type)
+               return(1);
+
+       assert(mdoc->last);
+       for (n = mdoc->last->parent; n; n = n->parent) 
                if (MDOC_BLOCK == n->type)
-                       if (MDOC_Bd == n->data.block.tok)
+                       if (MDOC_Bd == n->tok)
                                break;
        if (NULL == n)
                return(1);
-       return(mdoc_verr(mdoc, node, ERR_SCOPE_NONEST));
+       return(mdoc_nerr(mdoc, node, "displays may not be nested"));
+}
+
+
+static int
+pre_bl(struct mdoc *mdoc, struct mdoc_node *node)
+{
+       int              type, err;
+       struct mdoc_arg *argv;
+       size_t           i, argc;
+
+       if (MDOC_BLOCK != node->type)
+               return(1);
+       assert(MDOC_Bl == node->tok);
+
+       argv = NULL;
+       argc = node->data.block.argc; 
+
+       for (i = type = err = 0; i < argc; i++) {
+               argv = &node->data.block.argv[(int)i];
+               assert(argv);
+               switch (argv->arg) {
+               case (MDOC_Bullet):
+                       /* FALLTHROUGH */
+               case (MDOC_Dash):
+                       /* FALLTHROUGH */
+               case (MDOC_Enum):
+                       /* FALLTHROUGH */
+               case (MDOC_Hyphen):
+                       /* FALLTHROUGH */
+               case (MDOC_Item):
+                       /* FALLTHROUGH */
+               case (MDOC_Tag):
+                       /* FALLTHROUGH */
+               case (MDOC_Diag):
+                       /* FALLTHROUGH */
+               case (MDOC_Hang):
+                       /* FALLTHROUGH */
+               case (MDOC_Ohang):
+                       /* FALLTHROUGH */
+               case (MDOC_Inset):
+                       /* FALLTHROUGH */
+               case (MDOC_Column):
+                       if (type)
+                               err++;
+                       type++;
+                       break;
+               default:
+                       break;
+               }
+       }
+       if (0 == type)
+               return(mdoc_err(mdoc, "no list type specified"));
+       if (0 == err)
+               return(1);
+       assert(argv);
+       return(mdoc_perr(mdoc, argv->line, 
+                       argv->pos, "only one list type possible"));
+}
+
+
+static int
+pre_bd(struct mdoc *mdoc, struct mdoc_node *node)
+{
+       int              type, err;
+       struct mdoc_arg *argv;
+       size_t           i, argc;
+
+       if (MDOC_BLOCK != node->type)
+               return(1);
+       assert(MDOC_Bd == node->tok);
+
+       argv = NULL;
+       argc = node->data.block.argc;
+
+       for (err = i = type = 0; 0 == err && i < argc; i++) {
+               argv = &node->data.block.argv[(int)i];
+               assert(argv);
+               switch (argv->arg) {
+               case (MDOC_Ragged):
+                       /* FALLTHROUGH */
+               case (MDOC_Unfilled):
+                       /* FALLTHROUGH */
+               case (MDOC_Filled):
+                       /* FALLTHROUGH */
+               case (MDOC_Literal):
+                       /* FALLTHROUGH */
+               case (MDOC_File):
+                       if (type)
+                               err++;
+                       type++;
+                       break;
+               default:
+                       break;
+               }
+       }
+       if (0 == type)
+               return(mdoc_err(mdoc, "no display type specified"));
+       if (0 == err)
+               return(1);
+       assert(argv);
+       return(mdoc_perr(mdoc, argv->line, 
+                       argv->pos, "only one display type possible"));
+}
+
+
+static int
+pre_ss(struct mdoc *mdoc, struct mdoc_node *node)
+{
+
+       if (MDOC_BLOCK != node->type)
+               return(1);
+       return(pre_check_parent(mdoc, node, MDOC_Sh, MDOC_BODY));
+}
+
+
+static int
+pre_sh(struct mdoc *mdoc, struct mdoc_node *node)
+{
+
+       if (MDOC_BLOCK != node->type)
+               return(1);
+       return(pre_check_parent(mdoc, node, -1, MDOC_ROOT));
+}
+
+
+static int
+pre_an(struct mdoc *mdoc, struct mdoc_node *node)
+{
+       assert(MDOC_ELEM == node->type);
+       assert(MDOC_An == node->tok);
+       if (1 >= node->data.elem.argc)
+               return(1);
+       return(mdoc_nerr(mdoc, node, "macro may only have one argument"));
+}
+
+
+static int
+pre_ex(struct mdoc *mdoc, struct mdoc_node *node)
+{
+       enum mdoc_msec   msecs[3];
+
+       assert(MDOC_ELEM == node->type);
+
+       msecs[0] = MSEC_1;
+       msecs[1] = MSEC_6;
+       msecs[2] = MSEC_8;
+       if ( ! pre_check_msecs(mdoc, node, 3, msecs))
+               return(0);
+
+       if (1 != node->data.elem.argc) {
+               if ( ! mdoc_nwarn(mdoc, node, WARN_COMPAT, 
+                                       "macro suggests `%s' argument",
+                                       mdoc_argnames[MDOC_Std]))
+                       return(0);
+               return(1);
+       }
+       if (MDOC_Std != node->data.elem.argv[0].arg)
+               if ( ! mdoc_nwarn(mdoc, node, WARN_COMPAT, 
+                                       "macro suggests `%s' argument",
+                                       mdoc_argnames[MDOC_Std]))
+                       return(0);
+       return(1);
+}
+
+
+static int
+pre_er(struct mdoc *mdoc, struct mdoc_node *node)
+{
+       enum mdoc_msec   msecs[1];
+
+       msecs[0] = MSEC_2;
+       return(pre_check_msecs(mdoc, node, 1, msecs));
+}
+
+
+static int
+pre_cd(struct mdoc *mdoc, struct mdoc_node *node)
+{
+       enum mdoc_msec   msecs[1];
+
+       msecs[0] = MSEC_4;
+       return(pre_check_msecs(mdoc, node, 1, msecs));
+}
+
+
+static int
+pre_it(struct mdoc *mdoc, struct mdoc_node *node)
+{
+
+       if (MDOC_BLOCK != node->type)
+               return(1);
+       return(pre_check_parent(mdoc, node, MDOC_Bl, MDOC_BODY));
 }
 
 
@@ -227,24 +595,24 @@ pre_prologue(struct mdoc *mdoc, struct mdoc_node *node)
 {
 
        if (SEC_PROLOGUE != mdoc->sec_lastn)
-               return(mdoc_verr(mdoc, node, ERR_SEC_NPROLOGUE));
+               return(mdoc_nerr(mdoc, node, "macro may only be invoked in the prologue"));
        assert(MDOC_ELEM == node->type);
 
        /* Check for ordering. */
 
-       switch (node->data.elem.tok) {
+       switch (node->tok) {
        case (MDOC_Os):
                if (mdoc->meta.title[0] && mdoc->meta.date)
                        break;
-               return(mdoc_verr(mdoc, node, ERR_SEC_PROLOGUE_OO));
+               return(mdoc_nerr(mdoc, node, "prologue macro out-of-order"));
        case (MDOC_Dt):
                if (0 == mdoc->meta.title[0] && mdoc->meta.date)
                        break;
-               return(mdoc_verr(mdoc, node, ERR_SEC_PROLOGUE_OO));
+               return(mdoc_nerr(mdoc, node, "prologue macro out-of-order"));
        case (MDOC_Dd):
                if (0 == mdoc->meta.title[0] && 0 == mdoc->meta.date)
                        break;
-               return(mdoc_verr(mdoc, node, ERR_SEC_PROLOGUE_OO));
+               return(mdoc_nerr(mdoc, node, "prologue macro out-of-order"));
        default:
                abort();
                /* NOTREACHED */
@@ -252,7 +620,7 @@ pre_prologue(struct mdoc *mdoc, struct mdoc_node *node)
 
        /* Check for repetition. */
 
-       switch (node->data.elem.tok) {
+       switch (node->tok) {
        case (MDOC_Os):
                if (0 == mdoc->meta.os[0])
                        return(1);
@@ -270,14 +638,213 @@ pre_prologue(struct mdoc *mdoc, struct mdoc_node *node)
                /* NOTREACHED */
        }
 
-       return(mdoc_verr(mdoc, node, ERR_SEC_PROLOGUE_REP));
+       return(mdoc_nerr(mdoc, node, "prologue macro repeated"));
 }
 
 
-/*
- * Warn if sections (those that are with a known title, such as NAME,
- * DESCRIPTION, and so forth) are out of the conventional order.
- */
+static int
+post_an(struct mdoc *mdoc)
+{
+
+       assert(MDOC_ELEM == mdoc->last->type);
+       assert(MDOC_An == mdoc->last->tok);
+
+       if (0 != mdoc->last->data.elem.argc) {
+               if (NULL == mdoc->last->child)
+                       return(1);
+               return(mdoc_err(mdoc, "macro expects either argument or parameters"));
+       }
+
+       if (mdoc->last->child)
+               return(1);
+       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)
+       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;
+       
+       /* Some types require block-head, some not. */
+
+       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):
+                       /* FALLTHROUGH */
+               case (MDOC_Column):
+                       type = TYPE_BODY;
+                       sv = n->data.block.argv[(int)i].arg;
+                       break;
+               default:
+                       break;
+               }
+
+       assert(TYPE_NONE != type);
+
+       if (TYPE_HEAD == type) {
+               n = mdoc->last->data.block.head;
+               assert(n);
+               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);
+
+               return(1);
+       }
+
+       assert(TYPE_BODY == type);
+       assert(mdoc->last->data.block.head);
+
+       n = mdoc->last->data.block.head;
+       assert(n);
+       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);
+
+       if (MDOC_Column != sv) 
+               return(1);
+
+       /* Make sure the number of columns is sane. */
+
+       sv = 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 == (size_t)sv)
+               return(1);
+       return(mdoc_err(mdoc, "expected %d list columns, have %d", sv, (int)i));
+#undef TYPE_NONE
+#undef TYPE_BODY
+#undef TYPE_HEAD
+}
+
+
+/* Make sure that only `It' macros are our body-children. */
+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);
+
+       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
+elem_bool(struct mdoc *mdoc)
+{
+       struct mdoc_node *n;
+
+       assert(MDOC_ELEM == mdoc->last->type);
+       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 [on/off]"));
+}
+
+
+/* Warn if conventional sections are out of order. */
 static int
 post_sh(struct mdoc *mdoc)
 {
@@ -289,7 +856,7 @@ post_sh(struct mdoc *mdoc)
        if (MDOC_HEAD != mdoc->last->type)
                return(1);
        
-       assert(MDOC_Sh == mdoc->last->data.head.tok);
+       assert(MDOC_Sh == mdoc->last->tok);
 
        n = mdoc->last->child;
        assert(n);
@@ -308,36 +875,28 @@ post_sh(struct mdoc *mdoc)
                return(1);
 
        if (sec == mdoc->sec_lastn)
-               return(mdoc_warn(mdoc, WARN_SEC_REP));
-       return(mdoc_warn(mdoc, WARN_SEC_OO));
+               return(mdoc_warn(mdoc, WARN_SYNTAX, "section repeated"));
+       return(mdoc_warn(mdoc, WARN_SYNTAX, "section out of conventional order"));
 }
 
 
 int
 mdoc_valid_pre(struct mdoc *mdoc, struct mdoc_node *node)
 {
-       int              t;
+       v_pre           *p;
 
-       switch (node->type) {
-       case (MDOC_BODY):
-               t = node->data.body.tok;
-               break;
-       case (MDOC_ELEM):
-               t = node->data.elem.tok;
-               break;
-       case (MDOC_BLOCK):
-               t = node->data.block.tok;
-               break;
-       case (MDOC_HEAD):
-               t = node->data.head.tok;
-               break;
-       default:
+       /* TODO: character-escape checks. */
+
+       if (MDOC_TEXT == node->type)
                return(1);
-       }
+       assert(MDOC_ROOT != node->type);
 
-       if (NULL == mdoc_valids[t].pre)
+       if (NULL == mdoc_valids[node->tok].pre)
                return(1);
-       return((*mdoc_valids[t].pre)(mdoc, node));
+       for (p = mdoc_valids[node->tok].pre; *p; p++)
+               if ( ! (*p)(mdoc, node)) 
+                       return(0);
+       return(1);
 }
 
 
@@ -345,29 +904,17 @@ int
 mdoc_valid_post(struct mdoc *mdoc)
 {
        v_post          *p;
-       int              t;
 
-       switch (mdoc->last->type) {
-       case (MDOC_BODY):
-               t = mdoc->last->data.body.tok;
-               break;
-       case (MDOC_ELEM):
-               t = mdoc->last->data.elem.tok;
-               break;
-       case (MDOC_BLOCK):
-               t = mdoc->last->data.block.tok;
-               break;
-       case (MDOC_HEAD):
-               t = mdoc->last->data.head.tok;
-               break;
-       default:
+       if (MDOC_TEXT == mdoc->last->type)
+               return(1);
+       if (MDOC_ROOT == mdoc->last->type) {
+               /* TODO: make sure prologue is complete. */
                return(1);
        }
 
-       if (NULL == mdoc_valids[t].post)
+       if (NULL == mdoc_valids[mdoc->last->tok].post)
                return(1);
-
-       for (p = mdoc_valids[t].post; *p; p++)
+       for (p = mdoc_valids[mdoc->last->tok].post; *p; p++)
                if ( ! (*p)(mdoc)) 
                        return(0);