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