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