xref: /openbsd/usr.bin/mandoc/mdoc_validate.c (revision 479c151d)
1*479c151dSjsg /* $OpenBSD: mdoc_validate.c,v 1.307 2024/09/20 02:00:46 jsg Exp $ */
2f73abda9Skristaps /*
38055da74Sschwarze  * Copyright (c) 2010-2021 Ingo Schwarze <schwarze@openbsd.org>
40ac7e6ecSschwarze  * Copyright (c) 2008-2012 Kristaps Dzonsons <kristaps@bsd.lv>
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.
180ac7e6ecSschwarze  *
190ac7e6ecSschwarze  * Validation module for mdoc(7) syntax trees used by mandoc(1).
20f73abda9Skristaps  */
21dadc3a61Sschwarze #include <sys/types.h>
2220fa2881Sschwarze #ifndef OSNAME
2320fa2881Sschwarze #include <sys/utsname.h>
2420fa2881Sschwarze #endif
2520fa2881Sschwarze 
26f73abda9Skristaps #include <assert.h>
27f73abda9Skristaps #include <ctype.h>
28d92dc4efSschwarze #include <limits.h>
293216dddfSschwarze #include <stdio.h>
30f73abda9Skristaps #include <stdlib.h>
31f73abda9Skristaps #include <string.h>
3220fa2881Sschwarze #include <time.h>
33f73abda9Skristaps 
344f4f7972Sschwarze #include "mandoc_aux.h"
35d1982c71Sschwarze #include "mandoc.h"
3619b6bef7Sschwarze #include "mandoc_xr.h"
37d1982c71Sschwarze #include "roff.h"
38d1982c71Sschwarze #include "mdoc.h"
39f6854d5cSschwarze #include "libmandoc.h"
40fa2127f9Sschwarze #include "roff_int.h"
41d1982c71Sschwarze #include "libmdoc.h"
426e2a0df9Sschwarze #include "tag.h"
43f73abda9Skristaps 
44f73abda9Skristaps /* FIXME: .Bl -diag can't have non-text children in HEAD. */
45f73abda9Skristaps 
46ede1b9d0Sschwarze #define	POST_ARGS struct roff_man *mdoc
47f73abda9Skristaps 
487c2be9f8Sschwarze enum	check_ineq {
497c2be9f8Sschwarze 	CHECK_LT,
507c2be9f8Sschwarze 	CHECK_GT,
517c2be9f8Sschwarze 	CHECK_EQ
527c2be9f8Sschwarze };
537c2be9f8Sschwarze 
5498b8f00aSschwarze typedef	void	(*v_post)(POST_ARGS);
55f73abda9Skristaps 
568ccddcd3Sschwarze static	int	 build_list(struct roff_man *, int);
57ede1b9d0Sschwarze static	void	 check_argv(struct roff_man *,
583a0d07afSschwarze 			struct roff_node *, struct mdoc_argv *);
59ede1b9d0Sschwarze static	void	 check_args(struct roff_man *, struct roff_node *);
606dc98fe5Sschwarze static	void	 check_text(struct roff_man *, int, int, char *);
616dc98fe5Sschwarze static	void	 check_text_em(struct roff_man *, int, int, char *);
6248497dd5Sschwarze static	void	 check_toptext(struct roff_man *, int, int, const char *);
633a0d07afSschwarze static	int	 child_an(const struct roff_node *);
6414a309e3Sschwarze static	size_t		macro2len(enum roff_tok);
656050a3daSschwarze static	void	 rewrite_macro2len(struct roff_man *, char **);
664482121fSschwarze static	int	 similar(const char *, const char *);
6767c719adSschwarze 
6836dc2246Sschwarze static	void	 post_abort(POST_ARGS) __attribute__((__noreturn__));
6998b8f00aSschwarze static	void	 post_an(POST_ARGS);
703e642ba0Sschwarze static	void	 post_an_norm(POST_ARGS);
7198b8f00aSschwarze static	void	 post_at(POST_ARGS);
723e642ba0Sschwarze static	void	 post_bd(POST_ARGS);
7398b8f00aSschwarze static	void	 post_bf(POST_ARGS);
7498b8f00aSschwarze static	void	 post_bk(POST_ARGS);
7598b8f00aSschwarze static	void	 post_bl(POST_ARGS);
7698b8f00aSschwarze static	void	 post_bl_block(POST_ARGS);
7798b8f00aSschwarze static	void	 post_bl_head(POST_ARGS);
783e642ba0Sschwarze static	void	 post_bl_norm(POST_ARGS);
7998b8f00aSschwarze static	void	 post_bx(POST_ARGS);
8098b8f00aSschwarze static	void	 post_defaults(POST_ARGS);
813e642ba0Sschwarze static	void	 post_display(POST_ARGS);
8298b8f00aSschwarze static	void	 post_dd(POST_ARGS);
8304fbb99fSschwarze static	void	 post_delim(POST_ARGS);
84fe8e59edSschwarze static	void	 post_delim_nb(POST_ARGS);
8598b8f00aSschwarze static	void	 post_dt(POST_ARGS);
860ac7e6ecSschwarze static	void	 post_em(POST_ARGS);
8798b8f00aSschwarze static	void	 post_en(POST_ARGS);
880ac7e6ecSschwarze static	void	 post_er(POST_ARGS);
8998b8f00aSschwarze static	void	 post_es(POST_ARGS);
9098b8f00aSschwarze static	void	 post_eoln(POST_ARGS);
9198b8f00aSschwarze static	void	 post_ex(POST_ARGS);
9298b8f00aSschwarze static	void	 post_fa(POST_ARGS);
93b952e091Sschwarze static	void	 post_fl(POST_ARGS);
9498b8f00aSschwarze static	void	 post_fn(POST_ARGS);
9598b8f00aSschwarze static	void	 post_fname(POST_ARGS);
9698b8f00aSschwarze static	void	 post_fo(POST_ARGS);
9798b8f00aSschwarze static	void	 post_hyph(POST_ARGS);
9898b8f00aSschwarze static	void	 post_it(POST_ARGS);
9998b8f00aSschwarze static	void	 post_lb(POST_ARGS);
10098b8f00aSschwarze static	void	 post_nd(POST_ARGS);
10198b8f00aSschwarze static	void	 post_nm(POST_ARGS);
10298b8f00aSschwarze static	void	 post_ns(POST_ARGS);
1033e642ba0Sschwarze static	void	 post_obsolete(POST_ARGS);
10498b8f00aSschwarze static	void	 post_os(POST_ARGS);
10598b8f00aSschwarze static	void	 post_par(POST_ARGS);
1063e642ba0Sschwarze static	void	 post_prevpar(POST_ARGS);
10798b8f00aSschwarze static	void	 post_root(POST_ARGS);
10898b8f00aSschwarze static	void	 post_rs(POST_ARGS);
1098ccddcd3Sschwarze static	void	 post_rv(POST_ARGS);
110ba1a6076Sschwarze static	void	 post_section(POST_ARGS);
11198b8f00aSschwarze static	void	 post_sh(POST_ARGS);
11298b8f00aSschwarze static	void	 post_sh_head(POST_ARGS);
11398b8f00aSschwarze static	void	 post_sh_name(POST_ARGS);
11498b8f00aSschwarze static	void	 post_sh_see_also(POST_ARGS);
11598b8f00aSschwarze static	void	 post_sh_authors(POST_ARGS);
11698b8f00aSschwarze static	void	 post_sm(POST_ARGS);
11798b8f00aSschwarze static	void	 post_st(POST_ARGS);
1183e642ba0Sschwarze static	void	 post_std(POST_ARGS);
119fe8e59edSschwarze static	void	 post_sx(POST_ARGS);
1200ac7e6ecSschwarze static	void	 post_tag(POST_ARGS);
12192929bf6Sschwarze static	void	 post_tg(POST_ARGS);
122bc205043Sschwarze static	void	 post_useless(POST_ARGS);
1235ae08040Sschwarze static	void	 post_xr(POST_ARGS);
124816c3c54Sschwarze static	void	 post_xx(POST_ARGS);
12598b8f00aSschwarze 
126b3f54129Sschwarze static	const v_post mdoc_valids[MDOC_MAX - MDOC_Dd] = {
1273e642ba0Sschwarze 	post_dd,	/* Dd */
1283e642ba0Sschwarze 	post_dt,	/* Dt */
1293e642ba0Sschwarze 	post_os,	/* Os */
1303e642ba0Sschwarze 	post_sh,	/* Sh */
131ba1a6076Sschwarze 	post_section,	/* Ss */
1323e642ba0Sschwarze 	post_par,	/* Pp */
1333e642ba0Sschwarze 	post_display,	/* D1 */
1343e642ba0Sschwarze 	post_display,	/* Dl */
1353e642ba0Sschwarze 	post_display,	/* Bd */
1363e642ba0Sschwarze 	NULL,		/* Ed */
1373e642ba0Sschwarze 	post_bl,	/* Bl */
1383e642ba0Sschwarze 	NULL,		/* El */
1393e642ba0Sschwarze 	post_it,	/* It */
140fe8e59edSschwarze 	post_delim_nb,	/* Ad */
1413e642ba0Sschwarze 	post_an,	/* An */
14214a309e3Sschwarze 	NULL,		/* Ap */
1433e642ba0Sschwarze 	post_defaults,	/* Ar */
1443e642ba0Sschwarze 	NULL,		/* Cd */
1450ac7e6ecSschwarze 	post_tag,	/* Cm */
1460ac7e6ecSschwarze 	post_tag,	/* Dv */
1470ac7e6ecSschwarze 	post_er,	/* Er */
1480ac7e6ecSschwarze 	post_tag,	/* Ev */
1493e642ba0Sschwarze 	post_ex,	/* Ex */
1503e642ba0Sschwarze 	post_fa,	/* Fa */
1513e642ba0Sschwarze 	NULL,		/* Fd */
152b952e091Sschwarze 	post_fl,	/* Fl */
1533e642ba0Sschwarze 	post_fn,	/* Fn */
154fe8e59edSschwarze 	post_delim_nb,	/* Ft */
1550ac7e6ecSschwarze 	post_tag,	/* Ic */
156fe8e59edSschwarze 	post_delim_nb,	/* In */
1570ac7e6ecSschwarze 	post_tag,	/* Li */
1583e642ba0Sschwarze 	post_nd,	/* Nd */
1593e642ba0Sschwarze 	post_nm,	/* Nm */
160fe8e59edSschwarze 	post_delim_nb,	/* Op */
1617c539ecbSschwarze 	post_abort,	/* Ot */
1623e642ba0Sschwarze 	post_defaults,	/* Pa */
1638ccddcd3Sschwarze 	post_rv,	/* Rv */
1643e642ba0Sschwarze 	post_st,	/* St */
165e0c064b2Sschwarze 	post_tag,	/* Va */
166fe8e59edSschwarze 	post_delim_nb,	/* Vt */
1675ae08040Sschwarze 	post_xr,	/* Xr */
1683e642ba0Sschwarze 	NULL,		/* %A */
1693e642ba0Sschwarze 	post_hyph,	/* %B */ /* FIXME: can be used outside Rs/Re. */
1703e642ba0Sschwarze 	NULL,		/* %D */
1713e642ba0Sschwarze 	NULL,		/* %I */
1723e642ba0Sschwarze 	NULL,		/* %J */
1733e642ba0Sschwarze 	post_hyph,	/* %N */
1743e642ba0Sschwarze 	post_hyph,	/* %O */
1753e642ba0Sschwarze 	NULL,		/* %P */
1763e642ba0Sschwarze 	post_hyph,	/* %R */
1773e642ba0Sschwarze 	post_hyph,	/* %T */ /* FIXME: can be used outside Rs/Re. */
1783e642ba0Sschwarze 	NULL,		/* %V */
1793e642ba0Sschwarze 	NULL,		/* Ac */
1801abead14Sschwarze 	NULL,		/* Ao */
181fe8e59edSschwarze 	post_delim_nb,	/* Aq */
1823e642ba0Sschwarze 	post_at,	/* At */
1833e642ba0Sschwarze 	NULL,		/* Bc */
1843e642ba0Sschwarze 	post_bf,	/* Bf */
1851abead14Sschwarze 	NULL,		/* Bo */
1863e642ba0Sschwarze 	NULL,		/* Bq */
187816c3c54Sschwarze 	post_xx,	/* Bsx */
1883e642ba0Sschwarze 	post_bx,	/* Bx */
1893e642ba0Sschwarze 	post_obsolete,	/* Db */
1903e642ba0Sschwarze 	NULL,		/* Dc */
1913e642ba0Sschwarze 	NULL,		/* Do */
1923e642ba0Sschwarze 	NULL,		/* Dq */
1933e642ba0Sschwarze 	NULL,		/* Ec */
1943e642ba0Sschwarze 	NULL,		/* Ef */
1950ac7e6ecSschwarze 	post_em,	/* Em */
1963e642ba0Sschwarze 	NULL,		/* Eo */
197816c3c54Sschwarze 	post_xx,	/* Fx */
1980ac7e6ecSschwarze 	post_tag,	/* Ms */
1990ac7e6ecSschwarze 	post_tag,	/* No */
2003e642ba0Sschwarze 	post_ns,	/* Ns */
201816c3c54Sschwarze 	post_xx,	/* Nx */
202816c3c54Sschwarze 	post_xx,	/* Ox */
2033e642ba0Sschwarze 	NULL,		/* Pc */
2043e642ba0Sschwarze 	NULL,		/* Pf */
2051abead14Sschwarze 	NULL,		/* Po */
206fe8e59edSschwarze 	post_delim_nb,	/* Pq */
2073e642ba0Sschwarze 	NULL,		/* Qc */
208fe8e59edSschwarze 	post_delim_nb,	/* Ql */
2091abead14Sschwarze 	NULL,		/* Qo */
210fe8e59edSschwarze 	post_delim_nb,	/* Qq */
2113e642ba0Sschwarze 	NULL,		/* Re */
2123e642ba0Sschwarze 	post_rs,	/* Rs */
2133e642ba0Sschwarze 	NULL,		/* Sc */
2141abead14Sschwarze 	NULL,		/* So */
215fe8e59edSschwarze 	post_delim_nb,	/* Sq */
2163e642ba0Sschwarze 	post_sm,	/* Sm */
217fe8e59edSschwarze 	post_sx,	/* Sx */
2180ac7e6ecSschwarze 	post_em,	/* Sy */
219bc205043Sschwarze 	post_useless,	/* Tn */
220816c3c54Sschwarze 	post_xx,	/* Ux */
2213e642ba0Sschwarze 	NULL,		/* Xc */
2223e642ba0Sschwarze 	NULL,		/* Xo */
2233e642ba0Sschwarze 	post_fo,	/* Fo */
2243e642ba0Sschwarze 	NULL,		/* Fc */
2251abead14Sschwarze 	NULL,		/* Oo */
2263e642ba0Sschwarze 	NULL,		/* Oc */
2273e642ba0Sschwarze 	post_bk,	/* Bk */
2283e642ba0Sschwarze 	NULL,		/* Ek */
2293e642ba0Sschwarze 	post_eoln,	/* Bt */
23004fbb99fSschwarze 	post_obsolete,	/* Hf */
2313e642ba0Sschwarze 	post_obsolete,	/* Fr */
2323e642ba0Sschwarze 	post_eoln,	/* Ud */
2333e642ba0Sschwarze 	post_lb,	/* Lb */
2347c539ecbSschwarze 	post_abort,	/* Lp */
235fe8e59edSschwarze 	post_delim_nb,	/* Lk */
2363e642ba0Sschwarze 	post_defaults,	/* Mt */
237fe8e59edSschwarze 	post_delim_nb,	/* Brq */
2381abead14Sschwarze 	NULL,		/* Bro */
2393e642ba0Sschwarze 	NULL,		/* Brc */
2403e642ba0Sschwarze 	NULL,		/* %C */
2413e642ba0Sschwarze 	post_es,	/* Es */
2423e642ba0Sschwarze 	post_en,	/* En */
243816c3c54Sschwarze 	post_xx,	/* Dx */
2443e642ba0Sschwarze 	NULL,		/* %Q */
2453e642ba0Sschwarze 	NULL,		/* %U */
2463e642ba0Sschwarze 	NULL,		/* Ta */
24792929bf6Sschwarze 	post_tg,	/* Tg */
248f73abda9Skristaps };
249f73abda9Skristaps 
25020fa2881Sschwarze #define	RSORD_MAX 14 /* Number of `Rs' blocks. */
25120fa2881Sschwarze 
25214a309e3Sschwarze static	const enum roff_tok rsord[RSORD_MAX] = {
25320fa2881Sschwarze 	MDOC__A,
25420fa2881Sschwarze 	MDOC__T,
25520fa2881Sschwarze 	MDOC__B,
25620fa2881Sschwarze 	MDOC__I,
25720fa2881Sschwarze 	MDOC__J,
25820fa2881Sschwarze 	MDOC__R,
25920fa2881Sschwarze 	MDOC__N,
26020fa2881Sschwarze 	MDOC__V,
2610397c682Sschwarze 	MDOC__U,
26220fa2881Sschwarze 	MDOC__P,
26320fa2881Sschwarze 	MDOC__Q,
2644e32ec8fSschwarze 	MDOC__C,
26520fa2881Sschwarze 	MDOC__D,
2664e32ec8fSschwarze 	MDOC__O
26720fa2881Sschwarze };
26820fa2881Sschwarze 
26919a69263Sschwarze static	const char * const secnames[SEC__MAX] = {
27019a69263Sschwarze 	NULL,
27119a69263Sschwarze 	"NAME",
27219a69263Sschwarze 	"LIBRARY",
27319a69263Sschwarze 	"SYNOPSIS",
27419a69263Sschwarze 	"DESCRIPTION",
27503ab2f23Sdlg 	"CONTEXT",
27619a69263Sschwarze 	"IMPLEMENTATION NOTES",
27719a69263Sschwarze 	"RETURN VALUES",
27819a69263Sschwarze 	"ENVIRONMENT",
27919a69263Sschwarze 	"FILES",
28019a69263Sschwarze 	"EXIT STATUS",
28119a69263Sschwarze 	"EXAMPLES",
28219a69263Sschwarze 	"DIAGNOSTICS",
28319a69263Sschwarze 	"COMPATIBILITY",
28419a69263Sschwarze 	"ERRORS",
28519a69263Sschwarze 	"SEE ALSO",
28619a69263Sschwarze 	"STANDARDS",
28719a69263Sschwarze 	"HISTORY",
28819a69263Sschwarze 	"AUTHORS",
28919a69263Sschwarze 	"CAVEATS",
29019a69263Sschwarze 	"BUGS",
29119a69263Sschwarze 	"SECURITY CONSIDERATIONS",
29219a69263Sschwarze 	NULL
29319a69263Sschwarze };
294f73abda9Skristaps 
2950ac7e6ecSschwarze static	int	  fn_prio = TAG_STRONG;
2960ac7e6ecSschwarze 
29749aff9f8Sschwarze 
2987c539ecbSschwarze /* Validate the subtree rooted at mdoc->last. */
29998b8f00aSschwarze void
mdoc_validate(struct roff_man * mdoc)30083d65a5aSschwarze mdoc_validate(struct roff_man *mdoc)
301f73abda9Skristaps {
3026dc98fe5Sschwarze 	struct roff_node *n, *np;
30314a309e3Sschwarze 	const v_post *p;
304f73abda9Skristaps 
3057c539ecbSschwarze 	/*
3067c539ecbSschwarze 	 * Translate obsolete macros to modern macros first
3077c539ecbSschwarze 	 * such that later code does not need to look
3087c539ecbSschwarze 	 * for the obsolete versions.
3097c539ecbSschwarze 	 */
3107c539ecbSschwarze 
311753701eeSschwarze 	n = mdoc->last;
3127c539ecbSschwarze 	switch (n->tok) {
3137c539ecbSschwarze 	case MDOC_Lp:
3147c539ecbSschwarze 		n->tok = MDOC_Pp;
3157c539ecbSschwarze 		break;
3167c539ecbSschwarze 	case MDOC_Ot:
3177c539ecbSschwarze 		post_obsolete(mdoc);
3187c539ecbSschwarze 		n->tok = MDOC_Ft;
3197c539ecbSschwarze 		break;
3207c539ecbSschwarze 	default:
3217c539ecbSschwarze 		break;
3227c539ecbSschwarze 	}
3237c539ecbSschwarze 
3247c539ecbSschwarze 	/*
3257c539ecbSschwarze 	 * Iterate over all children, recursing into each one
3267c539ecbSschwarze 	 * in turn, depth-first.
3277c539ecbSschwarze 	 */
3287c539ecbSschwarze 
329396853b5Sschwarze 	mdoc->last = mdoc->last->child;
330396853b5Sschwarze 	while (mdoc->last != NULL) {
33183d65a5aSschwarze 		mdoc_validate(mdoc);
332396853b5Sschwarze 		if (mdoc->last == n)
333396853b5Sschwarze 			mdoc->last = mdoc->last->child;
334396853b5Sschwarze 		else
335396853b5Sschwarze 			mdoc->last = mdoc->last->next;
336396853b5Sschwarze 	}
337f73abda9Skristaps 
3387c539ecbSschwarze 	/* Finally validate the macro itself. */
3397c539ecbSschwarze 
340396853b5Sschwarze 	mdoc->last = n;
341396853b5Sschwarze 	mdoc->next = ROFF_NEXT_SIBLING;
342753701eeSschwarze 	switch (n->type) {
343d1982c71Sschwarze 	case ROFFT_TEXT:
3446dc98fe5Sschwarze 		np = n->parent;
34579ca1811Sschwarze 		if (n->sec != SEC_SYNOPSIS ||
3466dc98fe5Sschwarze 		    (np->tok != MDOC_Cd && np->tok != MDOC_Fd))
3473e642ba0Sschwarze 			check_text(mdoc, n->line, n->pos, n->string);
3487faedc4aSschwarze 		if ((n->flags & NODE_NOFILL) == 0 &&
3496dc98fe5Sschwarze 		    (np->tok != MDOC_It || np->type != ROFFT_HEAD ||
3506dc98fe5Sschwarze 		     np->parent->parent->norm->Bl.type != LIST_diag))
3516dc98fe5Sschwarze 			check_text_em(mdoc, n->line, n->pos, n->string);
3526dc98fe5Sschwarze 		if (np->tok == MDOC_It || (np->type == ROFFT_BODY &&
3536dc98fe5Sschwarze 		    (np->tok == MDOC_Sh || np->tok == MDOC_Ss)))
35448497dd5Sschwarze 			check_toptext(mdoc, n->line, n->pos, n->string);
3553e642ba0Sschwarze 		break;
3564c293873Sschwarze 	case ROFFT_COMMENT:
357d1982c71Sschwarze 	case ROFFT_EQN:
358d1982c71Sschwarze 	case ROFFT_TBL:
35998b8f00aSschwarze 		break;
360d1982c71Sschwarze 	case ROFFT_ROOT:
36198b8f00aSschwarze 		post_root(mdoc);
36298b8f00aSschwarze 		break;
3632791bd1cSschwarze 	default:
3643e642ba0Sschwarze 		check_args(mdoc, mdoc->last);
3656f9818f6Sschwarze 
3666f9818f6Sschwarze 		/*
3676f9818f6Sschwarze 		 * Closing delimiters are not special at the
3686f9818f6Sschwarze 		 * beginning of a block, opening delimiters
3696f9818f6Sschwarze 		 * are not special at the end.
3706f9818f6Sschwarze 		 */
3716f9818f6Sschwarze 
3726f9818f6Sschwarze 		if (n->child != NULL)
373c4b0939cSschwarze 			n->child->flags &= ~NODE_DELIMC;
3746f9818f6Sschwarze 		if (n->last != NULL)
375c4b0939cSschwarze 			n->last->flags &= ~NODE_DELIMO;
3766f9818f6Sschwarze 
3776f9818f6Sschwarze 		/* Call the macro's postprocessor. */
3786f9818f6Sschwarze 
37929478532Sschwarze 		if (n->tok < ROFF_MAX) {
380c4d3fa85Sschwarze 			roff_validate(mdoc);
381c4d3fa85Sschwarze 			break;
38229478532Sschwarze 		}
38329478532Sschwarze 
38429478532Sschwarze 		assert(n->tok >= MDOC_Dd && n->tok < MDOC_MAX);
385b3f54129Sschwarze 		p = mdoc_valids + (n->tok - MDOC_Dd);
38698b8f00aSschwarze 		if (*p)
38798b8f00aSschwarze 			(*p)(mdoc);
388396853b5Sschwarze 		if (mdoc->last == n)
389396853b5Sschwarze 			mdoc_state(mdoc, n);
39098b8f00aSschwarze 		break;
3912791bd1cSschwarze 	}
392f73abda9Skristaps }
393f73abda9Skristaps 
39498b8f00aSschwarze static void
check_args(struct roff_man * mdoc,struct roff_node * n)395ede1b9d0Sschwarze check_args(struct roff_man *mdoc, struct roff_node *n)
396f73abda9Skristaps {
397f73abda9Skristaps 	int		 i;
398f73abda9Skristaps 
399f73abda9Skristaps 	if (NULL == n->args)
40020fa2881Sschwarze 		return;
401f73abda9Skristaps 
402f73abda9Skristaps 	assert(n->args->argc);
403f73abda9Skristaps 	for (i = 0; i < (int)n->args->argc; i++)
4047ead8a4eSschwarze 		check_argv(mdoc, n, &n->args->argv[i]);
405f73abda9Skristaps }
406f73abda9Skristaps 
40720fa2881Sschwarze static void
check_argv(struct roff_man * mdoc,struct roff_node * n,struct mdoc_argv * v)408ede1b9d0Sschwarze check_argv(struct roff_man *mdoc, struct roff_node *n, struct mdoc_argv *v)
409f73abda9Skristaps {
410f73abda9Skristaps 	int		 i;
411f73abda9Skristaps 
412f73abda9Skristaps 	for (i = 0; i < (int)v->sz; i++)
4137ead8a4eSschwarze 		check_text(mdoc, v->line, v->pos, v->value[i]);
414f73abda9Skristaps }
415f73abda9Skristaps 
41620fa2881Sschwarze static void
check_text(struct roff_man * mdoc,int ln,int pos,char * p)417ede1b9d0Sschwarze check_text(struct roff_man *mdoc, int ln, int pos, char *p)
418f73abda9Skristaps {
41904e980cbSschwarze 	char		*cp;
420769ee804Sschwarze 
4217faedc4aSschwarze 	if (mdoc->last->flags & NODE_NOFILL)
4221cdbf331Sschwarze 		return;
4231cdbf331Sschwarze 
4241cdbf331Sschwarze 	for (cp = p; NULL != (p = strchr(p, '\t')); p++)
425a5a5f808Sschwarze 		mandoc_msg(MANDOCERR_FI_TAB, ln, pos + (int)(p - cp), NULL);
426f73abda9Skristaps }
427f73abda9Skristaps 
42898b8f00aSschwarze static void
check_text_em(struct roff_man * mdoc,int ln,int pos,char * p)4296dc98fe5Sschwarze check_text_em(struct roff_man *mdoc, int ln, int pos, char *p)
4306dc98fe5Sschwarze {
4316dc98fe5Sschwarze 	const struct roff_node	*np, *nn;
4326dc98fe5Sschwarze 	char			*cp;
4336dc98fe5Sschwarze 
4346dc98fe5Sschwarze 	np = mdoc->last->prev;
4356dc98fe5Sschwarze 	nn = mdoc->last->next;
4366dc98fe5Sschwarze 
4376dc98fe5Sschwarze 	/* Look for em-dashes wrongly encoded as "--". */
4386dc98fe5Sschwarze 
4396dc98fe5Sschwarze 	for (cp = p; *cp != '\0'; cp++) {
440dfdd4af9Sschwarze 		if (cp[0] != '-' || cp[1] != '-')
4416dc98fe5Sschwarze 			continue;
442dfdd4af9Sschwarze 		cp++;
4436dc98fe5Sschwarze 
4446dc98fe5Sschwarze 		/* Skip input sequences of more than two '-'. */
4456dc98fe5Sschwarze 
4466dc98fe5Sschwarze 		if (cp[1] == '-') {
4476dc98fe5Sschwarze 			while (cp[1] == '-')
4486dc98fe5Sschwarze 				cp++;
4496dc98fe5Sschwarze 			continue;
4506dc98fe5Sschwarze 		}
4516dc98fe5Sschwarze 
4526dc98fe5Sschwarze 		/* Skip "--" directly attached to something else. */
4536dc98fe5Sschwarze 
4546dc98fe5Sschwarze 		if ((cp - p > 1 && cp[-2] != ' ') ||
4556dc98fe5Sschwarze 		    (cp[1] != '\0' && cp[1] != ' '))
4566dc98fe5Sschwarze 			continue;
4576dc98fe5Sschwarze 
4586dc98fe5Sschwarze 		/* Require a letter right before or right afterwards. */
4596dc98fe5Sschwarze 
4606dc98fe5Sschwarze 		if ((cp - p > 2 ?
4616dc98fe5Sschwarze 		     isalpha((unsigned char)cp[-3]) :
4626dc98fe5Sschwarze 		     np != NULL &&
4636dc98fe5Sschwarze 		     np->type == ROFFT_TEXT &&
46489db6ba1Sschwarze 		     *np->string != '\0' &&
4656dc98fe5Sschwarze 		     isalpha((unsigned char)np->string[
4666dc98fe5Sschwarze 		       strlen(np->string) - 1])) ||
46700e80a2aSschwarze 		    (cp[1] != '\0' && cp[2] != '\0' ?
4686dc98fe5Sschwarze 		     isalpha((unsigned char)cp[2]) :
4696dc98fe5Sschwarze 		     nn != NULL &&
4706dc98fe5Sschwarze 		     nn->type == ROFFT_TEXT &&
4716dc98fe5Sschwarze 		     isalpha((unsigned char)*nn->string))) {
472a5a5f808Sschwarze 			mandoc_msg(MANDOCERR_DASHDASH,
4736dc98fe5Sschwarze 			    ln, pos + (int)(cp - p) - 1, NULL);
4746dc98fe5Sschwarze 			break;
4756dc98fe5Sschwarze 		}
4766dc98fe5Sschwarze 	}
4776dc98fe5Sschwarze }
4786dc98fe5Sschwarze 
4796dc98fe5Sschwarze static void
check_toptext(struct roff_man * mdoc,int ln,int pos,const char * p)48048497dd5Sschwarze check_toptext(struct roff_man *mdoc, int ln, int pos, const char *p)
481f0c18971Sschwarze {
48248497dd5Sschwarze 	const char	*cp, *cpr;
48348497dd5Sschwarze 
48448497dd5Sschwarze 	if (*p == '\0')
48548497dd5Sschwarze 		return;
486f0c18971Sschwarze 
487f0c18971Sschwarze 	if ((cp = strstr(p, "OpenBSD")) != NULL)
488a5a5f808Sschwarze 		mandoc_msg(MANDOCERR_BX, ln, pos + (int)(cp - p), "Ox");
489f0c18971Sschwarze 	if ((cp = strstr(p, "NetBSD")) != NULL)
490a5a5f808Sschwarze 		mandoc_msg(MANDOCERR_BX, ln, pos + (int)(cp - p), "Nx");
491f0c18971Sschwarze 	if ((cp = strstr(p, "FreeBSD")) != NULL)
492a5a5f808Sschwarze 		mandoc_msg(MANDOCERR_BX, ln, pos + (int)(cp - p), "Fx");
493f0c18971Sschwarze 	if ((cp = strstr(p, "DragonFly")) != NULL)
494a5a5f808Sschwarze 		mandoc_msg(MANDOCERR_BX, ln, pos + (int)(cp - p), "Dx");
49548497dd5Sschwarze 
49648497dd5Sschwarze 	cp = p;
49748497dd5Sschwarze 	while ((cp = strstr(cp + 1, "()")) != NULL) {
49848497dd5Sschwarze 		for (cpr = cp - 1; cpr >= p; cpr--)
49948497dd5Sschwarze 			if (*cpr != '_' && !isalnum((unsigned char)*cpr))
50048497dd5Sschwarze 				break;
50148497dd5Sschwarze 		if ((cpr < p || *cpr == ' ') && cpr + 1 < cp) {
50248497dd5Sschwarze 			cpr++;
503a5a5f808Sschwarze 			mandoc_msg(MANDOCERR_FUNC, ln, pos + (int)(cpr - p),
50448497dd5Sschwarze 			    "%.*s()", (int)(cp - cpr), cpr);
50548497dd5Sschwarze 		}
50648497dd5Sschwarze 	}
507f0c18971Sschwarze }
508f0c18971Sschwarze 
50936dc2246Sschwarze static void
post_abort(POST_ARGS)5107c539ecbSschwarze post_abort(POST_ARGS)
5117c539ecbSschwarze {
5127c539ecbSschwarze 	abort();
5137c539ecbSschwarze }
5147c539ecbSschwarze 
5157c539ecbSschwarze static void
post_delim(POST_ARGS)51604fbb99fSschwarze post_delim(POST_ARGS)
51704fbb99fSschwarze {
51804fbb99fSschwarze 	const struct roff_node	*nch;
519fe8e59edSschwarze 	const char		*lc;
520fe8e59edSschwarze 	enum mdelim		 delim;
521fe8e59edSschwarze 	enum roff_tok		 tok;
522fe8e59edSschwarze 
523fe8e59edSschwarze 	tok = mdoc->last->tok;
524fe8e59edSschwarze 	nch = mdoc->last->last;
525fe8e59edSschwarze 	if (nch == NULL || nch->type != ROFFT_TEXT)
526fe8e59edSschwarze 		return;
527fe8e59edSschwarze 	lc = strchr(nch->string, '\0') - 1;
528fe8e59edSschwarze 	if (lc < nch->string)
529fe8e59edSschwarze 		return;
530fe8e59edSschwarze 	delim = mdoc_isdelim(lc);
531fe8e59edSschwarze 	if (delim == DELIM_NONE || delim == DELIM_OPEN)
532fe8e59edSschwarze 		return;
533fe8e59edSschwarze 	if (*lc == ')' && (tok == MDOC_Nd || tok == MDOC_Sh ||
534fe8e59edSschwarze 	    tok == MDOC_Ss || tok == MDOC_Fo))
535fe8e59edSschwarze 		return;
536fe8e59edSschwarze 
537a5a5f808Sschwarze 	mandoc_msg(MANDOCERR_DELIM, nch->line,
538a5a5f808Sschwarze 	    nch->pos + (int)(lc - nch->string), "%s%s %s", roff_name[tok],
539fe8e59edSschwarze 	    nch == mdoc->last->child ? "" : " ...", nch->string);
540fe8e59edSschwarze }
541fe8e59edSschwarze 
542fe8e59edSschwarze static void
post_delim_nb(POST_ARGS)543fe8e59edSschwarze post_delim_nb(POST_ARGS)
544fe8e59edSschwarze {
545fe8e59edSschwarze 	const struct roff_node	*nch;
546b7c50e87Sschwarze 	const char		*lc, *cp;
547b7c50e87Sschwarze 	int			 nw;
54804fbb99fSschwarze 	enum mdelim		 delim;
549b7c50e87Sschwarze 	enum roff_tok		 tok;
55004fbb99fSschwarze 
551b7c50e87Sschwarze 	/*
552b7c50e87Sschwarze 	 * Find candidates: at least two bytes,
553b7c50e87Sschwarze 	 * the last one a closing or middle delimiter.
554b7c50e87Sschwarze 	 */
555b7c50e87Sschwarze 
556b7c50e87Sschwarze 	tok = mdoc->last->tok;
55704fbb99fSschwarze 	nch = mdoc->last->last;
55804fbb99fSschwarze 	if (nch == NULL || nch->type != ROFFT_TEXT)
55904fbb99fSschwarze 		return;
56004fbb99fSschwarze 	lc = strchr(nch->string, '\0') - 1;
56104fbb99fSschwarze 	if (lc <= nch->string)
56204fbb99fSschwarze 		return;
56304fbb99fSschwarze 	delim = mdoc_isdelim(lc);
56404fbb99fSschwarze 	if (delim == DELIM_NONE || delim == DELIM_OPEN)
56504fbb99fSschwarze 		return;
566b7c50e87Sschwarze 
567b7c50e87Sschwarze 	/*
568b7c50e87Sschwarze 	 * Reduce false positives by allowing various cases.
569b7c50e87Sschwarze 	 */
570b7c50e87Sschwarze 
571b7c50e87Sschwarze 	/* Escaped delimiters. */
572b7c50e87Sschwarze 	if (lc > nch->string + 1 && lc[-2] == '\\' &&
573b7c50e87Sschwarze 	    (lc[-1] == '&' || lc[-1] == 'e'))
574b7c50e87Sschwarze 		return;
575b7c50e87Sschwarze 
576b7c50e87Sschwarze 	/* Specific byte sequences. */
577b7c50e87Sschwarze 	switch (*lc) {
578b7c50e87Sschwarze 	case ')':
579b7c50e87Sschwarze 		for (cp = lc; cp >= nch->string; cp--)
580b7c50e87Sschwarze 			if (*cp == '(')
581b7c50e87Sschwarze 				return;
582b7c50e87Sschwarze 		break;
583b7c50e87Sschwarze 	case '.':
584b7c50e87Sschwarze 		if (lc > nch->string + 1 && lc[-2] == '.' && lc[-1] == '.')
585b7c50e87Sschwarze 			return;
586b7c50e87Sschwarze 		if (lc[-1] == '.')
587b7c50e87Sschwarze 			return;
588b7c50e87Sschwarze 		break;
589b7c50e87Sschwarze 	case ';':
590b7c50e87Sschwarze 		if (tok == MDOC_Vt)
591b7c50e87Sschwarze 			return;
592b7c50e87Sschwarze 		break;
593b7c50e87Sschwarze 	case '?':
594b7c50e87Sschwarze 		if (lc[-1] == '?')
595b7c50e87Sschwarze 			return;
596b7c50e87Sschwarze 		break;
597b7c50e87Sschwarze 	case ']':
598b7c50e87Sschwarze 		for (cp = lc; cp >= nch->string; cp--)
599b7c50e87Sschwarze 			if (*cp == '[')
600b7c50e87Sschwarze 				return;
601b7c50e87Sschwarze 		break;
602b7c50e87Sschwarze 	case '|':
603b7c50e87Sschwarze 		if (lc == nch->string + 1 && lc[-1] == '|')
604b7c50e87Sschwarze 			return;
605b7c50e87Sschwarze 	default:
606b7c50e87Sschwarze 		break;
607b7c50e87Sschwarze 	}
608b7c50e87Sschwarze 
609b7c50e87Sschwarze 	/* Exactly two non-alphanumeric bytes. */
610b7c50e87Sschwarze 	if (lc == nch->string + 1 && !isalnum((unsigned char)lc[-1]))
611b7c50e87Sschwarze 		return;
612b7c50e87Sschwarze 
613b7c50e87Sschwarze 	/* At least three alphabetic words with a sentence ending. */
614b7c50e87Sschwarze 	if (strchr("!.:?", *lc) != NULL && (tok == MDOC_Em ||
6151abead14Sschwarze 	    tok == MDOC_Li || tok == MDOC_Pq || tok == MDOC_Sy)) {
616b7c50e87Sschwarze 		nw = 0;
617b7c50e87Sschwarze 		for (cp = lc - 1; cp >= nch->string; cp--) {
618b7c50e87Sschwarze 			if (*cp == ' ') {
619b7c50e87Sschwarze 				nw++;
620b7c50e87Sschwarze 				if (cp > nch->string && cp[-1] == ',')
621b7c50e87Sschwarze 					cp--;
622b7c50e87Sschwarze 			} else if (isalpha((unsigned int)*cp)) {
623b7c50e87Sschwarze 				if (nw > 1)
624b7c50e87Sschwarze 					return;
625b7c50e87Sschwarze 			} else
626b7c50e87Sschwarze 				break;
627b7c50e87Sschwarze 		}
628b7c50e87Sschwarze 	}
629b7c50e87Sschwarze 
630a5a5f808Sschwarze 	mandoc_msg(MANDOCERR_DELIM_NB, nch->line,
631a5a5f808Sschwarze 	    nch->pos + (int)(lc - nch->string), "%s%s %s", roff_name[tok],
63204fbb99fSschwarze 	    nch == mdoc->last->child ? "" : " ...", nch->string);
63304fbb99fSschwarze }
63404fbb99fSschwarze 
63504fbb99fSschwarze static void
post_bl_norm(POST_ARGS)6363e642ba0Sschwarze post_bl_norm(POST_ARGS)
637f73abda9Skristaps {
6383e642ba0Sschwarze 	struct roff_node *n;
639aa99c14fSschwarze 	struct mdoc_argv *argv, *wa;
6404a9f685fSschwarze 	int		  i;
641aa99c14fSschwarze 	enum mdocargt	  mdoclt;
6424a9f685fSschwarze 	enum mdoc_list	  lt;
643f73abda9Skristaps 
6443e642ba0Sschwarze 	n = mdoc->last->parent;
6453e642ba0Sschwarze 	n->norm->Bl.type = LIST__NONE;
646f73abda9Skristaps 
6476093755cSschwarze 	/*
6486093755cSschwarze 	 * First figure out which kind of list to use: bind ourselves to
6496093755cSschwarze 	 * the first mentioned list type and warn about any remaining
6506093755cSschwarze 	 * ones.  If we find no list type, we default to LIST_item.
6516093755cSschwarze 	 */
652f73abda9Skristaps 
653dadc3a61Sschwarze 	wa = (n->args == NULL) ? NULL : n->args->argv;
654aa99c14fSschwarze 	mdoclt = MDOC_ARG_MAX;
6556093755cSschwarze 	for (i = 0; n->args && i < (int)n->args->argc; i++) {
6564a9f685fSschwarze 		argv = n->args->argv + i;
6576093755cSschwarze 		lt = LIST__NONE;
6584a9f685fSschwarze 		switch (argv->arg) {
6596093755cSschwarze 		/* Set list types. */
66049aff9f8Sschwarze 		case MDOC_Bullet:
6616093755cSschwarze 			lt = LIST_bullet;
6626093755cSschwarze 			break;
66349aff9f8Sschwarze 		case MDOC_Dash:
6646093755cSschwarze 			lt = LIST_dash;
6656093755cSschwarze 			break;
66649aff9f8Sschwarze 		case MDOC_Enum:
6676093755cSschwarze 			lt = LIST_enum;
6686093755cSschwarze 			break;
66949aff9f8Sschwarze 		case MDOC_Hyphen:
6706093755cSschwarze 			lt = LIST_hyphen;
6716093755cSschwarze 			break;
67249aff9f8Sschwarze 		case MDOC_Item:
6736093755cSschwarze 			lt = LIST_item;
6746093755cSschwarze 			break;
67549aff9f8Sschwarze 		case MDOC_Tag:
6766093755cSschwarze 			lt = LIST_tag;
6776093755cSschwarze 			break;
67849aff9f8Sschwarze 		case MDOC_Diag:
6796093755cSschwarze 			lt = LIST_diag;
6806093755cSschwarze 			break;
68149aff9f8Sschwarze 		case MDOC_Hang:
6826093755cSschwarze 			lt = LIST_hang;
6836093755cSschwarze 			break;
68449aff9f8Sschwarze 		case MDOC_Ohang:
6856093755cSschwarze 			lt = LIST_ohang;
6866093755cSschwarze 			break;
68749aff9f8Sschwarze 		case MDOC_Inset:
6886093755cSschwarze 			lt = LIST_inset;
6896093755cSschwarze 			break;
69049aff9f8Sschwarze 		case MDOC_Column:
6916093755cSschwarze 			lt = LIST_column;
69264d728e4Sschwarze 			break;
6936093755cSschwarze 		/* Set list arguments. */
69449aff9f8Sschwarze 		case MDOC_Compact:
6954a9f685fSschwarze 			if (n->norm->Bl.comp)
6964a9f685fSschwarze 				mandoc_msg(MANDOCERR_ARG_REP,
697a5a5f808Sschwarze 				    argv->line, argv->pos, "Bl -compact");
6984a9f685fSschwarze 			n->norm->Bl.comp = 1;
69950e63e03Sschwarze 			break;
70049aff9f8Sschwarze 		case MDOC_Width:
701aa99c14fSschwarze 			wa = argv;
7024a9f685fSschwarze 			if (0 == argv->sz) {
7034a9f685fSschwarze 				mandoc_msg(MANDOCERR_ARG_EMPTY,
704a5a5f808Sschwarze 				    argv->line, argv->pos, "Bl -width");
7054a9f685fSschwarze 				n->norm->Bl.width = "0n";
70622972b14Sschwarze 				break;
70722972b14Sschwarze 			}
7084a9f685fSschwarze 			if (NULL != n->norm->Bl.width)
709a5a5f808Sschwarze 				mandoc_msg(MANDOCERR_ARG_REP,
710a5a5f808Sschwarze 				    argv->line, argv->pos,
711a5a5f808Sschwarze 				    "Bl -width %s", argv->value[0]);
7126050a3daSschwarze 			rewrite_macro2len(mdoc, argv->value);
7134a9f685fSschwarze 			n->norm->Bl.width = argv->value[0];
71464d728e4Sschwarze 			break;
71549aff9f8Sschwarze 		case MDOC_Offset:
7164a9f685fSschwarze 			if (0 == argv->sz) {
7174a9f685fSschwarze 				mandoc_msg(MANDOCERR_ARG_EMPTY,
718a5a5f808Sschwarze 				    argv->line, argv->pos, "Bl -offset");
71931e23753Sschwarze 				break;
72031e23753Sschwarze 			}
7214a9f685fSschwarze 			if (NULL != n->norm->Bl.offs)
722a5a5f808Sschwarze 				mandoc_msg(MANDOCERR_ARG_REP,
723a5a5f808Sschwarze 				    argv->line, argv->pos,
724a5a5f808Sschwarze 				    "Bl -offset %s", argv->value[0]);
7256050a3daSschwarze 			rewrite_macro2len(mdoc, argv->value);
7264a9f685fSschwarze 			n->norm->Bl.offs = argv->value[0];
727f73abda9Skristaps 			break;
728ddce0b0cSschwarze 		default:
729ddce0b0cSschwarze 			continue;
730f73abda9Skristaps 		}
731dc0d8bb2Sschwarze 		if (LIST__NONE == lt)
732dc0d8bb2Sschwarze 			continue;
733aa99c14fSschwarze 		mdoclt = argv->arg;
734f73abda9Skristaps 
7356093755cSschwarze 		/* Check: multiple list types. */
7366093755cSschwarze 
737dc0d8bb2Sschwarze 		if (LIST__NONE != n->norm->Bl.type) {
738a5a5f808Sschwarze 			mandoc_msg(MANDOCERR_BL_REP, n->line, n->pos,
739bd594191Sschwarze 			    "Bl -%s", mdoc_argnames[argv->arg]);
740dc0d8bb2Sschwarze 			continue;
741769ee804Sschwarze 		}
7426093755cSschwarze 
7436093755cSschwarze 		/* The list type should come first. */
7446093755cSschwarze 
7458c62fbf5Sschwarze 		if (n->norm->Bl.width ||
7468c62fbf5Sschwarze 		    n->norm->Bl.offs ||
7478c62fbf5Sschwarze 		    n->norm->Bl.comp)
748a5a5f808Sschwarze 			mandoc_msg(MANDOCERR_BL_LATETYPE,
749a5a5f808Sschwarze 			    n->line, n->pos, "Bl -%s",
75066788495Sschwarze 			    mdoc_argnames[n->args->argv[0].arg]);
751dc0d8bb2Sschwarze 
752dc0d8bb2Sschwarze 		n->norm->Bl.type = lt;
753dc0d8bb2Sschwarze 		if (LIST_column == lt) {
754dc0d8bb2Sschwarze 			n->norm->Bl.ncols = argv->sz;
755dc0d8bb2Sschwarze 			n->norm->Bl.cols = (void *)argv->value;
756dc0d8bb2Sschwarze 		}
7576093755cSschwarze 	}
7586093755cSschwarze 
7596093755cSschwarze 	/* Allow lists to default to LIST_item. */
7606093755cSschwarze 
7618c62fbf5Sschwarze 	if (LIST__NONE == n->norm->Bl.type) {
762a5a5f808Sschwarze 		mandoc_msg(MANDOCERR_BL_NOTYPE, n->line, n->pos, "Bl");
7638c62fbf5Sschwarze 		n->norm->Bl.type = LIST_item;
7642c7eb483Sschwarze 		mdoclt = MDOC_Item;
7656e03d529Sschwarze 	}
766f73abda9Skristaps 
76764d728e4Sschwarze 	/*
76864d728e4Sschwarze 	 * Validate the width field.  Some list types don't need width
76964d728e4Sschwarze 	 * types and should be warned about them.  Others should have it
7705eced068Sschwarze 	 * and must also be warned.  Yet others have a default and need
7715eced068Sschwarze 	 * no warning.
77264d728e4Sschwarze 	 */
77364d728e4Sschwarze 
7748c62fbf5Sschwarze 	switch (n->norm->Bl.type) {
77549aff9f8Sschwarze 	case LIST_tag:
776162c3bafSschwarze 		if (n->norm->Bl.width == NULL)
777a5a5f808Sschwarze 			mandoc_msg(MANDOCERR_BL_NOWIDTH,
778bd594191Sschwarze 			    n->line, n->pos, "Bl -tag");
779f73abda9Skristaps 		break;
78049aff9f8Sschwarze 	case LIST_column:
78149aff9f8Sschwarze 	case LIST_diag:
78249aff9f8Sschwarze 	case LIST_ohang:
78349aff9f8Sschwarze 	case LIST_inset:
78449aff9f8Sschwarze 	case LIST_item:
785162c3bafSschwarze 		if (n->norm->Bl.width != NULL)
786a5a5f808Sschwarze 			mandoc_msg(MANDOCERR_BL_SKIPW, wa->line, wa->pos,
787a5a5f808Sschwarze 			    "Bl -%s", mdoc_argnames[mdoclt]);
788162c3bafSschwarze 		n->norm->Bl.width = NULL;
7896093755cSschwarze 		break;
79049aff9f8Sschwarze 	case LIST_bullet:
79149aff9f8Sschwarze 	case LIST_dash:
79249aff9f8Sschwarze 	case LIST_hyphen:
793162c3bafSschwarze 		if (n->norm->Bl.width == NULL)
7945eced068Sschwarze 			n->norm->Bl.width = "2n";
7955eced068Sschwarze 		break;
79649aff9f8Sschwarze 	case LIST_enum:
797162c3bafSschwarze 		if (n->norm->Bl.width == NULL)
7985eced068Sschwarze 			n->norm->Bl.width = "3n";
7995eced068Sschwarze 		break;
80064d728e4Sschwarze 	default:
801f73abda9Skristaps 		break;
80264d728e4Sschwarze 	}
803f73abda9Skristaps }
804f73abda9Skristaps 
80598b8f00aSschwarze static void
post_bd(POST_ARGS)8063e642ba0Sschwarze post_bd(POST_ARGS)
807f73abda9Skristaps {
8083e642ba0Sschwarze 	struct roff_node *n;
8094a9f685fSschwarze 	struct mdoc_argv *argv;
8104a9f685fSschwarze 	int		  i;
8114a9f685fSschwarze 	enum mdoc_disp	  dt;
812f73abda9Skristaps 
8133e642ba0Sschwarze 	n = mdoc->last;
81431e23753Sschwarze 	for (i = 0; n->args && i < (int)n->args->argc; i++) {
8154a9f685fSschwarze 		argv = n->args->argv + i;
81631e23753Sschwarze 		dt = DISP__NONE;
81731e23753Sschwarze 
8184a9f685fSschwarze 		switch (argv->arg) {
81949aff9f8Sschwarze 		case MDOC_Centred:
8202065e47aSschwarze 			dt = DISP_centered;
82131e23753Sschwarze 			break;
82249aff9f8Sschwarze 		case MDOC_Ragged:
82331e23753Sschwarze 			dt = DISP_ragged;
82431e23753Sschwarze 			break;
82549aff9f8Sschwarze 		case MDOC_Unfilled:
82631e23753Sschwarze 			dt = DISP_unfilled;
82731e23753Sschwarze 			break;
82849aff9f8Sschwarze 		case MDOC_Filled:
82931e23753Sschwarze 			dt = DISP_filled;
83031e23753Sschwarze 			break;
83149aff9f8Sschwarze 		case MDOC_Literal:
83231e23753Sschwarze 			dt = DISP_literal;
833f73abda9Skristaps 			break;
83449aff9f8Sschwarze 		case MDOC_File:
835a5a5f808Sschwarze 			mandoc_msg(MANDOCERR_BD_FILE, n->line, n->pos, NULL);
8361164a325Sschwarze 			break;
83749aff9f8Sschwarze 		case MDOC_Offset:
8384a9f685fSschwarze 			if (0 == argv->sz) {
8394a9f685fSschwarze 				mandoc_msg(MANDOCERR_ARG_EMPTY,
840a5a5f808Sschwarze 				    argv->line, argv->pos, "Bd -offset");
841f73abda9Skristaps 				break;
842f73abda9Skristaps 			}
8434a9f685fSschwarze 			if (NULL != n->norm->Bd.offs)
844a5a5f808Sschwarze 				mandoc_msg(MANDOCERR_ARG_REP,
845a5a5f808Sschwarze 				    argv->line, argv->pos,
846a5a5f808Sschwarze 				    "Bd -offset %s", argv->value[0]);
8476050a3daSschwarze 			rewrite_macro2len(mdoc, argv->value);
8484a9f685fSschwarze 			n->norm->Bd.offs = argv->value[0];
84931e23753Sschwarze 			break;
85049aff9f8Sschwarze 		case MDOC_Compact:
8514a9f685fSschwarze 			if (n->norm->Bd.comp)
8524a9f685fSschwarze 				mandoc_msg(MANDOCERR_ARG_REP,
853a5a5f808Sschwarze 				    argv->line, argv->pos, "Bd -compact");
8544a9f685fSschwarze 			n->norm->Bd.comp = 1;
85531e23753Sschwarze 			break;
85631e23753Sschwarze 		default:
85731e23753Sschwarze 			abort();
85831e23753Sschwarze 		}
859dc0d8bb2Sschwarze 		if (DISP__NONE == dt)
860dc0d8bb2Sschwarze 			continue;
86131e23753Sschwarze 
862dc0d8bb2Sschwarze 		if (DISP__NONE == n->norm->Bd.type)
8638c62fbf5Sschwarze 			n->norm->Bd.type = dt;
864dc0d8bb2Sschwarze 		else
865a5a5f808Sschwarze 			mandoc_msg(MANDOCERR_BD_REP, n->line, n->pos,
866bd594191Sschwarze 			    "Bd -%s", mdoc_argnames[argv->arg]);
86731e23753Sschwarze 	}
86831e23753Sschwarze 
8698c62fbf5Sschwarze 	if (DISP__NONE == n->norm->Bd.type) {
870a5a5f808Sschwarze 		mandoc_msg(MANDOCERR_BD_NOTYPE, n->line, n->pos, "Bd");
8718c62fbf5Sschwarze 		n->norm->Bd.type = DISP_ragged;
87231e23753Sschwarze 	}
873f73abda9Skristaps }
874f73abda9Skristaps 
8758ccddcd3Sschwarze /*
8768ccddcd3Sschwarze  * Stand-alone line macros.
8778ccddcd3Sschwarze  */
8788ccddcd3Sschwarze 
87998b8f00aSschwarze static void
post_an_norm(POST_ARGS)8803e642ba0Sschwarze post_an_norm(POST_ARGS)
881f73abda9Skristaps {
8823e642ba0Sschwarze 	struct roff_node *n;
883aa99c14fSschwarze 	struct mdoc_argv *argv;
884aa99c14fSschwarze 	size_t	 i;
885f73abda9Skristaps 
8863e642ba0Sschwarze 	n = mdoc->last;
887aa99c14fSschwarze 	if (n->args == NULL)
88898b8f00aSschwarze 		return;
889769ee804Sschwarze 
890aa99c14fSschwarze 	for (i = 1; i < n->args->argc; i++) {
891aa99c14fSschwarze 		argv = n->args->argv + i;
892a5a5f808Sschwarze 		mandoc_msg(MANDOCERR_AN_REP, argv->line, argv->pos,
893aa99c14fSschwarze 		    "An -%s", mdoc_argnames[argv->arg]);
894aa99c14fSschwarze 	}
8957c2be9f8Sschwarze 
896aa99c14fSschwarze 	argv = n->args->argv;
897aa99c14fSschwarze 	if (argv->arg == MDOC_Split)
8988c62fbf5Sschwarze 		n->norm->An.auth = AUTH_split;
899aa99c14fSschwarze 	else if (argv->arg == MDOC_Nosplit)
9008c62fbf5Sschwarze 		n->norm->An.auth = AUTH_nosplit;
901769ee804Sschwarze 	else
902769ee804Sschwarze 		abort();
903f73abda9Skristaps }
904f73abda9Skristaps 
90598b8f00aSschwarze static void
post_eoln(POST_ARGS)9068ccddcd3Sschwarze post_eoln(POST_ARGS)
9078ccddcd3Sschwarze {
9088ccddcd3Sschwarze 	struct roff_node	*n;
9098ccddcd3Sschwarze 
910bc205043Sschwarze 	post_useless(mdoc);
9118ccddcd3Sschwarze 	n = mdoc->last;
9128ccddcd3Sschwarze 	if (n->child != NULL)
913a5a5f808Sschwarze 		mandoc_msg(MANDOCERR_ARG_SKIP, n->line,
91414a309e3Sschwarze 		    n->pos, "%s %s", roff_name[n->tok], n->child->string);
9158ccddcd3Sschwarze 
9168ccddcd3Sschwarze 	while (n->child != NULL)
9178ccddcd3Sschwarze 		roff_node_delete(mdoc, n->child);
9188ccddcd3Sschwarze 
9198ccddcd3Sschwarze 	roff_word_alloc(mdoc, n->line, n->pos, n->tok == MDOC_Bt ?
9208ccddcd3Sschwarze 	    "is currently in beta test." : "currently under development.");
9218ccddcd3Sschwarze 	mdoc->last->flags |= NODE_EOS | NODE_NOSRC;
9228ccddcd3Sschwarze 	mdoc->last = n;
9238ccddcd3Sschwarze }
9248ccddcd3Sschwarze 
9258ccddcd3Sschwarze static int
build_list(struct roff_man * mdoc,int tok)9268ccddcd3Sschwarze build_list(struct roff_man *mdoc, int tok)
9278ccddcd3Sschwarze {
9288ccddcd3Sschwarze 	struct roff_node	*n;
9298ccddcd3Sschwarze 	int			 ic;
9308ccddcd3Sschwarze 
9318ccddcd3Sschwarze 	n = mdoc->last->next;
9328ccddcd3Sschwarze 	for (ic = 1;; ic++) {
9338ccddcd3Sschwarze 		roff_elem_alloc(mdoc, n->line, n->pos, tok);
9348ccddcd3Sschwarze 		mdoc->last->flags |= NODE_NOSRC;
9358251afdeSschwarze 		roff_node_relink(mdoc, n);
9368ccddcd3Sschwarze 		n = mdoc->last = mdoc->last->parent;
9378ccddcd3Sschwarze 		mdoc->next = ROFF_NEXT_SIBLING;
9388ccddcd3Sschwarze 		if (n->next == NULL)
9398ccddcd3Sschwarze 			return ic;
9408ccddcd3Sschwarze 		if (ic > 1 || n->next->next != NULL) {
9418ccddcd3Sschwarze 			roff_word_alloc(mdoc, n->line, n->pos, ",");
9428ccddcd3Sschwarze 			mdoc->last->flags |= NODE_DELIMC | NODE_NOSRC;
9438ccddcd3Sschwarze 		}
9448ccddcd3Sschwarze 		n = mdoc->last->next;
9458ccddcd3Sschwarze 		if (n->next == NULL) {
9468ccddcd3Sschwarze 			roff_word_alloc(mdoc, n->line, n->pos, "and");
9478ccddcd3Sschwarze 			mdoc->last->flags |= NODE_NOSRC;
9488ccddcd3Sschwarze 		}
9498ccddcd3Sschwarze 	}
9508ccddcd3Sschwarze }
9518ccddcd3Sschwarze 
9528ccddcd3Sschwarze static void
post_ex(POST_ARGS)9538ccddcd3Sschwarze post_ex(POST_ARGS)
9548ccddcd3Sschwarze {
9558ccddcd3Sschwarze 	struct roff_node	*n;
9568ccddcd3Sschwarze 	int			 ic;
9578ccddcd3Sschwarze 
9588ccddcd3Sschwarze 	post_std(mdoc);
9598ccddcd3Sschwarze 
9608ccddcd3Sschwarze 	n = mdoc->last;
9618ccddcd3Sschwarze 	mdoc->next = ROFF_NEXT_CHILD;
9628ccddcd3Sschwarze 	roff_word_alloc(mdoc, n->line, n->pos, "The");
9638ccddcd3Sschwarze 	mdoc->last->flags |= NODE_NOSRC;
9648ccddcd3Sschwarze 
9658ccddcd3Sschwarze 	if (mdoc->last->next != NULL)
9668ccddcd3Sschwarze 		ic = build_list(mdoc, MDOC_Nm);
9678ccddcd3Sschwarze 	else if (mdoc->meta.name != NULL) {
9688ccddcd3Sschwarze 		roff_elem_alloc(mdoc, n->line, n->pos, MDOC_Nm);
9698ccddcd3Sschwarze 		mdoc->last->flags |= NODE_NOSRC;
9708ccddcd3Sschwarze 		roff_word_alloc(mdoc, n->line, n->pos, mdoc->meta.name);
9718ccddcd3Sschwarze 		mdoc->last->flags |= NODE_NOSRC;
9728ccddcd3Sschwarze 		mdoc->last = mdoc->last->parent;
9738ccddcd3Sschwarze 		mdoc->next = ROFF_NEXT_SIBLING;
9748ccddcd3Sschwarze 		ic = 1;
9758ccddcd3Sschwarze 	} else {
976a5a5f808Sschwarze 		mandoc_msg(MANDOCERR_EX_NONAME, n->line, n->pos, "Ex");
9778ccddcd3Sschwarze 		ic = 0;
9788ccddcd3Sschwarze 	}
9798ccddcd3Sschwarze 
9808ccddcd3Sschwarze 	roff_word_alloc(mdoc, n->line, n->pos,
9818ccddcd3Sschwarze 	    ic > 1 ? "utilities exit\\~0" : "utility exits\\~0");
9828ccddcd3Sschwarze 	mdoc->last->flags |= NODE_NOSRC;
9838ccddcd3Sschwarze 	roff_word_alloc(mdoc, n->line, n->pos,
9848ccddcd3Sschwarze 	    "on success, and\\~>0 if an error occurs.");
9858ccddcd3Sschwarze 	mdoc->last->flags |= NODE_EOS | NODE_NOSRC;
9868ccddcd3Sschwarze 	mdoc->last = n;
9878ccddcd3Sschwarze }
9888ccddcd3Sschwarze 
9898ccddcd3Sschwarze static void
post_lb(POST_ARGS)9908ccddcd3Sschwarze post_lb(POST_ARGS)
9918ccddcd3Sschwarze {
9928ccddcd3Sschwarze 	struct roff_node	*n;
9938ccddcd3Sschwarze 
994fe8e59edSschwarze 	post_delim_nb(mdoc);
99504fbb99fSschwarze 
9968ccddcd3Sschwarze 	n = mdoc->last;
9978ccddcd3Sschwarze 	assert(n->child->type == ROFFT_TEXT);
9988ccddcd3Sschwarze 	mdoc->next = ROFF_NEXT_CHILD;
9998ccddcd3Sschwarze 	roff_word_alloc(mdoc, n->line, n->pos, "library");
10008ccddcd3Sschwarze 	mdoc->last->flags = NODE_NOSRC;
1001965f5c87Sschwarze 	roff_word_alloc(mdoc, n->line, n->pos, "\\(lq");
10028ccddcd3Sschwarze 	mdoc->last->flags = NODE_DELIMO | NODE_NOSRC;
10038ccddcd3Sschwarze 	mdoc->last = mdoc->last->next;
1004965f5c87Sschwarze 	roff_word_alloc(mdoc, n->line, n->pos, "\\(rq");
10058ccddcd3Sschwarze 	mdoc->last->flags = NODE_DELIMC | NODE_NOSRC;
10068ccddcd3Sschwarze 	mdoc->last = n;
10078ccddcd3Sschwarze }
10088ccddcd3Sschwarze 
10098ccddcd3Sschwarze static void
post_rv(POST_ARGS)10108ccddcd3Sschwarze post_rv(POST_ARGS)
10118ccddcd3Sschwarze {
10128ccddcd3Sschwarze 	struct roff_node	*n;
10138ccddcd3Sschwarze 	int			 ic;
10148ccddcd3Sschwarze 
10158ccddcd3Sschwarze 	post_std(mdoc);
10168ccddcd3Sschwarze 
10178ccddcd3Sschwarze 	n = mdoc->last;
10188ccddcd3Sschwarze 	mdoc->next = ROFF_NEXT_CHILD;
10198ccddcd3Sschwarze 	if (n->child != NULL) {
10208ccddcd3Sschwarze 		roff_word_alloc(mdoc, n->line, n->pos, "The");
10218ccddcd3Sschwarze 		mdoc->last->flags |= NODE_NOSRC;
10228ccddcd3Sschwarze 		ic = build_list(mdoc, MDOC_Fn);
10238ccddcd3Sschwarze 		roff_word_alloc(mdoc, n->line, n->pos,
10248ccddcd3Sschwarze 		    ic > 1 ? "functions return" : "function returns");
10258ccddcd3Sschwarze 		mdoc->last->flags |= NODE_NOSRC;
10268ccddcd3Sschwarze 		roff_word_alloc(mdoc, n->line, n->pos,
10278ccddcd3Sschwarze 		    "the value\\~0 if successful;");
10288ccddcd3Sschwarze 	} else
10298ccddcd3Sschwarze 		roff_word_alloc(mdoc, n->line, n->pos, "Upon successful "
10308ccddcd3Sschwarze 		    "completion, the value\\~0 is returned;");
10318ccddcd3Sschwarze 	mdoc->last->flags |= NODE_NOSRC;
10328ccddcd3Sschwarze 
10338ccddcd3Sschwarze 	roff_word_alloc(mdoc, n->line, n->pos, "otherwise "
10348ccddcd3Sschwarze 	    "the value\\~\\-1 is returned and the global variable");
10358ccddcd3Sschwarze 	mdoc->last->flags |= NODE_NOSRC;
10368ccddcd3Sschwarze 	roff_elem_alloc(mdoc, n->line, n->pos, MDOC_Va);
10378ccddcd3Sschwarze 	mdoc->last->flags |= NODE_NOSRC;
10388ccddcd3Sschwarze 	roff_word_alloc(mdoc, n->line, n->pos, "errno");
10398ccddcd3Sschwarze 	mdoc->last->flags |= NODE_NOSRC;
10408ccddcd3Sschwarze 	mdoc->last = mdoc->last->parent;
10418ccddcd3Sschwarze 	mdoc->next = ROFF_NEXT_SIBLING;
10428ccddcd3Sschwarze 	roff_word_alloc(mdoc, n->line, n->pos,
10438ccddcd3Sschwarze 	    "is set to indicate the error.");
10448ccddcd3Sschwarze 	mdoc->last->flags |= NODE_EOS | NODE_NOSRC;
10458ccddcd3Sschwarze 	mdoc->last = n;
10468ccddcd3Sschwarze }
10478ccddcd3Sschwarze 
10488ccddcd3Sschwarze static void
post_std(POST_ARGS)10493e642ba0Sschwarze post_std(POST_ARGS)
1050f73abda9Skristaps {
10513e642ba0Sschwarze 	struct roff_node *n;
1052f73abda9Skristaps 
1053fe8e59edSschwarze 	post_delim(mdoc);
1054fe8e59edSschwarze 
10553e642ba0Sschwarze 	n = mdoc->last;
10563e642ba0Sschwarze 	if (n->args && n->args->argc == 1)
10573e642ba0Sschwarze 		if (n->args->argv[0].arg == MDOC_Std)
105898b8f00aSschwarze 			return;
1059f73abda9Skristaps 
1060a5a5f808Sschwarze 	mandoc_msg(MANDOCERR_ARG_STD, n->line, n->pos,
1061a5a5f808Sschwarze 	    "%s", roff_name[n->tok]);
10626093755cSschwarze }
10636093755cSschwarze 
106498b8f00aSschwarze static void
post_st(POST_ARGS)10658ccddcd3Sschwarze post_st(POST_ARGS)
10668ccddcd3Sschwarze {
10678ccddcd3Sschwarze 	struct roff_node	 *n, *nch;
10688ccddcd3Sschwarze 	const char		 *p;
10698ccddcd3Sschwarze 
10708ccddcd3Sschwarze 	n = mdoc->last;
10718ccddcd3Sschwarze 	nch = n->child;
10728ccddcd3Sschwarze 	assert(nch->type == ROFFT_TEXT);
10738ccddcd3Sschwarze 
10748ccddcd3Sschwarze 	if ((p = mdoc_a2st(nch->string)) == NULL) {
1075a5a5f808Sschwarze 		mandoc_msg(MANDOCERR_ST_BAD,
10768ccddcd3Sschwarze 		    nch->line, nch->pos, "St %s", nch->string);
10778ccddcd3Sschwarze 		roff_node_delete(mdoc, n);
10788ccddcd3Sschwarze 		return;
10798ccddcd3Sschwarze 	}
10808ccddcd3Sschwarze 
10818ccddcd3Sschwarze 	nch->flags |= NODE_NOPRT;
10828ccddcd3Sschwarze 	mdoc->next = ROFF_NEXT_CHILD;
10838ccddcd3Sschwarze 	roff_word_alloc(mdoc, nch->line, nch->pos, p);
10848ccddcd3Sschwarze 	mdoc->last->flags |= NODE_NOSRC;
10858ccddcd3Sschwarze 	mdoc->last= n;
10868ccddcd3Sschwarze }
10878ccddcd3Sschwarze 
10888ccddcd3Sschwarze static void
post_tg(POST_ARGS)108992929bf6Sschwarze post_tg(POST_ARGS)
109092929bf6Sschwarze {
10910ac7e6ecSschwarze 	struct roff_node *n;	/* The .Tg node. */
10920ac7e6ecSschwarze 	struct roff_node *nch;	/* The first child of the .Tg node. */
10930ac7e6ecSschwarze 	struct roff_node *nn;   /* The next node after the .Tg node. */
1094e053e0fdSschwarze 	struct roff_node *np;	/* The parent of the next node. */
10950ac7e6ecSschwarze 	struct roff_node *nt;	/* The TEXT node containing the tag. */
10960ac7e6ecSschwarze 	size_t		  len;	/* The number of bytes in the tag. */
109792929bf6Sschwarze 
10989a542ed3Sschwarze 	/* Find the next node. */
109992929bf6Sschwarze 	n = mdoc->last;
11009a542ed3Sschwarze 	for (nn = n; nn != NULL; nn = nn->parent) {
1101176a26abSschwarze 		if (nn->type != ROFFT_HEAD && nn->type != ROFFT_BODY &&
1102176a26abSschwarze 		    nn->type != ROFFT_TAIL && nn->next != NULL) {
11039a542ed3Sschwarze 			nn = nn->next;
11049a542ed3Sschwarze 			break;
11059a542ed3Sschwarze 		}
11069a542ed3Sschwarze 	}
11079a542ed3Sschwarze 
11080ac7e6ecSschwarze 	/* Find the tag. */
11090ac7e6ecSschwarze 	nt = nch = n->child;
11100ac7e6ecSschwarze 	if (nch == NULL && nn != NULL && nn->child != NULL &&
11110ac7e6ecSschwarze 	    nn->child->type == ROFFT_TEXT)
11120ac7e6ecSschwarze 		nt = nn->child;
11139a542ed3Sschwarze 
11140ac7e6ecSschwarze 	/* Validate the tag. */
11150ac7e6ecSschwarze 	if (nt == NULL || *nt->string == '\0')
111692929bf6Sschwarze 		mandoc_msg(MANDOCERR_MACRO_EMPTY, n->line, n->pos, "Tg");
11170ac7e6ecSschwarze 	if (nt == NULL) {
111892929bf6Sschwarze 		roff_node_delete(mdoc, n);
111992929bf6Sschwarze 		return;
112092929bf6Sschwarze 	}
11210ac7e6ecSschwarze 	len = strcspn(nt->string, " \t\\");
11220ac7e6ecSschwarze 	if (nt->string[len] != '\0')
11230ac7e6ecSschwarze 		mandoc_msg(MANDOCERR_TG_SPC, nt->line,
11240ac7e6ecSschwarze 		    nt->pos + len, "Tg %s", nt->string);
11259a542ed3Sschwarze 
11269a542ed3Sschwarze 	/* Keep only the first argument. */
11270ac7e6ecSschwarze 	if (nch != NULL && nch->next != NULL) {
112892929bf6Sschwarze 		mandoc_msg(MANDOCERR_ARG_EXCESS, nch->next->line,
112992929bf6Sschwarze 		    nch->next->pos, "Tg ... %s", nch->next->string);
113092929bf6Sschwarze 		while (nch->next != NULL)
113192929bf6Sschwarze 			roff_node_delete(mdoc, nch->next);
113292929bf6Sschwarze 	}
11339a542ed3Sschwarze 
11349a542ed3Sschwarze 	/* Drop the macro if the first argument is invalid. */
11350ac7e6ecSschwarze 	if (len == 0 || nt->string[len] != '\0') {
113692929bf6Sschwarze 		roff_node_delete(mdoc, n);
11379a542ed3Sschwarze 		return;
11389a542ed3Sschwarze 	}
11399a542ed3Sschwarze 
11400ac7e6ecSschwarze 	/* By default, tag the .Tg node itself. */
1141e053e0fdSschwarze 	if (nn == NULL || nn->flags & NODE_ID)
11420ac7e6ecSschwarze 		nn = n;
11439a542ed3Sschwarze 
11449a542ed3Sschwarze 	/* Explicit tagging of specific macros. */
11459a542ed3Sschwarze 	switch (nn->tok) {
11469a542ed3Sschwarze 	case MDOC_Sh:
11479a542ed3Sschwarze 	case MDOC_Ss:
11480ac7e6ecSschwarze 	case MDOC_Fo:
1149e053e0fdSschwarze 		nn = nn->head->child == NULL ? n : nn->head;
1150e053e0fdSschwarze 		break;
1151e053e0fdSschwarze 	case MDOC_It:
1152e053e0fdSschwarze 		np = nn->parent;
1153e053e0fdSschwarze 		while (np->tok != MDOC_Bl)
1154e053e0fdSschwarze 			np = np->parent;
1155e053e0fdSschwarze 		switch (np->norm->Bl.type) {
1156e053e0fdSschwarze 		case LIST_column:
1157e053e0fdSschwarze 			break;
1158e053e0fdSschwarze 		case LIST_diag:
1159e053e0fdSschwarze 		case LIST_hang:
1160e053e0fdSschwarze 		case LIST_inset:
1161e053e0fdSschwarze 		case LIST_ohang:
1162e053e0fdSschwarze 		case LIST_tag:
11630ac7e6ecSschwarze 			nn = nn->head;
1164e053e0fdSschwarze 			break;
1165e053e0fdSschwarze 		case LIST_bullet:
1166e053e0fdSschwarze 		case LIST_dash:
1167e053e0fdSschwarze 		case LIST_enum:
1168e053e0fdSschwarze 		case LIST_hyphen:
1169e053e0fdSschwarze 		case LIST_item:
1170e053e0fdSschwarze 			nn = nn->body->child == NULL ? n : nn->body;
1171e053e0fdSschwarze 			break;
1172e053e0fdSschwarze 		default:
1173e053e0fdSschwarze 			abort();
1174e053e0fdSschwarze 		}
1175e053e0fdSschwarze 		break;
1176e053e0fdSschwarze 	case MDOC_Bd:
1177e053e0fdSschwarze 	case MDOC_Bl:
1178e053e0fdSschwarze 	case MDOC_D1:
1179e053e0fdSschwarze 	case MDOC_Dl:
1180e053e0fdSschwarze 		nn = nn->body->child == NULL ? n : nn->body;
1181e053e0fdSschwarze 		break;
1182e053e0fdSschwarze 	case MDOC_Pp:
1183e053e0fdSschwarze 		break;
11840ac7e6ecSschwarze 	case MDOC_Cm:
11850ac7e6ecSschwarze 	case MDOC_Dv:
11860ac7e6ecSschwarze 	case MDOC_Em:
11870ac7e6ecSschwarze 	case MDOC_Er:
11880ac7e6ecSschwarze 	case MDOC_Ev:
11890ac7e6ecSschwarze 	case MDOC_Fl:
11900ac7e6ecSschwarze 	case MDOC_Fn:
11910ac7e6ecSschwarze 	case MDOC_Ic:
11920ac7e6ecSschwarze 	case MDOC_Li:
11930ac7e6ecSschwarze 	case MDOC_Ms:
11940ac7e6ecSschwarze 	case MDOC_No:
11950ac7e6ecSschwarze 	case MDOC_Sy:
1196e053e0fdSschwarze 		if (nn->child == NULL)
1197e053e0fdSschwarze 			nn = n;
11989a542ed3Sschwarze 		break;
11999a542ed3Sschwarze 	default:
12000ac7e6ecSschwarze 		nn = n;
12019a542ed3Sschwarze 		break;
12029a542ed3Sschwarze 	}
12030ac7e6ecSschwarze 	tag_put(nt->string, TAG_MANUAL, nn);
12040ac7e6ecSschwarze 	if (nn != n)
12050ac7e6ecSschwarze 		n->flags |= NODE_NOPRT;
120692929bf6Sschwarze }
120792929bf6Sschwarze 
120892929bf6Sschwarze static void
post_obsolete(POST_ARGS)12093e642ba0Sschwarze post_obsolete(POST_ARGS)
1210551cd4a8Sschwarze {
12113e642ba0Sschwarze 	struct roff_node *n;
1212551cd4a8Sschwarze 
12133e642ba0Sschwarze 	n = mdoc->last;
1214d1982c71Sschwarze 	if (n->type == ROFFT_ELEM || n->type == ROFFT_BLOCK)
1215a5a5f808Sschwarze 		mandoc_msg(MANDOCERR_MACRO_OBS, n->line, n->pos,
1216a5a5f808Sschwarze 		    "%s", roff_name[n->tok]);
1217551cd4a8Sschwarze }
1218551cd4a8Sschwarze 
1219bc205043Sschwarze static void
post_useless(POST_ARGS)1220bc205043Sschwarze post_useless(POST_ARGS)
1221bc205043Sschwarze {
1222bc205043Sschwarze 	struct roff_node *n;
1223bc205043Sschwarze 
1224bc205043Sschwarze 	n = mdoc->last;
1225a5a5f808Sschwarze 	mandoc_msg(MANDOCERR_MACRO_USELESS, n->line, n->pos,
1226a5a5f808Sschwarze 	    "%s", roff_name[n->tok]);
1227bc205043Sschwarze }
1228bc205043Sschwarze 
12298ccddcd3Sschwarze /*
12308ccddcd3Sschwarze  * Block macros.
12318ccddcd3Sschwarze  */
12328ccddcd3Sschwarze 
123398b8f00aSschwarze static void
post_bf(POST_ARGS)1234f73abda9Skristaps post_bf(POST_ARGS)
1235f73abda9Skristaps {
12363a0d07afSschwarze 	struct roff_node *np, *nch;
1237f73abda9Skristaps 
1238769ee804Sschwarze 	/*
1239769ee804Sschwarze 	 * Unlike other data pointers, these are "housed" by the HEAD
1240769ee804Sschwarze 	 * element, which contains the goods.
1241769ee804Sschwarze 	 */
1242769ee804Sschwarze 
1243769ee804Sschwarze 	np = mdoc->last;
1244d1982c71Sschwarze 	if (np->type != ROFFT_HEAD)
1245ae2efdd8Sschwarze 		return;
1246ae2efdd8Sschwarze 
1247d1982c71Sschwarze 	assert(np->parent->type == ROFFT_BLOCK);
1248f051602aSschwarze 	assert(np->parent->tok == MDOC_Bf);
124950d41253Sschwarze 
1250ecb10c32Sschwarze 	/* Check the number of arguments. */
1251f73abda9Skristaps 
1252ecb10c32Sschwarze 	nch = np->child;
1253f051602aSschwarze 	if (np->parent->args == NULL) {
1254f051602aSschwarze 		if (nch == NULL) {
1255a5a5f808Sschwarze 			mandoc_msg(MANDOCERR_BF_NOFONT,
1256bd594191Sschwarze 			    np->line, np->pos, "Bf");
125798b8f00aSschwarze 			return;
125820fa2881Sschwarze 		}
1259ecb10c32Sschwarze 		nch = nch->next;
1260ecb10c32Sschwarze 	}
1261f051602aSschwarze 	if (nch != NULL)
1262a5a5f808Sschwarze 		mandoc_msg(MANDOCERR_ARG_EXCESS,
1263ecb10c32Sschwarze 		    nch->line, nch->pos, "Bf ... %s", nch->string);
1264769ee804Sschwarze 
1265769ee804Sschwarze 	/* Extract argument into data. */
1266769ee804Sschwarze 
1267f051602aSschwarze 	if (np->parent->args != NULL) {
1268f051602aSschwarze 		switch (np->parent->args->argv[0].arg) {
1269f051602aSschwarze 		case MDOC_Emphasis:
12708c62fbf5Sschwarze 			np->norm->Bf.font = FONT_Em;
1271f051602aSschwarze 			break;
1272f051602aSschwarze 		case MDOC_Literal:
12738c62fbf5Sschwarze 			np->norm->Bf.font = FONT_Li;
1274f051602aSschwarze 			break;
1275f051602aSschwarze 		case MDOC_Symbolic:
12768c62fbf5Sschwarze 			np->norm->Bf.font = FONT_Sy;
1277f051602aSschwarze 			break;
1278f051602aSschwarze 		default:
1279769ee804Sschwarze 			abort();
1280f051602aSschwarze 		}
128198b8f00aSschwarze 		return;
1282769ee804Sschwarze 	}
1283769ee804Sschwarze 
1284769ee804Sschwarze 	/* Extract parameter into data. */
1285769ee804Sschwarze 
1286f051602aSschwarze 	if ( ! strcmp(np->child->string, "Em"))
12878c62fbf5Sschwarze 		np->norm->Bf.font = FONT_Em;
1288f051602aSschwarze 	else if ( ! strcmp(np->child->string, "Li"))
12898c62fbf5Sschwarze 		np->norm->Bf.font = FONT_Li;
1290f051602aSschwarze 	else if ( ! strcmp(np->child->string, "Sy"))
12918c62fbf5Sschwarze 		np->norm->Bf.font = FONT_Sy;
129220fa2881Sschwarze 	else
1293a5a5f808Sschwarze 		mandoc_msg(MANDOCERR_BF_BADFONT, np->child->line,
1294a5a5f808Sschwarze 		    np->child->pos, "Bf %s", np->child->string);
1295f73abda9Skristaps }
1296f73abda9Skristaps 
129798b8f00aSschwarze static void
post_fname(POST_ARGS)12980c5064e3Sschwarze post_fname(POST_ARGS)
12990c5064e3Sschwarze {
13000ac7e6ecSschwarze 	struct roff_node	*n, *nch;
13010ff14c71Sschwarze 	const char		*cp;
13020c5064e3Sschwarze 	size_t			 pos;
13030c5064e3Sschwarze 
13040ac7e6ecSschwarze 	n = mdoc->last;
13050ac7e6ecSschwarze 	nch = n->child;
13060ac7e6ecSschwarze 	cp = nch->string;
13078dbab69bSschwarze 	if (*cp == '(') {
13088dbab69bSschwarze 		if (cp[strlen(cp + 1)] == ')')
13098dbab69bSschwarze 			return;
13108dbab69bSschwarze 		pos = 0;
13118dbab69bSschwarze 	} else {
13128dbab69bSschwarze 		pos = strcspn(cp, "()");
13130ac7e6ecSschwarze 		if (cp[pos] == '\0') {
13140ac7e6ecSschwarze 			if (n->sec == SEC_DESCRIPTION ||
13150ac7e6ecSschwarze 			    n->sec == SEC_CUSTOM)
13160ac7e6ecSschwarze 				tag_put(NULL, fn_prio++, n);
13178dbab69bSschwarze 			return;
13188dbab69bSschwarze 		}
13190ac7e6ecSschwarze 	}
13200ac7e6ecSschwarze 	mandoc_msg(MANDOCERR_FN_PAREN, nch->line, nch->pos + pos, "%s", cp);
13210c5064e3Sschwarze }
13220c5064e3Sschwarze 
132398b8f00aSschwarze static void
post_fn(POST_ARGS)13240c5064e3Sschwarze post_fn(POST_ARGS)
13250c5064e3Sschwarze {
13260c5064e3Sschwarze 	post_fname(mdoc);
13270c5064e3Sschwarze 	post_fa(mdoc);
13280c5064e3Sschwarze }
13290c5064e3Sschwarze 
133098b8f00aSschwarze static void
post_fo(POST_ARGS)1331753701eeSschwarze post_fo(POST_ARGS)
1332753701eeSschwarze {
13333a0d07afSschwarze 	const struct roff_node	*n;
1334753701eeSschwarze 
1335afcd1f03Sschwarze 	n = mdoc->last;
1336afcd1f03Sschwarze 
1337d1982c71Sschwarze 	if (n->type != ROFFT_HEAD)
1338afcd1f03Sschwarze 		return;
1339afcd1f03Sschwarze 
1340afcd1f03Sschwarze 	if (n->child == NULL) {
1341a5a5f808Sschwarze 		mandoc_msg(MANDOCERR_FO_NOHEAD, n->line, n->pos, "Fo");
1342afcd1f03Sschwarze 		return;
1343afcd1f03Sschwarze 	}
1344afcd1f03Sschwarze 	if (n->child != n->last) {
1345a5a5f808Sschwarze 		mandoc_msg(MANDOCERR_ARG_EXCESS,
1346afcd1f03Sschwarze 		    n->child->next->line, n->child->next->pos,
1347afcd1f03Sschwarze 		    "Fo ... %s", n->child->next->string);
1348afcd1f03Sschwarze 		while (n->child != n->last)
1349fa2127f9Sschwarze 			roff_node_delete(mdoc, n->last);
1350fe8e59edSschwarze 	} else
1351fe8e59edSschwarze 		post_delim(mdoc);
1352afcd1f03Sschwarze 
13530c5064e3Sschwarze 	post_fname(mdoc);
1354753701eeSschwarze }
1355753701eeSschwarze 
135698b8f00aSschwarze static void
post_fa(POST_ARGS)13577e92c062Sschwarze post_fa(POST_ARGS)
13587e92c062Sschwarze {
13593a0d07afSschwarze 	const struct roff_node *n;
13607e92c062Sschwarze 	const char *cp;
13617e92c062Sschwarze 
13627e92c062Sschwarze 	for (n = mdoc->last->child; n != NULL; n = n->next) {
13637e92c062Sschwarze 		for (cp = n->string; *cp != '\0'; cp++) {
13647e92c062Sschwarze 			/* Ignore callbacks and alterations. */
13657e92c062Sschwarze 			if (*cp == '(' || *cp == '{')
13667e92c062Sschwarze 				break;
13677e92c062Sschwarze 			if (*cp != ',')
13687e92c062Sschwarze 				continue;
1369a5a5f808Sschwarze 			mandoc_msg(MANDOCERR_FA_COMMA, n->line,
1370a5a5f808Sschwarze 			    n->pos + (int)(cp - n->string), "%s", n->string);
13717e92c062Sschwarze 			break;
13727e92c062Sschwarze 		}
13737e92c062Sschwarze 	}
1374fe8e59edSschwarze 	post_delim_nb(mdoc);
13757e92c062Sschwarze }
13767e92c062Sschwarze 
137798b8f00aSschwarze static void
post_nm(POST_ARGS)1378f73abda9Skristaps post_nm(POST_ARGS)
1379f73abda9Skristaps {
13803a0d07afSschwarze 	struct roff_node	*n;
13812d266539Sschwarze 
13822d266539Sschwarze 	n = mdoc->last;
13832d266539Sschwarze 
13842ab19127Sschwarze 	if (n->sec == SEC_NAME && n->child != NULL &&
13852ab19127Sschwarze 	    n->child->type == ROFFT_TEXT && mdoc->meta.msec != NULL)
138652d11c96Sschwarze 		mandoc_xr_add(mdoc->meta.msec, n->child->string, -1, -1);
138752d11c96Sschwarze 
13887c539ecbSschwarze 	if (n->last != NULL && n->last->tok == MDOC_Pp)
13898251afdeSschwarze 		roff_node_relink(mdoc, n->last);
139020fa2881Sschwarze 
1391f27faaccSschwarze 	if (mdoc->meta.name == NULL)
1392423631c9Sschwarze 		deroff(&mdoc->meta.name, n);
139320fa2881Sschwarze 
1394f27faaccSschwarze 	if (mdoc->meta.name == NULL ||
1395f27faaccSschwarze 	    (mdoc->lastsec == SEC_NAME && n->child == NULL))
1396a5a5f808Sschwarze 		mandoc_msg(MANDOCERR_NM_NONAME, n->line, n->pos, "Nm");
139790957cf5Sschwarze 
1398fe8e59edSschwarze 	switch (n->type) {
1399fe8e59edSschwarze 	case ROFFT_ELEM:
1400fe8e59edSschwarze 		post_delim_nb(mdoc);
1401fe8e59edSschwarze 		break;
1402fe8e59edSschwarze 	case ROFFT_HEAD:
140304fbb99fSschwarze 		post_delim(mdoc);
1404fe8e59edSschwarze 		break;
1405fe8e59edSschwarze 	default:
1406fe8e59edSschwarze 		return;
1407fe8e59edSschwarze 	}
140804fbb99fSschwarze 
1409fe8e59edSschwarze 	if ((n->child != NULL && n->child->type == ROFFT_TEXT) ||
141090957cf5Sschwarze 	    mdoc->meta.name == NULL)
141190957cf5Sschwarze 		return;
141290957cf5Sschwarze 
141390957cf5Sschwarze 	mdoc->next = ROFF_NEXT_CHILD;
141490957cf5Sschwarze 	roff_word_alloc(mdoc, n->line, n->pos, mdoc->meta.name);
141590957cf5Sschwarze 	mdoc->last->flags |= NODE_NOSRC;
141690957cf5Sschwarze 	mdoc->last = n;
141720fa2881Sschwarze }
141820fa2881Sschwarze 
141998b8f00aSschwarze static void
post_nd(POST_ARGS)1420753701eeSschwarze post_nd(POST_ARGS)
1421753701eeSschwarze {
14223a0d07afSschwarze 	struct roff_node	*n;
1423753701eeSschwarze 
14241570daf1Sschwarze 	n = mdoc->last;
14251570daf1Sschwarze 
1426d1982c71Sschwarze 	if (n->type != ROFFT_BODY)
14271570daf1Sschwarze 		return;
14281570daf1Sschwarze 
142956e9e976Sschwarze 	if (n->sec != SEC_NAME)
1430a5a5f808Sschwarze 		mandoc_msg(MANDOCERR_ND_LATE, n->line, n->pos, "Nd");
143156e9e976Sschwarze 
14321570daf1Sschwarze 	if (n->child == NULL)
1433a5a5f808Sschwarze 		mandoc_msg(MANDOCERR_ND_EMPTY, n->line, n->pos, "Nd");
1434fe8e59edSschwarze 	else
1435fe8e59edSschwarze 		post_delim(mdoc);
14361570daf1Sschwarze 
143798b8f00aSschwarze 	post_hyph(mdoc);
1438753701eeSschwarze }
1439753701eeSschwarze 
144098b8f00aSschwarze static void
post_display(POST_ARGS)14413e642ba0Sschwarze post_display(POST_ARGS)
1442753701eeSschwarze {
14433e642ba0Sschwarze 	struct roff_node *n, *np;
1444753701eeSschwarze 
1445b7530f2fSschwarze 	n = mdoc->last;
14463e642ba0Sschwarze 	switch (n->type) {
14473e642ba0Sschwarze 	case ROFFT_BODY:
14484b5c4138Sschwarze 		if (n->end != ENDBODY_NOT) {
1449611d9138Sschwarze 			if (n->tok == MDOC_Bd &&
1450611d9138Sschwarze 			    n->body->parent->args == NULL)
14514b5c4138Sschwarze 				roff_node_delete(mdoc, n);
14524b5c4138Sschwarze 		} else if (n->child == NULL)
1453a5a5f808Sschwarze 			mandoc_msg(MANDOCERR_BLK_EMPTY, n->line, n->pos,
1454a5a5f808Sschwarze 			    "%s", roff_name[n->tok]);
14553e642ba0Sschwarze 		else if (n->tok == MDOC_D1)
14563e642ba0Sschwarze 			post_hyph(mdoc);
14573e642ba0Sschwarze 		break;
14583e642ba0Sschwarze 	case ROFFT_BLOCK:
14593e642ba0Sschwarze 		if (n->tok == MDOC_Bd) {
1460a43e24e2Sschwarze 			if (n->args == NULL) {
1461a43e24e2Sschwarze 				mandoc_msg(MANDOCERR_BD_NOARG,
1462a5a5f808Sschwarze 				    n->line, n->pos, "Bd");
1463a43e24e2Sschwarze 				mdoc->next = ROFF_NEXT_SIBLING;
1464a43e24e2Sschwarze 				while (n->body->child != NULL)
14658251afdeSschwarze 					roff_node_relink(mdoc,
1466a43e24e2Sschwarze 					    n->body->child);
1467a43e24e2Sschwarze 				roff_node_delete(mdoc, n);
1468a43e24e2Sschwarze 				break;
1469a43e24e2Sschwarze 			}
14703e642ba0Sschwarze 			post_bd(mdoc);
14713e642ba0Sschwarze 			post_prevpar(mdoc);
14723e642ba0Sschwarze 		}
14733e642ba0Sschwarze 		for (np = n->parent; np != NULL; np = np->parent) {
14743e642ba0Sschwarze 			if (np->type == ROFFT_BLOCK && np->tok == MDOC_Bd) {
1475a5a5f808Sschwarze 				mandoc_msg(MANDOCERR_BD_NEST, n->line,
1476a5a5f808Sschwarze 				    n->pos, "%s in Bd", roff_name[n->tok]);
14773e642ba0Sschwarze 				break;
14783e642ba0Sschwarze 			}
14793e642ba0Sschwarze 		}
14803e642ba0Sschwarze 		break;
14813e642ba0Sschwarze 	default:
14823e642ba0Sschwarze 		break;
14833e642ba0Sschwarze 	}
148420fa2881Sschwarze }
148520fa2881Sschwarze 
148698b8f00aSschwarze static void
post_defaults(POST_ARGS)148720fa2881Sschwarze post_defaults(POST_ARGS)
148820fa2881Sschwarze {
14890ac7e6ecSschwarze 	struct roff_node *n;
149020fa2881Sschwarze 
14910ac7e6ecSschwarze 	n = mdoc->last;
14920ac7e6ecSschwarze 	if (n->child != NULL) {
1493fe8e59edSschwarze 		post_delim_nb(mdoc);
149404fbb99fSschwarze 		return;
149504fbb99fSschwarze 	}
1496396853b5Sschwarze 	mdoc->next = ROFF_NEXT_CHILD;
14970ac7e6ecSschwarze 	switch (n->tok) {
14980ac7e6ecSschwarze 	case MDOC_Ar:
14990ac7e6ecSschwarze 		roff_word_alloc(mdoc, n->line, n->pos, "file");
150043808411Sschwarze 		mdoc->last->flags |= NODE_NOSRC;
15010ac7e6ecSschwarze 		roff_word_alloc(mdoc, n->line, n->pos, "...");
150220fa2881Sschwarze 		break;
150349aff9f8Sschwarze 	case MDOC_Pa:
150449aff9f8Sschwarze 	case MDOC_Mt:
15050ac7e6ecSschwarze 		roff_word_alloc(mdoc, n->line, n->pos, "~");
150620fa2881Sschwarze 		break;
150720fa2881Sschwarze 	default:
150820fa2881Sschwarze 		abort();
1509f73abda9Skristaps 	}
15100ac7e6ecSschwarze 	mdoc->last->flags |= NODE_NOSRC;
15110ac7e6ecSschwarze 	mdoc->last = n;
151220fa2881Sschwarze }
1513f73abda9Skristaps 
151498b8f00aSschwarze static void
post_at(POST_ARGS)1515f73abda9Skristaps post_at(POST_ARGS)
1516f73abda9Skristaps {
15173af8e8d7Sschwarze 	struct roff_node	*n, *nch;
15183af8e8d7Sschwarze 	const char		*att;
151920fa2881Sschwarze 
1520753701eeSschwarze 	n = mdoc->last;
15213af8e8d7Sschwarze 	nch = n->child;
1522753701eeSschwarze 
152320fa2881Sschwarze 	/*
152420fa2881Sschwarze 	 * If we have a child, look it up in the standard keys.  If a
152520fa2881Sschwarze 	 * key exist, use that instead of the child; if it doesn't,
152620fa2881Sschwarze 	 * prefix "AT&T UNIX " to the existing data.
152720fa2881Sschwarze 	 */
1528f73abda9Skristaps 
15293af8e8d7Sschwarze 	att = NULL;
15303af8e8d7Sschwarze 	if (nch != NULL && ((att = mdoc_a2att(nch->string)) == NULL))
1531a5a5f808Sschwarze 		mandoc_msg(MANDOCERR_AT_BAD,
15323af8e8d7Sschwarze 		    nch->line, nch->pos, "At %s", nch->string);
1533f73abda9Skristaps 
15343af8e8d7Sschwarze 	mdoc->next = ROFF_NEXT_CHILD;
15353af8e8d7Sschwarze 	if (att != NULL) {
15363af8e8d7Sschwarze 		roff_word_alloc(mdoc, nch->line, nch->pos, att);
15373af8e8d7Sschwarze 		nch->flags |= NODE_NOPRT;
15383af8e8d7Sschwarze 	} else
15393af8e8d7Sschwarze 		roff_word_alloc(mdoc, n->line, n->pos, "AT&T UNIX");
15403af8e8d7Sschwarze 	mdoc->last->flags |= NODE_NOSRC;
15413af8e8d7Sschwarze 	mdoc->last = n;
154220fa2881Sschwarze }
1543f73abda9Skristaps 
154498b8f00aSschwarze static void
post_an(POST_ARGS)1545f73abda9Skristaps post_an(POST_ARGS)
1546f73abda9Skristaps {
15473a0d07afSschwarze 	struct roff_node *np, *nch;
1548f73abda9Skristaps 
15493e642ba0Sschwarze 	post_an_norm(mdoc);
15503e642ba0Sschwarze 
1551769ee804Sschwarze 	np = mdoc->last;
1552cba50636Sschwarze 	nch = np->child;
1553cba50636Sschwarze 	if (np->norm->An.auth == AUTH__NONE) {
1554cba50636Sschwarze 		if (nch == NULL)
1555a5a5f808Sschwarze 			mandoc_msg(MANDOCERR_MACRO_EMPTY,
1556cba50636Sschwarze 			    np->line, np->pos, "An");
155704fbb99fSschwarze 		else
1558fe8e59edSschwarze 			post_delim_nb(mdoc);
1559cba50636Sschwarze 	} else if (nch != NULL)
1560a5a5f808Sschwarze 		mandoc_msg(MANDOCERR_ARG_EXCESS,
15613798fb25Sschwarze 		    nch->line, nch->pos, "An ... %s", nch->string);
1562f73abda9Skristaps }
1563f73abda9Skristaps 
156498b8f00aSschwarze static void
post_em(POST_ARGS)15650ac7e6ecSschwarze post_em(POST_ARGS)
15660ac7e6ecSschwarze {
15670ac7e6ecSschwarze 	post_tag(mdoc);
15680ac7e6ecSschwarze 	tag_put(NULL, TAG_FALLBACK, mdoc->last);
15690ac7e6ecSschwarze }
15700ac7e6ecSschwarze 
15710ac7e6ecSschwarze static void
post_en(POST_ARGS)1572551cd4a8Sschwarze post_en(POST_ARGS)
1573551cd4a8Sschwarze {
15743e642ba0Sschwarze 	post_obsolete(mdoc);
1575d1982c71Sschwarze 	if (mdoc->last->type == ROFFT_BLOCK)
1576551cd4a8Sschwarze 		mdoc->last->norm->Es = mdoc->last_es;
1577551cd4a8Sschwarze }
1578551cd4a8Sschwarze 
157998b8f00aSschwarze static void
post_er(POST_ARGS)15800ac7e6ecSschwarze post_er(POST_ARGS)
15810ac7e6ecSschwarze {
15820ac7e6ecSschwarze 	struct roff_node *n;
15830ac7e6ecSschwarze 
15840ac7e6ecSschwarze 	n = mdoc->last;
15850ac7e6ecSschwarze 	if (n->sec == SEC_ERRORS &&
15860ac7e6ecSschwarze 	    (n->parent->tok == MDOC_It ||
15870ac7e6ecSschwarze 	     (n->parent->tok == MDOC_Bq &&
15880ac7e6ecSschwarze 	      n->parent->parent->parent->tok == MDOC_It)))
15890ac7e6ecSschwarze 		tag_put(NULL, TAG_STRONG, n);
15900ac7e6ecSschwarze 	post_delim_nb(mdoc);
15910ac7e6ecSschwarze }
15920ac7e6ecSschwarze 
15930ac7e6ecSschwarze static void
post_tag(POST_ARGS)15940ac7e6ecSschwarze post_tag(POST_ARGS)
15950ac7e6ecSschwarze {
15960ac7e6ecSschwarze 	struct roff_node *n;
15970ac7e6ecSschwarze 
15980ac7e6ecSschwarze 	n = mdoc->last;
15990ac7e6ecSschwarze 	if ((n->prev == NULL ||
16000ac7e6ecSschwarze 	     (n->prev->type == ROFFT_TEXT &&
16010ac7e6ecSschwarze 	      strcmp(n->prev->string, "|") == 0)) &&
16020ac7e6ecSschwarze 	    (n->parent->tok == MDOC_It ||
16030ac7e6ecSschwarze 	     (n->parent->tok == MDOC_Xo &&
16040ac7e6ecSschwarze 	      n->parent->parent->prev == NULL &&
16050ac7e6ecSschwarze 	      n->parent->parent->parent->tok == MDOC_It)))
16060ac7e6ecSschwarze 		tag_put(NULL, TAG_STRONG, n);
16070ac7e6ecSschwarze 	post_delim_nb(mdoc);
16080ac7e6ecSschwarze }
16090ac7e6ecSschwarze 
16100ac7e6ecSschwarze static void
post_es(POST_ARGS)1611551cd4a8Sschwarze post_es(POST_ARGS)
1612551cd4a8Sschwarze {
16133e642ba0Sschwarze 	post_obsolete(mdoc);
1614551cd4a8Sschwarze 	mdoc->last_es = mdoc->last;
1615551cd4a8Sschwarze }
1616551cd4a8Sschwarze 
161798b8f00aSschwarze static void
post_fl(POST_ARGS)1618b952e091Sschwarze post_fl(POST_ARGS)
1619b952e091Sschwarze {
1620b952e091Sschwarze 	struct roff_node	*n;
1621b952e091Sschwarze 	char			*cp;
1622b952e091Sschwarze 
1623b952e091Sschwarze 	/*
1624b952e091Sschwarze 	 * Transform ".Fl Fl long" to ".Fl \-long",
1625b952e091Sschwarze 	 * resulting for example in better HTML output.
1626b952e091Sschwarze 	 */
1627b952e091Sschwarze 
1628b952e091Sschwarze 	n = mdoc->last;
1629b952e091Sschwarze 	if (n->prev != NULL && n->prev->tok == MDOC_Fl &&
1630b952e091Sschwarze 	    n->prev->child == NULL && n->child != NULL &&
1631b952e091Sschwarze 	    (n->flags & NODE_LINE) == 0) {
1632b952e091Sschwarze 		mandoc_asprintf(&cp, "\\-%s", n->child->string);
1633b952e091Sschwarze 		free(n->child->string);
1634b952e091Sschwarze 		n->child->string = cp;
1635b952e091Sschwarze 		roff_node_delete(mdoc, n->prev);
1636b952e091Sschwarze 	}
1637b952e091Sschwarze 	post_tag(mdoc);
1638b952e091Sschwarze }
1639b952e091Sschwarze 
1640b952e091Sschwarze static void
post_xx(POST_ARGS)1641816c3c54Sschwarze post_xx(POST_ARGS)
1642816c3c54Sschwarze {
1643816c3c54Sschwarze 	struct roff_node	*n;
1644816c3c54Sschwarze 	const char		*os;
164507ff2819Sschwarze 	char			*v;
1646816c3c54Sschwarze 
1647fe8e59edSschwarze 	post_delim_nb(mdoc);
164804fbb99fSschwarze 
1649816c3c54Sschwarze 	n = mdoc->last;
1650816c3c54Sschwarze 	switch (n->tok) {
1651816c3c54Sschwarze 	case MDOC_Bsx:
1652816c3c54Sschwarze 		os = "BSD/OS";
1653816c3c54Sschwarze 		break;
1654816c3c54Sschwarze 	case MDOC_Dx:
1655816c3c54Sschwarze 		os = "DragonFly";
1656816c3c54Sschwarze 		break;
1657816c3c54Sschwarze 	case MDOC_Fx:
1658816c3c54Sschwarze 		os = "FreeBSD";
1659816c3c54Sschwarze 		break;
1660816c3c54Sschwarze 	case MDOC_Nx:
1661816c3c54Sschwarze 		os = "NetBSD";
166207ff2819Sschwarze 		if (n->child == NULL)
166307ff2819Sschwarze 			break;
166407ff2819Sschwarze 		v = n->child->string;
166507ff2819Sschwarze 		if ((v[0] != '0' && v[0] != '1') || v[1] != '.' ||
166607ff2819Sschwarze 		    v[2] < '0' || v[2] > '9' ||
166707ff2819Sschwarze 		    v[3] < 'a' || v[3] > 'z' || v[4] != '\0')
166807ff2819Sschwarze 			break;
166907ff2819Sschwarze 		n->child->flags |= NODE_NOPRT;
167007ff2819Sschwarze 		mdoc->next = ROFF_NEXT_CHILD;
167107ff2819Sschwarze 		roff_word_alloc(mdoc, n->child->line, n->child->pos, v);
167207ff2819Sschwarze 		v = mdoc->last->string;
167307ff2819Sschwarze 		v[3] = toupper((unsigned char)v[3]);
167407ff2819Sschwarze 		mdoc->last->flags |= NODE_NOSRC;
167507ff2819Sschwarze 		mdoc->last = n;
1676816c3c54Sschwarze 		break;
1677816c3c54Sschwarze 	case MDOC_Ox:
1678816c3c54Sschwarze 		os = "OpenBSD";
1679816c3c54Sschwarze 		break;
1680816c3c54Sschwarze 	case MDOC_Ux:
1681816c3c54Sschwarze 		os = "UNIX";
1682816c3c54Sschwarze 		break;
1683816c3c54Sschwarze 	default:
1684816c3c54Sschwarze 		abort();
1685816c3c54Sschwarze 	}
1686816c3c54Sschwarze 	mdoc->next = ROFF_NEXT_CHILD;
1687816c3c54Sschwarze 	roff_word_alloc(mdoc, n->line, n->pos, os);
1688816c3c54Sschwarze 	mdoc->last->flags |= NODE_NOSRC;
1689816c3c54Sschwarze 	mdoc->last = n;
1690816c3c54Sschwarze }
1691816c3c54Sschwarze 
1692816c3c54Sschwarze static void
post_it(POST_ARGS)1693f73abda9Skristaps post_it(POST_ARGS)
1694f73abda9Skristaps {
16953a0d07afSschwarze 	struct roff_node *nbl, *nit, *nch;
169619a69263Sschwarze 	int		  i, cols;
16976093755cSschwarze 	enum mdoc_list	  lt;
1698f73abda9Skristaps 
16993e642ba0Sschwarze 	post_prevpar(mdoc);
17003e642ba0Sschwarze 
17019530682eSschwarze 	nit = mdoc->last;
1702d1982c71Sschwarze 	if (nit->type != ROFFT_BLOCK)
170398b8f00aSschwarze 		return;
1704f73abda9Skristaps 
17059530682eSschwarze 	nbl = nit->parent->parent;
17069530682eSschwarze 	lt = nbl->norm->Bl.type;
17076093755cSschwarze 
17086093755cSschwarze 	switch (lt) {
170949aff9f8Sschwarze 	case LIST_tag:
171049aff9f8Sschwarze 	case LIST_hang:
171149aff9f8Sschwarze 	case LIST_ohang:
171249aff9f8Sschwarze 	case LIST_inset:
171349aff9f8Sschwarze 	case LIST_diag:
1714d26e35c2Sschwarze 		if (nit->head->child == NULL)
1715a5a5f808Sschwarze 			mandoc_msg(MANDOCERR_IT_NOHEAD,
1716a5a5f808Sschwarze 			    nit->line, nit->pos, "Bl -%s It",
17179530682eSschwarze 			    mdoc_argnames[nbl->args->argv[0].arg]);
1718f73abda9Skristaps 		break;
171949aff9f8Sschwarze 	case LIST_bullet:
172049aff9f8Sschwarze 	case LIST_dash:
172149aff9f8Sschwarze 	case LIST_enum:
172249aff9f8Sschwarze 	case LIST_hyphen:
1723d26e35c2Sschwarze 		if (nit->body == NULL || nit->body->child == NULL)
1724a5a5f808Sschwarze 			mandoc_msg(MANDOCERR_IT_NOBODY,
1725a5a5f808Sschwarze 			    nit->line, nit->pos, "Bl -%s It",
172666788495Sschwarze 			    mdoc_argnames[nbl->args->argv[0].arg]);
1727f73abda9Skristaps 		/* FALLTHROUGH */
172849aff9f8Sschwarze 	case LIST_item:
172925f8eeb1Sschwarze 		if ((nch = nit->head->child) != NULL)
1730a5a5f808Sschwarze 			mandoc_msg(MANDOCERR_ARG_SKIP,
173114a309e3Sschwarze 			    nit->line, nit->pos, "It %s",
17320ac7e6ecSschwarze 			    nch->type == ROFFT_TEXT ? nch->string :
17330ac7e6ecSschwarze 			    roff_name[nch->tok]);
1734f73abda9Skristaps 		break;
173549aff9f8Sschwarze 	case LIST_column:
17369530682eSschwarze 		cols = (int)nbl->norm->Bl.ncols;
17376093755cSschwarze 
1738d26e35c2Sschwarze 		assert(nit->head->child == NULL);
17396093755cSschwarze 
174080f58981Sschwarze 		if (nit->head->next->child == NULL &&
174180f58981Sschwarze 		    nit->head->next->next == NULL) {
1742a5a5f808Sschwarze 			mandoc_msg(MANDOCERR_MACRO_EMPTY,
174380f58981Sschwarze 			    nit->line, nit->pos, "It");
174480f58981Sschwarze 			roff_node_delete(mdoc, nit);
174580f58981Sschwarze 			break;
174680f58981Sschwarze 		}
174753292e81Sschwarze 
174880f58981Sschwarze 		i = 0;
174980f58981Sschwarze 		for (nch = nit->child; nch != NULL; nch = nch->next) {
175080f58981Sschwarze 			if (nch->type != ROFFT_BODY)
175180f58981Sschwarze 				continue;
175280f58981Sschwarze 			if (i++ && nch->flags & NODE_LINE)
1753a5a5f808Sschwarze 				mandoc_msg(MANDOCERR_TA_LINE,
175480f58981Sschwarze 				    nch->line, nch->pos, "Ta");
175580f58981Sschwarze 		}
1756e14c4c11Sschwarze 		if (i < cols || i > cols + 1)
1757a5a5f808Sschwarze 			mandoc_msg(MANDOCERR_BL_COL, nit->line, nit->pos,
1758bce599dfSschwarze 			    "%d columns, %d cells", cols, i);
175980f58981Sschwarze 		else if (nit->head->next->child != NULL &&
176085490b9fSschwarze 		    nit->head->next->child->flags & NODE_LINE)
1761a5a5f808Sschwarze 			mandoc_msg(MANDOCERR_IT_NOARG,
176280f58981Sschwarze 			    nit->line, nit->pos, "Bl -column It");
1763e14c4c11Sschwarze 		break;
1764f73abda9Skristaps 	default:
176566788495Sschwarze 		abort();
1766f73abda9Skristaps 	}
1767f73abda9Skristaps }
1768f73abda9Skristaps 
176998b8f00aSschwarze static void
post_bl_block(POST_ARGS)177020fa2881Sschwarze post_bl_block(POST_ARGS)
177120fa2881Sschwarze {
17723a0d07afSschwarze 	struct roff_node *n, *ni, *nc;
177320fa2881Sschwarze 
17743e642ba0Sschwarze 	post_prevpar(mdoc);
17753e642ba0Sschwarze 
177620fa2881Sschwarze 	n = mdoc->last;
1777f051602aSschwarze 	for (ni = n->body->child; ni != NULL; ni = ni->next) {
1778f051602aSschwarze 		if (ni->body == NULL)
1779bb99f0faSschwarze 			continue;
1780bb99f0faSschwarze 		nc = ni->body->last;
1781f051602aSschwarze 		while (nc != NULL) {
1782bb99f0faSschwarze 			switch (nc->tok) {
178349aff9f8Sschwarze 			case MDOC_Pp:
178429478532Sschwarze 			case ROFF_br:
1785bb99f0faSschwarze 				break;
1786bb99f0faSschwarze 			default:
1787bb99f0faSschwarze 				nc = NULL;
1788bb99f0faSschwarze 				continue;
1789bb99f0faSschwarze 			}
1790f051602aSschwarze 			if (ni->next == NULL) {
1791a5a5f808Sschwarze 				mandoc_msg(MANDOCERR_PAR_MOVE, nc->line,
1792a5a5f808Sschwarze 				    nc->pos, "%s", roff_name[nc->tok]);
17938251afdeSschwarze 				roff_node_relink(mdoc, nc);
1794f051602aSschwarze 			} else if (n->norm->Bl.comp == 0 &&
1795f051602aSschwarze 			    n->norm->Bl.type != LIST_column) {
1796a5a5f808Sschwarze 				mandoc_msg(MANDOCERR_PAR_SKIP,
1797a5a5f808Sschwarze 				    nc->line, nc->pos,
179814a309e3Sschwarze 				    "%s before It", roff_name[nc->tok]);
1799fa2127f9Sschwarze 				roff_node_delete(mdoc, nc);
1800bb99f0faSschwarze 			} else
1801bb99f0faSschwarze 				break;
1802bb99f0faSschwarze 			nc = ni->body->last;
1803bb99f0faSschwarze 		}
1804bb99f0faSschwarze 	}
180520fa2881Sschwarze }
180620fa2881Sschwarze 
180790d52a15Sschwarze /*
180890d52a15Sschwarze  * If the argument of -offset or -width is a macro,
180990d52a15Sschwarze  * replace it with the associated default width.
181090d52a15Sschwarze  */
18116050a3daSschwarze static void
rewrite_macro2len(struct roff_man * mdoc,char ** arg)18126050a3daSschwarze rewrite_macro2len(struct roff_man *mdoc, char **arg)
181320fa2881Sschwarze {
181420fa2881Sschwarze 	size_t		  width;
181514a309e3Sschwarze 	enum roff_tok	  tok;
181620fa2881Sschwarze 
181790d52a15Sschwarze 	if (*arg == NULL)
181890d52a15Sschwarze 		return;
181990d52a15Sschwarze 	else if ( ! strcmp(*arg, "Ds"))
182020fa2881Sschwarze 		width = 6;
18216050a3daSschwarze 	else if ((tok = roffhash_find(mdoc->mdocmac, *arg, 0)) == TOKEN_NONE)
182290d52a15Sschwarze 		return;
1823dc0d8bb2Sschwarze 	else
1824dc0d8bb2Sschwarze 		width = macro2len(tok);
182520fa2881Sschwarze 
182690d52a15Sschwarze 	free(*arg);
182790d52a15Sschwarze 	mandoc_asprintf(arg, "%zun", width);
182820fa2881Sschwarze }
182920fa2881Sschwarze 
183098b8f00aSschwarze static void
post_bl_head(POST_ARGS)1831395185ccSschwarze post_bl_head(POST_ARGS)
1832395185ccSschwarze {
18333a0d07afSschwarze 	struct roff_node *nbl, *nh, *nch, *nnext;
1834f5174743Sschwarze 	struct mdoc_argv *argv;
183520fa2881Sschwarze 	int		  i, j;
1836395185ccSschwarze 
18373e642ba0Sschwarze 	post_bl_norm(mdoc);
18382588c917Sschwarze 
18393e642ba0Sschwarze 	nh = mdoc->last;
18402588c917Sschwarze 	if (nh->norm->Bl.type != LIST_column) {
18412588c917Sschwarze 		if ((nch = nh->child) == NULL)
18422588c917Sschwarze 			return;
1843a5a5f808Sschwarze 		mandoc_msg(MANDOCERR_ARG_EXCESS,
18442588c917Sschwarze 		    nch->line, nch->pos, "Bl ... %s", nch->string);
18452588c917Sschwarze 		while (nch != NULL) {
1846fa2127f9Sschwarze 			roff_node_delete(mdoc, nch);
18472588c917Sschwarze 			nch = nh->child;
18482588c917Sschwarze 		}
184998b8f00aSschwarze 		return;
185098b8f00aSschwarze 	}
1851395185ccSschwarze 
185220fa2881Sschwarze 	/*
1853f5174743Sschwarze 	 * Append old-style lists, where the column width specifiers
185420fa2881Sschwarze 	 * trail as macro parameters, to the new-style ("normal-form")
185520fa2881Sschwarze 	 * lists where they're argument values following -column.
185620fa2881Sschwarze 	 */
185720fa2881Sschwarze 
18582588c917Sschwarze 	if (nh->child == NULL)
185998b8f00aSschwarze 		return;
186020fa2881Sschwarze 
18612588c917Sschwarze 	nbl = nh->parent;
18622588c917Sschwarze 	for (j = 0; j < (int)nbl->args->argc; j++)
18632588c917Sschwarze 		if (nbl->args->argv[j].arg == MDOC_Column)
186420fa2881Sschwarze 			break;
186520fa2881Sschwarze 
18662588c917Sschwarze 	assert(j < (int)nbl->args->argc);
186720fa2881Sschwarze 
186820fa2881Sschwarze 	/*
1869a5e11edeSschwarze 	 * Accommodate for new-style groff column syntax.  Shuffle the
187020fa2881Sschwarze 	 * child nodes, all of which must be TEXT, as arguments for the
187120fa2881Sschwarze 	 * column field.  Then, delete the head children.
187220fa2881Sschwarze 	 */
187320fa2881Sschwarze 
18742588c917Sschwarze 	argv = nbl->args->argv + j;
1875f5174743Sschwarze 	i = argv->sz;
187630e5ee06Sschwarze 	for (nch = nh->child; nch != NULL; nch = nch->next)
187730e5ee06Sschwarze 		argv->sz++;
1878f5174743Sschwarze 	argv->value = mandoc_reallocarray(argv->value,
1879f5174743Sschwarze 	    argv->sz, sizeof(char *));
188020fa2881Sschwarze 
18812588c917Sschwarze 	nh->norm->Bl.ncols = argv->sz;
18822588c917Sschwarze 	nh->norm->Bl.cols = (void *)argv->value;
188320fa2881Sschwarze 
18842588c917Sschwarze 	for (nch = nh->child; nch != NULL; nch = nnext) {
18852588c917Sschwarze 		argv->value[i++] = nch->string;
18862588c917Sschwarze 		nch->string = NULL;
18872588c917Sschwarze 		nnext = nch->next;
1888fa2127f9Sschwarze 		roff_node_delete(NULL, nch);
1889395185ccSschwarze 	}
18902588c917Sschwarze 	nh->child = NULL;
1891b16e7ddfSschwarze }
1892b16e7ddfSschwarze 
189398b8f00aSschwarze static void
post_bl(POST_ARGS)1894f73abda9Skristaps post_bl(POST_ARGS)
1895f73abda9Skristaps {
18967ebbefbeSschwarze 	struct roff_node	*nbody;           /* of the Bl */
18973a0d07afSschwarze 	struct roff_node	*nchild, *nnext;  /* of the Bl body */
1898ce0ef847Sschwarze 	const char		*prev_Er;
1899ce0ef847Sschwarze 	int			 order;
1900f73abda9Skristaps 
19012a427d60Sschwarze 	nbody = mdoc->last;
19022a427d60Sschwarze 	switch (nbody->type) {
1903d1982c71Sschwarze 	case ROFFT_BLOCK:
190498b8f00aSschwarze 		post_bl_block(mdoc);
190598b8f00aSschwarze 		return;
1906d1982c71Sschwarze 	case ROFFT_HEAD:
190798b8f00aSschwarze 		post_bl_head(mdoc);
190898b8f00aSschwarze 		return;
1909d1982c71Sschwarze 	case ROFFT_BODY:
1910f6127a73Sschwarze 		break;
19112a427d60Sschwarze 	default:
191298b8f00aSschwarze 		return;
1913f6127a73Sschwarze 	}
1914396853b5Sschwarze 	if (nbody->end != ENDBODY_NOT)
1915396853b5Sschwarze 		return;
1916f6127a73Sschwarze 
19177ebbefbeSschwarze 	/*
19187ebbefbeSschwarze 	 * Up to the first item, move nodes before the list,
19197ebbefbeSschwarze 	 * but leave transparent nodes where they are
19207ebbefbeSschwarze 	 * if they precede an item.
19217ebbefbeSschwarze 	 * The next non-transparent node is kept in nchild.
19227ebbefbeSschwarze 	 * It only needs to be updated after a non-transparent
19237ebbefbeSschwarze 	 * node was moved out, and at the very beginning
19247ebbefbeSschwarze 	 * when no node at all was moved yet.
19257ebbefbeSschwarze 	 */
19267ebbefbeSschwarze 
19277ebbefbeSschwarze 	nchild = mdoc->last;
19287ebbefbeSschwarze 	for (;;) {
19297ebbefbeSschwarze 		if (nchild == mdoc->last)
19307ebbefbeSschwarze 			nchild = roff_node_child(nbody);
1931b7530f2fSschwarze 		if (nchild == NULL) {
19327ebbefbeSschwarze 			mdoc->last = nbody;
1933a5a5f808Sschwarze 			mandoc_msg(MANDOCERR_BLK_EMPTY,
1934b7530f2fSschwarze 			    nbody->line, nbody->pos, "Bl");
1935b7530f2fSschwarze 			return;
1936b7530f2fSschwarze 		}
19377ebbefbeSschwarze 		if (nchild->tok == MDOC_It) {
19387ebbefbeSschwarze 			mdoc->last = nbody;
19397ebbefbeSschwarze 			break;
19407ebbefbeSschwarze 		}
19417ebbefbeSschwarze 		mandoc_msg(MANDOCERR_BL_MOVE, nbody->child->line,
19427ebbefbeSschwarze 		    nbody->child->pos, "%s", roff_name[nbody->child->tok]);
19437ebbefbeSschwarze 		if (nbody->parent->prev == NULL) {
19447ebbefbeSschwarze 			mdoc->last = nbody->parent->parent;
19457ebbefbeSschwarze 			mdoc->next = ROFF_NEXT_CHILD;
19467ebbefbeSschwarze 		} else {
19477ebbefbeSschwarze 			mdoc->last = nbody->parent->prev;
19487ebbefbeSschwarze 			mdoc->next = ROFF_NEXT_SIBLING;
19497ebbefbeSschwarze 		}
19507ebbefbeSschwarze 		roff_node_relink(mdoc, nbody->child);
1951ad7fa6e5Sschwarze 	}
1952ad7fa6e5Sschwarze 
1953ad7fa6e5Sschwarze 	/*
19547ebbefbeSschwarze 	 * We have reached the first item,
19557ebbefbeSschwarze 	 * so moving nodes out is no longer possible.
19567ebbefbeSschwarze 	 * But in .Bl -column, the first rows may be implicit,
1957ad7fa6e5Sschwarze 	 * that is, they may not start with .It macros.
1958ad7fa6e5Sschwarze 	 * Such rows may be followed by nodes generated on the
19597ebbefbeSschwarze 	 * roff level, for example .TS.
19607ebbefbeSschwarze 	 * Wrap such roff nodes into an implicit row.
1961ad7fa6e5Sschwarze 	 */
1962ad7fa6e5Sschwarze 
19637ebbefbeSschwarze 	while (nchild != NULL) {
19647ebbefbeSschwarze 		if (nchild->tok == MDOC_It) {
19657ebbefbeSschwarze 			nchild = roff_node_next(nchild);
19667ebbefbeSschwarze 			continue;
19677ebbefbeSschwarze 		}
19687ebbefbeSschwarze 		nnext = nchild->next;
19697ebbefbeSschwarze 		mdoc->last = nchild->prev;
1970ad7fa6e5Sschwarze 		mdoc->next = ROFF_NEXT_SIBLING;
19717ebbefbeSschwarze 		roff_block_alloc(mdoc, nchild->line, nchild->pos, MDOC_It);
19727ebbefbeSschwarze 		roff_head_alloc(mdoc, nchild->line, nchild->pos, MDOC_It);
1973ad7fa6e5Sschwarze 		mdoc->next = ROFF_NEXT_SIBLING;
19747ebbefbeSschwarze 		roff_body_alloc(mdoc, nchild->line, nchild->pos, MDOC_It);
1975ad7fa6e5Sschwarze 		while (nchild->tok != MDOC_It) {
19768251afdeSschwarze 			roff_node_relink(mdoc, nchild);
19777ebbefbeSschwarze 			if (nnext == NULL)
1978ad7fa6e5Sschwarze 				break;
19797ebbefbeSschwarze 			nchild = nnext;
1980ad7fa6e5Sschwarze 			nnext = nchild->next;
1981ad7fa6e5Sschwarze 			mdoc->next = ROFF_NEXT_SIBLING;
1982ad7fa6e5Sschwarze 		}
1983ad7fa6e5Sschwarze 		mdoc->last = nbody;
1984f73abda9Skristaps 	}
1985ce0ef847Sschwarze 
1986f3476b07Sschwarze 	if (mdoc->meta.os_e != MANDOC_OS_NETBSD)
1987ce0ef847Sschwarze 		return;
1988ce0ef847Sschwarze 
1989ce0ef847Sschwarze 	prev_Er = NULL;
1990ce0ef847Sschwarze 	for (nchild = nbody->child; nchild != NULL; nchild = nchild->next) {
1991ce0ef847Sschwarze 		if (nchild->tok != MDOC_It)
1992ce0ef847Sschwarze 			continue;
1993ce0ef847Sschwarze 		if ((nnext = nchild->head->child) == NULL)
1994ce0ef847Sschwarze 			continue;
1995ce0ef847Sschwarze 		if (nnext->type == ROFFT_BLOCK)
1996ce0ef847Sschwarze 			nnext = nnext->body->child;
1997ce0ef847Sschwarze 		if (nnext == NULL || nnext->tok != MDOC_Er)
1998ce0ef847Sschwarze 			continue;
1999ce0ef847Sschwarze 		nnext = nnext->child;
2000ce0ef847Sschwarze 		if (prev_Er != NULL) {
2001ce0ef847Sschwarze 			order = strcmp(prev_Er, nnext->string);
2002ce0ef847Sschwarze 			if (order > 0)
2003a5a5f808Sschwarze 				mandoc_msg(MANDOCERR_ER_ORDER,
2004a5a5f808Sschwarze 				    nnext->line, nnext->pos,
2005f3476b07Sschwarze 				    "Er %s %s (NetBSD)",
2006f3476b07Sschwarze 				    prev_Er, nnext->string);
2007ce0ef847Sschwarze 			else if (order == 0)
2008a5a5f808Sschwarze 				mandoc_msg(MANDOCERR_ER_REP,
2009a5a5f808Sschwarze 				    nnext->line, nnext->pos,
2010f3476b07Sschwarze 				    "Er %s (NetBSD)", prev_Er);
2011ce0ef847Sschwarze 		}
2012ce0ef847Sschwarze 		prev_Er = nnext->string;
2013ce0ef847Sschwarze 	}
2014f73abda9Skristaps }
2015f73abda9Skristaps 
201698b8f00aSschwarze static void
post_bk(POST_ARGS)2017753701eeSschwarze post_bk(POST_ARGS)
2018753701eeSschwarze {
20193a0d07afSschwarze 	struct roff_node	*n;
2020753701eeSschwarze 
20212588c917Sschwarze 	n = mdoc->last;
20222588c917Sschwarze 
2023d1982c71Sschwarze 	if (n->type == ROFFT_BLOCK && n->body->child == NULL) {
2024a5a5f808Sschwarze 		mandoc_msg(MANDOCERR_BLK_EMPTY, n->line, n->pos, "Bk");
2025fa2127f9Sschwarze 		roff_node_delete(mdoc, n);
20262588c917Sschwarze 	}
2027753701eeSschwarze }
2028753701eeSschwarze 
202998b8f00aSschwarze static void
post_sm(POST_ARGS)2030f051602aSschwarze post_sm(POST_ARGS)
2031f73abda9Skristaps {
20323a0d07afSschwarze 	struct roff_node	*nch;
2033f73abda9Skristaps 
2034dc0d8bb2Sschwarze 	nch = mdoc->last->child;
2035dc0d8bb2Sschwarze 
203678bbbab4Sschwarze 	if (nch == NULL) {
2037f9e7bf99Sschwarze 		mdoc->flags ^= MDOC_SMOFF;
203898b8f00aSschwarze 		return;
2039bb648afaSschwarze 	}
2040f9e7bf99Sschwarze 
2041d1982c71Sschwarze 	assert(nch->type == ROFFT_TEXT);
204220fa2881Sschwarze 
204378bbbab4Sschwarze 	if ( ! strcmp(nch->string, "on")) {
2044ec2beb53Sschwarze 		mdoc->flags &= ~MDOC_SMOFF;
204598b8f00aSschwarze 		return;
2046ec2beb53Sschwarze 	}
204778bbbab4Sschwarze 	if ( ! strcmp(nch->string, "off")) {
2048ec2beb53Sschwarze 		mdoc->flags |= MDOC_SMOFF;
204998b8f00aSschwarze 		return;
2050ec2beb53Sschwarze 	}
205120fa2881Sschwarze 
2052a5a5f808Sschwarze 	mandoc_msg(MANDOCERR_SM_BAD, nch->line, nch->pos,
205314a309e3Sschwarze 	    "%s %s", roff_name[mdoc->last->tok], nch->string);
20548251afdeSschwarze 	roff_node_relink(mdoc, nch);
205598b8f00aSschwarze 	return;
205620fa2881Sschwarze }
2057f73abda9Skristaps 
205898b8f00aSschwarze static void
post_root(POST_ARGS)2059f73abda9Skristaps post_root(POST_ARGS)
2060f73abda9Skristaps {
20613a0d07afSschwarze 	struct roff_node *n;
2062f73abda9Skristaps 
2063ac1f49d0Sschwarze 	/* Add missing prologue data. */
206420fa2881Sschwarze 
2065ac1f49d0Sschwarze 	if (mdoc->meta.date == NULL)
2066ea5923abSschwarze 		mdoc->meta.date = mandoc_normdate(NULL, NULL);
20673fdead0cSschwarze 
20683fdead0cSschwarze 	if (mdoc->meta.title == NULL) {
2069a5a5f808Sschwarze 		mandoc_msg(MANDOCERR_DT_NOTITLE, 0, 0, "EOF");
20703fdead0cSschwarze 		mdoc->meta.title = mandoc_strdup("UNTITLED");
20713fdead0cSschwarze 	}
20723fdead0cSschwarze 
2073ac1f49d0Sschwarze 	if (mdoc->meta.vol == NULL)
2074ac1f49d0Sschwarze 		mdoc->meta.vol = mandoc_strdup("LOCAL");
20753fdead0cSschwarze 
20763fdead0cSschwarze 	if (mdoc->meta.os == NULL) {
2077a5a5f808Sschwarze 		mandoc_msg(MANDOCERR_OS_MISSING, 0, 0, NULL);
20783fdead0cSschwarze 		mdoc->meta.os = mandoc_strdup("");
2079172864f7Sschwarze 	} else if (mdoc->meta.os_e &&
2080172864f7Sschwarze 	    (mdoc->meta.rcsids & (1 << mdoc->meta.os_e)) == 0)
2081a5a5f808Sschwarze 		mandoc_msg(MANDOCERR_RCS_MISSING, 0, 0,
2082f3476b07Sschwarze 		    mdoc->meta.os_e == MANDOC_OS_OPENBSD ?
2083f3476b07Sschwarze 		    "(OpenBSD)" : "(NetBSD)");
2084f73abda9Skristaps 
20858fd2959dSschwarze 	if (mdoc->meta.arch != NULL &&
2086f0fa0445Sschwarze 	    arch_valid(mdoc->meta.arch, mdoc->meta.os_e) == 0) {
20876b86842eSschwarze 		n = mdoc->meta.first->child;
20883f450c8cSschwarze 		while (n->tok != MDOC_Dt ||
20893f450c8cSschwarze 		    n->child == NULL ||
20903f450c8cSschwarze 		    n->child->next == NULL ||
20913f450c8cSschwarze 		    n->child->next->next == NULL)
20928fd2959dSschwarze 			n = n->next;
20938fd2959dSschwarze 		n = n->child->next->next;
2094a5a5f808Sschwarze 		mandoc_msg(MANDOCERR_ARCH_BAD, n->line, n->pos,
20958fd2959dSschwarze 		    "Dt ... %s %s", mdoc->meta.arch,
20968fd2959dSschwarze 		    mdoc->meta.os_e == MANDOC_OS_OPENBSD ?
20978fd2959dSschwarze 		    "(OpenBSD)" : "(NetBSD)");
20988fd2959dSschwarze 	}
20998fd2959dSschwarze 
210020fa2881Sschwarze 	/* Check that we begin with a proper `Sh'. */
210120fa2881Sschwarze 
21026b86842eSschwarze 	n = mdoc->meta.first->child;
21034c293873Sschwarze 	while (n != NULL &&
21044c293873Sschwarze 	    (n->type == ROFFT_COMMENT ||
21054c293873Sschwarze 	     (n->tok >= MDOC_Dd &&
210616fe0cfcSschwarze 	      mdoc_macro(n->tok)->flags & MDOC_PROLOGUE)))
2107e20417bdSschwarze 		n = n->next;
2108e20417bdSschwarze 
2109e20417bdSschwarze 	if (n == NULL)
2110a5a5f808Sschwarze 		mandoc_msg(MANDOCERR_DOC_EMPTY, 0, 0, NULL);
2111e20417bdSschwarze 	else if (n->tok != MDOC_Sh)
2112a5a5f808Sschwarze 		mandoc_msg(MANDOCERR_SEC_BEFORE, n->line, n->pos,
2113a5a5f808Sschwarze 		    "%s", roff_name[n->tok]);
211420fa2881Sschwarze }
2115f73abda9Skristaps 
211698b8f00aSschwarze static void
post_rs(POST_ARGS)2117011fe33bSschwarze post_rs(POST_ARGS)
2118011fe33bSschwarze {
21193a0d07afSschwarze 	struct roff_node *np, *nch, *next, *prev;
212020fa2881Sschwarze 	int		  i, j;
2121011fe33bSschwarze 
21226e96429aSschwarze 	np = mdoc->last;
21236e96429aSschwarze 
2124d1982c71Sschwarze 	if (np->type != ROFFT_BODY)
212598b8f00aSschwarze 		return;
21266e96429aSschwarze 
21276e96429aSschwarze 	if (np->child == NULL) {
2128a5a5f808Sschwarze 		mandoc_msg(MANDOCERR_RS_EMPTY, np->line, np->pos, "Rs");
212998b8f00aSschwarze 		return;
2130bb648afaSschwarze 	}
2131011fe33bSschwarze 
213220fa2881Sschwarze 	/*
213320fa2881Sschwarze 	 * The full `Rs' block needs special handling to order the
213420fa2881Sschwarze 	 * sub-elements according to `rsord'.  Pick through each element
2135b538baa5Sschwarze 	 * and correctly order it.  This is an insertion sort.
213620fa2881Sschwarze 	 */
213720fa2881Sschwarze 
213820fa2881Sschwarze 	next = NULL;
21396e96429aSschwarze 	for (nch = np->child->next; nch != NULL; nch = next) {
21406e96429aSschwarze 		/* Determine order number of this child. */
214120fa2881Sschwarze 		for (i = 0; i < RSORD_MAX; i++)
21426e96429aSschwarze 			if (rsord[i] == nch->tok)
214320fa2881Sschwarze 				break;
214420fa2881Sschwarze 
2145b538baa5Sschwarze 		if (i == RSORD_MAX) {
2146a5a5f808Sschwarze 			mandoc_msg(MANDOCERR_RS_BAD, nch->line, nch->pos,
2147a5a5f808Sschwarze 			    "%s", roff_name[nch->tok]);
2148b538baa5Sschwarze 			i = -1;
21496e96429aSschwarze 		} else if (nch->tok == MDOC__J || nch->tok == MDOC__B)
21506e96429aSschwarze 			np->norm->Rs.quote_T++;
2151b538baa5Sschwarze 
215220fa2881Sschwarze 		/*
21536e96429aSschwarze 		 * Remove this child from the chain.  This somewhat
2154fa2127f9Sschwarze 		 * repeats roff_node_unlink(), but since we're
215520fa2881Sschwarze 		 * just re-ordering, there's no need for the
215620fa2881Sschwarze 		 * full unlink process.
215720fa2881Sschwarze 		 */
215820fa2881Sschwarze 
21596e96429aSschwarze 		if ((next = nch->next) != NULL)
21606e96429aSschwarze 			next->prev = nch->prev;
216120fa2881Sschwarze 
21626e96429aSschwarze 		if ((prev = nch->prev) != NULL)
21636e96429aSschwarze 			prev->next = nch->next;
216420fa2881Sschwarze 
21656e96429aSschwarze 		nch->prev = nch->next = NULL;
216620fa2881Sschwarze 
216720fa2881Sschwarze 		/*
216820fa2881Sschwarze 		 * Scan back until we reach a node that's
21696e96429aSschwarze 		 * to be ordered before this child.
217020fa2881Sschwarze 		 */
217120fa2881Sschwarze 
217220fa2881Sschwarze 		for ( ; prev ; prev = prev->prev) {
217320fa2881Sschwarze 			/* Determine order of `prev'. */
217420fa2881Sschwarze 			for (j = 0; j < RSORD_MAX; j++)
217520fa2881Sschwarze 				if (rsord[j] == prev->tok)
217620fa2881Sschwarze 					break;
2177b538baa5Sschwarze 			if (j == RSORD_MAX)
2178b538baa5Sschwarze 				j = -1;
217920fa2881Sschwarze 
218020fa2881Sschwarze 			if (j <= i)
218120fa2881Sschwarze 				break;
218220fa2881Sschwarze 		}
218320fa2881Sschwarze 
218420fa2881Sschwarze 		/*
21856e96429aSschwarze 		 * Set this child back into its correct place
21866e96429aSschwarze 		 * in front of the `prev' node.
218720fa2881Sschwarze 		 */
218820fa2881Sschwarze 
21896e96429aSschwarze 		nch->prev = prev;
219020fa2881Sschwarze 
21916e96429aSschwarze 		if (prev == NULL) {
21926e96429aSschwarze 			np->child->prev = nch;
21936e96429aSschwarze 			nch->next = np->child;
21946e96429aSschwarze 			np->child = nch;
219520fa2881Sschwarze 		} else {
21966e96429aSschwarze 			if (prev->next)
21976e96429aSschwarze 				prev->next->prev = nch;
21986e96429aSschwarze 			nch->next = prev->next;
21996e96429aSschwarze 			prev->next = nch;
220020fa2881Sschwarze 		}
2201011fe33bSschwarze 	}
2202011fe33bSschwarze }
2203011fe33bSschwarze 
22044039b21cSschwarze /*
22054039b21cSschwarze  * For some arguments of some macros,
22064039b21cSschwarze  * convert all breakable hyphens into ASCII_HYPH.
22074039b21cSschwarze  */
220898b8f00aSschwarze static void
post_hyph(POST_ARGS)22094039b21cSschwarze post_hyph(POST_ARGS)
22104039b21cSschwarze {
22115ff59bf7Sschwarze 	struct roff_node	*n, *nch;
22124039b21cSschwarze 	char			*cp;
22134039b21cSschwarze 
22145ff59bf7Sschwarze 	n = mdoc->last;
22155ff59bf7Sschwarze 	for (nch = n->child; nch != NULL; nch = nch->next) {
2216d1982c71Sschwarze 		if (nch->type != ROFFT_TEXT)
22174039b21cSschwarze 			continue;
22184039b21cSschwarze 		cp = nch->string;
2219b7530f2fSschwarze 		if (*cp == '\0')
22204039b21cSschwarze 			continue;
2221b7530f2fSschwarze 		while (*(++cp) != '\0')
2222b7530f2fSschwarze 			if (*cp == '-' &&
22234039b21cSschwarze 			    isalpha((unsigned char)cp[-1]) &&
22245ff59bf7Sschwarze 			    isalpha((unsigned char)cp[1])) {
2225c220f9cfSschwarze 				if (n->tag == NULL && n->flags & NODE_ID)
2226c220f9cfSschwarze 					n->tag = mandoc_strdup(nch->string);
22274039b21cSschwarze 				*cp = ASCII_HYPH;
22284039b21cSschwarze 			}
22294039b21cSschwarze 	}
22305ff59bf7Sschwarze }
22314039b21cSschwarze 
223298b8f00aSschwarze static void
post_ns(POST_ARGS)2233af216717Sschwarze post_ns(POST_ARGS)
2234af216717Sschwarze {
2235676013e4Sschwarze 	struct roff_node	*n;
2236af216717Sschwarze 
2237676013e4Sschwarze 	n = mdoc->last;
2238676013e4Sschwarze 	if (n->flags & NODE_LINE ||
2239676013e4Sschwarze 	    (n->next != NULL && n->next->flags & NODE_DELIMC))
2240a5a5f808Sschwarze 		mandoc_msg(MANDOCERR_NS_SKIP, n->line, n->pos, NULL);
2241af216717Sschwarze }
2242af216717Sschwarze 
224398b8f00aSschwarze static void
post_sx(POST_ARGS)2244fe8e59edSschwarze post_sx(POST_ARGS)
2245fe8e59edSschwarze {
2246fe8e59edSschwarze 	post_delim(mdoc);
2247fe8e59edSschwarze 	post_hyph(mdoc);
2248fe8e59edSschwarze }
2249fe8e59edSschwarze 
2250fe8e59edSschwarze static void
post_sh(POST_ARGS)2251f73abda9Skristaps post_sh(POST_ARGS)
2252f73abda9Skristaps {
2253ba1a6076Sschwarze 	post_section(mdoc);
2254753701eeSschwarze 
2255cd6c268fSschwarze 	switch (mdoc->last->type) {
2256d1982c71Sschwarze 	case ROFFT_HEAD:
225798b8f00aSschwarze 		post_sh_head(mdoc);
225898b8f00aSschwarze 		break;
2259d1982c71Sschwarze 	case ROFFT_BODY:
2260cd6c268fSschwarze 		switch (mdoc->lastsec)  {
2261cd6c268fSschwarze 		case SEC_NAME:
226298b8f00aSschwarze 			post_sh_name(mdoc);
226398b8f00aSschwarze 			break;
22647c384856Sschwarze 		case SEC_SEE_ALSO:
226598b8f00aSschwarze 			post_sh_see_also(mdoc);
226698b8f00aSschwarze 			break;
2267cd6c268fSschwarze 		case SEC_AUTHORS:
226898b8f00aSschwarze 			post_sh_authors(mdoc);
226998b8f00aSschwarze 			break;
2270cd6c268fSschwarze 		default:
2271cd6c268fSschwarze 			break;
2272cd6c268fSschwarze 		}
2273cd6c268fSschwarze 		break;
2274cd6c268fSschwarze 	default:
2275cd6c268fSschwarze 		break;
2276cd6c268fSschwarze 	}
2277f73abda9Skristaps }
2278f73abda9Skristaps 
227998b8f00aSschwarze static void
post_sh_name(POST_ARGS)2280cd6c268fSschwarze post_sh_name(POST_ARGS)
2281f73abda9Skristaps {
22823a0d07afSschwarze 	struct roff_node *n;
228320e2cf25Sschwarze 	int hasnm, hasnd;
2284f73abda9Skristaps 
228520e2cf25Sschwarze 	hasnm = hasnd = 0;
2286f73abda9Skristaps 
228720e2cf25Sschwarze 	for (n = mdoc->last->child; n != NULL; n = n->next) {
228820e2cf25Sschwarze 		switch (n->tok) {
228920e2cf25Sschwarze 		case MDOC_Nm:
2290f27faaccSschwarze 			if (hasnm && n->child != NULL)
2291a5a5f808Sschwarze 				mandoc_msg(MANDOCERR_NAMESEC_PUNCT,
2292a5a5f808Sschwarze 				    n->line, n->pos,
2293f27faaccSschwarze 				    "Nm %s", n->child->string);
229420e2cf25Sschwarze 			hasnm = 1;
2295f27faaccSschwarze 			continue;
229620e2cf25Sschwarze 		case MDOC_Nd:
229720e2cf25Sschwarze 			hasnd = 1;
229820e2cf25Sschwarze 			if (n->next != NULL)
229920e2cf25Sschwarze 				mandoc_msg(MANDOCERR_NAMESEC_ND,
2300a5a5f808Sschwarze 				    n->line, n->pos, NULL);
230120e2cf25Sschwarze 			break;
23022d6f95d3Sschwarze 		case TOKEN_NONE:
2303f27faaccSschwarze 			if (n->type == ROFFT_TEXT &&
2304f27faaccSschwarze 			    n->string[0] == ',' && n->string[1] == '\0' &&
2305f27faaccSschwarze 			    n->next != NULL && n->next->tok == MDOC_Nm) {
2306f27faaccSschwarze 				n = n->next;
2307f27faaccSschwarze 				continue;
2308f27faaccSschwarze 			}
2309fa072f7fSschwarze 			/* FALLTHROUGH */
231020e2cf25Sschwarze 		default:
2311a5a5f808Sschwarze 			mandoc_msg(MANDOCERR_NAMESEC_BAD,
2312a5a5f808Sschwarze 			    n->line, n->pos, "%s", roff_name[n->tok]);
2313f27faaccSschwarze 			continue;
231420e2cf25Sschwarze 		}
2315f27faaccSschwarze 		break;
2316f73abda9Skristaps 	}
2317f73abda9Skristaps 
231820e2cf25Sschwarze 	if ( ! hasnm)
2319a5a5f808Sschwarze 		mandoc_msg(MANDOCERR_NAMESEC_NONM,
232020e2cf25Sschwarze 		    mdoc->last->line, mdoc->last->pos, NULL);
232120e2cf25Sschwarze 	if ( ! hasnd)
2322a5a5f808Sschwarze 		mandoc_msg(MANDOCERR_NAMESEC_NOND,
232320e2cf25Sschwarze 		    mdoc->last->line, mdoc->last->pos, NULL);
232420fa2881Sschwarze }
2325f73abda9Skristaps 
232698b8f00aSschwarze static void
post_sh_see_also(POST_ARGS)23277c384856Sschwarze post_sh_see_also(POST_ARGS)
23287c384856Sschwarze {
23293a0d07afSschwarze 	const struct roff_node	*n;
23307c384856Sschwarze 	const char		*name, *sec;
23317c384856Sschwarze 	const char		*lastname, *lastsec, *lastpunct;
23327c384856Sschwarze 	int			 cmp;
23337c384856Sschwarze 
23347c384856Sschwarze 	n = mdoc->last->child;
23357c384856Sschwarze 	lastname = lastsec = lastpunct = NULL;
23367c384856Sschwarze 	while (n != NULL) {
233730e5ee06Sschwarze 		if (n->tok != MDOC_Xr ||
233830e5ee06Sschwarze 		    n->child == NULL ||
233930e5ee06Sschwarze 		    n->child->next == NULL)
23407c384856Sschwarze 			break;
23417c384856Sschwarze 
23427c384856Sschwarze 		/* Process one .Xr node. */
23437c384856Sschwarze 
23447c384856Sschwarze 		name = n->child->string;
23457c384856Sschwarze 		sec = n->child->next->string;
23467c384856Sschwarze 		if (lastsec != NULL) {
23477c384856Sschwarze 			if (lastpunct[0] != ',' || lastpunct[1] != '\0')
2348a5a5f808Sschwarze 				mandoc_msg(MANDOCERR_XR_PUNCT, n->line,
2349a5a5f808Sschwarze 				    n->pos, "%s before %s(%s)",
2350a5a5f808Sschwarze 				    lastpunct, name, sec);
23517c384856Sschwarze 			cmp = strcmp(lastsec, sec);
23527c384856Sschwarze 			if (cmp > 0)
2353a5a5f808Sschwarze 				mandoc_msg(MANDOCERR_XR_ORDER, n->line,
2354a5a5f808Sschwarze 				    n->pos, "%s(%s) after %s(%s)",
2355a5a5f808Sschwarze 				    name, sec, lastname, lastsec);
23567c384856Sschwarze 			else if (cmp == 0 &&
23577c384856Sschwarze 			    strcasecmp(lastname, name) > 0)
2358a5a5f808Sschwarze 				mandoc_msg(MANDOCERR_XR_ORDER, n->line,
2359a5a5f808Sschwarze 				    n->pos, "%s after %s", name, lastname);
23607c384856Sschwarze 		}
23617c384856Sschwarze 		lastname = name;
23627c384856Sschwarze 		lastsec = sec;
23637c384856Sschwarze 
23647c384856Sschwarze 		/* Process the following node. */
23657c384856Sschwarze 
23667c384856Sschwarze 		n = n->next;
23677c384856Sschwarze 		if (n == NULL)
23687c384856Sschwarze 			break;
23697c384856Sschwarze 		if (n->tok == MDOC_Xr) {
23707c384856Sschwarze 			lastpunct = "none";
23717c384856Sschwarze 			continue;
23727c384856Sschwarze 		}
2373d1982c71Sschwarze 		if (n->type != ROFFT_TEXT)
23747c384856Sschwarze 			break;
23757c384856Sschwarze 		for (name = n->string; *name != '\0'; name++)
23767c384856Sschwarze 			if (isalpha((const unsigned char)*name))
237798b8f00aSschwarze 				return;
23787c384856Sschwarze 		lastpunct = n->string;
2379c7098240Sschwarze 		if (n->next == NULL || n->next->tok == MDOC_Rs)
2380a5a5f808Sschwarze 			mandoc_msg(MANDOCERR_XR_PUNCT, n->line,
2381a5a5f808Sschwarze 			    n->pos, "%s after %s(%s)",
23827c384856Sschwarze 			    lastpunct, lastname, lastsec);
23837c384856Sschwarze 		n = n->next;
23847c384856Sschwarze 	}
23857c384856Sschwarze }
23867c384856Sschwarze 
23877c384856Sschwarze static int
child_an(const struct roff_node * n)23883a0d07afSschwarze child_an(const struct roff_node *n)
2389cd6c268fSschwarze {
2390cd6c268fSschwarze 
2391cd6c268fSschwarze 	for (n = n->child; n != NULL; n = n->next)
239230e5ee06Sschwarze 		if ((n->tok == MDOC_An && n->child != NULL) || child_an(n))
2393526e306bSschwarze 			return 1;
2394526e306bSschwarze 	return 0;
2395cd6c268fSschwarze }
2396cd6c268fSschwarze 
239798b8f00aSschwarze static void
post_sh_authors(POST_ARGS)2398cd6c268fSschwarze post_sh_authors(POST_ARGS)
2399cd6c268fSschwarze {
2400cd6c268fSschwarze 
2401cd6c268fSschwarze 	if ( ! child_an(mdoc->last))
2402a5a5f808Sschwarze 		mandoc_msg(MANDOCERR_AN_MISSING,
2403cd6c268fSschwarze 		    mdoc->last->line, mdoc->last->pos, NULL);
2404cd6c268fSschwarze }
2405cd6c268fSschwarze 
24064482121fSschwarze /*
24074482121fSschwarze  * Return an upper bound for the string distance (allowing
24084482121fSschwarze  * transpositions).  Not a full Levenshtein implementation
24094482121fSschwarze  * because Levenshtein is quadratic in the string length
24104482121fSschwarze  * and this function is called for every standard name,
24114482121fSschwarze  * so the check for each custom name would be cubic.
24124482121fSschwarze  * The following crude heuristics is linear, resulting
24134482121fSschwarze  * in quadratic behaviour for checking one custom name,
24144482121fSschwarze  * which does not cause measurable slowdown.
24154482121fSschwarze  */
24164482121fSschwarze static int
similar(const char * s1,const char * s2)24174482121fSschwarze similar(const char *s1, const char *s2)
24184482121fSschwarze {
24194482121fSschwarze 	const int	maxdist = 3;
24204482121fSschwarze 	int		dist = 0;
24214482121fSschwarze 
24224482121fSschwarze 	while (s1[0] != '\0' && s2[0] != '\0') {
24234482121fSschwarze 		if (s1[0] == s2[0]) {
24244482121fSschwarze 			s1++;
24254482121fSschwarze 			s2++;
24264482121fSschwarze 			continue;
24274482121fSschwarze 		}
24284482121fSschwarze 		if (++dist > maxdist)
24294482121fSschwarze 			return INT_MAX;
24304482121fSschwarze 		if (s1[1] == s2[1]) {  /* replacement */
24314482121fSschwarze 			s1++;
24324482121fSschwarze 			s2++;
24334482121fSschwarze 		} else if (s1[0] == s2[1] && s1[1] == s2[0]) {
24344482121fSschwarze 			s1 += 2;	/* transposition */
24354482121fSschwarze 			s2 += 2;
24364482121fSschwarze 		} else if (s1[0] == s2[1])  /* insertion */
24374482121fSschwarze 			s2++;
24384482121fSschwarze 		else if (s1[1] == s2[0])  /* deletion */
24394482121fSschwarze 			s1++;
24404482121fSschwarze 		else
24414482121fSschwarze 			return INT_MAX;
24424482121fSschwarze 	}
24434482121fSschwarze 	dist += strlen(s1) + strlen(s2);
24444482121fSschwarze 	return dist > maxdist ? INT_MAX : dist;
24454482121fSschwarze }
24464482121fSschwarze 
244798b8f00aSschwarze static void
post_sh_head(POST_ARGS)2448f73abda9Skristaps post_sh_head(POST_ARGS)
2449f73abda9Skristaps {
2450cf0e2075Sschwarze 	struct roff_node	*nch;
245151fcab2fSschwarze 	const char		*goodsec;
24524482121fSschwarze 	const char *const	*testsec;
24534482121fSschwarze 	int			 dist, mindist;
24543a0d07afSschwarze 	enum roff_sec		 sec;
2455f73abda9Skristaps 
2456f73abda9Skristaps 	/*
2457f73abda9Skristaps 	 * Process a new section.  Sections are either "named" or
245820fa2881Sschwarze 	 * "custom".  Custom sections are user-defined, while named ones
245920fa2881Sschwarze 	 * follow a conventional order and may only appear in certain
246020fa2881Sschwarze 	 * manual sections.
2461f73abda9Skristaps 	 */
2462f73abda9Skristaps 
2463396853b5Sschwarze 	sec = mdoc->last->sec;
2464f73abda9Skristaps 
246520fa2881Sschwarze 	/* The NAME should be first. */
2466f73abda9Skristaps 
2467d37754b9Sschwarze 	if (sec != SEC_NAME && mdoc->lastnamed == SEC_NONE)
2468a5a5f808Sschwarze 		mandoc_msg(MANDOCERR_NAMESEC_FIRST,
2469d37754b9Sschwarze 		    mdoc->last->line, mdoc->last->pos, "Sh %s",
2470cf0e2075Sschwarze 		    sec != SEC_CUSTOM ? secnames[sec] :
2471cf0e2075Sschwarze 		    (nch = mdoc->last->child) == NULL ? "" :
2472cf0e2075Sschwarze 		    nch->type == ROFFT_TEXT ? nch->string :
247314a309e3Sschwarze 		    roff_name[nch->tok]);
247420fa2881Sschwarze 
247520fa2881Sschwarze 	/* The SYNOPSIS gets special attention in other areas. */
247620fa2881Sschwarze 
2477396853b5Sschwarze 	if (sec == SEC_SYNOPSIS) {
247875088a49Sschwarze 		roff_setreg(mdoc->roff, "nS", 1, '=');
247920fa2881Sschwarze 		mdoc->flags |= MDOC_SYNOPSIS;
248022881299Sschwarze 	} else {
248175088a49Sschwarze 		roff_setreg(mdoc->roff, "nS", 0, '=');
248220fa2881Sschwarze 		mdoc->flags &= ~MDOC_SYNOPSIS;
248322881299Sschwarze 	}
24840ac7e6ecSschwarze 	if (sec == SEC_DESCRIPTION)
24850ac7e6ecSschwarze 		fn_prio = TAG_STRONG;
248620fa2881Sschwarze 
248720fa2881Sschwarze 	/* Mark our last section. */
248820fa2881Sschwarze 
248920fa2881Sschwarze 	mdoc->lastsec = sec;
24901eccdf28Sschwarze 
249120fa2881Sschwarze 	/* We don't care about custom sections after this. */
2492fccfce9dSschwarze 
24934482121fSschwarze 	if (sec == SEC_CUSTOM) {
24944482121fSschwarze 		if ((nch = mdoc->last->child) == NULL ||
24954482121fSschwarze 		    nch->type != ROFFT_TEXT || nch->next != NULL)
249698b8f00aSschwarze 			return;
24974482121fSschwarze 		goodsec = NULL;
24984482121fSschwarze 		mindist = INT_MAX;
24994482121fSschwarze 		for (testsec = secnames + 1; *testsec != NULL; testsec++) {
25004482121fSschwarze 			dist = similar(nch->string, *testsec);
25014482121fSschwarze 			if (dist < mindist) {
25024482121fSschwarze 				goodsec = *testsec;
25034482121fSschwarze 				mindist = dist;
25044482121fSschwarze 			}
25054482121fSschwarze 		}
25064482121fSschwarze 		if (goodsec != NULL)
2507a5a5f808Sschwarze 			mandoc_msg(MANDOCERR_SEC_TYPO, nch->line, nch->pos,
2508a5a5f808Sschwarze 			    "Sh %s instead of %s", nch->string, goodsec);
25094482121fSschwarze 		return;
25104482121fSschwarze 	}
2511fccfce9dSschwarze 
25126be99f77Sschwarze 	/*
251320fa2881Sschwarze 	 * Check whether our non-custom section is being repeated or is
251420fa2881Sschwarze 	 * out of order.
25156be99f77Sschwarze 	 */
2516f73abda9Skristaps 
251720fa2881Sschwarze 	if (sec == mdoc->lastnamed)
2518a5a5f808Sschwarze 		mandoc_msg(MANDOCERR_SEC_REP, mdoc->last->line,
2519a5a5f808Sschwarze 		    mdoc->last->pos, "Sh %s", secnames[sec]);
252020fa2881Sschwarze 
252120fa2881Sschwarze 	if (sec < mdoc->lastnamed)
2522a5a5f808Sschwarze 		mandoc_msg(MANDOCERR_SEC_ORDER, mdoc->last->line,
2523a5a5f808Sschwarze 		    mdoc->last->pos, "Sh %s", secnames[sec]);
252420fa2881Sschwarze 
252520fa2881Sschwarze 	/* Mark the last named section. */
252620fa2881Sschwarze 
252720fa2881Sschwarze 	mdoc->lastnamed = sec;
252820fa2881Sschwarze 
252920fa2881Sschwarze 	/* Check particular section/manual conventions. */
253020fa2881Sschwarze 
2531396853b5Sschwarze 	if (mdoc->meta.msec == NULL)
253298b8f00aSschwarze 		return;
253320fa2881Sschwarze 
253451fcab2fSschwarze 	goodsec = NULL;
253520fa2881Sschwarze 	switch (sec) {
253649aff9f8Sschwarze 	case SEC_ERRORS:
2537be89e780Sschwarze 		if (*mdoc->meta.msec == '4')
2538be89e780Sschwarze 			break;
253951fcab2fSschwarze 		goodsec = "2, 3, 4, 9";
2540be89e780Sschwarze 		/* FALLTHROUGH */
254149aff9f8Sschwarze 	case SEC_RETURN_VALUES:
254249aff9f8Sschwarze 	case SEC_LIBRARY:
254392c0ca7fSschwarze 		if (*mdoc->meta.msec == '2')
2544f73abda9Skristaps 			break;
254592c0ca7fSschwarze 		if (*mdoc->meta.msec == '3')
254692c0ca7fSschwarze 			break;
254751fcab2fSschwarze 		if (NULL == goodsec)
254851fcab2fSschwarze 			goodsec = "2, 3, 9";
254903ab2f23Sdlg 		/* FALLTHROUGH */
255049aff9f8Sschwarze 	case SEC_CONTEXT:
255192c0ca7fSschwarze 		if (*mdoc->meta.msec == '9')
255292c0ca7fSschwarze 			break;
255351fcab2fSschwarze 		if (NULL == goodsec)
255451fcab2fSschwarze 			goodsec = "9";
2555a5a5f808Sschwarze 		mandoc_msg(MANDOCERR_SEC_MSEC,
255651fcab2fSschwarze 		    mdoc->last->line, mdoc->last->pos,
2557396853b5Sschwarze 		    "Sh %s for %s only", secnames[sec], goodsec);
255820fa2881Sschwarze 		break;
2559f73abda9Skristaps 	default:
2560f73abda9Skristaps 		break;
2561f73abda9Skristaps 	}
2562f73abda9Skristaps }
2563d39b9a9cSschwarze 
256498b8f00aSschwarze static void
post_xr(POST_ARGS)25655ae08040Sschwarze post_xr(POST_ARGS)
25665ae08040Sschwarze {
25675ae08040Sschwarze 	struct roff_node *n, *nch;
25685ae08040Sschwarze 
25695ae08040Sschwarze 	n = mdoc->last;
25705ae08040Sschwarze 	nch = n->child;
25715ae08040Sschwarze 	if (nch->next == NULL) {
2572a5a5f808Sschwarze 		mandoc_msg(MANDOCERR_XR_NOSEC,
25735ae08040Sschwarze 		    n->line, n->pos, "Xr %s", nch->string);
257419b6bef7Sschwarze 	} else {
25755ae08040Sschwarze 		assert(nch->next == n->last);
257652d11c96Sschwarze 		if(mandoc_xr_add(nch->next->string, nch->string,
257752d11c96Sschwarze 		    nch->line, nch->pos))
2578a5a5f808Sschwarze 			mandoc_msg(MANDOCERR_XR_SELF,
257952d11c96Sschwarze 			    nch->line, nch->pos, "Xr %s %s",
258052d11c96Sschwarze 			    nch->string, nch->next->string);
258119b6bef7Sschwarze 	}
2582fe8e59edSschwarze 	post_delim_nb(mdoc);
25835ae08040Sschwarze }
25845ae08040Sschwarze 
25855ae08040Sschwarze static void
post_section(POST_ARGS)2586ba1a6076Sschwarze post_section(POST_ARGS)
2587f6127a73Sschwarze {
2588ba1a6076Sschwarze 	struct roff_node *n, *nch;
2589ba1a6076Sschwarze 	char		 *cp, *tag;
2590f6127a73Sschwarze 
2591ba1a6076Sschwarze 	n = mdoc->last;
2592ba1a6076Sschwarze 	switch (n->type) {
25939c7c6a1fSschwarze 	case ROFFT_BLOCK:
25949c7c6a1fSschwarze 		post_prevpar(mdoc);
25959c7c6a1fSschwarze 		return;
2596d1982c71Sschwarze 	case ROFFT_HEAD:
2597ba1a6076Sschwarze 		tag = NULL;
2598ba1a6076Sschwarze 		deroff(&tag, n);
2599ba1a6076Sschwarze 		if (tag != NULL) {
2600ba1a6076Sschwarze 			for (cp = tag; *cp != '\0'; cp++)
2601ba1a6076Sschwarze 				if (*cp == ' ')
2602ba1a6076Sschwarze 					*cp = '_';
2603ba1a6076Sschwarze 			if ((nch = n->child) != NULL &&
2604ba1a6076Sschwarze 			    nch->type == ROFFT_TEXT &&
2605ba1a6076Sschwarze 			    strcmp(nch->string, tag) == 0)
2606e79243feSschwarze 				tag_put(NULL, TAG_STRONG, n);
2607ba1a6076Sschwarze 			else
2608ba1a6076Sschwarze 				tag_put(tag, TAG_FALLBACK, n);
2609ba1a6076Sschwarze 			free(tag);
2610ba1a6076Sschwarze 		}
2611fe8e59edSschwarze 		post_delim(mdoc);
2612753701eeSschwarze 		post_hyph(mdoc);
261398b8f00aSschwarze 		return;
2614d1982c71Sschwarze 	case ROFFT_BODY:
2615b7530f2fSschwarze 		break;
2616b7530f2fSschwarze 	default:
2617b7530f2fSschwarze 		return;
2618b7530f2fSschwarze 	}
2619ba1a6076Sschwarze 	if ((nch = n->child) != NULL &&
2620ba1a6076Sschwarze 	    (nch->tok == MDOC_Pp || nch->tok == ROFF_br ||
2621ba1a6076Sschwarze 	     nch->tok == ROFF_sp)) {
2622ba1a6076Sschwarze 		mandoc_msg(MANDOCERR_PAR_SKIP, nch->line, nch->pos,
2623ba1a6076Sschwarze 		    "%s after %s", roff_name[nch->tok],
2624ba1a6076Sschwarze 		    roff_name[n->tok]);
2625ba1a6076Sschwarze 		roff_node_delete(mdoc, nch);
2626f6127a73Sschwarze 	}
2627ba1a6076Sschwarze 	if ((nch = n->last) != NULL &&
2628ba1a6076Sschwarze 	    (nch->tok == MDOC_Pp || nch->tok == ROFF_br)) {
2629ba1a6076Sschwarze 		mandoc_msg(MANDOCERR_PAR_SKIP, nch->line, nch->pos,
2630ba1a6076Sschwarze 		    "%s at the end of %s", roff_name[nch->tok],
2631ba1a6076Sschwarze 		    roff_name[n->tok]);
2632ba1a6076Sschwarze 		roff_node_delete(mdoc, nch);
2633f6127a73Sschwarze 	}
2634f6127a73Sschwarze }
2635f6127a73Sschwarze 
263698b8f00aSschwarze static void
post_prevpar(POST_ARGS)26373e642ba0Sschwarze post_prevpar(POST_ARGS)
2638d39b9a9cSschwarze {
26397ebbefbeSschwarze 	struct roff_node *n, *np;
2640d39b9a9cSschwarze 
26413e642ba0Sschwarze 	n = mdoc->last;
2642d1982c71Sschwarze 	if (n->type != ROFFT_ELEM && n->type != ROFFT_BLOCK)
264398b8f00aSschwarze 		return;
26447ebbefbeSschwarze 	if ((np = roff_node_prev(n)) == NULL)
26457ebbefbeSschwarze 		return;
2646d39b9a9cSschwarze 
264720fa2881Sschwarze 	/*
26487c539ecbSschwarze 	 * Don't allow `Pp' prior to a paragraph-type
26497c539ecbSschwarze 	 * block: `Pp' or non-compact `Bd' or `Bl'.
265020fa2881Sschwarze 	 */
2651d39b9a9cSschwarze 
26527ebbefbeSschwarze 	if (np->tok != MDOC_Pp && np->tok != ROFF_br)
265398b8f00aSschwarze 		return;
26543e642ba0Sschwarze 	if (n->tok == MDOC_Bl && n->norm->Bl.comp)
265598b8f00aSschwarze 		return;
26563e642ba0Sschwarze 	if (n->tok == MDOC_Bd && n->norm->Bd.comp)
265798b8f00aSschwarze 		return;
26583e642ba0Sschwarze 	if (n->tok == MDOC_It && n->parent->norm->Bl.comp)
265998b8f00aSschwarze 		return;
2660d39b9a9cSschwarze 
26617ebbefbeSschwarze 	mandoc_msg(MANDOCERR_PAR_SKIP, np->line, np->pos,
26627ebbefbeSschwarze 	    "%s before %s", roff_name[np->tok], roff_name[n->tok]);
26637ebbefbeSschwarze 	roff_node_delete(mdoc, np);
2664d39b9a9cSschwarze }
266520fa2881Sschwarze 
266698b8f00aSschwarze static void
post_par(POST_ARGS)2667e0dd4c9cSschwarze post_par(POST_ARGS)
2668e0dd4c9cSschwarze {
26693a0d07afSschwarze 	struct roff_node *np;
2670e0dd4c9cSschwarze 
26710ac7e6ecSschwarze 	fn_prio = TAG_STRONG;
26723e642ba0Sschwarze 	post_prevpar(mdoc);
2673753701eeSschwarze 
26748251afdeSschwarze 	np = mdoc->last;
26758251afdeSschwarze 	if (np->child != NULL)
2676a5a5f808Sschwarze 		mandoc_msg(MANDOCERR_ARG_SKIP, np->line, np->pos,
2677a5a5f808Sschwarze 		    "%s %s", roff_name[np->tok], np->child->string);
2678e0dd4c9cSschwarze }
2679e0dd4c9cSschwarze 
268098b8f00aSschwarze static void
post_dd(POST_ARGS)268120fa2881Sschwarze post_dd(POST_ARGS)
268220fa2881Sschwarze {
26833a0d07afSschwarze 	struct roff_node *n;
268420fa2881Sschwarze 
2685b058e777Sschwarze 	n = mdoc->last;
268643808411Sschwarze 	n->flags |= NODE_NOPRT;
268743808411Sschwarze 
2688396853b5Sschwarze 	if (mdoc->meta.date != NULL) {
2689a5a5f808Sschwarze 		mandoc_msg(MANDOCERR_PROLOG_REP, n->line, n->pos, "Dd");
2690396853b5Sschwarze 		free(mdoc->meta.date);
2691396853b5Sschwarze 	} else if (mdoc->flags & MDOC_PBODY)
2692a5a5f808Sschwarze 		mandoc_msg(MANDOCERR_PROLOG_LATE, n->line, n->pos, "Dd");
2693396853b5Sschwarze 	else if (mdoc->meta.title != NULL)
2694a5a5f808Sschwarze 		mandoc_msg(MANDOCERR_PROLOG_ORDER,
2695396853b5Sschwarze 		    n->line, n->pos, "Dd after Dt");
2696396853b5Sschwarze 	else if (mdoc->meta.os != NULL)
2697a5a5f808Sschwarze 		mandoc_msg(MANDOCERR_PROLOG_ORDER,
2698396853b5Sschwarze 		    n->line, n->pos, "Dd after Os");
2699396853b5Sschwarze 
2700ea5923abSschwarze 	if (mdoc->quick && n != NULL)
2701ea5923abSschwarze 		mdoc->meta.date = mandoc_strdup("");
2702ea5923abSschwarze 	else
2703ea5923abSschwarze 		mdoc->meta.date = mandoc_normdate(n->child, n);
270404e980cbSschwarze }
270520fa2881Sschwarze 
270698b8f00aSschwarze static void
post_dt(POST_ARGS)270720fa2881Sschwarze post_dt(POST_ARGS)
270820fa2881Sschwarze {
27093a0d07afSschwarze 	struct roff_node *nn, *n;
271020fa2881Sschwarze 	const char	 *cp;
271120fa2881Sschwarze 	char		 *p;
271220fa2881Sschwarze 
271320fa2881Sschwarze 	n = mdoc->last;
271443808411Sschwarze 	n->flags |= NODE_NOPRT;
271543808411Sschwarze 
2716396853b5Sschwarze 	if (mdoc->flags & MDOC_PBODY) {
2717a5a5f808Sschwarze 		mandoc_msg(MANDOCERR_DT_LATE, n->line, n->pos, "Dt");
271843808411Sschwarze 		return;
2719396853b5Sschwarze 	}
2720396853b5Sschwarze 
2721396853b5Sschwarze 	if (mdoc->meta.title != NULL)
2722a5a5f808Sschwarze 		mandoc_msg(MANDOCERR_PROLOG_REP, n->line, n->pos, "Dt");
2723396853b5Sschwarze 	else if (mdoc->meta.os != NULL)
2724a5a5f808Sschwarze 		mandoc_msg(MANDOCERR_PROLOG_ORDER,
2725396853b5Sschwarze 		    n->line, n->pos, "Dt after Os");
272620fa2881Sschwarze 
272720fa2881Sschwarze 	free(mdoc->meta.title);
27283fdead0cSschwarze 	free(mdoc->meta.msec);
272920fa2881Sschwarze 	free(mdoc->meta.vol);
273020fa2881Sschwarze 	free(mdoc->meta.arch);
273120fa2881Sschwarze 
27323fdead0cSschwarze 	mdoc->meta.title = NULL;
27333fdead0cSschwarze 	mdoc->meta.msec = NULL;
27343fdead0cSschwarze 	mdoc->meta.vol = NULL;
27353fdead0cSschwarze 	mdoc->meta.arch = NULL;
273620fa2881Sschwarze 
27379a928a59Sschwarze 	/* Mandatory first argument: title. */
273820fa2881Sschwarze 
27399a928a59Sschwarze 	nn = n->child;
27409a928a59Sschwarze 	if (nn == NULL || *nn->string == '\0') {
2741a5a5f808Sschwarze 		mandoc_msg(MANDOCERR_DT_NOTITLE, n->line, n->pos, "Dt");
27429a928a59Sschwarze 		mdoc->meta.title = mandoc_strdup("UNTITLED");
27439a928a59Sschwarze 	} else {
27449a928a59Sschwarze 		mdoc->meta.title = mandoc_strdup(nn->string);
27459a928a59Sschwarze 
27469a928a59Sschwarze 		/* Check that all characters are uppercase. */
27479a928a59Sschwarze 
27489a928a59Sschwarze 		for (p = nn->string; *p != '\0'; p++)
27499a928a59Sschwarze 			if (islower((unsigned char)*p)) {
2750a5a5f808Sschwarze 				mandoc_msg(MANDOCERR_TITLE_CASE, nn->line,
2751a5a5f808Sschwarze 				    nn->pos + (int)(p - nn->string),
2752bd594191Sschwarze 				    "Dt %s", nn->string);
275320fa2881Sschwarze 				break;
275420fa2881Sschwarze 			}
275520fa2881Sschwarze 	}
275620fa2881Sschwarze 
27575ae08040Sschwarze 	/* Mandatory second argument: section. */
275820fa2881Sschwarze 
27599a928a59Sschwarze 	if (nn != NULL)
27609a928a59Sschwarze 		nn = nn->next;
276120fa2881Sschwarze 
27629a928a59Sschwarze 	if (nn == NULL) {
2763a5a5f808Sschwarze 		mandoc_msg(MANDOCERR_MSEC_MISSING, n->line, n->pos,
27643fdead0cSschwarze 		    "Dt %s", mdoc->meta.title);
276520fa2881Sschwarze 		mdoc->meta.vol = mandoc_strdup("LOCAL");
276643808411Sschwarze 		return;  /* msec and arch remain NULL. */
276720fa2881Sschwarze 	}
276820fa2881Sschwarze 
27699a928a59Sschwarze 	mdoc->meta.msec = mandoc_strdup(nn->string);
27709a928a59Sschwarze 
27719a928a59Sschwarze 	/* Infer volume title from section number. */
277220fa2881Sschwarze 
277388ec69e3Sschwarze 	cp = mandoc_a2msec(nn->string);
27749a928a59Sschwarze 	if (cp == NULL) {
2775a5a5f808Sschwarze 		mandoc_msg(MANDOCERR_MSEC_BAD,
2776bd594191Sschwarze 		    nn->line, nn->pos, "Dt ... %s", nn->string);
277720fa2881Sschwarze 		mdoc->meta.vol = mandoc_strdup(nn->string);
2778ee646987Sschwarze 	} else {
27799a928a59Sschwarze 		mdoc->meta.vol = mandoc_strdup(cp);
2780ee646987Sschwarze 		if (mdoc->filesec != '\0' &&
2781ee646987Sschwarze 		    mdoc->filesec != *nn->string &&
2782ee646987Sschwarze 		    *nn->string >= '1' && *nn->string <= '9')
2783ee646987Sschwarze 			mandoc_msg(MANDOCERR_MSEC_FILE, nn->line, nn->pos,
2784ee646987Sschwarze 			    "*.%c vs Dt ... %c", mdoc->filesec, *nn->string);
2785ee646987Sschwarze 	}
278620fa2881Sschwarze 
27879a928a59Sschwarze 	/* Optional third argument: architecture. */
278820fa2881Sschwarze 
27899a928a59Sschwarze 	if ((nn = nn->next) == NULL)
279043808411Sschwarze 		return;
27919a928a59Sschwarze 
27929a928a59Sschwarze 	for (p = nn->string; *p != '\0'; p++)
2793b94f27c5Sschwarze 		*p = tolower((unsigned char)*p);
2794b94f27c5Sschwarze 	mdoc->meta.arch = mandoc_strdup(nn->string);
279520fa2881Sschwarze 
27969a928a59Sschwarze 	/* Ignore fourth and later arguments. */
27979a928a59Sschwarze 
27989a928a59Sschwarze 	if ((nn = nn->next) != NULL)
2799a5a5f808Sschwarze 		mandoc_msg(MANDOCERR_ARG_EXCESS,
28009a928a59Sschwarze 		    nn->line, nn->pos, "Dt ... %s", nn->string);
280120fa2881Sschwarze }
280220fa2881Sschwarze 
280398b8f00aSschwarze static void
post_bx(POST_ARGS)2804992063deSschwarze post_bx(POST_ARGS)
2805992063deSschwarze {
28063af8e8d7Sschwarze 	struct roff_node	*n, *nch;
2807f0c18971Sschwarze 	const char		*macro;
28083af8e8d7Sschwarze 
2809fe8e59edSschwarze 	post_delim_nb(mdoc);
281004fbb99fSschwarze 
28113af8e8d7Sschwarze 	n = mdoc->last;
28123af8e8d7Sschwarze 	nch = n->child;
28133af8e8d7Sschwarze 
28143af8e8d7Sschwarze 	if (nch != NULL) {
2815f0c18971Sschwarze 		macro = !strcmp(nch->string, "Open") ? "Ox" :
2816f0c18971Sschwarze 		    !strcmp(nch->string, "Net") ? "Nx" :
2817f0c18971Sschwarze 		    !strcmp(nch->string, "Free") ? "Fx" :
2818f0c18971Sschwarze 		    !strcmp(nch->string, "DragonFly") ? "Dx" : NULL;
2819f0c18971Sschwarze 		if (macro != NULL)
2820a5a5f808Sschwarze 			mandoc_msg(MANDOCERR_BX,
2821a5a5f808Sschwarze 			    n->line, n->pos, "%s", macro);
28223af8e8d7Sschwarze 		mdoc->last = nch;
28233af8e8d7Sschwarze 		nch = nch->next;
28243af8e8d7Sschwarze 		mdoc->next = ROFF_NEXT_SIBLING;
28253af8e8d7Sschwarze 		roff_elem_alloc(mdoc, n->line, n->pos, MDOC_Ns);
28263af8e8d7Sschwarze 		mdoc->last->flags |= NODE_NOSRC;
28273af8e8d7Sschwarze 		mdoc->next = ROFF_NEXT_SIBLING;
28283af8e8d7Sschwarze 	} else
28293af8e8d7Sschwarze 		mdoc->next = ROFF_NEXT_CHILD;
28303af8e8d7Sschwarze 	roff_word_alloc(mdoc, n->line, n->pos, "BSD");
28313af8e8d7Sschwarze 	mdoc->last->flags |= NODE_NOSRC;
28323af8e8d7Sschwarze 
28333af8e8d7Sschwarze 	if (nch == NULL) {
28343af8e8d7Sschwarze 		mdoc->last = n;
28353af8e8d7Sschwarze 		return;
28363af8e8d7Sschwarze 	}
28373af8e8d7Sschwarze 
28383af8e8d7Sschwarze 	roff_elem_alloc(mdoc, n->line, n->pos, MDOC_Ns);
28393af8e8d7Sschwarze 	mdoc->last->flags |= NODE_NOSRC;
28403af8e8d7Sschwarze 	mdoc->next = ROFF_NEXT_SIBLING;
28413af8e8d7Sschwarze 	roff_word_alloc(mdoc, n->line, n->pos, "-");
28423af8e8d7Sschwarze 	mdoc->last->flags |= NODE_NOSRC;
28433af8e8d7Sschwarze 	roff_elem_alloc(mdoc, n->line, n->pos, MDOC_Ns);
28443af8e8d7Sschwarze 	mdoc->last->flags |= NODE_NOSRC;
28453af8e8d7Sschwarze 	mdoc->last = n;
2846992063deSschwarze 
2847992063deSschwarze 	/*
2848992063deSschwarze 	 * Make `Bx's second argument always start with an uppercase
2849992063deSschwarze 	 * letter.  Groff checks if it's an "accepted" term, but we just
2850992063deSschwarze 	 * uppercase blindly.
2851992063deSschwarze 	 */
2852992063deSschwarze 
28533af8e8d7Sschwarze 	*nch->string = (char)toupper((unsigned char)*nch->string);
2854992063deSschwarze }
2855992063deSschwarze 
285698b8f00aSschwarze static void
post_os(POST_ARGS)285720fa2881Sschwarze post_os(POST_ARGS)
285820fa2881Sschwarze {
285920fa2881Sschwarze #ifndef OSNAME
286020fa2881Sschwarze 	struct utsname	  utsname;
286120fa2881Sschwarze #endif
28623a0d07afSschwarze 	struct roff_node *n;
286320fa2881Sschwarze 
286420fa2881Sschwarze 	n = mdoc->last;
286543808411Sschwarze 	n->flags |= NODE_NOPRT;
286643808411Sschwarze 
2867396853b5Sschwarze 	if (mdoc->meta.os != NULL)
2868a5a5f808Sschwarze 		mandoc_msg(MANDOCERR_PROLOG_REP, n->line, n->pos, "Os");
2869396853b5Sschwarze 	else if (mdoc->flags & MDOC_PBODY)
2870a5a5f808Sschwarze 		mandoc_msg(MANDOCERR_PROLOG_LATE, n->line, n->pos, "Os");
287120fa2881Sschwarze 
2872fe8e59edSschwarze 	post_delim(mdoc);
2873fe8e59edSschwarze 
287420fa2881Sschwarze 	/*
2875353fa9ecSschwarze 	 * Set the operating system by way of the `Os' macro.
2876353fa9ecSschwarze 	 * The order of precedence is:
2877353fa9ecSschwarze 	 * 1. the argument of the `Os' macro, unless empty
2878353fa9ecSschwarze 	 * 2. the -Ios=foo command line argument, if provided
2879353fa9ecSschwarze 	 * 3. -DOSNAME="\"foo\"", if provided during compilation
2880353fa9ecSschwarze 	 * 4. "sysname release" from uname(3)
288120fa2881Sschwarze 	 */
288220fa2881Sschwarze 
288320fa2881Sschwarze 	free(mdoc->meta.os);
288483af2bccSschwarze 	mdoc->meta.os = NULL;
2885423631c9Sschwarze 	deroff(&mdoc->meta.os, n);
288683af2bccSschwarze 	if (mdoc->meta.os)
2887ce0ef847Sschwarze 		goto out;
28884c468128Sschwarze 
2889f3476b07Sschwarze 	if (mdoc->os_s != NULL) {
2890f3476b07Sschwarze 		mdoc->meta.os = mandoc_strdup(mdoc->os_s);
2891ce0ef847Sschwarze 		goto out;
2892353fa9ecSschwarze 	}
28934c468128Sschwarze 
289420fa2881Sschwarze #ifdef OSNAME
28954c468128Sschwarze 	mdoc->meta.os = mandoc_strdup(OSNAME);
289620fa2881Sschwarze #else /*!OSNAME */
28978055da74Sschwarze 	if (mdoc->os_r == NULL) {
2898f051602aSschwarze 		if (uname(&utsname) == -1) {
2899a5a5f808Sschwarze 			mandoc_msg(MANDOCERR_OS_UNAME, n->line, n->pos, "Os");
29008055da74Sschwarze 			mdoc->os_r = mandoc_strdup("UNKNOWN");
2901a450f7c4Sschwarze 		} else
29028055da74Sschwarze 			mandoc_asprintf(&mdoc->os_r, "%s %s",
2903a450f7c4Sschwarze 			    utsname.sysname, utsname.release);
290420fa2881Sschwarze 	}
29058055da74Sschwarze 	mdoc->meta.os = mandoc_strdup(mdoc->os_r);
290620fa2881Sschwarze #endif /*!OSNAME*/
2907ce0ef847Sschwarze 
2908f3476b07Sschwarze out:
2909f3476b07Sschwarze 	if (mdoc->meta.os_e == MANDOC_OS_OTHER) {
2910f3476b07Sschwarze 		if (strstr(mdoc->meta.os, "OpenBSD") != NULL)
2911f3476b07Sschwarze 			mdoc->meta.os_e = MANDOC_OS_OPENBSD;
2912f3476b07Sschwarze 		else if (strstr(mdoc->meta.os, "NetBSD") != NULL)
2913f3476b07Sschwarze 			mdoc->meta.os_e = MANDOC_OS_NETBSD;
2914f3476b07Sschwarze 	}
29153427e516Sschwarze 
29163427e516Sschwarze 	/*
29173427e516Sschwarze 	 * This is the earliest point where we can check
29183427e516Sschwarze 	 * Mdocdate conventions because we don't know
29193427e516Sschwarze 	 * the operating system earlier.
29203427e516Sschwarze 	 */
29213427e516Sschwarze 
29222f84042eSschwarze 	if (n->child != NULL)
2923a5a5f808Sschwarze 		mandoc_msg(MANDOCERR_OS_ARG, n->child->line, n->child->pos,
29242f84042eSschwarze 		    "Os %s (%s)", n->child->string,
29252f84042eSschwarze 		    mdoc->meta.os_e == MANDOC_OS_OPENBSD ?
29262f84042eSschwarze 		    "OpenBSD" : "NetBSD");
29272f84042eSschwarze 
29283427e516Sschwarze 	while (n->tok != MDOC_Dd)
29293427e516Sschwarze 		if ((n = n->prev) == NULL)
29303427e516Sschwarze 			return;
29313427e516Sschwarze 	if ((n = n->child) == NULL)
29323427e516Sschwarze 		return;
293307ec32d0Sschwarze 	if (strncmp(n->string, "$" "Mdocdate", 9)) {
2934f3476b07Sschwarze 		if (mdoc->meta.os_e == MANDOC_OS_OPENBSD)
2935a5a5f808Sschwarze 			mandoc_msg(MANDOCERR_MDOCDATE_MISSING, n->line,
2936a5a5f808Sschwarze 			    n->pos, "Dd %s (OpenBSD)", n->string);
29373427e516Sschwarze 	} else {
2938f3476b07Sschwarze 		if (mdoc->meta.os_e == MANDOC_OS_NETBSD)
2939a5a5f808Sschwarze 			mandoc_msg(MANDOCERR_MDOCDATE, n->line,
2940a5a5f808Sschwarze 			    n->pos, "Dd %s (NetBSD)", n->string);
29413427e516Sschwarze 	}
294220fa2881Sschwarze }
294320fa2881Sschwarze 
2944396853b5Sschwarze enum roff_sec
mdoc_a2sec(const char * p)2945396853b5Sschwarze mdoc_a2sec(const char *p)
294619a69263Sschwarze {
294719a69263Sschwarze 	int		 i;
294819a69263Sschwarze 
294919a69263Sschwarze 	for (i = 0; i < (int)SEC__MAX; i++)
295019a69263Sschwarze 		if (secnames[i] && 0 == strcmp(p, secnames[i]))
2951526e306bSschwarze 			return (enum roff_sec)i;
295219a69263Sschwarze 
2953526e306bSschwarze 	return SEC_CUSTOM;
295419a69263Sschwarze }
295519a69263Sschwarze 
295619a69263Sschwarze static size_t
macro2len(enum roff_tok macro)295714a309e3Sschwarze macro2len(enum roff_tok macro)
295819a69263Sschwarze {
295919a69263Sschwarze 
296019a69263Sschwarze 	switch (macro) {
296149aff9f8Sschwarze 	case MDOC_Ad:
2962526e306bSschwarze 		return 12;
296349aff9f8Sschwarze 	case MDOC_Ao:
2964526e306bSschwarze 		return 12;
296549aff9f8Sschwarze 	case MDOC_An:
2966526e306bSschwarze 		return 12;
296749aff9f8Sschwarze 	case MDOC_Aq:
2968526e306bSschwarze 		return 12;
296949aff9f8Sschwarze 	case MDOC_Ar:
2970526e306bSschwarze 		return 12;
297149aff9f8Sschwarze 	case MDOC_Bo:
2972526e306bSschwarze 		return 12;
297349aff9f8Sschwarze 	case MDOC_Bq:
2974526e306bSschwarze 		return 12;
297549aff9f8Sschwarze 	case MDOC_Cd:
2976526e306bSschwarze 		return 12;
297749aff9f8Sschwarze 	case MDOC_Cm:
2978526e306bSschwarze 		return 10;
297949aff9f8Sschwarze 	case MDOC_Do:
2980526e306bSschwarze 		return 10;
298149aff9f8Sschwarze 	case MDOC_Dq:
2982526e306bSschwarze 		return 12;
298349aff9f8Sschwarze 	case MDOC_Dv:
2984526e306bSschwarze 		return 12;
298549aff9f8Sschwarze 	case MDOC_Eo:
2986526e306bSschwarze 		return 12;
298749aff9f8Sschwarze 	case MDOC_Em:
2988526e306bSschwarze 		return 10;
298949aff9f8Sschwarze 	case MDOC_Er:
2990526e306bSschwarze 		return 17;
299149aff9f8Sschwarze 	case MDOC_Ev:
2992526e306bSschwarze 		return 15;
299349aff9f8Sschwarze 	case MDOC_Fa:
2994526e306bSschwarze 		return 12;
299549aff9f8Sschwarze 	case MDOC_Fl:
2996526e306bSschwarze 		return 10;
299749aff9f8Sschwarze 	case MDOC_Fo:
2998526e306bSschwarze 		return 16;
299949aff9f8Sschwarze 	case MDOC_Fn:
3000526e306bSschwarze 		return 16;
300149aff9f8Sschwarze 	case MDOC_Ic:
3002526e306bSschwarze 		return 10;
300349aff9f8Sschwarze 	case MDOC_Li:
3004526e306bSschwarze 		return 16;
300549aff9f8Sschwarze 	case MDOC_Ms:
3006526e306bSschwarze 		return 6;
300749aff9f8Sschwarze 	case MDOC_Nm:
3008526e306bSschwarze 		return 10;
300949aff9f8Sschwarze 	case MDOC_No:
3010526e306bSschwarze 		return 12;
301149aff9f8Sschwarze 	case MDOC_Oo:
3012526e306bSschwarze 		return 10;
301349aff9f8Sschwarze 	case MDOC_Op:
3014526e306bSschwarze 		return 14;
301549aff9f8Sschwarze 	case MDOC_Pa:
3016526e306bSschwarze 		return 32;
301749aff9f8Sschwarze 	case MDOC_Pf:
3018526e306bSschwarze 		return 12;
301949aff9f8Sschwarze 	case MDOC_Po:
3020526e306bSschwarze 		return 12;
302149aff9f8Sschwarze 	case MDOC_Pq:
3022526e306bSschwarze 		return 12;
302349aff9f8Sschwarze 	case MDOC_Ql:
3024526e306bSschwarze 		return 16;
302549aff9f8Sschwarze 	case MDOC_Qo:
3026526e306bSschwarze 		return 12;
302749aff9f8Sschwarze 	case MDOC_So:
3028526e306bSschwarze 		return 12;
302949aff9f8Sschwarze 	case MDOC_Sq:
3030526e306bSschwarze 		return 12;
303149aff9f8Sschwarze 	case MDOC_Sy:
3032526e306bSschwarze 		return 6;
303349aff9f8Sschwarze 	case MDOC_Sx:
3034526e306bSschwarze 		return 16;
303549aff9f8Sschwarze 	case MDOC_Tn:
3036526e306bSschwarze 		return 10;
303749aff9f8Sschwarze 	case MDOC_Va:
3038526e306bSschwarze 		return 12;
303949aff9f8Sschwarze 	case MDOC_Vt:
3040526e306bSschwarze 		return 12;
304149aff9f8Sschwarze 	case MDOC_Xr:
3042526e306bSschwarze 		return 10;
304319a69263Sschwarze 	default:
304419a69263Sschwarze 		break;
3045*479c151dSjsg 	}
3046526e306bSschwarze 	return 0;
304719a69263Sschwarze }
3048