116Salm/* $OpenBSD: mdoc_validate.c,v 1.306 2022/06/08 16:29:12 schwarze Exp $ */
2/*
3 * Copyright (c) 2010-2021 Ingo Schwarze <schwarze@openbsd.org>
4 * Copyright (c) 2008-2012 Kristaps Dzonsons <kristaps@bsd.lv>
5 * Copyright (c) 2010 Joerg Sonnenberger <joerg@netbsd.org>
6 *
7 * Permission to use, copy, modify, and distribute this software for any
8 * purpose with or without fee is hereby granted, provided that the above
9 * copyright notice and this permission notice appear in all copies.
10 *
11 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHORS DISCLAIM ALL WARRANTIES
12 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
13 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR
14 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
15 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
16 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
17 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
18 *
19 * Validation module for mdoc(7) syntax trees used by mandoc(1).
20 */
21#include <sys/types.h>
22#ifndef OSNAME
23#include <sys/utsname.h>
24#endif
25
26#include <assert.h>
27#include <ctype.h>
28#include <limits.h>
29#include <stdio.h>
30#include <stdlib.h>
31#include <string.h>
32#include <time.h>
33
34#include "mandoc_aux.h"
35#include "mandoc.h"
36#include "mandoc_xr.h"
37#include "roff.h"
38#include "mdoc.h"
39#include "libmandoc.h"
40#include "roff_int.h"
41#include "libmdoc.h"
42#include "tag.h"
43
44/* FIXME: .Bl -diag can't have non-text children in HEAD. */
45
46#define	POST_ARGS struct roff_man *mdoc
47
48enum	check_ineq {
49	CHECK_LT,
50	CHECK_GT,
51	CHECK_EQ
52};
53
54typedef	void	(*v_post)(POST_ARGS);
55
56static	int	 build_list(struct roff_man *, int);
57static	void	 check_argv(struct roff_man *,
58			struct roff_node *, struct mdoc_argv *);
59static	void	 check_args(struct roff_man *, struct roff_node *);
60static	void	 check_text(struct roff_man *, int, int, char *);
61static	void	 check_text_em(struct roff_man *, int, int, char *);
62static	void	 check_toptext(struct roff_man *, int, int, const char *);
63static	int	 child_an(const struct roff_node *);
64static	size_t		macro2len(enum roff_tok);
65static	void	 rewrite_macro2len(struct roff_man *, char **);
66static	int	 similar(const char *, const char *);
67
68static	void	 post_abort(POST_ARGS) __attribute__((__noreturn__));
69static	void	 post_an(POST_ARGS);
70static	void	 post_an_norm(POST_ARGS);
71static	void	 post_at(POST_ARGS);
72static	void	 post_bd(POST_ARGS);
73static	void	 post_bf(POST_ARGS);
74static	void	 post_bk(POST_ARGS);
75static	void	 post_bl(POST_ARGS);
76static	void	 post_bl_block(POST_ARGS);
77static	void	 post_bl_head(POST_ARGS);
78static	void	 post_bl_norm(POST_ARGS);
79static	void	 post_bx(POST_ARGS);
80static	void	 post_defaults(POST_ARGS);
81static	void	 post_display(POST_ARGS);
82static	void	 post_dd(POST_ARGS);
83static	void	 post_delim(POST_ARGS);
84static	void	 post_delim_nb(POST_ARGS);
85static	void	 post_dt(POST_ARGS);
86static	void	 post_em(POST_ARGS);
87static	void	 post_en(POST_ARGS);
88static	void	 post_er(POST_ARGS);
89static	void	 post_es(POST_ARGS);
90static	void	 post_eoln(POST_ARGS);
91static	void	 post_ex(POST_ARGS);
92static	void	 post_fa(POST_ARGS);
93static	void	 post_fl(POST_ARGS);
94static	void	 post_fn(POST_ARGS);
95static	void	 post_fname(POST_ARGS);
96static	void	 post_fo(POST_ARGS);
97static	void	 post_hyph(POST_ARGS);
98static	void	 post_it(POST_ARGS);
99static	void	 post_lb(POST_ARGS);
100static	void	 post_nd(POST_ARGS);
101static	void	 post_nm(POST_ARGS);
102static	void	 post_ns(POST_ARGS);
103static	void	 post_obsolete(POST_ARGS);
104static	void	 post_os(POST_ARGS);
105static	void	 post_par(POST_ARGS);
106static	void	 post_prevpar(POST_ARGS);
107static	void	 post_root(POST_ARGS);
108static	void	 post_rs(POST_ARGS);
109static	void	 post_rv(POST_ARGS);
110static	void	 post_section(POST_ARGS);
111static	void	 post_sh(POST_ARGS);
112static	void	 post_sh_head(POST_ARGS);
113static	void	 post_sh_name(POST_ARGS);
114static	void	 post_sh_see_also(POST_ARGS);
115static	void	 post_sh_authors(POST_ARGS);
116static	void	 post_sm(POST_ARGS);
117static	void	 post_st(POST_ARGS);
118static	void	 post_std(POST_ARGS);
119static	void	 post_sx(POST_ARGS);
120static	void	 post_tag(POST_ARGS);
121static	void	 post_tg(POST_ARGS);
122static	void	 post_useless(POST_ARGS);
123static	void	 post_xr(POST_ARGS);
124static	void	 post_xx(POST_ARGS);
125
126static	const v_post mdoc_valids[MDOC_MAX - MDOC_Dd] = {
127	post_dd,	/* Dd */
128	post_dt,	/* Dt */
129	post_os,	/* Os */
130	post_sh,	/* Sh */
131	post_section,	/* Ss */
132	post_par,	/* Pp */
133	post_display,	/* D1 */
134	post_display,	/* Dl */
135	post_display,	/* Bd */
136	NULL,		/* Ed */
137	post_bl,	/* Bl */
138	NULL,		/* El */
139	post_it,	/* It */
140	post_delim_nb,	/* Ad */
141	post_an,	/* An */
142	NULL,		/* Ap */
143	post_defaults,	/* Ar */
144	NULL,		/* Cd */
145	post_tag,	/* Cm */
146	post_tag,	/* Dv */
147	post_er,	/* Er */
148	post_tag,	/* Ev */
149	post_ex,	/* Ex */
150	post_fa,	/* Fa */
151	NULL,		/* Fd */
152	post_fl,	/* Fl */
153	post_fn,	/* Fn */
154	post_delim_nb,	/* Ft */
155	post_tag,	/* Ic */
156	post_delim_nb,	/* In */
157	post_tag,	/* Li */
158	post_nd,	/* Nd */
159	post_nm,	/* Nm */
160	post_delim_nb,	/* Op */
161	post_abort,	/* Ot */
162	post_defaults,	/* Pa */
163	post_rv,	/* Rv */
164	post_st,	/* St */
165	post_tag,	/* Va */
166	post_delim_nb,	/* Vt */
167	post_xr,	/* Xr */
168	NULL,		/* %A */
169	post_hyph,	/* %B */ /* FIXME: can be used outside Rs/Re. */
170	NULL,		/* %D */
171	NULL,		/* %I */
172	NULL,		/* %J */
173	post_hyph,	/* %N */
174	post_hyph,	/* %O */
175	NULL,		/* %P */
176	post_hyph,	/* %R */
177	post_hyph,	/* %T */ /* FIXME: can be used outside Rs/Re. */
178	NULL,		/* %V */
179	NULL,		/* Ac */
180	NULL,		/* Ao */
181	post_delim_nb,	/* Aq */
182	post_at,	/* At */
183	NULL,		/* Bc */
184	post_bf,	/* Bf */
185	NULL,		/* Bo */
186	NULL,		/* Bq */
187	post_xx,	/* Bsx */
188	post_bx,	/* Bx */
189	post_obsolete,	/* Db */
190	NULL,		/* Dc */
191	NULL,		/* Do */
192	NULL,		/* Dq */
193	NULL,		/* Ec */
194	NULL,		/* Ef */
195	post_em,	/* Em */
196	NULL,		/* Eo */
197	post_xx,	/* Fx */
198	post_tag,	/* Ms */
199	post_tag,	/* No */
200	post_ns,	/* Ns */
201	post_xx,	/* Nx */
202	post_xx,	/* Ox */
203	NULL,		/* Pc */
204	NULL,		/* Pf */
205	NULL,		/* Po */
206	post_delim_nb,	/* Pq */
207	NULL,		/* Qc */
208	post_delim_nb,	/* Ql */
209	NULL,		/* Qo */
210	post_delim_nb,	/* Qq */
211	NULL,		/* Re */
212	post_rs,	/* Rs */
213	NULL,		/* Sc */
214	NULL,		/* So */
215	post_delim_nb,	/* Sq */
216	post_sm,	/* Sm */
217	post_sx,	/* Sx */
218	post_em,	/* Sy */
219	post_useless,	/* Tn */
220	post_xx,	/* Ux */
221	NULL,		/* Xc */
222	NULL,		/* Xo */
223	post_fo,	/* Fo */
224	NULL,		/* Fc */
225	NULL,		/* Oo */
226	NULL,		/* Oc */
227	post_bk,	/* Bk */
228	NULL,		/* Ek */
229	post_eoln,	/* Bt */
230	post_obsolete,	/* Hf */
231	post_obsolete,	/* Fr */
232	post_eoln,	/* Ud */
233	post_lb,	/* Lb */
234	post_abort,	/* Lp */
235	post_delim_nb,	/* Lk */
236	post_defaults,	/* Mt */
237	post_delim_nb,	/* Brq */
238	NULL,		/* Bro */
239	NULL,		/* Brc */
240	NULL,		/* %C */
241	post_es,	/* Es */
242	post_en,	/* En */
243	post_xx,	/* Dx */
244	NULL,		/* %Q */
245	NULL,		/* %U */
246	NULL,		/* Ta */
247	post_tg,	/* Tg */
248};
249
250#define	RSORD_MAX 14 /* Number of `Rs' blocks. */
251
252static	const enum roff_tok rsord[RSORD_MAX] = {
253	MDOC__A,
254	MDOC__T,
255	MDOC__B,
256	MDOC__I,
257	MDOC__J,
258	MDOC__R,
259	MDOC__N,
260	MDOC__V,
261	MDOC__U,
262	MDOC__P,
263	MDOC__Q,
264	MDOC__C,
265	MDOC__D,
266	MDOC__O
267};
268
269static	const char * const secnames[SEC__MAX] = {
270	NULL,
271	"NAME",
272	"LIBRARY",
273	"SYNOPSIS",
274	"DESCRIPTION",
275	"CONTEXT",
276	"IMPLEMENTATION NOTES",
277	"RETURN VALUES",
278	"ENVIRONMENT",
279	"FILES",
280	"EXIT STATUS",
281	"EXAMPLES",
282	"DIAGNOSTICS",
283	"COMPATIBILITY",
284	"ERRORS",
285	"SEE ALSO",
286	"STANDARDS",
287	"HISTORY",
288	"AUTHORS",
289	"CAVEATS",
290	"BUGS",
291	"SECURITY CONSIDERATIONS",
292	NULL
293};
294
295static	int	  fn_prio = TAG_STRONG;
296
297
298/* Validate the subtree rooted at mdoc->last. */
299void
300mdoc_validate(struct roff_man *mdoc)
301{
302	struct roff_node *n, *np;
303	const v_post *p;
304
305	/*
306	 * Translate obsolete macros to modern macros first
307	 * such that later code does not need to look
308	 * for the obsolete versions.
309	 */
310
311	n = mdoc->last;
312	switch (n->tok) {
313	case MDOC_Lp:
314		n->tok = MDOC_Pp;
315		break;
316	case MDOC_Ot:
317		post_obsolete(mdoc);
318		n->tok = MDOC_Ft;
319		break;
320	default:
321		break;
322	}
323
324	/*
325	 * Iterate over all children, recursing into each one
326	 * in turn, depth-first.
327	 */
328
329	mdoc->last = mdoc->last->child;
330	while (mdoc->last != NULL) {
331		mdoc_validate(mdoc);
332		if (mdoc->last == n)
333			mdoc->last = mdoc->last->child;
334		else
335			mdoc->last = mdoc->last->next;
336	}
337
338	/* Finally validate the macro itself. */
339
340	mdoc->last = n;
341	mdoc->next = ROFF_NEXT_SIBLING;
342	switch (n->type) {
343	case ROFFT_TEXT:
344		np = n->parent;
345		if (n->sec != SEC_SYNOPSIS ||
346		    (np->tok != MDOC_Cd && np->tok != MDOC_Fd))
347			check_text(mdoc, n->line, n->pos, n->string);
348		if ((n->flags & NODE_NOFILL) == 0 &&
349		    (np->tok != MDOC_It || np->type != ROFFT_HEAD ||
350		     np->parent->parent->norm->Bl.type != LIST_diag))
351			check_text_em(mdoc, n->line, n->pos, n->string);
352		if (np->tok == MDOC_It || (np->type == ROFFT_BODY &&
353		    (np->tok == MDOC_Sh || np->tok == MDOC_Ss)))
354			check_toptext(mdoc, n->line, n->pos, n->string);
355		break;
356	case ROFFT_COMMENT:
357	case ROFFT_EQN:
358	case ROFFT_TBL:
359		break;
360	case ROFFT_ROOT:
361		post_root(mdoc);
362		break;
363	default:
364		check_args(mdoc, mdoc->last);
365
366		/*
367		 * Closing delimiters are not special at the
368		 * beginning of a block, opening delimiters
369		 * are not special at the end.
370		 */
371
372		if (n->child != NULL)
373			n->child->flags &= ~NODE_DELIMC;
374		if (n->last != NULL)
375			n->last->flags &= ~NODE_DELIMO;
376
377		/* Call the macro's postprocessor. */
378
379		if (n->tok < ROFF_MAX) {
380			roff_validate(mdoc);
381			break;
382		}
383
384		assert(n->tok >= MDOC_Dd && n->tok < MDOC_MAX);
385		p = mdoc_valids + (n->tok - MDOC_Dd);
386		if (*p)
387			(*p)(mdoc);
388		if (mdoc->last == n)
389			mdoc_state(mdoc, n);
390		break;
391	}
392}
393
394static void
395check_args(struct roff_man *mdoc, struct roff_node *n)
396{
397	int		 i;
398
399	if (NULL == n->args)
400		return;
401
402	assert(n->args->argc);
403	for (i = 0; i < (int)n->args->argc; i++)
404		check_argv(mdoc, n, &n->args->argv[i]);
405}
406
407static void
408check_argv(struct roff_man *mdoc, struct roff_node *n, struct mdoc_argv *v)
409{
410	int		 i;
411
412	for (i = 0; i < (int)v->sz; i++)
413		check_text(mdoc, v->line, v->pos, v->value[i]);
414}
415
416static void
417check_text(struct roff_man *mdoc, int ln, int pos, char *p)
418{
419	char		*cp;
420
421	if (mdoc->last->flags & NODE_NOFILL)
422		return;
423
424	for (cp = p; NULL != (p = strchr(p, '\t')); p++)
425		mandoc_msg(MANDOCERR_FI_TAB, ln, pos + (int)(p - cp), NULL);
426}
427
428static void
429check_text_em(struct roff_man *mdoc, int ln, int pos, char *p)
430{
431	const struct roff_node	*np, *nn;
432	char			*cp;
433
434	np = mdoc->last->prev;
435	nn = mdoc->last->next;
436
437	/* Look for em-dashes wrongly encoded as "--". */
438
439	for (cp = p; *cp != '\0'; cp++) {
440		if (cp[0] != '-' || cp[1] != '-')
441			continue;
442		cp++;
443
444		/* Skip input sequences of more than two '-'. */
445
446		if (cp[1] == '-') {
447			while (cp[1] == '-')
448				cp++;
449			continue;
450		}
451
452		/* Skip "--" directly attached to something else. */
453
454		if ((cp - p > 1 && cp[-2] != ' ') ||
455		    (cp[1] != '\0' && cp[1] != ' '))
456			continue;
457
458		/* Require a letter right before or right afterwards. */
459
460		if ((cp - p > 2 ?
461		     isalpha((unsigned char)cp[-3]) :
462		     np != NULL &&
463		     np->type == ROFFT_TEXT &&
464		     *np->string != '\0' &&
465		     isalpha((unsigned char)np->string[
466		       strlen(np->string) - 1])) ||
467		    (cp[1] != '\0' && cp[2] != '\0' ?
468		     isalpha((unsigned char)cp[2]) :
469		     nn != NULL &&
470		     nn->type == ROFFT_TEXT &&
471		     isalpha((unsigned char)*nn->string))) {
472			mandoc_msg(MANDOCERR_DASHDASH,
473			    ln, pos + (int)(cp - p) - 1, NULL);
474			break;
475		}
476	}
477}
478
479static void
480check_toptext(struct roff_man *mdoc, int ln, int pos, const char *p)
481{
482	const char	*cp, *cpr;
483
484	if (*p == '\0')
485		return;
486
487	if ((cp = strstr(p, "OpenBSD")) != NULL)
488		mandoc_msg(MANDOCERR_BX, ln, pos + (int)(cp - p), "Ox");
489	if ((cp = strstr(p, "NetBSD")) != NULL)
490		mandoc_msg(MANDOCERR_BX, ln, pos + (int)(cp - p), "Nx");
491	if ((cp = strstr(p, "FreeBSD")) != NULL)
492		mandoc_msg(MANDOCERR_BX, ln, pos + (int)(cp - p), "Fx");
493	if ((cp = strstr(p, "DragonFly")) != NULL)
494		mandoc_msg(MANDOCERR_BX, ln, pos + (int)(cp - p), "Dx");
495
496	cp = p;
497	while ((cp = strstr(cp + 1, "()")) != NULL) {
498		for (cpr = cp - 1; cpr >= p; cpr--)
499			if (*cpr != '_' && !isalnum((unsigned char)*cpr))
500				break;
501		if ((cpr < p || *cpr == ' ') && cpr + 1 < cp) {
502			cpr++;
503			mandoc_msg(MANDOCERR_FUNC, ln, pos + (int)(cpr - p),
504			    "%.*s()", (int)(cp - cpr), cpr);
505		}
506	}
507}
508
509static void
510post_abort(POST_ARGS)
511{
512	abort();
513}
514
515static void
516post_delim(POST_ARGS)
517{
518	const struct roff_node	*nch;
519	const char		*lc;
520	enum mdelim		 delim;
521	enum roff_tok		 tok;
522
523	tok = mdoc->last->tok;
524	nch = mdoc->last->last;
525	if (nch == NULL || nch->type != ROFFT_TEXT)
526		return;
527	lc = strchr(nch->string, '\0') - 1;
528	if (lc < nch->string)
529		return;
530	delim = mdoc_isdelim(lc);
531	if (delim == DELIM_NONE || delim == DELIM_OPEN)
532		return;
533	if (*lc == ')' && (tok == MDOC_Nd || tok == MDOC_Sh ||
534	    tok == MDOC_Ss || tok == MDOC_Fo))
535		return;
536
537	mandoc_msg(MANDOCERR_DELIM, nch->line,
538	    nch->pos + (int)(lc - nch->string), "%s%s %s", roff_name[tok],
539	    nch == mdoc->last->child ? "" : " ...", nch->string);
540}
541
542static void
543post_delim_nb(POST_ARGS)
544{
545	const struct roff_node	*nch;
546	const char		*lc, *cp;
547	int			 nw;
548	enum mdelim		 delim;
549	enum roff_tok		 tok;
550
551	/*
552	 * Find candidates: at least two bytes,
553	 * the last one a closing or middle delimiter.
554	 */
555
556	tok = mdoc->last->tok;
557	nch = mdoc->last->last;
558	if (nch == NULL || nch->type != ROFFT_TEXT)
559		return;
560	lc = strchr(nch->string, '\0') - 1;
561	if (lc <= nch->string)
562		return;
563	delim = mdoc_isdelim(lc);
564	if (delim == DELIM_NONE || delim == DELIM_OPEN)
565		return;
566
567	/*
568	 * Reduce false positives by allowing various cases.
569	 */
570
571	/* Escaped delimiters. */
572	if (lc > nch->string + 1 && lc[-2] == '\\' &&
573	    (lc[-1] == '&' || lc[-1] == 'e'))
574		return;
575
576	/* Specific byte sequences. */
577	switch (*lc) {
578	case ')':
579		for (cp = lc; cp >= nch->string; cp--)
580			if (*cp == '(')
581				return;
582		break;
583	case '.':
584		if (lc > nch->string + 1 && lc[-2] == '.' && lc[-1] == '.')
585			return;
586		if (lc[-1] == '.')
587			return;
588		break;
589	case ';':
590		if (tok == MDOC_Vt)
591			return;
592		break;
593	case '?':
594		if (lc[-1] == '?')
595			return;
596		break;
597	case ']':
598		for (cp = lc; cp >= nch->string; cp--)
599			if (*cp == '[')
600				return;
601		break;
602	case '|':
603		if (lc == nch->string + 1 && lc[-1] == '|')
604			return;
605	default:
606		break;
607	}
608
609	/* Exactly two non-alphanumeric bytes. */
610	if (lc == nch->string + 1 && !isalnum((unsigned char)lc[-1]))
611		return;
612
613	/* At least three alphabetic words with a sentence ending. */
614	if (strchr("!.:?", *lc) != NULL && (tok == MDOC_Em ||
615	    tok == MDOC_Li || tok == MDOC_Pq || tok == MDOC_Sy)) {
616		nw = 0;
617		for (cp = lc - 1; cp >= nch->string; cp--) {
618			if (*cp == ' ') {
619				nw++;
620				if (cp > nch->string && cp[-1] == ',')
621					cp--;
622			} else if (isalpha((unsigned int)*cp)) {
623				if (nw > 1)
624					return;
625			} else
626				break;
627		}
628	}
629
630	mandoc_msg(MANDOCERR_DELIM_NB, nch->line,
631	    nch->pos + (int)(lc - nch->string), "%s%s %s", roff_name[tok],
632	    nch == mdoc->last->child ? "" : " ...", nch->string);
633}
634
635static void
636post_bl_norm(POST_ARGS)
637{
638	struct roff_node *n;
639	struct mdoc_argv *argv, *wa;
640	int		  i;
641	enum mdocargt	  mdoclt;
642	enum mdoc_list	  lt;
643
644	n = mdoc->last->parent;
645	n->norm->Bl.type = LIST__NONE;
646
647	/*
648	 * First figure out which kind of list to use: bind ourselves to
649	 * the first mentioned list type and warn about any remaining
650	 * ones.  If we find no list type, we default to LIST_item.
651	 */
652
653	wa = (n->args == NULL) ? NULL : n->args->argv;
654	mdoclt = MDOC_ARG_MAX;
655	for (i = 0; n->args && i < (int)n->args->argc; i++) {
656		argv = n->args->argv + i;
657		lt = LIST__NONE;
658		switch (argv->arg) {
659		/* Set list types. */
660		case MDOC_Bullet:
661			lt = LIST_bullet;
662			break;
663		case MDOC_Dash:
664			lt = LIST_dash;
665			break;
666		case MDOC_Enum:
667			lt = LIST_enum;
668			break;
669		case MDOC_Hyphen:
670			lt = LIST_hyphen;
671			break;
672		case MDOC_Item:
673			lt = LIST_item;
674			break;
675		case MDOC_Tag:
676			lt = LIST_tag;
677			break;
678		case MDOC_Diag:
679			lt = LIST_diag;
680			break;
681		case MDOC_Hang:
682			lt = LIST_hang;
683			break;
684		case MDOC_Ohang:
685			lt = LIST_ohang;
686			break;
687		case MDOC_Inset:
688			lt = LIST_inset;
689			break;
690		case MDOC_Column:
691			lt = LIST_column;
692			break;
693		/* Set list arguments. */
694		case MDOC_Compact:
695			if (n->norm->Bl.comp)
696				mandoc_msg(MANDOCERR_ARG_REP,
697				    argv->line, argv->pos, "Bl -compact");
698			n->norm->Bl.comp = 1;
699			break;
700		case MDOC_Width:
701			wa = argv;
702			if (0 == argv->sz) {
703				mandoc_msg(MANDOCERR_ARG_EMPTY,
704				    argv->line, argv->pos, "Bl -width");
705				n->norm->Bl.width = "0n";
706				break;
707			}
708			if (NULL != n->norm->Bl.width)
709				mandoc_msg(MANDOCERR_ARG_REP,
710				    argv->line, argv->pos,
711				    "Bl -width %s", argv->value[0]);
712			rewrite_macro2len(mdoc, argv->value);
713			n->norm->Bl.width = argv->value[0];
714			break;
715		case MDOC_Offset:
716			if (0 == argv->sz) {
717				mandoc_msg(MANDOCERR_ARG_EMPTY,
718				    argv->line, argv->pos, "Bl -offset");
719				break;
720			}
721			if (NULL != n->norm->Bl.offs)
722				mandoc_msg(MANDOCERR_ARG_REP,
723				    argv->line, argv->pos,
724				    "Bl -offset %s", argv->value[0]);
725			rewrite_macro2len(mdoc, argv->value);
726			n->norm->Bl.offs = argv->value[0];
727			break;
728		default:
729			continue;
730		}
731		if (LIST__NONE == lt)
732			continue;
733		mdoclt = argv->arg;
734
735		/* Check: multiple list types. */
736
737		if (LIST__NONE != n->norm->Bl.type) {
738			mandoc_msg(MANDOCERR_BL_REP, n->line, n->pos,
739			    "Bl -%s", mdoc_argnames[argv->arg]);
740			continue;
741		}
742
743		/* The list type should come first. */
744
745		if (n->norm->Bl.width ||
746		    n->norm->Bl.offs ||
747		    n->norm->Bl.comp)
748			mandoc_msg(MANDOCERR_BL_LATETYPE,
749			    n->line, n->pos, "Bl -%s",
750			    mdoc_argnames[n->args->argv[0].arg]);
751
752		n->norm->Bl.type = lt;
753		if (LIST_column == lt) {
754			n->norm->Bl.ncols = argv->sz;
755			n->norm->Bl.cols = (void *)argv->value;
756		}
757	}
758
759	/* Allow lists to default to LIST_item. */
760
761	if (LIST__NONE == n->norm->Bl.type) {
762		mandoc_msg(MANDOCERR_BL_NOTYPE, n->line, n->pos, "Bl");
763		n->norm->Bl.type = LIST_item;
764		mdoclt = MDOC_Item;
765	}
766
767	/*
768	 * Validate the width field.  Some list types don't need width
769	 * types and should be warned about them.  Others should have it
770	 * and must also be warned.  Yet others have a default and need
771	 * no warning.
772	 */
773
774	switch (n->norm->Bl.type) {
775	case LIST_tag:
776		if (n->norm->Bl.width == NULL)
777			mandoc_msg(MANDOCERR_BL_NOWIDTH,
778			    n->line, n->pos, "Bl -tag");
779		break;
780	case LIST_column:
781	case LIST_diag:
782	case LIST_ohang:
783	case LIST_inset:
784	case LIST_item:
785		if (n->norm->Bl.width != NULL)
786			mandoc_msg(MANDOCERR_BL_SKIPW, wa->line, wa->pos,
787			    "Bl -%s", mdoc_argnames[mdoclt]);
788		n->norm->Bl.width = NULL;
789		break;
790	case LIST_bullet:
791	case LIST_dash:
792	case LIST_hyphen:
793		if (n->norm->Bl.width == NULL)
794			n->norm->Bl.width = "2n";
795		break;
796	case LIST_enum:
797		if (n->norm->Bl.width == NULL)
798			n->norm->Bl.width = "3n";
799		break;
800	default:
801		break;
802	}
803}
804
805static void
806post_bd(POST_ARGS)
807{
808	struct roff_node *n;
809	struct mdoc_argv *argv;
810	int		  i;
811	enum mdoc_disp	  dt;
812
813	n = mdoc->last;
814	for (i = 0; n->args && i < (int)n->args->argc; i++) {
815		argv = n->args->argv + i;
816		dt = DISP__NONE;
817
818		switch (argv->arg) {
819		case MDOC_Centred:
820			dt = DISP_centered;
821			break;
822		case MDOC_Ragged:
823			dt = DISP_ragged;
824			break;
825		case MDOC_Unfilled:
826			dt = DISP_unfilled;
827			break;
828		case MDOC_Filled:
829			dt = DISP_filled;
830			break;
831		case MDOC_Literal:
832			dt = DISP_literal;
833			break;
834		case MDOC_File:
835			mandoc_msg(MANDOCERR_BD_FILE, n->line, n->pos, NULL);
836			break;
837		case MDOC_Offset:
838			if (0 == argv->sz) {
839				mandoc_msg(MANDOCERR_ARG_EMPTY,
840				    argv->line, argv->pos, "Bd -offset");
841				break;
842			}
843			if (NULL != n->norm->Bd.offs)
844				mandoc_msg(MANDOCERR_ARG_REP,
845				    argv->line, argv->pos,
846				    "Bd -offset %s", argv->value[0]);
847			rewrite_macro2len(mdoc, argv->value);
848			n->norm->Bd.offs = argv->value[0];
849			break;
850		case MDOC_Compact:
851			if (n->norm->Bd.comp)
852				mandoc_msg(MANDOCERR_ARG_REP,
853				    argv->line, argv->pos, "Bd -compact");
854			n->norm->Bd.comp = 1;
855			break;
856		default:
857			abort();
858		}
859		if (DISP__NONE == dt)
860			continue;
861
862		if (DISP__NONE == n->norm->Bd.type)
863			n->norm->Bd.type = dt;
864		else
865			mandoc_msg(MANDOCERR_BD_REP, n->line, n->pos,
866			    "Bd -%s", mdoc_argnames[argv->arg]);
867	}
868
869	if (DISP__NONE == n->norm->Bd.type) {
870		mandoc_msg(MANDOCERR_BD_NOTYPE, n->line, n->pos, "Bd");
871		n->norm->Bd.type = DISP_ragged;
872	}
873}
874
875/*
876 * Stand-alone line macros.
877 */
878
879static void
880post_an_norm(POST_ARGS)
881{
882	struct roff_node *n;
883	struct mdoc_argv *argv;
884	size_t	 i;
885
886	n = mdoc->last;
887	if (n->args == NULL)
888		return;
889
890	for (i = 1; i < n->args->argc; i++) {
891		argv = n->args->argv + i;
892		mandoc_msg(MANDOCERR_AN_REP, argv->line, argv->pos,
893		    "An -%s", mdoc_argnames[argv->arg]);
894	}
895
896	argv = n->args->argv;
897	if (argv->arg == MDOC_Split)
898		n->norm->An.auth = AUTH_split;
899	else if (argv->arg == MDOC_Nosplit)
900		n->norm->An.auth = AUTH_nosplit;
901	else
902		abort();
903}
904
905static void
906post_eoln(POST_ARGS)
907{
908	struct roff_node	*n;
909
910	post_useless(mdoc);
911	n = mdoc->last;
912	if (n->child != NULL)
913		mandoc_msg(MANDOCERR_ARG_SKIP, n->line,
914		    n->pos, "%s %s", roff_name[n->tok], n->child->string);
915
916	while (n->child != NULL)
917		roff_node_delete(mdoc, n->child);
918
919	roff_word_alloc(mdoc, n->line, n->pos, n->tok == MDOC_Bt ?
920	    "is currently in beta test." : "currently under development.");
921	mdoc->last->flags |= NODE_EOS | NODE_NOSRC;
922	mdoc->last = n;
923}
924
925static int
926build_list(struct roff_man *mdoc, int tok)
927{
928	struct roff_node	*n;
929	int			 ic;
930
931	n = mdoc->last->next;
932	for (ic = 1;; ic++) {
933		roff_elem_alloc(mdoc, n->line, n->pos, tok);
934		mdoc->last->flags |= NODE_NOSRC;
935		roff_node_relink(mdoc, n);
936		n = mdoc->last = mdoc->last->parent;
937		mdoc->next = ROFF_NEXT_SIBLING;
938		if (n->next == NULL)
939			return ic;
940		if (ic > 1 || n->next->next != NULL) {
941			roff_word_alloc(mdoc, n->line, n->pos, ",");
942			mdoc->last->flags |= NODE_DELIMC | NODE_NOSRC;
943		}
944		n = mdoc->last->next;
945		if (n->next == NULL) {
946			roff_word_alloc(mdoc, n->line, n->pos, "and");
947			mdoc->last->flags |= NODE_NOSRC;
948		}
949	}
950}
951
952static void
953post_ex(POST_ARGS)
954{
955	struct roff_node	*n;
956	int			 ic;
957
958	post_std(mdoc);
959
960	n = mdoc->last;
961	mdoc->next = ROFF_NEXT_CHILD;
962	roff_word_alloc(mdoc, n->line, n->pos, "The");
963	mdoc->last->flags |= NODE_NOSRC;
964
965	if (mdoc->last->next != NULL)
966		ic = build_list(mdoc, MDOC_Nm);
967	else if (mdoc->meta.name != NULL) {
968		roff_elem_alloc(mdoc, n->line, n->pos, MDOC_Nm);
969		mdoc->last->flags |= NODE_NOSRC;
970		roff_word_alloc(mdoc, n->line, n->pos, mdoc->meta.name);
971		mdoc->last->flags |= NODE_NOSRC;
972		mdoc->last = mdoc->last->parent;
973		mdoc->next = ROFF_NEXT_SIBLING;
974		ic = 1;
975	} else {
976		mandoc_msg(MANDOCERR_EX_NONAME, n->line, n->pos, "Ex");
977		ic = 0;
978	}
979
980	roff_word_alloc(mdoc, n->line, n->pos,
981	    ic > 1 ? "utilities exit\\~0" : "utility exits\\~0");
982	mdoc->last->flags |= NODE_NOSRC;
983	roff_word_alloc(mdoc, n->line, n->pos,
984	    "on success, and\\~>0 if an error occurs.");
985	mdoc->last->flags |= NODE_EOS | NODE_NOSRC;
986	mdoc->last = n;
987}
988
989static void
990post_lb(POST_ARGS)
991{
992	struct roff_node	*n;
993
994	post_delim_nb(mdoc);
995
996	n = mdoc->last;
997	assert(n->child->type == ROFFT_TEXT);
998	mdoc->next = ROFF_NEXT_CHILD;
999	roff_word_alloc(mdoc, n->line, n->pos, "library");
1000	mdoc->last->flags = NODE_NOSRC;
1001	roff_word_alloc(mdoc, n->line, n->pos, "\\(lq");
1002	mdoc->last->flags = NODE_DELIMO | NODE_NOSRC;
1003	mdoc->last = mdoc->last->next;
1004	roff_word_alloc(mdoc, n->line, n->pos, "\\(rq");
1005	mdoc->last->flags = NODE_DELIMC | NODE_NOSRC;
1006	mdoc->last = n;
1007}
1008
1009static void
1010post_rv(POST_ARGS)
1011{
1012	struct roff_node	*n;
1013	int			 ic;
1014
1015	post_std(mdoc);
1016
1017	n = mdoc->last;
1018	mdoc->next = ROFF_NEXT_CHILD;
1019	if (n->child != NULL) {
1020		roff_word_alloc(mdoc, n->line, n->pos, "The");
1021		mdoc->last->flags |= NODE_NOSRC;
1022		ic = build_list(mdoc, MDOC_Fn);
1023		roff_word_alloc(mdoc, n->line, n->pos,
1024		    ic > 1 ? "functions return" : "function returns");
1025		mdoc->last->flags |= NODE_NOSRC;
1026		roff_word_alloc(mdoc, n->line, n->pos,
1027		    "the value\\~0 if successful;");
1028	} else
1029		roff_word_alloc(mdoc, n->line, n->pos, "Upon successful "
1030		    "completion, the value\\~0 is returned;");
1031	mdoc->last->flags |= NODE_NOSRC;
1032
1033	roff_word_alloc(mdoc, n->line, n->pos, "otherwise "
1034	    "the value\\~\\-1 is returned and the global variable");
1035	mdoc->last->flags |= NODE_NOSRC;
1036	roff_elem_alloc(mdoc, n->line, n->pos, MDOC_Va);
1037	mdoc->last->flags |= NODE_NOSRC;
1038	roff_word_alloc(mdoc, n->line, n->pos, "errno");
1039	mdoc->last->flags |= NODE_NOSRC;
1040	mdoc->last = mdoc->last->parent;
1041	mdoc->next = ROFF_NEXT_SIBLING;
1042	roff_word_alloc(mdoc, n->line, n->pos,
1043	    "is set to indicate the error.");
1044	mdoc->last->flags |= NODE_EOS | NODE_NOSRC;
1045	mdoc->last = n;
1046}
1047
1048static void
1049post_std(POST_ARGS)
1050{
1051	struct roff_node *n;
1052
1053	post_delim(mdoc);
1054
1055	n = mdoc->last;
1056	if (n->args && n->args->argc == 1)
1057		if (n->args->argv[0].arg == MDOC_Std)
1058			return;
1059
1060	mandoc_msg(MANDOCERR_ARG_STD, n->line, n->pos,
1061	    "%s", roff_name[n->tok]);
1062}
1063
1064static void
1065post_st(POST_ARGS)
1066{
1067	struct roff_node	 *n, *nch;
1068	const char		 *p;
1069
1070	n = mdoc->last;
1071	nch = n->child;
1072	assert(nch->type == ROFFT_TEXT);
1073
1074	if ((p = mdoc_a2st(nch->string)) == NULL) {
1075		mandoc_msg(MANDOCERR_ST_BAD,
1076		    nch->line, nch->pos, "St %s", nch->string);
1077		roff_node_delete(mdoc, n);
1078		return;
1079	}
1080
1081	nch->flags |= NODE_NOPRT;
1082	mdoc->next = ROFF_NEXT_CHILD;
1083	roff_word_alloc(mdoc, nch->line, nch->pos, p);
1084	mdoc->last->flags |= NODE_NOSRC;
1085	mdoc->last= n;
1086}
1087
1088static void
1089post_tg(POST_ARGS)
1090{
1091	struct roff_node *n;	/* The .Tg node. */
1092	struct roff_node *nch;	/* The first child of the .Tg node. */
1093	struct roff_node *nn;   /* The next node after the .Tg node. */
1094	struct roff_node *np;	/* The parent of the next node. */
1095	struct roff_node *nt;	/* The TEXT node containing the tag. */
1096	size_t		  len;	/* The number of bytes in the tag. */
1097
1098	/* Find the next node. */
1099	n = mdoc->last;
1100	for (nn = n; nn != NULL; nn = nn->parent) {
1101		if (nn->type != ROFFT_HEAD && nn->type != ROFFT_BODY &&
1102		    nn->type != ROFFT_TAIL && nn->next != NULL) {
1103			nn = nn->next;
1104			break;
1105		}
1106	}
1107
1108	/* Find the tag. */
1109	nt = nch = n->child;
1110	if (nch == NULL && nn != NULL && nn->child != NULL &&
1111	    nn->child->type == ROFFT_TEXT)
1112		nt = nn->child;
1113
1114	/* Validate the tag. */
1115	if (nt == NULL || *nt->string == '\0')
1116		mandoc_msg(MANDOCERR_MACRO_EMPTY, n->line, n->pos, "Tg");
1117	if (nt == NULL) {
1118		roff_node_delete(mdoc, n);
1119		return;
1120	}
1121	len = strcspn(nt->string, " \t\\");
1122	if (nt->string[len] != '\0')
1123		mandoc_msg(MANDOCERR_TG_SPC, nt->line,
1124		    nt->pos + len, "Tg %s", nt->string);
1125
1126	/* Keep only the first argument. */
1127	if (nch != NULL && nch->next != NULL) {
1128		mandoc_msg(MANDOCERR_ARG_EXCESS, nch->next->line,
1129		    nch->next->pos, "Tg ... %s", nch->next->string);
1130		while (nch->next != NULL)
1131			roff_node_delete(mdoc, nch->next);
1132	}
1133
1134	/* Drop the macro if the first argument is invalid. */
1135	if (len == 0 || nt->string[len] != '\0') {
1136		roff_node_delete(mdoc, n);
1137		return;
1138	}
1139
1140	/* By default, tag the .Tg node itself. */
1141	if (nn == NULL || nn->flags & NODE_ID)
1142		nn = n;
1143
1144	/* Explicit tagging of specific macros. */
1145	switch (nn->tok) {
1146	case MDOC_Sh:
1147	case MDOC_Ss:
1148	case MDOC_Fo:
1149		nn = nn->head->child == NULL ? n : nn->head;
1150		break;
1151	case MDOC_It:
1152		np = nn->parent;
1153		while (np->tok != MDOC_Bl)
1154			np = np->parent;
1155		switch (np->norm->Bl.type) {
1156		case LIST_column:
1157			break;
1158		case LIST_diag:
1159		case LIST_hang:
1160		case LIST_inset:
1161		case LIST_ohang:
1162		case LIST_tag:
1163			nn = nn->head;
1164			break;
1165		case LIST_bullet:
1166		case LIST_dash:
1167		case LIST_enum:
1168		case LIST_hyphen:
1169		case LIST_item:
1170			nn = nn->body->child == NULL ? n : nn->body;
1171			break;
1172		default:
1173			abort();
1174		}
1175		break;
1176	case MDOC_Bd:
1177	case MDOC_Bl:
1178	case MDOC_D1:
1179	case MDOC_Dl:
1180		nn = nn->body->child == NULL ? n : nn->body;
1181		break;
1182	case MDOC_Pp:
1183		break;
1184	case MDOC_Cm:
1185	case MDOC_Dv:
1186	case MDOC_Em:
1187	case MDOC_Er:
1188	case MDOC_Ev:
1189	case MDOC_Fl:
1190	case MDOC_Fn:
1191	case MDOC_Ic:
1192	case MDOC_Li:
1193	case MDOC_Ms:
1194	case MDOC_No:
1195	case MDOC_Sy:
1196		if (nn->child == NULL)
1197			nn = n;
1198		break;
1199	default:
1200		nn = n;
1201		break;
1202	}
1203	tag_put(nt->string, TAG_MANUAL, nn);
1204	if (nn != n)
1205		n->flags |= NODE_NOPRT;
1206}
1207
1208static void
1209post_obsolete(POST_ARGS)
1210{
1211	struct roff_node *n;
1212
1213	n = mdoc->last;
1214	if (n->type == ROFFT_ELEM || n->type == ROFFT_BLOCK)
1215		mandoc_msg(MANDOCERR_MACRO_OBS, n->line, n->pos,
1216		    "%s", roff_name[n->tok]);
1217}
1218
1219static void
1220post_useless(POST_ARGS)
1221{
1222	struct roff_node *n;
1223
1224	n = mdoc->last;
1225	mandoc_msg(MANDOCERR_MACRO_USELESS, n->line, n->pos,
1226	    "%s", roff_name[n->tok]);
1227}
1228
1229/*
1230 * Block macros.
1231 */
1232
1233static void
1234post_bf(POST_ARGS)
1235{
1236	struct roff_node *np, *nch;
1237
1238	/*
1239	 * Unlike other data pointers, these are "housed" by the HEAD
1240	 * element, which contains the goods.
1241	 */
1242
1243	np = mdoc->last;
1244	if (np->type != ROFFT_HEAD)
1245		return;
1246
1247	assert(np->parent->type == ROFFT_BLOCK);
1248	assert(np->parent->tok == MDOC_Bf);
1249
1250	/* Check the number of arguments. */
1251
1252	nch = np->child;
1253	if (np->parent->args == NULL) {
1254		if (nch == NULL) {
1255			mandoc_msg(MANDOCERR_BF_NOFONT,
1256			    np->line, np->pos, "Bf");
1257			return;
1258		}
1259		nch = nch->next;
1260	}
1261	if (nch != NULL)
1262		mandoc_msg(MANDOCERR_ARG_EXCESS,
1263		    nch->line, nch->pos, "Bf ... %s", nch->string);
1264
1265	/* Extract argument into data. */
1266
1267	if (np->parent->args != NULL) {
1268		switch (np->parent->args->argv[0].arg) {
1269		case MDOC_Emphasis:
1270			np->norm->Bf.font = FONT_Em;
1271			break;
1272		case MDOC_Literal:
1273			np->norm->Bf.font = FONT_Li;
1274			break;
1275		case MDOC_Symbolic:
1276			np->norm->Bf.font = FONT_Sy;
1277			break;
1278		default:
1279			abort();
1280		}
1281		return;
1282	}
1283
1284	/* Extract parameter into data. */
1285
1286	if ( ! strcmp(np->child->string, "Em"))
1287		np->norm->Bf.font = FONT_Em;
1288	else if ( ! strcmp(np->child->string, "Li"))
1289		np->norm->Bf.font = FONT_Li;
1290	else if ( ! strcmp(np->child->string, "Sy"))
1291		np->norm->Bf.font = FONT_Sy;
1292	else
1293		mandoc_msg(MANDOCERR_BF_BADFONT, np->child->line,
1294		    np->child->pos, "Bf %s", np->child->string);
1295}
1296
1297static void
1298post_fname(POST_ARGS)
1299{
1300	struct roff_node	*n, *nch;
1301	const char		*cp;
1302	size_t			 pos;
1303
1304	n = mdoc->last;
1305	nch = n->child;
1306	cp = nch->string;
1307	if (*cp == '(') {
1308		if (cp[strlen(cp + 1)] == ')')
1309			return;
1310		pos = 0;
1311	} else {
1312		pos = strcspn(cp, "()");
1313		if (cp[pos] == '\0') {
1314			if (n->sec == SEC_DESCRIPTION ||
1315			    n->sec == SEC_CUSTOM)
1316				tag_put(NULL, fn_prio++, n);
1317			return;
1318		}
1319	}
1320	mandoc_msg(MANDOCERR_FN_PAREN, nch->line, nch->pos + pos, "%s", cp);
1321}
1322
1323static void
1324post_fn(POST_ARGS)
1325{
1326	post_fname(mdoc);
1327	post_fa(mdoc);
1328}
1329
1330static void
1331post_fo(POST_ARGS)
1332{
1333	const struct roff_node	*n;
1334
1335	n = mdoc->last;
1336
1337	if (n->type != ROFFT_HEAD)
1338		return;
1339
1340	if (n->child == NULL) {
1341		mandoc_msg(MANDOCERR_FO_NOHEAD, n->line, n->pos, "Fo");
1342		return;
1343	}
1344	if (n->child != n->last) {
1345		mandoc_msg(MANDOCERR_ARG_EXCESS,
1346		    n->child->next->line, n->child->next->pos,
1347		    "Fo ... %s", n->child->next->string);
1348		while (n->child != n->last)
1349			roff_node_delete(mdoc, n->last);
1350	} else
1351		post_delim(mdoc);
1352
1353	post_fname(mdoc);
1354}
1355
1356static void
1357post_fa(POST_ARGS)
1358{
1359	const struct roff_node *n;
1360	const char *cp;
1361
1362	for (n = mdoc->last->child; n != NULL; n = n->next) {
1363		for (cp = n->string; *cp != '\0'; cp++) {
1364			/* Ignore callbacks and alterations. */
1365			if (*cp == '(' || *cp == '{')
1366				break;
1367			if (*cp != ',')
1368				continue;
1369			mandoc_msg(MANDOCERR_FA_COMMA, n->line,
1370			    n->pos + (int)(cp - n->string), "%s", n->string);
1371			break;
1372		}
1373	}
1374	post_delim_nb(mdoc);
1375}
1376
1377static void
1378post_nm(POST_ARGS)
1379{
1380	struct roff_node	*n;
1381
1382	n = mdoc->last;
1383
1384	if (n->sec == SEC_NAME && n->child != NULL &&
1385	    n->child->type == ROFFT_TEXT && mdoc->meta.msec != NULL)
1386		mandoc_xr_add(mdoc->meta.msec, n->child->string, -1, -1);
1387
1388	if (n->last != NULL && n->last->tok == MDOC_Pp)
1389		roff_node_relink(mdoc, n->last);
1390
1391	if (mdoc->meta.name == NULL)
1392		deroff(&mdoc->meta.name, n);
1393
1394	if (mdoc->meta.name == NULL ||
1395	    (mdoc->lastsec == SEC_NAME && n->child == NULL))
1396		mandoc_msg(MANDOCERR_NM_NONAME, n->line, n->pos, "Nm");
1397
1398	switch (n->type) {
1399	case ROFFT_ELEM:
1400		post_delim_nb(mdoc);
1401		break;
1402	case ROFFT_HEAD:
1403		post_delim(mdoc);
1404		break;
1405	default:
1406		return;
1407	}
1408
1409	if ((n->child != NULL && n->child->type == ROFFT_TEXT) ||
1410	    mdoc->meta.name == NULL)
1411		return;
1412
1413	mdoc->next = ROFF_NEXT_CHILD;
1414	roff_word_alloc(mdoc, n->line, n->pos, mdoc->meta.name);
1415	mdoc->last->flags |= NODE_NOSRC;
1416	mdoc->last = n;
1417}
1418
1419static void
1420post_nd(POST_ARGS)
1421{
1422	struct roff_node	*n;
1423
1424	n = mdoc->last;
1425
1426	if (n->type != ROFFT_BODY)
1427		return;
1428
1429	if (n->sec != SEC_NAME)
1430		mandoc_msg(MANDOCERR_ND_LATE, n->line, n->pos, "Nd");
1431
1432	if (n->child == NULL)
1433		mandoc_msg(MANDOCERR_ND_EMPTY, n->line, n->pos, "Nd");
1434	else
1435		post_delim(mdoc);
1436
1437	post_hyph(mdoc);
1438}
1439
1440static void
1441post_display(POST_ARGS)
1442{
1443	struct roff_node *n, *np;
1444
1445	n = mdoc->last;
1446	switch (n->type) {
1447	case ROFFT_BODY:
1448		if (n->end != ENDBODY_NOT) {
1449			if (n->tok == MDOC_Bd &&
1450			    n->body->parent->args == NULL)
1451				roff_node_delete(mdoc, n);
1452		} else if (n->child == NULL)
1453			mandoc_msg(MANDOCERR_BLK_EMPTY, n->line, n->pos,
1454			    "%s", roff_name[n->tok]);
1455		else if (n->tok == MDOC_D1)
1456			post_hyph(mdoc);
1457		break;
1458	case ROFFT_BLOCK:
1459		if (n->tok == MDOC_Bd) {
1460			if (n->args == NULL) {
1461				mandoc_msg(MANDOCERR_BD_NOARG,
1462				    n->line, n->pos, "Bd");
1463				mdoc->next = ROFF_NEXT_SIBLING;
1464				while (n->body->child != NULL)
1465					roff_node_relink(mdoc,
1466					    n->body->child);
1467				roff_node_delete(mdoc, n);
1468				break;
1469			}
1470			post_bd(mdoc);
1471			post_prevpar(mdoc);
1472		}
1473		for (np = n->parent; np != NULL; np = np->parent) {
1474			if (np->type == ROFFT_BLOCK && np->tok == MDOC_Bd) {
1475				mandoc_msg(MANDOCERR_BD_NEST, n->line,
1476				    n->pos, "%s in Bd", roff_name[n->tok]);
1477				break;
1478			}
1479		}
1480		break;
1481	default:
1482		break;
1483	}
1484}
1485
1486static void
1487post_defaults(POST_ARGS)
1488{
1489	struct roff_node *n;
1490
1491	n = mdoc->last;
1492	if (n->child != NULL) {
1493		post_delim_nb(mdoc);
1494		return;
1495	}
1496	mdoc->next = ROFF_NEXT_CHILD;
1497	switch (n->tok) {
1498	case MDOC_Ar:
1499		roff_word_alloc(mdoc, n->line, n->pos, "file");
1500		mdoc->last->flags |= NODE_NOSRC;
1501		roff_word_alloc(mdoc, n->line, n->pos, "...");
1502		break;
1503	case MDOC_Pa:
1504	case MDOC_Mt:
1505		roff_word_alloc(mdoc, n->line, n->pos, "~");
1506		break;
1507	default:
1508		abort();
1509	}
1510	mdoc->last->flags |= NODE_NOSRC;
1511	mdoc->last = n;
1512}
1513
1514static void
1515post_at(POST_ARGS)
1516{
1517	struct roff_node	*n, *nch;
1518	const char		*att;
1519
1520	n = mdoc->last;
1521	nch = n->child;
1522
1523	/*
1524	 * If we have a child, look it up in the standard keys.  If a
1525	 * key exist, use that instead of the child; if it doesn't,
1526	 * prefix "AT&T UNIX " to the existing data.
1527	 */
1528
1529	att = NULL;
1530	if (nch != NULL && ((att = mdoc_a2att(nch->string)) == NULL))
1531		mandoc_msg(MANDOCERR_AT_BAD,
1532		    nch->line, nch->pos, "At %s", nch->string);
1533
1534	mdoc->next = ROFF_NEXT_CHILD;
1535	if (att != NULL) {
1536		roff_word_alloc(mdoc, nch->line, nch->pos, att);
1537		nch->flags |= NODE_NOPRT;
1538	} else
1539		roff_word_alloc(mdoc, n->line, n->pos, "AT&T UNIX");
1540	mdoc->last->flags |= NODE_NOSRC;
1541	mdoc->last = n;
1542}
1543
1544static void
1545post_an(POST_ARGS)
1546{
1547	struct roff_node *np, *nch;
1548
1549	post_an_norm(mdoc);
1550
1551	np = mdoc->last;
1552	nch = np->child;
1553	if (np->norm->An.auth == AUTH__NONE) {
1554		if (nch == NULL)
1555			mandoc_msg(MANDOCERR_MACRO_EMPTY,
1556			    np->line, np->pos, "An");
1557		else
1558			post_delim_nb(mdoc);
1559	} else if (nch != NULL)
1560		mandoc_msg(MANDOCERR_ARG_EXCESS,
1561		    nch->line, nch->pos, "An ... %s", nch->string);
1562}
1563
1564static void
1565post_em(POST_ARGS)
1566{
1567	post_tag(mdoc);
1568	tag_put(NULL, TAG_FALLBACK, mdoc->last);
1569}
1570
1571static void
1572post_en(POST_ARGS)
1573{
1574	post_obsolete(mdoc);
1575	if (mdoc->last->type == ROFFT_BLOCK)
1576		mdoc->last->norm->Es = mdoc->last_es;
1577}
1578
1579static void
1580post_er(POST_ARGS)
1581{
1582	struct roff_node *n;
1583
1584	n = mdoc->last;
1585	if (n->sec == SEC_ERRORS &&
1586	    (n->parent->tok == MDOC_It ||
1587	     (n->parent->tok == MDOC_Bq &&
1588	      n->parent->parent->parent->tok == MDOC_It)))
1589		tag_put(NULL, TAG_STRONG, n);
1590	post_delim_nb(mdoc);
1591}
1592
1593static void
1594post_tag(POST_ARGS)
1595{
1596	struct roff_node *n;
1597
1598	n = mdoc->last;
1599	if ((n->prev == NULL ||
1600	     (n->prev->type == ROFFT_TEXT &&
1601	      strcmp(n->prev->string, "|") == 0)) &&
1602	    (n->parent->tok == MDOC_It ||
1603	     (n->parent->tok == MDOC_Xo &&
1604	      n->parent->parent->prev == NULL &&
1605	      n->parent->parent->parent->tok == MDOC_It)))
1606		tag_put(NULL, TAG_STRONG, n);
1607	post_delim_nb(mdoc);
1608}
1609
1610static void
1611post_es(POST_ARGS)
1612{
1613	post_obsolete(mdoc);
1614	mdoc->last_es = mdoc->last;
1615}
1616
1617static void
1618post_fl(POST_ARGS)
1619{
1620	struct roff_node	*n;
1621	char			*cp;
1622
1623	/*
1624	 * Transform ".Fl Fl long" to ".Fl \-long",
1625	 * resulting for example in better HTML output.
1626	 */
1627
1628	n = mdoc->last;
1629	if (n->prev != NULL && n->prev->tok == MDOC_Fl &&
1630	    n->prev->child == NULL && n->child != NULL &&
1631	    (n->flags & NODE_LINE) == 0) {
1632		mandoc_asprintf(&cp, "\\-%s", n->child->string);
1633		free(n->child->string);
1634		n->child->string = cp;
1635		roff_node_delete(mdoc, n->prev);
1636	}
1637	post_tag(mdoc);
1638}
1639
1640static void
1641post_xx(POST_ARGS)
1642{
1643	struct roff_node	*n;
1644	const char		*os;
1645	char			*v;
1646
1647	post_delim_nb(mdoc);
1648
1649	n = mdoc->last;
1650	switch (n->tok) {
1651	case MDOC_Bsx:
1652		os = "BSD/OS";
1653		break;
1654	case MDOC_Dx:
1655		os = "DragonFly";
1656		break;
1657	case MDOC_Fx:
1658		os = "FreeBSD";
1659		break;
1660	case MDOC_Nx:
1661		os = "NetBSD";
1662		if (n->child == NULL)
1663			break;
1664		v = n->child->string;
1665		if ((v[0] != '0' && v[0] != '1') || v[1] != '.' ||
1666		    v[2] < '0' || v[2] > '9' ||
1667		    v[3] < 'a' || v[3] > 'z' || v[4] != '\0')
1668			break;
1669		n->child->flags |= NODE_NOPRT;
1670		mdoc->next = ROFF_NEXT_CHILD;
1671		roff_word_alloc(mdoc, n->child->line, n->child->pos, v);
1672		v = mdoc->last->string;
1673		v[3] = toupper((unsigned char)v[3]);
1674		mdoc->last->flags |= NODE_NOSRC;
1675		mdoc->last = n;
1676		break;
1677	case MDOC_Ox:
1678		os = "OpenBSD";
1679		break;
1680	case MDOC_Ux:
1681		os = "UNIX";
1682		break;
1683	default:
1684		abort();
1685	}
1686	mdoc->next = ROFF_NEXT_CHILD;
1687	roff_word_alloc(mdoc, n->line, n->pos, os);
1688	mdoc->last->flags |= NODE_NOSRC;
1689	mdoc->last = n;
1690}
1691
1692static void
1693post_it(POST_ARGS)
1694{
1695	struct roff_node *nbl, *nit, *nch;
1696	int		  i, cols;
1697	enum mdoc_list	  lt;
1698
1699	post_prevpar(mdoc);
1700
1701	nit = mdoc->last;
1702	if (nit->type != ROFFT_BLOCK)
1703		return;
1704
1705	nbl = nit->parent->parent;
1706	lt = nbl->norm->Bl.type;
1707
1708	switch (lt) {
1709	case LIST_tag:
1710	case LIST_hang:
1711	case LIST_ohang:
1712	case LIST_inset:
1713	case LIST_diag:
1714		if (nit->head->child == NULL)
1715			mandoc_msg(MANDOCERR_IT_NOHEAD,
1716			    nit->line, nit->pos, "Bl -%s It",
1717			    mdoc_argnames[nbl->args->argv[0].arg]);
1718		break;
1719	case LIST_bullet:
1720	case LIST_dash:
1721	case LIST_enum:
1722	case LIST_hyphen:
1723		if (nit->body == NULL || nit->body->child == NULL)
1724			mandoc_msg(MANDOCERR_IT_NOBODY,
1725			    nit->line, nit->pos, "Bl -%s It",
1726			    mdoc_argnames[nbl->args->argv[0].arg]);
1727		/* FALLTHROUGH */
1728	case LIST_item:
1729		if ((nch = nit->head->child) != NULL)
1730			mandoc_msg(MANDOCERR_ARG_SKIP,
1731			    nit->line, nit->pos, "It %s",
1732			    nch->type == ROFFT_TEXT ? nch->string :
1733			    roff_name[nch->tok]);
1734		break;
1735	case LIST_column:
1736		cols = (int)nbl->norm->Bl.ncols;
1737
1738		assert(nit->head->child == NULL);
1739
1740		if (nit->head->next->child == NULL &&
1741		    nit->head->next->next == NULL) {
1742			mandoc_msg(MANDOCERR_MACRO_EMPTY,
1743			    nit->line, nit->pos, "It");
1744			roff_node_delete(mdoc, nit);
1745			break;
1746		}
1747
1748		i = 0;
1749		for (nch = nit->child; nch != NULL; nch = nch->next) {
1750			if (nch->type != ROFFT_BODY)
1751				continue;
1752			if (i++ && nch->flags & NODE_LINE)
1753				mandoc_msg(MANDOCERR_TA_LINE,
1754				    nch->line, nch->pos, "Ta");
1755		}
1756		if (i < cols || i > cols + 1)
1757			mandoc_msg(MANDOCERR_BL_COL, nit->line, nit->pos,
1758			    "%d columns, %d cells", cols, i);
1759		else if (nit->head->next->child != NULL &&
1760		    nit->head->next->child->flags & NODE_LINE)
1761			mandoc_msg(MANDOCERR_IT_NOARG,
1762			    nit->line, nit->pos, "Bl -column It");
1763		break;
1764	default:
1765		abort();
1766	}
1767}
1768
1769static void
1770post_bl_block(POST_ARGS)
1771{
1772	struct roff_node *n, *ni, *nc;
1773
1774	post_prevpar(mdoc);
1775
1776	n = mdoc->last;
1777	for (ni = n->body->child; ni != NULL; ni = ni->next) {
1778		if (ni->body == NULL)
1779			continue;
1780		nc = ni->body->last;
1781		while (nc != NULL) {
1782			switch (nc->tok) {
1783			case MDOC_Pp:
1784			case ROFF_br:
1785				break;
1786			default:
1787				nc = NULL;
1788				continue;
1789			}
1790			if (ni->next == NULL) {
1791				mandoc_msg(MANDOCERR_PAR_MOVE, nc->line,
1792				    nc->pos, "%s", roff_name[nc->tok]);
1793				roff_node_relink(mdoc, nc);
1794			} else if (n->norm->Bl.comp == 0 &&
1795			    n->norm->Bl.type != LIST_column) {
1796				mandoc_msg(MANDOCERR_PAR_SKIP,
1797				    nc->line, nc->pos,
1798				    "%s before It", roff_name[nc->tok]);
1799				roff_node_delete(mdoc, nc);
1800			} else
1801				break;
1802			nc = ni->body->last;
1803		}
1804	}
1805}
1806
1807/*
1808 * If the argument of -offset or -width is a macro,
1809 * replace it with the associated default width.
1810 */
1811static void
1812rewrite_macro2len(struct roff_man *mdoc, char **arg)
1813{
1814	size_t		  width;
1815	enum roff_tok	  tok;
1816
1817	if (*arg == NULL)
1818		return;
1819	else if ( ! strcmp(*arg, "Ds"))
1820		width = 6;
1821	else if ((tok = roffhash_find(mdoc->mdocmac, *arg, 0)) == TOKEN_NONE)
1822		return;
1823	else
1824		width = macro2len(tok);
1825
1826	free(*arg);
1827	mandoc_asprintf(arg, "%zun", width);
1828}
1829
1830static void
1831post_bl_head(POST_ARGS)
1832{
1833	struct roff_node *nbl, *nh, *nch, *nnext;
1834	struct mdoc_argv *argv;
1835	int		  i, j;
1836
1837	post_bl_norm(mdoc);
1838
1839	nh = mdoc->last;
1840	if (nh->norm->Bl.type != LIST_column) {
1841		if ((nch = nh->child) == NULL)
1842			return;
1843		mandoc_msg(MANDOCERR_ARG_EXCESS,
1844		    nch->line, nch->pos, "Bl ... %s", nch->string);
1845		while (nch != NULL) {
1846			roff_node_delete(mdoc, nch);
1847			nch = nh->child;
1848		}
1849		return;
1850	}
1851
1852	/*
1853	 * Append old-style lists, where the column width specifiers
1854	 * trail as macro parameters, to the new-style ("normal-form")
1855	 * lists where they're argument values following -column.
1856	 */
1857
1858	if (nh->child == NULL)
1859		return;
1860
1861	nbl = nh->parent;
1862	for (j = 0; j < (int)nbl->args->argc; j++)
1863		if (nbl->args->argv[j].arg == MDOC_Column)
1864			break;
1865
1866	assert(j < (int)nbl->args->argc);
1867
1868	/*
1869	 * Accommodate for new-style groff column syntax.  Shuffle the
1870	 * child nodes, all of which must be TEXT, as arguments for the
1871	 * column field.  Then, delete the head children.
1872	 */
1873
1874	argv = nbl->args->argv + j;
1875	i = argv->sz;
1876	for (nch = nh->child; nch != NULL; nch = nch->next)
1877		argv->sz++;
1878	argv->value = mandoc_reallocarray(argv->value,
1879	    argv->sz, sizeof(char *));
1880
1881	nh->norm->Bl.ncols = argv->sz;
1882	nh->norm->Bl.cols = (void *)argv->value;
1883
1884	for (nch = nh->child; nch != NULL; nch = nnext) {
1885		argv->value[i++] = nch->string;
1886		nch->string = NULL;
1887		nnext = nch->next;
1888		roff_node_delete(NULL, nch);
1889	}
1890	nh->child = NULL;
1891}
1892
1893static void
1894post_bl(POST_ARGS)
1895{
1896	struct roff_node	*nbody;           /* of the Bl */
1897	struct roff_node	*nchild, *nnext;  /* of the Bl body */
1898	const char		*prev_Er;
1899	int			 order;
1900
1901	nbody = mdoc->last;
1902	switch (nbody->type) {
1903	case ROFFT_BLOCK:
1904		post_bl_block(mdoc);
1905		return;
1906	case ROFFT_HEAD:
1907		post_bl_head(mdoc);
1908		return;
1909	case ROFFT_BODY:
1910		break;
1911	default:
1912		return;
1913	}
1914	if (nbody->end != ENDBODY_NOT)
1915		return;
1916
1917	/*
1918	 * Up to the first item, move nodes before the list,
1919	 * but leave transparent nodes where they are
1920	 * if they precede an item.
1921	 * The next non-transparent node is kept in nchild.
1922	 * It only needs to be updated after a non-transparent
1923	 * node was moved out, and at the very beginning
1924	 * when no node at all was moved yet.
1925	 */
1926
1927	nchild = mdoc->last;
1928	for (;;) {
1929		if (nchild == mdoc->last)
1930			nchild = roff_node_child(nbody);
1931		if (nchild == NULL) {
1932			mdoc->last = nbody;
1933			mandoc_msg(MANDOCERR_BLK_EMPTY,
1934			    nbody->line, nbody->pos, "Bl");
1935			return;
1936		}
1937		if (nchild->tok == MDOC_It) {
1938			mdoc->last = nbody;
1939			break;
1940		}
1941		mandoc_msg(MANDOCERR_BL_MOVE, nbody->child->line,
1942		    nbody->child->pos, "%s", roff_name[nbody->child->tok]);
1943		if (nbody->parent->prev == NULL) {
1944			mdoc->last = nbody->parent->parent;
1945			mdoc->next = ROFF_NEXT_CHILD;
1946		} else {
1947			mdoc->last = nbody->parent->prev;
1948			mdoc->next = ROFF_NEXT_SIBLING;
1949		}
1950		roff_node_relink(mdoc, nbody->child);
1951	}
1952
1953	/*
1954	 * We have reached the first item,
1955	 * so moving nodes out is no longer possible.
1956	 * But in .Bl -column, the first rows may be implicit,
1957	 * that is, they may not start with .It macros.
1958	 * Such rows may be followed by nodes generated on the
1959	 * roff level, for example .TS.
1960	 * Wrap such roff nodes into an implicit row.
1961	 */
1962
1963	while (nchild != NULL) {
1964		if (nchild->tok == MDOC_It) {
1965			nchild = roff_node_next(nchild);
1966			continue;
1967		}
1968		nnext = nchild->next;
1969		mdoc->last = nchild->prev;
1970		mdoc->next = ROFF_NEXT_SIBLING;
1971		roff_block_alloc(mdoc, nchild->line, nchild->pos, MDOC_It);
1972		roff_head_alloc(mdoc, nchild->line, nchild->pos, MDOC_It);
1973		mdoc->next = ROFF_NEXT_SIBLING;
1974		roff_body_alloc(mdoc, nchild->line, nchild->pos, MDOC_It);
1975		while (nchild->tok != MDOC_It) {
1976			roff_node_relink(mdoc, nchild);
1977			if (nnext == NULL)
1978				break;
1979			nchild = nnext;
1980			nnext = nchild->next;
1981			mdoc->next = ROFF_NEXT_SIBLING;
1982		}
1983		mdoc->last = nbody;
1984	}
1985
1986	if (mdoc->meta.os_e != MANDOC_OS_NETBSD)
1987		return;
1988
1989	prev_Er = NULL;
1990	for (nchild = nbody->child; nchild != NULL; nchild = nchild->next) {
1991		if (nchild->tok != MDOC_It)
1992			continue;
1993		if ((nnext = nchild->head->child) == NULL)
1994			continue;
1995		if (nnext->type == ROFFT_BLOCK)
1996			nnext = nnext->body->child;
1997		if (nnext == NULL || nnext->tok != MDOC_Er)
1998			continue;
1999		nnext = nnext->child;
2000		if (prev_Er != NULL) {
2001			order = strcmp(prev_Er, nnext->string);
2002			if (order > 0)
2003				mandoc_msg(MANDOCERR_ER_ORDER,
2004				    nnext->line, nnext->pos,
2005				    "Er %s %s (NetBSD)",
2006				    prev_Er, nnext->string);
2007			else if (order == 0)
2008				mandoc_msg(MANDOCERR_ER_REP,
2009				    nnext->line, nnext->pos,
2010				    "Er %s (NetBSD)", prev_Er);
2011		}
2012		prev_Er = nnext->string;
2013	}
2014}
2015
2016static void
2017post_bk(POST_ARGS)
2018{
2019	struct roff_node	*n;
2020
2021	n = mdoc->last;
2022
2023	if (n->type == ROFFT_BLOCK && n->body->child == NULL) {
2024		mandoc_msg(MANDOCERR_BLK_EMPTY, n->line, n->pos, "Bk");
2025		roff_node_delete(mdoc, n);
2026	}
2027}
2028
2029static void
2030post_sm(POST_ARGS)
2031{
2032	struct roff_node	*nch;
2033
2034	nch = mdoc->last->child;
2035
2036	if (nch == NULL) {
2037		mdoc->flags ^= MDOC_SMOFF;
2038		return;
2039	}
2040
2041	assert(nch->type == ROFFT_TEXT);
2042
2043	if ( ! strcmp(nch->string, "on")) {
2044		mdoc->flags &= ~MDOC_SMOFF;
2045		return;
2046	}
2047	if ( ! strcmp(nch->string, "off")) {
2048		mdoc->flags |= MDOC_SMOFF;
2049		return;
2050	}
2051
2052	mandoc_msg(MANDOCERR_SM_BAD, nch->line, nch->pos,
2053	    "%s %s", roff_name[mdoc->last->tok], nch->string);
2054	roff_node_relink(mdoc, nch);
2055	return;
2056}
2057
2058static void
2059post_root(POST_ARGS)
2060{
2061	struct roff_node *n;
2062
2063	/* Add missing prologue data. */
2064
2065	if (mdoc->meta.date == NULL)
2066		mdoc->meta.date = mandoc_normdate(NULL, NULL);
2067
2068	if (mdoc->meta.title == NULL) {
2069		mandoc_msg(MANDOCERR_DT_NOTITLE, 0, 0, "EOF");
2070		mdoc->meta.title = mandoc_strdup("UNTITLED");
2071	}
2072
2073	if (mdoc->meta.vol == NULL)
2074		mdoc->meta.vol = mandoc_strdup("LOCAL");
2075
2076	if (mdoc->meta.os == NULL) {
2077		mandoc_msg(MANDOCERR_OS_MISSING, 0, 0, NULL);
2078		mdoc->meta.os = mandoc_strdup("");
2079	} else if (mdoc->meta.os_e &&
2080	    (mdoc->meta.rcsids & (1 << mdoc->meta.os_e)) == 0)
2081		mandoc_msg(MANDOCERR_RCS_MISSING, 0, 0,
2082		    mdoc->meta.os_e == MANDOC_OS_OPENBSD ?
2083		    "(OpenBSD)" : "(NetBSD)");
2084
2085	if (mdoc->meta.arch != NULL &&
2086	    arch_valid(mdoc->meta.arch, mdoc->meta.os_e) == 0) {
2087		n = mdoc->meta.first->child;
2088		while (n->tok != MDOC_Dt ||
2089		    n->child == NULL ||
2090		    n->child->next == NULL ||
2091		    n->child->next->next == NULL)
2092			n = n->next;
2093		n = n->child->next->next;
2094		mandoc_msg(MANDOCERR_ARCH_BAD, n->line, n->pos,
2095		    "Dt ... %s %s", mdoc->meta.arch,
2096		    mdoc->meta.os_e == MANDOC_OS_OPENBSD ?
2097		    "(OpenBSD)" : "(NetBSD)");
2098	}
2099
2100	/* Check that we begin with a proper `Sh'. */
2101
2102	n = mdoc->meta.first->child;
2103	while (n != NULL &&
2104	    (n->type == ROFFT_COMMENT ||
2105	     (n->tok >= MDOC_Dd &&
2106	      mdoc_macro(n->tok)->flags & MDOC_PROLOGUE)))
2107		n = n->next;
2108
2109	if (n == NULL)
2110		mandoc_msg(MANDOCERR_DOC_EMPTY, 0, 0, NULL);
2111	else if (n->tok != MDOC_Sh)
2112		mandoc_msg(MANDOCERR_SEC_BEFORE, n->line, n->pos,
2113		    "%s", roff_name[n->tok]);
2114}
2115
2116static void
2117post_rs(POST_ARGS)
2118{
2119	struct roff_node *np, *nch, *next, *prev;
2120	int		  i, j;
2121
2122	np = mdoc->last;
2123
2124	if (np->type != ROFFT_BODY)
2125		return;
2126
2127	if (np->child == NULL) {
2128		mandoc_msg(MANDOCERR_RS_EMPTY, np->line, np->pos, "Rs");
2129		return;
2130	}
2131
2132	/*
2133	 * The full `Rs' block needs special handling to order the
2134	 * sub-elements according to `rsord'.  Pick through each element
2135	 * and correctly order it.  This is an insertion sort.
2136	 */
2137
2138	next = NULL;
2139	for (nch = np->child->next; nch != NULL; nch = next) {
2140		/* Determine order number of this child. */
2141		for (i = 0; i < RSORD_MAX; i++)
2142			if (rsord[i] == nch->tok)
2143				break;
2144
2145		if (i == RSORD_MAX) {
2146			mandoc_msg(MANDOCERR_RS_BAD, nch->line, nch->pos,
2147			    "%s", roff_name[nch->tok]);
2148			i = -1;
2149		} else if (nch->tok == MDOC__J || nch->tok == MDOC__B)
2150			np->norm->Rs.quote_T++;
2151
2152		/*
2153		 * Remove this child from the chain.  This somewhat
2154		 * repeats roff_node_unlink(), but since we're
2155		 * just re-ordering, there's no need for the
2156		 * full unlink process.
2157		 */
2158
2159		if ((next = nch->next) != NULL)
2160			next->prev = nch->prev;
2161
2162		if ((prev = nch->prev) != NULL)
2163			prev->next = nch->next;
2164
2165		nch->prev = nch->next = NULL;
2166
2167		/*
2168		 * Scan back until we reach a node that's
2169		 * to be ordered before this child.
2170		 */
2171
2172		for ( ; prev ; prev = prev->prev) {
2173			/* Determine order of `prev'. */
2174			for (j = 0; j < RSORD_MAX; j++)
2175				if (rsord[j] == prev->tok)
2176					break;
2177			if (j == RSORD_MAX)
2178				j = -1;
2179
2180			if (j <= i)
2181				break;
2182		}
2183
2184		/*
2185		 * Set this child back into its correct place
2186		 * in front of the `prev' node.
2187		 */
2188
2189		nch->prev = prev;
2190
2191		if (prev == NULL) {
2192			np->child->prev = nch;
2193			nch->next = np->child;
2194			np->child = nch;
2195		} else {
2196			if (prev->next)
2197				prev->next->prev = nch;
2198			nch->next = prev->next;
2199			prev->next = nch;
2200		}
2201	}
2202}
2203
2204/*
2205 * For some arguments of some macros,
2206 * convert all breakable hyphens into ASCII_HYPH.
2207 */
2208static void
2209post_hyph(POST_ARGS)
2210{
2211	struct roff_node	*n, *nch;
2212	char			*cp;
2213
2214	n = mdoc->last;
2215	for (nch = n->child; nch != NULL; nch = nch->next) {
2216		if (nch->type != ROFFT_TEXT)
2217			continue;
2218		cp = nch->string;
2219		if (*cp == '\0')
2220			continue;
2221		while (*(++cp) != '\0')
2222			if (*cp == '-' &&
2223			    isalpha((unsigned char)cp[-1]) &&
2224			    isalpha((unsigned char)cp[1])) {
2225				if (n->tag == NULL && n->flags & NODE_ID)
2226					n->tag = mandoc_strdup(nch->string);
2227				*cp = ASCII_HYPH;
2228			}
2229	}
2230}
2231
2232static void
2233post_ns(POST_ARGS)
2234{
2235	struct roff_node	*n;
2236
2237	n = mdoc->last;
2238	if (n->flags & NODE_LINE ||
2239	    (n->next != NULL && n->next->flags & NODE_DELIMC))
2240		mandoc_msg(MANDOCERR_NS_SKIP, n->line, n->pos, NULL);
2241}
2242
2243static void
2244post_sx(POST_ARGS)
2245{
2246	post_delim(mdoc);
2247	post_hyph(mdoc);
2248}
2249
2250static void
2251post_sh(POST_ARGS)
2252{
2253	post_section(mdoc);
2254
2255	switch (mdoc->last->type) {
2256	case ROFFT_HEAD:
2257		post_sh_head(mdoc);
2258		break;
2259	case ROFFT_BODY:
2260		switch (mdoc->lastsec)  {
2261		case SEC_NAME:
2262			post_sh_name(mdoc);
2263			break;
2264		case SEC_SEE_ALSO:
2265			post_sh_see_also(mdoc);
2266			break;
2267		case SEC_AUTHORS:
2268			post_sh_authors(mdoc);
2269			break;
2270		default:
2271			break;
2272		}
2273		break;
2274	default:
2275		break;
2276	}
2277}
2278
2279static void
2280post_sh_name(POST_ARGS)
2281{
2282	struct roff_node *n;
2283	int hasnm, hasnd;
2284
2285	hasnm = hasnd = 0;
2286
2287	for (n = mdoc->last->child; n != NULL; n = n->next) {
2288		switch (n->tok) {
2289		case MDOC_Nm:
2290			if (hasnm && n->child != NULL)
2291				mandoc_msg(MANDOCERR_NAMESEC_PUNCT,
2292				    n->line, n->pos,
2293				    "Nm %s", n->child->string);
2294			hasnm = 1;
2295			continue;
2296		case MDOC_Nd:
2297			hasnd = 1;
2298			if (n->next != NULL)
2299				mandoc_msg(MANDOCERR_NAMESEC_ND,
2300				    n->line, n->pos, NULL);
2301			break;
2302		case TOKEN_NONE:
2303			if (n->type == ROFFT_TEXT &&
2304			    n->string[0] == ',' && n->string[1] == '\0' &&
2305			    n->next != NULL && n->next->tok == MDOC_Nm) {
2306				n = n->next;
2307				continue;
2308			}
2309			/* FALLTHROUGH */
2310		default:
2311			mandoc_msg(MANDOCERR_NAMESEC_BAD,
2312			    n->line, n->pos, "%s", roff_name[n->tok]);
2313			continue;
2314		}
2315		break;
2316	}
2317
2318	if ( ! hasnm)
2319		mandoc_msg(MANDOCERR_NAMESEC_NONM,
2320		    mdoc->last->line, mdoc->last->pos, NULL);
2321	if ( ! hasnd)
2322		mandoc_msg(MANDOCERR_NAMESEC_NOND,
2323		    mdoc->last->line, mdoc->last->pos, NULL);
2324}
2325
2326static void
2327post_sh_see_also(POST_ARGS)
2328{
2329	const struct roff_node	*n;
2330	const char		*name, *sec;
2331	const char		*lastname, *lastsec, *lastpunct;
2332	int			 cmp;
2333
2334	n = mdoc->last->child;
2335	lastname = lastsec = lastpunct = NULL;
2336	while (n != NULL) {
2337		if (n->tok != MDOC_Xr ||
2338		    n->child == NULL ||
2339		    n->child->next == NULL)
2340			break;
2341
2342		/* Process one .Xr node. */
2343
2344		name = n->child->string;
2345		sec = n->child->next->string;
2346		if (lastsec != NULL) {
2347			if (lastpunct[0] != ',' || lastpunct[1] != '\0')
2348				mandoc_msg(MANDOCERR_XR_PUNCT, n->line,
2349				    n->pos, "%s before %s(%s)",
2350				    lastpunct, name, sec);
2351			cmp = strcmp(lastsec, sec);
2352			if (cmp > 0)
2353				mandoc_msg(MANDOCERR_XR_ORDER, n->line,
2354				    n->pos, "%s(%s) after %s(%s)",
2355				    name, sec, lastname, lastsec);
2356			else if (cmp == 0 &&
2357			    strcasecmp(lastname, name) > 0)
2358				mandoc_msg(MANDOCERR_XR_ORDER, n->line,
2359				    n->pos, "%s after %s", name, lastname);
2360		}
2361		lastname = name;
2362		lastsec = sec;
2363
2364		/* Process the following node. */
2365
2366		n = n->next;
2367		if (n == NULL)
2368			break;
2369		if (n->tok == MDOC_Xr) {
2370			lastpunct = "none";
2371			continue;
2372		}
2373		if (n->type != ROFFT_TEXT)
2374			break;
2375		for (name = n->string; *name != '\0'; name++)
2376			if (isalpha((const unsigned char)*name))
2377				return;
2378		lastpunct = n->string;
2379		if (n->next == NULL || n->next->tok == MDOC_Rs)
2380			mandoc_msg(MANDOCERR_XR_PUNCT, n->line,
2381			    n->pos, "%s after %s(%s)",
2382			    lastpunct, lastname, lastsec);
2383		n = n->next;
2384	}
2385}
2386
2387static int
2388child_an(const struct roff_node *n)
2389{
2390
2391	for (n = n->child; n != NULL; n = n->next)
2392		if ((n->tok == MDOC_An && n->child != NULL) || child_an(n))
2393			return 1;
2394	return 0;
2395}
2396
2397static void
2398post_sh_authors(POST_ARGS)
2399{
2400
2401	if ( ! child_an(mdoc->last))
2402		mandoc_msg(MANDOCERR_AN_MISSING,
2403		    mdoc->last->line, mdoc->last->pos, NULL);
2404}
2405
2406/*
2407 * Return an upper bound for the string distance (allowing
2408 * transpositions).  Not a full Levenshtein implementation
2409 * because Levenshtein is quadratic in the string length
2410 * and this function is called for every standard name,
2411 * so the check for each custom name would be cubic.
2412 * The following crude heuristics is linear, resulting
2413 * in quadratic behaviour for checking one custom name,
2414 * which does not cause measurable slowdown.
2415 */
2416static int
2417similar(const char *s1, const char *s2)
2418{
2419	const int	maxdist = 3;
2420	int		dist = 0;
2421
2422	while (s1[0] != '\0' && s2[0] != '\0') {
2423		if (s1[0] == s2[0]) {
2424			s1++;
2425			s2++;
2426			continue;
2427		}
2428		if (++dist > maxdist)
2429			return INT_MAX;
2430		if (s1[1] == s2[1]) {  /* replacement */
2431			s1++;
2432			s2++;
2433		} else if (s1[0] == s2[1] && s1[1] == s2[0]) {
2434			s1 += 2;	/* transposition */
2435			s2 += 2;
2436		} else if (s1[0] == s2[1])  /* insertion */
2437			s2++;
2438		else if (s1[1] == s2[0])  /* deletion */
2439			s1++;
2440		else
2441			return INT_MAX;
2442	}
2443	dist += strlen(s1) + strlen(s2);
2444	return dist > maxdist ? INT_MAX : dist;
2445}
2446
2447static void
2448post_sh_head(POST_ARGS)
2449{
2450	struct roff_node	*nch;
2451	const char		*goodsec;
2452	const char *const	*testsec;
2453	int			 dist, mindist;
2454	enum roff_sec		 sec;
2455
2456	/*
2457	 * Process a new section.  Sections are either "named" or
2458	 * "custom".  Custom sections are user-defined, while named ones
2459	 * follow a conventional order and may only appear in certain
2460	 * manual sections.
2461	 */
2462
2463	sec = mdoc->last->sec;
2464
2465	/* The NAME should be first. */
2466
2467	if (sec != SEC_NAME && mdoc->lastnamed == SEC_NONE)
2468		mandoc_msg(MANDOCERR_NAMESEC_FIRST,
2469		    mdoc->last->line, mdoc->last->pos, "Sh %s",
2470		    sec != SEC_CUSTOM ? secnames[sec] :
2471		    (nch = mdoc->last->child) == NULL ? "" :
2472		    nch->type == ROFFT_TEXT ? nch->string :
2473		    roff_name[nch->tok]);
2474
2475	/* The SYNOPSIS gets special attention in other areas. */
2476
2477	if (sec == SEC_SYNOPSIS) {
2478		roff_setreg(mdoc->roff, "nS", 1, '=');
2479		mdoc->flags |= MDOC_SYNOPSIS;
2480	} else {
2481		roff_setreg(mdoc->roff, "nS", 0, '=');
2482		mdoc->flags &= ~MDOC_SYNOPSIS;
2483	}
2484	if (sec == SEC_DESCRIPTION)
2485		fn_prio = TAG_STRONG;
2486
2487	/* Mark our last section. */
2488
2489	mdoc->lastsec = sec;
2490
2491	/* We don't care about custom sections after this. */
2492
2493	if (sec == SEC_CUSTOM) {
2494		if ((nch = mdoc->last->child) == NULL ||
2495		    nch->type != ROFFT_TEXT || nch->next != NULL)
2496			return;
2497		goodsec = NULL;
2498		mindist = INT_MAX;
2499		for (testsec = secnames + 1; *testsec != NULL; testsec++) {
2500			dist = similar(nch->string, *testsec);
2501			if (dist < mindist) {
2502				goodsec = *testsec;
2503				mindist = dist;
2504			}
2505		}
2506		if (goodsec != NULL)
2507			mandoc_msg(MANDOCERR_SEC_TYPO, nch->line, nch->pos,
2508			    "Sh %s instead of %s", nch->string, goodsec);
2509		return;
2510	}
2511
2512	/*
2513	 * Check whether our non-custom section is being repeated or is
2514	 * out of order.
2515	 */
2516
2517	if (sec == mdoc->lastnamed)
2518		mandoc_msg(MANDOCERR_SEC_REP, mdoc->last->line,
2519		    mdoc->last->pos, "Sh %s", secnames[sec]);
2520
2521	if (sec < mdoc->lastnamed)
2522		mandoc_msg(MANDOCERR_SEC_ORDER, mdoc->last->line,
2523		    mdoc->last->pos, "Sh %s", secnames[sec]);
2524
2525	/* Mark the last named section. */
2526
2527	mdoc->lastnamed = sec;
2528
2529	/* Check particular section/manual conventions. */
2530
2531	if (mdoc->meta.msec == NULL)
2532		return;
2533
2534	goodsec = NULL;
2535	switch (sec) {
2536	case SEC_ERRORS:
2537		if (*mdoc->meta.msec == '4')
2538			break;
2539		goodsec = "2, 3, 4, 9";
2540		/* FALLTHROUGH */
2541	case SEC_RETURN_VALUES:
2542	case SEC_LIBRARY:
2543		if (*mdoc->meta.msec == '2')
2544			break;
2545		if (*mdoc->meta.msec == '3')
2546			break;
2547		if (NULL == goodsec)
2548			goodsec = "2, 3, 9";
2549		/* FALLTHROUGH */
2550	case SEC_CONTEXT:
2551		if (*mdoc->meta.msec == '9')
2552			break;
2553		if (NULL == goodsec)
2554			goodsec = "9";
2555		mandoc_msg(MANDOCERR_SEC_MSEC,
2556		    mdoc->last->line, mdoc->last->pos,
2557		    "Sh %s for %s only", secnames[sec], goodsec);
2558		break;
2559	default:
2560		break;
2561	}
2562}
2563
2564static void
2565post_xr(POST_ARGS)
2566{
2567	struct roff_node *n, *nch;
2568
2569	n = mdoc->last;
2570	nch = n->child;
2571	if (nch->next == NULL) {
2572		mandoc_msg(MANDOCERR_XR_NOSEC,
2573		    n->line, n->pos, "Xr %s", nch->string);
2574	} else {
2575		assert(nch->next == n->last);
2576		if(mandoc_xr_add(nch->next->string, nch->string,
2577		    nch->line, nch->pos))
2578			mandoc_msg(MANDOCERR_XR_SELF,
2579			    nch->line, nch->pos, "Xr %s %s",
2580			    nch->string, nch->next->string);
2581	}
2582	post_delim_nb(mdoc);
2583}
2584
2585static void
2586post_section(POST_ARGS)
2587{
2588	struct roff_node *n, *nch;
2589	char		 *cp, *tag;
2590
2591	n = mdoc->last;
2592	switch (n->type) {
2593	case ROFFT_BLOCK:
2594		post_prevpar(mdoc);
2595		return;
2596	case ROFFT_HEAD:
2597		tag = NULL;
2598		deroff(&tag, n);
2599		if (tag != NULL) {
2600			for (cp = tag; *cp != '\0'; cp++)
2601				if (*cp == ' ')
2602					*cp = '_';
2603			if ((nch = n->child) != NULL &&
2604			    nch->type == ROFFT_TEXT &&
2605			    strcmp(nch->string, tag) == 0)
2606				tag_put(NULL, TAG_STRONG, n);
2607			else
2608				tag_put(tag, TAG_FALLBACK, n);
2609			free(tag);
2610		}
2611		post_delim(mdoc);
2612		post_hyph(mdoc);
2613		return;
2614	case ROFFT_BODY:
2615		break;
2616	default:
2617		return;
2618	}
2619	if ((nch = n->child) != NULL &&
2620	    (nch->tok == MDOC_Pp || nch->tok == ROFF_br ||
2621	     nch->tok == ROFF_sp)) {
2622		mandoc_msg(MANDOCERR_PAR_SKIP, nch->line, nch->pos,
2623		    "%s after %s", roff_name[nch->tok],
2624		    roff_name[n->tok]);
2625		roff_node_delete(mdoc, nch);
2626	}
2627	if ((nch = n->last) != NULL &&
2628	    (nch->tok == MDOC_Pp || nch->tok == ROFF_br)) {
2629		mandoc_msg(MANDOCERR_PAR_SKIP, nch->line, nch->pos,
2630		    "%s at the end of %s", roff_name[nch->tok],
2631		    roff_name[n->tok]);
2632		roff_node_delete(mdoc, nch);
2633	}
2634}
2635
2636static void
2637post_prevpar(POST_ARGS)
2638{
2639	struct roff_node *n, *np;
2640
2641	n = mdoc->last;
2642	if (n->type != ROFFT_ELEM && n->type != ROFFT_BLOCK)
2643		return;
2644	if ((np = roff_node_prev(n)) == NULL)
2645		return;
2646
2647	/*
2648	 * Don't allow `Pp' prior to a paragraph-type
2649	 * block: `Pp' or non-compact `Bd' or `Bl'.
2650	 */
2651
2652	if (np->tok != MDOC_Pp && np->tok != ROFF_br)
2653		return;
2654	if (n->tok == MDOC_Bl && n->norm->Bl.comp)
2655		return;
2656	if (n->tok == MDOC_Bd && n->norm->Bd.comp)
2657		return;
2658	if (n->tok == MDOC_It && n->parent->norm->Bl.comp)
2659		return;
2660
2661	mandoc_msg(MANDOCERR_PAR_SKIP, np->line, np->pos,
2662	    "%s before %s", roff_name[np->tok], roff_name[n->tok]);
2663	roff_node_delete(mdoc, np);
2664}
2665
2666static void
2667post_par(POST_ARGS)
2668{
2669	struct roff_node *np;
2670
2671	fn_prio = TAG_STRONG;
2672	post_prevpar(mdoc);
2673
2674	np = mdoc->last;
2675	if (np->child != NULL)
2676		mandoc_msg(MANDOCERR_ARG_SKIP, np->line, np->pos,
2677		    "%s %s", roff_name[np->tok], np->child->string);
2678}
2679
2680static void
2681post_dd(POST_ARGS)
2682{
2683	struct roff_node *n;
2684
2685	n = mdoc->last;
2686	n->flags |= NODE_NOPRT;
2687
2688	if (mdoc->meta.date != NULL) {
2689		mandoc_msg(MANDOCERR_PROLOG_REP, n->line, n->pos, "Dd");
2690		free(mdoc->meta.date);
2691	} else if (mdoc->flags & MDOC_PBODY)
2692		mandoc_msg(MANDOCERR_PROLOG_LATE, n->line, n->pos, "Dd");
2693	else if (mdoc->meta.title != NULL)
2694		mandoc_msg(MANDOCERR_PROLOG_ORDER,
2695		    n->line, n->pos, "Dd after Dt");
2696	else if (mdoc->meta.os != NULL)
2697		mandoc_msg(MANDOCERR_PROLOG_ORDER,
2698		    n->line, n->pos, "Dd after Os");
2699
2700	if (mdoc->quick && n != NULL)
2701		mdoc->meta.date = mandoc_strdup("");
2702	else
2703		mdoc->meta.date = mandoc_normdate(n->child, n);
2704}
2705
2706static void
2707post_dt(POST_ARGS)
2708{
2709	struct roff_node *nn, *n;
2710	const char	 *cp;
2711	char		 *p;
2712
2713	n = mdoc->last;
2714	n->flags |= NODE_NOPRT;
2715
2716	if (mdoc->flags & MDOC_PBODY) {
2717		mandoc_msg(MANDOCERR_DT_LATE, n->line, n->pos, "Dt");
2718		return;
2719	}
2720
2721	if (mdoc->meta.title != NULL)
2722		mandoc_msg(MANDOCERR_PROLOG_REP, n->line, n->pos, "Dt");
2723	else if (mdoc->meta.os != NULL)
2724		mandoc_msg(MANDOCERR_PROLOG_ORDER,
2725		    n->line, n->pos, "Dt after Os");
2726
2727	free(mdoc->meta.title);
2728	free(mdoc->meta.msec);
2729	free(mdoc->meta.vol);
2730	free(mdoc->meta.arch);
2731
2732	mdoc->meta.title = NULL;
2733	mdoc->meta.msec = NULL;
2734	mdoc->meta.vol = NULL;
2735	mdoc->meta.arch = NULL;
2736
2737	/* Mandatory first argument: title. */
2738
2739	nn = n->child;
2740	if (nn == NULL || *nn->string == '\0') {
2741		mandoc_msg(MANDOCERR_DT_NOTITLE, n->line, n->pos, "Dt");
2742		mdoc->meta.title = mandoc_strdup("UNTITLED");
2743	} else {
2744		mdoc->meta.title = mandoc_strdup(nn->string);
2745
2746		/* Check that all characters are uppercase. */
2747
2748		for (p = nn->string; *p != '\0'; p++)
2749			if (islower((unsigned char)*p)) {
2750				mandoc_msg(MANDOCERR_TITLE_CASE, nn->line,
2751				    nn->pos + (int)(p - nn->string),
2752				    "Dt %s", nn->string);
2753				break;
2754			}
2755	}
2756
2757	/* Mandatory second argument: section. */
2758
2759	if (nn != NULL)
2760		nn = nn->next;
2761
2762	if (nn == NULL) {
2763		mandoc_msg(MANDOCERR_MSEC_MISSING, n->line, n->pos,
2764		    "Dt %s", mdoc->meta.title);
2765		mdoc->meta.vol = mandoc_strdup("LOCAL");
2766		return;  /* msec and arch remain NULL. */
2767	}
2768
2769	mdoc->meta.msec = mandoc_strdup(nn->string);
2770
2771	/* Infer volume title from section number. */
2772
2773	cp = mandoc_a2msec(nn->string);
2774	if (cp == NULL) {
2775		mandoc_msg(MANDOCERR_MSEC_BAD,
2776		    nn->line, nn->pos, "Dt ... %s", nn->string);
2777		mdoc->meta.vol = mandoc_strdup(nn->string);
2778	} else {
2779		mdoc->meta.vol = mandoc_strdup(cp);
2780		if (mdoc->filesec != '\0' &&
2781		    mdoc->filesec != *nn->string &&
2782		    *nn->string >= '1' && *nn->string <= '9')
2783			mandoc_msg(MANDOCERR_MSEC_FILE, nn->line, nn->pos,
2784			    "*.%c vs Dt ... %c", mdoc->filesec, *nn->string);
2785	}
2786
2787	/* Optional third argument: architecture. */
2788
2789	if ((nn = nn->next) == NULL)
2790		return;
2791
2792	for (p = nn->string; *p != '\0'; p++)
2793		*p = tolower((unsigned char)*p);
2794	mdoc->meta.arch = mandoc_strdup(nn->string);
2795
2796	/* Ignore fourth and later arguments. */
2797
2798	if ((nn = nn->next) != NULL)
2799		mandoc_msg(MANDOCERR_ARG_EXCESS,
2800		    nn->line, nn->pos, "Dt ... %s", nn->string);
2801}
2802
2803static void
2804post_bx(POST_ARGS)
2805{
2806	struct roff_node	*n, *nch;
2807	const char		*macro;
2808
2809	post_delim_nb(mdoc);
2810
2811	n = mdoc->last;
2812	nch = n->child;
2813
2814	if (nch != NULL) {
2815		macro = !strcmp(nch->string, "Open") ? "Ox" :
2816		    !strcmp(nch->string, "Net") ? "Nx" :
2817		    !strcmp(nch->string, "Free") ? "Fx" :
2818		    !strcmp(nch->string, "DragonFly") ? "Dx" : NULL;
2819		if (macro != NULL)
2820			mandoc_msg(MANDOCERR_BX,
2821			    n->line, n->pos, "%s", macro);
2822		mdoc->last = nch;
2823		nch = nch->next;
2824		mdoc->next = ROFF_NEXT_SIBLING;
2825		roff_elem_alloc(mdoc, n->line, n->pos, MDOC_Ns);
2826		mdoc->last->flags |= NODE_NOSRC;
2827		mdoc->next = ROFF_NEXT_SIBLING;
2828	} else
2829		mdoc->next = ROFF_NEXT_CHILD;
2830	roff_word_alloc(mdoc, n->line, n->pos, "BSD");
2831	mdoc->last->flags |= NODE_NOSRC;
2832
2833	if (nch == NULL) {
2834		mdoc->last = n;
2835		return;
2836	}
2837
2838	roff_elem_alloc(mdoc, n->line, n->pos, MDOC_Ns);
2839	mdoc->last->flags |= NODE_NOSRC;
2840	mdoc->next = ROFF_NEXT_SIBLING;
2841	roff_word_alloc(mdoc, n->line, n->pos, "-");
2842	mdoc->last->flags |= NODE_NOSRC;
2843	roff_elem_alloc(mdoc, n->line, n->pos, MDOC_Ns);
2844	mdoc->last->flags |= NODE_NOSRC;
2845	mdoc->last = n;
2846
2847	/*
2848	 * Make `Bx's second argument always start with an uppercase
2849	 * letter.  Groff checks if it's an "accepted" term, but we just
2850	 * uppercase blindly.
2851	 */
2852
2853	*nch->string = (char)toupper((unsigned char)*nch->string);
2854}
2855
2856static void
2857post_os(POST_ARGS)
2858{
2859#ifndef OSNAME
2860	struct utsname	  utsname;
2861#endif
2862	struct roff_node *n;
2863
2864	n = mdoc->last;
2865	n->flags |= NODE_NOPRT;
2866
2867	if (mdoc->meta.os != NULL)
2868		mandoc_msg(MANDOCERR_PROLOG_REP, n->line, n->pos, "Os");
2869	else if (mdoc->flags & MDOC_PBODY)
2870		mandoc_msg(MANDOCERR_PROLOG_LATE, n->line, n->pos, "Os");
2871
2872	post_delim(mdoc);
2873
2874	/*
2875	 * Set the operating system by way of the `Os' macro.
2876	 * The order of precedence is:
2877	 * 1. the argument of the `Os' macro, unless empty
2878	 * 2. the -Ios=foo command line argument, if provided
2879	 * 3. -DOSNAME="\"foo\"", if provided during compilation
2880	 * 4. "sysname release" from uname(3)
2881	 */
2882
2883	free(mdoc->meta.os);
2884	mdoc->meta.os = NULL;
2885	deroff(&mdoc->meta.os, n);
2886	if (mdoc->meta.os)
2887		goto out;
2888
2889	if (mdoc->os_s != NULL) {
2890		mdoc->meta.os = mandoc_strdup(mdoc->os_s);
2891		goto out;
2892	}
2893
2894#ifdef OSNAME
2895	mdoc->meta.os = mandoc_strdup(OSNAME);
2896#else /*!OSNAME */
2897	if (mdoc->os_r == NULL) {
2898		if (uname(&utsname) == -1) {
2899			mandoc_msg(MANDOCERR_OS_UNAME, n->line, n->pos, "Os");
2900			mdoc->os_r = mandoc_strdup("UNKNOWN");
2901		} else
2902			mandoc_asprintf(&mdoc->os_r, "%s %s",
2903			    utsname.sysname, utsname.release);
2904	}
2905	mdoc->meta.os = mandoc_strdup(mdoc->os_r);
2906#endif /*!OSNAME*/
2907
2908out:
2909	if (mdoc->meta.os_e == MANDOC_OS_OTHER) {
2910		if (strstr(mdoc->meta.os, "OpenBSD") != NULL)
2911			mdoc->meta.os_e = MANDOC_OS_OPENBSD;
2912		else if (strstr(mdoc->meta.os, "NetBSD") != NULL)
2913			mdoc->meta.os_e = MANDOC_OS_NETBSD;
2914	}
2915
2916	/*
2917	 * This is the earliest point where we can check
2918	 * Mdocdate conventions because we don't know
2919	 * the operating system earlier.
2920	 */
2921
2922	if (n->child != NULL)
2923		mandoc_msg(MANDOCERR_OS_ARG, n->child->line, n->child->pos,
2924		    "Os %s (%s)", n->child->string,
2925		    mdoc->meta.os_e == MANDOC_OS_OPENBSD ?
2926		    "OpenBSD" : "NetBSD");
2927
2928	while (n->tok != MDOC_Dd)
2929		if ((n = n->prev) == NULL)
2930			return;
2931	if ((n = n->child) == NULL)
2932		return;
2933	if (strncmp(n->string, "$" "Mdocdate", 9)) {
2934		if (mdoc->meta.os_e == MANDOC_OS_OPENBSD)
2935			mandoc_msg(MANDOCERR_MDOCDATE_MISSING, n->line,
2936			    n->pos, "Dd %s (OpenBSD)", n->string);
2937	} else {
2938		if (mdoc->meta.os_e == MANDOC_OS_NETBSD)
2939			mandoc_msg(MANDOCERR_MDOCDATE, n->line,
2940			    n->pos, "Dd %s (NetBSD)", n->string);
2941	}
2942}
2943
2944enum roff_sec
2945mdoc_a2sec(const char *p)
2946{
2947	int		 i;
2948
2949	for (i = 0; i < (int)SEC__MAX; i++)
2950		if (secnames[i] && 0 == strcmp(p, secnames[i]))
2951			return (enum roff_sec)i;
2952
2953	return SEC_CUSTOM;
2954}
2955
2956static size_t
2957macro2len(enum roff_tok macro)
2958{
2959
2960	switch (macro) {
2961	case MDOC_Ad:
2962		return 12;
2963	case MDOC_Ao:
2964		return 12;
2965	case MDOC_An:
2966		return 12;
2967	case MDOC_Aq:
2968		return 12;
2969	case MDOC_Ar:
2970		return 12;
2971	case MDOC_Bo:
2972		return 12;
2973	case MDOC_Bq:
2974		return 12;
2975	case MDOC_Cd:
2976		return 12;
2977	case MDOC_Cm:
2978		return 10;
2979	case MDOC_Do:
2980		return 10;
2981	case MDOC_Dq:
2982		return 12;
2983	case MDOC_Dv:
2984		return 12;
2985	case MDOC_Eo:
2986		return 12;
2987	case MDOC_Em:
2988		return 10;
2989	case MDOC_Er:
2990		return 17;
2991	case MDOC_Ev:
2992		return 15;
2993	case MDOC_Fa:
2994		return 12;
2995	case MDOC_Fl:
2996		return 10;
2997	case MDOC_Fo:
2998		return 16;
2999	case MDOC_Fn:
3000		return 16;
3001	case MDOC_Ic:
3002		return 10;
3003	case MDOC_Li:
3004		return 16;
3005	case MDOC_Ms:
3006		return 6;
3007	case MDOC_Nm:
3008		return 10;
3009	case MDOC_No:
3010		return 12;
3011	case MDOC_Oo:
3012		return 10;
3013	case MDOC_Op:
3014		return 14;
3015	case MDOC_Pa:
3016		return 32;
3017	case MDOC_Pf:
3018		return 12;
3019	case MDOC_Po:
3020		return 12;
3021	case MDOC_Pq:
3022		return 12;
3023	case MDOC_Ql:
3024		return 16;
3025	case MDOC_Qo:
3026		return 12;
3027	case MDOC_So:
3028		return 12;
3029	case MDOC_Sq:
3030		return 12;
3031	case MDOC_Sy:
3032		return 6;
3033	case MDOC_Sx:
3034		return 16;
3035	case MDOC_Tn:
3036		return 10;
3037	case MDOC_Va:
3038		return 12;
3039	case MDOC_Vt:
3040		return 12;
3041	case MDOC_Xr:
3042		return 10;
3043	default:
3044		break;
3045	};
3046	return 0;
3047}
3048