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