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