X-Git-Url: https://git.cameronkatri.com/mandoc.git/blobdiff_plain/b6860e4400b8b5350bb31fa72b4632dfac772774..ac335ec98abf9e1439628e44539f84a225f5d743:/mdoc_validate.c?ds=sidebyside

diff --git a/mdoc_validate.c b/mdoc_validate.c
index 9d043526..44d71185 100644
--- a/mdoc_validate.c
+++ b/mdoc_validate.c
@@ -1,6 +1,6 @@
-/*	$Id: mdoc_validate.c,v 1.40 2009/07/24 12:09:37 kristaps Exp $ */
+/*	$Id: mdoc_validate.c,v 1.78 2010/05/15 16:24:38 kristaps Exp $ */
 /*
- * Copyright (c) 2008, 2009 Kristaps Dzonsons <kristaps@kth.se>
+ * Copyright (c) 2008, 2009 Kristaps Dzonsons <kristaps@bsd.lv>
  *
  * Permission to use, copy, modify, and distribute this software for any
  * purpose with or without fee is hereby granted, provided that the above
@@ -14,13 +14,15 @@
  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  */
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
 #include <sys/types.h>
 
 #include <assert.h>
 #include <ctype.h>
-#include <errno.h>
 #include <limits.h>
-#include <stdarg.h>
 #include <stdlib.h>
 #include <string.h>
 
@@ -41,9 +43,7 @@ struct	valids {
 	v_post	*post;
 };
 
