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