[BACK]Return to mdoc_validate.c CVS log [TXT][DIR] Up to [local] / src / usr.bin / mandoc

File: [local] / src / usr.bin / mandoc / mdoc_validate.c (download)

Revision 1.43, Tue Mar 2 00:38:59 2010 UTC (14 years, 3 months ago) by schwarze
Branch: MAIN
CVS Tags: OPENBSD_4_7_BASE, OPENBSD_4_7
Changes since 1.42: +4 -2 lines

Proper inter-sentence spacing for mdoc(7).
When a text line or a non-block macro line in the source code ends
in any of ".!?", consider that an end of sentence (EOS).
This makes Jason's rule "new sentence, new line" even more important.
Let the parser detect the EOS and insert a token into the AST.
Let the -Tascii frontend render the EOS token as a double space before
the next word.

/*	$Id: mdoc_validate.c,v 1.43 2010/03/02 00:38:59 schwarze Exp $ */
/*
 * Copyright (c) 2008, 2009 Kristaps Dzonsons <kristaps@kth.se>
 *
 * Permission to use, copy, modify, and distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */
#include <sys/types.h>

#include <assert.h>
#include <ctype.h>
#include <limits.h>
#include <stdarg.h>
#include <stdlib.h>
#include <string.h>

#include "libmdoc.h"
#include "libmandoc.h"

/* FIXME: .Bl -diag can't have non-text children in HEAD. */
/* TODO: ignoring Pp (it's superfluous in some invocations). */

#define	PRE_ARGS  struct mdoc *mdoc, const struct mdoc_node *n
#define	POST_ARGS struct mdoc *mdoc

typedef	int	(*v_pre)(PRE_ARGS);
typedef	int	(*v_post)(POST_ARGS);

struct	valids {
	v_pre	*pre;
	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_stdarg(PRE_ARGS);
static	int	 check_text(struct mdoc *, int, int, const char *);
static	int	 check_argv(struct mdoc *, 
			const struct mdoc_node *,
			const struct mdoc_argv *);
static	int	 check_args(struct mdoc *, 
			const struct mdoc_node *);
static	int	 err_child_lt(struct mdoc *, const char *, int);
static	int	 warn_child_lt(struct mdoc *, const char *, int);
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);

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_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_le1(POST_ARGS);

