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