338455739957ba7b316b19bebc526e48d3bf2366
[protos/libecoli.git] / lib / ecoli_tk_shlex.c
1 /*
2  * Copyright (c) 2016, Olivier MATZ <zer0@droids-corp.org>
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions are met:
6  *
7  *     * Redistributions of source code must retain the above copyright
8  *       notice, this list of conditions and the following disclaimer.
9  *     * Redistributions in binary form must reproduce the above copyright
10  *       notice, this list of conditions and the following disclaimer in the
11  *       documentation and/or other materials provided with the distribution.
12  *     * Neither the name of the University of California, Berkeley nor the
13  *       names of its contributors may be used to endorse or promote products
14  *       derived from this software without specific prior written permission.
15  *
16  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND ANY
17  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
19  * DISCLAIMED. IN NO EVENT SHALL THE REGENTS AND CONTRIBUTORS BE LIABLE FOR ANY
20  * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
21  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
22  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
23  * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
25  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26  */
27
28 #include <stdio.h>
29 #include <stdlib.h>
30 #include <string.h>
31 #include <assert.h>
32 #include <stdarg.h>
33 #include <ctype.h>
34 #include <errno.h>
35
36 #include <ecoli_malloc.h>
37 #include <ecoli_log.h>
38 #include <ecoli_test.h>
39 #include <ecoli_tk.h>
40 #include <ecoli_tk_str.h>
41 #include <ecoli_tk_option.h>
42 #include <ecoli_tk_shlex.h>
43
44 static int isend(char c)
45 {
46         if (c == '\0' || c == '#' || c == '\n' || c == '\r')
47                 return 1;
48         return 0;
49 }
50
51 /* Remove quotes and stop when we reach the end of token. Return the
52  * number of "eaten" bytes from the source buffer, or a negative value
53  * on error */
54 /* XXX support simple quotes, try to be posix-compatible */
55 int get_token(const char *src, char **p_dst)
56 {
57         unsigned s = 0, d = 0, dstlen;
58         int quoted = 0;
59         char *dst;
60
61         dstlen = strlen(src) + 1;
62         dst = ec_malloc(dstlen);
63         if (dst == NULL)
64                 return -ENOMEM;
65
66         /* skip spaces */
67         while (isblank(src[s]))
68                 s++;
69
70         /* empty token */
71         if (isend(src[s])) {
72                 ec_free(dst);
73                 return -ENOENT;
74         }
75
76         /* copy token and remove quotes */
77         while (src[s] != '\0') {
78                 if (d >= dstlen) {
79                         ec_free(dst);
80                         return -EMSGSIZE;
81                 }
82
83                 if ((isblank(src[s]) || isend(src[s])) && quoted == 0)
84                         break;
85
86                 if (src[s] == '\\' && src[s+1] == '"') {
87                         dst[d++] = '"';
88                         s += 2;
89                         continue;
90                 }
91                 if (src[s] == '\\' && src[s+1] == '\\') {
92                         dst[d++] = '\\';
93                         s += 2;
94                         continue;
95                 }
96                 if (src[s] == '"') {
97                         s++;
98                         quoted = !quoted;
99                         continue;
100                 }
101                 dst[d++] = src[s++];
102         }
103
104         /* not enough room in dst buffer */
105         if (d >= dstlen) {
106                 ec_free(dst);
107                 return -EMSGSIZE;
108         }
109
110         /* end of string during quote */
111         if (quoted) {
112                 ec_free(dst);
113                 return -EINVAL;
114         }
115
116         dst[d++] = '\0';
117         *p_dst = dst;
118         return s;
119 }
120
121 static int safe_realloc(void *arg, size_t size)
122 {
123         void **pptr = arg;
124         void *new_ptr = ec_realloc(*pptr, size);
125
126         if (new_ptr == NULL)
127                 return -1;
128         *pptr = new_ptr;
129         return 0;
130 }
131
132 static char **tokenize(const char *str, int add_empty)
133 {
134         char **table = NULL, *token;
135         unsigned i, count = 1, off = 0;
136         int ret;
137
138         if (safe_realloc(&table, sizeof(char *)) < 0)
139                 return NULL;
140
141         table[0] = NULL;
142
143         while (1) {
144                 ret = get_token(str + off, &token);
145                 if (ret == -ENOENT)
146                         break;
147                 else if (ret < 0)
148                         goto fail;
149
150                 off += ret;
151                 count++;
152                 if (safe_realloc(&table, sizeof(char *) * count) < 0)
153                         goto fail;
154                 table[count - 2] = token;
155                 table[count - 1] = NULL;
156         }
157
158         if (add_empty && (off != strlen(str) || strlen(str) == 0)) {
159                 token = ec_strdup("");
160                 if (token == NULL)
161                         goto fail;
162
163                 count++;
164                 if (safe_realloc(&table, sizeof(char *) * count) < 0)
165                         goto fail;
166                 table[count - 2] = token;
167                 table[count - 1] = NULL;
168         }
169
170         return table;
171
172  fail:
173         for (i = 0; i < count; i++)
174                 ec_free(table[i]);
175         ec_free(table);
176         return NULL;
177 }
178
179 /* XXX broken: how to support that:
180    shlex(
181      str("toto"),
182      many(str("titi")),
183    )
184
185    that would match:
186      toto
187      toto titi
188      toto titi titi ...
189
190      --> maybe we should not try to create/match the spaces automatically
191
192      it would become:
193
194    shlex(
195      option(space()),   auto?
196      str("toto"),
197      many(
198        space(),
199        str("titi coin"),
200      ),
201      option(space()),   auto?
202    )
203
204    -> the goal of shlex would only be to unquote
205    -> the creation of auto-spaces would be in another token shcmd
206
207    cmd = shcmd_new()
208    shcmd_add_tk("ip", tk_ip_new())
209    shcmd_set_syntax("show <ip>")
210
211
212  */
213 static struct ec_parsed_tk *ec_tk_shlex_parse(const struct ec_tk *gen_tk,
214         const char *str)
215 {
216         struct ec_tk_shlex *tk = (struct ec_tk_shlex *)gen_tk;
217         struct ec_parsed_tk *parsed_tk, *child_parsed_tk;
218         unsigned int i;
219         char **tokens, **t;
220
221         parsed_tk = ec_parsed_tk_new(gen_tk);
222         if (parsed_tk == NULL)
223                 return NULL;
224
225         tokens = tokenize(str, 0);
226         if (tokens == NULL)
227                 goto fail;
228
229         t = &tokens[0];
230         for (i = 0, t = &tokens[0]; i < tk->len; i++, t++) {
231                 if (*t == NULL)
232                         goto fail;
233
234                 child_parsed_tk = ec_tk_parse(tk->table[i], *t);
235                 if (child_parsed_tk == NULL)
236                         goto fail;
237
238                 ec_parsed_tk_add_child(parsed_tk, child_parsed_tk);
239                 if (strlen(child_parsed_tk->str) == 0)
240                         t--;
241                 else if (strlen(child_parsed_tk->str) != strlen(*t))
242                         goto fail;
243         }
244
245         /* check it was the last token */
246         if (*t != NULL)
247                 goto fail;
248
249         if (tokens != NULL) {
250                 for (t = &tokens[0]; *t != NULL; t++)
251                         ec_free(*t);
252                 ec_free(tokens);
253                 tokens = NULL;
254         }
255
256         parsed_tk->str = ec_strdup(str);
257
258         return parsed_tk;
259
260  fail:
261         if (tokens != NULL) {
262                 for (t = &tokens[0]; *t != NULL; t++)
263                         ec_free(*t);
264                 ec_free(tokens);
265         }
266         ec_parsed_tk_free(parsed_tk);
267
268         return NULL;
269 }
270
271 static struct ec_completed_tk *ec_tk_shlex_complete(const struct ec_tk *gen_tk,
272         const char *str)
273 {
274         struct ec_tk_shlex *tk = (struct ec_tk_shlex *)gen_tk;
275         struct ec_completed_tk *completed_tk, *child_completed_tk = NULL;
276         struct ec_parsed_tk *child_parsed_tk;
277         unsigned int i;
278         char **tokens, **t;
279
280         tokens = tokenize(str, 1);
281         if (tokens == NULL)
282                 goto fail;
283
284         printf("complete <%s>\n", str);
285         for (t = &tokens[0]; *t != NULL; t++)
286                 printf("  token <%s> %p\n", *t, *t);
287
288         t = &tokens[0];
289
290         completed_tk = ec_completed_tk_new();
291         if (completed_tk == NULL)
292                 return NULL;
293
294         for (i = 0, t = &tokens[0]; i < tk->len; i++, t++) {
295                 if (*(t + 1) != NULL) {
296                         child_parsed_tk = ec_tk_parse(tk->table[i], *t);
297                         if (child_parsed_tk == NULL)
298                                 goto fail;
299
300                         if (strlen(child_parsed_tk->str) == 0)
301                                 t--;
302                         else if (strlen(child_parsed_tk->str) != strlen(*t)) {
303                                 ec_parsed_tk_free(child_parsed_tk);
304                                 goto fail;
305                         }
306
307                         ec_parsed_tk_free(child_parsed_tk);
308                 } else {
309                         child_completed_tk = ec_tk_complete(tk->table[i], *t);
310                         if (child_completed_tk == NULL) {
311                                 ec_completed_tk_free(completed_tk);
312                                 return NULL;
313                         }
314                         ec_completed_tk_merge(completed_tk, child_completed_tk);
315
316                         child_parsed_tk = ec_tk_parse(tk->table[i], "");
317                         if (child_parsed_tk == NULL)
318                                 break;
319                         ec_parsed_tk_free(child_parsed_tk);
320                         t--;
321                 }
322         }
323
324         if (tokens != NULL) {
325                 for (t = &tokens[0]; *t != NULL; t++)
326                         ec_free(*t);
327                 ec_free(tokens);
328                 tokens = NULL;
329         }
330
331         ec_completed_tk_dump(stdout, completed_tk);
332
333         return completed_tk;
334
335  fail:
336         if (tokens != NULL) {
337                 for (t = &tokens[0]; *t != NULL; t++)
338                         ec_free(*t);
339                 ec_free(tokens);
340         }
341         ec_completed_tk_free(completed_tk);
342
343         return NULL;
344 }
345
346 static void ec_tk_shlex_free_priv(struct ec_tk *gen_tk)
347 {
348         struct ec_tk_shlex *tk = (struct ec_tk_shlex *)gen_tk;
349         unsigned int i;
350
351         for (i = 0; i < tk->len; i++)
352                 ec_tk_free(tk->table[i]);
353         ec_free(tk->table);
354 }
355
356 static struct ec_tk_ops ec_tk_shlex_ops = {
357         .parse = ec_tk_shlex_parse,
358         .complete = ec_tk_shlex_complete,
359         .free_priv = ec_tk_shlex_free_priv,
360 };
361
362 struct ec_tk *ec_tk_shlex_new(const char *id)
363 {
364         struct ec_tk_shlex *tk = NULL;
365
366         tk = (struct ec_tk_shlex *)ec_tk_new(id, &ec_tk_shlex_ops, sizeof(*tk));
367         if (tk == NULL)
368                 return NULL;
369
370         tk->table = NULL;
371         tk->len = 0;
372
373         return &tk->gen;
374 }
375
376 struct ec_tk *ec_tk_shlex_new_list(const char *id, ...)
377 {
378         struct ec_tk_shlex *tk = NULL;
379         struct ec_tk *child;
380         va_list ap;
381
382         va_start(ap, id);
383
384         tk = (struct ec_tk_shlex *)ec_tk_shlex_new(id);
385         if (tk == NULL)
386                 goto fail;
387
388         for (child = va_arg(ap, struct ec_tk *);
389              child != EC_TK_ENDLIST;
390              child = va_arg(ap, struct ec_tk *)) {
391                 if (child == NULL)
392                         goto fail;
393
394                 ec_tk_shlex_add(&tk->gen, child);
395         }
396
397         va_end(ap);
398         return &tk->gen;
399
400 fail:
401         ec_tk_free(&tk->gen); /* will also free children */
402         va_end(ap);
403         return NULL;
404 }
405
406 int ec_tk_shlex_add(struct ec_tk *gen_tk, struct ec_tk *child)
407 {
408         struct ec_tk_shlex *tk = (struct ec_tk_shlex *)gen_tk;
409         struct ec_tk **table;
410
411         // XXX check tk type
412
413         assert(tk != NULL);
414         assert(child != NULL);
415
416         table = ec_realloc(tk->table, (tk->len + 1) * sizeof(*tk->table));
417         if (table == NULL)
418                 return -1;
419
420         tk->table = table;
421         table[tk->len] = child;
422         tk->len ++;
423
424         return 0;
425 }
426
427 static int ec_tk_shlex_testcase(void)
428 {
429         struct ec_tk *tk;
430         int ret = 0;
431
432         tk = ec_tk_shlex_new_list(NULL,
433                 ec_tk_str_new(NULL, "foo"),
434                 ec_tk_option_new(NULL, ec_tk_str_new(NULL, "toto")),
435                 ec_tk_str_new(NULL, "bar"),
436                 EC_TK_ENDLIST);
437         if (tk == NULL) {
438                 ec_log(EC_LOG_ERR, "cannot create tk\n");
439                 return -1;
440         }
441         ret |= EC_TEST_CHECK_TK_PARSE(tk, "foo bar", "foo bar");
442         ret |= EC_TEST_CHECK_TK_PARSE(tk, " \"foo\" \"bar\"",
443                 " \"foo\" \"bar\"");
444         ret |= EC_TEST_CHECK_TK_PARSE(tk, "foo toto bar", "foo toto bar");
445         ret |= EC_TEST_CHECK_TK_PARSE(tk, " foo   bar ", " foo   bar ");
446         ret |= EC_TEST_CHECK_TK_PARSE(tk, "foo bar xxx", NULL);
447         ret |= EC_TEST_CHECK_TK_PARSE(tk, "foo barxxx", NULL);
448         ret |= EC_TEST_CHECK_TK_PARSE(tk, "foo", NULL);
449         ret |= EC_TEST_CHECK_TK_PARSE(tk, " \"foo \" \"bar\"", NULL);
450         ec_tk_free(tk);
451
452         /* test completion */
453         tk = ec_tk_shlex_new_list(NULL,
454                 ec_tk_str_new(NULL, "foo"),
455                 ec_tk_option_new(NULL, ec_tk_str_new(NULL, "toto")),
456                 ec_tk_str_new(NULL, "bar"),
457                 ec_tk_str_new(NULL, "titi"),
458                 EC_TK_ENDLIST);
459         if (tk == NULL) {
460                 ec_log(EC_LOG_ERR, "cannot create tk\n");
461                 return -1;
462         }
463         ret |= EC_TEST_CHECK_TK_COMPLETE(tk, "", "foo");
464         ret |= EC_TEST_CHECK_TK_COMPLETE(tk, " ", "foo");
465         ret |= EC_TEST_CHECK_TK_COMPLETE(tk, "f", "oo");
466         ret |= EC_TEST_CHECK_TK_COMPLETE(tk, "foo", "");
467         ret |= EC_TEST_CHECK_TK_COMPLETE_LIST(tk, "foo ",
468                 "bar", "toto", EC_TK_ENDLIST);
469         ret |= EC_TEST_CHECK_TK_COMPLETE(tk, "foo t", "oto");
470         ret |= EC_TEST_CHECK_TK_COMPLETE(tk, "foo b", "ar");
471         ret |= EC_TEST_CHECK_TK_COMPLETE(tk, "foo bar", "");
472         ret |= EC_TEST_CHECK_TK_COMPLETE(tk, "foo bar ", "titi");
473         ret |= EC_TEST_CHECK_TK_COMPLETE(tk, "foo toto bar ", "titi");
474         ret |= EC_TEST_CHECK_TK_COMPLETE(tk, "x", "");
475         ret |= EC_TEST_CHECK_TK_COMPLETE(tk, "foo barx", "");
476         ec_tk_free(tk);
477
478         return ret;
479 }
480
481 static struct ec_test ec_tk_shlex_test = {
482         .name = "tk_shlex",
483         .test = ec_tk_shlex_testcase,
484 };
485
486 EC_REGISTER_TEST(ec_tk_shlex_test);