From 650964a70b96aa6285de52be77ee097f4c9a8456 Mon Sep 17 00:00:00 2001 From: Olivier Matz Date: Thu, 1 Feb 2018 22:55:42 +0100 Subject: [PATCH] dynamic node --- lib/Makefile | 1 + lib/ecoli_completed.c | 10 +- lib/ecoli_completed.h | 1 + lib/ecoli_node_dynamic.c | 210 +++++++++++++++++++++++++++++++++++++++ lib/ecoli_node_dynamic.h | 42 ++++++++ lib/ecoli_parsed.c | 43 ++++++-- lib/ecoli_parsed.h | 19 ++-- 7 files changed, 304 insertions(+), 22 deletions(-) create mode 100644 lib/ecoli_node_dynamic.c create mode 100644 lib/ecoli_node_dynamic.h diff --git a/lib/Makefile b/lib/Makefile index 6c527f4..282519e 100644 --- a/lib/Makefile +++ b/lib/Makefile @@ -57,6 +57,7 @@ srcs += ecoli_node_cmd.c srcs += ecoli_node_empty.c srcs += ecoli_node_expr.c srcs += ecoli_node_expr_test.c +srcs += ecoli_node_dynamic.c srcs += ecoli_node_file.c srcs += ecoli_node_int.c srcs += ecoli_node_many.c diff --git a/lib/ecoli_completed.c b/lib/ecoli_completed.c index 25ae6cb..944c566 100644 --- a/lib/ecoli_completed.c +++ b/lib/ecoli_completed.c @@ -60,6 +60,10 @@ struct ec_completed *ec_completed(struct ec_parsed *state) if (completed == NULL) goto fail; + completed->attrs = ec_keyval(); + if (completed->attrs == NULL) + goto fail; + TAILQ_INIT(&completed->groups); completed->cur_state = state; @@ -67,6 +71,8 @@ struct ec_completed *ec_completed(struct ec_parsed *state) return completed; fail: + if (completed != NULL) + ec_keyval_free(completed->attrs); ec_free(completed); return NULL; @@ -100,13 +106,12 @@ ec_node_complete_child(struct ec_node *node, struct ec_completed *completed, /* save previous parse state, prepare child state */ cur_state = completed->cur_state; - child_state = ec_parsed(); + child_state = ec_parsed(node); if (child_state == NULL) return -ENOMEM; if (cur_state != NULL) ec_parsed_add_child(cur_state, child_state); - ec_parsed_set_node(child_state, node); completed->cur_state = child_state; cur_group = completed->cur_group; completed->cur_group = NULL; @@ -518,6 +523,7 @@ void ec_completed_free(struct ec_completed *completed) TAILQ_REMOVE(&completed->groups, grp, next); ec_completed_group_free(grp); } + ec_keyval_free(completed->attrs); ec_free(completed); } diff --git a/lib/ecoli_completed.h b/lib/ecoli_completed.h index f616e4b..191c443 100644 --- a/lib/ecoli_completed.h +++ b/lib/ecoli_completed.h @@ -70,6 +70,7 @@ struct ec_completed { struct ec_parsed *cur_state; struct ec_completed_group *cur_group; struct ec_completed_group_list groups; + struct ec_keyval *attrs; }; /* diff --git a/lib/ecoli_node_dynamic.c b/lib/ecoli_node_dynamic.c new file mode 100644 index 0000000..e4cdb71 --- /dev/null +++ b/lib/ecoli_node_dynamic.c @@ -0,0 +1,210 @@ +/* + * Copyright (c) 2017, Olivier MATZ + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the University of California, Berkeley nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE REGENTS AND CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "ecoli_node_dynamic.h" + +EC_LOG_TYPE_REGISTER(node_dynamic); + +struct ec_node_dynamic { + struct ec_node gen; + ec_node_dynamic_build_t build; + void *opaque; +}; + +static int +ec_node_dynamic_parse(const struct ec_node *gen_node, + struct ec_parsed *parsed, + const struct ec_strvec *strvec) +{ + struct ec_node_dynamic *node = (struct ec_node_dynamic *)gen_node; + struct ec_node *child = NULL; + void (*node_free)(struct ec_node *) = ec_node_free; + char key[64]; + int ret; + + child = node->build(parsed, node->opaque); + if (child == NULL) + goto fail; + + /* add the node pointer in the attributes, so it will be freed + * when parsed is freed */ + snprintf(key, sizeof(key), "_dyn_%p", child); + ret = ec_keyval_set(ec_parsed_get_attrs(parsed), key, child, + (void *)node_free); + if (ret < 0) + goto fail; + + return ec_node_parse_child(child, parsed, strvec); + +fail: + ec_node_free(child); + return ret; +} + +static int +ec_node_dynamic_complete(const struct ec_node *gen_node, + struct ec_completed *completed, + const struct ec_strvec *strvec) +{ + struct ec_node_dynamic *node = (struct ec_node_dynamic *)gen_node; + struct ec_parsed *parsed; + struct ec_node *child = NULL; + void (*node_free)(struct ec_node *) = ec_node_free; + char key[64]; + int ret; + + parsed = ec_completed_get_state(completed); + child = node->build(parsed, node->opaque); + if (child == NULL) + goto fail; + + /* add the node pointer in the attributes, so it will be freed + * when parsed is freed */ + snprintf(key, sizeof(key), "_dyn_%p", child); + ret = ec_keyval_set(completed->attrs, key, child, + (void *)node_free); + if (ret < 0) + goto fail; + + return ec_node_complete_child(child, completed, strvec); + +fail: + ec_node_free(child); + return ret; +} + +static struct ec_node_type ec_node_dynamic_type = { + .name = "dynamic", + .parse = ec_node_dynamic_parse, + .complete = ec_node_dynamic_complete, + .size = sizeof(struct ec_node_dynamic), +}; + +struct ec_node * +ec_node_dynamic(const char *id, ec_node_dynamic_build_t build, void *opaque) +{ + struct ec_node *gen_node = NULL; + struct ec_node_dynamic *node; + + if (build == NULL) { + errno = EINVAL; + goto fail; + } + + gen_node = __ec_node(&ec_node_dynamic_type, id); + if (gen_node == NULL) + goto fail; + + node = (struct ec_node_dynamic *)gen_node; + node->build = build; + node->opaque = opaque; + + return gen_node; + +fail: + ec_node_free(gen_node); + return NULL; + +} + +EC_NODE_TYPE_REGISTER(ec_node_dynamic_type); + +static struct ec_node * +build_counter(struct ec_parsed *parsed, void *opaque) +{ + const struct ec_node *node; + struct ec_parsed *iter; + unsigned int count = 0; + char buf[32]; + + (void)opaque; + for (iter = ec_parsed_get_root(parsed); iter != NULL; + iter = ec_parsed_iter_next(iter)) { + node = ec_parsed_get_node(iter); + if (node->id && !strcmp(node->id, "my-id")) + count++; + } + snprintf(buf, sizeof(buf), "count-%u", count); + + return ec_node_str("my-id", buf); +} + +/* LCOV_EXCL_START */ +static int ec_node_dynamic_testcase(void) +{ + struct ec_node *node; + int ret = 0; + + /* XXX use EC_NO_ID instead of NULL */ + node = ec_node_many(NULL, ec_node_dynamic(NULL, build_counter, NULL), + 1, 3); + if (node == NULL) { + EC_LOG(EC_LOG_ERR, "cannot create node\n"); + return -1; + } + ret |= EC_TEST_CHECK_PARSE(node, 1, "count-0"); + ret |= EC_TEST_CHECK_PARSE(node, 3, "count-0", "count-1", "count-2"); + ret |= EC_TEST_CHECK_PARSE(node, 1, "count-0", "count-0"); + + /* test completion */ + + ret |= EC_TEST_CHECK_COMPLETE(node, + "c", EC_NODE_ENDLIST, + "count-0", EC_NODE_ENDLIST); + ret |= EC_TEST_CHECK_COMPLETE(node, + "count-0", "", EC_NODE_ENDLIST, + "count-1", EC_NODE_ENDLIST, + "count-1"); + ec_node_free(node); + + return ret; +} +/* LCOV_EXCL_STOP */ + +static struct ec_test ec_node_dynamic_test = { + .name = "node_dynamic", + .test = ec_node_dynamic_testcase, +}; + +EC_TEST_REGISTER(ec_node_dynamic_test); diff --git a/lib/ecoli_node_dynamic.h b/lib/ecoli_node_dynamic.h new file mode 100644 index 0000000..be8eecf --- /dev/null +++ b/lib/ecoli_node_dynamic.h @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2017, 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_NODE_DYNAMIC_ +#define ECOLI_NODE_DYNAMIC_ + +struct ec_node; +struct ec_parsed; + +/* callback invoked by parse() or complete() to build the dynamic node + * the behavior of the node can depend on what is already parsed */ +typedef struct ec_node *(*ec_node_dynamic_build_t)( + struct ec_parsed *state, void *opaque); + +struct ec_node *ec_node_dynamic(const char *id, ec_node_dynamic_build_t build, + void *opaque); + +#endif diff --git a/lib/ecoli_parsed.c b/lib/ecoli_parsed.c index f7ca38f..103b3b0 100644 --- a/lib/ecoli_parsed.c +++ b/lib/ecoli_parsed.c @@ -79,7 +79,7 @@ static int __ec_node_parse_child(struct ec_node *node, struct ec_parsed *state, return -ENOTSUP; if (!is_root) { - child = ec_parsed(); + child = ec_parsed(node); if (child == NULL) return -ENOMEM; @@ -87,7 +87,6 @@ static int __ec_node_parse_child(struct ec_node *node, struct ec_parsed *state, } else { child = state; } - ec_parsed_set_node(child, node); ret = node->type->parse(node, child, strvec); if (ret < 0) // XXX should we free state, child? return ret; @@ -120,7 +119,7 @@ int ec_node_parse_child(struct ec_node *node, struct ec_parsed *state, struct ec_parsed *ec_node_parse_strvec(struct ec_node *node, const struct ec_strvec *strvec) { - struct ec_parsed *parsed = ec_parsed(); + struct ec_parsed *parsed = ec_parsed(node); int ret; if (parsed == NULL) @@ -161,7 +160,7 @@ struct ec_parsed *ec_node_parse(struct ec_node *node, const char *str) return NULL; } -struct ec_parsed *ec_parsed(void) +struct ec_parsed *ec_parsed(const struct ec_node *node) { struct ec_parsed *parsed = NULL; @@ -171,6 +170,7 @@ struct ec_parsed *ec_parsed(void) TAILQ_INIT(&parsed->children); + parsed->node = node; parsed->attrs = ec_keyval(); if (parsed->attrs == NULL) goto fail; @@ -196,7 +196,7 @@ __ec_parsed_dup(const struct ec_parsed *root, const struct ec_parsed *ref, if (root == NULL) return NULL; - dup = ec_parsed(); + dup = ec_parsed(root->node); if (dup == NULL) return NULL; @@ -208,7 +208,6 @@ __ec_parsed_dup(const struct ec_parsed *root, const struct ec_parsed *ref, goto fail; ec_keyval_free(dup->attrs); dup->attrs = attrs; - dup->node = root->node; if (root->strvec != NULL) { dup->strvec = ec_strvec_dup(root->strvec); @@ -365,11 +364,6 @@ const struct ec_node *ec_parsed_get_node(const struct ec_parsed *parsed) return parsed->node; } -void ec_parsed_set_node(struct ec_parsed *parsed, const struct ec_node *node) -{ - parsed->node = node; -} - void ec_parsed_del_last_child(struct ec_parsed *parsed) // rename in free { struct ec_parsed *child; @@ -398,6 +392,24 @@ struct ec_parsed *ec_parsed_get_parent(struct ec_parsed *parsed) return parsed->parent; } +struct ec_parsed *ec_parsed_iter_next(struct ec_parsed *parsed) +{ + struct ec_parsed *child, *parent, *next; + + child = TAILQ_FIRST(&parsed->children); + if (child != NULL) + return child; + parent = parsed->parent; + while (parent != NULL) { + next = TAILQ_NEXT(parsed, next); + if (next != NULL) + return next; + parsed = parent; + parent = parsed->parent; + } + return NULL; +} + struct ec_parsed *ec_parsed_find_first(struct ec_parsed *parsed, const char *id) { @@ -420,6 +432,15 @@ struct ec_parsed *ec_parsed_find_first(struct ec_parsed *parsed, return NULL; } +struct ec_keyval * +ec_parsed_get_attrs(struct ec_parsed *parsed) +{ + if (parsed == NULL) + return NULL; + + return parsed->attrs; +} + const struct ec_strvec *ec_parsed_strvec(const struct ec_parsed *parsed) { if (parsed == NULL || parsed->strvec == NULL) diff --git a/lib/ecoli_parsed.h b/lib/ecoli_parsed.h index 33da8fc..175e1cb 100644 --- a/lib/ecoli_parsed.h +++ b/lib/ecoli_parsed.h @@ -50,7 +50,7 @@ struct ec_parsed; * @return * The empty parse tree. */ -struct ec_parsed *ec_parsed(void); +struct ec_parsed *ec_parsed(const struct ec_node *node); /** * @@ -201,13 +201,6 @@ bool ec_parsed_has_child(const struct ec_parsed *parsed); */ const struct ec_node *ec_parsed_get_node(const struct ec_parsed *parsed); -/** - * - * - * - */ -void ec_parsed_set_node(struct ec_parsed *parsed, const struct ec_node *node); - /** * * @@ -220,7 +213,7 @@ void ec_parsed_del_last_child(struct ec_parsed *parsed); * * */ -int ec_parsed_get_path(struct ec_parsed *parsed, struct ec_node **path); +struct ec_keyval *ec_parsed_get_attrs(struct ec_parsed *parsed); /** * @@ -237,6 +230,14 @@ void ec_parsed_dump(FILE *out, const struct ec_parsed *parsed); struct ec_parsed *ec_parsed_find_first(struct ec_parsed *parsed, const char *id); +/** + * Iterate among parsed tree + * + * Use it with: + * for (iter = state; iter != NULL; iter = ec_parsed_iter_next(iter)) + */ +struct ec_parsed *ec_parsed_iter_next(struct ec_parsed *parsed); + /** * * -- 2.39.5