2 * Copyright (c) 2016, Olivier MATZ <zer0@droids-corp.org>
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions are met:
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.
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.
36 #include <ecoli_malloc.h>
37 #include <ecoli_log.h>
38 #include <ecoli_test.h>
40 #include <ecoli_tk_str.h>
41 #include <ecoli_tk_option.h>
42 #include <ecoli_tk_shlex.h>
44 static int isend(char c)
46 if (c == '\0' || c == '#' || c == '\n' || c == '\r')
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
54 /* XXX support simple quotes, try to be posix-compatible */
55 int get_token(const char *src, char **p_dst)
57 unsigned s = 0, d = 0, dstlen;
61 dstlen = strlen(src) + 1;
62 dst = ec_malloc(dstlen);
67 while (isblank(src[s]))
76 /* copy token and remove quotes */
77 while (src[s] != '\0') {
83 if ((isblank(src[s]) || isend(src[s])) && quoted == 0)
86 if (src[s] == '\\' && src[s+1] == '"') {
91 if (src[s] == '\\' && src[s+1] == '\\') {
104 /* not enough room in dst buffer */
110 /* end of string during quote */
121 static int safe_realloc(void *arg, size_t size)
124 void *new_ptr = ec_realloc(*pptr, size);
132 static char **tokenize(const char *str, int add_empty)
134 char **table = NULL, *token;
135 unsigned i, count = 1, off = 0;
138 if (safe_realloc(&table, sizeof(char *)) < 0)
144 ret = get_token(str + off, &token);
152 if (safe_realloc(&table, sizeof(char *) * count) < 0)
154 table[count - 2] = token;
155 table[count - 1] = NULL;
158 if (add_empty && (off != strlen(str) || strlen(str) == 0)) {
159 token = ec_strdup("");
164 if (safe_realloc(&table, sizeof(char *) * count) < 0)
166 table[count - 2] = token;
167 table[count - 1] = NULL;
173 for (i = 0; i < count; i++)
179 /* XXX broken: how to support that:
190 --> maybe we should not try to create/match the spaces automatically
195 option(space()), auto?
201 option(space()), auto?
204 -> the goal of shlex would only be to unquote
205 -> the creation of auto-spaces would be in another token shcmd
208 shcmd_add_tk("ip", tk_ip_new())
209 shcmd_set_syntax("show <ip>")
213 static struct ec_parsed_tk *ec_tk_shlex_parse(const struct ec_tk *gen_tk,
216 struct ec_tk_shlex *tk = (struct ec_tk_shlex *)gen_tk;
217 struct ec_parsed_tk *parsed_tk, *child_parsed_tk;
221 parsed_tk = ec_parsed_tk_new(gen_tk);
222 if (parsed_tk == NULL)
225 tokens = tokenize(str, 0);
230 for (i = 0, t = &tokens[0]; i < tk->len; i++, t++) {
234 child_parsed_tk = ec_tk_parse(tk->table[i], *t);
235 if (child_parsed_tk == NULL)
238 ec_parsed_tk_add_child(parsed_tk, child_parsed_tk);
239 if (strlen(child_parsed_tk->str) == 0)
241 else if (strlen(child_parsed_tk->str) != strlen(*t))
245 /* check it was the last token */
249 if (tokens != NULL) {
250 for (t = &tokens[0]; *t != NULL; t++)
256 parsed_tk->str = ec_strdup(str);
261 if (tokens != NULL) {
262 for (t = &tokens[0]; *t != NULL; t++)
266 ec_parsed_tk_free(parsed_tk);
271 static struct ec_completed_tk *ec_tk_shlex_complete(const struct ec_tk *gen_tk,
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;
280 tokens = tokenize(str, 1);
284 printf("complete <%s>\n", str);
285 for (t = &tokens[0]; *t != NULL; t++)
286 printf(" token <%s> %p\n", *t, *t);
290 completed_tk = ec_completed_tk_new();
291 if (completed_tk == NULL)
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)
300 if (strlen(child_parsed_tk->str) == 0)
302 else if (strlen(child_parsed_tk->str) != strlen(*t)) {
303 ec_parsed_tk_free(child_parsed_tk);
307 ec_parsed_tk_free(child_parsed_tk);
309 child_completed_tk = ec_tk_complete(tk->table[i], *t);
310 if (child_completed_tk == NULL) {
311 ec_completed_tk_free(completed_tk);
314 ec_completed_tk_merge(completed_tk, child_completed_tk);
316 child_parsed_tk = ec_tk_parse(tk->table[i], "");
317 if (child_parsed_tk == NULL)
319 ec_parsed_tk_free(child_parsed_tk);
324 if (tokens != NULL) {
325 for (t = &tokens[0]; *t != NULL; t++)
331 ec_completed_tk_dump(stdout, completed_tk);
336 if (tokens != NULL) {
337 for (t = &tokens[0]; *t != NULL; t++)
341 ec_completed_tk_free(completed_tk);
346 static void ec_tk_shlex_free_priv(struct ec_tk *gen_tk)
348 struct ec_tk_shlex *tk = (struct ec_tk_shlex *)gen_tk;
351 for (i = 0; i < tk->len; i++)
352 ec_tk_free(tk->table[i]);
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,
362 struct ec_tk *ec_tk_shlex_new(const char *id)
364 struct ec_tk_shlex *tk = NULL;
366 tk = (struct ec_tk_shlex *)ec_tk_new(id, &ec_tk_shlex_ops, sizeof(*tk));
376 struct ec_tk *ec_tk_shlex_new_list(const char *id, ...)
378 struct ec_tk_shlex *tk = NULL;
384 tk = (struct ec_tk_shlex *)ec_tk_shlex_new(id);
388 for (child = va_arg(ap, struct ec_tk *);
389 child != EC_TK_ENDLIST;
390 child = va_arg(ap, struct ec_tk *)) {
394 ec_tk_shlex_add(&tk->gen, child);
401 ec_tk_free(&tk->gen); /* will also free children */
406 int ec_tk_shlex_add(struct ec_tk *gen_tk, struct ec_tk *child)
408 struct ec_tk_shlex *tk = (struct ec_tk_shlex *)gen_tk;
409 struct ec_tk **table;
414 assert(child != NULL);
416 table = ec_realloc(tk->table, (tk->len + 1) * sizeof(*tk->table));
421 table[tk->len] = child;
427 static int ec_tk_shlex_testcase(void)
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"),
438 ec_log(EC_LOG_ERR, "cannot create tk\n");
441 ret |= EC_TEST_CHECK_TK_PARSE(tk, "foo bar", "foo bar");
442 ret |= EC_TEST_CHECK_TK_PARSE(tk, " \"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);
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"),
460 ec_log(EC_LOG_ERR, "cannot create tk\n");
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", "");
481 static struct ec_test ec_tk_shlex_test = {
483 .test = ec_tk_shlex_testcase,
486 EC_REGISTER_TEST(ec_tk_shlex_test);