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