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