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