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