xref: /freebsd/contrib/mandoc/mdoc_validate.c (revision e0c4386e)
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 "in" begins with a dot, a word, and whitespace, return a dynamically
1823  * allocated copy of "in" that skips all of those.  Otherwise, return NULL.
1824  *
1825  * This is a partial workaround for the TODO list item beginning with:
1826  * - When the -width string contains macros, the macros must be rendered
1827  */
1828 static char *
1829 skip_leading_dot_word(const char *in)
1830 {
1831 	const char *iter = in;
1832 	const char *space;
1833 
1834 	if (*iter != '.')
1835 		return NULL;
1836 	iter++;
1837 
1838 	while (*iter != '\0' && !isspace(*iter))
1839 		iter++;
1840 	/*
1841 	 * If the dot was followed by space or NUL,
1842 	 * do not skip anything.
1843 	 */
1844 	if (iter == in + 1)
1845 		return NULL;
1846 
1847 	space = iter;
1848 	while (isspace(*iter))
1849 		iter++;
1850 	/*
1851 	 * If the word was not followed by space,
1852 	 * do not skip anything.
1853 	 */
1854 	if (iter == space)
1855 		return NULL;
1856 
1857 	return strdup(iter);
1858 }
1859 
1860 /*
1861  * If the argument of -offset or -width is a macro,
1862  * replace it with the associated default width.
1863  */
1864 static void
1865 rewrite_macro2len(struct roff_man *mdoc, char **arg)
1866 {
1867 	size_t		  width;
1868 	enum roff_tok	  tok;
1869 	char		 *newarg;
1870 
1871 	newarg = NULL;
1872 	if (*arg == NULL)
1873 		return;
1874 	else if ( ! strcmp(*arg, "Ds"))
1875 		width = 6;
1876 	else if ((tok = roffhash_find(mdoc->mdocmac, *arg, 0)) != TOKEN_NONE)
1877 		width = macro2len(tok);
1878 	else if ((newarg = skip_leading_dot_word(*arg)) == NULL)
1879 		return;
1880 
1881 	free(*arg);
1882 	if (newarg != NULL)
1883 		*arg = newarg;
1884 	else
1885 		mandoc_asprintf(arg, "%zun", width);
1886 }
1887 
1888 static void
1889 post_bl_head(POST_ARGS)
1890 {
1891 	struct roff_node *nbl, *nh, *nch, *nnext;
1892 	struct mdoc_argv *argv;
1893 	int		  i, j;
1894 
1895 	post_bl_norm(mdoc);
1896 
1897 	nh = mdoc->last;
1898 	if (nh->norm->Bl.type != LIST_column) {
1899 		if ((nch = nh->child) == NULL)
1900 			return;
1901 		mandoc_msg(MANDOCERR_ARG_EXCESS,
1902 		    nch->line, nch->pos, "Bl ... %s", nch->string);
1903 		while (nch != NULL) {
1904 			roff_node_delete(mdoc, nch);
1905 			nch = nh->child;
1906 		}
1907 		return;
1908 	}
1909 
1910 	/*
1911 	 * Append old-style lists, where the column width specifiers
1912 	 * trail as macro parameters, to the new-style ("normal-form")
1913 	 * lists where they're argument values following -column.
1914 	 */
1915 
1916 	if (nh->child == NULL)
1917 		return;
1918 
1919 	nbl = nh->parent;
1920 	for (j = 0; j < (int)nbl->args->argc; j++)
1921 		if (nbl->args->argv[j].arg == MDOC_Column)
1922 			break;
1923 
1924 	assert(j < (int)nbl->args->argc);
1925 
1926 	/*
1927 	 * Accommodate for new-style groff column syntax.  Shuffle the
1928 	 * child nodes, all of which must be TEXT, as arguments for the
1929 	 * column field.  Then, delete the head children.
1930 	 */
1931 
1932 	argv = nbl->args->argv + j;
1933 	i = argv->sz;
1934 	for (nch = nh->child; nch != NULL; nch = nch->next)
1935 		argv->sz++;
1936 	argv->value = mandoc_reallocarray(argv->value,
1937 	    argv->sz, sizeof(char *));
1938 
1939 	nh->norm->Bl.ncols = argv->sz;
1940 	nh->norm->Bl.cols = (void *)argv->value;
1941 
1942 	for (nch = nh->child; nch != NULL; nch = nnext) {
1943 		argv->value[i++] = nch->string;
1944 		nch->string = NULL;
1945 		nnext = nch->next;
1946 		roff_node_delete(NULL, nch);
1947 	}
1948 	nh->child = NULL;
1949 }
1950 
1951 static void
1952 post_bl(POST_ARGS)
1953 {
1954 	struct roff_node	*nbody;           /* of the Bl */
1955 	struct roff_node	*nchild, *nnext;  /* of the Bl body */
1956 	const char		*prev_Er;
1957 	int			 order;
1958 
1959 	nbody = mdoc->last;
1960 	switch (nbody->type) {
1961 	case ROFFT_BLOCK:
1962 		post_bl_block(mdoc);
1963 		return;
1964 	case ROFFT_HEAD:
1965 		post_bl_head(mdoc);
1966 		return;
1967 	case ROFFT_BODY:
1968 		break;
1969 	default:
1970 		return;
1971 	}
1972 	if (nbody->end != ENDBODY_NOT)
1973 		return;
1974 
1975 	/*
1976 	 * Up to the first item, move nodes before the list,
1977 	 * but leave transparent nodes where they are
1978 	 * if they precede an item.
1979 	 * The next non-transparent node is kept in nchild.
1980 	 * It only needs to be updated after a non-transparent
1981 	 * node was moved out, and at the very beginning
1982 	 * when no node at all was moved yet.
1983 	 */
1984 
1985 	nchild = mdoc->last;
1986 	for (;;) {
1987 		if (nchild == mdoc->last)
1988 			nchild = roff_node_child(nbody);
1989 		if (nchild == NULL) {
1990 			mdoc->last = nbody;
1991 			mandoc_msg(MANDOCERR_BLK_EMPTY,
1992 			    nbody->line, nbody->pos, "Bl");
1993 			return;
1994 		}
1995 		if (nchild->tok == MDOC_It) {
1996 			mdoc->last = nbody;
1997 			break;
1998 		}
1999 		mandoc_msg(MANDOCERR_BL_MOVE, nbody->child->line,
2000 		    nbody->child->pos, "%s", roff_name[nbody->child->tok]);
2001 		if (nbody->parent->prev == NULL) {
2002 			mdoc->last = nbody->parent->parent;
2003 			mdoc->next = ROFF_NEXT_CHILD;
2004 		} else {
2005 			mdoc->last = nbody->parent->prev;
2006 			mdoc->next = ROFF_NEXT_SIBLING;
2007 		}
2008 		roff_node_relink(mdoc, nbody->child);
2009 	}
2010 
2011 	/*
2012 	 * We have reached the first item,
2013 	 * so moving nodes out is no longer possible.
2014 	 * But in .Bl -column, the first rows may be implicit,
2015 	 * that is, they may not start with .It macros.
2016 	 * Such rows may be followed by nodes generated on the
2017 	 * roff level, for example .TS.
2018 	 * Wrap such roff nodes into an implicit row.
2019 	 */
2020 
2021 	while (nchild != NULL) {
2022 		if (nchild->tok == MDOC_It) {
2023 			nchild = roff_node_next(nchild);
2024 			continue;
2025 		}
2026 		nnext = nchild->next;
2027 		mdoc->last = nchild->prev;
2028 		mdoc->next = ROFF_NEXT_SIBLING;
2029 		roff_block_alloc(mdoc, nchild->line, nchild->pos, MDOC_It);
2030 		roff_head_alloc(mdoc, nchild->line, nchild->pos, MDOC_It);
2031 		mdoc->next = ROFF_NEXT_SIBLING;
2032 		roff_body_alloc(mdoc, nchild->line, nchild->pos, MDOC_It);
2033 		while (nchild->tok != MDOC_It) {
2034 			roff_node_relink(mdoc, nchild);
2035 			if (nnext == NULL)
2036 				break;
2037 			nchild = nnext;
2038 			nnext = nchild->next;
2039 			mdoc->next = ROFF_NEXT_SIBLING;
2040 		}
2041 		mdoc->last = nbody;
2042 	}
2043 
2044 	if (mdoc->meta.os_e != MANDOC_OS_NETBSD)
2045 		return;
2046 
2047 	prev_Er = NULL;
2048 	for (nchild = nbody->child; nchild != NULL; nchild = nchild->next) {
2049 		if (nchild->tok != MDOC_It)
2050 			continue;
2051 		if ((nnext = nchild->head->child) == NULL)
2052 			continue;
2053 		if (nnext->type == ROFFT_BLOCK)
2054 			nnext = nnext->body->child;
2055 		if (nnext == NULL || nnext->tok != MDOC_Er)
2056 			continue;
2057 		nnext = nnext->child;
2058 		if (prev_Er != NULL) {
2059 			order = strcmp(prev_Er, nnext->string);
2060 			if (order > 0)
2061 				mandoc_msg(MANDOCERR_ER_ORDER,
2062 				    nnext->line, nnext->pos,
2063 				    "Er %s %s (NetBSD)",
2064 				    prev_Er, nnext->string);
2065 			else if (order == 0)
2066 				mandoc_msg(MANDOCERR_ER_REP,
2067 				    nnext->line, nnext->pos,
2068 				    "Er %s (NetBSD)", prev_Er);
2069 		}
2070 		prev_Er = nnext->string;
2071 	}
2072 }
2073 
2074 static void
2075 post_bk(POST_ARGS)
2076 {
2077 	struct roff_node	*n;
2078 
2079 	n = mdoc->last;
2080 
2081 	if (n->type == ROFFT_BLOCK && n->body->child == NULL) {
2082 		mandoc_msg(MANDOCERR_BLK_EMPTY, n->line, n->pos, "Bk");
2083 		roff_node_delete(mdoc, n);
2084 	}
2085 }
2086 
2087 static void
2088 post_sm(POST_ARGS)
2089 {
2090 	struct roff_node	*nch;
2091 
2092 	nch = mdoc->last->child;
2093 
2094 	if (nch == NULL) {
2095 		mdoc->flags ^= MDOC_SMOFF;
2096 		return;
2097 	}
2098 
2099 	assert(nch->type == ROFFT_TEXT);
2100 
2101 	if ( ! strcmp(nch->string, "on")) {
2102 		mdoc->flags &= ~MDOC_SMOFF;
2103 		return;
2104 	}
2105 	if ( ! strcmp(nch->string, "off")) {
2106 		mdoc->flags |= MDOC_SMOFF;
2107 		return;
2108 	}
2109 
2110 	mandoc_msg(MANDOCERR_SM_BAD, nch->line, nch->pos,
2111 	    "%s %s", roff_name[mdoc->last->tok], nch->string);
2112 	roff_node_relink(mdoc, nch);
2113 	return;
2114 }
2115 
2116 static void
2117 post_root(POST_ARGS)
2118 {
2119 	struct roff_node *n;
2120 
2121 	/* Add missing prologue data. */
2122 
2123 	if (mdoc->meta.date == NULL)
2124 		mdoc->meta.date = mandoc_normdate(NULL, NULL);
2125 
2126 	if (mdoc->meta.title == NULL) {
2127 		mandoc_msg(MANDOCERR_DT_NOTITLE, 0, 0, "EOF");
2128 		mdoc->meta.title = mandoc_strdup("UNTITLED");
2129 	}
2130 
2131 	if (mdoc->meta.vol == NULL)
2132 		mdoc->meta.vol = mandoc_strdup("LOCAL");
2133 
2134 	if (mdoc->meta.os == NULL) {
2135 		mandoc_msg(MANDOCERR_OS_MISSING, 0, 0, NULL);
2136 		mdoc->meta.os = mandoc_strdup("");
2137 	} else if (mdoc->meta.os_e &&
2138 	    (mdoc->meta.rcsids & (1 << mdoc->meta.os_e)) == 0)
2139 		mandoc_msg(MANDOCERR_RCS_MISSING, 0, 0,
2140 		    mdoc->meta.os_e == MANDOC_OS_OPENBSD ?
2141 		    "(OpenBSD)" : "(NetBSD)");
2142 
2143 	if (mdoc->meta.arch != NULL &&
2144 	    arch_valid(mdoc->meta.arch, mdoc->meta.os_e) == 0) {
2145 		n = mdoc->meta.first->child;
2146 		while (n->tok != MDOC_Dt ||
2147 		    n->child == NULL ||
2148 		    n->child->next == NULL ||
2149 		    n->child->next->next == NULL)
2150 			n = n->next;
2151 		n = n->child->next->next;
2152 		mandoc_msg(MANDOCERR_ARCH_BAD, n->line, n->pos,
2153 		    "Dt ... %s %s", mdoc->meta.arch,
2154 		    mdoc->meta.os_e == MANDOC_OS_OPENBSD ?
2155 		    "(OpenBSD)" : "(NetBSD)");
2156 	}
2157 
2158 	/* Check that we begin with a proper `Sh'. */
2159 
2160 	n = mdoc->meta.first->child;
2161 	while (n != NULL &&
2162 	    (n->type == ROFFT_COMMENT ||
2163 	     (n->tok >= MDOC_Dd &&
2164 	      mdoc_macro(n->tok)->flags & MDOC_PROLOGUE)))
2165 		n = n->next;
2166 
2167 	if (n == NULL)
2168 		mandoc_msg(MANDOCERR_DOC_EMPTY, 0, 0, NULL);
2169 	else if (n->tok != MDOC_Sh)
2170 		mandoc_msg(MANDOCERR_SEC_BEFORE, n->line, n->pos,
2171 		    "%s", roff_name[n->tok]);
2172 }
2173 
2174 static void
2175 post_rs(POST_ARGS)
2176 {
2177 	struct roff_node *np, *nch, *next, *prev;
2178 	int		  i, j;
2179 
2180 	np = mdoc->last;
2181 
2182 	if (np->type != ROFFT_BODY)
2183 		return;
2184 
2185 	if (np->child == NULL) {
2186 		mandoc_msg(MANDOCERR_RS_EMPTY, np->line, np->pos, "Rs");
2187 		return;
2188 	}
2189 
2190 	/*
2191 	 * The full `Rs' block needs special handling to order the
2192 	 * sub-elements according to `rsord'.  Pick through each element
2193 	 * and correctly order it.  This is an insertion sort.
2194 	 */
2195 
2196 	next = NULL;
2197 	for (nch = np->child->next; nch != NULL; nch = next) {
2198 		/* Determine order number of this child. */
2199 		for (i = 0; i < RSORD_MAX; i++)
2200 			if (rsord[i] == nch->tok)
2201 				break;
2202 
2203 		if (i == RSORD_MAX) {
2204 			mandoc_msg(MANDOCERR_RS_BAD, nch->line, nch->pos,
2205 			    "%s", roff_name[nch->tok]);
2206 			i = -1;
2207 		} else if (nch->tok == MDOC__J || nch->tok == MDOC__B)
2208 			np->norm->Rs.quote_T++;
2209 
2210 		/*
2211 		 * Remove this child from the chain.  This somewhat
2212 		 * repeats roff_node_unlink(), but since we're
2213 		 * just re-ordering, there's no need for the
2214 		 * full unlink process.
2215 		 */
2216 
2217 		if ((next = nch->next) != NULL)
2218 			next->prev = nch->prev;
2219 
2220 		if ((prev = nch->prev) != NULL)
2221 			prev->next = nch->next;
2222 
2223 		nch->prev = nch->next = NULL;
2224 
2225 		/*
2226 		 * Scan back until we reach a node that's
2227 		 * to be ordered before this child.
2228 		 */
2229 
2230 		for ( ; prev ; prev = prev->prev) {
2231 			/* Determine order of `prev'. */
2232 			for (j = 0; j < RSORD_MAX; j++)
2233 				if (rsord[j] == prev->tok)
2234 					break;
2235 			if (j == RSORD_MAX)
2236 				j = -1;
2237 
2238 			if (j <= i)
2239 				break;
2240 		}
2241 
2242 		/*
2243 		 * Set this child back into its correct place
2244 		 * in front of the `prev' node.
2245 		 */
2246 
2247 		nch->prev = prev;
2248 
2249 		if (prev == NULL) {
2250 			np->child->prev = nch;
2251 			nch->next = np->child;
2252 			np->child = nch;
2253 		} else {
2254 			if (prev->next)
2255 				prev->next->prev = nch;
2256 			nch->next = prev->next;
2257 			prev->next = nch;
2258 		}
2259 	}
2260 }
2261 
2262 /*
2263  * For some arguments of some macros,
2264  * convert all breakable hyphens into ASCII_HYPH.
2265  */
2266 static void
2267 post_hyph(POST_ARGS)
2268 {
2269 	struct roff_node	*n, *nch;
2270 	char			*cp;
2271 
2272 	n = mdoc->last;
2273 	for (nch = n->child; nch != NULL; nch = nch->next) {
2274 		if (nch->type != ROFFT_TEXT)
2275 			continue;
2276 		cp = nch->string;
2277 		if (*cp == '\0')
2278 			continue;
2279 		while (*(++cp) != '\0')
2280 			if (*cp == '-' &&
2281 			    isalpha((unsigned char)cp[-1]) &&
2282 			    isalpha((unsigned char)cp[1])) {
2283 				if (n->tag == NULL && n->flags & NODE_ID)
2284 					n->tag = mandoc_strdup(nch->string);
2285 				*cp = ASCII_HYPH;
2286 			}
2287 	}
2288 }
2289 
2290 static void
2291 post_ns(POST_ARGS)
2292 {
2293 	struct roff_node	*n;
2294 
2295 	n = mdoc->last;
2296 	if (n->flags & NODE_LINE ||
2297 	    (n->next != NULL && n->next->flags & NODE_DELIMC))
2298 		mandoc_msg(MANDOCERR_NS_SKIP, n->line, n->pos, NULL);
2299 }
2300 
2301 static void
2302 post_sx(POST_ARGS)
2303 {
2304 	post_delim(mdoc);
2305 	post_hyph(mdoc);
2306 }
2307 
2308 static void
2309 post_sh(POST_ARGS)
2310 {
2311 	post_section(mdoc);
2312 
2313 	switch (mdoc->last->type) {
2314 	case ROFFT_HEAD:
2315 		post_sh_head(mdoc);
2316 		break;
2317 	case ROFFT_BODY:
2318 		switch (mdoc->lastsec)  {
2319 		case SEC_NAME:
2320 			post_sh_name(mdoc);
2321 			break;
2322 		case SEC_SEE_ALSO:
2323 			post_sh_see_also(mdoc);
2324 			break;
2325 		case SEC_AUTHORS:
2326 			post_sh_authors(mdoc);
2327 			break;
2328 		default:
2329 			break;
2330 		}
2331 		break;
2332 	default:
2333 		break;
2334 	}
2335 }
2336 
2337 static void
2338 post_sh_name(POST_ARGS)
2339 {
2340 	struct roff_node *n;
2341 	int hasnm, hasnd;
2342 
2343 	hasnm = hasnd = 0;
2344 
2345 	for (n = mdoc->last->child; n != NULL; n = n->next) {
2346 		switch (n->tok) {
2347 		case MDOC_Nm:
2348 			if (hasnm && n->child != NULL)
2349 				mandoc_msg(MANDOCERR_NAMESEC_PUNCT,
2350 				    n->line, n->pos,
2351 				    "Nm %s", n->child->string);
2352 			hasnm = 1;
2353 			continue;
2354 		case MDOC_Nd:
2355 			hasnd = 1;
2356 			if (n->next != NULL)
2357 				mandoc_msg(MANDOCERR_NAMESEC_ND,
2358 				    n->line, n->pos, NULL);
2359 			break;
2360 		case TOKEN_NONE:
2361 			if (n->type == ROFFT_TEXT &&
2362 			    n->string[0] == ',' && n->string[1] == '\0' &&
2363 			    n->next != NULL && n->next->tok == MDOC_Nm) {
2364 				n = n->next;
2365 				continue;
2366 			}
2367 			/* FALLTHROUGH */
2368 		default:
2369 			mandoc_msg(MANDOCERR_NAMESEC_BAD,
2370 			    n->line, n->pos, "%s", roff_name[n->tok]);
2371 			continue;
2372 		}
2373 		break;
2374 	}
2375 
2376 	if ( ! hasnm)
2377 		mandoc_msg(MANDOCERR_NAMESEC_NONM,
2378 		    mdoc->last->line, mdoc->last->pos, NULL);
2379 	if ( ! hasnd)
2380 		mandoc_msg(MANDOCERR_NAMESEC_NOND,
2381 		    mdoc->last->line, mdoc->last->pos, NULL);
2382 }
2383 
2384 static void
2385 post_sh_see_also(POST_ARGS)
2386 {
2387 	const struct roff_node	*n;
2388 	const char		*name, *sec;
2389 	const char		*lastname, *lastsec, *lastpunct;
2390 	int			 cmp;
2391 
2392 	n = mdoc->last->child;
2393 	lastname = lastsec = lastpunct = NULL;
2394 	while (n != NULL) {
2395 		if (n->tok != MDOC_Xr ||
2396 		    n->child == NULL ||
2397 		    n->child->next == NULL)
2398 			break;
2399 
2400 		/* Process one .Xr node. */
2401 
2402 		name = n->child->string;
2403 		sec = n->child->next->string;
2404 		if (lastsec != NULL) {
2405 			if (lastpunct[0] != ',' || lastpunct[1] != '\0')
2406 				mandoc_msg(MANDOCERR_XR_PUNCT, n->line,
2407 				    n->pos, "%s before %s(%s)",
2408 				    lastpunct, name, sec);
2409 			cmp = strcmp(lastsec, sec);
2410 			if (cmp > 0)
2411 				mandoc_msg(MANDOCERR_XR_ORDER, n->line,
2412 				    n->pos, "%s(%s) after %s(%s)",
2413 				    name, sec, lastname, lastsec);
2414 			else if (cmp == 0 &&
2415 			    strcasecmp(lastname, name) > 0)
2416 				mandoc_msg(MANDOCERR_XR_ORDER, n->line,
2417 				    n->pos, "%s after %s", name, lastname);
2418 		}
2419 		lastname = name;
2420 		lastsec = sec;
2421 
2422 		/* Process the following node. */
2423 
2424 		n = n->next;
2425 		if (n == NULL)
2426 			break;
2427 		if (n->tok == MDOC_Xr) {
2428 			lastpunct = "none";
2429 			continue;
2430 		}
2431 		if (n->type != ROFFT_TEXT)
2432 			break;
2433 		for (name = n->string; *name != '\0'; name++)
2434 			if (isalpha((const unsigned char)*name))
2435 				return;
2436 		lastpunct = n->string;
2437 		if (n->next == NULL || n->next->tok == MDOC_Rs)
2438 			mandoc_msg(MANDOCERR_XR_PUNCT, n->line,
2439 			    n->pos, "%s after %s(%s)",
2440 			    lastpunct, lastname, lastsec);
2441 		n = n->next;
2442 	}
2443 }
2444 
2445 static int
2446 child_an(const struct roff_node *n)
2447 {
2448 
2449 	for (n = n->child; n != NULL; n = n->next)
2450 		if ((n->tok == MDOC_An && n->child != NULL) || child_an(n))
2451 			return 1;
2452 	return 0;
2453 }
2454 
2455 static void
2456 post_sh_authors(POST_ARGS)
2457 {
2458 
2459 	if ( ! child_an(mdoc->last))
2460 		mandoc_msg(MANDOCERR_AN_MISSING,
2461 		    mdoc->last->line, mdoc->last->pos, NULL);
2462 }
2463 
2464 /*
2465  * Return an upper bound for the string distance (allowing
2466  * transpositions).  Not a full Levenshtein implementation
2467  * because Levenshtein is quadratic in the string length
2468  * and this function is called for every standard name,
2469  * so the check for each custom name would be cubic.
2470  * The following crude heuristics is linear, resulting
2471  * in quadratic behaviour for checking one custom name,
2472  * which does not cause measurable slowdown.
2473  */
2474 static int
2475 similar(const char *s1, const char *s2)
2476 {
2477 	const int	maxdist = 3;
2478 	int		dist = 0;
2479 
2480 	while (s1[0] != '\0' && s2[0] != '\0') {
2481 		if (s1[0] == s2[0]) {
2482 			s1++;
2483 			s2++;
2484 			continue;
2485 		}
2486 		if (++dist > maxdist)
2487 			return INT_MAX;
2488 		if (s1[1] == s2[1]) {  /* replacement */
2489 			s1++;
2490 			s2++;
2491 		} else if (s1[0] == s2[1] && s1[1] == s2[0]) {
2492 			s1 += 2;	/* transposition */
2493 			s2 += 2;
2494 		} else if (s1[0] == s2[1])  /* insertion */
2495 			s2++;
2496 		else if (s1[1] == s2[0])  /* deletion */
2497 			s1++;
2498 		else
2499 			return INT_MAX;
2500 	}
2501 	dist += strlen(s1) + strlen(s2);
2502 	return dist > maxdist ? INT_MAX : dist;
2503 }
2504 
2505 static void
2506 post_sh_head(POST_ARGS)
2507 {
2508 	struct roff_node	*nch;
2509 	const char		*goodsec;
2510 	const char *const	*testsec;
2511 	int			 dist, mindist;
2512 	enum roff_sec		 sec;
2513 
2514 	/*
2515 	 * Process a new section.  Sections are either "named" or
2516 	 * "custom".  Custom sections are user-defined, while named ones
2517 	 * follow a conventional order and may only appear in certain
2518 	 * manual sections.
2519 	 */
2520 
2521 	sec = mdoc->last->sec;
2522 
2523 	/* The NAME should be first. */
2524 
2525 	if (sec != SEC_NAME && mdoc->lastnamed == SEC_NONE)
2526 		mandoc_msg(MANDOCERR_NAMESEC_FIRST,
2527 		    mdoc->last->line, mdoc->last->pos, "Sh %s",
2528 		    sec != SEC_CUSTOM ? secnames[sec] :
2529 		    (nch = mdoc->last->child) == NULL ? "" :
2530 		    nch->type == ROFFT_TEXT ? nch->string :
2531 		    roff_name[nch->tok]);
2532 
2533 	/* The SYNOPSIS gets special attention in other areas. */
2534 
2535 	if (sec == SEC_SYNOPSIS) {
2536 		roff_setreg(mdoc->roff, "nS", 1, '=');
2537 		mdoc->flags |= MDOC_SYNOPSIS;
2538 	} else {
2539 		roff_setreg(mdoc->roff, "nS", 0, '=');
2540 		mdoc->flags &= ~MDOC_SYNOPSIS;
2541 	}
2542 	if (sec == SEC_DESCRIPTION)
2543 		fn_prio = TAG_STRONG;
2544 
2545 	/* Mark our last section. */
2546 
2547 	mdoc->lastsec = sec;
2548 
2549 	/* We don't care about custom sections after this. */
2550 
2551 	if (sec == SEC_CUSTOM) {
2552 		if ((nch = mdoc->last->child) == NULL ||
2553 		    nch->type != ROFFT_TEXT || nch->next != NULL)
2554 			return;
2555 		goodsec = NULL;
2556 		mindist = INT_MAX;
2557 		for (testsec = secnames + 1; *testsec != NULL; testsec++) {
2558 			dist = similar(nch->string, *testsec);
2559 			if (dist < mindist) {
2560 				goodsec = *testsec;
2561 				mindist = dist;
2562 			}
2563 		}
2564 		if (goodsec != NULL)
2565 			mandoc_msg(MANDOCERR_SEC_TYPO, nch->line, nch->pos,
2566 			    "Sh %s instead of %s", nch->string, goodsec);
2567 		return;
2568 	}
2569 
2570 	/*
2571 	 * Check whether our non-custom section is being repeated or is
2572 	 * out of order.
2573 	 */
2574 
2575 	if (sec == mdoc->lastnamed)
2576 		mandoc_msg(MANDOCERR_SEC_REP, mdoc->last->line,
2577 		    mdoc->last->pos, "Sh %s", secnames[sec]);
2578 
2579 	if (sec < mdoc->lastnamed)
2580 		mandoc_msg(MANDOCERR_SEC_ORDER, mdoc->last->line,
2581 		    mdoc->last->pos, "Sh %s", secnames[sec]);
2582 
2583 	/* Mark the last named section. */
2584 
2585 	mdoc->lastnamed = sec;
2586 
2587 	/* Check particular section/manual conventions. */
2588 
2589 	if (mdoc->meta.msec == NULL)
2590 		return;
2591 
2592 	goodsec = NULL;
2593 	switch (sec) {
2594 	case SEC_ERRORS:
2595 		if (*mdoc->meta.msec == '4')
2596 			break;
2597 		goodsec = "2, 3, 4, 9";
2598 		/* FALLTHROUGH */
2599 	case SEC_RETURN_VALUES:
2600 	case SEC_LIBRARY:
2601 		if (*mdoc->meta.msec == '2')
2602 			break;
2603 		if (*mdoc->meta.msec == '3')
2604 			break;
2605 		if (NULL == goodsec)
2606 			goodsec = "2, 3, 9";
2607 		/* FALLTHROUGH */
2608 	case SEC_CONTEXT:
2609 		if (*mdoc->meta.msec == '9')
2610 			break;
2611 		if (NULL == goodsec)
2612 			goodsec = "9";
2613 		mandoc_msg(MANDOCERR_SEC_MSEC,
2614 		    mdoc->last->line, mdoc->last->pos,
2615 		    "Sh %s for %s only", secnames[sec], goodsec);
2616 		break;
2617 	default:
2618 		break;
2619 	}
2620 }
2621 
2622 static void
2623 post_xr(POST_ARGS)
2624 {
2625 	struct roff_node *n, *nch;
2626 
2627 	n = mdoc->last;
2628 	nch = n->child;
2629 	if (nch->next == NULL) {
2630 		mandoc_msg(MANDOCERR_XR_NOSEC,
2631 		    n->line, n->pos, "Xr %s", nch->string);
2632 	} else {
2633 		assert(nch->next == n->last);
2634 		if(mandoc_xr_add(nch->next->string, nch->string,
2635 		    nch->line, nch->pos))
2636 			mandoc_msg(MANDOCERR_XR_SELF,
2637 			    nch->line, nch->pos, "Xr %s %s",
2638 			    nch->string, nch->next->string);
2639 	}
2640 	post_delim_nb(mdoc);
2641 }
2642 
2643 static void
2644 post_section(POST_ARGS)
2645 {
2646 	struct roff_node *n, *nch;
2647 	char		 *cp, *tag;
2648 
2649 	n = mdoc->last;
2650 	switch (n->type) {
2651 	case ROFFT_BLOCK:
2652 		post_prevpar(mdoc);
2653 		return;
2654 	case ROFFT_HEAD:
2655 		tag = NULL;
2656 		deroff(&tag, n);
2657 		if (tag != NULL) {
2658 			for (cp = tag; *cp != '\0'; cp++)
2659 				if (*cp == ' ')
2660 					*cp = '_';
2661 			if ((nch = n->child) != NULL &&
2662 			    nch->type == ROFFT_TEXT &&
2663 			    strcmp(nch->string, tag) == 0)
2664 				tag_put(NULL, TAG_STRONG, n);
2665 			else
2666 				tag_put(tag, TAG_FALLBACK, n);
2667 			free(tag);
2668 		}
2669 		post_delim(mdoc);
2670 		post_hyph(mdoc);
2671 		return;
2672 	case ROFFT_BODY:
2673 		break;
2674 	default:
2675 		return;
2676 	}
2677 	if ((nch = n->child) != NULL &&
2678 	    (nch->tok == MDOC_Pp || nch->tok == ROFF_br ||
2679 	     nch->tok == ROFF_sp)) {
2680 		mandoc_msg(MANDOCERR_PAR_SKIP, nch->line, nch->pos,
2681 		    "%s after %s", roff_name[nch->tok],
2682 		    roff_name[n->tok]);
2683 		roff_node_delete(mdoc, nch);
2684 	}
2685 	if ((nch = n->last) != NULL &&
2686 	    (nch->tok == MDOC_Pp || nch->tok == ROFF_br)) {
2687 		mandoc_msg(MANDOCERR_PAR_SKIP, nch->line, nch->pos,
2688 		    "%s at the end of %s", roff_name[nch->tok],
2689 		    roff_name[n->tok]);
2690 		roff_node_delete(mdoc, nch);
2691 	}
2692 }
2693 
2694 static void
2695 post_prevpar(POST_ARGS)
2696 {
2697 	struct roff_node *n, *np;
2698 
2699 	n = mdoc->last;
2700 	if (n->type != ROFFT_ELEM && n->type != ROFFT_BLOCK)
2701 		return;
2702 	if ((np = roff_node_prev(n)) == NULL)
2703 		return;
2704 
2705 	/*
2706 	 * Don't allow `Pp' prior to a paragraph-type
2707 	 * block: `Pp' or non-compact `Bd' or `Bl'.
2708 	 */
2709 
2710 	if (np->tok != MDOC_Pp && np->tok != ROFF_br)
2711 		return;
2712 	if (n->tok == MDOC_Bl && n->norm->Bl.comp)
2713 		return;
2714 	if (n->tok == MDOC_Bd && n->norm->Bd.comp)
2715 		return;
2716 	if (n->tok == MDOC_It && n->parent->norm->Bl.comp)
2717 		return;
2718 
2719 	mandoc_msg(MANDOCERR_PAR_SKIP, np->line, np->pos,
2720 	    "%s before %s", roff_name[np->tok], roff_name[n->tok]);
2721 	roff_node_delete(mdoc, np);
2722 }
2723 
2724 static void
2725 post_par(POST_ARGS)
2726 {
2727 	struct roff_node *np;
2728 
2729 	fn_prio = TAG_STRONG;
2730 	post_prevpar(mdoc);
2731 
2732 	np = mdoc->last;
2733 	if (np->child != NULL)
2734 		mandoc_msg(MANDOCERR_ARG_SKIP, np->line, np->pos,
2735 		    "%s %s", roff_name[np->tok], np->child->string);
2736 }
2737 
2738 static void
2739 post_dd(POST_ARGS)
2740 {
2741 	struct roff_node *n;
2742 
2743 	n = mdoc->last;
2744 	n->flags |= NODE_NOPRT;
2745 
2746 	if (mdoc->meta.date != NULL) {
2747 		mandoc_msg(MANDOCERR_PROLOG_REP, n->line, n->pos, "Dd");
2748 		free(mdoc->meta.date);
2749 	} else if (mdoc->flags & MDOC_PBODY)
2750 		mandoc_msg(MANDOCERR_PROLOG_LATE, n->line, n->pos, "Dd");
2751 	else if (mdoc->meta.title != NULL)
2752 		mandoc_msg(MANDOCERR_PROLOG_ORDER,
2753 		    n->line, n->pos, "Dd after Dt");
2754 	else if (mdoc->meta.os != NULL)
2755 		mandoc_msg(MANDOCERR_PROLOG_ORDER,
2756 		    n->line, n->pos, "Dd after Os");
2757 
2758 	if (mdoc->quick && n != NULL)
2759 		mdoc->meta.date = mandoc_strdup("");
2760 	else
2761 		mdoc->meta.date = mandoc_normdate(n->child, n);
2762 }
2763 
2764 static void
2765 post_dt(POST_ARGS)
2766 {
2767 	struct roff_node *nn, *n;
2768 	const char	 *cp;
2769 	char		 *p;
2770 
2771 	n = mdoc->last;
2772 	n->flags |= NODE_NOPRT;
2773 
2774 	if (mdoc->flags & MDOC_PBODY) {
2775 		mandoc_msg(MANDOCERR_DT_LATE, n->line, n->pos, "Dt");
2776 		return;
2777 	}
2778 
2779 	if (mdoc->meta.title != NULL)
2780 		mandoc_msg(MANDOCERR_PROLOG_REP, n->line, n->pos, "Dt");
2781 	else if (mdoc->meta.os != NULL)
2782 		mandoc_msg(MANDOCERR_PROLOG_ORDER,
2783 		    n->line, n->pos, "Dt after Os");
2784 
2785 	free(mdoc->meta.title);
2786 	free(mdoc->meta.msec);
2787 	free(mdoc->meta.vol);
2788 	free(mdoc->meta.arch);
2789 
2790 	mdoc->meta.title = NULL;
2791 	mdoc->meta.msec = NULL;
2792 	mdoc->meta.vol = NULL;
2793 	mdoc->meta.arch = NULL;
2794 
2795 	/* Mandatory first argument: title. */
2796 
2797 	nn = n->child;
2798 	if (nn == NULL || *nn->string == '\0') {
2799 		mandoc_msg(MANDOCERR_DT_NOTITLE, n->line, n->pos, "Dt");
2800 		mdoc->meta.title = mandoc_strdup("UNTITLED");
2801 	} else {
2802 		mdoc->meta.title = mandoc_strdup(nn->string);
2803 
2804 		/* Check that all characters are uppercase. */
2805 
2806 		for (p = nn->string; *p != '\0'; p++)
2807 			if (islower((unsigned char)*p)) {
2808 				mandoc_msg(MANDOCERR_TITLE_CASE, nn->line,
2809 				    nn->pos + (int)(p - nn->string),
2810 				    "Dt %s", nn->string);
2811 				break;
2812 			}
2813 	}
2814 
2815 	/* Mandatory second argument: section. */
2816 
2817 	if (nn != NULL)
2818 		nn = nn->next;
2819 
2820 	if (nn == NULL) {
2821 		mandoc_msg(MANDOCERR_MSEC_MISSING, n->line, n->pos,
2822 		    "Dt %s", mdoc->meta.title);
2823 		mdoc->meta.vol = mandoc_strdup("LOCAL");
2824 		return;  /* msec and arch remain NULL. */
2825 	}
2826 
2827 	mdoc->meta.msec = mandoc_strdup(nn->string);
2828 
2829 	/* Infer volume title from section number. */
2830 
2831 	cp = mandoc_a2msec(nn->string);
2832 	if (cp == NULL) {
2833 		mandoc_msg(MANDOCERR_MSEC_BAD,
2834 		    nn->line, nn->pos, "Dt ... %s", nn->string);
2835 		mdoc->meta.vol = mandoc_strdup(nn->string);
2836 	} else {
2837 		mdoc->meta.vol = mandoc_strdup(cp);
2838 		if (mdoc->filesec != '\0' &&
2839 		    mdoc->filesec != *nn->string &&
2840 		    *nn->string >= '1' && *nn->string <= '9')
2841 			mandoc_msg(MANDOCERR_MSEC_FILE, nn->line, nn->pos,
2842 			    "*.%c vs Dt ... %c", mdoc->filesec, *nn->string);
2843 	}
2844 
2845 	/* Optional third argument: architecture. */
2846 
2847 	if ((nn = nn->next) == NULL)
2848 		return;
2849 
2850 	for (p = nn->string; *p != '\0'; p++)
2851 		*p = tolower((unsigned char)*p);
2852 	mdoc->meta.arch = mandoc_strdup(nn->string);
2853 
2854 	/* Ignore fourth and later arguments. */
2855 
2856 	if ((nn = nn->next) != NULL)
2857 		mandoc_msg(MANDOCERR_ARG_EXCESS,
2858 		    nn->line, nn->pos, "Dt ... %s", nn->string);
2859 }
2860 
2861 static void
2862 post_bx(POST_ARGS)
2863 {
2864 	struct roff_node	*n, *nch;
2865 	const char		*macro;
2866 
2867 	post_delim_nb(mdoc);
2868 
2869 	n = mdoc->last;
2870 	nch = n->child;
2871 
2872 	if (nch != NULL) {
2873 		macro = !strcmp(nch->string, "Open") ? "Ox" :
2874 		    !strcmp(nch->string, "Net") ? "Nx" :
2875 		    !strcmp(nch->string, "Free") ? "Fx" :
2876 		    !strcmp(nch->string, "DragonFly") ? "Dx" : NULL;
2877 		if (macro != NULL)
2878 			mandoc_msg(MANDOCERR_BX,
2879 			    n->line, n->pos, "%s", macro);
2880 		mdoc->last = nch;
2881 		nch = nch->next;
2882 		mdoc->next = ROFF_NEXT_SIBLING;
2883 		roff_elem_alloc(mdoc, n->line, n->pos, MDOC_Ns);
2884 		mdoc->last->flags |= NODE_NOSRC;
2885 		mdoc->next = ROFF_NEXT_SIBLING;
2886 	} else
2887 		mdoc->next = ROFF_NEXT_CHILD;
2888 	roff_word_alloc(mdoc, n->line, n->pos, "BSD");
2889 	mdoc->last->flags |= NODE_NOSRC;
2890 
2891 	if (nch == NULL) {
2892 		mdoc->last = n;
2893 		return;
2894 	}
2895 
2896 	roff_elem_alloc(mdoc, n->line, n->pos, MDOC_Ns);
2897 	mdoc->last->flags |= NODE_NOSRC;
2898 	mdoc->next = ROFF_NEXT_SIBLING;
2899 	roff_word_alloc(mdoc, n->line, n->pos, "-");
2900 	mdoc->last->flags |= NODE_NOSRC;
2901 	roff_elem_alloc(mdoc, n->line, n->pos, MDOC_Ns);
2902 	mdoc->last->flags |= NODE_NOSRC;
2903 	mdoc->last = n;
2904 
2905 	/*
2906 	 * Make `Bx's second argument always start with an uppercase
2907 	 * letter.  Groff checks if it's an "accepted" term, but we just
2908 	 * uppercase blindly.
2909 	 */
2910 
2911 	*nch->string = (char)toupper((unsigned char)*nch->string);
2912 }
2913 
2914 static void
2915 post_os(POST_ARGS)
2916 {
2917 #ifndef OSNAME
2918 	struct utsname	  utsname;
2919 	static char	 *defbuf;
2920 #endif
2921 	struct roff_node *n;
2922 
2923 	n = mdoc->last;
2924 	n->flags |= NODE_NOPRT;
2925 
2926 	if (mdoc->meta.os != NULL)
2927 		mandoc_msg(MANDOCERR_PROLOG_REP, n->line, n->pos, "Os");
2928 	else if (mdoc->flags & MDOC_PBODY)
2929 		mandoc_msg(MANDOCERR_PROLOG_LATE, n->line, n->pos, "Os");
2930 
2931 	post_delim(mdoc);
2932 
2933 	/*
2934 	 * Set the operating system by way of the `Os' macro.
2935 	 * The order of precedence is:
2936 	 * 1. the argument of the `Os' macro, unless empty
2937 	 * 2. the -Ios=foo command line argument, if provided
2938 	 * 3. -DOSNAME="\"foo\"", if provided during compilation
2939 	 * 4. "sysname release" from uname(3)
2940 	 */
2941 
2942 	free(mdoc->meta.os);
2943 	mdoc->meta.os = NULL;
2944 	deroff(&mdoc->meta.os, n);
2945 	if (mdoc->meta.os)
2946 		goto out;
2947 
2948 	if (mdoc->os_s != NULL) {
2949 		mdoc->meta.os = mandoc_strdup(mdoc->os_s);
2950 		goto out;
2951 	}
2952 
2953 #ifdef OSNAME
2954 	mdoc->meta.os = mandoc_strdup(OSNAME);
2955 #else /*!OSNAME */
2956 	if (defbuf == NULL) {
2957 		if (uname(&utsname) == -1) {
2958 			mandoc_msg(MANDOCERR_OS_UNAME, n->line, n->pos, "Os");
2959 			defbuf = mandoc_strdup("UNKNOWN");
2960 		} else
2961 			mandoc_asprintf(&defbuf, "%s %s",
2962 			    utsname.sysname, utsname.release);
2963 	}
2964 	mdoc->meta.os = mandoc_strdup(defbuf);
2965 #endif /*!OSNAME*/
2966 
2967 out:
2968 	if (mdoc->meta.os_e == MANDOC_OS_OTHER) {
2969 		if (strstr(mdoc->meta.os, "OpenBSD") != NULL)
2970 			mdoc->meta.os_e = MANDOC_OS_OPENBSD;
2971 		else if (strstr(mdoc->meta.os, "NetBSD") != NULL)
2972 			mdoc->meta.os_e = MANDOC_OS_NETBSD;
2973 	}
2974 
2975 	/*
2976 	 * This is the earliest point where we can check
2977 	 * Mdocdate conventions because we don't know
2978 	 * the operating system earlier.
2979 	 */
2980 
2981 	if (n->child != NULL)
2982 		mandoc_msg(MANDOCERR_OS_ARG, n->child->line, n->child->pos,
2983 		    "Os %s (%s)", n->child->string,
2984 		    mdoc->meta.os_e == MANDOC_OS_OPENBSD ?
2985 		    "OpenBSD" : "NetBSD");
2986 
2987 	while (n->tok != MDOC_Dd)
2988 		if ((n = n->prev) == NULL)
2989 			return;
2990 	if ((n = n->child) == NULL)
2991 		return;
2992 	if (strncmp(n->string, "$" "Mdocdate", 9)) {
2993 		if (mdoc->meta.os_e == MANDOC_OS_OPENBSD)
2994 			mandoc_msg(MANDOCERR_MDOCDATE_MISSING, n->line,
2995 			    n->pos, "Dd %s (OpenBSD)", n->string);
2996 	} else {
2997 		if (mdoc->meta.os_e == MANDOC_OS_NETBSD)
2998 			mandoc_msg(MANDOCERR_MDOCDATE, n->line,
2999 			    n->pos, "Dd %s (NetBSD)", n->string);
3000 	}
3001 }
3002 
3003 enum roff_sec
3004 mdoc_a2sec(const char *p)
3005 {
3006 	int		 i;
3007 
3008 	for (i = 0; i < (int)SEC__MAX; i++)
3009 		if (secnames[i] && 0 == strcmp(p, secnames[i]))
3010 			return (enum roff_sec)i;
3011 
3012 	return SEC_CUSTOM;
3013 }
3014 
3015 static size_t
3016 macro2len(enum roff_tok macro)
3017 {
3018 
3019 	switch (macro) {
3020 	case MDOC_Ad:
3021 		return 12;
3022 	case MDOC_Ao:
3023 		return 12;
3024 	case MDOC_An:
3025 		return 12;
3026 	case MDOC_Aq:
3027 		return 12;
3028 	case MDOC_Ar:
3029 		return 12;
3030 	case MDOC_Bo:
3031 		return 12;
3032 	case MDOC_Bq:
3033 		return 12;
3034 	case MDOC_Cd:
3035 		return 12;
3036 	case MDOC_Cm:
3037 		return 10;
3038 	case MDOC_Do:
3039 		return 10;
3040 	case MDOC_Dq:
3041 		return 12;
3042 	case MDOC_Dv:
3043 		return 12;
3044 	case MDOC_Eo:
3045 		return 12;
3046 	case MDOC_Em:
3047 		return 10;
3048 	case MDOC_Er:
3049 		return 17;
3050 	case MDOC_Ev:
3051 		return 15;
3052 	case MDOC_Fa:
3053 		return 12;
3054 	case MDOC_Fl:
3055 		return 10;
3056 	case MDOC_Fo:
3057 		return 16;
3058 	case MDOC_Fn:
3059 		return 16;
3060 	case MDOC_Ic:
3061 		return 10;
3062 	case MDOC_Li:
3063 		return 16;
3064 	case MDOC_Ms:
3065 		return 6;
3066 	case MDOC_Nm:
3067 		return 10;
3068 	case MDOC_No:
3069 		return 12;
3070 	case MDOC_Oo:
3071 		return 10;
3072 	case MDOC_Op:
3073 		return 14;
3074 	case MDOC_Pa:
3075 		return 32;
3076 	case MDOC_Pf:
3077 		return 12;
3078 	case MDOC_Po:
3079 		return 12;
3080 	case MDOC_Pq:
3081 		return 12;
3082 	case MDOC_Ql:
3083 		return 16;
3084 	case MDOC_Qo:
3085 		return 12;
3086 	case MDOC_So:
3087 		return 12;
3088 	case MDOC_Sq:
3089 		return 12;
3090 	case MDOC_Sy:
3091 		return 6;
3092 	case MDOC_Sx:
3093 		return 16;
3094 	case MDOC_Tn:
3095 		return 10;
3096 	case MDOC_Va:
3097 		return 12;
3098 	case MDOC_Vt:
3099 		return 12;
3100 	case MDOC_Xr:
3101 		return 10;
3102 	default:
3103 		break;
3104 	};
3105 	return 0;
3106 }
3107