-static	int	 check_parent(PRE_ARGS, int, enum mdoc_type);
-static	int	 check_msec(PRE_ARGS, ...);
-static	int	 check_sec(PRE_ARGS, ...);
+static	int	 check_parent(PRE_ARGS, enum mdoct, enum mdoc_type);
 static	int	 check_stdarg(PRE_ARGS);
 static	int	 check_text(struct mdoc *, int, int, const char *);
 static	int	 check_argv(struct mdoc *, 
@@ -57,31 +57,26 @@ static	int	 err_child_gt(struct mdoc *, const char *, int);
 static	int	 warn_child_gt(struct mdoc *, const char *, int);
 static	int	 err_child_eq(struct mdoc *, const char *, int);
 static	int	 warn_child_eq(struct mdoc *, const char *, int);
-static	int	 warn_print(struct mdoc *, int, int);
 static	int	 warn_count(struct mdoc *, const char *, 
 			int, const char *, int);
 static	int	 err_count(struct mdoc *, const char *, 
 			int, const char *, int);
 
-#ifdef __linux__
-extern	size_t	 strlcat(char *, const char *, size_t);
-#endif
-
 static	int	 berr_ge1(POST_ARGS);
 static	int	 bwarn_ge1(POST_ARGS);
 static	int	 ebool(POST_ARGS);
 static	int	 eerr_eq0(POST_ARGS);
 static	int	 eerr_eq1(POST_ARGS);
 static	int	 eerr_ge1(POST_ARGS);
-static	int	 eerr_le2(POST_ARGS);
+static	int	 eerr_le1(POST_ARGS);
 static	int	 ewarn_ge1(POST_ARGS);
 static	int	 herr_eq0(POST_ARGS);
 static	int	 herr_ge1(POST_ARGS);
 static	int	 hwarn_eq1(POST_ARGS);
+static	int	 hwarn_eq0(POST_ARGS);
 static	int	 hwarn_le1(POST_ARGS);
 
 static	int	 post_an(POST_ARGS);
-static	int	 post_args(POST_ARGS);
 static	int	 post_at(POST_ARGS);
 static	int	 post_bf(POST_ARGS);
 static	int	 post_bl(POST_ARGS);
@@ -90,23 +85,19 @@ static	int	 post_it(POST_ARGS);
 static	int	 post_lb(POST_ARGS);
 static	int	 post_nm(POST_ARGS);
 static	int	 post_root(POST_ARGS);
+static	int	 post_rs(POST_ARGS);
 static	int	 post_sh(POST_ARGS);
 static	int	 post_sh_body(POST_ARGS);
 static	int	 post_sh_head(POST_ARGS);
-static	int	 post_sp(POST_ARGS);
 static	int	 post_st(POST_ARGS);
+static	int	 post_vt(POST_ARGS);
 static	int	 pre_an(PRE_ARGS);
 static	int	 pre_bd(PRE_ARGS);
 static	int	 pre_bl(PRE_ARGS);
-static	int	 pre_cd(PRE_ARGS);
 static	int	 pre_dd(PRE_ARGS);
 static	int	 pre_display(PRE_ARGS);
 static	int	 pre_dt(PRE_ARGS);
-static	int	 pre_er(PRE_ARGS);
-static	int	 pre_ex(PRE_ARGS);
-static	int	 pre_fd(PRE_ARGS);
 static	int	 pre_it(PRE_ARGS);
-static	int	 pre_lb(PRE_ARGS);
 static	int	 pre_os(PRE_ARGS);
 static	int	 pre_rv(PRE_ARGS);
 static	int	 pre_sh(PRE_ARGS);
@@ -114,40 +105,38 @@ static	int	 pre_ss(PRE_ARGS);
 
 static	v_post	 posts_an[] = { post_an, NULL };
 static	v_post	 posts_at[] = { post_at, NULL };
-static	v_post	 posts_bd[] = { herr_eq0, bwarn_ge1, NULL };
+static	v_post	 posts_bd[] = { hwarn_eq0, bwarn_ge1, NULL };
 static	v_post	 posts_bf[] = { hwarn_le1, post_bf, NULL };
 static	v_post	 posts_bl[] = { bwarn_ge1, post_bl, NULL };
 static	v_post	 posts_bool[] = { eerr_eq1, ebool, NULL };
-static	v_post	 posts_ex[] = { eerr_eq0, post_args, NULL };
 static	v_post	 posts_fo[] = { hwarn_eq1, bwarn_ge1, NULL };
-static	v_post	 posts_in[] = { eerr_eq1, NULL };
 static	v_post	 posts_it[] = { post_it, NULL };
 static	v_post	 posts_lb[] = { eerr_eq1, post_lb, NULL };
 static	v_post	 posts_nd[] = { berr_ge1, NULL };
 static	v_post	 posts_nm[] = { post_nm, NULL };
 static	v_post	 posts_notext[] = { eerr_eq0, NULL };
-static	v_post	 posts_pf[] = { eerr_eq1, NULL };
-static	v_post	 posts_rv[] = { eerr_eq0, post_args, NULL };
+static	v_post	 posts_rs[] = { berr_ge1, herr_eq0, post_rs, NULL };
 static	v_post	 posts_sh[] = { herr_ge1, bwarn_ge1, post_sh, NULL };
-static	v_post	 posts_sp[] = { post_sp, NULL };
+static	v_post	 posts_sp[] = { eerr_le1, NULL };
 static	v_post	 posts_ss[] = { herr_ge1, NULL };
 static	v_post	 posts_st[] = { eerr_eq1, post_st, NULL };
 static	v_post	 posts_text[] = { eerr_ge1, NULL };
+static	v_post	 posts_text1[] = { eerr_eq1, NULL };
+static	v_post	 posts_vt[] = { post_vt, NULL };
 static	v_post	 posts_wline[] = { bwarn_ge1, herr_eq0, NULL };
 static	v_post	 posts_wtext[] = { ewarn_ge1, NULL };
-static	v_post	 posts_xr[] = { eerr_ge1, eerr_le2, NULL };
+static	v_post	 posts_xr[] = { ewarn_ge1, NULL };
 static	v_pre	 pres_an[] = { pre_an, NULL };
 static	v_pre	 pres_bd[] = { pre_display, pre_bd, NULL };
 static	v_pre	 pres_bl[] = { pre_bl, NULL };
-static	v_pre	 pres_cd[] = { pre_cd, NULL };
 static	v_pre	 pres_d1[] = { pre_display, NULL };
 static	v_pre	 pres_dd[] = { pre_dd, NULL };
 static	v_pre	 pres_dt[] = { pre_dt, NULL };
-static	v_pre	 pres_er[] = { pre_er, NULL };
-static	v_pre	 pres_ex[] = { pre_ex, NULL };
-static	v_pre	 pres_fd[] = { pre_fd, NULL };
+static	v_pre	 pres_er[] = { NULL, NULL };
+static	v_pre	 pres_ex[] = { NULL, NULL };
+static	v_pre	 pres_fd[] = { NULL, NULL };
 static	v_pre	 pres_it[] = { pre_it, NULL };
-static	v_pre	 pres_lb[] = { pre_lb, NULL };
+static	v_pre	 pres_lb[] = { NULL, NULL };
 static	v_pre	 pres_os[] = { pre_os, NULL };
 static	v_pre	 pres_rv[] = { pre_rv, NULL };
 static	v_pre	 pres_sh[] = { pre_sh, NULL };
@@ -171,40 +160,40 @@ const	struct valids mdoc_valids[MDOC_MAX] = {
 	{ NULL, posts_text },			/* Ad */ 
 	{ pres_an, posts_an },			/* An */ 
 	{ NULL, NULL },				/* Ar */
-	{ pres_cd, posts_text },		/* Cd */ 
+	{ NULL, posts_text },			/* Cd */ 
 	{ NULL, NULL },				/* Cm */
 	{ NULL, NULL },				/* Dv */ 
 	{ pres_er, posts_text },		/* Er */ 
 	{ NULL, NULL },				/* Ev */ 
-	{ pres_ex, posts_ex },			/* Ex */ 
+	{ pres_ex, NULL },			/* Ex */ 
 	{ NULL, NULL },				/* Fa */ 
 	{ pres_fd, posts_wtext },		/* Fd */
 	{ NULL, NULL },				/* Fl */
 	{ NULL, posts_text },			/* Fn */ 
 	{ NULL, posts_wtext },			/* Ft */ 
 	{ NULL, posts_text },			/* Ic */ 
-	{ NULL, posts_in },			/* In */ 
+	{ NULL, posts_text1 },			/* In */ 
 	{ NULL, NULL },				/* Li */
 	{ NULL, posts_nd },			/* Nd */
 	{ NULL, posts_nm },			/* Nm */
 	{ NULL, posts_wline },			/* Op */
 	{ NULL, NULL },				/* Ot */
 	{ NULL, NULL },				/* Pa */
-	{ pres_rv, posts_rv },			/* Rv */
+	{ pres_rv, NULL },			/* Rv */
 	{ NULL, posts_st },			/* St */ 
 	{ NULL, NULL },				/* Va */
-	{ NULL, posts_text },			/* Vt */ 
+	{ NULL, posts_vt },			/* Vt */ 
 	{ NULL, posts_xr },			/* Xr */ 
 	{ NULL, posts_text },			/* %A */
-	{ NULL, posts_text },			/* %B */
-	{ NULL, posts_text },			/* %D */
+	{ NULL, posts_text },			/* %B */ /* FIXME: can be used outside Rs/Re. */
+	{ NULL, posts_text },			/* %D */ /* FIXME: check date with mandoc_a2time(). */
 	{ 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 },			/* %T */ /* FIXME: can be used outside Rs/Re. */
 	{ NULL, posts_text },			/* %V */
 	{ NULL, NULL },				/* Ac */
 	{ NULL, NULL },				/* Ao */
@@ -231,7 +220,7 @@ const	struct valids mdoc_valids[MDOC_MAX] = {
 	{ NULL, NULL },				/* Nx */
 	{ NULL, NULL },				/* Ox */
 	{ NULL, NULL },				/* Pc */
-	{ NULL, posts_pf },			/* Pf */
+	{ NULL, posts_text1 },			/* Pf */
 	{ NULL, NULL },				/* Po */
 	{ NULL, posts_wline },			/* Pq */
 	{ NULL, NULL },				/* Qc */
@@ -239,7 +228,7 @@ const	struct valids mdoc_valids[MDOC_MAX] = {
 	{ NULL, NULL },				/* Qo */
 	{ NULL, posts_wline },			/* Qq */
 	{ NULL, NULL },				/* Re */
-	{ NULL, posts_wline },			/* Rs */
+	{ NULL, posts_rs },			/* Rs */
 	{ NULL, NULL },				/* Sc */
 	{ NULL, NULL },				/* So */
 	{ NULL, posts_wline },			/* Sq */
@@ -262,7 +251,7 @@ const	struct valids mdoc_valids[MDOC_MAX] = {
 	{ NULL, posts_notext },			/* Ud */
 	{ pres_lb, posts_lb },			/* Lb */
 	{ NULL, posts_notext },			/* Lp */ 
-	{ NULL, NULL },				/* Lk */ 
+	{ NULL, posts_text },			/* Lk */ 
 	{ NULL, posts_text },			/* Mt */ 
 	{ NULL, posts_wline },			/* Brq */ 
 	{ NULL, NULL },				/* Bro */ 
@@ -274,6 +263,7 @@ const	struct valids mdoc_valids[MDOC_MAX] = {
 	{ NULL, posts_text },			/* %Q */
 	{ NULL, posts_notext },			/* br */
 	{ NULL, posts_sp },			/* sp */
+	{ NULL, posts_text1 },			/* %U */
 };
 
 
@@ -326,16 +316,6 @@ mdoc_valid_post(struct mdoc *mdoc)
 }
 
 
-static int
-warn_print(struct mdoc *m, int ln, int pos)
-{
-
-	if (MDOC_IGN_CHARS & m->pflags)
-		return(mdoc_pwarn(m, ln, pos, EPRINT));
-	return(mdoc_perr(m, ln, pos, EPRINT));
-}
-
-
 static inline int
 warn_count(struct mdoc *m, const char *k, 
 		int want, const char *v, int has)
@@ -408,13 +388,14 @@ CHECK_BODY_DEFN(ge1, warn, warn_child_gt, 0)	/* bwarn_ge1() */
 CHECK_BODY_DEFN(ge1, err, err_child_gt, 0)	/* berr_ge1() */
 CHECK_ELEM_DEFN(ge1, warn, warn_child_gt, 0)	/* ewarn_gt1() */
 CHECK_ELEM_DEFN(eq1, err, err_child_eq, 1)	/* eerr_eq1() */
-CHECK_ELEM_DEFN(le2, err, err_child_lt, 3)	/* eerr_le2() */
+CHECK_ELEM_DEFN(le1, err, err_child_lt, 2)	/* eerr_le1() */
 CHECK_ELEM_DEFN(eq0, err, err_child_eq, 0)	/* eerr_eq0() */
 CHECK_ELEM_DEFN(ge1, err, err_child_gt, 0)	/* eerr_ge1() */
 CHECK_HEAD_DEFN(eq0, err, err_child_eq, 0)	/* herr_eq0() */
 CHECK_HEAD_DEFN(le1, warn, warn_child_lt, 2)	/* hwarn_le1() */
 CHECK_HEAD_DEFN(ge1, err, err_child_gt, 0)	/* herr_ge1() */
 CHECK_HEAD_DEFN(eq1, warn, warn_child_eq, 1)	/* hwarn_eq1() */
+CHECK_HEAD_DEFN(eq0, warn, warn_child_eq, 0)	/* hwarn_eq0() */
 
 
 static int
@@ -428,52 +409,6 @@ check_stdarg(PRE_ARGS)
 }
 
 
-static int
-check_sec(PRE_ARGS, ...)
-{
-	enum mdoc_sec	 sec;
-	va_list		 ap;
-
-	va_start(ap, n);
-
-	for (;;) {
-		/* LINTED */
-		sec = (enum mdoc_sec)va_arg(ap, int);
-		if (SEC_CUSTOM == sec)
-			break;
-		if (sec != mdoc->lastsec)
-			continue;
-		va_end(ap);
-		return(1);
-	}
-
-	va_end(ap);
-	return(mdoc_nwarn(mdoc, n, EBADSEC));
-}
-
-
-static int
-check_msec(PRE_ARGS, ...)
-{
-	va_list		 ap;
-	int		 msec;
-
-	va_start(ap, n);
-	for (;;) {
-		/* LINTED */
-		if (0 == (msec = va_arg(ap, int)))
-			break;
-		if (msec != mdoc->meta.msec)
-			continue;
-		va_end(ap);
-		return(1);
-	}
-
-	va_end(ap);
-	return(mdoc_nwarn(mdoc, n, EBADMSEC));
-}
-
-
 static int
 check_args(struct mdoc *m, const struct mdoc_node *n)
 {
@@ -520,10 +455,10 @@ check_text(struct mdoc *mdoc, int line, int pos, const char *p)
 	for ( ; *p; p++, pos++) {
 		if ('\t' == *p) {
 			if ( ! (MDOC_LITERAL & mdoc->flags))
-				if ( ! warn_print(mdoc, line, pos))
+				if ( ! mdoc_pwarn(mdoc, line, pos, EPRINT))
 					return(0);
 		} else if ( ! isprint((u_char)*p))
-			if ( ! warn_print(mdoc, line, pos))
+			if ( ! mdoc_pwarn(mdoc, line, pos, EPRINT))
 				return(0);
 
 		if ('\\' != *p)
@@ -548,7 +483,7 @@ check_text(struct mdoc *mdoc, int line, int pos, const char *p)
 
 
 static int
-check_parent(PRE_ARGS, int tok, enum mdoc_type t)
+check_parent(PRE_ARGS, enum mdoct tok, enum mdoc_type t)
 {
 
 	assert(n->parent);
@@ -622,25 +557,46 @@ pre_bl(PRE_ARGS)
 		case (MDOC_Inset):
 			/* FALLTHROUGH */
 		case (MDOC_Column):
-			if (-1 != type) 
-				return(mdoc_nerr(mdoc, n, EMULTILIST));
+			/*
+			 * Note that if a duplicate is detected, we
+			 * remove the duplicate instead of passing it
+			 * over.  If we don't do this, mdoc_action will
+			 * become confused when it scans over multiple
+			 * types whilst setting its bitmasks.
+			 *
+			 * FIXME: this should occur in mdoc_action.c.
+			 */
+			if (type >= 0) {
+				if ( ! mdoc_nwarn(mdoc, n, EMULTILIST))
+					return(0);
+				mdoc_argn_free(n->args, pos);
+				break;
+			}
 			type = n->args->argv[pos].arg;
 			break;
+		case (MDOC_Compact):
+			if (type < 0 && ! mdoc_nwarn(mdoc, n, ENOTYPE))
+				return(0);
+			break;
 		case (MDOC_Width):
-			if (-1 != width)
+			if (width >= 0)
 				return(mdoc_nerr(mdoc, n, EARGREP));
+			if (type < 0 && ! mdoc_nwarn(mdoc, n, ENOTYPE))
+				return(0);
 			width = n->args->argv[pos].arg;
 			break;
 		case (MDOC_Offset):
-			if (-1 != offset)
+			if (offset >= 0)
 				return(mdoc_nerr(mdoc, n, EARGREP));
+			if (type < 0 && ! mdoc_nwarn(mdoc, n, ENOTYPE))
+				return(0);
 			offset = n->args->argv[pos].arg;
 			break;
 		default:
 			break;
 		}
 
-	if (-1 == type)
+	if (type < 0)
 		return(mdoc_nerr(mdoc, n, ELISTTYPE));
 
 	/* 
@@ -651,17 +607,19 @@ pre_bl(PRE_ARGS)
 
 	switch (type) {
 	case (MDOC_Tag):
-		if (-1 == width && ! mdoc_nwarn(mdoc, n, EMISSWIDTH))
+		if (width < 0 && ! mdoc_nwarn(mdoc, n, EMISSWIDTH))
 			return(0);
 		break;
 	case (MDOC_Column):
 		/* FALLTHROUGH */
 	case (MDOC_Diag):
 		/* FALLTHROUGH */
+	case (MDOC_Ohang):
+		/* FALLTHROUGH */
 	case (MDOC_Inset):
 		/* FALLTHROUGH */
 	case (MDOC_Item):
-		if (-1 != width && ! mdoc_nwarn(mdoc, n, ENOWIDTH))
+		if (width >= 0 && ! mdoc_nwarn(mdoc, n, ENOWIDTH))
 			return(0);
 		break;
 	default:
@@ -688,6 +646,8 @@ pre_bd(PRE_ARGS)
 	for (i = 0, err = type = 0; ! err && 
 			i < (int)n->args->argc; i++)
 		switch (n->args->argv[i].arg) {
+		case (MDOC_Centred):
+			/* FALLTHROUGH */
 		case (MDOC_Ragged):
 			/* FALLTHROUGH */
 		case (MDOC_Unfilled):
@@ -695,8 +655,6 @@ pre_bd(PRE_ARGS)
 		case (MDOC_Filled):
 			/* FALLTHROUGH */
 		case (MDOC_Literal):
-			/* FALLTHROUGH */
-		case (MDOC_File):
 			if (0 == type++) 
 				break;
 			return(mdoc_nerr(mdoc, n, EMULTIDISP));
@@ -726,7 +684,7 @@ pre_sh(PRE_ARGS)
 
 	if (MDOC_BLOCK != n->type)
 		return(1);
-	return(check_parent(mdoc, n, -1, MDOC_ROOT));
+	return(check_parent(mdoc, n, MDOC_MAX, MDOC_ROOT));
 }
 
 
@@ -751,54 +709,20 @@ pre_an(PRE_ARGS)
 }
 
 
-static int
-pre_lb(PRE_ARGS)
-{
-
-	return(check_sec(mdoc, n, SEC_LIBRARY, SEC_CUSTOM));
-}
-
-
 static int
 pre_rv(PRE_ARGS)
 {
 
-	if ( ! check_msec(mdoc, n, 2, 3, 0))
-		return(0);
-	return(check_stdarg(mdoc, n));
-}
-
-
-static int
-pre_ex(PRE_ARGS)
-{
-
-	if ( ! check_msec(mdoc, n, 1, 6, 8, 0))
-		return(0);
 	return(check_stdarg(mdoc, n));
 }
 
 
-static int
-pre_er(PRE_ARGS)
-{
-
-	return(check_msec(mdoc, n, 2, 3, 9, 0));
-}
-
-
-static int
-pre_cd(PRE_ARGS)
-{
-
-	return(check_msec(mdoc, n, 4, 0));
-}
-
-
 static int
 pre_dt(PRE_ARGS)
 {
 
+	/* FIXME: make sure is capitalised. */
+
 	if (0 == mdoc->meta.date || mdoc->meta.os)
 		if ( ! mdoc_nwarn(mdoc, n, EPROLOOO))
 			return(0);
@@ -879,6 +803,32 @@ post_lb(POST_ARGS)
 }
 
 
+static int
+post_vt(POST_ARGS)
+{
+	const struct mdoc_node *n;
+
+	/*
+	 * The Vt macro comes in both ELEM and BLOCK form, both of which
+	 * have different syntaxes (yet more context-sensitive
+	 * behaviour).  ELEM types must have a child; BLOCK types,
+	 * specifically the BODY, should only have TEXT children.
+	 */
+
+	if (MDOC_ELEM == mdoc->last->type)
+		return(eerr_ge1(mdoc));
+	if (MDOC_BODY != mdoc->last->type)
+		return(1);
+	
+	for (n = mdoc->last->child; n; n = n->next)
+		if (MDOC_TEXT != n->type) 
+			if ( ! mdoc_nwarn(mdoc, n, EBADCHILD))
+				return(0);
+
+	return(1);
+}
+
+
 static int
 post_nm(POST_ARGS)
 {
@@ -901,7 +851,7 @@ post_at(POST_ARGS)
 		return(mdoc_nerr(mdoc, mdoc->last, EATT));
 	if (mdoc_a2att(mdoc->last->child->string))
 		return(1);
-	return(mdoc_nerr(mdoc, mdoc->last, EATT));
+	return(mdoc_nwarn(mdoc, mdoc->last, EATT));
 }
 
 
@@ -921,16 +871,6 @@ post_an(POST_ARGS)
 }
 
 
-static int
-post_args(POST_ARGS)
-{
-
-	if (mdoc->last->args)
-		return(1);
-	return(mdoc_nerr(mdoc, mdoc->last, ELINE));
-}
-
-
 static int
 post_it(POST_ARGS)
 {
@@ -1029,14 +969,14 @@ post_it(POST_ARGS)
 		for (i = 0; c && MDOC_HEAD == c->type; c = c->next)
 			i++;
 
-		if (i < cols || i == (cols + 1)) {
+		if (i < cols) {
 			if ( ! mdoc_vwarn(mdoc, mdoc->last->line, 
 					mdoc->last->pos, "column "
 					"mismatch: have %d, want %d", 
 					i, cols))
 				return(0);
 			break;
-		} else if (i == cols)
+		} else if (i == cols || i == cols + 1)
 			break;
 
 		return(mdoc_verr(mdoc, mdoc->last->line, 
@@ -1055,21 +995,23 @@ post_bl_head(POST_ARGS)
 {
 	int			i;
 	const struct mdoc_node *n;
+	const struct mdoc_argv *a;
 
 	n = mdoc->last->parent;
 	assert(n->args);
 
-	for (i = 0; i < (int)n->args->argc; i++)
-		if (n->args->argv[i].arg == MDOC_Column)
-			break;
+	for (i = 0; i < (int)n->args->argc; i++) {
+		a = &n->args->argv[i];
+		if (a->arg == MDOC_Column) {
+			if (a->sz && mdoc->last->nchild)
+				return(mdoc_nerr(mdoc, n, ECOLMIS));
+			return(1);
+		}
+	}
 
-	if (i == (int)n->args->argc)
+	if (0 == (i = mdoc->last->nchild))
 		return(1);
-
-	if (n->args->argv[i].sz && mdoc->last->child)
-		return(mdoc_nerr(mdoc, n, ECOLMIS));
-
-	return(1);
+	return(warn_count(mdoc, "==", 0, "line arguments", i));
 }
 
 
@@ -1085,14 +1027,19 @@ post_bl(POST_ARGS)
 	if (NULL == mdoc->last->child)
 		return(1);
 
+	/*
+	 * We only allow certain children of `Bl'.  This is usually on
+	 * `It', but apparently `Sm' occurs here and there, so we let
+	 * that one through, too.
+	 */
+
 	/* LINTED */
 	for (n = mdoc->last->child; n; n = n->next) {
-		if (MDOC_BLOCK == n->type) 
-			if (MDOC_It == n->tok)
-				continue;
-		return(mdoc_verr(mdoc, n->line, n->pos, 
-				"bad child of parent %s",
-				mdoc_macronames[mdoc->last->tok]));
+		if (MDOC_BLOCK == n->type && MDOC_It == n->tok)
+			continue;
+		if (MDOC_Sm == n->tok)
+			continue;
+		return(mdoc_nerr(mdoc, n, EBADCHILD));
 	}
 
 	return(1);
@@ -1140,43 +1087,58 @@ post_root(POST_ARGS)
 
 
 static int
-post_sp(POST_ARGS)
+post_st(POST_ARGS)
 {
-	long		 lval;
-	char		*ep, *buf;
 
-	if (NULL == mdoc->last->child)
+	if (mdoc_a2st(mdoc->last->child->string))
 		return(1);
-	else if ( ! eerr_eq1(mdoc))
-		return(0);
-
-	assert(MDOC_TEXT == mdoc->last->child->type);
-	buf = mdoc->last->child->string;
-	assert(buf);
-	
-	/* From OpenBSD's strtol(3). */
-	errno = 0;
-	lval = strtol(buf, &ep, 10);
-	if (buf[0] == '\0' || *ep != '\0')
-		return(mdoc_nerr(mdoc, mdoc->last->child, ENUMFMT));
-
-	if ((errno == ERANGE && (lval == LONG_MAX || lval == LONG_MIN)) ||
-			(lval > INT_MAX || lval < 0))
-		return(mdoc_nerr(mdoc, mdoc->last->child, ENUMFMT));
-
-	return(1);
+	return(mdoc_nwarn(mdoc, mdoc->last, EBADSTAND));
 }
 
 
-
-
 static int
-post_st(POST_ARGS)
+post_rs(POST_ARGS)
 {
+	struct mdoc_node	*nn;
 
-	if (mdoc_a2st(mdoc->last->child->string))
+	if (MDOC_BODY != mdoc->last->type)
 		return(1);
-	return(mdoc_nerr(mdoc, mdoc->last, EBADSTAND));
+
+	for (nn = mdoc->last->child; nn; nn = nn->next)
+		switch (nn->tok) {
+		case(MDOC__U):
+			/* FALLTHROUGH */
+		case(MDOC__Q):
+			/* FALLTHROUGH */
+		case(MDOC__C):
+			/* FALLTHROUGH */
+		case(MDOC__A):
+			/* FALLTHROUGH */
+		case(MDOC__B):
+			/* FALLTHROUGH */
+		case(MDOC__D):
+			/* FALLTHROUGH */
+		case(MDOC__I):
+			/* FALLTHROUGH */
+		case(MDOC__J):
+			/* FALLTHROUGH */
+		case(MDOC__N):
+			/* FALLTHROUGH */
+		case(MDOC__O):
+			/* FALLTHROUGH */
+		case(MDOC__P):
+			/* FALLTHROUGH */
+		case(MDOC__R):
+			/* FALLTHROUGH */
+		case(MDOC__T):
+			/* FALLTHROUGH */
+		case(MDOC__V):
+			break;
+		default:
+			return(mdoc_nerr(mdoc, nn, EBADCHILD));
+		}
+
+	return(1);
 }
 
 
@@ -1219,6 +1181,7 @@ post_sh_body(POST_ARGS)
 			return(0);
 	}
 
+	assert(n);
 	if (MDOC_BLOCK == n->type && MDOC_Nd == n->tok)
 		return(1);
 	return(mdoc_nwarn(mdoc, mdoc->last, ENAMESECINC));
@@ -1253,7 +1216,7 @@ post_sh_head(POST_ARGS)
 			return(mdoc_nerr(mdoc, n, ETOOLONG));
 	}
 
-	sec = mdoc_atosec(buf);
+	sec = mdoc_str2sec(buf);
 
 	/* 
 	 * Check: NAME should always be first, CUSTOM has no roles,
@@ -1261,43 +1224,38 @@ post_sh_head(POST_ARGS)
 	 */
 
 	if (SEC_NAME != sec && SEC_NONE == mdoc->lastnamed)
-		return(mdoc_nerr(mdoc, mdoc->last, ESECNAME));
+		if ( ! mdoc_nwarn(mdoc, mdoc->last, ESECNAME))
+			return(0);
+
 	if (SEC_CUSTOM == sec)
 		return(1);
+
 	if (sec == mdoc->lastnamed)
 		if ( ! mdoc_nwarn(mdoc, mdoc->last, ESECREP))
 			return(0);
+
 	if (sec < mdoc->lastnamed)
 		if ( ! mdoc_nwarn(mdoc, mdoc->last, ESECOOO))
 			return(0);
 
 	/* 
 	 * Check particular section/manual conventions.  LIBRARY can
-	 * only occur in msec 2, 3 (TODO: are there more of these?).
+	 * only occur in manual section 2, 3, and 9.
 	 */
 
 	switch (sec) {
 	case (SEC_LIBRARY):
-		switch (mdoc->meta.msec) {
-		case (2):
-			/* FALLTHROUGH */
-		case (3):
+		assert(mdoc->meta.msec);
+		if (*mdoc->meta.msec == '2')
 			break;
-		default:
-			return(mdoc_nwarn(mdoc, mdoc->last, EWRONGMSEC));
-		}
-		break;
+		if (*mdoc->meta.msec == '3')
+			break;
+		if (*mdoc->meta.msec == '9')
+			break;
+		return(mdoc_nwarn(mdoc, mdoc->last, EWRONGMSEC));
 	default:
 		break;
 	}
 
 	return(1);
 }
-
-
-static int
-pre_fd(PRE_ARGS)
-{
-
-	return(check_sec(mdoc, n, SEC_SYNOPSIS, SEC_CUSTOM));
-}