1/*	$Id: mdoc_validate.c,v 1.301 2016/01/08 17:48:09 schwarze Exp $ */
2/*
3 * Copyright (c) 2008-2012 Kristaps Dzonsons <kristaps@bsd.lv>
4 * Copyright (c) 2010-2016 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 "roff.h"
37#include "mdoc.h"
38#include "libmandoc.h"
39#include "roff_int.h"
40#include "libmdoc.h"
41
42/* FIXME: .Bl -diag can't have non-text children in HEAD. */
43
44#define	POST_ARGS struct roff_man *mdoc
45
46enum	check_ineq {
47	CHECK_LT,
48	CHECK_GT,
49	CHECK_EQ
50};
51
52typedef	void	(*v_post)(POST_ARGS);
53
54static	void	 check_text(struct roff_man *, int, int, char *);
55static	void	 check_argv(struct roff_man *,
56			struct roff_node *, struct mdoc_argv *);
57static	void	 check_args(struct roff_man *, struct roff_node *);
58static	int	 child_an(const struct roff_node *);
59static	size_t		macro2len(int);
60static	void	 rewrite_macro2len(char **);
61
62static	void	 post_an(POST_ARGS);
63static	void	 post_an_norm(POST_ARGS);
64static	void	 post_at(POST_ARGS);
65static	void	 post_bd(POST_ARGS);
66static	void	 post_bf(POST_ARGS);
67static	void	 post_bk(POST_ARGS);
68static	void	 post_bl(POST_ARGS);
69static	void	 post_bl_block(POST_ARGS);
70static	void	 post_bl_block_tag(POST_ARGS);
71static	void	 post_bl_head(POST_ARGS);
72static	void	 post_bl_norm(POST_ARGS);
73static	void	 post_bx(POST_ARGS);
74static	void	 post_defaults(POST_ARGS);
75static	void	 post_display(POST_ARGS);
76static	void	 post_dd(POST_ARGS);
77static	void	 post_dt(POST_ARGS);
78static	void	 post_en(POST_ARGS);
79static	void	 post_es(POST_ARGS);
80static	void	 post_eoln(POST_ARGS);
81static	void	 post_ex(POST_ARGS);
82static	void	 post_fa(POST_ARGS);
83static	void	 post_fn(POST_ARGS);
84static	void	 post_fname(POST_ARGS);
85static	void	 post_fo(POST_ARGS);
86static	void	 post_hyph(POST_ARGS);
87static	void	 post_ignpar(POST_ARGS);
88static	void	 post_it(POST_ARGS);
89static	void	 post_lb(POST_ARGS);
90static	void	 post_nd(POST_ARGS);
91static	void	 post_nm(POST_ARGS);
92static	void	 post_ns(POST_ARGS);
93static	void	 post_obsolete(POST_ARGS);
94static	void	 post_os(POST_ARGS);
95static	void	 post_par(POST_ARGS);
96static	void	 post_prevpar(POST_ARGS);
97static	void	 post_root(POST_ARGS);
98static	void	 post_rs(POST_ARGS);
99static	void	 post_sh(POST_ARGS);
100static	void	 post_sh_head(POST_ARGS);
101static	void	 post_sh_name(POST_ARGS);
102static	void	 post_sh_see_also(POST_ARGS);
103static	void	 post_sh_authors(POST_ARGS);
104static	void	 post_sm(POST_ARGS);
105static	void	 post_st(POST_ARGS);
106static	void	 post_std(POST_ARGS);
107
108static	v_post mdoc_valids[MDOC_MAX] = {
109	NULL,		/* Ap */
110	post_dd,	/* Dd */
111	post_dt,	/* Dt */
112	post_os,	/* Os */
113	post_sh,	/* Sh */
114	post_ignpar,	/* Ss */
115	post_par,	/* Pp */
116	post_display,	/* D1 */
117	post_display,	/* Dl */
118	post_display,	/* Bd */
119	NULL,		/* Ed */
120	post_bl,	/* Bl */
121	NULL,		/* El */
122	post_it,	/* It */
123	NULL,		/* Ad */
124	post_an,	/* An */
125	post_defaults,	/* Ar */
126	NULL,		/* Cd */
127	NULL,		/* Cm */
128	NULL,		/* Dv */
129	NULL,		/* Er */
130	NULL,		/* Ev */
131	post_ex,	/* Ex */
132	post_fa,	/* Fa */
133	NULL,		/* Fd */
134	NULL,		/* Fl */
135	post_fn,	/* Fn */
136	NULL,		/* Ft */
137	NULL,		/* Ic */
138	NULL,		/* In */
139	post_defaults,	/* Li */
140	post_nd,	/* Nd */
141	post_nm,	/* Nm */
142	NULL,		/* Op */
143	post_obsolete,	/* Ot */
144	post_defaults,	/* Pa */
145	post_std,	/* Rv */
146	post_st,	/* St */
147	NULL,		/* Va */
148	NULL,		/* Vt */
149	NULL,		/* Xr */
150	NULL,		/* %A */
151	post_hyph,	/* %B */ /* FIXME: can be used outside Rs/Re. */
152	NULL,		/* %D */
153	NULL,		/* %I */
154	NULL,		/* %J */
155	post_hyph,	/* %N */
156	post_hyph,	/* %O */
157	NULL,		/* %P */
158	post_hyph,	/* %R */
159	post_hyph,	/* %T */ /* FIXME: can be used outside Rs/Re. */
160	NULL,		/* %V */
161	NULL,		/* Ac */
162	NULL,		/* Ao */
163	NULL,		/* Aq */
164	post_at,	/* At */
165	NULL,		/* Bc */
166	post_bf,	/* Bf */
167	NULL,		/* Bo */
168	NULL,		/* Bq */
169	NULL,		/* Bsx */
170	post_bx,	/* Bx */
171	post_obsolete,	/* Db */
172	NULL,		/* Dc */
173	NULL,		/* Do */
174	NULL,		/* Dq */
175	NULL,		/* Ec */
176	NULL,		/* Ef */
177	NULL,		/* Em */
178	NULL,		/* Eo */
179	NULL,		/* Fx */
180	NULL,		/* Ms */
181	NULL,		/* No */
182	post_ns,	/* Ns */
183	NULL,		/* Nx */
184	NULL,		/* Ox */
185	NULL,		/* Pc */
186	NULL,		/* Pf */
187	NULL,		/* Po */
188	NULL,		/* Pq */
189	NULL,		/* Qc */
190	NULL,		/* Ql */
191	NULL,		/* Qo */
192	NULL,		/* Qq */
193	NULL,		/* Re */
194	post_rs,	/* Rs */
195	NULL,		/* Sc */
196	NULL,		/* So */
197	NULL,		/* Sq */
198	post_sm,	/* Sm */
199	post_hyph,	/* Sx */
200	NULL,		/* Sy */
201	NULL,		/* Tn */
202	NULL,		/* Ux */
203	NULL,		/* Xc */
204	NULL,		/* Xo */
205	post_fo,	/* Fo */
206	NULL,		/* Fc */
207	NULL,		/* Oo */
208	NULL,		/* Oc */
209	post_bk,	/* Bk */
210	NULL,		/* Ek */
211	post_eoln,	/* Bt */
212	NULL,		/* Hf */
213	post_obsolete,	/* Fr */
214	post_eoln,	/* Ud */
215	post_lb,	/* Lb */
216	post_par,	/* Lp */
217	NULL,		/* Lk */
218	post_defaults,	/* Mt */
219	NULL,		/* Brq */
220	NULL,		/* Bro */
221	NULL,		/* Brc */
222	NULL,		/* %C */
223	post_es,	/* Es */
224	post_en,	/* En */
225	NULL,		/* Dx */
226	NULL,		/* %Q */
227	post_par,	/* br */
228	post_par,	/* sp */
229	NULL,		/* %U */
230	NULL,		/* Ta */
231	NULL,		/* ll */
232};
233
234#define	RSORD_MAX 14 /* Number of `Rs' blocks. */
235
236static	const int rsord[RSORD_MAX] = {
237	MDOC__A,
238	MDOC__T,
239	MDOC__B,
240	MDOC__I,
241	MDOC__J,
242	MDOC__R,
243	MDOC__N,
244	MDOC__V,
245	MDOC__U,
246	MDOC__P,
247	MDOC__Q,
248	MDOC__C,
249	MDOC__D,
250	MDOC__O
251};
252
253static	const char * const secnames[SEC__MAX] = {
254	NULL,
255	"NAME",
256	"LIBRARY",
257	"SYNOPSIS",
258	"DESCRIPTION",
259	"CONTEXT",
260	"IMPLEMENTATION NOTES",
261	"RETURN VALUES",
262	"ENVIRONMENT",
263	"FILES",
264	"EXIT STATUS",
265	"EXAMPLES",
266	"DIAGNOSTICS",
267	"COMPATIBILITY",
268	"ERRORS",
269	"SEE ALSO",
270	"STANDARDS",
271	"HISTORY",
272	"AUTHORS",
273	"CAVEATS",
274	"BUGS",
275	"SECURITY CONSIDERATIONS",
276	NULL
277};
278
279
280void
281mdoc_node_validate(struct roff_man *mdoc)
282{
283	struct roff_node *n;
284	v_post *p;
285
286	n = mdoc->last;
287	mdoc->last = mdoc->last->child;
288	while (mdoc->last != NULL) {
289		mdoc_node_validate(mdoc);
290		if (mdoc->last == n)
291			mdoc->last = mdoc->last->child;
292		else
293			mdoc->last = mdoc->last->next;
294	}
295
296	mdoc->last = n;
297	mdoc->next = ROFF_NEXT_SIBLING;
298	switch (n->type) {
299	case ROFFT_TEXT:
300		if (n->sec != SEC_SYNOPSIS || n->parent->tok != MDOC_Fd)
301			check_text(mdoc, n->line, n->pos, n->string);
302		break;
303	case ROFFT_EQN:
304	case ROFFT_TBL:
305		break;
306	case ROFFT_ROOT:
307		post_root(mdoc);
308		break;
309	default:
310		check_args(mdoc, mdoc->last);
311
312		/*
313		 * Closing delimiters are not special at the
314		 * beginning of a block, opening delimiters
315		 * are not special at the end.
316		 */
317
318		if (n->child != NULL)
319			n->child->flags &= ~MDOC_DELIMC;
320		if (n->last != NULL)
321			n->last->flags &= ~MDOC_DELIMO;
322
323		/* Call the macro's postprocessor. */
324
325		p = mdoc_valids + n->tok;
326		if (*p)
327			(*p)(mdoc);
328		if (mdoc->last == n)
329			mdoc_state(mdoc, n);
330		break;
331	}
332}
333
334static void
335check_args(struct roff_man *mdoc, struct roff_node *n)
336{
337	int		 i;
338
339	if (NULL == n->args)
340		return;
341
342	assert(n->args->argc);
343	for (i = 0; i < (int)n->args->argc; i++)
344		check_argv(mdoc, n, &n->args->argv[i]);
345}
346
347static void
348check_argv(struct roff_man *mdoc, struct roff_node *n, struct mdoc_argv *v)
349{
350	int		 i;
351
352	for (i = 0; i < (int)v->sz; i++)
353		check_text(mdoc, v->line, v->pos, v->value[i]);
354}
355
356static void
357check_text(struct roff_man *mdoc, int ln, int pos, char *p)
358{
359	char		*cp;
360
361	if (MDOC_LITERAL & mdoc->flags)
362		return;
363
364	for (cp = p; NULL != (p = strchr(p, '\t')); p++)
365		mandoc_msg(MANDOCERR_FI_TAB, mdoc->parse,
366		    ln, pos + (int)(p - cp), NULL);
367}
368
369static void
370post_bl_norm(POST_ARGS)
371{
372	struct roff_node *n;
373	struct mdoc_argv *argv, *wa;
374	int		  i;
375	enum mdocargt	  mdoclt;
376	enum mdoc_list	  lt;
377
378	n = mdoc->last->parent;
379	n->norm->Bl.type = LIST__NONE;
380
381	/*
382	 * First figure out which kind of list to use: bind ourselves to
383	 * the first mentioned list type and warn about any remaining
384	 * ones.  If we find no list type, we default to LIST_item.
385	 */
386
387	wa = (n->args == NULL) ? NULL : n->args->argv;
388	mdoclt = MDOC_ARG_MAX;
389	for (i = 0; n->args && i < (int)n->args->argc; i++) {
390		argv = n->args->argv + i;
391		lt = LIST__NONE;
392		switch (argv->arg) {
393		/* Set list types. */
394		case MDOC_Bullet:
395			lt = LIST_bullet;
396			break;
397		case MDOC_Dash:
398			lt = LIST_dash;
399			break;
400		case MDOC_Enum:
401			lt = LIST_enum;
402			break;
403		case MDOC_Hyphen:
404			lt = LIST_hyphen;
405			break;
406		case MDOC_Item:
407			lt = LIST_item;
408			break;
409		case MDOC_Tag:
410			lt = LIST_tag;
411			break;
412		case MDOC_Diag:
413			lt = LIST_diag;
414			break;
415		case MDOC_Hang:
416			lt = LIST_hang;
417			break;
418		case MDOC_Ohang:
419			lt = LIST_ohang;
420			break;
421		case MDOC_Inset:
422			lt = LIST_inset;
423			break;
424		case MDOC_Column:
425			lt = LIST_column;
426			break;
427		/* Set list arguments. */
428		case MDOC_Compact:
429			if (n->norm->Bl.comp)
430				mandoc_msg(MANDOCERR_ARG_REP,
431				    mdoc->parse, argv->line,
432				    argv->pos, "Bl -compact");
433			n->norm->Bl.comp = 1;
434			break;
435		case MDOC_Width:
436			wa = argv;
437			if (0 == argv->sz) {
438				mandoc_msg(MANDOCERR_ARG_EMPTY,
439				    mdoc->parse, argv->line,
440				    argv->pos, "Bl -width");
441				n->norm->Bl.width = "0n";
442				break;
443			}
444			if (NULL != n->norm->Bl.width)
445				mandoc_vmsg(MANDOCERR_ARG_REP,
446				    mdoc->parse, argv->line,
447				    argv->pos, "Bl -width %s",
448				    argv->value[0]);
449			rewrite_macro2len(argv->value);
450			n->norm->Bl.width = argv->value[0];
451			break;
452		case MDOC_Offset:
453			if (0 == argv->sz) {
454				mandoc_msg(MANDOCERR_ARG_EMPTY,
455				    mdoc->parse, argv->line,
456				    argv->pos, "Bl -offset");
457				break;
458			}
459			if (NULL != n->norm->Bl.offs)
460				mandoc_vmsg(MANDOCERR_ARG_REP,
461				    mdoc->parse, argv->line,
462				    argv->pos, "Bl -offset %s",
463				    argv->value[0]);
464			rewrite_macro2len(argv->value);
465			n->norm->Bl.offs = argv->value[0];
466			break;
467		default:
468			continue;
469		}
470		if (LIST__NONE == lt)
471			continue;
472		mdoclt = argv->arg;
473
474		/* Check: multiple list types. */
475
476		if (LIST__NONE != n->norm->Bl.type) {
477			mandoc_vmsg(MANDOCERR_BL_REP,
478			    mdoc->parse, n->line, n->pos,
479			    "Bl -%s", mdoc_argnames[argv->arg]);
480			continue;
481		}
482
483		/* The list type should come first. */
484
485		if (n->norm->Bl.width ||
486		    n->norm->Bl.offs ||
487		    n->norm->Bl.comp)
488			mandoc_vmsg(MANDOCERR_BL_LATETYPE,
489			    mdoc->parse, n->line, n->pos, "Bl -%s",
490			    mdoc_argnames[n->args->argv[0].arg]);
491
492		n->norm->Bl.type = lt;
493		if (LIST_column == lt) {
494			n->norm->Bl.ncols = argv->sz;
495			n->norm->Bl.cols = (void *)argv->value;
496		}
497	}
498
499	/* Allow lists to default to LIST_item. */
500
501	if (LIST__NONE == n->norm->Bl.type) {
502		mandoc_msg(MANDOCERR_BL_NOTYPE, mdoc->parse,
503		    n->line, n->pos, "Bl");
504		n->norm->Bl.type = LIST_item;
505	}
506
507	/*
508	 * Validate the width field.  Some list types don't need width
509	 * types and should be warned about them.  Others should have it
510	 * and must also be warned.  Yet others have a default and need
511	 * no warning.
512	 */
513
514	switch (n->norm->Bl.type) {
515	case LIST_tag:
516		if (NULL == n->norm->Bl.width)
517			mandoc_msg(MANDOCERR_BL_NOWIDTH, mdoc->parse,
518			    n->line, n->pos, "Bl -tag");
519		break;
520	case LIST_column:
521	case LIST_diag:
522	case LIST_ohang:
523	case LIST_inset:
524	case LIST_item:
525		if (n->norm->Bl.width)
526			mandoc_vmsg(MANDOCERR_BL_SKIPW, mdoc->parse,
527			    wa->line, wa->pos, "Bl -%s",
528			    mdoc_argnames[mdoclt]);
529		break;
530	case LIST_bullet:
531	case LIST_dash:
532	case LIST_hyphen:
533		if (NULL == n->norm->Bl.width)
534			n->norm->Bl.width = "2n";
535		break;
536	case LIST_enum:
537		if (NULL == n->norm->Bl.width)
538			n->norm->Bl.width = "3n";
539		break;
540	default:
541		break;
542	}
543}
544
545static void
546post_bd(POST_ARGS)
547{
548	struct roff_node *n;
549	struct mdoc_argv *argv;
550	int		  i;
551	enum mdoc_disp	  dt;
552
553	n = mdoc->last;
554	for (i = 0; n->args && i < (int)n->args->argc; i++) {
555		argv = n->args->argv + i;
556		dt = DISP__NONE;
557
558		switch (argv->arg) {
559		case MDOC_Centred:
560			dt = DISP_centered;
561			break;
562		case MDOC_Ragged:
563			dt = DISP_ragged;
564			break;
565		case MDOC_Unfilled:
566			dt = DISP_unfilled;
567			break;
568		case MDOC_Filled:
569			dt = DISP_filled;
570			break;
571		case MDOC_Literal:
572			dt = DISP_literal;
573			break;
574		case MDOC_File:
575			mandoc_msg(MANDOCERR_BD_FILE, mdoc->parse,
576			    n->line, n->pos, NULL);
577			break;
578		case MDOC_Offset:
579			if (0 == argv->sz) {
580				mandoc_msg(MANDOCERR_ARG_EMPTY,
581				    mdoc->parse, argv->line,
582				    argv->pos, "Bd -offset");
583				break;
584			}
585			if (NULL != n->norm->Bd.offs)
586				mandoc_vmsg(MANDOCERR_ARG_REP,
587				    mdoc->parse, argv->line,
588				    argv->pos, "Bd -offset %s",
589				    argv->value[0]);
590			rewrite_macro2len(argv->value);
591			n->norm->Bd.offs = argv->value[0];
592			break;
593		case MDOC_Compact:
594			if (n->norm->Bd.comp)
595				mandoc_msg(MANDOCERR_ARG_REP,
596				    mdoc->parse, argv->line,
597				    argv->pos, "Bd -compact");
598			n->norm->Bd.comp = 1;
599			break;
600		default:
601			abort();
602		}
603		if (DISP__NONE == dt)
604			continue;
605
606		if (DISP__NONE == n->norm->Bd.type)
607			n->norm->Bd.type = dt;
608		else
609			mandoc_vmsg(MANDOCERR_BD_REP,
610			    mdoc->parse, n->line, n->pos,
611			    "Bd -%s", mdoc_argnames[argv->arg]);
612	}
613
614	if (DISP__NONE == n->norm->Bd.type) {
615		mandoc_msg(MANDOCERR_BD_NOTYPE, mdoc->parse,
616		    n->line, n->pos, "Bd");
617		n->norm->Bd.type = DISP_ragged;
618	}
619}
620
621static void
622post_an_norm(POST_ARGS)
623{
624	struct roff_node *n;
625	struct mdoc_argv *argv;
626	size_t	 i;
627
628	n = mdoc->last;
629	if (n->args == NULL)
630		return;
631
632	for (i = 1; i < n->args->argc; i++) {
633		argv = n->args->argv + i;
634		mandoc_vmsg(MANDOCERR_AN_REP,
635		    mdoc->parse, argv->line, argv->pos,
636		    "An -%s", mdoc_argnames[argv->arg]);
637	}
638
639	argv = n->args->argv;
640	if (argv->arg == MDOC_Split)
641		n->norm->An.auth = AUTH_split;
642	else if (argv->arg == MDOC_Nosplit)
643		n->norm->An.auth = AUTH_nosplit;
644	else
645		abort();
646}
647
648static void
649post_std(POST_ARGS)
650{
651	struct roff_node *n;
652
653	n = mdoc->last;
654	if (n->args && n->args->argc == 1)
655		if (n->args->argv[0].arg == MDOC_Std)
656			return;
657
658	mandoc_msg(MANDOCERR_ARG_STD, mdoc->parse,
659	    n->line, n->pos, mdoc_macronames[n->tok]);
660}
661
662static void
663post_obsolete(POST_ARGS)
664{
665	struct roff_node *n;
666
667	n = mdoc->last;
668	if (n->type == ROFFT_ELEM || n->type == ROFFT_BLOCK)
669		mandoc_msg(MANDOCERR_MACRO_OBS, mdoc->parse,
670		    n->line, n->pos, mdoc_macronames[n->tok]);
671}
672
673static void
674post_bf(POST_ARGS)
675{
676	struct roff_node *np, *nch;
677
678	/*
679	 * Unlike other data pointers, these are "housed" by the HEAD
680	 * element, which contains the goods.
681	 */
682
683	np = mdoc->last;
684	if (np->type != ROFFT_HEAD)
685		return;
686
687	assert(np->parent->type == ROFFT_BLOCK);
688	assert(np->parent->tok == MDOC_Bf);
689
690	/* Check the number of arguments. */
691
692	nch = np->child;
693	if (np->parent->args == NULL) {
694		if (nch == NULL) {
695			mandoc_msg(MANDOCERR_BF_NOFONT, mdoc->parse,
696			    np->line, np->pos, "Bf");
697			return;
698		}
699		nch = nch->next;
700	}
701	if (nch != NULL)
702		mandoc_vmsg(MANDOCERR_ARG_EXCESS, mdoc->parse,
703		    nch->line, nch->pos, "Bf ... %s", nch->string);
704
705	/* Extract argument into data. */
706
707	if (np->parent->args != NULL) {
708		switch (np->parent->args->argv[0].arg) {
709		case MDOC_Emphasis:
710			np->norm->Bf.font = FONT_Em;
711			break;
712		case MDOC_Literal:
713			np->norm->Bf.font = FONT_Li;
714			break;
715		case MDOC_Symbolic:
716			np->norm->Bf.font = FONT_Sy;
717			break;
718		default:
719			abort();
720		}
721		return;
722	}
723
724	/* Extract parameter into data. */
725
726	if ( ! strcmp(np->child->string, "Em"))
727		np->norm->Bf.font = FONT_Em;
728	else if ( ! strcmp(np->child->string, "Li"))
729		np->norm->Bf.font = FONT_Li;
730	else if ( ! strcmp(np->child->string, "Sy"))
731		np->norm->Bf.font = FONT_Sy;
732	else
733		mandoc_vmsg(MANDOCERR_BF_BADFONT, mdoc->parse,
734		    np->child->line, np->child->pos,
735		    "Bf %s", np->child->string);
736}
737
738static void
739post_lb(POST_ARGS)
740{
741	struct roff_node	*n;
742	const char		*stdlibname;
743	char			*libname;
744
745	n = mdoc->last->child;
746	assert(n->type == ROFFT_TEXT);
747
748	if (NULL == (stdlibname = mdoc_a2lib(n->string)))
749		mandoc_asprintf(&libname,
750		    "library \\(Lq%s\\(Rq", n->string);
751	else
752		libname = mandoc_strdup(stdlibname);
753
754	free(n->string);
755	n->string = libname;
756}
757
758static void
759post_eoln(POST_ARGS)
760{
761	const struct roff_node *n;
762
763	n = mdoc->last;
764	if (n->child != NULL)
765		mandoc_vmsg(MANDOCERR_ARG_SKIP,
766		    mdoc->parse, n->line, n->pos,
767		    "%s %s", mdoc_macronames[n->tok],
768		    n->child->string);
769}
770
771static void
772post_fname(POST_ARGS)
773{
774	const struct roff_node	*n;
775	const char		*cp;
776	size_t			 pos;
777
778	n = mdoc->last->child;
779	pos = strcspn(n->string, "()");
780	cp = n->string + pos;
781	if ( ! (cp[0] == '\0' || (cp[0] == '(' && cp[1] == '*')))
782		mandoc_msg(MANDOCERR_FN_PAREN, mdoc->parse,
783		    n->line, n->pos + pos, n->string);
784}
785
786static void
787post_fn(POST_ARGS)
788{
789
790	post_fname(mdoc);
791	post_fa(mdoc);
792}
793
794static void
795post_fo(POST_ARGS)
796{
797	const struct roff_node	*n;
798
799	n = mdoc->last;
800
801	if (n->type != ROFFT_HEAD)
802		return;
803
804	if (n->child == NULL) {
805		mandoc_msg(MANDOCERR_FO_NOHEAD, mdoc->parse,
806		    n->line, n->pos, "Fo");
807		return;
808	}
809	if (n->child != n->last) {
810		mandoc_vmsg(MANDOCERR_ARG_EXCESS, mdoc->parse,
811		    n->child->next->line, n->child->next->pos,
812		    "Fo ... %s", n->child->next->string);
813		while (n->child != n->last)
814			roff_node_delete(mdoc, n->last);
815	}
816
817	post_fname(mdoc);
818}
819
820static void
821post_fa(POST_ARGS)
822{
823	const struct roff_node *n;
824	const char *cp;
825
826	for (n = mdoc->last->child; n != NULL; n = n->next) {
827		for (cp = n->string; *cp != '\0'; cp++) {
828			/* Ignore callbacks and alterations. */
829			if (*cp == '(' || *cp == '{')
830				break;
831			if (*cp != ',')
832				continue;
833			mandoc_msg(MANDOCERR_FA_COMMA, mdoc->parse,
834			    n->line, n->pos + (cp - n->string),
835			    n->string);
836			break;
837		}
838	}
839}
840
841static void
842post_nm(POST_ARGS)
843{
844	struct roff_node	*n;
845
846	n = mdoc->last;
847
848	if (n->last != NULL &&
849	    (n->last->tok == MDOC_Pp ||
850	     n->last->tok == MDOC_Lp))
851		mdoc_node_relink(mdoc, n->last);
852
853	if (mdoc->meta.name != NULL)
854		return;
855
856	deroff(&mdoc->meta.name, n);
857
858	if (mdoc->meta.name == NULL)
859		mandoc_msg(MANDOCERR_NM_NONAME, mdoc->parse,
860		    n->line, n->pos, "Nm");
861}
862
863static void
864post_nd(POST_ARGS)
865{
866	struct roff_node	*n;
867
868	n = mdoc->last;
869
870	if (n->type != ROFFT_BODY)
871		return;
872
873	if (n->child == NULL)
874		mandoc_msg(MANDOCERR_ND_EMPTY, mdoc->parse,
875		    n->line, n->pos, "Nd");
876
877	post_hyph(mdoc);
878}
879
880static void
881post_display(POST_ARGS)
882{
883	struct roff_node *n, *np;
884
885	n = mdoc->last;
886	switch (n->type) {
887	case ROFFT_BODY:
888		if (n->end != ENDBODY_NOT)
889			break;
890		if (n->child == NULL)
891			mandoc_msg(MANDOCERR_BLK_EMPTY, mdoc->parse,
892			    n->line, n->pos, mdoc_macronames[n->tok]);
893		else if (n->tok == MDOC_D1)
894			post_hyph(mdoc);
895		break;
896	case ROFFT_BLOCK:
897		if (n->tok == MDOC_Bd) {
898			if (n->args == NULL) {
899				mandoc_msg(MANDOCERR_BD_NOARG,
900				    mdoc->parse, n->line, n->pos, "Bd");
901				mdoc->next = ROFF_NEXT_SIBLING;
902				while (n->body->child != NULL)
903					mdoc_node_relink(mdoc,
904					    n->body->child);
905				roff_node_delete(mdoc, n);
906				break;
907			}
908			post_bd(mdoc);
909			post_prevpar(mdoc);
910		}
911		for (np = n->parent; np != NULL; np = np->parent) {
912			if (np->type == ROFFT_BLOCK && np->tok == MDOC_Bd) {
913				mandoc_vmsg(MANDOCERR_BD_NEST,
914				    mdoc->parse, n->line, n->pos,
915				    "%s in Bd", mdoc_macronames[n->tok]);
916				break;
917			}
918		}
919		break;
920	default:
921		break;
922	}
923}
924
925static void
926post_defaults(POST_ARGS)
927{
928	struct roff_node *nn;
929
930	/*
931	 * The `Ar' defaults to "file ..." if no value is provided as an
932	 * argument; the `Mt' and `Pa' macros use "~"; the `Li' just
933	 * gets an empty string.
934	 */
935
936	if (mdoc->last->child != NULL)
937		return;
938
939	nn = mdoc->last;
940
941	switch (nn->tok) {
942	case MDOC_Ar:
943		mdoc->next = ROFF_NEXT_CHILD;
944		roff_word_alloc(mdoc, nn->line, nn->pos, "file");
945		roff_word_alloc(mdoc, nn->line, nn->pos, "...");
946		break;
947	case MDOC_Pa:
948	case MDOC_Mt:
949		mdoc->next = ROFF_NEXT_CHILD;
950		roff_word_alloc(mdoc, nn->line, nn->pos, "~");
951		break;
952	default:
953		abort();
954	}
955	mdoc->last = nn;
956}
957
958static void
959post_at(POST_ARGS)
960{
961	struct roff_node	*n;
962	const char		*std_att;
963	char			*att;
964
965	n = mdoc->last;
966	if (n->child == NULL) {
967		mdoc->next = ROFF_NEXT_CHILD;
968		roff_word_alloc(mdoc, n->line, n->pos, "AT&T UNIX");
969		mdoc->last = n;
970		return;
971	}
972
973	/*
974	 * If we have a child, look it up in the standard keys.  If a
975	 * key exist, use that instead of the child; if it doesn't,
976	 * prefix "AT&T UNIX " to the existing data.
977	 */
978
979	n = n->child;
980	assert(n->type == ROFFT_TEXT);
981	if ((std_att = mdoc_a2att(n->string)) == NULL) {
982		mandoc_vmsg(MANDOCERR_AT_BAD, mdoc->parse,
983		    n->line, n->pos, "At %s", n->string);
984		mandoc_asprintf(&att, "AT&T UNIX %s", n->string);
985	} else
986		att = mandoc_strdup(std_att);
987
988	free(n->string);
989	n->string = att;
990}
991
992static void
993post_an(POST_ARGS)
994{
995	struct roff_node *np, *nch;
996
997	post_an_norm(mdoc);
998
999	np = mdoc->last;
1000	nch = np->child;
1001	if (np->norm->An.auth == AUTH__NONE) {
1002		if (nch == NULL)
1003			mandoc_msg(MANDOCERR_MACRO_EMPTY, mdoc->parse,
1004			    np->line, np->pos, "An");
1005	} else if (nch != NULL)
1006		mandoc_vmsg(MANDOCERR_ARG_EXCESS, mdoc->parse,
1007		    nch->line, nch->pos, "An ... %s", nch->string);
1008}
1009
1010static void
1011post_en(POST_ARGS)
1012{
1013
1014	post_obsolete(mdoc);
1015	if (mdoc->last->type == ROFFT_BLOCK)
1016		mdoc->last->norm->Es = mdoc->last_es;
1017}
1018
1019static void
1020post_es(POST_ARGS)
1021{
1022
1023	post_obsolete(mdoc);
1024	mdoc->last_es = mdoc->last;
1025}
1026
1027static void
1028post_it(POST_ARGS)
1029{
1030	struct roff_node *nbl, *nit, *nch;
1031	int		  i, cols;
1032	enum mdoc_list	  lt;
1033
1034	post_prevpar(mdoc);
1035
1036	nit = mdoc->last;
1037	if (nit->type != ROFFT_BLOCK)
1038		return;
1039
1040	nbl = nit->parent->parent;
1041	lt = nbl->norm->Bl.type;
1042
1043	switch (lt) {
1044	case LIST_tag:
1045	case LIST_hang:
1046	case LIST_ohang:
1047	case LIST_inset:
1048	case LIST_diag:
1049		if (nit->head->child == NULL)
1050			mandoc_vmsg(MANDOCERR_IT_NOHEAD,
1051			    mdoc->parse, nit->line, nit->pos,
1052			    "Bl -%s It",
1053			    mdoc_argnames[nbl->args->argv[0].arg]);
1054		break;
1055	case LIST_bullet:
1056	case LIST_dash:
1057	case LIST_enum:
1058	case LIST_hyphen:
1059		if (nit->body == NULL || nit->body->child == NULL)
1060			mandoc_vmsg(MANDOCERR_IT_NOBODY,
1061			    mdoc->parse, nit->line, nit->pos,
1062			    "Bl -%s It",
1063			    mdoc_argnames[nbl->args->argv[0].arg]);
1064		/* FALLTHROUGH */
1065	case LIST_item:
1066		if (nit->head->child != NULL)
1067			mandoc_vmsg(MANDOCERR_ARG_SKIP,
1068			    mdoc->parse, nit->line, nit->pos,
1069			    "It %s", nit->head->child->string);
1070		break;
1071	case LIST_column:
1072		cols = (int)nbl->norm->Bl.ncols;
1073
1074		assert(nit->head->child == NULL);
1075
1076		i = 0;
1077		for (nch = nit->child; nch != NULL; nch = nch->next)
1078			if (nch->type == ROFFT_BODY)
1079				i++;
1080
1081		if (i < cols || i > cols + 1)
1082			mandoc_vmsg(MANDOCERR_BL_COL,
1083			    mdoc->parse, nit->line, nit->pos,
1084			    "%d columns, %d cells", cols, i);
1085		break;
1086	default:
1087		abort();
1088	}
1089}
1090
1091static void
1092post_bl_block(POST_ARGS)
1093{
1094	struct roff_node *n, *ni, *nc;
1095
1096	post_prevpar(mdoc);
1097
1098	/*
1099	 * These are fairly complicated, so we've broken them into two
1100	 * functions.  post_bl_block_tag() is called when a -tag is
1101	 * specified, but no -width (it must be guessed).  The second
1102	 * when a -width is specified (macro indicators must be
1103	 * rewritten into real lengths).
1104	 */
1105
1106	n = mdoc->last;
1107
1108	if (n->norm->Bl.type == LIST_tag &&
1109	    n->norm->Bl.width == NULL) {
1110		post_bl_block_tag(mdoc);
1111		assert(n->norm->Bl.width != NULL);
1112	}
1113
1114	for (ni = n->body->child; ni != NULL; ni = ni->next) {
1115		if (ni->body == NULL)
1116			continue;
1117		nc = ni->body->last;
1118		while (nc != NULL) {
1119			switch (nc->tok) {
1120			case MDOC_Pp:
1121			case MDOC_Lp:
1122			case MDOC_br:
1123				break;
1124			default:
1125				nc = NULL;
1126				continue;
1127			}
1128			if (ni->next == NULL) {
1129				mandoc_msg(MANDOCERR_PAR_MOVE,
1130				    mdoc->parse, nc->line, nc->pos,
1131				    mdoc_macronames[nc->tok]);
1132				mdoc_node_relink(mdoc, nc);
1133			} else if (n->norm->Bl.comp == 0 &&
1134			    n->norm->Bl.type != LIST_column) {
1135				mandoc_vmsg(MANDOCERR_PAR_SKIP,
1136				    mdoc->parse, nc->line, nc->pos,
1137				    "%s before It",
1138				    mdoc_macronames[nc->tok]);
1139				roff_node_delete(mdoc, nc);
1140			} else
1141				break;
1142			nc = ni->body->last;
1143		}
1144	}
1145}
1146
1147/*
1148 * If the argument of -offset or -width is a macro,
1149 * replace it with the associated default width.
1150 */
1151void
1152rewrite_macro2len(char **arg)
1153{
1154	size_t		  width;
1155	int		  tok;
1156
1157	if (*arg == NULL)
1158		return;
1159	else if ( ! strcmp(*arg, "Ds"))
1160		width = 6;
1161	else if ((tok = mdoc_hash_find(*arg)) == TOKEN_NONE)
1162		return;
1163	else
1164		width = macro2len(tok);
1165
1166	free(*arg);
1167	mandoc_asprintf(arg, "%zun", width);
1168}
1169
1170static void
1171post_bl_block_tag(POST_ARGS)
1172{
1173	struct roff_node *n, *nn;
1174	size_t		  sz, ssz;
1175	int		  i;
1176	char		  buf[24];
1177
1178	/*
1179	 * Calculate the -width for a `Bl -tag' list if it hasn't been
1180	 * provided.  Uses the first head macro.  NOTE AGAIN: this is
1181	 * ONLY if the -width argument has NOT been provided.  See
1182	 * rewrite_macro2len() for converting the -width string.
1183	 */
1184
1185	sz = 10;
1186	n = mdoc->last;
1187
1188	for (nn = n->body->child; nn != NULL; nn = nn->next) {
1189		if (nn->tok != MDOC_It)
1190			continue;
1191
1192		assert(nn->type == ROFFT_BLOCK);
1193		nn = nn->head->child;
1194
1195		if (nn == NULL)
1196			break;
1197
1198		if (nn->type == ROFFT_TEXT) {
1199			sz = strlen(nn->string) + 1;
1200			break;
1201		}
1202
1203		if (0 != (ssz = macro2len(nn->tok)))
1204			sz = ssz;
1205
1206		break;
1207	}
1208
1209	/* Defaults to ten ens. */
1210
1211	(void)snprintf(buf, sizeof(buf), "%un", (unsigned int)sz);
1212
1213	/*
1214	 * We have to dynamically add this to the macro's argument list.
1215	 * We're guaranteed that a MDOC_Width doesn't already exist.
1216	 */
1217
1218	assert(n->args != NULL);
1219	i = (int)(n->args->argc)++;
1220
1221	n->args->argv = mandoc_reallocarray(n->args->argv,
1222	    n->args->argc, sizeof(struct mdoc_argv));
1223
1224	n->args->argv[i].arg = MDOC_Width;
1225	n->args->argv[i].line = n->line;
1226	n->args->argv[i].pos = n->pos;
1227	n->args->argv[i].sz = 1;
1228	n->args->argv[i].value = mandoc_malloc(sizeof(char *));
1229	n->args->argv[i].value[0] = mandoc_strdup(buf);
1230
1231	/* Set our width! */
1232	n->norm->Bl.width = n->args->argv[i].value[0];
1233}
1234
1235static void
1236post_bl_head(POST_ARGS)
1237{
1238	struct roff_node *nbl, *nh, *nch, *nnext;
1239	struct mdoc_argv *argv;
1240	int		  i, j;
1241
1242	post_bl_norm(mdoc);
1243
1244	nh = mdoc->last;
1245	if (nh->norm->Bl.type != LIST_column) {
1246		if ((nch = nh->child) == NULL)
1247			return;
1248		mandoc_vmsg(MANDOCERR_ARG_EXCESS, mdoc->parse,
1249		    nch->line, nch->pos, "Bl ... %s", nch->string);
1250		while (nch != NULL) {
1251			roff_node_delete(mdoc, nch);
1252			nch = nh->child;
1253		}
1254		return;
1255	}
1256
1257	/*
1258	 * Append old-style lists, where the column width specifiers
1259	 * trail as macro parameters, to the new-style ("normal-form")
1260	 * lists where they're argument values following -column.
1261	 */
1262
1263	if (nh->child == NULL)
1264		return;
1265
1266	nbl = nh->parent;
1267	for (j = 0; j < (int)nbl->args->argc; j++)
1268		if (nbl->args->argv[j].arg == MDOC_Column)
1269			break;
1270
1271	assert(j < (int)nbl->args->argc);
1272
1273	/*
1274	 * Accommodate for new-style groff column syntax.  Shuffle the
1275	 * child nodes, all of which must be TEXT, as arguments for the
1276	 * column field.  Then, delete the head children.
1277	 */
1278
1279	argv = nbl->args->argv + j;
1280	i = argv->sz;
1281	for (nch = nh->child; nch != NULL; nch = nch->next)
1282		argv->sz++;
1283	argv->value = mandoc_reallocarray(argv->value,
1284	    argv->sz, sizeof(char *));
1285
1286	nh->norm->Bl.ncols = argv->sz;
1287	nh->norm->Bl.cols = (void *)argv->value;
1288
1289	for (nch = nh->child; nch != NULL; nch = nnext) {
1290		argv->value[i++] = nch->string;
1291		nch->string = NULL;
1292		nnext = nch->next;
1293		roff_node_delete(NULL, nch);
1294	}
1295	nh->child = NULL;
1296}
1297
1298static void
1299post_bl(POST_ARGS)
1300{
1301	struct roff_node	*nparent, *nprev; /* of the Bl block */
1302	struct roff_node	*nblock, *nbody;  /* of the Bl */
1303	struct roff_node	*nchild, *nnext;  /* of the Bl body */
1304
1305	nbody = mdoc->last;
1306	switch (nbody->type) {
1307	case ROFFT_BLOCK:
1308		post_bl_block(mdoc);
1309		return;
1310	case ROFFT_HEAD:
1311		post_bl_head(mdoc);
1312		return;
1313	case ROFFT_BODY:
1314		break;
1315	default:
1316		return;
1317	}
1318	if (nbody->end != ENDBODY_NOT)
1319		return;
1320
1321	nchild = nbody->child;
1322	if (nchild == NULL) {
1323		mandoc_msg(MANDOCERR_BLK_EMPTY, mdoc->parse,
1324		    nbody->line, nbody->pos, "Bl");
1325		return;
1326	}
1327	while (nchild != NULL) {
1328		if (nchild->tok == MDOC_It ||
1329		    (nchild->tok == MDOC_Sm &&
1330		     nchild->next != NULL &&
1331		     nchild->next->tok == MDOC_It)) {
1332			nchild = nchild->next;
1333			continue;
1334		}
1335
1336		mandoc_msg(MANDOCERR_BL_MOVE, mdoc->parse,
1337		    nchild->line, nchild->pos,
1338		    mdoc_macronames[nchild->tok]);
1339
1340		/*
1341		 * Move the node out of the Bl block.
1342		 * First, collect all required node pointers.
1343		 */
1344
1345		nblock  = nbody->parent;
1346		nprev   = nblock->prev;
1347		nparent = nblock->parent;
1348		nnext   = nchild->next;
1349
1350		/*
1351		 * Unlink this child.
1352		 */
1353
1354		assert(nchild->prev == NULL);
1355		nbody->child = nnext;
1356		if (nnext == NULL)
1357			nbody->last  = NULL;
1358		else
1359			nnext->prev = NULL;
1360
1361		/*
1362		 * Relink this child.
1363		 */
1364
1365		nchild->parent = nparent;
1366		nchild->prev   = nprev;
1367		nchild->next   = nblock;
1368
1369		nblock->prev = nchild;
1370		if (nprev == NULL)
1371			nparent->child = nchild;
1372		else
1373			nprev->next = nchild;
1374
1375		nchild = nnext;
1376	}
1377}
1378
1379static void
1380post_bk(POST_ARGS)
1381{
1382	struct roff_node	*n;
1383
1384	n = mdoc->last;
1385
1386	if (n->type == ROFFT_BLOCK && n->body->child == NULL) {
1387		mandoc_msg(MANDOCERR_BLK_EMPTY,
1388		    mdoc->parse, n->line, n->pos, "Bk");
1389		roff_node_delete(mdoc, n);
1390	}
1391}
1392
1393static void
1394post_sm(POST_ARGS)
1395{
1396	struct roff_node	*nch;
1397
1398	nch = mdoc->last->child;
1399
1400	if (nch == NULL) {
1401		mdoc->flags ^= MDOC_SMOFF;
1402		return;
1403	}
1404
1405	assert(nch->type == ROFFT_TEXT);
1406
1407	if ( ! strcmp(nch->string, "on")) {
1408		mdoc->flags &= ~MDOC_SMOFF;
1409		return;
1410	}
1411	if ( ! strcmp(nch->string, "off")) {
1412		mdoc->flags |= MDOC_SMOFF;
1413		return;
1414	}
1415
1416	mandoc_vmsg(MANDOCERR_SM_BAD,
1417	    mdoc->parse, nch->line, nch->pos,
1418	    "%s %s", mdoc_macronames[mdoc->last->tok], nch->string);
1419	mdoc_node_relink(mdoc, nch);
1420	return;
1421}
1422
1423static void
1424post_root(POST_ARGS)
1425{
1426	struct roff_node *n;
1427
1428	/* Add missing prologue data. */
1429
1430	if (mdoc->meta.date == NULL)
1431		mdoc->meta.date = mdoc->quick ?
1432		    mandoc_strdup("") :
1433		    mandoc_normdate(mdoc->parse, NULL, 0, 0);
1434
1435	if (mdoc->meta.title == NULL) {
1436		mandoc_msg(MANDOCERR_DT_NOTITLE,
1437		    mdoc->parse, 0, 0, "EOF");
1438		mdoc->meta.title = mandoc_strdup("UNTITLED");
1439	}
1440
1441	if (mdoc->meta.vol == NULL)
1442		mdoc->meta.vol = mandoc_strdup("LOCAL");
1443
1444	if (mdoc->meta.os == NULL) {
1445		mandoc_msg(MANDOCERR_OS_MISSING,
1446		    mdoc->parse, 0, 0, NULL);
1447		mdoc->meta.os = mandoc_strdup("");
1448	}
1449
1450	/* Check that we begin with a proper `Sh'. */
1451
1452	n = mdoc->first->child;
1453	while (n != NULL && n->tok != TOKEN_NONE &&
1454	    mdoc_macros[n->tok].flags & MDOC_PROLOGUE)
1455		n = n->next;
1456
1457	if (n == NULL)
1458		mandoc_msg(MANDOCERR_DOC_EMPTY, mdoc->parse, 0, 0, NULL);
1459	else if (n->tok != MDOC_Sh)
1460		mandoc_msg(MANDOCERR_SEC_BEFORE, mdoc->parse,
1461		    n->line, n->pos, mdoc_macronames[n->tok]);
1462}
1463
1464static void
1465post_st(POST_ARGS)
1466{
1467	struct roff_node	 *n, *nch;
1468	const char		 *p;
1469
1470	n = mdoc->last;
1471	nch = n->child;
1472
1473	assert(nch->type == ROFFT_TEXT);
1474
1475	if ((p = mdoc_a2st(nch->string)) == NULL) {
1476		mandoc_vmsg(MANDOCERR_ST_BAD, mdoc->parse,
1477		    nch->line, nch->pos, "St %s", nch->string);
1478		roff_node_delete(mdoc, n);
1479	} else {
1480		free(nch->string);
1481		nch->string = mandoc_strdup(p);
1482	}
1483}
1484
1485static void
1486post_rs(POST_ARGS)
1487{
1488	struct roff_node *np, *nch, *next, *prev;
1489	int		  i, j;
1490
1491	np = mdoc->last;
1492
1493	if (np->type != ROFFT_BODY)
1494		return;
1495
1496	if (np->child == NULL) {
1497		mandoc_msg(MANDOCERR_RS_EMPTY, mdoc->parse,
1498		    np->line, np->pos, "Rs");
1499		return;
1500	}
1501
1502	/*
1503	 * The full `Rs' block needs special handling to order the
1504	 * sub-elements according to `rsord'.  Pick through each element
1505	 * and correctly order it.  This is an insertion sort.
1506	 */
1507
1508	next = NULL;
1509	for (nch = np->child->next; nch != NULL; nch = next) {
1510		/* Determine order number of this child. */
1511		for (i = 0; i < RSORD_MAX; i++)
1512			if (rsord[i] == nch->tok)
1513				break;
1514
1515		if (i == RSORD_MAX) {
1516			mandoc_msg(MANDOCERR_RS_BAD,
1517			    mdoc->parse, nch->line, nch->pos,
1518			    mdoc_macronames[nch->tok]);
1519			i = -1;
1520		} else if (nch->tok == MDOC__J || nch->tok == MDOC__B)
1521			np->norm->Rs.quote_T++;
1522
1523		/*
1524		 * Remove this child from the chain.  This somewhat
1525		 * repeats roff_node_unlink(), but since we're
1526		 * just re-ordering, there's no need for the
1527		 * full unlink process.
1528		 */
1529
1530		if ((next = nch->next) != NULL)
1531			next->prev = nch->prev;
1532
1533		if ((prev = nch->prev) != NULL)
1534			prev->next = nch->next;
1535
1536		nch->prev = nch->next = NULL;
1537
1538		/*
1539		 * Scan back until we reach a node that's
1540		 * to be ordered before this child.
1541		 */
1542
1543		for ( ; prev ; prev = prev->prev) {
1544			/* Determine order of `prev'. */
1545			for (j = 0; j < RSORD_MAX; j++)
1546				if (rsord[j] == prev->tok)
1547					break;
1548			if (j == RSORD_MAX)
1549				j = -1;
1550
1551			if (j <= i)
1552				break;
1553		}
1554
1555		/*
1556		 * Set this child back into its correct place
1557		 * in front of the `prev' node.
1558		 */
1559
1560		nch->prev = prev;
1561
1562		if (prev == NULL) {
1563			np->child->prev = nch;
1564			nch->next = np->child;
1565			np->child = nch;
1566		} else {
1567			if (prev->next)
1568				prev->next->prev = nch;
1569			nch->next = prev->next;
1570			prev->next = nch;
1571		}
1572	}
1573}
1574
1575/*
1576 * For some arguments of some macros,
1577 * convert all breakable hyphens into ASCII_HYPH.
1578 */
1579static void
1580post_hyph(POST_ARGS)
1581{
1582	struct roff_node	*nch;
1583	char			*cp;
1584
1585	for (nch = mdoc->last->child; nch != NULL; nch = nch->next) {
1586		if (nch->type != ROFFT_TEXT)
1587			continue;
1588		cp = nch->string;
1589		if (*cp == '\0')
1590			continue;
1591		while (*(++cp) != '\0')
1592			if (*cp == '-' &&
1593			    isalpha((unsigned char)cp[-1]) &&
1594			    isalpha((unsigned char)cp[1]))
1595				*cp = ASCII_HYPH;
1596	}
1597}
1598
1599static void
1600post_ns(POST_ARGS)
1601{
1602
1603	if (mdoc->last->flags & MDOC_LINE)
1604		mandoc_msg(MANDOCERR_NS_SKIP, mdoc->parse,
1605		    mdoc->last->line, mdoc->last->pos, NULL);
1606}
1607
1608static void
1609post_sh(POST_ARGS)
1610{
1611
1612	post_ignpar(mdoc);
1613
1614	switch (mdoc->last->type) {
1615	case ROFFT_HEAD:
1616		post_sh_head(mdoc);
1617		break;
1618	case ROFFT_BODY:
1619		switch (mdoc->lastsec)  {
1620		case SEC_NAME:
1621			post_sh_name(mdoc);
1622			break;
1623		case SEC_SEE_ALSO:
1624			post_sh_see_also(mdoc);
1625			break;
1626		case SEC_AUTHORS:
1627			post_sh_authors(mdoc);
1628			break;
1629		default:
1630			break;
1631		}
1632		break;
1633	default:
1634		break;
1635	}
1636}
1637
1638static void
1639post_sh_name(POST_ARGS)
1640{
1641	struct roff_node *n;
1642	int hasnm, hasnd;
1643
1644	hasnm = hasnd = 0;
1645
1646	for (n = mdoc->last->child; n != NULL; n = n->next) {
1647		switch (n->tok) {
1648		case MDOC_Nm:
1649			hasnm = 1;
1650			break;
1651		case MDOC_Nd:
1652			hasnd = 1;
1653			if (n->next != NULL)
1654				mandoc_msg(MANDOCERR_NAMESEC_ND,
1655				    mdoc->parse, n->line, n->pos, NULL);
1656			break;
1657		case TOKEN_NONE:
1658			if (hasnm)
1659				break;
1660			/* FALLTHROUGH */
1661		default:
1662			mandoc_msg(MANDOCERR_NAMESEC_BAD, mdoc->parse,
1663			    n->line, n->pos, mdoc_macronames[n->tok]);
1664			break;
1665		}
1666	}
1667
1668	if ( ! hasnm)
1669		mandoc_msg(MANDOCERR_NAMESEC_NONM, mdoc->parse,
1670		    mdoc->last->line, mdoc->last->pos, NULL);
1671	if ( ! hasnd)
1672		mandoc_msg(MANDOCERR_NAMESEC_NOND, mdoc->parse,
1673		    mdoc->last->line, mdoc->last->pos, NULL);
1674}
1675
1676static void
1677post_sh_see_also(POST_ARGS)
1678{
1679	const struct roff_node	*n;
1680	const char		*name, *sec;
1681	const char		*lastname, *lastsec, *lastpunct;
1682	int			 cmp;
1683
1684	n = mdoc->last->child;
1685	lastname = lastsec = lastpunct = NULL;
1686	while (n != NULL) {
1687		if (n->tok != MDOC_Xr ||
1688		    n->child == NULL ||
1689		    n->child->next == NULL)
1690			break;
1691
1692		/* Process one .Xr node. */
1693
1694		name = n->child->string;
1695		sec = n->child->next->string;
1696		if (lastsec != NULL) {
1697			if (lastpunct[0] != ',' || lastpunct[1] != '\0')
1698				mandoc_vmsg(MANDOCERR_XR_PUNCT,
1699				    mdoc->parse, n->line, n->pos,
1700				    "%s before %s(%s)", lastpunct,
1701				    name, sec);
1702			cmp = strcmp(lastsec, sec);
1703			if (cmp > 0)
1704				mandoc_vmsg(MANDOCERR_XR_ORDER,
1705				    mdoc->parse, n->line, n->pos,
1706				    "%s(%s) after %s(%s)", name,
1707				    sec, lastname, lastsec);
1708			else if (cmp == 0 &&
1709			    strcasecmp(lastname, name) > 0)
1710				mandoc_vmsg(MANDOCERR_XR_ORDER,
1711				    mdoc->parse, n->line, n->pos,
1712				    "%s after %s", name, lastname);
1713		}
1714		lastname = name;
1715		lastsec = sec;
1716
1717		/* Process the following node. */
1718
1719		n = n->next;
1720		if (n == NULL)
1721			break;
1722		if (n->tok == MDOC_Xr) {
1723			lastpunct = "none";
1724			continue;
1725		}
1726		if (n->type != ROFFT_TEXT)
1727			break;
1728		for (name = n->string; *name != '\0'; name++)
1729			if (isalpha((const unsigned char)*name))
1730				return;
1731		lastpunct = n->string;
1732		if (n->next == NULL)
1733			mandoc_vmsg(MANDOCERR_XR_PUNCT, mdoc->parse,
1734			    n->line, n->pos, "%s after %s(%s)",
1735			    lastpunct, lastname, lastsec);
1736		n = n->next;
1737	}
1738}
1739
1740static int
1741child_an(const struct roff_node *n)
1742{
1743
1744	for (n = n->child; n != NULL; n = n->next)
1745		if ((n->tok == MDOC_An && n->child != NULL) || child_an(n))
1746			return 1;
1747	return 0;
1748}
1749
1750static void
1751post_sh_authors(POST_ARGS)
1752{
1753
1754	if ( ! child_an(mdoc->last))
1755		mandoc_msg(MANDOCERR_AN_MISSING, mdoc->parse,
1756		    mdoc->last->line, mdoc->last->pos, NULL);
1757}
1758
1759static void
1760post_sh_head(POST_ARGS)
1761{
1762	const char	*goodsec;
1763	enum roff_sec	 sec;
1764
1765	/*
1766	 * Process a new section.  Sections are either "named" or
1767	 * "custom".  Custom sections are user-defined, while named ones
1768	 * follow a conventional order and may only appear in certain
1769	 * manual sections.
1770	 */
1771
1772	sec = mdoc->last->sec;
1773
1774	/* The NAME should be first. */
1775
1776	if (SEC_NAME != sec && SEC_NONE == mdoc->lastnamed)
1777		mandoc_vmsg(MANDOCERR_NAMESEC_FIRST, mdoc->parse,
1778		    mdoc->last->line, mdoc->last->pos,
1779		    "Sh %s", secnames[sec]);
1780
1781	/* The SYNOPSIS gets special attention in other areas. */
1782
1783	if (sec == SEC_SYNOPSIS) {
1784		roff_setreg(mdoc->roff, "nS", 1, '=');
1785		mdoc->flags |= MDOC_SYNOPSIS;
1786	} else {
1787		roff_setreg(mdoc->roff, "nS", 0, '=');
1788		mdoc->flags &= ~MDOC_SYNOPSIS;
1789	}
1790
1791	/* Mark our last section. */
1792
1793	mdoc->lastsec = sec;
1794
1795	/* We don't care about custom sections after this. */
1796
1797	if (sec == SEC_CUSTOM)
1798		return;
1799
1800	/*
1801	 * Check whether our non-custom section is being repeated or is
1802	 * out of order.
1803	 */
1804
1805	if (sec == mdoc->lastnamed)
1806		mandoc_vmsg(MANDOCERR_SEC_REP, mdoc->parse,
1807		    mdoc->last->line, mdoc->last->pos,
1808		    "Sh %s", secnames[sec]);
1809
1810	if (sec < mdoc->lastnamed)
1811		mandoc_vmsg(MANDOCERR_SEC_ORDER, mdoc->parse,
1812		    mdoc->last->line, mdoc->last->pos,
1813		    "Sh %s", secnames[sec]);
1814
1815	/* Mark the last named section. */
1816
1817	mdoc->lastnamed = sec;
1818
1819	/* Check particular section/manual conventions. */
1820
1821	if (mdoc->meta.msec == NULL)
1822		return;
1823
1824	goodsec = NULL;
1825	switch (sec) {
1826	case SEC_ERRORS:
1827		if (*mdoc->meta.msec == '4')
1828			break;
1829		goodsec = "2, 3, 4, 9";
1830		/* FALLTHROUGH */
1831	case SEC_RETURN_VALUES:
1832	case SEC_LIBRARY:
1833		if (*mdoc->meta.msec == '2')
1834			break;
1835		if (*mdoc->meta.msec == '3')
1836			break;
1837		if (NULL == goodsec)
1838			goodsec = "2, 3, 9";
1839		/* FALLTHROUGH */
1840	case SEC_CONTEXT:
1841		if (*mdoc->meta.msec == '9')
1842			break;
1843		if (NULL == goodsec)
1844			goodsec = "9";
1845		mandoc_vmsg(MANDOCERR_SEC_MSEC, mdoc->parse,
1846		    mdoc->last->line, mdoc->last->pos,
1847		    "Sh %s for %s only", secnames[sec], goodsec);
1848		break;
1849	default:
1850		break;
1851	}
1852}
1853
1854static void
1855post_ignpar(POST_ARGS)
1856{
1857	struct roff_node *np;
1858
1859	switch (mdoc->last->type) {
1860	case ROFFT_HEAD:
1861		post_hyph(mdoc);
1862		return;
1863	case ROFFT_BODY:
1864		break;
1865	default:
1866		return;
1867	}
1868
1869	if ((np = mdoc->last->child) != NULL)
1870		if (np->tok == MDOC_Pp || np->tok == MDOC_Lp) {
1871			mandoc_vmsg(MANDOCERR_PAR_SKIP,
1872			    mdoc->parse, np->line, np->pos,
1873			    "%s after %s", mdoc_macronames[np->tok],
1874			    mdoc_macronames[mdoc->last->tok]);
1875			roff_node_delete(mdoc, np);
1876		}
1877
1878	if ((np = mdoc->last->last) != NULL)
1879		if (np->tok == MDOC_Pp || np->tok == MDOC_Lp) {
1880			mandoc_vmsg(MANDOCERR_PAR_SKIP, mdoc->parse,
1881			    np->line, np->pos, "%s at the end of %s",
1882			    mdoc_macronames[np->tok],
1883			    mdoc_macronames[mdoc->last->tok]);
1884			roff_node_delete(mdoc, np);
1885		}
1886}
1887
1888static void
1889post_prevpar(POST_ARGS)
1890{
1891	struct roff_node *n;
1892
1893	n = mdoc->last;
1894	if (NULL == n->prev)
1895		return;
1896	if (n->type != ROFFT_ELEM && n->type != ROFFT_BLOCK)
1897		return;
1898
1899	/*
1900	 * Don't allow prior `Lp' or `Pp' prior to a paragraph-type
1901	 * block:  `Lp', `Pp', or non-compact `Bd' or `Bl'.
1902	 */
1903
1904	if (n->prev->tok != MDOC_Pp &&
1905	    n->prev->tok != MDOC_Lp &&
1906	    n->prev->tok != MDOC_br)
1907		return;
1908	if (n->tok == MDOC_Bl && n->norm->Bl.comp)
1909		return;
1910	if (n->tok == MDOC_Bd && n->norm->Bd.comp)
1911		return;
1912	if (n->tok == MDOC_It && n->parent->norm->Bl.comp)
1913		return;
1914
1915	mandoc_vmsg(MANDOCERR_PAR_SKIP, mdoc->parse,
1916	    n->prev->line, n->prev->pos,
1917	    "%s before %s", mdoc_macronames[n->prev->tok],
1918	    mdoc_macronames[n->tok]);
1919	roff_node_delete(mdoc, n->prev);
1920}
1921
1922static void
1923post_par(POST_ARGS)
1924{
1925	struct roff_node *np;
1926
1927	np = mdoc->last;
1928	if (np->tok != MDOC_br && np->tok != MDOC_sp)
1929		post_prevpar(mdoc);
1930
1931	if (np->tok == MDOC_sp) {
1932		if (np->child != NULL && np->child->next != NULL)
1933			mandoc_vmsg(MANDOCERR_ARG_EXCESS, mdoc->parse,
1934			    np->child->next->line, np->child->next->pos,
1935			    "sp ... %s", np->child->next->string);
1936	} else if (np->child != NULL)
1937		mandoc_vmsg(MANDOCERR_ARG_SKIP,
1938		    mdoc->parse, np->line, np->pos, "%s %s",
1939		    mdoc_macronames[np->tok], np->child->string);
1940
1941	if ((np = mdoc->last->prev) == NULL) {
1942		np = mdoc->last->parent;
1943		if (np->tok != MDOC_Sh && np->tok != MDOC_Ss)
1944			return;
1945	} else if (np->tok != MDOC_Pp && np->tok != MDOC_Lp &&
1946	    (mdoc->last->tok != MDOC_br ||
1947	     (np->tok != MDOC_sp && np->tok != MDOC_br)))
1948		return;
1949
1950	mandoc_vmsg(MANDOCERR_PAR_SKIP, mdoc->parse,
1951	    mdoc->last->line, mdoc->last->pos,
1952	    "%s after %s", mdoc_macronames[mdoc->last->tok],
1953	    mdoc_macronames[np->tok]);
1954	roff_node_delete(mdoc, mdoc->last);
1955}
1956
1957static void
1958post_dd(POST_ARGS)
1959{
1960	struct roff_node *n;
1961	char		 *datestr;
1962
1963	n = mdoc->last;
1964	if (mdoc->meta.date != NULL) {
1965		mandoc_msg(MANDOCERR_PROLOG_REP, mdoc->parse,
1966		    n->line, n->pos, "Dd");
1967		free(mdoc->meta.date);
1968	} else if (mdoc->flags & MDOC_PBODY)
1969		mandoc_msg(MANDOCERR_PROLOG_LATE, mdoc->parse,
1970		    n->line, n->pos, "Dd");
1971	else if (mdoc->meta.title != NULL)
1972		mandoc_msg(MANDOCERR_PROLOG_ORDER, mdoc->parse,
1973		    n->line, n->pos, "Dd after Dt");
1974	else if (mdoc->meta.os != NULL)
1975		mandoc_msg(MANDOCERR_PROLOG_ORDER, mdoc->parse,
1976		    n->line, n->pos, "Dd after Os");
1977
1978	if (n->child == NULL || n->child->string[0] == '\0') {
1979		mdoc->meta.date = mdoc->quick ? mandoc_strdup("") :
1980		    mandoc_normdate(mdoc->parse, NULL, n->line, n->pos);
1981		goto out;
1982	}
1983
1984	datestr = NULL;
1985	deroff(&datestr, n);
1986	if (mdoc->quick)
1987		mdoc->meta.date = datestr;
1988	else {
1989		mdoc->meta.date = mandoc_normdate(mdoc->parse,
1990		    datestr, n->line, n->pos);
1991		free(datestr);
1992	}
1993out:
1994	roff_node_delete(mdoc, n);
1995}
1996
1997static void
1998post_dt(POST_ARGS)
1999{
2000	struct roff_node *nn, *n;
2001	const char	 *cp;
2002	char		 *p;
2003
2004	n = mdoc->last;
2005	if (mdoc->flags & MDOC_PBODY) {
2006		mandoc_msg(MANDOCERR_DT_LATE, mdoc->parse,
2007		    n->line, n->pos, "Dt");
2008		goto out;
2009	}
2010
2011	if (mdoc->meta.title != NULL)
2012		mandoc_msg(MANDOCERR_PROLOG_REP, mdoc->parse,
2013		    n->line, n->pos, "Dt");
2014	else if (mdoc->meta.os != NULL)
2015		mandoc_msg(MANDOCERR_PROLOG_ORDER, mdoc->parse,
2016		    n->line, n->pos, "Dt after Os");
2017
2018	free(mdoc->meta.title);
2019	free(mdoc->meta.msec);
2020	free(mdoc->meta.vol);
2021	free(mdoc->meta.arch);
2022
2023	mdoc->meta.title = NULL;
2024	mdoc->meta.msec = NULL;
2025	mdoc->meta.vol = NULL;
2026	mdoc->meta.arch = NULL;
2027
2028	/* Mandatory first argument: title. */
2029
2030	nn = n->child;
2031	if (nn == NULL || *nn->string == '\0') {
2032		mandoc_msg(MANDOCERR_DT_NOTITLE,
2033		    mdoc->parse, n->line, n->pos, "Dt");
2034		mdoc->meta.title = mandoc_strdup("UNTITLED");
2035	} else {
2036		mdoc->meta.title = mandoc_strdup(nn->string);
2037
2038		/* Check that all characters are uppercase. */
2039
2040		for (p = nn->string; *p != '\0'; p++)
2041			if (islower((unsigned char)*p)) {
2042				mandoc_vmsg(MANDOCERR_TITLE_CASE,
2043				    mdoc->parse, nn->line,
2044				    nn->pos + (p - nn->string),
2045				    "Dt %s", nn->string);
2046				break;
2047			}
2048	}
2049
2050	/* Mandatory second argument: section.�*/
2051
2052	if (nn != NULL)
2053		nn = nn->next;
2054
2055	if (nn == NULL) {
2056		mandoc_vmsg(MANDOCERR_MSEC_MISSING,
2057		    mdoc->parse, n->line, n->pos,
2058		    "Dt %s", mdoc->meta.title);
2059		mdoc->meta.vol = mandoc_strdup("LOCAL");
2060		goto out;  /* msec and arch remain NULL. */
2061	}
2062
2063	mdoc->meta.msec = mandoc_strdup(nn->string);
2064
2065	/* Infer volume title from section number. */
2066
2067	cp = mandoc_a2msec(nn->string);
2068	if (cp == NULL) {
2069		mandoc_vmsg(MANDOCERR_MSEC_BAD, mdoc->parse,
2070		    nn->line, nn->pos, "Dt ... %s", nn->string);
2071		mdoc->meta.vol = mandoc_strdup(nn->string);
2072	} else
2073		mdoc->meta.vol = mandoc_strdup(cp);
2074
2075	/* Optional third argument: architecture. */
2076
2077	if ((nn = nn->next) == NULL)
2078		goto out;
2079
2080	for (p = nn->string; *p != '\0'; p++)
2081		*p = tolower((unsigned char)*p);
2082	mdoc->meta.arch = mandoc_strdup(nn->string);
2083
2084	/* Ignore fourth and later arguments. */
2085
2086	if ((nn = nn->next) != NULL)
2087		mandoc_vmsg(MANDOCERR_ARG_EXCESS, mdoc->parse,
2088		    nn->line, nn->pos, "Dt ... %s", nn->string);
2089
2090out:
2091	roff_node_delete(mdoc, n);
2092}
2093
2094static void
2095post_bx(POST_ARGS)
2096{
2097	struct roff_node	*n;
2098
2099	/*
2100	 * Make `Bx's second argument always start with an uppercase
2101	 * letter.  Groff checks if it's an "accepted" term, but we just
2102	 * uppercase blindly.
2103	 */
2104
2105	if ((n = mdoc->last->child) != NULL && (n = n->next) != NULL)
2106		*n->string = (char)toupper((unsigned char)*n->string);
2107}
2108
2109static void
2110post_os(POST_ARGS)
2111{
2112#ifndef OSNAME
2113	struct utsname	  utsname;
2114	static char	 *defbuf;
2115#endif
2116	struct roff_node *n;
2117
2118	n = mdoc->last;
2119	if (mdoc->meta.os != NULL)
2120		mandoc_msg(MANDOCERR_PROLOG_REP, mdoc->parse,
2121		    n->line, n->pos, "Os");
2122	else if (mdoc->flags & MDOC_PBODY)
2123		mandoc_msg(MANDOCERR_PROLOG_LATE, mdoc->parse,
2124		    n->line, n->pos, "Os");
2125
2126	/*
2127	 * Set the operating system by way of the `Os' macro.
2128	 * The order of precedence is:
2129	 * 1. the argument of the `Os' macro, unless empty
2130	 * 2. the -Ios=foo command line argument, if provided
2131	 * 3. -DOSNAME="\"foo\"", if provided during compilation
2132	 * 4. "sysname release" from uname(3)
2133	 */
2134
2135	free(mdoc->meta.os);
2136	mdoc->meta.os = NULL;
2137	deroff(&mdoc->meta.os, n);
2138	if (mdoc->meta.os)
2139		goto out;
2140
2141	if (mdoc->defos) {
2142		mdoc->meta.os = mandoc_strdup(mdoc->defos);
2143		goto out;
2144	}
2145
2146#ifdef OSNAME
2147	mdoc->meta.os = mandoc_strdup(OSNAME);
2148#else /*!OSNAME */
2149	if (defbuf == NULL) {
2150		if (uname(&utsname) == -1) {
2151			mandoc_msg(MANDOCERR_OS_UNAME, mdoc->parse,
2152			    n->line, n->pos, "Os");
2153			defbuf = mandoc_strdup("UNKNOWN");
2154		} else
2155			mandoc_asprintf(&defbuf, "%s %s",
2156			    utsname.sysname, utsname.release);
2157	}
2158	mdoc->meta.os = mandoc_strdup(defbuf);
2159#endif /*!OSNAME*/
2160
2161out:
2162	roff_node_delete(mdoc, n);
2163}
2164
2165/*
2166 * If no argument is provided,
2167 * fill in the name of the current manual page.
2168 */
2169static void
2170post_ex(POST_ARGS)
2171{
2172	struct roff_node *n;
2173
2174	post_std(mdoc);
2175
2176	n = mdoc->last;
2177	if (n->child != NULL)
2178		return;
2179
2180	if (mdoc->meta.name == NULL) {
2181		mandoc_msg(MANDOCERR_EX_NONAME, mdoc->parse,
2182		    n->line, n->pos, "Ex");
2183		return;
2184	}
2185
2186	mdoc->next = ROFF_NEXT_CHILD;
2187	roff_word_alloc(mdoc, n->line, n->pos, mdoc->meta.name);
2188	mdoc->last = n;
2189}
2190
2191enum roff_sec
2192mdoc_a2sec(const char *p)
2193{
2194	int		 i;
2195
2196	for (i = 0; i < (int)SEC__MAX; i++)
2197		if (secnames[i] && 0 == strcmp(p, secnames[i]))
2198			return (enum roff_sec)i;
2199
2200	return SEC_CUSTOM;
2201}
2202
2203static size_t
2204macro2len(int macro)
2205{
2206
2207	switch (macro) {
2208	case MDOC_Ad:
2209		return 12;
2210	case MDOC_Ao:
2211		return 12;
2212	case MDOC_An:
2213		return 12;
2214	case MDOC_Aq:
2215		return 12;
2216	case MDOC_Ar:
2217		return 12;
2218	case MDOC_Bo:
2219		return 12;
2220	case MDOC_Bq:
2221		return 12;
2222	case MDOC_Cd:
2223		return 12;
2224	case MDOC_Cm:
2225		return 10;
2226	case MDOC_Do:
2227		return 10;
2228	case MDOC_Dq:
2229		return 12;
2230	case MDOC_Dv:
2231		return 12;
2232	case MDOC_Eo:
2233		return 12;
2234	case MDOC_Em:
2235		return 10;
2236	case MDOC_Er:
2237		return 17;
2238	case MDOC_Ev:
2239		return 15;
2240	case MDOC_Fa:
2241		return 12;
2242	case MDOC_Fl:
2243		return 10;
2244	case MDOC_Fo:
2245		return 16;
2246	case MDOC_Fn:
2247		return 16;
2248	case MDOC_Ic:
2249		return 10;
2250	case MDOC_Li:
2251		return 16;
2252	case MDOC_Ms:
2253		return 6;
2254	case MDOC_Nm:
2255		return 10;
2256	case MDOC_No:
2257		return 12;
2258	case MDOC_Oo:
2259		return 10;
2260	case MDOC_Op:
2261		return 14;
2262	case MDOC_Pa:
2263		return 32;
2264	case MDOC_Pf:
2265		return 12;
2266	case MDOC_Po:
2267		return 12;
2268	case MDOC_Pq:
2269		return 12;
2270	case MDOC_Ql:
2271		return 16;
2272	case MDOC_Qo:
2273		return 12;
2274	case MDOC_So:
2275		return 12;
2276	case MDOC_Sq:
2277		return 12;
2278	case MDOC_Sy:
2279		return 6;
2280	case MDOC_Sx:
2281		return 16;
2282	case MDOC_Tn:
2283		return 10;
2284	case MDOC_Va:
2285		return 12;
2286	case MDOC_Vt:
2287		return 12;
2288	case MDOC_Xr:
2289		return 10;
2290	default:
2291		break;
2292	};
2293	return 0;
2294}
2295