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