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