static	int	 post_an(POST_ARGS);
static	int	 post_at(POST_ARGS);
static	int	 post_bf(POST_ARGS);
static	int	 post_bl(POST_ARGS);
static	int	 post_bl_head(POST_ARGS);
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_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);
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_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_fo[] = { hwarn_eq1, bwarn_ge1, 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_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[] = { 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, 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_it[] = { pre_it, NULL };
static	v_pre	 pres_lb[] = { pre_lb, 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 };
static	v_pre	 pres_ss[] = { pre_ss, NULL };

const	struct valids mdoc_valids[MDOC_MAX] = {
	{ NULL, NULL },				/* Ap */
	{ pres_dd, posts_text },		/* Dd */
	{ pres_dt, NULL },			/* Dt */
	{ pres_os, NULL },			/* Os */
	{ pres_sh, posts_sh },			/* Sh */ 
	{ pres_ss, posts_ss },			/* Ss */ 
	{ NULL, posts_notext },			/* Pp */ 
	{ pres_d1, posts_wline },		/* D1 */
	{ pres_d1, posts_wline },		/* Dl */
	{ pres_bd, posts_bd },			/* Bd */
	{ NULL, NULL },				/* Ed */
	{ pres_bl, posts_bl },			/* Bl */ 
	{ NULL, NULL },				/* El */
	{ pres_it, posts_it },			/* It */
	{ NULL, posts_text },			/* Ad */ 
	{ pres_an, posts_an },			/* An */ 
	{ NULL, NULL },				/* Ar */
	{ pres_cd, posts_text },		/* Cd */ 
	{ NULL, NULL },				/* Cm */
	{ NULL, NULL },				/* Dv */ 
	{ pres_er, posts_text },		/* Er */ 
	{ NULL, NULL },				/* Ev */ 
	{ 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_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, NULL },			/* Rv */
	{ NULL, posts_st },			/* St */ 
	{ NULL, NULL },				/* Va */
	{ NULL, posts_vt },			/* Vt */ 
	{ NULL, posts_xr },			/* Xr */ 
	{ NULL, posts_text },			/* %A */
	{ 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 */ /* FIXME: can be used outside Rs/Re. */
	{ NULL, posts_text },			/* %V */
	{ NULL, NULL },				/* Ac */
	{ NULL, NULL },				/* Ao */
	{ NULL, posts_wline },			/* Aq */
	{ NULL, posts_at },			/* At */ 
	{ NULL, NULL },				/* Bc */
	{ NULL, posts_bf },			/* Bf */
	{ NULL, NULL },				/* Bo */
	{ NULL, posts_wline },			/* Bq */
	{ NULL, NULL },				/* Bsx */
	{ NULL, NULL },				/* Bx */
	{ NULL, posts_bool },			/* Db */
	{ NULL, NULL },				/* Dc */
	{ NULL, NULL },				/* Do */
	{ NULL, posts_wline },			/* Dq */
	{ NULL, NULL },				/* Ec */
	{ NULL, NULL },				/* Ef */ 
	{ NULL, NULL },				/* Em */ 
	{ NULL, NULL },				/* Eo */
	{ NULL, NULL },				/* Fx */
	{ NULL, posts_text },			/* Ms */ 
	{ NULL, posts_notext },			/* No */
	{ NULL, posts_notext },			/* Ns */
	{ NULL, NULL },				/* Nx */
	{ NULL, NULL },				/* Ox */
	{ NULL, NULL },				/* Pc */
	{ NULL, posts_text1 },			/* Pf */
	{ NULL, NULL },				/* Po */
	{ NULL, posts_wline },			/* Pq */
	{ NULL, NULL },				/* Qc */
	{ NULL, posts_wline },			/* Ql */
	{ NULL, NULL },				/* Qo */
	{ NULL, posts_wline },			/* Qq */
	{ NULL, NULL },				/* Re */
	{ NULL, posts_rs },			/* Rs */
	{ NULL, NULL },				/* Sc */
	{ NULL, NULL },				/* So */
	{ 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 */
	{ NULL, posts_fo },			/* Fo */ 
	{ NULL, NULL },				/* Fc */ 
	{ NULL, NULL },				/* Oo */
	{ NULL, NULL },				/* Oc */
	{ NULL, posts_wline },			/* Bk */
	{ NULL, NULL },				/* Ek */
	{ NULL, posts_notext },			/* Bt */
	{ NULL, NULL },				/* Hf */
	{ NULL, NULL },				/* Fr */
	{ NULL, posts_notext },			/* Ud */
	{ pres_lb, posts_lb },			/* Lb */
	{ NULL, posts_notext },			/* Lp */ 
	{ NULL, posts_text },			/* Lk */ 
	{ NULL, posts_text },			/* Mt */ 
	{ NULL, posts_wline },			/* Brq */ 
	{ NULL, NULL },				/* Bro */ 
	{ NULL, NULL },				/* Brc */ 
	{ NULL, posts_text },			/* %C */
	{ NULL, NULL },				/* Es */
	{ NULL, NULL },				/* En */
	{ NULL, NULL },				/* Dx */
	{ NULL, posts_text },			/* %Q */
	{ NULL, posts_notext },			/* br */
	{ NULL, posts_sp },			/* sp */
	{ NULL, posts_text1 },			/* %U */
	{ NULL, NULL },				/* eos */
};


int
mdoc_valid_pre(struct mdoc *mdoc, const struct mdoc_node *n)
{
	v_pre		*p;
	int		 line, pos;
	const char	*tp;

	if (MDOC_TEXT == n->type) {
		tp = n->string;
		line = n->line;
		pos = n->pos;
		return(check_text(mdoc, line, pos, tp));
	}

	if ( ! check_args(mdoc, n))
		return(0);
	if (NULL == mdoc_valids[n->tok].pre)
		return(1);
	for (p = mdoc_valids[n->tok].pre; *p; p++)
		if ( ! (*p)(mdoc, n)) 
			return(0);
	return(1);
}


int
mdoc_valid_post(struct mdoc *mdoc)
{
	v_post		*p;

	if (MDOC_VALID & mdoc->last->flags)
		return(1);
	mdoc->last->flags |= MDOC_VALID;

	if (MDOC_TEXT == mdoc->last->type)
		return(1);
	if (MDOC_ROOT == mdoc->last->type)
		return(post_root(mdoc));

	if (NULL == mdoc_valids[mdoc->last->tok].post)
		return(1);
	for (p = mdoc_valids[mdoc->last->tok].post; *p; p++)
		if ( ! (*p)(mdoc)) 
			return(0);

	return(1);
}


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)
{

	return(mdoc_vwarn(m, m->last->line, m->last->pos, 
		"suggests %s %s %d (has %d)", v, k, want, has));
}


static inline int
err_count(struct mdoc *m, const char *k,
		int want, const char *v, int has)
{

	return(mdoc_verr(m, m->last->line, m->last->pos,
		"requires %s %s %d (has %d)", v, k, want, has));
}


/*
 * Build these up with macros because they're basically the same check
 * for different inequalities.  Yes, this could be done with functions,
 * but this is reasonable for now.
 */

#define CHECK_CHILD_DEFN(lvl, name, ineq) 			\
static int 							\
lvl##_child_##name(struct mdoc *mdoc, const char *p, int sz) 	\
{ 								\
	if (mdoc->last->nchild ineq sz)				\
		return(1); 					\
	return(lvl##_count(mdoc, #ineq, sz, p, mdoc->last->nchild)); \
}

#define CHECK_BODY_DEFN(name, lvl, func, num) 			\
static int 							\
b##lvl##_##name(POST_ARGS) 					\
{ 								\
	if (MDOC_BODY != mdoc->last->type) 			\
		return(1); 					\
	return(func(mdoc, "multi-line arguments", (num))); 	\
}

#define CHECK_ELEM_DEFN(name, lvl, func, num) 			\
static int							\
e##lvl##_##name(POST_ARGS) 					\
{ 								\
	assert(MDOC_ELEM == mdoc->last->type); 			\
	return(func(mdoc, "line arguments", (num))); 		\
}

#define CHECK_HEAD_DEFN(name, lvl, func, num)			\
static int 							\
h##lvl##_##name(POST_ARGS) 					\
{ 								\
	if (MDOC_HEAD != mdoc->last->type) 			\
		return(1); 					\
	return(func(mdoc, "line arguments", (num)));	 	\
}


CHECK_CHILD_DEFN(warn, gt, >)			/* warn_child_gt() */
CHECK_CHILD_DEFN(err, gt, >)			/* err_child_gt() */
CHECK_CHILD_DEFN(warn, eq, ==)			/* warn_child_eq() */
CHECK_CHILD_DEFN(err, eq, ==)			/* err_child_eq() */
CHECK_CHILD_DEFN(err, lt, <)			/* err_child_lt() */
CHECK_CHILD_DEFN(warn, lt, <)			/* warn_child_lt() */
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(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() */


static int
check_stdarg(PRE_ARGS)
{

	if (n->args && 1 == n->args->argc)
		if (MDOC_Std == n->args->argv[0].arg)
			return(1);
	return(mdoc_nwarn(mdoc, n, EARGVAL));
}


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)
{
	int		 i;

	if (NULL == n->args)
		return(1);

	assert(n->args->argc);
	for (i = 0; i < (int)n->args->argc; i++)
		if ( ! check_argv(m, n, &n->args->argv[i]))
			return(0);

	return(1);
}


static int
check_argv(struct mdoc *m, const struct mdoc_node *n, 
		const struct mdoc_argv *v)
{
	int		 i;

	for (i = 0; i < (int)v->sz; i++)
		if ( ! check_text(m, v->line, v->pos, v->value[i]))
			return(0);

	if (MDOC_Std == v->arg) {
		/* `Nm' name must be set. */
		if (v->sz || m->meta.name)
			return(1);
		return(mdoc_nerr(m, n, ENAME));
	}

	return(1);
}


static int
check_text(struct mdoc *mdoc, int line, int pos, const char *p)
{
	int		 c;

	for ( ; *p; p++, pos++) {
		if ('\t' == *p) {
			if ( ! (MDOC_LITERAL & mdoc->flags))
				if ( ! warn_print(mdoc, line, pos))
					return(0);
		} else if ( ! isprint((u_char)*p))
			if ( ! warn_print(mdoc, line, pos))
				return(0);

		if ('\\' != *p)
			continue;

		c = mandoc_special(p);
		if (c) {
			p += c - 1;
			pos += c - 1;
			continue;
		}
		if ( ! (MDOC_IGN_ESCAPE & mdoc->pflags))
			return(mdoc_perr(mdoc, line, pos, EESCAPE));
		if ( ! mdoc_pwarn(mdoc, line, pos, EESCAPE))
			return(0);
	}

	return(1);
}




static int
check_parent(PRE_ARGS, int tok, enum mdoc_type t)
{

	assert(n->parent);
	if ((MDOC_ROOT == t || tok == n->parent->tok) &&
			(t == n->parent->type))
		return(1);

	return(mdoc_verr(mdoc, n->line, n->pos, "require parent %s",
		MDOC_ROOT == t ? "<root>" : mdoc_macronames[tok]));
}



static int
pre_display(PRE_ARGS)
{
	struct mdoc_node *node;

	/* Display elements (`Bd', `D1'...) cannot be nested. */

	if (MDOC_BLOCK != n->type)
		return(1);

	/* LINTED */
	for (node = mdoc->last->parent; node; node = node->parent) 
		if (MDOC_BLOCK == node->type)
			if (MDOC_Bd == node->tok)
				break;
	if (NULL == node)
		return(1);

	return(mdoc_nerr(mdoc, n, ENESTDISP));
}


static int
pre_bl(PRE_ARGS)
{
	int		 pos, type, width, offset;

	if (MDOC_BLOCK != n->type)
		return(1);
	if (NULL == n->args)
		return(mdoc_nerr(mdoc, n, ELISTTYPE));

	/* Make sure that only one type of list is specified.  */

	type = offset = width = -1;

	/* LINTED */
	for (pos = 0; pos < (int)n->args->argc; pos++)
		switch (n->args->argv[pos].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 >= 0) 
				return(mdoc_nerr(mdoc, n, EMULTILIST));
			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 (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 (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 (type < 0)
		return(mdoc_nerr(mdoc, n, ELISTTYPE));

	/* 
	 * Validate the width field.  Some list types don't need width
	 * types and should be warned about them.  Others should have it
	 * and must also be warned.
	 */

	switch (type) {
	case (MDOC_Tag):
		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 (width >= 0 && ! mdoc_nwarn(mdoc, n, ENOWIDTH))
			return(0);
		break;
	default:
		break;
	}

	return(1);
}


static int
pre_bd(PRE_ARGS)
{
	int		 i, type, err;

	if (MDOC_BLOCK != n->type)
		return(1);
	if (NULL == n->args) 
		return(mdoc_nerr(mdoc, n, EDISPTYPE));

	/* Make sure that only one type of display is specified.  */

	/* LINTED */
	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):
			/* FALLTHROUGH */
		case (MDOC_Filled):
			/* FALLTHROUGH */
		case (MDOC_Literal):
			if (0 == type++) 
				break;
			return(mdoc_nerr(mdoc, n, EMULTIDISP));
		default:
			break;
		}

	if (type)
		return(1);
	return(mdoc_nerr(mdoc, n, EDISPTYPE));
}


static int
pre_ss(PRE_ARGS)
{

	if (MDOC_BLOCK != n->type)
		return(1);
	return(check_parent(mdoc, n, MDOC_Sh, MDOC_BODY));
}


static int
pre_sh(PRE_ARGS)
{

	if (MDOC_BLOCK != n->type)
		return(1);
	return(check_parent(mdoc, n, -1, MDOC_ROOT));
}


static int
pre_it(PRE_ARGS)
{

	if (MDOC_BLOCK != n->type)
		return(1);
	return(check_parent(mdoc, n, MDOC_Bl, MDOC_BODY));
}


static int
pre_an(PRE_ARGS)
{

	if (NULL == n->args || 1 == n->args->argc)
		return(1);
	return(mdoc_verr(mdoc, n->line, n->pos, 
				"only one argument allowed"));
}


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);
	if (mdoc->meta.title)
		if ( ! mdoc_nwarn(mdoc, n, EPROLREP))
			return(0);
	return(1);
}


static int
pre_os(PRE_ARGS)
{

	if (NULL == mdoc->meta.title || 0 == mdoc->meta.date)
		if ( ! mdoc_nwarn(mdoc, n, EPROLOOO))
			return(0);
	if (mdoc->meta.os)
		if ( ! mdoc_nwarn(mdoc, n, EPROLREP))
			return(0);
	return(1);
}


static int
pre_dd(PRE_ARGS)
{

	if (mdoc->meta.title || mdoc->meta.os)
		if ( ! mdoc_nwarn(mdoc, n, EPROLOOO))
			return(0);
	if (mdoc->meta.date)
		if ( ! mdoc_nwarn(mdoc, n, EPROLREP))
			return(0);
	return(1);
}


static int
post_bf(POST_ARGS)
{
	char		 *p;
	struct mdoc_node *head;

	if (MDOC_BLOCK != mdoc->last->type)
		return(1);

	head = mdoc->last->head;

	if (mdoc->last->args && head->child)
		return(mdoc_nerr(mdoc, mdoc->last, ELINE));
	else if (mdoc->last->args)
		return(1);

	if (NULL == head->child || MDOC_TEXT != head->child->type)
		return(mdoc_nerr(mdoc, mdoc->last, ELINE));

	p = head->child->string;

	if (0 == strcmp(p, "Em"))
		return(1);
	else if (0 == strcmp(p, "Li"))
		return(1);
	else if (0 == strcmp(p, "Sy"))
		return(1);

	return(mdoc_nerr(mdoc, head, EFONT));
}


static int
post_lb(POST_ARGS)
{

	if (mdoc_a2lib(mdoc->last->child->string))
		return(1);
	return(mdoc_nwarn(mdoc, mdoc->last, ELIB));
}


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 &&
		    (MDOC_ELEM != n->type || MDOC_eos != n->tok)) 
			if ( ! mdoc_nwarn(mdoc, n, EBADCHILD))
				return(0);

	return(1);
}


