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