1/*	Id: mdoc_validate.c,v 1.371 2019/03/04 13:01:57 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) __dead;
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			struct roff_node *p = n->last;
1224			roff_node_delete(mdoc, p);
1225		}
1226
1227	} else
1228		post_delim(mdoc);
1229
1230	post_fname(mdoc);
1231}
1232
1233static void
1234post_fa(POST_ARGS)
1235{
1236	const struct roff_node *n;
1237	const char *cp;
1238
1239	for (n = mdoc->last->child; n != NULL; n = n->next) {
1240		for (cp = n->string; *cp != '\0'; cp++) {
1241			/* Ignore callbacks and alterations. */
1242			if (*cp == '(' || *cp == '{')
1243				break;
1244			if (*cp != ',')
1245				continue;
1246			mandoc_msg(MANDOCERR_FA_COMMA, n->line,
1247			    n->pos + (int)(cp - n->string), "%s", n->string);
1248			break;
1249		}
1250	}
1251	post_delim_nb(mdoc);
1252}
1253
1254static void
1255post_nm(POST_ARGS)
1256{
1257	struct roff_node	*n;
1258
1259	n = mdoc->last;
1260
1261	if (n->sec == SEC_NAME && n->child != NULL &&
1262	    n->child->type == ROFFT_TEXT && mdoc->meta.msec != NULL)
1263		mandoc_xr_add(mdoc->meta.msec, n->child->string, -1, -1);
1264
1265	if (n->last != NULL && n->last->tok == MDOC_Pp)
1266		roff_node_relink(mdoc, n->last);
1267
1268	if (mdoc->meta.name == NULL)
1269		deroff(&mdoc->meta.name, n);
1270
1271	if (mdoc->meta.name == NULL ||
1272	    (mdoc->lastsec == SEC_NAME && n->child == NULL))
1273		mandoc_msg(MANDOCERR_NM_NONAME, n->line, n->pos, "Nm");
1274
1275	switch (n->type) {
1276	case ROFFT_ELEM:
1277		post_delim_nb(mdoc);
1278		break;
1279	case ROFFT_HEAD:
1280		post_delim(mdoc);
1281		break;
1282	default:
1283		return;
1284	}
1285
1286	if ((n->child != NULL && n->child->type == ROFFT_TEXT) ||
1287	    mdoc->meta.name == NULL)
1288		return;
1289
1290	mdoc->next = ROFF_NEXT_CHILD;
1291	roff_word_alloc(mdoc, n->line, n->pos, mdoc->meta.name);
1292	mdoc->last->flags |= NODE_NOSRC;
1293	mdoc->last = n;
1294}
1295
1296static void
1297post_nd(POST_ARGS)
1298{
1299	struct roff_node	*n;
1300
1301	n = mdoc->last;
1302
1303	if (n->type != ROFFT_BODY)
1304		return;
1305
1306	if (n->sec != SEC_NAME)
1307		mandoc_msg(MANDOCERR_ND_LATE, n->line, n->pos, "Nd");
1308
1309	if (n->child == NULL)
1310		mandoc_msg(MANDOCERR_ND_EMPTY, n->line, n->pos, "Nd");
1311	else
1312		post_delim(mdoc);
1313
1314	post_hyph(mdoc);
1315}
1316
1317static void
1318post_display(POST_ARGS)
1319{
1320	struct roff_node *n, *np;
1321
1322	n = mdoc->last;
1323	switch (n->type) {
1324	case ROFFT_BODY:
1325		if (n->end != ENDBODY_NOT) {
1326			if (n->tok == MDOC_Bd &&
1327			    n->body->parent->args == NULL)
1328				roff_node_delete(mdoc, n);
1329		} else if (n->child == NULL)
1330			mandoc_msg(MANDOCERR_BLK_EMPTY, n->line, n->pos,
1331			    "%s", roff_name[n->tok]);
1332		else if (n->tok == MDOC_D1)
1333			post_hyph(mdoc);
1334		break;
1335	case ROFFT_BLOCK:
1336		if (n->tok == MDOC_Bd) {
1337			if (n->args == NULL) {
1338				mandoc_msg(MANDOCERR_BD_NOARG,
1339				    n->line, n->pos, "Bd");
1340				mdoc->next = ROFF_NEXT_SIBLING;
1341				while (n->body->child != NULL)
1342					roff_node_relink(mdoc,
1343					    n->body->child);
1344				roff_node_delete(mdoc, n);
1345				break;
1346			}
1347			post_bd(mdoc);
1348			post_prevpar(mdoc);
1349		}
1350		for (np = n->parent; np != NULL; np = np->parent) {
1351			if (np->type == ROFFT_BLOCK && np->tok == MDOC_Bd) {
1352				mandoc_msg(MANDOCERR_BD_NEST, n->line,
1353				    n->pos, "%s in Bd", roff_name[n->tok]);
1354				break;
1355			}
1356		}
1357		break;
1358	default:
1359		break;
1360	}
1361}
1362
1363static void
1364post_defaults(POST_ARGS)
1365{
1366	struct roff_node *nn;
1367
1368	if (mdoc->last->child != NULL) {
1369		post_delim_nb(mdoc);
1370		return;
1371	}
1372
1373	/*
1374	 * The `Ar' defaults to "file ..." if no value is provided as an
1375	 * argument; the `Mt' and `Pa' macros use "~"; the `Li' just
1376	 * gets an empty string.
1377	 */
1378
1379	nn = mdoc->last;
1380	switch (nn->tok) {
1381	case MDOC_Ar:
1382		mdoc->next = ROFF_NEXT_CHILD;
1383		roff_word_alloc(mdoc, nn->line, nn->pos, "file");
1384		mdoc->last->flags |= NODE_NOSRC;
1385		roff_word_alloc(mdoc, nn->line, nn->pos, "...");
1386		mdoc->last->flags |= NODE_NOSRC;
1387		break;
1388	case MDOC_Pa:
1389	case MDOC_Mt:
1390		mdoc->next = ROFF_NEXT_CHILD;
1391		roff_word_alloc(mdoc, nn->line, nn->pos, "~");
1392		mdoc->last->flags |= NODE_NOSRC;
1393		break;
1394	default:
1395		abort();
1396	}
1397	mdoc->last = nn;
1398}
1399
1400static void
1401post_at(POST_ARGS)
1402{
1403	struct roff_node	*n, *nch;
1404	const char		*att;
1405
1406	n = mdoc->last;
1407	nch = n->child;
1408
1409	/*
1410	 * If we have a child, look it up in the standard keys.  If a
1411	 * key exist, use that instead of the child; if it doesn't,
1412	 * prefix "AT&T UNIX " to the existing data.
1413	 */
1414
1415	att = NULL;
1416	if (nch != NULL && ((att = mdoc_a2att(nch->string)) == NULL))
1417		mandoc_msg(MANDOCERR_AT_BAD,
1418		    nch->line, nch->pos, "At %s", nch->string);
1419
1420	mdoc->next = ROFF_NEXT_CHILD;
1421	if (att != NULL) {
1422		roff_word_alloc(mdoc, nch->line, nch->pos, att);
1423		nch->flags |= NODE_NOPRT;
1424	} else
1425		roff_word_alloc(mdoc, n->line, n->pos, "AT&T UNIX");
1426	mdoc->last->flags |= NODE_NOSRC;
1427	mdoc->last = n;
1428}
1429
1430static void
1431post_an(POST_ARGS)
1432{
1433	struct roff_node *np, *nch;
1434
1435	post_an_norm(mdoc);
1436
1437	np = mdoc->last;
1438	nch = np->child;
1439	if (np->norm->An.auth == AUTH__NONE) {
1440		if (nch == NULL)
1441			mandoc_msg(MANDOCERR_MACRO_EMPTY,
1442			    np->line, np->pos, "An");
1443		else
1444			post_delim_nb(mdoc);
1445	} else if (nch != NULL)
1446		mandoc_msg(MANDOCERR_ARG_EXCESS,
1447		    nch->line, nch->pos, "An ... %s", nch->string);
1448}
1449
1450static void
1451post_en(POST_ARGS)
1452{
1453
1454	post_obsolete(mdoc);
1455	if (mdoc->last->type == ROFFT_BLOCK)
1456		mdoc->last->norm->Es = mdoc->last_es;
1457}
1458
1459static void
1460post_es(POST_ARGS)
1461{
1462
1463	post_obsolete(mdoc);
1464	mdoc->last_es = mdoc->last;
1465}
1466
1467static void
1468post_xx(POST_ARGS)
1469{
1470	struct roff_node	*n;
1471	const char		*os;
1472	char			*v;
1473
1474	post_delim_nb(mdoc);
1475
1476	n = mdoc->last;
1477	switch (n->tok) {
1478	case MDOC_Bsx:
1479		os = "BSD/OS";
1480		break;
1481	case MDOC_Dx:
1482		os = "DragonFly";
1483		break;
1484	case MDOC_Fx:
1485		os = "FreeBSD";
1486		break;
1487	case MDOC_Nx:
1488		os = "NetBSD";
1489		if (n->child == NULL)
1490			break;
1491		v = n->child->string;
1492		if ((v[0] != '0' && v[0] != '1') || v[1] != '.' ||
1493		    v[2] < '0' || v[2] > '9' ||
1494		    v[3] < 'a' || v[3] > 'z' || v[4] != '\0')
1495			break;
1496		n->child->flags |= NODE_NOPRT;
1497		mdoc->next = ROFF_NEXT_CHILD;
1498		roff_word_alloc(mdoc, n->child->line, n->child->pos, v);
1499		v = mdoc->last->string;
1500		v[3] = toupper((unsigned char)v[3]);
1501		mdoc->last->flags |= NODE_NOSRC;
1502		mdoc->last = n;
1503		break;
1504	case MDOC_Ox:
1505		os = "OpenBSD";
1506		break;
1507	case MDOC_Ux:
1508		os = "UNIX";
1509		break;
1510	default:
1511		abort();
1512	}
1513	mdoc->next = ROFF_NEXT_CHILD;
1514	roff_word_alloc(mdoc, n->line, n->pos, os);
1515	mdoc->last->flags |= NODE_NOSRC;
1516	mdoc->last = n;
1517}
1518
1519static void
1520post_it(POST_ARGS)
1521{
1522	struct roff_node *nbl, *nit, *nch;
1523	int		  i, cols;
1524	enum mdoc_list	  lt;
1525
1526	post_prevpar(mdoc);
1527
1528	nit = mdoc->last;
1529	if (nit->type != ROFFT_BLOCK)
1530		return;
1531
1532	nbl = nit->parent->parent;
1533	lt = nbl->norm->Bl.type;
1534
1535	switch (lt) {
1536	case LIST_tag:
1537	case LIST_hang:
1538	case LIST_ohang:
1539	case LIST_inset:
1540	case LIST_diag:
1541		if (nit->head->child == NULL)
1542			mandoc_msg(MANDOCERR_IT_NOHEAD,
1543			    nit->line, nit->pos, "Bl -%s It",
1544			    mdoc_argnames[nbl->args->argv[0].arg]);
1545		break;
1546	case LIST_bullet:
1547	case LIST_dash:
1548	case LIST_enum:
1549	case LIST_hyphen:
1550		if (nit->body == NULL || nit->body->child == NULL)
1551			mandoc_msg(MANDOCERR_IT_NOBODY,
1552			    nit->line, nit->pos, "Bl -%s It",
1553			    mdoc_argnames[nbl->args->argv[0].arg]);
1554		/* FALLTHROUGH */
1555	case LIST_item:
1556		if ((nch = nit->head->child) != NULL)
1557			mandoc_msg(MANDOCERR_ARG_SKIP,
1558			    nit->line, nit->pos, "It %s",
1559			    nch->string == NULL ? roff_name[nch->tok] :
1560			    nch->string);
1561		break;
1562	case LIST_column:
1563		cols = (int)nbl->norm->Bl.ncols;
1564
1565		assert(nit->head->child == NULL);
1566
1567		if (nit->head->next->child == NULL &&
1568		    nit->head->next->next == NULL) {
1569			mandoc_msg(MANDOCERR_MACRO_EMPTY,
1570			    nit->line, nit->pos, "It");
1571			roff_node_delete(mdoc, nit);
1572			break;
1573		}
1574
1575		i = 0;
1576		for (nch = nit->child; nch != NULL; nch = nch->next) {
1577			if (nch->type != ROFFT_BODY)
1578				continue;
1579			if (i++ && nch->flags & NODE_LINE)
1580				mandoc_msg(MANDOCERR_TA_LINE,
1581				    nch->line, nch->pos, "Ta");
1582		}
1583		if (i < cols || i > cols + 1)
1584			mandoc_msg(MANDOCERR_BL_COL, nit->line, nit->pos,
1585			    "%d columns, %d cells", cols, i);
1586		else if (nit->head->next->child != NULL &&
1587		    nit->head->next->child->flags & NODE_LINE)
1588			mandoc_msg(MANDOCERR_IT_NOARG,
1589			    nit->line, nit->pos, "Bl -column It");
1590		break;
1591	default:
1592		abort();
1593	}
1594}
1595
1596static void
1597post_bl_block(POST_ARGS)
1598{
1599	struct roff_node *n, *ni, *nc;
1600
1601	post_prevpar(mdoc);
1602
1603	n = mdoc->last;
1604	for (ni = n->body->child; ni != NULL; ni = ni->next) {
1605		if (ni->body == NULL)
1606			continue;
1607		nc = ni->body->last;
1608		while (nc != NULL) {
1609			switch (nc->tok) {
1610			case MDOC_Pp:
1611			case ROFF_br:
1612				break;
1613			default:
1614				nc = NULL;
1615				continue;
1616			}
1617			if (ni->next == NULL) {
1618				mandoc_msg(MANDOCERR_PAR_MOVE, nc->line,
1619				    nc->pos, "%s", roff_name[nc->tok]);
1620				roff_node_relink(mdoc, nc);
1621			} else if (n->norm->Bl.comp == 0 &&
1622			    n->norm->Bl.type != LIST_column) {
1623				mandoc_msg(MANDOCERR_PAR_SKIP,
1624				    nc->line, nc->pos,
1625				    "%s before It", roff_name[nc->tok]);
1626				roff_node_delete(mdoc, nc);
1627			} else
1628				break;
1629			nc = ni->body->last;
1630		}
1631	}
1632}
1633
1634/*
1635 * If the argument of -offset or -width is a macro,
1636 * replace it with the associated default width.
1637 */
1638static void
1639rewrite_macro2len(struct roff_man *mdoc, char **arg)
1640{
1641	size_t		  width;
1642	enum roff_tok	  tok;
1643
1644	if (*arg == NULL)
1645		return;
1646	else if ( ! strcmp(*arg, "Ds"))
1647		width = 6;
1648	else if ((tok = roffhash_find(mdoc->mdocmac, *arg, 0)) == TOKEN_NONE)
1649		return;
1650	else
1651		width = macro2len(tok);
1652
1653	free(*arg);
1654	mandoc_asprintf(arg, "%zun", width);
1655}
1656
1657static void
1658post_bl_head(POST_ARGS)
1659{
1660	struct roff_node *nbl, *nh, *nch, *nnext;
1661	struct mdoc_argv *argv;
1662	int		  i, j;
1663
1664	post_bl_norm(mdoc);
1665
1666	nh = mdoc->last;
1667	if (nh->norm->Bl.type != LIST_column) {
1668		if ((nch = nh->child) == NULL)
1669			return;
1670		mandoc_msg(MANDOCERR_ARG_EXCESS,
1671		    nch->line, nch->pos, "Bl ... %s", nch->string);
1672		while (nch != NULL) {
1673			roff_node_delete(mdoc, nch);
1674			nch = nh->child;
1675		}
1676		return;
1677	}
1678
1679	/*
1680	 * Append old-style lists, where the column width specifiers
1681	 * trail as macro parameters, to the new-style ("normal-form")
1682	 * lists where they're argument values following -column.
1683	 */
1684
1685	if (nh->child == NULL)
1686		return;
1687
1688	nbl = nh->parent;
1689	for (j = 0; j < (int)nbl->args->argc; j++)
1690		if (nbl->args->argv[j].arg == MDOC_Column)
1691			break;
1692
1693	assert(j < (int)nbl->args->argc);
1694
1695	/*
1696	 * Accommodate for new-style groff column syntax.  Shuffle the
1697	 * child nodes, all of which must be TEXT, as arguments for the
1698	 * column field.  Then, delete the head children.
1699	 */
1700
1701	argv = nbl->args->argv + j;
1702	i = argv->sz;
1703	for (nch = nh->child; nch != NULL; nch = nch->next)
1704		argv->sz++;
1705	argv->value = mandoc_reallocarray(argv->value,
1706	    argv->sz, sizeof(char *));
1707
1708	nh->norm->Bl.ncols = argv->sz;
1709	nh->norm->Bl.cols = (void *)argv->value;
1710
1711	for (nch = nh->child; nch != NULL; nch = nnext) {
1712		argv->value[i++] = nch->string;
1713		nch->string = NULL;
1714		nnext = nch->next;
1715		roff_node_delete(NULL, nch);
1716	}
1717	nh->child = NULL;
1718}
1719
1720static void
1721post_bl(POST_ARGS)
1722{
1723	struct roff_node	*nparent, *nprev; /* of the Bl block */
1724	struct roff_node	*nblock, *nbody;  /* of the Bl */
1725	struct roff_node	*nchild, *nnext;  /* of the Bl body */
1726	const char		*prev_Er;
1727	int			 order;
1728
1729	nbody = mdoc->last;
1730	switch (nbody->type) {
1731	case ROFFT_BLOCK:
1732		post_bl_block(mdoc);
1733		return;
1734	case ROFFT_HEAD:
1735		post_bl_head(mdoc);
1736		return;
1737	case ROFFT_BODY:
1738		break;
1739	default:
1740		return;
1741	}
1742	if (nbody->end != ENDBODY_NOT)
1743		return;
1744
1745	nchild = nbody->child;
1746	if (nchild == NULL) {
1747		mandoc_msg(MANDOCERR_BLK_EMPTY,
1748		    nbody->line, nbody->pos, "Bl");
1749		return;
1750	}
1751	while (nchild != NULL) {
1752		nnext = nchild->next;
1753		if (nchild->tok == MDOC_It ||
1754		    (nchild->tok == MDOC_Sm &&
1755		     nnext != NULL && nnext->tok == MDOC_It)) {
1756			nchild = nnext;
1757			continue;
1758		}
1759
1760		/*
1761		 * In .Bl -column, the first rows may be implicit,
1762		 * that is, they may not start with .It macros.
1763		 * Such rows may be followed by nodes generated on the
1764		 * roff level, for example .TS, which cannot be moved
1765		 * out of the list.  In that case, wrap such roff nodes
1766		 * into an implicit row.
1767		 */
1768
1769		if (nchild->prev != NULL) {
1770			mdoc->last = nchild;
1771			mdoc->next = ROFF_NEXT_SIBLING;
1772			roff_block_alloc(mdoc, nchild->line,
1773			    nchild->pos, MDOC_It);
1774			roff_head_alloc(mdoc, nchild->line,
1775			    nchild->pos, MDOC_It);
1776			mdoc->next = ROFF_NEXT_SIBLING;
1777			roff_body_alloc(mdoc, nchild->line,
1778			    nchild->pos, MDOC_It);
1779			while (nchild->tok != MDOC_It) {
1780				roff_node_relink(mdoc, nchild);
1781				if ((nchild = nnext) == NULL)
1782					break;
1783				nnext = nchild->next;
1784				mdoc->next = ROFF_NEXT_SIBLING;
1785			}
1786			mdoc->last = nbody;
1787			continue;
1788		}
1789
1790		mandoc_msg(MANDOCERR_BL_MOVE, nchild->line, nchild->pos,
1791		    "%s", roff_name[nchild->tok]);
1792
1793		/*
1794		 * Move the node out of the Bl block.
1795		 * First, collect all required node pointers.
1796		 */
1797
1798		nblock  = nbody->parent;
1799		nprev   = nblock->prev;
1800		nparent = nblock->parent;
1801
1802		/*
1803		 * Unlink this child.
1804		 */
1805
1806		nbody->child = nnext;
1807		if (nnext == NULL)
1808			nbody->last  = NULL;
1809		else
1810			nnext->prev = NULL;
1811
1812		/*
1813		 * Relink this child.
1814		 */
1815
1816		nchild->parent = nparent;
1817		nchild->prev   = nprev;
1818		nchild->next   = nblock;
1819
1820		nblock->prev = nchild;
1821		if (nprev == NULL)
1822			nparent->child = nchild;
1823		else
1824			nprev->next = nchild;
1825
1826		nchild = nnext;
1827	}
1828
1829	if (mdoc->meta.os_e != MANDOC_OS_NETBSD)
1830		return;
1831
1832	prev_Er = NULL;
1833	for (nchild = nbody->child; nchild != NULL; nchild = nchild->next) {
1834		if (nchild->tok != MDOC_It)
1835			continue;
1836		if ((nnext = nchild->head->child) == NULL)
1837			continue;
1838		if (nnext->type == ROFFT_BLOCK)
1839			nnext = nnext->body->child;
1840		if (nnext == NULL || nnext->tok != MDOC_Er)
1841			continue;
1842		nnext = nnext->child;
1843		if (prev_Er != NULL) {
1844			order = strcmp(prev_Er, nnext->string);
1845			if (order > 0)
1846				mandoc_msg(MANDOCERR_ER_ORDER,
1847				    nnext->line, nnext->pos,
1848				    "Er %s %s (NetBSD)",
1849				    prev_Er, nnext->string);
1850			else if (order == 0)
1851				mandoc_msg(MANDOCERR_ER_REP,
1852				    nnext->line, nnext->pos,
1853				    "Er %s (NetBSD)", prev_Er);
1854		}
1855		prev_Er = nnext->string;
1856	}
1857}
1858
1859static void
1860post_bk(POST_ARGS)
1861{
1862	struct roff_node	*n;
1863
1864	n = mdoc->last;
1865
1866	if (n->type == ROFFT_BLOCK && n->body->child == NULL) {
1867		mandoc_msg(MANDOCERR_BLK_EMPTY, n->line, n->pos, "Bk");
1868		roff_node_delete(mdoc, n);
1869	}
1870}
1871
1872static void
1873post_sm(POST_ARGS)
1874{
1875	struct roff_node	*nch;
1876
1877	nch = mdoc->last->child;
1878
1879	if (nch == NULL) {
1880		mdoc->flags ^= MDOC_SMOFF;
1881		return;
1882	}
1883
1884	assert(nch->type == ROFFT_TEXT);
1885
1886	if ( ! strcmp(nch->string, "on")) {
1887		mdoc->flags &= ~MDOC_SMOFF;
1888		return;
1889	}
1890	if ( ! strcmp(nch->string, "off")) {
1891		mdoc->flags |= MDOC_SMOFF;
1892		return;
1893	}
1894
1895	mandoc_msg(MANDOCERR_SM_BAD, nch->line, nch->pos,
1896	    "%s %s", roff_name[mdoc->last->tok], nch->string);
1897	roff_node_relink(mdoc, nch);
1898	return;
1899}
1900
1901static void
1902post_root(POST_ARGS)
1903{
1904	struct roff_node *n;
1905
1906	/* Add missing prologue data. */
1907
1908	if (mdoc->meta.date == NULL)
1909		mdoc->meta.date = mdoc->quick ? mandoc_strdup("") :
1910		    mandoc_normdate(mdoc, NULL, 0, 0);
1911
1912	if (mdoc->meta.title == NULL) {
1913		mandoc_msg(MANDOCERR_DT_NOTITLE, 0, 0, "EOF");
1914		mdoc->meta.title = mandoc_strdup("UNTITLED");
1915	}
1916
1917	if (mdoc->meta.vol == NULL)
1918		mdoc->meta.vol = mandoc_strdup("LOCAL");
1919
1920	if (mdoc->meta.os == NULL) {
1921		mandoc_msg(MANDOCERR_OS_MISSING, 0, 0, NULL);
1922		mdoc->meta.os = mandoc_strdup("");
1923	} else if (mdoc->meta.os_e &&
1924	    (mdoc->meta.rcsids & (1 << mdoc->meta.os_e)) == 0)
1925		mandoc_msg(MANDOCERR_RCS_MISSING, 0, 0,
1926		    mdoc->meta.os_e == MANDOC_OS_OPENBSD ?
1927		    "(OpenBSD)" : "(NetBSD)");
1928
1929	if (mdoc->meta.arch != NULL &&
1930	    arch_valid(mdoc->meta.arch, mdoc->meta.os_e) == 0) {
1931		n = mdoc->meta.first->child;
1932		while (n->tok != MDOC_Dt ||
1933		    n->child == NULL ||
1934		    n->child->next == NULL ||
1935		    n->child->next->next == NULL)
1936			n = n->next;
1937		n = n->child->next->next;
1938		mandoc_msg(MANDOCERR_ARCH_BAD, n->line, n->pos,
1939		    "Dt ... %s %s", mdoc->meta.arch,
1940		    mdoc->meta.os_e == MANDOC_OS_OPENBSD ?
1941		    "(OpenBSD)" : "(NetBSD)");
1942	}
1943
1944	/* Check that we begin with a proper `Sh'. */
1945
1946	n = mdoc->meta.first->child;
1947	while (n != NULL &&
1948	    (n->type == ROFFT_COMMENT ||
1949	     (n->tok >= MDOC_Dd &&
1950	      mdoc_macro(n->tok)->flags & MDOC_PROLOGUE)))
1951		n = n->next;
1952
1953	if (n == NULL)
1954		mandoc_msg(MANDOCERR_DOC_EMPTY, 0, 0, NULL);
1955	else if (n->tok != MDOC_Sh)
1956		mandoc_msg(MANDOCERR_SEC_BEFORE, n->line, n->pos,
1957		    "%s", roff_name[n->tok]);
1958}
1959
1960static void
1961post_rs(POST_ARGS)
1962{
1963	struct roff_node *np, *nch, *next, *prev;
1964	int		  i, j;
1965
1966	np = mdoc->last;
1967
1968	if (np->type != ROFFT_BODY)
1969		return;
1970
1971	if (np->child == NULL) {
1972		mandoc_msg(MANDOCERR_RS_EMPTY, np->line, np->pos, "Rs");
1973		return;
1974	}
1975
1976	/*
1977	 * The full `Rs' block needs special handling to order the
1978	 * sub-elements according to `rsord'.  Pick through each element
1979	 * and correctly order it.  This is an insertion sort.
1980	 */
1981
1982	next = NULL;
1983	for (nch = np->child->next; nch != NULL; nch = next) {
1984		/* Determine order number of this child. */
1985		for (i = 0; i < RSORD_MAX; i++)
1986			if (rsord[i] == nch->tok)
1987				break;
1988
1989		if (i == RSORD_MAX) {
1990			mandoc_msg(MANDOCERR_RS_BAD, nch->line, nch->pos,
1991			    "%s", roff_name[nch->tok]);
1992			i = -1;
1993		} else if (nch->tok == MDOC__J || nch->tok == MDOC__B)
1994			np->norm->Rs.quote_T++;
1995
1996		/*
1997		 * Remove this child from the chain.  This somewhat
1998		 * repeats roff_node_unlink(), but since we're
1999		 * just re-ordering, there's no need for the
2000		 * full unlink process.
2001		 */
2002
2003		if ((next = nch->next) != NULL)
2004			next->prev = nch->prev;
2005
2006		if ((prev = nch->prev) != NULL)
2007			prev->next = nch->next;
2008
2009		nch->prev = nch->next = NULL;
2010
2011		/*
2012		 * Scan back until we reach a node that's
2013		 * to be ordered before this child.
2014		 */
2015
2016		for ( ; prev ; prev = prev->prev) {
2017			/* Determine order of `prev'. */
2018			for (j = 0; j < RSORD_MAX; j++)
2019				if (rsord[j] == prev->tok)
2020					break;
2021			if (j == RSORD_MAX)
2022				j = -1;
2023
2024			if (j <= i)
2025				break;
2026		}
2027
2028		/*
2029		 * Set this child back into its correct place
2030		 * in front of the `prev' node.
2031		 */
2032
2033		nch->prev = prev;
2034
2035		if (prev == NULL) {
2036			np->child->prev = nch;
2037			nch->next = np->child;
2038			np->child = nch;
2039		} else {
2040			if (prev->next)
2041				prev->next->prev = nch;
2042			nch->next = prev->next;
2043			prev->next = nch;
2044		}
2045	}
2046}
2047
2048/*
2049 * For some arguments of some macros,
2050 * convert all breakable hyphens into ASCII_HYPH.
2051 */
2052static void
2053post_hyph(POST_ARGS)
2054{
2055	struct roff_node	*nch;
2056	char			*cp;
2057
2058	for (nch = mdoc->last->child; nch != NULL; nch = nch->next) {
2059		if (nch->type != ROFFT_TEXT)
2060			continue;
2061		cp = nch->string;
2062		if (*cp == '\0')
2063			continue;
2064		while (*(++cp) != '\0')
2065			if (*cp == '-' &&
2066			    isalpha((unsigned char)cp[-1]) &&
2067			    isalpha((unsigned char)cp[1]))
2068				*cp = ASCII_HYPH;
2069	}
2070}
2071
2072static void
2073post_ns(POST_ARGS)
2074{
2075	struct roff_node	*n;
2076
2077	n = mdoc->last;
2078	if (n->flags & NODE_LINE ||
2079	    (n->next != NULL && n->next->flags & NODE_DELIMC))
2080		mandoc_msg(MANDOCERR_NS_SKIP, n->line, n->pos, NULL);
2081}
2082
2083static void
2084post_sx(POST_ARGS)
2085{
2086	post_delim(mdoc);
2087	post_hyph(mdoc);
2088}
2089
2090static void
2091post_sh(POST_ARGS)
2092{
2093
2094	post_ignpar(mdoc);
2095
2096	switch (mdoc->last->type) {
2097	case ROFFT_HEAD:
2098		post_sh_head(mdoc);
2099		break;
2100	case ROFFT_BODY:
2101		switch (mdoc->lastsec)  {
2102		case SEC_NAME:
2103			post_sh_name(mdoc);
2104			break;
2105		case SEC_SEE_ALSO:
2106			post_sh_see_also(mdoc);
2107			break;
2108		case SEC_AUTHORS:
2109			post_sh_authors(mdoc);
2110			break;
2111		default:
2112			break;
2113		}
2114		break;
2115	default:
2116		break;
2117	}
2118}
2119
2120static void
2121post_sh_name(POST_ARGS)
2122{
2123	struct roff_node *n;
2124	int hasnm, hasnd;
2125
2126	hasnm = hasnd = 0;
2127
2128	for (n = mdoc->last->child; n != NULL; n = n->next) {
2129		switch (n->tok) {
2130		case MDOC_Nm:
2131			if (hasnm && n->child != NULL)
2132				mandoc_msg(MANDOCERR_NAMESEC_PUNCT,
2133				    n->line, n->pos,
2134				    "Nm %s", n->child->string);
2135			hasnm = 1;
2136			continue;
2137		case MDOC_Nd:
2138			hasnd = 1;
2139			if (n->next != NULL)
2140				mandoc_msg(MANDOCERR_NAMESEC_ND,
2141				    n->line, n->pos, NULL);
2142			break;
2143		case TOKEN_NONE:
2144			if (n->type == ROFFT_TEXT &&
2145			    n->string[0] == ',' && n->string[1] == '\0' &&
2146			    n->next != NULL && n->next->tok == MDOC_Nm) {
2147				n = n->next;
2148				continue;
2149			}
2150			/* FALLTHROUGH */
2151		default:
2152			mandoc_msg(MANDOCERR_NAMESEC_BAD,
2153			    n->line, n->pos, "%s", roff_name[n->tok]);
2154			continue;
2155		}
2156		break;
2157	}
2158
2159	if ( ! hasnm)
2160		mandoc_msg(MANDOCERR_NAMESEC_NONM,
2161		    mdoc->last->line, mdoc->last->pos, NULL);
2162	if ( ! hasnd)
2163		mandoc_msg(MANDOCERR_NAMESEC_NOND,
2164		    mdoc->last->line, mdoc->last->pos, NULL);
2165}
2166
2167static void
2168post_sh_see_also(POST_ARGS)
2169{
2170	const struct roff_node	*n;
2171	const char		*name, *sec;
2172	const char		*lastname, *lastsec, *lastpunct;
2173	int			 cmp;
2174
2175	n = mdoc->last->child;
2176	lastname = lastsec = lastpunct = NULL;
2177	while (n != NULL) {
2178		if (n->tok != MDOC_Xr ||
2179		    n->child == NULL ||
2180		    n->child->next == NULL)
2181			break;
2182
2183		/* Process one .Xr node. */
2184
2185		name = n->child->string;
2186		sec = n->child->next->string;
2187		if (lastsec != NULL) {
2188			if (lastpunct[0] != ',' || lastpunct[1] != '\0')
2189				mandoc_msg(MANDOCERR_XR_PUNCT, n->line,
2190				    n->pos, "%s before %s(%s)",
2191				    lastpunct, name, sec);
2192			cmp = strcmp(lastsec, sec);
2193			if (cmp > 0)
2194				mandoc_msg(MANDOCERR_XR_ORDER, n->line,
2195				    n->pos, "%s(%s) after %s(%s)",
2196				    name, sec, lastname, lastsec);
2197			else if (cmp == 0 &&
2198			    strcasecmp(lastname, name) > 0)
2199				mandoc_msg(MANDOCERR_XR_ORDER, n->line,
2200				    n->pos, "%s after %s", name, lastname);
2201		}
2202		lastname = name;
2203		lastsec = sec;
2204
2205		/* Process the following node. */
2206
2207		n = n->next;
2208		if (n == NULL)
2209			break;
2210		if (n->tok == MDOC_Xr) {
2211			lastpunct = "none";
2212			continue;
2213		}
2214		if (n->type != ROFFT_TEXT)
2215			break;
2216		for (name = n->string; *name != '\0'; name++)
2217			if (isalpha((const unsigned char)*name))
2218				return;
2219		lastpunct = n->string;
2220		if (n->next == NULL || n->next->tok == MDOC_Rs)
2221			mandoc_msg(MANDOCERR_XR_PUNCT, n->line,
2222			    n->pos, "%s after %s(%s)",
2223			    lastpunct, lastname, lastsec);
2224		n = n->next;
2225	}
2226}
2227
2228static int
2229child_an(const struct roff_node *n)
2230{
2231
2232	for (n = n->child; n != NULL; n = n->next)
2233		if ((n->tok == MDOC_An && n->child != NULL) || child_an(n))
2234			return 1;
2235	return 0;
2236}
2237
2238static void
2239post_sh_authors(POST_ARGS)
2240{
2241
2242	if ( ! child_an(mdoc->last))
2243		mandoc_msg(MANDOCERR_AN_MISSING,
2244		    mdoc->last->line, mdoc->last->pos, NULL);
2245}
2246
2247/*
2248 * Return an upper bound for the string distance (allowing
2249 * transpositions).  Not a full Levenshtein implementation
2250 * because Levenshtein is quadratic in the string length
2251 * and this function is called for every standard name,
2252 * so the check for each custom name would be cubic.
2253 * The following crude heuristics is linear, resulting
2254 * in quadratic behaviour for checking one custom name,
2255 * which does not cause measurable slowdown.
2256 */
2257static int
2258similar(const char *s1, const char *s2)
2259{
2260	const int	maxdist = 3;
2261	int		dist = 0;
2262
2263	while (s1[0] != '\0' && s2[0] != '\0') {
2264		if (s1[0] == s2[0]) {
2265			s1++;
2266			s2++;
2267			continue;
2268		}
2269		if (++dist > maxdist)
2270			return INT_MAX;
2271		if (s1[1] == s2[1]) {  /* replacement */
2272			s1++;
2273			s2++;
2274		} else if (s1[0] == s2[1] && s1[1] == s2[0]) {
2275			s1 += 2;	/* transposition */
2276			s2 += 2;
2277		} else if (s1[0] == s2[1])  /* insertion */
2278			s2++;
2279		else if (s1[1] == s2[0])  /* deletion */
2280			s1++;
2281		else
2282			return INT_MAX;
2283	}
2284	dist += strlen(s1) + strlen(s2);
2285	return dist > maxdist ? INT_MAX : dist;
2286}
2287
2288static void
2289post_sh_head(POST_ARGS)
2290{
2291	struct roff_node	*nch;
2292	const char		*goodsec;
2293	const char *const	*testsec;
2294	int			 dist, mindist;
2295	enum roff_sec		 sec;
2296
2297	/*
2298	 * Process a new section.  Sections are either "named" or
2299	 * "custom".  Custom sections are user-defined, while named ones
2300	 * follow a conventional order and may only appear in certain
2301	 * manual sections.
2302	 */
2303
2304	sec = mdoc->last->sec;
2305
2306	/* The NAME should be first. */
2307
2308	if (sec != SEC_NAME && mdoc->lastnamed == SEC_NONE)
2309		mandoc_msg(MANDOCERR_NAMESEC_FIRST,
2310		    mdoc->last->line, mdoc->last->pos, "Sh %s",
2311		    sec != SEC_CUSTOM ? secnames[sec] :
2312		    (nch = mdoc->last->child) == NULL ? "" :
2313		    nch->type == ROFFT_TEXT ? nch->string :
2314		    roff_name[nch->tok]);
2315
2316	/* The SYNOPSIS gets special attention in other areas. */
2317
2318	if (sec == SEC_SYNOPSIS) {
2319		roff_setreg(mdoc->roff, "nS", 1, '=');
2320		mdoc->flags |= MDOC_SYNOPSIS;
2321	} else {
2322		roff_setreg(mdoc->roff, "nS", 0, '=');
2323		mdoc->flags &= ~MDOC_SYNOPSIS;
2324	}
2325
2326	/* Mark our last section. */
2327
2328	mdoc->lastsec = sec;
2329
2330	/* We don't care about custom sections after this. */
2331
2332	if (sec == SEC_CUSTOM) {
2333		if ((nch = mdoc->last->child) == NULL ||
2334		    nch->type != ROFFT_TEXT || nch->next != NULL)
2335			return;
2336		goodsec = NULL;
2337		mindist = INT_MAX;
2338		for (testsec = secnames + 1; *testsec != NULL; testsec++) {
2339			dist = similar(nch->string, *testsec);
2340			if (dist < mindist) {
2341				goodsec = *testsec;
2342				mindist = dist;
2343			}
2344		}
2345		if (goodsec != NULL)
2346			mandoc_msg(MANDOCERR_SEC_TYPO, nch->line, nch->pos,
2347			    "Sh %s instead of %s", nch->string, goodsec);
2348		return;
2349	}
2350
2351	/*
2352	 * Check whether our non-custom section is being repeated or is
2353	 * out of order.
2354	 */
2355
2356	if (sec == mdoc->lastnamed)
2357		mandoc_msg(MANDOCERR_SEC_REP, mdoc->last->line,
2358		    mdoc->last->pos, "Sh %s", secnames[sec]);
2359
2360	if (sec < mdoc->lastnamed)
2361		mandoc_msg(MANDOCERR_SEC_ORDER, mdoc->last->line,
2362		    mdoc->last->pos, "Sh %s", secnames[sec]);
2363
2364	/* Mark the last named section. */
2365
2366	mdoc->lastnamed = sec;
2367
2368	/* Check particular section/manual conventions. */
2369
2370	if (mdoc->meta.msec == NULL)
2371		return;
2372
2373	goodsec = NULL;
2374	switch (sec) {
2375	case SEC_ERRORS:
2376		if (*mdoc->meta.msec == '4')
2377			break;
2378		goodsec = "2, 3, 4, 9";
2379		/* FALLTHROUGH */
2380	case SEC_RETURN_VALUES:
2381	case SEC_LIBRARY:
2382		if (*mdoc->meta.msec == '2')
2383			break;
2384		if (*mdoc->meta.msec == '3')
2385			break;
2386		if (NULL == goodsec)
2387			goodsec = "2, 3, 9";
2388		/* FALLTHROUGH */
2389	case SEC_CONTEXT:
2390		if (*mdoc->meta.msec == '9')
2391			break;
2392		if (NULL == goodsec)
2393			goodsec = "9";
2394		mandoc_msg(MANDOCERR_SEC_MSEC,
2395		    mdoc->last->line, mdoc->last->pos,
2396		    "Sh %s for %s only", secnames[sec], goodsec);
2397		break;
2398	default:
2399		break;
2400	}
2401}
2402
2403static void
2404post_xr(POST_ARGS)
2405{
2406	struct roff_node *n, *nch;
2407
2408	n = mdoc->last;
2409	nch = n->child;
2410	if (nch->next == NULL) {
2411		mandoc_msg(MANDOCERR_XR_NOSEC,
2412		    n->line, n->pos, "Xr %s", nch->string);
2413	} else {
2414		assert(nch->next == n->last);
2415		if(mandoc_xr_add(nch->next->string, nch->string,
2416		    nch->line, nch->pos))
2417			mandoc_msg(MANDOCERR_XR_SELF,
2418			    nch->line, nch->pos, "Xr %s %s",
2419			    nch->string, nch->next->string);
2420	}
2421	post_delim_nb(mdoc);
2422}
2423
2424static void
2425post_ignpar(POST_ARGS)
2426{
2427	struct roff_node *np;
2428
2429	switch (mdoc->last->type) {
2430	case ROFFT_BLOCK:
2431		post_prevpar(mdoc);
2432		return;
2433	case ROFFT_HEAD:
2434		post_delim(mdoc);
2435		post_hyph(mdoc);
2436		return;
2437	case ROFFT_BODY:
2438		break;
2439	default:
2440		return;
2441	}
2442
2443	if ((np = mdoc->last->child) != NULL)
2444		if (np->tok == MDOC_Pp ||
2445		    np->tok == ROFF_br || np->tok == ROFF_sp) {
2446			mandoc_msg(MANDOCERR_PAR_SKIP, np->line, np->pos,
2447			    "%s after %s", roff_name[np->tok],
2448			    roff_name[mdoc->last->tok]);
2449			roff_node_delete(mdoc, np);
2450		}
2451
2452	if ((np = mdoc->last->last) != NULL)
2453		if (np->tok == MDOC_Pp || np->tok == ROFF_br) {
2454			mandoc_msg(MANDOCERR_PAR_SKIP, np->line, np->pos,
2455			    "%s at the end of %s", roff_name[np->tok],
2456			    roff_name[mdoc->last->tok]);
2457			roff_node_delete(mdoc, np);
2458		}
2459}
2460
2461static void
2462post_prevpar(POST_ARGS)
2463{
2464	struct roff_node *n;
2465
2466	n = mdoc->last;
2467	if (NULL == n->prev)
2468		return;
2469	if (n->type != ROFFT_ELEM && n->type != ROFFT_BLOCK)
2470		return;
2471
2472	/*
2473	 * Don't allow `Pp' prior to a paragraph-type
2474	 * block: `Pp' or non-compact `Bd' or `Bl'.
2475	 */
2476
2477	if (n->prev->tok != MDOC_Pp && n->prev->tok != ROFF_br)
2478		return;
2479	if (n->tok == MDOC_Bl && n->norm->Bl.comp)
2480		return;
2481	if (n->tok == MDOC_Bd && n->norm->Bd.comp)
2482		return;
2483	if (n->tok == MDOC_It && n->parent->norm->Bl.comp)
2484		return;
2485
2486	mandoc_msg(MANDOCERR_PAR_SKIP, n->prev->line, n->prev->pos,
2487	    "%s before %s", roff_name[n->prev->tok], roff_name[n->tok]);
2488	roff_node_delete(mdoc, n->prev);
2489}
2490
2491static void
2492post_par(POST_ARGS)
2493{
2494	struct roff_node *np;
2495
2496	post_prevpar(mdoc);
2497
2498	np = mdoc->last;
2499	if (np->child != NULL)
2500		mandoc_msg(MANDOCERR_ARG_SKIP, np->line, np->pos,
2501		    "%s %s", roff_name[np->tok], np->child->string);
2502}
2503
2504static void
2505post_dd(POST_ARGS)
2506{
2507	struct roff_node *n;
2508	char		 *datestr;
2509
2510	n = mdoc->last;
2511	n->flags |= NODE_NOPRT;
2512
2513	if (mdoc->meta.date != NULL) {
2514		mandoc_msg(MANDOCERR_PROLOG_REP, n->line, n->pos, "Dd");
2515		free(mdoc->meta.date);
2516	} else if (mdoc->flags & MDOC_PBODY)
2517		mandoc_msg(MANDOCERR_PROLOG_LATE, n->line, n->pos, "Dd");
2518	else if (mdoc->meta.title != NULL)
2519		mandoc_msg(MANDOCERR_PROLOG_ORDER,
2520		    n->line, n->pos, "Dd after Dt");
2521	else if (mdoc->meta.os != NULL)
2522		mandoc_msg(MANDOCERR_PROLOG_ORDER,
2523		    n->line, n->pos, "Dd after Os");
2524
2525	if (n->child == NULL || n->child->string[0] == '\0') {
2526		mdoc->meta.date = mdoc->quick ? mandoc_strdup("") :
2527		    mandoc_normdate(mdoc, NULL, n->line, n->pos);
2528		return;
2529	}
2530
2531	datestr = NULL;
2532	deroff(&datestr, n);
2533	if (mdoc->quick)
2534		mdoc->meta.date = datestr;
2535	else {
2536		mdoc->meta.date = mandoc_normdate(mdoc,
2537		    datestr, n->line, n->pos);
2538		free(datestr);
2539	}
2540}
2541
2542static void
2543post_dt(POST_ARGS)
2544{
2545	struct roff_node *nn, *n;
2546	const char	 *cp;
2547	char		 *p;
2548
2549	n = mdoc->last;
2550	n->flags |= NODE_NOPRT;
2551
2552	if (mdoc->flags & MDOC_PBODY) {
2553		mandoc_msg(MANDOCERR_DT_LATE, n->line, n->pos, "Dt");
2554		return;
2555	}
2556
2557	if (mdoc->meta.title != NULL)
2558		mandoc_msg(MANDOCERR_PROLOG_REP, n->line, n->pos, "Dt");
2559	else if (mdoc->meta.os != NULL)
2560		mandoc_msg(MANDOCERR_PROLOG_ORDER,
2561		    n->line, n->pos, "Dt after Os");
2562
2563	free(mdoc->meta.title);
2564	free(mdoc->meta.msec);
2565	free(mdoc->meta.vol);
2566	free(mdoc->meta.arch);
2567
2568	mdoc->meta.title = NULL;
2569	mdoc->meta.msec = NULL;
2570	mdoc->meta.vol = NULL;
2571	mdoc->meta.arch = NULL;
2572
2573	/* Mandatory first argument: title. */
2574
2575	nn = n->child;
2576	if (nn == NULL || *nn->string == '\0') {
2577		mandoc_msg(MANDOCERR_DT_NOTITLE, n->line, n->pos, "Dt");
2578		mdoc->meta.title = mandoc_strdup("UNTITLED");
2579	} else {
2580		mdoc->meta.title = mandoc_strdup(nn->string);
2581
2582		/* Check that all characters are uppercase. */
2583
2584		for (p = nn->string; *p != '\0'; p++)
2585			if (islower((unsigned char)*p)) {
2586				mandoc_msg(MANDOCERR_TITLE_CASE, nn->line,
2587				    nn->pos + (int)(p - nn->string),
2588				    "Dt %s", nn->string);
2589				break;
2590			}
2591	}
2592
2593	/* Mandatory second argument: section. */
2594
2595	if (nn != NULL)
2596		nn = nn->next;
2597
2598	if (nn == NULL) {
2599		mandoc_msg(MANDOCERR_MSEC_MISSING, n->line, n->pos,
2600		    "Dt %s", mdoc->meta.title);
2601		mdoc->meta.vol = mandoc_strdup("LOCAL");
2602		return;  /* msec and arch remain NULL. */
2603	}
2604
2605	mdoc->meta.msec = mandoc_strdup(nn->string);
2606
2607	/* Infer volume title from section number. */
2608
2609	cp = mandoc_a2msec(nn->string);
2610	if (cp == NULL) {
2611		mandoc_msg(MANDOCERR_MSEC_BAD,
2612		    nn->line, nn->pos, "Dt ... %s", nn->string);
2613		mdoc->meta.vol = mandoc_strdup(nn->string);
2614	} else
2615		mdoc->meta.vol = mandoc_strdup(cp);
2616
2617	/* Optional third argument: architecture. */
2618
2619	if ((nn = nn->next) == NULL)
2620		return;
2621
2622	for (p = nn->string; *p != '\0'; p++)
2623		*p = tolower((unsigned char)*p);
2624	mdoc->meta.arch = mandoc_strdup(nn->string);
2625
2626	/* Ignore fourth and later arguments. */
2627
2628	if ((nn = nn->next) != NULL)
2629		mandoc_msg(MANDOCERR_ARG_EXCESS,
2630		    nn->line, nn->pos, "Dt ... %s", nn->string);
2631}
2632
2633static void
2634post_bx(POST_ARGS)
2635{
2636	struct roff_node	*n, *nch;
2637	const char		*macro;
2638
2639	post_delim_nb(mdoc);
2640
2641	n = mdoc->last;
2642	nch = n->child;
2643
2644	if (nch != NULL) {
2645		macro = !strcmp(nch->string, "Open") ? "Ox" :
2646		    !strcmp(nch->string, "Net") ? "Nx" :
2647		    !strcmp(nch->string, "Free") ? "Fx" :
2648		    !strcmp(nch->string, "DragonFly") ? "Dx" : NULL;
2649		if (macro != NULL)
2650			mandoc_msg(MANDOCERR_BX,
2651			    n->line, n->pos, "%s", macro);
2652		mdoc->last = nch;
2653		nch = nch->next;
2654		mdoc->next = ROFF_NEXT_SIBLING;
2655		roff_elem_alloc(mdoc, n->line, n->pos, MDOC_Ns);
2656		mdoc->last->flags |= NODE_NOSRC;
2657		mdoc->next = ROFF_NEXT_SIBLING;
2658	} else
2659		mdoc->next = ROFF_NEXT_CHILD;
2660	roff_word_alloc(mdoc, n->line, n->pos, "BSD");
2661	mdoc->last->flags |= NODE_NOSRC;
2662
2663	if (nch == NULL) {
2664		mdoc->last = n;
2665		return;
2666	}
2667
2668	roff_elem_alloc(mdoc, n->line, n->pos, MDOC_Ns);
2669	mdoc->last->flags |= NODE_NOSRC;
2670	mdoc->next = ROFF_NEXT_SIBLING;
2671	roff_word_alloc(mdoc, n->line, n->pos, "-");
2672	mdoc->last->flags |= NODE_NOSRC;
2673	roff_elem_alloc(mdoc, n->line, n->pos, MDOC_Ns);
2674	mdoc->last->flags |= NODE_NOSRC;
2675	mdoc->last = n;
2676
2677	/*
2678	 * Make `Bx's second argument always start with an uppercase
2679	 * letter.  Groff checks if it's an "accepted" term, but we just
2680	 * uppercase blindly.
2681	 */
2682
2683	*nch->string = (char)toupper((unsigned char)*nch->string);
2684}
2685
2686static void
2687post_os(POST_ARGS)
2688{
2689#ifndef OSNAME
2690	struct utsname	  utsname;
2691	static char	 *defbuf;
2692#endif
2693	struct roff_node *n;
2694
2695	n = mdoc->last;
2696	n->flags |= NODE_NOPRT;
2697
2698	if (mdoc->meta.os != NULL)
2699		mandoc_msg(MANDOCERR_PROLOG_REP, n->line, n->pos, "Os");
2700	else if (mdoc->flags & MDOC_PBODY)
2701		mandoc_msg(MANDOCERR_PROLOG_LATE, n->line, n->pos, "Os");
2702
2703	post_delim(mdoc);
2704
2705	/*
2706	 * Set the operating system by way of the `Os' macro.
2707	 * The order of precedence is:
2708	 * 1. the argument of the `Os' macro, unless empty
2709	 * 2. the -Ios=foo command line argument, if provided
2710	 * 3. -DOSNAME="\"foo\"", if provided during compilation
2711	 * 4. "sysname release" from uname(3)
2712	 */
2713
2714	free(mdoc->meta.os);
2715	mdoc->meta.os = NULL;
2716	deroff(&mdoc->meta.os, n);
2717	if (mdoc->meta.os)
2718		goto out;
2719
2720	if (mdoc->os_s != NULL) {
2721		mdoc->meta.os = mandoc_strdup(mdoc->os_s);
2722		goto out;
2723	}
2724
2725#ifdef OSNAME
2726	mdoc->meta.os = mandoc_strdup(OSNAME);
2727#else /*!OSNAME */
2728	if (defbuf == NULL) {
2729		if (uname(&utsname) == -1) {
2730			mandoc_msg(MANDOCERR_OS_UNAME, n->line, n->pos, "Os");
2731			defbuf = mandoc_strdup("UNKNOWN");
2732		} else
2733			mandoc_asprintf(&defbuf, "%s %s",
2734			    utsname.sysname, utsname.release);
2735	}
2736	mdoc->meta.os = mandoc_strdup(defbuf);
2737#endif /*!OSNAME*/
2738
2739out:
2740	if (mdoc->meta.os_e == MANDOC_OS_OTHER) {
2741		if (strstr(mdoc->meta.os, "OpenBSD") != NULL)
2742			mdoc->meta.os_e = MANDOC_OS_OPENBSD;
2743		else if (strstr(mdoc->meta.os, "NetBSD") != NULL)
2744			mdoc->meta.os_e = MANDOC_OS_NETBSD;
2745	}
2746
2747	/*
2748	 * This is the earliest point where we can check
2749	 * Mdocdate conventions because we don't know
2750	 * the operating system earlier.
2751	 */
2752
2753	if (n->child != NULL)
2754		mandoc_msg(MANDOCERR_OS_ARG, n->child->line, n->child->pos,
2755		    "Os %s (%s)", n->child->string,
2756		    mdoc->meta.os_e == MANDOC_OS_OPENBSD ?
2757		    "OpenBSD" : "NetBSD");
2758
2759	while (n->tok != MDOC_Dd)
2760		if ((n = n->prev) == NULL)
2761			return;
2762	if ((n = n->child) == NULL)
2763		return;
2764	if (strncmp(n->string, "$" "Mdocdate", 9)) {
2765		if (mdoc->meta.os_e == MANDOC_OS_OPENBSD)
2766			mandoc_msg(MANDOCERR_MDOCDATE_MISSING, n->line,
2767			    n->pos, "Dd %s (OpenBSD)", n->string);
2768	} else {
2769		if (mdoc->meta.os_e == MANDOC_OS_NETBSD)
2770			mandoc_msg(MANDOCERR_MDOCDATE, n->line,
2771			    n->pos, "Dd %s (NetBSD)", n->string);
2772	}
2773}
2774
2775enum roff_sec
2776mdoc_a2sec(const char *p)
2777{
2778	int		 i;
2779
2780	for (i = 0; i < (int)SEC__MAX; i++)
2781		if (secnames[i] && 0 == strcmp(p, secnames[i]))
2782			return (enum roff_sec)i;
2783
2784	return SEC_CUSTOM;
2785}
2786
2787static size_t
2788macro2len(enum roff_tok macro)
2789{
2790
2791	switch (macro) {
2792	case MDOC_Ad:
2793		return 12;
2794	case MDOC_Ao:
2795		return 12;
2796	case MDOC_An:
2797		return 12;
2798	case MDOC_Aq:
2799		return 12;
2800	case MDOC_Ar:
2801		return 12;
2802	case MDOC_Bo:
2803		return 12;
2804	case MDOC_Bq:
2805		return 12;
2806	case MDOC_Cd:
2807		return 12;
2808	case MDOC_Cm:
2809		return 10;
2810	case MDOC_Do:
2811		return 10;
2812	case MDOC_Dq:
2813		return 12;
2814	case MDOC_Dv:
2815		return 12;
2816	case MDOC_Eo:
2817		return 12;
2818	case MDOC_Em:
2819		return 10;
2820	case MDOC_Er:
2821		return 17;
2822	case MDOC_Ev:
2823		return 15;
2824	case MDOC_Fa:
2825		return 12;
2826	case MDOC_Fl:
2827		return 10;
2828	case MDOC_Fo:
2829		return 16;
2830	case MDOC_Fn:
2831		return 16;
2832	case MDOC_Ic:
2833		return 10;
2834	case MDOC_Li:
2835		return 16;
2836	case MDOC_Ms:
2837		return 6;
2838	case MDOC_Nm:
2839		return 10;
2840	case MDOC_No:
2841		return 12;
2842	case MDOC_Oo:
2843		return 10;
2844	case MDOC_Op:
2845		return 14;
2846	case MDOC_Pa:
2847		return 32;
2848	case MDOC_Pf:
2849		return 12;
2850	case MDOC_Po:
2851		return 12;
2852	case MDOC_Pq:
2853		return 12;
2854	case MDOC_Ql:
2855		return 16;
2856	case MDOC_Qo:
2857		return 12;
2858	case MDOC_So:
2859		return 12;
2860	case MDOC_Sq:
2861		return 12;
2862	case MDOC_Sy:
2863		return 6;
2864	case MDOC_Sx:
2865		return 16;
2866	case MDOC_Tn:
2867		return 10;
2868	case MDOC_Va:
2869		return 12;
2870	case MDOC_Vt:
2871		return 12;
2872	case MDOC_Xr:
2873		return 10;
2874	default:
2875		break;
2876	};
2877	return 0;
2878}
2879