xref: /openbsd-src/usr.bin/mandoc/mdoc_validate.c (revision 8e0c768258d4632c51876b4397034bc3152bf8db)
1 /*	$OpenBSD: mdoc_validate.c,v 1.290 2019/09/13 19:18:48 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 	cp = n->string;
1175 	if (*cp == '(') {
1176 		if (cp[strlen(cp + 1)] == ')')
1177 			return;
1178 		pos = 0;
1179 	} else {
1180 		pos = strcspn(cp, "()");
1181 		if (cp[pos] == '\0')
1182 			return;
1183 	}
1184 	mandoc_msg(MANDOCERR_FN_PAREN, n->line, n->pos + pos, "%s", cp);
1185 }
1186 
1187 static void
1188 post_fn(POST_ARGS)
1189 {
1190 
1191 	post_fname(mdoc);
1192 	post_fa(mdoc);
1193 }
1194 
1195 static void
1196 post_fo(POST_ARGS)
1197 {
1198 	const struct roff_node	*n;
1199 
1200 	n = mdoc->last;
1201 
1202 	if (n->type != ROFFT_HEAD)
1203 		return;
1204 
1205 	if (n->child == NULL) {
1206 		mandoc_msg(MANDOCERR_FO_NOHEAD, n->line, n->pos, "Fo");
1207 		return;
1208 	}
1209 	if (n->child != n->last) {
1210 		mandoc_msg(MANDOCERR_ARG_EXCESS,
1211 		    n->child->next->line, n->child->next->pos,
1212 		    "Fo ... %s", n->child->next->string);
1213 		while (n->child != n->last)
1214 			roff_node_delete(mdoc, n->last);
1215 	} else
1216 		post_delim(mdoc);
1217 
1218 	post_fname(mdoc);
1219 }
1220 
1221 static void
1222 post_fa(POST_ARGS)
1223 {
1224 	const struct roff_node *n;
1225 	const char *cp;
1226 
1227 	for (n = mdoc->last->child; n != NULL; n = n->next) {
1228 		for (cp = n->string; *cp != '\0'; cp++) {
1229 			/* Ignore callbacks and alterations. */
1230 			if (*cp == '(' || *cp == '{')
1231 				break;
1232 			if (*cp != ',')
1233 				continue;
1234 			mandoc_msg(MANDOCERR_FA_COMMA, n->line,
1235 			    n->pos + (int)(cp - n->string), "%s", n->string);
1236 			break;
1237 		}
1238 	}
1239 	post_delim_nb(mdoc);
1240 }
1241 
1242 static void
1243 post_nm(POST_ARGS)
1244 {
1245 	struct roff_node	*n;
1246 
1247 	n = mdoc->last;
1248 
1249 	if (n->sec == SEC_NAME && n->child != NULL &&
1250 	    n->child->type == ROFFT_TEXT && mdoc->meta.msec != NULL)
1251 		mandoc_xr_add(mdoc->meta.msec, n->child->string, -1, -1);
1252 
1253 	if (n->last != NULL && n->last->tok == MDOC_Pp)
1254 		roff_node_relink(mdoc, n->last);
1255 
1256 	if (mdoc->meta.name == NULL)
1257 		deroff(&mdoc->meta.name, n);
1258 
1259 	if (mdoc->meta.name == NULL ||
1260 	    (mdoc->lastsec == SEC_NAME && n->child == NULL))
1261 		mandoc_msg(MANDOCERR_NM_NONAME, n->line, n->pos, "Nm");
1262 
1263 	switch (n->type) {
1264 	case ROFFT_ELEM:
1265 		post_delim_nb(mdoc);
1266 		break;
1267 	case ROFFT_HEAD:
1268 		post_delim(mdoc);
1269 		break;
1270 	default:
1271 		return;
1272 	}
1273 
1274 	if ((n->child != NULL && n->child->type == ROFFT_TEXT) ||
1275 	    mdoc->meta.name == NULL)
1276 		return;
1277 
1278 	mdoc->next = ROFF_NEXT_CHILD;
1279 	roff_word_alloc(mdoc, n->line, n->pos, mdoc->meta.name);
1280 	mdoc->last->flags |= NODE_NOSRC;
1281 	mdoc->last = n;
1282 }
1283 
1284 static void
1285 post_nd(POST_ARGS)
1286 {
1287 	struct roff_node	*n;
1288 
1289 	n = mdoc->last;
1290 
1291 	if (n->type != ROFFT_BODY)
1292 		return;
1293 
1294 	if (n->sec != SEC_NAME)
1295 		mandoc_msg(MANDOCERR_ND_LATE, n->line, n->pos, "Nd");
1296 
1297 	if (n->child == NULL)
1298 		mandoc_msg(MANDOCERR_ND_EMPTY, n->line, n->pos, "Nd");
1299 	else
1300 		post_delim(mdoc);
1301 
1302 	post_hyph(mdoc);
1303 }
1304 
1305 static void
1306 post_display(POST_ARGS)
1307 {
1308 	struct roff_node *n, *np;
1309 
1310 	n = mdoc->last;
1311 	switch (n->type) {
1312 	case ROFFT_BODY:
1313 		if (n->end != ENDBODY_NOT) {
1314 			if (n->tok == MDOC_Bd &&
1315 			    n->body->parent->args == NULL)
1316 				roff_node_delete(mdoc, n);
1317 		} else if (n->child == NULL)
1318 			mandoc_msg(MANDOCERR_BLK_EMPTY, n->line, n->pos,
1319 			    "%s", roff_name[n->tok]);
1320 		else if (n->tok == MDOC_D1)
1321 			post_hyph(mdoc);
1322 		break;
1323 	case ROFFT_BLOCK:
1324 		if (n->tok == MDOC_Bd) {
1325 			if (n->args == NULL) {
1326 				mandoc_msg(MANDOCERR_BD_NOARG,
1327 				    n->line, n->pos, "Bd");
1328 				mdoc->next = ROFF_NEXT_SIBLING;
1329 				while (n->body->child != NULL)
1330 					roff_node_relink(mdoc,
1331 					    n->body->child);
1332 				roff_node_delete(mdoc, n);
1333 				break;
1334 			}
1335 			post_bd(mdoc);
1336 			post_prevpar(mdoc);
1337 		}
1338 		for (np = n->parent; np != NULL; np = np->parent) {
1339 			if (np->type == ROFFT_BLOCK && np->tok == MDOC_Bd) {
1340 				mandoc_msg(MANDOCERR_BD_NEST, n->line,
1341 				    n->pos, "%s in Bd", roff_name[n->tok]);
1342 				break;
1343 			}
1344 		}
1345 		break;
1346 	default:
1347 		break;
1348 	}
1349 }
1350 
1351 static void
1352 post_defaults(POST_ARGS)
1353 {
1354 	struct roff_node *nn;
1355 
1356 	if (mdoc->last->child != NULL) {
1357 		post_delim_nb(mdoc);
1358 		return;
1359 	}
1360 
1361 	/*
1362 	 * The `Ar' defaults to "file ..." if no value is provided as an
1363 	 * argument; the `Mt' and `Pa' macros use "~"; the `Li' just
1364 	 * gets an empty string.
1365 	 */
1366 
1367 	nn = mdoc->last;
1368 	switch (nn->tok) {
1369 	case MDOC_Ar:
1370 		mdoc->next = ROFF_NEXT_CHILD;
1371 		roff_word_alloc(mdoc, nn->line, nn->pos, "file");
1372 		mdoc->last->flags |= NODE_NOSRC;
1373 		roff_word_alloc(mdoc, nn->line, nn->pos, "...");
1374 		mdoc->last->flags |= NODE_NOSRC;
1375 		break;
1376 	case MDOC_Pa:
1377 	case MDOC_Mt:
1378 		mdoc->next = ROFF_NEXT_CHILD;
1379 		roff_word_alloc(mdoc, nn->line, nn->pos, "~");
1380 		mdoc->last->flags |= NODE_NOSRC;
1381 		break;
1382 	default:
1383 		abort();
1384 	}
1385 	mdoc->last = nn;
1386 }
1387 
1388 static void
1389 post_at(POST_ARGS)
1390 {
1391 	struct roff_node	*n, *nch;
1392 	const char		*att;
1393 
1394 	n = mdoc->last;
1395 	nch = n->child;
1396 
1397 	/*
1398 	 * If we have a child, look it up in the standard keys.  If a
1399 	 * key exist, use that instead of the child; if it doesn't,
1400 	 * prefix "AT&T UNIX " to the existing data.
1401 	 */
1402 
1403 	att = NULL;
1404 	if (nch != NULL && ((att = mdoc_a2att(nch->string)) == NULL))
1405 		mandoc_msg(MANDOCERR_AT_BAD,
1406 		    nch->line, nch->pos, "At %s", nch->string);
1407 
1408 	mdoc->next = ROFF_NEXT_CHILD;
1409 	if (att != NULL) {
1410 		roff_word_alloc(mdoc, nch->line, nch->pos, att);
1411 		nch->flags |= NODE_NOPRT;
1412 	} else
1413 		roff_word_alloc(mdoc, n->line, n->pos, "AT&T UNIX");
1414 	mdoc->last->flags |= NODE_NOSRC;
1415 	mdoc->last = n;
1416 }
1417 
1418 static void
1419 post_an(POST_ARGS)
1420 {
1421 	struct roff_node *np, *nch;
1422 
1423 	post_an_norm(mdoc);
1424 
1425 	np = mdoc->last;
1426 	nch = np->child;
1427 	if (np->norm->An.auth == AUTH__NONE) {
1428 		if (nch == NULL)
1429 			mandoc_msg(MANDOCERR_MACRO_EMPTY,
1430 			    np->line, np->pos, "An");
1431 		else
1432 			post_delim_nb(mdoc);
1433 	} else if (nch != NULL)
1434 		mandoc_msg(MANDOCERR_ARG_EXCESS,
1435 		    nch->line, nch->pos, "An ... %s", nch->string);
1436 }
1437 
1438 static void
1439 post_en(POST_ARGS)
1440 {
1441 
1442 	post_obsolete(mdoc);
1443 	if (mdoc->last->type == ROFFT_BLOCK)
1444 		mdoc->last->norm->Es = mdoc->last_es;
1445 }
1446 
1447 static void
1448 post_es(POST_ARGS)
1449 {
1450 
1451 	post_obsolete(mdoc);
1452 	mdoc->last_es = mdoc->last;
1453 }
1454 
1455 static void
1456 post_xx(POST_ARGS)
1457 {
1458 	struct roff_node	*n;
1459 	const char		*os;
1460 	char			*v;
1461 
1462 	post_delim_nb(mdoc);
1463 
1464 	n = mdoc->last;
1465 	switch (n->tok) {
1466 	case MDOC_Bsx:
1467 		os = "BSD/OS";
1468 		break;
1469 	case MDOC_Dx:
1470 		os = "DragonFly";
1471 		break;
1472 	case MDOC_Fx:
1473 		os = "FreeBSD";
1474 		break;
1475 	case MDOC_Nx:
1476 		os = "NetBSD";
1477 		if (n->child == NULL)
1478 			break;
1479 		v = n->child->string;
1480 		if ((v[0] != '0' && v[0] != '1') || v[1] != '.' ||
1481 		    v[2] < '0' || v[2] > '9' ||
1482 		    v[3] < 'a' || v[3] > 'z' || v[4] != '\0')
1483 			break;
1484 		n->child->flags |= NODE_NOPRT;
1485 		mdoc->next = ROFF_NEXT_CHILD;
1486 		roff_word_alloc(mdoc, n->child->line, n->child->pos, v);
1487 		v = mdoc->last->string;
1488 		v[3] = toupper((unsigned char)v[3]);
1489 		mdoc->last->flags |= NODE_NOSRC;
1490 		mdoc->last = n;
1491 		break;
1492 	case MDOC_Ox:
1493 		os = "OpenBSD";
1494 		break;
1495 	case MDOC_Ux:
1496 		os = "UNIX";
1497 		break;
1498 	default:
1499 		abort();
1500 	}
1501 	mdoc->next = ROFF_NEXT_CHILD;
1502 	roff_word_alloc(mdoc, n->line, n->pos, os);
1503 	mdoc->last->flags |= NODE_NOSRC;
1504 	mdoc->last = n;
1505 }
1506 
1507 static void
1508 post_it(POST_ARGS)
1509 {
1510 	struct roff_node *nbl, *nit, *nch;
1511 	int		  i, cols;
1512 	enum mdoc_list	  lt;
1513 
1514 	post_prevpar(mdoc);
1515 
1516 	nit = mdoc->last;
1517 	if (nit->type != ROFFT_BLOCK)
1518 		return;
1519 
1520 	nbl = nit->parent->parent;
1521 	lt = nbl->norm->Bl.type;
1522 
1523 	switch (lt) {
1524 	case LIST_tag:
1525 	case LIST_hang:
1526 	case LIST_ohang:
1527 	case LIST_inset:
1528 	case LIST_diag:
1529 		if (nit->head->child == NULL)
1530 			mandoc_msg(MANDOCERR_IT_NOHEAD,
1531 			    nit->line, nit->pos, "Bl -%s It",
1532 			    mdoc_argnames[nbl->args->argv[0].arg]);
1533 		break;
1534 	case LIST_bullet:
1535 	case LIST_dash:
1536 	case LIST_enum:
1537 	case LIST_hyphen:
1538 		if (nit->body == NULL || nit->body->child == NULL)
1539 			mandoc_msg(MANDOCERR_IT_NOBODY,
1540 			    nit->line, nit->pos, "Bl -%s It",
1541 			    mdoc_argnames[nbl->args->argv[0].arg]);
1542 		/* FALLTHROUGH */
1543 	case LIST_item:
1544 		if ((nch = nit->head->child) != NULL)
1545 			mandoc_msg(MANDOCERR_ARG_SKIP,
1546 			    nit->line, nit->pos, "It %s",
1547 			    nch->string == NULL ? roff_name[nch->tok] :
1548 			    nch->string);
1549 		break;
1550 	case LIST_column:
1551 		cols = (int)nbl->norm->Bl.ncols;
1552 
1553 		assert(nit->head->child == NULL);
1554 
1555 		if (nit->head->next->child == NULL &&
1556 		    nit->head->next->next == NULL) {
1557 			mandoc_msg(MANDOCERR_MACRO_EMPTY,
1558 			    nit->line, nit->pos, "It");
1559 			roff_node_delete(mdoc, nit);
1560 			break;
1561 		}
1562 
1563 		i = 0;
1564 		for (nch = nit->child; nch != NULL; nch = nch->next) {
1565 			if (nch->type != ROFFT_BODY)
1566 				continue;
1567 			if (i++ && nch->flags & NODE_LINE)
1568 				mandoc_msg(MANDOCERR_TA_LINE,
1569 				    nch->line, nch->pos, "Ta");
1570 		}
1571 		if (i < cols || i > cols + 1)
1572 			mandoc_msg(MANDOCERR_BL_COL, nit->line, nit->pos,
1573 			    "%d columns, %d cells", cols, i);
1574 		else if (nit->head->next->child != NULL &&
1575 		    nit->head->next->child->flags & NODE_LINE)
1576 			mandoc_msg(MANDOCERR_IT_NOARG,
1577 			    nit->line, nit->pos, "Bl -column It");
1578 		break;
1579 	default:
1580 		abort();
1581 	}
1582 }
1583 
1584 static void
1585 post_bl_block(POST_ARGS)
1586 {
1587 	struct roff_node *n, *ni, *nc;
1588 
1589 	post_prevpar(mdoc);
1590 
1591 	n = mdoc->last;
1592 	for (ni = n->body->child; ni != NULL; ni = ni->next) {
1593 		if (ni->body == NULL)
1594 			continue;
1595 		nc = ni->body->last;
1596 		while (nc != NULL) {
1597 			switch (nc->tok) {
1598 			case MDOC_Pp:
1599 			case ROFF_br:
1600 				break;
1601 			default:
1602 				nc = NULL;
1603 				continue;
1604 			}
1605 			if (ni->next == NULL) {
1606 				mandoc_msg(MANDOCERR_PAR_MOVE, nc->line,
1607 				    nc->pos, "%s", roff_name[nc->tok]);
1608 				roff_node_relink(mdoc, nc);
1609 			} else if (n->norm->Bl.comp == 0 &&
1610 			    n->norm->Bl.type != LIST_column) {
1611 				mandoc_msg(MANDOCERR_PAR_SKIP,
1612 				    nc->line, nc->pos,
1613 				    "%s before It", roff_name[nc->tok]);
1614 				roff_node_delete(mdoc, nc);
1615 			} else
1616 				break;
1617 			nc = ni->body->last;
1618 		}
1619 	}
1620 }
1621 
1622 /*
1623  * If the argument of -offset or -width is a macro,
1624  * replace it with the associated default width.
1625  */
1626 static void
1627 rewrite_macro2len(struct roff_man *mdoc, char **arg)
1628 {
1629 	size_t		  width;
1630 	enum roff_tok	  tok;
1631 
1632 	if (*arg == NULL)
1633 		return;
1634 	else if ( ! strcmp(*arg, "Ds"))
1635 		width = 6;
1636 	else if ((tok = roffhash_find(mdoc->mdocmac, *arg, 0)) == TOKEN_NONE)
1637 		return;
1638 	else
1639 		width = macro2len(tok);
1640 
1641 	free(*arg);
1642 	mandoc_asprintf(arg, "%zun", width);
1643 }
1644 
1645 static void
1646 post_bl_head(POST_ARGS)
1647 {
1648 	struct roff_node *nbl, *nh, *nch, *nnext;
1649 	struct mdoc_argv *argv;
1650 	int		  i, j;
1651 
1652 	post_bl_norm(mdoc);
1653 
1654 	nh = mdoc->last;
1655 	if (nh->norm->Bl.type != LIST_column) {
1656 		if ((nch = nh->child) == NULL)
1657 			return;
1658 		mandoc_msg(MANDOCERR_ARG_EXCESS,
1659 		    nch->line, nch->pos, "Bl ... %s", nch->string);
1660 		while (nch != NULL) {
1661 			roff_node_delete(mdoc, nch);
1662 			nch = nh->child;
1663 		}
1664 		return;
1665 	}
1666 
1667 	/*
1668 	 * Append old-style lists, where the column width specifiers
1669 	 * trail as macro parameters, to the new-style ("normal-form")
1670 	 * lists where they're argument values following -column.
1671 	 */
1672 
1673 	if (nh->child == NULL)
1674 		return;
1675 
1676 	nbl = nh->parent;
1677 	for (j = 0; j < (int)nbl->args->argc; j++)
1678 		if (nbl->args->argv[j].arg == MDOC_Column)
1679 			break;
1680 
1681 	assert(j < (int)nbl->args->argc);
1682 
1683 	/*
1684 	 * Accommodate for new-style groff column syntax.  Shuffle the
1685 	 * child nodes, all of which must be TEXT, as arguments for the
1686 	 * column field.  Then, delete the head children.
1687 	 */
1688 
1689 	argv = nbl->args->argv + j;
1690 	i = argv->sz;
1691 	for (nch = nh->child; nch != NULL; nch = nch->next)
1692 		argv->sz++;
1693 	argv->value = mandoc_reallocarray(argv->value,
1694 	    argv->sz, sizeof(char *));
1695 
1696 	nh->norm->Bl.ncols = argv->sz;
1697 	nh->norm->Bl.cols = (void *)argv->value;
1698 
1699 	for (nch = nh->child; nch != NULL; nch = nnext) {
1700 		argv->value[i++] = nch->string;
1701 		nch->string = NULL;
1702 		nnext = nch->next;
1703 		roff_node_delete(NULL, nch);
1704 	}
1705 	nh->child = NULL;
1706 }
1707 
1708 static void
1709 post_bl(POST_ARGS)
1710 {
1711 	struct roff_node	*nparent, *nprev; /* of the Bl block */
1712 	struct roff_node	*nblock, *nbody;  /* of the Bl */
1713 	struct roff_node	*nchild, *nnext;  /* of the Bl body */
1714 	const char		*prev_Er;
1715 	int			 order;
1716 
1717 	nbody = mdoc->last;
1718 	switch (nbody->type) {
1719 	case ROFFT_BLOCK:
1720 		post_bl_block(mdoc);
1721 		return;
1722 	case ROFFT_HEAD:
1723 		post_bl_head(mdoc);
1724 		return;
1725 	case ROFFT_BODY:
1726 		break;
1727 	default:
1728 		return;
1729 	}
1730 	if (nbody->end != ENDBODY_NOT)
1731 		return;
1732 
1733 	nchild = nbody->child;
1734 	if (nchild == NULL) {
1735 		mandoc_msg(MANDOCERR_BLK_EMPTY,
1736 		    nbody->line, nbody->pos, "Bl");
1737 		return;
1738 	}
1739 	while (nchild != NULL) {
1740 		nnext = nchild->next;
1741 		if (nchild->tok == MDOC_It ||
1742 		    (nchild->tok == MDOC_Sm &&
1743 		     nnext != NULL && nnext->tok == MDOC_It)) {
1744 			nchild = nnext;
1745 			continue;
1746 		}
1747 
1748 		/*
1749 		 * In .Bl -column, the first rows may be implicit,
1750 		 * that is, they may not start with .It macros.
1751 		 * Such rows may be followed by nodes generated on the
1752 		 * roff level, for example .TS, which cannot be moved
1753 		 * out of the list.  In that case, wrap such roff nodes
1754 		 * into an implicit row.
1755 		 */
1756 
1757 		if (nchild->prev != NULL) {
1758 			mdoc->last = nchild;
1759 			mdoc->next = ROFF_NEXT_SIBLING;
1760 			roff_block_alloc(mdoc, nchild->line,
1761 			    nchild->pos, MDOC_It);
1762 			roff_head_alloc(mdoc, nchild->line,
1763 			    nchild->pos, MDOC_It);
1764 			mdoc->next = ROFF_NEXT_SIBLING;
1765 			roff_body_alloc(mdoc, nchild->line,
1766 			    nchild->pos, MDOC_It);
1767 			while (nchild->tok != MDOC_It) {
1768 				roff_node_relink(mdoc, nchild);
1769 				if ((nchild = nnext) == NULL)
1770 					break;
1771 				nnext = nchild->next;
1772 				mdoc->next = ROFF_NEXT_SIBLING;
1773 			}
1774 			mdoc->last = nbody;
1775 			continue;
1776 		}
1777 
1778 		mandoc_msg(MANDOCERR_BL_MOVE, nchild->line, nchild->pos,
1779 		    "%s", roff_name[nchild->tok]);
1780 
1781 		/*
1782 		 * Move the node out of the Bl block.
1783 		 * First, collect all required node pointers.
1784 		 */
1785 
1786 		nblock  = nbody->parent;
1787 		nprev   = nblock->prev;
1788 		nparent = nblock->parent;
1789 
1790 		/*
1791 		 * Unlink this child.
1792 		 */
1793 
1794 		nbody->child = nnext;
1795 		if (nnext == NULL)
1796 			nbody->last  = NULL;
1797 		else
1798 			nnext->prev = NULL;
1799 
1800 		/*
1801 		 * Relink this child.
1802 		 */
1803 
1804 		nchild->parent = nparent;
1805 		nchild->prev   = nprev;
1806 		nchild->next   = nblock;
1807 
1808 		nblock->prev = nchild;
1809 		if (nprev == NULL)
1810 			nparent->child = nchild;
1811 		else
1812 			nprev->next = nchild;
1813 
1814 		nchild = nnext;
1815 	}
1816 
1817 	if (mdoc->meta.os_e != MANDOC_OS_NETBSD)
1818 		return;
1819 
1820 	prev_Er = NULL;
1821 	for (nchild = nbody->child; nchild != NULL; nchild = nchild->next) {
1822 		if (nchild->tok != MDOC_It)
1823 			continue;
1824 		if ((nnext = nchild->head->child) == NULL)
1825 			continue;
1826 		if (nnext->type == ROFFT_BLOCK)
1827 			nnext = nnext->body->child;
1828 		if (nnext == NULL || nnext->tok != MDOC_Er)
1829 			continue;
1830 		nnext = nnext->child;
1831 		if (prev_Er != NULL) {
1832 			order = strcmp(prev_Er, nnext->string);
1833 			if (order > 0)
1834 				mandoc_msg(MANDOCERR_ER_ORDER,
1835 				    nnext->line, nnext->pos,
1836 				    "Er %s %s (NetBSD)",
1837 				    prev_Er, nnext->string);
1838 			else if (order == 0)
1839 				mandoc_msg(MANDOCERR_ER_REP,
1840 				    nnext->line, nnext->pos,
1841 				    "Er %s (NetBSD)", prev_Er);
1842 		}
1843 		prev_Er = nnext->string;
1844 	}
1845 }
1846 
1847 static void
1848 post_bk(POST_ARGS)
1849 {
1850 	struct roff_node	*n;
1851 
1852 	n = mdoc->last;
1853 
1854 	if (n->type == ROFFT_BLOCK && n->body->child == NULL) {
1855 		mandoc_msg(MANDOCERR_BLK_EMPTY, n->line, n->pos, "Bk");
1856 		roff_node_delete(mdoc, n);
1857 	}
1858 }
1859 
1860 static void
1861 post_sm(POST_ARGS)
1862 {
1863 	struct roff_node	*nch;
1864 
1865 	nch = mdoc->last->child;
1866 
1867 	if (nch == NULL) {
1868 		mdoc->flags ^= MDOC_SMOFF;
1869 		return;
1870 	}
1871 
1872 	assert(nch->type == ROFFT_TEXT);
1873 
1874 	if ( ! strcmp(nch->string, "on")) {
1875 		mdoc->flags &= ~MDOC_SMOFF;
1876 		return;
1877 	}
1878 	if ( ! strcmp(nch->string, "off")) {
1879 		mdoc->flags |= MDOC_SMOFF;
1880 		return;
1881 	}
1882 
1883 	mandoc_msg(MANDOCERR_SM_BAD, nch->line, nch->pos,
1884 	    "%s %s", roff_name[mdoc->last->tok], nch->string);
1885 	roff_node_relink(mdoc, nch);
1886 	return;
1887 }
1888 
1889 static void
1890 post_root(POST_ARGS)
1891 {
1892 	struct roff_node *n;
1893 
1894 	/* Add missing prologue data. */
1895 
1896 	if (mdoc->meta.date == NULL)
1897 		mdoc->meta.date = mandoc_normdate(mdoc, NULL, 0, 0);
1898 
1899 	if (mdoc->meta.title == NULL) {
1900 		mandoc_msg(MANDOCERR_DT_NOTITLE, 0, 0, "EOF");
1901 		mdoc->meta.title = mandoc_strdup("UNTITLED");
1902 	}
1903 
1904 	if (mdoc->meta.vol == NULL)
1905 		mdoc->meta.vol = mandoc_strdup("LOCAL");
1906 
1907 	if (mdoc->meta.os == NULL) {
1908 		mandoc_msg(MANDOCERR_OS_MISSING, 0, 0, NULL);
1909 		mdoc->meta.os = mandoc_strdup("");
1910 	} else if (mdoc->meta.os_e &&
1911 	    (mdoc->meta.rcsids & (1 << mdoc->meta.os_e)) == 0)
1912 		mandoc_msg(MANDOCERR_RCS_MISSING, 0, 0,
1913 		    mdoc->meta.os_e == MANDOC_OS_OPENBSD ?
1914 		    "(OpenBSD)" : "(NetBSD)");
1915 
1916 	if (mdoc->meta.arch != NULL &&
1917 	    arch_valid(mdoc->meta.arch, mdoc->meta.os_e) == 0) {
1918 		n = mdoc->meta.first->child;
1919 		while (n->tok != MDOC_Dt ||
1920 		    n->child == NULL ||
1921 		    n->child->next == NULL ||
1922 		    n->child->next->next == NULL)
1923 			n = n->next;
1924 		n = n->child->next->next;
1925 		mandoc_msg(MANDOCERR_ARCH_BAD, n->line, n->pos,
1926 		    "Dt ... %s %s", mdoc->meta.arch,
1927 		    mdoc->meta.os_e == MANDOC_OS_OPENBSD ?
1928 		    "(OpenBSD)" : "(NetBSD)");
1929 	}
1930 
1931 	/* Check that we begin with a proper `Sh'. */
1932 
1933 	n = mdoc->meta.first->child;
1934 	while (n != NULL &&
1935 	    (n->type == ROFFT_COMMENT ||
1936 	     (n->tok >= MDOC_Dd &&
1937 	      mdoc_macro(n->tok)->flags & MDOC_PROLOGUE)))
1938 		n = n->next;
1939 
1940 	if (n == NULL)
1941 		mandoc_msg(MANDOCERR_DOC_EMPTY, 0, 0, NULL);
1942 	else if (n->tok != MDOC_Sh)
1943 		mandoc_msg(MANDOCERR_SEC_BEFORE, n->line, n->pos,
1944 		    "%s", roff_name[n->tok]);
1945 }
1946 
1947 static void
1948 post_rs(POST_ARGS)
1949 {
1950 	struct roff_node *np, *nch, *next, *prev;
1951 	int		  i, j;
1952 
1953 	np = mdoc->last;
1954 
1955 	if (np->type != ROFFT_BODY)
1956 		return;
1957 
1958 	if (np->child == NULL) {
1959 		mandoc_msg(MANDOCERR_RS_EMPTY, np->line, np->pos, "Rs");
1960 		return;
1961 	}
1962 
1963 	/*
1964 	 * The full `Rs' block needs special handling to order the
1965 	 * sub-elements according to `rsord'.  Pick through each element
1966 	 * and correctly order it.  This is an insertion sort.
1967 	 */
1968 
1969 	next = NULL;
1970 	for (nch = np->child->next; nch != NULL; nch = next) {
1971 		/* Determine order number of this child. */
1972 		for (i = 0; i < RSORD_MAX; i++)
1973 			if (rsord[i] == nch->tok)
1974 				break;
1975 
1976 		if (i == RSORD_MAX) {
1977 			mandoc_msg(MANDOCERR_RS_BAD, nch->line, nch->pos,
1978 			    "%s", roff_name[nch->tok]);
1979 			i = -1;
1980 		} else if (nch->tok == MDOC__J || nch->tok == MDOC__B)
1981 			np->norm->Rs.quote_T++;
1982 
1983 		/*
1984 		 * Remove this child from the chain.  This somewhat
1985 		 * repeats roff_node_unlink(), but since we're
1986 		 * just re-ordering, there's no need for the
1987 		 * full unlink process.
1988 		 */
1989 
1990 		if ((next = nch->next) != NULL)
1991 			next->prev = nch->prev;
1992 
1993 		if ((prev = nch->prev) != NULL)
1994 			prev->next = nch->next;
1995 
1996 		nch->prev = nch->next = NULL;
1997 
1998 		/*
1999 		 * Scan back until we reach a node that's
2000 		 * to be ordered before this child.
2001 		 */
2002 
2003 		for ( ; prev ; prev = prev->prev) {
2004 			/* Determine order of `prev'. */
2005 			for (j = 0; j < RSORD_MAX; j++)
2006 				if (rsord[j] == prev->tok)
2007 					break;
2008 			if (j == RSORD_MAX)
2009 				j = -1;
2010 
2011 			if (j <= i)
2012 				break;
2013 		}
2014 
2015 		/*
2016 		 * Set this child back into its correct place
2017 		 * in front of the `prev' node.
2018 		 */
2019 
2020 		nch->prev = prev;
2021 
2022 		if (prev == NULL) {
2023 			np->child->prev = nch;
2024 			nch->next = np->child;
2025 			np->child = nch;
2026 		} else {
2027 			if (prev->next)
2028 				prev->next->prev = nch;
2029 			nch->next = prev->next;
2030 			prev->next = nch;
2031 		}
2032 	}
2033 }
2034 
2035 /*
2036  * For some arguments of some macros,
2037  * convert all breakable hyphens into ASCII_HYPH.
2038  */
2039 static void
2040 post_hyph(POST_ARGS)
2041 {
2042 	struct roff_node	*nch;
2043 	char			*cp;
2044 
2045 	for (nch = mdoc->last->child; nch != NULL; nch = nch->next) {
2046 		if (nch->type != ROFFT_TEXT)
2047 			continue;
2048 		cp = nch->string;
2049 		if (*cp == '\0')
2050 			continue;
2051 		while (*(++cp) != '\0')
2052 			if (*cp == '-' &&
2053 			    isalpha((unsigned char)cp[-1]) &&
2054 			    isalpha((unsigned char)cp[1]))
2055 				*cp = ASCII_HYPH;
2056 	}
2057 }
2058 
2059 static void
2060 post_ns(POST_ARGS)
2061 {
2062 	struct roff_node	*n;
2063 
2064 	n = mdoc->last;
2065 	if (n->flags & NODE_LINE ||
2066 	    (n->next != NULL && n->next->flags & NODE_DELIMC))
2067 		mandoc_msg(MANDOCERR_NS_SKIP, n->line, n->pos, NULL);
2068 }
2069 
2070 static void
2071 post_sx(POST_ARGS)
2072 {
2073 	post_delim(mdoc);
2074 	post_hyph(mdoc);
2075 }
2076 
2077 static void
2078 post_sh(POST_ARGS)
2079 {
2080 
2081 	post_ignpar(mdoc);
2082 
2083 	switch (mdoc->last->type) {
2084 	case ROFFT_HEAD:
2085 		post_sh_head(mdoc);
2086 		break;
2087 	case ROFFT_BODY:
2088 		switch (mdoc->lastsec)  {
2089 		case SEC_NAME:
2090 			post_sh_name(mdoc);
2091 			break;
2092 		case SEC_SEE_ALSO:
2093 			post_sh_see_also(mdoc);
2094 			break;
2095 		case SEC_AUTHORS:
2096 			post_sh_authors(mdoc);
2097 			break;
2098 		default:
2099 			break;
2100 		}
2101 		break;
2102 	default:
2103 		break;
2104 	}
2105 }
2106 
2107 static void
2108 post_sh_name(POST_ARGS)
2109 {
2110 	struct roff_node *n;
2111 	int hasnm, hasnd;
2112 
2113 	hasnm = hasnd = 0;
2114 
2115 	for (n = mdoc->last->child; n != NULL; n = n->next) {
2116 		switch (n->tok) {
2117 		case MDOC_Nm:
2118 			if (hasnm && n->child != NULL)
2119 				mandoc_msg(MANDOCERR_NAMESEC_PUNCT,
2120 				    n->line, n->pos,
2121 				    "Nm %s", n->child->string);
2122 			hasnm = 1;
2123 			continue;
2124 		case MDOC_Nd:
2125 			hasnd = 1;
2126 			if (n->next != NULL)
2127 				mandoc_msg(MANDOCERR_NAMESEC_ND,
2128 				    n->line, n->pos, NULL);
2129 			break;
2130 		case TOKEN_NONE:
2131 			if (n->type == ROFFT_TEXT &&
2132 			    n->string[0] == ',' && n->string[1] == '\0' &&
2133 			    n->next != NULL && n->next->tok == MDOC_Nm) {
2134 				n = n->next;
2135 				continue;
2136 			}
2137 			/* FALLTHROUGH */
2138 		default:
2139 			mandoc_msg(MANDOCERR_NAMESEC_BAD,
2140 			    n->line, n->pos, "%s", roff_name[n->tok]);
2141 			continue;
2142 		}
2143 		break;
2144 	}
2145 
2146 	if ( ! hasnm)
2147 		mandoc_msg(MANDOCERR_NAMESEC_NONM,
2148 		    mdoc->last->line, mdoc->last->pos, NULL);
2149 	if ( ! hasnd)
2150 		mandoc_msg(MANDOCERR_NAMESEC_NOND,
2151 		    mdoc->last->line, mdoc->last->pos, NULL);
2152 }
2153 
2154 static void
2155 post_sh_see_also(POST_ARGS)
2156 {
2157 	const struct roff_node	*n;
2158 	const char		*name, *sec;
2159 	const char		*lastname, *lastsec, *lastpunct;
2160 	int			 cmp;
2161 
2162 	n = mdoc->last->child;
2163 	lastname = lastsec = lastpunct = NULL;
2164 	while (n != NULL) {
2165 		if (n->tok != MDOC_Xr ||
2166 		    n->child == NULL ||
2167 		    n->child->next == NULL)
2168 			break;
2169 
2170 		/* Process one .Xr node. */
2171 
2172 		name = n->child->string;
2173 		sec = n->child->next->string;
2174 		if (lastsec != NULL) {
2175 			if (lastpunct[0] != ',' || lastpunct[1] != '\0')
2176 				mandoc_msg(MANDOCERR_XR_PUNCT, n->line,
2177 				    n->pos, "%s before %s(%s)",
2178 				    lastpunct, name, sec);
2179 			cmp = strcmp(lastsec, sec);
2180 			if (cmp > 0)
2181 				mandoc_msg(MANDOCERR_XR_ORDER, n->line,
2182 				    n->pos, "%s(%s) after %s(%s)",
2183 				    name, sec, lastname, lastsec);
2184 			else if (cmp == 0 &&
2185 			    strcasecmp(lastname, name) > 0)
2186 				mandoc_msg(MANDOCERR_XR_ORDER, n->line,
2187 				    n->pos, "%s after %s", name, lastname);
2188 		}
2189 		lastname = name;
2190 		lastsec = sec;
2191 
2192 		/* Process the following node. */
2193 
2194 		n = n->next;
2195 		if (n == NULL)
2196 			break;
2197 		if (n->tok == MDOC_Xr) {
2198 			lastpunct = "none";
2199 			continue;
2200 		}
2201 		if (n->type != ROFFT_TEXT)
2202 			break;
2203 		for (name = n->string; *name != '\0'; name++)
2204 			if (isalpha((const unsigned char)*name))
2205 				return;
2206 		lastpunct = n->string;
2207 		if (n->next == NULL || n->next->tok == MDOC_Rs)
2208 			mandoc_msg(MANDOCERR_XR_PUNCT, n->line,
2209 			    n->pos, "%s after %s(%s)",
2210 			    lastpunct, lastname, lastsec);
2211 		n = n->next;
2212 	}
2213 }
2214 
2215 static int
2216 child_an(const struct roff_node *n)
2217 {
2218 
2219 	for (n = n->child; n != NULL; n = n->next)
2220 		if ((n->tok == MDOC_An && n->child != NULL) || child_an(n))
2221 			return 1;
2222 	return 0;
2223 }
2224 
2225 static void
2226 post_sh_authors(POST_ARGS)
2227 {
2228 
2229 	if ( ! child_an(mdoc->last))
2230 		mandoc_msg(MANDOCERR_AN_MISSING,
2231 		    mdoc->last->line, mdoc->last->pos, NULL);
2232 }
2233 
2234 /*
2235  * Return an upper bound for the string distance (allowing
2236  * transpositions).  Not a full Levenshtein implementation
2237  * because Levenshtein is quadratic in the string length
2238  * and this function is called for every standard name,
2239  * so the check for each custom name would be cubic.
2240  * The following crude heuristics is linear, resulting
2241  * in quadratic behaviour for checking one custom name,
2242  * which does not cause measurable slowdown.
2243  */
2244 static int
2245 similar(const char *s1, const char *s2)
2246 {
2247 	const int	maxdist = 3;
2248 	int		dist = 0;
2249 
2250 	while (s1[0] != '\0' && s2[0] != '\0') {
2251 		if (s1[0] == s2[0]) {
2252 			s1++;
2253 			s2++;
2254 			continue;
2255 		}
2256 		if (++dist > maxdist)
2257 			return INT_MAX;
2258 		if (s1[1] == s2[1]) {  /* replacement */
2259 			s1++;
2260 			s2++;
2261 		} else if (s1[0] == s2[1] && s1[1] == s2[0]) {
2262 			s1 += 2;	/* transposition */
2263 			s2 += 2;
2264 		} else if (s1[0] == s2[1])  /* insertion */
2265 			s2++;
2266 		else if (s1[1] == s2[0])  /* deletion */
2267 			s1++;
2268 		else
2269 			return INT_MAX;
2270 	}
2271 	dist += strlen(s1) + strlen(s2);
2272 	return dist > maxdist ? INT_MAX : dist;
2273 }
2274 
2275 static void
2276 post_sh_head(POST_ARGS)
2277 {
2278 	struct roff_node	*nch;
2279 	const char		*goodsec;
2280 	const char *const	*testsec;
2281 	int			 dist, mindist;
2282 	enum roff_sec		 sec;
2283 
2284 	/*
2285 	 * Process a new section.  Sections are either "named" or
2286 	 * "custom".  Custom sections are user-defined, while named ones
2287 	 * follow a conventional order and may only appear in certain
2288 	 * manual sections.
2289 	 */
2290 
2291 	sec = mdoc->last->sec;
2292 
2293 	/* The NAME should be first. */
2294 
2295 	if (sec != SEC_NAME && mdoc->lastnamed == SEC_NONE)
2296 		mandoc_msg(MANDOCERR_NAMESEC_FIRST,
2297 		    mdoc->last->line, mdoc->last->pos, "Sh %s",
2298 		    sec != SEC_CUSTOM ? secnames[sec] :
2299 		    (nch = mdoc->last->child) == NULL ? "" :
2300 		    nch->type == ROFFT_TEXT ? nch->string :
2301 		    roff_name[nch->tok]);
2302 
2303 	/* The SYNOPSIS gets special attention in other areas. */
2304 
2305 	if (sec == SEC_SYNOPSIS) {
2306 		roff_setreg(mdoc->roff, "nS", 1, '=');
2307 		mdoc->flags |= MDOC_SYNOPSIS;
2308 	} else {
2309 		roff_setreg(mdoc->roff, "nS", 0, '=');
2310 		mdoc->flags &= ~MDOC_SYNOPSIS;
2311 	}
2312 
2313 	/* Mark our last section. */
2314 
2315 	mdoc->lastsec = sec;
2316 
2317 	/* We don't care about custom sections after this. */
2318 
2319 	if (sec == SEC_CUSTOM) {
2320 		if ((nch = mdoc->last->child) == NULL ||
2321 		    nch->type != ROFFT_TEXT || nch->next != NULL)
2322 			return;
2323 		goodsec = NULL;
2324 		mindist = INT_MAX;
2325 		for (testsec = secnames + 1; *testsec != NULL; testsec++) {
2326 			dist = similar(nch->string, *testsec);
2327 			if (dist < mindist) {
2328 				goodsec = *testsec;
2329 				mindist = dist;
2330 			}
2331 		}
2332 		if (goodsec != NULL)
2333 			mandoc_msg(MANDOCERR_SEC_TYPO, nch->line, nch->pos,
2334 			    "Sh %s instead of %s", nch->string, goodsec);
2335 		return;
2336 	}
2337 
2338 	/*
2339 	 * Check whether our non-custom section is being repeated or is
2340 	 * out of order.
2341 	 */
2342 
2343 	if (sec == mdoc->lastnamed)
2344 		mandoc_msg(MANDOCERR_SEC_REP, mdoc->last->line,
2345 		    mdoc->last->pos, "Sh %s", secnames[sec]);
2346 
2347 	if (sec < mdoc->lastnamed)
2348 		mandoc_msg(MANDOCERR_SEC_ORDER, mdoc->last->line,
2349 		    mdoc->last->pos, "Sh %s", secnames[sec]);
2350 
2351 	/* Mark the last named section. */
2352 
2353 	mdoc->lastnamed = sec;
2354 
2355 	/* Check particular section/manual conventions. */
2356 
2357 	if (mdoc->meta.msec == NULL)
2358 		return;
2359 
2360 	goodsec = NULL;
2361 	switch (sec) {
2362 	case SEC_ERRORS:
2363 		if (*mdoc->meta.msec == '4')
2364 			break;
2365 		goodsec = "2, 3, 4, 9";
2366 		/* FALLTHROUGH */
2367 	case SEC_RETURN_VALUES:
2368 	case SEC_LIBRARY:
2369 		if (*mdoc->meta.msec == '2')
2370 			break;
2371 		if (*mdoc->meta.msec == '3')
2372 			break;
2373 		if (NULL == goodsec)
2374 			goodsec = "2, 3, 9";
2375 		/* FALLTHROUGH */
2376 	case SEC_CONTEXT:
2377 		if (*mdoc->meta.msec == '9')
2378 			break;
2379 		if (NULL == goodsec)
2380 			goodsec = "9";
2381 		mandoc_msg(MANDOCERR_SEC_MSEC,
2382 		    mdoc->last->line, mdoc->last->pos,
2383 		    "Sh %s for %s only", secnames[sec], goodsec);
2384 		break;
2385 	default:
2386 		break;
2387 	}
2388 }
2389 
2390 static void
2391 post_xr(POST_ARGS)
2392 {
2393 	struct roff_node *n, *nch;
2394 
2395 	n = mdoc->last;
2396 	nch = n->child;
2397 	if (nch->next == NULL) {
2398 		mandoc_msg(MANDOCERR_XR_NOSEC,
2399 		    n->line, n->pos, "Xr %s", nch->string);
2400 	} else {
2401 		assert(nch->next == n->last);
2402 		if(mandoc_xr_add(nch->next->string, nch->string,
2403 		    nch->line, nch->pos))
2404 			mandoc_msg(MANDOCERR_XR_SELF,
2405 			    nch->line, nch->pos, "Xr %s %s",
2406 			    nch->string, nch->next->string);
2407 	}
2408 	post_delim_nb(mdoc);
2409 }
2410 
2411 static void
2412 post_ignpar(POST_ARGS)
2413 {
2414 	struct roff_node *np;
2415 
2416 	switch (mdoc->last->type) {
2417 	case ROFFT_BLOCK:
2418 		post_prevpar(mdoc);
2419 		return;
2420 	case ROFFT_HEAD:
2421 		post_delim(mdoc);
2422 		post_hyph(mdoc);
2423 		return;
2424 	case ROFFT_BODY:
2425 		break;
2426 	default:
2427 		return;
2428 	}
2429 
2430 	if ((np = mdoc->last->child) != NULL)
2431 		if (np->tok == MDOC_Pp ||
2432 		    np->tok == ROFF_br || np->tok == ROFF_sp) {
2433 			mandoc_msg(MANDOCERR_PAR_SKIP, np->line, np->pos,
2434 			    "%s after %s", roff_name[np->tok],
2435 			    roff_name[mdoc->last->tok]);
2436 			roff_node_delete(mdoc, np);
2437 		}
2438 
2439 	if ((np = mdoc->last->last) != NULL)
2440 		if (np->tok == MDOC_Pp || np->tok == ROFF_br) {
2441 			mandoc_msg(MANDOCERR_PAR_SKIP, np->line, np->pos,
2442 			    "%s at the end of %s", roff_name[np->tok],
2443 			    roff_name[mdoc->last->tok]);
2444 			roff_node_delete(mdoc, np);
2445 		}
2446 }
2447 
2448 static void
2449 post_prevpar(POST_ARGS)
2450 {
2451 	struct roff_node *n;
2452 
2453 	n = mdoc->last;
2454 	if (NULL == n->prev)
2455 		return;
2456 	if (n->type != ROFFT_ELEM && n->type != ROFFT_BLOCK)
2457 		return;
2458 
2459 	/*
2460 	 * Don't allow `Pp' prior to a paragraph-type
2461 	 * block: `Pp' or non-compact `Bd' or `Bl'.
2462 	 */
2463 
2464 	if (n->prev->tok != MDOC_Pp && n->prev->tok != ROFF_br)
2465 		return;
2466 	if (n->tok == MDOC_Bl && n->norm->Bl.comp)
2467 		return;
2468 	if (n->tok == MDOC_Bd && n->norm->Bd.comp)
2469 		return;
2470 	if (n->tok == MDOC_It && n->parent->norm->Bl.comp)
2471 		return;
2472 
2473 	mandoc_msg(MANDOCERR_PAR_SKIP, n->prev->line, n->prev->pos,
2474 	    "%s before %s", roff_name[n->prev->tok], roff_name[n->tok]);
2475 	roff_node_delete(mdoc, n->prev);
2476 }
2477 
2478 static void
2479 post_par(POST_ARGS)
2480 {
2481 	struct roff_node *np;
2482 
2483 	post_prevpar(mdoc);
2484 
2485 	np = mdoc->last;
2486 	if (np->child != NULL)
2487 		mandoc_msg(MANDOCERR_ARG_SKIP, np->line, np->pos,
2488 		    "%s %s", roff_name[np->tok], np->child->string);
2489 }
2490 
2491 static void
2492 post_dd(POST_ARGS)
2493 {
2494 	struct roff_node *n;
2495 	char		 *datestr;
2496 
2497 	n = mdoc->last;
2498 	n->flags |= NODE_NOPRT;
2499 
2500 	if (mdoc->meta.date != NULL) {
2501 		mandoc_msg(MANDOCERR_PROLOG_REP, n->line, n->pos, "Dd");
2502 		free(mdoc->meta.date);
2503 	} else if (mdoc->flags & MDOC_PBODY)
2504 		mandoc_msg(MANDOCERR_PROLOG_LATE, n->line, n->pos, "Dd");
2505 	else if (mdoc->meta.title != NULL)
2506 		mandoc_msg(MANDOCERR_PROLOG_ORDER,
2507 		    n->line, n->pos, "Dd after Dt");
2508 	else if (mdoc->meta.os != NULL)
2509 		mandoc_msg(MANDOCERR_PROLOG_ORDER,
2510 		    n->line, n->pos, "Dd after Os");
2511 
2512 	datestr = NULL;
2513 	deroff(&datestr, n);
2514 	mdoc->meta.date = mandoc_normdate(mdoc, datestr, n->line, n->pos);
2515 	free(datestr);
2516 }
2517 
2518 static void
2519 post_dt(POST_ARGS)
2520 {
2521 	struct roff_node *nn, *n;
2522 	const char	 *cp;
2523 	char		 *p;
2524 
2525 	n = mdoc->last;
2526 	n->flags |= NODE_NOPRT;
2527 
2528 	if (mdoc->flags & MDOC_PBODY) {
2529 		mandoc_msg(MANDOCERR_DT_LATE, n->line, n->pos, "Dt");
2530 		return;
2531 	}
2532 
2533 	if (mdoc->meta.title != NULL)
2534 		mandoc_msg(MANDOCERR_PROLOG_REP, n->line, n->pos, "Dt");
2535 	else if (mdoc->meta.os != NULL)
2536 		mandoc_msg(MANDOCERR_PROLOG_ORDER,
2537 		    n->line, n->pos, "Dt after Os");
2538 
2539 	free(mdoc->meta.title);
2540 	free(mdoc->meta.msec);
2541 	free(mdoc->meta.vol);
2542 	free(mdoc->meta.arch);
2543 
2544 	mdoc->meta.title = NULL;
2545 	mdoc->meta.msec = NULL;
2546 	mdoc->meta.vol = NULL;
2547 	mdoc->meta.arch = NULL;
2548 
2549 	/* Mandatory first argument: title. */
2550 
2551 	nn = n->child;
2552 	if (nn == NULL || *nn->string == '\0') {
2553 		mandoc_msg(MANDOCERR_DT_NOTITLE, n->line, n->pos, "Dt");
2554 		mdoc->meta.title = mandoc_strdup("UNTITLED");
2555 	} else {
2556 		mdoc->meta.title = mandoc_strdup(nn->string);
2557 
2558 		/* Check that all characters are uppercase. */
2559 
2560 		for (p = nn->string; *p != '\0'; p++)
2561 			if (islower((unsigned char)*p)) {
2562 				mandoc_msg(MANDOCERR_TITLE_CASE, nn->line,
2563 				    nn->pos + (int)(p - nn->string),
2564 				    "Dt %s", nn->string);
2565 				break;
2566 			}
2567 	}
2568 
2569 	/* Mandatory second argument: section. */
2570 
2571 	if (nn != NULL)
2572 		nn = nn->next;
2573 
2574 	if (nn == NULL) {
2575 		mandoc_msg(MANDOCERR_MSEC_MISSING, n->line, n->pos,
2576 		    "Dt %s", mdoc->meta.title);
2577 		mdoc->meta.vol = mandoc_strdup("LOCAL");
2578 		return;  /* msec and arch remain NULL. */
2579 	}
2580 
2581 	mdoc->meta.msec = mandoc_strdup(nn->string);
2582 
2583 	/* Infer volume title from section number. */
2584 
2585 	cp = mandoc_a2msec(nn->string);
2586 	if (cp == NULL) {
2587 		mandoc_msg(MANDOCERR_MSEC_BAD,
2588 		    nn->line, nn->pos, "Dt ... %s", nn->string);
2589 		mdoc->meta.vol = mandoc_strdup(nn->string);
2590 	} else
2591 		mdoc->meta.vol = mandoc_strdup(cp);
2592 
2593 	/* Optional third argument: architecture. */
2594 
2595 	if ((nn = nn->next) == NULL)
2596 		return;
2597 
2598 	for (p = nn->string; *p != '\0'; p++)
2599 		*p = tolower((unsigned char)*p);
2600 	mdoc->meta.arch = mandoc_strdup(nn->string);
2601 
2602 	/* Ignore fourth and later arguments. */
2603 
2604 	if ((nn = nn->next) != NULL)
2605 		mandoc_msg(MANDOCERR_ARG_EXCESS,
2606 		    nn->line, nn->pos, "Dt ... %s", nn->string);
2607 }
2608 
2609 static void
2610 post_bx(POST_ARGS)
2611 {
2612 	struct roff_node	*n, *nch;
2613 	const char		*macro;
2614 
2615 	post_delim_nb(mdoc);
2616 
2617 	n = mdoc->last;
2618 	nch = n->child;
2619 
2620 	if (nch != NULL) {
2621 		macro = !strcmp(nch->string, "Open") ? "Ox" :
2622 		    !strcmp(nch->string, "Net") ? "Nx" :
2623 		    !strcmp(nch->string, "Free") ? "Fx" :
2624 		    !strcmp(nch->string, "DragonFly") ? "Dx" : NULL;
2625 		if (macro != NULL)
2626 			mandoc_msg(MANDOCERR_BX,
2627 			    n->line, n->pos, "%s", macro);
2628 		mdoc->last = nch;
2629 		nch = nch->next;
2630 		mdoc->next = ROFF_NEXT_SIBLING;
2631 		roff_elem_alloc(mdoc, n->line, n->pos, MDOC_Ns);
2632 		mdoc->last->flags |= NODE_NOSRC;
2633 		mdoc->next = ROFF_NEXT_SIBLING;
2634 	} else
2635 		mdoc->next = ROFF_NEXT_CHILD;
2636 	roff_word_alloc(mdoc, n->line, n->pos, "BSD");
2637 	mdoc->last->flags |= NODE_NOSRC;
2638 
2639 	if (nch == NULL) {
2640 		mdoc->last = n;
2641 		return;
2642 	}
2643 
2644 	roff_elem_alloc(mdoc, n->line, n->pos, MDOC_Ns);
2645 	mdoc->last->flags |= NODE_NOSRC;
2646 	mdoc->next = ROFF_NEXT_SIBLING;
2647 	roff_word_alloc(mdoc, n->line, n->pos, "-");
2648 	mdoc->last->flags |= NODE_NOSRC;
2649 	roff_elem_alloc(mdoc, n->line, n->pos, MDOC_Ns);
2650 	mdoc->last->flags |= NODE_NOSRC;
2651 	mdoc->last = n;
2652 
2653 	/*
2654 	 * Make `Bx's second argument always start with an uppercase
2655 	 * letter.  Groff checks if it's an "accepted" term, but we just
2656 	 * uppercase blindly.
2657 	 */
2658 
2659 	*nch->string = (char)toupper((unsigned char)*nch->string);
2660 }
2661 
2662 static void
2663 post_os(POST_ARGS)
2664 {
2665 #ifndef OSNAME
2666 	struct utsname	  utsname;
2667 	static char	 *defbuf;
2668 #endif
2669 	struct roff_node *n;
2670 
2671 	n = mdoc->last;
2672 	n->flags |= NODE_NOPRT;
2673 
2674 	if (mdoc->meta.os != NULL)
2675 		mandoc_msg(MANDOCERR_PROLOG_REP, n->line, n->pos, "Os");
2676 	else if (mdoc->flags & MDOC_PBODY)
2677 		mandoc_msg(MANDOCERR_PROLOG_LATE, n->line, n->pos, "Os");
2678 
2679 	post_delim(mdoc);
2680 
2681 	/*
2682 	 * Set the operating system by way of the `Os' macro.
2683 	 * The order of precedence is:
2684 	 * 1. the argument of the `Os' macro, unless empty
2685 	 * 2. the -Ios=foo command line argument, if provided
2686 	 * 3. -DOSNAME="\"foo\"", if provided during compilation
2687 	 * 4. "sysname release" from uname(3)
2688 	 */
2689 
2690 	free(mdoc->meta.os);
2691 	mdoc->meta.os = NULL;
2692 	deroff(&mdoc->meta.os, n);
2693 	if (mdoc->meta.os)
2694 		goto out;
2695 
2696 	if (mdoc->os_s != NULL) {
2697 		mdoc->meta.os = mandoc_strdup(mdoc->os_s);
2698 		goto out;
2699 	}
2700 
2701 #ifdef OSNAME
2702 	mdoc->meta.os = mandoc_strdup(OSNAME);
2703 #else /*!OSNAME */
2704 	if (defbuf == NULL) {
2705 		if (uname(&utsname) == -1) {
2706 			mandoc_msg(MANDOCERR_OS_UNAME, n->line, n->pos, "Os");
2707 			defbuf = mandoc_strdup("UNKNOWN");
2708 		} else
2709 			mandoc_asprintf(&defbuf, "%s %s",
2710 			    utsname.sysname, utsname.release);
2711 	}
2712 	mdoc->meta.os = mandoc_strdup(defbuf);
2713 #endif /*!OSNAME*/
2714 
2715 out:
2716 	if (mdoc->meta.os_e == MANDOC_OS_OTHER) {
2717 		if (strstr(mdoc->meta.os, "OpenBSD") != NULL)
2718 			mdoc->meta.os_e = MANDOC_OS_OPENBSD;
2719 		else if (strstr(mdoc->meta.os, "NetBSD") != NULL)
2720 			mdoc->meta.os_e = MANDOC_OS_NETBSD;
2721 	}
2722 
2723 	/*
2724 	 * This is the earliest point where we can check
2725 	 * Mdocdate conventions because we don't know
2726 	 * the operating system earlier.
2727 	 */
2728 
2729 	if (n->child != NULL)
2730 		mandoc_msg(MANDOCERR_OS_ARG, n->child->line, n->child->pos,
2731 		    "Os %s (%s)", n->child->string,
2732 		    mdoc->meta.os_e == MANDOC_OS_OPENBSD ?
2733 		    "OpenBSD" : "NetBSD");
2734 
2735 	while (n->tok != MDOC_Dd)
2736 		if ((n = n->prev) == NULL)
2737 			return;
2738 	if ((n = n->child) == NULL)
2739 		return;
2740 	if (strncmp(n->string, "$" "Mdocdate", 9)) {
2741 		if (mdoc->meta.os_e == MANDOC_OS_OPENBSD)
2742 			mandoc_msg(MANDOCERR_MDOCDATE_MISSING, n->line,
2743 			    n->pos, "Dd %s (OpenBSD)", n->string);
2744 	} else {
2745 		if (mdoc->meta.os_e == MANDOC_OS_NETBSD)
2746 			mandoc_msg(MANDOCERR_MDOCDATE, n->line,
2747 			    n->pos, "Dd %s (NetBSD)", n->string);
2748 	}
2749 }
2750 
2751 enum roff_sec
2752 mdoc_a2sec(const char *p)
2753 {
2754 	int		 i;
2755 
2756 	for (i = 0; i < (int)SEC__MAX; i++)
2757 		if (secnames[i] && 0 == strcmp(p, secnames[i]))
2758 			return (enum roff_sec)i;
2759 
2760 	return SEC_CUSTOM;
2761 }
2762 
2763 static size_t
2764 macro2len(enum roff_tok macro)
2765 {
2766 
2767 	switch (macro) {
2768 	case MDOC_Ad:
2769 		return 12;
2770 	case MDOC_Ao:
2771 		return 12;
2772 	case MDOC_An:
2773 		return 12;
2774 	case MDOC_Aq:
2775 		return 12;
2776 	case MDOC_Ar:
2777 		return 12;
2778 	case MDOC_Bo:
2779 		return 12;
2780 	case MDOC_Bq:
2781 		return 12;
2782 	case MDOC_Cd:
2783 		return 12;
2784 	case MDOC_Cm:
2785 		return 10;
2786 	case MDOC_Do:
2787 		return 10;
2788 	case MDOC_Dq:
2789 		return 12;
2790 	case MDOC_Dv:
2791 		return 12;
2792 	case MDOC_Eo:
2793 		return 12;
2794 	case MDOC_Em:
2795 		return 10;
2796 	case MDOC_Er:
2797 		return 17;
2798 	case MDOC_Ev:
2799 		return 15;
2800 	case MDOC_Fa:
2801 		return 12;
2802 	case MDOC_Fl:
2803 		return 10;
2804 	case MDOC_Fo:
2805 		return 16;
2806 	case MDOC_Fn:
2807 		return 16;
2808 	case MDOC_Ic:
2809 		return 10;
2810 	case MDOC_Li:
2811 		return 16;
2812 	case MDOC_Ms:
2813 		return 6;
2814 	case MDOC_Nm:
2815 		return 10;
2816 	case MDOC_No:
2817 		return 12;
2818 	case MDOC_Oo:
2819 		return 10;
2820 	case MDOC_Op:
2821 		return 14;
2822 	case MDOC_Pa:
2823 		return 32;
2824 	case MDOC_Pf:
2825 		return 12;
2826 	case MDOC_Po:
2827 		return 12;
2828 	case MDOC_Pq:
2829 		return 12;
2830 	case MDOC_Ql:
2831 		return 16;
2832 	case MDOC_Qo:
2833 		return 12;
2834 	case MDOC_So:
2835 		return 12;
2836 	case MDOC_Sq:
2837 		return 12;
2838 	case MDOC_Sy:
2839 		return 6;
2840 	case MDOC_Sx:
2841 		return 16;
2842 	case MDOC_Tn:
2843 		return 10;
2844 	case MDOC_Va:
2845 		return 12;
2846 	case MDOC_Vt:
2847 		return 12;
2848 	case MDOC_Xr:
2849 		return 10;
2850 	default:
2851 		break;
2852 	};
2853 	return 0;
2854 }
2855