1294113Sbapt/*	$Id: mdoc_validate.c,v 1.301 2016/01/08 17:48:09 schwarze Exp $ */
2241675Suqs/*
3261344Suqs * Copyright (c) 2008-2012 Kristaps Dzonsons <kristaps@bsd.lv>
4294113Sbapt * Copyright (c) 2010-2016 Ingo Schwarze <schwarze@openbsd.org>
5274880Sbapt * Copyright (c) 2010 Joerg Sonnenberger <joerg@netbsd.org>
6241675Suqs *
7241675Suqs * Permission to use, copy, modify, and distribute this software for any
8241675Suqs * purpose with or without fee is hereby granted, provided that the above
9241675Suqs * copyright notice and this permission notice appear in all copies.
10241675Suqs *
11294113Sbapt * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHORS DISCLAIM ALL WARRANTIES
12241675Suqs * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
13294113Sbapt * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR
14241675Suqs * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
15241675Suqs * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
16241675Suqs * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
17241675Suqs * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
18241675Suqs */
19241675Suqs#include "config.h"
20241675Suqs
21275432Sbapt#include <sys/types.h>
22261344Suqs#ifndef OSNAME
23241675Suqs#include <sys/utsname.h>
24241675Suqs#endif
25241675Suqs
26241675Suqs#include <assert.h>
27241675Suqs#include <ctype.h>
28241675Suqs#include <limits.h>
29241675Suqs#include <stdio.h>
30241675Suqs#include <stdlib.h>
31241675Suqs#include <string.h>
32241675Suqs#include <time.h>
33241675Suqs
34294113Sbapt#include "mandoc_aux.h"
35294113Sbapt#include "mandoc.h"
36294113Sbapt#include "roff.h"
37241675Suqs#include "mdoc.h"
38294113Sbapt#include "libmandoc.h"
39294113Sbapt#include "roff_int.h"
40241675Suqs#include "libmdoc.h"
41241675Suqs
42241675Suqs/* FIXME: .Bl -diag can't have non-text children in HEAD. */
43241675Suqs
44294113Sbapt#define	POST_ARGS struct roff_man *mdoc
45241675Suqs
46241675Suqsenum	check_ineq {
47241675Suqs	CHECK_LT,
48241675Suqs	CHECK_GT,
49241675Suqs	CHECK_EQ
50241675Suqs};
51241675Suqs
52275432Sbapttypedef	void	(*v_post)(POST_ARGS);
53241675Suqs
54294113Sbaptstatic	void	 check_text(struct roff_man *, int, int, char *);
55294113Sbaptstatic	void	 check_argv(struct roff_man *,
56294113Sbapt			struct roff_node *, struct mdoc_argv *);
57294113Sbaptstatic	void	 check_args(struct roff_man *, struct roff_node *);
58294113Sbaptstatic	int	 child_an(const struct roff_node *);
59294113Sbaptstatic	size_t		macro2len(int);
60275432Sbaptstatic	void	 rewrite_macro2len(char **);
61241675Suqs
62275432Sbaptstatic	void	 post_an(POST_ARGS);
63294113Sbaptstatic	void	 post_an_norm(POST_ARGS);
64275432Sbaptstatic	void	 post_at(POST_ARGS);
65294113Sbaptstatic	void	 post_bd(POST_ARGS);
66275432Sbaptstatic	void	 post_bf(POST_ARGS);
67275432Sbaptstatic	void	 post_bk(POST_ARGS);
68275432Sbaptstatic	void	 post_bl(POST_ARGS);
69275432Sbaptstatic	void	 post_bl_block(POST_ARGS);
70275432Sbaptstatic	void	 post_bl_block_tag(POST_ARGS);
71275432Sbaptstatic	void	 post_bl_head(POST_ARGS);
72294113Sbaptstatic	void	 post_bl_norm(POST_ARGS);
73275432Sbaptstatic	void	 post_bx(POST_ARGS);
74275432Sbaptstatic	void	 post_defaults(POST_ARGS);
75294113Sbaptstatic	void	 post_display(POST_ARGS);
76275432Sbaptstatic	void	 post_dd(POST_ARGS);
77275432Sbaptstatic	void	 post_dt(POST_ARGS);
78275432Sbaptstatic	void	 post_en(POST_ARGS);
79275432Sbaptstatic	void	 post_es(POST_ARGS);
80275432Sbaptstatic	void	 post_eoln(POST_ARGS);
81275432Sbaptstatic	void	 post_ex(POST_ARGS);
82275432Sbaptstatic	void	 post_fa(POST_ARGS);
83275432Sbaptstatic	void	 post_fn(POST_ARGS);
84275432Sbaptstatic	void	 post_fname(POST_ARGS);
85275432Sbaptstatic	void	 post_fo(POST_ARGS);
86275432Sbaptstatic	void	 post_hyph(POST_ARGS);
87275432Sbaptstatic	void	 post_ignpar(POST_ARGS);
88275432Sbaptstatic	void	 post_it(POST_ARGS);
89275432Sbaptstatic	void	 post_lb(POST_ARGS);
90275432Sbaptstatic	void	 post_nd(POST_ARGS);
91275432Sbaptstatic	void	 post_nm(POST_ARGS);
92275432Sbaptstatic	void	 post_ns(POST_ARGS);
93294113Sbaptstatic	void	 post_obsolete(POST_ARGS);
94275432Sbaptstatic	void	 post_os(POST_ARGS);
95275432Sbaptstatic	void	 post_par(POST_ARGS);
96294113Sbaptstatic	void	 post_prevpar(POST_ARGS);
97275432Sbaptstatic	void	 post_root(POST_ARGS);
98275432Sbaptstatic	void	 post_rs(POST_ARGS);
99275432Sbaptstatic	void	 post_sh(POST_ARGS);
100275432Sbaptstatic	void	 post_sh_head(POST_ARGS);
101275432Sbaptstatic	void	 post_sh_name(POST_ARGS);
102275432Sbaptstatic	void	 post_sh_see_also(POST_ARGS);
103275432Sbaptstatic	void	 post_sh_authors(POST_ARGS);
104275432Sbaptstatic	void	 post_sm(POST_ARGS);
105275432Sbaptstatic	void	 post_st(POST_ARGS);
106294113Sbaptstatic	void	 post_std(POST_ARGS);
107241675Suqs
108294113Sbaptstatic	v_post mdoc_valids[MDOC_MAX] = {
109294113Sbapt	NULL,		/* Ap */
110294113Sbapt	post_dd,	/* Dd */
111294113Sbapt	post_dt,	/* Dt */
112294113Sbapt	post_os,	/* Os */
113294113Sbapt	post_sh,	/* Sh */
114294113Sbapt	post_ignpar,	/* Ss */
115294113Sbapt	post_par,	/* Pp */
116294113Sbapt	post_display,	/* D1 */
117294113Sbapt	post_display,	/* Dl */
118294113Sbapt	post_display,	/* Bd */
119294113Sbapt	NULL,		/* Ed */
120294113Sbapt	post_bl,	/* Bl */
121294113Sbapt	NULL,		/* El */
122294113Sbapt	post_it,	/* It */
123294113Sbapt	NULL,		/* Ad */
124294113Sbapt	post_an,	/* An */
125294113Sbapt	post_defaults,	/* Ar */
126294113Sbapt	NULL,		/* Cd */
127294113Sbapt	NULL,		/* Cm */
128294113Sbapt	NULL,		/* Dv */
129294113Sbapt	NULL,		/* Er */
130294113Sbapt	NULL,		/* Ev */
131294113Sbapt	post_ex,	/* Ex */
132294113Sbapt	post_fa,	/* Fa */
133294113Sbapt	NULL,		/* Fd */
134294113Sbapt	NULL,		/* Fl */
135294113Sbapt	post_fn,	/* Fn */
136294113Sbapt	NULL,		/* Ft */
137294113Sbapt	NULL,		/* Ic */
138294113Sbapt	NULL,		/* In */
139294113Sbapt	post_defaults,	/* Li */
140294113Sbapt	post_nd,	/* Nd */
141294113Sbapt	post_nm,	/* Nm */
142294113Sbapt	NULL,		/* Op */
143294113Sbapt	post_obsolete,	/* Ot */
144294113Sbapt	post_defaults,	/* Pa */
145294113Sbapt	post_std,	/* Rv */
146294113Sbapt	post_st,	/* St */
147294113Sbapt	NULL,		/* Va */
148294113Sbapt	NULL,		/* Vt */
149294113Sbapt	NULL,		/* Xr */
150294113Sbapt	NULL,		/* %A */
151294113Sbapt	post_hyph,	/* %B */ /* FIXME: can be used outside Rs/Re. */
152294113Sbapt	NULL,		/* %D */
153294113Sbapt	NULL,		/* %I */
154294113Sbapt	NULL,		/* %J */
155294113Sbapt	post_hyph,	/* %N */
156294113Sbapt	post_hyph,	/* %O */
157294113Sbapt	NULL,		/* %P */
158294113Sbapt	post_hyph,	/* %R */
159294113Sbapt	post_hyph,	/* %T */ /* FIXME: can be used outside Rs/Re. */
160294113Sbapt	NULL,		/* %V */
161294113Sbapt	NULL,		/* Ac */
162294113Sbapt	NULL,		/* Ao */
163294113Sbapt	NULL,		/* Aq */
164294113Sbapt	post_at,	/* At */
165294113Sbapt	NULL,		/* Bc */
166294113Sbapt	post_bf,	/* Bf */
167294113Sbapt	NULL,		/* Bo */
168294113Sbapt	NULL,		/* Bq */
169294113Sbapt	NULL,		/* Bsx */
170294113Sbapt	post_bx,	/* Bx */
171294113Sbapt	post_obsolete,	/* Db */
172294113Sbapt	NULL,		/* Dc */
173294113Sbapt	NULL,		/* Do */
174294113Sbapt	NULL,		/* Dq */
175294113Sbapt	NULL,		/* Ec */
176294113Sbapt	NULL,		/* Ef */
177294113Sbapt	NULL,		/* Em */
178294113Sbapt	NULL,		/* Eo */
179294113Sbapt	NULL,		/* Fx */
180294113Sbapt	NULL,		/* Ms */
181294113Sbapt	NULL,		/* No */
182294113Sbapt	post_ns,	/* Ns */
183294113Sbapt	NULL,		/* Nx */
184294113Sbapt	NULL,		/* Ox */
185294113Sbapt	NULL,		/* Pc */
186294113Sbapt	NULL,		/* Pf */
187294113Sbapt	NULL,		/* Po */
188294113Sbapt	NULL,		/* Pq */
189294113Sbapt	NULL,		/* Qc */
190294113Sbapt	NULL,		/* Ql */
191294113Sbapt	NULL,		/* Qo */
192294113Sbapt	NULL,		/* Qq */
193294113Sbapt	NULL,		/* Re */
194294113Sbapt	post_rs,	/* Rs */
195294113Sbapt	NULL,		/* Sc */
196294113Sbapt	NULL,		/* So */
197294113Sbapt	NULL,		/* Sq */
198294113Sbapt	post_sm,	/* Sm */
199294113Sbapt	post_hyph,	/* Sx */
200294113Sbapt	NULL,		/* Sy */
201294113Sbapt	NULL,		/* Tn */
202294113Sbapt	NULL,		/* Ux */
203294113Sbapt	NULL,		/* Xc */
204294113Sbapt	NULL,		/* Xo */
205294113Sbapt	post_fo,	/* Fo */
206294113Sbapt	NULL,		/* Fc */
207294113Sbapt	NULL,		/* Oo */
208294113Sbapt	NULL,		/* Oc */
209294113Sbapt	post_bk,	/* Bk */
210294113Sbapt	NULL,		/* Ek */
211294113Sbapt	post_eoln,	/* Bt */
212294113Sbapt	NULL,		/* Hf */
213294113Sbapt	post_obsolete,	/* Fr */
214294113Sbapt	post_eoln,	/* Ud */
215294113Sbapt	post_lb,	/* Lb */
216294113Sbapt	post_par,	/* Lp */
217294113Sbapt	NULL,		/* Lk */
218294113Sbapt	post_defaults,	/* Mt */
219294113Sbapt	NULL,		/* Brq */
220294113Sbapt	NULL,		/* Bro */
221294113Sbapt	NULL,		/* Brc */
222294113Sbapt	NULL,		/* %C */
223294113Sbapt	post_es,	/* Es */
224294113Sbapt	post_en,	/* En */
225294113Sbapt	NULL,		/* Dx */
226294113Sbapt	NULL,		/* %Q */
227294113Sbapt	post_par,	/* br */
228294113Sbapt	post_par,	/* sp */
229294113Sbapt	NULL,		/* %U */
230294113Sbapt	NULL,		/* Ta */
231294113Sbapt	NULL,		/* ll */
232241675Suqs};
233241675Suqs
234241675Suqs#define	RSORD_MAX 14 /* Number of `Rs' blocks. */
235241675Suqs
236294113Sbaptstatic	const int rsord[RSORD_MAX] = {
237241675Suqs	MDOC__A,
238241675Suqs	MDOC__T,
239241675Suqs	MDOC__B,
240241675Suqs	MDOC__I,
241241675Suqs	MDOC__J,
242241675Suqs	MDOC__R,
243241675Suqs	MDOC__N,
244241675Suqs	MDOC__V,
245261344Suqs	MDOC__U,
246241675Suqs	MDOC__P,
247241675Suqs	MDOC__Q,
248261344Suqs	MDOC__C,
249241675Suqs	MDOC__D,
250261344Suqs	MDOC__O
251241675Suqs};
252241675Suqs
253241675Suqsstatic	const char * const secnames[SEC__MAX] = {
254241675Suqs	NULL,
255241675Suqs	"NAME",
256241675Suqs	"LIBRARY",
257241675Suqs	"SYNOPSIS",
258241675Suqs	"DESCRIPTION",
259274880Sbapt	"CONTEXT",
260241675Suqs	"IMPLEMENTATION NOTES",
261241675Suqs	"RETURN VALUES",
262241675Suqs	"ENVIRONMENT",
263241675Suqs	"FILES",
264241675Suqs	"EXIT STATUS",
265241675Suqs	"EXAMPLES",
266241675Suqs	"DIAGNOSTICS",
267241675Suqs	"COMPATIBILITY",
268241675Suqs	"ERRORS",
269241675Suqs	"SEE ALSO",
270241675Suqs	"STANDARDS",
271241675Suqs	"HISTORY",
272241675Suqs	"AUTHORS",
273241675Suqs	"CAVEATS",
274241675Suqs	"BUGS",
275241675Suqs	"SECURITY CONSIDERATIONS",
276241675Suqs	NULL
277241675Suqs};
278241675Suqs
279274880Sbapt
280275432Sbaptvoid
281294113Sbaptmdoc_node_validate(struct roff_man *mdoc)
282241675Suqs{
283294113Sbapt	struct roff_node *n;
284294113Sbapt	v_post *p;
285241675Suqs
286294113Sbapt	n = mdoc->last;
287294113Sbapt	mdoc->last = mdoc->last->child;
288294113Sbapt	while (mdoc->last != NULL) {
289294113Sbapt		mdoc_node_validate(mdoc);
290294113Sbapt		if (mdoc->last == n)
291294113Sbapt			mdoc->last = mdoc->last->child;
292294113Sbapt		else
293294113Sbapt			mdoc->last = mdoc->last->next;
294294113Sbapt	}
295294113Sbapt
296294113Sbapt	mdoc->last = n;
297294113Sbapt	mdoc->next = ROFF_NEXT_SIBLING;
298241675Suqs	switch (n->type) {
299294113Sbapt	case ROFFT_TEXT:
300279527Sbapt		if (n->sec != SEC_SYNOPSIS || n->parent->tok != MDOC_Fd)
301279527Sbapt			check_text(mdoc, n->line, n->pos, n->string);
302241675Suqs		break;
303294113Sbapt	case ROFFT_EQN:
304294113Sbapt	case ROFFT_TBL:
305275432Sbapt		break;
306294113Sbapt	case ROFFT_ROOT:
307275432Sbapt		post_root(mdoc);
308275432Sbapt		break;
309241675Suqs	default:
310294113Sbapt		check_args(mdoc, mdoc->last);
311275432Sbapt
312275432Sbapt		/*
313275432Sbapt		 * Closing delimiters are not special at the
314275432Sbapt		 * beginning of a block, opening delimiters
315275432Sbapt		 * are not special at the end.
316275432Sbapt		 */
317275432Sbapt
318275432Sbapt		if (n->child != NULL)
319275432Sbapt			n->child->flags &= ~MDOC_DELIMC;
320275432Sbapt		if (n->last != NULL)
321275432Sbapt			n->last->flags &= ~MDOC_DELIMO;
322275432Sbapt
323275432Sbapt		/* Call the macro's postprocessor. */
324275432Sbapt
325294113Sbapt		p = mdoc_valids + n->tok;
326275432Sbapt		if (*p)
327275432Sbapt			(*p)(mdoc);
328294113Sbapt		if (mdoc->last == n)
329294113Sbapt			mdoc_state(mdoc, n);
330275432Sbapt		break;
331241675Suqs	}
332241675Suqs}
333241675Suqs
334275432Sbaptstatic void
335294113Sbaptcheck_args(struct roff_man *mdoc, struct roff_node *n)
336241675Suqs{
337241675Suqs	int		 i;
338241675Suqs
339241675Suqs	if (NULL == n->args)
340241675Suqs		return;
341241675Suqs
342241675Suqs	assert(n->args->argc);
343241675Suqs	for (i = 0; i < (int)n->args->argc; i++)
344261344Suqs		check_argv(mdoc, n, &n->args->argv[i]);
345241675Suqs}
346241675Suqs
347241675Suqsstatic void
348294113Sbaptcheck_argv(struct roff_man *mdoc, struct roff_node *n, struct mdoc_argv *v)
349241675Suqs{
350241675Suqs	int		 i;
351241675Suqs
352241675Suqs	for (i = 0; i < (int)v->sz; i++)
353261344Suqs		check_text(mdoc, v->line, v->pos, v->value[i]);
354241675Suqs}
355241675Suqs
356241675Suqsstatic void
357294113Sbaptcheck_text(struct roff_man *mdoc, int ln, int pos, char *p)
358241675Suqs{
359241675Suqs	char		*cp;
360241675Suqs
361261344Suqs	if (MDOC_LITERAL & mdoc->flags)
362241675Suqs		return;
363241675Suqs
364241675Suqs	for (cp = p; NULL != (p = strchr(p, '\t')); p++)
365274880Sbapt		mandoc_msg(MANDOCERR_FI_TAB, mdoc->parse,
366274880Sbapt		    ln, pos + (int)(p - cp), NULL);
367241675Suqs}
368241675Suqs
369275432Sbaptstatic void
370294113Sbaptpost_bl_norm(POST_ARGS)
371241675Suqs{
372294113Sbapt	struct roff_node *n;
373274880Sbapt	struct mdoc_argv *argv, *wa;
374274880Sbapt	int		  i;
375274880Sbapt	enum mdocargt	  mdoclt;
376241675Suqs	enum mdoc_list	  lt;
377241675Suqs
378294113Sbapt	n = mdoc->last->parent;
379294113Sbapt	n->norm->Bl.type = LIST__NONE;
380241675Suqs
381274880Sbapt	/*
382241675Suqs	 * First figure out which kind of list to use: bind ourselves to
383241675Suqs	 * the first mentioned list type and warn about any remaining
384241675Suqs	 * ones.  If we find no list type, we default to LIST_item.
385241675Suqs	 */
386241675Suqs
387275432Sbapt	wa = (n->args == NULL) ? NULL : n->args->argv;
388274880Sbapt	mdoclt = MDOC_ARG_MAX;
389241675Suqs	for (i = 0; n->args && i < (int)n->args->argc; i++) {
390274880Sbapt		argv = n->args->argv + i;
391241675Suqs		lt = LIST__NONE;
392274880Sbapt		switch (argv->arg) {
393241675Suqs		/* Set list types. */
394274880Sbapt		case MDOC_Bullet:
395241675Suqs			lt = LIST_bullet;
396241675Suqs			break;
397274880Sbapt		case MDOC_Dash:
398241675Suqs			lt = LIST_dash;
399241675Suqs			break;
400274880Sbapt		case MDOC_Enum:
401241675Suqs			lt = LIST_enum;
402241675Suqs			break;
403274880Sbapt		case MDOC_Hyphen:
404241675Suqs			lt = LIST_hyphen;
405241675Suqs			break;
406274880Sbapt		case MDOC_Item:
407241675Suqs			lt = LIST_item;
408241675Suqs			break;
409274880Sbapt		case MDOC_Tag:
410241675Suqs			lt = LIST_tag;
411241675Suqs			break;
412274880Sbapt		case MDOC_Diag:
413241675Suqs			lt = LIST_diag;
414241675Suqs			break;
415274880Sbapt		case MDOC_Hang:
416241675Suqs			lt = LIST_hang;
417241675Suqs			break;
418274880Sbapt		case MDOC_Ohang:
419241675Suqs			lt = LIST_ohang;
420241675Suqs			break;
421274880Sbapt		case MDOC_Inset:
422241675Suqs			lt = LIST_inset;
423241675Suqs			break;
424274880Sbapt		case MDOC_Column:
425241675Suqs			lt = LIST_column;
426241675Suqs			break;
427241675Suqs		/* Set list arguments. */
428274880Sbapt		case MDOC_Compact:
429274880Sbapt			if (n->norm->Bl.comp)
430274880Sbapt				mandoc_msg(MANDOCERR_ARG_REP,
431274880Sbapt				    mdoc->parse, argv->line,
432274880Sbapt				    argv->pos, "Bl -compact");
433274880Sbapt			n->norm->Bl.comp = 1;
434241675Suqs			break;
435274880Sbapt		case MDOC_Width:
436274880Sbapt			wa = argv;
437274880Sbapt			if (0 == argv->sz) {
438274880Sbapt				mandoc_msg(MANDOCERR_ARG_EMPTY,
439274880Sbapt				    mdoc->parse, argv->line,
440274880Sbapt				    argv->pos, "Bl -width");
441274880Sbapt				n->norm->Bl.width = "0n";
442241675Suqs				break;
443241675Suqs			}
444274880Sbapt			if (NULL != n->norm->Bl.width)
445274880Sbapt				mandoc_vmsg(MANDOCERR_ARG_REP,
446274880Sbapt				    mdoc->parse, argv->line,
447274880Sbapt				    argv->pos, "Bl -width %s",
448274880Sbapt				    argv->value[0]);
449275432Sbapt			rewrite_macro2len(argv->value);
450274880Sbapt			n->norm->Bl.width = argv->value[0];
451241675Suqs			break;
452274880Sbapt		case MDOC_Offset:
453274880Sbapt			if (0 == argv->sz) {
454274880Sbapt				mandoc_msg(MANDOCERR_ARG_EMPTY,
455274880Sbapt				    mdoc->parse, argv->line,
456274880Sbapt				    argv->pos, "Bl -offset");
457241675Suqs				break;
458241675Suqs			}
459274880Sbapt			if (NULL != n->norm->Bl.offs)
460274880Sbapt				mandoc_vmsg(MANDOCERR_ARG_REP,
461274880Sbapt				    mdoc->parse, argv->line,
462274880Sbapt				    argv->pos, "Bl -offset %s",
463274880Sbapt				    argv->value[0]);
464275432Sbapt			rewrite_macro2len(argv->value);
465274880Sbapt			n->norm->Bl.offs = argv->value[0];
466241675Suqs			break;
467241675Suqs		default:
468241675Suqs			continue;
469241675Suqs		}
470274880Sbapt		if (LIST__NONE == lt)
471274880Sbapt			continue;
472274880Sbapt		mdoclt = argv->arg;
473241675Suqs
474241675Suqs		/* Check: multiple list types. */
475241675Suqs
476274880Sbapt		if (LIST__NONE != n->norm->Bl.type) {
477274880Sbapt			mandoc_vmsg(MANDOCERR_BL_REP,
478274880Sbapt			    mdoc->parse, n->line, n->pos,
479274880Sbapt			    "Bl -%s", mdoc_argnames[argv->arg]);
480274880Sbapt			continue;
481241675Suqs		}
482241675Suqs
483241675Suqs		/* The list type should come first. */
484241675Suqs
485274880Sbapt		if (n->norm->Bl.width ||
486274880Sbapt		    n->norm->Bl.offs ||
487274880Sbapt		    n->norm->Bl.comp)
488274880Sbapt			mandoc_vmsg(MANDOCERR_BL_LATETYPE,
489274880Sbapt			    mdoc->parse, n->line, n->pos, "Bl -%s",
490274880Sbapt			    mdoc_argnames[n->args->argv[0].arg]);
491241675Suqs
492274880Sbapt		n->norm->Bl.type = lt;
493274880Sbapt		if (LIST_column == lt) {
494274880Sbapt			n->norm->Bl.ncols = argv->sz;
495274880Sbapt			n->norm->Bl.cols = (void *)argv->value;
496274880Sbapt		}
497241675Suqs	}
498241675Suqs
499241675Suqs	/* Allow lists to default to LIST_item. */
500241675Suqs
501241675Suqs	if (LIST__NONE == n->norm->Bl.type) {
502274880Sbapt		mandoc_msg(MANDOCERR_BL_NOTYPE, mdoc->parse,
503274880Sbapt		    n->line, n->pos, "Bl");
504241675Suqs		n->norm->Bl.type = LIST_item;
505241675Suqs	}
506241675Suqs
507274880Sbapt	/*
508241675Suqs	 * Validate the width field.  Some list types don't need width
509241675Suqs	 * types and should be warned about them.  Others should have it
510261344Suqs	 * and must also be warned.  Yet others have a default and need
511261344Suqs	 * no warning.
512241675Suqs	 */
513241675Suqs
514241675Suqs	switch (n->norm->Bl.type) {
515274880Sbapt	case LIST_tag:
516261344Suqs		if (NULL == n->norm->Bl.width)
517274880Sbapt			mandoc_msg(MANDOCERR_BL_NOWIDTH, mdoc->parse,
518274880Sbapt			    n->line, n->pos, "Bl -tag");
519241675Suqs		break;
520274880Sbapt	case LIST_column:
521274880Sbapt	case LIST_diag:
522274880Sbapt	case LIST_ohang:
523274880Sbapt	case LIST_inset:
524274880Sbapt	case LIST_item:
525241675Suqs		if (n->norm->Bl.width)
526274880Sbapt			mandoc_vmsg(MANDOCERR_BL_SKIPW, mdoc->parse,
527274880Sbapt			    wa->line, wa->pos, "Bl -%s",
528274880Sbapt			    mdoc_argnames[mdoclt]);
529241675Suqs		break;
530274880Sbapt	case LIST_bullet:
531274880Sbapt	case LIST_dash:
532274880Sbapt	case LIST_hyphen:
533261344Suqs		if (NULL == n->norm->Bl.width)
534261344Suqs			n->norm->Bl.width = "2n";
535261344Suqs		break;
536274880Sbapt	case LIST_enum:
537261344Suqs		if (NULL == n->norm->Bl.width)
538261344Suqs			n->norm->Bl.width = "3n";
539261344Suqs		break;
540241675Suqs	default:
541241675Suqs		break;
542241675Suqs	}
543241675Suqs}
544241675Suqs
545275432Sbaptstatic void
546294113Sbaptpost_bd(POST_ARGS)
547241675Suqs{
548294113Sbapt	struct roff_node *n;
549274880Sbapt	struct mdoc_argv *argv;
550274880Sbapt	int		  i;
551274880Sbapt	enum mdoc_disp	  dt;
552241675Suqs
553294113Sbapt	n = mdoc->last;
554241675Suqs	for (i = 0; n->args && i < (int)n->args->argc; i++) {
555274880Sbapt		argv = n->args->argv + i;
556241675Suqs		dt = DISP__NONE;
557241675Suqs
558274880Sbapt		switch (argv->arg) {
559274880Sbapt		case MDOC_Centred:
560274880Sbapt			dt = DISP_centered;
561241675Suqs			break;
562274880Sbapt		case MDOC_Ragged:
563241675Suqs			dt = DISP_ragged;
564241675Suqs			break;
565274880Sbapt		case MDOC_Unfilled:
566241675Suqs			dt = DISP_unfilled;
567241675Suqs			break;
568274880Sbapt		case MDOC_Filled:
569241675Suqs			dt = DISP_filled;
570241675Suqs			break;
571274880Sbapt		case MDOC_Literal:
572241675Suqs			dt = DISP_literal;
573241675Suqs			break;
574274880Sbapt		case MDOC_File:
575274880Sbapt			mandoc_msg(MANDOCERR_BD_FILE, mdoc->parse,
576274880Sbapt			    n->line, n->pos, NULL);
577275432Sbapt			break;
578274880Sbapt		case MDOC_Offset:
579274880Sbapt			if (0 == argv->sz) {
580274880Sbapt				mandoc_msg(MANDOCERR_ARG_EMPTY,
581274880Sbapt				    mdoc->parse, argv->line,
582274880Sbapt				    argv->pos, "Bd -offset");
583241675Suqs				break;
584241675Suqs			}
585274880Sbapt			if (NULL != n->norm->Bd.offs)
586274880Sbapt				mandoc_vmsg(MANDOCERR_ARG_REP,
587274880Sbapt				    mdoc->parse, argv->line,
588274880Sbapt				    argv->pos, "Bd -offset %s",
589274880Sbapt				    argv->value[0]);
590275432Sbapt			rewrite_macro2len(argv->value);
591274880Sbapt			n->norm->Bd.offs = argv->value[0];
592241675Suqs			break;
593274880Sbapt		case MDOC_Compact:
594274880Sbapt			if (n->norm->Bd.comp)
595274880Sbapt				mandoc_msg(MANDOCERR_ARG_REP,
596274880Sbapt				    mdoc->parse, argv->line,
597274880Sbapt				    argv->pos, "Bd -compact");
598274880Sbapt			n->norm->Bd.comp = 1;
599241675Suqs			break;
600241675Suqs		default:
601241675Suqs			abort();
602241675Suqs		}
603274880Sbapt		if (DISP__NONE == dt)
604274880Sbapt			continue;
605241675Suqs
606274880Sbapt		if (DISP__NONE == n->norm->Bd.type)
607241675Suqs			n->norm->Bd.type = dt;
608274880Sbapt		else
609274880Sbapt			mandoc_vmsg(MANDOCERR_BD_REP,
610274880Sbapt			    mdoc->parse, n->line, n->pos,
611274880Sbapt			    "Bd -%s", mdoc_argnames[argv->arg]);
612241675Suqs	}
613241675Suqs
614241675Suqs	if (DISP__NONE == n->norm->Bd.type) {
615274880Sbapt		mandoc_msg(MANDOCERR_BD_NOTYPE, mdoc->parse,
616274880Sbapt		    n->line, n->pos, "Bd");
617241675Suqs		n->norm->Bd.type = DISP_ragged;
618241675Suqs	}
619241675Suqs}
620241675Suqs
621275432Sbaptstatic void
622294113Sbaptpost_an_norm(POST_ARGS)
623241675Suqs{
624294113Sbapt	struct roff_node *n;
625274880Sbapt	struct mdoc_argv *argv;
626274880Sbapt	size_t	 i;
627241675Suqs
628294113Sbapt	n = mdoc->last;
629274880Sbapt	if (n->args == NULL)
630275432Sbapt		return;
631241675Suqs
632274880Sbapt	for (i = 1; i < n->args->argc; i++) {
633274880Sbapt		argv = n->args->argv + i;
634274880Sbapt		mandoc_vmsg(MANDOCERR_AN_REP,
635274880Sbapt		    mdoc->parse, argv->line, argv->pos,
636274880Sbapt		    "An -%s", mdoc_argnames[argv->arg]);
637274880Sbapt	}
638241675Suqs
639274880Sbapt	argv = n->args->argv;
640274880Sbapt	if (argv->arg == MDOC_Split)
641241675Suqs		n->norm->An.auth = AUTH_split;
642274880Sbapt	else if (argv->arg == MDOC_Nosplit)
643241675Suqs		n->norm->An.auth = AUTH_nosplit;
644241675Suqs	else
645241675Suqs		abort();
646241675Suqs}
647241675Suqs
648275432Sbaptstatic void
649294113Sbaptpost_std(POST_ARGS)
650241675Suqs{
651294113Sbapt	struct roff_node *n;
652241675Suqs
653294113Sbapt	n = mdoc->last;
654294113Sbapt	if (n->args && n->args->argc == 1)
655294113Sbapt		if (n->args->argv[0].arg == MDOC_Std)
656275432Sbapt			return;
657241675Suqs
658274880Sbapt	mandoc_msg(MANDOCERR_ARG_STD, mdoc->parse,
659274880Sbapt	    n->line, n->pos, mdoc_macronames[n->tok]);
660241675Suqs}
661241675Suqs
662275432Sbaptstatic void
663294113Sbaptpost_obsolete(POST_ARGS)
664241675Suqs{
665294113Sbapt	struct roff_node *n;
666241675Suqs
667294113Sbapt	n = mdoc->last;
668294113Sbapt	if (n->type == ROFFT_ELEM || n->type == ROFFT_BLOCK)
669274880Sbapt		mandoc_msg(MANDOCERR_MACRO_OBS, mdoc->parse,
670274880Sbapt		    n->line, n->pos, mdoc_macronames[n->tok]);
671274880Sbapt}
672241675Suqs
673275432Sbaptstatic void
674241675Suqspost_bf(POST_ARGS)
675241675Suqs{
676294113Sbapt	struct roff_node *np, *nch;
677241675Suqs
678241675Suqs	/*
679241675Suqs	 * Unlike other data pointers, these are "housed" by the HEAD
680241675Suqs	 * element, which contains the goods.
681241675Suqs	 */
682241675Suqs
683279527Sbapt	np = mdoc->last;
684294113Sbapt	if (np->type != ROFFT_HEAD)
685275432Sbapt		return;
686241675Suqs
687294113Sbapt	assert(np->parent->type == ROFFT_BLOCK);
688294113Sbapt	assert(np->parent->tok == MDOC_Bf);
689241675Suqs
690274880Sbapt	/* Check the number of arguments. */
691241675Suqs
692274880Sbapt	nch = np->child;
693294113Sbapt	if (np->parent->args == NULL) {
694294113Sbapt		if (nch == NULL) {
695274880Sbapt			mandoc_msg(MANDOCERR_BF_NOFONT, mdoc->parse,
696274880Sbapt			    np->line, np->pos, "Bf");
697275432Sbapt			return;
698274880Sbapt		}
699274880Sbapt		nch = nch->next;
700241675Suqs	}
701294113Sbapt	if (nch != NULL)
702274880Sbapt		mandoc_vmsg(MANDOCERR_ARG_EXCESS, mdoc->parse,
703274880Sbapt		    nch->line, nch->pos, "Bf ... %s", nch->string);
704241675Suqs
705241675Suqs	/* Extract argument into data. */
706274880Sbapt
707294113Sbapt	if (np->parent->args != NULL) {
708294113Sbapt		switch (np->parent->args->argv[0].arg) {
709294113Sbapt		case MDOC_Emphasis:
710241675Suqs			np->norm->Bf.font = FONT_Em;
711294113Sbapt			break;
712294113Sbapt		case MDOC_Literal:
713241675Suqs			np->norm->Bf.font = FONT_Li;
714294113Sbapt			break;
715294113Sbapt		case MDOC_Symbolic:
716241675Suqs			np->norm->Bf.font = FONT_Sy;
717294113Sbapt			break;
718294113Sbapt		default:
719241675Suqs			abort();
720294113Sbapt		}
721275432Sbapt		return;
722241675Suqs	}
723241675Suqs
724241675Suqs	/* Extract parameter into data. */
725241675Suqs
726294113Sbapt	if ( ! strcmp(np->child->string, "Em"))
727241675Suqs		np->norm->Bf.font = FONT_Em;
728294113Sbapt	else if ( ! strcmp(np->child->string, "Li"))
729241675Suqs		np->norm->Bf.font = FONT_Li;
730294113Sbapt	else if ( ! strcmp(np->child->string, "Sy"))
731241675Suqs		np->norm->Bf.font = FONT_Sy;
732274880Sbapt	else
733274880Sbapt		mandoc_vmsg(MANDOCERR_BF_BADFONT, mdoc->parse,
734274880Sbapt		    np->child->line, np->child->pos,
735274880Sbapt		    "Bf %s", np->child->string);
736241675Suqs}
737241675Suqs
738275432Sbaptstatic void
739241675Suqspost_lb(POST_ARGS)
740241675Suqs{
741294113Sbapt	struct roff_node	*n;
742274880Sbapt	const char		*stdlibname;
743274880Sbapt	char			*libname;
744241675Suqs
745274880Sbapt	n = mdoc->last->child;
746294113Sbapt	assert(n->type == ROFFT_TEXT);
747241675Suqs
748274880Sbapt	if (NULL == (stdlibname = mdoc_a2lib(n->string)))
749274880Sbapt		mandoc_asprintf(&libname,
750279527Sbapt		    "library \\(Lq%s\\(Rq", n->string);
751274880Sbapt	else
752274880Sbapt		libname = mandoc_strdup(stdlibname);
753241675Suqs
754274880Sbapt	free(n->string);
755274880Sbapt	n->string = libname;
756274880Sbapt}
757241675Suqs
758275432Sbaptstatic void
759274880Sbaptpost_eoln(POST_ARGS)
760274880Sbapt{
761294113Sbapt	const struct roff_node *n;
762241675Suqs
763274880Sbapt	n = mdoc->last;
764294113Sbapt	if (n->child != NULL)
765274880Sbapt		mandoc_vmsg(MANDOCERR_ARG_SKIP,
766274880Sbapt		    mdoc->parse, n->line, n->pos,
767274880Sbapt		    "%s %s", mdoc_macronames[n->tok],
768274880Sbapt		    n->child->string);
769241675Suqs}
770241675Suqs
771275432Sbaptstatic void
772275432Sbaptpost_fname(POST_ARGS)
773275432Sbapt{
774294113Sbapt	const struct roff_node	*n;
775275432Sbapt	const char		*cp;
776275432Sbapt	size_t			 pos;
777275432Sbapt
778275432Sbapt	n = mdoc->last->child;
779275432Sbapt	pos = strcspn(n->string, "()");
780275432Sbapt	cp = n->string + pos;
781275432Sbapt	if ( ! (cp[0] == '\0' || (cp[0] == '(' && cp[1] == '*')))
782275432Sbapt		mandoc_msg(MANDOCERR_FN_PAREN, mdoc->parse,
783275432Sbapt		    n->line, n->pos + pos, n->string);
784275432Sbapt}
785275432Sbapt
786275432Sbaptstatic void
787275432Sbaptpost_fn(POST_ARGS)
788275432Sbapt{
789275432Sbapt
790275432Sbapt	post_fname(mdoc);
791275432Sbapt	post_fa(mdoc);
792275432Sbapt}
793275432Sbapt
794275432Sbaptstatic void
795274880Sbaptpost_fo(POST_ARGS)
796241675Suqs{
797294113Sbapt	const struct roff_node	*n;
798241675Suqs
799279527Sbapt	n = mdoc->last;
800279527Sbapt
801294113Sbapt	if (n->type != ROFFT_HEAD)
802279527Sbapt		return;
803279527Sbapt
804279527Sbapt	if (n->child == NULL) {
805279527Sbapt		mandoc_msg(MANDOCERR_FO_NOHEAD, mdoc->parse,
806279527Sbapt		    n->line, n->pos, "Fo");
807279527Sbapt		return;
808279527Sbapt	}
809279527Sbapt	if (n->child != n->last) {
810279527Sbapt		mandoc_vmsg(MANDOCERR_ARG_EXCESS, mdoc->parse,
811279527Sbapt		    n->child->next->line, n->child->next->pos,
812279527Sbapt		    "Fo ... %s", n->child->next->string);
813279527Sbapt		while (n->child != n->last)
814294113Sbapt			roff_node_delete(mdoc, n->last);
815279527Sbapt	}
816279527Sbapt
817279527Sbapt	post_fname(mdoc);
818241675Suqs}
819241675Suqs
820275432Sbaptstatic void
821275432Sbaptpost_fa(POST_ARGS)
822275432Sbapt{
823294113Sbapt	const struct roff_node *n;
824275432Sbapt	const char *cp;
825275432Sbapt
826275432Sbapt	for (n = mdoc->last->child; n != NULL; n = n->next) {
827275432Sbapt		for (cp = n->string; *cp != '\0'; cp++) {
828275432Sbapt			/* Ignore callbacks and alterations. */
829275432Sbapt			if (*cp == '(' || *cp == '{')
830275432Sbapt				break;
831275432Sbapt			if (*cp != ',')
832275432Sbapt				continue;
833275432Sbapt			mandoc_msg(MANDOCERR_FA_COMMA, mdoc->parse,
834275432Sbapt			    n->line, n->pos + (cp - n->string),
835275432Sbapt			    n->string);
836275432Sbapt			break;
837275432Sbapt		}
838275432Sbapt	}
839275432Sbapt}
840275432Sbapt
841275432Sbaptstatic void
842241675Suqspost_nm(POST_ARGS)
843241675Suqs{
844294113Sbapt	struct roff_node	*n;
845241675Suqs
846279527Sbapt	n = mdoc->last;
847279527Sbapt
848279527Sbapt	if (n->last != NULL &&
849279527Sbapt	    (n->last->tok == MDOC_Pp ||
850279527Sbapt	     n->last->tok == MDOC_Lp))
851279527Sbapt		mdoc_node_relink(mdoc, n->last);
852279527Sbapt
853294113Sbapt	if (mdoc->meta.name != NULL)
854275432Sbapt		return;
855241675Suqs
856294113Sbapt	deroff(&mdoc->meta.name, n);
857241675Suqs
858294113Sbapt	if (mdoc->meta.name == NULL)
859274880Sbapt		mandoc_msg(MANDOCERR_NM_NONAME, mdoc->parse,
860279527Sbapt		    n->line, n->pos, "Nm");
861241675Suqs}
862241675Suqs
863275432Sbaptstatic void
864274880Sbaptpost_nd(POST_ARGS)
865274880Sbapt{
866294113Sbapt	struct roff_node	*n;
867274880Sbapt
868279527Sbapt	n = mdoc->last;
869279527Sbapt
870294113Sbapt	if (n->type != ROFFT_BODY)
871279527Sbapt		return;
872279527Sbapt
873279527Sbapt	if (n->child == NULL)
874279527Sbapt		mandoc_msg(MANDOCERR_ND_EMPTY, mdoc->parse,
875279527Sbapt		    n->line, n->pos, "Nd");
876279527Sbapt
877275432Sbapt	post_hyph(mdoc);
878274880Sbapt}
879274880Sbapt
880275432Sbaptstatic void
881294113Sbaptpost_display(POST_ARGS)
882274880Sbapt{
883294113Sbapt	struct roff_node *n, *np;
884274880Sbapt
885279527Sbapt	n = mdoc->last;
886294113Sbapt	switch (n->type) {
887294113Sbapt	case ROFFT_BODY:
888294113Sbapt		if (n->end != ENDBODY_NOT)
889294113Sbapt			break;
890294113Sbapt		if (n->child == NULL)
891294113Sbapt			mandoc_msg(MANDOCERR_BLK_EMPTY, mdoc->parse,
892294113Sbapt			    n->line, n->pos, mdoc_macronames[n->tok]);
893294113Sbapt		else if (n->tok == MDOC_D1)
894294113Sbapt			post_hyph(mdoc);
895294113Sbapt		break;
896294113Sbapt	case ROFFT_BLOCK:
897294113Sbapt		if (n->tok == MDOC_Bd) {
898294113Sbapt			if (n->args == NULL) {
899294113Sbapt				mandoc_msg(MANDOCERR_BD_NOARG,
900294113Sbapt				    mdoc->parse, n->line, n->pos, "Bd");
901294113Sbapt				mdoc->next = ROFF_NEXT_SIBLING;
902294113Sbapt				while (n->body->child != NULL)
903294113Sbapt					mdoc_node_relink(mdoc,
904294113Sbapt					    n->body->child);
905294113Sbapt				roff_node_delete(mdoc, n);
906294113Sbapt				break;
907294113Sbapt			}
908294113Sbapt			post_bd(mdoc);
909294113Sbapt			post_prevpar(mdoc);
910294113Sbapt		}
911294113Sbapt		for (np = n->parent; np != NULL; np = np->parent) {
912294113Sbapt			if (np->type == ROFFT_BLOCK && np->tok == MDOC_Bd) {
913294113Sbapt				mandoc_vmsg(MANDOCERR_BD_NEST,
914294113Sbapt				    mdoc->parse, n->line, n->pos,
915294113Sbapt				    "%s in Bd", mdoc_macronames[n->tok]);
916294113Sbapt				break;
917294113Sbapt			}
918294113Sbapt		}
919294113Sbapt		break;
920294113Sbapt	default:
921294113Sbapt		break;
922294113Sbapt	}
923274880Sbapt}
924274880Sbapt
925275432Sbaptstatic void
926241675Suqspost_defaults(POST_ARGS)
927241675Suqs{
928294113Sbapt	struct roff_node *nn;
929241675Suqs
930241675Suqs	/*
931241675Suqs	 * The `Ar' defaults to "file ..." if no value is provided as an
932241675Suqs	 * argument; the `Mt' and `Pa' macros use "~"; the `Li' just
933241675Suqs	 * gets an empty string.
934241675Suqs	 */
935241675Suqs
936294113Sbapt	if (mdoc->last->child != NULL)
937275432Sbapt		return;
938274880Sbapt
939241675Suqs	nn = mdoc->last;
940241675Suqs
941241675Suqs	switch (nn->tok) {
942274880Sbapt	case MDOC_Ar:
943294113Sbapt		mdoc->next = ROFF_NEXT_CHILD;
944294113Sbapt		roff_word_alloc(mdoc, nn->line, nn->pos, "file");
945294113Sbapt		roff_word_alloc(mdoc, nn->line, nn->pos, "...");
946241675Suqs		break;
947274880Sbapt	case MDOC_Pa:
948274880Sbapt	case MDOC_Mt:
949294113Sbapt		mdoc->next = ROFF_NEXT_CHILD;
950294113Sbapt		roff_word_alloc(mdoc, nn->line, nn->pos, "~");
951241675Suqs		break;
952241675Suqs	default:
953241675Suqs		abort();
954274880Sbapt	}
955241675Suqs	mdoc->last = nn;
956241675Suqs}
957241675Suqs
958275432Sbaptstatic void
959241675Suqspost_at(POST_ARGS)
960241675Suqs{
961294113Sbapt	struct roff_node	*n;
962274880Sbapt	const char		*std_att;
963274880Sbapt	char			*att;
964241675Suqs
965274880Sbapt	n = mdoc->last;
966274880Sbapt	if (n->child == NULL) {
967294113Sbapt		mdoc->next = ROFF_NEXT_CHILD;
968294113Sbapt		roff_word_alloc(mdoc, n->line, n->pos, "AT&T UNIX");
969274880Sbapt		mdoc->last = n;
970275432Sbapt		return;
971274880Sbapt	}
972274880Sbapt
973241675Suqs	/*
974241675Suqs	 * If we have a child, look it up in the standard keys.  If a
975241675Suqs	 * key exist, use that instead of the child; if it doesn't,
976241675Suqs	 * prefix "AT&T UNIX " to the existing data.
977241675Suqs	 */
978241675Suqs
979274880Sbapt	n = n->child;
980294113Sbapt	assert(n->type == ROFFT_TEXT);
981294113Sbapt	if ((std_att = mdoc_a2att(n->string)) == NULL) {
982274880Sbapt		mandoc_vmsg(MANDOCERR_AT_BAD, mdoc->parse,
983274880Sbapt		    n->line, n->pos, "At %s", n->string);
984274880Sbapt		mandoc_asprintf(&att, "AT&T UNIX %s", n->string);
985274880Sbapt	} else
986274880Sbapt		att = mandoc_strdup(std_att);
987241675Suqs
988274880Sbapt	free(n->string);
989274880Sbapt	n->string = att;
990241675Suqs}
991241675Suqs
992275432Sbaptstatic void
993241675Suqspost_an(POST_ARGS)
994241675Suqs{
995294113Sbapt	struct roff_node *np, *nch;
996241675Suqs
997294113Sbapt	post_an_norm(mdoc);
998294113Sbapt
999241675Suqs	np = mdoc->last;
1000279527Sbapt	nch = np->child;
1001279527Sbapt	if (np->norm->An.auth == AUTH__NONE) {
1002279527Sbapt		if (nch == NULL)
1003279527Sbapt			mandoc_msg(MANDOCERR_MACRO_EMPTY, mdoc->parse,
1004279527Sbapt			    np->line, np->pos, "An");
1005279527Sbapt	} else if (nch != NULL)
1006279527Sbapt		mandoc_vmsg(MANDOCERR_ARG_EXCESS, mdoc->parse,
1007279527Sbapt		    nch->line, nch->pos, "An ... %s", nch->string);
1008241675Suqs}
1009241675Suqs
1010275432Sbaptstatic void
1011274880Sbaptpost_en(POST_ARGS)
1012274880Sbapt{
1013241675Suqs
1014294113Sbapt	post_obsolete(mdoc);
1015294113Sbapt	if (mdoc->last->type == ROFFT_BLOCK)
1016274880Sbapt		mdoc->last->norm->Es = mdoc->last_es;
1017274880Sbapt}
1018274880Sbapt
1019275432Sbaptstatic void
1020274880Sbaptpost_es(POST_ARGS)
1021274880Sbapt{
1022274880Sbapt
1023294113Sbapt	post_obsolete(mdoc);
1024274880Sbapt	mdoc->last_es = mdoc->last;
1025274880Sbapt}
1026274880Sbapt
1027275432Sbaptstatic void
1028241675Suqspost_it(POST_ARGS)
1029241675Suqs{
1030294113Sbapt	struct roff_node *nbl, *nit, *nch;
1031241675Suqs	int		  i, cols;
1032241675Suqs	enum mdoc_list	  lt;
1033241675Suqs
1034294113Sbapt	post_prevpar(mdoc);
1035294113Sbapt
1036274880Sbapt	nit = mdoc->last;
1037294113Sbapt	if (nit->type != ROFFT_BLOCK)
1038275432Sbapt		return;
1039241675Suqs
1040274880Sbapt	nbl = nit->parent->parent;
1041274880Sbapt	lt = nbl->norm->Bl.type;
1042241675Suqs
1043241675Suqs	switch (lt) {
1044274880Sbapt	case LIST_tag:
1045274880Sbapt	case LIST_hang:
1046274880Sbapt	case LIST_ohang:
1047274880Sbapt	case LIST_inset:
1048274880Sbapt	case LIST_diag:
1049279527Sbapt		if (nit->head->child == NULL)
1050274880Sbapt			mandoc_vmsg(MANDOCERR_IT_NOHEAD,
1051274880Sbapt			    mdoc->parse, nit->line, nit->pos,
1052274880Sbapt			    "Bl -%s It",
1053274880Sbapt			    mdoc_argnames[nbl->args->argv[0].arg]);
1054241675Suqs		break;
1055274880Sbapt	case LIST_bullet:
1056274880Sbapt	case LIST_dash:
1057274880Sbapt	case LIST_enum:
1058274880Sbapt	case LIST_hyphen:
1059279527Sbapt		if (nit->body == NULL || nit->body->child == NULL)
1060274880Sbapt			mandoc_vmsg(MANDOCERR_IT_NOBODY,
1061274880Sbapt			    mdoc->parse, nit->line, nit->pos,
1062274880Sbapt			    "Bl -%s It",
1063274880Sbapt			    mdoc_argnames[nbl->args->argv[0].arg]);
1064241675Suqs		/* FALLTHROUGH */
1065274880Sbapt	case LIST_item:
1066279527Sbapt		if (nit->head->child != NULL)
1067274880Sbapt			mandoc_vmsg(MANDOCERR_ARG_SKIP,
1068274880Sbapt			    mdoc->parse, nit->line, nit->pos,
1069274880Sbapt			    "It %s", nit->head->child->string);
1070241675Suqs		break;
1071274880Sbapt	case LIST_column:
1072274880Sbapt		cols = (int)nbl->norm->Bl.ncols;
1073241675Suqs
1074279527Sbapt		assert(nit->head->child == NULL);
1075241675Suqs
1076294113Sbapt		i = 0;
1077294113Sbapt		for (nch = nit->child; nch != NULL; nch = nch->next)
1078294113Sbapt			if (nch->type == ROFFT_BODY)
1079241675Suqs				i++;
1080241675Suqs
1081274880Sbapt		if (i < cols || i > cols + 1)
1082279527Sbapt			mandoc_vmsg(MANDOCERR_BL_COL,
1083274880Sbapt			    mdoc->parse, nit->line, nit->pos,
1084279527Sbapt			    "%d columns, %d cells", cols, i);
1085274880Sbapt		break;
1086241675Suqs	default:
1087274880Sbapt		abort();
1088241675Suqs	}
1089241675Suqs}
1090241675Suqs
1091275432Sbaptstatic void
1092274880Sbaptpost_bl_block(POST_ARGS)
1093241675Suqs{
1094294113Sbapt	struct roff_node *n, *ni, *nc;
1095241675Suqs
1096294113Sbapt	post_prevpar(mdoc);
1097294113Sbapt
1098241675Suqs	/*
1099241675Suqs	 * These are fairly complicated, so we've broken them into two
1100241675Suqs	 * functions.  post_bl_block_tag() is called when a -tag is
1101241675Suqs	 * specified, but no -width (it must be guessed).  The second
1102241675Suqs	 * when a -width is specified (macro indicators must be
1103241675Suqs	 * rewritten into real lengths).
1104241675Suqs	 */
1105241675Suqs
1106241675Suqs	n = mdoc->last;
1107241675Suqs
1108294113Sbapt	if (n->norm->Bl.type == LIST_tag &&
1109294113Sbapt	    n->norm->Bl.width == NULL) {
1110275432Sbapt		post_bl_block_tag(mdoc);
1111294113Sbapt		assert(n->norm->Bl.width != NULL);
1112261344Suqs	}
1113241675Suqs
1114294113Sbapt	for (ni = n->body->child; ni != NULL; ni = ni->next) {
1115294113Sbapt		if (ni->body == NULL)
1116261344Suqs			continue;
1117261344Suqs		nc = ni->body->last;
1118294113Sbapt		while (nc != NULL) {
1119261344Suqs			switch (nc->tok) {
1120274880Sbapt			case MDOC_Pp:
1121274880Sbapt			case MDOC_Lp:
1122274880Sbapt			case MDOC_br:
1123261344Suqs				break;
1124261344Suqs			default:
1125261344Suqs				nc = NULL;
1126261344Suqs				continue;
1127261344Suqs			}
1128294113Sbapt			if (ni->next == NULL) {
1129274880Sbapt				mandoc_msg(MANDOCERR_PAR_MOVE,
1130274880Sbapt				    mdoc->parse, nc->line, nc->pos,
1131274880Sbapt				    mdoc_macronames[nc->tok]);
1132275432Sbapt				mdoc_node_relink(mdoc, nc);
1133294113Sbapt			} else if (n->norm->Bl.comp == 0 &&
1134294113Sbapt			    n->norm->Bl.type != LIST_column) {
1135274880Sbapt				mandoc_vmsg(MANDOCERR_PAR_SKIP,
1136274880Sbapt				    mdoc->parse, nc->line, nc->pos,
1137274880Sbapt				    "%s before It",
1138274880Sbapt				    mdoc_macronames[nc->tok]);
1139294113Sbapt				roff_node_delete(mdoc, nc);
1140261344Suqs			} else
1141261344Suqs				break;
1142261344Suqs			nc = ni->body->last;
1143261344Suqs		}
1144261344Suqs	}
1145241675Suqs}
1146241675Suqs
1147275432Sbapt/*
1148275432Sbapt * If the argument of -offset or -width is a macro,
1149275432Sbapt * replace it with the associated default width.
1150275432Sbapt */
1151275432Sbaptvoid
1152275432Sbaptrewrite_macro2len(char **arg)
1153241675Suqs{
1154241675Suqs	size_t		  width;
1155294113Sbapt	int		  tok;
1156241675Suqs
1157275432Sbapt	if (*arg == NULL)
1158275432Sbapt		return;
1159275432Sbapt	else if ( ! strcmp(*arg, "Ds"))
1160241675Suqs		width = 6;
1161294113Sbapt	else if ((tok = mdoc_hash_find(*arg)) == TOKEN_NONE)
1162275432Sbapt		return;
1163274880Sbapt	else
1164274880Sbapt		width = macro2len(tok);
1165241675Suqs
1166275432Sbapt	free(*arg);
1167275432Sbapt	mandoc_asprintf(arg, "%zun", width);
1168241675Suqs}
1169241675Suqs
1170275432Sbaptstatic void
1171241675Suqspost_bl_block_tag(POST_ARGS)
1172241675Suqs{
1173294113Sbapt	struct roff_node *n, *nn;
1174241675Suqs	size_t		  sz, ssz;
1175241675Suqs	int		  i;
1176274880Sbapt	char		  buf[24];
1177241675Suqs
1178241675Suqs	/*
1179241675Suqs	 * Calculate the -width for a `Bl -tag' list if it hasn't been
1180241675Suqs	 * provided.  Uses the first head macro.  NOTE AGAIN: this is
1181241675Suqs	 * ONLY if the -width argument has NOT been provided.  See
1182275432Sbapt	 * rewrite_macro2len() for converting the -width string.
1183241675Suqs	 */
1184241675Suqs
1185241675Suqs	sz = 10;
1186241675Suqs	n = mdoc->last;
1187241675Suqs
1188294113Sbapt	for (nn = n->body->child; nn != NULL; nn = nn->next) {
1189294113Sbapt		if (nn->tok != MDOC_It)
1190241675Suqs			continue;
1191241675Suqs
1192294113Sbapt		assert(nn->type == ROFFT_BLOCK);
1193241675Suqs		nn = nn->head->child;
1194241675Suqs
1195241675Suqs		if (nn == NULL)
1196241675Suqs			break;
1197241675Suqs
1198294113Sbapt		if (nn->type == ROFFT_TEXT) {
1199241675Suqs			sz = strlen(nn->string) + 1;
1200241675Suqs			break;
1201241675Suqs		}
1202241675Suqs
1203241675Suqs		if (0 != (ssz = macro2len(nn->tok)))
1204241675Suqs			sz = ssz;
1205241675Suqs
1206241675Suqs		break;
1207274880Sbapt	}
1208241675Suqs
1209241675Suqs	/* Defaults to ten ens. */
1210241675Suqs
1211274880Sbapt	(void)snprintf(buf, sizeof(buf), "%un", (unsigned int)sz);
1212241675Suqs
1213241675Suqs	/*
1214241675Suqs	 * We have to dynamically add this to the macro's argument list.
1215241675Suqs	 * We're guaranteed that a MDOC_Width doesn't already exist.
1216241675Suqs	 */
1217241675Suqs
1218294113Sbapt	assert(n->args != NULL);
1219241675Suqs	i = (int)(n->args->argc)++;
1220241675Suqs
1221274880Sbapt	n->args->argv = mandoc_reallocarray(n->args->argv,
1222274880Sbapt	    n->args->argc, sizeof(struct mdoc_argv));
1223241675Suqs
1224241675Suqs	n->args->argv[i].arg = MDOC_Width;
1225241675Suqs	n->args->argv[i].line = n->line;
1226241675Suqs	n->args->argv[i].pos = n->pos;
1227241675Suqs	n->args->argv[i].sz = 1;
1228241675Suqs	n->args->argv[i].value = mandoc_malloc(sizeof(char *));
1229241675Suqs	n->args->argv[i].value[0] = mandoc_strdup(buf);
1230241675Suqs
1231241675Suqs	/* Set our width! */
1232241675Suqs	n->norm->Bl.width = n->args->argv[i].value[0];
1233241675Suqs}
1234241675Suqs
1235275432Sbaptstatic void
1236274880Sbaptpost_bl_head(POST_ARGS)
1237241675Suqs{
1238294113Sbapt	struct roff_node *nbl, *nh, *nch, *nnext;
1239274880Sbapt	struct mdoc_argv *argv;
1240241675Suqs	int		  i, j;
1241241675Suqs
1242294113Sbapt	post_bl_norm(mdoc);
1243294113Sbapt
1244279527Sbapt	nh = mdoc->last;
1245279527Sbapt	if (nh->norm->Bl.type != LIST_column) {
1246279527Sbapt		if ((nch = nh->child) == NULL)
1247279527Sbapt			return;
1248279527Sbapt		mandoc_vmsg(MANDOCERR_ARG_EXCESS, mdoc->parse,
1249279527Sbapt		    nch->line, nch->pos, "Bl ... %s", nch->string);
1250279527Sbapt		while (nch != NULL) {
1251294113Sbapt			roff_node_delete(mdoc, nch);
1252279527Sbapt			nch = nh->child;
1253279527Sbapt		}
1254275432Sbapt		return;
1255275432Sbapt	}
1256241675Suqs
1257241675Suqs	/*
1258274880Sbapt	 * Append old-style lists, where the column width specifiers
1259241675Suqs	 * trail as macro parameters, to the new-style ("normal-form")
1260241675Suqs	 * lists where they're argument values following -column.
1261241675Suqs	 */
1262241675Suqs
1263279527Sbapt	if (nh->child == NULL)
1264275432Sbapt		return;
1265241675Suqs
1266279527Sbapt	nbl = nh->parent;
1267279527Sbapt	for (j = 0; j < (int)nbl->args->argc; j++)
1268279527Sbapt		if (nbl->args->argv[j].arg == MDOC_Column)
1269241675Suqs			break;
1270241675Suqs
1271279527Sbapt	assert(j < (int)nbl->args->argc);
1272241675Suqs
1273241675Suqs	/*
1274241675Suqs	 * Accommodate for new-style groff column syntax.  Shuffle the
1275241675Suqs	 * child nodes, all of which must be TEXT, as arguments for the
1276241675Suqs	 * column field.  Then, delete the head children.
1277241675Suqs	 */
1278241675Suqs
1279279527Sbapt	argv = nbl->args->argv + j;
1280274880Sbapt	i = argv->sz;
1281294113Sbapt	for (nch = nh->child; nch != NULL; nch = nch->next)
1282294113Sbapt		argv->sz++;
1283274880Sbapt	argv->value = mandoc_reallocarray(argv->value,
1284274880Sbapt	    argv->sz, sizeof(char *));
1285241675Suqs
1286279527Sbapt	nh->norm->Bl.ncols = argv->sz;
1287279527Sbapt	nh->norm->Bl.cols = (void *)argv->value;
1288241675Suqs
1289279527Sbapt	for (nch = nh->child; nch != NULL; nch = nnext) {
1290279527Sbapt		argv->value[i++] = nch->string;
1291279527Sbapt		nch->string = NULL;
1292279527Sbapt		nnext = nch->next;
1293294113Sbapt		roff_node_delete(NULL, nch);
1294241675Suqs	}
1295279527Sbapt	nh->child = NULL;
1296241675Suqs}
1297241675Suqs
1298275432Sbaptstatic void
1299241675Suqspost_bl(POST_ARGS)
1300241675Suqs{
1301294113Sbapt	struct roff_node	*nparent, *nprev; /* of the Bl block */
1302294113Sbapt	struct roff_node	*nblock, *nbody;  /* of the Bl */
1303294113Sbapt	struct roff_node	*nchild, *nnext;  /* of the Bl body */
1304241675Suqs
1305261344Suqs	nbody = mdoc->last;
1306261344Suqs	switch (nbody->type) {
1307294113Sbapt	case ROFFT_BLOCK:
1308275432Sbapt		post_bl_block(mdoc);
1309275432Sbapt		return;
1310294113Sbapt	case ROFFT_HEAD:
1311275432Sbapt		post_bl_head(mdoc);
1312275432Sbapt		return;
1313294113Sbapt	case ROFFT_BODY:
1314261344Suqs		break;
1315261344Suqs	default:
1316275432Sbapt		return;
1317261344Suqs	}
1318294113Sbapt	if (nbody->end != ENDBODY_NOT)
1319294113Sbapt		return;
1320241675Suqs
1321261344Suqs	nchild = nbody->child;
1322279527Sbapt	if (nchild == NULL) {
1323279527Sbapt		mandoc_msg(MANDOCERR_BLK_EMPTY, mdoc->parse,
1324279527Sbapt		    nbody->line, nbody->pos, "Bl");
1325279527Sbapt		return;
1326279527Sbapt	}
1327279527Sbapt	while (nchild != NULL) {
1328279527Sbapt		if (nchild->tok == MDOC_It ||
1329279527Sbapt		    (nchild->tok == MDOC_Sm &&
1330279527Sbapt		     nchild->next != NULL &&
1331279527Sbapt		     nchild->next->tok == MDOC_It)) {
1332261344Suqs			nchild = nchild->next;
1333241675Suqs			continue;
1334241675Suqs		}
1335241675Suqs
1336274880Sbapt		mandoc_msg(MANDOCERR_BL_MOVE, mdoc->parse,
1337274880Sbapt		    nchild->line, nchild->pos,
1338274880Sbapt		    mdoc_macronames[nchild->tok]);
1339261344Suqs
1340261344Suqs		/*
1341261344Suqs		 * Move the node out of the Bl block.
1342261344Suqs		 * First, collect all required node pointers.
1343261344Suqs		 */
1344261344Suqs
1345261344Suqs		nblock  = nbody->parent;
1346261344Suqs		nprev   = nblock->prev;
1347261344Suqs		nparent = nblock->parent;
1348261344Suqs		nnext   = nchild->next;
1349261344Suqs
1350261344Suqs		/*
1351261344Suqs		 * Unlink this child.
1352261344Suqs		 */
1353261344Suqs
1354294113Sbapt		assert(nchild->prev == NULL);
1355294113Sbapt		nbody->child = nnext;
1356294113Sbapt		if (nnext == NULL)
1357261344Suqs			nbody->last  = NULL;
1358294113Sbapt		else
1359261344Suqs			nnext->prev = NULL;
1360261344Suqs
1361261344Suqs		/*
1362261344Suqs		 * Relink this child.
1363261344Suqs		 */
1364261344Suqs
1365261344Suqs		nchild->parent = nparent;
1366261344Suqs		nchild->prev   = nprev;
1367261344Suqs		nchild->next   = nblock;
1368261344Suqs
1369261344Suqs		nblock->prev = nchild;
1370294113Sbapt		if (nprev == NULL)
1371261344Suqs			nparent->child = nchild;
1372261344Suqs		else
1373261344Suqs			nprev->next = nchild;
1374261344Suqs
1375261344Suqs		nchild = nnext;
1376241675Suqs	}
1377241675Suqs}
1378241675Suqs
1379275432Sbaptstatic void
1380274880Sbaptpost_bk(POST_ARGS)
1381274880Sbapt{
1382294113Sbapt	struct roff_node	*n;
1383274880Sbapt
1384279527Sbapt	n = mdoc->last;
1385279527Sbapt
1386294113Sbapt	if (n->type == ROFFT_BLOCK && n->body->child == NULL) {
1387279527Sbapt		mandoc_msg(MANDOCERR_BLK_EMPTY,
1388279527Sbapt		    mdoc->parse, n->line, n->pos, "Bk");
1389294113Sbapt		roff_node_delete(mdoc, n);
1390279527Sbapt	}
1391274880Sbapt}
1392274880Sbapt
1393275432Sbaptstatic void
1394294113Sbaptpost_sm(POST_ARGS)
1395241675Suqs{
1396294113Sbapt	struct roff_node	*nch;
1397241675Suqs
1398274880Sbapt	nch = mdoc->last->child;
1399274880Sbapt
1400275432Sbapt	if (nch == NULL) {
1401275432Sbapt		mdoc->flags ^= MDOC_SMOFF;
1402275432Sbapt		return;
1403241675Suqs	}
1404241675Suqs
1405294113Sbapt	assert(nch->type == ROFFT_TEXT);
1406241675Suqs
1407275432Sbapt	if ( ! strcmp(nch->string, "on")) {
1408275432Sbapt		mdoc->flags &= ~MDOC_SMOFF;
1409275432Sbapt		return;
1410261344Suqs	}
1411275432Sbapt	if ( ! strcmp(nch->string, "off")) {
1412275432Sbapt		mdoc->flags |= MDOC_SMOFF;
1413275432Sbapt		return;
1414261344Suqs	}
1415241675Suqs
1416274880Sbapt	mandoc_vmsg(MANDOCERR_SM_BAD,
1417274880Sbapt	    mdoc->parse, nch->line, nch->pos,
1418275432Sbapt	    "%s %s", mdoc_macronames[mdoc->last->tok], nch->string);
1419275432Sbapt	mdoc_node_relink(mdoc, nch);
1420275432Sbapt	return;
1421241675Suqs}
1422241675Suqs
1423275432Sbaptstatic void
1424241675Suqspost_root(POST_ARGS)
1425241675Suqs{
1426294113Sbapt	struct roff_node *n;
1427241675Suqs
1428274880Sbapt	/* Add missing prologue data. */
1429241675Suqs
1430274880Sbapt	if (mdoc->meta.date == NULL)
1431274880Sbapt		mdoc->meta.date = mdoc->quick ?
1432274880Sbapt		    mandoc_strdup("") :
1433274880Sbapt		    mandoc_normdate(mdoc->parse, NULL, 0, 0);
1434241675Suqs
1435274880Sbapt	if (mdoc->meta.title == NULL) {
1436274880Sbapt		mandoc_msg(MANDOCERR_DT_NOTITLE,
1437274880Sbapt		    mdoc->parse, 0, 0, "EOF");
1438274880Sbapt		mdoc->meta.title = mandoc_strdup("UNTITLED");
1439241675Suqs	}
1440241675Suqs
1441274880Sbapt	if (mdoc->meta.vol == NULL)
1442274880Sbapt		mdoc->meta.vol = mandoc_strdup("LOCAL");
1443274880Sbapt
1444274880Sbapt	if (mdoc->meta.os == NULL) {
1445274880Sbapt		mandoc_msg(MANDOCERR_OS_MISSING,
1446274880Sbapt		    mdoc->parse, 0, 0, NULL);
1447274880Sbapt		mdoc->meta.os = mandoc_strdup("");
1448274880Sbapt	}
1449274880Sbapt
1450241675Suqs	/* Check that we begin with a proper `Sh'. */
1451241675Suqs
1452275432Sbapt	n = mdoc->first->child;
1453294113Sbapt	while (n != NULL && n->tok != TOKEN_NONE &&
1454294113Sbapt	    mdoc_macros[n->tok].flags & MDOC_PROLOGUE)
1455275432Sbapt		n = n->next;
1456275432Sbapt
1457275432Sbapt	if (n == NULL)
1458275432Sbapt		mandoc_msg(MANDOCERR_DOC_EMPTY, mdoc->parse, 0, 0, NULL);
1459275432Sbapt	else if (n->tok != MDOC_Sh)
1460274880Sbapt		mandoc_msg(MANDOCERR_SEC_BEFORE, mdoc->parse,
1461275432Sbapt		    n->line, n->pos, mdoc_macronames[n->tok]);
1462241675Suqs}
1463241675Suqs
1464275432Sbaptstatic void
1465241675Suqspost_st(POST_ARGS)
1466241675Suqs{
1467294113Sbapt	struct roff_node	 *n, *nch;
1468241675Suqs	const char		 *p;
1469241675Suqs
1470274880Sbapt	n = mdoc->last;
1471274880Sbapt	nch = n->child;
1472274880Sbapt
1473294113Sbapt	assert(nch->type == ROFFT_TEXT);
1474241675Suqs
1475294113Sbapt	if ((p = mdoc_a2st(nch->string)) == NULL) {
1476274880Sbapt		mandoc_vmsg(MANDOCERR_ST_BAD, mdoc->parse,
1477274880Sbapt		    nch->line, nch->pos, "St %s", nch->string);
1478294113Sbapt		roff_node_delete(mdoc, n);
1479241675Suqs	} else {
1480274880Sbapt		free(nch->string);
1481274880Sbapt		nch->string = mandoc_strdup(p);
1482241675Suqs	}
1483241675Suqs}
1484241675Suqs
1485275432Sbaptstatic void
1486241675Suqspost_rs(POST_ARGS)
1487241675Suqs{
1488294113Sbapt	struct roff_node *np, *nch, *next, *prev;
1489241675Suqs	int		  i, j;
1490241675Suqs
1491279527Sbapt	np = mdoc->last;
1492279527Sbapt
1493294113Sbapt	if (np->type != ROFFT_BODY)
1494275432Sbapt		return;
1495279527Sbapt
1496279527Sbapt	if (np->child == NULL) {
1497279527Sbapt		mandoc_msg(MANDOCERR_RS_EMPTY, mdoc->parse,
1498279527Sbapt		    np->line, np->pos, "Rs");
1499275432Sbapt		return;
1500241675Suqs	}
1501241675Suqs
1502241675Suqs	/*
1503241675Suqs	 * The full `Rs' block needs special handling to order the
1504241675Suqs	 * sub-elements according to `rsord'.  Pick through each element
1505274880Sbapt	 * and correctly order it.  This is an insertion sort.
1506241675Suqs	 */
1507241675Suqs
1508241675Suqs	next = NULL;
1509279527Sbapt	for (nch = np->child->next; nch != NULL; nch = next) {
1510279527Sbapt		/* Determine order number of this child. */
1511241675Suqs		for (i = 0; i < RSORD_MAX; i++)
1512279527Sbapt			if (rsord[i] == nch->tok)
1513241675Suqs				break;
1514241675Suqs
1515274880Sbapt		if (i == RSORD_MAX) {
1516274880Sbapt			mandoc_msg(MANDOCERR_RS_BAD,
1517279527Sbapt			    mdoc->parse, nch->line, nch->pos,
1518279527Sbapt			    mdoc_macronames[nch->tok]);
1519274880Sbapt			i = -1;
1520279527Sbapt		} else if (nch->tok == MDOC__J || nch->tok == MDOC__B)
1521279527Sbapt			np->norm->Rs.quote_T++;
1522274880Sbapt
1523274880Sbapt		/*
1524279527Sbapt		 * Remove this child from the chain.  This somewhat
1525294113Sbapt		 * repeats roff_node_unlink(), but since we're
1526241675Suqs		 * just re-ordering, there's no need for the
1527241675Suqs		 * full unlink process.
1528241675Suqs		 */
1529274880Sbapt
1530279527Sbapt		if ((next = nch->next) != NULL)
1531279527Sbapt			next->prev = nch->prev;
1532241675Suqs
1533279527Sbapt		if ((prev = nch->prev) != NULL)
1534279527Sbapt			prev->next = nch->next;
1535241675Suqs
1536279527Sbapt		nch->prev = nch->next = NULL;
1537241675Suqs
1538274880Sbapt		/*
1539241675Suqs		 * Scan back until we reach a node that's
1540279527Sbapt		 * to be ordered before this child.
1541241675Suqs		 */
1542241675Suqs
1543241675Suqs		for ( ; prev ; prev = prev->prev) {
1544241675Suqs			/* Determine order of `prev'. */
1545241675Suqs			for (j = 0; j < RSORD_MAX; j++)
1546241675Suqs				if (rsord[j] == prev->tok)
1547241675Suqs					break;
1548274880Sbapt			if (j == RSORD_MAX)
1549274880Sbapt				j = -1;
1550241675Suqs
1551241675Suqs			if (j <= i)
1552241675Suqs				break;
1553241675Suqs		}
1554241675Suqs
1555241675Suqs		/*
1556279527Sbapt		 * Set this child back into its correct place
1557279527Sbapt		 * in front of the `prev' node.
1558241675Suqs		 */
1559241675Suqs
1560279527Sbapt		nch->prev = prev;
1561241675Suqs
1562279527Sbapt		if (prev == NULL) {
1563279527Sbapt			np->child->prev = nch;
1564279527Sbapt			nch->next = np->child;
1565279527Sbapt			np->child = nch;
1566279527Sbapt		} else {
1567241675Suqs			if (prev->next)
1568279527Sbapt				prev->next->prev = nch;
1569279527Sbapt			nch->next = prev->next;
1570279527Sbapt			prev->next = nch;
1571241675Suqs		}
1572241675Suqs	}
1573241675Suqs}
1574241675Suqs
1575261344Suqs/*
1576261344Suqs * For some arguments of some macros,
1577261344Suqs * convert all breakable hyphens into ASCII_HYPH.
1578261344Suqs */
1579275432Sbaptstatic void
1580261344Suqspost_hyph(POST_ARGS)
1581261344Suqs{
1582294113Sbapt	struct roff_node	*nch;
1583261344Suqs	char			*cp;
1584261344Suqs
1585279527Sbapt	for (nch = mdoc->last->child; nch != NULL; nch = nch->next) {
1586294113Sbapt		if (nch->type != ROFFT_TEXT)
1587261344Suqs			continue;
1588261344Suqs		cp = nch->string;
1589279527Sbapt		if (*cp == '\0')
1590261344Suqs			continue;
1591279527Sbapt		while (*(++cp) != '\0')
1592279527Sbapt			if (*cp == '-' &&
1593261344Suqs			    isalpha((unsigned char)cp[-1]) &&
1594261344Suqs			    isalpha((unsigned char)cp[1]))
1595261344Suqs				*cp = ASCII_HYPH;
1596261344Suqs	}
1597261344Suqs}
1598261344Suqs
1599275432Sbaptstatic void
1600241675Suqspost_ns(POST_ARGS)
1601241675Suqs{
1602241675Suqs
1603294113Sbapt	if (mdoc->last->flags & MDOC_LINE)
1604274880Sbapt		mandoc_msg(MANDOCERR_NS_SKIP, mdoc->parse,
1605274880Sbapt		    mdoc->last->line, mdoc->last->pos, NULL);
1606241675Suqs}
1607241675Suqs
1608275432Sbaptstatic void
1609241675Suqspost_sh(POST_ARGS)
1610241675Suqs{
1611241675Suqs
1612274880Sbapt	post_ignpar(mdoc);
1613274880Sbapt
1614275432Sbapt	switch (mdoc->last->type) {
1615294113Sbapt	case ROFFT_HEAD:
1616275432Sbapt		post_sh_head(mdoc);
1617275432Sbapt		break;
1618294113Sbapt	case ROFFT_BODY:
1619275432Sbapt		switch (mdoc->lastsec)  {
1620275432Sbapt		case SEC_NAME:
1621275432Sbapt			post_sh_name(mdoc);
1622275432Sbapt			break;
1623275432Sbapt		case SEC_SEE_ALSO:
1624275432Sbapt			post_sh_see_also(mdoc);
1625275432Sbapt			break;
1626275432Sbapt		case SEC_AUTHORS:
1627275432Sbapt			post_sh_authors(mdoc);
1628275432Sbapt			break;
1629275432Sbapt		default:
1630275432Sbapt			break;
1631275432Sbapt		}
1632275432Sbapt		break;
1633275432Sbapt	default:
1634275432Sbapt		break;
1635275432Sbapt	}
1636241675Suqs}
1637241675Suqs
1638275432Sbaptstatic void
1639275432Sbaptpost_sh_name(POST_ARGS)
1640241675Suqs{
1641294113Sbapt	struct roff_node *n;
1642279527Sbapt	int hasnm, hasnd;
1643241675Suqs
1644279527Sbapt	hasnm = hasnd = 0;
1645241675Suqs
1646279527Sbapt	for (n = mdoc->last->child; n != NULL; n = n->next) {
1647279527Sbapt		switch (n->tok) {
1648279527Sbapt		case MDOC_Nm:
1649279527Sbapt			hasnm = 1;
1650279527Sbapt			break;
1651279527Sbapt		case MDOC_Nd:
1652279527Sbapt			hasnd = 1;
1653279527Sbapt			if (n->next != NULL)
1654279527Sbapt				mandoc_msg(MANDOCERR_NAMESEC_ND,
1655279527Sbapt				    mdoc->parse, n->line, n->pos, NULL);
1656279527Sbapt			break;
1657294113Sbapt		case TOKEN_NONE:
1658279527Sbapt			if (hasnm)
1659279527Sbapt				break;
1660279527Sbapt			/* FALLTHROUGH */
1661279527Sbapt		default:
1662279527Sbapt			mandoc_msg(MANDOCERR_NAMESEC_BAD, mdoc->parse,
1663279527Sbapt			    n->line, n->pos, mdoc_macronames[n->tok]);
1664279527Sbapt			break;
1665279527Sbapt		}
1666241675Suqs	}
1667241675Suqs
1668279527Sbapt	if ( ! hasnm)
1669279527Sbapt		mandoc_msg(MANDOCERR_NAMESEC_NONM, mdoc->parse,
1670279527Sbapt		    mdoc->last->line, mdoc->last->pos, NULL);
1671279527Sbapt	if ( ! hasnd)
1672279527Sbapt		mandoc_msg(MANDOCERR_NAMESEC_NOND, mdoc->parse,
1673279527Sbapt		    mdoc->last->line, mdoc->last->pos, NULL);
1674241675Suqs}
1675241675Suqs
1676275432Sbaptstatic void
1677275432Sbaptpost_sh_see_also(POST_ARGS)
1678275432Sbapt{
1679294113Sbapt	const struct roff_node	*n;
1680279527Sbapt	const char		*name, *sec;
1681275432Sbapt	const char		*lastname, *lastsec, *lastpunct;
1682275432Sbapt	int			 cmp;
1683275432Sbapt
1684275432Sbapt	n = mdoc->last->child;
1685275432Sbapt	lastname = lastsec = lastpunct = NULL;
1686275432Sbapt	while (n != NULL) {
1687294113Sbapt		if (n->tok != MDOC_Xr ||
1688294113Sbapt		    n->child == NULL ||
1689294113Sbapt		    n->child->next == NULL)
1690275432Sbapt			break;
1691275432Sbapt
1692275432Sbapt		/* Process one .Xr node. */
1693275432Sbapt
1694275432Sbapt		name = n->child->string;
1695275432Sbapt		sec = n->child->next->string;
1696275432Sbapt		if (lastsec != NULL) {
1697275432Sbapt			if (lastpunct[0] != ',' || lastpunct[1] != '\0')
1698275432Sbapt				mandoc_vmsg(MANDOCERR_XR_PUNCT,
1699275432Sbapt				    mdoc->parse, n->line, n->pos,
1700275432Sbapt				    "%s before %s(%s)", lastpunct,
1701275432Sbapt				    name, sec);
1702275432Sbapt			cmp = strcmp(lastsec, sec);
1703275432Sbapt			if (cmp > 0)
1704275432Sbapt				mandoc_vmsg(MANDOCERR_XR_ORDER,
1705275432Sbapt				    mdoc->parse, n->line, n->pos,
1706275432Sbapt				    "%s(%s) after %s(%s)", name,
1707275432Sbapt				    sec, lastname, lastsec);
1708275432Sbapt			else if (cmp == 0 &&
1709275432Sbapt			    strcasecmp(lastname, name) > 0)
1710275432Sbapt				mandoc_vmsg(MANDOCERR_XR_ORDER,
1711275432Sbapt				    mdoc->parse, n->line, n->pos,
1712275432Sbapt				    "%s after %s", name, lastname);
1713275432Sbapt		}
1714275432Sbapt		lastname = name;
1715275432Sbapt		lastsec = sec;
1716275432Sbapt
1717275432Sbapt		/* Process the following node. */
1718275432Sbapt
1719275432Sbapt		n = n->next;
1720275432Sbapt		if (n == NULL)
1721275432Sbapt			break;
1722275432Sbapt		if (n->tok == MDOC_Xr) {
1723275432Sbapt			lastpunct = "none";
1724275432Sbapt			continue;
1725275432Sbapt		}
1726294113Sbapt		if (n->type != ROFFT_TEXT)
1727275432Sbapt			break;
1728275432Sbapt		for (name = n->string; *name != '\0'; name++)
1729275432Sbapt			if (isalpha((const unsigned char)*name))
1730275432Sbapt				return;
1731275432Sbapt		lastpunct = n->string;
1732275432Sbapt		if (n->next == NULL)
1733275432Sbapt			mandoc_vmsg(MANDOCERR_XR_PUNCT, mdoc->parse,
1734275432Sbapt			    n->line, n->pos, "%s after %s(%s)",
1735275432Sbapt			    lastpunct, lastname, lastsec);
1736275432Sbapt		n = n->next;
1737275432Sbapt	}
1738275432Sbapt}
1739275432Sbapt
1740241675Suqsstatic int
1741294113Sbaptchild_an(const struct roff_node *n)
1742275432Sbapt{
1743275432Sbapt
1744275432Sbapt	for (n = n->child; n != NULL; n = n->next)
1745294113Sbapt		if ((n->tok == MDOC_An && n->child != NULL) || child_an(n))
1746294113Sbapt			return 1;
1747294113Sbapt	return 0;
1748275432Sbapt}
1749275432Sbapt
1750275432Sbaptstatic void
1751275432Sbaptpost_sh_authors(POST_ARGS)
1752275432Sbapt{
1753275432Sbapt
1754275432Sbapt	if ( ! child_an(mdoc->last))
1755275432Sbapt		mandoc_msg(MANDOCERR_AN_MISSING, mdoc->parse,
1756275432Sbapt		    mdoc->last->line, mdoc->last->pos, NULL);
1757275432Sbapt}
1758275432Sbapt
1759275432Sbaptstatic void
1760241675Suqspost_sh_head(POST_ARGS)
1761241675Suqs{
1762274880Sbapt	const char	*goodsec;
1763294113Sbapt	enum roff_sec	 sec;
1764241675Suqs
1765241675Suqs	/*
1766241675Suqs	 * Process a new section.  Sections are either "named" or
1767241675Suqs	 * "custom".  Custom sections are user-defined, while named ones
1768241675Suqs	 * follow a conventional order and may only appear in certain
1769241675Suqs	 * manual sections.
1770241675Suqs	 */
1771241675Suqs
1772294113Sbapt	sec = mdoc->last->sec;
1773241675Suqs
1774241675Suqs	/* The NAME should be first. */
1775241675Suqs
1776241675Suqs	if (SEC_NAME != sec && SEC_NONE == mdoc->lastnamed)
1777274880Sbapt		mandoc_vmsg(MANDOCERR_NAMESEC_FIRST, mdoc->parse,
1778274880Sbapt		    mdoc->last->line, mdoc->last->pos,
1779294113Sbapt		    "Sh %s", secnames[sec]);
1780241675Suqs
1781241675Suqs	/* The SYNOPSIS gets special attention in other areas. */
1782241675Suqs
1783294113Sbapt	if (sec == SEC_SYNOPSIS) {
1784261344Suqs		roff_setreg(mdoc->roff, "nS", 1, '=');
1785241675Suqs		mdoc->flags |= MDOC_SYNOPSIS;
1786261344Suqs	} else {
1787261344Suqs		roff_setreg(mdoc->roff, "nS", 0, '=');
1788241675Suqs		mdoc->flags &= ~MDOC_SYNOPSIS;
1789261344Suqs	}
1790241675Suqs
1791241675Suqs	/* Mark our last section. */
1792241675Suqs
1793241675Suqs	mdoc->lastsec = sec;
1794241675Suqs
1795241675Suqs	/* We don't care about custom sections after this. */
1796241675Suqs
1797294113Sbapt	if (sec == SEC_CUSTOM)
1798275432Sbapt		return;
1799241675Suqs
1800241675Suqs	/*
1801241675Suqs	 * Check whether our non-custom section is being repeated or is
1802241675Suqs	 * out of order.
1803241675Suqs	 */
1804241675Suqs
1805241675Suqs	if (sec == mdoc->lastnamed)
1806274880Sbapt		mandoc_vmsg(MANDOCERR_SEC_REP, mdoc->parse,
1807274880Sbapt		    mdoc->last->line, mdoc->last->pos,
1808294113Sbapt		    "Sh %s", secnames[sec]);
1809241675Suqs
1810241675Suqs	if (sec < mdoc->lastnamed)
1811274880Sbapt		mandoc_vmsg(MANDOCERR_SEC_ORDER, mdoc->parse,
1812274880Sbapt		    mdoc->last->line, mdoc->last->pos,
1813294113Sbapt		    "Sh %s", secnames[sec]);
1814241675Suqs
1815241675Suqs	/* Mark the last named section. */
1816241675Suqs
1817241675Suqs	mdoc->lastnamed = sec;
1818241675Suqs
1819241675Suqs	/* Check particular section/manual conventions. */
1820241675Suqs
1821294113Sbapt	if (mdoc->meta.msec == NULL)
1822275432Sbapt		return;
1823241675Suqs
1824274880Sbapt	goodsec = NULL;
1825241675Suqs	switch (sec) {
1826274880Sbapt	case SEC_ERRORS:
1827274880Sbapt		if (*mdoc->meta.msec == '4')
1828274880Sbapt			break;
1829274880Sbapt		goodsec = "2, 3, 4, 9";
1830241675Suqs		/* FALLTHROUGH */
1831274880Sbapt	case SEC_RETURN_VALUES:
1832274880Sbapt	case SEC_LIBRARY:
1833241675Suqs		if (*mdoc->meta.msec == '2')
1834241675Suqs			break;
1835241675Suqs		if (*mdoc->meta.msec == '3')
1836241675Suqs			break;
1837274880Sbapt		if (NULL == goodsec)
1838274880Sbapt			goodsec = "2, 3, 9";
1839274880Sbapt		/* FALLTHROUGH */
1840274880Sbapt	case SEC_CONTEXT:
1841241675Suqs		if (*mdoc->meta.msec == '9')
1842241675Suqs			break;
1843274880Sbapt		if (NULL == goodsec)
1844274880Sbapt			goodsec = "9";
1845274880Sbapt		mandoc_vmsg(MANDOCERR_SEC_MSEC, mdoc->parse,
1846274880Sbapt		    mdoc->last->line, mdoc->last->pos,
1847294113Sbapt		    "Sh %s for %s only", secnames[sec], goodsec);
1848241675Suqs		break;
1849241675Suqs	default:
1850241675Suqs		break;
1851241675Suqs	}
1852241675Suqs}
1853241675Suqs
1854275432Sbaptstatic void
1855241675Suqspost_ignpar(POST_ARGS)
1856241675Suqs{
1857294113Sbapt	struct roff_node *np;
1858241675Suqs
1859279527Sbapt	switch (mdoc->last->type) {
1860294113Sbapt	case ROFFT_HEAD:
1861279527Sbapt		post_hyph(mdoc);
1862275432Sbapt		return;
1863294113Sbapt	case ROFFT_BODY:
1864279527Sbapt		break;
1865279527Sbapt	default:
1866279527Sbapt		return;
1867279527Sbapt	}
1868241675Suqs
1869294113Sbapt	if ((np = mdoc->last->child) != NULL)
1870294113Sbapt		if (np->tok == MDOC_Pp || np->tok == MDOC_Lp) {
1871274880Sbapt			mandoc_vmsg(MANDOCERR_PAR_SKIP,
1872274880Sbapt			    mdoc->parse, np->line, np->pos,
1873274880Sbapt			    "%s after %s", mdoc_macronames[np->tok],
1874274880Sbapt			    mdoc_macronames[mdoc->last->tok]);
1875294113Sbapt			roff_node_delete(mdoc, np);
1876241675Suqs		}
1877241675Suqs
1878294113Sbapt	if ((np = mdoc->last->last) != NULL)
1879294113Sbapt		if (np->tok == MDOC_Pp || np->tok == MDOC_Lp) {
1880274880Sbapt			mandoc_vmsg(MANDOCERR_PAR_SKIP, mdoc->parse,
1881274880Sbapt			    np->line, np->pos, "%s at the end of %s",
1882274880Sbapt			    mdoc_macronames[np->tok],
1883274880Sbapt			    mdoc_macronames[mdoc->last->tok]);
1884294113Sbapt			roff_node_delete(mdoc, np);
1885241675Suqs		}
1886241675Suqs}
1887241675Suqs
1888275432Sbaptstatic void
1889294113Sbaptpost_prevpar(POST_ARGS)
1890241675Suqs{
1891294113Sbapt	struct roff_node *n;
1892241675Suqs
1893294113Sbapt	n = mdoc->last;
1894294113Sbapt	if (NULL == n->prev)
1895275432Sbapt		return;
1896294113Sbapt	if (n->type != ROFFT_ELEM && n->type != ROFFT_BLOCK)
1897275432Sbapt		return;
1898241675Suqs
1899274880Sbapt	/*
1900241675Suqs	 * Don't allow prior `Lp' or `Pp' prior to a paragraph-type
1901241675Suqs	 * block:  `Lp', `Pp', or non-compact `Bd' or `Bl'.
1902241675Suqs	 */
1903241675Suqs
1904294113Sbapt	if (n->prev->tok != MDOC_Pp &&
1905294113Sbapt	    n->prev->tok != MDOC_Lp &&
1906294113Sbapt	    n->prev->tok != MDOC_br)
1907275432Sbapt		return;
1908294113Sbapt	if (n->tok == MDOC_Bl && n->norm->Bl.comp)
1909275432Sbapt		return;
1910294113Sbapt	if (n->tok == MDOC_Bd && n->norm->Bd.comp)
1911275432Sbapt		return;
1912294113Sbapt	if (n->tok == MDOC_It && n->parent->norm->Bl.comp)
1913275432Sbapt		return;
1914241675Suqs
1915274880Sbapt	mandoc_vmsg(MANDOCERR_PAR_SKIP, mdoc->parse,
1916294113Sbapt	    n->prev->line, n->prev->pos,
1917294113Sbapt	    "%s before %s", mdoc_macronames[n->prev->tok],
1918274880Sbapt	    mdoc_macronames[n->tok]);
1919294113Sbapt	roff_node_delete(mdoc, n->prev);
1920241675Suqs}
1921241675Suqs
1922275432Sbaptstatic void
1923261344Suqspost_par(POST_ARGS)
1924261344Suqs{
1925294113Sbapt	struct roff_node *np;
1926261344Suqs
1927279527Sbapt	np = mdoc->last;
1928294113Sbapt	if (np->tok != MDOC_br && np->tok != MDOC_sp)
1929294113Sbapt		post_prevpar(mdoc);
1930274880Sbapt
1931279527Sbapt	if (np->tok == MDOC_sp) {
1932294113Sbapt		if (np->child != NULL && np->child->next != NULL)
1933279527Sbapt			mandoc_vmsg(MANDOCERR_ARG_EXCESS, mdoc->parse,
1934279527Sbapt			    np->child->next->line, np->child->next->pos,
1935279527Sbapt			    "sp ... %s", np->child->next->string);
1936279527Sbapt	} else if (np->child != NULL)
1937279527Sbapt		mandoc_vmsg(MANDOCERR_ARG_SKIP,
1938279527Sbapt		    mdoc->parse, np->line, np->pos, "%s %s",
1939279527Sbapt		    mdoc_macronames[np->tok], np->child->string);
1940261344Suqs
1941294113Sbapt	if ((np = mdoc->last->prev) == NULL) {
1942274880Sbapt		np = mdoc->last->parent;
1943294113Sbapt		if (np->tok != MDOC_Sh && np->tok != MDOC_Ss)
1944275432Sbapt			return;
1945294113Sbapt	} else if (np->tok != MDOC_Pp && np->tok != MDOC_Lp &&
1946294113Sbapt	    (mdoc->last->tok != MDOC_br ||
1947294113Sbapt	     (np->tok != MDOC_sp && np->tok != MDOC_br)))
1948275432Sbapt		return;
1949261344Suqs
1950274880Sbapt	mandoc_vmsg(MANDOCERR_PAR_SKIP, mdoc->parse,
1951274880Sbapt	    mdoc->last->line, mdoc->last->pos,
1952274880Sbapt	    "%s after %s", mdoc_macronames[mdoc->last->tok],
1953274880Sbapt	    mdoc_macronames[np->tok]);
1954294113Sbapt	roff_node_delete(mdoc, mdoc->last);
1955261344Suqs}
1956261344Suqs
1957275432Sbaptstatic void
1958241675Suqspost_dd(POST_ARGS)
1959241675Suqs{
1960294113Sbapt	struct roff_node *n;
1961274880Sbapt	char		 *datestr;
1962241675Suqs
1963294113Sbapt	n = mdoc->last;
1964294113Sbapt	if (mdoc->meta.date != NULL) {
1965294113Sbapt		mandoc_msg(MANDOCERR_PROLOG_REP, mdoc->parse,
1966294113Sbapt		    n->line, n->pos, "Dd");
1967241675Suqs		free(mdoc->meta.date);
1968294113Sbapt	} else if (mdoc->flags & MDOC_PBODY)
1969294113Sbapt		mandoc_msg(MANDOCERR_PROLOG_LATE, mdoc->parse,
1970294113Sbapt		    n->line, n->pos, "Dd");
1971294113Sbapt	else if (mdoc->meta.title != NULL)
1972294113Sbapt		mandoc_msg(MANDOCERR_PROLOG_ORDER, mdoc->parse,
1973294113Sbapt		    n->line, n->pos, "Dd after Dt");
1974294113Sbapt	else if (mdoc->meta.os != NULL)
1975294113Sbapt		mandoc_msg(MANDOCERR_PROLOG_ORDER, mdoc->parse,
1976294113Sbapt		    n->line, n->pos, "Dd after Os");
1977241675Suqs
1978294113Sbapt	if (n->child == NULL || n->child->string[0] == '\0') {
1979274880Sbapt		mdoc->meta.date = mdoc->quick ? mandoc_strdup("") :
1980274880Sbapt		    mandoc_normdate(mdoc->parse, NULL, n->line, n->pos);
1981274880Sbapt		goto out;
1982241675Suqs	}
1983241675Suqs
1984274880Sbapt	datestr = NULL;
1985294113Sbapt	deroff(&datestr, n);
1986274880Sbapt	if (mdoc->quick)
1987274880Sbapt		mdoc->meta.date = datestr;
1988274880Sbapt	else {
1989274880Sbapt		mdoc->meta.date = mandoc_normdate(mdoc->parse,
1990274880Sbapt		    datestr, n->line, n->pos);
1991274880Sbapt		free(datestr);
1992241675Suqs	}
1993274880Sbaptout:
1994294113Sbapt	roff_node_delete(mdoc, n);
1995241675Suqs}
1996241675Suqs
1997275432Sbaptstatic void
1998241675Suqspost_dt(POST_ARGS)
1999241675Suqs{
2000294113Sbapt	struct roff_node *nn, *n;
2001241675Suqs	const char	 *cp;
2002241675Suqs	char		 *p;
2003241675Suqs
2004241675Suqs	n = mdoc->last;
2005294113Sbapt	if (mdoc->flags & MDOC_PBODY) {
2006294113Sbapt		mandoc_msg(MANDOCERR_DT_LATE, mdoc->parse,
2007294113Sbapt		    n->line, n->pos, "Dt");
2008294113Sbapt		goto out;
2009294113Sbapt	}
2010241675Suqs
2011294113Sbapt	if (mdoc->meta.title != NULL)
2012294113Sbapt		mandoc_msg(MANDOCERR_PROLOG_REP, mdoc->parse,
2013294113Sbapt		    n->line, n->pos, "Dt");
2014294113Sbapt	else if (mdoc->meta.os != NULL)
2015294113Sbapt		mandoc_msg(MANDOCERR_PROLOG_ORDER, mdoc->parse,
2016294113Sbapt		    n->line, n->pos, "Dt after Os");
2017294113Sbapt
2018274880Sbapt	free(mdoc->meta.title);
2019274880Sbapt	free(mdoc->meta.msec);
2020274880Sbapt	free(mdoc->meta.vol);
2021274880Sbapt	free(mdoc->meta.arch);
2022241675Suqs
2023274880Sbapt	mdoc->meta.title = NULL;
2024274880Sbapt	mdoc->meta.msec = NULL;
2025274880Sbapt	mdoc->meta.vol = NULL;
2026274880Sbapt	mdoc->meta.arch = NULL;
2027241675Suqs
2028279527Sbapt	/* Mandatory first argument: title. */
2029241675Suqs
2030279527Sbapt	nn = n->child;
2031279527Sbapt	if (nn == NULL || *nn->string == '\0') {
2032274880Sbapt		mandoc_msg(MANDOCERR_DT_NOTITLE,
2033274880Sbapt		    mdoc->parse, n->line, n->pos, "Dt");
2034274880Sbapt		mdoc->meta.title = mandoc_strdup("UNTITLED");
2035279527Sbapt	} else {
2036279527Sbapt		mdoc->meta.title = mandoc_strdup(nn->string);
2037279527Sbapt
2038279527Sbapt		/* Check that all characters are uppercase. */
2039279527Sbapt
2040279527Sbapt		for (p = nn->string; *p != '\0'; p++)
2041279527Sbapt			if (islower((unsigned char)*p)) {
2042279527Sbapt				mandoc_vmsg(MANDOCERR_TITLE_CASE,
2043279527Sbapt				    mdoc->parse, nn->line,
2044279527Sbapt				    nn->pos + (p - nn->string),
2045279527Sbapt				    "Dt %s", nn->string);
2046279527Sbapt				break;
2047279527Sbapt			}
2048241675Suqs	}
2049241675Suqs
2050279527Sbapt	/* Mandatory second argument: section.�*/
2051241675Suqs
2052279527Sbapt	if (nn != NULL)
2053279527Sbapt		nn = nn->next;
2054241675Suqs
2055279527Sbapt	if (nn == NULL) {
2056274880Sbapt		mandoc_vmsg(MANDOCERR_MSEC_MISSING,
2057274880Sbapt		    mdoc->parse, n->line, n->pos,
2058274880Sbapt		    "Dt %s", mdoc->meta.title);
2059241675Suqs		mdoc->meta.vol = mandoc_strdup("LOCAL");
2060279527Sbapt		goto out;  /* msec and arch remain NULL. */
2061241675Suqs	}
2062241675Suqs
2063279527Sbapt	mdoc->meta.msec = mandoc_strdup(nn->string);
2064241675Suqs
2065279527Sbapt	/* Infer volume title from section number. */
2066279527Sbapt
2067241675Suqs	cp = mandoc_a2msec(nn->string);
2068279527Sbapt	if (cp == NULL) {
2069274880Sbapt		mandoc_vmsg(MANDOCERR_MSEC_BAD, mdoc->parse,
2070274880Sbapt		    nn->line, nn->pos, "Dt ... %s", nn->string);
2071241675Suqs		mdoc->meta.vol = mandoc_strdup(nn->string);
2072279527Sbapt	} else
2073279527Sbapt		mdoc->meta.vol = mandoc_strdup(cp);
2074241675Suqs
2075279527Sbapt	/* Optional third argument: architecture. */
2076241675Suqs
2077279527Sbapt	if ((nn = nn->next) == NULL)
2078279527Sbapt		goto out;
2079241675Suqs
2080279527Sbapt	for (p = nn->string; *p != '\0'; p++)
2081279527Sbapt		*p = tolower((unsigned char)*p);
2082279527Sbapt	mdoc->meta.arch = mandoc_strdup(nn->string);
2083279527Sbapt
2084279527Sbapt	/* Ignore fourth and later arguments. */
2085279527Sbapt
2086279527Sbapt	if ((nn = nn->next) != NULL)
2087279527Sbapt		mandoc_vmsg(MANDOCERR_ARG_EXCESS, mdoc->parse,
2088279527Sbapt		    nn->line, nn->pos, "Dt ... %s", nn->string);
2089279527Sbapt
2090274880Sbaptout:
2091294113Sbapt	roff_node_delete(mdoc, n);
2092241675Suqs}
2093241675Suqs
2094275432Sbaptstatic void
2095241675Suqspost_bx(POST_ARGS)
2096241675Suqs{
2097294113Sbapt	struct roff_node	*n;
2098241675Suqs
2099274880Sbapt	/*
2100241675Suqs	 * Make `Bx's second argument always start with an uppercase
2101241675Suqs	 * letter.  Groff checks if it's an "accepted" term, but we just
2102241675Suqs	 * uppercase blindly.
2103241675Suqs	 */
2104241675Suqs
2105294113Sbapt	if ((n = mdoc->last->child) != NULL && (n = n->next) != NULL)
2106274880Sbapt		*n->string = (char)toupper((unsigned char)*n->string);
2107241675Suqs}
2108241675Suqs
2109275432Sbaptstatic void
2110241675Suqspost_os(POST_ARGS)
2111241675Suqs{
2112241675Suqs#ifndef OSNAME
2113241675Suqs	struct utsname	  utsname;
2114274880Sbapt	static char	 *defbuf;
2115241675Suqs#endif
2116294113Sbapt	struct roff_node *n;
2117241675Suqs
2118241675Suqs	n = mdoc->last;
2119294113Sbapt	if (mdoc->meta.os != NULL)
2120294113Sbapt		mandoc_msg(MANDOCERR_PROLOG_REP, mdoc->parse,
2121294113Sbapt		    n->line, n->pos, "Os");
2122294113Sbapt	else if (mdoc->flags & MDOC_PBODY)
2123294113Sbapt		mandoc_msg(MANDOCERR_PROLOG_LATE, mdoc->parse,
2124294113Sbapt		    n->line, n->pos, "Os");
2125241675Suqs
2126241675Suqs	/*
2127261344Suqs	 * Set the operating system by way of the `Os' macro.
2128261344Suqs	 * The order of precedence is:
2129261344Suqs	 * 1. the argument of the `Os' macro, unless empty
2130261344Suqs	 * 2. the -Ios=foo command line argument, if provided
2131261344Suqs	 * 3. -DOSNAME="\"foo\"", if provided during compilation
2132261344Suqs	 * 4. "sysname release" from uname(3)
2133274880Sbapt	 */
2134241675Suqs
2135261344Suqs	free(mdoc->meta.os);
2136274880Sbapt	mdoc->meta.os = NULL;
2137294113Sbapt	deroff(&mdoc->meta.os, n);
2138274880Sbapt	if (mdoc->meta.os)
2139274880Sbapt		goto out;
2140241675Suqs
2141274880Sbapt	if (mdoc->defos) {
2142274880Sbapt		mdoc->meta.os = mandoc_strdup(mdoc->defos);
2143274880Sbapt		goto out;
2144241675Suqs	}
2145241675Suqs
2146241675Suqs#ifdef OSNAME
2147274880Sbapt	mdoc->meta.os = mandoc_strdup(OSNAME);
2148241675Suqs#else /*!OSNAME */
2149294113Sbapt	if (defbuf == NULL) {
2150294113Sbapt		if (uname(&utsname) == -1) {
2151274880Sbapt			mandoc_msg(MANDOCERR_OS_UNAME, mdoc->parse,
2152274880Sbapt			    n->line, n->pos, "Os");
2153274880Sbapt			defbuf = mandoc_strdup("UNKNOWN");
2154274880Sbapt		} else
2155274880Sbapt			mandoc_asprintf(&defbuf, "%s %s",
2156274880Sbapt			    utsname.sysname, utsname.release);
2157274880Sbapt	}
2158274880Sbapt	mdoc->meta.os = mandoc_strdup(defbuf);
2159241675Suqs#endif /*!OSNAME*/
2160241675Suqs
2161274880Sbaptout:
2162294113Sbapt	roff_node_delete(mdoc, n);
2163241675Suqs}
2164241675Suqs
2165274880Sbapt/*
2166274880Sbapt * If no argument is provided,
2167274880Sbapt * fill in the name of the current manual page.
2168274880Sbapt */
2169275432Sbaptstatic void
2170274880Sbaptpost_ex(POST_ARGS)
2171241675Suqs{
2172294113Sbapt	struct roff_node *n;
2173241675Suqs
2174294113Sbapt	post_std(mdoc);
2175294113Sbapt
2176241675Suqs	n = mdoc->last;
2177294113Sbapt	if (n->child != NULL)
2178275432Sbapt		return;
2179241675Suqs
2180274880Sbapt	if (mdoc->meta.name == NULL) {
2181274880Sbapt		mandoc_msg(MANDOCERR_EX_NONAME, mdoc->parse,
2182274880Sbapt		    n->line, n->pos, "Ex");
2183275432Sbapt		return;
2184274880Sbapt	}
2185274880Sbapt
2186294113Sbapt	mdoc->next = ROFF_NEXT_CHILD;
2187294113Sbapt	roff_word_alloc(mdoc, n->line, n->pos, mdoc->meta.name);
2188274880Sbapt	mdoc->last = n;
2189241675Suqs}
2190241675Suqs
2191294113Sbaptenum roff_sec
2192294113Sbaptmdoc_a2sec(const char *p)
2193241675Suqs{
2194241675Suqs	int		 i;
2195241675Suqs
2196274880Sbapt	for (i = 0; i < (int)SEC__MAX; i++)
2197241675Suqs		if (secnames[i] && 0 == strcmp(p, secnames[i]))
2198294113Sbapt			return (enum roff_sec)i;
2199241675Suqs
2200294113Sbapt	return SEC_CUSTOM;
2201241675Suqs}
2202241675Suqs
2203241675Suqsstatic size_t
2204294113Sbaptmacro2len(int macro)
2205241675Suqs{
2206241675Suqs
2207241675Suqs	switch (macro) {
2208274880Sbapt	case MDOC_Ad:
2209294113Sbapt		return 12;
2210274880Sbapt	case MDOC_Ao:
2211294113Sbapt		return 12;
2212274880Sbapt	case MDOC_An:
2213294113Sbapt		return 12;
2214274880Sbapt	case MDOC_Aq:
2215294113Sbapt		return 12;
2216274880Sbapt	case MDOC_Ar:
2217294113Sbapt		return 12;
2218274880Sbapt	case MDOC_Bo:
2219294113Sbapt		return 12;
2220274880Sbapt	case MDOC_Bq:
2221294113Sbapt		return 12;
2222274880Sbapt	case MDOC_Cd:
2223294113Sbapt		return 12;
2224274880Sbapt	case MDOC_Cm:
2225294113Sbapt		return 10;
2226274880Sbapt	case MDOC_Do:
2227294113Sbapt		return 10;
2228274880Sbapt	case MDOC_Dq:
2229294113Sbapt		return 12;
2230274880Sbapt	case MDOC_Dv:
2231294113Sbapt		return 12;
2232274880Sbapt	case MDOC_Eo:
2233294113Sbapt		return 12;
2234274880Sbapt	case MDOC_Em:
2235294113Sbapt		return 10;
2236274880Sbapt	case MDOC_Er:
2237294113Sbapt		return 17;
2238274880Sbapt	case MDOC_Ev:
2239294113Sbapt		return 15;
2240274880Sbapt	case MDOC_Fa:
2241294113Sbapt		return 12;
2242274880Sbapt	case MDOC_Fl:
2243294113Sbapt		return 10;
2244274880Sbapt	case MDOC_Fo:
2245294113Sbapt		return 16;
2246274880Sbapt	case MDOC_Fn:
2247294113Sbapt		return 16;
2248274880Sbapt	case MDOC_Ic:
2249294113Sbapt		return 10;
2250274880Sbapt	case MDOC_Li:
2251294113Sbapt		return 16;
2252274880Sbapt	case MDOC_Ms:
2253294113Sbapt		return 6;
2254274880Sbapt	case MDOC_Nm:
2255294113Sbapt		return 10;
2256274880Sbapt	case MDOC_No:
2257294113Sbapt		return 12;
2258274880Sbapt	case MDOC_Oo:
2259294113Sbapt		return 10;
2260274880Sbapt	case MDOC_Op:
2261294113Sbapt		return 14;
2262274880Sbapt	case MDOC_Pa:
2263294113Sbapt		return 32;
2264274880Sbapt	case MDOC_Pf:
2265294113Sbapt		return 12;
2266274880Sbapt	case MDOC_Po:
2267294113Sbapt		return 12;
2268274880Sbapt	case MDOC_Pq:
2269294113Sbapt		return 12;
2270274880Sbapt	case MDOC_Ql:
2271294113Sbapt		return 16;
2272274880Sbapt	case MDOC_Qo:
2273294113Sbapt		return 12;
2274274880Sbapt	case MDOC_So:
2275294113Sbapt		return 12;
2276274880Sbapt	case MDOC_Sq:
2277294113Sbapt		return 12;
2278274880Sbapt	case MDOC_Sy:
2279294113Sbapt		return 6;
2280274880Sbapt	case MDOC_Sx:
2281294113Sbapt		return 16;
2282274880Sbapt	case MDOC_Tn:
2283294113Sbapt		return 10;
2284274880Sbapt	case MDOC_Va:
2285294113Sbapt		return 12;
2286274880Sbapt	case MDOC_Vt:
2287294113Sbapt		return 12;
2288274880Sbapt	case MDOC_Xr:
2289294113Sbapt		return 10;
2290241675Suqs	default:
2291241675Suqs		break;
2292241675Suqs	};
2293294113Sbapt	return 0;
2294241675Suqs}
2295