static int
post_nm(POST_ARGS)
{

	if (mdoc->last->child)
		return(1);
	if (mdoc->meta.name)
		return(1);
	return(mdoc_nerr(mdoc, mdoc->last, ENAME));
}


static int
post_at(POST_ARGS)
{

	if (NULL == mdoc->last->child)
		return(1);
	if (MDOC_TEXT != mdoc->last->child->type)
		return(mdoc_nerr(mdoc, mdoc->last, EATT));
	if (mdoc_a2att(mdoc->last->child->string))
		return(1);
	return(mdoc_nerr(mdoc, mdoc->last, EATT));
}


static int
post_an(POST_ARGS)
{

	if (mdoc->last->args) {
		if (NULL == mdoc->last->child)
			return(1);
		return(mdoc_nerr(mdoc, mdoc->last, ENOLINE));
	}

	if (mdoc->last->child)
		return(1);
	return(mdoc_nerr(mdoc, mdoc->last, ELINE));
}


static int
post_it(POST_ARGS)
{
	int		  type, i, cols;
	struct mdoc_node *n, *c;

	if (MDOC_BLOCK != mdoc->last->type)
		return(1);

	n = mdoc->last->parent->parent;
	if (NULL == n->args)
		return(mdoc_nerr(mdoc, mdoc->last, ELISTTYPE));

	/* Some types require block-head, some not. */

	/* LINTED */
	for (cols = type = -1, i = 0; -1 == type && 
			i < (int)n->args->argc; i++)
		switch (n->args->argv[i].arg) {
		case (MDOC_Tag):
			/* FALLTHROUGH */
		case (MDOC_Diag):
			/* FALLTHROUGH */
		case (MDOC_Hang):
			/* FALLTHROUGH */
		case (MDOC_Ohang):
			/* FALLTHROUGH */
		case (MDOC_Inset):
			/* FALLTHROUGH */
		case (MDOC_Bullet):
			/* FALLTHROUGH */
		case (MDOC_Dash):
			/* FALLTHROUGH */
		case (MDOC_Enum):
			/* FALLTHROUGH */
		case (MDOC_Hyphen):
			/* FALLTHROUGH */
		case (MDOC_Item):
			type = n->args->argv[i].arg;
			break;
		case (MDOC_Column):
			type = n->args->argv[i].arg;
			cols = (int)n->args->argv[i].sz;
			break;
		default:
			break;
		}

	if (-1 == type)
		return(mdoc_nerr(mdoc, mdoc->last, ELISTTYPE));

	switch (type) {
	case (MDOC_Tag):
		if (NULL == mdoc->last->head->child)
			if ( ! mdoc_nwarn(mdoc, mdoc->last, ELINE))
				return(0);
		break;
	case (MDOC_Hang):
		/* FALLTHROUGH */
	case (MDOC_Ohang):
		/* FALLTHROUGH */
	case (MDOC_Inset):
		/* FALLTHROUGH */
	case (MDOC_Diag):
		if (NULL == mdoc->last->head->child)
			if ( ! mdoc_nwarn(mdoc, mdoc->last, ELINE))
				return(0);
		if (NULL == mdoc->last->body->child)
			if ( ! mdoc_nwarn(mdoc, mdoc->last, EMULTILINE))
				return(0);
		break;
	case (MDOC_Bullet):
		/* FALLTHROUGH */
	case (MDOC_Dash):
		/* FALLTHROUGH */
	case (MDOC_Enum):
		/* FALLTHROUGH */
	case (MDOC_Hyphen):
		/* FALLTHROUGH */
	case (MDOC_Item):
		if (mdoc->last->head->child)
			if ( ! mdoc_nwarn(mdoc, mdoc->last, ENOLINE))
				return(0);
		if (NULL == mdoc->last->body->child)
			if ( ! mdoc_nwarn(mdoc, mdoc->last, EMULTILINE))
				return(0);
		break;
	case (MDOC_Column):
		if (NULL == mdoc->last->head->child)
			if ( ! mdoc_nwarn(mdoc, mdoc->last, ELINE))
				return(0);
		if (mdoc->last->body->child)
			if ( ! mdoc_nwarn(mdoc, mdoc->last, ENOMULTILINE))
				return(0);
		c = mdoc->last->child;
		for (i = 0; c && MDOC_HEAD == c->type; c = c->next)
			i++;

		if (i < cols || i == (cols + 1)) {
			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)
			break;

		return(mdoc_verr(mdoc, mdoc->last->line, 
				mdoc->last->pos, "column mismatch: "
				"have %d, want %d", i, cols));
	default:
		break;
	}

	return(1);
}


