xref: /minix3/external/bsd/mdocml/dist/tbl_layout.c (revision 0a6a1f1d05b60e214de2f05a7310ddd1f0e590e7)
1*0a6a1f1dSLionel Sambuc /*	Id: tbl_layout.c,v 1.23 2012/05/27 17:54:54 schwarze Exp  */
2d65f6f70SBen Gras /*
392395e9cSLionel Sambuc  * Copyright (c) 2009, 2010, 2011 Kristaps Dzonsons <kristaps@bsd.lv>
4*0a6a1f1dSLionel Sambuc  * Copyright (c) 2012 Ingo Schwarze <schwarze@openbsd.org>
5d65f6f70SBen Gras  *
6d65f6f70SBen Gras  * Permission to use, copy, modify, and distribute this software for any
7d65f6f70SBen Gras  * purpose with or without fee is hereby granted, provided that the above
8d65f6f70SBen Gras  * copyright notice and this permission notice appear in all copies.
9d65f6f70SBen Gras  *
10d65f6f70SBen Gras  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11d65f6f70SBen Gras  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12d65f6f70SBen Gras  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13d65f6f70SBen Gras  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14d65f6f70SBen Gras  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15d65f6f70SBen Gras  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16d65f6f70SBen Gras  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17d65f6f70SBen Gras  */
1892395e9cSLionel Sambuc #ifdef HAVE_CONFIG_H
1992395e9cSLionel Sambuc #include "config.h"
2092395e9cSLionel Sambuc #endif
2192395e9cSLionel Sambuc 
22d65f6f70SBen Gras #include <assert.h>
23d65f6f70SBen Gras #include <ctype.h>
24d65f6f70SBen Gras #include <stdlib.h>
25d65f6f70SBen Gras #include <string.h>
26d65f6f70SBen Gras #include <time.h>
27d65f6f70SBen Gras 
28d65f6f70SBen Gras #include "mandoc.h"
29d65f6f70SBen Gras #include "libmandoc.h"
30d65f6f70SBen Gras #include "libroff.h"
31d65f6f70SBen Gras 
32d65f6f70SBen Gras struct	tbl_phrase {
33d65f6f70SBen Gras 	char		 name;
34d65f6f70SBen Gras 	enum tbl_cellt	 key;
35d65f6f70SBen Gras };
36d65f6f70SBen Gras 
37d65f6f70SBen Gras /*
38d65f6f70SBen Gras  * FIXME: we can make this parse a lot nicer by, when an error is
39d65f6f70SBen Gras  * encountered in a layout key, bailing to the next key (i.e. to the
40d65f6f70SBen Gras  * next whitespace then continuing).
41d65f6f70SBen Gras  */
42d65f6f70SBen Gras 
43d65f6f70SBen Gras #define	KEYS_MAX	 11
44d65f6f70SBen Gras 
45d65f6f70SBen Gras static	const struct tbl_phrase keys[KEYS_MAX] = {
46d65f6f70SBen Gras 	{ 'c',		 TBL_CELL_CENTRE },
47d65f6f70SBen Gras 	{ 'r',		 TBL_CELL_RIGHT },
48d65f6f70SBen Gras 	{ 'l',		 TBL_CELL_LEFT },
49d65f6f70SBen Gras 	{ 'n',		 TBL_CELL_NUMBER },
50d65f6f70SBen Gras 	{ 's',		 TBL_CELL_SPAN },
51d65f6f70SBen Gras 	{ 'a',		 TBL_CELL_LONG },
52d65f6f70SBen Gras 	{ '^',		 TBL_CELL_DOWN },
53d65f6f70SBen Gras 	{ '-',		 TBL_CELL_HORIZ },
54d65f6f70SBen Gras 	{ '_',		 TBL_CELL_HORIZ },
55*0a6a1f1dSLionel Sambuc 	{ '=',		 TBL_CELL_DHORIZ }
56d65f6f70SBen Gras };
57d65f6f70SBen Gras 
58d65f6f70SBen Gras static	int		 mods(struct tbl_node *, struct tbl_cell *,
59d65f6f70SBen Gras 				int, const char *, int *);
60d65f6f70SBen Gras static	int		 cell(struct tbl_node *, struct tbl_row *,
61d65f6f70SBen Gras 				int, const char *, int *);
62d65f6f70SBen Gras static	void		 row(struct tbl_node *, int, const char *, int *);
63*0a6a1f1dSLionel Sambuc static	struct tbl_cell *cell_alloc(struct tbl_node *, struct tbl_row *,
64*0a6a1f1dSLionel Sambuc 				enum tbl_cellt, int vert);
65d65f6f70SBen Gras 
66d65f6f70SBen Gras static int
mods(struct tbl_node * tbl,struct tbl_cell * cp,int ln,const char * p,int * pos)67d65f6f70SBen Gras mods(struct tbl_node *tbl, struct tbl_cell *cp,
68d65f6f70SBen Gras 		int ln, const char *p, int *pos)
69d65f6f70SBen Gras {
70d65f6f70SBen Gras 	char		 buf[5];
71d65f6f70SBen Gras 	int		 i;
72d65f6f70SBen Gras 
7392395e9cSLionel Sambuc 	/* Not all types accept modifiers. */
7492395e9cSLionel Sambuc 
7592395e9cSLionel Sambuc 	switch (cp->pos) {
7692395e9cSLionel Sambuc 	case (TBL_CELL_DOWN):
7792395e9cSLionel Sambuc 		/* FALLTHROUGH */
7892395e9cSLionel Sambuc 	case (TBL_CELL_HORIZ):
7992395e9cSLionel Sambuc 		/* FALLTHROUGH */
8092395e9cSLionel Sambuc 	case (TBL_CELL_DHORIZ):
8192395e9cSLionel Sambuc 		return(1);
8292395e9cSLionel Sambuc 	default:
8392395e9cSLionel Sambuc 		break;
8492395e9cSLionel Sambuc 	}
8592395e9cSLionel Sambuc 
86d65f6f70SBen Gras mod:
87d65f6f70SBen Gras 	/*
88d65f6f70SBen Gras 	 * XXX: since, at least for now, modifiers are non-conflicting
89d65f6f70SBen Gras 	 * (are separable by value, regardless of position), we let
90d65f6f70SBen Gras 	 * modifiers come in any order.  The existing tbl doesn't let
91d65f6f70SBen Gras 	 * this happen.
92d65f6f70SBen Gras 	 */
93d65f6f70SBen Gras 	switch (p[*pos]) {
94d65f6f70SBen Gras 	case ('\0'):
95d65f6f70SBen Gras 		/* FALLTHROUGH */
96d65f6f70SBen Gras 	case (' '):
97d65f6f70SBen Gras 		/* FALLTHROUGH */
98d65f6f70SBen Gras 	case ('\t'):
99d65f6f70SBen Gras 		/* FALLTHROUGH */
100d65f6f70SBen Gras 	case (','):
101d65f6f70SBen Gras 		/* FALLTHROUGH */
102d65f6f70SBen Gras 	case ('.'):
103d65f6f70SBen Gras 		return(1);
104d65f6f70SBen Gras 	default:
105d65f6f70SBen Gras 		break;
106d65f6f70SBen Gras 	}
107d65f6f70SBen Gras 
108d65f6f70SBen Gras 	/* Throw away parenthesised expression. */
109d65f6f70SBen Gras 
110d65f6f70SBen Gras 	if ('(' == p[*pos]) {
111d65f6f70SBen Gras 		(*pos)++;
112d65f6f70SBen Gras 		while (p[*pos] && ')' != p[*pos])
113d65f6f70SBen Gras 			(*pos)++;
114d65f6f70SBen Gras 		if (')' == p[*pos]) {
115d65f6f70SBen Gras 			(*pos)++;
116d65f6f70SBen Gras 			goto mod;
117d65f6f70SBen Gras 		}
11892395e9cSLionel Sambuc 		mandoc_msg(MANDOCERR_TBLLAYOUT,
11992395e9cSLionel Sambuc 				tbl->parse, ln, *pos, NULL);
120d65f6f70SBen Gras 		return(0);
121d65f6f70SBen Gras 	}
122d65f6f70SBen Gras 
123d65f6f70SBen Gras 	/* Parse numerical spacing from modifier string. */
124d65f6f70SBen Gras 
125d65f6f70SBen Gras 	if (isdigit((unsigned char)p[*pos])) {
126d65f6f70SBen Gras 		for (i = 0; i < 4; i++) {
127d65f6f70SBen Gras 			if ( ! isdigit((unsigned char)p[*pos + i]))
128d65f6f70SBen Gras 				break;
129d65f6f70SBen Gras 			buf[i] = p[*pos + i];
130d65f6f70SBen Gras 		}
131d65f6f70SBen Gras 		buf[i] = '\0';
132d65f6f70SBen Gras 
133d65f6f70SBen Gras 		/* No greater than 4 digits. */
134d65f6f70SBen Gras 
135d65f6f70SBen Gras 		if (4 == i) {
13692395e9cSLionel Sambuc 			mandoc_msg(MANDOCERR_TBLLAYOUT, tbl->parse,
13792395e9cSLionel Sambuc 					ln, *pos, NULL);
138d65f6f70SBen Gras 			return(0);
139d65f6f70SBen Gras 		}
140d65f6f70SBen Gras 
141d65f6f70SBen Gras 		*pos += i;
14292395e9cSLionel Sambuc 		cp->spacing = (size_t)atoi(buf);
143d65f6f70SBen Gras 
144d65f6f70SBen Gras 		goto mod;
145d65f6f70SBen Gras 		/* NOTREACHED */
146d65f6f70SBen Gras 	}
147d65f6f70SBen Gras 
148d65f6f70SBen Gras 	/* TODO: GNU has many more extensions. */
149d65f6f70SBen Gras 
150d65f6f70SBen Gras 	switch (tolower((unsigned char)p[(*pos)++])) {
151d65f6f70SBen Gras 	case ('z'):
152d65f6f70SBen Gras 		cp->flags |= TBL_CELL_WIGN;
153d65f6f70SBen Gras 		goto mod;
154d65f6f70SBen Gras 	case ('u'):
155d65f6f70SBen Gras 		cp->flags |= TBL_CELL_UP;
156d65f6f70SBen Gras 		goto mod;
157d65f6f70SBen Gras 	case ('e'):
158d65f6f70SBen Gras 		cp->flags |= TBL_CELL_EQUAL;
159d65f6f70SBen Gras 		goto mod;
160d65f6f70SBen Gras 	case ('t'):
161d65f6f70SBen Gras 		cp->flags |= TBL_CELL_TALIGN;
162d65f6f70SBen Gras 		goto mod;
163d65f6f70SBen Gras 	case ('d'):
164d65f6f70SBen Gras 		cp->flags |= TBL_CELL_BALIGN;
165d65f6f70SBen Gras 		goto mod;
166d65f6f70SBen Gras 	case ('w'):  /* XXX for now, ignore minimal column width */
167d65f6f70SBen Gras 		goto mod;
168d65f6f70SBen Gras 	case ('f'):
169d65f6f70SBen Gras 		break;
17092395e9cSLionel Sambuc 	case ('r'):
17192395e9cSLionel Sambuc 		/* FALLTHROUGH */
172d65f6f70SBen Gras 	case ('b'):
173d65f6f70SBen Gras 		/* FALLTHROUGH */
174d65f6f70SBen Gras 	case ('i'):
175d65f6f70SBen Gras 		(*pos)--;
176d65f6f70SBen Gras 		break;
177d65f6f70SBen Gras 	default:
17892395e9cSLionel Sambuc 		mandoc_msg(MANDOCERR_TBLLAYOUT, tbl->parse,
17992395e9cSLionel Sambuc 				ln, *pos - 1, NULL);
180d65f6f70SBen Gras 		return(0);
181d65f6f70SBen Gras 	}
182d65f6f70SBen Gras 
183d65f6f70SBen Gras 	switch (tolower((unsigned char)p[(*pos)++])) {
18492395e9cSLionel Sambuc 	case ('3'):
18592395e9cSLionel Sambuc 		/* FALLTHROUGH */
186d65f6f70SBen Gras 	case ('b'):
187d65f6f70SBen Gras 		cp->flags |= TBL_CELL_BOLD;
188d65f6f70SBen Gras 		goto mod;
18992395e9cSLionel Sambuc 	case ('2'):
19092395e9cSLionel Sambuc 		/* FALLTHROUGH */
191d65f6f70SBen Gras 	case ('i'):
192d65f6f70SBen Gras 		cp->flags |= TBL_CELL_ITALIC;
193d65f6f70SBen Gras 		goto mod;
19492395e9cSLionel Sambuc 	case ('1'):
19592395e9cSLionel Sambuc 		/* FALLTHROUGH */
19692395e9cSLionel Sambuc 	case ('r'):
19792395e9cSLionel Sambuc 		goto mod;
198d65f6f70SBen Gras 	default:
199d65f6f70SBen Gras 		break;
200d65f6f70SBen Gras 	}
201d65f6f70SBen Gras 
20292395e9cSLionel Sambuc 	mandoc_msg(MANDOCERR_TBLLAYOUT, tbl->parse,
20392395e9cSLionel Sambuc 			ln, *pos - 1, NULL);
204d65f6f70SBen Gras 	return(0);
205d65f6f70SBen Gras }
206d65f6f70SBen Gras 
207d65f6f70SBen Gras static int
cell(struct tbl_node * tbl,struct tbl_row * rp,int ln,const char * p,int * pos)208d65f6f70SBen Gras cell(struct tbl_node *tbl, struct tbl_row *rp,
209d65f6f70SBen Gras 		int ln, const char *p, int *pos)
210d65f6f70SBen Gras {
211*0a6a1f1dSLionel Sambuc 	int		 vert, i;
212d65f6f70SBen Gras 	enum tbl_cellt	 c;
213d65f6f70SBen Gras 
214*0a6a1f1dSLionel Sambuc 	/* Handle vertical lines. */
215*0a6a1f1dSLionel Sambuc 
216*0a6a1f1dSLionel Sambuc 	for (vert = 0; '|' == p[*pos]; ++*pos)
217*0a6a1f1dSLionel Sambuc 		vert++;
218*0a6a1f1dSLionel Sambuc 	while (' ' == p[*pos])
219*0a6a1f1dSLionel Sambuc 		(*pos)++;
220*0a6a1f1dSLionel Sambuc 
221*0a6a1f1dSLionel Sambuc 	/* Parse the column position (`c', `l', `r', ...). */
222d65f6f70SBen Gras 
223d65f6f70SBen Gras 	for (i = 0; i < KEYS_MAX; i++)
224d65f6f70SBen Gras 		if (tolower((unsigned char)p[*pos]) == keys[i].name)
225d65f6f70SBen Gras 			break;
226d65f6f70SBen Gras 
227d65f6f70SBen Gras 	if (KEYS_MAX == i) {
22892395e9cSLionel Sambuc 		mandoc_msg(MANDOCERR_TBLLAYOUT, tbl->parse,
22992395e9cSLionel Sambuc 				ln, *pos, NULL);
230d65f6f70SBen Gras 		return(0);
231d65f6f70SBen Gras 	}
232d65f6f70SBen Gras 
233d65f6f70SBen Gras 	c = keys[i].key;
234d65f6f70SBen Gras 
235d65f6f70SBen Gras 	/*
236d65f6f70SBen Gras 	 * If a span cell is found first, raise a warning and abort the
23792395e9cSLionel Sambuc 	 * parse.  If a span cell is found and the last layout element
23892395e9cSLionel Sambuc 	 * isn't a "normal" layout, bail.
23992395e9cSLionel Sambuc 	 *
24092395e9cSLionel Sambuc 	 * FIXME: recover from this somehow?
241d65f6f70SBen Gras 	 */
242d65f6f70SBen Gras 
24392395e9cSLionel Sambuc 	if (TBL_CELL_SPAN == c) {
24492395e9cSLionel Sambuc 		if (NULL == rp->first) {
24592395e9cSLionel Sambuc 			mandoc_msg(MANDOCERR_TBLLAYOUT, tbl->parse,
24692395e9cSLionel Sambuc 					ln, *pos, NULL);
24792395e9cSLionel Sambuc 			return(0);
24892395e9cSLionel Sambuc 		} else if (rp->last)
24992395e9cSLionel Sambuc 			switch (rp->last->pos) {
25092395e9cSLionel Sambuc 			case (TBL_CELL_HORIZ):
25192395e9cSLionel Sambuc 			case (TBL_CELL_DHORIZ):
25292395e9cSLionel Sambuc 				mandoc_msg(MANDOCERR_TBLLAYOUT, tbl->parse,
25392395e9cSLionel Sambuc 						ln, *pos, NULL);
25492395e9cSLionel Sambuc 				return(0);
25592395e9cSLionel Sambuc 			default:
25692395e9cSLionel Sambuc 				break;
25792395e9cSLionel Sambuc 			}
25892395e9cSLionel Sambuc 	}
25992395e9cSLionel Sambuc 
26092395e9cSLionel Sambuc 	/*
26192395e9cSLionel Sambuc 	 * If a vertical spanner is found, we may not be in the first
26292395e9cSLionel Sambuc 	 * row.
26392395e9cSLionel Sambuc 	 */
26492395e9cSLionel Sambuc 
26592395e9cSLionel Sambuc 	if (TBL_CELL_DOWN == c && rp == tbl->first_row) {
26692395e9cSLionel Sambuc 		mandoc_msg(MANDOCERR_TBLLAYOUT, tbl->parse, ln, *pos, NULL);
267d65f6f70SBen Gras 		return(0);
268d65f6f70SBen Gras 	}
269d65f6f70SBen Gras 
270d65f6f70SBen Gras 	(*pos)++;
271d65f6f70SBen Gras 
272d65f6f70SBen Gras 	/* Disallow adjacent spacers. */
273d65f6f70SBen Gras 
274*0a6a1f1dSLionel Sambuc 	if (vert > 2) {
27592395e9cSLionel Sambuc 		mandoc_msg(MANDOCERR_TBLLAYOUT, tbl->parse, ln, *pos - 1, NULL);
276d65f6f70SBen Gras 		return(0);
277d65f6f70SBen Gras 	}
278d65f6f70SBen Gras 
279d65f6f70SBen Gras 	/* Allocate cell then parse its modifiers. */
280d65f6f70SBen Gras 
281*0a6a1f1dSLionel Sambuc 	return(mods(tbl, cell_alloc(tbl, rp, c, vert), ln, p, pos));
282d65f6f70SBen Gras }
283d65f6f70SBen Gras 
284d65f6f70SBen Gras 
285d65f6f70SBen Gras static void
row(struct tbl_node * tbl,int ln,const char * p,int * pos)286d65f6f70SBen Gras row(struct tbl_node *tbl, int ln, const char *p, int *pos)
287d65f6f70SBen Gras {
288d65f6f70SBen Gras 	struct tbl_row	*rp;
289d65f6f70SBen Gras 
290d65f6f70SBen Gras row:	/*
291d65f6f70SBen Gras 	 * EBNF describing this section:
292d65f6f70SBen Gras 	 *
293d65f6f70SBen Gras 	 * row		::= row_list [:space:]* [.]?[\n]
294d65f6f70SBen Gras 	 * row_list	::= [:space:]* row_elem row_tail
295d65f6f70SBen Gras 	 * row_tail	::= [:space:]*[,] row_list |
296d65f6f70SBen Gras 	 *                  epsilon
297d65f6f70SBen Gras 	 * row_elem	::= [\t\ ]*[:alpha:]+
298d65f6f70SBen Gras 	 */
299d65f6f70SBen Gras 
300d65f6f70SBen Gras 	rp = mandoc_calloc(1, sizeof(struct tbl_row));
301*0a6a1f1dSLionel Sambuc 	if (tbl->last_row)
302d65f6f70SBen Gras 		tbl->last_row->next = rp;
303*0a6a1f1dSLionel Sambuc 	else
304*0a6a1f1dSLionel Sambuc 		tbl->first_row = rp;
305d65f6f70SBen Gras 	tbl->last_row = rp;
306d65f6f70SBen Gras 
307d65f6f70SBen Gras cell:
308d65f6f70SBen Gras 	while (isspace((unsigned char)p[*pos]))
309d65f6f70SBen Gras 		(*pos)++;
310d65f6f70SBen Gras 
311d65f6f70SBen Gras 	/* Safely exit layout context. */
312d65f6f70SBen Gras 
313d65f6f70SBen Gras 	if ('.' == p[*pos]) {
314d65f6f70SBen Gras 		tbl->part = TBL_PART_DATA;
315d65f6f70SBen Gras 		if (NULL == tbl->first_row)
31692395e9cSLionel Sambuc 			mandoc_msg(MANDOCERR_TBLNOLAYOUT, tbl->parse,
31792395e9cSLionel Sambuc 					ln, *pos, NULL);
318d65f6f70SBen Gras 		(*pos)++;
319d65f6f70SBen Gras 		return;
320d65f6f70SBen Gras 	}
321d65f6f70SBen Gras 
322d65f6f70SBen Gras 	/* End (and possibly restart) a row. */
323d65f6f70SBen Gras 
324d65f6f70SBen Gras 	if (',' == p[*pos]) {
325d65f6f70SBen Gras 		(*pos)++;
326d65f6f70SBen Gras 		goto row;
327d65f6f70SBen Gras 	} else if ('\0' == p[*pos])
328d65f6f70SBen Gras 		return;
329d65f6f70SBen Gras 
330d65f6f70SBen Gras 	if ( ! cell(tbl, rp, ln, p, pos))
331d65f6f70SBen Gras 		return;
332d65f6f70SBen Gras 
333d65f6f70SBen Gras 	goto cell;
334d65f6f70SBen Gras 	/* NOTREACHED */
335d65f6f70SBen Gras }
336d65f6f70SBen Gras 
337d65f6f70SBen Gras int
tbl_layout(struct tbl_node * tbl,int ln,const char * p)338d65f6f70SBen Gras tbl_layout(struct tbl_node *tbl, int ln, const char *p)
339d65f6f70SBen Gras {
340d65f6f70SBen Gras 	int		 pos;
341d65f6f70SBen Gras 
342d65f6f70SBen Gras 	pos = 0;
343d65f6f70SBen Gras 	row(tbl, ln, p, &pos);
344d65f6f70SBen Gras 
345d65f6f70SBen Gras 	/* Always succeed. */
346d65f6f70SBen Gras 	return(1);
347d65f6f70SBen Gras }
348d65f6f70SBen Gras 
349d65f6f70SBen Gras static struct tbl_cell *
cell_alloc(struct tbl_node * tbl,struct tbl_row * rp,enum tbl_cellt pos,int vert)350*0a6a1f1dSLionel Sambuc cell_alloc(struct tbl_node *tbl, struct tbl_row *rp, enum tbl_cellt pos,
351*0a6a1f1dSLionel Sambuc 		int vert)
352d65f6f70SBen Gras {
353d65f6f70SBen Gras 	struct tbl_cell	*p, *pp;
354d65f6f70SBen Gras 	struct tbl_head	*h, *hp;
355d65f6f70SBen Gras 
356d65f6f70SBen Gras 	p = mandoc_calloc(1, sizeof(struct tbl_cell));
357d65f6f70SBen Gras 
358d65f6f70SBen Gras 	if (NULL != (pp = rp->last)) {
359*0a6a1f1dSLionel Sambuc 		pp->next = p;
360*0a6a1f1dSLionel Sambuc 		h = pp->head->next;
361*0a6a1f1dSLionel Sambuc 	} else {
362*0a6a1f1dSLionel Sambuc 		rp->first = p;
363*0a6a1f1dSLionel Sambuc 		h = tbl->first_head;
364*0a6a1f1dSLionel Sambuc 	}
365d65f6f70SBen Gras 	rp->last = p;
366d65f6f70SBen Gras 
367d65f6f70SBen Gras 	p->pos = pos;
368*0a6a1f1dSLionel Sambuc 	p->vert = vert;
369d65f6f70SBen Gras 
370*0a6a1f1dSLionel Sambuc 	/* Re-use header. */
371d65f6f70SBen Gras 
372d65f6f70SBen Gras 	if (h) {
373d65f6f70SBen Gras 		p->head = h;
374d65f6f70SBen Gras 		return(p);
375d65f6f70SBen Gras 	}
376d65f6f70SBen Gras 
377d65f6f70SBen Gras 	hp = mandoc_calloc(1, sizeof(struct tbl_head));
378d65f6f70SBen Gras 	hp->ident = tbl->opts.cols++;
379*0a6a1f1dSLionel Sambuc 	hp->vert = vert;
380d65f6f70SBen Gras 
381d65f6f70SBen Gras 	if (tbl->last_head) {
382d65f6f70SBen Gras 		hp->prev = tbl->last_head;
383d65f6f70SBen Gras 		tbl->last_head->next = hp;
384d65f6f70SBen Gras 	} else
385*0a6a1f1dSLionel Sambuc 		tbl->first_head = hp;
386*0a6a1f1dSLionel Sambuc 	tbl->last_head = hp;
387d65f6f70SBen Gras 
388d65f6f70SBen Gras 	p->head = hp;
389d65f6f70SBen Gras 	return(p);
390d65f6f70SBen Gras }
391