xref: /freebsd/contrib/mandoc/mdoc_validate.c (revision 10ff414c)
1 /*	$Id: mdoc_validate.c,v 1.374 2019/06/27 15:07:30 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) __attribute__((__noreturn__));
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 = mandoc_normdate(mdoc, NULL, 0, 0);
1907 
1908 	if (mdoc->meta.title == NULL) {
1909 		mandoc_msg(MANDOCERR_DT_NOTITLE, 0, 0, "EOF");
1910 		mdoc->meta.title = mandoc_strdup("UNTITLED");
1911 	}
1912 
1913 	if (mdoc->meta.vol == NULL)
1914 		mdoc->meta.vol = mandoc_strdup("LOCAL");
1915 
1916 	if (mdoc->meta.os == NULL) {
1917 		mandoc_msg(MANDOCERR_OS_MISSING, 0, 0, NULL);
1918 		mdoc->meta.os = mandoc_strdup("");
1919 	} else if (mdoc->meta.os_e &&
1920 	    (mdoc->meta.rcsids & (1 << mdoc->meta.os_e)) == 0)
1921 		mandoc_msg(MANDOCERR_RCS_MISSING, 0, 0,
1922 		    mdoc->meta.os_e == MANDOC_OS_OPENBSD ?
1923 		    "(OpenBSD)" : "(NetBSD)");
1924 
1925 	if (mdoc->meta.arch != NULL &&
1926 	    arch_valid(mdoc->meta.arch, mdoc->meta.os_e) == 0) {
1927 		n = mdoc->meta.first->child;
1928 		while (n->tok != MDOC_Dt ||
1929 		    n->child == NULL ||
1930 		    n->child->next == NULL ||
1931 		    n->child->next->next == NULL)
1932 			n = n->next;
1933 		n = n->child->next->next;
1934 		mandoc_msg(MANDOCERR_ARCH_BAD, n->line, n->pos,
1935 		    "Dt ... %s %s", mdoc->meta.arch,
1936 		    mdoc->meta.os_e == MANDOC_OS_OPENBSD ?
1937 		    "(OpenBSD)" : "(NetBSD)");
1938 	}
1939 
1940 	/* Check that we begin with a proper `Sh'. */
1941 
1942 	n = mdoc->meta.first->child;
1943 	while (n != NULL &&
1944 	    (n->type == ROFFT_COMMENT ||
1945 	     (n->tok >= MDOC_Dd &&
1946 	      mdoc_macro(n->tok)->flags & MDOC_PROLOGUE)))
1947 		n = n->next;
1948 
1949 	if (n == NULL)
1950 		mandoc_msg(MANDOCERR_DOC_EMPTY, 0, 0, NULL);
1951 	else if (n->tok != MDOC_Sh)
1952 		mandoc_msg(MANDOCERR_SEC_BEFORE, n->line, n->pos,
1953 		    "%s", roff_name[n->tok]);
1954 }
1955 
1956 static void
1957 post_rs(POST_ARGS)
1958 {
1959 	struct roff_node *np, *nch, *next, *prev;
1960 	int		  i, j;
1961 
1962 	np = mdoc->last;
1963 
1964 	if (np->type != ROFFT_BODY)
1965 		return;
1966 
1967 	if (np->child == NULL) {
1968 		mandoc_msg(MANDOCERR_RS_EMPTY, np->line, np->pos, "Rs");
1969 		return;
1970 	}
1971 
1972 	/*
1973 	 * The full `Rs' block needs special handling to order the
1974 	 * sub-elements according to `rsord'.  Pick through each element
1975 	 * and correctly order it.  This is an insertion sort.
1976 	 */
1977 
1978 	next = NULL;
1979 	for (nch = np->child->next; nch != NULL; nch = next) {
1980 		/* Determine order number of this child. */
1981 		for (i = 0; i < RSORD_MAX; i++)
1982 			if (rsord[i] == nch->tok)
1983 				break;
1984 
1985 		if (i == RSORD_MAX) {
1986 			mandoc_msg(MANDOCERR_RS_BAD, nch->line, nch->pos,
1987 			    "%s", roff_name[nch->tok]);
1988 			i = -1;
1989 		} else if (nch->tok == MDOC__J || nch->tok == MDOC__B)
1990 			np->norm->Rs.quote_T++;
1991 
1992 		/*
1993 		 * Remove this child from the chain.  This somewhat
1994 		 * repeats roff_node_unlink(), but since we're
1995 		 * just re-ordering, there's no need for the
1996 		 * full unlink process.
1997 		 */
1998 
1999 		if ((next = nch->next) != NULL)
2000 			next->prev = nch->prev;
2001 
2002 		if ((prev = nch->prev) != NULL)
2003 			prev->next = nch->next;
2004 
2005 		nch->prev = nch->next = NULL;
2006 
2007 		/*
2008 		 * Scan back until we reach a node that's
2009 		 * to be ordered before this child.
2010 		 */
2011 
2012 		for ( ; prev ; prev = prev->prev) {
2013 			/* Determine order of `prev'. */
2014 			for (j = 0; j < RSORD_MAX; j++)
2015 				if (rsord[j] == prev->tok)
2016 					break;
2017 			if (j == RSORD_MAX)
2018 				j = -1;
2019 
2020 			if (j <= i)
2021 				break;
2022 		}
2023 
2024 		/*
2025 		 * Set this child back into its correct place
2026 		 * in front of the `prev' node.
2027 		 */
2028 
2029 		nch->prev = prev;
2030 
2031 		if (prev == NULL) {
2032 			np->child->prev = nch;
2033 			nch->next = np->child;
2034 			np->child = nch;
2035 		} else {
2036 			if (prev->next)
2037 				prev->next->prev = nch;
2038 			nch->next = prev->next;
2039 			prev->next = nch;
2040 		}
2041 	}
2042 }
2043 
2044 /*
2045  * For some arguments of some macros,
2046  * convert all breakable hyphens into ASCII_HYPH.
2047  */
2048 static void
2049 post_hyph(POST_ARGS)
2050 {
2051 	struct roff_node	*nch;
2052 	char			*cp;
2053 
2054 	for (nch = mdoc->last->child; nch != NULL; nch = nch->next) {
2055 		if (nch->type != ROFFT_TEXT)
2056 			continue;
2057 		cp = nch->string;
2058 		if (*cp == '\0')
2059 			continue;
2060 		while (*(++cp) != '\0')
2061 			if (*cp == '-' &&
2062 			    isalpha((unsigned char)cp[-1]) &&
2063 			    isalpha((unsigned char)cp[1]))
2064 				*cp = ASCII_HYPH;
2065 	}
2066 }
2067 
2068 static void
2069 post_ns(POST_ARGS)
2070 {
2071 	struct roff_node	*n;
2072 
2073 	n = mdoc->last;
2074 	if (n->flags & NODE_LINE ||
2075 	    (n->next != NULL && n->next->flags & NODE_DELIMC))
2076 		mandoc_msg(MANDOCERR_NS_SKIP, n->line, n->pos, NULL);
2077 }
2078 
2079 static void
2080 post_sx(POST_ARGS)
2081 {
2082 	post_delim(mdoc);
2083 	post_hyph(mdoc);
2084 }
2085 
2086 static void
2087 post_sh(POST_ARGS)
2088 {
2089 
2090 	post_ignpar(mdoc);
2091 
2092 	switch (mdoc->last->type) {
2093 	case ROFFT_HEAD:
2094 		post_sh_head(mdoc);
2095 		break;
2096 	case ROFFT_BODY:
2097 		switch (mdoc->lastsec)  {
2098 		case SEC_NAME:
2099 			post_sh_name(mdoc);
2100 			break;
2101 		case SEC_SEE_ALSO:
2102 			post_sh_see_also(mdoc);
2103 			break;
2104 		case SEC_AUTHORS:
2105 			post_sh_authors(mdoc);
2106 			break;
2107 		default:
2108 			break;
2109 		}
2110 		break;
2111 	default:
2112 		break;
2113 	}
2114 }
2115 
2116 static void
2117 post_sh_name(POST_ARGS)
2118 {
2119 	struct roff_node *n;
2120 	int hasnm, hasnd;
2121 
2122 	hasnm = hasnd = 0;
2123 
2124 	for (n = mdoc->last->child; n != NULL; n = n->next) {
2125 		switch (n->tok) {
2126 		case MDOC_Nm:
2127 			if (hasnm && n->child != NULL)
2128 				mandoc_msg(MANDOCERR_NAMESEC_PUNCT,
2129 				    n->line, n->pos,
2130 				    "Nm %s", n->child->string);
2131 			hasnm = 1;
2132 			continue;
2133 		case MDOC_Nd:
2134 			hasnd = 1;
2135 			if (n->next != NULL)
2136 				mandoc_msg(MANDOCERR_NAMESEC_ND,
2137 				    n->line, n->pos, NULL);
2138 			break;
2139 		case TOKEN_NONE:
2140 			if (n->type == ROFFT_TEXT &&
2141 			    n->string[0] == ',' && n->string[1] == '\0' &&
2142 			    n->next != NULL && n->next->tok == MDOC_Nm) {
2143 				n = n->next;
2144 				continue;
2145 			}
2146 			/* FALLTHROUGH */
2147 		default:
2148 			mandoc_msg(MANDOCERR_NAMESEC_BAD,
2149 			    n->line, n->pos, "%s", roff_name[n->tok]);
2150 			continue;
2151 		}
2152 		break;
2153 	}
2154 
2155 	if ( ! hasnm)
2156 		mandoc_msg(MANDOCERR_NAMESEC_NONM,
2157 		    mdoc->last->line, mdoc->last->pos, NULL);
2158 	if ( ! hasnd)
2159 		mandoc_msg(MANDOCERR_NAMESEC_NOND,
2160 		    mdoc->last->line, mdoc->last->pos, NULL);
2161 }
2162 
2163 static void
2164 post_sh_see_also(POST_ARGS)
2165 {
2166 	const struct roff_node	*n;
2167 	const char		*name, *sec;
2168 	const char		*lastname, *lastsec, *lastpunct;
2169 	int			 cmp;
2170 
2171 	n = mdoc->last->child;
2172 	lastname = lastsec = lastpunct = NULL;
2173 	while (n != NULL) {
2174 		if (n->tok != MDOC_Xr ||
2175 		    n->child == NULL ||
2176 		    n->child->next == NULL)
2177 			break;
2178 
2179 		/* Process one .Xr node. */
2180 
2181 		name = n->child->string;
2182 		sec = n->child->next->string;
2183 		if (lastsec != NULL) {
2184 			if (lastpunct[0] != ',' || lastpunct[1] != '\0')
2185 				mandoc_msg(MANDOCERR_XR_PUNCT, n->line,
2186 				    n->pos, "%s before %s(%s)",
2187 				    lastpunct, name, sec);
2188 			cmp = strcmp(lastsec, sec);
2189 			if (cmp > 0)
2190 				mandoc_msg(MANDOCERR_XR_ORDER, n->line,
2191 				    n->pos, "%s(%s) after %s(%s)",
2192 				    name, sec, lastname, lastsec);
2193 			else if (cmp == 0 &&
2194 			    strcasecmp(lastname, name) > 0)
2195 				mandoc_msg(MANDOCERR_XR_ORDER, n->line,
2196 				    n->pos, "%s after %s", name, lastname);
2197 		}
2198 		lastname = name;
2199 		lastsec = sec;
2200 
2201 		/* Process the following node. */
2202 
2203 		n = n->next;
2204 		if (n == NULL)
2205 			break;
2206 		if (n->tok == MDOC_Xr) {
2207 			lastpunct = "none";
2208 			continue;
2209 		}
2210 		if (n->type != ROFFT_TEXT)
2211 			break;
2212 		for (name = n->string; *name != '\0'; name++)
2213 			if (isalpha((const unsigned char)*name))
2214 				return;
2215 		lastpunct = n->string;
2216 		if (n->next == NULL || n->next->tok == MDOC_Rs)
2217 			mandoc_msg(MANDOCERR_XR_PUNCT, n->line,
2218 			    n->pos, "%s after %s(%s)",
2219 			    lastpunct, lastname, lastsec);
2220 		n = n->next;
2221 	}
2222 }
2223 
2224 static int
2225 child_an(const struct roff_node *n)
2226 {
2227 
2228 	for (n = n->child; n != NULL; n = n->next)
2229 		if ((n->tok == MDOC_An && n->child != NULL) || child_an(n))
2230 			return 1;
2231 	return 0;
2232 }
2233 
2234 static void
2235 post_sh_authors(POST_ARGS)
2236 {
2237 
2238 	if ( ! child_an(mdoc->last))
2239 		mandoc_msg(MANDOCERR_AN_MISSING,
2240 		    mdoc->last->line, mdoc->last->pos, NULL);
2241 }
2242 
2243 /*
2244  * Return an upper bound for the string distance (allowing
2245  * transpositions).  Not a full Levenshtein implementation
2246  * because Levenshtein is quadratic in the string length
2247  * and this function is called for every standard name,
2248  * so the check for each custom name would be cubic.
2249  * The following crude heuristics is linear, resulting
2250  * in quadratic behaviour for checking one custom name,
2251  * which does not cause measurable slowdown.
2252  */
2253 static int
2254 similar(const char *s1, const char *s2)
2255 {
2256 	const int	maxdist = 3;
2257 	int		dist = 0;
2258 
2259 	while (s1[0] != '\0' && s2[0] != '\0') {
2260 		if (s1[0] == s2[0]) {
2261 			s1++;
2262 			s2++;
2263 			continue;
2264 		}
2265 		if (++dist > maxdist)
2266 			return INT_MAX;
2267 		if (s1[1] == s2[1]) {  /* replacement */
2268 			s1++;
2269 			s2++;
2270 		} else if (s1[0] == s2[1] && s1[1] == s2[0]) {
2271 			s1 += 2;	/* transposition */
2272 			s2 += 2;
2273 		} else if (s1[0] == s2[1])  /* insertion */
2274 			s2++;
2275 		else if (s1[1] == s2[0])  /* deletion */
2276 			s1++;
2277 		else
2278 			return INT_MAX;
2279 	}
2280 	dist += strlen(s1) + strlen(s2);
2281 	return dist > maxdist ? INT_MAX : dist;
2282 }
2283 
2284 static void
2285 post_sh_head(POST_ARGS)
2286 {
2287 	struct roff_node	*nch;
2288 	const char		*goodsec;
2289 	const char *const	*testsec;
2290 	int			 dist, mindist;
2291 	enum roff_sec		 sec;
2292 
2293 	/*
2294 	 * Process a new section.  Sections are either "named" or
2295 	 * "custom".  Custom sections are user-defined, while named ones
2296 	 * follow a conventional order and may only appear in certain
2297 	 * manual sections.
2298 	 */
2299 
2300 	sec = mdoc->last->sec;
2301 
2302 	/* The NAME should be first. */
2303 
2304 	if (sec != SEC_NAME && mdoc->lastnamed == SEC_NONE)
2305 		mandoc_msg(MANDOCERR_NAMESEC_FIRST,
2306 		    mdoc->last->line, mdoc->last->pos, "Sh %s",
2307 		    sec != SEC_CUSTOM ? secnames[sec] :
2308 		    (nch = mdoc->last->child) == NULL ? "" :
2309 		    nch->type == ROFFT_TEXT ? nch->string :
2310 		    roff_name[nch->tok]);
2311 
2312 	/* The SYNOPSIS gets special attention in other areas. */
2313 
2314 	if (sec == SEC_SYNOPSIS) {
2315 		roff_setreg(mdoc->roff, "nS", 1, '=');
2316 		mdoc->flags |= MDOC_SYNOPSIS;
2317 	} else {
2318 		roff_setreg(mdoc->roff, "nS", 0, '=');
2319 		mdoc->flags &= ~MDOC_SYNOPSIS;
2320 	}
2321 
2322 	/* Mark our last section. */
2323 
2324 	mdoc->lastsec = sec;
2325 
2326 	/* We don't care about custom sections after this. */
2327 
2328 	if (sec == SEC_CUSTOM) {
2329 		if ((nch = mdoc->last->child) == NULL ||
2330 		    nch->type != ROFFT_TEXT || nch->next != NULL)
2331 			return;
2332 		goodsec = NULL;
2333 		mindist = INT_MAX;
2334 		for (testsec = secnames + 1; *testsec != NULL; testsec++) {
2335 			dist = similar(nch->string, *testsec);
2336 			if (dist < mindist) {
2337 				goodsec = *testsec;
2338 				mindist = dist;
2339 			}
2340 		}
2341 		if (goodsec != NULL)
2342 			mandoc_msg(MANDOCERR_SEC_TYPO, nch->line, nch->pos,
2343 			    "Sh %s instead of %s", nch->string, goodsec);
2344 		return;
2345 	}
2346 
2347 	/*
2348 	 * Check whether our non-custom section is being repeated or is
2349 	 * out of order.
2350 	 */
2351 
2352 	if (sec == mdoc->lastnamed)
2353 		mandoc_msg(MANDOCERR_SEC_REP, mdoc->last->line,
2354 		    mdoc->last->pos, "Sh %s", secnames[sec]);
2355 
2356 	if (sec < mdoc->lastnamed)
2357 		mandoc_msg(MANDOCERR_SEC_ORDER, mdoc->last->line,
2358 		    mdoc->last->pos, "Sh %s", secnames[sec]);
2359 
2360 	/* Mark the last named section. */
2361 
2362 	mdoc->lastnamed = sec;
2363 
2364 	/* Check particular section/manual conventions. */
2365 
2366 	if (mdoc->meta.msec == NULL)
2367 		return;
2368 
2369 	goodsec = NULL;
2370 	switch (sec) {
2371 	case SEC_ERRORS:
2372 		if (*mdoc->meta.msec == '4')
2373 			break;
2374 		goodsec = "2, 3, 4, 9";
2375 		/* FALLTHROUGH */
2376 	case SEC_RETURN_VALUES:
2377 	case SEC_LIBRARY:
2378 		if (*mdoc->meta.msec == '2')
2379 			break;
2380 		if (*mdoc->meta.msec == '3')
2381 			break;
2382 		if (NULL == goodsec)
2383 			goodsec = "2, 3, 9";
2384 		/* FALLTHROUGH */
2385 	case SEC_CONTEXT:
2386 		if (*mdoc->meta.msec == '9')
2387 			break;
2388 		if (NULL == goodsec)
2389 			goodsec = "9";
2390 		mandoc_msg(MANDOCERR_SEC_MSEC,
2391 		    mdoc->last->line, mdoc->last->pos,
2392 		    "Sh %s for %s only", secnames[sec], goodsec);
2393 		break;
2394 	default:
2395 		break;
2396 	}
2397 }
2398 
2399 static void
2400 post_xr(POST_ARGS)
2401 {
2402 	struct roff_node *n, *nch;
2403 
2404 	n = mdoc->last;
2405 	nch = n->child;
2406 	if (nch->next == NULL) {
2407 		mandoc_msg(MANDOCERR_XR_NOSEC,
2408 		    n->line, n->pos, "Xr %s", nch->string);
2409 	} else {
2410 		assert(nch->next == n->last);
2411 		if(mandoc_xr_add(nch->next->string, nch->string,
2412 		    nch->line, nch->pos))
2413 			mandoc_msg(MANDOCERR_XR_SELF,
2414 			    nch->line, nch->pos, "Xr %s %s",
2415 			    nch->string, nch->next->string);
2416 	}
2417 	post_delim_nb(mdoc);
2418 }
2419 
2420 static void
2421 post_ignpar(POST_ARGS)
2422 {
2423 	struct roff_node *np;
2424 
2425 	switch (mdoc->last->type) {
2426 	case ROFFT_BLOCK:
2427 		post_prevpar(mdoc);
2428 		return;
2429 	case ROFFT_HEAD:
2430 		post_delim(mdoc);
2431 		post_hyph(mdoc);
2432 		return;
2433 	case ROFFT_BODY:
2434 		break;
2435 	default:
2436 		return;
2437 	}
2438 
2439 	if ((np = mdoc->last->child) != NULL)
2440 		if (np->tok == MDOC_Pp ||
2441 		    np->tok == ROFF_br || np->tok == ROFF_sp) {
2442 			mandoc_msg(MANDOCERR_PAR_SKIP, np->line, np->pos,
2443 			    "%s after %s", roff_name[np->tok],
2444 			    roff_name[mdoc->last->tok]);
2445 			roff_node_delete(mdoc, np);
2446 		}
2447 
2448 	if ((np = mdoc->last->last) != NULL)
2449 		if (np->tok == MDOC_Pp || np->tok == ROFF_br) {
2450 			mandoc_msg(MANDOCERR_PAR_SKIP, np->line, np->pos,
2451 			    "%s at the end of %s", roff_name[np->tok],
2452 			    roff_name[mdoc->last->tok]);
2453 			roff_node_delete(mdoc, np);
2454 		}
2455 }
2456 
2457 static void
2458 post_prevpar(POST_ARGS)
2459 {
2460 	struct roff_node *n;
2461 
2462 	n = mdoc->last;
2463 	if (NULL == n->prev)
2464 		return;
2465 	if (n->type != ROFFT_ELEM && n->type != ROFFT_BLOCK)
2466 		return;
2467 
2468 	/*
2469 	 * Don't allow `Pp' prior to a paragraph-type
2470 	 * block: `Pp' or non-compact `Bd' or `Bl'.
2471 	 */
2472 
2473 	if (n->prev->tok != MDOC_Pp && n->prev->tok != ROFF_br)
2474 		return;
2475 	if (n->tok == MDOC_Bl && n->norm->Bl.comp)
2476 		return;
2477 	if (n->tok == MDOC_Bd && n->norm->Bd.comp)
2478 		return;
2479 	if (n->tok == MDOC_It && n->parent->norm->Bl.comp)
2480 		return;
2481 
2482 	mandoc_msg(MANDOCERR_PAR_SKIP, n->prev->line, n->prev->pos,
2483 	    "%s before %s", roff_name[n->prev->tok], roff_name[n->tok]);
2484 	roff_node_delete(mdoc, n->prev);
2485 }
2486 
2487 static void
2488 post_par(POST_ARGS)
2489 {
2490 	struct roff_node *np;
2491 
2492 	post_prevpar(mdoc);
2493 
2494 	np = mdoc->last;
2495 	if (np->child != NULL)
2496 		mandoc_msg(MANDOCERR_ARG_SKIP, np->line, np->pos,
2497 		    "%s %s", roff_name[np->tok], np->child->string);
2498 }
2499 
2500 static void
2501 post_dd(POST_ARGS)
2502 {
2503 	struct roff_node *n;
2504 	char		 *datestr;
2505 
2506 	n = mdoc->last;
2507 	n->flags |= NODE_NOPRT;
2508 
2509 	if (mdoc->meta.date != NULL) {
2510 		mandoc_msg(MANDOCERR_PROLOG_REP, n->line, n->pos, "Dd");
2511 		free(mdoc->meta.date);
2512 	} else if (mdoc->flags & MDOC_PBODY)
2513 		mandoc_msg(MANDOCERR_PROLOG_LATE, n->line, n->pos, "Dd");
2514 	else if (mdoc->meta.title != NULL)
2515 		mandoc_msg(MANDOCERR_PROLOG_ORDER,
2516 		    n->line, n->pos, "Dd after Dt");
2517 	else if (mdoc->meta.os != NULL)
2518 		mandoc_msg(MANDOCERR_PROLOG_ORDER,
2519 		    n->line, n->pos, "Dd after Os");
2520 
2521 	datestr = NULL;
2522 	deroff(&datestr, n);
2523 	mdoc->meta.date = mandoc_normdate(mdoc, datestr, n->line, n->pos);
2524 	free(datestr);
2525 }
2526 
2527 static void
2528 post_dt(POST_ARGS)
2529 {
2530 	struct roff_node *nn, *n;
2531 	const char	 *cp;
2532 	char		 *p;
2533 
2534 	n = mdoc->last;
2535 	n->flags |= NODE_NOPRT;
2536 
2537 	if (mdoc->flags & MDOC_PBODY) {
2538 		mandoc_msg(MANDOCERR_DT_LATE, n->line, n->pos, "Dt");
2539 		return;
2540 	}
2541 
2542 	if (mdoc->meta.title != NULL)
2543 		mandoc_msg(MANDOCERR_PROLOG_REP, n->line, n->pos, "Dt");
2544 	else if (mdoc->meta.os != NULL)
2545 		mandoc_msg(MANDOCERR_PROLOG_ORDER,
2546 		    n->line, n->pos, "Dt after Os");
2547 
2548 	free(mdoc->meta.title);
2549 	free(mdoc->meta.msec);
2550 	free(mdoc->meta.vol);
2551 	free(mdoc->meta.arch);
2552 
2553 	mdoc->meta.title = NULL;
2554 	mdoc->meta.msec = NULL;
2555 	mdoc->meta.vol = NULL;
2556 	mdoc->meta.arch = NULL;
2557 
2558 	/* Mandatory first argument: title. */
2559 
2560 	nn = n->child;
2561 	if (nn == NULL || *nn->string == '\0') {
2562 		mandoc_msg(MANDOCERR_DT_NOTITLE, n->line, n->pos, "Dt");
2563 		mdoc->meta.title = mandoc_strdup("UNTITLED");
2564 	} else {
2565 		mdoc->meta.title = mandoc_strdup(nn->string);
2566 
2567 		/* Check that all characters are uppercase. */
2568 
2569 		for (p = nn->string; *p != '\0'; p++)
2570 			if (islower((unsigned char)*p)) {
2571 				mandoc_msg(MANDOCERR_TITLE_CASE, nn->line,
2572 				    nn->pos + (int)(p - nn->string),
2573 				    "Dt %s", nn->string);
2574 				break;
2575 			}
2576 	}
2577 
2578 	/* Mandatory second argument: section. */
2579 
2580 	if (nn != NULL)
2581 		nn = nn->next;
2582 
2583 	if (nn == NULL) {
2584 		mandoc_msg(MANDOCERR_MSEC_MISSING, n->line, n->pos,
2585 		    "Dt %s", mdoc->meta.title);
2586 		mdoc->meta.vol = mandoc_strdup("LOCAL");
2587 		return;  /* msec and arch remain NULL. */
2588 	}
2589 
2590 	mdoc->meta.msec = mandoc_strdup(nn->string);
2591 
2592 	/* Infer volume title from section number. */
2593 
2594 	cp = mandoc_a2msec(nn->string);
2595 	if (cp == NULL) {
2596 		mandoc_msg(MANDOCERR_MSEC_BAD,
2597 		    nn->line, nn->pos, "Dt ... %s", nn->string);
2598 		mdoc->meta.vol = mandoc_strdup(nn->string);
2599 	} else
2600 		mdoc->meta.vol = mandoc_strdup(cp);
2601 
2602 	/* Optional third argument: architecture. */
2603 
2604 	if ((nn = nn->next) == NULL)
2605 		return;
2606 
2607 	for (p = nn->string; *p != '\0'; p++)
2608 		*p = tolower((unsigned char)*p);
2609 	mdoc->meta.arch = mandoc_strdup(nn->string);
2610 
2611 	/* Ignore fourth and later arguments. */
2612 
2613 	if ((nn = nn->next) != NULL)
2614 		mandoc_msg(MANDOCERR_ARG_EXCESS,
2615 		    nn->line, nn->pos, "Dt ... %s", nn->string);
2616 }
2617 
2618 static void
2619 post_bx(POST_ARGS)
2620 {
2621 	struct roff_node	*n, *nch;
2622 	const char		*macro;
2623 
2624 	post_delim_nb(mdoc);
2625 
2626 	n = mdoc->last;
2627 	nch = n->child;
2628 
2629 	if (nch != NULL) {
2630 		macro = !strcmp(nch->string, "Open") ? "Ox" :
2631 		    !strcmp(nch->string, "Net") ? "Nx" :
2632 		    !strcmp(nch->string, "Free") ? "Fx" :
2633 		    !strcmp(nch->string, "DragonFly") ? "Dx" : NULL;
2634 		if (macro != NULL)
2635 			mandoc_msg(MANDOCERR_BX,
2636 			    n->line, n->pos, "%s", macro);
2637 		mdoc->last = nch;
2638 		nch = nch->next;
2639 		mdoc->next = ROFF_NEXT_SIBLING;
2640 		roff_elem_alloc(mdoc, n->line, n->pos, MDOC_Ns);
2641 		mdoc->last->flags |= NODE_NOSRC;
2642 		mdoc->next = ROFF_NEXT_SIBLING;
2643 	} else
2644 		mdoc->next = ROFF_NEXT_CHILD;
2645 	roff_word_alloc(mdoc, n->line, n->pos, "BSD");
2646 	mdoc->last->flags |= NODE_NOSRC;
2647 
2648 	if (nch == NULL) {
2649 		mdoc->last = n;
2650 		return;
2651 	}
2652 
2653 	roff_elem_alloc(mdoc, n->line, n->pos, MDOC_Ns);
2654 	mdoc->last->flags |= NODE_NOSRC;
2655 	mdoc->next = ROFF_NEXT_SIBLING;
2656 	roff_word_alloc(mdoc, n->line, n->pos, "-");
2657 	mdoc->last->flags |= NODE_NOSRC;
2658 	roff_elem_alloc(mdoc, n->line, n->pos, MDOC_Ns);
2659 	mdoc->last->flags |= NODE_NOSRC;
2660 	mdoc->last = n;
2661 
2662 	/*
2663 	 * Make `Bx's second argument always start with an uppercase
2664 	 * letter.  Groff checks if it's an "accepted" term, but we just
2665 	 * uppercase blindly.
2666 	 */
2667 
2668 	*nch->string = (char)toupper((unsigned char)*nch->string);
2669 }
2670 
2671 static void
2672 post_os(POST_ARGS)
2673 {
2674 #ifndef OSNAME
2675 	struct utsname	  utsname;
2676 	static char	 *defbuf;
2677 #endif
2678 	struct roff_node *n;
2679 
2680 	n = mdoc->last;
2681 	n->flags |= NODE_NOPRT;
2682 
2683 	if (mdoc->meta.os != NULL)
2684 		mandoc_msg(MANDOCERR_PROLOG_REP, n->line, n->pos, "Os");
2685 	else if (mdoc->flags & MDOC_PBODY)
2686 		mandoc_msg(MANDOCERR_PROLOG_LATE, n->line, n->pos, "Os");
2687 
2688 	post_delim(mdoc);
2689 
2690 	/*
2691 	 * Set the operating system by way of the `Os' macro.
2692 	 * The order of precedence is:
2693 	 * 1. the argument of the `Os' macro, unless empty
2694 	 * 2. the -Ios=foo command line argument, if provided
2695 	 * 3. -DOSNAME="\"foo\"", if provided during compilation
2696 	 * 4. "sysname release" from uname(3)
2697 	 */
2698 
2699 	free(mdoc->meta.os);
2700 	mdoc->meta.os = NULL;
2701 	deroff(&mdoc->meta.os, n);
2702 	if (mdoc->meta.os)
2703 		goto out;
2704 
2705 	if (mdoc->os_s != NULL) {
2706 		mdoc->meta.os = mandoc_strdup(mdoc->os_s);
2707 		goto out;
2708 	}
2709 
2710 #ifdef OSNAME
2711 	mdoc->meta.os = mandoc_strdup(OSNAME);
2712 #else /*!OSNAME */
2713 	if (defbuf == NULL) {
2714 		if (uname(&utsname) == -1) {
2715 			mandoc_msg(MANDOCERR_OS_UNAME, n->line, n->pos, "Os");
2716 			defbuf = mandoc_strdup("UNKNOWN");
2717 		} else
2718 			mandoc_asprintf(&defbuf, "%s %s",
2719 			    utsname.sysname, utsname.release);
2720 	}
2721 	mdoc->meta.os = mandoc_strdup(defbuf);
2722 #endif /*!OSNAME*/
2723 
2724 out:
2725 	if (mdoc->meta.os_e == MANDOC_OS_OTHER) {
2726 		if (strstr(mdoc->meta.os, "OpenBSD") != NULL)
2727 			mdoc->meta.os_e = MANDOC_OS_OPENBSD;
2728 		else if (strstr(mdoc->meta.os, "NetBSD") != NULL)
2729 			mdoc->meta.os_e = MANDOC_OS_NETBSD;
2730 	}
2731 
2732 	/*
2733 	 * This is the earliest point where we can check
2734 	 * Mdocdate conventions because we don't know
2735 	 * the operating system earlier.
2736 	 */
2737 
2738 	if (n->child != NULL)
2739 		mandoc_msg(MANDOCERR_OS_ARG, n->child->line, n->child->pos,
2740 		    "Os %s (%s)", n->child->string,
2741 		    mdoc->meta.os_e == MANDOC_OS_OPENBSD ?
2742 		    "OpenBSD" : "NetBSD");
2743 
2744 	while (n->tok != MDOC_Dd)
2745 		if ((n = n->prev) == NULL)
2746 			return;
2747 	if ((n = n->child) == NULL)
2748 		return;
2749 	if (strncmp(n->string, "$" "Mdocdate", 9)) {
2750 		if (mdoc->meta.os_e == MANDOC_OS_OPENBSD)
2751 			mandoc_msg(MANDOCERR_MDOCDATE_MISSING, n->line,
2752 			    n->pos, "Dd %s (OpenBSD)", n->string);
2753 	} else {
2754 		if (mdoc->meta.os_e == MANDOC_OS_NETBSD)
2755 			mandoc_msg(MANDOCERR_MDOCDATE, n->line,
2756 			    n->pos, "Dd %s (NetBSD)", n->string);
2757 	}
2758 }
2759 
2760 enum roff_sec
2761 mdoc_a2sec(const char *p)
2762 {
2763 	int		 i;
2764 
2765 	for (i = 0; i < (int)SEC__MAX; i++)
2766 		if (secnames[i] && 0 == strcmp(p, secnames[i]))
2767 			return (enum roff_sec)i;
2768 
2769 	return SEC_CUSTOM;
2770 }
2771 
2772 static size_t
2773 macro2len(enum roff_tok macro)
2774 {
2775 
2776 	switch (macro) {
2777 	case MDOC_Ad:
2778 		return 12;
2779 	case MDOC_Ao:
2780 		return 12;
2781 	case MDOC_An:
2782 		return 12;
2783 	case MDOC_Aq:
2784 		return 12;
2785 	case MDOC_Ar:
2786 		return 12;
2787 	case MDOC_Bo:
2788 		return 12;
2789 	case MDOC_Bq:
2790 		return 12;
2791 	case MDOC_Cd:
2792 		return 12;
2793 	case MDOC_Cm:
2794 		return 10;
2795 	case MDOC_Do:
2796 		return 10;
2797 	case MDOC_Dq:
2798 		return 12;
2799 	case MDOC_Dv:
2800 		return 12;
2801 	case MDOC_Eo:
2802 		return 12;
2803 	case MDOC_Em:
2804 		return 10;
2805 	case MDOC_Er:
2806 		return 17;
2807 	case MDOC_Ev:
2808 		return 15;
2809 	case MDOC_Fa:
2810 		return 12;
2811 	case MDOC_Fl:
2812 		return 10;
2813 	case MDOC_Fo:
2814 		return 16;
2815 	case MDOC_Fn:
2816 		return 16;
2817 	case MDOC_Ic:
2818 		return 10;
2819 	case MDOC_Li:
2820 		return 16;
2821 	case MDOC_Ms:
2822 		return 6;
2823 	case MDOC_Nm:
2824 		return 10;
2825 	case MDOC_No:
2826 		return 12;
2827 	case MDOC_Oo:
2828 		return 10;
2829 	case MDOC_Op:
2830 		return 14;
2831 	case MDOC_Pa:
2832 		return 32;
2833 	case MDOC_Pf:
2834 		return 12;
2835 	case MDOC_Po:
2836 		return 12;
2837 	case MDOC_Pq:
2838 		return 12;
2839 	case MDOC_Ql:
2840 		return 16;
2841 	case MDOC_Qo:
2842 		return 12;
2843 	case MDOC_So:
2844 		return 12;
2845 	case MDOC_Sq:
2846 		return 12;
2847 	case MDOC_Sy:
2848 		return 6;
2849 	case MDOC_Sx:
2850 		return 16;
2851 	case MDOC_Tn:
2852 		return 10;
2853 	case MDOC_Va:
2854 		return 12;
2855 	case MDOC_Vt:
2856 		return 12;
2857 	case MDOC_Xr:
2858 		return 10;
2859 	default:
2860 		break;
2861 	};
2862 	return 0;
2863 }
2864