Merge branches 't/debian', 't/dynamic-lib', 't/lls_check_arg_count' and 't/news'
[lopsub.git] / config_file.l
1 /*
2  * Copyright (C) 2016 Andre Noll <maan@tuebingen.mpg.de>
3  *
4  * Licensed under the LGPL v3, see http://www.gnu.org/licenses/lgpl-3.0.html
5  */
6
7  /* We don't want symbols to clash with those of other flex users. */
8 %option prefix="lls_yy"
9
10  /* Generate a scanner which is safe to use in multithreaded programs. */
11 %option reentrant
12
13  /* To maintain state in a reentrant way. */
14 %option extra-type="struct lls_yy_config_file_extra *"
15
16 %option stack
17 %option never-interactive
18 %option yylineno
19
20 %x SC_ARG
21 %s SC_SCANNING
22
23 IDENTIFIER [a-zA-Z]+[a-zA-Z0-9_-]*
24 EQUALS [[:space:]]*=[[:space:]]*
25 OPTION [a-zA-Z]+[a-zA-Z0-9_-]*
26
27 %{
28         #include <stdlib.h>
29         #include <ctype.h>
30         #include <assert.h>
31         #include <stdbool.h>
32         #include <inttypes.h>
33         #include "lopsub-internal.h"
34         #include "lopsub.h"
35
36         struct lls_yy_config_file_extra {
37                 int argc;
38                 char **argv;
39                 const char *subcmd;
40         };
41
42         static int add_option(yyscan_t yyscanner);
43         static int parse_arg(char **result, yyscan_t yyscanner);
44         static int yywrap(yyscan_t yyscanner) {return 1;}
45 %}
46
47 %%
48
49  /* skip comments and whitespace */
50 ^[[:space:]]*#.*\n ;
51 [[:space:]]|\n+ ;
52
53 <INITIAL,SC_SCANNING>\[[[:space:]]*{IDENTIFIER}[[:space:]]*\][[:space:]]*\n {
54         int i, j;
55         const char *subcmd = yyget_extra(yyscanner)->subcmd;
56
57         assert(yytext[0] == '[');
58         if (!subcmd)
59                 return 0;
60         for (i = 1; i < yyleng; i++)
61                 if (!isspace(yytext[i]))
62                         break;
63         for (j = i; j < yyleng; j++)
64                 if (yytext[j] == ']' || isspace(yytext[j]))
65                         break;
66         assert(j < yyleng);
67         yytext[j] = '\0';
68         if (strcmp(yytext + i, subcmd))
69                 BEGIN(INITIAL);
70         else
71                 BEGIN(SC_SCANNING);
72 }
73
74 <SC_SCANNING>{OPTION}[[:space:]]*\n add_option(yyscanner);
75
76 <SC_SCANNING>{OPTION}({EQUALS}|[[:space:]]+) {
77         int ret = add_option(yyscanner);
78
79         if (ret < 0)
80                 return ret;
81         BEGIN(SC_ARG);
82 }
83
84 <SC_ARG>.*\n {
85         struct lls_yy_config_file_extra *extra = yyget_extra(yyscanner);
86         const char *opt = extra->argv[extra->argc - 1];
87         char *arg, *result;
88         size_t opt_len = strlen(opt), arg_len;
89         int ret = parse_arg(&arg, yyscanner);
90
91         if (ret < 0)
92                 return ret;
93         arg_len = ret;
94         result = malloc(opt_len + arg_len + 2);
95         if (!result)
96                 return -E_LLS_NOMEM;
97         strcpy(result, opt);
98         result[opt_len] = '=';
99         strcpy(result + opt_len + 1, arg);
100         free(arg);
101         result[opt_len + arg_len + 1] = '\0';
102         free(extra->argv[extra->argc - 1]);
103         extra->argv[extra->argc - 1] = result;
104         BEGIN(SC_SCANNING);
105 }
106
107 <INITIAL>.*\n {}
108
109  /* This rule runs iff none of the above patterns matched */
110 . {return -1;}
111 %%
112
113 #include <sys/types.h>
114 #include <sys/stat.h>
115 #include <fcntl.h>
116 #include <stdio.h>
117
118 static int expand_result(yyscan_t yyscanner)
119 {
120         struct lls_yy_config_file_extra *extra = yyget_extra(yyscanner);
121         int nargc = extra->argc + 1;
122         char **nrargv = realloc(extra->argv, (nargc + 1) * sizeof(char *));
123
124         if (!nrargv)
125                 return -E_LLS_NOMEM;
126         extra->argc = nargc;
127         extra->argv = nrargv;
128         extra->argv[extra->argc] = NULL;
129         return 1;
130 }
131
132 static int add_option(yyscan_t yyscanner)
133 {
134         struct lls_yy_config_file_extra *extra = yyget_extra(yyscanner);
135         int ret;
136         unsigned n;
137         int len = yyget_leng(yyscanner);
138         char *narg, *text = yyget_text(yyscanner);
139
140         for (n = 0; n < len; n++)
141                 if (!isalnum(text[n]) && !(text[n] == '-'))
142                         break;
143         assert(n > 0);
144         ret = expand_result(yyscanner);
145         if (ret < 0)
146                 return ret;
147         narg = malloc(n + 2 + 1);
148         if (!narg)
149                 return -E_LLS_NOMEM;
150         narg[0] = narg[1] = '-';
151         memcpy(narg + 2, text, n);
152         narg[n + 2] = '\0';
153         extra->argv[extra->argc - 1] = narg;
154         return 1;
155 }
156
157 static int parse_arg(char **result, yyscan_t yyscanner)
158 {
159         bool backslash = false, quote = false;
160         const char *in;
161         char *out;
162         int ret;
163         int len = yyget_leng(yyscanner);
164         char *text = yyget_text(yyscanner);
165
166         *result = malloc(len + 1);
167         if (!*result)
168                 return -E_LLS_NOMEM;
169         for (in = text, out = *result; *in; in++) {
170                 if (*in == '\\') {
171                         if (!backslash) {
172                                 backslash = true;
173                                 continue;
174                         }
175                 } else if (*in == 'n' || *in == 't') {
176                         if (backslash) { /* \n or \t */
177                                 *out++ = (*in == 'n')? '\n' : '\t';
178                                 backslash = false;
179                                 continue;
180                         }
181                 } else if (*in == '"') {
182                         if (!backslash) {
183                                 quote = !quote;
184                                 continue;
185                         }
186                 } else if (isspace(*in)) {
187                         if (!backslash && !quote)
188                                 break;
189                 }
190                 /* copy the character */
191                 *out++ = *in;
192                 backslash = false;
193         }
194         ret = -E_LLS_TRAILING_BACKSLASH;
195         if (backslash)
196                 goto fail;
197         ret = -E_LLS_UNMATCHED_QUOTE;
198         if (quote)
199                 goto fail;
200         /* look at first non-space character */
201         for (; *in; in++) {
202                 if (isspace(*in))
203                         continue;
204                 if (*in == '#')
205                         break;
206                 ret = -E_LLS_TRAILING_GARBAGE;
207                 goto fail;
208         }
209         /* success */
210         *out = '\0';
211         return out - *result;
212 fail:
213         assert(ret < 0);
214         free(*result);
215         *result = NULL;
216         return ret;
217 }
218
219 void lls_free_argv(char **argv)
220 {
221         int i;
222
223         if (!argv)
224                 return;
225         for (i = 0; argv[i]; i++)
226                 free(argv[i]);
227         free(argv);
228 }
229
230 int lls_convert_config(const char *buf, size_t nbytes, const char *subcmd,
231                  char ***result, char **errctx)
232 {
233         int ret;
234         YY_BUFFER_STATE yybs;
235         yyscan_t yyscanner;
236         struct lls_yy_config_file_extra extra;
237
238         *result = NULL;
239         if (errctx)
240                 *errctx = NULL;
241
242         extra.argc = 1;
243         extra.argv = malloc((extra.argc + 1) * sizeof(char *));
244         if (!extra.argv)
245                 return -E_LLS_NOMEM;
246         extra.argv[0] = strdup(__FUNCTION__);
247         if (!extra.argv[0]) {
248                 free(extra.argv);
249                 return -E_LLS_NOMEM;
250         }
251         extra.argv[1] = NULL;
252         extra.subcmd = subcmd;
253
254         ret = yylex_init_extra(&extra, &yyscanner);
255         if (ret != 0) {
256                 ret = -E_LLS_NOMEM;
257                 goto free_argv;
258         }
259         yy_push_state(subcmd? INITIAL : SC_SCANNING, yyscanner);
260         yybs = yy_scan_bytes(buf, nbytes, yyscanner);
261         if (!yybs) {
262                 ret = -E_LLS_YY_SCAN;
263                 goto destroy;
264         }
265         ret = yylex(yyscanner);
266         if (ret < 0) {
267                 ret = -E_LLS_YY_LEX;
268                 if (errctx) {
269                         *errctx = malloc(100);
270                         if (*errctx)
271                                 sprintf(*errctx, "error at line %d",
272                                         yyget_lineno(yyscanner));
273                 }
274         }
275         yy_delete_buffer(yybs, yyscanner);
276 destroy:
277         yylex_destroy(yyscanner);
278 free_argv:
279         if (ret < 0)
280                 lls_free_argv(extra.argv);
281         else {
282                 *result = extra.argv;
283                 ret = extra.argc;
284         }
285         return ret;
286 }
287
288 #if 0
289 int main(void)
290 {
291         char buf[100 * 1024];
292         int ret, len, i, argc;
293         char **argv;
294
295         ret = read(STDIN_FILENO, buf, sizeof(buf));
296         if (ret <= 0)
297                 exit(EXIT_FAILURE);
298         len = ret;
299         ret = lls_convert_config(buf, len, NULL, &argv, NULL);
300         if (ret < 0)
301                 exit(EXIT_FAILURE);
302         argc = ret;
303         for (i = 0; i < argc; i++)
304                 printf("argv[%d]: %s\n", i, rargv[i]);
305         return EXIT_SUCCESS;
306 }
307 #endif