xref: /netbsd-src/external/bsd/mdocml/dist/mdoc_validate.c (revision d90f6d4d226c33bf97117facccccfac1fa715850)
16167eca2Schristos /*	Id: mdoc_validate.c,v 1.371 2019/03/04 13:01:57 schwarze Exp  */
24154958bSjoerg /*
3603fc4ebSjoerg  * Copyright (c) 2008-2012 Kristaps Dzonsons <kristaps@bsd.lv>
46167eca2Schristos  * Copyright (c) 2010-2019 Ingo Schwarze <schwarze@openbsd.org>
55c413d0cSchristos  * Copyright (c) 2010 Joerg Sonnenberger <joerg@netbsd.org>
64154958bSjoerg  *
74154958bSjoerg  * Permission to use, copy, modify, and distribute this software for any
84154958bSjoerg  * purpose with or without fee is hereby granted, provided that the above
94154958bSjoerg  * copyright notice and this permission notice appear in all copies.
104154958bSjoerg  *
11f47368cfSchristos  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHORS DISCLAIM ALL WARRANTIES
124154958bSjoerg  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
13f47368cfSchristos  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR
144154958bSjoerg  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
154154958bSjoerg  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
164154958bSjoerg  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
174154958bSjoerg  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
184154958bSjoerg  */
19d5e63c8dSjoerg #include "config.h"
20d5e63c8dSjoerg 
215c413d0cSchristos #include <sys/types.h>
22c0d9444aSjoerg #ifndef OSNAME
23c0d9444aSjoerg #include <sys/utsname.h>
24c0d9444aSjoerg #endif
25c0d9444aSjoerg 
264154958bSjoerg #include <assert.h>
274154958bSjoerg #include <ctype.h>
284154958bSjoerg #include <limits.h>
290a84adc5Sjoerg #include <stdio.h>
304154958bSjoerg #include <stdlib.h>
314154958bSjoerg #include <string.h>
32c0d9444aSjoerg #include <time.h>
334154958bSjoerg 
345c413d0cSchristos #include "mandoc_aux.h"
35f47368cfSchristos #include "mandoc.h"
3614e7489eSchristos #include "mandoc_xr.h"
37f47368cfSchristos #include "roff.h"
38f47368cfSchristos #include "mdoc.h"
394154958bSjoerg #include "libmandoc.h"
40f47368cfSchristos #include "roff_int.h"
41f47368cfSchristos #include "libmdoc.h"
424154958bSjoerg 
434154958bSjoerg /* FIXME: .Bl -diag can't have non-text children in HEAD. */
444154958bSjoerg 
45f47368cfSchristos #define	POST_ARGS struct roff_man *mdoc
464154958bSjoerg 
47c0d9444aSjoerg enum	check_ineq {
48c0d9444aSjoerg 	CHECK_LT,
49c0d9444aSjoerg 	CHECK_GT,
50c0d9444aSjoerg 	CHECK_EQ
51c0d9444aSjoerg };
52c0d9444aSjoerg 
535c413d0cSchristos typedef	void	(*v_post)(POST_ARGS);
544154958bSjoerg 
5537ef69edSchristos static	int	 build_list(struct roff_man *, int);
56f47368cfSchristos static	void	 check_argv(struct roff_man *,
57f47368cfSchristos 			struct roff_node *, struct mdoc_argv *);
58f47368cfSchristos static	void	 check_args(struct roff_man *, struct roff_node *);
5914e7489eSchristos static	void	 check_text(struct roff_man *, int, int, char *);
6014e7489eSchristos static	void	 check_text_em(struct roff_man *, int, int, char *);
6114e7489eSchristos static	void	 check_toptext(struct roff_man *, int, int, const char *);
62f47368cfSchristos static	int	 child_an(const struct roff_node *);
6314e7489eSchristos static	size_t		macro2len(enum roff_tok);
6414e7489eSchristos static	void	 rewrite_macro2len(struct roff_man *, char **);
6514e7489eSchristos static	int	 similar(const char *, const char *);
66c0d9444aSjoerg 
67*d90f6d4dSchristos static	void	 post_abort(POST_ARGS) __dead;
685c413d0cSchristos static	void	 post_an(POST_ARGS);
69f47368cfSchristos static	void	 post_an_norm(POST_ARGS);
705c413d0cSchristos static	void	 post_at(POST_ARGS);
71f47368cfSchristos static	void	 post_bd(POST_ARGS);
725c413d0cSchristos static	void	 post_bf(POST_ARGS);
735c413d0cSchristos static	void	 post_bk(POST_ARGS);
745c413d0cSchristos static	void	 post_bl(POST_ARGS);
755c413d0cSchristos static	void	 post_bl_block(POST_ARGS);
765c413d0cSchristos static	void	 post_bl_head(POST_ARGS);
77f47368cfSchristos static	void	 post_bl_norm(POST_ARGS);
785c413d0cSchristos static	void	 post_bx(POST_ARGS);
795c413d0cSchristos static	void	 post_defaults(POST_ARGS);
80f47368cfSchristos static	void	 post_display(POST_ARGS);
815c413d0cSchristos static	void	 post_dd(POST_ARGS);
8214e7489eSchristos static	void	 post_delim(POST_ARGS);
8314e7489eSchristos static	void	 post_delim_nb(POST_ARGS);
845c413d0cSchristos static	void	 post_dt(POST_ARGS);
855c413d0cSchristos static	void	 post_en(POST_ARGS);
865c413d0cSchristos static	void	 post_es(POST_ARGS);
875c413d0cSchristos static	void	 post_eoln(POST_ARGS);
885c413d0cSchristos static	void	 post_ex(POST_ARGS);
895c413d0cSchristos static	void	 post_fa(POST_ARGS);
905c413d0cSchristos static	void	 post_fn(POST_ARGS);
915c413d0cSchristos static	void	 post_fname(POST_ARGS);
925c413d0cSchristos static	void	 post_fo(POST_ARGS);
935c413d0cSchristos static	void	 post_hyph(POST_ARGS);
945c413d0cSchristos static	void	 post_ignpar(POST_ARGS);
955c413d0cSchristos static	void	 post_it(POST_ARGS);
965c413d0cSchristos static	void	 post_lb(POST_ARGS);
975c413d0cSchristos static	void	 post_nd(POST_ARGS);
985c413d0cSchristos static	void	 post_nm(POST_ARGS);
995c413d0cSchristos static	void	 post_ns(POST_ARGS);
100f47368cfSchristos static	void	 post_obsolete(POST_ARGS);
1015c413d0cSchristos static	void	 post_os(POST_ARGS);
1025c413d0cSchristos static	void	 post_par(POST_ARGS);
103f47368cfSchristos static	void	 post_prevpar(POST_ARGS);
1045c413d0cSchristos static	void	 post_root(POST_ARGS);
1055c413d0cSchristos static	void	 post_rs(POST_ARGS);
10637ef69edSchristos static	void	 post_rv(POST_ARGS);
1075c413d0cSchristos static	void	 post_sh(POST_ARGS);
1085c413d0cSchristos static	void	 post_sh_head(POST_ARGS);
1095c413d0cSchristos static	void	 post_sh_name(POST_ARGS);
1105c413d0cSchristos static	void	 post_sh_see_also(POST_ARGS);
1115c413d0cSchristos static	void	 post_sh_authors(POST_ARGS);
1125c413d0cSchristos static	void	 post_sm(POST_ARGS);
1135c413d0cSchristos static	void	 post_st(POST_ARGS);
114f47368cfSchristos static	void	 post_std(POST_ARGS);
11514e7489eSchristos static	void	 post_sx(POST_ARGS);
11614e7489eSchristos static	void	 post_useless(POST_ARGS);
11737ef69edSchristos static	void	 post_xr(POST_ARGS);
11837ef69edSchristos static	void	 post_xx(POST_ARGS);
1194154958bSjoerg 
1206167eca2Schristos static	const v_post mdoc_valids[MDOC_MAX - MDOC_Dd] = {
121f47368cfSchristos 	post_dd,	/* Dd */
122f47368cfSchristos 	post_dt,	/* Dt */
123f47368cfSchristos 	post_os,	/* Os */
124f47368cfSchristos 	post_sh,	/* Sh */
125f47368cfSchristos 	post_ignpar,	/* Ss */
126f47368cfSchristos 	post_par,	/* Pp */
127f47368cfSchristos 	post_display,	/* D1 */
128f47368cfSchristos 	post_display,	/* Dl */
129f47368cfSchristos 	post_display,	/* Bd */
130f47368cfSchristos 	NULL,		/* Ed */
131f47368cfSchristos 	post_bl,	/* Bl */
132f47368cfSchristos 	NULL,		/* El */
133f47368cfSchristos 	post_it,	/* It */
13414e7489eSchristos 	post_delim_nb,	/* Ad */
135f47368cfSchristos 	post_an,	/* An */
13614e7489eSchristos 	NULL,		/* Ap */
137f47368cfSchristos 	post_defaults,	/* Ar */
138f47368cfSchristos 	NULL,		/* Cd */
13914e7489eSchristos 	post_delim_nb,	/* Cm */
14014e7489eSchristos 	post_delim_nb,	/* Dv */
14114e7489eSchristos 	post_delim_nb,	/* Er */
14214e7489eSchristos 	post_delim_nb,	/* Ev */
143f47368cfSchristos 	post_ex,	/* Ex */
144f47368cfSchristos 	post_fa,	/* Fa */
145f47368cfSchristos 	NULL,		/* Fd */
14614e7489eSchristos 	post_delim_nb,	/* Fl */
147f47368cfSchristos 	post_fn,	/* Fn */
14814e7489eSchristos 	post_delim_nb,	/* Ft */
14914e7489eSchristos 	post_delim_nb,	/* Ic */
15014e7489eSchristos 	post_delim_nb,	/* In */
151f47368cfSchristos 	post_defaults,	/* Li */
152f47368cfSchristos 	post_nd,	/* Nd */
153f47368cfSchristos 	post_nm,	/* Nm */
15414e7489eSchristos 	post_delim_nb,	/* Op */
1556167eca2Schristos 	post_abort,	/* Ot */
156f47368cfSchristos 	post_defaults,	/* Pa */
15737ef69edSchristos 	post_rv,	/* Rv */
158f47368cfSchristos 	post_st,	/* St */
15914e7489eSchristos 	post_delim_nb,	/* Va */
16014e7489eSchristos 	post_delim_nb,	/* Vt */
16137ef69edSchristos 	post_xr,	/* Xr */
162f47368cfSchristos 	NULL,		/* %A */
163f47368cfSchristos 	post_hyph,	/* %B */ /* FIXME: can be used outside Rs/Re. */
164f47368cfSchristos 	NULL,		/* %D */
165f47368cfSchristos 	NULL,		/* %I */
166f47368cfSchristos 	NULL,		/* %J */
167f47368cfSchristos 	post_hyph,	/* %N */
168f47368cfSchristos 	post_hyph,	/* %O */
169f47368cfSchristos 	NULL,		/* %P */
170f47368cfSchristos 	post_hyph,	/* %R */
171f47368cfSchristos 	post_hyph,	/* %T */ /* FIXME: can be used outside Rs/Re. */
172f47368cfSchristos 	NULL,		/* %V */
173f47368cfSchristos 	NULL,		/* Ac */
174f47368cfSchristos 	NULL,		/* Ao */
17514e7489eSchristos 	post_delim_nb,	/* Aq */
176f47368cfSchristos 	post_at,	/* At */
177f47368cfSchristos 	NULL,		/* Bc */
178f47368cfSchristos 	post_bf,	/* Bf */
179f47368cfSchristos 	NULL,		/* Bo */
180f47368cfSchristos 	NULL,		/* Bq */
18137ef69edSchristos 	post_xx,	/* Bsx */
182f47368cfSchristos 	post_bx,	/* Bx */
183f47368cfSchristos 	post_obsolete,	/* Db */
184f47368cfSchristos 	NULL,		/* Dc */
185f47368cfSchristos 	NULL,		/* Do */
186f47368cfSchristos 	NULL,		/* Dq */
187f47368cfSchristos 	NULL,		/* Ec */
188f47368cfSchristos 	NULL,		/* Ef */
18914e7489eSchristos 	post_delim_nb,	/* Em */
190f47368cfSchristos 	NULL,		/* Eo */
19137ef69edSchristos 	post_xx,	/* Fx */
19214e7489eSchristos 	post_delim_nb,	/* Ms */
193f47368cfSchristos 	NULL,		/* No */
194f47368cfSchristos 	post_ns,	/* Ns */
19537ef69edSchristos 	post_xx,	/* Nx */
19637ef69edSchristos 	post_xx,	/* Ox */
197f47368cfSchristos 	NULL,		/* Pc */
198f47368cfSchristos 	NULL,		/* Pf */
199f47368cfSchristos 	NULL,		/* Po */
20014e7489eSchristos 	post_delim_nb,	/* Pq */
201f47368cfSchristos 	NULL,		/* Qc */
20214e7489eSchristos 	post_delim_nb,	/* Ql */
203f47368cfSchristos 	NULL,		/* Qo */
20414e7489eSchristos 	post_delim_nb,	/* Qq */
205f47368cfSchristos 	NULL,		/* Re */
206f47368cfSchristos 	post_rs,	/* Rs */
207f47368cfSchristos 	NULL,		/* Sc */
208f47368cfSchristos 	NULL,		/* So */
20914e7489eSchristos 	post_delim_nb,	/* Sq */
210f47368cfSchristos 	post_sm,	/* Sm */
21114e7489eSchristos 	post_sx,	/* Sx */
21214e7489eSchristos 	post_delim_nb,	/* Sy */
21314e7489eSchristos 	post_useless,	/* Tn */
21437ef69edSchristos 	post_xx,	/* Ux */
215f47368cfSchristos 	NULL,		/* Xc */
216f47368cfSchristos 	NULL,		/* Xo */
217f47368cfSchristos 	post_fo,	/* Fo */
218f47368cfSchristos 	NULL,		/* Fc */
219f47368cfSchristos 	NULL,		/* Oo */
220f47368cfSchristos 	NULL,		/* Oc */
221f47368cfSchristos 	post_bk,	/* Bk */
222f47368cfSchristos 	NULL,		/* Ek */
223f47368cfSchristos 	post_eoln,	/* Bt */
22414e7489eSchristos 	post_obsolete,	/* Hf */
225f47368cfSchristos 	post_obsolete,	/* Fr */
226f47368cfSchristos 	post_eoln,	/* Ud */
227f47368cfSchristos 	post_lb,	/* Lb */
2286167eca2Schristos 	post_abort,	/* Lp */
22914e7489eSchristos 	post_delim_nb,	/* Lk */
230f47368cfSchristos 	post_defaults,	/* Mt */
23114e7489eSchristos 	post_delim_nb,	/* Brq */
232f47368cfSchristos 	NULL,		/* Bro */
233f47368cfSchristos 	NULL,		/* Brc */
234f47368cfSchristos 	NULL,		/* %C */
235f47368cfSchristos 	post_es,	/* Es */
236f47368cfSchristos 	post_en,	/* En */
23737ef69edSchristos 	post_xx,	/* Dx */
238f47368cfSchristos 	NULL,		/* %Q */
239f47368cfSchristos 	NULL,		/* %U */
240f47368cfSchristos 	NULL,		/* Ta */
2414154958bSjoerg };
2424154958bSjoerg 
243c0d9444aSjoerg #define	RSORD_MAX 14 /* Number of `Rs' blocks. */
244c0d9444aSjoerg 
24514e7489eSchristos static	const enum roff_tok rsord[RSORD_MAX] = {
246c0d9444aSjoerg 	MDOC__A,
247c0d9444aSjoerg 	MDOC__T,
248c0d9444aSjoerg 	MDOC__B,
249c0d9444aSjoerg 	MDOC__I,
250c0d9444aSjoerg 	MDOC__J,
251c0d9444aSjoerg 	MDOC__R,
252c0d9444aSjoerg 	MDOC__N,
253c0d9444aSjoerg 	MDOC__V,
254603fc4ebSjoerg 	MDOC__U,
255c0d9444aSjoerg 	MDOC__P,
256c0d9444aSjoerg 	MDOC__Q,
257c0d9444aSjoerg 	MDOC__C,
258603fc4ebSjoerg 	MDOC__D,
259603fc4ebSjoerg 	MDOC__O
260c0d9444aSjoerg };
261c0d9444aSjoerg 
26248741257Sjoerg static	const char * const secnames[SEC__MAX] = {
26348741257Sjoerg 	NULL,
26448741257Sjoerg 	"NAME",
26548741257Sjoerg 	"LIBRARY",
26648741257Sjoerg 	"SYNOPSIS",
26748741257Sjoerg 	"DESCRIPTION",
2685c413d0cSchristos 	"CONTEXT",
26948741257Sjoerg 	"IMPLEMENTATION NOTES",
27048741257Sjoerg 	"RETURN VALUES",
27148741257Sjoerg 	"ENVIRONMENT",
27248741257Sjoerg 	"FILES",
27348741257Sjoerg 	"EXIT STATUS",
27448741257Sjoerg 	"EXAMPLES",
27548741257Sjoerg 	"DIAGNOSTICS",
27648741257Sjoerg 	"COMPATIBILITY",
27748741257Sjoerg 	"ERRORS",
27848741257Sjoerg 	"SEE ALSO",
27948741257Sjoerg 	"STANDARDS",
28048741257Sjoerg 	"HISTORY",
28148741257Sjoerg 	"AUTHORS",
28248741257Sjoerg 	"CAVEATS",
28348741257Sjoerg 	"BUGS",
28448741257Sjoerg 	"SECURITY CONSIDERATIONS",
28548741257Sjoerg 	NULL
28648741257Sjoerg };
2874154958bSjoerg 
2885c413d0cSchristos 
2896167eca2Schristos /* Validate the subtree rooted at mdoc->last. */
2905c413d0cSchristos void
mdoc_validate(struct roff_man * mdoc)2916167eca2Schristos mdoc_validate(struct roff_man *mdoc)
2924154958bSjoerg {
29314e7489eSchristos 	struct roff_node *n, *np;
29414e7489eSchristos 	const v_post *p;
2954154958bSjoerg 
2966167eca2Schristos 	/*
2976167eca2Schristos 	 * Translate obsolete macros to modern macros first
2986167eca2Schristos 	 * such that later code does not need to look
2996167eca2Schristos 	 * for the obsolete versions.
3006167eca2Schristos 	 */
3016167eca2Schristos 
3025c413d0cSchristos 	n = mdoc->last;
3036167eca2Schristos 	switch (n->tok) {
3046167eca2Schristos 	case MDOC_Lp:
3056167eca2Schristos 		n->tok = MDOC_Pp;
3066167eca2Schristos 		break;
3076167eca2Schristos 	case MDOC_Ot:
3086167eca2Schristos 		post_obsolete(mdoc);
3096167eca2Schristos 		n->tok = MDOC_Ft;
3106167eca2Schristos 		break;
3116167eca2Schristos 	default:
3126167eca2Schristos 		break;
3136167eca2Schristos 	}
3146167eca2Schristos 
3156167eca2Schristos 	/*
3166167eca2Schristos 	 * Iterate over all children, recursing into each one
3176167eca2Schristos 	 * in turn, depth-first.
3186167eca2Schristos 	 */
3196167eca2Schristos 
320f47368cfSchristos 	mdoc->last = mdoc->last->child;
321f47368cfSchristos 	while (mdoc->last != NULL) {
3226167eca2Schristos 		mdoc_validate(mdoc);
323f47368cfSchristos 		if (mdoc->last == n)
324f47368cfSchristos 			mdoc->last = mdoc->last->child;
325f47368cfSchristos 		else
326f47368cfSchristos 			mdoc->last = mdoc->last->next;
327f47368cfSchristos 	}
3284154958bSjoerg 
3296167eca2Schristos 	/* Finally validate the macro itself. */
3306167eca2Schristos 
331f47368cfSchristos 	mdoc->last = n;
332f47368cfSchristos 	mdoc->next = ROFF_NEXT_SIBLING;
3335c413d0cSchristos 	switch (n->type) {
334f47368cfSchristos 	case ROFFT_TEXT:
33514e7489eSchristos 		np = n->parent;
33637ef69edSchristos 		if (n->sec != SEC_SYNOPSIS ||
33714e7489eSchristos 		    (np->tok != MDOC_Cd && np->tok != MDOC_Fd))
338f47368cfSchristos 			check_text(mdoc, n->line, n->pos, n->string);
3396167eca2Schristos 		if ((n->flags & NODE_NOFILL) == 0 &&
34014e7489eSchristos 		    (np->tok != MDOC_It || np->type != ROFFT_HEAD ||
34114e7489eSchristos 		     np->parent->parent->norm->Bl.type != LIST_diag))
34214e7489eSchristos 			check_text_em(mdoc, n->line, n->pos, n->string);
34314e7489eSchristos 		if (np->tok == MDOC_It || (np->type == ROFFT_BODY &&
34414e7489eSchristos 		    (np->tok == MDOC_Sh || np->tok == MDOC_Ss)))
34514e7489eSchristos 			check_toptext(mdoc, n->line, n->pos, n->string);
346c0d9444aSjoerg 		break;
34714e7489eSchristos 	case ROFFT_COMMENT:
348f47368cfSchristos 	case ROFFT_EQN:
349f47368cfSchristos 	case ROFFT_TBL:
350f47368cfSchristos 		break;
351f47368cfSchristos 	case ROFFT_ROOT:
3525c413d0cSchristos 		post_root(mdoc);
353c0d9444aSjoerg 		break;
354c0d9444aSjoerg 	default:
355f47368cfSchristos 		check_args(mdoc, mdoc->last);
3565c413d0cSchristos 
3575c413d0cSchristos 		/*
3585c413d0cSchristos 		 * Closing delimiters are not special at the
3595c413d0cSchristos 		 * beginning of a block, opening delimiters
3605c413d0cSchristos 		 * are not special at the end.
3615c413d0cSchristos 		 */
3625c413d0cSchristos 
3635c413d0cSchristos 		if (n->child != NULL)
36437ef69edSchristos 			n->child->flags &= ~NODE_DELIMC;
3655c413d0cSchristos 		if (n->last != NULL)
36637ef69edSchristos 			n->last->flags &= ~NODE_DELIMO;
3675c413d0cSchristos 
3685c413d0cSchristos 		/* Call the macro's postprocessor. */
3695c413d0cSchristos 
37014e7489eSchristos 		if (n->tok < ROFF_MAX) {
37114e7489eSchristos 			roff_validate(mdoc);
37214e7489eSchristos 			break;
37314e7489eSchristos 		}
37414e7489eSchristos 
37514e7489eSchristos 		assert(n->tok >= MDOC_Dd && n->tok < MDOC_MAX);
3766167eca2Schristos 		p = mdoc_valids + (n->tok - MDOC_Dd);
3775c413d0cSchristos 		if (*p)
3785c413d0cSchristos 			(*p)(mdoc);
379f47368cfSchristos 		if (mdoc->last == n)
380f47368cfSchristos 			mdoc_state(mdoc, n);
3815c413d0cSchristos 		break;
3824154958bSjoerg 	}
383c0d9444aSjoerg }
384c0d9444aSjoerg 
385c0d9444aSjoerg static void
check_args(struct roff_man * mdoc,struct roff_node * n)386f47368cfSchristos check_args(struct roff_man *mdoc, struct roff_node *n)
3874154958bSjoerg {
3884154958bSjoerg 	int		 i;
3894154958bSjoerg 
3904154958bSjoerg 	if (NULL == n->args)
391c0d9444aSjoerg 		return;
3924154958bSjoerg 
3934154958bSjoerg 	assert(n->args->argc);
3944154958bSjoerg 	for (i = 0; i < (int)n->args->argc; i++)
395603fc4ebSjoerg 		check_argv(mdoc, n, &n->args->argv[i]);
3964154958bSjoerg }
3974154958bSjoerg 
398c0d9444aSjoerg static void
check_argv(struct roff_man * mdoc,struct roff_node * n,struct mdoc_argv * v)399f47368cfSchristos check_argv(struct roff_man *mdoc, struct roff_node *n, struct mdoc_argv *v)
4004154958bSjoerg {
4014154958bSjoerg 	int		 i;
4024154958bSjoerg 
4034154958bSjoerg 	for (i = 0; i < (int)v->sz; i++)
404603fc4ebSjoerg 		check_text(mdoc, v->line, v->pos, v->value[i]);
4054154958bSjoerg }
4064154958bSjoerg 
407c0d9444aSjoerg static void
check_text(struct roff_man * mdoc,int ln,int pos,char * p)408f47368cfSchristos check_text(struct roff_man *mdoc, int ln, int pos, char *p)
4094154958bSjoerg {
410b1e8115bSjoerg 	char		*cp;
41182361f10Sjoerg 
4126167eca2Schristos 	if (mdoc->last->flags & NODE_NOFILL)
4131350fe09Sjoerg 		return;
4141350fe09Sjoerg 
4151350fe09Sjoerg 	for (cp = p; NULL != (p = strchr(p, '\t')); p++)
4166167eca2Schristos 		mandoc_msg(MANDOCERR_FI_TAB, ln, pos + (int)(p - cp), NULL);
4174154958bSjoerg }
4184154958bSjoerg 
4195c413d0cSchristos static void
check_text_em(struct roff_man * mdoc,int ln,int pos,char * p)42014e7489eSchristos check_text_em(struct roff_man *mdoc, int ln, int pos, char *p)
42114e7489eSchristos {
42214e7489eSchristos 	const struct roff_node	*np, *nn;
42314e7489eSchristos 	char			*cp;
42414e7489eSchristos 
42514e7489eSchristos 	np = mdoc->last->prev;
42614e7489eSchristos 	nn = mdoc->last->next;
42714e7489eSchristos 
42814e7489eSchristos 	/* Look for em-dashes wrongly encoded as "--". */
42914e7489eSchristos 
43014e7489eSchristos 	for (cp = p; *cp != '\0'; cp++) {
43114e7489eSchristos 		if (cp[0] != '-' || cp[1] != '-')
43214e7489eSchristos 			continue;
43314e7489eSchristos 		cp++;
43414e7489eSchristos 
43514e7489eSchristos 		/* Skip input sequences of more than two '-'. */
43614e7489eSchristos 
43714e7489eSchristos 		if (cp[1] == '-') {
43814e7489eSchristos 			while (cp[1] == '-')
43914e7489eSchristos 				cp++;
44014e7489eSchristos 			continue;
44114e7489eSchristos 		}
44214e7489eSchristos 
44314e7489eSchristos 		/* Skip "--" directly attached to something else. */
44414e7489eSchristos 
44514e7489eSchristos 		if ((cp - p > 1 && cp[-2] != ' ') ||
44614e7489eSchristos 		    (cp[1] != '\0' && cp[1] != ' '))
44714e7489eSchristos 			continue;
44814e7489eSchristos 
44914e7489eSchristos 		/* Require a letter right before or right afterwards. */
45014e7489eSchristos 
45114e7489eSchristos 		if ((cp - p > 2 ?
45214e7489eSchristos 		     isalpha((unsigned char)cp[-3]) :
45314e7489eSchristos 		     np != NULL &&
45414e7489eSchristos 		     np->type == ROFFT_TEXT &&
45514e7489eSchristos 		     *np->string != '\0' &&
45614e7489eSchristos 		     isalpha((unsigned char)np->string[
45714e7489eSchristos 		       strlen(np->string) - 1])) ||
45814e7489eSchristos 		    (cp[1] != '\0' && cp[2] != '\0' ?
45914e7489eSchristos 		     isalpha((unsigned char)cp[2]) :
46014e7489eSchristos 		     nn != NULL &&
46114e7489eSchristos 		     nn->type == ROFFT_TEXT &&
46214e7489eSchristos 		     isalpha((unsigned char)*nn->string))) {
4636167eca2Schristos 			mandoc_msg(MANDOCERR_DASHDASH,
46414e7489eSchristos 			    ln, pos + (int)(cp - p) - 1, NULL);
46514e7489eSchristos 			break;
46614e7489eSchristos 		}
46714e7489eSchristos 	}
46814e7489eSchristos }
46914e7489eSchristos 
47014e7489eSchristos static void
check_toptext(struct roff_man * mdoc,int ln,int pos,const char * p)47114e7489eSchristos check_toptext(struct roff_man *mdoc, int ln, int pos, const char *p)
47214e7489eSchristos {
47314e7489eSchristos 	const char	*cp, *cpr;
47414e7489eSchristos 
47514e7489eSchristos 	if (*p == '\0')
47614e7489eSchristos 		return;
47714e7489eSchristos 
47814e7489eSchristos 	if ((cp = strstr(p, "OpenBSD")) != NULL)
4796167eca2Schristos 		mandoc_msg(MANDOCERR_BX, ln, pos + (int)(cp - p), "Ox");
48014e7489eSchristos 	if ((cp = strstr(p, "NetBSD")) != NULL)
4816167eca2Schristos 		mandoc_msg(MANDOCERR_BX, ln, pos + (int)(cp - p), "Nx");
48214e7489eSchristos 	if ((cp = strstr(p, "FreeBSD")) != NULL)
4836167eca2Schristos 		mandoc_msg(MANDOCERR_BX, ln, pos + (int)(cp - p), "Fx");
48414e7489eSchristos 	if ((cp = strstr(p, "DragonFly")) != NULL)
4856167eca2Schristos 		mandoc_msg(MANDOCERR_BX, ln, pos + (int)(cp - p), "Dx");
48614e7489eSchristos 
48714e7489eSchristos 	cp = p;
48814e7489eSchristos 	while ((cp = strstr(cp + 1, "()")) != NULL) {
48914e7489eSchristos 		for (cpr = cp - 1; cpr >= p; cpr--)
49014e7489eSchristos 			if (*cpr != '_' && !isalnum((unsigned char)*cpr))
49114e7489eSchristos 				break;
49214e7489eSchristos 		if ((cpr < p || *cpr == ' ') && cpr + 1 < cp) {
49314e7489eSchristos 			cpr++;
4946167eca2Schristos 			mandoc_msg(MANDOCERR_FUNC, ln, pos + (int)(cpr - p),
49514e7489eSchristos 			    "%.*s()", (int)(cp - cpr), cpr);
49614e7489eSchristos 		}
49714e7489eSchristos 	}
49814e7489eSchristos }
49914e7489eSchristos 
50014e7489eSchristos static void
post_abort(POST_ARGS)5016167eca2Schristos post_abort(POST_ARGS)
5026167eca2Schristos {
5036167eca2Schristos 	abort();
5046167eca2Schristos }
5056167eca2Schristos 
5066167eca2Schristos static void
post_delim(POST_ARGS)50714e7489eSchristos post_delim(POST_ARGS)
50814e7489eSchristos {
50914e7489eSchristos 	const struct roff_node	*nch;
51014e7489eSchristos 	const char		*lc;
51114e7489eSchristos 	enum mdelim		 delim;
51214e7489eSchristos 	enum roff_tok		 tok;
51314e7489eSchristos 
51414e7489eSchristos 	tok = mdoc->last->tok;
51514e7489eSchristos 	nch = mdoc->last->last;
51614e7489eSchristos 	if (nch == NULL || nch->type != ROFFT_TEXT)
51714e7489eSchristos 		return;
51814e7489eSchristos 	lc = strchr(nch->string, '\0') - 1;
51914e7489eSchristos 	if (lc < nch->string)
52014e7489eSchristos 		return;
52114e7489eSchristos 	delim = mdoc_isdelim(lc);
52214e7489eSchristos 	if (delim == DELIM_NONE || delim == DELIM_OPEN)
52314e7489eSchristos 		return;
52414e7489eSchristos 	if (*lc == ')' && (tok == MDOC_Nd || tok == MDOC_Sh ||
52514e7489eSchristos 	    tok == MDOC_Ss || tok == MDOC_Fo))
52614e7489eSchristos 		return;
52714e7489eSchristos 
5286167eca2Schristos 	mandoc_msg(MANDOCERR_DELIM, nch->line,
5296167eca2Schristos 	    nch->pos + (int)(lc - nch->string), "%s%s %s", roff_name[tok],
53014e7489eSchristos 	    nch == mdoc->last->child ? "" : " ...", nch->string);
53114e7489eSchristos }
53214e7489eSchristos 
53314e7489eSchristos static void
post_delim_nb(POST_ARGS)53414e7489eSchristos post_delim_nb(POST_ARGS)
53514e7489eSchristos {
53614e7489eSchristos 	const struct roff_node	*nch;
53714e7489eSchristos 	const char		*lc, *cp;
53814e7489eSchristos 	int			 nw;
53914e7489eSchristos 	enum mdelim		 delim;
54014e7489eSchristos 	enum roff_tok		 tok;
54114e7489eSchristos 
54214e7489eSchristos 	/*
54314e7489eSchristos 	 * Find candidates: at least two bytes,
54414e7489eSchristos 	 * the last one a closing or middle delimiter.
54514e7489eSchristos 	 */
54614e7489eSchristos 
54714e7489eSchristos 	tok = mdoc->last->tok;
54814e7489eSchristos 	nch = mdoc->last->last;
54914e7489eSchristos 	if (nch == NULL || nch->type != ROFFT_TEXT)
55014e7489eSchristos 		return;
55114e7489eSchristos 	lc = strchr(nch->string, '\0') - 1;
55214e7489eSchristos 	if (lc <= nch->string)
55314e7489eSchristos 		return;
55414e7489eSchristos 	delim = mdoc_isdelim(lc);
55514e7489eSchristos 	if (delim == DELIM_NONE || delim == DELIM_OPEN)
55614e7489eSchristos 		return;
55714e7489eSchristos 
55814e7489eSchristos 	/*
55914e7489eSchristos 	 * Reduce false positives by allowing various cases.
56014e7489eSchristos 	 */
56114e7489eSchristos 
56214e7489eSchristos 	/* Escaped delimiters. */
56314e7489eSchristos 	if (lc > nch->string + 1 && lc[-2] == '\\' &&
56414e7489eSchristos 	    (lc[-1] == '&' || lc[-1] == 'e'))
56514e7489eSchristos 		return;
56614e7489eSchristos 
56714e7489eSchristos 	/* Specific byte sequences. */
56814e7489eSchristos 	switch (*lc) {
56914e7489eSchristos 	case ')':
57014e7489eSchristos 		for (cp = lc; cp >= nch->string; cp--)
57114e7489eSchristos 			if (*cp == '(')
57214e7489eSchristos 				return;
57314e7489eSchristos 		break;
57414e7489eSchristos 	case '.':
57514e7489eSchristos 		if (lc > nch->string + 1 && lc[-2] == '.' && lc[-1] == '.')
57614e7489eSchristos 			return;
57714e7489eSchristos 		if (lc[-1] == '.')
57814e7489eSchristos 			return;
57914e7489eSchristos 		break;
58014e7489eSchristos 	case ';':
58114e7489eSchristos 		if (tok == MDOC_Vt)
58214e7489eSchristos 			return;
58314e7489eSchristos 		break;
58414e7489eSchristos 	case '?':
58514e7489eSchristos 		if (lc[-1] == '?')
58614e7489eSchristos 			return;
58714e7489eSchristos 		break;
58814e7489eSchristos 	case ']':
58914e7489eSchristos 		for (cp = lc; cp >= nch->string; cp--)
59014e7489eSchristos 			if (*cp == '[')
59114e7489eSchristos 				return;
59214e7489eSchristos 		break;
59314e7489eSchristos 	case '|':
59414e7489eSchristos 		if (lc == nch->string + 1 && lc[-1] == '|')
59514e7489eSchristos 			return;
59614e7489eSchristos 	default:
59714e7489eSchristos 		break;
59814e7489eSchristos 	}
59914e7489eSchristos 
60014e7489eSchristos 	/* Exactly two non-alphanumeric bytes. */
60114e7489eSchristos 	if (lc == nch->string + 1 && !isalnum((unsigned char)lc[-1]))
60214e7489eSchristos 		return;
60314e7489eSchristos 
60414e7489eSchristos 	/* At least three alphabetic words with a sentence ending. */
60514e7489eSchristos 	if (strchr("!.:?", *lc) != NULL && (tok == MDOC_Em ||
60614e7489eSchristos 	    tok == MDOC_Li || tok == MDOC_Pq || tok == MDOC_Sy)) {
60714e7489eSchristos 		nw = 0;
60814e7489eSchristos 		for (cp = lc - 1; cp >= nch->string; cp--) {
60914e7489eSchristos 			if (*cp == ' ') {
61014e7489eSchristos 				nw++;
61114e7489eSchristos 				if (cp > nch->string && cp[-1] == ',')
61214e7489eSchristos 					cp--;
61314e7489eSchristos 			} else if (isalpha((unsigned int)*cp)) {
61414e7489eSchristos 				if (nw > 1)
61514e7489eSchristos 					return;
61614e7489eSchristos 			} else
61714e7489eSchristos 				break;
61814e7489eSchristos 		}
61914e7489eSchristos 	}
62014e7489eSchristos 
6216167eca2Schristos 	mandoc_msg(MANDOCERR_DELIM_NB, nch->line,
6226167eca2Schristos 	    nch->pos + (int)(lc - nch->string), "%s%s %s", roff_name[tok],
62314e7489eSchristos 	    nch == mdoc->last->child ? "" : " ...", nch->string);
62414e7489eSchristos }
62514e7489eSchristos 
62614e7489eSchristos static void
post_bl_norm(POST_ARGS)627f47368cfSchristos post_bl_norm(POST_ARGS)
6284154958bSjoerg {
629f47368cfSchristos 	struct roff_node *n;
6305c413d0cSchristos 	struct mdoc_argv *argv, *wa;
6315c413d0cSchristos 	int		  i;
6325c413d0cSchristos 	enum mdocargt	  mdoclt;
6337574e07eSjoerg 	enum mdoc_list	  lt;
6344154958bSjoerg 
635f47368cfSchristos 	n = mdoc->last->parent;
636f47368cfSchristos 	n->norm->Bl.type = LIST__NONE;
6374154958bSjoerg 
6387574e07eSjoerg 	/*
6397574e07eSjoerg 	 * First figure out which kind of list to use: bind ourselves to
6407574e07eSjoerg 	 * the first mentioned list type and warn about any remaining
6417574e07eSjoerg 	 * ones.  If we find no list type, we default to LIST_item.
6427574e07eSjoerg 	 */
6434154958bSjoerg 
6445c413d0cSchristos 	wa = (n->args == NULL) ? NULL : n->args->argv;
6455c413d0cSchristos 	mdoclt = MDOC_ARG_MAX;
6467574e07eSjoerg 	for (i = 0; n->args && i < (int)n->args->argc; i++) {
6475c413d0cSchristos 		argv = n->args->argv + i;
6487574e07eSjoerg 		lt = LIST__NONE;
6495c413d0cSchristos 		switch (argv->arg) {
6507574e07eSjoerg 		/* Set list types. */
6515c413d0cSchristos 		case MDOC_Bullet:
6527574e07eSjoerg 			lt = LIST_bullet;
6537574e07eSjoerg 			break;
6545c413d0cSchristos 		case MDOC_Dash:
6557574e07eSjoerg 			lt = LIST_dash;
6567574e07eSjoerg 			break;
6575c413d0cSchristos 		case MDOC_Enum:
6587574e07eSjoerg 			lt = LIST_enum;
6597574e07eSjoerg 			break;
6605c413d0cSchristos 		case MDOC_Hyphen:
6617574e07eSjoerg 			lt = LIST_hyphen;
6627574e07eSjoerg 			break;
6635c413d0cSchristos 		case MDOC_Item:
6647574e07eSjoerg 			lt = LIST_item;
6657574e07eSjoerg 			break;
6665c413d0cSchristos 		case MDOC_Tag:
6677574e07eSjoerg 			lt = LIST_tag;
6687574e07eSjoerg 			break;
6695c413d0cSchristos 		case MDOC_Diag:
6707574e07eSjoerg 			lt = LIST_diag;
6717574e07eSjoerg 			break;
6725c413d0cSchristos 		case MDOC_Hang:
6737574e07eSjoerg 			lt = LIST_hang;
6747574e07eSjoerg 			break;
6755c413d0cSchristos 		case MDOC_Ohang:
6767574e07eSjoerg 			lt = LIST_ohang;
6777574e07eSjoerg 			break;
6785c413d0cSchristos 		case MDOC_Inset:
6797574e07eSjoerg 			lt = LIST_inset;
6807574e07eSjoerg 			break;
6815c413d0cSchristos 		case MDOC_Column:
6827574e07eSjoerg 			lt = LIST_column;
6834154958bSjoerg 			break;
6847574e07eSjoerg 		/* Set list arguments. */
6855c413d0cSchristos 		case MDOC_Compact:
6865c413d0cSchristos 			if (n->norm->Bl.comp)
6875c413d0cSchristos 				mandoc_msg(MANDOCERR_ARG_REP,
6886167eca2Schristos 				    argv->line, argv->pos, "Bl -compact");
6895c413d0cSchristos 			n->norm->Bl.comp = 1;
6900a84adc5Sjoerg 			break;
6915c413d0cSchristos 		case MDOC_Width:
6925c413d0cSchristos 			wa = argv;
6935c413d0cSchristos 			if (0 == argv->sz) {
6945c413d0cSchristos 				mandoc_msg(MANDOCERR_ARG_EMPTY,
6956167eca2Schristos 				    argv->line, argv->pos, "Bl -width");
6965c413d0cSchristos 				n->norm->Bl.width = "0n";
69743ed5f5eSchristos 				break;
69843ed5f5eSchristos 			}
6995c413d0cSchristos 			if (NULL != n->norm->Bl.width)
7006167eca2Schristos 				mandoc_msg(MANDOCERR_ARG_REP,
7016167eca2Schristos 				    argv->line, argv->pos,
7026167eca2Schristos 				    "Bl -width %s", argv->value[0]);
70314e7489eSchristos 			rewrite_macro2len(mdoc, argv->value);
7045c413d0cSchristos 			n->norm->Bl.width = argv->value[0];
7054154958bSjoerg 			break;
7065c413d0cSchristos 		case MDOC_Offset:
7075c413d0cSchristos 			if (0 == argv->sz) {
7085c413d0cSchristos 				mandoc_msg(MANDOCERR_ARG_EMPTY,
7096167eca2Schristos 				    argv->line, argv->pos, "Bl -offset");
7106c26a9aaSjoerg 				break;
7116c26a9aaSjoerg 			}
7125c413d0cSchristos 			if (NULL != n->norm->Bl.offs)
7136167eca2Schristos 				mandoc_msg(MANDOCERR_ARG_REP,
7146167eca2Schristos 				    argv->line, argv->pos,
7156167eca2Schristos 				    "Bl -offset %s", argv->value[0]);
71614e7489eSchristos 			rewrite_macro2len(mdoc, argv->value);
7175c413d0cSchristos 			n->norm->Bl.offs = argv->value[0];
7184154958bSjoerg 			break;
7197da9b934Sjoerg 		default:
7207da9b934Sjoerg 			continue;
7214154958bSjoerg 		}
7225c413d0cSchristos 		if (LIST__NONE == lt)
7235c413d0cSchristos 			continue;
7245c413d0cSchristos 		mdoclt = argv->arg;
7256c26a9aaSjoerg 
7267574e07eSjoerg 		/* Check: multiple list types. */
7277574e07eSjoerg 
7285c413d0cSchristos 		if (LIST__NONE != n->norm->Bl.type) {
7296167eca2Schristos 			mandoc_msg(MANDOCERR_BL_REP, n->line, n->pos,
7305c413d0cSchristos 			    "Bl -%s", mdoc_argnames[argv->arg]);
7315c413d0cSchristos 			continue;
73282361f10Sjoerg 		}
7337574e07eSjoerg 
7347574e07eSjoerg 		/* The list type should come first. */
7357574e07eSjoerg 
736c0d9444aSjoerg 		if (n->norm->Bl.width ||
737c0d9444aSjoerg 		    n->norm->Bl.offs ||
738c0d9444aSjoerg 		    n->norm->Bl.comp)
7396167eca2Schristos 			mandoc_msg(MANDOCERR_BL_LATETYPE,
7406167eca2Schristos 			    n->line, n->pos, "Bl -%s",
7415c413d0cSchristos 			    mdoc_argnames[n->args->argv[0].arg]);
7427574e07eSjoerg 
7435c413d0cSchristos 		n->norm->Bl.type = lt;
7445c413d0cSchristos 		if (LIST_column == lt) {
7455c413d0cSchristos 			n->norm->Bl.ncols = argv->sz;
7465c413d0cSchristos 			n->norm->Bl.cols = (void *)argv->value;
7475c413d0cSchristos 		}
7487574e07eSjoerg 	}
7497574e07eSjoerg 
7507574e07eSjoerg 	/* Allow lists to default to LIST_item. */
7517574e07eSjoerg 
752c0d9444aSjoerg 	if (LIST__NONE == n->norm->Bl.type) {
7536167eca2Schristos 		mandoc_msg(MANDOCERR_BL_NOTYPE, n->line, n->pos, "Bl");
754c0d9444aSjoerg 		n->norm->Bl.type = LIST_item;
75537ef69edSchristos 		mdoclt = MDOC_Item;
7560a84adc5Sjoerg 	}
7574154958bSjoerg 
7584154958bSjoerg 	/*
7594154958bSjoerg 	 * Validate the width field.  Some list types don't need width
7604154958bSjoerg 	 * types and should be warned about them.  Others should have it
761603fc4ebSjoerg 	 * and must also be warned.  Yet others have a default and need
762603fc4ebSjoerg 	 * no warning.
7634154958bSjoerg 	 */
7644154958bSjoerg 
765c0d9444aSjoerg 	switch (n->norm->Bl.type) {
7665c413d0cSchristos 	case LIST_tag:
76714e7489eSchristos 		if (n->norm->Bl.width == NULL)
7686167eca2Schristos 			mandoc_msg(MANDOCERR_BL_NOWIDTH,
7695c413d0cSchristos 			    n->line, n->pos, "Bl -tag");
7704154958bSjoerg 		break;
7715c413d0cSchristos 	case LIST_column:
7725c413d0cSchristos 	case LIST_diag:
7735c413d0cSchristos 	case LIST_ohang:
7745c413d0cSchristos 	case LIST_inset:
7755c413d0cSchristos 	case LIST_item:
77614e7489eSchristos 		if (n->norm->Bl.width != NULL)
7776167eca2Schristos 			mandoc_msg(MANDOCERR_BL_SKIPW, wa->line, wa->pos,
7786167eca2Schristos 			    "Bl -%s", mdoc_argnames[mdoclt]);
77914e7489eSchristos 		n->norm->Bl.width = NULL;
7807574e07eSjoerg 		break;
7815c413d0cSchristos 	case LIST_bullet:
7825c413d0cSchristos 	case LIST_dash:
7835c413d0cSchristos 	case LIST_hyphen:
78414e7489eSchristos 		if (n->norm->Bl.width == NULL)
785603fc4ebSjoerg 			n->norm->Bl.width = "2n";
786603fc4ebSjoerg 		break;
7875c413d0cSchristos 	case LIST_enum:
78814e7489eSchristos 		if (n->norm->Bl.width == NULL)
789603fc4ebSjoerg 			n->norm->Bl.width = "3n";
790603fc4ebSjoerg 		break;
7914154958bSjoerg 	default:
7924154958bSjoerg 		break;
7934154958bSjoerg 	}
7944154958bSjoerg }
7954154958bSjoerg 
7965c413d0cSchristos static void
post_bd(POST_ARGS)797f47368cfSchristos post_bd(POST_ARGS)
7984154958bSjoerg {
799f47368cfSchristos 	struct roff_node *n;
8005c413d0cSchristos 	struct mdoc_argv *argv;
8015c413d0cSchristos 	int		  i;
8026c26a9aaSjoerg 	enum mdoc_disp	  dt;
8034154958bSjoerg 
804f47368cfSchristos 	n = mdoc->last;
8056c26a9aaSjoerg 	for (i = 0; n->args && i < (int)n->args->argc; i++) {
8065c413d0cSchristos 		argv = n->args->argv + i;
8076c26a9aaSjoerg 		dt = DISP__NONE;
8086c26a9aaSjoerg 
8095c413d0cSchristos 		switch (argv->arg) {
8105c413d0cSchristos 		case MDOC_Centred:
8115c413d0cSchristos 			dt = DISP_centered;
8126c26a9aaSjoerg 			break;
8135c413d0cSchristos 		case MDOC_Ragged:
8146c26a9aaSjoerg 			dt = DISP_ragged;
8156c26a9aaSjoerg 			break;
8165c413d0cSchristos 		case MDOC_Unfilled:
8176c26a9aaSjoerg 			dt = DISP_unfilled;
8186c26a9aaSjoerg 			break;
8195c413d0cSchristos 		case MDOC_Filled:
8206c26a9aaSjoerg 			dt = DISP_filled;
8216c26a9aaSjoerg 			break;
8225c413d0cSchristos 		case MDOC_Literal:
8236c26a9aaSjoerg 			dt = DISP_literal;
8244154958bSjoerg 			break;
8255c413d0cSchristos 		case MDOC_File:
8266167eca2Schristos 			mandoc_msg(MANDOCERR_BD_FILE, n->line, n->pos, NULL);
8275c413d0cSchristos 			break;
8285c413d0cSchristos 		case MDOC_Offset:
8295c413d0cSchristos 			if (0 == argv->sz) {
8305c413d0cSchristos 				mandoc_msg(MANDOCERR_ARG_EMPTY,
8316167eca2Schristos 				    argv->line, argv->pos, "Bd -offset");
8324154958bSjoerg 				break;
8334154958bSjoerg 			}
8345c413d0cSchristos 			if (NULL != n->norm->Bd.offs)
8356167eca2Schristos 				mandoc_msg(MANDOCERR_ARG_REP,
8366167eca2Schristos 				    argv->line, argv->pos,
8376167eca2Schristos 				    "Bd -offset %s", argv->value[0]);
83814e7489eSchristos 			rewrite_macro2len(mdoc, argv->value);
8395c413d0cSchristos 			n->norm->Bd.offs = argv->value[0];
8406c26a9aaSjoerg 			break;
8415c413d0cSchristos 		case MDOC_Compact:
8425c413d0cSchristos 			if (n->norm->Bd.comp)
8435c413d0cSchristos 				mandoc_msg(MANDOCERR_ARG_REP,
8446167eca2Schristos 				    argv->line, argv->pos, "Bd -compact");
8455c413d0cSchristos 			n->norm->Bd.comp = 1;
8466c26a9aaSjoerg 			break;
8476c26a9aaSjoerg 		default:
8486c26a9aaSjoerg 			abort();
8496c26a9aaSjoerg 		}
8505c413d0cSchristos 		if (DISP__NONE == dt)
8515c413d0cSchristos 			continue;
8526c26a9aaSjoerg 
8535c413d0cSchristos 		if (DISP__NONE == n->norm->Bd.type)
854c0d9444aSjoerg 			n->norm->Bd.type = dt;
8555c413d0cSchristos 		else
8566167eca2Schristos 			mandoc_msg(MANDOCERR_BD_REP, n->line, n->pos,
8575c413d0cSchristos 			    "Bd -%s", mdoc_argnames[argv->arg]);
8586c26a9aaSjoerg 	}
8596c26a9aaSjoerg 
860c0d9444aSjoerg 	if (DISP__NONE == n->norm->Bd.type) {
8616167eca2Schristos 		mandoc_msg(MANDOCERR_BD_NOTYPE, n->line, n->pos, "Bd");
862c0d9444aSjoerg 		n->norm->Bd.type = DISP_ragged;
8636c26a9aaSjoerg 	}
8644154958bSjoerg }
8654154958bSjoerg 
86637ef69edSchristos /*
86737ef69edSchristos  * Stand-alone line macros.
86837ef69edSchristos  */
86937ef69edSchristos 
8705c413d0cSchristos static void
post_an_norm(POST_ARGS)871f47368cfSchristos post_an_norm(POST_ARGS)
8724154958bSjoerg {
873f47368cfSchristos 	struct roff_node *n;
8745c413d0cSchristos 	struct mdoc_argv *argv;
8755c413d0cSchristos 	size_t	 i;
8764154958bSjoerg 
877f47368cfSchristos 	n = mdoc->last;
8785c413d0cSchristos 	if (n->args == NULL)
8795c413d0cSchristos 		return;
880c0d9444aSjoerg 
8815c413d0cSchristos 	for (i = 1; i < n->args->argc; i++) {
8825c413d0cSchristos 		argv = n->args->argv + i;
8836167eca2Schristos 		mandoc_msg(MANDOCERR_AN_REP, argv->line, argv->pos,
8845c413d0cSchristos 		    "An -%s", mdoc_argnames[argv->arg]);
8855c413d0cSchristos 	}
88682361f10Sjoerg 
8875c413d0cSchristos 	argv = n->args->argv;
8885c413d0cSchristos 	if (argv->arg == MDOC_Split)
889c0d9444aSjoerg 		n->norm->An.auth = AUTH_split;
8905c413d0cSchristos 	else if (argv->arg == MDOC_Nosplit)
891c0d9444aSjoerg 		n->norm->An.auth = AUTH_nosplit;
89282361f10Sjoerg 	else
89382361f10Sjoerg 		abort();
8944154958bSjoerg }
8954154958bSjoerg 
8965c413d0cSchristos static void
post_eoln(POST_ARGS)89737ef69edSchristos post_eoln(POST_ARGS)
89837ef69edSchristos {
89937ef69edSchristos 	struct roff_node	*n;
90037ef69edSchristos 
90114e7489eSchristos 	post_useless(mdoc);
90237ef69edSchristos 	n = mdoc->last;
90337ef69edSchristos 	if (n->child != NULL)
9046167eca2Schristos 		mandoc_msg(MANDOCERR_ARG_SKIP, n->line,
90514e7489eSchristos 		    n->pos, "%s %s", roff_name[n->tok], n->child->string);
90637ef69edSchristos 
90737ef69edSchristos 	while (n->child != NULL)
90837ef69edSchristos 		roff_node_delete(mdoc, n->child);
90937ef69edSchristos 
91037ef69edSchristos 	roff_word_alloc(mdoc, n->line, n->pos, n->tok == MDOC_Bt ?
91137ef69edSchristos 	    "is currently in beta test." : "currently under development.");
91237ef69edSchristos 	mdoc->last->flags |= NODE_EOS | NODE_NOSRC;
91337ef69edSchristos 	mdoc->last = n;
91437ef69edSchristos }
91537ef69edSchristos 
91637ef69edSchristos static int
build_list(struct roff_man * mdoc,int tok)91737ef69edSchristos build_list(struct roff_man *mdoc, int tok)
91837ef69edSchristos {
91937ef69edSchristos 	struct roff_node	*n;
92037ef69edSchristos 	int			 ic;
92137ef69edSchristos 
92237ef69edSchristos 	n = mdoc->last->next;
92337ef69edSchristos 	for (ic = 1;; ic++) {
92437ef69edSchristos 		roff_elem_alloc(mdoc, n->line, n->pos, tok);
92537ef69edSchristos 		mdoc->last->flags |= NODE_NOSRC;
9266167eca2Schristos 		roff_node_relink(mdoc, n);
92737ef69edSchristos 		n = mdoc->last = mdoc->last->parent;
92837ef69edSchristos 		mdoc->next = ROFF_NEXT_SIBLING;
92937ef69edSchristos 		if (n->next == NULL)
93037ef69edSchristos 			return ic;
93137ef69edSchristos 		if (ic > 1 || n->next->next != NULL) {
93237ef69edSchristos 			roff_word_alloc(mdoc, n->line, n->pos, ",");
93337ef69edSchristos 			mdoc->last->flags |= NODE_DELIMC | NODE_NOSRC;
93437ef69edSchristos 		}
93537ef69edSchristos 		n = mdoc->last->next;
93637ef69edSchristos 		if (n->next == NULL) {
93737ef69edSchristos 			roff_word_alloc(mdoc, n->line, n->pos, "and");
93837ef69edSchristos 			mdoc->last->flags |= NODE_NOSRC;
93937ef69edSchristos 		}
94037ef69edSchristos 	}
94137ef69edSchristos }
94237ef69edSchristos 
94337ef69edSchristos static void
post_ex(POST_ARGS)94437ef69edSchristos post_ex(POST_ARGS)
94537ef69edSchristos {
94637ef69edSchristos 	struct roff_node	*n;
94737ef69edSchristos 	int			 ic;
94837ef69edSchristos 
94937ef69edSchristos 	post_std(mdoc);
95037ef69edSchristos 
95137ef69edSchristos 	n = mdoc->last;
95237ef69edSchristos 	mdoc->next = ROFF_NEXT_CHILD;
95337ef69edSchristos 	roff_word_alloc(mdoc, n->line, n->pos, "The");
95437ef69edSchristos 	mdoc->last->flags |= NODE_NOSRC;
95537ef69edSchristos 
95637ef69edSchristos 	if (mdoc->last->next != NULL)
95737ef69edSchristos 		ic = build_list(mdoc, MDOC_Nm);
95837ef69edSchristos 	else if (mdoc->meta.name != NULL) {
95937ef69edSchristos 		roff_elem_alloc(mdoc, n->line, n->pos, MDOC_Nm);
96037ef69edSchristos 		mdoc->last->flags |= NODE_NOSRC;
96137ef69edSchristos 		roff_word_alloc(mdoc, n->line, n->pos, mdoc->meta.name);
96237ef69edSchristos 		mdoc->last->flags |= NODE_NOSRC;
96337ef69edSchristos 		mdoc->last = mdoc->last->parent;
96437ef69edSchristos 		mdoc->next = ROFF_NEXT_SIBLING;
96537ef69edSchristos 		ic = 1;
96637ef69edSchristos 	} else {
9676167eca2Schristos 		mandoc_msg(MANDOCERR_EX_NONAME, n->line, n->pos, "Ex");
96837ef69edSchristos 		ic = 0;
96937ef69edSchristos 	}
97037ef69edSchristos 
97137ef69edSchristos 	roff_word_alloc(mdoc, n->line, n->pos,
97237ef69edSchristos 	    ic > 1 ? "utilities exit\\~0" : "utility exits\\~0");
97337ef69edSchristos 	mdoc->last->flags |= NODE_NOSRC;
97437ef69edSchristos 	roff_word_alloc(mdoc, n->line, n->pos,
97537ef69edSchristos 	    "on success, and\\~>0 if an error occurs.");
97637ef69edSchristos 	mdoc->last->flags |= NODE_EOS | NODE_NOSRC;
97737ef69edSchristos 	mdoc->last = n;
97837ef69edSchristos }
97937ef69edSchristos 
98037ef69edSchristos static void
post_lb(POST_ARGS)98137ef69edSchristos post_lb(POST_ARGS)
98237ef69edSchristos {
98337ef69edSchristos 	struct roff_node	*n;
98437ef69edSchristos 	const char		*p;
98537ef69edSchristos 
98614e7489eSchristos 	post_delim_nb(mdoc);
98714e7489eSchristos 
98837ef69edSchristos 	n = mdoc->last;
98937ef69edSchristos 	assert(n->child->type == ROFFT_TEXT);
99037ef69edSchristos 	mdoc->next = ROFF_NEXT_CHILD;
99137ef69edSchristos 
99237ef69edSchristos 	if ((p = mdoc_a2lib(n->child->string)) != NULL) {
99337ef69edSchristos 		n->child->flags |= NODE_NOPRT;
99437ef69edSchristos 		roff_word_alloc(mdoc, n->line, n->pos, p);
99537ef69edSchristos 		mdoc->last->flags = NODE_NOSRC;
99637ef69edSchristos 		mdoc->last = n;
99737ef69edSchristos 		return;
99837ef69edSchristos 	}
99937ef69edSchristos 
10006167eca2Schristos 	mandoc_msg(MANDOCERR_LB_BAD, n->child->line,
100114e7489eSchristos 	    n->child->pos, "Lb %s", n->child->string);
100214e7489eSchristos 
100337ef69edSchristos 	roff_word_alloc(mdoc, n->line, n->pos, "library");
100437ef69edSchristos 	mdoc->last->flags = NODE_NOSRC;
100514e7489eSchristos 	roff_word_alloc(mdoc, n->line, n->pos, "\\(lq");
100637ef69edSchristos 	mdoc->last->flags = NODE_DELIMO | NODE_NOSRC;
100737ef69edSchristos 	mdoc->last = mdoc->last->next;
100814e7489eSchristos 	roff_word_alloc(mdoc, n->line, n->pos, "\\(rq");
100937ef69edSchristos 	mdoc->last->flags = NODE_DELIMC | NODE_NOSRC;
101037ef69edSchristos 	mdoc->last = n;
101137ef69edSchristos }
101237ef69edSchristos 
101337ef69edSchristos static void
post_rv(POST_ARGS)101437ef69edSchristos post_rv(POST_ARGS)
101537ef69edSchristos {
101637ef69edSchristos 	struct roff_node	*n;
101737ef69edSchristos 	int			 ic;
101837ef69edSchristos 
101937ef69edSchristos 	post_std(mdoc);
102037ef69edSchristos 
102137ef69edSchristos 	n = mdoc->last;
102237ef69edSchristos 	mdoc->next = ROFF_NEXT_CHILD;
102337ef69edSchristos 	if (n->child != NULL) {
102437ef69edSchristos 		roff_word_alloc(mdoc, n->line, n->pos, "The");
102537ef69edSchristos 		mdoc->last->flags |= NODE_NOSRC;
102637ef69edSchristos 		ic = build_list(mdoc, MDOC_Fn);
102737ef69edSchristos 		roff_word_alloc(mdoc, n->line, n->pos,
102837ef69edSchristos 		    ic > 1 ? "functions return" : "function returns");
102937ef69edSchristos 		mdoc->last->flags |= NODE_NOSRC;
103037ef69edSchristos 		roff_word_alloc(mdoc, n->line, n->pos,
103137ef69edSchristos 		    "the value\\~0 if successful;");
103237ef69edSchristos 	} else
103337ef69edSchristos 		roff_word_alloc(mdoc, n->line, n->pos, "Upon successful "
103437ef69edSchristos 		    "completion, the value\\~0 is returned;");
103537ef69edSchristos 	mdoc->last->flags |= NODE_NOSRC;
103637ef69edSchristos 
103737ef69edSchristos 	roff_word_alloc(mdoc, n->line, n->pos, "otherwise "
103837ef69edSchristos 	    "the value\\~\\-1 is returned and the global variable");
103937ef69edSchristos 	mdoc->last->flags |= NODE_NOSRC;
104037ef69edSchristos 	roff_elem_alloc(mdoc, n->line, n->pos, MDOC_Va);
104137ef69edSchristos 	mdoc->last->flags |= NODE_NOSRC;
104237ef69edSchristos 	roff_word_alloc(mdoc, n->line, n->pos, "errno");
104337ef69edSchristos 	mdoc->last->flags |= NODE_NOSRC;
104437ef69edSchristos 	mdoc->last = mdoc->last->parent;
104537ef69edSchristos 	mdoc->next = ROFF_NEXT_SIBLING;
104637ef69edSchristos 	roff_word_alloc(mdoc, n->line, n->pos,
104737ef69edSchristos 	    "is set to indicate the error.");
104837ef69edSchristos 	mdoc->last->flags |= NODE_EOS | NODE_NOSRC;
104937ef69edSchristos 	mdoc->last = n;
105037ef69edSchristos }
105137ef69edSchristos 
105237ef69edSchristos static void
post_std(POST_ARGS)1053f47368cfSchristos post_std(POST_ARGS)
10544154958bSjoerg {
1055f47368cfSchristos 	struct roff_node *n;
10564154958bSjoerg 
105714e7489eSchristos 	post_delim(mdoc);
105814e7489eSchristos 
1059f47368cfSchristos 	n = mdoc->last;
1060f47368cfSchristos 	if (n->args && n->args->argc == 1)
1061f47368cfSchristos 		if (n->args->argv[0].arg == MDOC_Std)
10625c413d0cSchristos 			return;
10634154958bSjoerg 
10646167eca2Schristos 	mandoc_msg(MANDOCERR_ARG_STD, n->line, n->pos,
10656167eca2Schristos 	    "%s", roff_name[n->tok]);
10667574e07eSjoerg }
10677574e07eSjoerg 
10685c413d0cSchristos static void
post_st(POST_ARGS)106937ef69edSchristos post_st(POST_ARGS)
107037ef69edSchristos {
107137ef69edSchristos 	struct roff_node	 *n, *nch;
107237ef69edSchristos 	const char		 *p;
107337ef69edSchristos 
107437ef69edSchristos 	n = mdoc->last;
107537ef69edSchristos 	nch = n->child;
107637ef69edSchristos 	assert(nch->type == ROFFT_TEXT);
107737ef69edSchristos 
107837ef69edSchristos 	if ((p = mdoc_a2st(nch->string)) == NULL) {
10796167eca2Schristos 		mandoc_msg(MANDOCERR_ST_BAD,
108037ef69edSchristos 		    nch->line, nch->pos, "St %s", nch->string);
108137ef69edSchristos 		roff_node_delete(mdoc, n);
108237ef69edSchristos 		return;
108337ef69edSchristos 	}
108437ef69edSchristos 
108537ef69edSchristos 	nch->flags |= NODE_NOPRT;
108637ef69edSchristos 	mdoc->next = ROFF_NEXT_CHILD;
108737ef69edSchristos 	roff_word_alloc(mdoc, nch->line, nch->pos, p);
108837ef69edSchristos 	mdoc->last->flags |= NODE_NOSRC;
108937ef69edSchristos 	mdoc->last= n;
109037ef69edSchristos }
109137ef69edSchristos 
109237ef69edSchristos static void
post_obsolete(POST_ARGS)1093f47368cfSchristos post_obsolete(POST_ARGS)
10945c413d0cSchristos {
1095f47368cfSchristos 	struct roff_node *n;
10965c413d0cSchristos 
1097f47368cfSchristos 	n = mdoc->last;
1098f47368cfSchristos 	if (n->type == ROFFT_ELEM || n->type == ROFFT_BLOCK)
10996167eca2Schristos 		mandoc_msg(MANDOCERR_MACRO_OBS, n->line, n->pos,
11006167eca2Schristos 		    "%s", roff_name[n->tok]);
110114e7489eSchristos }
110214e7489eSchristos 
110314e7489eSchristos static void
post_useless(POST_ARGS)110414e7489eSchristos post_useless(POST_ARGS)
110514e7489eSchristos {
110614e7489eSchristos 	struct roff_node *n;
110714e7489eSchristos 
110814e7489eSchristos 	n = mdoc->last;
11096167eca2Schristos 	mandoc_msg(MANDOCERR_MACRO_USELESS, n->line, n->pos,
11106167eca2Schristos 	    "%s", roff_name[n->tok]);
11115c413d0cSchristos }
11125c413d0cSchristos 
111337ef69edSchristos /*
111437ef69edSchristos  * Block macros.
111537ef69edSchristos  */
111637ef69edSchristos 
11175c413d0cSchristos static void
post_bf(POST_ARGS)11184154958bSjoerg post_bf(POST_ARGS)
11194154958bSjoerg {
1120f47368cfSchristos 	struct roff_node *np, *nch;
11214154958bSjoerg 
112282361f10Sjoerg 	/*
112382361f10Sjoerg 	 * Unlike other data pointers, these are "housed" by the HEAD
112482361f10Sjoerg 	 * element, which contains the goods.
112582361f10Sjoerg 	 */
112682361f10Sjoerg 
112782361f10Sjoerg 	np = mdoc->last;
1128f47368cfSchristos 	if (np->type != ROFFT_HEAD)
11295c413d0cSchristos 		return;
11305c413d0cSchristos 
1131f47368cfSchristos 	assert(np->parent->type == ROFFT_BLOCK);
1132f47368cfSchristos 	assert(np->parent->tok == MDOC_Bf);
11334154958bSjoerg 
11345c413d0cSchristos 	/* Check the number of arguments. */
11354154958bSjoerg 
11365c413d0cSchristos 	nch = np->child;
1137f47368cfSchristos 	if (np->parent->args == NULL) {
1138f47368cfSchristos 		if (nch == NULL) {
11396167eca2Schristos 			mandoc_msg(MANDOCERR_BF_NOFONT,
11405c413d0cSchristos 			    np->line, np->pos, "Bf");
11415c413d0cSchristos 			return;
1142c0d9444aSjoerg 		}
11435c413d0cSchristos 		nch = nch->next;
11445c413d0cSchristos 	}
1145f47368cfSchristos 	if (nch != NULL)
11466167eca2Schristos 		mandoc_msg(MANDOCERR_ARG_EXCESS,
11475c413d0cSchristos 		    nch->line, nch->pos, "Bf ... %s", nch->string);
114882361f10Sjoerg 
114982361f10Sjoerg 	/* Extract argument into data. */
115082361f10Sjoerg 
1151f47368cfSchristos 	if (np->parent->args != NULL) {
1152f47368cfSchristos 		switch (np->parent->args->argv[0].arg) {
1153f47368cfSchristos 		case MDOC_Emphasis:
1154c0d9444aSjoerg 			np->norm->Bf.font = FONT_Em;
1155f47368cfSchristos 			break;
1156f47368cfSchristos 		case MDOC_Literal:
1157c0d9444aSjoerg 			np->norm->Bf.font = FONT_Li;
1158f47368cfSchristos 			break;
1159f47368cfSchristos 		case MDOC_Symbolic:
1160c0d9444aSjoerg 			np->norm->Bf.font = FONT_Sy;
1161f47368cfSchristos 			break;
1162f47368cfSchristos 		default:
116382361f10Sjoerg 			abort();
1164f47368cfSchristos 		}
11655c413d0cSchristos 		return;
116682361f10Sjoerg 	}
116782361f10Sjoerg 
116882361f10Sjoerg 	/* Extract parameter into data. */
116982361f10Sjoerg 
1170f47368cfSchristos 	if ( ! strcmp(np->child->string, "Em"))
1171c0d9444aSjoerg 		np->norm->Bf.font = FONT_Em;
1172f47368cfSchristos 	else if ( ! strcmp(np->child->string, "Li"))
1173c0d9444aSjoerg 		np->norm->Bf.font = FONT_Li;
1174f47368cfSchristos 	else if ( ! strcmp(np->child->string, "Sy"))
1175c0d9444aSjoerg 		np->norm->Bf.font = FONT_Sy;
1176c0d9444aSjoerg 	else
11776167eca2Schristos 		mandoc_msg(MANDOCERR_BF_BADFONT, np->child->line,
11786167eca2Schristos 		    np->child->pos, "Bf %s", np->child->string);
11794154958bSjoerg }
11804154958bSjoerg 
11815c413d0cSchristos static void
post_fname(POST_ARGS)11825c413d0cSchristos post_fname(POST_ARGS)
11835c413d0cSchristos {
1184f47368cfSchristos 	const struct roff_node	*n;
11855c413d0cSchristos 	const char		*cp;
11865c413d0cSchristos 	size_t			 pos;
11874154958bSjoerg 
11885c413d0cSchristos 	n = mdoc->last->child;
11895c413d0cSchristos 	pos = strcspn(n->string, "()");
11905c413d0cSchristos 	cp = n->string + pos;
11915c413d0cSchristos 	if ( ! (cp[0] == '\0' || (cp[0] == '(' && cp[1] == '*')))
11926167eca2Schristos 		mandoc_msg(MANDOCERR_FN_PAREN, n->line, n->pos + pos,
11936167eca2Schristos 		    "%s", n->string);
11945c413d0cSchristos }
11955c413d0cSchristos 
11965c413d0cSchristos static void
post_fn(POST_ARGS)11975c413d0cSchristos post_fn(POST_ARGS)
11985c413d0cSchristos {
11995c413d0cSchristos 
12005c413d0cSchristos 	post_fname(mdoc);
12015c413d0cSchristos 	post_fa(mdoc);
12025c413d0cSchristos }
12035c413d0cSchristos 
12045c413d0cSchristos static void
post_fo(POST_ARGS)12055c413d0cSchristos post_fo(POST_ARGS)
12065c413d0cSchristos {
1207f47368cfSchristos 	const struct roff_node	*n;
12085c413d0cSchristos 
12095c413d0cSchristos 	n = mdoc->last;
12105c413d0cSchristos 
1211f47368cfSchristos 	if (n->type != ROFFT_HEAD)
12125c413d0cSchristos 		return;
12135c413d0cSchristos 
12145c413d0cSchristos 	if (n->child == NULL) {
12156167eca2Schristos 		mandoc_msg(MANDOCERR_FO_NOHEAD, n->line, n->pos, "Fo");
12165c413d0cSchristos 		return;
12175c413d0cSchristos 	}
12185c413d0cSchristos 	if (n->child != n->last) {
12196167eca2Schristos 		mandoc_msg(MANDOCERR_ARG_EXCESS,
12205c413d0cSchristos 		    n->child->next->line, n->child->next->pos,
12215c413d0cSchristos 		    "Fo ... %s", n->child->next->string);
1222cde7fc85Schristos 		while (n->child != n->last) {
12239b35adc8Schristos 			struct roff_node *p = n->last;
12249b35adc8Schristos 			roff_node_delete(mdoc, p);
1225cde7fc85Schristos 		}
1226cde7fc85Schristos 
122714e7489eSchristos 	} else
122814e7489eSchristos 		post_delim(mdoc);
12295c413d0cSchristos 
12305c413d0cSchristos 	post_fname(mdoc);
12315c413d0cSchristos }
12325c413d0cSchristos 
12335c413d0cSchristos static void
post_fa(POST_ARGS)12345c413d0cSchristos post_fa(POST_ARGS)
12355c413d0cSchristos {
1236f47368cfSchristos 	const struct roff_node *n;
12375c413d0cSchristos 	const char *cp;
12385c413d0cSchristos 
12395c413d0cSchristos 	for (n = mdoc->last->child; n != NULL; n = n->next) {
12405c413d0cSchristos 		for (cp = n->string; *cp != '\0'; cp++) {
12415c413d0cSchristos 			/* Ignore callbacks and alterations. */
12425c413d0cSchristos 			if (*cp == '(' || *cp == '{')
12435c413d0cSchristos 				break;
12445c413d0cSchristos 			if (*cp != ',')
12455c413d0cSchristos 				continue;
12466167eca2Schristos 			mandoc_msg(MANDOCERR_FA_COMMA, n->line,
12476167eca2Schristos 			    n->pos + (int)(cp - n->string), "%s", n->string);
12485c413d0cSchristos 			break;
12495c413d0cSchristos 		}
12505c413d0cSchristos 	}
125114e7489eSchristos 	post_delim_nb(mdoc);
12525c413d0cSchristos }
12535c413d0cSchristos 
12545c413d0cSchristos static void
post_nm(POST_ARGS)12554154958bSjoerg post_nm(POST_ARGS)
12564154958bSjoerg {
1257f47368cfSchristos 	struct roff_node	*n;
12585c413d0cSchristos 
12595c413d0cSchristos 	n = mdoc->last;
12605c413d0cSchristos 
126114e7489eSchristos 	if (n->sec == SEC_NAME && n->child != NULL &&
126214e7489eSchristos 	    n->child->type == ROFFT_TEXT && mdoc->meta.msec != NULL)
126314e7489eSchristos 		mandoc_xr_add(mdoc->meta.msec, n->child->string, -1, -1);
126414e7489eSchristos 
12656167eca2Schristos 	if (n->last != NULL && n->last->tok == MDOC_Pp)
12666167eca2Schristos 		roff_node_relink(mdoc, n->last);
1267c0d9444aSjoerg 
126837ef69edSchristos 	if (mdoc->meta.name == NULL)
1269f47368cfSchristos 		deroff(&mdoc->meta.name, n);
1270c0d9444aSjoerg 
127137ef69edSchristos 	if (mdoc->meta.name == NULL ||
127237ef69edSchristos 	    (mdoc->lastsec == SEC_NAME && n->child == NULL))
12736167eca2Schristos 		mandoc_msg(MANDOCERR_NM_NONAME, n->line, n->pos, "Nm");
127437ef69edSchristos 
127514e7489eSchristos 	switch (n->type) {
127614e7489eSchristos 	case ROFFT_ELEM:
127714e7489eSchristos 		post_delim_nb(mdoc);
127814e7489eSchristos 		break;
127914e7489eSchristos 	case ROFFT_HEAD:
128014e7489eSchristos 		post_delim(mdoc);
128114e7489eSchristos 		break;
128214e7489eSchristos 	default:
128314e7489eSchristos 		return;
128414e7489eSchristos 	}
128514e7489eSchristos 
128614e7489eSchristos 	if ((n->child != NULL && n->child->type == ROFFT_TEXT) ||
128737ef69edSchristos 	    mdoc->meta.name == NULL)
128837ef69edSchristos 		return;
128937ef69edSchristos 
129037ef69edSchristos 	mdoc->next = ROFF_NEXT_CHILD;
129137ef69edSchristos 	roff_word_alloc(mdoc, n->line, n->pos, mdoc->meta.name);
129237ef69edSchristos 	mdoc->last->flags |= NODE_NOSRC;
129337ef69edSchristos 	mdoc->last = n;
1294c0d9444aSjoerg }
1295c0d9444aSjoerg 
12965c413d0cSchristos static void
post_nd(POST_ARGS)12975c413d0cSchristos post_nd(POST_ARGS)
12985c413d0cSchristos {
1299f47368cfSchristos 	struct roff_node	*n;
13005c413d0cSchristos 
13015c413d0cSchristos 	n = mdoc->last;
13025c413d0cSchristos 
1303f47368cfSchristos 	if (n->type != ROFFT_BODY)
13045c413d0cSchristos 		return;
13055c413d0cSchristos 
130614e7489eSchristos 	if (n->sec != SEC_NAME)
13076167eca2Schristos 		mandoc_msg(MANDOCERR_ND_LATE, n->line, n->pos, "Nd");
130814e7489eSchristos 
13095c413d0cSchristos 	if (n->child == NULL)
13106167eca2Schristos 		mandoc_msg(MANDOCERR_ND_EMPTY, n->line, n->pos, "Nd");
131114e7489eSchristos 	else
131214e7489eSchristos 		post_delim(mdoc);
13135c413d0cSchristos 
13145c413d0cSchristos 	post_hyph(mdoc);
13155c413d0cSchristos }
13165c413d0cSchristos 
13175c413d0cSchristos static void
post_display(POST_ARGS)1318f47368cfSchristos post_display(POST_ARGS)
13195c413d0cSchristos {
1320f47368cfSchristos 	struct roff_node *n, *np;
13215c413d0cSchristos 
13225c413d0cSchristos 	n = mdoc->last;
1323f47368cfSchristos 	switch (n->type) {
1324f47368cfSchristos 	case ROFFT_BODY:
132537ef69edSchristos 		if (n->end != ENDBODY_NOT) {
132637ef69edSchristos 			if (n->tok == MDOC_Bd &&
132737ef69edSchristos 			    n->body->parent->args == NULL)
132837ef69edSchristos 				roff_node_delete(mdoc, n);
132937ef69edSchristos 		} else if (n->child == NULL)
13306167eca2Schristos 			mandoc_msg(MANDOCERR_BLK_EMPTY, n->line, n->pos,
13316167eca2Schristos 			    "%s", roff_name[n->tok]);
1332f47368cfSchristos 		else if (n->tok == MDOC_D1)
1333f47368cfSchristos 			post_hyph(mdoc);
1334f47368cfSchristos 		break;
1335f47368cfSchristos 	case ROFFT_BLOCK:
1336f47368cfSchristos 		if (n->tok == MDOC_Bd) {
1337f47368cfSchristos 			if (n->args == NULL) {
1338f47368cfSchristos 				mandoc_msg(MANDOCERR_BD_NOARG,
13396167eca2Schristos 				    n->line, n->pos, "Bd");
1340f47368cfSchristos 				mdoc->next = ROFF_NEXT_SIBLING;
1341f47368cfSchristos 				while (n->body->child != NULL)
13426167eca2Schristos 					roff_node_relink(mdoc,
1343f47368cfSchristos 					    n->body->child);
1344f47368cfSchristos 				roff_node_delete(mdoc, n);
1345f47368cfSchristos 				break;
1346f47368cfSchristos 			}
1347f47368cfSchristos 			post_bd(mdoc);
1348f47368cfSchristos 			post_prevpar(mdoc);
1349f47368cfSchristos 		}
1350f47368cfSchristos 		for (np = n->parent; np != NULL; np = np->parent) {
1351f47368cfSchristos 			if (np->type == ROFFT_BLOCK && np->tok == MDOC_Bd) {
13526167eca2Schristos 				mandoc_msg(MANDOCERR_BD_NEST, n->line,
13536167eca2Schristos 				    n->pos, "%s in Bd", roff_name[n->tok]);
1354f47368cfSchristos 				break;
1355f47368cfSchristos 			}
1356f47368cfSchristos 		}
1357f47368cfSchristos 		break;
1358f47368cfSchristos 	default:
1359f47368cfSchristos 		break;
1360f47368cfSchristos 	}
1361c0d9444aSjoerg }
1362c0d9444aSjoerg 
13635c413d0cSchristos static void
post_defaults(POST_ARGS)1364c0d9444aSjoerg post_defaults(POST_ARGS)
1365c0d9444aSjoerg {
1366f47368cfSchristos 	struct roff_node *nn;
1367c0d9444aSjoerg 
136814e7489eSchristos 	if (mdoc->last->child != NULL) {
136914e7489eSchristos 		post_delim_nb(mdoc);
137014e7489eSchristos 		return;
137114e7489eSchristos 	}
137214e7489eSchristos 
1373c0d9444aSjoerg 	/*
1374c0d9444aSjoerg 	 * The `Ar' defaults to "file ..." if no value is provided as an
1375c0d9444aSjoerg 	 * argument; the `Mt' and `Pa' macros use "~"; the `Li' just
1376c0d9444aSjoerg 	 * gets an empty string.
1377c0d9444aSjoerg 	 */
13784154958bSjoerg 
1379c0d9444aSjoerg 	nn = mdoc->last;
1380c0d9444aSjoerg 	switch (nn->tok) {
13815c413d0cSchristos 	case MDOC_Ar:
1382f47368cfSchristos 		mdoc->next = ROFF_NEXT_CHILD;
1383f47368cfSchristos 		roff_word_alloc(mdoc, nn->line, nn->pos, "file");
138437ef69edSchristos 		mdoc->last->flags |= NODE_NOSRC;
1385f47368cfSchristos 		roff_word_alloc(mdoc, nn->line, nn->pos, "...");
138637ef69edSchristos 		mdoc->last->flags |= NODE_NOSRC;
1387c0d9444aSjoerg 		break;
13885c413d0cSchristos 	case MDOC_Pa:
13895c413d0cSchristos 	case MDOC_Mt:
1390f47368cfSchristos 		mdoc->next = ROFF_NEXT_CHILD;
1391f47368cfSchristos 		roff_word_alloc(mdoc, nn->line, nn->pos, "~");
139237ef69edSchristos 		mdoc->last->flags |= NODE_NOSRC;
1393c0d9444aSjoerg 		break;
1394c0d9444aSjoerg 	default:
1395c0d9444aSjoerg 		abort();
13964154958bSjoerg 	}
1397c0d9444aSjoerg 	mdoc->last = nn;
1398c0d9444aSjoerg }
13994154958bSjoerg 
14005c413d0cSchristos static void
post_at(POST_ARGS)14014154958bSjoerg post_at(POST_ARGS)
14024154958bSjoerg {
140337ef69edSchristos 	struct roff_node	*n, *nch;
140437ef69edSchristos 	const char		*att;
14055c413d0cSchristos 
14065c413d0cSchristos 	n = mdoc->last;
140737ef69edSchristos 	nch = n->child;
1408c0d9444aSjoerg 
1409c0d9444aSjoerg 	/*
1410c0d9444aSjoerg 	 * If we have a child, look it up in the standard keys.  If a
1411c0d9444aSjoerg 	 * key exist, use that instead of the child; if it doesn't,
1412c0d9444aSjoerg 	 * prefix "AT&T UNIX " to the existing data.
1413c0d9444aSjoerg 	 */
14144154958bSjoerg 
141537ef69edSchristos 	att = NULL;
141637ef69edSchristos 	if (nch != NULL && ((att = mdoc_a2att(nch->string)) == NULL))
14176167eca2Schristos 		mandoc_msg(MANDOCERR_AT_BAD,
141837ef69edSchristos 		    nch->line, nch->pos, "At %s", nch->string);
1419c0d9444aSjoerg 
142037ef69edSchristos 	mdoc->next = ROFF_NEXT_CHILD;
142137ef69edSchristos 	if (att != NULL) {
142237ef69edSchristos 		roff_word_alloc(mdoc, nch->line, nch->pos, att);
142337ef69edSchristos 		nch->flags |= NODE_NOPRT;
142437ef69edSchristos 	} else
142537ef69edSchristos 		roff_word_alloc(mdoc, n->line, n->pos, "AT&T UNIX");
142637ef69edSchristos 	mdoc->last->flags |= NODE_NOSRC;
142737ef69edSchristos 	mdoc->last = n;
14284154958bSjoerg }
14294154958bSjoerg 
14305c413d0cSchristos static void
post_an(POST_ARGS)14314154958bSjoerg post_an(POST_ARGS)
14324154958bSjoerg {
1433f47368cfSchristos 	struct roff_node *np, *nch;
1434f47368cfSchristos 
1435f47368cfSchristos 	post_an_norm(mdoc);
14364154958bSjoerg 
143782361f10Sjoerg 	np = mdoc->last;
14385c413d0cSchristos 	nch = np->child;
14395c413d0cSchristos 	if (np->norm->An.auth == AUTH__NONE) {
14405c413d0cSchristos 		if (nch == NULL)
14416167eca2Schristos 			mandoc_msg(MANDOCERR_MACRO_EMPTY,
14425c413d0cSchristos 			    np->line, np->pos, "An");
144314e7489eSchristos 		else
144414e7489eSchristos 			post_delim_nb(mdoc);
14455c413d0cSchristos 	} else if (nch != NULL)
14466167eca2Schristos 		mandoc_msg(MANDOCERR_ARG_EXCESS,
14475c413d0cSchristos 		    nch->line, nch->pos, "An ... %s", nch->string);
14484154958bSjoerg }
14494154958bSjoerg 
14505c413d0cSchristos static void
post_en(POST_ARGS)14515c413d0cSchristos post_en(POST_ARGS)
14525c413d0cSchristos {
14534154958bSjoerg 
1454f47368cfSchristos 	post_obsolete(mdoc);
1455f47368cfSchristos 	if (mdoc->last->type == ROFFT_BLOCK)
14565c413d0cSchristos 		mdoc->last->norm->Es = mdoc->last_es;
14575c413d0cSchristos }
14585c413d0cSchristos 
14595c413d0cSchristos static void
post_es(POST_ARGS)14605c413d0cSchristos post_es(POST_ARGS)
14615c413d0cSchristos {
14625c413d0cSchristos 
1463f47368cfSchristos 	post_obsolete(mdoc);
14645c413d0cSchristos 	mdoc->last_es = mdoc->last;
14655c413d0cSchristos }
14665c413d0cSchristos 
14675c413d0cSchristos static void
post_xx(POST_ARGS)146837ef69edSchristos post_xx(POST_ARGS)
146937ef69edSchristos {
147037ef69edSchristos 	struct roff_node	*n;
147137ef69edSchristos 	const char		*os;
147214e7489eSchristos 	char			*v;
147314e7489eSchristos 
147414e7489eSchristos 	post_delim_nb(mdoc);
147537ef69edSchristos 
147637ef69edSchristos 	n = mdoc->last;
147737ef69edSchristos 	switch (n->tok) {
147837ef69edSchristos 	case MDOC_Bsx:
147937ef69edSchristos 		os = "BSD/OS";
148037ef69edSchristos 		break;
148137ef69edSchristos 	case MDOC_Dx:
148237ef69edSchristos 		os = "DragonFly";
148337ef69edSchristos 		break;
148437ef69edSchristos 	case MDOC_Fx:
148537ef69edSchristos 		os = "FreeBSD";
148637ef69edSchristos 		break;
148737ef69edSchristos 	case MDOC_Nx:
148837ef69edSchristos 		os = "NetBSD";
148914e7489eSchristos 		if (n->child == NULL)
149014e7489eSchristos 			break;
149114e7489eSchristos 		v = n->child->string;
149214e7489eSchristos 		if ((v[0] != '0' && v[0] != '1') || v[1] != '.' ||
149314e7489eSchristos 		    v[2] < '0' || v[2] > '9' ||
149414e7489eSchristos 		    v[3] < 'a' || v[3] > 'z' || v[4] != '\0')
149514e7489eSchristos 			break;
149614e7489eSchristos 		n->child->flags |= NODE_NOPRT;
149714e7489eSchristos 		mdoc->next = ROFF_NEXT_CHILD;
149814e7489eSchristos 		roff_word_alloc(mdoc, n->child->line, n->child->pos, v);
149914e7489eSchristos 		v = mdoc->last->string;
150014e7489eSchristos 		v[3] = toupper((unsigned char)v[3]);
150114e7489eSchristos 		mdoc->last->flags |= NODE_NOSRC;
150214e7489eSchristos 		mdoc->last = n;
150337ef69edSchristos 		break;
150437ef69edSchristos 	case MDOC_Ox:
150537ef69edSchristos 		os = "OpenBSD";
150637ef69edSchristos 		break;
150737ef69edSchristos 	case MDOC_Ux:
150837ef69edSchristos 		os = "UNIX";
150937ef69edSchristos 		break;
151037ef69edSchristos 	default:
151137ef69edSchristos 		abort();
151237ef69edSchristos 	}
151337ef69edSchristos 	mdoc->next = ROFF_NEXT_CHILD;
151437ef69edSchristos 	roff_word_alloc(mdoc, n->line, n->pos, os);
151537ef69edSchristos 	mdoc->last->flags |= NODE_NOSRC;
151637ef69edSchristos 	mdoc->last = n;
151737ef69edSchristos }
151837ef69edSchristos 
151937ef69edSchristos static void
post_it(POST_ARGS)15204154958bSjoerg post_it(POST_ARGS)
15214154958bSjoerg {
1522f47368cfSchristos 	struct roff_node *nbl, *nit, *nch;
152348741257Sjoerg 	int		  i, cols;
15247574e07eSjoerg 	enum mdoc_list	  lt;
1525f47368cfSchristos 
1526f47368cfSchristos 	post_prevpar(mdoc);
15274154958bSjoerg 
15285c413d0cSchristos 	nit = mdoc->last;
1529f47368cfSchristos 	if (nit->type != ROFFT_BLOCK)
15305c413d0cSchristos 		return;
15314154958bSjoerg 
15325c413d0cSchristos 	nbl = nit->parent->parent;
15335c413d0cSchristos 	lt = nbl->norm->Bl.type;
15344154958bSjoerg 
15357574e07eSjoerg 	switch (lt) {
15365c413d0cSchristos 	case LIST_tag:
15375c413d0cSchristos 	case LIST_hang:
15385c413d0cSchristos 	case LIST_ohang:
15395c413d0cSchristos 	case LIST_inset:
15405c413d0cSchristos 	case LIST_diag:
15415c413d0cSchristos 		if (nit->head->child == NULL)
15426167eca2Schristos 			mandoc_msg(MANDOCERR_IT_NOHEAD,
15436167eca2Schristos 			    nit->line, nit->pos, "Bl -%s It",
15445c413d0cSchristos 			    mdoc_argnames[nbl->args->argv[0].arg]);
15454154958bSjoerg 		break;
15465c413d0cSchristos 	case LIST_bullet:
15475c413d0cSchristos 	case LIST_dash:
15485c413d0cSchristos 	case LIST_enum:
15495c413d0cSchristos 	case LIST_hyphen:
15505c413d0cSchristos 		if (nit->body == NULL || nit->body->child == NULL)
15516167eca2Schristos 			mandoc_msg(MANDOCERR_IT_NOBODY,
15526167eca2Schristos 			    nit->line, nit->pos, "Bl -%s It",
15535c413d0cSchristos 			    mdoc_argnames[nbl->args->argv[0].arg]);
15545c413d0cSchristos 		/* FALLTHROUGH */
15555c413d0cSchristos 	case LIST_item:
155637ef69edSchristos 		if ((nch = nit->head->child) != NULL)
15576167eca2Schristos 			mandoc_msg(MANDOCERR_ARG_SKIP,
155814e7489eSchristos 			    nit->line, nit->pos, "It %s",
155914e7489eSchristos 			    nch->string == NULL ? roff_name[nch->tok] :
156014e7489eSchristos 			    nch->string);
15614154958bSjoerg 		break;
15625c413d0cSchristos 	case LIST_column:
15635c413d0cSchristos 		cols = (int)nbl->norm->Bl.ncols;
15647574e07eSjoerg 
15655c413d0cSchristos 		assert(nit->head->child == NULL);
15667574e07eSjoerg 
156714e7489eSchristos 		if (nit->head->next->child == NULL &&
156814e7489eSchristos 		    nit->head->next->next == NULL) {
15696167eca2Schristos 			mandoc_msg(MANDOCERR_MACRO_EMPTY,
157014e7489eSchristos 			    nit->line, nit->pos, "It");
157114e7489eSchristos 			roff_node_delete(mdoc, nit);
157214e7489eSchristos 			break;
157314e7489eSchristos 		}
15744154958bSjoerg 
157514e7489eSchristos 		i = 0;
157614e7489eSchristos 		for (nch = nit->child; nch != NULL; nch = nch->next) {
157714e7489eSchristos 			if (nch->type != ROFFT_BODY)
157814e7489eSchristos 				continue;
157914e7489eSchristos 			if (i++ && nch->flags & NODE_LINE)
15806167eca2Schristos 				mandoc_msg(MANDOCERR_TA_LINE,
158114e7489eSchristos 				    nch->line, nch->pos, "Ta");
158214e7489eSchristos 		}
15835c413d0cSchristos 		if (i < cols || i > cols + 1)
15846167eca2Schristos 			mandoc_msg(MANDOCERR_BL_COL, nit->line, nit->pos,
15855c413d0cSchristos 			    "%d columns, %d cells", cols, i);
158614e7489eSchristos 		else if (nit->head->next->child != NULL &&
15876167eca2Schristos 		    nit->head->next->child->flags & NODE_LINE)
15886167eca2Schristos 			mandoc_msg(MANDOCERR_IT_NOARG,
158914e7489eSchristos 			    nit->line, nit->pos, "Bl -column It");
15904154958bSjoerg 		break;
15914154958bSjoerg 	default:
15925c413d0cSchristos 		abort();
15935c413d0cSchristos 	}
15944154958bSjoerg }
15954154958bSjoerg 
15965c413d0cSchristos static void
post_bl_block(POST_ARGS)1597c0d9444aSjoerg post_bl_block(POST_ARGS)
1598c0d9444aSjoerg {
1599f47368cfSchristos 	struct roff_node *n, *ni, *nc;
1600f47368cfSchristos 
1601f47368cfSchristos 	post_prevpar(mdoc);
1602c0d9444aSjoerg 
1603c0d9444aSjoerg 	n = mdoc->last;
1604f47368cfSchristos 	for (ni = n->body->child; ni != NULL; ni = ni->next) {
1605f47368cfSchristos 		if (ni->body == NULL)
1606603fc4ebSjoerg 			continue;
1607603fc4ebSjoerg 		nc = ni->body->last;
1608f47368cfSchristos 		while (nc != NULL) {
1609603fc4ebSjoerg 			switch (nc->tok) {
16105c413d0cSchristos 			case MDOC_Pp:
161114e7489eSchristos 			case ROFF_br:
1612603fc4ebSjoerg 				break;
1613603fc4ebSjoerg 			default:
1614603fc4ebSjoerg 				nc = NULL;
1615603fc4ebSjoerg 				continue;
1616603fc4ebSjoerg 			}
1617f47368cfSchristos 			if (ni->next == NULL) {
16186167eca2Schristos 				mandoc_msg(MANDOCERR_PAR_MOVE, nc->line,
16196167eca2Schristos 				    nc->pos, "%s", roff_name[nc->tok]);
16206167eca2Schristos 				roff_node_relink(mdoc, nc);
1621f47368cfSchristos 			} else if (n->norm->Bl.comp == 0 &&
1622f47368cfSchristos 			    n->norm->Bl.type != LIST_column) {
16236167eca2Schristos 				mandoc_msg(MANDOCERR_PAR_SKIP,
16246167eca2Schristos 				    nc->line, nc->pos,
162514e7489eSchristos 				    "%s before It", roff_name[nc->tok]);
1626f47368cfSchristos 				roff_node_delete(mdoc, nc);
1627603fc4ebSjoerg 			} else
1628603fc4ebSjoerg 				break;
1629603fc4ebSjoerg 			nc = ni->body->last;
1630603fc4ebSjoerg 		}
1631603fc4ebSjoerg 	}
1632c0d9444aSjoerg }
1633c0d9444aSjoerg 
1634c0d9444aSjoerg /*
16355c413d0cSchristos  * If the argument of -offset or -width is a macro,
16365c413d0cSchristos  * replace it with the associated default width.
1637c0d9444aSjoerg  */
163814e7489eSchristos static void
rewrite_macro2len(struct roff_man * mdoc,char ** arg)163914e7489eSchristos rewrite_macro2len(struct roff_man *mdoc, char **arg)
16405c413d0cSchristos {
16415c413d0cSchristos 	size_t		  width;
164214e7489eSchristos 	enum roff_tok	  tok;
1643c0d9444aSjoerg 
16445c413d0cSchristos 	if (*arg == NULL)
16455c413d0cSchristos 		return;
16465c413d0cSchristos 	else if ( ! strcmp(*arg, "Ds"))
1647c0d9444aSjoerg 		width = 6;
164814e7489eSchristos 	else if ((tok = roffhash_find(mdoc->mdocmac, *arg, 0)) == TOKEN_NONE)
16495c413d0cSchristos 		return;
16505c413d0cSchristos 	else
16515c413d0cSchristos 		width = macro2len(tok);
16525c413d0cSchristos 
16535c413d0cSchristos 	free(*arg);
16545c413d0cSchristos 	mandoc_asprintf(arg, "%zun", width);
1655c0d9444aSjoerg }
1656c0d9444aSjoerg 
16575c413d0cSchristos static void
post_bl_head(POST_ARGS)16584154958bSjoerg post_bl_head(POST_ARGS)
16594154958bSjoerg {
1660f47368cfSchristos 	struct roff_node *nbl, *nh, *nch, *nnext;
16615c413d0cSchristos 	struct mdoc_argv *argv;
1662c0d9444aSjoerg 	int		  i, j;
16634154958bSjoerg 
1664f47368cfSchristos 	post_bl_norm(mdoc);
16655c413d0cSchristos 
1666f47368cfSchristos 	nh = mdoc->last;
16675c413d0cSchristos 	if (nh->norm->Bl.type != LIST_column) {
16685c413d0cSchristos 		if ((nch = nh->child) == NULL)
16695c413d0cSchristos 			return;
16706167eca2Schristos 		mandoc_msg(MANDOCERR_ARG_EXCESS,
16715c413d0cSchristos 		    nch->line, nch->pos, "Bl ... %s", nch->string);
16725c413d0cSchristos 		while (nch != NULL) {
1673f47368cfSchristos 			roff_node_delete(mdoc, nch);
16745c413d0cSchristos 			nch = nh->child;
16755c413d0cSchristos 		}
16765c413d0cSchristos 		return;
16775c413d0cSchristos 	}
16784154958bSjoerg 
1679c0d9444aSjoerg 	/*
16805c413d0cSchristos 	 * Append old-style lists, where the column width specifiers
1681c0d9444aSjoerg 	 * trail as macro parameters, to the new-style ("normal-form")
1682c0d9444aSjoerg 	 * lists where they're argument values following -column.
1683c0d9444aSjoerg 	 */
1684c0d9444aSjoerg 
16855c413d0cSchristos 	if (nh->child == NULL)
16865c413d0cSchristos 		return;
1687c0d9444aSjoerg 
16885c413d0cSchristos 	nbl = nh->parent;
16895c413d0cSchristos 	for (j = 0; j < (int)nbl->args->argc; j++)
16905c413d0cSchristos 		if (nbl->args->argv[j].arg == MDOC_Column)
1691c0d9444aSjoerg 			break;
1692c0d9444aSjoerg 
16935c413d0cSchristos 	assert(j < (int)nbl->args->argc);
1694c0d9444aSjoerg 
1695c0d9444aSjoerg 	/*
1696b1e8115bSjoerg 	 * Accommodate for new-style groff column syntax.  Shuffle the
1697c0d9444aSjoerg 	 * child nodes, all of which must be TEXT, as arguments for the
1698c0d9444aSjoerg 	 * column field.  Then, delete the head children.
1699c0d9444aSjoerg 	 */
1700c0d9444aSjoerg 
17015c413d0cSchristos 	argv = nbl->args->argv + j;
17025c413d0cSchristos 	i = argv->sz;
1703f47368cfSchristos 	for (nch = nh->child; nch != NULL; nch = nch->next)
1704f47368cfSchristos 		argv->sz++;
17055c413d0cSchristos 	argv->value = mandoc_reallocarray(argv->value,
17065c413d0cSchristos 	    argv->sz, sizeof(char *));
1707c0d9444aSjoerg 
17085c413d0cSchristos 	nh->norm->Bl.ncols = argv->sz;
17095c413d0cSchristos 	nh->norm->Bl.cols = (void *)argv->value;
1710c0d9444aSjoerg 
17115c413d0cSchristos 	for (nch = nh->child; nch != NULL; nch = nnext) {
17125c413d0cSchristos 		argv->value[i++] = nch->string;
17135c413d0cSchristos 		nch->string = NULL;
17145c413d0cSchristos 		nnext = nch->next;
1715f47368cfSchristos 		roff_node_delete(NULL, nch);
17165c413d0cSchristos 	}
17175c413d0cSchristos 	nh->child = NULL;
17187bcc2a5fSjoerg }
17197bcc2a5fSjoerg 
17205c413d0cSchristos static void
post_bl(POST_ARGS)17214154958bSjoerg post_bl(POST_ARGS)
17224154958bSjoerg {
1723f47368cfSchristos 	struct roff_node	*nparent, *nprev; /* of the Bl block */
1724f47368cfSchristos 	struct roff_node	*nblock, *nbody;  /* of the Bl */
1725f47368cfSchristos 	struct roff_node	*nchild, *nnext;  /* of the Bl body */
172614e7489eSchristos 	const char		*prev_Er;
172714e7489eSchristos 	int			 order;
17284154958bSjoerg 
1729603fc4ebSjoerg 	nbody = mdoc->last;
1730603fc4ebSjoerg 	switch (nbody->type) {
1731f47368cfSchristos 	case ROFFT_BLOCK:
17325c413d0cSchristos 		post_bl_block(mdoc);
17335c413d0cSchristos 		return;
1734f47368cfSchristos 	case ROFFT_HEAD:
17355c413d0cSchristos 		post_bl_head(mdoc);
17365c413d0cSchristos 		return;
1737f47368cfSchristos 	case ROFFT_BODY:
1738c0d9444aSjoerg 		break;
1739603fc4ebSjoerg 	default:
17405c413d0cSchristos 		return;
1741c0d9444aSjoerg 	}
1742f47368cfSchristos 	if (nbody->end != ENDBODY_NOT)
1743f47368cfSchristos 		return;
1744c0d9444aSjoerg 
1745603fc4ebSjoerg 	nchild = nbody->child;
17465c413d0cSchristos 	if (nchild == NULL) {
17476167eca2Schristos 		mandoc_msg(MANDOCERR_BLK_EMPTY,
17485c413d0cSchristos 		    nbody->line, nbody->pos, "Bl");
17495c413d0cSchristos 		return;
17505c413d0cSchristos 	}
17515c413d0cSchristos 	while (nchild != NULL) {
175237ef69edSchristos 		nnext = nchild->next;
17535c413d0cSchristos 		if (nchild->tok == MDOC_It ||
17545c413d0cSchristos 		    (nchild->tok == MDOC_Sm &&
175537ef69edSchristos 		     nnext != NULL && nnext->tok == MDOC_It)) {
175637ef69edSchristos 			nchild = nnext;
175737ef69edSchristos 			continue;
175837ef69edSchristos 		}
175937ef69edSchristos 
176037ef69edSchristos 		/*
176137ef69edSchristos 		 * In .Bl -column, the first rows may be implicit,
176237ef69edSchristos 		 * that is, they may not start with .It macros.
176337ef69edSchristos 		 * Such rows may be followed by nodes generated on the
176437ef69edSchristos 		 * roff level, for example .TS, which cannot be moved
176537ef69edSchristos 		 * out of the list.  In that case, wrap such roff nodes
176637ef69edSchristos 		 * into an implicit row.
176737ef69edSchristos 		 */
176837ef69edSchristos 
176937ef69edSchristos 		if (nchild->prev != NULL) {
177037ef69edSchristos 			mdoc->last = nchild;
177137ef69edSchristos 			mdoc->next = ROFF_NEXT_SIBLING;
177237ef69edSchristos 			roff_block_alloc(mdoc, nchild->line,
177337ef69edSchristos 			    nchild->pos, MDOC_It);
177437ef69edSchristos 			roff_head_alloc(mdoc, nchild->line,
177537ef69edSchristos 			    nchild->pos, MDOC_It);
177637ef69edSchristos 			mdoc->next = ROFF_NEXT_SIBLING;
177737ef69edSchristos 			roff_body_alloc(mdoc, nchild->line,
177837ef69edSchristos 			    nchild->pos, MDOC_It);
177937ef69edSchristos 			while (nchild->tok != MDOC_It) {
17806167eca2Schristos 				roff_node_relink(mdoc, nchild);
178137ef69edSchristos 				if ((nchild = nnext) == NULL)
178237ef69edSchristos 					break;
178337ef69edSchristos 				nnext = nchild->next;
178437ef69edSchristos 				mdoc->next = ROFF_NEXT_SIBLING;
178537ef69edSchristos 			}
178637ef69edSchristos 			mdoc->last = nbody;
1787603fc4ebSjoerg 			continue;
1788603fc4ebSjoerg 		}
1789603fc4ebSjoerg 
17906167eca2Schristos 		mandoc_msg(MANDOCERR_BL_MOVE, nchild->line, nchild->pos,
17916167eca2Schristos 		    "%s", roff_name[nchild->tok]);
1792603fc4ebSjoerg 
1793603fc4ebSjoerg 		/*
1794603fc4ebSjoerg 		 * Move the node out of the Bl block.
1795603fc4ebSjoerg 		 * First, collect all required node pointers.
1796603fc4ebSjoerg 		 */
1797603fc4ebSjoerg 
1798603fc4ebSjoerg 		nblock  = nbody->parent;
1799603fc4ebSjoerg 		nprev   = nblock->prev;
1800603fc4ebSjoerg 		nparent = nblock->parent;
1801603fc4ebSjoerg 
1802603fc4ebSjoerg 		/*
1803603fc4ebSjoerg 		 * Unlink this child.
1804603fc4ebSjoerg 		 */
1805603fc4ebSjoerg 
1806603fc4ebSjoerg 		nbody->child = nnext;
1807f47368cfSchristos 		if (nnext == NULL)
1808f47368cfSchristos 			nbody->last  = NULL;
1809f47368cfSchristos 		else
1810603fc4ebSjoerg 			nnext->prev = NULL;
1811603fc4ebSjoerg 
1812603fc4ebSjoerg 		/*
1813603fc4ebSjoerg 		 * Relink this child.
1814603fc4ebSjoerg 		 */
1815603fc4ebSjoerg 
1816603fc4ebSjoerg 		nchild->parent = nparent;
1817603fc4ebSjoerg 		nchild->prev   = nprev;
1818603fc4ebSjoerg 		nchild->next   = nblock;
1819603fc4ebSjoerg 
1820603fc4ebSjoerg 		nblock->prev = nchild;
1821f47368cfSchristos 		if (nprev == NULL)
1822603fc4ebSjoerg 			nparent->child = nchild;
1823603fc4ebSjoerg 		else
1824603fc4ebSjoerg 			nprev->next = nchild;
1825603fc4ebSjoerg 
1826603fc4ebSjoerg 		nchild = nnext;
18274154958bSjoerg 	}
182814e7489eSchristos 
182914e7489eSchristos 	if (mdoc->meta.os_e != MANDOC_OS_NETBSD)
183014e7489eSchristos 		return;
183114e7489eSchristos 
183214e7489eSchristos 	prev_Er = NULL;
183314e7489eSchristos 	for (nchild = nbody->child; nchild != NULL; nchild = nchild->next) {
183414e7489eSchristos 		if (nchild->tok != MDOC_It)
183514e7489eSchristos 			continue;
183614e7489eSchristos 		if ((nnext = nchild->head->child) == NULL)
183714e7489eSchristos 			continue;
183814e7489eSchristos 		if (nnext->type == ROFFT_BLOCK)
183914e7489eSchristos 			nnext = nnext->body->child;
184014e7489eSchristos 		if (nnext == NULL || nnext->tok != MDOC_Er)
184114e7489eSchristos 			continue;
184214e7489eSchristos 		nnext = nnext->child;
184314e7489eSchristos 		if (prev_Er != NULL) {
184414e7489eSchristos 			order = strcmp(prev_Er, nnext->string);
184514e7489eSchristos 			if (order > 0)
18466167eca2Schristos 				mandoc_msg(MANDOCERR_ER_ORDER,
18476167eca2Schristos 				    nnext->line, nnext->pos,
184814e7489eSchristos 				    "Er %s %s (NetBSD)",
184914e7489eSchristos 				    prev_Er, nnext->string);
185014e7489eSchristos 			else if (order == 0)
18516167eca2Schristos 				mandoc_msg(MANDOCERR_ER_REP,
18526167eca2Schristos 				    nnext->line, nnext->pos,
185314e7489eSchristos 				    "Er %s (NetBSD)", prev_Er);
185414e7489eSchristos 		}
185514e7489eSchristos 		prev_Er = nnext->string;
185614e7489eSchristos 	}
18574154958bSjoerg }
18584154958bSjoerg 
18595c413d0cSchristos static void
post_bk(POST_ARGS)18605c413d0cSchristos post_bk(POST_ARGS)
18614154958bSjoerg {
1862f47368cfSchristos 	struct roff_node	*n;
18634154958bSjoerg 
18645c413d0cSchristos 	n = mdoc->last;
1865c0d9444aSjoerg 
1866f47368cfSchristos 	if (n->type == ROFFT_BLOCK && n->body->child == NULL) {
18676167eca2Schristos 		mandoc_msg(MANDOCERR_BLK_EMPTY, n->line, n->pos, "Bk");
1868f47368cfSchristos 		roff_node_delete(mdoc, n);
18695c413d0cSchristos 	}
18704154958bSjoerg }
18714154958bSjoerg 
18725c413d0cSchristos static void
post_sm(POST_ARGS)1873f47368cfSchristos post_sm(POST_ARGS)
18745c413d0cSchristos {
1875f47368cfSchristos 	struct roff_node	*nch;
18765c413d0cSchristos 
18775c413d0cSchristos 	nch = mdoc->last->child;
18785c413d0cSchristos 
18795c413d0cSchristos 	if (nch == NULL) {
18805c413d0cSchristos 		mdoc->flags ^= MDOC_SMOFF;
18815c413d0cSchristos 		return;
18825c413d0cSchristos 	}
18835c413d0cSchristos 
1884f47368cfSchristos 	assert(nch->type == ROFFT_TEXT);
18855c413d0cSchristos 
18865c413d0cSchristos 	if ( ! strcmp(nch->string, "on")) {
18875c413d0cSchristos 		mdoc->flags &= ~MDOC_SMOFF;
18885c413d0cSchristos 		return;
18895c413d0cSchristos 	}
18905c413d0cSchristos 	if ( ! strcmp(nch->string, "off")) {
18915c413d0cSchristos 		mdoc->flags |= MDOC_SMOFF;
18925c413d0cSchristos 		return;
18935c413d0cSchristos 	}
18945c413d0cSchristos 
18956167eca2Schristos 	mandoc_msg(MANDOCERR_SM_BAD, nch->line, nch->pos,
189614e7489eSchristos 	    "%s %s", roff_name[mdoc->last->tok], nch->string);
18976167eca2Schristos 	roff_node_relink(mdoc, nch);
18985c413d0cSchristos 	return;
18995c413d0cSchristos }
19005c413d0cSchristos 
19015c413d0cSchristos static void
post_root(POST_ARGS)19025c413d0cSchristos post_root(POST_ARGS)
19035c413d0cSchristos {
1904f47368cfSchristos 	struct roff_node *n;
19055c413d0cSchristos 
19065c413d0cSchristos 	/* Add missing prologue data. */
19075c413d0cSchristos 
19085c413d0cSchristos 	if (mdoc->meta.date == NULL)
190914e7489eSchristos 		mdoc->meta.date = mdoc->quick ? mandoc_strdup("") :
191014e7489eSchristos 		    mandoc_normdate(mdoc, NULL, 0, 0);
19115c413d0cSchristos 
19125c413d0cSchristos 	if (mdoc->meta.title == NULL) {
19136167eca2Schristos 		mandoc_msg(MANDOCERR_DT_NOTITLE, 0, 0, "EOF");
19145c413d0cSchristos 		mdoc->meta.title = mandoc_strdup("UNTITLED");
19155c413d0cSchristos 	}
19165c413d0cSchristos 
19175c413d0cSchristos 	if (mdoc->meta.vol == NULL)
19185c413d0cSchristos 		mdoc->meta.vol = mandoc_strdup("LOCAL");
19195c413d0cSchristos 
19205c413d0cSchristos 	if (mdoc->meta.os == NULL) {
19216167eca2Schristos 		mandoc_msg(MANDOCERR_OS_MISSING, 0, 0, NULL);
19225c413d0cSchristos 		mdoc->meta.os = mandoc_strdup("");
192314e7489eSchristos 	} else if (mdoc->meta.os_e &&
192414e7489eSchristos 	    (mdoc->meta.rcsids & (1 << mdoc->meta.os_e)) == 0)
19256167eca2Schristos 		mandoc_msg(MANDOCERR_RCS_MISSING, 0, 0,
192614e7489eSchristos 		    mdoc->meta.os_e == MANDOC_OS_OPENBSD ?
192714e7489eSchristos 		    "(OpenBSD)" : "(NetBSD)");
192814e7489eSchristos 
192914e7489eSchristos 	if (mdoc->meta.arch != NULL &&
19306167eca2Schristos 	    arch_valid(mdoc->meta.arch, mdoc->meta.os_e) == 0) {
19316167eca2Schristos 		n = mdoc->meta.first->child;
193214e7489eSchristos 		while (n->tok != MDOC_Dt ||
193314e7489eSchristos 		    n->child == NULL ||
193414e7489eSchristos 		    n->child->next == NULL ||
193514e7489eSchristos 		    n->child->next->next == NULL)
193614e7489eSchristos 			n = n->next;
193714e7489eSchristos 		n = n->child->next->next;
19386167eca2Schristos 		mandoc_msg(MANDOCERR_ARCH_BAD, n->line, n->pos,
193914e7489eSchristos 		    "Dt ... %s %s", mdoc->meta.arch,
194014e7489eSchristos 		    mdoc->meta.os_e == MANDOC_OS_OPENBSD ?
194114e7489eSchristos 		    "(OpenBSD)" : "(NetBSD)");
194214e7489eSchristos 	}
1943c0d9444aSjoerg 
1944c0d9444aSjoerg 	/* Check that we begin with a proper `Sh'. */
1945c0d9444aSjoerg 
19466167eca2Schristos 	n = mdoc->meta.first->child;
194714e7489eSchristos 	while (n != NULL &&
194814e7489eSchristos 	    (n->type == ROFFT_COMMENT ||
194914e7489eSchristos 	     (n->tok >= MDOC_Dd &&
19506167eca2Schristos 	      mdoc_macro(n->tok)->flags & MDOC_PROLOGUE)))
19515c413d0cSchristos 		n = n->next;
19525c413d0cSchristos 
19535c413d0cSchristos 	if (n == NULL)
19546167eca2Schristos 		mandoc_msg(MANDOCERR_DOC_EMPTY, 0, 0, NULL);
19555c413d0cSchristos 	else if (n->tok != MDOC_Sh)
19566167eca2Schristos 		mandoc_msg(MANDOCERR_SEC_BEFORE, n->line, n->pos,
19576167eca2Schristos 		    "%s", roff_name[n->tok]);
1958c0d9444aSjoerg }
1959c0d9444aSjoerg 
19605c413d0cSchristos static void
post_rs(POST_ARGS)19614154958bSjoerg post_rs(POST_ARGS)
19624154958bSjoerg {
1963f47368cfSchristos 	struct roff_node *np, *nch, *next, *prev;
1964c0d9444aSjoerg 	int		  i, j;
19654154958bSjoerg 
19665c413d0cSchristos 	np = mdoc->last;
19675c413d0cSchristos 
1968f47368cfSchristos 	if (np->type != ROFFT_BODY)
19695c413d0cSchristos 		return;
19705c413d0cSchristos 
19715c413d0cSchristos 	if (np->child == NULL) {
19726167eca2Schristos 		mandoc_msg(MANDOCERR_RS_EMPTY, np->line, np->pos, "Rs");
19735c413d0cSchristos 		return;
1974c0d9444aSjoerg 	}
1975c0d9444aSjoerg 
1976c0d9444aSjoerg 	/*
1977c0d9444aSjoerg 	 * The full `Rs' block needs special handling to order the
1978c0d9444aSjoerg 	 * sub-elements according to `rsord'.  Pick through each element
19795c413d0cSchristos 	 * and correctly order it.  This is an insertion sort.
1980c0d9444aSjoerg 	 */
1981c0d9444aSjoerg 
1982c0d9444aSjoerg 	next = NULL;
19835c413d0cSchristos 	for (nch = np->child->next; nch != NULL; nch = next) {
19845c413d0cSchristos 		/* Determine order number of this child. */
1985c0d9444aSjoerg 		for (i = 0; i < RSORD_MAX; i++)
19865c413d0cSchristos 			if (rsord[i] == nch->tok)
1987c0d9444aSjoerg 				break;
1988c0d9444aSjoerg 
19895c413d0cSchristos 		if (i == RSORD_MAX) {
19906167eca2Schristos 			mandoc_msg(MANDOCERR_RS_BAD, nch->line, nch->pos,
19916167eca2Schristos 			    "%s", roff_name[nch->tok]);
19925c413d0cSchristos 			i = -1;
19935c413d0cSchristos 		} else if (nch->tok == MDOC__J || nch->tok == MDOC__B)
19945c413d0cSchristos 			np->norm->Rs.quote_T++;
19955c413d0cSchristos 
1996c0d9444aSjoerg 		/*
19975c413d0cSchristos 		 * Remove this child from the chain.  This somewhat
1998f47368cfSchristos 		 * repeats roff_node_unlink(), but since we're
1999c0d9444aSjoerg 		 * just re-ordering, there's no need for the
2000c0d9444aSjoerg 		 * full unlink process.
2001c0d9444aSjoerg 		 */
2002c0d9444aSjoerg 
20035c413d0cSchristos 		if ((next = nch->next) != NULL)
20045c413d0cSchristos 			next->prev = nch->prev;
2005c0d9444aSjoerg 
20065c413d0cSchristos 		if ((prev = nch->prev) != NULL)
20075c413d0cSchristos 			prev->next = nch->next;
2008c0d9444aSjoerg 
20095c413d0cSchristos 		nch->prev = nch->next = NULL;
2010c0d9444aSjoerg 
2011c0d9444aSjoerg 		/*
2012c0d9444aSjoerg 		 * Scan back until we reach a node that's
20135c413d0cSchristos 		 * to be ordered before this child.
2014c0d9444aSjoerg 		 */
2015c0d9444aSjoerg 
2016c0d9444aSjoerg 		for ( ; prev ; prev = prev->prev) {
2017c0d9444aSjoerg 			/* Determine order of `prev'. */
2018c0d9444aSjoerg 			for (j = 0; j < RSORD_MAX; j++)
2019c0d9444aSjoerg 				if (rsord[j] == prev->tok)
2020c0d9444aSjoerg 					break;
20215c413d0cSchristos 			if (j == RSORD_MAX)
20225c413d0cSchristos 				j = -1;
2023c0d9444aSjoerg 
2024c0d9444aSjoerg 			if (j <= i)
2025c0d9444aSjoerg 				break;
2026c0d9444aSjoerg 		}
2027c0d9444aSjoerg 
2028c0d9444aSjoerg 		/*
20295c413d0cSchristos 		 * Set this child back into its correct place
20305c413d0cSchristos 		 * in front of the `prev' node.
2031c0d9444aSjoerg 		 */
2032c0d9444aSjoerg 
20335c413d0cSchristos 		nch->prev = prev;
2034c0d9444aSjoerg 
20355c413d0cSchristos 		if (prev == NULL) {
20365c413d0cSchristos 			np->child->prev = nch;
20375c413d0cSchristos 			nch->next = np->child;
20385c413d0cSchristos 			np->child = nch;
2039c0d9444aSjoerg 		} else {
20405c413d0cSchristos 			if (prev->next)
20415c413d0cSchristos 				prev->next->prev = nch;
20425c413d0cSchristos 			nch->next = prev->next;
20435c413d0cSchristos 			prev->next = nch;
2044c0d9444aSjoerg 		}
20454154958bSjoerg 	}
20464154958bSjoerg }
20474154958bSjoerg 
2048603fc4ebSjoerg /*
2049603fc4ebSjoerg  * For some arguments of some macros,
2050603fc4ebSjoerg  * convert all breakable hyphens into ASCII_HYPH.
2051603fc4ebSjoerg  */
20525c413d0cSchristos static void
post_hyph(POST_ARGS)2053603fc4ebSjoerg post_hyph(POST_ARGS)
2054603fc4ebSjoerg {
2055f47368cfSchristos 	struct roff_node	*nch;
2056603fc4ebSjoerg 	char			*cp;
2057603fc4ebSjoerg 
20585c413d0cSchristos 	for (nch = mdoc->last->child; nch != NULL; nch = nch->next) {
2059f47368cfSchristos 		if (nch->type != ROFFT_TEXT)
2060603fc4ebSjoerg 			continue;
2061603fc4ebSjoerg 		cp = nch->string;
20625c413d0cSchristos 		if (*cp == '\0')
2063603fc4ebSjoerg 			continue;
20645c413d0cSchristos 		while (*(++cp) != '\0')
20655c413d0cSchristos 			if (*cp == '-' &&
2066603fc4ebSjoerg 			    isalpha((unsigned char)cp[-1]) &&
2067603fc4ebSjoerg 			    isalpha((unsigned char)cp[1]))
2068603fc4ebSjoerg 				*cp = ASCII_HYPH;
2069603fc4ebSjoerg 	}
2070603fc4ebSjoerg }
2071603fc4ebSjoerg 
20725c413d0cSchristos static void
post_ns(POST_ARGS)207348741257Sjoerg post_ns(POST_ARGS)
207448741257Sjoerg {
207514e7489eSchristos 	struct roff_node	*n;
207648741257Sjoerg 
207714e7489eSchristos 	n = mdoc->last;
207814e7489eSchristos 	if (n->flags & NODE_LINE ||
207914e7489eSchristos 	    (n->next != NULL && n->next->flags & NODE_DELIMC))
20806167eca2Schristos 		mandoc_msg(MANDOCERR_NS_SKIP, n->line, n->pos, NULL);
208114e7489eSchristos }
208214e7489eSchristos 
208314e7489eSchristos static void
post_sx(POST_ARGS)208414e7489eSchristos post_sx(POST_ARGS)
208514e7489eSchristos {
208614e7489eSchristos 	post_delim(mdoc);
208714e7489eSchristos 	post_hyph(mdoc);
208848741257Sjoerg }
208948741257Sjoerg 
20905c413d0cSchristos static void
post_sh(POST_ARGS)20914154958bSjoerg post_sh(POST_ARGS)
20924154958bSjoerg {
20934154958bSjoerg 
20945c413d0cSchristos 	post_ignpar(mdoc);
20954154958bSjoerg 
20965c413d0cSchristos 	switch (mdoc->last->type) {
2097f47368cfSchristos 	case ROFFT_HEAD:
20985c413d0cSchristos 		post_sh_head(mdoc);
20995c413d0cSchristos 		break;
2100f47368cfSchristos 	case ROFFT_BODY:
21015c413d0cSchristos 		switch (mdoc->lastsec)  {
21025c413d0cSchristos 		case SEC_NAME:
21035c413d0cSchristos 			post_sh_name(mdoc);
21045c413d0cSchristos 			break;
21055c413d0cSchristos 		case SEC_SEE_ALSO:
21065c413d0cSchristos 			post_sh_see_also(mdoc);
21075c413d0cSchristos 			break;
21085c413d0cSchristos 		case SEC_AUTHORS:
21095c413d0cSchristos 			post_sh_authors(mdoc);
21105c413d0cSchristos 			break;
21115c413d0cSchristos 		default:
21125c413d0cSchristos 			break;
21135c413d0cSchristos 		}
21145c413d0cSchristos 		break;
21155c413d0cSchristos 	default:
21165c413d0cSchristos 		break;
21175c413d0cSchristos 	}
21184154958bSjoerg }
21194154958bSjoerg 
21205c413d0cSchristos static void
post_sh_name(POST_ARGS)21215c413d0cSchristos post_sh_name(POST_ARGS)
21224154958bSjoerg {
2123f47368cfSchristos 	struct roff_node *n;
21245c413d0cSchristos 	int hasnm, hasnd;
21254154958bSjoerg 
21265c413d0cSchristos 	hasnm = hasnd = 0;
21274154958bSjoerg 
21285c413d0cSchristos 	for (n = mdoc->last->child; n != NULL; n = n->next) {
21295c413d0cSchristos 		switch (n->tok) {
21305c413d0cSchristos 		case MDOC_Nm:
213137ef69edSchristos 			if (hasnm && n->child != NULL)
21326167eca2Schristos 				mandoc_msg(MANDOCERR_NAMESEC_PUNCT,
21336167eca2Schristos 				    n->line, n->pos,
213437ef69edSchristos 				    "Nm %s", n->child->string);
21355c413d0cSchristos 			hasnm = 1;
213637ef69edSchristos 			continue;
21375c413d0cSchristos 		case MDOC_Nd:
21385c413d0cSchristos 			hasnd = 1;
21395c413d0cSchristos 			if (n->next != NULL)
21405c413d0cSchristos 				mandoc_msg(MANDOCERR_NAMESEC_ND,
21416167eca2Schristos 				    n->line, n->pos, NULL);
21425c413d0cSchristos 			break;
2143f47368cfSchristos 		case TOKEN_NONE:
214437ef69edSchristos 			if (n->type == ROFFT_TEXT &&
214537ef69edSchristos 			    n->string[0] == ',' && n->string[1] == '\0' &&
214637ef69edSchristos 			    n->next != NULL && n->next->tok == MDOC_Nm) {
214737ef69edSchristos 				n = n->next;
214837ef69edSchristos 				continue;
214937ef69edSchristos 			}
21505c413d0cSchristos 			/* FALLTHROUGH */
21515c413d0cSchristos 		default:
21526167eca2Schristos 			mandoc_msg(MANDOCERR_NAMESEC_BAD,
21536167eca2Schristos 			    n->line, n->pos, "%s", roff_name[n->tok]);
215437ef69edSchristos 			continue;
21555c413d0cSchristos 		}
215637ef69edSchristos 		break;
2157c0d9444aSjoerg 	}
21584154958bSjoerg 
21595c413d0cSchristos 	if ( ! hasnm)
21606167eca2Schristos 		mandoc_msg(MANDOCERR_NAMESEC_NONM,
21615c413d0cSchristos 		    mdoc->last->line, mdoc->last->pos, NULL);
21625c413d0cSchristos 	if ( ! hasnd)
21636167eca2Schristos 		mandoc_msg(MANDOCERR_NAMESEC_NOND,
21645c413d0cSchristos 		    mdoc->last->line, mdoc->last->pos, NULL);
21654154958bSjoerg }
21664154958bSjoerg 
21675c413d0cSchristos static void
post_sh_see_also(POST_ARGS)21685c413d0cSchristos post_sh_see_also(POST_ARGS)
21695c413d0cSchristos {
2170f47368cfSchristos 	const struct roff_node	*n;
21715c413d0cSchristos 	const char		*name, *sec;
21725c413d0cSchristos 	const char		*lastname, *lastsec, *lastpunct;
21735c413d0cSchristos 	int			 cmp;
21744154958bSjoerg 
21755c413d0cSchristos 	n = mdoc->last->child;
21765c413d0cSchristos 	lastname = lastsec = lastpunct = NULL;
21775c413d0cSchristos 	while (n != NULL) {
2178f47368cfSchristos 		if (n->tok != MDOC_Xr ||
2179f47368cfSchristos 		    n->child == NULL ||
2180f47368cfSchristos 		    n->child->next == NULL)
21815c413d0cSchristos 			break;
21825c413d0cSchristos 
21835c413d0cSchristos 		/* Process one .Xr node. */
21845c413d0cSchristos 
21855c413d0cSchristos 		name = n->child->string;
21865c413d0cSchristos 		sec = n->child->next->string;
21875c413d0cSchristos 		if (lastsec != NULL) {
21885c413d0cSchristos 			if (lastpunct[0] != ',' || lastpunct[1] != '\0')
21896167eca2Schristos 				mandoc_msg(MANDOCERR_XR_PUNCT, n->line,
21906167eca2Schristos 				    n->pos, "%s before %s(%s)",
21916167eca2Schristos 				    lastpunct, name, sec);
21925c413d0cSchristos 			cmp = strcmp(lastsec, sec);
21935c413d0cSchristos 			if (cmp > 0)
21946167eca2Schristos 				mandoc_msg(MANDOCERR_XR_ORDER, n->line,
21956167eca2Schristos 				    n->pos, "%s(%s) after %s(%s)",
21966167eca2Schristos 				    name, sec, lastname, lastsec);
21975c413d0cSchristos 			else if (cmp == 0 &&
21985c413d0cSchristos 			    strcasecmp(lastname, name) > 0)
21996167eca2Schristos 				mandoc_msg(MANDOCERR_XR_ORDER, n->line,
22006167eca2Schristos 				    n->pos, "%s after %s", name, lastname);
22015c413d0cSchristos 		}
22025c413d0cSchristos 		lastname = name;
22035c413d0cSchristos 		lastsec = sec;
22045c413d0cSchristos 
22055c413d0cSchristos 		/* Process the following node. */
22065c413d0cSchristos 
22075c413d0cSchristos 		n = n->next;
22085c413d0cSchristos 		if (n == NULL)
22095c413d0cSchristos 			break;
22105c413d0cSchristos 		if (n->tok == MDOC_Xr) {
22115c413d0cSchristos 			lastpunct = "none";
22125c413d0cSchristos 			continue;
22135c413d0cSchristos 		}
2214f47368cfSchristos 		if (n->type != ROFFT_TEXT)
22155c413d0cSchristos 			break;
22165c413d0cSchristos 		for (name = n->string; *name != '\0'; name++)
22175c413d0cSchristos 			if (isalpha((const unsigned char)*name))
22185c413d0cSchristos 				return;
22195c413d0cSchristos 		lastpunct = n->string;
222014e7489eSchristos 		if (n->next == NULL || n->next->tok == MDOC_Rs)
22216167eca2Schristos 			mandoc_msg(MANDOCERR_XR_PUNCT, n->line,
22226167eca2Schristos 			    n->pos, "%s after %s(%s)",
22235c413d0cSchristos 			    lastpunct, lastname, lastsec);
22245c413d0cSchristos 		n = n->next;
22255c413d0cSchristos 	}
2226c0d9444aSjoerg }
22274154958bSjoerg 
22284154958bSjoerg static int
child_an(const struct roff_node * n)2229f47368cfSchristos child_an(const struct roff_node *n)
22305c413d0cSchristos {
22315c413d0cSchristos 
22325c413d0cSchristos 	for (n = n->child; n != NULL; n = n->next)
2233f47368cfSchristos 		if ((n->tok == MDOC_An && n->child != NULL) || child_an(n))
2234f47368cfSchristos 			return 1;
2235f47368cfSchristos 	return 0;
22365c413d0cSchristos }
22375c413d0cSchristos 
22385c413d0cSchristos static void
post_sh_authors(POST_ARGS)22395c413d0cSchristos post_sh_authors(POST_ARGS)
22405c413d0cSchristos {
22415c413d0cSchristos 
22425c413d0cSchristos 	if ( ! child_an(mdoc->last))
22436167eca2Schristos 		mandoc_msg(MANDOCERR_AN_MISSING,
22445c413d0cSchristos 		    mdoc->last->line, mdoc->last->pos, NULL);
22455c413d0cSchristos }
22465c413d0cSchristos 
224714e7489eSchristos /*
224814e7489eSchristos  * Return an upper bound for the string distance (allowing
224914e7489eSchristos  * transpositions).  Not a full Levenshtein implementation
225014e7489eSchristos  * because Levenshtein is quadratic in the string length
225114e7489eSchristos  * and this function is called for every standard name,
225214e7489eSchristos  * so the check for each custom name would be cubic.
225314e7489eSchristos  * The following crude heuristics is linear, resulting
225414e7489eSchristos  * in quadratic behaviour for checking one custom name,
225514e7489eSchristos  * which does not cause measurable slowdown.
225614e7489eSchristos  */
225714e7489eSchristos static int
similar(const char * s1,const char * s2)225814e7489eSchristos similar(const char *s1, const char *s2)
225914e7489eSchristos {
226014e7489eSchristos 	const int	maxdist = 3;
226114e7489eSchristos 	int		dist = 0;
226214e7489eSchristos 
226314e7489eSchristos 	while (s1[0] != '\0' && s2[0] != '\0') {
226414e7489eSchristos 		if (s1[0] == s2[0]) {
226514e7489eSchristos 			s1++;
226614e7489eSchristos 			s2++;
226714e7489eSchristos 			continue;
226814e7489eSchristos 		}
226914e7489eSchristos 		if (++dist > maxdist)
227014e7489eSchristos 			return INT_MAX;
227114e7489eSchristos 		if (s1[1] == s2[1]) {  /* replacement */
227214e7489eSchristos 			s1++;
227314e7489eSchristos 			s2++;
227414e7489eSchristos 		} else if (s1[0] == s2[1] && s1[1] == s2[0]) {
227514e7489eSchristos 			s1 += 2;	/* transposition */
227614e7489eSchristos 			s2 += 2;
227714e7489eSchristos 		} else if (s1[0] == s2[1])  /* insertion */
227814e7489eSchristos 			s2++;
227914e7489eSchristos 		else if (s1[1] == s2[0])  /* deletion */
228014e7489eSchristos 			s1++;
228114e7489eSchristos 		else
228214e7489eSchristos 			return INT_MAX;
228314e7489eSchristos 	}
228414e7489eSchristos 	dist += strlen(s1) + strlen(s2);
228514e7489eSchristos 	return dist > maxdist ? INT_MAX : dist;
228614e7489eSchristos }
228714e7489eSchristos 
22885c413d0cSchristos static void
post_sh_head(POST_ARGS)22894154958bSjoerg post_sh_head(POST_ARGS)
22904154958bSjoerg {
229137ef69edSchristos 	struct roff_node	*nch;
22925c413d0cSchristos 	const char		*goodsec;
229314e7489eSchristos 	const char *const	*testsec;
229414e7489eSchristos 	int			 dist, mindist;
2295f47368cfSchristos 	enum roff_sec		 sec;
22964154958bSjoerg 
22974154958bSjoerg 	/*
22984154958bSjoerg 	 * Process a new section.  Sections are either "named" or
2299c0d9444aSjoerg 	 * "custom".  Custom sections are user-defined, while named ones
2300c0d9444aSjoerg 	 * follow a conventional order and may only appear in certain
2301c0d9444aSjoerg 	 * manual sections.
23024154958bSjoerg 	 */
23034154958bSjoerg 
2304f47368cfSchristos 	sec = mdoc->last->sec;
23054154958bSjoerg 
2306c0d9444aSjoerg 	/* The NAME should be first. */
23074154958bSjoerg 
230837ef69edSchristos 	if (sec != SEC_NAME && mdoc->lastnamed == SEC_NONE)
23096167eca2Schristos 		mandoc_msg(MANDOCERR_NAMESEC_FIRST,
231037ef69edSchristos 		    mdoc->last->line, mdoc->last->pos, "Sh %s",
231137ef69edSchristos 		    sec != SEC_CUSTOM ? secnames[sec] :
231237ef69edSchristos 		    (nch = mdoc->last->child) == NULL ? "" :
231337ef69edSchristos 		    nch->type == ROFFT_TEXT ? nch->string :
231414e7489eSchristos 		    roff_name[nch->tok]);
2315c0d9444aSjoerg 
2316c0d9444aSjoerg 	/* The SYNOPSIS gets special attention in other areas. */
2317c0d9444aSjoerg 
2318f47368cfSchristos 	if (sec == SEC_SYNOPSIS) {
2319603fc4ebSjoerg 		roff_setreg(mdoc->roff, "nS", 1, '=');
2320c0d9444aSjoerg 		mdoc->flags |= MDOC_SYNOPSIS;
2321603fc4ebSjoerg 	} else {
2322603fc4ebSjoerg 		roff_setreg(mdoc->roff, "nS", 0, '=');
2323c0d9444aSjoerg 		mdoc->flags &= ~MDOC_SYNOPSIS;
2324603fc4ebSjoerg 	}
2325c0d9444aSjoerg 
2326c0d9444aSjoerg 	/* Mark our last section. */
2327c0d9444aSjoerg 
2328c0d9444aSjoerg 	mdoc->lastsec = sec;
2329c0d9444aSjoerg 
2330c0d9444aSjoerg 	/* We don't care about custom sections after this. */
23310a84adc5Sjoerg 
233214e7489eSchristos 	if (sec == SEC_CUSTOM) {
233314e7489eSchristos 		if ((nch = mdoc->last->child) == NULL ||
233414e7489eSchristos 		    nch->type != ROFFT_TEXT || nch->next != NULL)
23355c413d0cSchristos 			return;
233614e7489eSchristos 		goodsec = NULL;
233714e7489eSchristos 		mindist = INT_MAX;
233814e7489eSchristos 		for (testsec = secnames + 1; *testsec != NULL; testsec++) {
233914e7489eSchristos 			dist = similar(nch->string, *testsec);
234014e7489eSchristos 			if (dist < mindist) {
234114e7489eSchristos 				goodsec = *testsec;
234214e7489eSchristos 				mindist = dist;
234314e7489eSchristos 			}
234414e7489eSchristos 		}
234514e7489eSchristos 		if (goodsec != NULL)
23466167eca2Schristos 			mandoc_msg(MANDOCERR_SEC_TYPO, nch->line, nch->pos,
23476167eca2Schristos 			    "Sh %s instead of %s", nch->string, goodsec);
234814e7489eSchristos 		return;
234914e7489eSchristos 	}
23500a84adc5Sjoerg 
23514154958bSjoerg 	/*
2352c0d9444aSjoerg 	 * Check whether our non-custom section is being repeated or is
2353c0d9444aSjoerg 	 * out of order.
23544154958bSjoerg 	 */
23554154958bSjoerg 
2356c0d9444aSjoerg 	if (sec == mdoc->lastnamed)
23576167eca2Schristos 		mandoc_msg(MANDOCERR_SEC_REP, mdoc->last->line,
23586167eca2Schristos 		    mdoc->last->pos, "Sh %s", secnames[sec]);
2359c0d9444aSjoerg 
2360c0d9444aSjoerg 	if (sec < mdoc->lastnamed)
23616167eca2Schristos 		mandoc_msg(MANDOCERR_SEC_ORDER, mdoc->last->line,
23626167eca2Schristos 		    mdoc->last->pos, "Sh %s", secnames[sec]);
2363c0d9444aSjoerg 
2364c0d9444aSjoerg 	/* Mark the last named section. */
2365c0d9444aSjoerg 
2366c0d9444aSjoerg 	mdoc->lastnamed = sec;
2367c0d9444aSjoerg 
2368c0d9444aSjoerg 	/* Check particular section/manual conventions. */
2369c0d9444aSjoerg 
2370f47368cfSchristos 	if (mdoc->meta.msec == NULL)
23715c413d0cSchristos 		return;
2372c0d9444aSjoerg 
23735c413d0cSchristos 	goodsec = NULL;
2374c0d9444aSjoerg 	switch (sec) {
23755c413d0cSchristos 	case SEC_ERRORS:
23765c413d0cSchristos 		if (*mdoc->meta.msec == '4')
23775c413d0cSchristos 			break;
23785c413d0cSchristos 		goodsec = "2, 3, 4, 9";
2379c0d9444aSjoerg 		/* FALLTHROUGH */
23805c413d0cSchristos 	case SEC_RETURN_VALUES:
23815c413d0cSchristos 	case SEC_LIBRARY:
23820a84adc5Sjoerg 		if (*mdoc->meta.msec == '2')
23834154958bSjoerg 			break;
23840a84adc5Sjoerg 		if (*mdoc->meta.msec == '3')
23854154958bSjoerg 			break;
23865c413d0cSchristos 		if (NULL == goodsec)
23875c413d0cSchristos 			goodsec = "2, 3, 9";
23885c413d0cSchristos 		/* FALLTHROUGH */
23895c413d0cSchristos 	case SEC_CONTEXT:
23900a84adc5Sjoerg 		if (*mdoc->meta.msec == '9')
23910a84adc5Sjoerg 			break;
23925c413d0cSchristos 		if (NULL == goodsec)
23935c413d0cSchristos 			goodsec = "9";
23946167eca2Schristos 		mandoc_msg(MANDOCERR_SEC_MSEC,
23955c413d0cSchristos 		    mdoc->last->line, mdoc->last->pos,
2396f47368cfSchristos 		    "Sh %s for %s only", secnames[sec], goodsec);
2397c0d9444aSjoerg 		break;
23984154958bSjoerg 	default:
23994154958bSjoerg 		break;
24004154958bSjoerg 	}
24014154958bSjoerg }
2402c0d9444aSjoerg 
24035c413d0cSchristos static void
post_xr(POST_ARGS)240437ef69edSchristos post_xr(POST_ARGS)
240537ef69edSchristos {
240637ef69edSchristos 	struct roff_node *n, *nch;
240737ef69edSchristos 
240837ef69edSchristos 	n = mdoc->last;
240937ef69edSchristos 	nch = n->child;
241037ef69edSchristos 	if (nch->next == NULL) {
24116167eca2Schristos 		mandoc_msg(MANDOCERR_XR_NOSEC,
241237ef69edSchristos 		    n->line, n->pos, "Xr %s", nch->string);
241314e7489eSchristos 	} else {
241437ef69edSchristos 		assert(nch->next == n->last);
241514e7489eSchristos 		if(mandoc_xr_add(nch->next->string, nch->string,
241614e7489eSchristos 		    nch->line, nch->pos))
24176167eca2Schristos 			mandoc_msg(MANDOCERR_XR_SELF,
241814e7489eSchristos 			    nch->line, nch->pos, "Xr %s %s",
241914e7489eSchristos 			    nch->string, nch->next->string);
242014e7489eSchristos 	}
242114e7489eSchristos 	post_delim_nb(mdoc);
242237ef69edSchristos }
242337ef69edSchristos 
242437ef69edSchristos static void
post_ignpar(POST_ARGS)2425c0d9444aSjoerg post_ignpar(POST_ARGS)
2426c0d9444aSjoerg {
2427f47368cfSchristos 	struct roff_node *np;
2428c0d9444aSjoerg 
24295c413d0cSchristos 	switch (mdoc->last->type) {
243014e7489eSchristos 	case ROFFT_BLOCK:
243114e7489eSchristos 		post_prevpar(mdoc);
243214e7489eSchristos 		return;
2433f47368cfSchristos 	case ROFFT_HEAD:
243414e7489eSchristos 		post_delim(mdoc);
24355c413d0cSchristos 		post_hyph(mdoc);
24365c413d0cSchristos 		return;
2437f47368cfSchristos 	case ROFFT_BODY:
24385c413d0cSchristos 		break;
24395c413d0cSchristos 	default:
24405c413d0cSchristos 		return;
24415c413d0cSchristos 	}
2442c0d9444aSjoerg 
2443f47368cfSchristos 	if ((np = mdoc->last->child) != NULL)
24446167eca2Schristos 		if (np->tok == MDOC_Pp ||
24456167eca2Schristos 		    np->tok == ROFF_br || np->tok == ROFF_sp) {
24466167eca2Schristos 			mandoc_msg(MANDOCERR_PAR_SKIP, np->line, np->pos,
244714e7489eSchristos 			    "%s after %s", roff_name[np->tok],
244814e7489eSchristos 			    roff_name[mdoc->last->tok]);
2449f47368cfSchristos 			roff_node_delete(mdoc, np);
2450c0d9444aSjoerg 		}
2451c0d9444aSjoerg 
2452f47368cfSchristos 	if ((np = mdoc->last->last) != NULL)
24536167eca2Schristos 		if (np->tok == MDOC_Pp || np->tok == ROFF_br) {
24546167eca2Schristos 			mandoc_msg(MANDOCERR_PAR_SKIP, np->line, np->pos,
24556167eca2Schristos 			    "%s at the end of %s", roff_name[np->tok],
245614e7489eSchristos 			    roff_name[mdoc->last->tok]);
2457f47368cfSchristos 			roff_node_delete(mdoc, np);
2458c0d9444aSjoerg 		}
2459c0d9444aSjoerg }
2460c0d9444aSjoerg 
24615c413d0cSchristos static void
post_prevpar(POST_ARGS)2462f47368cfSchristos post_prevpar(POST_ARGS)
2463c0d9444aSjoerg {
2464f47368cfSchristos 	struct roff_node *n;
2465c0d9444aSjoerg 
2466f47368cfSchristos 	n = mdoc->last;
2467f47368cfSchristos 	if (NULL == n->prev)
24685c413d0cSchristos 		return;
2469f47368cfSchristos 	if (n->type != ROFFT_ELEM && n->type != ROFFT_BLOCK)
24705c413d0cSchristos 		return;
2471c0d9444aSjoerg 
2472c0d9444aSjoerg 	/*
24736167eca2Schristos 	 * Don't allow `Pp' prior to a paragraph-type
24746167eca2Schristos 	 * block: `Pp' or non-compact `Bd' or `Bl'.
2475c0d9444aSjoerg 	 */
2476c0d9444aSjoerg 
24776167eca2Schristos 	if (n->prev->tok != MDOC_Pp && n->prev->tok != ROFF_br)
24785c413d0cSchristos 		return;
2479f47368cfSchristos 	if (n->tok == MDOC_Bl && n->norm->Bl.comp)
24805c413d0cSchristos 		return;
2481f47368cfSchristos 	if (n->tok == MDOC_Bd && n->norm->Bd.comp)
24825c413d0cSchristos 		return;
2483f47368cfSchristos 	if (n->tok == MDOC_It && n->parent->norm->Bl.comp)
24845c413d0cSchristos 		return;
2485c0d9444aSjoerg 
24866167eca2Schristos 	mandoc_msg(MANDOCERR_PAR_SKIP, n->prev->line, n->prev->pos,
24876167eca2Schristos 	    "%s before %s", roff_name[n->prev->tok], roff_name[n->tok]);
2488f47368cfSchristos 	roff_node_delete(mdoc, n->prev);
2489c0d9444aSjoerg }
2490c0d9444aSjoerg 
24915c413d0cSchristos static void
post_par(POST_ARGS)2492603fc4ebSjoerg post_par(POST_ARGS)
2493603fc4ebSjoerg {
2494f47368cfSchristos 	struct roff_node *np;
2495603fc4ebSjoerg 
2496f47368cfSchristos 	post_prevpar(mdoc);
2497603fc4ebSjoerg 
24986167eca2Schristos 	np = mdoc->last;
24996167eca2Schristos 	if (np->child != NULL)
25006167eca2Schristos 		mandoc_msg(MANDOCERR_ARG_SKIP, np->line, np->pos,
25016167eca2Schristos 		    "%s %s", roff_name[np->tok], np->child->string);
2502c0d9444aSjoerg }
2503c0d9444aSjoerg 
25045c413d0cSchristos static void
post_dd(POST_ARGS)2505c0d9444aSjoerg post_dd(POST_ARGS)
2506c0d9444aSjoerg {
2507f47368cfSchristos 	struct roff_node *n;
25085c413d0cSchristos 	char		 *datestr;
2509c0d9444aSjoerg 
251048741257Sjoerg 	n = mdoc->last;
251137ef69edSchristos 	n->flags |= NODE_NOPRT;
251237ef69edSchristos 
2513f47368cfSchristos 	if (mdoc->meta.date != NULL) {
25146167eca2Schristos 		mandoc_msg(MANDOCERR_PROLOG_REP, n->line, n->pos, "Dd");
2515f47368cfSchristos 		free(mdoc->meta.date);
2516f47368cfSchristos 	} else if (mdoc->flags & MDOC_PBODY)
25176167eca2Schristos 		mandoc_msg(MANDOCERR_PROLOG_LATE, n->line, n->pos, "Dd");
2518f47368cfSchristos 	else if (mdoc->meta.title != NULL)
25196167eca2Schristos 		mandoc_msg(MANDOCERR_PROLOG_ORDER,
2520f47368cfSchristos 		    n->line, n->pos, "Dd after Dt");
2521f47368cfSchristos 	else if (mdoc->meta.os != NULL)
25226167eca2Schristos 		mandoc_msg(MANDOCERR_PROLOG_ORDER,
2523f47368cfSchristos 		    n->line, n->pos, "Dd after Os");
2524f47368cfSchristos 
2525f47368cfSchristos 	if (n->child == NULL || n->child->string[0] == '\0') {
25265c413d0cSchristos 		mdoc->meta.date = mdoc->quick ? mandoc_strdup("") :
252714e7489eSchristos 		    mandoc_normdate(mdoc, NULL, n->line, n->pos);
252837ef69edSchristos 		return;
2529c0d9444aSjoerg 	}
2530c0d9444aSjoerg 
25315c413d0cSchristos 	datestr = NULL;
2532f47368cfSchristos 	deroff(&datestr, n);
25335c413d0cSchristos 	if (mdoc->quick)
25345c413d0cSchristos 		mdoc->meta.date = datestr;
25355c413d0cSchristos 	else {
253614e7489eSchristos 		mdoc->meta.date = mandoc_normdate(mdoc,
25375c413d0cSchristos 		    datestr, n->line, n->pos);
25385c413d0cSchristos 		free(datestr);
25395c413d0cSchristos 	}
2540b1e8115bSjoerg }
2541c0d9444aSjoerg 
25425c413d0cSchristos static void
post_dt(POST_ARGS)2543c0d9444aSjoerg post_dt(POST_ARGS)
2544c0d9444aSjoerg {
2545f47368cfSchristos 	struct roff_node *nn, *n;
2546c0d9444aSjoerg 	const char	 *cp;
2547c0d9444aSjoerg 	char		 *p;
2548c0d9444aSjoerg 
2549c0d9444aSjoerg 	n = mdoc->last;
255037ef69edSchristos 	n->flags |= NODE_NOPRT;
255137ef69edSchristos 
2552f47368cfSchristos 	if (mdoc->flags & MDOC_PBODY) {
25536167eca2Schristos 		mandoc_msg(MANDOCERR_DT_LATE, n->line, n->pos, "Dt");
255437ef69edSchristos 		return;
2555f47368cfSchristos 	}
2556f47368cfSchristos 
2557f47368cfSchristos 	if (mdoc->meta.title != NULL)
25586167eca2Schristos 		mandoc_msg(MANDOCERR_PROLOG_REP, n->line, n->pos, "Dt");
2559f47368cfSchristos 	else if (mdoc->meta.os != NULL)
25606167eca2Schristos 		mandoc_msg(MANDOCERR_PROLOG_ORDER,
2561f47368cfSchristos 		    n->line, n->pos, "Dt after Os");
2562c0d9444aSjoerg 
2563c0d9444aSjoerg 	free(mdoc->meta.title);
25645c413d0cSchristos 	free(mdoc->meta.msec);
2565c0d9444aSjoerg 	free(mdoc->meta.vol);
2566c0d9444aSjoerg 	free(mdoc->meta.arch);
2567c0d9444aSjoerg 
25685c413d0cSchristos 	mdoc->meta.title = NULL;
25695c413d0cSchristos 	mdoc->meta.msec = NULL;
25705c413d0cSchristos 	mdoc->meta.vol = NULL;
25715c413d0cSchristos 	mdoc->meta.arch = NULL;
2572c0d9444aSjoerg 
25735c413d0cSchristos 	/* Mandatory first argument: title. */
2574c0d9444aSjoerg 
25755c413d0cSchristos 	nn = n->child;
25765c413d0cSchristos 	if (nn == NULL || *nn->string == '\0') {
25776167eca2Schristos 		mandoc_msg(MANDOCERR_DT_NOTITLE, n->line, n->pos, "Dt");
25785c413d0cSchristos 		mdoc->meta.title = mandoc_strdup("UNTITLED");
25795c413d0cSchristos 	} else {
25805c413d0cSchristos 		mdoc->meta.title = mandoc_strdup(nn->string);
2581c0d9444aSjoerg 
25825c413d0cSchristos 		/* Check that all characters are uppercase. */
25835c413d0cSchristos 
25845c413d0cSchristos 		for (p = nn->string; *p != '\0'; p++)
25855c413d0cSchristos 			if (islower((unsigned char)*p)) {
25866167eca2Schristos 				mandoc_msg(MANDOCERR_TITLE_CASE, nn->line,
25876167eca2Schristos 				    nn->pos + (int)(p - nn->string),
25885c413d0cSchristos 				    "Dt %s", nn->string);
2589c0d9444aSjoerg 				break;
2590c0d9444aSjoerg 			}
2591c0d9444aSjoerg 	}
2592c0d9444aSjoerg 
259337ef69edSchristos 	/* Mandatory second argument: section. */
2594c0d9444aSjoerg 
25955c413d0cSchristos 	if (nn != NULL)
25965c413d0cSchristos 		nn = nn->next;
2597c0d9444aSjoerg 
25985c413d0cSchristos 	if (nn == NULL) {
25996167eca2Schristos 		mandoc_msg(MANDOCERR_MSEC_MISSING, n->line, n->pos,
26005c413d0cSchristos 		    "Dt %s", mdoc->meta.title);
2601c0d9444aSjoerg 		mdoc->meta.vol = mandoc_strdup("LOCAL");
260237ef69edSchristos 		return;  /* msec and arch remain NULL. */
2603c0d9444aSjoerg 	}
2604c0d9444aSjoerg 
26055c413d0cSchristos 	mdoc->meta.msec = mandoc_strdup(nn->string);
26065c413d0cSchristos 
26075c413d0cSchristos 	/* Infer volume title from section number. */
2608c0d9444aSjoerg 
26091350fe09Sjoerg 	cp = mandoc_a2msec(nn->string);
26105c413d0cSchristos 	if (cp == NULL) {
26116167eca2Schristos 		mandoc_msg(MANDOCERR_MSEC_BAD,
26125c413d0cSchristos 		    nn->line, nn->pos, "Dt ... %s", nn->string);
2613c0d9444aSjoerg 		mdoc->meta.vol = mandoc_strdup(nn->string);
2614c0d9444aSjoerg 	} else
26155c413d0cSchristos 		mdoc->meta.vol = mandoc_strdup(cp);
26165c413d0cSchristos 
26175c413d0cSchristos 	/* Optional third argument: architecture. */
26185c413d0cSchristos 
26195c413d0cSchristos 	if ((nn = nn->next) == NULL)
262037ef69edSchristos 		return;
26215c413d0cSchristos 
26225c413d0cSchristos 	for (p = nn->string; *p != '\0'; p++)
26235c413d0cSchristos 		*p = tolower((unsigned char)*p);
26245c413d0cSchristos 	mdoc->meta.arch = mandoc_strdup(nn->string);
26255c413d0cSchristos 
26265c413d0cSchristos 	/* Ignore fourth and later arguments. */
26275c413d0cSchristos 
26285c413d0cSchristos 	if ((nn = nn->next) != NULL)
26296167eca2Schristos 		mandoc_msg(MANDOCERR_ARG_EXCESS,
26305c413d0cSchristos 		    nn->line, nn->pos, "Dt ... %s", nn->string);
2631c0d9444aSjoerg }
2632c0d9444aSjoerg 
26335c413d0cSchristos static void
post_bx(POST_ARGS)263448741257Sjoerg post_bx(POST_ARGS)
263548741257Sjoerg {
263637ef69edSchristos 	struct roff_node	*n, *nch;
263714e7489eSchristos 	const char		*macro;
263814e7489eSchristos 
263914e7489eSchristos 	post_delim_nb(mdoc);
264037ef69edSchristos 
264137ef69edSchristos 	n = mdoc->last;
264237ef69edSchristos 	nch = n->child;
264337ef69edSchristos 
264437ef69edSchristos 	if (nch != NULL) {
264514e7489eSchristos 		macro = !strcmp(nch->string, "Open") ? "Ox" :
264614e7489eSchristos 		    !strcmp(nch->string, "Net") ? "Nx" :
264714e7489eSchristos 		    !strcmp(nch->string, "Free") ? "Fx" :
264814e7489eSchristos 		    !strcmp(nch->string, "DragonFly") ? "Dx" : NULL;
264914e7489eSchristos 		if (macro != NULL)
26506167eca2Schristos 			mandoc_msg(MANDOCERR_BX,
26516167eca2Schristos 			    n->line, n->pos, "%s", macro);
265237ef69edSchristos 		mdoc->last = nch;
265337ef69edSchristos 		nch = nch->next;
265437ef69edSchristos 		mdoc->next = ROFF_NEXT_SIBLING;
265537ef69edSchristos 		roff_elem_alloc(mdoc, n->line, n->pos, MDOC_Ns);
265637ef69edSchristos 		mdoc->last->flags |= NODE_NOSRC;
265737ef69edSchristos 		mdoc->next = ROFF_NEXT_SIBLING;
265837ef69edSchristos 	} else
265937ef69edSchristos 		mdoc->next = ROFF_NEXT_CHILD;
266037ef69edSchristos 	roff_word_alloc(mdoc, n->line, n->pos, "BSD");
266137ef69edSchristos 	mdoc->last->flags |= NODE_NOSRC;
266237ef69edSchristos 
266337ef69edSchristos 	if (nch == NULL) {
266437ef69edSchristos 		mdoc->last = n;
266537ef69edSchristos 		return;
266637ef69edSchristos 	}
266737ef69edSchristos 
266837ef69edSchristos 	roff_elem_alloc(mdoc, n->line, n->pos, MDOC_Ns);
266937ef69edSchristos 	mdoc->last->flags |= NODE_NOSRC;
267037ef69edSchristos 	mdoc->next = ROFF_NEXT_SIBLING;
267137ef69edSchristos 	roff_word_alloc(mdoc, n->line, n->pos, "-");
267237ef69edSchristos 	mdoc->last->flags |= NODE_NOSRC;
267337ef69edSchristos 	roff_elem_alloc(mdoc, n->line, n->pos, MDOC_Ns);
267437ef69edSchristos 	mdoc->last->flags |= NODE_NOSRC;
267537ef69edSchristos 	mdoc->last = n;
267648741257Sjoerg 
267748741257Sjoerg 	/*
267848741257Sjoerg 	 * Make `Bx's second argument always start with an uppercase
267948741257Sjoerg 	 * letter.  Groff checks if it's an "accepted" term, but we just
268048741257Sjoerg 	 * uppercase blindly.
268148741257Sjoerg 	 */
268248741257Sjoerg 
268337ef69edSchristos 	*nch->string = (char)toupper((unsigned char)*nch->string);
268448741257Sjoerg }
268548741257Sjoerg 
26865c413d0cSchristos static void
post_os(POST_ARGS)2687c0d9444aSjoerg post_os(POST_ARGS)
2688c0d9444aSjoerg {
2689c0d9444aSjoerg #ifndef OSNAME
2690c0d9444aSjoerg 	struct utsname	  utsname;
26915c413d0cSchristos 	static char	 *defbuf;
2692c0d9444aSjoerg #endif
2693f47368cfSchristos 	struct roff_node *n;
2694c0d9444aSjoerg 
2695c0d9444aSjoerg 	n = mdoc->last;
269637ef69edSchristos 	n->flags |= NODE_NOPRT;
269737ef69edSchristos 
2698f47368cfSchristos 	if (mdoc->meta.os != NULL)
26996167eca2Schristos 		mandoc_msg(MANDOCERR_PROLOG_REP, n->line, n->pos, "Os");
2700f47368cfSchristos 	else if (mdoc->flags & MDOC_PBODY)
27016167eca2Schristos 		mandoc_msg(MANDOCERR_PROLOG_LATE, n->line, n->pos, "Os");
2702c0d9444aSjoerg 
270314e7489eSchristos 	post_delim(mdoc);
270414e7489eSchristos 
2705c0d9444aSjoerg 	/*
2706603fc4ebSjoerg 	 * Set the operating system by way of the `Os' macro.
2707603fc4ebSjoerg 	 * The order of precedence is:
2708603fc4ebSjoerg 	 * 1. the argument of the `Os' macro, unless empty
2709603fc4ebSjoerg 	 * 2. the -Ios=foo command line argument, if provided
2710603fc4ebSjoerg 	 * 3. -DOSNAME="\"foo\"", if provided during compilation
2711603fc4ebSjoerg 	 * 4. "sysname release" from uname(3)
2712c0d9444aSjoerg 	 */
2713c0d9444aSjoerg 
2714c0d9444aSjoerg 	free(mdoc->meta.os);
27155c413d0cSchristos 	mdoc->meta.os = NULL;
2716f47368cfSchristos 	deroff(&mdoc->meta.os, n);
27175c413d0cSchristos 	if (mdoc->meta.os)
271814e7489eSchristos 		goto out;
2719c0d9444aSjoerg 
272014e7489eSchristos 	if (mdoc->os_s != NULL) {
272114e7489eSchristos 		mdoc->meta.os = mandoc_strdup(mdoc->os_s);
272214e7489eSchristos 		goto out;
2723603fc4ebSjoerg 	}
27245c413d0cSchristos 
2725c0d9444aSjoerg #ifdef OSNAME
27265c413d0cSchristos 	mdoc->meta.os = mandoc_strdup(OSNAME);
2727c0d9444aSjoerg #else /*!OSNAME */
2728f47368cfSchristos 	if (defbuf == NULL) {
2729f47368cfSchristos 		if (uname(&utsname) == -1) {
27306167eca2Schristos 			mandoc_msg(MANDOCERR_OS_UNAME, n->line, n->pos, "Os");
27315c413d0cSchristos 			defbuf = mandoc_strdup("UNKNOWN");
27325c413d0cSchristos 		} else
27335c413d0cSchristos 			mandoc_asprintf(&defbuf, "%s %s",
27345c413d0cSchristos 			    utsname.sysname, utsname.release);
2735c0d9444aSjoerg 	}
27365c413d0cSchristos 	mdoc->meta.os = mandoc_strdup(defbuf);
2737c0d9444aSjoerg #endif /*!OSNAME*/
273814e7489eSchristos 
273914e7489eSchristos out:
274014e7489eSchristos 	if (mdoc->meta.os_e == MANDOC_OS_OTHER) {
274114e7489eSchristos 		if (strstr(mdoc->meta.os, "OpenBSD") != NULL)
274214e7489eSchristos 			mdoc->meta.os_e = MANDOC_OS_OPENBSD;
274314e7489eSchristos 		else if (strstr(mdoc->meta.os, "NetBSD") != NULL)
274414e7489eSchristos 			mdoc->meta.os_e = MANDOC_OS_NETBSD;
274514e7489eSchristos 	}
274614e7489eSchristos 
274714e7489eSchristos 	/*
274814e7489eSchristos 	 * This is the earliest point where we can check
274914e7489eSchristos 	 * Mdocdate conventions because we don't know
275014e7489eSchristos 	 * the operating system earlier.
275114e7489eSchristos 	 */
275214e7489eSchristos 
275314e7489eSchristos 	if (n->child != NULL)
27546167eca2Schristos 		mandoc_msg(MANDOCERR_OS_ARG, n->child->line, n->child->pos,
275514e7489eSchristos 		    "Os %s (%s)", n->child->string,
275614e7489eSchristos 		    mdoc->meta.os_e == MANDOC_OS_OPENBSD ?
275714e7489eSchristos 		    "OpenBSD" : "NetBSD");
275814e7489eSchristos 
275914e7489eSchristos 	while (n->tok != MDOC_Dd)
276014e7489eSchristos 		if ((n = n->prev) == NULL)
276114e7489eSchristos 			return;
276214e7489eSchristos 	if ((n = n->child) == NULL)
276314e7489eSchristos 		return;
276414e7489eSchristos 	if (strncmp(n->string, "$" "Mdocdate", 9)) {
276514e7489eSchristos 		if (mdoc->meta.os_e == MANDOC_OS_OPENBSD)
27666167eca2Schristos 			mandoc_msg(MANDOCERR_MDOCDATE_MISSING, n->line,
27676167eca2Schristos 			    n->pos, "Dd %s (OpenBSD)", n->string);
276814e7489eSchristos 	} else {
276914e7489eSchristos 		if (mdoc->meta.os_e == MANDOC_OS_NETBSD)
27706167eca2Schristos 			mandoc_msg(MANDOCERR_MDOCDATE, n->line,
27716167eca2Schristos 			    n->pos, "Dd %s (NetBSD)", n->string);
277214e7489eSchristos 	}
2773c0d9444aSjoerg }
2774c0d9444aSjoerg 
2775f47368cfSchristos enum roff_sec
mdoc_a2sec(const char * p)2776f47368cfSchristos mdoc_a2sec(const char *p)
277748741257Sjoerg {
277848741257Sjoerg 	int		 i;
277948741257Sjoerg 
278048741257Sjoerg 	for (i = 0; i < (int)SEC__MAX; i++)
278148741257Sjoerg 		if (secnames[i] && 0 == strcmp(p, secnames[i]))
2782f47368cfSchristos 			return (enum roff_sec)i;
278348741257Sjoerg 
2784f47368cfSchristos 	return SEC_CUSTOM;
278548741257Sjoerg }
278648741257Sjoerg 
278748741257Sjoerg static size_t
macro2len(enum roff_tok macro)278814e7489eSchristos macro2len(enum roff_tok macro)
278948741257Sjoerg {
279048741257Sjoerg 
279148741257Sjoerg 	switch (macro) {
27925c413d0cSchristos 	case MDOC_Ad:
2793f47368cfSchristos 		return 12;
27945c413d0cSchristos 	case MDOC_Ao:
2795f47368cfSchristos 		return 12;
27965c413d0cSchristos 	case MDOC_An:
2797f47368cfSchristos 		return 12;
27985c413d0cSchristos 	case MDOC_Aq:
2799f47368cfSchristos 		return 12;
28005c413d0cSchristos 	case MDOC_Ar:
2801f47368cfSchristos 		return 12;
28025c413d0cSchristos 	case MDOC_Bo:
2803f47368cfSchristos 		return 12;
28045c413d0cSchristos 	case MDOC_Bq:
2805f47368cfSchristos 		return 12;
28065c413d0cSchristos 	case MDOC_Cd:
2807f47368cfSchristos 		return 12;
28085c413d0cSchristos 	case MDOC_Cm:
2809f47368cfSchristos 		return 10;
28105c413d0cSchristos 	case MDOC_Do:
2811f47368cfSchristos 		return 10;
28125c413d0cSchristos 	case MDOC_Dq:
2813f47368cfSchristos 		return 12;
28145c413d0cSchristos 	case MDOC_Dv:
2815f47368cfSchristos 		return 12;
28165c413d0cSchristos 	case MDOC_Eo:
2817f47368cfSchristos 		return 12;
28185c413d0cSchristos 	case MDOC_Em:
2819f47368cfSchristos 		return 10;
28205c413d0cSchristos 	case MDOC_Er:
2821f47368cfSchristos 		return 17;
28225c413d0cSchristos 	case MDOC_Ev:
2823f47368cfSchristos 		return 15;
28245c413d0cSchristos 	case MDOC_Fa:
2825f47368cfSchristos 		return 12;
28265c413d0cSchristos 	case MDOC_Fl:
2827f47368cfSchristos 		return 10;
28285c413d0cSchristos 	case MDOC_Fo:
2829f47368cfSchristos 		return 16;
28305c413d0cSchristos 	case MDOC_Fn:
2831f47368cfSchristos 		return 16;
28325c413d0cSchristos 	case MDOC_Ic:
2833f47368cfSchristos 		return 10;
28345c413d0cSchristos 	case MDOC_Li:
2835f47368cfSchristos 		return 16;
28365c413d0cSchristos 	case MDOC_Ms:
2837f47368cfSchristos 		return 6;
28385c413d0cSchristos 	case MDOC_Nm:
2839f47368cfSchristos 		return 10;
28405c413d0cSchristos 	case MDOC_No:
2841f47368cfSchristos 		return 12;
28425c413d0cSchristos 	case MDOC_Oo:
2843f47368cfSchristos 		return 10;
28445c413d0cSchristos 	case MDOC_Op:
2845f47368cfSchristos 		return 14;
28465c413d0cSchristos 	case MDOC_Pa:
2847f47368cfSchristos 		return 32;
28485c413d0cSchristos 	case MDOC_Pf:
2849f47368cfSchristos 		return 12;
28505c413d0cSchristos 	case MDOC_Po:
2851f47368cfSchristos 		return 12;
28525c413d0cSchristos 	case MDOC_Pq:
2853f47368cfSchristos 		return 12;
28545c413d0cSchristos 	case MDOC_Ql:
2855f47368cfSchristos 		return 16;
28565c413d0cSchristos 	case MDOC_Qo:
2857f47368cfSchristos 		return 12;
28585c413d0cSchristos 	case MDOC_So:
2859f47368cfSchristos 		return 12;
28605c413d0cSchristos 	case MDOC_Sq:
2861f47368cfSchristos 		return 12;
28625c413d0cSchristos 	case MDOC_Sy:
2863f47368cfSchristos 		return 6;
28645c413d0cSchristos 	case MDOC_Sx:
2865f47368cfSchristos 		return 16;
28665c413d0cSchristos 	case MDOC_Tn:
2867f47368cfSchristos 		return 10;
28685c413d0cSchristos 	case MDOC_Va:
2869f47368cfSchristos 		return 12;
28705c413d0cSchristos 	case MDOC_Vt:
2871f47368cfSchristos 		return 12;
28725c413d0cSchristos 	case MDOC_Xr:
2873f47368cfSchristos 		return 10;
287448741257Sjoerg 	default:
287548741257Sjoerg 		break;
287648741257Sjoerg 	};
2877f47368cfSchristos 	return 0;
287848741257Sjoerg }
2879