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