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