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