xref: /openbsd-src/usr.bin/mandoc/tree.c (revision d2672555187c915062a2f51eb6c0b569ce2aae0e)
1*d2672555Sschwarze /* $OpenBSD: tree.c,v 1.59 2022/01/12 04:53:57 schwarze Exp $ */
2f73abda9Skristaps /*
30ac7e6ecSschwarze  * Copyright (c) 2008, 2009, 2011, 2014 Kristaps Dzonsons <kristaps@bsd.lv>
4*d2672555Sschwarze  * Copyright (c) 2013-2015, 2017-2022 Ingo Schwarze <schwarze@openbsd.org>
5f73abda9Skristaps  *
6f73abda9Skristaps  * Permission to use, copy, modify, and distribute this software for any
7a6464425Sschwarze  * purpose with or without fee is hereby granted, provided that the above
8a6464425Sschwarze  * copyright notice and this permission notice appear in all copies.
9f73abda9Skristaps  *
10d1982c71Sschwarze  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHORS DISCLAIM ALL WARRANTIES
11a6464425Sschwarze  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12d1982c71Sschwarze  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR
13a6464425Sschwarze  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14a6464425Sschwarze  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15a6464425Sschwarze  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16a6464425Sschwarze  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
170ac7e6ecSschwarze  *
180ac7e6ecSschwarze  * Formatting module to let mandoc(1) show
190ac7e6ecSschwarze  * a human readable representation of the syntax tree.
20f73abda9Skristaps  */
21f8617809Sschwarze #include <sys/types.h>
22f8617809Sschwarze 
23f73abda9Skristaps #include <assert.h>
24f8618d99Sschwarze #include <limits.h>
25f73abda9Skristaps #include <stdio.h>
26f73abda9Skristaps #include <stdlib.h>
27a66b65d0Sschwarze #include <time.h>
28f73abda9Skristaps 
296e03d529Sschwarze #include "mandoc.h"
30d1982c71Sschwarze #include "roff.h"
31f73abda9Skristaps #include "mdoc.h"
32f73abda9Skristaps #include "man.h"
33fae2491eSschwarze #include "tbl.h"
34d4c8d4a3Sschwarze #include "eqn.h"
354175bdabSschwarze #include "main.h"
36f73abda9Skristaps 
37c220f9cfSschwarze static	void	print_attr(const struct roff_node *);
38f8618d99Sschwarze static	void	print_box(const struct eqn_box *, int);
39e5c2e3c9Sschwarze static	void	print_cellt(enum tbl_cellt);
403a0d07afSschwarze static	void	print_man(const struct roff_node *, int);
41d5fe3964Sschwarze static	void	print_meta(const struct roff_meta *);
423a0d07afSschwarze static	void	print_mdoc(const struct roff_node *, int);
432791bd1cSschwarze static	void	print_span(const struct tbl_span *, int);
44f73abda9Skristaps 
45f73abda9Skristaps 
466ae2e8acSschwarze void
tree_mdoc(void * arg,const struct roff_meta * mdoc)476b86842eSschwarze tree_mdoc(void *arg, const struct roff_meta *mdoc)
48f73abda9Skristaps {
496b86842eSschwarze 	print_meta(mdoc);
50d5fe3964Sschwarze 	putchar('\n');
516d0e9b63Sschwarze 	print_mdoc(mdoc->first->child, 0);
52f73abda9Skristaps }
53f73abda9Skristaps 
546ae2e8acSschwarze void
tree_man(void * arg,const struct roff_meta * man)556b86842eSschwarze tree_man(void *arg, const struct roff_meta *man)
56f73abda9Skristaps {
576b86842eSschwarze 	print_meta(man);
586b86842eSschwarze 	if (man->hasbody == 0)
59d5fe3964Sschwarze 		puts("body  = empty");
60d5fe3964Sschwarze 	putchar('\n');
616d0e9b63Sschwarze 	print_man(man->first->child, 0);
62f73abda9Skristaps }
63f73abda9Skristaps 
64f73abda9Skristaps static void
print_meta(const struct roff_meta * meta)65d5fe3964Sschwarze print_meta(const struct roff_meta *meta)
66d5fe3964Sschwarze {
67d5fe3964Sschwarze 	if (meta->title != NULL)
68d5fe3964Sschwarze 		printf("title = \"%s\"\n", meta->title);
69d5fe3964Sschwarze 	if (meta->name != NULL)
70d5fe3964Sschwarze 		printf("name  = \"%s\"\n", meta->name);
71d5fe3964Sschwarze 	if (meta->msec != NULL)
72d5fe3964Sschwarze 		printf("sec   = \"%s\"\n", meta->msec);
73d5fe3964Sschwarze 	if (meta->vol != NULL)
74d5fe3964Sschwarze 		printf("vol   = \"%s\"\n", meta->vol);
75d5fe3964Sschwarze 	if (meta->arch != NULL)
76d5fe3964Sschwarze 		printf("arch  = \"%s\"\n", meta->arch);
77d5fe3964Sschwarze 	if (meta->os != NULL)
78d5fe3964Sschwarze 		printf("os    = \"%s\"\n", meta->os);
79d5fe3964Sschwarze 	if (meta->date != NULL)
80d5fe3964Sschwarze 		printf("date  = \"%s\"\n", meta->date);
81d5fe3964Sschwarze }
82d5fe3964Sschwarze 
83d5fe3964Sschwarze static void
print_mdoc(const struct roff_node * n,int indent)843a0d07afSschwarze print_mdoc(const struct roff_node *n, int indent)
85f73abda9Skristaps {
86f73abda9Skristaps 	const char	 *p, *t;
87f73abda9Skristaps 	int		  i, j;
8856d07b44Sschwarze 	size_t		  argc;
89f73abda9Skristaps 	struct mdoc_argv *argv;
90f73abda9Skristaps 
910a42f588Sschwarze 	if (n == NULL)
920a42f588Sschwarze 		return;
930a42f588Sschwarze 
94f73abda9Skristaps 	argv = NULL;
9556d07b44Sschwarze 	argc = 0;
96f8618d99Sschwarze 	t = p = NULL;
97f73abda9Skristaps 
98f73abda9Skristaps 	switch (n->type) {
99d1982c71Sschwarze 	case ROFFT_ROOT:
100f73abda9Skristaps 		t = "root";
101f73abda9Skristaps 		break;
102d1982c71Sschwarze 	case ROFFT_BLOCK:
103f73abda9Skristaps 		t = "block";
104f73abda9Skristaps 		break;
105d1982c71Sschwarze 	case ROFFT_HEAD:
10631a38b0eSschwarze 		t = "head";
107f73abda9Skristaps 		break;
108d1982c71Sschwarze 	case ROFFT_BODY:
10932dadbacSschwarze 		if (n->end)
11032dadbacSschwarze 			t = "body-end";
11132dadbacSschwarze 		else
11231a38b0eSschwarze 			t = "body";
113f73abda9Skristaps 		break;
114d1982c71Sschwarze 	case ROFFT_TAIL:
11531a38b0eSschwarze 		t = "tail";
116f73abda9Skristaps 		break;
117d1982c71Sschwarze 	case ROFFT_ELEM:
118f73abda9Skristaps 		t = "elem";
119f73abda9Skristaps 		break;
120d1982c71Sschwarze 	case ROFFT_TEXT:
121f73abda9Skristaps 		t = "text";
122f73abda9Skristaps 		break;
1234c293873Sschwarze 	case ROFFT_COMMENT:
1244c293873Sschwarze 		t = "comment";
1254c293873Sschwarze 		break;
126d1982c71Sschwarze 	case ROFFT_TBL:
12725fe02efSschwarze 		break;
128d1982c71Sschwarze 	case ROFFT_EQN:
12925fe02efSschwarze 		t = "eqn";
1308d973ab1Sschwarze 		break;
131f73abda9Skristaps 	default:
132f73abda9Skristaps 		abort();
133f73abda9Skristaps 	}
134f73abda9Skristaps 
135f73abda9Skristaps 	switch (n->type) {
136d1982c71Sschwarze 	case ROFFT_TEXT:
1374c293873Sschwarze 	case ROFFT_COMMENT:
138f73abda9Skristaps 		p = n->string;
139f73abda9Skristaps 		break;
140d1982c71Sschwarze 	case ROFFT_BODY:
14114a309e3Sschwarze 		p = roff_name[n->tok];
142f73abda9Skristaps 		break;
143d1982c71Sschwarze 	case ROFFT_HEAD:
14414a309e3Sschwarze 		p = roff_name[n->tok];
145f73abda9Skristaps 		break;
146d1982c71Sschwarze 	case ROFFT_TAIL:
14714a309e3Sschwarze 		p = roff_name[n->tok];
148f73abda9Skristaps 		break;
149d1982c71Sschwarze 	case ROFFT_ELEM:
15014a309e3Sschwarze 		p = roff_name[n->tok];
151f73abda9Skristaps 		if (n->args) {
152f73abda9Skristaps 			argv = n->args->argv;
153f73abda9Skristaps 			argc = n->args->argc;
154f73abda9Skristaps 		}
155f73abda9Skristaps 		break;
156d1982c71Sschwarze 	case ROFFT_BLOCK:
15714a309e3Sschwarze 		p = roff_name[n->tok];
158f73abda9Skristaps 		if (n->args) {
159f73abda9Skristaps 			argv = n->args->argv;
160f73abda9Skristaps 			argc = n->args->argc;
161f73abda9Skristaps 		}
162f73abda9Skristaps 		break;
163d1982c71Sschwarze 	case ROFFT_TBL:
16425fe02efSschwarze 		break;
165d1982c71Sschwarze 	case ROFFT_EQN:
16625fe02efSschwarze 		p = "EQ";
1678d973ab1Sschwarze 		break;
168d1982c71Sschwarze 	case ROFFT_ROOT:
169f73abda9Skristaps 		p = "root";
170f73abda9Skristaps 		break;
171f73abda9Skristaps 	default:
172f73abda9Skristaps 		abort();
173f73abda9Skristaps 	}
174f73abda9Skristaps 
1752791bd1cSschwarze 	if (n->span) {
176f8618d99Sschwarze 		assert(NULL == p && NULL == t);
1772791bd1cSschwarze 		print_span(n->span, indent);
1782791bd1cSschwarze 	} else {
179f73abda9Skristaps 		for (i = 0; i < indent; i++)
1800a42f588Sschwarze 			putchar(' ');
1812791bd1cSschwarze 
1822791bd1cSschwarze 		printf("%s (%s)", p, t);
183f73abda9Skristaps 
184f73abda9Skristaps 		for (i = 0; i < (int)argc; i++) {
1852791bd1cSschwarze 			printf(" -%s", mdoc_argnames[argv[i].arg]);
186f73abda9Skristaps 			if (argv[i].sz > 0)
1872791bd1cSschwarze 				printf(" [");
188f73abda9Skristaps 			for (j = 0; j < (int)argv[i].sz; j++)
1892791bd1cSschwarze 				printf(" [%s]", argv[i].value[j]);
190f73abda9Skristaps 			if (argv[i].sz > 0)
1912791bd1cSschwarze 				printf(" ]");
192f73abda9Skristaps 		}
193c220f9cfSschwarze 		print_attr(n);
1949a542ed3Sschwarze 	}
19525fe02efSschwarze 	if (n->eqn)
196bf9acac6Sschwarze 		print_box(n->eqn->first, indent + 4);
197f73abda9Skristaps 	if (n->child)
1980a42f588Sschwarze 		print_mdoc(n->child, indent +
199d1982c71Sschwarze 		    (n->type == ROFFT_BLOCK ? 2 : 4));
200f73abda9Skristaps 	if (n->next)
201f73abda9Skristaps 		print_mdoc(n->next, indent);
202f73abda9Skristaps }
203f73abda9Skristaps 
204f73abda9Skristaps static void
print_man(const struct roff_node * n,int indent)2053a0d07afSschwarze print_man(const struct roff_node *n, int indent)
206f73abda9Skristaps {
207f73abda9Skristaps 	const char	 *p, *t;
208f73abda9Skristaps 	int		  i;
209f73abda9Skristaps 
2100a42f588Sschwarze 	if (n == NULL)
2110a42f588Sschwarze 		return;
2120a42f588Sschwarze 
213f8618d99Sschwarze 	t = p = NULL;
214f8618d99Sschwarze 
215f73abda9Skristaps 	switch (n->type) {
216d1982c71Sschwarze 	case ROFFT_ROOT:
217f73abda9Skristaps 		t = "root";
218f73abda9Skristaps 		break;
219d1982c71Sschwarze 	case ROFFT_ELEM:
220f73abda9Skristaps 		t = "elem";
221f73abda9Skristaps 		break;
222d1982c71Sschwarze 	case ROFFT_TEXT:
223f73abda9Skristaps 		t = "text";
224f73abda9Skristaps 		break;
2254c293873Sschwarze 	case ROFFT_COMMENT:
2264c293873Sschwarze 		t = "comment";
2274c293873Sschwarze 		break;
228d1982c71Sschwarze 	case ROFFT_BLOCK:
229e35cb253Sschwarze 		t = "block";
230e35cb253Sschwarze 		break;
231d1982c71Sschwarze 	case ROFFT_HEAD:
23231a38b0eSschwarze 		t = "head";
233e35cb253Sschwarze 		break;
234d1982c71Sschwarze 	case ROFFT_BODY:
23531a38b0eSschwarze 		t = "body";
236e35cb253Sschwarze 		break;
237d1982c71Sschwarze 	case ROFFT_TBL:
23825fe02efSschwarze 		break;
239d1982c71Sschwarze 	case ROFFT_EQN:
24025fe02efSschwarze 		t = "eqn";
2418d973ab1Sschwarze 		break;
242f73abda9Skristaps 	default:
243f73abda9Skristaps 		abort();
244f73abda9Skristaps 	}
245f73abda9Skristaps 
246f73abda9Skristaps 	switch (n->type) {
247d1982c71Sschwarze 	case ROFFT_TEXT:
2484c293873Sschwarze 	case ROFFT_COMMENT:
249f73abda9Skristaps 		p = n->string;
250f73abda9Skristaps 		break;
251d1982c71Sschwarze 	case ROFFT_ELEM:
252d1982c71Sschwarze 	case ROFFT_BLOCK:
253d1982c71Sschwarze 	case ROFFT_HEAD:
254d1982c71Sschwarze 	case ROFFT_BODY:
25514a309e3Sschwarze 		p = roff_name[n->tok];
256f73abda9Skristaps 		break;
257d1982c71Sschwarze 	case ROFFT_ROOT:
258f73abda9Skristaps 		p = "root";
259f73abda9Skristaps 		break;
260d1982c71Sschwarze 	case ROFFT_TBL:
26125fe02efSschwarze 		break;
262d1982c71Sschwarze 	case ROFFT_EQN:
26325fe02efSschwarze 		p = "EQ";
2648d973ab1Sschwarze 		break;
265f73abda9Skristaps 	default:
266f73abda9Skristaps 		abort();
267f73abda9Skristaps 	}
268f73abda9Skristaps 
2692791bd1cSschwarze 	if (n->span) {
270f8618d99Sschwarze 		assert(NULL == p && NULL == t);
2712791bd1cSschwarze 		print_span(n->span, indent);
2722791bd1cSschwarze 	} else {
273f73abda9Skristaps 		for (i = 0; i < indent; i++)
2740a42f588Sschwarze 			putchar(' ');
275074125cbSschwarze 		printf("%s (%s)", p, t);
276c220f9cfSschwarze 		print_attr(n);
277c220f9cfSschwarze 	}
278c220f9cfSschwarze 	if (n->eqn)
279c220f9cfSschwarze 		print_box(n->eqn->first, indent + 4);
280c220f9cfSschwarze 	if (n->child)
281c220f9cfSschwarze 		print_man(n->child, indent +
282c220f9cfSschwarze 		    (n->type == ROFFT_BLOCK ? 2 : 4));
283c220f9cfSschwarze 	if (n->next)
284c220f9cfSschwarze 		print_man(n->next, indent);
285c220f9cfSschwarze }
286c220f9cfSschwarze 
287c220f9cfSschwarze static void
print_attr(const struct roff_node * n)288c220f9cfSschwarze print_attr(const struct roff_node *n)
289c220f9cfSschwarze {
290c220f9cfSschwarze 	putchar(' ');
291c220f9cfSschwarze 	if (n->flags & NODE_DELIMO)
292c220f9cfSschwarze 		putchar('(');
293b6438c31Sschwarze 	if (n->flags & NODE_LINE)
294074125cbSschwarze 		putchar('*');
29531a38b0eSschwarze 	printf("%d:%d", n->line, n->pos + 1);
296b6438c31Sschwarze 	if (n->flags & NODE_DELIMC)
297252b7e4aSschwarze 		putchar(')');
298b6438c31Sschwarze 	if (n->flags & NODE_EOS)
29931a38b0eSschwarze 		putchar('.');
3000ac7e6ecSschwarze 	if (n->flags & NODE_ID) {
3010ac7e6ecSschwarze 		printf(" ID");
302c220f9cfSschwarze 		if (n->flags & NODE_HREF)
303c220f9cfSschwarze 			printf("=HREF");
304c220f9cfSschwarze 	} else if (n->flags & NODE_HREF)
305c220f9cfSschwarze 		printf(" HREF");
306c220f9cfSschwarze 	else if (n->tag != NULL)
307c220f9cfSschwarze 		printf(" STRAYTAG");
308c220f9cfSschwarze 	if (n->tag != NULL)
309c220f9cfSschwarze 		printf("=%s", n->tag);
310c220f9cfSschwarze 	if (n->flags & NODE_BROKEN)
311c220f9cfSschwarze 		printf(" BROKEN");
312b6438c31Sschwarze 	if (n->flags & NODE_NOFILL)
313b6438c31Sschwarze 		printf(" NOFILL");
314c220f9cfSschwarze 	if (n->flags & NODE_NOSRC)
315c220f9cfSschwarze 		printf(" NOSRC");
316c220f9cfSschwarze 	if (n->flags & NODE_NOPRT)
317c220f9cfSschwarze 		printf(" NOPRT");
31831a38b0eSschwarze 	putchar('\n');
3192791bd1cSschwarze }
3202791bd1cSschwarze 
3212791bd1cSschwarze static void
print_box(const struct eqn_box * ep,int indent)322f8618d99Sschwarze print_box(const struct eqn_box *ep, int indent)
323f8618d99Sschwarze {
324f8618d99Sschwarze 	int		 i;
325f8618d99Sschwarze 	const char	*t;
326f8618d99Sschwarze 
327f8617809Sschwarze 	static const char *posnames[] = {
328f8617809Sschwarze 	    NULL, "sup", "subsup", "sub",
329f8617809Sschwarze 	    "to", "from", "fromto",
330f8617809Sschwarze 	    "over", "sqrt", NULL };
331f8617809Sschwarze 
332f8618d99Sschwarze 	if (NULL == ep)
333f8618d99Sschwarze 		return;
334f8618d99Sschwarze 	for (i = 0; i < indent; i++)
3350a42f588Sschwarze 		putchar(' ');
336f8618d99Sschwarze 
337f8618d99Sschwarze 	t = NULL;
338f8618d99Sschwarze 	switch (ep->type) {
33949aff9f8Sschwarze 	case EQN_LIST:
340f8618d99Sschwarze 		t = "eqn-list";
341f8618d99Sschwarze 		break;
34249aff9f8Sschwarze 	case EQN_SUBEXPR:
343f8618d99Sschwarze 		t = "eqn-expr";
344f8618d99Sschwarze 		break;
34549aff9f8Sschwarze 	case EQN_TEXT:
346f8618d99Sschwarze 		t = "eqn-text";
347f8618d99Sschwarze 		break;
348f8617809Sschwarze 	case EQN_PILE:
349f8617809Sschwarze 		t = "eqn-pile";
350f8617809Sschwarze 		break;
35149aff9f8Sschwarze 	case EQN_MATRIX:
352f8618d99Sschwarze 		t = "eqn-matrix";
353f8618d99Sschwarze 		break;
354f8618d99Sschwarze 	}
355f8618d99Sschwarze 
356f8617809Sschwarze 	fputs(t, stdout);
357f8617809Sschwarze 	if (ep->pos)
358f8617809Sschwarze 		printf(" pos=%s", posnames[ep->pos]);
359f8617809Sschwarze 	if (ep->left)
360f8617809Sschwarze 		printf(" left=\"%s\"", ep->left);
361f8617809Sschwarze 	if (ep->right)
362f8617809Sschwarze 		printf(" right=\"%s\"", ep->right);
363f8617809Sschwarze 	if (ep->top)
364f8617809Sschwarze 		printf(" top=\"%s\"", ep->top);
365f8617809Sschwarze 	if (ep->bottom)
366f8617809Sschwarze 		printf(" bottom=\"%s\"", ep->bottom);
367f8617809Sschwarze 	if (ep->text)
368f8617809Sschwarze 		printf(" text=\"%s\"", ep->text);
369f8617809Sschwarze 	if (ep->font)
370f8617809Sschwarze 		printf(" font=%d", ep->font);
371f8617809Sschwarze 	if (ep->size != EQN_DEFSIZE)
372f8617809Sschwarze 		printf(" size=%d", ep->size);
373f8617809Sschwarze 	if (ep->expectargs != UINT_MAX && ep->expectargs != ep->args)
374f8617809Sschwarze 		printf(" badargs=%zu(%zu)", ep->args, ep->expectargs);
375f8617809Sschwarze 	else if (ep->args)
376f8617809Sschwarze 		printf(" args=%zu", ep->args);
377f8617809Sschwarze 	putchar('\n');
378f8618d99Sschwarze 
3790a42f588Sschwarze 	print_box(ep->first, indent + 4);
380f8618d99Sschwarze 	print_box(ep->next, indent);
381f8618d99Sschwarze }
382f8618d99Sschwarze 
383f8618d99Sschwarze static void
print_cellt(enum tbl_cellt pos)384e5c2e3c9Sschwarze print_cellt(enum tbl_cellt pos)
385e5c2e3c9Sschwarze {
386e5c2e3c9Sschwarze 	switch(pos) {
387e5c2e3c9Sschwarze 	case TBL_CELL_LEFT:
388e5c2e3c9Sschwarze 		putchar('L');
389e5c2e3c9Sschwarze 		break;
390e5c2e3c9Sschwarze 	case TBL_CELL_LONG:
391e5c2e3c9Sschwarze 		putchar('a');
392e5c2e3c9Sschwarze 		break;
393e5c2e3c9Sschwarze 	case TBL_CELL_CENTRE:
394e5c2e3c9Sschwarze 		putchar('c');
395e5c2e3c9Sschwarze 		break;
396e5c2e3c9Sschwarze 	case TBL_CELL_RIGHT:
397e5c2e3c9Sschwarze 		putchar('r');
398e5c2e3c9Sschwarze 		break;
399e5c2e3c9Sschwarze 	case TBL_CELL_NUMBER:
400e5c2e3c9Sschwarze 		putchar('n');
401e5c2e3c9Sschwarze 		break;
402e5c2e3c9Sschwarze 	case TBL_CELL_SPAN:
403e5c2e3c9Sschwarze 		putchar('s');
404e5c2e3c9Sschwarze 		break;
405e5c2e3c9Sschwarze 	case TBL_CELL_DOWN:
406e5c2e3c9Sschwarze 		putchar('^');
407e5c2e3c9Sschwarze 		break;
408e5c2e3c9Sschwarze 	case TBL_CELL_HORIZ:
409e5c2e3c9Sschwarze 		putchar('-');
410e5c2e3c9Sschwarze 		break;
411e5c2e3c9Sschwarze 	case TBL_CELL_DHORIZ:
412e5c2e3c9Sschwarze 		putchar('=');
413e5c2e3c9Sschwarze 		break;
414e5c2e3c9Sschwarze 	case TBL_CELL_MAX:
415e5c2e3c9Sschwarze 		putchar('#');
416e5c2e3c9Sschwarze 		break;
417e5c2e3c9Sschwarze 	}
418e5c2e3c9Sschwarze }
419e5c2e3c9Sschwarze 
420e5c2e3c9Sschwarze static void
print_span(const struct tbl_span * sp,int indent)4212791bd1cSschwarze print_span(const struct tbl_span *sp, int indent)
4222791bd1cSschwarze {
4232791bd1cSschwarze 	const struct tbl_dat *dp;
424e5c2e3c9Sschwarze 	const struct tbl_cell *cp;
4252791bd1cSschwarze 	int		 i;
4262791bd1cSschwarze 
427e5c2e3c9Sschwarze 	if (sp->prev == NULL) {
428e5c2e3c9Sschwarze 		for (i = 0; i < indent; i++)
429e5c2e3c9Sschwarze 			putchar(' ');
430e5c2e3c9Sschwarze 		printf("%d", sp->opts->cols);
431e5c2e3c9Sschwarze 		if (sp->opts->opts & TBL_OPT_CENTRE)
432e5c2e3c9Sschwarze 			fputs(" center", stdout);
433e5c2e3c9Sschwarze 		if (sp->opts->opts & TBL_OPT_EXPAND)
434e5c2e3c9Sschwarze 			fputs(" expand", stdout);
435e5c2e3c9Sschwarze 		if (sp->opts->opts & TBL_OPT_ALLBOX)
436e5c2e3c9Sschwarze 			fputs(" allbox", stdout);
437e5c2e3c9Sschwarze 		if (sp->opts->opts & TBL_OPT_BOX)
438e5c2e3c9Sschwarze 			fputs(" box", stdout);
439e5c2e3c9Sschwarze 		if (sp->opts->opts & TBL_OPT_DBOX)
440e5c2e3c9Sschwarze 			fputs(" doublebox", stdout);
441e5c2e3c9Sschwarze 		if (sp->opts->opts & TBL_OPT_NOKEEP)
442e5c2e3c9Sschwarze 			fputs(" nokeep", stdout);
443e5c2e3c9Sschwarze 		if (sp->opts->opts & TBL_OPT_NOSPACE)
444e5c2e3c9Sschwarze 			fputs(" nospaces", stdout);
445e5c2e3c9Sschwarze 		if (sp->opts->opts & TBL_OPT_NOWARN)
446e5c2e3c9Sschwarze 			fputs(" nowarn", stdout);
447e5c2e3c9Sschwarze 		printf(" (tbl options) %d:1\n", sp->line);
448e5c2e3c9Sschwarze 	}
449e5c2e3c9Sschwarze 
4502791bd1cSschwarze 	for (i = 0; i < indent; i++)
4510a42f588Sschwarze 		putchar(' ');
4522791bd1cSschwarze 
4532791bd1cSschwarze 	switch (sp->pos) {
45449aff9f8Sschwarze 	case TBL_SPAN_HORIZ:
4552791bd1cSschwarze 		putchar('-');
456a93944b2Sschwarze 		putchar(' ');
457a93944b2Sschwarze 		break;
45849aff9f8Sschwarze 	case TBL_SPAN_DHORIZ:
4592791bd1cSschwarze 		putchar('=');
460a93944b2Sschwarze 		putchar(' ');
4612791bd1cSschwarze 		break;
462a93944b2Sschwarze 	default:
463e5c2e3c9Sschwarze 		for (cp = sp->layout->first; cp != NULL; cp = cp->next)
464e5c2e3c9Sschwarze 			print_cellt(cp->pos);
465e5c2e3c9Sschwarze 		putchar(' ');
4662791bd1cSschwarze 		for (dp = sp->first; dp; dp = dp->next) {
467e5c2e3c9Sschwarze 			if ((cp = dp->layout) == NULL)
468e5c2e3c9Sschwarze 				putchar('*');
469e5c2e3c9Sschwarze 			else {
470e5c2e3c9Sschwarze 				printf("%d", cp->col);
471e5c2e3c9Sschwarze 				print_cellt(dp->layout->pos);
4727d063611Sschwarze 				switch (cp->font) {
473f8e2f34eSschwarze 				case ESCAPE_FONTROMAN:
474f8e2f34eSschwarze 					break;
4757d063611Sschwarze 				case ESCAPE_FONTBOLD:
476e5c2e3c9Sschwarze 					putchar('b');
4777d063611Sschwarze 					break;
4787d063611Sschwarze 				case ESCAPE_FONTITALIC:
479e5c2e3c9Sschwarze 					putchar('i');
4807d063611Sschwarze 					break;
4817d063611Sschwarze 				case ESCAPE_FONTBI:
4827d063611Sschwarze 					fputs("bi", stdout);
4837d063611Sschwarze 					break;
4847d063611Sschwarze 				case ESCAPE_FONTCR:
4857d063611Sschwarze 					putchar('c');
4867d063611Sschwarze 					break;
4877d063611Sschwarze 				case ESCAPE_FONTCB:
4887d063611Sschwarze 					fputs("cb", stdout);
4897d063611Sschwarze 					break;
4907d063611Sschwarze 				case ESCAPE_FONTCI:
4917d063611Sschwarze 					fputs("ci", stdout);
4927d063611Sschwarze 					break;
4937d063611Sschwarze 				default:
4947d063611Sschwarze 					abort();
4957d063611Sschwarze 				}
496e5c2e3c9Sschwarze 				if (cp->flags & TBL_CELL_TALIGN)
497e5c2e3c9Sschwarze 					putchar('t');
498e5c2e3c9Sschwarze 				if (cp->flags & TBL_CELL_UP)
499e5c2e3c9Sschwarze 					putchar('u');
500e5c2e3c9Sschwarze 				if (cp->flags & TBL_CELL_BALIGN)
501e5c2e3c9Sschwarze 					putchar('d');
502e5c2e3c9Sschwarze 				if (cp->flags & TBL_CELL_WIGN)
503e5c2e3c9Sschwarze 					putchar('z');
504e5c2e3c9Sschwarze 				if (cp->flags & TBL_CELL_EQUAL)
505e5c2e3c9Sschwarze 					putchar('e');
506e5c2e3c9Sschwarze 				if (cp->flags & TBL_CELL_WMAX)
507e5c2e3c9Sschwarze 					putchar('x');
508e5c2e3c9Sschwarze 			}
5092791bd1cSschwarze 			switch (dp->pos) {
51049aff9f8Sschwarze 			case TBL_DATA_NHORIZ:
511*d2672555Sschwarze 				putchar('\\');
512*d2672555Sschwarze 				/* FALLTHROUGH */
513*d2672555Sschwarze 			case TBL_DATA_HORIZ:
514*d2672555Sschwarze 				putchar('_');
515e5c2e3c9Sschwarze 				break;
51649aff9f8Sschwarze 			case TBL_DATA_NDHORIZ:
517*d2672555Sschwarze 				putchar('\\');
518*d2672555Sschwarze 				/* FALLTHROUGH */
519*d2672555Sschwarze 			case TBL_DATA_DHORIZ:
5202791bd1cSschwarze 				putchar('=');
521e5c2e3c9Sschwarze 				break;
5222791bd1cSschwarze 			default:
523e5c2e3c9Sschwarze 				putchar(dp->block ? '{' : '[');
524e5c2e3c9Sschwarze 				if (dp->string != NULL)
525e5c2e3c9Sschwarze 					fputs(dp->string, stdout);
526e5c2e3c9Sschwarze 				putchar(dp->block ? '}' : ']');
5272791bd1cSschwarze 				break;
5282791bd1cSschwarze 			}
529a93944b2Sschwarze 			if (dp->hspans)
530a93944b2Sschwarze 				printf(">%d", dp->hspans);
531a93944b2Sschwarze 			if (dp->vspans)
532a93944b2Sschwarze 				printf("v%d", dp->vspans);
5332791bd1cSschwarze 			putchar(' ');
5342791bd1cSschwarze 		}
535a93944b2Sschwarze 		break;
536a93944b2Sschwarze 	}
537f8618d99Sschwarze 	printf("(tbl) %d:1\n", sp->line);
5382791bd1cSschwarze }
539