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