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