1 /*      $Id: tbl_layout.c,v 1.22 2011/09/18 14:14:15 schwarze Exp $ */
   2 /*
   3  * Copyright (c) 2009, 2010, 2011 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 #ifdef HAVE_CONFIG_H
  18 #include "config.h"
  19 #endif
  20 
  21 #include <assert.h>
  22 #include <ctype.h>
  23 #include <stdlib.h>
  24 #include <string.h>
  25 #include <time.h>
  26 
  27 #include "mandoc.h"
  28 #include "libmandoc.h"
  29 #include "libroff.h"
  30 
  31 struct  tbl_phrase {
  32         char             name;
  33         enum tbl_cellt   key;
  34 };
  35 
  36 /*
  37  * FIXME: we can make this parse a lot nicer by, when an error is
  38  * encountered in a layout key, bailing to the next key (i.e. to the
  39  * next whitespace then continuing).
  40  */
  41 
  42 #define KEYS_MAX         11
  43 
  44 static  const struct tbl_phrase keys[KEYS_MAX] = {
  45         { 'c',           TBL_CELL_CENTRE },
  46         { 'r',           TBL_CELL_RIGHT },
  47         { 'l',           TBL_CELL_LEFT },
  48         { 'n',           TBL_CELL_NUMBER },
  49         { 's',           TBL_CELL_SPAN },
  50         { 'a',           TBL_CELL_LONG },
  51         { '^',           TBL_CELL_DOWN },
  52         { '-',           TBL_CELL_HORIZ },
  53         { '_',           TBL_CELL_HORIZ },
  54         { '=',           TBL_CELL_DHORIZ },
  55         { '|',           TBL_CELL_VERT }
  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 *, 
  64                                 struct tbl_row *, enum tbl_cellt);
  65 static  void             head_adjust(const struct tbl_cell *, 
  66                                 struct tbl_head *);
  67 
  68 static int
  69 mods(struct tbl_node *tbl, struct tbl_cell *cp, 
  70                 int ln, const char *p, int *pos)
  71 {
  72         char             buf[5];
  73         int              i;
  74 
  75         /* Not all types accept modifiers. */
  76 
  77         switch (cp->pos) {
  78         case (TBL_CELL_DOWN):
  79                 /* FALLTHROUGH */
  80         case (TBL_CELL_HORIZ):
  81                 /* FALLTHROUGH */
  82         case (TBL_CELL_DHORIZ):
  83                 /* FALLTHROUGH */
  84         case (TBL_CELL_VERT):
  85                 /* FALLTHROUGH */
  86         case (TBL_CELL_DVERT):
  87                 return(1);
  88         default:
  89                 break;
  90         }
  91 
  92 mod:
  93         /* 
  94          * XXX: since, at least for now, modifiers are non-conflicting
  95          * (are separable by value, regardless of position), we let
  96          * modifiers come in any order.  The existing tbl doesn't let
  97          * this happen.
  98          */
  99         switch (p[*pos]) {
 100         case ('\0'):
 101                 /* FALLTHROUGH */
 102         case (' '):
 103                 /* FALLTHROUGH */
 104         case ('\t'):
 105                 /* FALLTHROUGH */
 106         case (','):
 107                 /* FALLTHROUGH */
 108         case ('.'):
 109                 return(1);
 110         default:
 111                 break;
 112         }
 113 
 114         /* Throw away parenthesised expression. */
 115 
 116         if ('(' == p[*pos]) {
 117                 (*pos)++;
 118                 while (p[*pos] && ')' != p[*pos])
 119                         (*pos)++;
 120                 if (')' == p[*pos]) {
 121                         (*pos)++;
 122                         goto mod;
 123                 }
 124                 mandoc_msg(MANDOCERR_TBLLAYOUT, 
 125                                 tbl->parse, ln, *pos, NULL);
 126                 return(0);
 127         }
 128 
 129         /* Parse numerical spacing from modifier string. */
 130 
 131         if (isdigit((unsigned char)p[*pos])) {
 132                 for (i = 0; i < 4; i++) {
 133                         if ( ! isdigit((unsigned char)p[*pos + i]))
 134                                 break;
 135                         buf[i] = p[*pos + i];
 136                 }
 137                 buf[i] = '\0';
 138 
 139                 /* No greater than 4 digits. */
 140 
 141                 if (4 == i) {
 142                         mandoc_msg(MANDOCERR_TBLLAYOUT, tbl->parse,
 143                                         ln, *pos, NULL);
 144                         return(0);
 145                 }
 146 
 147                 *pos += i;
 148                 cp->spacing = (size_t)atoi(buf);
 149 
 150                 goto mod;
 151                 /* NOTREACHED */
 152         } 
 153 
 154         /* TODO: GNU has many more extensions. */
 155 
 156         switch (tolower((unsigned char)p[(*pos)++])) {
 157         case ('z'):
 158                 cp->flags |= TBL_CELL_WIGN;
 159                 goto mod;
 160         case ('u'):
 161                 cp->flags |= TBL_CELL_UP;
 162                 goto mod;
 163         case ('e'):
 164                 cp->flags |= TBL_CELL_EQUAL;
 165                 goto mod;
 166         case ('t'):
 167                 cp->flags |= TBL_CELL_TALIGN;
 168                 goto mod;
 169         case ('d'):
 170                 cp->flags |= TBL_CELL_BALIGN;
 171                 goto mod;
 172         case ('w'):  /* XXX for now, ignore minimal column width */
 173                 goto mod;
 174         case ('f'):
 175                 break;
 176         case ('r'):
 177                 /* FALLTHROUGH */
 178         case ('b'):
 179                 /* FALLTHROUGH */
 180         case ('i'):
 181                 (*pos)--;
 182                 break;
 183         default:
 184                 mandoc_msg(MANDOCERR_TBLLAYOUT, tbl->parse,
 185                                 ln, *pos - 1, NULL);
 186                 return(0);
 187         }
 188 
 189         switch (tolower((unsigned char)p[(*pos)++])) {
 190         case ('3'):
 191                 /* FALLTHROUGH */
 192         case ('b'):
 193                 cp->flags |= TBL_CELL_BOLD;
 194                 goto mod;
 195         case ('2'):
 196                 /* FALLTHROUGH */
 197         case ('i'):
 198                 cp->flags |= TBL_CELL_ITALIC;
 199                 goto mod;
 200         case ('1'):
 201                 /* FALLTHROUGH */
 202         case ('r'):
 203                 goto mod;
 204         default:
 205                 break;
 206         }
 207 
 208         mandoc_msg(MANDOCERR_TBLLAYOUT, tbl->parse,
 209                         ln, *pos - 1, NULL);
 210         return(0);
 211 }
 212 
 213 static int
 214 cell(struct tbl_node *tbl, struct tbl_row *rp, 
 215                 int ln, const char *p, int *pos)
 216 {
 217         int              i;
 218         enum tbl_cellt   c;
 219 
 220         /* Parse the column position (`r', `R', `|', ...). */
 221 
 222         for (i = 0; i < KEYS_MAX; i++)
 223                 if (tolower((unsigned char)p[*pos]) == keys[i].name)
 224                         break;
 225 
 226         if (KEYS_MAX == i) {
 227                 mandoc_msg(MANDOCERR_TBLLAYOUT, tbl->parse, 
 228                                 ln, *pos, NULL);
 229                 return(0);
 230         }
 231 
 232         c = keys[i].key;
 233 
 234         /*
 235          * If a span cell is found first, raise a warning and abort the
 236          * parse.  If a span cell is found and the last layout element
 237          * isn't a "normal" layout, bail.
 238          *
 239          * FIXME: recover from this somehow?
 240          */
 241 
 242         if (TBL_CELL_SPAN == c) {
 243                 if (NULL == rp->first) {
 244                         mandoc_msg(MANDOCERR_TBLLAYOUT, tbl->parse,
 245                                         ln, *pos, NULL);
 246                         return(0);
 247                 } else if (rp->last)
 248                         switch (rp->last->pos) {
 249                         case (TBL_CELL_VERT):
 250                         case (TBL_CELL_DVERT):
 251                         case (TBL_CELL_HORIZ):
 252                         case (TBL_CELL_DHORIZ):
 253                                 mandoc_msg(MANDOCERR_TBLLAYOUT, tbl->parse,
 254                                                 ln, *pos, NULL);
 255                                 return(0);
 256                         default:
 257                                 break;
 258                         }
 259         }
 260 
 261         /*
 262          * If a vertical spanner is found, we may not be in the first
 263          * row.
 264          */
 265 
 266         if (TBL_CELL_DOWN == c && rp == tbl->first_row) {
 267                 mandoc_msg(MANDOCERR_TBLLAYOUT, tbl->parse, ln, *pos, NULL);
 268                 return(0);
 269         }
 270 
 271         (*pos)++;
 272 
 273         /* Extra check for the double-vertical. */
 274 
 275         if (TBL_CELL_VERT == c && '|' == p[*pos]) {
 276                 (*pos)++;
 277                 c = TBL_CELL_DVERT;
 278         } 
 279         
 280         /* Disallow adjacent spacers. */
 281 
 282         if (rp->last && (TBL_CELL_VERT == c || TBL_CELL_DVERT == c) &&
 283                         (TBL_CELL_VERT == rp->last->pos || 
 284                          TBL_CELL_DVERT == rp->last->pos)) {
 285                 mandoc_msg(MANDOCERR_TBLLAYOUT, tbl->parse, ln, *pos - 1, NULL);
 286                 return(0);
 287         }
 288 
 289         /* Allocate cell then parse its modifiers. */
 290 
 291         return(mods(tbl, cell_alloc(tbl, rp, c), ln, p, pos));
 292 }
 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                 tbl->last_row = rp;
 314         } else
 315                 tbl->last_row = tbl->first_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, tbl->parse, 
 327                                         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 {
 362         struct tbl_cell *p, *pp;
 363         struct tbl_head *h, *hp;
 364 
 365         p = mandoc_calloc(1, sizeof(struct tbl_cell));
 366 
 367         if (NULL != (pp = rp->last)) {
 368                 rp->last->next = p;
 369                 rp->last = p;
 370         } else
 371                 rp->last = rp->first = p;
 372 
 373         p->pos = pos;
 374 
 375         /*
 376          * This is a little bit complicated.  Here we determine the
 377          * header the corresponds to a cell.  We add headers dynamically
 378          * when need be or re-use them, otherwise.  As an example, given
 379          * the following:
 380          *
 381          *      1  c || l 
 382          *      2  | c | l
 383          *      3  l l
 384          *      3  || c | l |.
 385          *
 386          * We first add the new headers (as there are none) in (1); then
 387          * in (2) we insert the first spanner (as it doesn't match up
 388          * with the header); then we re-use the prior data headers,
 389          * skipping over the spanners; then we re-use everything and add
 390          * a last spanner.  Note that VERT headers are made into DVERT
 391          * ones.
 392          */
 393 
 394         h = pp ? pp->head->next : tbl->first_head;
 395 
 396         if (h) {
 397                 /* Re-use data header. */
 398                 if (TBL_HEAD_DATA == h->pos && 
 399                                 (TBL_CELL_VERT != p->pos &&
 400                                  TBL_CELL_DVERT != p->pos)) {
 401                         p->head = h;
 402                         return(p);
 403                 }
 404 
 405                 /* Re-use spanner header. */
 406                 if (TBL_HEAD_DATA != h->pos && 
 407                                 (TBL_CELL_VERT == p->pos ||
 408                                  TBL_CELL_DVERT == p->pos)) {
 409                         head_adjust(p, h);
 410                         p->head = h;
 411                         return(p);
 412                 }
 413 
 414                 /* Right-shift headers with a new spanner. */
 415                 if (TBL_HEAD_DATA == h->pos && 
 416                                 (TBL_CELL_VERT == p->pos ||
 417                                  TBL_CELL_DVERT == p->pos)) {
 418                         hp = mandoc_calloc(1, sizeof(struct tbl_head));
 419                         hp->ident = tbl->opts.cols++;
 420                         hp->prev = h->prev;
 421                         if (h->prev)
 422                                 h->prev->next = hp;
 423                         if (h == tbl->first_head)
 424                                 tbl->first_head = hp;
 425                         h->prev = hp;
 426                         hp->next = h;
 427                         head_adjust(p, hp);
 428                         p->head = hp;
 429                         return(p);
 430                 }
 431 
 432                 if (NULL != (h = h->next)) {
 433                         head_adjust(p, h);
 434                         p->head = h;
 435                         return(p);
 436                 }
 437 
 438                 /* Fall through to default case... */
 439         }
 440 
 441         hp = mandoc_calloc(1, sizeof(struct tbl_head));
 442         hp->ident = tbl->opts.cols++;
 443 
 444         if (tbl->last_head) {
 445                 hp->prev = tbl->last_head;
 446                 tbl->last_head->next = hp;
 447                 tbl->last_head = hp;
 448         } else
 449                 tbl->last_head = tbl->first_head = hp;
 450 
 451         head_adjust(p, hp);
 452         p->head = hp;
 453         return(p);
 454 }
 455 
 456 static void
 457 head_adjust(const struct tbl_cell *cellp, struct tbl_head *head)
 458 {
 459         if (TBL_CELL_VERT != cellp->pos &&
 460                         TBL_CELL_DVERT != cellp->pos) {
 461                 head->pos = TBL_HEAD_DATA;
 462                 return;
 463         }
 464 
 465         if (TBL_CELL_VERT == cellp->pos)
 466                 if (TBL_HEAD_DVERT != head->pos)
 467                         head->pos = TBL_HEAD_VERT;
 468 
 469         if (TBL_CELL_DVERT == cellp->pos)
 470                 head->pos = TBL_HEAD_DVERT;
 471 }
 472