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