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