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