Annotation of src/usr.bin/mandoc/tbl_layout.c, Revision 1.14
1.14 ! schwarze 1: /* $Id: tbl_layout.c,v 1.13 2014/03/28 23:25:54 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;
199: }
200:
1.8 schwarze 201: mandoc_msg(MANDOCERR_TBLLAYOUT, tbl->parse,
1.14 ! schwarze 202: ln, *pos - 1, NULL);
1.4 schwarze 203: return(0);
1.1 schwarze 204: }
205:
206: static int
1.14 ! schwarze 207: cell(struct tbl_node *tbl, struct tbl_row *rp,
1.4 schwarze 208: int ln, const char *p, int *pos)
1.1 schwarze 209: {
1.11 schwarze 210: int vert, i;
1.1 schwarze 211: enum tbl_cellt c;
212:
1.11 schwarze 213: /* Handle vertical lines. */
214:
215: for (vert = 0; '|' == p[*pos]; ++*pos)
216: vert++;
217: while (' ' == p[*pos])
218: (*pos)++;
1.13 schwarze 219:
220: /* Handle trailing vertical lines */
221:
222: if ('.' == p[*pos] || '\0' == p[*pos]) {
223: rp->vert = vert;
224: return(1);
225: }
1.11 schwarze 226:
227: /* Parse the column position (`c', `l', `r', ...). */
1.1 schwarze 228:
1.4 schwarze 229: for (i = 0; i < KEYS_MAX; i++)
1.6 schwarze 230: if (tolower((unsigned char)p[*pos]) == keys[i].name)
1.4 schwarze 231: break;
232:
233: if (KEYS_MAX == i) {
1.14 ! schwarze 234: mandoc_msg(MANDOCERR_TBLLAYOUT, tbl->parse,
! 235: ln, *pos, NULL);
1.4 schwarze 236: return(0);
1.1 schwarze 237: }
238:
1.6 schwarze 239: c = keys[i].key;
240:
241: /*
242: * If a span cell is found first, raise a warning and abort the
1.7 schwarze 243: * parse. If a span cell is found and the last layout element
244: * isn't a "normal" layout, bail.
245: *
246: * FIXME: recover from this somehow?
1.6 schwarze 247: */
248:
1.7 schwarze 249: if (TBL_CELL_SPAN == c) {
250: if (NULL == rp->first) {
1.8 schwarze 251: mandoc_msg(MANDOCERR_TBLLAYOUT, tbl->parse,
1.14 ! schwarze 252: ln, *pos, NULL);
1.7 schwarze 253: return(0);
254: } else if (rp->last)
255: switch (rp->last->pos) {
1.14 ! schwarze 256: case TBL_CELL_HORIZ:
! 257: /* FALLTHROUGH */
! 258: case TBL_CELL_DHORIZ:
! 259: mandoc_msg(MANDOCERR_TBLLAYOUT,
! 260: tbl->parse, ln, *pos, NULL);
1.7 schwarze 261: return(0);
262: default:
263: break;
264: }
265: }
266:
267: /*
268: * If a vertical spanner is found, we may not be in the first
269: * row.
270: */
271:
272: if (TBL_CELL_DOWN == c && rp == tbl->first_row) {
1.8 schwarze 273: mandoc_msg(MANDOCERR_TBLLAYOUT, tbl->parse, ln, *pos, NULL);
1.6 schwarze 274: return(0);
275: }
276:
1.4 schwarze 277: (*pos)++;
1.1 schwarze 278:
1.4 schwarze 279: /* Disallow adjacent spacers. */
1.1 schwarze 280:
1.11 schwarze 281: if (vert > 2) {
1.8 schwarze 282: mandoc_msg(MANDOCERR_TBLLAYOUT, tbl->parse, ln, *pos - 1, NULL);
1.4 schwarze 283: return(0);
284: }
1.1 schwarze 285:
286: /* Allocate cell then parse its modifiers. */
287:
1.11 schwarze 288: return(mods(tbl, cell_alloc(tbl, rp, c, vert), ln, p, pos));
1.1 schwarze 289: }
290:
1.4 schwarze 291: static void
292: row(struct tbl_node *tbl, int ln, const char *p, int *pos)
1.1 schwarze 293: {
294: struct tbl_row *rp;
295:
1.4 schwarze 296: row: /*
1.1 schwarze 297: * EBNF describing this section:
298: *
299: * row ::= row_list [:space:]* [.]?[\n]
300: * row_list ::= [:space:]* row_elem row_tail
301: * row_tail ::= [:space:]*[,] row_list |
302: * epsilon
303: * row_elem ::= [\t\ ]*[:alpha:]+
304: */
305:
1.4 schwarze 306: rp = mandoc_calloc(1, sizeof(struct tbl_row));
1.11 schwarze 307: if (tbl->last_row)
1.4 schwarze 308: tbl->last_row->next = rp;
1.11 schwarze 309: else
310: tbl->first_row = rp;
311: tbl->last_row = rp;
1.4 schwarze 312:
313: cell:
314: while (isspace((unsigned char)p[*pos]))
315: (*pos)++;
316:
317: /* Safely exit layout context. */
318:
319: if ('.' == p[*pos]) {
1.1 schwarze 320: tbl->part = TBL_PART_DATA;
1.14 ! schwarze 321: if (NULL == tbl->first_row)
! 322: mandoc_msg(MANDOCERR_TBLNOLAYOUT,
! 323: tbl->parse, ln, *pos, NULL);
1.4 schwarze 324: (*pos)++;
325: return;
1.1 schwarze 326: }
327:
1.4 schwarze 328: /* End (and possibly restart) a row. */
329:
330: if (',' == p[*pos]) {
331: (*pos)++;
332: goto row;
333: } else if ('\0' == p[*pos])
334: return;
335:
336: if ( ! cell(tbl, rp, ln, p, pos))
337: return;
338:
339: goto cell;
340: /* NOTREACHED */
1.1 schwarze 341: }
342:
343: int
1.4 schwarze 344: tbl_layout(struct tbl_node *tbl, int ln, const char *p)
1.1 schwarze 345: {
346: int pos;
347:
348: pos = 0;
1.4 schwarze 349: row(tbl, ln, p, &pos);
350:
351: /* Always succeed. */
352: return(1);
1.1 schwarze 353: }
1.4 schwarze 354:
355: static struct tbl_cell *
1.11 schwarze 356: cell_alloc(struct tbl_node *tbl, struct tbl_row *rp, enum tbl_cellt pos,
357: int vert)
1.4 schwarze 358: {
359: struct tbl_cell *p, *pp;
360: struct tbl_head *h, *hp;
361:
362: p = mandoc_calloc(1, sizeof(struct tbl_cell));
363:
364: if (NULL != (pp = rp->last)) {
1.11 schwarze 365: pp->next = p;
366: h = pp->head->next;
367: } else {
368: rp->first = p;
369: h = tbl->first_head;
370: }
371: rp->last = p;
1.4 schwarze 372:
373: p->pos = pos;
1.11 schwarze 374: p->vert = vert;
1.4 schwarze 375:
1.11 schwarze 376: /* Re-use header. */
1.4 schwarze 377:
378: if (h) {
1.11 schwarze 379: p->head = h;
380: return(p);
1.4 schwarze 381: }
382:
383: hp = mandoc_calloc(1, sizeof(struct tbl_head));
384: hp->ident = tbl->opts.cols++;
1.11 schwarze 385: hp->vert = vert;
1.4 schwarze 386:
387: if (tbl->last_head) {
388: hp->prev = tbl->last_head;
389: tbl->last_head->next = hp;
390: } else
1.11 schwarze 391: tbl->first_head = hp;
392: tbl->last_head = hp;
1.4 schwarze 393:
394: p->head = hp;
395: return(p);
396: }