1 /*      $Id: tbl_opts.c,v 1.12 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 <ctype.h>
  22 #include <stdio.h>
  23 #include <stdlib.h>
  24 #include <string.h>
  25 
  26 #include "mandoc.h"
  27 #include "libmandoc.h"
  28 #include "libroff.h"
  29 
  30 enum    tbl_ident {
  31         KEY_CENTRE = 0,
  32         KEY_DELIM,
  33         KEY_EXPAND,
  34         KEY_BOX,
  35         KEY_DBOX,
  36         KEY_ALLBOX,
  37         KEY_TAB,
  38         KEY_LINESIZE,
  39         KEY_NOKEEP,
  40         KEY_DPOINT,
  41         KEY_NOSPACE,
  42         KEY_FRAME,
  43         KEY_DFRAME,
  44         KEY_MAX
  45 };
  46 
  47 struct  tbl_phrase {
  48         const char      *name;
  49         int              key;
  50         enum tbl_ident   ident;
  51 };
  52 
  53 /* Handle Commonwealth/American spellings. */
  54 #define KEY_MAXKEYS      14
  55 
  56 /* Maximum length of key name string. */
  57 #define KEY_MAXNAME      13
  58 
  59 /* Maximum length of key number size. */
  60 #define KEY_MAXNUMSZ     10
  61 
  62 static  const struct tbl_phrase keys[KEY_MAXKEYS] = {
  63         { "center",      TBL_OPT_CENTRE,        KEY_CENTRE},
  64         { "centre",      TBL_OPT_CENTRE,        KEY_CENTRE},
  65         { "delim",       0,                     KEY_DELIM},
  66         { "expand",      TBL_OPT_EXPAND,        KEY_EXPAND},
  67         { "box",         TBL_OPT_BOX,           KEY_BOX},
  68         { "doublebox",   TBL_OPT_DBOX,          KEY_DBOX},
  69         { "allbox",      TBL_OPT_ALLBOX,        KEY_ALLBOX},
  70         { "frame",       TBL_OPT_BOX,           KEY_FRAME},
  71         { "doubleframe", TBL_OPT_DBOX,          KEY_DFRAME},
  72         { "tab",         0,                     KEY_TAB},
  73         { "linesize",    0,                     KEY_LINESIZE},
  74         { "nokeep",      TBL_OPT_NOKEEP,        KEY_NOKEEP},
  75         { "decimalpoint", 0,                    KEY_DPOINT},
  76         { "nospaces",    TBL_OPT_NOSPACE,       KEY_NOSPACE},
  77 };
  78 
  79 static  int              arg(struct tbl_node *, int, 
  80                                 const char *, int *, enum tbl_ident);
  81 static  void             opt(struct tbl_node *, int, 
  82                                 const char *, int *);
  83 
  84 static int
  85 arg(struct tbl_node *tbl, int ln, const char *p, int *pos, enum tbl_ident key)
  86 {
  87         int              i;
  88         char             buf[KEY_MAXNUMSZ];
  89 
  90         while (isspace((unsigned char)p[*pos]))
  91                 (*pos)++;
  92 
  93         /* Arguments always begin with a parenthesis. */
  94 
  95         if ('(' != p[*pos]) {
  96                 mandoc_msg(MANDOCERR_TBL, tbl->parse, 
  97                                 ln, *pos, NULL);
  98                 return(0);
  99         }
 100 
 101         (*pos)++;
 102 
 103         /*
 104          * The arguments can be ANY value, so we can't just stop at the
 105          * next close parenthesis (the argument can be a closed
 106          * parenthesis itself).
 107          */
 108 
 109         switch (key) {
 110         case (KEY_DELIM):
 111                 if ('\0' == p[(*pos)++]) {
 112                         mandoc_msg(MANDOCERR_TBL, tbl->parse,
 113                                         ln, *pos - 1, NULL);
 114                         return(0);
 115                 } 
 116 
 117                 if ('\0' == p[(*pos)++]) {
 118                         mandoc_msg(MANDOCERR_TBL, tbl->parse,
 119                                         ln, *pos - 1, NULL);
 120                         return(0);
 121                 } 
 122                 break;
 123         case (KEY_TAB):
 124                 if ('\0' != (tbl->opts.tab = p[(*pos)++]))
 125                         break;
 126 
 127                 mandoc_msg(MANDOCERR_TBL, tbl->parse,
 128                                 ln, *pos - 1, NULL);
 129                 return(0);
 130         case (KEY_LINESIZE):
 131                 for (i = 0; i < KEY_MAXNUMSZ && p[*pos]; i++, (*pos)++) {
 132                         buf[i] = p[*pos];
 133                         if ( ! isdigit((unsigned char)buf[i]))
 134                                 break;
 135                 }
 136 
 137                 if (i < KEY_MAXNUMSZ) {
 138                         buf[i] = '\0';
 139                         tbl->opts.linesize = atoi(buf);
 140                         break;
 141                 }
 142 
 143                 mandoc_msg(MANDOCERR_TBL, tbl->parse, ln, *pos, NULL);
 144                 return(0);
 145         case (KEY_DPOINT):
 146                 if ('\0' != (tbl->opts.decimal = p[(*pos)++]))
 147                         break;
 148 
 149                 mandoc_msg(MANDOCERR_TBL, tbl->parse, 
 150                                 ln, *pos - 1, NULL);
 151                 return(0);
 152         default:
 153                 abort();
 154                 /* NOTREACHED */
 155         }
 156 
 157         /* End with a close parenthesis. */
 158 
 159         if (')' == p[(*pos)++])
 160                 return(1);
 161 
 162         mandoc_msg(MANDOCERR_TBL, tbl->parse, ln, *pos - 1, NULL);
 163         return(0);
 164 }
 165 
 166 static void
 167 opt(struct tbl_node *tbl, int ln, const char *p, int *pos)
 168 {
 169         int              i, sv;
 170         char             buf[KEY_MAXNAME];
 171 
 172         /*
 173          * Parse individual options from the stream as surrounded by
 174          * this goto.  Each pass through the routine parses out a single
 175          * option and registers it.  Option arguments are processed in
 176          * the arg() function.
 177          */
 178 
 179 again:  /*
 180          * EBNF describing this section:
 181          *
 182          * options      ::= option_list [:space:]* [;][\n]
 183          * option_list  ::= option option_tail
 184          * option_tail  ::= [:space:]+ option_list |
 185          *              ::= epsilon
 186          * option       ::= [:alpha:]+ args
 187          * args         ::= [:space:]* [(] [:alpha:]+ [)]
 188          */
 189 
 190         while (isspace((unsigned char)p[*pos]))
 191                 (*pos)++;
 192 
 193         /* Safe exit point. */
 194 
 195         if (';' == p[*pos])
 196                 return;
 197 
 198         /* Copy up to first non-alpha character. */
 199 
 200         for (sv = *pos, i = 0; i < KEY_MAXNAME; i++, (*pos)++) {
 201                 buf[i] = (char)tolower((unsigned char)p[*pos]);
 202                 if ( ! isalpha((unsigned char)buf[i]))
 203                         break;
 204         }
 205 
 206         /* Exit if buffer is empty (or overrun). */
 207 
 208         if (KEY_MAXNAME == i || 0 == i) {
 209                 mandoc_msg(MANDOCERR_TBL, tbl->parse, ln, *pos, NULL);
 210                 return;
 211         }
 212 
 213         buf[i] = '\0';
 214 
 215         while (isspace((unsigned char)p[*pos]))
 216                 (*pos)++;
 217 
 218         /* 
 219          * Look through all of the available keys to find one that
 220          * matches the input.  FIXME: hashtable this.
 221          */
 222 
 223         for (i = 0; i < KEY_MAXKEYS; i++) {
 224                 if (strcmp(buf, keys[i].name))
 225                         continue;
 226 
 227                 /*
 228                  * Note: this is more difficult to recover from, as we
 229                  * can be anywhere in the option sequence and it's
 230                  * harder to jump to the next.  Meanwhile, just bail out
 231                  * of the sequence altogether.
 232                  */
 233 
 234                 if (keys[i].key) 
 235                         tbl->opts.opts |= keys[i].key;
 236                 else if ( ! arg(tbl, ln, p, pos, keys[i].ident))
 237                         return;
 238 
 239                 break;
 240         }
 241 
 242         /* 
 243          * Allow us to recover from bad options by continuing to another
 244          * parse sequence.
 245          */
 246 
 247         if (KEY_MAXKEYS == i)
 248                 mandoc_msg(MANDOCERR_TBLOPT, tbl->parse, ln, sv, NULL);
 249 
 250         goto again;
 251         /* NOTREACHED */
 252 }
 253 
 254 int
 255 tbl_option(struct tbl_node *tbl, int ln, const char *p)
 256 {
 257         int              pos;
 258 
 259         /*
 260          * Table options are always on just one line, so automatically
 261          * switch into the next input mode here.
 262          */
 263         tbl->part = TBL_PART_LAYOUT;
 264 
 265         pos = 0;
 266         opt(tbl, ln, p, &pos);
 267 
 268         /* Always succeed. */
 269         return(1);
 270 }