static int
post_bl_head(POST_ARGS) 
{
	int			i;
	const struct mdoc_node *n;

	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;

	if (i == (int)n->args->argc)
		return(1);

	if (n->args->argv[i].sz && mdoc->last->child)
		return(mdoc_nerr(mdoc, n, ECOLMIS));

	return(1);
}


static int
post_bl(POST_ARGS)
{
	struct mdoc_node	*n;

	if (MDOC_HEAD == mdoc->last->type) 
		return(post_bl_head(mdoc));
	if (MDOC_BODY != mdoc->last->type)
		return(1);
	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 && MDOC_It == n->tok)
			continue;
		if (MDOC_Sm == n->tok)
			continue;
		return(mdoc_nerr(mdoc, n, EBADCHILD));
	}

	return(1);
}


static int
ebool(struct mdoc *mdoc)
{
	struct mdoc_node *n;

	/* LINTED */
	for (n = mdoc->last->child; n; n = n->next) {
		if (MDOC_TEXT != n->type)
			break;
		if (0 == strcmp(n->string, "on"))
			continue;
		if (0 == strcmp(n->string, "off"))
			continue;
		break;
	}

	if (NULL == n)
		return(1);
	return(mdoc_nerr(mdoc, n, EBOOL));
}


static int
post_root(POST_ARGS)
{

	if (NULL == mdoc->first->child)
		return(mdoc_nerr(mdoc, mdoc->first, ENODAT));
	if ( ! (MDOC_PBODY & mdoc->flags))
		return(mdoc_nerr(mdoc, mdoc->first, ENOPROLOGUE));

	if (MDOC_BLOCK != mdoc->first->child->type)
		return(mdoc_nerr(mdoc, mdoc->first, ENODAT));
	if (MDOC_Sh != mdoc->first->child->tok)
		return(mdoc_nerr(mdoc, mdoc->first, ENODAT));

	return(1);
}


