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