xref: /openbsd-src/usr.bin/mandoc/mdoc_term.c (revision 6396a31b28c13abcc71f05292f11b42abbafd7d3)
1 /*	$OpenBSD: mdoc_term.c,v 1.272 2019/06/03 19:50:31 schwarze Exp $ */
2 /*
3  * Copyright (c) 2008, 2009, 2010, 2011 Kristaps Dzonsons <kristaps@bsd.lv>
4  * Copyright (c) 2010, 2012-2019 Ingo Schwarze <schwarze@openbsd.org>
5  * Copyright (c) 2013 Franco Fichtner <franco@lastsummer.de>
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 <sys/types.h>
20 
21 #include <assert.h>
22 #include <ctype.h>
23 #include <limits.h>
24 #include <stdint.h>
25 #include <stdio.h>
26 #include <stdlib.h>
27 #include <string.h>
28 
29 #include "mandoc_aux.h"
30 #include "roff.h"
31 #include "mdoc.h"
32 #include "out.h"
33 #include "term.h"
34 #include "tag.h"
35 #include "main.h"
36 
37 struct	termpair {
38 	struct termpair	 *ppair;
39 	int		  count;
40 };
41 
42 #define	DECL_ARGS struct termp *p, \
43 		  struct termpair *pair, \
44 		  const struct roff_meta *meta, \
45 		  struct roff_node *n
46 
47 struct	mdoc_term_act {
48 	int	(*pre)(DECL_ARGS);
49 	void	(*post)(DECL_ARGS);
50 };
51 
52 static	int	  a2width(const struct termp *, const char *);
53 
54 static	void	  print_bvspace(struct termp *,
55 			const struct roff_node *,
56 			const struct roff_node *);
57 static	void	  print_mdoc_node(DECL_ARGS);
58 static	void	  print_mdoc_nodelist(DECL_ARGS);
59 static	void	  print_mdoc_head(struct termp *, const struct roff_meta *);
60 static	void	  print_mdoc_foot(struct termp *, const struct roff_meta *);
61 static	void	  synopsis_pre(struct termp *,
62 			const struct roff_node *);
63 
64 static	void	  termp____post(DECL_ARGS);
65 static	void	  termp__t_post(DECL_ARGS);
66 static	void	  termp_bd_post(DECL_ARGS);
67 static	void	  termp_bk_post(DECL_ARGS);
68 static	void	  termp_bl_post(DECL_ARGS);
69 static	void	  termp_eo_post(DECL_ARGS);
70 static	void	  termp_fd_post(DECL_ARGS);
71 static	void	  termp_fo_post(DECL_ARGS);
72 static	void	  termp_in_post(DECL_ARGS);
73 static	void	  termp_it_post(DECL_ARGS);
74 static	void	  termp_lb_post(DECL_ARGS);
75 static	void	  termp_nm_post(DECL_ARGS);
76 static	void	  termp_pf_post(DECL_ARGS);
77 static	void	  termp_quote_post(DECL_ARGS);
78 static	void	  termp_sh_post(DECL_ARGS);
79 static	void	  termp_ss_post(DECL_ARGS);
80 static	void	  termp_xx_post(DECL_ARGS);
81 
82 static	int	  termp__a_pre(DECL_ARGS);
83 static	int	  termp__t_pre(DECL_ARGS);
84 static	int	  termp_abort_pre(DECL_ARGS);
85 static	int	  termp_an_pre(DECL_ARGS);
86 static	int	  termp_ap_pre(DECL_ARGS);
87 static	int	  termp_bd_pre(DECL_ARGS);
88 static	int	  termp_bf_pre(DECL_ARGS);
89 static	int	  termp_bk_pre(DECL_ARGS);
90 static	int	  termp_bl_pre(DECL_ARGS);
91 static	int	  termp_bold_pre(DECL_ARGS);
92 static	int	  termp_cd_pre(DECL_ARGS);
93 static	int	  termp_d1_pre(DECL_ARGS);
94 static	int	  termp_eo_pre(DECL_ARGS);
95 static	int	  termp_em_pre(DECL_ARGS);
96 static	int	  termp_er_pre(DECL_ARGS);
97 static	int	  termp_ex_pre(DECL_ARGS);
98 static	int	  termp_fa_pre(DECL_ARGS);
99 static	int	  termp_fd_pre(DECL_ARGS);
100 static	int	  termp_fl_pre(DECL_ARGS);
101 static	int	  termp_fn_pre(DECL_ARGS);
102 static	int	  termp_fo_pre(DECL_ARGS);
103 static	int	  termp_ft_pre(DECL_ARGS);
104 static	int	  termp_in_pre(DECL_ARGS);
105 static	int	  termp_it_pre(DECL_ARGS);
106 static	int	  termp_li_pre(DECL_ARGS);
107 static	int	  termp_lk_pre(DECL_ARGS);
108 static	int	  termp_nd_pre(DECL_ARGS);
109 static	int	  termp_nm_pre(DECL_ARGS);
110 static	int	  termp_ns_pre(DECL_ARGS);
111 static	int	  termp_quote_pre(DECL_ARGS);
112 static	int	  termp_rs_pre(DECL_ARGS);
113 static	int	  termp_sh_pre(DECL_ARGS);
114 static	int	  termp_skip_pre(DECL_ARGS);
115 static	int	  termp_sm_pre(DECL_ARGS);
116 static	int	  termp_pp_pre(DECL_ARGS);
117 static	int	  termp_ss_pre(DECL_ARGS);
118 static	int	  termp_sy_pre(DECL_ARGS);
119 static	int	  termp_tag_pre(DECL_ARGS);
120 static	int	  termp_under_pre(DECL_ARGS);
121 static	int	  termp_vt_pre(DECL_ARGS);
122 static	int	  termp_xr_pre(DECL_ARGS);
123 static	int	  termp_xx_pre(DECL_ARGS);
124 
125 static const struct mdoc_term_act mdoc_term_acts[MDOC_MAX - MDOC_Dd] = {
126 	{ NULL, NULL }, /* Dd */
127 	{ NULL, NULL }, /* Dt */
128 	{ NULL, NULL }, /* Os */
129 	{ termp_sh_pre, termp_sh_post }, /* Sh */
130 	{ termp_ss_pre, termp_ss_post }, /* Ss */
131 	{ termp_pp_pre, NULL }, /* Pp */
132 	{ termp_d1_pre, termp_bl_post }, /* D1 */
133 	{ termp_d1_pre, termp_bl_post }, /* Dl */
134 	{ termp_bd_pre, termp_bd_post }, /* Bd */
135 	{ NULL, NULL }, /* Ed */
136 	{ termp_bl_pre, termp_bl_post }, /* Bl */
137 	{ NULL, NULL }, /* El */
138 	{ termp_it_pre, termp_it_post }, /* It */
139 	{ termp_under_pre, NULL }, /* Ad */
140 	{ termp_an_pre, NULL }, /* An */
141 	{ termp_ap_pre, NULL }, /* Ap */
142 	{ termp_under_pre, NULL }, /* Ar */
143 	{ termp_cd_pre, NULL }, /* Cd */
144 	{ termp_bold_pre, NULL }, /* Cm */
145 	{ termp_li_pre, NULL }, /* Dv */
146 	{ termp_er_pre, NULL }, /* Er */
147 	{ termp_tag_pre, NULL }, /* Ev */
148 	{ termp_ex_pre, NULL }, /* Ex */
149 	{ termp_fa_pre, NULL }, /* Fa */
150 	{ termp_fd_pre, termp_fd_post }, /* Fd */
151 	{ termp_fl_pre, NULL }, /* Fl */
152 	{ termp_fn_pre, NULL }, /* Fn */
153 	{ termp_ft_pre, NULL }, /* Ft */
154 	{ termp_bold_pre, NULL }, /* Ic */
155 	{ termp_in_pre, termp_in_post }, /* In */
156 	{ termp_li_pre, NULL }, /* Li */
157 	{ termp_nd_pre, NULL }, /* Nd */
158 	{ termp_nm_pre, termp_nm_post }, /* Nm */
159 	{ termp_quote_pre, termp_quote_post }, /* Op */
160 	{ termp_abort_pre, NULL }, /* Ot */
161 	{ termp_under_pre, NULL }, /* Pa */
162 	{ termp_ex_pre, NULL }, /* Rv */
163 	{ NULL, NULL }, /* St */
164 	{ termp_under_pre, NULL }, /* Va */
165 	{ termp_vt_pre, NULL }, /* Vt */
166 	{ termp_xr_pre, NULL }, /* Xr */
167 	{ termp__a_pre, termp____post }, /* %A */
168 	{ termp_under_pre, termp____post }, /* %B */
169 	{ NULL, termp____post }, /* %D */
170 	{ termp_under_pre, termp____post }, /* %I */
171 	{ termp_under_pre, termp____post }, /* %J */
172 	{ NULL, termp____post }, /* %N */
173 	{ NULL, termp____post }, /* %O */
174 	{ NULL, termp____post }, /* %P */
175 	{ NULL, termp____post }, /* %R */
176 	{ termp__t_pre, termp__t_post }, /* %T */
177 	{ NULL, termp____post }, /* %V */
178 	{ NULL, NULL }, /* Ac */
179 	{ termp_quote_pre, termp_quote_post }, /* Ao */
180 	{ termp_quote_pre, termp_quote_post }, /* Aq */
181 	{ NULL, NULL }, /* At */
182 	{ NULL, NULL }, /* Bc */
183 	{ termp_bf_pre, NULL }, /* Bf */
184 	{ termp_quote_pre, termp_quote_post }, /* Bo */
185 	{ termp_quote_pre, termp_quote_post }, /* Bq */
186 	{ termp_xx_pre, termp_xx_post }, /* Bsx */
187 	{ NULL, NULL }, /* Bx */
188 	{ termp_skip_pre, NULL }, /* Db */
189 	{ NULL, NULL }, /* Dc */
190 	{ termp_quote_pre, termp_quote_post }, /* Do */
191 	{ termp_quote_pre, termp_quote_post }, /* Dq */
192 	{ NULL, NULL }, /* Ec */ /* FIXME: no space */
193 	{ NULL, NULL }, /* Ef */
194 	{ termp_em_pre, NULL }, /* Em */
195 	{ termp_eo_pre, termp_eo_post }, /* Eo */
196 	{ termp_xx_pre, termp_xx_post }, /* Fx */
197 	{ termp_bold_pre, NULL }, /* Ms */
198 	{ termp_li_pre, NULL }, /* No */
199 	{ termp_ns_pre, NULL }, /* Ns */
200 	{ termp_xx_pre, termp_xx_post }, /* Nx */
201 	{ termp_xx_pre, termp_xx_post }, /* Ox */
202 	{ NULL, NULL }, /* Pc */
203 	{ NULL, termp_pf_post }, /* Pf */
204 	{ termp_quote_pre, termp_quote_post }, /* Po */
205 	{ termp_quote_pre, termp_quote_post }, /* Pq */
206 	{ NULL, NULL }, /* Qc */
207 	{ termp_quote_pre, termp_quote_post }, /* Ql */
208 	{ termp_quote_pre, termp_quote_post }, /* Qo */
209 	{ termp_quote_pre, termp_quote_post }, /* Qq */
210 	{ NULL, NULL }, /* Re */
211 	{ termp_rs_pre, NULL }, /* Rs */
212 	{ NULL, NULL }, /* Sc */
213 	{ termp_quote_pre, termp_quote_post }, /* So */
214 	{ termp_quote_pre, termp_quote_post }, /* Sq */
215 	{ termp_sm_pre, NULL }, /* Sm */
216 	{ termp_under_pre, NULL }, /* Sx */
217 	{ termp_sy_pre, NULL }, /* Sy */
218 	{ NULL, NULL }, /* Tn */
219 	{ termp_xx_pre, termp_xx_post }, /* Ux */
220 	{ NULL, NULL }, /* Xc */
221 	{ NULL, NULL }, /* Xo */
222 	{ termp_fo_pre, termp_fo_post }, /* Fo */
223 	{ NULL, NULL }, /* Fc */
224 	{ termp_quote_pre, termp_quote_post }, /* Oo */
225 	{ NULL, NULL }, /* Oc */
226 	{ termp_bk_pre, termp_bk_post }, /* Bk */
227 	{ NULL, NULL }, /* Ek */
228 	{ NULL, NULL }, /* Bt */
229 	{ NULL, NULL }, /* Hf */
230 	{ termp_under_pre, NULL }, /* Fr */
231 	{ NULL, NULL }, /* Ud */
232 	{ NULL, termp_lb_post }, /* Lb */
233 	{ termp_abort_pre, NULL }, /* Lp */
234 	{ termp_lk_pre, NULL }, /* Lk */
235 	{ termp_under_pre, NULL }, /* Mt */
236 	{ termp_quote_pre, termp_quote_post }, /* Brq */
237 	{ termp_quote_pre, termp_quote_post }, /* Bro */
238 	{ NULL, NULL }, /* Brc */
239 	{ NULL, termp____post }, /* %C */
240 	{ termp_skip_pre, NULL }, /* Es */
241 	{ termp_quote_pre, termp_quote_post }, /* En */
242 	{ termp_xx_pre, termp_xx_post }, /* Dx */
243 	{ NULL, termp____post }, /* %Q */
244 	{ NULL, termp____post }, /* %U */
245 	{ NULL, NULL }, /* Ta */
246 };
247 
248 static	int	 fn_prio;
249 
250 
251 void
252 terminal_mdoc(void *arg, const struct roff_meta *mdoc)
253 {
254 	struct roff_node	*n;
255 	struct termp		*p;
256 	size_t			 save_defindent;
257 
258 	p = (struct termp *)arg;
259 	p->tcol->rmargin = p->maxrmargin = p->defrmargin;
260 	term_tab_set(p, NULL);
261 	term_tab_set(p, "T");
262 	term_tab_set(p, ".5i");
263 
264 	n = mdoc->first->child;
265 	if (p->synopsisonly) {
266 		while (n != NULL) {
267 			if (n->tok == MDOC_Sh && n->sec == SEC_SYNOPSIS) {
268 				if (n->child->next->child != NULL)
269 					print_mdoc_nodelist(p, NULL,
270 					    mdoc, n->child->next->child);
271 				term_newln(p);
272 				break;
273 			}
274 			n = n->next;
275 		}
276 	} else {
277 		save_defindent = p->defindent;
278 		if (p->defindent == 0)
279 			p->defindent = 5;
280 		term_begin(p, print_mdoc_head, print_mdoc_foot, mdoc);
281 		while (n != NULL &&
282 		    (n->type == ROFFT_COMMENT ||
283 		     n->flags & NODE_NOPRT))
284 			n = n->next;
285 		if (n != NULL) {
286 			if (n->tok != MDOC_Sh)
287 				term_vspace(p);
288 			print_mdoc_nodelist(p, NULL, mdoc, n);
289 		}
290 		term_end(p);
291 		p->defindent = save_defindent;
292 	}
293 }
294 
295 static void
296 print_mdoc_nodelist(DECL_ARGS)
297 {
298 
299 	while (n != NULL) {
300 		print_mdoc_node(p, pair, meta, n);
301 		n = n->next;
302 	}
303 }
304 
305 static void
306 print_mdoc_node(DECL_ARGS)
307 {
308 	const struct mdoc_term_act *act;
309 	struct termpair	 npair;
310 	size_t		 offset, rmargin;
311 	int		 chld;
312 
313 	/*
314 	 * In no-fill mode, break the output line at the beginning
315 	 * of new input lines except after \c, and nowhere else.
316 	 */
317 
318 	if (n->flags & NODE_NOFILL) {
319 		if (n->flags & NODE_LINE &&
320 		    (p->flags & TERMP_NONEWLINE) == 0)
321 			term_newln(p);
322 		p->flags |= TERMP_BRNEVER;
323 	} else
324 		p->flags &= ~TERMP_BRNEVER;
325 
326 	if (n->type == ROFFT_COMMENT || n->flags & NODE_NOPRT)
327 		return;
328 
329 	chld = 1;
330 	offset = p->tcol->offset;
331 	rmargin = p->tcol->rmargin;
332 	n->flags &= ~NODE_ENDED;
333 	n->prev_font = p->fonti;
334 
335 	memset(&npair, 0, sizeof(struct termpair));
336 	npair.ppair = pair;
337 
338 	/*
339 	 * Keeps only work until the end of a line.  If a keep was
340 	 * invoked in a prior line, revert it to PREKEEP.
341 	 */
342 
343 	if (p->flags & TERMP_KEEP && n->flags & NODE_LINE) {
344 		p->flags &= ~TERMP_KEEP;
345 		p->flags |= TERMP_PREKEEP;
346 	}
347 
348 	/*
349 	 * After the keep flags have been set up, we may now
350 	 * produce output.  Note that some pre-handlers do so.
351 	 */
352 
353 	act = NULL;
354 	switch (n->type) {
355 	case ROFFT_TEXT:
356 		if (n->flags & NODE_LINE) {
357 			switch (*n->string) {
358 			case '\0':
359 				if (p->flags & TERMP_NONEWLINE)
360 					term_newln(p);
361 				else
362 					term_vspace(p);
363 				return;
364 			case ' ':
365 				if ((p->flags & TERMP_NONEWLINE) == 0)
366 					term_newln(p);
367 				break;
368 			default:
369 				break;
370 			}
371 		}
372 		if (NODE_DELIMC & n->flags)
373 			p->flags |= TERMP_NOSPACE;
374 		term_word(p, n->string);
375 		if (NODE_DELIMO & n->flags)
376 			p->flags |= TERMP_NOSPACE;
377 		break;
378 	case ROFFT_EQN:
379 		if ( ! (n->flags & NODE_LINE))
380 			p->flags |= TERMP_NOSPACE;
381 		term_eqn(p, n->eqn);
382 		if (n->next != NULL && ! (n->next->flags & NODE_LINE))
383 			p->flags |= TERMP_NOSPACE;
384 		break;
385 	case ROFFT_TBL:
386 		if (p->tbl.cols == NULL)
387 			term_newln(p);
388 		term_tbl(p, n->span);
389 		break;
390 	default:
391 		if (n->tok < ROFF_MAX) {
392 			roff_term_pre(p, n);
393 			return;
394 		}
395 		assert(n->tok >= MDOC_Dd && n->tok < MDOC_MAX);
396 		act = mdoc_term_acts + (n->tok - MDOC_Dd);
397 		if (act->pre != NULL &&
398 		    (n->end == ENDBODY_NOT || n->child != NULL))
399 			chld = (*act->pre)(p, &npair, meta, n);
400 		break;
401 	}
402 
403 	if (chld && n->child)
404 		print_mdoc_nodelist(p, &npair, meta, n->child);
405 
406 	term_fontpopq(p,
407 	    (ENDBODY_NOT == n->end ? n : n->body)->prev_font);
408 
409 	switch (n->type) {
410 	case ROFFT_TEXT:
411 		break;
412 	case ROFFT_TBL:
413 		break;
414 	case ROFFT_EQN:
415 		break;
416 	default:
417 		if (act->post == NULL || n->flags & NODE_ENDED)
418 			break;
419 		(void)(*act->post)(p, &npair, meta, n);
420 
421 		/*
422 		 * Explicit end tokens not only call the post
423 		 * handler, but also tell the respective block
424 		 * that it must not call the post handler again.
425 		 */
426 		if (ENDBODY_NOT != n->end)
427 			n->body->flags |= NODE_ENDED;
428 		break;
429 	}
430 
431 	if (NODE_EOS & n->flags)
432 		p->flags |= TERMP_SENTENCE;
433 
434 	if (n->type != ROFFT_TEXT)
435 		p->tcol->offset = offset;
436 	p->tcol->rmargin = rmargin;
437 }
438 
439 static void
440 print_mdoc_foot(struct termp *p, const struct roff_meta *meta)
441 {
442 	size_t sz;
443 
444 	term_fontrepl(p, TERMFONT_NONE);
445 
446 	/*
447 	 * Output the footer in new-groff style, that is, three columns
448 	 * with the middle being the manual date and flanking columns
449 	 * being the operating system:
450 	 *
451 	 * SYSTEM                  DATE                    SYSTEM
452 	 */
453 
454 	term_vspace(p);
455 
456 	p->tcol->offset = 0;
457 	sz = term_strlen(p, meta->date);
458 	p->tcol->rmargin = p->maxrmargin > sz ?
459 	    (p->maxrmargin + term_len(p, 1) - sz) / 2 : 0;
460 	p->trailspace = 1;
461 	p->flags |= TERMP_NOSPACE | TERMP_NOBREAK;
462 
463 	term_word(p, meta->os);
464 	term_flushln(p);
465 
466 	p->tcol->offset = p->tcol->rmargin;
467 	sz = term_strlen(p, meta->os);
468 	p->tcol->rmargin = p->maxrmargin > sz ? p->maxrmargin - sz : 0;
469 	p->flags |= TERMP_NOSPACE;
470 
471 	term_word(p, meta->date);
472 	term_flushln(p);
473 
474 	p->tcol->offset = p->tcol->rmargin;
475 	p->tcol->rmargin = p->maxrmargin;
476 	p->trailspace = 0;
477 	p->flags &= ~TERMP_NOBREAK;
478 	p->flags |= TERMP_NOSPACE;
479 
480 	term_word(p, meta->os);
481 	term_flushln(p);
482 
483 	p->tcol->offset = 0;
484 	p->tcol->rmargin = p->maxrmargin;
485 	p->flags = 0;
486 }
487 
488 static void
489 print_mdoc_head(struct termp *p, const struct roff_meta *meta)
490 {
491 	char			*volume, *title;
492 	size_t			 vollen, titlen;
493 
494 	/*
495 	 * The header is strange.  It has three components, which are
496 	 * really two with the first duplicated.  It goes like this:
497 	 *
498 	 * IDENTIFIER              TITLE                   IDENTIFIER
499 	 *
500 	 * The IDENTIFIER is NAME(SECTION), which is the command-name
501 	 * (if given, or "unknown" if not) followed by the manual page
502 	 * section.  These are given in `Dt'.  The TITLE is a free-form
503 	 * string depending on the manual volume.  If not specified, it
504 	 * switches on the manual section.
505 	 */
506 
507 	assert(meta->vol);
508 	if (NULL == meta->arch)
509 		volume = mandoc_strdup(meta->vol);
510 	else
511 		mandoc_asprintf(&volume, "%s (%s)",
512 		    meta->vol, meta->arch);
513 	vollen = term_strlen(p, volume);
514 
515 	if (NULL == meta->msec)
516 		title = mandoc_strdup(meta->title);
517 	else
518 		mandoc_asprintf(&title, "%s(%s)",
519 		    meta->title, meta->msec);
520 	titlen = term_strlen(p, title);
521 
522 	p->flags |= TERMP_NOBREAK | TERMP_NOSPACE;
523 	p->trailspace = 1;
524 	p->tcol->offset = 0;
525 	p->tcol->rmargin = 2 * (titlen+1) + vollen < p->maxrmargin ?
526 	    (p->maxrmargin - vollen + term_len(p, 1)) / 2 :
527 	    vollen < p->maxrmargin ?  p->maxrmargin - vollen : 0;
528 
529 	term_word(p, title);
530 	term_flushln(p);
531 
532 	p->flags |= TERMP_NOSPACE;
533 	p->tcol->offset = p->tcol->rmargin;
534 	p->tcol->rmargin = p->tcol->offset + vollen + titlen <
535 	    p->maxrmargin ? p->maxrmargin - titlen : p->maxrmargin;
536 
537 	term_word(p, volume);
538 	term_flushln(p);
539 
540 	p->flags &= ~TERMP_NOBREAK;
541 	p->trailspace = 0;
542 	if (p->tcol->rmargin + titlen <= p->maxrmargin) {
543 		p->flags |= TERMP_NOSPACE;
544 		p->tcol->offset = p->tcol->rmargin;
545 		p->tcol->rmargin = p->maxrmargin;
546 		term_word(p, title);
547 		term_flushln(p);
548 	}
549 
550 	p->flags &= ~TERMP_NOSPACE;
551 	p->tcol->offset = 0;
552 	p->tcol->rmargin = p->maxrmargin;
553 	free(title);
554 	free(volume);
555 }
556 
557 static int
558 a2width(const struct termp *p, const char *v)
559 {
560 	struct roffsu	 su;
561 	const char	*end;
562 
563 	end = a2roffsu(v, &su, SCALE_MAX);
564 	if (end == NULL || *end != '\0') {
565 		SCALE_HS_INIT(&su, term_strlen(p, v));
566 		su.scale /= term_strlen(p, "0");
567 	}
568 	return term_hen(p, &su);
569 }
570 
571 /*
572  * Determine how much space to print out before block elements of `It'
573  * (and thus `Bl') and `Bd'.  And then go ahead and print that space,
574  * too.
575  */
576 static void
577 print_bvspace(struct termp *p,
578 	const struct roff_node *bl,
579 	const struct roff_node *n)
580 {
581 	const struct roff_node	*nn;
582 
583 	assert(n);
584 
585 	term_newln(p);
586 
587 	if (MDOC_Bd == bl->tok && bl->norm->Bd.comp)
588 		return;
589 	if (MDOC_Bl == bl->tok && bl->norm->Bl.comp)
590 		return;
591 
592 	/* Do not vspace directly after Ss/Sh. */
593 
594 	nn = n;
595 	while (nn->prev != NULL &&
596 	    (nn->prev->type == ROFFT_COMMENT ||
597 	     nn->prev->flags & NODE_NOPRT))
598 		nn = nn->prev;
599 	while (nn->prev == NULL) {
600 		do {
601 			nn = nn->parent;
602 			if (nn->type == ROFFT_ROOT)
603 				return;
604 		} while (nn->type != ROFFT_BLOCK);
605 		if (nn->tok == MDOC_Sh || nn->tok == MDOC_Ss)
606 			return;
607 		if (nn->tok == MDOC_It &&
608 		    nn->parent->parent->norm->Bl.type != LIST_item)
609 			break;
610 	}
611 
612 	/* A `-column' does not assert vspace within the list. */
613 
614 	if (MDOC_Bl == bl->tok && LIST_column == bl->norm->Bl.type)
615 		if (n->prev && MDOC_It == n->prev->tok)
616 			return;
617 
618 	/* A `-diag' without body does not vspace. */
619 
620 	if (MDOC_Bl == bl->tok && LIST_diag == bl->norm->Bl.type)
621 		if (n->prev && MDOC_It == n->prev->tok) {
622 			assert(n->prev->body);
623 			if (NULL == n->prev->body->child)
624 				return;
625 		}
626 
627 	term_vspace(p);
628 }
629 
630 
631 static int
632 termp_it_pre(DECL_ARGS)
633 {
634 	struct roffsu		su;
635 	char			buf[24];
636 	const struct roff_node *bl, *nn;
637 	size_t			ncols, dcol;
638 	int			i, offset, width;
639 	enum mdoc_list		type;
640 
641 	if (n->type == ROFFT_BLOCK) {
642 		print_bvspace(p, n->parent->parent, n);
643 		return 1;
644 	}
645 
646 	bl = n->parent->parent->parent;
647 	type = bl->norm->Bl.type;
648 
649 	/*
650 	 * Defaults for specific list types.
651 	 */
652 
653 	switch (type) {
654 	case LIST_bullet:
655 	case LIST_dash:
656 	case LIST_hyphen:
657 	case LIST_enum:
658 		width = term_len(p, 2);
659 		break;
660 	case LIST_hang:
661 	case LIST_tag:
662 		width = term_len(p, 8);
663 		break;
664 	case LIST_column:
665 		width = term_len(p, 10);
666 		break;
667 	default:
668 		width = 0;
669 		break;
670 	}
671 	offset = 0;
672 
673 	/*
674 	 * First calculate width and offset.  This is pretty easy unless
675 	 * we're a -column list, in which case all prior columns must
676 	 * be accounted for.
677 	 */
678 
679 	if (bl->norm->Bl.offs != NULL) {
680 		offset = a2width(p, bl->norm->Bl.offs);
681 		if (offset < 0 && (size_t)(-offset) > p->tcol->offset)
682 			offset = -p->tcol->offset;
683 		else if (offset > SHRT_MAX)
684 			offset = 0;
685 	}
686 
687 	switch (type) {
688 	case LIST_column:
689 		if (n->type == ROFFT_HEAD)
690 			break;
691 
692 		/*
693 		 * Imitate groff's column handling:
694 		 * - For each earlier column, add its width.
695 		 * - For less than 5 columns, add four more blanks per
696 		 *   column.
697 		 * - For exactly 5 columns, add three more blank per
698 		 *   column.
699 		 * - For more than 5 columns, add only one column.
700 		 */
701 		ncols = bl->norm->Bl.ncols;
702 		dcol = ncols < 5 ? term_len(p, 4) :
703 		    ncols == 5 ? term_len(p, 3) : term_len(p, 1);
704 
705 		/*
706 		 * Calculate the offset by applying all prior ROFFT_BODY,
707 		 * so we stop at the ROFFT_HEAD (nn->prev == NULL).
708 		 */
709 
710 		for (i = 0, nn = n->prev;
711 		    nn->prev && i < (int)ncols;
712 		    nn = nn->prev, i++) {
713 			SCALE_HS_INIT(&su,
714 			    term_strlen(p, bl->norm->Bl.cols[i]));
715 			su.scale /= term_strlen(p, "0");
716 			offset += term_hen(p, &su) + dcol;
717 		}
718 
719 		/*
720 		 * When exceeding the declared number of columns, leave
721 		 * the remaining widths at 0.  This will later be
722 		 * adjusted to the default width of 10, or, for the last
723 		 * column, stretched to the right margin.
724 		 */
725 		if (i >= (int)ncols)
726 			break;
727 
728 		/*
729 		 * Use the declared column widths, extended as explained
730 		 * in the preceding paragraph.
731 		 */
732 		SCALE_HS_INIT(&su, term_strlen(p, bl->norm->Bl.cols[i]));
733 		su.scale /= term_strlen(p, "0");
734 		width = term_hen(p, &su) + dcol;
735 		break;
736 	default:
737 		if (NULL == bl->norm->Bl.width)
738 			break;
739 
740 		/*
741 		 * Note: buffer the width by 2, which is groff's magic
742 		 * number for buffering single arguments.  See the above
743 		 * handling for column for how this changes.
744 		 */
745 		width = a2width(p, bl->norm->Bl.width) + term_len(p, 2);
746 		if (width < 0 && (size_t)(-width) > p->tcol->offset)
747 			width = -p->tcol->offset;
748 		else if (width > SHRT_MAX)
749 			width = 0;
750 		break;
751 	}
752 
753 	/*
754 	 * Whitespace control.  Inset bodies need an initial space,
755 	 * while diagonal bodies need two.
756 	 */
757 
758 	p->flags |= TERMP_NOSPACE;
759 
760 	switch (type) {
761 	case LIST_diag:
762 		if (n->type == ROFFT_BODY)
763 			term_word(p, "\\ \\ ");
764 		break;
765 	case LIST_inset:
766 		if (n->type == ROFFT_BODY && n->parent->head->child != NULL)
767 			term_word(p, "\\ ");
768 		break;
769 	default:
770 		break;
771 	}
772 
773 	p->flags |= TERMP_NOSPACE;
774 
775 	switch (type) {
776 	case LIST_diag:
777 		if (n->type == ROFFT_HEAD)
778 			term_fontpush(p, TERMFONT_BOLD);
779 		break;
780 	default:
781 		break;
782 	}
783 
784 	/*
785 	 * Pad and break control.  This is the tricky part.  These flags
786 	 * are documented in term_flushln() in term.c.  Note that we're
787 	 * going to unset all of these flags in termp_it_post() when we
788 	 * exit.
789 	 */
790 
791 	switch (type) {
792 	case LIST_enum:
793 	case LIST_bullet:
794 	case LIST_dash:
795 	case LIST_hyphen:
796 		if (n->type == ROFFT_HEAD) {
797 			p->flags |= TERMP_NOBREAK | TERMP_HANG;
798 			p->trailspace = 1;
799 		} else if (width <= (int)term_len(p, 2))
800 			p->flags |= TERMP_NOPAD;
801 		break;
802 	case LIST_hang:
803 		if (n->type != ROFFT_HEAD)
804 			break;
805 		p->flags |= TERMP_NOBREAK | TERMP_BRIND | TERMP_HANG;
806 		p->trailspace = 1;
807 		break;
808 	case LIST_tag:
809 		if (n->type != ROFFT_HEAD)
810 			break;
811 
812 		p->flags |= TERMP_NOBREAK | TERMP_BRTRSP | TERMP_BRIND;
813 		p->trailspace = 2;
814 
815 		if (NULL == n->next || NULL == n->next->child)
816 			p->flags |= TERMP_HANG;
817 		break;
818 	case LIST_column:
819 		if (n->type == ROFFT_HEAD)
820 			break;
821 
822 		if (NULL == n->next) {
823 			p->flags &= ~TERMP_NOBREAK;
824 			p->trailspace = 0;
825 		} else {
826 			p->flags |= TERMP_NOBREAK;
827 			p->trailspace = 1;
828 		}
829 
830 		break;
831 	case LIST_diag:
832 		if (n->type != ROFFT_HEAD)
833 			break;
834 		p->flags |= TERMP_NOBREAK | TERMP_BRIND;
835 		p->trailspace = 1;
836 		break;
837 	default:
838 		break;
839 	}
840 
841 	/*
842 	 * Margin control.  Set-head-width lists have their right
843 	 * margins shortened.  The body for these lists has the offset
844 	 * necessarily lengthened.  Everybody gets the offset.
845 	 */
846 
847 	p->tcol->offset += offset;
848 
849 	switch (type) {
850 	case LIST_bullet:
851 	case LIST_dash:
852 	case LIST_enum:
853 	case LIST_hyphen:
854 	case LIST_hang:
855 	case LIST_tag:
856 		if (n->type == ROFFT_HEAD)
857 			p->tcol->rmargin = p->tcol->offset + width;
858 		else
859 			p->tcol->offset += width;
860 		break;
861 	case LIST_column:
862 		assert(width);
863 		p->tcol->rmargin = p->tcol->offset + width;
864 		/*
865 		 * XXX - this behaviour is not documented: the
866 		 * right-most column is filled to the right margin.
867 		 */
868 		if (n->type == ROFFT_HEAD)
869 			break;
870 		if (n->next == NULL && p->tcol->rmargin < p->maxrmargin)
871 			p->tcol->rmargin = p->maxrmargin;
872 		break;
873 	default:
874 		break;
875 	}
876 
877 	/*
878 	 * The dash, hyphen, bullet and enum lists all have a special
879 	 * HEAD character (temporarily bold, in some cases).
880 	 */
881 
882 	if (n->type == ROFFT_HEAD)
883 		switch (type) {
884 		case LIST_bullet:
885 			term_fontpush(p, TERMFONT_BOLD);
886 			term_word(p, "\\[bu]");
887 			term_fontpop(p);
888 			break;
889 		case LIST_dash:
890 		case LIST_hyphen:
891 			term_fontpush(p, TERMFONT_BOLD);
892 			term_word(p, "-");
893 			term_fontpop(p);
894 			break;
895 		case LIST_enum:
896 			(pair->ppair->ppair->count)++;
897 			(void)snprintf(buf, sizeof(buf), "%d.",
898 			    pair->ppair->ppair->count);
899 			term_word(p, buf);
900 			break;
901 		default:
902 			break;
903 		}
904 
905 	/*
906 	 * If we're not going to process our children, indicate so here.
907 	 */
908 
909 	switch (type) {
910 	case LIST_bullet:
911 	case LIST_item:
912 	case LIST_dash:
913 	case LIST_hyphen:
914 	case LIST_enum:
915 		if (n->type == ROFFT_HEAD)
916 			return 0;
917 		break;
918 	case LIST_column:
919 		if (n->type == ROFFT_HEAD)
920 			return 0;
921 		p->minbl = 0;
922 		break;
923 	default:
924 		break;
925 	}
926 
927 	return 1;
928 }
929 
930 static void
931 termp_it_post(DECL_ARGS)
932 {
933 	enum mdoc_list	   type;
934 
935 	if (n->type == ROFFT_BLOCK)
936 		return;
937 
938 	type = n->parent->parent->parent->norm->Bl.type;
939 
940 	switch (type) {
941 	case LIST_item:
942 	case LIST_diag:
943 	case LIST_inset:
944 		if (n->type == ROFFT_BODY)
945 			term_newln(p);
946 		break;
947 	case LIST_column:
948 		if (n->type == ROFFT_BODY)
949 			term_flushln(p);
950 		break;
951 	default:
952 		term_newln(p);
953 		break;
954 	}
955 
956 	/*
957 	 * Now that our output is flushed, we can reset our tags.  Since
958 	 * only `It' sets these flags, we're free to assume that nobody
959 	 * has munged them in the meanwhile.
960 	 */
961 
962 	p->flags &= ~(TERMP_NOBREAK | TERMP_BRTRSP | TERMP_BRIND | TERMP_HANG);
963 	p->trailspace = 0;
964 }
965 
966 static int
967 termp_nm_pre(DECL_ARGS)
968 {
969 	const char	*cp;
970 
971 	if (n->type == ROFFT_BLOCK) {
972 		p->flags |= TERMP_PREKEEP;
973 		return 1;
974 	}
975 
976 	if (n->type == ROFFT_BODY) {
977 		if (n->child == NULL)
978 			return 0;
979 		p->flags |= TERMP_NOSPACE;
980 		cp = NULL;
981 		if (n->prev->child != NULL)
982 		    cp = n->prev->child->string;
983 		if (cp == NULL)
984 			cp = meta->name;
985 		if (cp == NULL)
986 			p->tcol->offset += term_len(p, 6);
987 		else
988 			p->tcol->offset += term_len(p, 1) +
989 			    term_strlen(p, cp);
990 		return 1;
991 	}
992 
993 	if (n->child == NULL)
994 		return 0;
995 
996 	if (n->type == ROFFT_HEAD)
997 		synopsis_pre(p, n->parent);
998 
999 	if (n->type == ROFFT_HEAD &&
1000 	    n->next != NULL && n->next->child != NULL) {
1001 		p->flags |= TERMP_NOSPACE | TERMP_NOBREAK | TERMP_BRIND;
1002 		p->trailspace = 1;
1003 		p->tcol->rmargin = p->tcol->offset + term_len(p, 1);
1004 		if (n->child == NULL)
1005 			p->tcol->rmargin += term_strlen(p, meta->name);
1006 		else if (n->child->type == ROFFT_TEXT) {
1007 			p->tcol->rmargin += term_strlen(p, n->child->string);
1008 			if (n->child->next != NULL)
1009 				p->flags |= TERMP_HANG;
1010 		} else {
1011 			p->tcol->rmargin += term_len(p, 5);
1012 			p->flags |= TERMP_HANG;
1013 		}
1014 	}
1015 
1016 	term_fontpush(p, TERMFONT_BOLD);
1017 	return 1;
1018 }
1019 
1020 static void
1021 termp_nm_post(DECL_ARGS)
1022 {
1023 
1024 	if (n->type == ROFFT_BLOCK) {
1025 		p->flags &= ~(TERMP_KEEP | TERMP_PREKEEP);
1026 	} else if (n->type == ROFFT_HEAD &&
1027 	    NULL != n->next && NULL != n->next->child) {
1028 		term_flushln(p);
1029 		p->flags &= ~(TERMP_NOBREAK | TERMP_BRIND | TERMP_HANG);
1030 		p->trailspace = 0;
1031 	} else if (n->type == ROFFT_BODY && n->child != NULL)
1032 		term_flushln(p);
1033 }
1034 
1035 static int
1036 termp_fl_pre(DECL_ARGS)
1037 {
1038 
1039 	termp_tag_pre(p, pair, meta, n);
1040 	term_fontpush(p, TERMFONT_BOLD);
1041 	term_word(p, "\\-");
1042 
1043 	if (!(n->child == NULL &&
1044 	    (n->next == NULL ||
1045 	     n->next->type == ROFFT_TEXT ||
1046 	     n->next->flags & NODE_LINE)))
1047 		p->flags |= TERMP_NOSPACE;
1048 
1049 	return 1;
1050 }
1051 
1052 static int
1053 termp__a_pre(DECL_ARGS)
1054 {
1055 
1056 	if (n->prev && MDOC__A == n->prev->tok)
1057 		if (NULL == n->next || MDOC__A != n->next->tok)
1058 			term_word(p, "and");
1059 
1060 	return 1;
1061 }
1062 
1063 static int
1064 termp_an_pre(DECL_ARGS)
1065 {
1066 
1067 	if (n->norm->An.auth == AUTH_split) {
1068 		p->flags &= ~TERMP_NOSPLIT;
1069 		p->flags |= TERMP_SPLIT;
1070 		return 0;
1071 	}
1072 	if (n->norm->An.auth == AUTH_nosplit) {
1073 		p->flags &= ~TERMP_SPLIT;
1074 		p->flags |= TERMP_NOSPLIT;
1075 		return 0;
1076 	}
1077 
1078 	if (p->flags & TERMP_SPLIT)
1079 		term_newln(p);
1080 
1081 	if (n->sec == SEC_AUTHORS && ! (p->flags & TERMP_NOSPLIT))
1082 		p->flags |= TERMP_SPLIT;
1083 
1084 	return 1;
1085 }
1086 
1087 static int
1088 termp_ns_pre(DECL_ARGS)
1089 {
1090 
1091 	if ( ! (NODE_LINE & n->flags))
1092 		p->flags |= TERMP_NOSPACE;
1093 	return 1;
1094 }
1095 
1096 static int
1097 termp_rs_pre(DECL_ARGS)
1098 {
1099 
1100 	if (SEC_SEE_ALSO != n->sec)
1101 		return 1;
1102 	if (n->type == ROFFT_BLOCK && n->prev != NULL)
1103 		term_vspace(p);
1104 	return 1;
1105 }
1106 
1107 static int
1108 termp_ex_pre(DECL_ARGS)
1109 {
1110 	term_newln(p);
1111 	return 1;
1112 }
1113 
1114 static int
1115 termp_nd_pre(DECL_ARGS)
1116 {
1117 
1118 	if (n->type == ROFFT_BODY)
1119 		term_word(p, "\\(en");
1120 	return 1;
1121 }
1122 
1123 static int
1124 termp_bl_pre(DECL_ARGS)
1125 {
1126 
1127 	return n->type != ROFFT_HEAD;
1128 }
1129 
1130 static void
1131 termp_bl_post(DECL_ARGS)
1132 {
1133 
1134 	if (n->type != ROFFT_BLOCK)
1135 		return;
1136 	term_newln(p);
1137 	if (n->tok != MDOC_Bl || n->norm->Bl.type != LIST_column)
1138 		return;
1139 	term_tab_set(p, NULL);
1140 	term_tab_set(p, "T");
1141 	term_tab_set(p, ".5i");
1142 }
1143 
1144 static int
1145 termp_xr_pre(DECL_ARGS)
1146 {
1147 
1148 	if (NULL == (n = n->child))
1149 		return 0;
1150 
1151 	assert(n->type == ROFFT_TEXT);
1152 	term_word(p, n->string);
1153 
1154 	if (NULL == (n = n->next))
1155 		return 0;
1156 
1157 	p->flags |= TERMP_NOSPACE;
1158 	term_word(p, "(");
1159 	p->flags |= TERMP_NOSPACE;
1160 
1161 	assert(n->type == ROFFT_TEXT);
1162 	term_word(p, n->string);
1163 
1164 	p->flags |= TERMP_NOSPACE;
1165 	term_word(p, ")");
1166 
1167 	return 0;
1168 }
1169 
1170 /*
1171  * This decides how to assert whitespace before any of the SYNOPSIS set
1172  * of macros (which, as in the case of Ft/Fo and Ft/Fn, may contain
1173  * macro combos).
1174  */
1175 static void
1176 synopsis_pre(struct termp *p, const struct roff_node *n)
1177 {
1178 	/*
1179 	 * Obviously, if we're not in a SYNOPSIS or no prior macros
1180 	 * exist, do nothing.
1181 	 */
1182 	if (NULL == n->prev || ! (NODE_SYNPRETTY & n->flags))
1183 		return;
1184 
1185 	/*
1186 	 * If we're the second in a pair of like elements, emit our
1187 	 * newline and return.  UNLESS we're `Fo', `Fn', `Fn', in which
1188 	 * case we soldier on.
1189 	 */
1190 	if (n->prev->tok == n->tok &&
1191 	    MDOC_Ft != n->tok &&
1192 	    MDOC_Fo != n->tok &&
1193 	    MDOC_Fn != n->tok) {
1194 		term_newln(p);
1195 		return;
1196 	}
1197 
1198 	/*
1199 	 * If we're one of the SYNOPSIS set and non-like pair-wise after
1200 	 * another (or Fn/Fo, which we've let slip through) then assert
1201 	 * vertical space, else only newline and move on.
1202 	 */
1203 	switch (n->prev->tok) {
1204 	case MDOC_Fd:
1205 	case MDOC_Fn:
1206 	case MDOC_Fo:
1207 	case MDOC_In:
1208 	case MDOC_Vt:
1209 		term_vspace(p);
1210 		break;
1211 	case MDOC_Ft:
1212 		if (MDOC_Fn != n->tok && MDOC_Fo != n->tok) {
1213 			term_vspace(p);
1214 			break;
1215 		}
1216 		/* FALLTHROUGH */
1217 	default:
1218 		term_newln(p);
1219 		break;
1220 	}
1221 }
1222 
1223 static int
1224 termp_vt_pre(DECL_ARGS)
1225 {
1226 
1227 	if (n->type == ROFFT_ELEM) {
1228 		synopsis_pre(p, n);
1229 		return termp_under_pre(p, pair, meta, n);
1230 	} else if (n->type == ROFFT_BLOCK) {
1231 		synopsis_pre(p, n);
1232 		return 1;
1233 	} else if (n->type == ROFFT_HEAD)
1234 		return 0;
1235 
1236 	return termp_under_pre(p, pair, meta, n);
1237 }
1238 
1239 static int
1240 termp_bold_pre(DECL_ARGS)
1241 {
1242 
1243 	termp_tag_pre(p, pair, meta, n);
1244 	term_fontpush(p, TERMFONT_BOLD);
1245 	return 1;
1246 }
1247 
1248 static int
1249 termp_fd_pre(DECL_ARGS)
1250 {
1251 
1252 	synopsis_pre(p, n);
1253 	return termp_bold_pre(p, pair, meta, n);
1254 }
1255 
1256 static void
1257 termp_fd_post(DECL_ARGS)
1258 {
1259 
1260 	term_newln(p);
1261 }
1262 
1263 static int
1264 termp_sh_pre(DECL_ARGS)
1265 {
1266 
1267 	switch (n->type) {
1268 	case ROFFT_BLOCK:
1269 		/*
1270 		 * Vertical space before sections, except
1271 		 * when the previous section was empty.
1272 		 */
1273 		if (n->prev == NULL ||
1274 		    n->prev->tok != MDOC_Sh ||
1275 		    (n->prev->body != NULL &&
1276 		     n->prev->body->child != NULL))
1277 			term_vspace(p);
1278 		break;
1279 	case ROFFT_HEAD:
1280 		term_fontpush(p, TERMFONT_BOLD);
1281 		break;
1282 	case ROFFT_BODY:
1283 		p->tcol->offset = term_len(p, p->defindent);
1284 		term_tab_set(p, NULL);
1285 		term_tab_set(p, "T");
1286 		term_tab_set(p, ".5i");
1287 		switch (n->sec) {
1288 		case SEC_DESCRIPTION:
1289 			fn_prio = 0;
1290 			break;
1291 		case SEC_AUTHORS:
1292 			p->flags &= ~(TERMP_SPLIT|TERMP_NOSPLIT);
1293 			break;
1294 		default:
1295 			break;
1296 		}
1297 		break;
1298 	default:
1299 		break;
1300 	}
1301 	return 1;
1302 }
1303 
1304 static void
1305 termp_sh_post(DECL_ARGS)
1306 {
1307 
1308 	switch (n->type) {
1309 	case ROFFT_HEAD:
1310 		term_newln(p);
1311 		break;
1312 	case ROFFT_BODY:
1313 		term_newln(p);
1314 		p->tcol->offset = 0;
1315 		break;
1316 	default:
1317 		break;
1318 	}
1319 }
1320 
1321 static void
1322 termp_lb_post(DECL_ARGS)
1323 {
1324 
1325 	if (SEC_LIBRARY == n->sec && NODE_LINE & n->flags)
1326 		term_newln(p);
1327 }
1328 
1329 static int
1330 termp_d1_pre(DECL_ARGS)
1331 {
1332 
1333 	if (n->type != ROFFT_BLOCK)
1334 		return 1;
1335 	term_newln(p);
1336 	p->tcol->offset += term_len(p, p->defindent + 1);
1337 	term_tab_set(p, NULL);
1338 	term_tab_set(p, "T");
1339 	term_tab_set(p, ".5i");
1340 	return 1;
1341 }
1342 
1343 static int
1344 termp_ft_pre(DECL_ARGS)
1345 {
1346 
1347 	/* NB: NODE_LINE does not effect this! */
1348 	synopsis_pre(p, n);
1349 	term_fontpush(p, TERMFONT_UNDER);
1350 	return 1;
1351 }
1352 
1353 static int
1354 termp_fn_pre(DECL_ARGS)
1355 {
1356 	size_t		 rmargin = 0;
1357 	int		 pretty;
1358 
1359 	pretty = NODE_SYNPRETTY & n->flags;
1360 
1361 	synopsis_pre(p, n);
1362 
1363 	if (NULL == (n = n->child))
1364 		return 0;
1365 
1366 	if (pretty) {
1367 		rmargin = p->tcol->rmargin;
1368 		p->tcol->rmargin = p->tcol->offset + term_len(p, 4);
1369 		p->flags |= TERMP_NOBREAK | TERMP_BRIND | TERMP_HANG;
1370 	}
1371 
1372 	assert(n->type == ROFFT_TEXT);
1373 	term_fontpush(p, TERMFONT_BOLD);
1374 	term_word(p, n->string);
1375 	term_fontpop(p);
1376 
1377 	if (n->sec == SEC_DESCRIPTION || n->sec == SEC_CUSTOM)
1378 		tag_put(n->string, ++fn_prio, p->line);
1379 
1380 	if (pretty) {
1381 		term_flushln(p);
1382 		p->flags &= ~(TERMP_NOBREAK | TERMP_BRIND | TERMP_HANG);
1383 		p->flags |= TERMP_NOPAD;
1384 		p->tcol->offset = p->tcol->rmargin;
1385 		p->tcol->rmargin = rmargin;
1386 	}
1387 
1388 	p->flags |= TERMP_NOSPACE;
1389 	term_word(p, "(");
1390 	p->flags |= TERMP_NOSPACE;
1391 
1392 	for (n = n->next; n; n = n->next) {
1393 		assert(n->type == ROFFT_TEXT);
1394 		term_fontpush(p, TERMFONT_UNDER);
1395 		if (pretty)
1396 			p->flags |= TERMP_NBRWORD;
1397 		term_word(p, n->string);
1398 		term_fontpop(p);
1399 
1400 		if (n->next) {
1401 			p->flags |= TERMP_NOSPACE;
1402 			term_word(p, ",");
1403 		}
1404 	}
1405 
1406 	p->flags |= TERMP_NOSPACE;
1407 	term_word(p, ")");
1408 
1409 	if (pretty) {
1410 		p->flags |= TERMP_NOSPACE;
1411 		term_word(p, ";");
1412 		term_flushln(p);
1413 	}
1414 
1415 	return 0;
1416 }
1417 
1418 static int
1419 termp_fa_pre(DECL_ARGS)
1420 {
1421 	const struct roff_node	*nn;
1422 
1423 	if (n->parent->tok != MDOC_Fo) {
1424 		term_fontpush(p, TERMFONT_UNDER);
1425 		return 1;
1426 	}
1427 
1428 	for (nn = n->child; nn; nn = nn->next) {
1429 		term_fontpush(p, TERMFONT_UNDER);
1430 		p->flags |= TERMP_NBRWORD;
1431 		term_word(p, nn->string);
1432 		term_fontpop(p);
1433 
1434 		if (nn->next || (n->next && n->next->tok == MDOC_Fa)) {
1435 			p->flags |= TERMP_NOSPACE;
1436 			term_word(p, ",");
1437 		}
1438 	}
1439 
1440 	return 0;
1441 }
1442 
1443 static int
1444 termp_bd_pre(DECL_ARGS)
1445 {
1446 	int			 offset;
1447 
1448 	if (n->type == ROFFT_BLOCK) {
1449 		print_bvspace(p, n, n);
1450 		return 1;
1451 	} else if (n->type == ROFFT_HEAD)
1452 		return 0;
1453 
1454 	/* Handle the -offset argument. */
1455 
1456 	if (n->norm->Bd.offs == NULL ||
1457 	    ! strcmp(n->norm->Bd.offs, "left"))
1458 		/* nothing */;
1459 	else if ( ! strcmp(n->norm->Bd.offs, "indent"))
1460 		p->tcol->offset += term_len(p, p->defindent + 1);
1461 	else if ( ! strcmp(n->norm->Bd.offs, "indent-two"))
1462 		p->tcol->offset += term_len(p, (p->defindent + 1) * 2);
1463 	else {
1464 		offset = a2width(p, n->norm->Bd.offs);
1465 		if (offset < 0 && (size_t)(-offset) > p->tcol->offset)
1466 			p->tcol->offset = 0;
1467 		else if (offset < SHRT_MAX)
1468 			p->tcol->offset += offset;
1469 	}
1470 
1471 	switch (n->norm->Bd.type) {
1472 	case DISP_literal:
1473 		term_tab_set(p, NULL);
1474 		term_tab_set(p, "T");
1475 		term_tab_set(p, "8n");
1476 		break;
1477 	case DISP_centered:
1478 		p->flags |= TERMP_CENTER;
1479 		break;
1480 	default:
1481 		break;
1482 	}
1483 	return 1;
1484 }
1485 
1486 static void
1487 termp_bd_post(DECL_ARGS)
1488 {
1489 	if (n->type != ROFFT_BODY)
1490 		return;
1491 	if (n->norm->Bd.type == DISP_unfilled ||
1492 	    n->norm->Bd.type == DISP_literal)
1493 		p->flags |= TERMP_BRNEVER;
1494 	p->flags |= TERMP_NOSPACE;
1495 	term_newln(p);
1496 	p->flags &= ~TERMP_BRNEVER;
1497 	if (n->norm->Bd.type == DISP_centered)
1498 		p->flags &= ~TERMP_CENTER;
1499 }
1500 
1501 static int
1502 termp_xx_pre(DECL_ARGS)
1503 {
1504 	if ((n->aux = p->flags & TERMP_PREKEEP) == 0)
1505 		p->flags |= TERMP_PREKEEP;
1506 	return 1;
1507 }
1508 
1509 static void
1510 termp_xx_post(DECL_ARGS)
1511 {
1512 	if (n->aux == 0)
1513 		p->flags &= ~(TERMP_KEEP | TERMP_PREKEEP);
1514 }
1515 
1516 static void
1517 termp_pf_post(DECL_ARGS)
1518 {
1519 
1520 	if ( ! (n->next == NULL || n->next->flags & NODE_LINE))
1521 		p->flags |= TERMP_NOSPACE;
1522 }
1523 
1524 static int
1525 termp_ss_pre(DECL_ARGS)
1526 {
1527 	struct roff_node *nn;
1528 
1529 	switch (n->type) {
1530 	case ROFFT_BLOCK:
1531 		term_newln(p);
1532 		for (nn = n->prev; nn != NULL; nn = nn->prev)
1533 			if (nn->type != ROFFT_COMMENT &&
1534 			    (nn->flags & NODE_NOPRT) == 0)
1535 				break;
1536 		if (nn != NULL)
1537 			term_vspace(p);
1538 		break;
1539 	case ROFFT_HEAD:
1540 		term_fontpush(p, TERMFONT_BOLD);
1541 		p->tcol->offset = term_len(p, (p->defindent+1)/2);
1542 		break;
1543 	case ROFFT_BODY:
1544 		p->tcol->offset = term_len(p, p->defindent);
1545 		term_tab_set(p, NULL);
1546 		term_tab_set(p, "T");
1547 		term_tab_set(p, ".5i");
1548 		break;
1549 	default:
1550 		break;
1551 	}
1552 
1553 	return 1;
1554 }
1555 
1556 static void
1557 termp_ss_post(DECL_ARGS)
1558 {
1559 
1560 	if (n->type == ROFFT_HEAD || n->type == ROFFT_BODY)
1561 		term_newln(p);
1562 }
1563 
1564 static int
1565 termp_cd_pre(DECL_ARGS)
1566 {
1567 
1568 	synopsis_pre(p, n);
1569 	term_fontpush(p, TERMFONT_BOLD);
1570 	return 1;
1571 }
1572 
1573 static int
1574 termp_in_pre(DECL_ARGS)
1575 {
1576 
1577 	synopsis_pre(p, n);
1578 
1579 	if (NODE_SYNPRETTY & n->flags && NODE_LINE & n->flags) {
1580 		term_fontpush(p, TERMFONT_BOLD);
1581 		term_word(p, "#include");
1582 		term_word(p, "<");
1583 	} else {
1584 		term_word(p, "<");
1585 		term_fontpush(p, TERMFONT_UNDER);
1586 	}
1587 
1588 	p->flags |= TERMP_NOSPACE;
1589 	return 1;
1590 }
1591 
1592 static void
1593 termp_in_post(DECL_ARGS)
1594 {
1595 
1596 	if (NODE_SYNPRETTY & n->flags)
1597 		term_fontpush(p, TERMFONT_BOLD);
1598 
1599 	p->flags |= TERMP_NOSPACE;
1600 	term_word(p, ">");
1601 
1602 	if (NODE_SYNPRETTY & n->flags)
1603 		term_fontpop(p);
1604 }
1605 
1606 static int
1607 termp_pp_pre(DECL_ARGS)
1608 {
1609 	fn_prio = 0;
1610 	term_vspace(p);
1611 	return 0;
1612 }
1613 
1614 static int
1615 termp_skip_pre(DECL_ARGS)
1616 {
1617 
1618 	return 0;
1619 }
1620 
1621 static int
1622 termp_quote_pre(DECL_ARGS)
1623 {
1624 
1625 	if (n->type != ROFFT_BODY && n->type != ROFFT_ELEM)
1626 		return 1;
1627 
1628 	switch (n->tok) {
1629 	case MDOC_Ao:
1630 	case MDOC_Aq:
1631 		term_word(p, n->child != NULL && n->child->next == NULL &&
1632 		    n->child->tok == MDOC_Mt ? "<" : "\\(la");
1633 		break;
1634 	case MDOC_Bro:
1635 	case MDOC_Brq:
1636 		term_word(p, "{");
1637 		break;
1638 	case MDOC_Oo:
1639 	case MDOC_Op:
1640 	case MDOC_Bo:
1641 	case MDOC_Bq:
1642 		term_word(p, "[");
1643 		break;
1644 	case MDOC__T:
1645 		/* FALLTHROUGH */
1646 	case MDOC_Do:
1647 	case MDOC_Dq:
1648 		term_word(p, "\\(lq");
1649 		break;
1650 	case MDOC_En:
1651 		if (NULL == n->norm->Es ||
1652 		    NULL == n->norm->Es->child)
1653 			return 1;
1654 		term_word(p, n->norm->Es->child->string);
1655 		break;
1656 	case MDOC_Po:
1657 	case MDOC_Pq:
1658 		term_word(p, "(");
1659 		break;
1660 	case MDOC_Qo:
1661 	case MDOC_Qq:
1662 		term_word(p, "\"");
1663 		break;
1664 	case MDOC_Ql:
1665 	case MDOC_So:
1666 	case MDOC_Sq:
1667 		term_word(p, "\\(oq");
1668 		break;
1669 	default:
1670 		abort();
1671 	}
1672 
1673 	p->flags |= TERMP_NOSPACE;
1674 	return 1;
1675 }
1676 
1677 static void
1678 termp_quote_post(DECL_ARGS)
1679 {
1680 
1681 	if (n->type != ROFFT_BODY && n->type != ROFFT_ELEM)
1682 		return;
1683 
1684 	p->flags |= TERMP_NOSPACE;
1685 
1686 	switch (n->tok) {
1687 	case MDOC_Ao:
1688 	case MDOC_Aq:
1689 		term_word(p, n->child != NULL && n->child->next == NULL &&
1690 		    n->child->tok == MDOC_Mt ? ">" : "\\(ra");
1691 		break;
1692 	case MDOC_Bro:
1693 	case MDOC_Brq:
1694 		term_word(p, "}");
1695 		break;
1696 	case MDOC_Oo:
1697 	case MDOC_Op:
1698 	case MDOC_Bo:
1699 	case MDOC_Bq:
1700 		term_word(p, "]");
1701 		break;
1702 	case MDOC__T:
1703 		/* FALLTHROUGH */
1704 	case MDOC_Do:
1705 	case MDOC_Dq:
1706 		term_word(p, "\\(rq");
1707 		break;
1708 	case MDOC_En:
1709 		if (n->norm->Es == NULL ||
1710 		    n->norm->Es->child == NULL ||
1711 		    n->norm->Es->child->next == NULL)
1712 			p->flags &= ~TERMP_NOSPACE;
1713 		else
1714 			term_word(p, n->norm->Es->child->next->string);
1715 		break;
1716 	case MDOC_Po:
1717 	case MDOC_Pq:
1718 		term_word(p, ")");
1719 		break;
1720 	case MDOC_Qo:
1721 	case MDOC_Qq:
1722 		term_word(p, "\"");
1723 		break;
1724 	case MDOC_Ql:
1725 	case MDOC_So:
1726 	case MDOC_Sq:
1727 		term_word(p, "\\(cq");
1728 		break;
1729 	default:
1730 		abort();
1731 	}
1732 }
1733 
1734 static int
1735 termp_eo_pre(DECL_ARGS)
1736 {
1737 
1738 	if (n->type != ROFFT_BODY)
1739 		return 1;
1740 
1741 	if (n->end == ENDBODY_NOT &&
1742 	    n->parent->head->child == NULL &&
1743 	    n->child != NULL &&
1744 	    n->child->end != ENDBODY_NOT)
1745 		term_word(p, "\\&");
1746 	else if (n->end != ENDBODY_NOT ? n->child != NULL :
1747 	     n->parent->head->child != NULL && (n->child != NULL ||
1748 	     (n->parent->tail != NULL && n->parent->tail->child != NULL)))
1749 		p->flags |= TERMP_NOSPACE;
1750 
1751 	return 1;
1752 }
1753 
1754 static void
1755 termp_eo_post(DECL_ARGS)
1756 {
1757 	int	 body, tail;
1758 
1759 	if (n->type != ROFFT_BODY)
1760 		return;
1761 
1762 	if (n->end != ENDBODY_NOT) {
1763 		p->flags &= ~TERMP_NOSPACE;
1764 		return;
1765 	}
1766 
1767 	body = n->child != NULL || n->parent->head->child != NULL;
1768 	tail = n->parent->tail != NULL && n->parent->tail->child != NULL;
1769 
1770 	if (body && tail)
1771 		p->flags |= TERMP_NOSPACE;
1772 	else if ( ! (body || tail))
1773 		term_word(p, "\\&");
1774 	else if ( ! tail)
1775 		p->flags &= ~TERMP_NOSPACE;
1776 }
1777 
1778 static int
1779 termp_fo_pre(DECL_ARGS)
1780 {
1781 	size_t		 rmargin = 0;
1782 	int		 pretty;
1783 
1784 	pretty = NODE_SYNPRETTY & n->flags;
1785 
1786 	if (n->type == ROFFT_BLOCK) {
1787 		synopsis_pre(p, n);
1788 		return 1;
1789 	} else if (n->type == ROFFT_BODY) {
1790 		if (pretty) {
1791 			rmargin = p->tcol->rmargin;
1792 			p->tcol->rmargin = p->tcol->offset + term_len(p, 4);
1793 			p->flags |= TERMP_NOBREAK | TERMP_BRIND |
1794 					TERMP_HANG;
1795 		}
1796 		p->flags |= TERMP_NOSPACE;
1797 		term_word(p, "(");
1798 		p->flags |= TERMP_NOSPACE;
1799 		if (pretty) {
1800 			term_flushln(p);
1801 			p->flags &= ~(TERMP_NOBREAK | TERMP_BRIND |
1802 					TERMP_HANG);
1803 			p->flags |= TERMP_NOPAD;
1804 			p->tcol->offset = p->tcol->rmargin;
1805 			p->tcol->rmargin = rmargin;
1806 		}
1807 		return 1;
1808 	}
1809 
1810 	if (NULL == n->child)
1811 		return 0;
1812 
1813 	/* XXX: we drop non-initial arguments as per groff. */
1814 
1815 	assert(n->child->string);
1816 	term_fontpush(p, TERMFONT_BOLD);
1817 	term_word(p, n->child->string);
1818 	return 0;
1819 }
1820 
1821 static void
1822 termp_fo_post(DECL_ARGS)
1823 {
1824 
1825 	if (n->type != ROFFT_BODY)
1826 		return;
1827 
1828 	p->flags |= TERMP_NOSPACE;
1829 	term_word(p, ")");
1830 
1831 	if (NODE_SYNPRETTY & n->flags) {
1832 		p->flags |= TERMP_NOSPACE;
1833 		term_word(p, ";");
1834 		term_flushln(p);
1835 	}
1836 }
1837 
1838 static int
1839 termp_bf_pre(DECL_ARGS)
1840 {
1841 
1842 	if (n->type == ROFFT_HEAD)
1843 		return 0;
1844 	else if (n->type != ROFFT_BODY)
1845 		return 1;
1846 
1847 	if (FONT_Em == n->norm->Bf.font)
1848 		term_fontpush(p, TERMFONT_UNDER);
1849 	else if (FONT_Sy == n->norm->Bf.font)
1850 		term_fontpush(p, TERMFONT_BOLD);
1851 	else
1852 		term_fontpush(p, TERMFONT_NONE);
1853 
1854 	return 1;
1855 }
1856 
1857 static int
1858 termp_sm_pre(DECL_ARGS)
1859 {
1860 
1861 	if (NULL == n->child)
1862 		p->flags ^= TERMP_NONOSPACE;
1863 	else if (0 == strcmp("on", n->child->string))
1864 		p->flags &= ~TERMP_NONOSPACE;
1865 	else
1866 		p->flags |= TERMP_NONOSPACE;
1867 
1868 	if (p->col && ! (TERMP_NONOSPACE & p->flags))
1869 		p->flags &= ~TERMP_NOSPACE;
1870 
1871 	return 0;
1872 }
1873 
1874 static int
1875 termp_ap_pre(DECL_ARGS)
1876 {
1877 
1878 	p->flags |= TERMP_NOSPACE;
1879 	term_word(p, "'");
1880 	p->flags |= TERMP_NOSPACE;
1881 	return 1;
1882 }
1883 
1884 static void
1885 termp____post(DECL_ARGS)
1886 {
1887 
1888 	/*
1889 	 * Handle lists of authors.  In general, print each followed by
1890 	 * a comma.  Don't print the comma if there are only two
1891 	 * authors.
1892 	 */
1893 	if (MDOC__A == n->tok && n->next && MDOC__A == n->next->tok)
1894 		if (NULL == n->next->next || MDOC__A != n->next->next->tok)
1895 			if (NULL == n->prev || MDOC__A != n->prev->tok)
1896 				return;
1897 
1898 	/* TODO: %U. */
1899 
1900 	if (NULL == n->parent || MDOC_Rs != n->parent->tok)
1901 		return;
1902 
1903 	p->flags |= TERMP_NOSPACE;
1904 	if (NULL == n->next) {
1905 		term_word(p, ".");
1906 		p->flags |= TERMP_SENTENCE;
1907 	} else
1908 		term_word(p, ",");
1909 }
1910 
1911 static int
1912 termp_li_pre(DECL_ARGS)
1913 {
1914 
1915 	termp_tag_pre(p, pair, meta, n);
1916 	term_fontpush(p, TERMFONT_NONE);
1917 	return 1;
1918 }
1919 
1920 static int
1921 termp_lk_pre(DECL_ARGS)
1922 {
1923 	const struct roff_node *link, *descr, *punct;
1924 
1925 	if ((link = n->child) == NULL)
1926 		return 0;
1927 
1928 	/* Find beginning of trailing punctuation. */
1929 	punct = n->last;
1930 	while (punct != link && punct->flags & NODE_DELIMC)
1931 		punct = punct->prev;
1932 	punct = punct->next;
1933 
1934 	/* Link text. */
1935 	if ((descr = link->next) != NULL && descr != punct) {
1936 		term_fontpush(p, TERMFONT_UNDER);
1937 		while (descr != punct) {
1938 			if (descr->flags & (NODE_DELIMC | NODE_DELIMO))
1939 				p->flags |= TERMP_NOSPACE;
1940 			term_word(p, descr->string);
1941 			descr = descr->next;
1942 		}
1943 		term_fontpop(p);
1944 		p->flags |= TERMP_NOSPACE;
1945 		term_word(p, ":");
1946 	}
1947 
1948 	/* Link target. */
1949 	term_fontpush(p, TERMFONT_BOLD);
1950 	term_word(p, link->string);
1951 	term_fontpop(p);
1952 
1953 	/* Trailing punctuation. */
1954 	while (punct != NULL) {
1955 		p->flags |= TERMP_NOSPACE;
1956 		term_word(p, punct->string);
1957 		punct = punct->next;
1958 	}
1959 	return 0;
1960 }
1961 
1962 static int
1963 termp_bk_pre(DECL_ARGS)
1964 {
1965 
1966 	switch (n->type) {
1967 	case ROFFT_BLOCK:
1968 		break;
1969 	case ROFFT_HEAD:
1970 		return 0;
1971 	case ROFFT_BODY:
1972 		if (n->parent->args != NULL || n->prev->child == NULL)
1973 			p->flags |= TERMP_PREKEEP;
1974 		break;
1975 	default:
1976 		abort();
1977 	}
1978 
1979 	return 1;
1980 }
1981 
1982 static void
1983 termp_bk_post(DECL_ARGS)
1984 {
1985 
1986 	if (n->type == ROFFT_BODY)
1987 		p->flags &= ~(TERMP_KEEP | TERMP_PREKEEP);
1988 }
1989 
1990 static void
1991 termp__t_post(DECL_ARGS)
1992 {
1993 
1994 	/*
1995 	 * If we're in an `Rs' and there's a journal present, then quote
1996 	 * us instead of underlining us (for disambiguation).
1997 	 */
1998 	if (n->parent && MDOC_Rs == n->parent->tok &&
1999 	    n->parent->norm->Rs.quote_T)
2000 		termp_quote_post(p, pair, meta, n);
2001 
2002 	termp____post(p, pair, meta, n);
2003 }
2004 
2005 static int
2006 termp__t_pre(DECL_ARGS)
2007 {
2008 
2009 	/*
2010 	 * If we're in an `Rs' and there's a journal present, then quote
2011 	 * us instead of underlining us (for disambiguation).
2012 	 */
2013 	if (n->parent && MDOC_Rs == n->parent->tok &&
2014 	    n->parent->norm->Rs.quote_T)
2015 		return termp_quote_pre(p, pair, meta, n);
2016 
2017 	term_fontpush(p, TERMFONT_UNDER);
2018 	return 1;
2019 }
2020 
2021 static int
2022 termp_under_pre(DECL_ARGS)
2023 {
2024 
2025 	term_fontpush(p, TERMFONT_UNDER);
2026 	return 1;
2027 }
2028 
2029 static int
2030 termp_em_pre(DECL_ARGS)
2031 {
2032 	if (n->child != NULL &&
2033 	    n->child->type == ROFFT_TEXT)
2034 		tag_put(n->child->string, 0, p->line);
2035 	term_fontpush(p, TERMFONT_UNDER);
2036 	return 1;
2037 }
2038 
2039 static int
2040 termp_sy_pre(DECL_ARGS)
2041 {
2042 	if (n->child != NULL &&
2043 	    n->child->type == ROFFT_TEXT)
2044 		tag_put(n->child->string, 0, p->line);
2045 	term_fontpush(p, TERMFONT_BOLD);
2046 	return 1;
2047 }
2048 
2049 static int
2050 termp_er_pre(DECL_ARGS)
2051 {
2052 
2053 	if (n->sec == SEC_ERRORS &&
2054 	    (n->parent->tok == MDOC_It ||
2055 	     (n->parent->tok == MDOC_Bq &&
2056 	      n->parent->parent->parent->tok == MDOC_It)))
2057 		tag_put(n->child->string, 1, p->line);
2058 	return 1;
2059 }
2060 
2061 static int
2062 termp_tag_pre(DECL_ARGS)
2063 {
2064 
2065 	if (n->child != NULL &&
2066 	    n->child->type == ROFFT_TEXT &&
2067 	    (n->prev == NULL ||
2068 	     (n->prev->type == ROFFT_TEXT &&
2069 	      strcmp(n->prev->string, "|") == 0)) &&
2070 	    (n->parent->tok == MDOC_It ||
2071 	     (n->parent->tok == MDOC_Xo &&
2072 	      n->parent->parent->prev == NULL &&
2073 	      n->parent->parent->parent->tok == MDOC_It)))
2074 		tag_put(n->child->string, 1, p->line);
2075 	return 1;
2076 }
2077 
2078 static int
2079 termp_abort_pre(DECL_ARGS)
2080 {
2081 	abort();
2082 }
2083