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