From: Olivier Matz Date: Thu, 3 Nov 2016 19:29:26 +0000 (+0100) Subject: option, shlex X-Git-Url: http://git.droids-corp.org/?a=commitdiff_plain;h=ae2c4709444ee6fbbf388b853ab4039fcdde2e95;p=protos%2Flibecoli.git option, shlex --- diff --git a/lib/Makefile b/lib/Makefile index 08a1b40..42a969e 100644 --- a/lib/Makefile +++ b/lib/Makefile @@ -36,11 +36,15 @@ CFLAGS += -I. srcs := ecoli_tk.c ecoli_tk_str.c ecoli_tk_seq.c srcs += ecoli_tk_space.c ecoli_tk_or.c ecoli_test.c srcs += ecoli_tk_empty.c ecoli_tk_int.c -srcs += ecoli_malloc.c ecoli_log.c +srcs += ecoli_malloc.c ecoli_log.c ecoli_tk_option.c +srcs += ecoli_tk_shlex.c shlib-y-$(O)libecoli.so := $(srcs) exe-y-$(O)test = $(srcs) main.c +ldflags-$(O)readline = -lreadline +exe-y-$(O)readline = $(srcs) main-readline.c + include $(ECOLI)/mk/ecoli-post.mk all: _ecoli_all diff --git a/lib/build/test b/lib/build/test index 8ef7e3e..6484b70 100755 Binary files a/lib/build/test and b/lib/build/test differ diff --git a/lib/ecoli_test.c b/lib/ecoli_test.c index f6521da..c859d07 100644 --- a/lib/ecoli_test.c +++ b/lib/ecoli_test.c @@ -28,6 +28,7 @@ #include #include #include +#include #include #include @@ -75,20 +76,16 @@ int ec_test_check_tk_complete(const struct ec_tk *tk, const char *input, const char *s; int ret = -1; + assert(expected != NULL); + c = ec_tk_complete(tk, input); s = ec_completed_tk_smallest_start(c); - if (s == NULL && expected == NULL) - ret = 0; - else if (s != NULL && expected != NULL && - !strcmp(s, expected)) + if (!strcmp(s, expected)) ret = 0; - if (expected == NULL && ret != 0) - ec_log(EC_LOG_ERR, - "tk should not complete but completes with <%s>\n", s); - if (expected != NULL && ret != 0) + if (ret != 0) ec_log(EC_LOG_ERR, - "tk should complete with <%s> but completes with <%s>\n", + "should complete with <%s> but completes with <%s>\n", expected, s); ec_completed_tk_free(c); @@ -135,6 +132,7 @@ int ec_test_check_tk_complete_list(const struct ec_tk *tk, ec_log(EC_LOG_ERR, "nb_completion (%d) does not match (%d)\n", count, ec_completed_tk_count(c)); + ec_completed_tk_dump(stdout, c); goto out; } @@ -310,10 +308,6 @@ void debug_alloc_dump(void) } } -/* XXX todo */ -/* int ec_test_check_tk_complete_list(const struct ec_tk *tk, */ -/* const char *input, ...) */ - int ec_test_all(void) { struct ec_test *test; @@ -322,7 +316,7 @@ int ec_test_all(void) TAILQ_INIT(&debug_alloc_hdr_list); /* register a new malloc to trac memleaks */ - if (0 && ec_malloc_register(debug_malloc, debug_free, debug_realloc) < 0) { + if (ec_malloc_register(debug_malloc, debug_free, debug_realloc) < 0) { ec_log(EC_LOG_ERR, "cannot register new malloc\n"); return -1; } diff --git a/lib/ecoli_tk.c b/lib/ecoli_tk.c index cdb064e..8f22fbd 100644 --- a/lib/ecoli_tk.c +++ b/lib/ecoli_tk.c @@ -223,6 +223,7 @@ struct ec_completed_tk_elt *ec_completed_tk_elt_new(const struct ec_tk *tk, /* XXX define when to use ec_tk_complete() or tk->complete() * (same for parse) * suggestion: tk->op() is internal, user calls the function + * other idea: have 2 functions */ struct ec_completed_tk *ec_tk_complete(const struct ec_tk *tk, const char *str) @@ -273,17 +274,13 @@ void ec_completed_tk_elt_free(struct ec_completed_tk_elt *elt) ec_free(elt); } -struct ec_completed_tk *ec_completed_tk_merge( - struct ec_completed_tk *completed_tk1, +void ec_completed_tk_merge(struct ec_completed_tk *completed_tk1, struct ec_completed_tk *completed_tk2) { struct ec_completed_tk_elt *elt; - if (completed_tk2 == NULL) - return completed_tk1; - - if (completed_tk1 == NULL) - return completed_tk2; + assert(completed_tk1 != NULL); + assert(completed_tk2 != NULL); while (!TAILQ_EMPTY(&completed_tk2->elts)) { elt = TAILQ_FIRST(&completed_tk2->elts); @@ -292,8 +289,6 @@ struct ec_completed_tk *ec_completed_tk_merge( } ec_completed_tk_free(completed_tk2); - - return completed_tk1; } void ec_completed_tk_free(struct ec_completed_tk *completed_tk) @@ -333,8 +328,8 @@ void ec_completed_tk_dump(FILE *out, const struct ec_completed_tk *completed_tk) const char *ec_completed_tk_smallest_start( const struct ec_completed_tk *completed_tk) { - if (completed_tk == NULL) - return NULL; + if (completed_tk == NULL || completed_tk->smallest_start == NULL) + return ""; return completed_tk->smallest_start; } @@ -346,3 +341,26 @@ unsigned int ec_completed_tk_count(const struct ec_completed_tk *completed_tk) return completed_tk->count; } + +void ec_completed_tk_iter_start(struct ec_completed_tk *completed_tk) +{ + if (completed_tk == NULL) + return; + + completed_tk->cur = NULL; +} + +const struct ec_completed_tk_elt *ec_completed_tk_iter_next( + struct ec_completed_tk *completed_tk) +{ + if (completed_tk == NULL) + return NULL; + + if (completed_tk->cur == NULL) { + completed_tk->cur = TAILQ_FIRST(&completed_tk->elts); + } else { + completed_tk->cur = TAILQ_NEXT(completed_tk->cur, next); + } + + return completed_tk->cur; +} diff --git a/lib/ecoli_tk.h b/lib/ecoli_tk.h index cc0a208..72aee30 100644 --- a/lib/ecoli_tk.h +++ b/lib/ecoli_tk.h @@ -97,6 +97,7 @@ TAILQ_HEAD(ec_completed_tk_elt_list, ec_completed_tk_elt); struct ec_completed_tk { struct ec_completed_tk_elt_list elts; + const struct ec_completed_tk_elt *cur; unsigned count; char *smallest_start; }; @@ -113,17 +114,20 @@ struct ec_completed_tk_elt *ec_completed_tk_elt_new(const struct ec_tk *tk, void ec_completed_tk_add_elt(struct ec_completed_tk *completed_tk, struct ec_completed_tk_elt *elt); void ec_completed_tk_elt_free(struct ec_completed_tk_elt *elt); -struct ec_completed_tk *ec_completed_tk_merge( - struct ec_completed_tk *completed_tk1, +void ec_completed_tk_merge(struct ec_completed_tk *completed_tk1, struct ec_completed_tk *completed_tk2); void ec_completed_tk_free(struct ec_completed_tk *completed_tk); void ec_completed_tk_dump(FILE *out, const struct ec_completed_tk *completed_tk); -/* can return NULL */ +/* cannot return NULL */ const char *ec_completed_tk_smallest_start( const struct ec_completed_tk *completed_tk); unsigned int ec_completed_tk_count(const struct ec_completed_tk *completed_tk); +void ec_completed_tk_iter_start(struct ec_completed_tk *completed_tk); +const struct ec_completed_tk_elt *ec_completed_tk_iter_next( + struct ec_completed_tk *completed_tk); + #endif diff --git a/lib/ecoli_tk_empty.c b/lib/ecoli_tk_empty.c index e3e24f9..bc180d7 100644 --- a/lib/ecoli_tk_empty.c +++ b/lib/ecoli_tk_empty.c @@ -64,7 +64,6 @@ static int ec_tk_empty_testcase(void) struct ec_tk *tk; int ret = 0; - /* all inputs match */ tk = ec_tk_empty_new(NULL); if (tk == NULL) { ec_log(EC_LOG_ERR, "cannot create tk\n"); @@ -81,8 +80,8 @@ static int ec_tk_empty_testcase(void) ec_log(EC_LOG_ERR, "cannot create tk\n"); return -1; } - ret |= EC_TEST_CHECK_TK_COMPLETE(tk, "", NULL); - ret |= EC_TEST_CHECK_TK_COMPLETE(tk, "foo", NULL); + ret |= EC_TEST_CHECK_TK_COMPLETE(tk, "", ""); + ret |= EC_TEST_CHECK_TK_COMPLETE(tk, "foo", ""); ec_tk_free(tk); return ret; diff --git a/lib/ecoli_tk_int.c b/lib/ecoli_tk_int.c index 8aeb6bc..a6cc12d 100644 --- a/lib/ecoli_tk_int.c +++ b/lib/ecoli_tk_int.c @@ -198,9 +198,9 @@ static int ec_tk_int_testcase(void) ec_log(EC_LOG_ERR, "cannot create tk\n"); return -1; } - ret |= EC_TEST_CHECK_TK_COMPLETE(tk, "", NULL); - ret |= EC_TEST_CHECK_TK_COMPLETE(tk, "x", NULL); - ret |= EC_TEST_CHECK_TK_COMPLETE(tk, "1", NULL); + ret |= EC_TEST_CHECK_TK_COMPLETE(tk, "", ""); + ret |= EC_TEST_CHECK_TK_COMPLETE(tk, "x", ""); + ret |= EC_TEST_CHECK_TK_COMPLETE(tk, "1", ""); ec_tk_free(tk); return ret; diff --git a/lib/ecoli_tk_option.c b/lib/ecoli_tk_option.c new file mode 100644 index 0000000..2b4e4cb --- /dev/null +++ b/lib/ecoli_tk_option.c @@ -0,0 +1,137 @@ +/* + * Copyright (c) 2016, Olivier MATZ + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the University of California, Berkeley nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE REGENTS AND CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +static struct ec_parsed_tk *ec_tk_option_parse(const struct ec_tk *gen_tk, + const char *str) +{ + struct ec_tk_option *tk = (struct ec_tk_option *)gen_tk; + struct ec_parsed_tk *parsed_tk, *child_parsed_tk; + + parsed_tk = ec_parsed_tk_new(gen_tk); + if (parsed_tk == NULL) + return NULL; + + child_parsed_tk = ec_tk_parse(tk->child, str); + if (child_parsed_tk != NULL) { + ec_parsed_tk_add_child(parsed_tk, child_parsed_tk); + parsed_tk->str = ec_strndup(child_parsed_tk->str, + strlen(child_parsed_tk->str)); + } else { + parsed_tk->str = ec_strdup(""); + } + + return parsed_tk; +} + +static struct ec_completed_tk *ec_tk_option_complete(const struct ec_tk *gen_tk, + const char *str) +{ + struct ec_tk_option *tk = (struct ec_tk_option *)gen_tk; + + return ec_tk_complete(tk->child, str); +} + +static void ec_tk_option_free_priv(struct ec_tk *gen_tk) +{ + struct ec_tk_option *tk = (struct ec_tk_option *)gen_tk; + + ec_tk_free(tk->child); +} + +static struct ec_tk_ops ec_tk_option_ops = { + .parse = ec_tk_option_parse, + .complete = ec_tk_option_complete, + .free_priv = ec_tk_option_free_priv, +}; + +struct ec_tk *ec_tk_option_new(const char *id, struct ec_tk *child) +{ + struct ec_tk_option *tk = NULL; + + if (child == NULL) + return NULL; + + tk = (struct ec_tk_option *)ec_tk_new(id, &ec_tk_option_ops, + sizeof(*tk)); + if (tk == NULL) + return NULL; + + tk->child = child; + + return &tk->gen; +} + +static int ec_tk_option_testcase(void) +{ + struct ec_tk *tk; + int ret = 0; + + tk = ec_tk_option_new(NULL, ec_tk_str_new(NULL, "foo")); + if (tk == NULL) { + ec_log(EC_LOG_ERR, "cannot create tk\n"); + return -1; + } + ret |= EC_TEST_CHECK_TK_PARSE(tk, "", ""); + ret |= EC_TEST_CHECK_TK_PARSE(tk, "foo", "foo"); + ret |= EC_TEST_CHECK_TK_PARSE(tk, "bar", ""); + ec_tk_free(tk); + + /* test completion */ + tk = ec_tk_option_new(NULL, ec_tk_str_new(NULL, "foo")); + if (tk == NULL) { + ec_log(EC_LOG_ERR, "cannot create tk\n"); + return -1; + } + ret |= EC_TEST_CHECK_TK_COMPLETE(tk, "", "foo"); + ret |= EC_TEST_CHECK_TK_COMPLETE(tk, "f", "oo"); + ret |= EC_TEST_CHECK_TK_COMPLETE(tk, "b", ""); + ret |= EC_TEST_CHECK_TK_COMPLETE_LIST(tk, "", + "foo", EC_TK_ENDLIST); + ec_tk_free(tk); + + return ret; +} + +static struct ec_test ec_tk_option_test = { + .name = "tk_option", + .test = ec_tk_option_testcase, +}; + +EC_REGISTER_TEST(ec_tk_option_test); diff --git a/lib/ecoli_tk_option.h b/lib/ecoli_tk_option.h new file mode 100644 index 0000000..cadcc05 --- /dev/null +++ b/lib/ecoli_tk_option.h @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2016, Olivier MATZ + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the University of California, Berkeley nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE REGENTS AND CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef ECOLI_TK_OPTION_ +#define ECOLI_TK_OPTION_ + +#include + +struct ec_tk_option { + struct ec_tk gen; + struct ec_tk *child; +}; + +struct ec_tk *ec_tk_option_new(const char *id, struct ec_tk *tk); + +#endif diff --git a/lib/ecoli_tk_or.c b/lib/ecoli_tk_or.c index 4c782dd..900ee23 100644 --- a/lib/ecoli_tk_or.c +++ b/lib/ecoli_tk_or.c @@ -74,17 +74,20 @@ static struct ec_completed_tk *ec_tk_or_complete(const struct ec_tk *gen_tk, const char *str) { struct ec_tk_or *tk = (struct ec_tk_or *)gen_tk; - struct ec_completed_tk *completed_tk = NULL, *child_completed_tk; + struct ec_completed_tk *completed_tk, *child_completed_tk; size_t n; + completed_tk = ec_completed_tk_new(); + if (completed_tk == NULL) + return NULL; + for (n = 0; n < tk->len; n++) { child_completed_tk = ec_tk_complete(tk->table[n], str); if (child_completed_tk == NULL) continue; - completed_tk = ec_completed_tk_merge(completed_tk, - child_completed_tk); + ec_completed_tk_merge(completed_tk, child_completed_tk); } return completed_tk; @@ -174,7 +177,6 @@ static int ec_tk_or_testcase(void) struct ec_tk *tk; int ret = 0; - /* all inputs starting with foo should match */ tk = ec_tk_or_new_list(NULL, ec_tk_str_new(NULL, "foo"), ec_tk_str_new(NULL, "bar"), @@ -206,7 +208,7 @@ static int ec_tk_or_testcase(void) ret |= EC_TEST_CHECK_TK_COMPLETE(tk, "b", "ar"); ret |= EC_TEST_CHECK_TK_COMPLETE(tk, "t", ""); ret |= EC_TEST_CHECK_TK_COMPLETE(tk, "to", "to"); - ret |= EC_TEST_CHECK_TK_COMPLETE(tk, "x", NULL); + ret |= EC_TEST_CHECK_TK_COMPLETE(tk, "x", ""); ret |= EC_TEST_CHECK_TK_COMPLETE_LIST(tk, "", "foo", "bar", "bar2", "toto", "titi", EC_TK_ENDLIST); ret |= EC_TEST_CHECK_TK_COMPLETE_LIST(tk, "f", diff --git a/lib/ecoli_tk_seq.c b/lib/ecoli_tk_seq.c index fb1b434..7f840c0 100644 --- a/lib/ecoli_tk_seq.c +++ b/lib/ecoli_tk_seq.c @@ -36,10 +36,9 @@ #include #include #include +#include #include -// XXX to handle the quote, it will be done in tk_shseq -// it will unquote the string and parse each token separately static struct ec_parsed_tk *ec_tk_seq_parse(const struct ec_tk *gen_tk, const char *str) { @@ -74,16 +73,26 @@ static struct ec_completed_tk *ec_tk_seq_complete(const struct ec_tk *gen_tk, const char *str) { struct ec_tk_seq *tk = (struct ec_tk_seq *)gen_tk; - struct ec_completed_tk *completed_tk; + struct ec_completed_tk *completed_tk, *child_completed_tk; struct ec_parsed_tk *parsed_tk; size_t len = 0; unsigned int i; + completed_tk = ec_completed_tk_new(); + if (completed_tk == NULL) + return NULL; + if (tk->len == 0) - return ec_completed_tk_new(); + return completed_tk; + + for (i = 0; i < tk->len; i++) { + child_completed_tk = ec_tk_complete(tk->table[i], str + len); + if (child_completed_tk == NULL) { + ec_completed_tk_free(completed_tk); + return NULL; + } + ec_completed_tk_merge(completed_tk, child_completed_tk); - /* parse the first tokens */ - for (i = 0; i < tk->len - 1; i++) { parsed_tk = ec_tk_parse(tk->table[i], str + len); if (parsed_tk == NULL) break; @@ -92,8 +101,6 @@ static struct ec_completed_tk *ec_tk_seq_complete(const struct ec_tk *gen_tk, ec_parsed_tk_free(parsed_tk); } - completed_tk = ec_tk_complete(tk->table[i], str + len); - return completed_tk; } @@ -183,7 +190,6 @@ static int ec_tk_seq_testcase(void) struct ec_tk *tk; int ret = 0; - /* all inputs starting with foo should match */ tk = ec_tk_seq_new_list(NULL, ec_tk_str_new(NULL, "foo"), ec_tk_str_new(NULL, "bar"), @@ -202,6 +208,7 @@ static int ec_tk_seq_testcase(void) /* test completion */ tk = ec_tk_seq_new_list(NULL, ec_tk_str_new(NULL, "foo"), + ec_tk_option_new(NULL, ec_tk_str_new(NULL, "toto")), ec_tk_str_new(NULL, "bar"), EC_TK_ENDLIST); if (tk == NULL) { @@ -210,11 +217,14 @@ static int ec_tk_seq_testcase(void) } ret |= EC_TEST_CHECK_TK_COMPLETE(tk, "", "foo"); ret |= EC_TEST_CHECK_TK_COMPLETE(tk, "f", "oo"); - ret |= EC_TEST_CHECK_TK_COMPLETE(tk, "foo", "bar"); + ret |= EC_TEST_CHECK_TK_COMPLETE(tk, "foo", ""); + ret |= EC_TEST_CHECK_TK_COMPLETE_LIST(tk, "foo", + "bar", "toto", EC_TK_ENDLIST); + ret |= EC_TEST_CHECK_TK_COMPLETE(tk, "foot", "oto"); ret |= EC_TEST_CHECK_TK_COMPLETE(tk, "foob", "ar"); ret |= EC_TEST_CHECK_TK_COMPLETE(tk, "foobar", ""); - ret |= EC_TEST_CHECK_TK_COMPLETE(tk, "x", NULL); - ret |= EC_TEST_CHECK_TK_COMPLETE(tk, "foobarx", NULL); + ret |= EC_TEST_CHECK_TK_COMPLETE(tk, "x", ""); + ret |= EC_TEST_CHECK_TK_COMPLETE(tk, "foobarx", ""); ec_tk_free(tk); return ret; diff --git a/lib/ecoli_tk_shlex.c b/lib/ecoli_tk_shlex.c new file mode 100644 index 0000000..7b62c0c --- /dev/null +++ b/lib/ecoli_tk_shlex.c @@ -0,0 +1,452 @@ +/* + * Copyright (c) 2016, Olivier MATZ + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the University of California, Berkeley nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE REGENTS AND CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +static int isend(char c) +{ + if (c == '\0' || c == '#' || c == '\n' || c == '\r') + return 1; + return 0; +} + +/* Remove quotes and stop when we reach the end of token. Return the + * number of "eaten" bytes from the source buffer, or a negative value + * on error */ +/* XXX support simple quotes, try to be posix-compatible */ +int get_token(const char *src, char **p_dst) +{ + unsigned s = 0, d = 0, dstlen; + int quoted = 0; + char *dst; + + dstlen = strlen(src) + 1; + dst = ec_malloc(dstlen); + if (dst == NULL) + return -ENOMEM; + + /* skip spaces */ + while (isblank(src[s])) + s++; + + /* empty token */ + if (isend(src[s])) { + ec_free(dst); + return -ENOENT; + } + + /* copy token and remove quotes */ + while (src[s] != '\0') { + if (d >= dstlen) { + ec_free(dst); + return -EMSGSIZE; + } + + if ((isblank(src[s]) || isend(src[s])) && quoted == 0) + break; + + if (src[s] == '\\' && src[s+1] == '"') { + dst[d++] = '"'; + s += 2; + continue; + } + if (src[s] == '\\' && src[s+1] == '\\') { + dst[d++] = '\\'; + s += 2; + continue; + } + if (src[s] == '"') { + s++; + quoted = !quoted; + continue; + } + dst[d++] = src[s++]; + } + + /* not enough room in dst buffer */ + if (d >= dstlen) { + ec_free(dst); + return -EMSGSIZE; + } + + /* end of string during quote */ + if (quoted) { + ec_free(dst); + return -EINVAL; + } + + dst[d++] = '\0'; + *p_dst = dst; + return s; +} + +static int safe_realloc(void *arg, size_t size) +{ + void **pptr = arg; + void *new_ptr = ec_realloc(*pptr, size); + + if (new_ptr == NULL) + return -1; + *pptr = new_ptr; + return 0; +} + +static char **tokenize(const char *str, int add_empty) +{ + char **table = NULL, *token; + unsigned i, count = 1, off = 0; + int ret; + + if (safe_realloc(&table, sizeof(char *)) < 0) + return NULL; + + table[0] = NULL; + + while (1) { + ret = get_token(str + off, &token); + if (ret == -ENOENT) + break; + else if (ret < 0) + goto fail; + + off += ret; + count++; + if (safe_realloc(&table, sizeof(char *) * count) < 0) + goto fail; + table[count - 2] = token; + table[count - 1] = NULL; + } + + if (add_empty && (off != strlen(str) || strlen(str) == 0)) { + token = ec_strdup(""); + if (token == NULL) + goto fail; + + count++; + if (safe_realloc(&table, sizeof(char *) * count) < 0) + goto fail; + table[count - 2] = token; + table[count - 1] = NULL; + } + + return table; + + fail: + for (i = 0; i < count; i++) + ec_free(table[i]); + ec_free(table); + return NULL; +} + +static struct ec_parsed_tk *ec_tk_shlex_parse(const struct ec_tk *gen_tk, + const char *str) +{ + struct ec_tk_shlex *tk = (struct ec_tk_shlex *)gen_tk; + struct ec_parsed_tk *parsed_tk, *child_parsed_tk; + unsigned int i; + char **tokens, **t; + + parsed_tk = ec_parsed_tk_new(gen_tk); + if (parsed_tk == NULL) + return NULL; + + tokens = tokenize(str, 0); + if (tokens == NULL) + goto fail; + + t = &tokens[0]; + for (i = 0, t = &tokens[0]; i < tk->len; i++, t++) { + if (*t == NULL) + goto fail; + + child_parsed_tk = ec_tk_parse(tk->table[i], *t); + if (child_parsed_tk == NULL) + goto fail; + + ec_parsed_tk_add_child(parsed_tk, child_parsed_tk); + if (strlen(child_parsed_tk->str) == 0) + t--; + else if (strlen(child_parsed_tk->str) != strlen(*t)) + goto fail; + } + + /* check it was the last token */ + if (*t != NULL) + goto fail; + + if (tokens != NULL) { + for (t = &tokens[0]; *t != NULL; t++) + ec_free(*t); + ec_free(tokens); + tokens = NULL; + } + + parsed_tk->str = ec_strdup(str); + + return parsed_tk; + + fail: + if (tokens != NULL) { + for (t = &tokens[0]; *t != NULL; t++) + ec_free(*t); + ec_free(tokens); + } + ec_parsed_tk_free(parsed_tk); + + return NULL; +} + +static struct ec_completed_tk *ec_tk_shlex_complete(const struct ec_tk *gen_tk, + const char *str) +{ + struct ec_tk_shlex *tk = (struct ec_tk_shlex *)gen_tk; + struct ec_completed_tk *completed_tk, *child_completed_tk = NULL; + struct ec_parsed_tk *child_parsed_tk; + unsigned int i; + char **tokens, **t; + + tokens = tokenize(str, 1); + if (tokens == NULL) + goto fail; + + printf("complete <%s>\n", str); + for (t = &tokens[0]; *t != NULL; t++) + printf(" token <%s> %p\n", *t, *t); + + t = &tokens[0]; + + completed_tk = ec_completed_tk_new(); + if (completed_tk == NULL) + return NULL; + + for (i = 0, t = &tokens[0]; i < tk->len; i++, t++) { + if (*(t + 1) != NULL) { + child_parsed_tk = ec_tk_parse(tk->table[i], *t); + if (child_parsed_tk == NULL) + goto fail; + + if (strlen(child_parsed_tk->str) == 0) + t--; + else if (strlen(child_parsed_tk->str) != strlen(*t)) { + ec_parsed_tk_free(child_parsed_tk); + goto fail; + } + + ec_parsed_tk_free(child_parsed_tk); + } else { + child_completed_tk = ec_tk_complete(tk->table[i], *t); + if (child_completed_tk == NULL) { + ec_completed_tk_free(completed_tk); + return NULL; + } + ec_completed_tk_merge(completed_tk, child_completed_tk); + + child_parsed_tk = ec_tk_parse(tk->table[i], ""); + if (child_parsed_tk == NULL) + break; + ec_parsed_tk_free(child_parsed_tk); + t--; + } + } + + if (tokens != NULL) { + for (t = &tokens[0]; *t != NULL; t++) + ec_free(*t); + ec_free(tokens); + tokens = NULL; + } + + ec_completed_tk_dump(stdout, completed_tk); + + return completed_tk; + + fail: + if (tokens != NULL) { + for (t = &tokens[0]; *t != NULL; t++) + ec_free(*t); + ec_free(tokens); + } + ec_completed_tk_free(completed_tk); + + return NULL; +} + +static void ec_tk_shlex_free_priv(struct ec_tk *gen_tk) +{ + struct ec_tk_shlex *tk = (struct ec_tk_shlex *)gen_tk; + unsigned int i; + + for (i = 0; i < tk->len; i++) + ec_tk_free(tk->table[i]); + ec_free(tk->table); +} + +static struct ec_tk_ops ec_tk_shlex_ops = { + .parse = ec_tk_shlex_parse, + .complete = ec_tk_shlex_complete, + .free_priv = ec_tk_shlex_free_priv, +}; + +struct ec_tk *ec_tk_shlex_new(const char *id) +{ + struct ec_tk_shlex *tk = NULL; + + tk = (struct ec_tk_shlex *)ec_tk_new(id, &ec_tk_shlex_ops, sizeof(*tk)); + if (tk == NULL) + return NULL; + + tk->table = NULL; + tk->len = 0; + + return &tk->gen; +} + +struct ec_tk *ec_tk_shlex_new_list(const char *id, ...) +{ + struct ec_tk_shlex *tk = NULL; + struct ec_tk *child; + va_list ap; + + va_start(ap, id); + + tk = (struct ec_tk_shlex *)ec_tk_shlex_new(id); + if (tk == NULL) + goto fail; + + for (child = va_arg(ap, struct ec_tk *); + child != EC_TK_ENDLIST; + child = va_arg(ap, struct ec_tk *)) { + if (child == NULL) + goto fail; + + ec_tk_shlex_add(&tk->gen, child); + } + + va_end(ap); + return &tk->gen; + +fail: + ec_tk_free(&tk->gen); /* will also free children */ + va_end(ap); + return NULL; +} + +int ec_tk_shlex_add(struct ec_tk *gen_tk, struct ec_tk *child) +{ + struct ec_tk_shlex *tk = (struct ec_tk_shlex *)gen_tk; + struct ec_tk **table; + + // XXX check tk type + + assert(tk != NULL); + assert(child != NULL); + + table = ec_realloc(tk->table, (tk->len + 1) * sizeof(*tk->table)); + if (table == NULL) + return -1; + + tk->table = table; + table[tk->len] = child; + tk->len ++; + + return 0; +} + +static int ec_tk_shlex_testcase(void) +{ + struct ec_tk *tk; + int ret = 0; + + tk = ec_tk_shlex_new_list(NULL, + ec_tk_str_new(NULL, "foo"), + ec_tk_option_new(NULL, ec_tk_str_new(NULL, "toto")), + ec_tk_str_new(NULL, "bar"), + EC_TK_ENDLIST); + if (tk == NULL) { + ec_log(EC_LOG_ERR, "cannot create tk\n"); + return -1; + } + ret |= EC_TEST_CHECK_TK_PARSE(tk, "foo bar", "foo bar"); + ret |= EC_TEST_CHECK_TK_PARSE(tk, " \"foo\" \"bar\"", + " \"foo\" \"bar\""); + ret |= EC_TEST_CHECK_TK_PARSE(tk, "foo toto bar", "foo toto bar"); + ret |= EC_TEST_CHECK_TK_PARSE(tk, " foo bar ", " foo bar "); + ret |= EC_TEST_CHECK_TK_PARSE(tk, "foo bar xxx", NULL); + ret |= EC_TEST_CHECK_TK_PARSE(tk, "foo barxxx", NULL); + ret |= EC_TEST_CHECK_TK_PARSE(tk, "foo", NULL); + ret |= EC_TEST_CHECK_TK_PARSE(tk, " \"foo \" \"bar\"", NULL); + ec_tk_free(tk); + + /* test completion */ + tk = ec_tk_shlex_new_list(NULL, + ec_tk_str_new(NULL, "foo"), + ec_tk_option_new(NULL, ec_tk_str_new(NULL, "toto")), + ec_tk_str_new(NULL, "bar"), + ec_tk_str_new(NULL, "titi"), + EC_TK_ENDLIST); + if (tk == NULL) { + ec_log(EC_LOG_ERR, "cannot create tk\n"); + return -1; + } + ret |= EC_TEST_CHECK_TK_COMPLETE(tk, "", "foo"); + ret |= EC_TEST_CHECK_TK_COMPLETE(tk, " ", "foo"); + ret |= EC_TEST_CHECK_TK_COMPLETE(tk, "f", "oo"); + ret |= EC_TEST_CHECK_TK_COMPLETE(tk, "foo", ""); + ret |= EC_TEST_CHECK_TK_COMPLETE_LIST(tk, "foo ", + "bar", "toto", EC_TK_ENDLIST); + ret |= EC_TEST_CHECK_TK_COMPLETE(tk, "foo t", "oto"); + ret |= EC_TEST_CHECK_TK_COMPLETE(tk, "foo b", "ar"); + ret |= EC_TEST_CHECK_TK_COMPLETE(tk, "foo bar", ""); + ret |= EC_TEST_CHECK_TK_COMPLETE(tk, "foo bar ", "titi"); + ret |= EC_TEST_CHECK_TK_COMPLETE(tk, "foo toto bar ", "titi"); + ret |= EC_TEST_CHECK_TK_COMPLETE(tk, "x", ""); + ret |= EC_TEST_CHECK_TK_COMPLETE(tk, "foo barx", ""); + ec_tk_free(tk); + + return ret; +} + +static struct ec_test ec_tk_shlex_test = { + .name = "tk_shlex", + .test = ec_tk_shlex_testcase, +}; + +EC_REGISTER_TEST(ec_tk_shlex_test); diff --git a/lib/ecoli_tk_shlex.h b/lib/ecoli_tk_shlex.h new file mode 100644 index 0000000..52bc6a7 --- /dev/null +++ b/lib/ecoli_tk_shlex.h @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2016, Olivier MATZ + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the University of California, Berkeley nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE REGENTS AND CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef ECOLI_TK_SHLEX_ +#define ECOLI_TK_SHLEX_ + +#include + +#include + +struct ec_tk_shlex { + struct ec_tk gen; + struct ec_tk **table; + unsigned int len; +}; + +struct ec_tk *ec_tk_shlex_new(const char *id); + +/* list must be terminated with EC_TK_ENDLIST */ +struct ec_tk *ec_tk_shlex_new_list(const char *id, ...); + +int ec_tk_shlex_add(struct ec_tk *tk, struct ec_tk *child); + +#endif diff --git a/lib/ecoli_tk_space.c b/lib/ecoli_tk_space.c index 20facf8..6c69b3b 100644 --- a/lib/ecoli_tk_space.c +++ b/lib/ecoli_tk_space.c @@ -30,6 +30,8 @@ #include #include +#include +#include #include #include #include @@ -64,3 +66,40 @@ struct ec_tk *ec_tk_space_new(const char *id) return ec_tk_new(id, &ec_tk_space_ops, sizeof(struct ec_tk_space)); } +static int ec_tk_space_testcase(void) +{ + struct ec_tk *tk; + int ret = 0; + + tk = ec_tk_space_new(NULL); + if (tk == NULL) { + ec_log(EC_LOG_ERR, "cannot create tk\n"); + return -1; + } + ret |= EC_TEST_CHECK_TK_PARSE(tk, " ", " "); + ret |= EC_TEST_CHECK_TK_PARSE(tk, " ", " "); + ret |= EC_TEST_CHECK_TK_PARSE(tk, " foo", " "); + ret |= EC_TEST_CHECK_TK_PARSE(tk, "foo", NULL); + ret |= EC_TEST_CHECK_TK_PARSE(tk, "foo ", NULL); + ec_tk_free(tk); + + /* test completion */ + tk = ec_tk_space_new(NULL); + if (tk == NULL) { + ec_log(EC_LOG_ERR, "cannot create tk\n"); + return -1; + } + ret |= EC_TEST_CHECK_TK_COMPLETE(tk, "", ""); + ret |= EC_TEST_CHECK_TK_COMPLETE(tk, " ", ""); + ret |= EC_TEST_CHECK_TK_COMPLETE(tk, "foo", ""); + ec_tk_free(tk); + + return ret; +} + +static struct ec_test ec_tk_space_test = { + .name = "tk_space", + .test = ec_tk_space_testcase, +}; + +EC_REGISTER_TEST(ec_tk_space_test); diff --git a/lib/ecoli_tk_str.c b/lib/ecoli_tk_str.c index ad5d119..61953ef 100644 --- a/lib/ecoli_tk_str.c +++ b/lib/ecoli_tk_str.c @@ -61,17 +61,20 @@ static struct ec_completed_tk *ec_tk_str_complete(const struct ec_tk *gen_tk, struct ec_completed_tk_elt *completed_tk_elt; size_t n; + completed_tk = ec_completed_tk_new(); + if (completed_tk == NULL) + return NULL; + + /* check the string has the same beginning than the token */ for (n = 0; n < tk->len; n++) { if (str[n] != tk->string[n]) break; } if (str[n] != '\0') - return NULL; - - completed_tk = ec_completed_tk_new(); - if (completed_tk == NULL) - return NULL; + return completed_tk; + if (tk->string[n] == '\0') + return completed_tk; completed_tk_elt = ec_completed_tk_elt_new(gen_tk, tk->string + n, tk->string); @@ -127,7 +130,6 @@ static int ec_tk_str_testcase(void) struct ec_tk *tk; int ret = 0; - /* all inputs starting with foo should match */ tk = ec_tk_str_new(NULL, "foo"); if (tk == NULL) { ec_log(EC_LOG_ERR, "cannot create tk\n"); @@ -140,7 +142,6 @@ static int ec_tk_str_testcase(void) ret |= EC_TEST_CHECK_TK_PARSE(tk, "foo", "foo"); ec_tk_free(tk); - /* all inputs starting with foo should match */ tk = ec_tk_str_new(NULL, "Здравствуйте"); if (tk == NULL) { ec_log(EC_LOG_ERR, "cannot create tk\n"); @@ -171,7 +172,7 @@ static int ec_tk_str_testcase(void) ret |= EC_TEST_CHECK_TK_COMPLETE(tk, "", "foo"); ret |= EC_TEST_CHECK_TK_COMPLETE(tk, "f", "oo"); ret |= EC_TEST_CHECK_TK_COMPLETE(tk, "foo", ""); - ret |= EC_TEST_CHECK_TK_COMPLETE(tk, "x", NULL); + ret |= EC_TEST_CHECK_TK_COMPLETE(tk, "x", ""); ec_tk_free(tk); return ret; diff --git a/lib/main-readline.c b/lib/main-readline.c new file mode 100644 index 0000000..3bdc703 --- /dev/null +++ b/lib/main-readline.c @@ -0,0 +1,133 @@ +/* + * Copyright (c) 2016, Olivier MATZ + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the University of California, Berkeley nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE REGENTS AND CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include + +#include +#include + +#include +#include +#include +#include + +static struct ec_tk *commands; + +/* Set to a character describing the type of completion being attempted by + rl_complete_internal; available for use by application completion + functions. */ +extern int rl_completion_type; +/* Set to the last key used to invoke one of the completion functions */ +extern int rl_completion_invoking_key; + +int my_complete(int x, int y) +{ + (void)x; + (void)y; + + return 0; +} + +char *my_completion_entry(const char *s, int state) +{ + static struct ec_completed_tk *c; + static const struct ec_completed_tk_elt *elt; + + (void)s; + + if (state == 0) { + char *start; + + if (c != NULL) + ec_completed_tk_free(c); + + start = strdup(rl_line_buffer); + if (start == NULL) + return NULL; + start[rl_point] = '\0'; + + c = ec_tk_complete(commands, start); + ec_completed_tk_iter_start(c); + } + + elt = ec_completed_tk_iter_next(c); + if (elt == NULL) + return NULL; + + return strdup(elt->full); +} + +char **my_attempted_completion(const char *text, int start, int end) +{ + (void)start; + (void)end; + // XXX when it returns NULL, it completes with a file + return rl_completion_matches(text, my_completion_entry); +} + +int main(void) +{ + struct ec_parsed_tk *p; +// const char *name; + char *line; + + commands = ec_tk_seq_new_list(NULL, + ec_tk_str_new(NULL, "hello"), + ec_tk_space_new(NULL), + ec_tk_or_new_list("name", + ec_tk_str_new(NULL, "john"), + ec_tk_str_new(NULL, "mike"), + EC_TK_ENDLIST), + EC_TK_ENDLIST); + if (commands == NULL) { + printf("cannot create token\n"); + return 1; + } + + //rl_bind_key('\t', my_complete); + + //rl_completion_entry_function = my_completion_entry; + rl_attempted_completion_function = my_attempted_completion; + + while (1) { + line = readline("> "); + if (line == NULL) + break; + + // XXX need a "parse_all" + p = ec_tk_parse(commands, line); + ec_parsed_tk_dump(stdout, p); + add_history(line); + ec_parsed_tk_free(p); + } + + + ec_tk_free(commands); + return 0; +}