xref: /openbsd/usr.bin/mandoc/mdoc_validate.c (revision 8fd2959d)
1*8fd2959dSschwarze /*	$OpenBSD: mdoc_validate.c,v 1.257 2017/06/24 18:58:09 schwarze Exp $ */
2f73abda9Skristaps /*
322972b14Sschwarze  * Copyright (c) 2008-2012 Kristaps Dzonsons <kristaps@bsd.lv>
4f27faaccSschwarze  * Copyright (c) 2010-2017 Ingo Schwarze <schwarze@openbsd.org>
539c2a57eSschwarze  * Copyright (c) 2010 Joerg Sonnenberger <joerg@netbsd.org>
6f73abda9Skristaps  *
7f73abda9Skristaps  * Permission to use, copy, modify, and distribute this software for any
8a6464425Sschwarze  * purpose with or without fee is hereby granted, provided that the above
9a6464425Sschwarze  * copyright notice and this permission notice appear in all copies.
10f73abda9Skristaps  *
11d1982c71Sschwarze  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHORS DISCLAIM ALL WARRANTIES
12a6464425Sschwarze  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
13d1982c71Sschwarze  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR
14a6464425Sschwarze  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
15a6464425Sschwarze  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
16a6464425Sschwarze  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
17a6464425Sschwarze  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
18f73abda9Skristaps  */
19dadc3a61Sschwarze #include <sys/types.h>
2020fa2881Sschwarze #ifndef OSNAME
2120fa2881Sschwarze #include <sys/utsname.h>
2220fa2881Sschwarze #endif
2320fa2881Sschwarze 
24f73abda9Skristaps #include <assert.h>
25f73abda9Skristaps #include <ctype.h>
26d92dc4efSschwarze #include <limits.h>
273216dddfSschwarze #include <stdio.h>
28f73abda9Skristaps #include <stdlib.h>
29f73abda9Skristaps #include <string.h>
3020fa2881Sschwarze #include <time.h>
31f73abda9Skristaps 
324f4f7972Sschwarze #include "mandoc_aux.h"
33d1982c71Sschwarze #include "mandoc.h"
34d1982c71Sschwarze #include "roff.h"
35d1982c71Sschwarze #include "mdoc.h"
36f6854d5cSschwarze #include "libmandoc.h"
37fa2127f9Sschwarze #include "roff_int.h"
38d1982c71Sschwarze #include "libmdoc.h"
39f73abda9Skristaps 
40f73abda9Skristaps /* FIXME: .Bl -diag can't have non-text children in HEAD. */
41f73abda9Skristaps 
42ede1b9d0Sschwarze #define	POST_ARGS struct roff_man *mdoc
43f73abda9Skristaps 
447c2be9f8Sschwarze enum	check_ineq {
457c2be9f8Sschwarze 	CHECK_LT,
467c2be9f8Sschwarze 	CHECK_GT,
477c2be9f8Sschwarze 	CHECK_EQ
487c2be9f8Sschwarze };
497c2be9f8Sschwarze 
5098b8f00aSschwarze typedef	void	(*v_post)(POST_ARGS);
51f73abda9Skristaps 
528ccddcd3Sschwarze static	int	 build_list(struct roff_man *, int);
53ede1b9d0Sschwarze static	void	 check_text(struct roff_man *, int, int, char *);
54ede1b9d0Sschwarze static	void	 check_argv(struct roff_man *,
553a0d07afSschwarze 			struct roff_node *, struct mdoc_argv *);
56ede1b9d0Sschwarze static	void	 check_args(struct roff_man *, struct roff_node *);
5748497dd5Sschwarze static	void	 check_toptext(struct roff_man *, int, int, const char *);
583a0d07afSschwarze static	int	 child_an(const struct roff_node *);
5914a309e3Sschwarze static	size_t		macro2len(enum roff_tok);
606050a3daSschwarze static	void	 rewrite_macro2len(struct roff_man *, char **);
6167c719adSschwarze 
6298b8f00aSschwarze static	void	 post_an(POST_ARGS);
633e642ba0Sschwarze static	void	 post_an_norm(POST_ARGS);
6498b8f00aSschwarze static	void	 post_at(POST_ARGS);
653e642ba0Sschwarze static	void	 post_bd(POST_ARGS);
6698b8f00aSschwarze static	void	 post_bf(POST_ARGS);
6798b8f00aSschwarze static	void	 post_bk(POST_ARGS);
6898b8f00aSschwarze static	void	 post_bl(POST_ARGS);
6998b8f00aSschwarze static	void	 post_bl_block(POST_ARGS);
7098b8f00aSschwarze static	void	 post_bl_head(POST_ARGS);
713e642ba0Sschwarze static	void	 post_bl_norm(POST_ARGS);
7298b8f00aSschwarze static	void	 post_bx(POST_ARGS);
7398b8f00aSschwarze static	void	 post_defaults(POST_ARGS);
743e642ba0Sschwarze static	void	 post_display(POST_ARGS);
7598b8f00aSschwarze static	void	 post_dd(POST_ARGS);
7604fbb99fSschwarze static	void	 post_delim(POST_ARGS);
7798b8f00aSschwarze static	void	 post_dt(POST_ARGS);
7898b8f00aSschwarze static	void	 post_en(POST_ARGS);
7998b8f00aSschwarze static	void	 post_es(POST_ARGS);
8098b8f00aSschwarze static	void	 post_eoln(POST_ARGS);
8198b8f00aSschwarze static	void	 post_ex(POST_ARGS);
8298b8f00aSschwarze static	void	 post_fa(POST_ARGS);
8398b8f00aSschwarze static	void	 post_fn(POST_ARGS);
8498b8f00aSschwarze static	void	 post_fname(POST_ARGS);
8598b8f00aSschwarze static	void	 post_fo(POST_ARGS);
8698b8f00aSschwarze static	void	 post_hyph(POST_ARGS);
8798b8f00aSschwarze static	void	 post_ignpar(POST_ARGS);
8898b8f00aSschwarze static	void	 post_it(POST_ARGS);
8998b8f00aSschwarze static	void	 post_lb(POST_ARGS);
9098b8f00aSschwarze static	void	 post_nd(POST_ARGS);
9198b8f00aSschwarze static	void	 post_nm(POST_ARGS);
9298b8f00aSschwarze static	void	 post_ns(POST_ARGS);
933e642ba0Sschwarze static	void	 post_obsolete(POST_ARGS);
9498b8f00aSschwarze static	void	 post_os(POST_ARGS);
9598b8f00aSschwarze static	void	 post_par(POST_ARGS);
963e642ba0Sschwarze static	void	 post_prevpar(POST_ARGS);
9798b8f00aSschwarze static	void	 post_root(POST_ARGS);
9898b8f00aSschwarze static	void	 post_rs(POST_ARGS);
998ccddcd3Sschwarze static	void	 post_rv(POST_ARGS);
10098b8f00aSschwarze static	void	 post_sh(POST_ARGS);
10198b8f00aSschwarze static	void	 post_sh_head(POST_ARGS);
10298b8f00aSschwarze static	void	 post_sh_name(POST_ARGS);
10398b8f00aSschwarze static	void	 post_sh_see_also(POST_ARGS);
10498b8f00aSschwarze static	void	 post_sh_authors(POST_ARGS);
10598b8f00aSschwarze static	void	 post_sm(POST_ARGS);
10698b8f00aSschwarze static	void	 post_st(POST_ARGS);
1073e642ba0Sschwarze static	void	 post_std(POST_ARGS);
108bc205043Sschwarze static	void	 post_useless(POST_ARGS);
1095ae08040Sschwarze static	void	 post_xr(POST_ARGS);
110816c3c54Sschwarze static	void	 post_xx(POST_ARGS);
11198b8f00aSschwarze 
11214a309e3Sschwarze static	const v_post __mdoc_valids[MDOC_MAX - MDOC_Dd] = {
1133e642ba0Sschwarze 	post_dd,	/* Dd */
1143e642ba0Sschwarze 	post_dt,	/* Dt */
1153e642ba0Sschwarze 	post_os,	/* Os */
1163e642ba0Sschwarze 	post_sh,	/* Sh */
1173e642ba0Sschwarze 	post_ignpar,	/* Ss */
1183e642ba0Sschwarze 	post_par,	/* Pp */
1193e642ba0Sschwarze 	post_display,	/* D1 */
1203e642ba0Sschwarze 	post_display,	/* Dl */
1213e642ba0Sschwarze 	post_display,	/* Bd */
1223e642ba0Sschwarze 	NULL,		/* Ed */
1233e642ba0Sschwarze 	post_bl,	/* Bl */
1243e642ba0Sschwarze 	NULL,		/* El */
1253e642ba0Sschwarze 	post_it,	/* It */
12604fbb99fSschwarze 	post_delim,	/* Ad */
1273e642ba0Sschwarze 	post_an,	/* An */
12814a309e3Sschwarze 	NULL,		/* Ap */
1293e642ba0Sschwarze 	post_defaults,	/* Ar */
1303e642ba0Sschwarze 	NULL,		/* Cd */
13104fbb99fSschwarze 	post_delim,	/* Cm */
13204fbb99fSschwarze 	post_delim,	/* Dv */
13304fbb99fSschwarze 	post_delim,	/* Er */
13404fbb99fSschwarze 	post_delim,	/* Ev */
1353e642ba0Sschwarze 	post_ex,	/* Ex */
1363e642ba0Sschwarze 	post_fa,	/* Fa */
1373e642ba0Sschwarze 	NULL,		/* Fd */
13804fbb99fSschwarze 	post_delim,	/* Fl */
1393e642ba0Sschwarze 	post_fn,	/* Fn */
14004fbb99fSschwarze 	post_delim,	/* Ft */
14104fbb99fSschwarze 	post_delim,	/* Ic */
14204fbb99fSschwarze 	post_delim,	/* In */
1433e642ba0Sschwarze 	post_defaults,	/* Li */
1443e642ba0Sschwarze 	post_nd,	/* Nd */
1453e642ba0Sschwarze 	post_nm,	/* Nm */
14604fbb99fSschwarze 	post_delim,	/* Op */
1473e642ba0Sschwarze 	post_obsolete,	/* Ot */
1483e642ba0Sschwarze 	post_defaults,	/* Pa */
1498ccddcd3Sschwarze 	post_rv,	/* Rv */
1503e642ba0Sschwarze 	post_st,	/* St */
15104fbb99fSschwarze 	post_delim,	/* Va */
15204fbb99fSschwarze 	post_delim,	/* Vt */
1535ae08040Sschwarze 	post_xr,	/* Xr */
1543e642ba0Sschwarze 	NULL,		/* %A */
1553e642ba0Sschwarze 	post_hyph,	/* %B */ /* FIXME: can be used outside Rs/Re. */
1563e642ba0Sschwarze 	NULL,		/* %D */
1573e642ba0Sschwarze 	NULL,		/* %I */
1583e642ba0Sschwarze 	NULL,		/* %J */
1593e642ba0Sschwarze 	post_hyph,	/* %N */
1603e642ba0Sschwarze 	post_hyph,	/* %O */
1613e642ba0Sschwarze 	NULL,		/* %P */
1623e642ba0Sschwarze 	post_hyph,	/* %R */
1633e642ba0Sschwarze 	post_hyph,	/* %T */ /* FIXME: can be used outside Rs/Re. */
1643e642ba0Sschwarze 	NULL,		/* %V */
1653e642ba0Sschwarze 	NULL,		/* Ac */
16604fbb99fSschwarze 	post_delim,	/* Ao */
16704fbb99fSschwarze 	post_delim,	/* Aq */
1683e642ba0Sschwarze 	post_at,	/* At */
1693e642ba0Sschwarze 	NULL,		/* Bc */
1703e642ba0Sschwarze 	post_bf,	/* Bf */
17104fbb99fSschwarze 	post_delim,	/* Bo */
1723e642ba0Sschwarze 	NULL,		/* Bq */
173816c3c54Sschwarze 	post_xx,	/* Bsx */
1743e642ba0Sschwarze 	post_bx,	/* Bx */
1753e642ba0Sschwarze 	post_obsolete,	/* Db */
1763e642ba0Sschwarze 	NULL,		/* Dc */
1773e642ba0Sschwarze 	NULL,		/* Do */
1783e642ba0Sschwarze 	NULL,		/* Dq */
1793e642ba0Sschwarze 	NULL,		/* Ec */
1803e642ba0Sschwarze 	NULL,		/* Ef */
18104fbb99fSschwarze 	post_delim,	/* Em */
1823e642ba0Sschwarze 	NULL,		/* Eo */
183816c3c54Sschwarze 	post_xx,	/* Fx */
18404fbb99fSschwarze 	post_delim,	/* Ms */
185abcca917Sschwarze 	NULL,		/* No */
1863e642ba0Sschwarze 	post_ns,	/* Ns */
187816c3c54Sschwarze 	post_xx,	/* Nx */
188816c3c54Sschwarze 	post_xx,	/* Ox */
1893e642ba0Sschwarze 	NULL,		/* Pc */
1903e642ba0Sschwarze 	NULL,		/* Pf */
19104fbb99fSschwarze 	post_delim,	/* Po */
19204fbb99fSschwarze 	post_delim,	/* Pq */
1933e642ba0Sschwarze 	NULL,		/* Qc */
19404fbb99fSschwarze 	post_delim,	/* Ql */
19504fbb99fSschwarze 	post_delim,	/* Qo */
19604fbb99fSschwarze 	post_delim,	/* Qq */
1973e642ba0Sschwarze 	NULL,		/* Re */
1983e642ba0Sschwarze 	post_rs,	/* Rs */
1993e642ba0Sschwarze 	NULL,		/* Sc */
20004fbb99fSschwarze 	post_delim,	/* So */
20104fbb99fSschwarze 	post_delim,	/* Sq */
2023e642ba0Sschwarze 	post_sm,	/* Sm */
2033e642ba0Sschwarze 	post_hyph,	/* Sx */
20404fbb99fSschwarze 	post_delim,	/* Sy */
205bc205043Sschwarze 	post_useless,	/* Tn */
206816c3c54Sschwarze 	post_xx,	/* Ux */
2073e642ba0Sschwarze 	NULL,		/* Xc */
2083e642ba0Sschwarze 	NULL,		/* Xo */
2093e642ba0Sschwarze 	post_fo,	/* Fo */
2103e642ba0Sschwarze 	NULL,		/* Fc */
21104fbb99fSschwarze 	post_delim,	/* Oo */
2123e642ba0Sschwarze 	NULL,		/* Oc */
2133e642ba0Sschwarze 	post_bk,	/* Bk */
2143e642ba0Sschwarze 	NULL,		/* Ek */
2153e642ba0Sschwarze 	post_eoln,	/* Bt */
21604fbb99fSschwarze 	post_obsolete,	/* Hf */
2173e642ba0Sschwarze 	post_obsolete,	/* Fr */
2183e642ba0Sschwarze 	post_eoln,	/* Ud */
2193e642ba0Sschwarze 	post_lb,	/* Lb */
2203e642ba0Sschwarze 	post_par,	/* Lp */
22104fbb99fSschwarze 	post_delim,	/* Lk */
2223e642ba0Sschwarze 	post_defaults,	/* Mt */
22304fbb99fSschwarze 	post_delim,	/* Brq */
22404fbb99fSschwarze 	post_delim,	/* Bro */
2253e642ba0Sschwarze 	NULL,		/* Brc */
2263e642ba0Sschwarze 	NULL,		/* %C */
2273e642ba0Sschwarze 	post_es,	/* Es */
2283e642ba0Sschwarze 	post_en,	/* En */
229816c3c54Sschwarze 	post_xx,	/* Dx */
2303e642ba0Sschwarze 	NULL,		/* %Q */
2313e642ba0Sschwarze 	NULL,		/* %U */
2323e642ba0Sschwarze 	NULL,		/* Ta */
233f73abda9Skristaps };
23414a309e3Sschwarze static	const v_post *const mdoc_valids = __mdoc_valids - MDOC_Dd;
235f73abda9Skristaps 
23620fa2881Sschwarze #define	RSORD_MAX 14 /* Number of `Rs' blocks. */
23720fa2881Sschwarze 
23814a309e3Sschwarze static	const enum roff_tok rsord[RSORD_MAX] = {
23920fa2881Sschwarze 	MDOC__A,
24020fa2881Sschwarze 	MDOC__T,
24120fa2881Sschwarze 	MDOC__B,
24220fa2881Sschwarze 	MDOC__I,
24320fa2881Sschwarze 	MDOC__J,
24420fa2881Sschwarze 	MDOC__R,
24520fa2881Sschwarze 	MDOC__N,
24620fa2881Sschwarze 	MDOC__V,
2470397c682Sschwarze 	MDOC__U,
24820fa2881Sschwarze 	MDOC__P,
24920fa2881Sschwarze 	MDOC__Q,
2504e32ec8fSschwarze 	MDOC__C,
25120fa2881Sschwarze 	MDOC__D,
2524e32ec8fSschwarze 	MDOC__O
25320fa2881Sschwarze };
25420fa2881Sschwarze 
25519a69263Sschwarze static	const char * const secnames[SEC__MAX] = {
25619a69263Sschwarze 	NULL,
25719a69263Sschwarze 	"NAME",
25819a69263Sschwarze 	"LIBRARY",
25919a69263Sschwarze 	"SYNOPSIS",
26019a69263Sschwarze 	"DESCRIPTION",
26103ab2f23Sdlg 	"CONTEXT",
26219a69263Sschwarze 	"IMPLEMENTATION NOTES",
26319a69263Sschwarze 	"RETURN VALUES",
26419a69263Sschwarze 	"ENVIRONMENT",
26519a69263Sschwarze 	"FILES",
26619a69263Sschwarze 	"EXIT STATUS",
26719a69263Sschwarze 	"EXAMPLES",
26819a69263Sschwarze 	"DIAGNOSTICS",
26919a69263Sschwarze 	"COMPATIBILITY",
27019a69263Sschwarze 	"ERRORS",
27119a69263Sschwarze 	"SEE ALSO",
27219a69263Sschwarze 	"STANDARDS",
27319a69263Sschwarze 	"HISTORY",
27419a69263Sschwarze 	"AUTHORS",
27519a69263Sschwarze 	"CAVEATS",
27619a69263Sschwarze 	"BUGS",
27719a69263Sschwarze 	"SECURITY CONSIDERATIONS",
27819a69263Sschwarze 	NULL
27919a69263Sschwarze };
280f73abda9Skristaps 
28149aff9f8Sschwarze 
28298b8f00aSschwarze void
283396853b5Sschwarze mdoc_node_validate(struct roff_man *mdoc)
284f73abda9Skristaps {
2853a0d07afSschwarze 	struct roff_node *n;
28614a309e3Sschwarze 	const v_post *p;
287f73abda9Skristaps 
288753701eeSschwarze 	n = mdoc->last;
289396853b5Sschwarze 	mdoc->last = mdoc->last->child;
290396853b5Sschwarze 	while (mdoc->last != NULL) {
291396853b5Sschwarze 		mdoc_node_validate(mdoc);
292396853b5Sschwarze 		if (mdoc->last == n)
293396853b5Sschwarze 			mdoc->last = mdoc->last->child;
294396853b5Sschwarze 		else
295396853b5Sschwarze 			mdoc->last = mdoc->last->next;
296396853b5Sschwarze 	}
297f73abda9Skristaps 
298396853b5Sschwarze 	mdoc->last = n;
299396853b5Sschwarze 	mdoc->next = ROFF_NEXT_SIBLING;
300753701eeSschwarze 	switch (n->type) {
301d1982c71Sschwarze 	case ROFFT_TEXT:
30279ca1811Sschwarze 		if (n->sec != SEC_SYNOPSIS ||
30379ca1811Sschwarze 		    (n->parent->tok != MDOC_Cd && n->parent->tok != MDOC_Fd))
3043e642ba0Sschwarze 			check_text(mdoc, n->line, n->pos, n->string);
30548497dd5Sschwarze 		if (n->parent->tok == MDOC_It ||
30648497dd5Sschwarze 		    (n->parent->type == ROFFT_BODY &&
30748497dd5Sschwarze 		     (n->parent->tok == MDOC_Sh ||
30848497dd5Sschwarze 		      n->parent->tok == MDOC_Ss)))
30948497dd5Sschwarze 			check_toptext(mdoc, n->line, n->pos, n->string);
3103e642ba0Sschwarze 		break;
311d1982c71Sschwarze 	case ROFFT_EQN:
312d1982c71Sschwarze 	case ROFFT_TBL:
31398b8f00aSschwarze 		break;
314d1982c71Sschwarze 	case ROFFT_ROOT:
31598b8f00aSschwarze 		post_root(mdoc);
31698b8f00aSschwarze 		break;
3172791bd1cSschwarze 	default:
3183e642ba0Sschwarze 		check_args(mdoc, mdoc->last);
3196f9818f6Sschwarze 
3206f9818f6Sschwarze 		/*
3216f9818f6Sschwarze 		 * Closing delimiters are not special at the
3226f9818f6Sschwarze 		 * beginning of a block, opening delimiters
3236f9818f6Sschwarze 		 * are not special at the end.
3246f9818f6Sschwarze 		 */
3256f9818f6Sschwarze 
3266f9818f6Sschwarze 		if (n->child != NULL)
327c4b0939cSschwarze 			n->child->flags &= ~NODE_DELIMC;
3286f9818f6Sschwarze 		if (n->last != NULL)
329c4b0939cSschwarze 			n->last->flags &= ~NODE_DELIMO;
3306f9818f6Sschwarze 
3316f9818f6Sschwarze 		/* Call the macro's postprocessor. */
3326f9818f6Sschwarze 
33329478532Sschwarze 		if (n->tok < ROFF_MAX) {
33429478532Sschwarze 			switch(n->tok) {
33529478532Sschwarze 			case ROFF_br:
3366561cb23Sschwarze 			case ROFF_sp:
33729478532Sschwarze 				post_par(mdoc);
33829478532Sschwarze 				break;
33929478532Sschwarze 			default:
340c4d3fa85Sschwarze 				roff_validate(mdoc);
341c4d3fa85Sschwarze 				break;
34229478532Sschwarze 			}
34329478532Sschwarze 			break;
34429478532Sschwarze 		}
34529478532Sschwarze 
34629478532Sschwarze 		assert(n->tok >= MDOC_Dd && n->tok < MDOC_MAX);
3473e642ba0Sschwarze 		p = mdoc_valids + n->tok;
34898b8f00aSschwarze 		if (*p)
34998b8f00aSschwarze 			(*p)(mdoc);
350396853b5Sschwarze 		if (mdoc->last == n)
351396853b5Sschwarze 			mdoc_state(mdoc, n);
35298b8f00aSschwarze 		break;
3532791bd1cSschwarze 	}
354f73abda9Skristaps }
355f73abda9Skristaps 
35698b8f00aSschwarze static void
357ede1b9d0Sschwarze check_args(struct roff_man *mdoc, struct roff_node *n)
358f73abda9Skristaps {
359f73abda9Skristaps 	int		 i;
360f73abda9Skristaps 
361f73abda9Skristaps 	if (NULL == n->args)
36220fa2881Sschwarze 		return;
363f73abda9Skristaps 
364f73abda9Skristaps 	assert(n->args->argc);
365f73abda9Skristaps 	for (i = 0; i < (int)n->args->argc; i++)
3667ead8a4eSschwarze 		check_argv(mdoc, n, &n->args->argv[i]);
367f73abda9Skristaps }
368f73abda9Skristaps 
36920fa2881Sschwarze static void
370ede1b9d0Sschwarze check_argv(struct roff_man *mdoc, struct roff_node *n, struct mdoc_argv *v)
371f73abda9Skristaps {
372f73abda9Skristaps 	int		 i;
373f73abda9Skristaps 
374f73abda9Skristaps 	for (i = 0; i < (int)v->sz; i++)
3757ead8a4eSschwarze 		check_text(mdoc, v->line, v->pos, v->value[i]);
376f73abda9Skristaps }
377f73abda9Skristaps 
37820fa2881Sschwarze static void
379ede1b9d0Sschwarze check_text(struct roff_man *mdoc, int ln, int pos, char *p)
380f73abda9Skristaps {
38104e980cbSschwarze 	char		*cp;
382769ee804Sschwarze 
3837ead8a4eSschwarze 	if (MDOC_LITERAL & mdoc->flags)
3841cdbf331Sschwarze 		return;
3851cdbf331Sschwarze 
3861cdbf331Sschwarze 	for (cp = p; NULL != (p = strchr(p, '\t')); p++)
387dd5b31c3Sschwarze 		mandoc_msg(MANDOCERR_FI_TAB, mdoc->parse,
388dd5b31c3Sschwarze 		    ln, pos + (int)(p - cp), NULL);
389f73abda9Skristaps }
390f73abda9Skristaps 
39198b8f00aSschwarze static void
39248497dd5Sschwarze check_toptext(struct roff_man *mdoc, int ln, int pos, const char *p)
393f0c18971Sschwarze {
39448497dd5Sschwarze 	const char	*cp, *cpr;
39548497dd5Sschwarze 
39648497dd5Sschwarze 	if (*p == '\0')
39748497dd5Sschwarze 		return;
398f0c18971Sschwarze 
399f0c18971Sschwarze 	if ((cp = strstr(p, "OpenBSD")) != NULL)
400f0c18971Sschwarze 		mandoc_msg(MANDOCERR_BX, mdoc->parse,
401f0c18971Sschwarze 		    ln, pos + (cp - p), "Ox");
402f0c18971Sschwarze 	if ((cp = strstr(p, "NetBSD")) != NULL)
403f0c18971Sschwarze 		mandoc_msg(MANDOCERR_BX, mdoc->parse,
404f0c18971Sschwarze 		    ln, pos + (cp - p), "Nx");
405f0c18971Sschwarze 	if ((cp = strstr(p, "FreeBSD")) != NULL)
406f0c18971Sschwarze 		mandoc_msg(MANDOCERR_BX, mdoc->parse,
407f0c18971Sschwarze 		    ln, pos + (cp - p), "Fx");
408f0c18971Sschwarze 	if ((cp = strstr(p, "DragonFly")) != NULL)
409f0c18971Sschwarze 		mandoc_msg(MANDOCERR_BX, mdoc->parse,
410f0c18971Sschwarze 		    ln, pos + (cp - p), "Dx");
41148497dd5Sschwarze 
41248497dd5Sschwarze 	cp = p;
41348497dd5Sschwarze 	while ((cp = strstr(cp + 1, "()")) != NULL) {
41448497dd5Sschwarze 		for (cpr = cp - 1; cpr >= p; cpr--)
41548497dd5Sschwarze 			if (*cpr != '_' && !isalnum((unsigned char)*cpr))
41648497dd5Sschwarze 				break;
41748497dd5Sschwarze 		if ((cpr < p || *cpr == ' ') && cpr + 1 < cp) {
41848497dd5Sschwarze 			cpr++;
41948497dd5Sschwarze 			mandoc_vmsg(MANDOCERR_FUNC, mdoc->parse,
42048497dd5Sschwarze 			    ln, pos + (cpr - p),
42148497dd5Sschwarze 			    "%.*s()", (int)(cp - cpr), cpr);
42248497dd5Sschwarze 		}
42348497dd5Sschwarze 	}
424f0c18971Sschwarze }
425f0c18971Sschwarze 
426f0c18971Sschwarze static void
42704fbb99fSschwarze post_delim(POST_ARGS)
42804fbb99fSschwarze {
42904fbb99fSschwarze 	const struct roff_node	*nch;
430b7c50e87Sschwarze 	const char		*lc, *cp;
431b7c50e87Sschwarze 	int			 nw;
43204fbb99fSschwarze 	enum mdelim		 delim;
433b7c50e87Sschwarze 	enum roff_tok		 tok;
43404fbb99fSschwarze 
435b7c50e87Sschwarze 	/*
436b7c50e87Sschwarze 	 * Find candidates: at least two bytes,
437b7c50e87Sschwarze 	 * the last one a closing or middle delimiter.
438b7c50e87Sschwarze 	 */
439b7c50e87Sschwarze 
440b7c50e87Sschwarze 	tok = mdoc->last->tok;
44104fbb99fSschwarze 	nch = mdoc->last->last;
44204fbb99fSschwarze 	if (nch == NULL || nch->type != ROFFT_TEXT)
44304fbb99fSschwarze 		return;
44404fbb99fSschwarze 	lc = strchr(nch->string, '\0') - 1;
44504fbb99fSschwarze 	if (lc <= nch->string)
44604fbb99fSschwarze 		return;
44704fbb99fSschwarze 	delim = mdoc_isdelim(lc);
44804fbb99fSschwarze 	if (delim == DELIM_NONE || delim == DELIM_OPEN)
44904fbb99fSschwarze 		return;
450b7c50e87Sschwarze 
451b7c50e87Sschwarze 	/*
452b7c50e87Sschwarze 	 * Reduce false positives by allowing various cases.
453b7c50e87Sschwarze 	 */
454b7c50e87Sschwarze 
455b7c50e87Sschwarze 	/* Escaped delimiters. */
456b7c50e87Sschwarze 	if (lc > nch->string + 1 && lc[-2] == '\\' &&
457b7c50e87Sschwarze 	    (lc[-1] == '&' || lc[-1] == 'e'))
458b7c50e87Sschwarze 		return;
459b7c50e87Sschwarze 
460b7c50e87Sschwarze 	/* Specific byte sequences. */
461b7c50e87Sschwarze 	switch (*lc) {
462b7c50e87Sschwarze 	case ')':
463b7c50e87Sschwarze 		for (cp = lc; cp >= nch->string; cp--)
464b7c50e87Sschwarze 			if (*cp == '(')
465b7c50e87Sschwarze 				return;
466b7c50e87Sschwarze 		break;
467b7c50e87Sschwarze 	case '.':
468b7c50e87Sschwarze 		if (lc > nch->string + 1 && lc[-2] == '.' && lc[-1] == '.')
469b7c50e87Sschwarze 			return;
470b7c50e87Sschwarze 		if (lc[-1] == '.')
471b7c50e87Sschwarze 			return;
472b7c50e87Sschwarze 		break;
473b7c50e87Sschwarze 	case ';':
474b7c50e87Sschwarze 		if (tok == MDOC_Vt)
475b7c50e87Sschwarze 			return;
476b7c50e87Sschwarze 		break;
477b7c50e87Sschwarze 	case '?':
478b7c50e87Sschwarze 		if (lc[-1] == '?')
479b7c50e87Sschwarze 			return;
480b7c50e87Sschwarze 		break;
481b7c50e87Sschwarze 	case ']':
482b7c50e87Sschwarze 		for (cp = lc; cp >= nch->string; cp--)
483b7c50e87Sschwarze 			if (*cp == '[')
484b7c50e87Sschwarze 				return;
485b7c50e87Sschwarze 		break;
486b7c50e87Sschwarze 	case '|':
487b7c50e87Sschwarze 		if (lc == nch->string + 1 && lc[-1] == '|')
488b7c50e87Sschwarze 			return;
489b7c50e87Sschwarze 	default:
490b7c50e87Sschwarze 		break;
491b7c50e87Sschwarze 	}
492b7c50e87Sschwarze 
493b7c50e87Sschwarze 	/* Exactly two non-alphanumeric bytes. */
494b7c50e87Sschwarze 	if (lc == nch->string + 1 && !isalnum((unsigned char)lc[-1]))
495b7c50e87Sschwarze 		return;
496b7c50e87Sschwarze 
497b7c50e87Sschwarze 	/* At least three alphabetic words with a sentence ending. */
498b7c50e87Sschwarze 	if (strchr("!.:?", *lc) != NULL && (tok == MDOC_Em ||
499abcca917Sschwarze 	    tok == MDOC_Li || tok == MDOC_Po || tok == MDOC_Pq ||
500abcca917Sschwarze 	    tok == MDOC_Sy)) {
501b7c50e87Sschwarze 		nw = 0;
502b7c50e87Sschwarze 		for (cp = lc - 1; cp >= nch->string; cp--) {
503b7c50e87Sschwarze 			if (*cp == ' ') {
504b7c50e87Sschwarze 				nw++;
505b7c50e87Sschwarze 				if (cp > nch->string && cp[-1] == ',')
506b7c50e87Sschwarze 					cp--;
507b7c50e87Sschwarze 			} else if (isalpha((unsigned int)*cp)) {
508b7c50e87Sschwarze 				if (nw > 1)
509b7c50e87Sschwarze 					return;
510b7c50e87Sschwarze 			} else
511b7c50e87Sschwarze 				break;
512b7c50e87Sschwarze 		}
513b7c50e87Sschwarze 	}
514b7c50e87Sschwarze 
51504fbb99fSschwarze 	mandoc_vmsg(MANDOCERR_DELIM, mdoc->parse,
51604fbb99fSschwarze 	    nch->line, nch->pos + (lc - nch->string),
517b7c50e87Sschwarze 	    "%s%s %s", roff_name[tok],
51804fbb99fSschwarze 	    nch == mdoc->last->child ? "" : " ...", nch->string);
51904fbb99fSschwarze }
52004fbb99fSschwarze 
52104fbb99fSschwarze static void
5223e642ba0Sschwarze post_bl_norm(POST_ARGS)
523f73abda9Skristaps {
5243e642ba0Sschwarze 	struct roff_node *n;
525aa99c14fSschwarze 	struct mdoc_argv *argv, *wa;
5264a9f685fSschwarze 	int		  i;
527aa99c14fSschwarze 	enum mdocargt	  mdoclt;
5284a9f685fSschwarze 	enum mdoc_list	  lt;
529f73abda9Skristaps 
5303e642ba0Sschwarze 	n = mdoc->last->parent;
5313e642ba0Sschwarze 	n->norm->Bl.type = LIST__NONE;
532f73abda9Skristaps 
5336093755cSschwarze 	/*
5346093755cSschwarze 	 * First figure out which kind of list to use: bind ourselves to
5356093755cSschwarze 	 * the first mentioned list type and warn about any remaining
5366093755cSschwarze 	 * ones.  If we find no list type, we default to LIST_item.
5376093755cSschwarze 	 */
538f73abda9Skristaps 
539dadc3a61Sschwarze 	wa = (n->args == NULL) ? NULL : n->args->argv;
540aa99c14fSschwarze 	mdoclt = MDOC_ARG_MAX;
5416093755cSschwarze 	for (i = 0; n->args && i < (int)n->args->argc; i++) {
5424a9f685fSschwarze 		argv = n->args->argv + i;
5436093755cSschwarze 		lt = LIST__NONE;
5444a9f685fSschwarze 		switch (argv->arg) {
5456093755cSschwarze 		/* Set list types. */
54649aff9f8Sschwarze 		case MDOC_Bullet:
5476093755cSschwarze 			lt = LIST_bullet;
5486093755cSschwarze 			break;
54949aff9f8Sschwarze 		case MDOC_Dash:
5506093755cSschwarze 			lt = LIST_dash;
5516093755cSschwarze 			break;
55249aff9f8Sschwarze 		case MDOC_Enum:
5536093755cSschwarze 			lt = LIST_enum;
5546093755cSschwarze 			break;
55549aff9f8Sschwarze 		case MDOC_Hyphen:
5566093755cSschwarze 			lt = LIST_hyphen;
5576093755cSschwarze 			break;
55849aff9f8Sschwarze 		case MDOC_Item:
5596093755cSschwarze 			lt = LIST_item;
5606093755cSschwarze 			break;
56149aff9f8Sschwarze 		case MDOC_Tag:
5626093755cSschwarze 			lt = LIST_tag;
5636093755cSschwarze 			break;
56449aff9f8Sschwarze 		case MDOC_Diag:
5656093755cSschwarze 			lt = LIST_diag;
5666093755cSschwarze 			break;
56749aff9f8Sschwarze 		case MDOC_Hang:
5686093755cSschwarze 			lt = LIST_hang;
5696093755cSschwarze 			break;
57049aff9f8Sschwarze 		case MDOC_Ohang:
5716093755cSschwarze 			lt = LIST_ohang;
5726093755cSschwarze 			break;
57349aff9f8Sschwarze 		case MDOC_Inset:
5746093755cSschwarze 			lt = LIST_inset;
5756093755cSschwarze 			break;
57649aff9f8Sschwarze 		case MDOC_Column:
5776093755cSschwarze 			lt = LIST_column;
57864d728e4Sschwarze 			break;
5796093755cSschwarze 		/* Set list arguments. */
58049aff9f8Sschwarze 		case MDOC_Compact:
5814a9f685fSschwarze 			if (n->norm->Bl.comp)
5824a9f685fSschwarze 				mandoc_msg(MANDOCERR_ARG_REP,
5834a9f685fSschwarze 				    mdoc->parse, argv->line,
5844a9f685fSschwarze 				    argv->pos, "Bl -compact");
5854a9f685fSschwarze 			n->norm->Bl.comp = 1;
58650e63e03Sschwarze 			break;
58749aff9f8Sschwarze 		case MDOC_Width:
588aa99c14fSschwarze 			wa = argv;
5894a9f685fSschwarze 			if (0 == argv->sz) {
5904a9f685fSschwarze 				mandoc_msg(MANDOCERR_ARG_EMPTY,
5914a9f685fSschwarze 				    mdoc->parse, argv->line,
5924a9f685fSschwarze 				    argv->pos, "Bl -width");
5934a9f685fSschwarze 				n->norm->Bl.width = "0n";
59422972b14Sschwarze 				break;
59522972b14Sschwarze 			}
5964a9f685fSschwarze 			if (NULL != n->norm->Bl.width)
5974a9f685fSschwarze 				mandoc_vmsg(MANDOCERR_ARG_REP,
5984a9f685fSschwarze 				    mdoc->parse, argv->line,
5994a9f685fSschwarze 				    argv->pos, "Bl -width %s",
6004a9f685fSschwarze 				    argv->value[0]);
6016050a3daSschwarze 			rewrite_macro2len(mdoc, argv->value);
6024a9f685fSschwarze 			n->norm->Bl.width = argv->value[0];
60364d728e4Sschwarze 			break;
60449aff9f8Sschwarze 		case MDOC_Offset:
6054a9f685fSschwarze 			if (0 == argv->sz) {
6064a9f685fSschwarze 				mandoc_msg(MANDOCERR_ARG_EMPTY,
6074a9f685fSschwarze 				    mdoc->parse, argv->line,
6084a9f685fSschwarze 				    argv->pos, "Bl -offset");
60931e23753Sschwarze 				break;
61031e23753Sschwarze 			}
6114a9f685fSschwarze 			if (NULL != n->norm->Bl.offs)
6124a9f685fSschwarze 				mandoc_vmsg(MANDOCERR_ARG_REP,
6134a9f685fSschwarze 				    mdoc->parse, argv->line,
6144a9f685fSschwarze 				    argv->pos, "Bl -offset %s",
6154a9f685fSschwarze 				    argv->value[0]);
6166050a3daSschwarze 			rewrite_macro2len(mdoc, argv->value);
6174a9f685fSschwarze 			n->norm->Bl.offs = argv->value[0];
618f73abda9Skristaps 			break;
619ddce0b0cSschwarze 		default:
620ddce0b0cSschwarze 			continue;
621f73abda9Skristaps 		}
622dc0d8bb2Sschwarze 		if (LIST__NONE == lt)
623dc0d8bb2Sschwarze 			continue;
624aa99c14fSschwarze 		mdoclt = argv->arg;
625f73abda9Skristaps 
6266093755cSschwarze 		/* Check: multiple list types. */
6276093755cSschwarze 
628dc0d8bb2Sschwarze 		if (LIST__NONE != n->norm->Bl.type) {
629bd594191Sschwarze 			mandoc_vmsg(MANDOCERR_BL_REP,
630dc0d8bb2Sschwarze 			    mdoc->parse, n->line, n->pos,
631bd594191Sschwarze 			    "Bl -%s", mdoc_argnames[argv->arg]);
632dc0d8bb2Sschwarze 			continue;
633769ee804Sschwarze 		}
6346093755cSschwarze 
6356093755cSschwarze 		/* The list type should come first. */
6366093755cSschwarze 
6378c62fbf5Sschwarze 		if (n->norm->Bl.width ||
6388c62fbf5Sschwarze 		    n->norm->Bl.offs ||
6398c62fbf5Sschwarze 		    n->norm->Bl.comp)
640bd594191Sschwarze 			mandoc_vmsg(MANDOCERR_BL_LATETYPE,
641bd594191Sschwarze 			    mdoc->parse, n->line, n->pos, "Bl -%s",
64266788495Sschwarze 			    mdoc_argnames[n->args->argv[0].arg]);
643dc0d8bb2Sschwarze 
644dc0d8bb2Sschwarze 		n->norm->Bl.type = lt;
645dc0d8bb2Sschwarze 		if (LIST_column == lt) {
646dc0d8bb2Sschwarze 			n->norm->Bl.ncols = argv->sz;
647dc0d8bb2Sschwarze 			n->norm->Bl.cols = (void *)argv->value;
648dc0d8bb2Sschwarze 		}
6496093755cSschwarze 	}
6506093755cSschwarze 
6516093755cSschwarze 	/* Allow lists to default to LIST_item. */
6526093755cSschwarze 
6538c62fbf5Sschwarze 	if (LIST__NONE == n->norm->Bl.type) {
654bd594191Sschwarze 		mandoc_msg(MANDOCERR_BL_NOTYPE, mdoc->parse,
655bd594191Sschwarze 		    n->line, n->pos, "Bl");
6568c62fbf5Sschwarze 		n->norm->Bl.type = LIST_item;
6572c7eb483Sschwarze 		mdoclt = MDOC_Item;
6586e03d529Sschwarze 	}
659f73abda9Skristaps 
66064d728e4Sschwarze 	/*
66164d728e4Sschwarze 	 * Validate the width field.  Some list types don't need width
66264d728e4Sschwarze 	 * types and should be warned about them.  Others should have it
6635eced068Sschwarze 	 * and must also be warned.  Yet others have a default and need
6645eced068Sschwarze 	 * no warning.
66564d728e4Sschwarze 	 */
66664d728e4Sschwarze 
6678c62fbf5Sschwarze 	switch (n->norm->Bl.type) {
66849aff9f8Sschwarze 	case LIST_tag:
6695eced068Sschwarze 		if (NULL == n->norm->Bl.width)
670bd594191Sschwarze 			mandoc_msg(MANDOCERR_BL_NOWIDTH, mdoc->parse,
671bd594191Sschwarze 			    n->line, n->pos, "Bl -tag");
672f73abda9Skristaps 		break;
67349aff9f8Sschwarze 	case LIST_column:
67449aff9f8Sschwarze 	case LIST_diag:
67549aff9f8Sschwarze 	case LIST_ohang:
67649aff9f8Sschwarze 	case LIST_inset:
67749aff9f8Sschwarze 	case LIST_item:
6788c62fbf5Sschwarze 		if (n->norm->Bl.width)
679aa99c14fSschwarze 			mandoc_vmsg(MANDOCERR_BL_SKIPW, mdoc->parse,
680aa99c14fSschwarze 			    wa->line, wa->pos, "Bl -%s",
681aa99c14fSschwarze 			    mdoc_argnames[mdoclt]);
6826093755cSschwarze 		break;
68349aff9f8Sschwarze 	case LIST_bullet:
68449aff9f8Sschwarze 	case LIST_dash:
68549aff9f8Sschwarze 	case LIST_hyphen:
6865eced068Sschwarze 		if (NULL == n->norm->Bl.width)
6875eced068Sschwarze 			n->norm->Bl.width = "2n";
6885eced068Sschwarze 		break;
68949aff9f8Sschwarze 	case LIST_enum:
6905eced068Sschwarze 		if (NULL == n->norm->Bl.width)
6915eced068Sschwarze 			n->norm->Bl.width = "3n";
6925eced068Sschwarze 		break;
69364d728e4Sschwarze 	default:
694f73abda9Skristaps 		break;
69564d728e4Sschwarze 	}
696f73abda9Skristaps }
697f73abda9Skristaps 
69898b8f00aSschwarze static void
6993e642ba0Sschwarze post_bd(POST_ARGS)
700f73abda9Skristaps {
7013e642ba0Sschwarze 	struct roff_node *n;
7024a9f685fSschwarze 	struct mdoc_argv *argv;
7034a9f685fSschwarze 	int		  i;
7044a9f685fSschwarze 	enum mdoc_disp	  dt;
705f73abda9Skristaps 
7063e642ba0Sschwarze 	n = mdoc->last;
70731e23753Sschwarze 	for (i = 0; n->args && i < (int)n->args->argc; i++) {
7084a9f685fSschwarze 		argv = n->args->argv + i;
70931e23753Sschwarze 		dt = DISP__NONE;
71031e23753Sschwarze 
7114a9f685fSschwarze 		switch (argv->arg) {
71249aff9f8Sschwarze 		case MDOC_Centred:
7132065e47aSschwarze 			dt = DISP_centered;
71431e23753Sschwarze 			break;
71549aff9f8Sschwarze 		case MDOC_Ragged:
71631e23753Sschwarze 			dt = DISP_ragged;
71731e23753Sschwarze 			break;
71849aff9f8Sschwarze 		case MDOC_Unfilled:
71931e23753Sschwarze 			dt = DISP_unfilled;
72031e23753Sschwarze 			break;
72149aff9f8Sschwarze 		case MDOC_Filled:
72231e23753Sschwarze 			dt = DISP_filled;
72331e23753Sschwarze 			break;
72449aff9f8Sschwarze 		case MDOC_Literal:
72531e23753Sschwarze 			dt = DISP_literal;
726f73abda9Skristaps 			break;
72749aff9f8Sschwarze 		case MDOC_File:
728bd594191Sschwarze 			mandoc_msg(MANDOCERR_BD_FILE, mdoc->parse,
729bd594191Sschwarze 			    n->line, n->pos, NULL);
7301164a325Sschwarze 			break;
73149aff9f8Sschwarze 		case MDOC_Offset:
7324a9f685fSschwarze 			if (0 == argv->sz) {
7334a9f685fSschwarze 				mandoc_msg(MANDOCERR_ARG_EMPTY,
7344a9f685fSschwarze 				    mdoc->parse, argv->line,
7354a9f685fSschwarze 				    argv->pos, "Bd -offset");
736f73abda9Skristaps 				break;
737f73abda9Skristaps 			}
7384a9f685fSschwarze 			if (NULL != n->norm->Bd.offs)
7394a9f685fSschwarze 				mandoc_vmsg(MANDOCERR_ARG_REP,
7404a9f685fSschwarze 				    mdoc->parse, argv->line,
7414a9f685fSschwarze 				    argv->pos, "Bd -offset %s",
7424a9f685fSschwarze 				    argv->value[0]);
7436050a3daSschwarze 			rewrite_macro2len(mdoc, argv->value);
7444a9f685fSschwarze 			n->norm->Bd.offs = argv->value[0];
74531e23753Sschwarze 			break;
74649aff9f8Sschwarze 		case MDOC_Compact:
7474a9f685fSschwarze 			if (n->norm->Bd.comp)
7484a9f685fSschwarze 				mandoc_msg(MANDOCERR_ARG_REP,
7494a9f685fSschwarze 				    mdoc->parse, argv->line,
7504a9f685fSschwarze 				    argv->pos, "Bd -compact");
7514a9f685fSschwarze 			n->norm->Bd.comp = 1;
75231e23753Sschwarze 			break;
75331e23753Sschwarze 		default:
75431e23753Sschwarze 			abort();
75531e23753Sschwarze 		}
756dc0d8bb2Sschwarze 		if (DISP__NONE == dt)
757dc0d8bb2Sschwarze 			continue;
75831e23753Sschwarze 
759dc0d8bb2Sschwarze 		if (DISP__NONE == n->norm->Bd.type)
7608c62fbf5Sschwarze 			n->norm->Bd.type = dt;
761dc0d8bb2Sschwarze 		else
762bd594191Sschwarze 			mandoc_vmsg(MANDOCERR_BD_REP,
763dc0d8bb2Sschwarze 			    mdoc->parse, n->line, n->pos,
764bd594191Sschwarze 			    "Bd -%s", mdoc_argnames[argv->arg]);
76531e23753Sschwarze 	}
76631e23753Sschwarze 
7678c62fbf5Sschwarze 	if (DISP__NONE == n->norm->Bd.type) {
768bd594191Sschwarze 		mandoc_msg(MANDOCERR_BD_NOTYPE, mdoc->parse,
769bd594191Sschwarze 		    n->line, n->pos, "Bd");
7708c62fbf5Sschwarze 		n->norm->Bd.type = DISP_ragged;
77131e23753Sschwarze 	}
772f73abda9Skristaps }
773f73abda9Skristaps 
7748ccddcd3Sschwarze /*
7758ccddcd3Sschwarze  * Stand-alone line macros.
7768ccddcd3Sschwarze  */
7778ccddcd3Sschwarze 
77898b8f00aSschwarze static void
7793e642ba0Sschwarze post_an_norm(POST_ARGS)
780f73abda9Skristaps {
7813e642ba0Sschwarze 	struct roff_node *n;
782aa99c14fSschwarze 	struct mdoc_argv *argv;
783aa99c14fSschwarze 	size_t	 i;
784f73abda9Skristaps 
7853e642ba0Sschwarze 	n = mdoc->last;
786aa99c14fSschwarze 	if (n->args == NULL)
78798b8f00aSschwarze 		return;
788769ee804Sschwarze 
789aa99c14fSschwarze 	for (i = 1; i < n->args->argc; i++) {
790aa99c14fSschwarze 		argv = n->args->argv + i;
791aa99c14fSschwarze 		mandoc_vmsg(MANDOCERR_AN_REP,
792aa99c14fSschwarze 		    mdoc->parse, argv->line, argv->pos,
793aa99c14fSschwarze 		    "An -%s", mdoc_argnames[argv->arg]);
794aa99c14fSschwarze 	}
7957c2be9f8Sschwarze 
796aa99c14fSschwarze 	argv = n->args->argv;
797aa99c14fSschwarze 	if (argv->arg == MDOC_Split)
7988c62fbf5Sschwarze 		n->norm->An.auth = AUTH_split;
799aa99c14fSschwarze 	else if (argv->arg == MDOC_Nosplit)
8008c62fbf5Sschwarze 		n->norm->An.auth = AUTH_nosplit;
801769ee804Sschwarze 	else
802769ee804Sschwarze 		abort();
803f73abda9Skristaps }
804f73abda9Skristaps 
80598b8f00aSschwarze static void
8068ccddcd3Sschwarze post_eoln(POST_ARGS)
8078ccddcd3Sschwarze {
8088ccddcd3Sschwarze 	struct roff_node	*n;
8098ccddcd3Sschwarze 
810bc205043Sschwarze 	post_useless(mdoc);
8118ccddcd3Sschwarze 	n = mdoc->last;
8128ccddcd3Sschwarze 	if (n->child != NULL)
81314a309e3Sschwarze 		mandoc_vmsg(MANDOCERR_ARG_SKIP, mdoc->parse, n->line,
81414a309e3Sschwarze 		    n->pos, "%s %s", roff_name[n->tok], n->child->string);
8158ccddcd3Sschwarze 
8168ccddcd3Sschwarze 	while (n->child != NULL)
8178ccddcd3Sschwarze 		roff_node_delete(mdoc, n->child);
8188ccddcd3Sschwarze 
8198ccddcd3Sschwarze 	roff_word_alloc(mdoc, n->line, n->pos, n->tok == MDOC_Bt ?
8208ccddcd3Sschwarze 	    "is currently in beta test." : "currently under development.");
8218ccddcd3Sschwarze 	mdoc->last->flags |= NODE_EOS | NODE_NOSRC;
8228ccddcd3Sschwarze 	mdoc->last = n;
8238ccddcd3Sschwarze }
8248ccddcd3Sschwarze 
8258ccddcd3Sschwarze static int
8268ccddcd3Sschwarze build_list(struct roff_man *mdoc, int tok)
8278ccddcd3Sschwarze {
8288ccddcd3Sschwarze 	struct roff_node	*n;
8298ccddcd3Sschwarze 	int			 ic;
8308ccddcd3Sschwarze 
8318ccddcd3Sschwarze 	n = mdoc->last->next;
8328ccddcd3Sschwarze 	for (ic = 1;; ic++) {
8338ccddcd3Sschwarze 		roff_elem_alloc(mdoc, n->line, n->pos, tok);
8348ccddcd3Sschwarze 		mdoc->last->flags |= NODE_NOSRC;
8358ccddcd3Sschwarze 		mdoc_node_relink(mdoc, n);
8368ccddcd3Sschwarze 		n = mdoc->last = mdoc->last->parent;
8378ccddcd3Sschwarze 		mdoc->next = ROFF_NEXT_SIBLING;
8388ccddcd3Sschwarze 		if (n->next == NULL)
8398ccddcd3Sschwarze 			return ic;
8408ccddcd3Sschwarze 		if (ic > 1 || n->next->next != NULL) {
8418ccddcd3Sschwarze 			roff_word_alloc(mdoc, n->line, n->pos, ",");
8428ccddcd3Sschwarze 			mdoc->last->flags |= NODE_DELIMC | NODE_NOSRC;
8438ccddcd3Sschwarze 		}
8448ccddcd3Sschwarze 		n = mdoc->last->next;
8458ccddcd3Sschwarze 		if (n->next == NULL) {
8468ccddcd3Sschwarze 			roff_word_alloc(mdoc, n->line, n->pos, "and");
8478ccddcd3Sschwarze 			mdoc->last->flags |= NODE_NOSRC;
8488ccddcd3Sschwarze 		}
8498ccddcd3Sschwarze 	}
8508ccddcd3Sschwarze }
8518ccddcd3Sschwarze 
8528ccddcd3Sschwarze static void
8538ccddcd3Sschwarze post_ex(POST_ARGS)
8548ccddcd3Sschwarze {
8558ccddcd3Sschwarze 	struct roff_node	*n;
8568ccddcd3Sschwarze 	int			 ic;
8578ccddcd3Sschwarze 
8588ccddcd3Sschwarze 	post_std(mdoc);
8598ccddcd3Sschwarze 
8608ccddcd3Sschwarze 	n = mdoc->last;
8618ccddcd3Sschwarze 	mdoc->next = ROFF_NEXT_CHILD;
8628ccddcd3Sschwarze 	roff_word_alloc(mdoc, n->line, n->pos, "The");
8638ccddcd3Sschwarze 	mdoc->last->flags |= NODE_NOSRC;
8648ccddcd3Sschwarze 
8658ccddcd3Sschwarze 	if (mdoc->last->next != NULL)
8668ccddcd3Sschwarze 		ic = build_list(mdoc, MDOC_Nm);
8678ccddcd3Sschwarze 	else if (mdoc->meta.name != NULL) {
8688ccddcd3Sschwarze 		roff_elem_alloc(mdoc, n->line, n->pos, MDOC_Nm);
8698ccddcd3Sschwarze 		mdoc->last->flags |= NODE_NOSRC;
8708ccddcd3Sschwarze 		roff_word_alloc(mdoc, n->line, n->pos, mdoc->meta.name);
8718ccddcd3Sschwarze 		mdoc->last->flags |= NODE_NOSRC;
8728ccddcd3Sschwarze 		mdoc->last = mdoc->last->parent;
8738ccddcd3Sschwarze 		mdoc->next = ROFF_NEXT_SIBLING;
8748ccddcd3Sschwarze 		ic = 1;
8758ccddcd3Sschwarze 	} else {
8768ccddcd3Sschwarze 		mandoc_msg(MANDOCERR_EX_NONAME, mdoc->parse,
8778ccddcd3Sschwarze 		    n->line, n->pos, "Ex");
8788ccddcd3Sschwarze 		ic = 0;
8798ccddcd3Sschwarze 	}
8808ccddcd3Sschwarze 
8818ccddcd3Sschwarze 	roff_word_alloc(mdoc, n->line, n->pos,
8828ccddcd3Sschwarze 	    ic > 1 ? "utilities exit\\~0" : "utility exits\\~0");
8838ccddcd3Sschwarze 	mdoc->last->flags |= NODE_NOSRC;
8848ccddcd3Sschwarze 	roff_word_alloc(mdoc, n->line, n->pos,
8858ccddcd3Sschwarze 	    "on success, and\\~>0 if an error occurs.");
8868ccddcd3Sschwarze 	mdoc->last->flags |= NODE_EOS | NODE_NOSRC;
8878ccddcd3Sschwarze 	mdoc->last = n;
8888ccddcd3Sschwarze }
8898ccddcd3Sschwarze 
8908ccddcd3Sschwarze static void
8918ccddcd3Sschwarze post_lb(POST_ARGS)
8928ccddcd3Sschwarze {
8938ccddcd3Sschwarze 	struct roff_node	*n;
8948ccddcd3Sschwarze 
89504fbb99fSschwarze 	post_delim(mdoc);
89604fbb99fSschwarze 
8978ccddcd3Sschwarze 	n = mdoc->last;
8988ccddcd3Sschwarze 	assert(n->child->type == ROFFT_TEXT);
8998ccddcd3Sschwarze 	mdoc->next = ROFF_NEXT_CHILD;
9008ccddcd3Sschwarze 	roff_word_alloc(mdoc, n->line, n->pos, "library");
9018ccddcd3Sschwarze 	mdoc->last->flags = NODE_NOSRC;
9028ccddcd3Sschwarze 	roff_word_alloc(mdoc, n->line, n->pos, "\\(Lq");
9038ccddcd3Sschwarze 	mdoc->last->flags = NODE_DELIMO | NODE_NOSRC;
9048ccddcd3Sschwarze 	mdoc->last = mdoc->last->next;
9058ccddcd3Sschwarze 	roff_word_alloc(mdoc, n->line, n->pos, "\\(Rq");
9068ccddcd3Sschwarze 	mdoc->last->flags = NODE_DELIMC | NODE_NOSRC;
9078ccddcd3Sschwarze 	mdoc->last = n;
9088ccddcd3Sschwarze }
9098ccddcd3Sschwarze 
9108ccddcd3Sschwarze static void
9118ccddcd3Sschwarze post_rv(POST_ARGS)
9128ccddcd3Sschwarze {
9138ccddcd3Sschwarze 	struct roff_node	*n;
9148ccddcd3Sschwarze 	int			 ic;
9158ccddcd3Sschwarze 
9168ccddcd3Sschwarze 	post_std(mdoc);
9178ccddcd3Sschwarze 
9188ccddcd3Sschwarze 	n = mdoc->last;
9198ccddcd3Sschwarze 	mdoc->next = ROFF_NEXT_CHILD;
9208ccddcd3Sschwarze 	if (n->child != NULL) {
9218ccddcd3Sschwarze 		roff_word_alloc(mdoc, n->line, n->pos, "The");
9228ccddcd3Sschwarze 		mdoc->last->flags |= NODE_NOSRC;
9238ccddcd3Sschwarze 		ic = build_list(mdoc, MDOC_Fn);
9248ccddcd3Sschwarze 		roff_word_alloc(mdoc, n->line, n->pos,
9258ccddcd3Sschwarze 		    ic > 1 ? "functions return" : "function returns");
9268ccddcd3Sschwarze 		mdoc->last->flags |= NODE_NOSRC;
9278ccddcd3Sschwarze 		roff_word_alloc(mdoc, n->line, n->pos,
9288ccddcd3Sschwarze 		    "the value\\~0 if successful;");
9298ccddcd3Sschwarze 	} else
9308ccddcd3Sschwarze 		roff_word_alloc(mdoc, n->line, n->pos, "Upon successful "
9318ccddcd3Sschwarze 		    "completion, the value\\~0 is returned;");
9328ccddcd3Sschwarze 	mdoc->last->flags |= NODE_NOSRC;
9338ccddcd3Sschwarze 
9348ccddcd3Sschwarze 	roff_word_alloc(mdoc, n->line, n->pos, "otherwise "
9358ccddcd3Sschwarze 	    "the value\\~\\-1 is returned and the global variable");
9368ccddcd3Sschwarze 	mdoc->last->flags |= NODE_NOSRC;
9378ccddcd3Sschwarze 	roff_elem_alloc(mdoc, n->line, n->pos, MDOC_Va);
9388ccddcd3Sschwarze 	mdoc->last->flags |= NODE_NOSRC;
9398ccddcd3Sschwarze 	roff_word_alloc(mdoc, n->line, n->pos, "errno");
9408ccddcd3Sschwarze 	mdoc->last->flags |= NODE_NOSRC;
9418ccddcd3Sschwarze 	mdoc->last = mdoc->last->parent;
9428ccddcd3Sschwarze 	mdoc->next = ROFF_NEXT_SIBLING;
9438ccddcd3Sschwarze 	roff_word_alloc(mdoc, n->line, n->pos,
9448ccddcd3Sschwarze 	    "is set to indicate the error.");
9458ccddcd3Sschwarze 	mdoc->last->flags |= NODE_EOS | NODE_NOSRC;
9468ccddcd3Sschwarze 	mdoc->last = n;
9478ccddcd3Sschwarze }
9488ccddcd3Sschwarze 
9498ccddcd3Sschwarze static void
9503e642ba0Sschwarze post_std(POST_ARGS)
951f73abda9Skristaps {
9523e642ba0Sschwarze 	struct roff_node *n;
953f73abda9Skristaps 
9543e642ba0Sschwarze 	n = mdoc->last;
9553e642ba0Sschwarze 	if (n->args && n->args->argc == 1)
9563e642ba0Sschwarze 		if (n->args->argv[0].arg == MDOC_Std)
95798b8f00aSschwarze 			return;
958f73abda9Skristaps 
95966788495Sschwarze 	mandoc_msg(MANDOCERR_ARG_STD, mdoc->parse,
96014a309e3Sschwarze 	    n->line, n->pos, roff_name[n->tok]);
9616093755cSschwarze }
9626093755cSschwarze 
96398b8f00aSschwarze static void
9648ccddcd3Sschwarze post_st(POST_ARGS)
9658ccddcd3Sschwarze {
9668ccddcd3Sschwarze 	struct roff_node	 *n, *nch;
9678ccddcd3Sschwarze 	const char		 *p;
9688ccddcd3Sschwarze 
9698ccddcd3Sschwarze 	n = mdoc->last;
9708ccddcd3Sschwarze 	nch = n->child;
9718ccddcd3Sschwarze 	assert(nch->type == ROFFT_TEXT);
9728ccddcd3Sschwarze 
9738ccddcd3Sschwarze 	if ((p = mdoc_a2st(nch->string)) == NULL) {
9748ccddcd3Sschwarze 		mandoc_vmsg(MANDOCERR_ST_BAD, mdoc->parse,
9758ccddcd3Sschwarze 		    nch->line, nch->pos, "St %s", nch->string);
9768ccddcd3Sschwarze 		roff_node_delete(mdoc, n);
9778ccddcd3Sschwarze 		return;
9788ccddcd3Sschwarze 	}
9798ccddcd3Sschwarze 
9808ccddcd3Sschwarze 	nch->flags |= NODE_NOPRT;
9818ccddcd3Sschwarze 	mdoc->next = ROFF_NEXT_CHILD;
9828ccddcd3Sschwarze 	roff_word_alloc(mdoc, nch->line, nch->pos, p);
9838ccddcd3Sschwarze 	mdoc->last->flags |= NODE_NOSRC;
9848ccddcd3Sschwarze 	mdoc->last= n;
9858ccddcd3Sschwarze }
9868ccddcd3Sschwarze 
9878ccddcd3Sschwarze static void
9883e642ba0Sschwarze post_obsolete(POST_ARGS)
989551cd4a8Sschwarze {
9903e642ba0Sschwarze 	struct roff_node *n;
991551cd4a8Sschwarze 
9923e642ba0Sschwarze 	n = mdoc->last;
993d1982c71Sschwarze 	if (n->type == ROFFT_ELEM || n->type == ROFFT_BLOCK)
994551cd4a8Sschwarze 		mandoc_msg(MANDOCERR_MACRO_OBS, mdoc->parse,
99514a309e3Sschwarze 		    n->line, n->pos, roff_name[n->tok]);
996551cd4a8Sschwarze }
997551cd4a8Sschwarze 
998bc205043Sschwarze static void
999bc205043Sschwarze post_useless(POST_ARGS)
1000bc205043Sschwarze {
1001bc205043Sschwarze 	struct roff_node *n;
1002bc205043Sschwarze 
1003bc205043Sschwarze 	n = mdoc->last;
1004bc205043Sschwarze 	mandoc_msg(MANDOCERR_MACRO_USELESS, mdoc->parse,
1005bc205043Sschwarze 	    n->line, n->pos, roff_name[n->tok]);
1006bc205043Sschwarze }
1007bc205043Sschwarze 
10088ccddcd3Sschwarze /*
10098ccddcd3Sschwarze  * Block macros.
10108ccddcd3Sschwarze  */
10118ccddcd3Sschwarze 
101298b8f00aSschwarze static void
1013f73abda9Skristaps post_bf(POST_ARGS)
1014f73abda9Skristaps {
10153a0d07afSschwarze 	struct roff_node *np, *nch;
1016f73abda9Skristaps 
1017769ee804Sschwarze 	/*
1018769ee804Sschwarze 	 * Unlike other data pointers, these are "housed" by the HEAD
1019769ee804Sschwarze 	 * element, which contains the goods.
1020769ee804Sschwarze 	 */
1021769ee804Sschwarze 
1022769ee804Sschwarze 	np = mdoc->last;
1023d1982c71Sschwarze 	if (np->type != ROFFT_HEAD)
1024ae2efdd8Sschwarze 		return;
1025ae2efdd8Sschwarze 
1026d1982c71Sschwarze 	assert(np->parent->type == ROFFT_BLOCK);
1027f051602aSschwarze 	assert(np->parent->tok == MDOC_Bf);
102850d41253Sschwarze 
1029ecb10c32Sschwarze 	/* Check the number of arguments. */
1030f73abda9Skristaps 
1031ecb10c32Sschwarze 	nch = np->child;
1032f051602aSschwarze 	if (np->parent->args == NULL) {
1033f051602aSschwarze 		if (nch == NULL) {
1034bd594191Sschwarze 			mandoc_msg(MANDOCERR_BF_NOFONT, mdoc->parse,
1035bd594191Sschwarze 			    np->line, np->pos, "Bf");
103698b8f00aSschwarze 			return;
103720fa2881Sschwarze 		}
1038ecb10c32Sschwarze 		nch = nch->next;
1039ecb10c32Sschwarze 	}
1040f051602aSschwarze 	if (nch != NULL)
1041ecb10c32Sschwarze 		mandoc_vmsg(MANDOCERR_ARG_EXCESS, mdoc->parse,
1042ecb10c32Sschwarze 		    nch->line, nch->pos, "Bf ... %s", nch->string);
1043769ee804Sschwarze 
1044769ee804Sschwarze 	/* Extract argument into data. */
1045769ee804Sschwarze 
1046f051602aSschwarze 	if (np->parent->args != NULL) {
1047f051602aSschwarze 		switch (np->parent->args->argv[0].arg) {
1048f051602aSschwarze 		case MDOC_Emphasis:
10498c62fbf5Sschwarze 			np->norm->Bf.font = FONT_Em;
1050f051602aSschwarze 			break;
1051f051602aSschwarze 		case MDOC_Literal:
10528c62fbf5Sschwarze 			np->norm->Bf.font = FONT_Li;
1053f051602aSschwarze 			break;
1054f051602aSschwarze 		case MDOC_Symbolic:
10558c62fbf5Sschwarze 			np->norm->Bf.font = FONT_Sy;
1056f051602aSschwarze 			break;
1057f051602aSschwarze 		default:
1058769ee804Sschwarze 			abort();
1059f051602aSschwarze 		}
106098b8f00aSschwarze 		return;
1061769ee804Sschwarze 	}
1062769ee804Sschwarze 
1063769ee804Sschwarze 	/* Extract parameter into data. */
1064769ee804Sschwarze 
1065f051602aSschwarze 	if ( ! strcmp(np->child->string, "Em"))
10668c62fbf5Sschwarze 		np->norm->Bf.font = FONT_Em;
1067f051602aSschwarze 	else if ( ! strcmp(np->child->string, "Li"))
10688c62fbf5Sschwarze 		np->norm->Bf.font = FONT_Li;
1069f051602aSschwarze 	else if ( ! strcmp(np->child->string, "Sy"))
10708c62fbf5Sschwarze 		np->norm->Bf.font = FONT_Sy;
107120fa2881Sschwarze 	else
1072ecb10c32Sschwarze 		mandoc_vmsg(MANDOCERR_BF_BADFONT, mdoc->parse,
1073ecb10c32Sschwarze 		    np->child->line, np->child->pos,
1074ecb10c32Sschwarze 		    "Bf %s", np->child->string);
1075f73abda9Skristaps }
1076f73abda9Skristaps 
107798b8f00aSschwarze static void
10780c5064e3Sschwarze post_fname(POST_ARGS)
10790c5064e3Sschwarze {
10803a0d07afSschwarze 	const struct roff_node	*n;
10810ff14c71Sschwarze 	const char		*cp;
10820c5064e3Sschwarze 	size_t			 pos;
10830c5064e3Sschwarze 
10840c5064e3Sschwarze 	n = mdoc->last->child;
10850c5064e3Sschwarze 	pos = strcspn(n->string, "()");
10860ff14c71Sschwarze 	cp = n->string + pos;
10870ff14c71Sschwarze 	if ( ! (cp[0] == '\0' || (cp[0] == '(' && cp[1] == '*')))
10880c5064e3Sschwarze 		mandoc_msg(MANDOCERR_FN_PAREN, mdoc->parse,
10890c5064e3Sschwarze 		    n->line, n->pos + pos, n->string);
10900c5064e3Sschwarze }
10910c5064e3Sschwarze 
109298b8f00aSschwarze static void
10930c5064e3Sschwarze post_fn(POST_ARGS)
10940c5064e3Sschwarze {
10950c5064e3Sschwarze 
10960c5064e3Sschwarze 	post_fname(mdoc);
10970c5064e3Sschwarze 	post_fa(mdoc);
10980c5064e3Sschwarze }
10990c5064e3Sschwarze 
110098b8f00aSschwarze static void
1101753701eeSschwarze post_fo(POST_ARGS)
1102753701eeSschwarze {
11033a0d07afSschwarze 	const struct roff_node	*n;
1104753701eeSschwarze 
1105afcd1f03Sschwarze 	n = mdoc->last;
1106afcd1f03Sschwarze 
1107d1982c71Sschwarze 	if (n->type != ROFFT_HEAD)
1108afcd1f03Sschwarze 		return;
1109afcd1f03Sschwarze 
1110afcd1f03Sschwarze 	if (n->child == NULL) {
1111afcd1f03Sschwarze 		mandoc_msg(MANDOCERR_FO_NOHEAD, mdoc->parse,
1112afcd1f03Sschwarze 		    n->line, n->pos, "Fo");
1113afcd1f03Sschwarze 		return;
1114afcd1f03Sschwarze 	}
1115afcd1f03Sschwarze 	if (n->child != n->last) {
1116afcd1f03Sschwarze 		mandoc_vmsg(MANDOCERR_ARG_EXCESS, mdoc->parse,
1117afcd1f03Sschwarze 		    n->child->next->line, n->child->next->pos,
1118afcd1f03Sschwarze 		    "Fo ... %s", n->child->next->string);
1119afcd1f03Sschwarze 		while (n->child != n->last)
1120fa2127f9Sschwarze 			roff_node_delete(mdoc, n->last);
1121afcd1f03Sschwarze 	}
1122afcd1f03Sschwarze 
11230c5064e3Sschwarze 	post_fname(mdoc);
1124753701eeSschwarze }
1125753701eeSschwarze 
112698b8f00aSschwarze static void
11277e92c062Sschwarze post_fa(POST_ARGS)
11287e92c062Sschwarze {
11293a0d07afSschwarze 	const struct roff_node *n;
11307e92c062Sschwarze 	const char *cp;
11317e92c062Sschwarze 
11327e92c062Sschwarze 	for (n = mdoc->last->child; n != NULL; n = n->next) {
11337e92c062Sschwarze 		for (cp = n->string; *cp != '\0'; cp++) {
11347e92c062Sschwarze 			/* Ignore callbacks and alterations. */
11357e92c062Sschwarze 			if (*cp == '(' || *cp == '{')
11367e92c062Sschwarze 				break;
11377e92c062Sschwarze 			if (*cp != ',')
11387e92c062Sschwarze 				continue;
11397e92c062Sschwarze 			mandoc_msg(MANDOCERR_FA_COMMA, mdoc->parse,
11407e92c062Sschwarze 			    n->line, n->pos + (cp - n->string),
11417e92c062Sschwarze 			    n->string);
11427e92c062Sschwarze 			break;
11437e92c062Sschwarze 		}
11447e92c062Sschwarze 	}
114504fbb99fSschwarze 	post_delim(mdoc);
11467e92c062Sschwarze }
11477e92c062Sschwarze 
114898b8f00aSschwarze static void
1149f73abda9Skristaps post_nm(POST_ARGS)
1150f73abda9Skristaps {
11513a0d07afSschwarze 	struct roff_node	*n;
11522d266539Sschwarze 
11532d266539Sschwarze 	n = mdoc->last;
11542d266539Sschwarze 
11552d266539Sschwarze 	if (n->last != NULL &&
11562d266539Sschwarze 	    (n->last->tok == MDOC_Pp ||
11572d266539Sschwarze 	     n->last->tok == MDOC_Lp))
11582d266539Sschwarze 		mdoc_node_relink(mdoc, n->last);
115920fa2881Sschwarze 
1160f27faaccSschwarze 	if (mdoc->meta.name == NULL)
1161423631c9Sschwarze 		deroff(&mdoc->meta.name, n);
116220fa2881Sschwarze 
1163f27faaccSschwarze 	if (mdoc->meta.name == NULL ||
1164f27faaccSschwarze 	    (mdoc->lastsec == SEC_NAME && n->child == NULL))
1165bd594191Sschwarze 		mandoc_msg(MANDOCERR_NM_NONAME, mdoc->parse,
11662d266539Sschwarze 		    n->line, n->pos, "Nm");
116790957cf5Sschwarze 
116804fbb99fSschwarze 	if (n->type == ROFFT_ELEM)
116904fbb99fSschwarze 		post_delim(mdoc);
117004fbb99fSschwarze 
117190957cf5Sschwarze 	if ((n->type != ROFFT_ELEM && n->type != ROFFT_HEAD) ||
117290957cf5Sschwarze 	    (n->child != NULL && n->child->type == ROFFT_TEXT) ||
117390957cf5Sschwarze 	    mdoc->meta.name == NULL)
117490957cf5Sschwarze 		return;
117590957cf5Sschwarze 
117690957cf5Sschwarze 	mdoc->next = ROFF_NEXT_CHILD;
117790957cf5Sschwarze 	roff_word_alloc(mdoc, n->line, n->pos, mdoc->meta.name);
117890957cf5Sschwarze 	mdoc->last->flags |= NODE_NOSRC;
117990957cf5Sschwarze 	mdoc->last = n;
118020fa2881Sschwarze }
118120fa2881Sschwarze 
118298b8f00aSschwarze static void
1183753701eeSschwarze post_nd(POST_ARGS)
1184753701eeSschwarze {
11853a0d07afSschwarze 	struct roff_node	*n;
1186648df85bSschwarze 	size_t			 sz;
1187753701eeSschwarze 
11881570daf1Sschwarze 	n = mdoc->last;
11891570daf1Sschwarze 
1190d1982c71Sschwarze 	if (n->type != ROFFT_BODY)
11911570daf1Sschwarze 		return;
11921570daf1Sschwarze 
119356e9e976Sschwarze 	if (n->sec != SEC_NAME)
119456e9e976Sschwarze 		mandoc_msg(MANDOCERR_ND_LATE, mdoc->parse,
119556e9e976Sschwarze 		    n->line, n->pos, "Nd");
119656e9e976Sschwarze 
11971570daf1Sschwarze 	if (n->child == NULL)
11981570daf1Sschwarze 		mandoc_msg(MANDOCERR_ND_EMPTY, mdoc->parse,
11991570daf1Sschwarze 		    n->line, n->pos, "Nd");
1200648df85bSschwarze 	else if (n->last->type == ROFFT_TEXT &&
1201648df85bSschwarze 	    (sz = strlen(n->last->string)) != 0 &&
1202648df85bSschwarze 	    n->last->string[sz - 1] == '.')
1203648df85bSschwarze 		mandoc_msg(MANDOCERR_ND_DOT, mdoc->parse,
1204648df85bSschwarze 		    n->last->line, n->last->pos + sz - 1, NULL);
12051570daf1Sschwarze 
120698b8f00aSschwarze 	post_hyph(mdoc);
1207753701eeSschwarze }
1208753701eeSschwarze 
120998b8f00aSschwarze static void
12103e642ba0Sschwarze post_display(POST_ARGS)
1211753701eeSschwarze {
12123e642ba0Sschwarze 	struct roff_node *n, *np;
1213753701eeSschwarze 
1214b7530f2fSschwarze 	n = mdoc->last;
12153e642ba0Sschwarze 	switch (n->type) {
12163e642ba0Sschwarze 	case ROFFT_BODY:
12174b5c4138Sschwarze 		if (n->end != ENDBODY_NOT) {
1218611d9138Sschwarze 			if (n->tok == MDOC_Bd &&
1219611d9138Sschwarze 			    n->body->parent->args == NULL)
12204b5c4138Sschwarze 				roff_node_delete(mdoc, n);
12214b5c4138Sschwarze 		} else if (n->child == NULL)
12221d0823adSschwarze 			mandoc_msg(MANDOCERR_BLK_EMPTY, mdoc->parse,
122314a309e3Sschwarze 			    n->line, n->pos, roff_name[n->tok]);
12243e642ba0Sschwarze 		else if (n->tok == MDOC_D1)
12253e642ba0Sschwarze 			post_hyph(mdoc);
12263e642ba0Sschwarze 		break;
12273e642ba0Sschwarze 	case ROFFT_BLOCK:
12283e642ba0Sschwarze 		if (n->tok == MDOC_Bd) {
1229a43e24e2Sschwarze 			if (n->args == NULL) {
1230a43e24e2Sschwarze 				mandoc_msg(MANDOCERR_BD_NOARG,
1231a43e24e2Sschwarze 				    mdoc->parse, n->line, n->pos, "Bd");
1232a43e24e2Sschwarze 				mdoc->next = ROFF_NEXT_SIBLING;
1233a43e24e2Sschwarze 				while (n->body->child != NULL)
1234a43e24e2Sschwarze 					mdoc_node_relink(mdoc,
1235a43e24e2Sschwarze 					    n->body->child);
1236a43e24e2Sschwarze 				roff_node_delete(mdoc, n);
1237a43e24e2Sschwarze 				break;
1238a43e24e2Sschwarze 			}
12393e642ba0Sschwarze 			post_bd(mdoc);
12403e642ba0Sschwarze 			post_prevpar(mdoc);
12413e642ba0Sschwarze 		}
12423e642ba0Sschwarze 		for (np = n->parent; np != NULL; np = np->parent) {
12433e642ba0Sschwarze 			if (np->type == ROFFT_BLOCK && np->tok == MDOC_Bd) {
12443e642ba0Sschwarze 				mandoc_vmsg(MANDOCERR_BD_NEST,
12453e642ba0Sschwarze 				    mdoc->parse, n->line, n->pos,
124614a309e3Sschwarze 				    "%s in Bd", roff_name[n->tok]);
12473e642ba0Sschwarze 				break;
12483e642ba0Sschwarze 			}
12493e642ba0Sschwarze 		}
12503e642ba0Sschwarze 		break;
12513e642ba0Sschwarze 	default:
12523e642ba0Sschwarze 		break;
12533e642ba0Sschwarze 	}
125420fa2881Sschwarze }
125520fa2881Sschwarze 
125698b8f00aSschwarze static void
125720fa2881Sschwarze post_defaults(POST_ARGS)
125820fa2881Sschwarze {
12593a0d07afSschwarze 	struct roff_node *nn;
126020fa2881Sschwarze 
126104fbb99fSschwarze 	if (mdoc->last->child != NULL) {
126204fbb99fSschwarze 		post_delim(mdoc);
126304fbb99fSschwarze 		return;
126404fbb99fSschwarze 	}
126504fbb99fSschwarze 
126620fa2881Sschwarze 	/*
126720fa2881Sschwarze 	 * The `Ar' defaults to "file ..." if no value is provided as an
126820fa2881Sschwarze 	 * argument; the `Mt' and `Pa' macros use "~"; the `Li' just
126920fa2881Sschwarze 	 * gets an empty string.
127020fa2881Sschwarze 	 */
1271f73abda9Skristaps 
127220fa2881Sschwarze 	nn = mdoc->last;
127320fa2881Sschwarze 	switch (nn->tok) {
127449aff9f8Sschwarze 	case MDOC_Ar:
1275396853b5Sschwarze 		mdoc->next = ROFF_NEXT_CHILD;
127669c34eaaSschwarze 		roff_word_alloc(mdoc, nn->line, nn->pos, "file");
127743808411Sschwarze 		mdoc->last->flags |= NODE_NOSRC;
127869c34eaaSschwarze 		roff_word_alloc(mdoc, nn->line, nn->pos, "...");
127943808411Sschwarze 		mdoc->last->flags |= NODE_NOSRC;
128020fa2881Sschwarze 		break;
128149aff9f8Sschwarze 	case MDOC_Pa:
128249aff9f8Sschwarze 	case MDOC_Mt:
1283396853b5Sschwarze 		mdoc->next = ROFF_NEXT_CHILD;
128469c34eaaSschwarze 		roff_word_alloc(mdoc, nn->line, nn->pos, "~");
128543808411Sschwarze 		mdoc->last->flags |= NODE_NOSRC;
128620fa2881Sschwarze 		break;
128720fa2881Sschwarze 	default:
128820fa2881Sschwarze 		abort();
1289f73abda9Skristaps 	}
129020fa2881Sschwarze 	mdoc->last = nn;
129120fa2881Sschwarze }
1292f73abda9Skristaps 
129398b8f00aSschwarze static void
1294f73abda9Skristaps post_at(POST_ARGS)
1295f73abda9Skristaps {
12963af8e8d7Sschwarze 	struct roff_node	*n, *nch;
12973af8e8d7Sschwarze 	const char		*att;
129820fa2881Sschwarze 
1299753701eeSschwarze 	n = mdoc->last;
13003af8e8d7Sschwarze 	nch = n->child;
1301753701eeSschwarze 
130220fa2881Sschwarze 	/*
130320fa2881Sschwarze 	 * If we have a child, look it up in the standard keys.  If a
130420fa2881Sschwarze 	 * key exist, use that instead of the child; if it doesn't,
130520fa2881Sschwarze 	 * prefix "AT&T UNIX " to the existing data.
130620fa2881Sschwarze 	 */
1307f73abda9Skristaps 
13083af8e8d7Sschwarze 	att = NULL;
13093af8e8d7Sschwarze 	if (nch != NULL && ((att = mdoc_a2att(nch->string)) == NULL))
1310bd594191Sschwarze 		mandoc_vmsg(MANDOCERR_AT_BAD, mdoc->parse,
13113af8e8d7Sschwarze 		    nch->line, nch->pos, "At %s", nch->string);
1312f73abda9Skristaps 
13133af8e8d7Sschwarze 	mdoc->next = ROFF_NEXT_CHILD;
13143af8e8d7Sschwarze 	if (att != NULL) {
13153af8e8d7Sschwarze 		roff_word_alloc(mdoc, nch->line, nch->pos, att);
13163af8e8d7Sschwarze 		nch->flags |= NODE_NOPRT;
13173af8e8d7Sschwarze 	} else
13183af8e8d7Sschwarze 		roff_word_alloc(mdoc, n->line, n->pos, "AT&T UNIX");
13193af8e8d7Sschwarze 	mdoc->last->flags |= NODE_NOSRC;
13203af8e8d7Sschwarze 	mdoc->last = n;
132120fa2881Sschwarze }
1322f73abda9Skristaps 
132398b8f00aSschwarze static void
1324f73abda9Skristaps post_an(POST_ARGS)
1325f73abda9Skristaps {
13263a0d07afSschwarze 	struct roff_node *np, *nch;
1327f73abda9Skristaps 
13283e642ba0Sschwarze 	post_an_norm(mdoc);
13293e642ba0Sschwarze 
1330769ee804Sschwarze 	np = mdoc->last;
1331cba50636Sschwarze 	nch = np->child;
1332cba50636Sschwarze 	if (np->norm->An.auth == AUTH__NONE) {
1333cba50636Sschwarze 		if (nch == NULL)
1334cba50636Sschwarze 			mandoc_msg(MANDOCERR_MACRO_EMPTY, mdoc->parse,
1335cba50636Sschwarze 			    np->line, np->pos, "An");
133604fbb99fSschwarze 		else
133704fbb99fSschwarze 			post_delim(mdoc);
1338cba50636Sschwarze 	} else if (nch != NULL)
13393798fb25Sschwarze 		mandoc_vmsg(MANDOCERR_ARG_EXCESS, mdoc->parse,
13403798fb25Sschwarze 		    nch->line, nch->pos, "An ... %s", nch->string);
1341f73abda9Skristaps }
1342f73abda9Skristaps 
134398b8f00aSschwarze static void
1344551cd4a8Sschwarze post_en(POST_ARGS)
1345551cd4a8Sschwarze {
1346551cd4a8Sschwarze 
13473e642ba0Sschwarze 	post_obsolete(mdoc);
1348d1982c71Sschwarze 	if (mdoc->last->type == ROFFT_BLOCK)
1349551cd4a8Sschwarze 		mdoc->last->norm->Es = mdoc->last_es;
1350551cd4a8Sschwarze }
1351551cd4a8Sschwarze 
135298b8f00aSschwarze static void
1353551cd4a8Sschwarze post_es(POST_ARGS)
1354551cd4a8Sschwarze {
1355551cd4a8Sschwarze 
13563e642ba0Sschwarze 	post_obsolete(mdoc);
1357551cd4a8Sschwarze 	mdoc->last_es = mdoc->last;
1358551cd4a8Sschwarze }
1359551cd4a8Sschwarze 
136098b8f00aSschwarze static void
1361816c3c54Sschwarze post_xx(POST_ARGS)
1362816c3c54Sschwarze {
1363816c3c54Sschwarze 	struct roff_node	*n;
1364816c3c54Sschwarze 	const char		*os;
1365816c3c54Sschwarze 
136604fbb99fSschwarze 	post_delim(mdoc);
136704fbb99fSschwarze 
1368816c3c54Sschwarze 	n = mdoc->last;
1369816c3c54Sschwarze 	switch (n->tok) {
1370816c3c54Sschwarze 	case MDOC_Bsx:
1371816c3c54Sschwarze 		os = "BSD/OS";
1372816c3c54Sschwarze 		break;
1373816c3c54Sschwarze 	case MDOC_Dx:
1374816c3c54Sschwarze 		os = "DragonFly";
1375816c3c54Sschwarze 		break;
1376816c3c54Sschwarze 	case MDOC_Fx:
1377816c3c54Sschwarze 		os = "FreeBSD";
1378816c3c54Sschwarze 		break;
1379816c3c54Sschwarze 	case MDOC_Nx:
1380816c3c54Sschwarze 		os = "NetBSD";
1381816c3c54Sschwarze 		break;
1382816c3c54Sschwarze 	case MDOC_Ox:
1383816c3c54Sschwarze 		os = "OpenBSD";
1384816c3c54Sschwarze 		break;
1385816c3c54Sschwarze 	case MDOC_Ux:
1386816c3c54Sschwarze 		os = "UNIX";
1387816c3c54Sschwarze 		break;
1388816c3c54Sschwarze 	default:
1389816c3c54Sschwarze 		abort();
1390816c3c54Sschwarze 	}
1391816c3c54Sschwarze 	mdoc->next = ROFF_NEXT_CHILD;
1392816c3c54Sschwarze 	roff_word_alloc(mdoc, n->line, n->pos, os);
1393816c3c54Sschwarze 	mdoc->last->flags |= NODE_NOSRC;
1394816c3c54Sschwarze 	mdoc->last = n;
1395816c3c54Sschwarze }
1396816c3c54Sschwarze 
1397816c3c54Sschwarze static void
1398f73abda9Skristaps post_it(POST_ARGS)
1399f73abda9Skristaps {
14003a0d07afSschwarze 	struct roff_node *nbl, *nit, *nch;
140119a69263Sschwarze 	int		  i, cols;
14026093755cSschwarze 	enum mdoc_list	  lt;
1403f73abda9Skristaps 
14043e642ba0Sschwarze 	post_prevpar(mdoc);
14053e642ba0Sschwarze 
14069530682eSschwarze 	nit = mdoc->last;
1407d1982c71Sschwarze 	if (nit->type != ROFFT_BLOCK)
140898b8f00aSschwarze 		return;
1409f73abda9Skristaps 
14109530682eSschwarze 	nbl = nit->parent->parent;
14119530682eSschwarze 	lt = nbl->norm->Bl.type;
14126093755cSschwarze 
14136093755cSschwarze 	switch (lt) {
141449aff9f8Sschwarze 	case LIST_tag:
141549aff9f8Sschwarze 	case LIST_hang:
141649aff9f8Sschwarze 	case LIST_ohang:
141749aff9f8Sschwarze 	case LIST_inset:
141849aff9f8Sschwarze 	case LIST_diag:
1419d26e35c2Sschwarze 		if (nit->head->child == NULL)
1420bd594191Sschwarze 			mandoc_vmsg(MANDOCERR_IT_NOHEAD,
14219530682eSschwarze 			    mdoc->parse, nit->line, nit->pos,
1422bd594191Sschwarze 			    "Bl -%s It",
14239530682eSschwarze 			    mdoc_argnames[nbl->args->argv[0].arg]);
1424f73abda9Skristaps 		break;
142549aff9f8Sschwarze 	case LIST_bullet:
142649aff9f8Sschwarze 	case LIST_dash:
142749aff9f8Sschwarze 	case LIST_enum:
142849aff9f8Sschwarze 	case LIST_hyphen:
1429d26e35c2Sschwarze 		if (nit->body == NULL || nit->body->child == NULL)
1430bd594191Sschwarze 			mandoc_vmsg(MANDOCERR_IT_NOBODY,
143166788495Sschwarze 			    mdoc->parse, nit->line, nit->pos,
1432bd594191Sschwarze 			    "Bl -%s It",
143366788495Sschwarze 			    mdoc_argnames[nbl->args->argv[0].arg]);
1434f73abda9Skristaps 		/* FALLTHROUGH */
143549aff9f8Sschwarze 	case LIST_item:
143625f8eeb1Sschwarze 		if ((nch = nit->head->child) != NULL)
143714a309e3Sschwarze 			mandoc_vmsg(MANDOCERR_ARG_SKIP, mdoc->parse,
143814a309e3Sschwarze 			    nit->line, nit->pos, "It %s",
143914a309e3Sschwarze 			    nch->string == NULL ? roff_name[nch->tok] :
144014a309e3Sschwarze 			    nch->string);
1441f73abda9Skristaps 		break;
144249aff9f8Sschwarze 	case LIST_column:
14439530682eSschwarze 		cols = (int)nbl->norm->Bl.ncols;
14446093755cSschwarze 
1445d26e35c2Sschwarze 		assert(nit->head->child == NULL);
14466093755cSschwarze 
1447f051602aSschwarze 		i = 0;
1448f051602aSschwarze 		for (nch = nit->child; nch != NULL; nch = nch->next)
1449d1982c71Sschwarze 			if (nch->type == ROFFT_BODY)
1450f73abda9Skristaps 				i++;
145153292e81Sschwarze 
1452e14c4c11Sschwarze 		if (i < cols || i > cols + 1)
1453bce599dfSschwarze 			mandoc_vmsg(MANDOCERR_BL_COL,
1454e14c4c11Sschwarze 			    mdoc->parse, nit->line, nit->pos,
1455bce599dfSschwarze 			    "%d columns, %d cells", cols, i);
1456e14c4c11Sschwarze 		break;
1457f73abda9Skristaps 	default:
145866788495Sschwarze 		abort();
1459f73abda9Skristaps 	}
1460f73abda9Skristaps }
1461f73abda9Skristaps 
146298b8f00aSschwarze static void
146320fa2881Sschwarze post_bl_block(POST_ARGS)
146420fa2881Sschwarze {
14653a0d07afSschwarze 	struct roff_node *n, *ni, *nc;
146620fa2881Sschwarze 
14673e642ba0Sschwarze 	post_prevpar(mdoc);
14683e642ba0Sschwarze 
146920fa2881Sschwarze 	n = mdoc->last;
1470f051602aSschwarze 	for (ni = n->body->child; ni != NULL; ni = ni->next) {
1471f051602aSschwarze 		if (ni->body == NULL)
1472bb99f0faSschwarze 			continue;
1473bb99f0faSschwarze 		nc = ni->body->last;
1474f051602aSschwarze 		while (nc != NULL) {
1475bb99f0faSschwarze 			switch (nc->tok) {
147649aff9f8Sschwarze 			case MDOC_Pp:
147749aff9f8Sschwarze 			case MDOC_Lp:
147829478532Sschwarze 			case ROFF_br:
1479bb99f0faSschwarze 				break;
1480bb99f0faSschwarze 			default:
1481bb99f0faSschwarze 				nc = NULL;
1482bb99f0faSschwarze 				continue;
1483bb99f0faSschwarze 			}
1484f051602aSschwarze 			if (ni->next == NULL) {
148520369664Sschwarze 				mandoc_msg(MANDOCERR_PAR_MOVE,
148620369664Sschwarze 				    mdoc->parse, nc->line, nc->pos,
148714a309e3Sschwarze 				    roff_name[nc->tok]);
148898b8f00aSschwarze 				mdoc_node_relink(mdoc, nc);
1489f051602aSschwarze 			} else if (n->norm->Bl.comp == 0 &&
1490f051602aSschwarze 			    n->norm->Bl.type != LIST_column) {
149120369664Sschwarze 				mandoc_vmsg(MANDOCERR_PAR_SKIP,
149220369664Sschwarze 				    mdoc->parse, nc->line, nc->pos,
149314a309e3Sschwarze 				    "%s before It", roff_name[nc->tok]);
1494fa2127f9Sschwarze 				roff_node_delete(mdoc, nc);
1495bb99f0faSschwarze 			} else
1496bb99f0faSschwarze 				break;
1497bb99f0faSschwarze 			nc = ni->body->last;
1498bb99f0faSschwarze 		}
1499bb99f0faSschwarze 	}
150020fa2881Sschwarze }
150120fa2881Sschwarze 
150290d52a15Sschwarze /*
150390d52a15Sschwarze  * If the argument of -offset or -width is a macro,
150490d52a15Sschwarze  * replace it with the associated default width.
150590d52a15Sschwarze  */
15066050a3daSschwarze static void
15076050a3daSschwarze rewrite_macro2len(struct roff_man *mdoc, char **arg)
150820fa2881Sschwarze {
150920fa2881Sschwarze 	size_t		  width;
151014a309e3Sschwarze 	enum roff_tok	  tok;
151120fa2881Sschwarze 
151290d52a15Sschwarze 	if (*arg == NULL)
151390d52a15Sschwarze 		return;
151490d52a15Sschwarze 	else if ( ! strcmp(*arg, "Ds"))
151520fa2881Sschwarze 		width = 6;
15166050a3daSschwarze 	else if ((tok = roffhash_find(mdoc->mdocmac, *arg, 0)) == TOKEN_NONE)
151790d52a15Sschwarze 		return;
1518dc0d8bb2Sschwarze 	else
1519dc0d8bb2Sschwarze 		width = macro2len(tok);
152020fa2881Sschwarze 
152190d52a15Sschwarze 	free(*arg);
152290d52a15Sschwarze 	mandoc_asprintf(arg, "%zun", width);
152320fa2881Sschwarze }
152420fa2881Sschwarze 
152598b8f00aSschwarze static void
1526395185ccSschwarze post_bl_head(POST_ARGS)
1527395185ccSschwarze {
15283a0d07afSschwarze 	struct roff_node *nbl, *nh, *nch, *nnext;
1529f5174743Sschwarze 	struct mdoc_argv *argv;
153020fa2881Sschwarze 	int		  i, j;
1531395185ccSschwarze 
15323e642ba0Sschwarze 	post_bl_norm(mdoc);
15332588c917Sschwarze 
15343e642ba0Sschwarze 	nh = mdoc->last;
15352588c917Sschwarze 	if (nh->norm->Bl.type != LIST_column) {
15362588c917Sschwarze 		if ((nch = nh->child) == NULL)
15372588c917Sschwarze 			return;
15382588c917Sschwarze 		mandoc_vmsg(MANDOCERR_ARG_EXCESS, mdoc->parse,
15392588c917Sschwarze 		    nch->line, nch->pos, "Bl ... %s", nch->string);
15402588c917Sschwarze 		while (nch != NULL) {
1541fa2127f9Sschwarze 			roff_node_delete(mdoc, nch);
15422588c917Sschwarze 			nch = nh->child;
15432588c917Sschwarze 		}
154498b8f00aSschwarze 		return;
154598b8f00aSschwarze 	}
1546395185ccSschwarze 
154720fa2881Sschwarze 	/*
1548f5174743Sschwarze 	 * Append old-style lists, where the column width specifiers
154920fa2881Sschwarze 	 * trail as macro parameters, to the new-style ("normal-form")
155020fa2881Sschwarze 	 * lists where they're argument values following -column.
155120fa2881Sschwarze 	 */
155220fa2881Sschwarze 
15532588c917Sschwarze 	if (nh->child == NULL)
155498b8f00aSschwarze 		return;
155520fa2881Sschwarze 
15562588c917Sschwarze 	nbl = nh->parent;
15572588c917Sschwarze 	for (j = 0; j < (int)nbl->args->argc; j++)
15582588c917Sschwarze 		if (nbl->args->argv[j].arg == MDOC_Column)
155920fa2881Sschwarze 			break;
156020fa2881Sschwarze 
15612588c917Sschwarze 	assert(j < (int)nbl->args->argc);
156220fa2881Sschwarze 
156320fa2881Sschwarze 	/*
1564a5e11edeSschwarze 	 * Accommodate for new-style groff column syntax.  Shuffle the
156520fa2881Sschwarze 	 * child nodes, all of which must be TEXT, as arguments for the
156620fa2881Sschwarze 	 * column field.  Then, delete the head children.
156720fa2881Sschwarze 	 */
156820fa2881Sschwarze 
15692588c917Sschwarze 	argv = nbl->args->argv + j;
1570f5174743Sschwarze 	i = argv->sz;
157130e5ee06Sschwarze 	for (nch = nh->child; nch != NULL; nch = nch->next)
157230e5ee06Sschwarze 		argv->sz++;
1573f5174743Sschwarze 	argv->value = mandoc_reallocarray(argv->value,
1574f5174743Sschwarze 	    argv->sz, sizeof(char *));
157520fa2881Sschwarze 
15762588c917Sschwarze 	nh->norm->Bl.ncols = argv->sz;
15772588c917Sschwarze 	nh->norm->Bl.cols = (void *)argv->value;
157820fa2881Sschwarze 
15792588c917Sschwarze 	for (nch = nh->child; nch != NULL; nch = nnext) {
15802588c917Sschwarze 		argv->value[i++] = nch->string;
15812588c917Sschwarze 		nch->string = NULL;
15822588c917Sschwarze 		nnext = nch->next;
1583fa2127f9Sschwarze 		roff_node_delete(NULL, nch);
1584395185ccSschwarze 	}
15852588c917Sschwarze 	nh->child = NULL;
1586b16e7ddfSschwarze }
1587b16e7ddfSschwarze 
158898b8f00aSschwarze static void
1589f73abda9Skristaps post_bl(POST_ARGS)
1590f73abda9Skristaps {
15913a0d07afSschwarze 	struct roff_node	*nparent, *nprev; /* of the Bl block */
15923a0d07afSschwarze 	struct roff_node	*nblock, *nbody;  /* of the Bl */
15933a0d07afSschwarze 	struct roff_node	*nchild, *nnext;  /* of the Bl body */
1594ce0ef847Sschwarze 	const char		*prev_Er;
1595ce0ef847Sschwarze 	int			 order;
1596f73abda9Skristaps 
15972a427d60Sschwarze 	nbody = mdoc->last;
15982a427d60Sschwarze 	switch (nbody->type) {
1599d1982c71Sschwarze 	case ROFFT_BLOCK:
160098b8f00aSschwarze 		post_bl_block(mdoc);
160198b8f00aSschwarze 		return;
1602d1982c71Sschwarze 	case ROFFT_HEAD:
160398b8f00aSschwarze 		post_bl_head(mdoc);
160498b8f00aSschwarze 		return;
1605d1982c71Sschwarze 	case ROFFT_BODY:
1606f6127a73Sschwarze 		break;
16072a427d60Sschwarze 	default:
160898b8f00aSschwarze 		return;
1609f6127a73Sschwarze 	}
1610396853b5Sschwarze 	if (nbody->end != ENDBODY_NOT)
1611396853b5Sschwarze 		return;
1612f6127a73Sschwarze 
16132a427d60Sschwarze 	nchild = nbody->child;
1614b7530f2fSschwarze 	if (nchild == NULL) {
16151d0823adSschwarze 		mandoc_msg(MANDOCERR_BLK_EMPTY, mdoc->parse,
1616b7530f2fSschwarze 		    nbody->line, nbody->pos, "Bl");
1617b7530f2fSschwarze 		return;
1618b7530f2fSschwarze 	}
1619b7530f2fSschwarze 	while (nchild != NULL) {
1620ad7fa6e5Sschwarze 		nnext = nchild->next;
1621204684a7Sschwarze 		if (nchild->tok == MDOC_It ||
1622204684a7Sschwarze 		    (nchild->tok == MDOC_Sm &&
1623ad7fa6e5Sschwarze 		     nnext != NULL && nnext->tok == MDOC_It)) {
1624ad7fa6e5Sschwarze 			nchild = nnext;
1625ad7fa6e5Sschwarze 			continue;
1626ad7fa6e5Sschwarze 		}
1627ad7fa6e5Sschwarze 
1628ad7fa6e5Sschwarze 		/*
1629ad7fa6e5Sschwarze 		 * In .Bl -column, the first rows may be implicit,
1630ad7fa6e5Sschwarze 		 * that is, they may not start with .It macros.
1631ad7fa6e5Sschwarze 		 * Such rows may be followed by nodes generated on the
1632ad7fa6e5Sschwarze 		 * roff level, for example .TS, which cannot be moved
1633ad7fa6e5Sschwarze 		 * out of the list.  In that case, wrap such roff nodes
1634ad7fa6e5Sschwarze 		 * into an implicit row.
1635ad7fa6e5Sschwarze 		 */
1636ad7fa6e5Sschwarze 
1637ad7fa6e5Sschwarze 		if (nchild->prev != NULL) {
1638ad7fa6e5Sschwarze 			mdoc->last = nchild;
1639ad7fa6e5Sschwarze 			mdoc->next = ROFF_NEXT_SIBLING;
1640ad7fa6e5Sschwarze 			roff_block_alloc(mdoc, nchild->line,
1641ad7fa6e5Sschwarze 			    nchild->pos, MDOC_It);
1642ad7fa6e5Sschwarze 			roff_head_alloc(mdoc, nchild->line,
1643ad7fa6e5Sschwarze 			    nchild->pos, MDOC_It);
1644ad7fa6e5Sschwarze 			mdoc->next = ROFF_NEXT_SIBLING;
1645ad7fa6e5Sschwarze 			roff_body_alloc(mdoc, nchild->line,
1646ad7fa6e5Sschwarze 			    nchild->pos, MDOC_It);
1647ad7fa6e5Sschwarze 			while (nchild->tok != MDOC_It) {
1648ad7fa6e5Sschwarze 				mdoc_node_relink(mdoc, nchild);
1649ad7fa6e5Sschwarze 				if ((nchild = nnext) == NULL)
1650ad7fa6e5Sschwarze 					break;
1651ad7fa6e5Sschwarze 				nnext = nchild->next;
1652ad7fa6e5Sschwarze 				mdoc->next = ROFF_NEXT_SIBLING;
1653ad7fa6e5Sschwarze 			}
1654ad7fa6e5Sschwarze 			mdoc->last = nbody;
16552a427d60Sschwarze 			continue;
16562a427d60Sschwarze 		}
16572a427d60Sschwarze 
1658dd25b57cSschwarze 		mandoc_msg(MANDOCERR_BL_MOVE, mdoc->parse,
165914a309e3Sschwarze 		    nchild->line, nchild->pos, roff_name[nchild->tok]);
16602a427d60Sschwarze 
16612a427d60Sschwarze 		/*
16622a427d60Sschwarze 		 * Move the node out of the Bl block.
16632a427d60Sschwarze 		 * First, collect all required node pointers.
16642a427d60Sschwarze 		 */
16652a427d60Sschwarze 
16662a427d60Sschwarze 		nblock  = nbody->parent;
16672a427d60Sschwarze 		nprev   = nblock->prev;
16682a427d60Sschwarze 		nparent = nblock->parent;
16692a427d60Sschwarze 
16702a427d60Sschwarze 		/*
16712a427d60Sschwarze 		 * Unlink this child.
16722a427d60Sschwarze 		 */
16732a427d60Sschwarze 
16742a427d60Sschwarze 		nbody->child = nnext;
167530e5ee06Sschwarze 		if (nnext == NULL)
167630e5ee06Sschwarze 			nbody->last  = NULL;
167730e5ee06Sschwarze 		else
16782a427d60Sschwarze 			nnext->prev = NULL;
16792a427d60Sschwarze 
16802a427d60Sschwarze 		/*
16812a427d60Sschwarze 		 * Relink this child.
16822a427d60Sschwarze 		 */
16832a427d60Sschwarze 
16842a427d60Sschwarze 		nchild->parent = nparent;
16852a427d60Sschwarze 		nchild->prev   = nprev;
16862a427d60Sschwarze 		nchild->next   = nblock;
16872a427d60Sschwarze 
16882a427d60Sschwarze 		nblock->prev = nchild;
1689f051602aSschwarze 		if (nprev == NULL)
16902a427d60Sschwarze 			nparent->child = nchild;
16912a427d60Sschwarze 		else
16922a427d60Sschwarze 			nprev->next = nchild;
16932a427d60Sschwarze 
16942a427d60Sschwarze 		nchild = nnext;
1695f73abda9Skristaps 	}
1696ce0ef847Sschwarze 
1697f3476b07Sschwarze 	if (mdoc->meta.os_e != MANDOC_OS_NETBSD)
1698ce0ef847Sschwarze 		return;
1699ce0ef847Sschwarze 
1700ce0ef847Sschwarze 	prev_Er = NULL;
1701ce0ef847Sschwarze 	for (nchild = nbody->child; nchild != NULL; nchild = nchild->next) {
1702ce0ef847Sschwarze 		if (nchild->tok != MDOC_It)
1703ce0ef847Sschwarze 			continue;
1704ce0ef847Sschwarze 		if ((nnext = nchild->head->child) == NULL)
1705ce0ef847Sschwarze 			continue;
1706ce0ef847Sschwarze 		if (nnext->type == ROFFT_BLOCK)
1707ce0ef847Sschwarze 			nnext = nnext->body->child;
1708ce0ef847Sschwarze 		if (nnext == NULL || nnext->tok != MDOC_Er)
1709ce0ef847Sschwarze 			continue;
1710ce0ef847Sschwarze 		nnext = nnext->child;
1711ce0ef847Sschwarze 		if (prev_Er != NULL) {
1712ce0ef847Sschwarze 			order = strcmp(prev_Er, nnext->string);
1713ce0ef847Sschwarze 			if (order > 0)
1714ce0ef847Sschwarze 				mandoc_vmsg(MANDOCERR_ER_ORDER,
1715ce0ef847Sschwarze 				    mdoc->parse, nnext->line, nnext->pos,
1716f3476b07Sschwarze 				    "Er %s %s (NetBSD)",
1717f3476b07Sschwarze 				    prev_Er, nnext->string);
1718ce0ef847Sschwarze 			else if (order == 0)
1719ce0ef847Sschwarze 				mandoc_vmsg(MANDOCERR_ER_REP,
1720ce0ef847Sschwarze 				    mdoc->parse, nnext->line, nnext->pos,
1721f3476b07Sschwarze 				    "Er %s (NetBSD)", prev_Er);
1722ce0ef847Sschwarze 		}
1723ce0ef847Sschwarze 		prev_Er = nnext->string;
1724ce0ef847Sschwarze 	}
1725f73abda9Skristaps }
1726f73abda9Skristaps 
172798b8f00aSschwarze static void
1728753701eeSschwarze post_bk(POST_ARGS)
1729753701eeSschwarze {
17303a0d07afSschwarze 	struct roff_node	*n;
1731753701eeSschwarze 
17322588c917Sschwarze 	n = mdoc->last;
17332588c917Sschwarze 
1734d1982c71Sschwarze 	if (n->type == ROFFT_BLOCK && n->body->child == NULL) {
17351d0823adSschwarze 		mandoc_msg(MANDOCERR_BLK_EMPTY,
17362588c917Sschwarze 		    mdoc->parse, n->line, n->pos, "Bk");
1737fa2127f9Sschwarze 		roff_node_delete(mdoc, n);
17382588c917Sschwarze 	}
1739753701eeSschwarze }
1740753701eeSschwarze 
174198b8f00aSschwarze static void
1742f051602aSschwarze post_sm(POST_ARGS)
1743f73abda9Skristaps {
17443a0d07afSschwarze 	struct roff_node	*nch;
1745f73abda9Skristaps 
1746dc0d8bb2Sschwarze 	nch = mdoc->last->child;
1747dc0d8bb2Sschwarze 
174878bbbab4Sschwarze 	if (nch == NULL) {
1749f9e7bf99Sschwarze 		mdoc->flags ^= MDOC_SMOFF;
175098b8f00aSschwarze 		return;
1751bb648afaSschwarze 	}
1752f9e7bf99Sschwarze 
1753d1982c71Sschwarze 	assert(nch->type == ROFFT_TEXT);
175420fa2881Sschwarze 
175578bbbab4Sschwarze 	if ( ! strcmp(nch->string, "on")) {
1756ec2beb53Sschwarze 		mdoc->flags &= ~MDOC_SMOFF;
175798b8f00aSschwarze 		return;
1758ec2beb53Sschwarze 	}
175978bbbab4Sschwarze 	if ( ! strcmp(nch->string, "off")) {
1760ec2beb53Sschwarze 		mdoc->flags |= MDOC_SMOFF;
176198b8f00aSschwarze 		return;
1762ec2beb53Sschwarze 	}
176320fa2881Sschwarze 
1764dc0d8bb2Sschwarze 	mandoc_vmsg(MANDOCERR_SM_BAD,
1765dc0d8bb2Sschwarze 	    mdoc->parse, nch->line, nch->pos,
176614a309e3Sschwarze 	    "%s %s", roff_name[mdoc->last->tok], nch->string);
176798b8f00aSschwarze 	mdoc_node_relink(mdoc, nch);
176898b8f00aSschwarze 	return;
176920fa2881Sschwarze }
1770f73abda9Skristaps 
177198b8f00aSschwarze static void
1772f73abda9Skristaps post_root(POST_ARGS)
1773f73abda9Skristaps {
1774*8fd2959dSschwarze 	const char *openbsd_arch[] = {
1775*8fd2959dSschwarze 		"alpha", "amd64", "arm64", "armv7", "hppa", "i386",
1776*8fd2959dSschwarze 		"landisk", "loongson", "luna88k", "macppc", "mips64",
1777*8fd2959dSschwarze 		"octeon", "sgi", "socppc", "sparc64", NULL
1778*8fd2959dSschwarze 	};
1779*8fd2959dSschwarze 	const char *netbsd_arch[] = {
1780*8fd2959dSschwarze 		"acorn26", "acorn32", "algor", "alpha", "amiga",
1781*8fd2959dSschwarze 		"arc", "atari",
1782*8fd2959dSschwarze 		"bebox", "cats", "cesfic", "cobalt", "dreamcast",
1783*8fd2959dSschwarze 		"emips", "evbarm", "evbmips", "evbppc", "evbsh3", "evbsh5",
1784*8fd2959dSschwarze 		"hp300", "hpcarm", "hpcmips", "hpcsh", "hppa",
1785*8fd2959dSschwarze 		"i386", "ibmnws", "luna68k",
1786*8fd2959dSschwarze 		"mac68k", "macppc", "mipsco", "mmeye", "mvme68k", "mvmeppc",
1787*8fd2959dSschwarze 		"netwinder", "news68k", "newsmips", "next68k",
1788*8fd2959dSschwarze 		"pc532", "playstation2", "pmax", "pmppc", "prep",
1789*8fd2959dSschwarze 		"sandpoint", "sbmips", "sgimips", "shark",
1790*8fd2959dSschwarze 		"sparc", "sparc64", "sun2", "sun3",
1791*8fd2959dSschwarze 		"vax", "walnut", "x68k", "x86", "x86_64", "xen", NULL
1792*8fd2959dSschwarze         };
1793*8fd2959dSschwarze 	const char **arches[] = { NULL, netbsd_arch, openbsd_arch };
1794*8fd2959dSschwarze 
17953a0d07afSschwarze 	struct roff_node *n;
1796*8fd2959dSschwarze 	const char **arch;
1797f73abda9Skristaps 
1798ac1f49d0Sschwarze 	/* Add missing prologue data. */
179920fa2881Sschwarze 
1800ac1f49d0Sschwarze 	if (mdoc->meta.date == NULL)
18013427e516Sschwarze 		mdoc->meta.date = mdoc->quick ? mandoc_strdup("") :
18023427e516Sschwarze 		    mandoc_normdate(mdoc, NULL, 0, 0);
18033fdead0cSschwarze 
18043fdead0cSschwarze 	if (mdoc->meta.title == NULL) {
18053fdead0cSschwarze 		mandoc_msg(MANDOCERR_DT_NOTITLE,
18063fdead0cSschwarze 		    mdoc->parse, 0, 0, "EOF");
18073fdead0cSschwarze 		mdoc->meta.title = mandoc_strdup("UNTITLED");
18083fdead0cSschwarze 	}
18093fdead0cSschwarze 
1810ac1f49d0Sschwarze 	if (mdoc->meta.vol == NULL)
1811ac1f49d0Sschwarze 		mdoc->meta.vol = mandoc_strdup("LOCAL");
18123fdead0cSschwarze 
18133fdead0cSschwarze 	if (mdoc->meta.os == NULL) {
18143fdead0cSschwarze 		mandoc_msg(MANDOCERR_OS_MISSING,
18153fdead0cSschwarze 		    mdoc->parse, 0, 0, NULL);
18163fdead0cSschwarze 		mdoc->meta.os = mandoc_strdup("");
1817172864f7Sschwarze 	} else if (mdoc->meta.os_e &&
1818172864f7Sschwarze 	    (mdoc->meta.rcsids & (1 << mdoc->meta.os_e)) == 0)
1819f3476b07Sschwarze 		mandoc_msg(MANDOCERR_RCS_MISSING, mdoc->parse, 0, 0,
1820f3476b07Sschwarze 		    mdoc->meta.os_e == MANDOC_OS_OPENBSD ?
1821f3476b07Sschwarze 		    "(OpenBSD)" : "(NetBSD)");
1822f73abda9Skristaps 
1823*8fd2959dSschwarze 	if (mdoc->meta.arch != NULL &&
1824*8fd2959dSschwarze 	    (arch = arches[mdoc->meta.os_e]) != NULL) {
1825*8fd2959dSschwarze 		while (*arch != NULL && strcmp(*arch, mdoc->meta.arch))
1826*8fd2959dSschwarze 			arch++;
1827*8fd2959dSschwarze 		if (*arch == NULL) {
1828*8fd2959dSschwarze 			n = mdoc->first->child;
1829*8fd2959dSschwarze 			while (n->tok != MDOC_Dt)
1830*8fd2959dSschwarze 				n = n->next;
1831*8fd2959dSschwarze 			n = n->child->next->next;
1832*8fd2959dSschwarze 			mandoc_vmsg(MANDOCERR_ARCH_BAD,
1833*8fd2959dSschwarze 			    mdoc->parse, n->line, n->pos,
1834*8fd2959dSschwarze 			    "Dt ... %s %s", mdoc->meta.arch,
1835*8fd2959dSschwarze 			    mdoc->meta.os_e == MANDOC_OS_OPENBSD ?
1836*8fd2959dSschwarze 			    "(OpenBSD)" : "(NetBSD)");
1837*8fd2959dSschwarze 		}
1838*8fd2959dSschwarze 	}
1839*8fd2959dSschwarze 
184020fa2881Sschwarze 	/* Check that we begin with a proper `Sh'. */
184120fa2881Sschwarze 
1842e20417bdSschwarze 	n = mdoc->first->child;
1843bfef5133Sschwarze 	while (n != NULL && n->tok != TOKEN_NONE &&
1844bfef5133Sschwarze 	    mdoc_macros[n->tok].flags & MDOC_PROLOGUE)
1845e20417bdSschwarze 		n = n->next;
1846e20417bdSschwarze 
1847e20417bdSschwarze 	if (n == NULL)
1848e20417bdSschwarze 		mandoc_msg(MANDOCERR_DOC_EMPTY, mdoc->parse, 0, 0, NULL);
1849e20417bdSschwarze 	else if (n->tok != MDOC_Sh)
185051fcab2fSschwarze 		mandoc_msg(MANDOCERR_SEC_BEFORE, mdoc->parse,
185114a309e3Sschwarze 		    n->line, n->pos, roff_name[n->tok]);
185220fa2881Sschwarze }
1853f73abda9Skristaps 
185498b8f00aSschwarze static void
1855011fe33bSschwarze post_rs(POST_ARGS)
1856011fe33bSschwarze {
18573a0d07afSschwarze 	struct roff_node *np, *nch, *next, *prev;
185820fa2881Sschwarze 	int		  i, j;
1859011fe33bSschwarze 
18606e96429aSschwarze 	np = mdoc->last;
18616e96429aSschwarze 
1862d1982c71Sschwarze 	if (np->type != ROFFT_BODY)
186398b8f00aSschwarze 		return;
18646e96429aSschwarze 
18656e96429aSschwarze 	if (np->child == NULL) {
18666e96429aSschwarze 		mandoc_msg(MANDOCERR_RS_EMPTY, mdoc->parse,
18676e96429aSschwarze 		    np->line, np->pos, "Rs");
186898b8f00aSschwarze 		return;
1869bb648afaSschwarze 	}
1870011fe33bSschwarze 
187120fa2881Sschwarze 	/*
187220fa2881Sschwarze 	 * The full `Rs' block needs special handling to order the
187320fa2881Sschwarze 	 * sub-elements according to `rsord'.  Pick through each element
1874b538baa5Sschwarze 	 * and correctly order it.  This is an insertion sort.
187520fa2881Sschwarze 	 */
187620fa2881Sschwarze 
187720fa2881Sschwarze 	next = NULL;
18786e96429aSschwarze 	for (nch = np->child->next; nch != NULL; nch = next) {
18796e96429aSschwarze 		/* Determine order number of this child. */
188020fa2881Sschwarze 		for (i = 0; i < RSORD_MAX; i++)
18816e96429aSschwarze 			if (rsord[i] == nch->tok)
188220fa2881Sschwarze 				break;
188320fa2881Sschwarze 
1884b538baa5Sschwarze 		if (i == RSORD_MAX) {
188514a309e3Sschwarze 			mandoc_msg(MANDOCERR_RS_BAD, mdoc->parse,
188614a309e3Sschwarze 			    nch->line, nch->pos, roff_name[nch->tok]);
1887b538baa5Sschwarze 			i = -1;
18886e96429aSschwarze 		} else if (nch->tok == MDOC__J || nch->tok == MDOC__B)
18896e96429aSschwarze 			np->norm->Rs.quote_T++;
1890b538baa5Sschwarze 
189120fa2881Sschwarze 		/*
18926e96429aSschwarze 		 * Remove this child from the chain.  This somewhat
1893fa2127f9Sschwarze 		 * repeats roff_node_unlink(), but since we're
189420fa2881Sschwarze 		 * just re-ordering, there's no need for the
189520fa2881Sschwarze 		 * full unlink process.
189620fa2881Sschwarze 		 */
189720fa2881Sschwarze 
18986e96429aSschwarze 		if ((next = nch->next) != NULL)
18996e96429aSschwarze 			next->prev = nch->prev;
190020fa2881Sschwarze 
19016e96429aSschwarze 		if ((prev = nch->prev) != NULL)
19026e96429aSschwarze 			prev->next = nch->next;
190320fa2881Sschwarze 
19046e96429aSschwarze 		nch->prev = nch->next = NULL;
190520fa2881Sschwarze 
190620fa2881Sschwarze 		/*
190720fa2881Sschwarze 		 * Scan back until we reach a node that's
19086e96429aSschwarze 		 * to be ordered before this child.
190920fa2881Sschwarze 		 */
191020fa2881Sschwarze 
191120fa2881Sschwarze 		for ( ; prev ; prev = prev->prev) {
191220fa2881Sschwarze 			/* Determine order of `prev'. */
191320fa2881Sschwarze 			for (j = 0; j < RSORD_MAX; j++)
191420fa2881Sschwarze 				if (rsord[j] == prev->tok)
191520fa2881Sschwarze 					break;
1916b538baa5Sschwarze 			if (j == RSORD_MAX)
1917b538baa5Sschwarze 				j = -1;
191820fa2881Sschwarze 
191920fa2881Sschwarze 			if (j <= i)
192020fa2881Sschwarze 				break;
192120fa2881Sschwarze 		}
192220fa2881Sschwarze 
192320fa2881Sschwarze 		/*
19246e96429aSschwarze 		 * Set this child back into its correct place
19256e96429aSschwarze 		 * in front of the `prev' node.
192620fa2881Sschwarze 		 */
192720fa2881Sschwarze 
19286e96429aSschwarze 		nch->prev = prev;
192920fa2881Sschwarze 
19306e96429aSschwarze 		if (prev == NULL) {
19316e96429aSschwarze 			np->child->prev = nch;
19326e96429aSschwarze 			nch->next = np->child;
19336e96429aSschwarze 			np->child = nch;
193420fa2881Sschwarze 		} else {
19356e96429aSschwarze 			if (prev->next)
19366e96429aSschwarze 				prev->next->prev = nch;
19376e96429aSschwarze 			nch->next = prev->next;
19386e96429aSschwarze 			prev->next = nch;
193920fa2881Sschwarze 		}
1940011fe33bSschwarze 	}
1941011fe33bSschwarze }
1942011fe33bSschwarze 
19434039b21cSschwarze /*
19444039b21cSschwarze  * For some arguments of some macros,
19454039b21cSschwarze  * convert all breakable hyphens into ASCII_HYPH.
19464039b21cSschwarze  */
194798b8f00aSschwarze static void
19484039b21cSschwarze post_hyph(POST_ARGS)
19494039b21cSschwarze {
19503a0d07afSschwarze 	struct roff_node	*nch;
19514039b21cSschwarze 	char			*cp;
19524039b21cSschwarze 
1953b7530f2fSschwarze 	for (nch = mdoc->last->child; nch != NULL; nch = nch->next) {
1954d1982c71Sschwarze 		if (nch->type != ROFFT_TEXT)
19554039b21cSschwarze 			continue;
19564039b21cSschwarze 		cp = nch->string;
1957b7530f2fSschwarze 		if (*cp == '\0')
19584039b21cSschwarze 			continue;
1959b7530f2fSschwarze 		while (*(++cp) != '\0')
1960b7530f2fSschwarze 			if (*cp == '-' &&
19614039b21cSschwarze 			    isalpha((unsigned char)cp[-1]) &&
19624039b21cSschwarze 			    isalpha((unsigned char)cp[1]))
19634039b21cSschwarze 				*cp = ASCII_HYPH;
19644039b21cSschwarze 	}
19654039b21cSschwarze }
19664039b21cSschwarze 
196798b8f00aSschwarze static void
1968af216717Sschwarze post_ns(POST_ARGS)
1969af216717Sschwarze {
1970af216717Sschwarze 
1971c4b0939cSschwarze 	if (mdoc->last->flags & NODE_LINE)
197228153913Sschwarze 		mandoc_msg(MANDOCERR_NS_SKIP, mdoc->parse,
197328153913Sschwarze 		    mdoc->last->line, mdoc->last->pos, NULL);
1974af216717Sschwarze }
1975af216717Sschwarze 
197698b8f00aSschwarze static void
1977f73abda9Skristaps post_sh(POST_ARGS)
1978f73abda9Skristaps {
1979f73abda9Skristaps 
1980753701eeSschwarze 	post_ignpar(mdoc);
1981753701eeSschwarze 
1982cd6c268fSschwarze 	switch (mdoc->last->type) {
1983d1982c71Sschwarze 	case ROFFT_HEAD:
198498b8f00aSschwarze 		post_sh_head(mdoc);
198598b8f00aSschwarze 		break;
1986d1982c71Sschwarze 	case ROFFT_BODY:
1987cd6c268fSschwarze 		switch (mdoc->lastsec)  {
1988cd6c268fSschwarze 		case SEC_NAME:
198998b8f00aSschwarze 			post_sh_name(mdoc);
199098b8f00aSschwarze 			break;
19917c384856Sschwarze 		case SEC_SEE_ALSO:
199298b8f00aSschwarze 			post_sh_see_also(mdoc);
199398b8f00aSschwarze 			break;
1994cd6c268fSschwarze 		case SEC_AUTHORS:
199598b8f00aSschwarze 			post_sh_authors(mdoc);
199698b8f00aSschwarze 			break;
1997cd6c268fSschwarze 		default:
1998cd6c268fSschwarze 			break;
1999cd6c268fSschwarze 		}
2000cd6c268fSschwarze 		break;
2001cd6c268fSschwarze 	default:
2002cd6c268fSschwarze 		break;
2003cd6c268fSschwarze 	}
2004f73abda9Skristaps }
2005f73abda9Skristaps 
200698b8f00aSschwarze static void
2007cd6c268fSschwarze post_sh_name(POST_ARGS)
2008f73abda9Skristaps {
20093a0d07afSschwarze 	struct roff_node *n;
201020e2cf25Sschwarze 	int hasnm, hasnd;
2011f73abda9Skristaps 
201220e2cf25Sschwarze 	hasnm = hasnd = 0;
2013f73abda9Skristaps 
201420e2cf25Sschwarze 	for (n = mdoc->last->child; n != NULL; n = n->next) {
201520e2cf25Sschwarze 		switch (n->tok) {
201620e2cf25Sschwarze 		case MDOC_Nm:
2017f27faaccSschwarze 			if (hasnm && n->child != NULL)
2018f27faaccSschwarze 				mandoc_vmsg(MANDOCERR_NAMESEC_PUNCT,
2019f27faaccSschwarze 				    mdoc->parse, n->line, n->pos,
2020f27faaccSschwarze 				    "Nm %s", n->child->string);
202120e2cf25Sschwarze 			hasnm = 1;
2022f27faaccSschwarze 			continue;
202320e2cf25Sschwarze 		case MDOC_Nd:
202420e2cf25Sschwarze 			hasnd = 1;
202520e2cf25Sschwarze 			if (n->next != NULL)
202620e2cf25Sschwarze 				mandoc_msg(MANDOCERR_NAMESEC_ND,
202720e2cf25Sschwarze 				    mdoc->parse, n->line, n->pos, NULL);
202820e2cf25Sschwarze 			break;
20292d6f95d3Sschwarze 		case TOKEN_NONE:
2030f27faaccSschwarze 			if (n->type == ROFFT_TEXT &&
2031f27faaccSschwarze 			    n->string[0] == ',' && n->string[1] == '\0' &&
2032f27faaccSschwarze 			    n->next != NULL && n->next->tok == MDOC_Nm) {
2033f27faaccSschwarze 				n = n->next;
2034f27faaccSschwarze 				continue;
2035f27faaccSschwarze 			}
2036fa072f7fSschwarze 			/* FALLTHROUGH */
203720e2cf25Sschwarze 		default:
203851fcab2fSschwarze 			mandoc_msg(MANDOCERR_NAMESEC_BAD, mdoc->parse,
203914a309e3Sschwarze 			    n->line, n->pos, roff_name[n->tok]);
2040f27faaccSschwarze 			continue;
204120e2cf25Sschwarze 		}
2042f27faaccSschwarze 		break;
2043f73abda9Skristaps 	}
2044f73abda9Skristaps 
204520e2cf25Sschwarze 	if ( ! hasnm)
204620e2cf25Sschwarze 		mandoc_msg(MANDOCERR_NAMESEC_NONM, mdoc->parse,
204720e2cf25Sschwarze 		    mdoc->last->line, mdoc->last->pos, NULL);
204820e2cf25Sschwarze 	if ( ! hasnd)
204920e2cf25Sschwarze 		mandoc_msg(MANDOCERR_NAMESEC_NOND, mdoc->parse,
205020e2cf25Sschwarze 		    mdoc->last->line, mdoc->last->pos, NULL);
205120fa2881Sschwarze }
2052f73abda9Skristaps 
205398b8f00aSschwarze static void
20547c384856Sschwarze post_sh_see_also(POST_ARGS)
20557c384856Sschwarze {
20563a0d07afSschwarze 	const struct roff_node	*n;
20577c384856Sschwarze 	const char		*name, *sec;
20587c384856Sschwarze 	const char		*lastname, *lastsec, *lastpunct;
20597c384856Sschwarze 	int			 cmp;
20607c384856Sschwarze 
20617c384856Sschwarze 	n = mdoc->last->child;
20627c384856Sschwarze 	lastname = lastsec = lastpunct = NULL;
20637c384856Sschwarze 	while (n != NULL) {
206430e5ee06Sschwarze 		if (n->tok != MDOC_Xr ||
206530e5ee06Sschwarze 		    n->child == NULL ||
206630e5ee06Sschwarze 		    n->child->next == NULL)
20677c384856Sschwarze 			break;
20687c384856Sschwarze 
20697c384856Sschwarze 		/* Process one .Xr node. */
20707c384856Sschwarze 
20717c384856Sschwarze 		name = n->child->string;
20727c384856Sschwarze 		sec = n->child->next->string;
20737c384856Sschwarze 		if (lastsec != NULL) {
20747c384856Sschwarze 			if (lastpunct[0] != ',' || lastpunct[1] != '\0')
20757c384856Sschwarze 				mandoc_vmsg(MANDOCERR_XR_PUNCT,
20767c384856Sschwarze 				    mdoc->parse, n->line, n->pos,
20777c384856Sschwarze 				    "%s before %s(%s)", lastpunct,
20787c384856Sschwarze 				    name, sec);
20797c384856Sschwarze 			cmp = strcmp(lastsec, sec);
20807c384856Sschwarze 			if (cmp > 0)
20817c384856Sschwarze 				mandoc_vmsg(MANDOCERR_XR_ORDER,
20827c384856Sschwarze 				    mdoc->parse, n->line, n->pos,
20837c384856Sschwarze 				    "%s(%s) after %s(%s)", name,
20847c384856Sschwarze 				    sec, lastname, lastsec);
20857c384856Sschwarze 			else if (cmp == 0 &&
20867c384856Sschwarze 			    strcasecmp(lastname, name) > 0)
20877c384856Sschwarze 				mandoc_vmsg(MANDOCERR_XR_ORDER,
20887c384856Sschwarze 				    mdoc->parse, n->line, n->pos,
20897c384856Sschwarze 				    "%s after %s", name, lastname);
20907c384856Sschwarze 		}
20917c384856Sschwarze 		lastname = name;
20927c384856Sschwarze 		lastsec = sec;
20937c384856Sschwarze 
20947c384856Sschwarze 		/* Process the following node. */
20957c384856Sschwarze 
20967c384856Sschwarze 		n = n->next;
20977c384856Sschwarze 		if (n == NULL)
20987c384856Sschwarze 			break;
20997c384856Sschwarze 		if (n->tok == MDOC_Xr) {
21007c384856Sschwarze 			lastpunct = "none";
21017c384856Sschwarze 			continue;
21027c384856Sschwarze 		}
2103d1982c71Sschwarze 		if (n->type != ROFFT_TEXT)
21047c384856Sschwarze 			break;
21057c384856Sschwarze 		for (name = n->string; *name != '\0'; name++)
21067c384856Sschwarze 			if (isalpha((const unsigned char)*name))
210798b8f00aSschwarze 				return;
21087c384856Sschwarze 		lastpunct = n->string;
2109c7098240Sschwarze 		if (n->next == NULL || n->next->tok == MDOC_Rs)
21107c384856Sschwarze 			mandoc_vmsg(MANDOCERR_XR_PUNCT, mdoc->parse,
21117c384856Sschwarze 			    n->line, n->pos, "%s after %s(%s)",
21127c384856Sschwarze 			    lastpunct, lastname, lastsec);
21137c384856Sschwarze 		n = n->next;
21147c384856Sschwarze 	}
21157c384856Sschwarze }
21167c384856Sschwarze 
21177c384856Sschwarze static int
21183a0d07afSschwarze child_an(const struct roff_node *n)
2119cd6c268fSschwarze {
2120cd6c268fSschwarze 
2121cd6c268fSschwarze 	for (n = n->child; n != NULL; n = n->next)
212230e5ee06Sschwarze 		if ((n->tok == MDOC_An && n->child != NULL) || child_an(n))
2123526e306bSschwarze 			return 1;
2124526e306bSschwarze 	return 0;
2125cd6c268fSschwarze }
2126cd6c268fSschwarze 
212798b8f00aSschwarze static void
2128cd6c268fSschwarze post_sh_authors(POST_ARGS)
2129cd6c268fSschwarze {
2130cd6c268fSschwarze 
2131cd6c268fSschwarze 	if ( ! child_an(mdoc->last))
2132cd6c268fSschwarze 		mandoc_msg(MANDOCERR_AN_MISSING, mdoc->parse,
2133cd6c268fSschwarze 		    mdoc->last->line, mdoc->last->pos, NULL);
2134cd6c268fSschwarze }
2135cd6c268fSschwarze 
213698b8f00aSschwarze static void
2137f73abda9Skristaps post_sh_head(POST_ARGS)
2138f73abda9Skristaps {
2139cf0e2075Sschwarze 	struct roff_node	*nch;
214051fcab2fSschwarze 	const char		*goodsec;
21413a0d07afSschwarze 	enum roff_sec		 sec;
2142f73abda9Skristaps 
2143f73abda9Skristaps 	/*
2144f73abda9Skristaps 	 * Process a new section.  Sections are either "named" or
214520fa2881Sschwarze 	 * "custom".  Custom sections are user-defined, while named ones
214620fa2881Sschwarze 	 * follow a conventional order and may only appear in certain
214720fa2881Sschwarze 	 * manual sections.
2148f73abda9Skristaps 	 */
2149f73abda9Skristaps 
2150396853b5Sschwarze 	sec = mdoc->last->sec;
2151f73abda9Skristaps 
215220fa2881Sschwarze 	/* The NAME should be first. */
2153f73abda9Skristaps 
2154d37754b9Sschwarze 	if (sec != SEC_NAME && mdoc->lastnamed == SEC_NONE)
2155bd594191Sschwarze 		mandoc_vmsg(MANDOCERR_NAMESEC_FIRST, mdoc->parse,
2156d37754b9Sschwarze 		    mdoc->last->line, mdoc->last->pos, "Sh %s",
2157cf0e2075Sschwarze 		    sec != SEC_CUSTOM ? secnames[sec] :
2158cf0e2075Sschwarze 		    (nch = mdoc->last->child) == NULL ? "" :
2159cf0e2075Sschwarze 		    nch->type == ROFFT_TEXT ? nch->string :
216014a309e3Sschwarze 		    roff_name[nch->tok]);
216120fa2881Sschwarze 
216220fa2881Sschwarze 	/* The SYNOPSIS gets special attention in other areas. */
216320fa2881Sschwarze 
2164396853b5Sschwarze 	if (sec == SEC_SYNOPSIS) {
216575088a49Sschwarze 		roff_setreg(mdoc->roff, "nS", 1, '=');
216620fa2881Sschwarze 		mdoc->flags |= MDOC_SYNOPSIS;
216722881299Sschwarze 	} else {
216875088a49Sschwarze 		roff_setreg(mdoc->roff, "nS", 0, '=');
216920fa2881Sschwarze 		mdoc->flags &= ~MDOC_SYNOPSIS;
217022881299Sschwarze 	}
217120fa2881Sschwarze 
217220fa2881Sschwarze 	/* Mark our last section. */
217320fa2881Sschwarze 
217420fa2881Sschwarze 	mdoc->lastsec = sec;
21751eccdf28Sschwarze 
217620fa2881Sschwarze 	/* We don't care about custom sections after this. */
2177fccfce9dSschwarze 
2178396853b5Sschwarze 	if (sec == SEC_CUSTOM)
217998b8f00aSschwarze 		return;
2180fccfce9dSschwarze 
21816be99f77Sschwarze 	/*
218220fa2881Sschwarze 	 * Check whether our non-custom section is being repeated or is
218320fa2881Sschwarze 	 * out of order.
21846be99f77Sschwarze 	 */
2185f73abda9Skristaps 
218620fa2881Sschwarze 	if (sec == mdoc->lastnamed)
2187bd594191Sschwarze 		mandoc_vmsg(MANDOCERR_SEC_REP, mdoc->parse,
2188bd594191Sschwarze 		    mdoc->last->line, mdoc->last->pos,
2189396853b5Sschwarze 		    "Sh %s", secnames[sec]);
219020fa2881Sschwarze 
219120fa2881Sschwarze 	if (sec < mdoc->lastnamed)
2192bd594191Sschwarze 		mandoc_vmsg(MANDOCERR_SEC_ORDER, mdoc->parse,
2193bd594191Sschwarze 		    mdoc->last->line, mdoc->last->pos,
2194396853b5Sschwarze 		    "Sh %s", secnames[sec]);
219520fa2881Sschwarze 
219620fa2881Sschwarze 	/* Mark the last named section. */
219720fa2881Sschwarze 
219820fa2881Sschwarze 	mdoc->lastnamed = sec;
219920fa2881Sschwarze 
220020fa2881Sschwarze 	/* Check particular section/manual conventions. */
220120fa2881Sschwarze 
2202396853b5Sschwarze 	if (mdoc->meta.msec == NULL)
220398b8f00aSschwarze 		return;
220420fa2881Sschwarze 
220551fcab2fSschwarze 	goodsec = NULL;
220620fa2881Sschwarze 	switch (sec) {
220749aff9f8Sschwarze 	case SEC_ERRORS:
2208be89e780Sschwarze 		if (*mdoc->meta.msec == '4')
2209be89e780Sschwarze 			break;
221051fcab2fSschwarze 		goodsec = "2, 3, 4, 9";
2211be89e780Sschwarze 		/* FALLTHROUGH */
221249aff9f8Sschwarze 	case SEC_RETURN_VALUES:
221349aff9f8Sschwarze 	case SEC_LIBRARY:
221492c0ca7fSschwarze 		if (*mdoc->meta.msec == '2')
2215f73abda9Skristaps 			break;
221692c0ca7fSschwarze 		if (*mdoc->meta.msec == '3')
221792c0ca7fSschwarze 			break;
221851fcab2fSschwarze 		if (NULL == goodsec)
221951fcab2fSschwarze 			goodsec = "2, 3, 9";
222003ab2f23Sdlg 		/* FALLTHROUGH */
222149aff9f8Sschwarze 	case SEC_CONTEXT:
222292c0ca7fSschwarze 		if (*mdoc->meta.msec == '9')
222392c0ca7fSschwarze 			break;
222451fcab2fSschwarze 		if (NULL == goodsec)
222551fcab2fSschwarze 			goodsec = "9";
222651fcab2fSschwarze 		mandoc_vmsg(MANDOCERR_SEC_MSEC, mdoc->parse,
222751fcab2fSschwarze 		    mdoc->last->line, mdoc->last->pos,
2228396853b5Sschwarze 		    "Sh %s for %s only", secnames[sec], goodsec);
222920fa2881Sschwarze 		break;
2230f73abda9Skristaps 	default:
2231f73abda9Skristaps 		break;
2232f73abda9Skristaps 	}
2233f73abda9Skristaps }
2234d39b9a9cSschwarze 
223598b8f00aSschwarze static void
22365ae08040Sschwarze post_xr(POST_ARGS)
22375ae08040Sschwarze {
22385ae08040Sschwarze 	struct roff_node *n, *nch;
22395ae08040Sschwarze 
22405ae08040Sschwarze 	n = mdoc->last;
22415ae08040Sschwarze 	nch = n->child;
22425ae08040Sschwarze 	if (nch->next == NULL) {
22435ae08040Sschwarze 		mandoc_vmsg(MANDOCERR_XR_NOSEC, mdoc->parse,
22445ae08040Sschwarze 		    n->line, n->pos, "Xr %s", nch->string);
224504fbb99fSschwarze 	} else
22465ae08040Sschwarze 		assert(nch->next == n->last);
224704fbb99fSschwarze 	post_delim(mdoc);
22485ae08040Sschwarze }
22495ae08040Sschwarze 
22505ae08040Sschwarze static void
2251f6127a73Sschwarze post_ignpar(POST_ARGS)
2252f6127a73Sschwarze {
22533a0d07afSschwarze 	struct roff_node *np;
2254f6127a73Sschwarze 
2255b7530f2fSschwarze 	switch (mdoc->last->type) {
22569c7c6a1fSschwarze 	case ROFFT_BLOCK:
22579c7c6a1fSschwarze 		post_prevpar(mdoc);
22589c7c6a1fSschwarze 		return;
2259d1982c71Sschwarze 	case ROFFT_HEAD:
2260753701eeSschwarze 		post_hyph(mdoc);
226198b8f00aSschwarze 		return;
2262d1982c71Sschwarze 	case ROFFT_BODY:
2263b7530f2fSschwarze 		break;
2264b7530f2fSschwarze 	default:
2265b7530f2fSschwarze 		return;
2266b7530f2fSschwarze 	}
2267f6127a73Sschwarze 
2268f051602aSschwarze 	if ((np = mdoc->last->child) != NULL)
2269f051602aSschwarze 		if (np->tok == MDOC_Pp || np->tok == MDOC_Lp) {
227020369664Sschwarze 			mandoc_vmsg(MANDOCERR_PAR_SKIP,
227120369664Sschwarze 			    mdoc->parse, np->line, np->pos,
227214a309e3Sschwarze 			    "%s after %s", roff_name[np->tok],
227314a309e3Sschwarze 			    roff_name[mdoc->last->tok]);
2274fa2127f9Sschwarze 			roff_node_delete(mdoc, np);
2275f6127a73Sschwarze 		}
2276f6127a73Sschwarze 
2277f051602aSschwarze 	if ((np = mdoc->last->last) != NULL)
2278f051602aSschwarze 		if (np->tok == MDOC_Pp || np->tok == MDOC_Lp) {
227920369664Sschwarze 			mandoc_vmsg(MANDOCERR_PAR_SKIP, mdoc->parse,
228020369664Sschwarze 			    np->line, np->pos, "%s at the end of %s",
228114a309e3Sschwarze 			    roff_name[np->tok],
228214a309e3Sschwarze 			    roff_name[mdoc->last->tok]);
2283fa2127f9Sschwarze 			roff_node_delete(mdoc, np);
2284f6127a73Sschwarze 		}
2285f6127a73Sschwarze }
2286f6127a73Sschwarze 
228798b8f00aSschwarze static void
22883e642ba0Sschwarze post_prevpar(POST_ARGS)
2289d39b9a9cSschwarze {
22903e642ba0Sschwarze 	struct roff_node *n;
2291d39b9a9cSschwarze 
22923e642ba0Sschwarze 	n = mdoc->last;
22933e642ba0Sschwarze 	if (NULL == n->prev)
229498b8f00aSschwarze 		return;
2295d1982c71Sschwarze 	if (n->type != ROFFT_ELEM && n->type != ROFFT_BLOCK)
229698b8f00aSschwarze 		return;
2297d39b9a9cSschwarze 
229820fa2881Sschwarze 	/*
229920fa2881Sschwarze 	 * Don't allow prior `Lp' or `Pp' prior to a paragraph-type
230020fa2881Sschwarze 	 * block:  `Lp', `Pp', or non-compact `Bd' or `Bl'.
230120fa2881Sschwarze 	 */
2302d39b9a9cSschwarze 
23033e642ba0Sschwarze 	if (n->prev->tok != MDOC_Pp &&
23043e642ba0Sschwarze 	    n->prev->tok != MDOC_Lp &&
230529478532Sschwarze 	    n->prev->tok != ROFF_br)
230698b8f00aSschwarze 		return;
23073e642ba0Sschwarze 	if (n->tok == MDOC_Bl && n->norm->Bl.comp)
230898b8f00aSschwarze 		return;
23093e642ba0Sschwarze 	if (n->tok == MDOC_Bd && n->norm->Bd.comp)
231098b8f00aSschwarze 		return;
23113e642ba0Sschwarze 	if (n->tok == MDOC_It && n->parent->norm->Bl.comp)
231298b8f00aSschwarze 		return;
2313d39b9a9cSschwarze 
231420369664Sschwarze 	mandoc_vmsg(MANDOCERR_PAR_SKIP, mdoc->parse,
231514a309e3Sschwarze 	    n->prev->line, n->prev->pos, "%s before %s",
231614a309e3Sschwarze 	    roff_name[n->prev->tok], roff_name[n->tok]);
23173e642ba0Sschwarze 	roff_node_delete(mdoc, n->prev);
2318d39b9a9cSschwarze }
231920fa2881Sschwarze 
232098b8f00aSschwarze static void
2321e0dd4c9cSschwarze post_par(POST_ARGS)
2322e0dd4c9cSschwarze {
23233a0d07afSschwarze 	struct roff_node *np;
2324e0dd4c9cSschwarze 
23253798fb25Sschwarze 	np = mdoc->last;
23266561cb23Sschwarze 	if (np->tok != ROFF_br && np->tok != ROFF_sp)
23273e642ba0Sschwarze 		post_prevpar(mdoc);
2328753701eeSschwarze 
23296561cb23Sschwarze 	if (np->tok == ROFF_sp) {
233030e5ee06Sschwarze 		if (np->child != NULL && np->child->next != NULL)
23313798fb25Sschwarze 			mandoc_vmsg(MANDOCERR_ARG_EXCESS, mdoc->parse,
23323798fb25Sschwarze 			    np->child->next->line, np->child->next->pos,
23333798fb25Sschwarze 			    "sp ... %s", np->child->next->string);
23343798fb25Sschwarze 	} else if (np->child != NULL)
23353798fb25Sschwarze 		mandoc_vmsg(MANDOCERR_ARG_SKIP,
23363798fb25Sschwarze 		    mdoc->parse, np->line, np->pos, "%s %s",
233714a309e3Sschwarze 		    roff_name[np->tok], np->child->string);
2338e0dd4c9cSschwarze 
2339f051602aSschwarze 	if ((np = mdoc->last->prev) == NULL) {
234020369664Sschwarze 		np = mdoc->last->parent;
2341f051602aSschwarze 		if (np->tok != MDOC_Sh && np->tok != MDOC_Ss)
234298b8f00aSschwarze 			return;
2343f051602aSschwarze 	} else if (np->tok != MDOC_Pp && np->tok != MDOC_Lp &&
234429478532Sschwarze 	    (mdoc->last->tok != ROFF_br ||
23456561cb23Sschwarze 	     (np->tok != ROFF_sp && np->tok != ROFF_br)))
234698b8f00aSschwarze 		return;
2347e0dd4c9cSschwarze 
234820369664Sschwarze 	mandoc_vmsg(MANDOCERR_PAR_SKIP, mdoc->parse,
234914a309e3Sschwarze 	    mdoc->last->line, mdoc->last->pos, "%s after %s",
235014a309e3Sschwarze 	    roff_name[mdoc->last->tok], roff_name[np->tok]);
2351fa2127f9Sschwarze 	roff_node_delete(mdoc, mdoc->last);
2352e0dd4c9cSschwarze }
2353e0dd4c9cSschwarze 
235498b8f00aSschwarze static void
235520fa2881Sschwarze post_dd(POST_ARGS)
235620fa2881Sschwarze {
23573a0d07afSschwarze 	struct roff_node *n;
235883af2bccSschwarze 	char		 *datestr;
235920fa2881Sschwarze 
2360b058e777Sschwarze 	n = mdoc->last;
236143808411Sschwarze 	n->flags |= NODE_NOPRT;
236243808411Sschwarze 
2363396853b5Sschwarze 	if (mdoc->meta.date != NULL) {
2364396853b5Sschwarze 		mandoc_msg(MANDOCERR_PROLOG_REP, mdoc->parse,
2365396853b5Sschwarze 		    n->line, n->pos, "Dd");
2366396853b5Sschwarze 		free(mdoc->meta.date);
2367396853b5Sschwarze 	} else if (mdoc->flags & MDOC_PBODY)
2368396853b5Sschwarze 		mandoc_msg(MANDOCERR_PROLOG_LATE, mdoc->parse,
2369396853b5Sschwarze 		    n->line, n->pos, "Dd");
2370396853b5Sschwarze 	else if (mdoc->meta.title != NULL)
2371396853b5Sschwarze 		mandoc_msg(MANDOCERR_PROLOG_ORDER, mdoc->parse,
2372396853b5Sschwarze 		    n->line, n->pos, "Dd after Dt");
2373396853b5Sschwarze 	else if (mdoc->meta.os != NULL)
2374396853b5Sschwarze 		mandoc_msg(MANDOCERR_PROLOG_ORDER, mdoc->parse,
2375396853b5Sschwarze 		    n->line, n->pos, "Dd after Os");
2376396853b5Sschwarze 
2377f051602aSschwarze 	if (n->child == NULL || n->child->string[0] == '\0') {
2378231c7061Sschwarze 		mdoc->meta.date = mdoc->quick ? mandoc_strdup("") :
23793427e516Sschwarze 		    mandoc_normdate(mdoc, NULL, n->line, n->pos);
238043808411Sschwarze 		return;
238120fa2881Sschwarze 	}
238220fa2881Sschwarze 
238383af2bccSschwarze 	datestr = NULL;
2384423631c9Sschwarze 	deroff(&datestr, n);
238583af2bccSschwarze 	if (mdoc->quick)
238683af2bccSschwarze 		mdoc->meta.date = datestr;
238783af2bccSschwarze 	else {
23883427e516Sschwarze 		mdoc->meta.date = mandoc_normdate(mdoc,
238983af2bccSschwarze 		    datestr, n->line, n->pos);
239083af2bccSschwarze 		free(datestr);
239104e980cbSschwarze 	}
239220fa2881Sschwarze }
239320fa2881Sschwarze 
239498b8f00aSschwarze static void
239520fa2881Sschwarze post_dt(POST_ARGS)
239620fa2881Sschwarze {
23973a0d07afSschwarze 	struct roff_node *nn, *n;
239820fa2881Sschwarze 	const char	 *cp;
239920fa2881Sschwarze 	char		 *p;
240020fa2881Sschwarze 
240120fa2881Sschwarze 	n = mdoc->last;
240243808411Sschwarze 	n->flags |= NODE_NOPRT;
240343808411Sschwarze 
2404396853b5Sschwarze 	if (mdoc->flags & MDOC_PBODY) {
2405396853b5Sschwarze 		mandoc_msg(MANDOCERR_DT_LATE, mdoc->parse,
2406396853b5Sschwarze 		    n->line, n->pos, "Dt");
240743808411Sschwarze 		return;
2408396853b5Sschwarze 	}
2409396853b5Sschwarze 
2410396853b5Sschwarze 	if (mdoc->meta.title != NULL)
2411396853b5Sschwarze 		mandoc_msg(MANDOCERR_PROLOG_REP, mdoc->parse,
2412396853b5Sschwarze 		    n->line, n->pos, "Dt");
2413396853b5Sschwarze 	else if (mdoc->meta.os != NULL)
2414396853b5Sschwarze 		mandoc_msg(MANDOCERR_PROLOG_ORDER, mdoc->parse,
2415396853b5Sschwarze 		    n->line, n->pos, "Dt after Os");
241620fa2881Sschwarze 
241720fa2881Sschwarze 	free(mdoc->meta.title);
24183fdead0cSschwarze 	free(mdoc->meta.msec);
241920fa2881Sschwarze 	free(mdoc->meta.vol);
242020fa2881Sschwarze 	free(mdoc->meta.arch);
242120fa2881Sschwarze 
24223fdead0cSschwarze 	mdoc->meta.title = NULL;
24233fdead0cSschwarze 	mdoc->meta.msec = NULL;
24243fdead0cSschwarze 	mdoc->meta.vol = NULL;
24253fdead0cSschwarze 	mdoc->meta.arch = NULL;
242620fa2881Sschwarze 
24279a928a59Sschwarze 	/* Mandatory first argument: title. */
242820fa2881Sschwarze 
24299a928a59Sschwarze 	nn = n->child;
24309a928a59Sschwarze 	if (nn == NULL || *nn->string == '\0') {
24319a928a59Sschwarze 		mandoc_msg(MANDOCERR_DT_NOTITLE,
24329a928a59Sschwarze 		    mdoc->parse, n->line, n->pos, "Dt");
24339a928a59Sschwarze 		mdoc->meta.title = mandoc_strdup("UNTITLED");
24349a928a59Sschwarze 	} else {
24359a928a59Sschwarze 		mdoc->meta.title = mandoc_strdup(nn->string);
24369a928a59Sschwarze 
24379a928a59Sschwarze 		/* Check that all characters are uppercase. */
24389a928a59Sschwarze 
24399a928a59Sschwarze 		for (p = nn->string; *p != '\0'; p++)
24409a928a59Sschwarze 			if (islower((unsigned char)*p)) {
2441bd594191Sschwarze 				mandoc_vmsg(MANDOCERR_TITLE_CASE,
244251fcab2fSschwarze 				    mdoc->parse, nn->line,
244351fcab2fSschwarze 				    nn->pos + (p - nn->string),
2444bd594191Sschwarze 				    "Dt %s", nn->string);
244520fa2881Sschwarze 				break;
244620fa2881Sschwarze 			}
244720fa2881Sschwarze 	}
244820fa2881Sschwarze 
24495ae08040Sschwarze 	/* Mandatory second argument: section. */
245020fa2881Sschwarze 
24519a928a59Sschwarze 	if (nn != NULL)
24529a928a59Sschwarze 		nn = nn->next;
245320fa2881Sschwarze 
24549a928a59Sschwarze 	if (nn == NULL) {
24553fdead0cSschwarze 		mandoc_vmsg(MANDOCERR_MSEC_MISSING,
24563fdead0cSschwarze 		    mdoc->parse, n->line, n->pos,
24573fdead0cSschwarze 		    "Dt %s", mdoc->meta.title);
245820fa2881Sschwarze 		mdoc->meta.vol = mandoc_strdup("LOCAL");
245943808411Sschwarze 		return;  /* msec and arch remain NULL. */
246020fa2881Sschwarze 	}
246120fa2881Sschwarze 
24629a928a59Sschwarze 	mdoc->meta.msec = mandoc_strdup(nn->string);
24639a928a59Sschwarze 
24649a928a59Sschwarze 	/* Infer volume title from section number. */
246520fa2881Sschwarze 
246688ec69e3Sschwarze 	cp = mandoc_a2msec(nn->string);
24679a928a59Sschwarze 	if (cp == NULL) {
2468bd594191Sschwarze 		mandoc_vmsg(MANDOCERR_MSEC_BAD, mdoc->parse,
2469bd594191Sschwarze 		    nn->line, nn->pos, "Dt ... %s", nn->string);
247020fa2881Sschwarze 		mdoc->meta.vol = mandoc_strdup(nn->string);
24719a928a59Sschwarze 	} else
24729a928a59Sschwarze 		mdoc->meta.vol = mandoc_strdup(cp);
247320fa2881Sschwarze 
24749a928a59Sschwarze 	/* Optional third argument: architecture. */
247520fa2881Sschwarze 
24769a928a59Sschwarze 	if ((nn = nn->next) == NULL)
247743808411Sschwarze 		return;
24789a928a59Sschwarze 
24799a928a59Sschwarze 	for (p = nn->string; *p != '\0'; p++)
2480b94f27c5Sschwarze 		*p = tolower((unsigned char)*p);
2481b94f27c5Sschwarze 	mdoc->meta.arch = mandoc_strdup(nn->string);
248220fa2881Sschwarze 
24839a928a59Sschwarze 	/* Ignore fourth and later arguments. */
24849a928a59Sschwarze 
24859a928a59Sschwarze 	if ((nn = nn->next) != NULL)
24869a928a59Sschwarze 		mandoc_vmsg(MANDOCERR_ARG_EXCESS, mdoc->parse,
24879a928a59Sschwarze 		    nn->line, nn->pos, "Dt ... %s", nn->string);
248820fa2881Sschwarze }
248920fa2881Sschwarze 
249098b8f00aSschwarze static void
2491992063deSschwarze post_bx(POST_ARGS)
2492992063deSschwarze {
24933af8e8d7Sschwarze 	struct roff_node	*n, *nch;
2494f0c18971Sschwarze 	const char		*macro;
24953af8e8d7Sschwarze 
249604fbb99fSschwarze 	post_delim(mdoc);
249704fbb99fSschwarze 
24983af8e8d7Sschwarze 	n = mdoc->last;
24993af8e8d7Sschwarze 	nch = n->child;
25003af8e8d7Sschwarze 
25013af8e8d7Sschwarze 	if (nch != NULL) {
2502f0c18971Sschwarze 		macro = !strcmp(nch->string, "Open") ? "Ox" :
2503f0c18971Sschwarze 		    !strcmp(nch->string, "Net") ? "Nx" :
2504f0c18971Sschwarze 		    !strcmp(nch->string, "Free") ? "Fx" :
2505f0c18971Sschwarze 		    !strcmp(nch->string, "DragonFly") ? "Dx" : NULL;
2506f0c18971Sschwarze 		if (macro != NULL)
2507f0c18971Sschwarze 			mandoc_msg(MANDOCERR_BX, mdoc->parse,
2508f0c18971Sschwarze 			    n->line, n->pos, macro);
25093af8e8d7Sschwarze 		mdoc->last = nch;
25103af8e8d7Sschwarze 		nch = nch->next;
25113af8e8d7Sschwarze 		mdoc->next = ROFF_NEXT_SIBLING;
25123af8e8d7Sschwarze 		roff_elem_alloc(mdoc, n->line, n->pos, MDOC_Ns);
25133af8e8d7Sschwarze 		mdoc->last->flags |= NODE_NOSRC;
25143af8e8d7Sschwarze 		mdoc->next = ROFF_NEXT_SIBLING;
25153af8e8d7Sschwarze 	} else
25163af8e8d7Sschwarze 		mdoc->next = ROFF_NEXT_CHILD;
25173af8e8d7Sschwarze 	roff_word_alloc(mdoc, n->line, n->pos, "BSD");
25183af8e8d7Sschwarze 	mdoc->last->flags |= NODE_NOSRC;
25193af8e8d7Sschwarze 
25203af8e8d7Sschwarze 	if (nch == NULL) {
25213af8e8d7Sschwarze 		mdoc->last = n;
25223af8e8d7Sschwarze 		return;
25233af8e8d7Sschwarze 	}
25243af8e8d7Sschwarze 
25253af8e8d7Sschwarze 	roff_elem_alloc(mdoc, n->line, n->pos, MDOC_Ns);
25263af8e8d7Sschwarze 	mdoc->last->flags |= NODE_NOSRC;
25273af8e8d7Sschwarze 	mdoc->next = ROFF_NEXT_SIBLING;
25283af8e8d7Sschwarze 	roff_word_alloc(mdoc, n->line, n->pos, "-");
25293af8e8d7Sschwarze 	mdoc->last->flags |= NODE_NOSRC;
25303af8e8d7Sschwarze 	roff_elem_alloc(mdoc, n->line, n->pos, MDOC_Ns);
25313af8e8d7Sschwarze 	mdoc->last->flags |= NODE_NOSRC;
25323af8e8d7Sschwarze 	mdoc->last = n;
2533992063deSschwarze 
2534992063deSschwarze 	/*
2535992063deSschwarze 	 * Make `Bx's second argument always start with an uppercase
2536992063deSschwarze 	 * letter.  Groff checks if it's an "accepted" term, but we just
2537992063deSschwarze 	 * uppercase blindly.
2538992063deSschwarze 	 */
2539992063deSschwarze 
25403af8e8d7Sschwarze 	*nch->string = (char)toupper((unsigned char)*nch->string);
2541992063deSschwarze }
2542992063deSschwarze 
254398b8f00aSschwarze static void
254420fa2881Sschwarze post_os(POST_ARGS)
254520fa2881Sschwarze {
254620fa2881Sschwarze #ifndef OSNAME
254720fa2881Sschwarze 	struct utsname	  utsname;
25484c468128Sschwarze 	static char	 *defbuf;
254920fa2881Sschwarze #endif
25503a0d07afSschwarze 	struct roff_node *n;
255120fa2881Sschwarze 
255220fa2881Sschwarze 	n = mdoc->last;
255343808411Sschwarze 	n->flags |= NODE_NOPRT;
255443808411Sschwarze 
2555396853b5Sschwarze 	if (mdoc->meta.os != NULL)
2556396853b5Sschwarze 		mandoc_msg(MANDOCERR_PROLOG_REP, mdoc->parse,
2557396853b5Sschwarze 		    n->line, n->pos, "Os");
2558396853b5Sschwarze 	else if (mdoc->flags & MDOC_PBODY)
2559396853b5Sschwarze 		mandoc_msg(MANDOCERR_PROLOG_LATE, mdoc->parse,
2560396853b5Sschwarze 		    n->line, n->pos, "Os");
256120fa2881Sschwarze 
256220fa2881Sschwarze 	/*
2563353fa9ecSschwarze 	 * Set the operating system by way of the `Os' macro.
2564353fa9ecSschwarze 	 * The order of precedence is:
2565353fa9ecSschwarze 	 * 1. the argument of the `Os' macro, unless empty
2566353fa9ecSschwarze 	 * 2. the -Ios=foo command line argument, if provided
2567353fa9ecSschwarze 	 * 3. -DOSNAME="\"foo\"", if provided during compilation
2568353fa9ecSschwarze 	 * 4. "sysname release" from uname(3)
256920fa2881Sschwarze 	 */
257020fa2881Sschwarze 
257120fa2881Sschwarze 	free(mdoc->meta.os);
257283af2bccSschwarze 	mdoc->meta.os = NULL;
2573423631c9Sschwarze 	deroff(&mdoc->meta.os, n);
257483af2bccSschwarze 	if (mdoc->meta.os)
2575ce0ef847Sschwarze 		goto out;
25764c468128Sschwarze 
2577f3476b07Sschwarze 	if (mdoc->os_s != NULL) {
2578f3476b07Sschwarze 		mdoc->meta.os = mandoc_strdup(mdoc->os_s);
2579ce0ef847Sschwarze 		goto out;
2580353fa9ecSschwarze 	}
25814c468128Sschwarze 
258220fa2881Sschwarze #ifdef OSNAME
25834c468128Sschwarze 	mdoc->meta.os = mandoc_strdup(OSNAME);
258420fa2881Sschwarze #else /*!OSNAME */
2585f051602aSschwarze 	if (defbuf == NULL) {
2586f051602aSschwarze 		if (uname(&utsname) == -1) {
2587f79e7afeSschwarze 			mandoc_msg(MANDOCERR_OS_UNAME, mdoc->parse,
2588f79e7afeSschwarze 			    n->line, n->pos, "Os");
25894c468128Sschwarze 			defbuf = mandoc_strdup("UNKNOWN");
2590a450f7c4Sschwarze 		} else
2591a450f7c4Sschwarze 			mandoc_asprintf(&defbuf, "%s %s",
2592a450f7c4Sschwarze 			    utsname.sysname, utsname.release);
259320fa2881Sschwarze 	}
25944c468128Sschwarze 	mdoc->meta.os = mandoc_strdup(defbuf);
259520fa2881Sschwarze #endif /*!OSNAME*/
2596ce0ef847Sschwarze 
2597f3476b07Sschwarze out:
2598f3476b07Sschwarze 	if (mdoc->meta.os_e == MANDOC_OS_OTHER) {
2599f3476b07Sschwarze 		if (strstr(mdoc->meta.os, "OpenBSD") != NULL)
2600f3476b07Sschwarze 			mdoc->meta.os_e = MANDOC_OS_OPENBSD;
2601f3476b07Sschwarze 		else if (strstr(mdoc->meta.os, "NetBSD") != NULL)
2602f3476b07Sschwarze 			mdoc->meta.os_e = MANDOC_OS_NETBSD;
2603f3476b07Sschwarze 	}
26043427e516Sschwarze 
26053427e516Sschwarze 	/*
26063427e516Sschwarze 	 * This is the earliest point where we can check
26073427e516Sschwarze 	 * Mdocdate conventions because we don't know
26083427e516Sschwarze 	 * the operating system earlier.
26093427e516Sschwarze 	 */
26103427e516Sschwarze 
26112f84042eSschwarze 	if (n->child != NULL)
26122f84042eSschwarze 		mandoc_vmsg(MANDOCERR_OS_ARG, mdoc->parse,
26132f84042eSschwarze 		    n->child->line, n->child->pos,
26142f84042eSschwarze 		    "Os %s (%s)", n->child->string,
26152f84042eSschwarze 		    mdoc->meta.os_e == MANDOC_OS_OPENBSD ?
26162f84042eSschwarze 		    "OpenBSD" : "NetBSD");
26172f84042eSschwarze 
26183427e516Sschwarze 	while (n->tok != MDOC_Dd)
26193427e516Sschwarze 		if ((n = n->prev) == NULL)
26203427e516Sschwarze 			return;
26213427e516Sschwarze 	if ((n = n->child) == NULL)
26223427e516Sschwarze 		return;
262307ec32d0Sschwarze 	if (strncmp(n->string, "$" "Mdocdate", 9)) {
2624f3476b07Sschwarze 		if (mdoc->meta.os_e == MANDOC_OS_OPENBSD)
26253427e516Sschwarze 			mandoc_vmsg(MANDOCERR_MDOCDATE_MISSING,
26263427e516Sschwarze 			    mdoc->parse, n->line, n->pos,
2627f3476b07Sschwarze 			    "Dd %s (OpenBSD)", n->string);
26283427e516Sschwarze 	} else {
2629f3476b07Sschwarze 		if (mdoc->meta.os_e == MANDOC_OS_NETBSD)
26303427e516Sschwarze 			mandoc_vmsg(MANDOCERR_MDOCDATE,
26313427e516Sschwarze 			    mdoc->parse, n->line, n->pos,
2632f3476b07Sschwarze 			    "Dd %s (NetBSD)", n->string);
26333427e516Sschwarze 	}
263420fa2881Sschwarze }
263520fa2881Sschwarze 
2636396853b5Sschwarze enum roff_sec
2637396853b5Sschwarze mdoc_a2sec(const char *p)
263819a69263Sschwarze {
263919a69263Sschwarze 	int		 i;
264019a69263Sschwarze 
264119a69263Sschwarze 	for (i = 0; i < (int)SEC__MAX; i++)
264219a69263Sschwarze 		if (secnames[i] && 0 == strcmp(p, secnames[i]))
2643526e306bSschwarze 			return (enum roff_sec)i;
264419a69263Sschwarze 
2645526e306bSschwarze 	return SEC_CUSTOM;
264619a69263Sschwarze }
264719a69263Sschwarze 
264819a69263Sschwarze static size_t
264914a309e3Sschwarze macro2len(enum roff_tok macro)
265019a69263Sschwarze {
265119a69263Sschwarze 
265219a69263Sschwarze 	switch (macro) {
265349aff9f8Sschwarze 	case MDOC_Ad:
2654526e306bSschwarze 		return 12;
265549aff9f8Sschwarze 	case MDOC_Ao:
2656526e306bSschwarze 		return 12;
265749aff9f8Sschwarze 	case MDOC_An:
2658526e306bSschwarze 		return 12;
265949aff9f8Sschwarze 	case MDOC_Aq:
2660526e306bSschwarze 		return 12;
266149aff9f8Sschwarze 	case MDOC_Ar:
2662526e306bSschwarze 		return 12;
266349aff9f8Sschwarze 	case MDOC_Bo:
2664526e306bSschwarze 		return 12;
266549aff9f8Sschwarze 	case MDOC_Bq:
2666526e306bSschwarze 		return 12;
266749aff9f8Sschwarze 	case MDOC_Cd:
2668526e306bSschwarze 		return 12;
266949aff9f8Sschwarze 	case MDOC_Cm:
2670526e306bSschwarze 		return 10;
267149aff9f8Sschwarze 	case MDOC_Do:
2672526e306bSschwarze 		return 10;
267349aff9f8Sschwarze 	case MDOC_Dq:
2674526e306bSschwarze 		return 12;
267549aff9f8Sschwarze 	case MDOC_Dv:
2676526e306bSschwarze 		return 12;
267749aff9f8Sschwarze 	case MDOC_Eo:
2678526e306bSschwarze 		return 12;
267949aff9f8Sschwarze 	case MDOC_Em:
2680526e306bSschwarze 		return 10;
268149aff9f8Sschwarze 	case MDOC_Er:
2682526e306bSschwarze 		return 17;
268349aff9f8Sschwarze 	case MDOC_Ev:
2684526e306bSschwarze 		return 15;
268549aff9f8Sschwarze 	case MDOC_Fa:
2686526e306bSschwarze 		return 12;
268749aff9f8Sschwarze 	case MDOC_Fl:
2688526e306bSschwarze 		return 10;
268949aff9f8Sschwarze 	case MDOC_Fo:
2690526e306bSschwarze 		return 16;
269149aff9f8Sschwarze 	case MDOC_Fn:
2692526e306bSschwarze 		return 16;
269349aff9f8Sschwarze 	case MDOC_Ic:
2694526e306bSschwarze 		return 10;
269549aff9f8Sschwarze 	case MDOC_Li:
2696526e306bSschwarze 		return 16;
269749aff9f8Sschwarze 	case MDOC_Ms:
2698526e306bSschwarze 		return 6;
269949aff9f8Sschwarze 	case MDOC_Nm:
2700526e306bSschwarze 		return 10;
270149aff9f8Sschwarze 	case MDOC_No:
2702526e306bSschwarze 		return 12;
270349aff9f8Sschwarze 	case MDOC_Oo:
2704526e306bSschwarze 		return 10;
270549aff9f8Sschwarze 	case MDOC_Op:
2706526e306bSschwarze 		return 14;
270749aff9f8Sschwarze 	case MDOC_Pa:
2708526e306bSschwarze 		return 32;
270949aff9f8Sschwarze 	case MDOC_Pf:
2710526e306bSschwarze 		return 12;
271149aff9f8Sschwarze 	case MDOC_Po:
2712526e306bSschwarze 		return 12;
271349aff9f8Sschwarze 	case MDOC_Pq:
2714526e306bSschwarze 		return 12;
271549aff9f8Sschwarze 	case MDOC_Ql:
2716526e306bSschwarze 		return 16;
271749aff9f8Sschwarze 	case MDOC_Qo:
2718526e306bSschwarze 		return 12;
271949aff9f8Sschwarze 	case MDOC_So:
2720526e306bSschwarze 		return 12;
272149aff9f8Sschwarze 	case MDOC_Sq:
2722526e306bSschwarze 		return 12;
272349aff9f8Sschwarze 	case MDOC_Sy:
2724526e306bSschwarze 		return 6;
272549aff9f8Sschwarze 	case MDOC_Sx:
2726526e306bSschwarze 		return 16;
272749aff9f8Sschwarze 	case MDOC_Tn:
2728526e306bSschwarze 		return 10;
272949aff9f8Sschwarze 	case MDOC_Va:
2730526e306bSschwarze 		return 12;
273149aff9f8Sschwarze 	case MDOC_Vt:
2732526e306bSschwarze 		return 12;
273349aff9f8Sschwarze 	case MDOC_Xr:
2734526e306bSschwarze 		return 10;
273519a69263Sschwarze 	default:
273619a69263Sschwarze 		break;
273719a69263Sschwarze 	};
2738526e306bSschwarze 	return 0;
273919a69263Sschwarze }
2740