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