From 89d4ea2128501a119d835fccbf2500076c69695d Mon Sep 17 00:00:00 2001 From: Olivier Matz Date: Sat, 19 Nov 2016 22:47:27 +0100 Subject: [PATCH] cont --- lib/Makefile | 6 +- lib/ecoli_malloc.c | 2 +- lib/ecoli_malloc.h | 2 + lib/ecoli_strvec.c | 152 ++++++++++++ lib/ecoli_strvec.h | 49 ++++ lib/ecoli_test.c | 129 ++++++---- lib/ecoli_test.h | 33 +-- lib/ecoli_tk.c | 237 ++++++++++++++---- lib/ecoli_tk.h | 65 ++++- lib/ecoli_tk_empty.c | 46 +++- lib/ecoli_tk_empty.h | 6 - lib/ecoli_tk_int.c | 106 +++++--- lib/ecoli_tk_int.h | 7 - lib/ecoli_tk_many.c | 247 +++++++++++++++++++ lib/ecoli_tk_many.h | 37 +++ lib/ecoli_tk_option.c | 70 ++++-- lib/ecoli_tk_option.h | 5 - lib/ecoli_tk_or.c | 107 +++++--- lib/ecoli_tk_or.h | 8 - lib/ecoli_tk_seq.c | 126 +++++++--- lib/ecoli_tk_seq.h | 8 - lib/ecoli_tk_shlex.c | 559 +++++++++++++++++++++--------------------- lib/ecoli_tk_shlex.h | 15 +- lib/ecoli_tk_space.c | 61 +++-- lib/ecoli_tk_space.h | 4 - lib/ecoli_tk_str.c | 115 ++++++--- lib/ecoli_tk_str.h | 6 - lib/main-readline.c | 113 +++++---- lib/main.c | 153 ++++++++++-- 29 files changed, 1760 insertions(+), 714 deletions(-) create mode 100644 lib/ecoli_strvec.c create mode 100644 lib/ecoli_strvec.h create mode 100644 lib/ecoli_tk_many.c create mode 100644 lib/ecoli_tk_many.h diff --git a/lib/Makefile b/lib/Makefile index 42a969e..28fce46 100644 --- a/lib/Makefile +++ b/lib/Makefile @@ -33,13 +33,15 @@ O ?= build/ CFLAGS = -g -O0 -Wall -Werror -W -fPIC CFLAGS += -I. -srcs := ecoli_tk.c ecoli_tk_str.c ecoli_tk_seq.c +srcs := ecoli_tk.c ecoli_malloc.c ecoli_log.c ecoli_strvec.c +srcs += 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 ecoli_tk_option.c +srcs += ecoli_tk_option.c ecoli_tk_many.c srcs += ecoli_tk_shlex.c shlib-y-$(O)libecoli.so := $(srcs) +ldflags-$(O)test = -rdynamic exe-y-$(O)test = $(srcs) main.c ldflags-$(O)readline = -lreadline diff --git a/lib/ecoli_malloc.c b/lib/ecoli_malloc.c index 0bdbf21..0871247 100644 --- a/lib/ecoli_malloc.c +++ b/lib/ecoli_malloc.c @@ -80,7 +80,7 @@ void *__ec_calloc(size_t nmemb, size_t size, const char *file, if (ptr == NULL) return NULL; - memset(ptr, 0, size); + memset(ptr, 0, total); return ptr; } diff --git a/lib/ecoli_malloc.h b/lib/ecoli_malloc.h index 4c49649..f5bdc39 100644 --- a/lib/ecoli_malloc.h +++ b/lib/ecoli_malloc.h @@ -29,6 +29,8 @@ #define ECOLI_MALLOC_ #include +#include +#include typedef void *(*ec_malloc_t)(size_t size, const char *file, unsigned int line); typedef void (*ec_free_t)(void *ptr, const char *file, unsigned int line); diff --git a/lib/ecoli_strvec.c b/lib/ecoli_strvec.c new file mode 100644 index 0000000..17a10c8 --- /dev/null +++ b/lib/ecoli_strvec.c @@ -0,0 +1,152 @@ +/* + * 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 + +struct ec_strvec *ec_strvec_new(void) +{ + struct ec_strvec *strvec; + + strvec = ec_calloc(1, sizeof(*strvec)); + if (strvec == NULL) + return NULL; + + return strvec; +} + +int ec_strvec_add(struct ec_strvec *strvec, const char *s) +{ + char **new_vec; + + new_vec = ec_realloc(strvec->vec, + sizeof(*strvec->vec) * (strvec->len + 1)); + if (new_vec == NULL) + return -1; + + strvec->vec = new_vec; + strvec->vec[strvec->len] = ec_strdup(s); + if (strvec->vec[strvec->len] == NULL) + return -1; + + strvec->len++; + return 0; +} + +struct ec_strvec *ec_strvec_ndup(const struct ec_strvec *strvec, size_t len) +{ + struct ec_strvec *copy = NULL; + size_t i, veclen; + + copy = ec_strvec_new(); + if (copy == NULL) + goto fail; + + if (len == 0) + return copy; + + veclen = ec_strvec_len(strvec); + if (len > veclen) + len = veclen; + copy->vec = ec_calloc(len, sizeof(*copy->vec)); + if (copy->vec == NULL) + goto fail; + + for (i = 0; i < len; i++) { + copy->vec[i] = ec_strdup(strvec->vec[i]); + if (copy->vec[i] == NULL) + goto fail; + copy->len++; + } + + return copy; + +fail: + ec_strvec_free(copy); + return NULL; +} + +struct ec_strvec *ec_strvec_dup(const struct ec_strvec *strvec) +{ + return ec_strvec_ndup(strvec, ec_strvec_len(strvec)); +} + +void ec_strvec_free(struct ec_strvec *strvec) +{ + size_t i; + + if (strvec == NULL) + return; + + for (i = 0; i < ec_strvec_len(strvec); i++) + ec_free(ec_strvec_val(strvec, i)); + + ec_free(strvec->vec); + ec_free(strvec); +} + +size_t ec_strvec_len(const struct ec_strvec *strvec) +{ + return strvec->len; +} + +char *ec_strvec_val(const struct ec_strvec *strvec, size_t idx) +{ + if (strvec == NULL || idx >= strvec->len) + return NULL; + + return strvec->vec[idx]; +} + +int ec_strvec_slice(struct ec_strvec *strvec, const struct ec_strvec *from, + size_t off) +{ + if (off > from->len) + return -1; + + strvec->len = from->len - off; + strvec->vec = &from->vec[off]; + + return 0; +} + +void ec_strvec_dump(const struct ec_strvec *strvec, FILE *out) +{ + size_t i; + + if (strvec == NULL) { + fprintf(out, "empty strvec\n"); + return; + } + + fprintf(out, "strvec:\n"); + for (i = 0; i < ec_strvec_len(strvec); i++) + fprintf(out, " %zd: %s\n", i, strvec->vec[i]); +} diff --git a/lib/ecoli_strvec.h b/lib/ecoli_strvec.h new file mode 100644 index 0000000..a606177 --- /dev/null +++ b/lib/ecoli_strvec.h @@ -0,0 +1,49 @@ +/* + * 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_STRVEC_ +#define ECOLI_STRVEC_ + +#include + +struct ec_strvec { + size_t len; + char **vec; +}; + +struct ec_strvec *ec_strvec_new(void); +int ec_strvec_add(struct ec_strvec *strvec, const char *s); +struct ec_strvec *ec_strvec_dup(const struct ec_strvec *strvec); +struct ec_strvec *ec_strvec_ndup(const struct ec_strvec *strvec, size_t len); +void ec_strvec_free(struct ec_strvec *strvec); +size_t ec_strvec_len(const struct ec_strvec *strvec); +char *ec_strvec_val(const struct ec_strvec *strvec, size_t idx); +int ec_strvec_slice(struct ec_strvec *strvec, const struct ec_strvec *from, + size_t off); +void ec_strvec_dump(const struct ec_strvec *strvec, FILE *out); + +#endif diff --git a/lib/ecoli_test.c b/lib/ecoli_test.c index 3cce2da..1408f5a 100644 --- a/lib/ecoli_test.c +++ b/lib/ecoli_test.c @@ -33,6 +33,7 @@ #include #include #include +#include #include static struct ec_test_list test_list = TAILQ_HEAD_INITIALIZER(test_list); @@ -43,70 +44,72 @@ void ec_test_register(struct ec_test *test) TAILQ_INSERT_TAIL(&test_list, test, next); } -int ec_test_check_tk_parse(const struct ec_tk *tk, const char *input, - const char *expected) +int ec_test_check_tk_parse(const struct ec_tk *tk, int expected, ...) { struct ec_parsed_tk *p; + struct ec_strvec *vec = NULL; const char *s; - int ret = -1; - - p = ec_tk_parse(tk, input); - s = ec_parsed_tk_to_string(p); - if (s == NULL && expected == NULL) - ret = 0; - else if (s != NULL && expected != NULL && - !strcmp(s, expected)) - ret = 0; - - if (expected == NULL && ret != 0) - ec_log(EC_LOG_ERR, "tk should not match but matches <%s>\n", s); - if (expected != NULL && ret != 0) - ec_log(EC_LOG_ERR, "tk should match <%s> but matches <%s>\n", - expected, s); + int ret = -1, match; + va_list ap; - ec_parsed_tk_free(p); + va_start(ap, expected); - return ret; -} + /* build a string vector */ + vec = ec_strvec_new(); + if (vec == NULL) + goto out; -int ec_test_check_tk_complete(const struct ec_tk *tk, const char *input, - const char *expected) -{ - struct ec_completed_tk *c; - const char *s; - int ret = -1; + for (s = va_arg(ap, const char *); + s != EC_TK_ENDLIST; + s = va_arg(ap, const char *)) { + if (s == NULL) + goto out; - assert(expected != NULL); + if (ec_strvec_add(vec, s) < 0) + goto out; + } - c = ec_tk_complete(tk, input); - s = ec_completed_tk_smallest_start(c); - if (!strcmp(s, expected)) + p = ec_tk_parse_tokens(tk, vec); + /* XXX only for debug */ + ec_parsed_tk_dump(stdout, p); + if (p == NULL) { + ec_log(EC_LOG_ERR, "parsed_tk is NULL\n"); + } + if (ec_parsed_tk_matches(p)) + match = ec_parsed_tk_len(p); + else + match = -1; + if (expected == match) { ret = 0; - - if (ret != 0) + } else { ec_log(EC_LOG_ERR, - "should complete with <%s> but completes with <%s>\n", - expected, s); + "tk parsed len (%d) does not match expected (%d)\n", + match, expected); + } - ec_completed_tk_free(c); + ec_parsed_tk_free(p); +out: + ec_strvec_free(vec); + va_end(ap); return ret; } -int ec_test_check_tk_complete_list(const struct ec_tk *tk, - const char *input, ...) +int ec_test_check_tk_complete(const struct ec_tk *tk, ...) { struct ec_completed_tk *c = NULL; struct ec_completed_tk_elt *elt; - const char *s; - int ret = -1; + struct ec_strvec *vec = NULL; + const char *s, *expected; + int ret = 0; unsigned int count = 0; va_list ap; - va_start(ap, input); + va_start(ap, tk); - c = ec_tk_complete(tk, input); - if (c == NULL) + /* build a string vector */ + vec = ec_strvec_new(); + if (vec == NULL) goto out; for (s = va_arg(ap, const char *); @@ -115,30 +118,58 @@ int ec_test_check_tk_complete_list(const struct ec_tk *tk, if (s == NULL) goto out; + if (ec_strvec_add(vec, s) < 0) + goto out; + } + + c = ec_tk_complete_tokens(tk, vec); + if (c == NULL) { + ret = -1; + goto out; + } + + for (s = va_arg(ap, const char *); + s != EC_TK_ENDLIST; + s = va_arg(ap, const char *)) { + if (s == NULL) { + ret = -1; + goto out; + } + count++; TAILQ_FOREACH(elt, &c->elts, next) { - if (strcmp(elt->add, s) == 0) + /* only check matching completions */ + if (elt->add != NULL && strcmp(elt->add, s) == 0) break; } if (elt == NULL) { ec_log(EC_LOG_ERR, "completion <%s> not in list\n", s); - goto out; + ret = -1; } } - if (count != ec_completed_tk_count(c)) { + if (count != ec_completed_tk_count_match(c)) { ec_log(EC_LOG_ERR, "nb_completion (%d) does not match (%d)\n", - count, ec_completed_tk_count(c)); + count, ec_completed_tk_count_match(c)); ec_completed_tk_dump(stdout, c); - goto out; + ret = -1; } - ret = 0; + + expected = va_arg(ap, const char *); + s = ec_completed_tk_smallest_start(c); + if (strcmp(s, expected)) { + ret = -1; + ec_log(EC_LOG_ERR, + "should complete with <%s> but completes with <%s>\n", + expected, s); + } out: + ec_strvec_free(vec); ec_completed_tk_free(c); va_end(ap); return ret; @@ -150,6 +181,8 @@ int ec_test_all(void) int ret = 0; TAILQ_FOREACH(test, &test_list, next) { + ec_log(EC_LOG_INFO, "== starting test %-20s\n", test->name); + if (test->test() == 0) { ec_log(EC_LOG_INFO, "== test %-20s success\n", test->name); diff --git a/lib/ecoli_test.h b/lib/ecoli_test.h index 45d1d4e..c1246c4 100644 --- a/lib/ecoli_test.h +++ b/lib/ecoli_test.h @@ -67,38 +67,27 @@ void ec_test_register(struct ec_test *test); int ec_test_all(void); -int ec_test_check_tk_parse(const struct ec_tk *tk, const char *input, - const char *expected); +/* expected == -1 means no match */ +int ec_test_check_tk_parse(const struct ec_tk *tk, int expected, ...); #define TEST_ERR() \ ec_log(EC_LOG_ERR, "%s:%d: error: test failed\n", \ __FILE__, __LINE__); \ -#define EC_TEST_CHECK_TK_PARSE(tk, input, expected) ({ \ - int ret = ec_test_check_tk_parse(tk, input, expected); \ - if (ret) \ +#define EC_TEST_CHECK_TK_PARSE(tk, input, expected...) ({ \ + int ret_ = ec_test_check_tk_parse(tk, input, expected); \ + if (ret_) \ TEST_ERR(); \ - ret; \ + ret_; \ }) -int ec_test_check_tk_complete(const struct ec_tk *tk, const char *input, - const char *expected); +int ec_test_check_tk_complete(const struct ec_tk *tk, ...); -#define EC_TEST_CHECK_TK_COMPLETE(tk, input, expected) ({ \ - int ret = ec_test_check_tk_complete(tk, input, expected); \ - if (ret) \ +#define EC_TEST_CHECK_TK_COMPLETE(tk, args...) ({ \ + int ret_ = ec_test_check_tk_complete(tk, args); \ + if (ret_) \ TEST_ERR(); \ - ret; \ -}) - -int ec_test_check_tk_complete_list(const struct ec_tk *tk, - const char *input, ...); - -#define EC_TEST_CHECK_TK_COMPLETE_LIST(tk, input, expected...) ({ \ - int ret = ec_test_check_tk_complete_list(tk, input, expected); \ - if (ret) \ - TEST_ERR(); \ - ret; \ + ret_; \ }) #endif diff --git a/lib/ecoli_tk.c b/lib/ecoli_tk.c index 8f22fbd..e21df55 100644 --- a/lib/ecoli_tk.c +++ b/lib/ecoli_tk.c @@ -29,8 +29,10 @@ #include #include #include +#include #include +#include #include struct ec_tk *ec_tk_new(const char *id, const struct ec_tk_ops *ops, @@ -72,27 +74,53 @@ void ec_tk_free(struct ec_tk *tk) struct ec_parsed_tk *ec_tk_parse(const struct ec_tk *tk, const char *str) { + struct ec_strvec *strvec = NULL; struct ec_parsed_tk *parsed_tk; - /* by default, it does not match anything */ - if (tk->ops->parse == NULL) + errno = ENOMEM; + strvec = ec_strvec_new(); + if (strvec == NULL) + goto fail; + + if (ec_strvec_add(strvec, str) < 0) + goto fail; + + parsed_tk = ec_tk_parse_tokens(tk, strvec); + if (parsed_tk == NULL) + goto fail; + + ec_strvec_free(strvec); + return parsed_tk; + + fail: + ec_strvec_free(strvec); + return NULL; +} + +struct ec_parsed_tk *ec_tk_parse_tokens(const struct ec_tk *tk, + const struct ec_strvec *strvec) +{ + struct ec_parsed_tk *parsed_tk; + + if (tk->ops->parse == NULL) { + errno = ENOTSUP; return NULL; + } - parsed_tk = tk->ops->parse(tk, str); + parsed_tk = tk->ops->parse(tk, strvec); return parsed_tk; } -struct ec_parsed_tk *ec_parsed_tk_new(const struct ec_tk *tk) +struct ec_parsed_tk *ec_parsed_tk_new(void) { - struct ec_parsed_tk *parsed_tk; + struct ec_parsed_tk *parsed_tk = NULL; parsed_tk = ec_calloc(1, sizeof(*parsed_tk)); if (parsed_tk == NULL) goto fail; - parsed_tk->tk = tk; TAILQ_INIT(&parsed_tk->children); return parsed_tk; @@ -101,7 +129,14 @@ struct ec_parsed_tk *ec_parsed_tk_new(const struct ec_tk *tk) return NULL; } -void ec_parsed_tk_free(struct ec_parsed_tk *parsed_tk) +void ec_parsed_tk_set_match(struct ec_parsed_tk *parsed_tk, + const struct ec_tk *tk, struct ec_strvec *strvec) +{ + parsed_tk->tk = tk; + parsed_tk->strvec = strvec; +} + +void ec_parsed_tk_free_children(struct ec_parsed_tk *parsed_tk) { struct ec_parsed_tk *child; @@ -113,22 +148,37 @@ void ec_parsed_tk_free(struct ec_parsed_tk *parsed_tk) TAILQ_REMOVE(&parsed_tk->children, child, next); ec_parsed_tk_free(child); } - ec_free(parsed_tk->str); +} + +void ec_parsed_tk_free(struct ec_parsed_tk *parsed_tk) +{ + if (parsed_tk == NULL) + return; + + ec_parsed_tk_free_children(parsed_tk); + ec_strvec_free(parsed_tk->strvec); ec_free(parsed_tk); } -static void __ec_parsed_tk_dump(FILE *out, const struct ec_parsed_tk *parsed_tk, - size_t indent) +static void __ec_parsed_tk_dump(FILE *out, + const struct ec_parsed_tk *parsed_tk, size_t indent) { struct ec_parsed_tk *child; size_t i; - const char *s; + const char *s, *id = "None", *typename = "None"; /* XXX enhance */ for (i = 0; i < indent; i++) fprintf(out, " "); + s = ec_parsed_tk_to_string(parsed_tk); - fprintf(out, "id=%s, s=<%s>\n", parsed_tk->tk->id, s); + if (parsed_tk->tk != NULL) { + if (parsed_tk->tk->id != NULL) + id = parsed_tk->tk->id; + typename = parsed_tk->tk->ops->typename; + } + + fprintf(out, "tk_type=%s, id=%s, s=<%s>\n", typename, id, s); TAILQ_FOREACH(child, &parsed_tk->children, next) __ec_parsed_tk_dump(out, child, indent + 2); @@ -137,6 +187,10 @@ static void __ec_parsed_tk_dump(FILE *out, const struct ec_parsed_tk *parsed_tk, void ec_parsed_tk_dump(FILE *out, const struct ec_parsed_tk *parsed_tk) { if (parsed_tk == NULL) { + fprintf(out, "parsed_tk is NULL, error in parse\n"); + return; + } + if (!ec_parsed_tk_matches(parsed_tk)) { fprintf(out, "no match\n"); return; } @@ -158,7 +212,9 @@ struct ec_parsed_tk *ec_parsed_tk_find_first(struct ec_parsed_tk *parsed_tk, if (parsed_tk == NULL) return NULL; - if (parsed_tk->tk->id != NULL && !strcmp(parsed_tk->tk->id, id)) + if (parsed_tk->tk != NULL && + parsed_tk->tk->id != NULL && + !strcmp(parsed_tk->tk->id, id)) return parsed_tk; TAILQ_FOREACH(child, &parsed_tk->children, next) { @@ -170,12 +226,34 @@ struct ec_parsed_tk *ec_parsed_tk_find_first(struct ec_parsed_tk *parsed_tk, return NULL; } +/* XXX return NUL if it matches several tokens? + or add a parameter to join() the tokens ? */ const char *ec_parsed_tk_to_string(const struct ec_parsed_tk *parsed_tk) { - if (parsed_tk == NULL) + if (parsed_tk == NULL || parsed_tk->strvec == NULL) return NULL; - return parsed_tk->str; + return ec_strvec_val(parsed_tk->strvec, 0); +} + +/* number of parsed tokens */ +size_t ec_parsed_tk_len(const struct ec_parsed_tk *parsed_tk) +{ + if (parsed_tk == NULL || parsed_tk->strvec == NULL) + return 0; + + return ec_strvec_len(parsed_tk->strvec); +} + +size_t ec_parsed_tk_matches(const struct ec_parsed_tk *parsed_tk) +{ + if (parsed_tk == NULL) + return 0; + + if (parsed_tk->tk == NULL && TAILQ_EMPTY(&parsed_tk->children)) + return 0; + + return 1; } struct ec_completed_tk *ec_completed_tk_new(void) @@ -187,13 +265,13 @@ struct ec_completed_tk *ec_completed_tk_new(void) return NULL; TAILQ_INIT(&completed_tk->elts); - completed_tk->count = 0; + completed_tk->count_match = 0; return completed_tk; } struct ec_completed_tk_elt *ec_completed_tk_elt_new(const struct ec_tk *tk, - const char *add, const char *full) + const char *add) { struct ec_completed_tk_elt *elt = NULL; @@ -209,13 +287,6 @@ struct ec_completed_tk_elt *ec_completed_tk_elt_new(const struct ec_tk *tk, return NULL; } } - if (full != NULL) { - elt->full = ec_strdup(full); - if (elt->full == NULL) { - ec_completed_tk_elt_free(elt); - return NULL; - } - } return elt; } @@ -227,17 +298,60 @@ struct ec_completed_tk_elt *ec_completed_tk_elt_new(const struct ec_tk *tk, */ struct ec_completed_tk *ec_tk_complete(const struct ec_tk *tk, const char *str) +{ + struct ec_strvec *strvec = NULL; + struct ec_completed_tk *completed_tk; + + errno = ENOMEM; + strvec = ec_strvec_new(); + if (strvec == NULL) + goto fail; + + if (ec_strvec_add(strvec, str) < 0) + goto fail; + + completed_tk = ec_tk_complete_tokens(tk, strvec); + if (completed_tk == NULL) + goto fail; + + ec_strvec_free(strvec); + return completed_tk; + + fail: + ec_strvec_free(strvec); + return NULL; +} + +/* default completion function: return a no-match element */ +struct ec_completed_tk *ec_tk_default_complete(const struct ec_tk *gen_tk, + const struct ec_strvec *strvec) { struct ec_completed_tk *completed_tk; + struct ec_completed_tk_elt *completed_tk_elt; + + (void)strvec; + + completed_tk = ec_completed_tk_new(); + if (completed_tk == NULL) + return NULL; - if (tk->ops->complete == NULL) - return ec_completed_tk_new(); + completed_tk_elt = ec_completed_tk_elt_new(gen_tk, NULL); + if (completed_tk_elt == NULL) { + ec_completed_tk_free(completed_tk); + return NULL; + } - completed_tk = tk->ops->complete(tk, str); + ec_completed_tk_add_elt(completed_tk, completed_tk_elt); return completed_tk; } +struct ec_completed_tk *ec_tk_complete_tokens(const struct ec_tk *tk, + const struct ec_strvec *strvec) +{ + return tk->ops->complete(tk, strvec); +} + /* count the number of identical chars at the beginning of 2 strings */ static size_t strcmp_count(const char *s1, const char *s2) { @@ -256,6 +370,8 @@ void ec_completed_tk_add_elt( TAILQ_INSERT_TAIL(&completed_tk->elts, elt, next); completed_tk->count++; + if (elt->add != NULL) + completed_tk->count_match++; if (elt->add != NULL) { if (completed_tk->smallest_start == NULL) { completed_tk->smallest_start = ec_strdup(elt->add); @@ -270,7 +386,6 @@ void ec_completed_tk_add_elt( void ec_completed_tk_elt_free(struct ec_completed_tk_elt *elt) { ec_free(elt->add); - ec_free(elt->full); ec_free(elt); } @@ -316,12 +431,13 @@ void ec_completed_tk_dump(FILE *out, const struct ec_completed_tk *completed_tk) return; } - fprintf(out, "completion: count=%u smallest_start=<%s>\n", - completed_tk->count, completed_tk->smallest_start); + fprintf(out, "completion: count=%u match=%u smallest_start=<%s>\n", + completed_tk->count, completed_tk->count_match, + completed_tk->smallest_start); TAILQ_FOREACH(elt, &completed_tk->elts, next) { - fprintf(out, "add=<%s>, full=<%s>, tk=%p\n", - elt->add, elt->full, elt->tk); + fprintf(out, "add=<%s>, tk=%p, tk_type=%s\n", + elt->add, elt->tk, elt->tk->ops->typename); } } @@ -334,33 +450,62 @@ const char *ec_completed_tk_smallest_start( return completed_tk->smallest_start; } -unsigned int ec_completed_tk_count(const struct ec_completed_tk *completed_tk) +unsigned int ec_completed_tk_count_match( + const struct ec_completed_tk *completed_tk) { if (completed_tk == NULL) return 0; - return completed_tk->count; + return completed_tk->count_match; } -void ec_completed_tk_iter_start(struct ec_completed_tk *completed_tk) +struct ec_completed_tk_iter * +ec_completed_tk_iter_new(struct ec_completed_tk *completed_tk, + enum ec_completed_tk_filter_flags flags) { - if (completed_tk == NULL) - return; + struct ec_completed_tk_iter *iter; + + iter = ec_calloc(1, sizeof(*iter)); + if (iter == NULL) + return NULL; - completed_tk->cur = NULL; + iter->completed_tk = completed_tk; + iter->flags = flags; + iter->cur = NULL; + + return iter; } const struct ec_completed_tk_elt *ec_completed_tk_iter_next( - struct ec_completed_tk *completed_tk) + struct ec_completed_tk_iter *iter) { - if (completed_tk == NULL) + if (iter->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); - } + do { + if (iter->cur == NULL) { + iter->cur = TAILQ_FIRST(&iter->completed_tk->elts); + } else { + iter->cur = TAILQ_NEXT(iter->cur, next); + } + + if (iter->cur == NULL) + break; - return completed_tk->cur; + if (iter->cur->add == NULL && + (iter->flags & ITER_NO_MATCH)) + break; + + if (iter->cur->add != NULL && + (iter->flags & ITER_MATCH)) + break; + + } while (iter->cur != NULL); + + return iter->cur; +} + +void ec_completed_tk_iter_free(struct ec_completed_tk_iter *iter) +{ + ec_free(iter); } diff --git a/lib/ecoli_tk.h b/lib/ecoli_tk.h index 72aee30..e243c08 100644 --- a/lib/ecoli_tk.h +++ b/lib/ecoli_tk.h @@ -30,21 +30,22 @@ #include #include - #include #define EC_TK_ENDLIST ((void *)1) struct ec_tk; struct ec_parsed_tk; +struct ec_strvec; typedef struct ec_parsed_tk *(*ec_tk_parse_t)(const struct ec_tk *tk, - const char *str); + const struct ec_strvec *strvec); typedef struct ec_completed_tk *(*ec_tk_complete_t)(const struct ec_tk *tk, - const char *str); + const struct ec_strvec *strvec); typedef void (*ec_tk_free_priv_t)(struct ec_tk *); struct ec_tk_ops { + const char *typename; ec_tk_parse_t parse; ec_tk_complete_t complete; ec_tk_free_priv_t free_priv; @@ -62,21 +63,38 @@ void ec_tk_free(struct ec_tk *tk); TAILQ_HEAD(ec_parsed_tk_list, ec_parsed_tk); +/* + tk == NULL + empty children list means "no match" +*/ struct ec_parsed_tk { - struct ec_parsed_tk_list children; TAILQ_ENTRY(ec_parsed_tk) next; + struct ec_parsed_tk_list children; const struct ec_tk *tk; - char *str; + struct ec_strvec *strvec; }; +struct ec_parsed_tk *ec_parsed_tk_new(void); + +void ec_parsed_tk_set_match(struct ec_parsed_tk *parsed_tk, + const struct ec_tk *tk, struct ec_strvec *strvec); + /* XXX we could use a cache to store possible completions or match: the * cache would be per-node, and would be reset for each call to parse() * or complete() ? */ - -struct ec_parsed_tk *ec_parsed_tk_new(const struct ec_tk *tk); +/* a NULL return value is an error, with errno set + ENOTSUP: no ->parse() operation +*/ struct ec_parsed_tk *ec_tk_parse(const struct ec_tk *token, const char *str); + +/* mostly internal to tokens */ +/* XXX it should not reset cache + * ... not sure... it is used by tests */ +struct ec_parsed_tk *ec_tk_parse_tokens(const struct ec_tk *token, + const struct ec_strvec *strvec); + void ec_parsed_tk_add_child(struct ec_parsed_tk *parsed_tk, struct ec_parsed_tk *child); +void ec_parsed_tk_free_children(struct ec_parsed_tk *parsed_tk); void ec_parsed_tk_dump(FILE *out, const struct ec_parsed_tk *parsed_tk); void ec_parsed_tk_free(struct ec_parsed_tk *parsed_tk); @@ -84,12 +102,13 @@ struct ec_parsed_tk *ec_parsed_tk_find_first(struct ec_parsed_tk *parsed_tk, const char *id); const char *ec_parsed_tk_to_string(const struct ec_parsed_tk *parsed_tk); +size_t ec_parsed_tk_len(const struct ec_parsed_tk *parsed_tk); +size_t ec_parsed_tk_matches(const struct ec_parsed_tk *parsed_tk); struct ec_completed_tk_elt { TAILQ_ENTRY(ec_completed_tk_elt) next; const struct ec_tk *tk; char *add; - char *full; }; TAILQ_HEAD(ec_completed_tk_elt_list, ec_completed_tk_elt); @@ -97,8 +116,8 @@ 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; + unsigned count_match; char *smallest_start; }; @@ -108,9 +127,11 @@ struct ec_completed_tk { */ struct ec_completed_tk *ec_tk_complete(const struct ec_tk *token, const char *str); +struct ec_completed_tk *ec_tk_complete_tokens(const struct ec_tk *token, + const struct ec_strvec *strvec); struct ec_completed_tk *ec_completed_tk_new(void); struct ec_completed_tk_elt *ec_completed_tk_elt_new(const struct ec_tk *tk, - const char *add, const char *full); + const char *add); 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); @@ -119,15 +140,33 @@ void ec_completed_tk_merge(struct ec_completed_tk *completed_tk1, 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); +struct ec_completed_tk *ec_tk_default_complete(const struct ec_tk *gen_tk, + const struct ec_strvec *strvec); /* 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); +unsigned int ec_completed_tk_count_match( + const struct ec_completed_tk *completed_tk); + +enum ec_completed_tk_filter_flags { + ITER_MATCH = 1, + ITER_NO_MATCH, +}; + +struct ec_completed_tk_iter { + enum ec_completed_tk_filter_flags flags; + const struct ec_completed_tk *completed_tk; + const struct ec_completed_tk_elt *cur; +}; + +struct ec_completed_tk_iter * +ec_completed_tk_iter_new(struct ec_completed_tk *completed_tk, + enum ec_completed_tk_filter_flags flags); -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); + struct ec_completed_tk_iter *iter); +void ec_completed_tk_iter_free(struct ec_completed_tk_iter *iter); #endif diff --git a/lib/ecoli_tk_empty.c b/lib/ecoli_tk_empty.c index bc180d7..196041c 100644 --- a/lib/ecoli_tk_empty.c +++ b/lib/ecoli_tk_empty.c @@ -32,31 +32,49 @@ #include #include #include +#include #include #include +struct ec_tk_empty { + struct ec_tk gen; +}; + static struct ec_parsed_tk *ec_tk_empty_parse(const struct ec_tk *gen_tk, - const char *str) + const struct ec_strvec *strvec) { struct ec_parsed_tk *parsed_tk; + struct ec_strvec *match_strvec; + + (void)strvec; - parsed_tk = ec_parsed_tk_new(gen_tk); + parsed_tk = ec_parsed_tk_new(); if (parsed_tk == NULL) - return NULL; + goto fail; - (void)str; - parsed_tk->str = ec_strdup(""); + match_strvec = ec_strvec_new(); + if (match_strvec == NULL) + goto fail; + + ec_parsed_tk_set_match(parsed_tk, gen_tk, match_strvec); return parsed_tk; + + fail: + ec_parsed_tk_free(parsed_tk); + return NULL; } static struct ec_tk_ops ec_tk_empty_ops = { + .typename = "empty", .parse = ec_tk_empty_parse, + .complete = ec_tk_default_complete, }; struct ec_tk *ec_tk_empty_new(const char *id) { - return ec_tk_new(id, &ec_tk_empty_ops, sizeof(struct ec_tk_empty)); + return ec_tk_new(id, &ec_tk_empty_ops, + sizeof(struct ec_tk_empty)); } static int ec_tk_empty_testcase(void) @@ -69,9 +87,9 @@ static int ec_tk_empty_testcase(void) ec_log(EC_LOG_ERR, "cannot create tk\n"); return -1; } - ret |= EC_TEST_CHECK_TK_PARSE(tk, "foo", ""); - ret |= EC_TEST_CHECK_TK_PARSE(tk, " foo", ""); - ret |= EC_TEST_CHECK_TK_PARSE(tk, "", ""); + ret |= EC_TEST_CHECK_TK_PARSE(tk, 0, "foo", EC_TK_ENDLIST); + ret |= EC_TEST_CHECK_TK_PARSE(tk, 0, EC_TK_ENDLIST); + ret |= EC_TEST_CHECK_TK_PARSE(tk, 0, "foo", "bar", EC_TK_ENDLIST); ec_tk_free(tk); /* never completes */ @@ -80,8 +98,14 @@ 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, "", ""); - ret |= EC_TEST_CHECK_TK_COMPLETE(tk, "foo", ""); + ret |= EC_TEST_CHECK_TK_COMPLETE(tk, + "", EC_TK_ENDLIST, + EC_TK_ENDLIST, + ""); + ret |= EC_TEST_CHECK_TK_COMPLETE(tk, + "foo", EC_TK_ENDLIST, + EC_TK_ENDLIST, + ""); ec_tk_free(tk); return ret; diff --git a/lib/ecoli_tk_empty.h b/lib/ecoli_tk_empty.h index 4e22420..37c0eef 100644 --- a/lib/ecoli_tk_empty.h +++ b/lib/ecoli_tk_empty.h @@ -28,12 +28,6 @@ #ifndef ECOLI_TK_EMPTY_ #define ECOLI_TK_EMPTY_ -#include - -struct ec_tk_empty { - struct ec_tk gen; -}; - struct ec_tk *ec_tk_empty_new(const char *id); #endif diff --git a/lib/ecoli_tk_int.c b/lib/ecoli_tk_int.c index a6cc12d..f4cca45 100644 --- a/lib/ecoli_tk_int.c +++ b/lib/ecoli_tk_int.c @@ -34,11 +34,19 @@ #include #include +#include #include #include #include -static size_t parse_llint(struct ec_tk_int *tk, const char *str, +struct ec_tk_int { + struct ec_tk gen; + long long int min; + long long int max; + unsigned int base; +}; + +static int parse_llint(struct ec_tk_int *tk, const char *str, long long *val) { char *endptr; @@ -46,44 +54,57 @@ static size_t parse_llint(struct ec_tk_int *tk, const char *str, errno = 0; *val = strtoll(str, &endptr, tk->base); - /* starts with a space */ - if (isspace(str[0])) - return 0; - /* out of range */ if ((errno == ERANGE && (*val == LLONG_MAX || *val == LLONG_MIN)) || (errno != 0 && *val == 0)) - return 0; + return -1; if (*val < tk->min || *val > tk->max) - return 0; + return -1; + + if (*endptr != 0) + return -1; - return endptr - str; + return 0; } static struct ec_parsed_tk *ec_tk_int_parse(const struct ec_tk *gen_tk, - const char *str) + const struct ec_strvec *strvec) { struct ec_tk_int *tk = (struct ec_tk_int *)gen_tk; struct ec_parsed_tk *parsed_tk; + struct ec_strvec *match_strvec; + const char *str; long long val; - size_t len; - len = parse_llint(tk, str, &val); - if (len == 0) - return NULL; - - parsed_tk = ec_parsed_tk_new(gen_tk); + parsed_tk = ec_parsed_tk_new(); if (parsed_tk == NULL) - return NULL; + goto fail; + + if (ec_strvec_len(strvec) == 0) + return parsed_tk; - parsed_tk->str = ec_strndup(str, len); + str = ec_strvec_val(strvec, 0); + if (parse_llint(tk, str, &val) < 0) + return parsed_tk; + + match_strvec = ec_strvec_ndup(strvec, 1); + if (match_strvec == NULL) + goto fail; + + ec_parsed_tk_set_match(parsed_tk, gen_tk, match_strvec); return parsed_tk; + + fail: + ec_parsed_tk_free(parsed_tk); + return NULL; } static struct ec_tk_ops ec_tk_int_ops = { + .typename = "int", .parse = ec_tk_int_parse, + .complete = ec_tk_default_complete, }; struct ec_tk *ec_tk_int_new(const char *id, long long int min, @@ -127,12 +148,12 @@ static int ec_tk_int_testcase(void) ec_log(EC_LOG_ERR, "cannot create tk\n"); return -1; } - ret |= EC_TEST_CHECK_TK_PARSE(tk, "0", "0"); - ret |= EC_TEST_CHECK_TK_PARSE(tk, "256", "256"); - ret |= EC_TEST_CHECK_TK_PARSE(tk, "0x100", "0x100"); - ret |= EC_TEST_CHECK_TK_PARSE(tk, "-1", NULL); - ret |= EC_TEST_CHECK_TK_PARSE(tk, "0x101", NULL); - ret |= EC_TEST_CHECK_TK_PARSE(tk, " 1", NULL); + ret |= EC_TEST_CHECK_TK_PARSE(tk, 1, "0", EC_TK_ENDLIST); + ret |= EC_TEST_CHECK_TK_PARSE(tk, 1, "256", "foo", EC_TK_ENDLIST); + ret |= EC_TEST_CHECK_TK_PARSE(tk, 1, "0x100", EC_TK_ENDLIST); + ret |= EC_TEST_CHECK_TK_PARSE(tk, 1, " 1", EC_TK_ENDLIST); + ret |= EC_TEST_CHECK_TK_PARSE(tk, -1, "-1", EC_TK_ENDLIST); + ret |= EC_TEST_CHECK_TK_PARSE(tk, -1, "0x101", EC_TK_ENDLIST); p = ec_tk_parse(tk, "0"); s = ec_parsed_tk_to_string(p); @@ -160,13 +181,11 @@ static int ec_tk_int_testcase(void) ec_log(EC_LOG_ERR, "cannot create tk\n"); return -1; } - ret |= EC_TEST_CHECK_TK_PARSE(tk, "0", "0"); - ret |= EC_TEST_CHECK_TK_PARSE(tk, "-1", "-1"); - ret |= EC_TEST_CHECK_TK_PARSE(tk, "7fffffffffffffff", - "7fffffffffffffff"); - ret |= EC_TEST_CHECK_TK_PARSE(tk, "0x7fffffffffffffff", - "0x7fffffffffffffff"); - ret |= EC_TEST_CHECK_TK_PARSE(tk, "-2", NULL); + ret |= EC_TEST_CHECK_TK_PARSE(tk, 1, "0", EC_TK_ENDLIST); + ret |= EC_TEST_CHECK_TK_PARSE(tk, 1, "-1", EC_TK_ENDLIST); + ret |= EC_TEST_CHECK_TK_PARSE(tk, 1, "7fffffffffffffff", EC_TK_ENDLIST); + ret |= EC_TEST_CHECK_TK_PARSE(tk, 1, "0x7fffffffffffffff", EC_TK_ENDLIST); + ret |= EC_TEST_CHECK_TK_PARSE(tk, -1, "-2", EC_TK_ENDLIST); p = ec_tk_parse(tk, "10"); s = ec_parsed_tk_to_string(p); @@ -184,12 +203,12 @@ static int ec_tk_int_testcase(void) ec_log(EC_LOG_ERR, "cannot create tk\n"); return -1; } - ret |= EC_TEST_CHECK_TK_PARSE(tk, "0", "0"); - ret |= EC_TEST_CHECK_TK_PARSE(tk, "-1", "-1"); - ret |= EC_TEST_CHECK_TK_PARSE(tk, "-9223372036854775808", - "-9223372036854775808"); - ret |= EC_TEST_CHECK_TK_PARSE(tk, "0x0", "0"); - ret |= EC_TEST_CHECK_TK_PARSE(tk, "1", NULL); + ret |= EC_TEST_CHECK_TK_PARSE(tk, 1, "0", EC_TK_ENDLIST); + ret |= EC_TEST_CHECK_TK_PARSE(tk, 1, "-1", EC_TK_ENDLIST); + ret |= EC_TEST_CHECK_TK_PARSE(tk, 1, "-9223372036854775808", + EC_TK_ENDLIST); + ret |= EC_TEST_CHECK_TK_PARSE(tk, -1, "0x0", EC_TK_ENDLIST); + ret |= EC_TEST_CHECK_TK_PARSE(tk, -1, "1", EC_TK_ENDLIST); ec_tk_free(tk); /* test completion */ @@ -198,9 +217,18 @@ 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, "", ""); - ret |= EC_TEST_CHECK_TK_COMPLETE(tk, "x", ""); - ret |= EC_TEST_CHECK_TK_COMPLETE(tk, "1", ""); + ret |= EC_TEST_CHECK_TK_COMPLETE(tk, + "", EC_TK_ENDLIST, + EC_TK_ENDLIST, + ""); + ret |= EC_TEST_CHECK_TK_COMPLETE(tk, + "x", EC_TK_ENDLIST, + EC_TK_ENDLIST, + ""); + ret |= EC_TEST_CHECK_TK_COMPLETE(tk, + "1", EC_TK_ENDLIST, + EC_TK_ENDLIST, + ""); ec_tk_free(tk); return ret; diff --git a/lib/ecoli_tk_int.h b/lib/ecoli_tk_int.h index e210279..147e2d0 100644 --- a/lib/ecoli_tk_int.h +++ b/lib/ecoli_tk_int.h @@ -30,13 +30,6 @@ #include -struct ec_tk_int { - struct ec_tk gen; - long long int min; - long long int max; - unsigned int base; -}; - struct ec_tk *ec_tk_int_new(const char *id, long long int min, long long int max, unsigned int base); long long ec_tk_int_getval(struct ec_tk *tk, const char *str); diff --git a/lib/ecoli_tk_many.c b/lib/ecoli_tk_many.c new file mode 100644 index 0000000..6d0b501 --- /dev/null +++ b/lib/ecoli_tk_many.c @@ -0,0 +1,247 @@ +/* + * 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 + +struct ec_tk_many { + struct ec_tk gen; + unsigned int min; + unsigned int max; + struct ec_tk *child; +}; + +static struct ec_parsed_tk *ec_tk_many_parse(const struct ec_tk *gen_tk, + const struct ec_strvec *strvec) +{ + struct ec_tk_many *tk = (struct ec_tk_many *)gen_tk; + struct ec_parsed_tk *parsed_tk, *child_parsed_tk; + struct ec_strvec *match_strvec; + struct ec_strvec childvec; + size_t off = 0, len, count; + + parsed_tk = ec_parsed_tk_new(); + if (parsed_tk == NULL) + goto fail; + + for (count = 0; tk->max == 0 || count < tk->max; count++) { + if (ec_strvec_slice(&childvec, strvec, off) < 0) + goto fail; + + child_parsed_tk = ec_tk_parse_tokens(tk->child, &childvec); + if (child_parsed_tk == NULL) + goto fail; + + if (!ec_parsed_tk_matches(child_parsed_tk)) { + ec_parsed_tk_free(child_parsed_tk); + break; + } + + ec_parsed_tk_add_child(parsed_tk, child_parsed_tk); + + /* it matches "no token", no need to continue */ + len = ec_parsed_tk_len(child_parsed_tk); + if (len == 0) { + ec_parsed_tk_free(child_parsed_tk); + break; + } + + off += len; + } + + if (count < tk->min) { + ec_parsed_tk_free_children(parsed_tk); + return parsed_tk; + } + + match_strvec = ec_strvec_ndup(strvec, off); + if (match_strvec == NULL) + goto fail; + + ec_parsed_tk_set_match(parsed_tk, gen_tk, match_strvec); + + return parsed_tk; + + fail: + ec_parsed_tk_free(parsed_tk); + return NULL; +} + +#if 0 +static struct ec_completed_tk *ec_tk_many_complete(const struct ec_tk *gen_tk, + const struct ec_strvec *strvec) +{ + struct ec_tk_many *tk = (struct ec_tk_many *)gen_tk; + struct ec_completed_tk *completed_tk, *child_completed_tk; + struct ec_strvec childvec; + 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 completed_tk; + + for (i = 0; i < tk->len; i++) { + if (ec_strvec_slice(&childvec, strvec, len) < 0) + return completed_tk; /* XXX fail ? */ + + child_completed_tk = ec_tk_complete_tokens(tk->table[i], + &childvec); + if (child_completed_tk == NULL) { + ec_completed_tk_free(completed_tk); + return NULL; + } + ec_completed_tk_merge(completed_tk, child_completed_tk); + + parsed_tk = ec_tk_parse_tokens(tk->table[i], &childvec); + if (parsed_tk == NULL) + goto fail; + if (!ec_parsed_tk_matches(parsed_tk)) { + ec_parsed_tk_free(parsed_tk); + break; + } + + len += ec_strvec_len(parsed_tk->strvec); + ec_parsed_tk_free(parsed_tk); + } + + return completed_tk; + +fail: + /* XXX */ + return NULL; +} +#endif + +static void ec_tk_many_free_priv(struct ec_tk *gen_tk) +{ + struct ec_tk_many *tk = (struct ec_tk_many *)gen_tk; + + ec_tk_free(tk->child); +} + +static struct ec_tk_ops ec_tk_many_ops = { + .typename = "many", + .parse = ec_tk_many_parse, + .complete = ec_tk_default_complete, +//XXX .complete = ec_tk_many_complete, + .free_priv = ec_tk_many_free_priv, +}; + +struct ec_tk *ec_tk_many_new(const char *id, struct ec_tk *child, + unsigned int min, unsigned int max) +{ + struct ec_tk_many *tk = NULL; + + if (child == NULL) + return NULL; + + tk = (struct ec_tk_many *)ec_tk_new(id, &ec_tk_many_ops, + sizeof(*tk)); + if (tk == NULL) { + ec_tk_free(child); + return NULL; + } + + tk->child = child; + tk->min = min; + tk->max = max; + + return &tk->gen; +} + +static int ec_tk_many_testcase(void) +{ + struct ec_tk *tk; + int ret = 0; + + tk = ec_tk_many_new(NULL, ec_tk_str_new(NULL, "foo"), 0, 0); + if (tk == NULL) { + ec_log(EC_LOG_ERR, "cannot create tk\n"); + return -1; + } + ret |= EC_TEST_CHECK_TK_PARSE(tk, 0, "bar", EC_TK_ENDLIST); + ret |= EC_TEST_CHECK_TK_PARSE(tk, 1, "foo", "bar", EC_TK_ENDLIST); + ret |= EC_TEST_CHECK_TK_PARSE(tk, 2, "foo", "foo", "bar", + EC_TK_ENDLIST); + ret |= EC_TEST_CHECK_TK_PARSE(tk, 0, EC_TK_ENDLIST); + ec_tk_free(tk); + + tk = ec_tk_many_new(NULL, ec_tk_str_new(NULL, "foo"), 1, 0); + if (tk == NULL) { + ec_log(EC_LOG_ERR, "cannot create tk\n"); + return -1; + } + ret |= EC_TEST_CHECK_TK_PARSE(tk, -1, "bar", EC_TK_ENDLIST); + ret |= EC_TEST_CHECK_TK_PARSE(tk, 1, "foo", "bar", EC_TK_ENDLIST); + ret |= EC_TEST_CHECK_TK_PARSE(tk, 2, "foo", "foo", "bar", + EC_TK_ENDLIST); + ret |= EC_TEST_CHECK_TK_PARSE(tk, -1, EC_TK_ENDLIST); + ec_tk_free(tk); + + tk = ec_tk_many_new(NULL, ec_tk_str_new(NULL, "foo"), 1, 2); + if (tk == NULL) { + ec_log(EC_LOG_ERR, "cannot create tk\n"); + return -1; + } + ret |= EC_TEST_CHECK_TK_PARSE(tk, -1, "bar", EC_TK_ENDLIST); + ret |= EC_TEST_CHECK_TK_PARSE(tk, 1, "foo", "bar", EC_TK_ENDLIST); + ret |= EC_TEST_CHECK_TK_PARSE(tk, 2, "foo", "foo", "bar", + EC_TK_ENDLIST); + ret |= EC_TEST_CHECK_TK_PARSE(tk, 2, "foo", "foo", "foo", + EC_TK_ENDLIST); + ret |= EC_TEST_CHECK_TK_PARSE(tk, -1, EC_TK_ENDLIST); + ec_tk_free(tk); + + /* test completion */ + /* XXX */ + + return ret; +} + +static struct ec_test ec_tk_many_test = { + .name = "many", + .test = ec_tk_many_testcase, +}; + +EC_REGISTER_TEST(ec_tk_many_test); diff --git a/lib/ecoli_tk_many.h b/lib/ecoli_tk_many.h new file mode 100644 index 0000000..ed32075 --- /dev/null +++ b/lib/ecoli_tk_many.h @@ -0,0 +1,37 @@ +/* + * 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_MANY_ +#define ECOLI_TK_MANY_ + +/* + * if min == max == 0, there is no limit + */ +struct ec_tk *ec_tk_many_new(const char *id, struct ec_tk *child, + unsigned int min, unsigned int max); + +#endif diff --git a/lib/ecoli_tk_option.c b/lib/ecoli_tk_option.c index 2b4e4cb..5427e19 100644 --- a/lib/ecoli_tk_option.c +++ b/lib/ecoli_tk_option.c @@ -33,39 +33,58 @@ #include #include +#include #include #include #include #include +struct ec_tk_option { + struct ec_tk gen; + struct ec_tk *child; +}; + static struct ec_parsed_tk *ec_tk_option_parse(const struct ec_tk *gen_tk, - const char *str) + const struct ec_strvec *strvec) { struct ec_tk_option *tk = (struct ec_tk_option *)gen_tk; - struct ec_parsed_tk *parsed_tk, *child_parsed_tk; + struct ec_parsed_tk *parsed_tk = NULL, *child_parsed_tk; + struct ec_strvec *match_strvec; - parsed_tk = ec_parsed_tk_new(gen_tk); + parsed_tk = ec_parsed_tk_new(); if (parsed_tk == NULL) - return NULL; + goto fail; - child_parsed_tk = ec_tk_parse(tk->child, str); - if (child_parsed_tk != NULL) { + child_parsed_tk = ec_tk_parse_tokens(tk->child, strvec); + if (child_parsed_tk == NULL) + goto fail; + + if (ec_parsed_tk_matches(child_parsed_tk)) { ec_parsed_tk_add_child(parsed_tk, child_parsed_tk); - parsed_tk->str = ec_strndup(child_parsed_tk->str, - strlen(child_parsed_tk->str)); + match_strvec = ec_strvec_dup(child_parsed_tk->strvec); } else { - parsed_tk->str = ec_strdup(""); + ec_parsed_tk_free(child_parsed_tk); + match_strvec = ec_strvec_new(); } + if (match_strvec == NULL) + goto fail; + + ec_parsed_tk_set_match(parsed_tk, gen_tk, match_strvec); + return parsed_tk; + + fail: + ec_parsed_tk_free(parsed_tk); + return NULL; } static struct ec_completed_tk *ec_tk_option_complete(const struct ec_tk *gen_tk, - const char *str) + const struct ec_strvec *strvec) { struct ec_tk_option *tk = (struct ec_tk_option *)gen_tk; - return ec_tk_complete(tk->child, str); + return ec_tk_complete_tokens(tk->child, strvec); } static void ec_tk_option_free_priv(struct ec_tk *gen_tk) @@ -76,6 +95,7 @@ static void ec_tk_option_free_priv(struct ec_tk *gen_tk) } static struct ec_tk_ops ec_tk_option_ops = { + .typename = "option", .parse = ec_tk_option_parse, .complete = ec_tk_option_complete, .free_priv = ec_tk_option_free_priv, @@ -90,8 +110,10 @@ struct ec_tk *ec_tk_option_new(const char *id, struct ec_tk *child) tk = (struct ec_tk_option *)ec_tk_new(id, &ec_tk_option_ops, sizeof(*tk)); - if (tk == NULL) + if (tk == NULL) { + ec_tk_free(child); return NULL; + } tk->child = child; @@ -108,9 +130,10 @@ static int ec_tk_option_testcase(void) 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", ""); + ret |= EC_TEST_CHECK_TK_PARSE(tk, 1, "foo", EC_TK_ENDLIST); + ret |= EC_TEST_CHECK_TK_PARSE(tk, 1, "foo", "bar", EC_TK_ENDLIST); + ret |= EC_TEST_CHECK_TK_PARSE(tk, 0, "bar", EC_TK_ENDLIST); + ret |= EC_TEST_CHECK_TK_PARSE(tk, 0, EC_TK_ENDLIST); ec_tk_free(tk); /* test completion */ @@ -119,11 +142,18 @@ static int ec_tk_option_testcase(void) 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); + ret |= EC_TEST_CHECK_TK_COMPLETE(tk, + "", EC_TK_ENDLIST, + "foo", EC_TK_ENDLIST, + "foo"); + ret |= EC_TEST_CHECK_TK_COMPLETE(tk, + "f", EC_TK_ENDLIST, + "oo", EC_TK_ENDLIST, + "oo"); + ret |= EC_TEST_CHECK_TK_COMPLETE(tk, + "b", EC_TK_ENDLIST, + EC_TK_ENDLIST, + ""); ec_tk_free(tk); return ret; diff --git a/lib/ecoli_tk_option.h b/lib/ecoli_tk_option.h index cadcc05..338749d 100644 --- a/lib/ecoli_tk_option.h +++ b/lib/ecoli_tk_option.h @@ -30,11 +30,6 @@ #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 900ee23..2438679 100644 --- a/lib/ecoli_tk_or.c +++ b/lib/ecoli_tk_or.c @@ -33,45 +33,61 @@ #include #include +#include #include #include #include #include +struct ec_tk_or { + struct ec_tk gen; + struct ec_tk **table; + unsigned int len; +}; + static struct ec_parsed_tk *ec_tk_or_parse(const struct ec_tk *gen_tk, - const char *str) + const struct ec_strvec *strvec) { struct ec_tk_or *tk = (struct ec_tk_or *)gen_tk; - struct ec_parsed_tk *parsed_tk, *child_parsed_tk; + struct ec_parsed_tk *parsed_tk, *child_parsed_tk = NULL; + struct ec_strvec *match_strvec; unsigned int i; - parsed_tk = ec_parsed_tk_new(gen_tk); + parsed_tk = ec_parsed_tk_new(); if (parsed_tk == NULL) - return NULL; + goto fail; for (i = 0; i < tk->len; i++) { - child_parsed_tk = ec_tk_parse(tk->table[i], str); - if (child_parsed_tk != NULL) + child_parsed_tk = ec_tk_parse_tokens(tk->table[i], strvec); + if (child_parsed_tk == NULL) + goto fail; + if (ec_parsed_tk_matches(child_parsed_tk)) break; + ec_parsed_tk_free(child_parsed_tk); + child_parsed_tk = NULL; } - if (child_parsed_tk == NULL) + /* no match */ + if (i == tk->len) + return parsed_tk; + + match_strvec = ec_strvec_dup(child_parsed_tk->strvec); + if (match_strvec == NULL) goto fail; + ec_parsed_tk_set_match(parsed_tk, gen_tk, match_strvec); ec_parsed_tk_add_child(parsed_tk, child_parsed_tk); - parsed_tk->str = ec_strndup(child_parsed_tk->str, - strlen(child_parsed_tk->str)); - return parsed_tk; fail: + ec_parsed_tk_free(child_parsed_tk); ec_parsed_tk_free(parsed_tk); return NULL; } static struct ec_completed_tk *ec_tk_or_complete(const struct ec_tk *gen_tk, - const char *str) + const struct ec_strvec *strvec) { struct ec_tk_or *tk = (struct ec_tk_or *)gen_tk; struct ec_completed_tk *completed_tk, *child_completed_tk; @@ -82,9 +98,10 @@ static struct ec_completed_tk *ec_tk_or_complete(const struct ec_tk *gen_tk, return NULL; for (n = 0; n < tk->len; n++) { - child_completed_tk = ec_tk_complete(tk->table[n], str); + child_completed_tk = ec_tk_complete_tokens(tk->table[n], + strvec); - if (child_completed_tk == NULL) + if (child_completed_tk == NULL) // XXX fail instead? continue; ec_completed_tk_merge(completed_tk, child_completed_tk); @@ -104,6 +121,7 @@ static void ec_tk_or_free_priv(struct ec_tk *gen_tk) } static struct ec_tk_ops ec_tk_or_ops = { + .typename = "or", .parse = ec_tk_or_parse, .complete = ec_tk_or_complete, .free_priv = ec_tk_or_free_priv, @@ -128,6 +146,7 @@ struct ec_tk *ec_tk_or_new_list(const char *id, ...) struct ec_tk_or *tk = NULL; struct ec_tk *child; va_list ap; + int fail = 0; va_start(ap, id); @@ -138,12 +157,15 @@ struct ec_tk *ec_tk_or_new_list(const char *id, ...) for (child = va_arg(ap, struct ec_tk *); child != EC_TK_ENDLIST; child = va_arg(ap, struct ec_tk *)) { - if (child == NULL) - goto fail; + /* on error, don't quit the loop to avoid leaks */ - ec_tk_or_add(&tk->gen, child); + if (child == NULL || ec_tk_or_add(&tk->gen, child) < 0) + fail = 1; } + if (fail == 1) + goto fail; + va_end(ap); return &tk->gen; @@ -185,10 +207,13 @@ static int ec_tk_or_testcase(void) ec_log(EC_LOG_ERR, "cannot create tk\n"); return -1; } - ret |= EC_TEST_CHECK_TK_PARSE(tk, "foo", "foo"); - ret |= EC_TEST_CHECK_TK_PARSE(tk, "fooxxx", "foo"); - ret |= EC_TEST_CHECK_TK_PARSE(tk, "bar", "bar"); - ret |= EC_TEST_CHECK_TK_PARSE(tk, "oo", NULL); + ret |= EC_TEST_CHECK_TK_PARSE(tk, 1, "foo", EC_TK_ENDLIST); + ret |= EC_TEST_CHECK_TK_PARSE(tk, 1, "bar", EC_TK_ENDLIST); + ret |= EC_TEST_CHECK_TK_PARSE(tk, 1, "foo", "bar", EC_TK_ENDLIST); + ret |= EC_TEST_CHECK_TK_PARSE(tk, -1, " ", EC_TK_ENDLIST); + ret |= EC_TEST_CHECK_TK_PARSE(tk, -1, "foox", EC_TK_ENDLIST); + ret |= EC_TEST_CHECK_TK_PARSE(tk, -1, "toto", EC_TK_ENDLIST); + ret |= EC_TEST_CHECK_TK_PARSE(tk, -1, "", EC_TK_ENDLIST); ec_tk_free(tk); /* test completion */ @@ -203,20 +228,34 @@ static int ec_tk_or_testcase(void) 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, "f", "oo"); - 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", ""); - 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", - "oo", EC_TK_ENDLIST); - ret |= EC_TEST_CHECK_TK_COMPLETE_LIST(tk, "b", - "ar", "ar2", EC_TK_ENDLIST); - ret |= EC_TEST_CHECK_TK_COMPLETE_LIST(tk, "t", - "oto", "iti", EC_TK_ENDLIST); + ret |= EC_TEST_CHECK_TK_COMPLETE(tk, + "", EC_TK_ENDLIST, + "foo", "bar", "bar2", "toto", "titi", EC_TK_ENDLIST, + ""); + ret |= EC_TEST_CHECK_TK_COMPLETE(tk, + "f", EC_TK_ENDLIST, + "oo", EC_TK_ENDLIST, + "oo"); + ret |= EC_TEST_CHECK_TK_COMPLETE(tk, + "b", EC_TK_ENDLIST, + "ar", "ar2", EC_TK_ENDLIST, + "ar"); + ret |= EC_TEST_CHECK_TK_COMPLETE(tk, + "bar", EC_TK_ENDLIST, + "", "2", EC_TK_ENDLIST, + ""); + ret |= EC_TEST_CHECK_TK_COMPLETE(tk, + "t", EC_TK_ENDLIST, + "oto", "iti", EC_TK_ENDLIST, + ""); + ret |= EC_TEST_CHECK_TK_COMPLETE(tk, + "to", EC_TK_ENDLIST, + "to", EC_TK_ENDLIST, + "to"); + ret |= EC_TEST_CHECK_TK_COMPLETE(tk, + "x", EC_TK_ENDLIST, + EC_TK_ENDLIST, + ""); ec_tk_free(tk); return ret; diff --git a/lib/ecoli_tk_or.h b/lib/ecoli_tk_or.h index a04c6a3..07d4cea 100644 --- a/lib/ecoli_tk_or.h +++ b/lib/ecoli_tk_or.h @@ -28,16 +28,8 @@ #ifndef ECOLI_TK_OR_ #define ECOLI_TK_OR_ -#include - #include -struct ec_tk_or { - struct ec_tk gen; - struct ec_tk **table; - unsigned int len; -}; - struct ec_tk *ec_tk_or_new(const char *id); /* list must be terminated with EC_TK_ENDLIST */ diff --git a/lib/ecoli_tk_seq.c b/lib/ecoli_tk_seq.c index 7f840c0..d7bf998 100644 --- a/lib/ecoli_tk_seq.c +++ b/lib/ecoli_tk_seq.c @@ -34,33 +34,54 @@ #include #include #include +#include #include #include #include #include +struct ec_tk_seq { + struct ec_tk gen; + struct ec_tk **table; + unsigned int len; +}; + static struct ec_parsed_tk *ec_tk_seq_parse(const struct ec_tk *gen_tk, - const char *str) + const struct ec_strvec *strvec) { struct ec_tk_seq *tk = (struct ec_tk_seq *)gen_tk; struct ec_parsed_tk *parsed_tk, *child_parsed_tk; + struct ec_strvec *match_strvec; + struct ec_strvec childvec; size_t len = 0; unsigned int i; - parsed_tk = ec_parsed_tk_new(gen_tk); + parsed_tk = ec_parsed_tk_new(); if (parsed_tk == NULL) - return NULL; + goto fail; for (i = 0; i < tk->len; i++) { - child_parsed_tk = ec_tk_parse(tk->table[i], str + len); + if (ec_strvec_slice(&childvec, strvec, len) < 0) + goto fail; + + child_parsed_tk = ec_tk_parse_tokens(tk->table[i], &childvec); if (child_parsed_tk == NULL) goto fail; + if (!ec_parsed_tk_matches(child_parsed_tk)) { + ec_parsed_tk_free(child_parsed_tk); + ec_parsed_tk_free_children(parsed_tk); + return parsed_tk; + } - len += strlen(child_parsed_tk->str); ec_parsed_tk_add_child(parsed_tk, child_parsed_tk); + len += ec_parsed_tk_len(child_parsed_tk); } - parsed_tk->str = ec_strndup(str, len); + match_strvec = ec_strvec_ndup(strvec, len); + if (match_strvec == NULL) + goto fail; + + ec_parsed_tk_set_match(parsed_tk, gen_tk, match_strvec); return parsed_tk; @@ -70,10 +91,11 @@ static struct ec_parsed_tk *ec_tk_seq_parse(const struct ec_tk *gen_tk, } static struct ec_completed_tk *ec_tk_seq_complete(const struct ec_tk *gen_tk, - const char *str) + const struct ec_strvec *strvec) { struct ec_tk_seq *tk = (struct ec_tk_seq *)gen_tk; struct ec_completed_tk *completed_tk, *child_completed_tk; + struct ec_strvec childvec; struct ec_parsed_tk *parsed_tk; size_t len = 0; unsigned int i; @@ -85,23 +107,35 @@ static struct ec_completed_tk *ec_tk_seq_complete(const struct ec_tk *gen_tk, if (tk->len == 0) return completed_tk; - for (i = 0; i < tk->len; i++) { - child_completed_tk = ec_tk_complete(tk->table[i], str + len); + for (i = 0; i < tk->len && len < ec_strvec_len(strvec); i++) { + if (ec_strvec_slice(&childvec, strvec, len) < 0) + goto fail; + + child_completed_tk = ec_tk_complete_tokens(tk->table[i], + &childvec); if (child_completed_tk == NULL) { ec_completed_tk_free(completed_tk); return NULL; } ec_completed_tk_merge(completed_tk, child_completed_tk); - parsed_tk = ec_tk_parse(tk->table[i], str + len); + parsed_tk = ec_tk_parse_tokens(tk->table[i], &childvec); if (parsed_tk == NULL) + goto fail; + if (!ec_parsed_tk_matches(parsed_tk)) { + ec_parsed_tk_free(parsed_tk); break; + } - len += strlen(parsed_tk->str); + len += ec_strvec_len(parsed_tk->strvec); ec_parsed_tk_free(parsed_tk); } return completed_tk; + +fail: + /* XXX */ + return NULL; } static void ec_tk_seq_free_priv(struct ec_tk *gen_tk) @@ -115,6 +149,7 @@ static void ec_tk_seq_free_priv(struct ec_tk *gen_tk) } static struct ec_tk_ops ec_tk_seq_ops = { + .typename = "seq", .parse = ec_tk_seq_parse, .complete = ec_tk_seq_complete, .free_priv = ec_tk_seq_free_priv, @@ -139,6 +174,7 @@ struct ec_tk *ec_tk_seq_new_list(const char *id, ...) struct ec_tk_seq *tk = NULL; struct ec_tk *child; va_list ap; + int fail = 0; va_start(ap, id); @@ -149,12 +185,15 @@ struct ec_tk *ec_tk_seq_new_list(const char *id, ...) for (child = va_arg(ap, struct ec_tk *); child != EC_TK_ENDLIST; child = va_arg(ap, struct ec_tk *)) { - if (child == NULL) - goto fail; + /* on error, don't quit the loop to avoid leaks */ - ec_tk_seq_add(&tk->gen, child); + if (child == NULL || ec_tk_seq_add(&tk->gen, child) < 0) + fail = 1; } + if (fail == 1) + goto fail; + va_end(ap); return &tk->gen; @@ -198,11 +237,14 @@ static int ec_tk_seq_testcase(void) ec_log(EC_LOG_ERR, "cannot create tk\n"); return -1; } - ret |= EC_TEST_CHECK_TK_PARSE(tk, "foobar", "foobar"); - ret |= EC_TEST_CHECK_TK_PARSE(tk, "foobarxxx", "foobar"); - ret |= EC_TEST_CHECK_TK_PARSE(tk, " foobar", NULL); - ret |= EC_TEST_CHECK_TK_PARSE(tk, "foo", NULL); - ret |= EC_TEST_CHECK_TK_PARSE(tk, "bar", NULL); + ret |= EC_TEST_CHECK_TK_PARSE(tk, 2, "foo", "bar", EC_TK_ENDLIST); + ret |= EC_TEST_CHECK_TK_PARSE(tk, 2, "foo", "bar", "toto", + EC_TK_ENDLIST); + ret |= EC_TEST_CHECK_TK_PARSE(tk, -1, "foo", EC_TK_ENDLIST); + ret |= EC_TEST_CHECK_TK_PARSE(tk, -1, "foox", "bar", EC_TK_ENDLIST); + ret |= EC_TEST_CHECK_TK_PARSE(tk, -1, "foo", "barx", EC_TK_ENDLIST); + ret |= EC_TEST_CHECK_TK_PARSE(tk, -1, "bar", "foo", EC_TK_ENDLIST); + ret |= EC_TEST_CHECK_TK_PARSE(tk, -1, "", "foo", EC_TK_ENDLIST); ec_tk_free(tk); /* test completion */ @@ -215,16 +257,42 @@ static int ec_tk_seq_testcase(void) 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, "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", ""); - ret |= EC_TEST_CHECK_TK_COMPLETE(tk, "foobarx", ""); + ret |= EC_TEST_CHECK_TK_COMPLETE(tk, + "", EC_TK_ENDLIST, + "foo", EC_TK_ENDLIST, + "foo"); + ret |= EC_TEST_CHECK_TK_COMPLETE(tk, + "f", EC_TK_ENDLIST, + "oo", EC_TK_ENDLIST, + "oo"); + ret |= EC_TEST_CHECK_TK_COMPLETE(tk, + "foo", EC_TK_ENDLIST, + "", EC_TK_ENDLIST, + ""); + ret |= EC_TEST_CHECK_TK_COMPLETE(tk, + "foo", "", EC_TK_ENDLIST, + "bar", "toto", EC_TK_ENDLIST, + ""); + ret |= EC_TEST_CHECK_TK_COMPLETE(tk, + "foo", "t", EC_TK_ENDLIST, + "oto", EC_TK_ENDLIST, + "oto"); + ret |= EC_TEST_CHECK_TK_COMPLETE(tk, + "foo", "b", EC_TK_ENDLIST, + "ar", EC_TK_ENDLIST, + "ar"); + ret |= EC_TEST_CHECK_TK_COMPLETE(tk, + "foo", "bar", EC_TK_ENDLIST, + "", EC_TK_ENDLIST, + ""); + ret |= EC_TEST_CHECK_TK_COMPLETE(tk, + "x", EC_TK_ENDLIST, + EC_TK_ENDLIST, + ""); + ret |= EC_TEST_CHECK_TK_COMPLETE(tk, + "foobarx", EC_TK_ENDLIST, + EC_TK_ENDLIST, + ""); ec_tk_free(tk); return ret; diff --git a/lib/ecoli_tk_seq.h b/lib/ecoli_tk_seq.h index 5c7bfed..2385da2 100644 --- a/lib/ecoli_tk_seq.h +++ b/lib/ecoli_tk_seq.h @@ -28,16 +28,8 @@ #ifndef ECOLI_TK_SEQ_ #define ECOLI_TK_SEQ_ -#include - #include -struct ec_tk_seq { - struct ec_tk gen; - struct ec_tk **table; - unsigned int len; -}; - struct ec_tk *ec_tk_seq_new(const char *id); /* list must be terminated with EC_TK_ENDLIST */ diff --git a/lib/ecoli_tk_shlex.c b/lib/ecoli_tk_shlex.c index 3384557..ba6d20a 100644 --- a/lib/ecoli_tk_shlex.c +++ b/lib/ecoli_tk_shlex.c @@ -36,261 +36,287 @@ #include #include #include +#include #include +#include #include #include #include -static int isend(char c) +struct ec_tk_shlex { + struct ec_tk gen; + struct ec_tk *child; +}; + +static size_t eat_spaces(const char *str) { - if (c == '\0' || c == '#' || c == '\n' || c == '\r') - return 1; - return 0; + size_t i = 0; + + /* skip spaces */ + while (isblank(str[i])) + i++; + + return i; } -/* 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) +/* + * Allocate a new string which is a copy of the input string with quotes + * removed. If quotes are not closed properly, set missing_quote to the + * missing quote char. + */ +static char *unquote_str(const char *str, size_t n, int allow_missing_quote, + char *missing_quote) { - unsigned s = 0, d = 0, dstlen; - int quoted = 0; + unsigned s = 1, d = 0; + char quote = str[0]; char *dst; + int closed = 0; - 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; + dst = ec_malloc(n); + if (dst == NULL) { + errno = ENOMEM; + return NULL; } /* 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++] = '"'; + while (s < n && d < n && str[s] != '\0') { + if (str[s] == '\\' && str[s+1] == quote) { + dst[d++] = quote; s += 2; continue; } - if (src[s] == '\\' && src[s+1] == '\\') { + if (str[s] == '\\' && str[s+1] == '\\') { dst[d++] = '\\'; s += 2; continue; } - if (src[s] == '"') { + if (str[s] == quote) { s++; - quoted = !quoted; - continue; + closed = 1; + break; } - dst[d++] = src[s++]; + dst[d++] = str[s++]; } - /* not enough room in dst buffer */ - if (d >= dstlen) { + /* not enough room in dst buffer (should not happen) */ + if (d >= n) { ec_free(dst); - return -EMSGSIZE; + errno = EMSGSIZE; + return NULL; } - /* end of string during quote */ - if (quoted) { - ec_free(dst); - return -EINVAL; + /* quote not closed */ + if (closed == 0) { + if (missing_quote != NULL) + *missing_quote = str[0]; + if (allow_missing_quote == 0) { + ec_free(dst); + errno = EINVAL; + return NULL; + } } - dst[d++] = '\0'; - *p_dst = dst; - return s; + + return dst; } -static int safe_realloc(void *arg, size_t size) +static size_t eat_quoted_str(const char *str) { - void **pptr = arg; - void *new_ptr = ec_realloc(*pptr, size); + size_t i = 0; + char quote = str[0]; - if (new_ptr == NULL) - return -1; - *pptr = new_ptr; - return 0; + while (str[i] != '\0') { + if (str[i] != '\\' && str[i+1] == quote) + return i + 2; + i++; + } + + /* unclosed quote, will be detected later */ + return i; } -static char **tokenize(const char *str, int add_empty) +static size_t eat_str(const char *str) { - char **table = NULL, *token; - unsigned i, count = 1, off = 0; - int ret; + size_t i = 0; - if (safe_realloc(&table, sizeof(char *)) < 0) - return NULL; + /* skip spaces */ + while (!isblank(str[i]) && str[i] != '\0') + i++; - table[0] = NULL; + return i; +} - while (1) { - ret = get_token(str + off, &token); - if (ret == -ENOENT) - break; - else if (ret < 0) - goto fail; +static struct ec_strvec *tokenize(const char *str, int completion, + int allow_missing_quote, char *missing_quote) +{ + struct ec_strvec *strvec = NULL; + size_t off = 0, len, suboff, sublen; + char *word = NULL, *concat = NULL, *tmp; + int last_is_space = 1; - off += ret; - count++; - if (safe_realloc(&table, sizeof(char *) * count) < 0) - goto fail; - table[count - 2] = token; - table[count - 1] = NULL; - } +// printf("str=%s\n", str); - if (add_empty && (off != strlen(str) || strlen(str) == 0)) { - token = ec_strdup(""); - if (token == NULL) - goto fail; + strvec = ec_strvec_new(); + if (strvec == NULL) + goto fail; - count++; - if (safe_realloc(&table, sizeof(char *) * count) < 0) + while (str[off] != '\0') { + len = eat_spaces(&str[off]); + if (len > 0) + last_is_space = 1; +// printf("space=%zd\n", len); + off += len; + + len = 0; + suboff = off; + while (str[suboff] != '\0') { + last_is_space = 0; + if (str[suboff] == '"' || str[suboff] == '\'') { + sublen = eat_quoted_str(&str[suboff]); +// printf("sublen=%zd\n", sublen); + word = unquote_str(&str[suboff], sublen, + allow_missing_quote, missing_quote); + } else { + sublen = eat_str(&str[suboff]); +// printf("sublen=%zd\n", sublen); + if (sublen == 0) + break; + word = ec_strndup(&str[suboff], sublen); + } + + if (word == NULL) + goto fail; +// printf("word=%s\n", word); + + len += sublen; + suboff += sublen; + + if (concat == NULL) { + concat = word; + word = NULL; + } else { + tmp = ec_realloc(concat, len + 1); + if (tmp == NULL) + goto fail; + concat = tmp; + strcat(concat, word); + ec_free(word); + word = NULL; + } + } + + if (concat != NULL) { + if (ec_strvec_add(strvec, concat) < 0) + goto fail; + ec_free(concat); + concat = NULL; + } + +// printf("str off=%zd len=%zd\n", off, len); + off += len; + } + + /* in completion mode, append an empty token if the string ends + * with space */ + if (completion && last_is_space) { + if (ec_strvec_add(strvec, "") < 0) goto fail; - table[count - 2] = token; - table[count - 1] = NULL; } - return table; + return strvec; fail: - for (i = 0; i < count; i++) - ec_free(table[i]); - ec_free(table); + ec_free(word); + ec_free(concat); + ec_strvec_free(strvec); return NULL; } -/* XXX broken: how to support that: - shlex( - str("toto"), - many(str("titi")), - ) - - that would match: - toto - toto titi - toto titi titi ... - - --> maybe we should not try to create/match the spaces automatically - - it would become: - - shlex( - option(space()), auto? - str("toto"), - many( - space(), - str("titi coin"), - ), - option(space()), auto? - ) - - -> the goal of shlex would only be to unquote - -> the creation of auto-spaces would be in another token shcmd - - cmd = shcmd_new() - shcmd_add_tk("ip", tk_ip_new()) - shcmd_set_syntax("show ") - - - */ static struct ec_parsed_tk *ec_tk_shlex_parse(const struct ec_tk *gen_tk, - const char *str) + const struct ec_strvec *strvec) { 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; + struct ec_strvec *new_vec = NULL, *match_strvec; + struct ec_parsed_tk *parsed_tk = NULL, *child_parsed_tk; + const char *str; - parsed_tk = ec_parsed_tk_new(gen_tk); + parsed_tk = ec_parsed_tk_new(); 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; + if (ec_strvec_len(strvec) == 0) + return parsed_tk; - 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; - } + str = ec_strvec_val(strvec, 0); + new_vec = tokenize(str, 0, 0, NULL); + if (new_vec == NULL) + goto fail; - /* check it was the last token */ - if (*t != NULL) + child_parsed_tk = ec_tk_parse_tokens(tk->child, new_vec); + if (child_parsed_tk == NULL) goto fail; - if (tokens != NULL) { - for (t = &tokens[0]; *t != NULL; t++) - ec_free(*t); - ec_free(tokens); - tokens = NULL; + if (!ec_parsed_tk_matches(child_parsed_tk) || + ec_parsed_tk_len(child_parsed_tk) != + ec_strvec_len(new_vec)) { + ec_strvec_free(new_vec); + ec_parsed_tk_free(child_parsed_tk); + return parsed_tk; } + ec_strvec_free(new_vec); + new_vec = NULL; - parsed_tk->str = ec_strdup(str); + ec_parsed_tk_add_child(parsed_tk, child_parsed_tk); + match_strvec = ec_strvec_ndup(strvec, 1); + if (match_strvec == NULL) + goto fail; + ec_parsed_tk_set_match(parsed_tk, gen_tk, match_strvec); return parsed_tk; fail: - if (tokens != NULL) { - for (t = &tokens[0]; *t != NULL; t++) - ec_free(*t); - ec_free(tokens); - } + ec_strvec_free(new_vec); 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) + const struct ec_strvec *strvec) { 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; + struct ec_strvec *new_vec = NULL; + const char *str; + char missing_quote; + +// printf("==================\n"); + completed_tk = ec_completed_tk_new(); + if (completed_tk == NULL) + return NULL; - tokens = tokenize(str, 1); - if (tokens == NULL) + if (ec_strvec_len(strvec) != 1) + return completed_tk; + + str = ec_strvec_val(strvec, 0); + new_vec = tokenize(str, 1, 1, &missing_quote); + if (new_vec == NULL) goto fail; - printf("complete <%s>\n", str); - for (t = &tokens[0]; *t != NULL; t++) - printf(" token <%s> %p\n", *t, *t); +// ec_strvec_dump(new_vec, stdout); - t = &tokens[0]; + child_completed_tk = ec_tk_complete_tokens(tk->child, new_vec); + if (child_completed_tk == NULL) + goto fail; + + ec_strvec_free(new_vec); + new_vec = NULL; + ec_completed_tk_merge(completed_tk, child_completed_tk); + + return completed_tk; - completed_tk = ec_completed_tk_new(); - if (completed_tk == NULL) - return NULL; +#if 0 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); @@ -329,99 +355,45 @@ static struct ec_completed_tk *ec_tk_shlex_complete(const struct ec_tk *gen_tk, } ec_completed_tk_dump(stdout, completed_tk); - - return completed_tk; +#endif fail: - if (tokens != NULL) { - for (t = &tokens[0]; *t != NULL; t++) - ec_free(*t); - ec_free(tokens); - } + ec_strvec_free(new_vec); 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); + ec_tk_free(tk->child); } static struct ec_tk_ops ec_tk_shlex_ops = { + .typename = "shlex", .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 *ec_tk_shlex_new(const char *id, struct ec_tk *child) { struct ec_tk_shlex *tk = NULL; - tk = (struct ec_tk_shlex *)ec_tk_new(id, &ec_tk_shlex_ops, sizeof(*tk)); - if (tk == NULL) + if (child == 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); + tk = (struct ec_tk_shlex *)ec_tk_new(id, &ec_tk_shlex_ops, + sizeof(*tk)); + if (tk == NULL) { + ec_tk_free(child); + return NULL; } - va_end(ap); - return &tk->gen; + tk->child = child; -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; + return &tk->gen; } static int ec_tk_shlex_testcase(void) @@ -429,52 +401,93 @@ 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); + tk = ec_tk_shlex_new(NULL, + 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) { 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); + ret |= EC_TEST_CHECK_TK_PARSE(tk, 1, "foo bar", EC_TK_ENDLIST); + ret |= EC_TEST_CHECK_TK_PARSE(tk, 1, " foo bar", EC_TK_ENDLIST); + ret |= EC_TEST_CHECK_TK_PARSE(tk, 1, " 'foo' \"bar\"", EC_TK_ENDLIST); + ret |= EC_TEST_CHECK_TK_PARSE(tk, 1, " 'f'oo 'toto' bar", + EC_TK_ENDLIST); 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); + tk = ec_tk_shlex_new(NULL, + 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_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); + ret |= EC_TEST_CHECK_TK_COMPLETE(tk, + "", EC_TK_ENDLIST, + "foo", EC_TK_ENDLIST, + "foo"); + ret |= EC_TEST_CHECK_TK_COMPLETE(tk, + " ", EC_TK_ENDLIST, + "foo", EC_TK_ENDLIST, + "foo"); + ret |= EC_TEST_CHECK_TK_COMPLETE(tk, + "f", EC_TK_ENDLIST, + "oo", EC_TK_ENDLIST, + "oo"); + ret |= EC_TEST_CHECK_TK_COMPLETE(tk, + "foo", EC_TK_ENDLIST, + "", EC_TK_ENDLIST, + ""); + ret |= EC_TEST_CHECK_TK_COMPLETE(tk, + "foo ", EC_TK_ENDLIST, + "bar", "toto", EC_TK_ENDLIST, + ""); + ret |= EC_TEST_CHECK_TK_COMPLETE(tk, + "foo t", EC_TK_ENDLIST, + "oto", EC_TK_ENDLIST, + "oto"); + ret |= EC_TEST_CHECK_TK_COMPLETE(tk, + "foo b", EC_TK_ENDLIST, + "ar", EC_TK_ENDLIST, + "ar"); + ret |= EC_TEST_CHECK_TK_COMPLETE(tk, + "foo bar", EC_TK_ENDLIST, + "", EC_TK_ENDLIST, + ""); + ret |= EC_TEST_CHECK_TK_COMPLETE(tk, + "foo bar ", EC_TK_ENDLIST, + "titi", EC_TK_ENDLIST, + "titi"); + ret |= EC_TEST_CHECK_TK_COMPLETE(tk, + "foo toto bar ", EC_TK_ENDLIST, + "titi", EC_TK_ENDLIST, + "titi"); + ret |= EC_TEST_CHECK_TK_COMPLETE(tk, + "x", EC_TK_ENDLIST, + EC_TK_ENDLIST, + ""); + ret |= EC_TEST_CHECK_TK_COMPLETE(tk, + "foo barx", EC_TK_ENDLIST, + EC_TK_ENDLIST, + ""); + ec_tk_free(tk); return ret; } diff --git a/lib/ecoli_tk_shlex.h b/lib/ecoli_tk_shlex.h index 52bc6a7..5aa2246 100644 --- a/lib/ecoli_tk_shlex.h +++ b/lib/ecoli_tk_shlex.h @@ -28,21 +28,8 @@ #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); +struct ec_tk *ec_tk_shlex_new(const char *id, struct ec_tk *child); #endif diff --git a/lib/ecoli_tk_space.c b/lib/ecoli_tk_space.c index 6c69b3b..8ec1650 100644 --- a/lib/ecoli_tk_space.c +++ b/lib/ecoli_tk_space.c @@ -33,32 +33,52 @@ #include #include #include +#include #include #include +struct ec_tk_space { + struct ec_tk gen; +}; + static struct ec_parsed_tk *ec_tk_space_parse(const struct ec_tk *gen_tk, - const char *str) + const struct ec_strvec *strvec) { - struct ec_parsed_tk *parsed_tk; + struct ec_parsed_tk *parsed_tk = NULL; + struct ec_strvec *match_strvec; + const char *str; size_t len = 0; - if (!isspace(str[0])) - return NULL; - - parsed_tk = ec_parsed_tk_new(gen_tk); + parsed_tk = ec_parsed_tk_new(); if (parsed_tk == NULL) - return NULL; + goto fail; + if (ec_strvec_len(strvec) == 0) + return parsed_tk; + + str = ec_strvec_val(strvec, 0); while (isspace(str[len])) len++; + if (len == 0 || len != strlen(str)) + return parsed_tk; + + match_strvec = ec_strvec_ndup(strvec, 1); + if (match_strvec == NULL) + goto fail; - parsed_tk->str = ec_strndup(str, len); + ec_parsed_tk_set_match(parsed_tk, gen_tk, match_strvec); return parsed_tk; + + fail: + ec_parsed_tk_free(parsed_tk); + return NULL; } static struct ec_tk_ops ec_tk_space_ops = { + .typename = "space", .parse = ec_tk_space_parse, + .complete = ec_tk_default_complete, }; struct ec_tk *ec_tk_space_new(const char *id) @@ -76,11 +96,11 @@ static int ec_tk_space_testcase(void) 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); + ret |= EC_TEST_CHECK_TK_PARSE(tk, 1, " ", EC_TK_ENDLIST); + ret |= EC_TEST_CHECK_TK_PARSE(tk, 1, " ", "foo", EC_TK_ENDLIST); + ret |= EC_TEST_CHECK_TK_PARSE(tk, -1, "", EC_TK_ENDLIST); + ret |= EC_TEST_CHECK_TK_PARSE(tk, -1, " foo", EC_TK_ENDLIST); + ret |= EC_TEST_CHECK_TK_PARSE(tk, -1, "foo ", EC_TK_ENDLIST); ec_tk_free(tk); /* test completion */ @@ -89,9 +109,18 @@ static int ec_tk_space_testcase(void) 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", ""); + ret |= EC_TEST_CHECK_TK_COMPLETE(tk, + "", EC_TK_ENDLIST, + EC_TK_ENDLIST, + ""); + ret |= EC_TEST_CHECK_TK_COMPLETE(tk, + " ", EC_TK_ENDLIST, + EC_TK_ENDLIST, + ""); + ret |= EC_TEST_CHECK_TK_COMPLETE(tk, + "foo", EC_TK_ENDLIST, + EC_TK_ENDLIST, + ""); ec_tk_free(tk); return ret; diff --git a/lib/ecoli_tk_space.h b/lib/ecoli_tk_space.h index 6327c8a..08aa48f 100644 --- a/lib/ecoli_tk_space.h +++ b/lib/ecoli_tk_space.h @@ -30,10 +30,6 @@ #include -struct ec_tk_space { - struct ec_tk gen; -}; - struct ec_tk *ec_tk_space_new(const char *id); #endif diff --git a/lib/ecoli_tk_str.c b/lib/ecoli_tk_str.c index 61953ef..4fe3062 100644 --- a/lib/ecoli_tk_str.c +++ b/lib/ecoli_tk_str.c @@ -32,52 +32,78 @@ #include #include #include +#include #include #include +struct ec_tk_str { + struct ec_tk gen; + char *string; + unsigned len; +}; + static struct ec_parsed_tk *ec_tk_str_parse(const struct ec_tk *gen_tk, - const char *str) + const struct ec_strvec *strvec) { struct ec_tk_str *tk = (struct ec_tk_str *)gen_tk; - struct ec_parsed_tk *parsed_tk; + struct ec_strvec *match_strvec; + struct ec_parsed_tk *parsed_tk = NULL; + const char *str; - if (strncmp(str, tk->string, tk->len) != 0) - return NULL; - - parsed_tk = ec_parsed_tk_new(gen_tk); + parsed_tk = ec_parsed_tk_new(); if (parsed_tk == NULL) - return NULL; + goto fail; + + if (ec_strvec_len(strvec) == 0) + return parsed_tk; - parsed_tk->str = ec_strndup(str, tk->len); + str = ec_strvec_val(strvec, 0); + if (strcmp(str, tk->string) != 0) + return parsed_tk; + + match_strvec = ec_strvec_ndup(strvec, 1); + if (match_strvec == NULL) + goto fail; + + ec_parsed_tk_set_match(parsed_tk, gen_tk, match_strvec); return parsed_tk; + + fail: + ec_parsed_tk_free(parsed_tk); + return NULL; } static struct ec_completed_tk *ec_tk_str_complete(const struct ec_tk *gen_tk, - const char *str) + const struct ec_strvec *strvec) { struct ec_tk_str *tk = (struct ec_tk_str *)gen_tk; struct ec_completed_tk *completed_tk; struct ec_completed_tk_elt *completed_tk_elt; - size_t n; + const char *str, *add; + size_t n = 0; 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 completed_tk; - if (tk->string[n] == '\0') + if (ec_strvec_len(strvec) > 1) return completed_tk; - completed_tk_elt = ec_completed_tk_elt_new(gen_tk, tk->string + n, - tk->string); + if (ec_strvec_len(strvec) == 1) { + str = ec_strvec_val(strvec, 0); + for (n = 0; n < tk->len; n++) { + if (str[n] != tk->string[n]) + break; + } + + if (str[n] != '\0') + add = NULL; + else + add = tk->string + n; + } + + completed_tk_elt = ec_completed_tk_elt_new(gen_tk, add); if (completed_tk_elt == NULL) { ec_completed_tk_free(completed_tk); return NULL; @@ -95,7 +121,8 @@ static void ec_tk_str_free_priv(struct ec_tk *gen_tk) ec_free(tk->string); } -static struct ec_tk_ops ec_tk_str_ops = { +static const struct ec_tk_ops ec_tk_str_ops = { + .typename = "str", .parse = ec_tk_str_parse, .complete = ec_tk_str_complete, .free_priv = ec_tk_str_free_priv, @@ -135,11 +162,11 @@ static int ec_tk_str_testcase(void) ec_log(EC_LOG_ERR, "cannot create tk\n"); return -1; } - ret |= EC_TEST_CHECK_TK_PARSE(tk, "foo", "foo"); - ret |= EC_TEST_CHECK_TK_PARSE(tk, "foobar", "foo"); - ret |= EC_TEST_CHECK_TK_PARSE(tk, " foo", NULL); - ret |= EC_TEST_CHECK_TK_PARSE(tk, "", NULL); - ret |= EC_TEST_CHECK_TK_PARSE(tk, "foo", "foo"); + ret |= EC_TEST_CHECK_TK_PARSE(tk, 1, "foo", EC_TK_ENDLIST); + ret |= EC_TEST_CHECK_TK_PARSE(tk, 1, "foo", "bar", EC_TK_ENDLIST); + ret |= EC_TEST_CHECK_TK_PARSE(tk, -1, "foobar", EC_TK_ENDLIST); + ret |= EC_TEST_CHECK_TK_PARSE(tk, -1, " foo", EC_TK_ENDLIST); + ret |= EC_TEST_CHECK_TK_PARSE(tk, -1, "", EC_TK_ENDLIST); ec_tk_free(tk); tk = ec_tk_str_new(NULL, "Здравствуйте"); @@ -147,10 +174,11 @@ static int ec_tk_str_testcase(void) 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, "Здравствуйте John!", "Здравствуйте"); - ret |= EC_TEST_CHECK_TK_PARSE(tk, "foo", NULL); - ret |= EC_TEST_CHECK_TK_PARSE(tk, "", NULL); + ret |= EC_TEST_CHECK_TK_PARSE(tk, 1, "Здравствуйте", EC_TK_ENDLIST); + ret |= EC_TEST_CHECK_TK_PARSE(tk, 1, "Здравствуйте", + "John!", EC_TK_ENDLIST); + ret |= EC_TEST_CHECK_TK_PARSE(tk, -1, "foo", EC_TK_ENDLIST); + ret |= EC_TEST_CHECK_TK_PARSE(tk, -1, "", EC_TK_ENDLIST); ec_tk_free(tk); /* an empty token string always matches */ @@ -159,8 +187,9 @@ static int ec_tk_str_testcase(void) 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", ""); + ret |= EC_TEST_CHECK_TK_PARSE(tk, 1, "", EC_TK_ENDLIST); + ret |= EC_TEST_CHECK_TK_PARSE(tk, 1, "", "foo", EC_TK_ENDLIST); + ret |= EC_TEST_CHECK_TK_PARSE(tk, -1, "foo", EC_TK_ENDLIST); ec_tk_free(tk); /* test completion */ @@ -169,10 +198,22 @@ static int ec_tk_str_testcase(void) 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, "foo", ""); - ret |= EC_TEST_CHECK_TK_COMPLETE(tk, "x", ""); + ret |= EC_TEST_CHECK_TK_COMPLETE(tk, + "", EC_TK_ENDLIST, + "foo", EC_TK_ENDLIST, + "foo"); + ret |= EC_TEST_CHECK_TK_COMPLETE(tk, + "f", EC_TK_ENDLIST, + "oo", EC_TK_ENDLIST, + "oo"); + ret |= EC_TEST_CHECK_TK_COMPLETE(tk, + "foo", EC_TK_ENDLIST, + "", EC_TK_ENDLIST, + ""); + ret |= EC_TEST_CHECK_TK_COMPLETE(tk, + "x", EC_TK_ENDLIST, + EC_TK_ENDLIST, + ""); ec_tk_free(tk); return ret; diff --git a/lib/ecoli_tk_str.h b/lib/ecoli_tk_str.h index e7c0759..00632ec 100644 --- a/lib/ecoli_tk_str.h +++ b/lib/ecoli_tk_str.h @@ -30,12 +30,6 @@ #include -struct ec_tk_str { - struct ec_tk gen; - char *string; - unsigned len; -}; - struct ec_tk *ec_tk_str_new(const char *id, const char *str); #endif diff --git a/lib/main-readline.c b/lib/main-readline.c index 3bdc703..379c36e 100644 --- a/lib/main-readline.c +++ b/lib/main-readline.c @@ -25,6 +25,7 @@ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ +#define _GNU_SOURCE /* for asprintf */ #include #include #include @@ -36,83 +37,114 @@ #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 char *my_completion_entry(const char *s, int state) { static struct ec_completed_tk *c; + static struct ec_completed_tk_iter *iter; static const struct ec_completed_tk_elt *elt; + char *out_string; - (void)s; if (state == 0) { - char *start; + char *line; + + ec_completed_tk_free(c); - if (c != NULL) - ec_completed_tk_free(c); + line = strdup(rl_line_buffer); + if (line == NULL) + return NULL; + line[rl_point] = '\0'; - start = strdup(rl_line_buffer); - if (start == NULL) + c = ec_tk_complete(commands, line); + free(line); + if (c == NULL) return NULL; - start[rl_point] = '\0'; - c = ec_tk_complete(commands, start); - ec_completed_tk_iter_start(c); + ec_completed_tk_iter_free(iter); + iter = ec_completed_tk_iter_new(c, ITER_MATCH); + if (iter == NULL) + return NULL; } - elt = ec_completed_tk_iter_next(c); + elt = ec_completed_tk_iter_next(iter); if (elt == NULL) return NULL; - return strdup(elt->full); + if (asprintf(&out_string, "%s%s", s, elt->add) < 0) + return NULL; + + return out_string; } -char **my_attempted_completion(const char *text, int start, int end) +static 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 + + /* remove default file completion */ + rl_attempted_completion_over = 1; + return rl_completion_matches(text, my_completion_entry); } + +static int show_help(int ignore, int invoking_key) +{ + struct ec_completed_tk *c; + char *line; + + (void)ignore; + (void)invoking_key; + + printf("\nhelp:\n"); + line = strdup(rl_line_buffer); + if (line == NULL) + return 1; + line[rl_point] = '\0'; + + c = ec_tk_complete(commands, line); + free(line); + if (c == NULL) + return 1; + ec_completed_tk_dump(stdout, c); + ec_completed_tk_free(c); + + rl_forced_update_display(); + + return 0; +} + 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); + rl_bind_key('?', show_help); + + commands = ec_tk_shlex_new(NULL, + ec_tk_seq_new_list(NULL, + ec_tk_str_new(NULL, "hello"), + ec_tk_or_new_list("name", + ec_tk_str_new(NULL, "john"), + ec_tk_str_new(NULL, "johnny"), + ec_tk_str_new(NULL, "mike"), + ec_tk_int_new(NULL, 0, 10, 10), + 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) { @@ -120,7 +152,6 @@ int main(void) if (line == NULL) break; - // XXX need a "parse_all" p = ec_tk_parse(commands, line); ec_parsed_tk_dump(stdout, p); add_history(line); diff --git a/lib/main.c b/lib/main.c index a2d101b..d3d35b3 100644 --- a/lib/main.c +++ b/lib/main.c @@ -29,33 +29,70 @@ #include #include #include +#include +#include #include #include +#define COUNT_OF(x) ((sizeof(x)/sizeof(0[x])) / \ + ((size_t)(!(sizeof(x) % sizeof(0[x]))))) + +static int log_level = EC_LOG_INFO; +static int alloc_fail_proba = 0; + static const char ec_short_options[] = - "h" /* help */ + "h" /* help */ + "l:" /* log-level */ + "r:" /* random-alloc-fail */ ; -enum { - /* long options */ - EC_OPT_LONG_MIN_NUM = 256, #define EC_OPT_HELP "help" - EC_OPT_HELP_NUM, -}; +#define EC_OPT_LOG_LEVEL "log-level" +#define EC_OPT_RANDOM_ALLOC_FAIL "random-alloc-fail" static const struct option ec_long_options[] = { - {EC_OPT_HELP, 1, NULL, EC_OPT_HELP_NUM}, + {EC_OPT_HELP, 1, NULL, 'h'}, + {EC_OPT_LOG_LEVEL, 1, NULL, 'l'}, + {EC_OPT_RANDOM_ALLOC_FAIL, 1, NULL, 'r'}, {NULL, 0, NULL, 0} }; static void usage(const char *prgname) { printf("%s [options]\n" - " --"EC_OPT_HELP": show this help\n" + " -h\n" + " --"EC_OPT_HELP"\n" + " Show this help.\n" + " -l \n" + " --"EC_OPT_LOG_LEVEL"=\n" + " Set log level (0 = no log, 6 = verbose).\n" + " -r \n" + " --"EC_OPT_RANDOM_ALLOC_FAIL"=\n" + " Cause malloc to fail randomly. This helps to debug\n" + " leaks or crashes in error cases. The probability is\n" + " between 0 and 100.\n" , prgname); } +static int +parse_int(const char *s, int min, int max, int *ret, unsigned int base) +{ + char *end = NULL; + long long n; + + n = strtoll(s, &end, base); + if ((s[0] == '\0') || (end == NULL) || (*end != '\0')) + return -1; + if (n < min) + return -1; + if (n > max) + return -1; + + *ret = n; + return 0; +} + static void parse_args(int argc, char **argv) { int opt; @@ -65,10 +102,27 @@ static void parse_args(int argc, char **argv) switch (opt) { case 'h': /* help */ - case EC_OPT_HELP_NUM: usage(argv[0]); exit(0); + case 'l': /* log-level */ + if (parse_int(optarg, EC_LOG_EMERG, + EC_LOG_DEBUG, &log_level, 10) < 0) { + printf("Invalid log value\n"); + usage(argv[0]); + exit(1); + } + break; + + case 'r': /* random-alloc-fail */ + if (parse_int(optarg, 0, 100, &alloc_fail_proba, + 10) < 0) { + printf("Invalid probability value\n"); + usage(argv[0]); + exit(1); + } + break; + default: usage(argv[0]); exit(1); @@ -80,11 +134,14 @@ TAILQ_HEAD(debug_alloc_hdr_list, debug_alloc_hdr); static struct debug_alloc_hdr_list debug_alloc_hdr_list = TAILQ_HEAD_INITIALIZER(debug_alloc_hdr_list); +#define STACK_SZ 16 struct debug_alloc_hdr { TAILQ_ENTRY(debug_alloc_hdr) next; const char *file; unsigned int line; size_t size; + void *stack[STACK_SZ]; + int stacklen; unsigned int cookie; }; @@ -99,22 +156,28 @@ static void *debug_malloc(size_t size, const char *file, unsigned int line) size_t new_size = size + sizeof(*hdr) + sizeof(*ftr); void *ret; - hdr = malloc(new_size); + + if (alloc_fail_proba != 0 && (random() % 100) < alloc_fail_proba) + hdr = NULL; + else + hdr = malloc(new_size); + if (hdr == NULL) { ret = NULL; } else { hdr->file = file; hdr->line = line; hdr->size = size; + hdr->stacklen = backtrace(hdr->stack, COUNT_OF(hdr->stack)); hdr->cookie = 0x12345678; TAILQ_INSERT_TAIL(&debug_alloc_hdr_list, hdr, next); ret = hdr + 1; ftr = (struct debug_alloc_ftr *)( (char *)hdr + size + sizeof(*hdr)); - ftr->cookie = 0x12345678; + ftr->cookie = 0x87654321; } - ec_log(EC_LOG_INFO, "%s:%d: info: malloc(%zd) -> %p\n", + ec_log(EC_LOG_DEBUG, "%s:%d: info: malloc(%zd) -> %p\n", file, line, size, ret); return ret; @@ -128,7 +191,7 @@ static void debug_free(void *ptr, const char *file, unsigned int line) (void)file; (void)line; - ec_log(EC_LOG_INFO, "%s:%d: info: free(%p)\n", file, line, ptr); + ec_log(EC_LOG_DEBUG, "%s:%d: info: free(%p)\n", file, line, ptr); if (ptr == NULL) return; @@ -141,7 +204,7 @@ static void debug_free(void *ptr, const char *file, unsigned int line) } ftr = (ptr + hdr->size); - if (ftr->cookie != 0x12345678) { + if (ftr->cookie != 0x87654321) { ec_log(EC_LOG_ERR, "%s:%d: error: free(%p): bad end cookie\n", file, line, ptr); abort(); @@ -162,7 +225,8 @@ static void debug_free(void *ptr, const char *file, unsigned int line) free(hdr); } -void *debug_realloc(void *ptr, size_t size, const char *file, unsigned int line) +static void *debug_realloc(void *ptr, size_t size, const char *file, + unsigned int line) { struct debug_alloc_hdr *hdr, *h; struct debug_alloc_ftr *ftr; @@ -179,7 +243,7 @@ void *debug_realloc(void *ptr, size_t size, const char *file, unsigned int line) } ftr = (ptr + hdr->size); - if (ftr->cookie != 0x12345678) { + if (ftr->cookie != 0x87654321) { ec_log(EC_LOG_ERR, "%s:%d: error: realloc(%p): bad end cookie\n", file, line, ptr); @@ -217,37 +281,71 @@ void *debug_realloc(void *ptr, size_t size, const char *file, unsigned int line) hdr->file = file; hdr->line = line; hdr->size = size; + hdr->stacklen = backtrace(hdr->stack, COUNT_OF(hdr->stack)); hdr->cookie = 0x12345678; TAILQ_INSERT_TAIL(&debug_alloc_hdr_list, hdr, next); ftr = (struct debug_alloc_ftr *)( (char *)hdr + size + sizeof(*hdr)); - ftr->cookie = 0x12345678; + ftr->cookie = 0x87654321; } - ec_log(EC_LOG_INFO, "%s:%d: info: realloc(%p, %zd) -> %p\n", + ec_log(EC_LOG_DEBUG, "%s:%d: info: realloc(%p, %zd) -> %p\n", file, line, ptr, size, ret); return ret; } -void debug_alloc_dump(void) +static int debug_alloc_dump_leaks(void) { struct debug_alloc_hdr *hdr; + int i; + char **buffer; + + if (TAILQ_EMPTY(&debug_alloc_hdr_list)) + return 0; TAILQ_FOREACH(hdr, &debug_alloc_hdr_list, next) { - ec_log(EC_LOG_ERR, "%s:%d: error: memory leak size=%zd ptr=%p\n", + ec_log(EC_LOG_ERR, + "%s:%d: error: memory leak size=%zd ptr=%p\n", hdr->file, hdr->line, hdr->size, hdr + 1); + buffer = backtrace_symbols(hdr->stack, hdr->stacklen); + if (buffer == NULL) { + for (i = 0; i < hdr->stacklen; i++) + ec_log(EC_LOG_ERR, " %p\n", hdr->stack[i]); + } else { + for (i = 0; i < hdr->stacklen; i++) + ec_log(EC_LOG_ERR, " %s\n", + buffer ? buffer[i] : "unknown"); + } + free(buffer); } + + ec_log(EC_LOG_ERR, + " missing static syms, use: addr2line -f -e \n"); + + return -1; +} + +static int debug_log(unsigned int level, void *opaque, const char *str) +{ + (void)opaque; + + if (level > (unsigned int)log_level) + return 0; + + return printf("%s", str); } int main(int argc, char **argv) { - int ret; + int ret, leaks; parse_args(argc, argv); - TAILQ_INIT(&debug_alloc_hdr_list); + ec_log_register(debug_log, NULL); + /* register a new malloc to track memleaks */ + TAILQ_INIT(&debug_alloc_hdr_list); if (ec_malloc_register(debug_malloc, debug_free, debug_realloc) < 0) { ec_log(EC_LOG_ERR, "cannot register new malloc\n"); return -1; @@ -256,10 +354,17 @@ int main(int argc, char **argv) ret = ec_test_all(); ec_malloc_unregister(); - debug_alloc_dump(); + leaks = debug_alloc_dump_leaks(); - if (ret != 0) + if (alloc_fail_proba == 0 && ret != 0) { printf("tests failed\n"); + return 1; + } else if (alloc_fail_proba != 0 && leaks != 0) { + printf("tests failed (memory leak)\n"); + return 1; + } + + printf("\ntests ok\n"); return 0; } -- 2.39.5