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