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