static int
post_st(POST_ARGS)
{

	if (mdoc_a2st(mdoc->last->child->string))
		return(1);
	return(mdoc_nerr(mdoc, mdoc->last, EBADSTAND));
}


static int
post_rs(POST_ARGS)
{
	struct mdoc_node	*nn;

	if (MDOC_BODY != mdoc->last->type)
		return(1);

	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);
}


static int
post_sh(POST_ARGS)
{

	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(POST_ARGS)
{
	struct mdoc_node *n;

	if (SEC_NAME != mdoc->lastsec)
		return(1);

	/*
	 * 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))
		return(mdoc_nwarn(mdoc, mdoc->last, ENAMESECINC));

	for ( ; n && n->next; n = n->next) {
		if (MDOC_ELEM == n->type && MDOC_Nm == n->tok)
			continue;
		if (MDOC_TEXT == n->type)
			continue;
		if ( ! mdoc_nwarn(mdoc, mdoc->last, ENAMESECINC))
			return(0);
	}

	assert(n);
	if (MDOC_BLOCK == n->type && MDOC_Nd == n->tok)
		return(1);
	return(mdoc_nwarn(mdoc, mdoc->last, ENAMESECINC));
}


static int
post_sh_head(POST_ARGS)
{
	char		        buf[64];
	enum mdoc_sec	        sec;
	const struct mdoc_node *n;

	/*
	 * Process a new section.  Sections are either "named" or
	 * "custom"; custom sections are user-defined, while named ones
	 * usually follow a conventional order and may only appear in
	 * certain manual sections.
	 */

	buf[0] = 0;

	for (n = mdoc->last->child; n; n = n->next) {
		/* XXX - copied from compact(). */
		assert(MDOC_TEXT == n->type);

		if (strlcat(buf, n->string, 64) >= 64)
			return(mdoc_nerr(mdoc, n, ETOOLONG));
		if (NULL == n->next)
			continue;
		if (strlcat(buf, " ", 64) >= 64)
			return(mdoc_nerr(mdoc, n, ETOOLONG));
	}

	sec = mdoc_atosec(buf);

	/* 
	 * Check: NAME should always be first, CUSTOM has no roles,
	 * non-CUSTOM has a conventional order to be followed.
	 */

	if (SEC_NAME != sec && SEC_NONE == mdoc->lastnamed)
		return(mdoc_nerr(mdoc, mdoc->last, ESECNAME));
	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?).
	 */

	switch (sec) {
	case (SEC_LIBRARY):
		switch (mdoc->meta.msec) {
		case (2):
			/* FALLTHROUGH */
		case (3):
			break;
		default:
			return(mdoc_nwarn(mdoc, mdoc->last, EWRONGMSEC));
		}
		break;
	default:
		break;
	}

	return(1);
}


static int
pre_fd(PRE_ARGS)
{

	return(check_sec(mdoc, n, SEC_SYNOPSIS, SEC_CUSTOM));
}