From e900ed6e1cb162855289bc7a9acc9b3d1af9d697 Mon Sep 17 00:00:00 2001 From: Olivier Matz Date: Thu, 4 Apr 2019 20:30:26 +0200 Subject: [PATCH] first working version of ec_node_cond (still in progress) --- include/ecoli_node_cond.h | 31 ++ src/ecoli_node_cond.c | 854 ++++++++++++++++++++++++++++++++++++++ src/meson.build | 2 +- 3 files changed, 886 insertions(+), 1 deletion(-) create mode 100644 include/ecoli_node_cond.h create mode 100644 src/ecoli_node_cond.c diff --git a/include/ecoli_node_cond.h b/include/ecoli_node_cond.h new file mode 100644 index 0000000..37da1a4 --- /dev/null +++ b/include/ecoli_node_cond.h @@ -0,0 +1,31 @@ +/* SPDX-License-Identifier: BSD-3-Clause + * Copyright 2019, Olivier MATZ + */ + +/** + * The condition node checks that an expression is true before + * parsing/completing the child node. If it is false, the node + * doesn't match anything. + */ + +#ifndef ECOLI_NODE_COND_ +#define ECOLI_NODE_COND_ + +struct ec_node; + +/** + * Create a condition node. + * + * @param id + * The node identifier. + * @param cond_str + * The condition string. This is an function-based expression. + * @param child + * The ecoli child node. + * @return + * The new ecoli cond node. + */ +struct ec_node *ec_node_cond(const char *id, const char *cond_str, + struct ec_node *child); + +#endif diff --git a/src/ecoli_node_cond.c b/src/ecoli_node_cond.c new file mode 100644 index 0000000..57d5097 --- /dev/null +++ b/src/ecoli_node_cond.c @@ -0,0 +1,854 @@ +/* SPDX-License-Identifier: BSD-3-Clause + * Copyright 2019, Olivier MATZ + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +EC_LOG_TYPE_REGISTER(node_cond); + +static struct ec_node *ec_node_cond_parser; /* the expression parser. */ +static struct ec_dict *ec_node_cond_functions; /* functions dictionary */ + +struct ec_node_cond { + struct ec_node gen; + char *cond_str; /* the condition string. */ + struct ec_parse *parsed_cond; /* the parsed condition. */ + struct ec_node *child; /* the child node. */ +}; + +enum cond_result_type { + NODESET, + BOOLEAN, + INT, + STR, +}; + +/* + compare(eq|ne|gt|lt|ge|le, x, y) + find(nodeset, id) + count(nodeset) + + find by attrs? get_attr ? +*/ + +struct cond_result { + enum cond_result_type type; + union { + struct ec_htable *htable; + char *str; + int64_t int64; + bool boolean; + }; +}; + +typedef struct cond_result *(cond_func_t)( + const struct ec_parse *state, + struct cond_result **in, size_t in_len); + +void cond_result_free(struct cond_result *res) +{ + if (res == NULL) + return; + + switch (res->type) { + case NODESET: + ec_htable_free(res->htable); + break; + case STR: + ec_free(res->str); + break; + case BOOLEAN: + case INT: + break; + } + + ec_free(res); +} + +void cond_result_table_free(struct cond_result **table, size_t len) +{ + size_t i; + + for (i = 0; i < len; i++) { + cond_result_free(table[i]); + table[i] = NULL; + } + ec_free(table); +} + +static struct ec_node * +ec_node_cond_build_parser(void) +{ + struct ec_node *lex = NULL; + struct ec_node *expr = NULL; + int ret; + + expr = ec_node("or", "id_arg"); + if (expr == NULL) + goto fail; + + if (ec_node_or_add(expr, + EC_NODE_SEQ("id_function", + ec_node_any("id_function_name", "a_identifier"), + ec_node_any(EC_NO_ID, "a_open"), + ec_node_option("id_arg_list", + EC_NODE_SEQ(EC_NO_ID, + ec_node_clone(expr), + ec_node_many(EC_NO_ID, + EC_NODE_SEQ(EC_NO_ID, + ec_node_str(EC_NO_ID, + ","), + ec_node_clone(expr)), + 0, 0))), + ec_node_any(EC_NO_ID, "a_close"))) < 0) + goto fail; + + if (ec_node_or_add(expr, + ec_node_any("id_value", "a_identifier")) < 0) + goto fail; + + /* prepend a lexer to the expression node */ + lex = ec_node_re_lex(EC_NO_ID, ec_node_clone(expr)); + if (lex == NULL) + goto fail; + + ec_node_free(expr); + expr = NULL; + + ret = ec_node_re_lex_add(lex, "[_a-zA-Z][._a-zA-Z0-9]*", 1, + "a_identifier"); + if (ret < 0) + goto fail; + ret = ec_node_re_lex_add(lex, "\\(", 1, "a_open"); + if (ret < 0) + goto fail; + ret = ec_node_re_lex_add(lex, "\\)", 1, "a_close"); + if (ret < 0) + goto fail; + ret = ec_node_re_lex_add(lex, ",", 1, NULL); + if (ret < 0) + goto fail; + ret = ec_node_re_lex_add(lex, "[ ]", 0, NULL); + if (ret < 0) + goto fail; + + return lex; + +fail: + ec_node_free(lex); + ec_node_free(expr); + + return NULL; +} + +static struct ec_parse * +ec_node_cond_build(const char *cond_str) +{ + struct ec_parse *p = NULL; + + /* parse the condition expression */ + p = ec_node_parse(ec_node_cond_parser, cond_str); + if (p == NULL) + goto fail; + + if (!ec_parse_matches(p)) { + errno = EINVAL; + goto fail; + } + if (!ec_parse_has_child(p)) { + errno = EINVAL; + goto fail; + } + + return p; + +fail: + ec_parse_free(p); + return NULL; +} + +static struct cond_result * +eval_root(const struct ec_parse *state, struct cond_result **in, size_t in_len) +{ + struct cond_result *out = NULL; + const struct ec_parse *root = NULL; + + (void)in; + + if (in_len != 0) { + EC_LOG(LOG_ERR, "root() does not take any argument\n"); + errno = EINVAL; + goto fail; + } + + out = ec_malloc(sizeof(*out)); + if (out == NULL) + goto fail; + + out->type = NODESET; + out->htable = ec_htable(); + if (out->htable == NULL) + goto fail; + + root = ec_parse_get_root(state); + if (ec_htable_set(out->htable, &root, sizeof(root), NULL, NULL) < 0) + goto fail; + + cond_result_table_free(in, in_len); + return out; + +fail: + cond_result_free(out); + cond_result_table_free(in, in_len); + return NULL; +} + +static struct cond_result * +eval_current(const struct ec_parse *state, struct cond_result **in, + size_t in_len) +{ + struct cond_result *out = NULL; + + (void)in; + + if (in_len != 0) { + EC_LOG(LOG_ERR, "current() does not take any argument\n"); + errno = EINVAL; + goto fail; + } + + out = ec_malloc(sizeof(*out)); + if (out == NULL) + goto fail; + + out->type = NODESET; + out->htable = ec_htable(); + if (out->htable == NULL) + goto fail; + + if (ec_htable_set(out->htable, &state, sizeof(state), NULL, NULL) < 0) + goto fail; + + cond_result_table_free(in, in_len); + return out; + +fail: + cond_result_free(out); + cond_result_table_free(in, in_len); + return NULL; +} + +static bool +boolean_value(const struct cond_result *res) +{ + switch (res->type) { + case NODESET: + return (ec_htable_len(res->htable) > 0); + case BOOLEAN: + return res->boolean; + case INT: + return (res->int64 != 0); + case STR: + return (res->str[0] != 0); + } + + return false; +} + +static struct cond_result * +eval_bool(const struct ec_parse *state, struct cond_result **in, size_t in_len) +{ + struct cond_result *out = NULL; + + (void)state; + + if (in_len != 1) { + EC_LOG(LOG_ERR, "bool() takes one argument.\n"); + errno = EINVAL; + goto fail; + } + + out = ec_malloc(sizeof(*out)); + if (out == NULL) + goto fail; + + out->type = BOOLEAN; + out->boolean = boolean_value(in[0]); + + cond_result_table_free(in, in_len); + return out; + +fail: + cond_result_free(out); + cond_result_table_free(in, in_len); + return NULL; +} + +static struct cond_result * +eval_or(const struct ec_parse *state, struct cond_result **in, size_t in_len) +{ + struct cond_result *out = NULL; + size_t i; + + (void)state; + + if (in_len < 2) { + EC_LOG(LOG_ERR, "or() takes at least two arguments\n"); + errno = EINVAL; + goto fail; + } + + /* return the first true element, or the last one */ + for (i = 0; i < in_len; i++) { + if (boolean_value(in[i])) + break; + } + if (i == in_len) + i--; + + out = in[i]; + in[i] = NULL; + + cond_result_table_free(in, in_len); + return out; + +fail: + cond_result_free(out); + cond_result_table_free(in, in_len); + return NULL; +} + +static struct cond_result * +eval_and(const struct ec_parse *state, struct cond_result **in, size_t in_len) +{ + struct cond_result *out = NULL; + size_t i; + + (void)state; + + if (in_len < 2) { + EC_LOG(LOG_ERR, "or() takes at least two arguments\n"); + errno = EINVAL; + goto fail; + } + + /* return the first false element, or the last one */ + for (i = 0; i < in_len; i++) { + if (!boolean_value(in[i])) + break; + } + if (i == in_len) + i--; + + out = in[i]; + in[i] = NULL; + + cond_result_table_free(in, in_len); + return out; + +fail: + cond_result_free(out); + cond_result_table_free(in, in_len); + return NULL; +} + +static struct cond_result * +eval_first_child(const struct ec_parse *state, struct cond_result **in, + size_t in_len) +{ + struct cond_result *out = NULL; + struct ec_htable_elt_ref *iter; + const struct ec_parse * const *pparse; + struct ec_parse *parse; + + (void)state; + + if (in_len != 1 || in[0]->type != NODESET) { + EC_LOG(LOG_ERR, "first_child() takes one argument of type nodeset.\n"); + errno = EINVAL; + goto fail; + } + + out = ec_malloc(sizeof(*out)); + if (out == NULL) + goto fail; + + out->type = NODESET; + out->htable = ec_htable(); + if (out->htable == NULL) + goto fail; + + for (iter = ec_htable_iter(in[0]->htable); + iter != NULL; iter = ec_htable_iter_next(iter)) { + pparse = ec_htable_iter_get_key(iter); + parse = ec_parse_get_first_child(*pparse); + if (parse == NULL) + continue; + if (ec_htable_set(out->htable, &parse, sizeof(parse), NULL, + NULL) < 0) + goto fail; + } + + cond_result_table_free(in, in_len); + return out; + +fail: + cond_result_free(out); + cond_result_table_free(in, in_len); + return NULL; +} + +static struct cond_result * +eval_find(const struct ec_parse *state, struct cond_result **in, + size_t in_len) +{ + struct cond_result *out = NULL; + struct ec_htable_elt_ref *iter; + struct ec_parse * const *pparse; + struct ec_parse *parse; + const char *id; + + (void)state; + + if (in_len != 2 || in[0]->type != NODESET || in[1]->type != STR) { + EC_LOG(LOG_ERR, "find() takes two arguments (nodeset, str).\n"); + errno = EINVAL; + goto fail; + } + + out = ec_malloc(sizeof(*out)); + if (out == NULL) + goto fail; + + out->type = NODESET; + out->htable = ec_htable(); + if (out->htable == NULL) + goto fail; + + id = in[1]->str; + for (iter = ec_htable_iter(in[0]->htable); + iter != NULL; iter = ec_htable_iter_next(iter)) { + pparse = ec_htable_iter_get_key(iter); + parse = ec_parse_find(*pparse, id); + while (parse != NULL) { + if (ec_htable_set(out->htable, &parse, + sizeof(parse), NULL, + NULL) < 0) + goto fail; + parse = ec_parse_find_next(*pparse, parse, id, 1); + } + } + + cond_result_table_free(in, in_len); + return out; + +fail: + cond_result_free(out); + cond_result_table_free(in, in_len); + return NULL; +} + +static struct cond_result * +eval_func(const char *name, const struct ec_parse *state, + struct cond_result **in, size_t in_len) +{ + cond_func_t *f; + + f = ec_dict_get(ec_node_cond_functions, name); + if (f == NULL) { + EC_LOG(LOG_ERR, "No such function <%s>\n", + name); + errno = ENOENT; + cond_result_table_free(in, in_len); + return NULL; + } + + return f(state, in, in_len); +} + + +static struct cond_result * +eval_condition(const struct ec_parse *cond, const struct ec_parse *state) +{ + const struct ec_parse *iter; + struct cond_result *res = NULL; + struct cond_result **args = NULL; + const struct ec_parse *func = NULL, *func_name = NULL, *arg_list = NULL; + const struct ec_parse *value = NULL; + const char *id; + size_t n_arg = 0; + + // XXX fix cast (x3) + func = ec_parse_find((void *)cond, "id_function"); + value = ec_parse_find((void *)cond, "id_value"); + if (func != NULL) { + EC_PARSE_FOREACH_CHILD(iter, func) { + id = ec_node_id(ec_parse_get_node(iter)); + if (!strcmp(id, "id_function_name")) + func_name = iter; + if (!strcmp(id, "id_arg_list")) + arg_list = iter; + } + + iter = ec_parse_find((void *)arg_list, "id_arg"); + while (iter != NULL) { + args = ec_realloc(args, (n_arg + 1) * sizeof(*args)); + args[n_arg] = eval_condition(iter, state); + if (args[n_arg] == NULL) + goto fail; + n_arg++; + iter = ec_parse_find_next((void *)arg_list, + (void *)iter, "id_arg", 0); + } + + res = eval_func(ec_strvec_val(ec_parse_strvec(func_name), 0), + state, args, n_arg); + printf("%s(%p[%zd]) -> %p\n", ec_strvec_val(ec_parse_strvec(func_name), 0), + args, n_arg, res); + args = NULL; + } else if (value != NULL) { + printf("%s\n", ec_strvec_val(ec_parse_strvec(value), 0)); + res = ec_malloc(sizeof(*res)); + if (res == NULL) + goto fail; + res->type = STR; + res->str = ec_strdup(ec_strvec_val(ec_parse_strvec(value), 0)); + if (res->str == NULL) + goto fail; + } else { + goto fail; + } + + return res; + +fail: + cond_result_free(res); + cond_result_table_free(args, n_arg); + return NULL; +} + +static int +validate_condition(const struct ec_parse *cond, const struct ec_parse *state) +{ + struct cond_result *res; + int ret; + + res = eval_condition(cond, state); + if (res == NULL) + return -1; + + ret = boolean_value(res); + cond_result_free(res); + + return ret; +} + +static int +ec_node_cond_parse(const struct ec_node *gen_node, struct ec_parse *state, + const struct ec_strvec *strvec) +{ + struct ec_node_cond *node = (struct ec_node_cond *)gen_node; + int ret; + + ret = validate_condition(node->parsed_cond, state); + if (ret < 0) + return ret; + + if (ret == 0) + return EC_PARSE_NOMATCH; + + return ec_node_parse_child(node->child, state, strvec); +} + +static int +ec_node_cond_complete(const struct ec_node *gen_node, + struct ec_comp *comp, + const struct ec_strvec *strvec) +{ + struct ec_node_cond *node = (struct ec_node_cond *)gen_node; + + // XXX eval condition + // XXX before or after completing ? configurable ? + + return ec_node_complete_child(node->child, comp, strvec); +} + +static void ec_node_cond_free_priv(struct ec_node *gen_node) +{ + struct ec_node_cond *node = (struct ec_node_cond *)gen_node; + + ec_free(node->cond_str); + node->cond_str = NULL; + ec_parse_free(node->parsed_cond); + node->parsed_cond = NULL; + ec_node_free(node->child); +} + +static const struct ec_config_schema ec_node_cond_schema[] = { + { + .key = "expr", + .desc = "XXX", + .type = EC_CONFIG_TYPE_STRING, + }, + { + .key = "child", + .desc = "The child node.", + .type = EC_CONFIG_TYPE_NODE, + }, + { + .type = EC_CONFIG_TYPE_NONE, + }, +}; + +static int ec_node_cond_set_config(struct ec_node *gen_node, + const struct ec_config *config) +{ + struct ec_node_cond *node = (struct ec_node_cond *)gen_node; + const struct ec_config *cond = NULL; + struct ec_parse *parsed_cond = NULL; + const struct ec_config *child; + char *cond_str = NULL; + + cond = ec_config_dict_get(config, "expr"); + if (cond == NULL) { + errno = EINVAL; + goto fail; + } + + cond_str = ec_strdup(cond->string); + if (cond_str == NULL) + goto fail; + + child = ec_config_dict_get(config, "child"); + if (child == NULL) + goto fail; + + /* parse expression to build the cmd child node */ + parsed_cond = ec_node_cond_build(cond_str); + if (parsed_cond == NULL) + goto fail; + + /* ok, store the config */ + ec_parse_free(node->parsed_cond); + node->parsed_cond = parsed_cond; + ec_free(node->cond_str); + node->cond_str = cond_str; + ec_node_free(node->child); + node->child = ec_node_clone(child->node); + + return 0; + +fail: + ec_parse_free(parsed_cond); + ec_free(cond_str); + return -1; +} + +static size_t +ec_node_cond_get_children_count(const struct ec_node *gen_node) +{ + struct ec_node_cond *node = (struct ec_node_cond *)gen_node; + + if (node->child == NULL) + return 0; + return 1; +} + +static int +ec_node_cond_get_child(const struct ec_node *gen_node, size_t i, + struct ec_node **child, unsigned int *refs) +{ + struct ec_node_cond *node = (struct ec_node_cond *)gen_node; + + if (i > 0) + return -1; + + *child = node->child; + *refs = 1; + return 0; +} + +static struct ec_node_type ec_node_cond_type = { + .name = "cond", + .schema = ec_node_cond_schema, + .set_config = ec_node_cond_set_config, + .parse = ec_node_cond_parse, + .complete = ec_node_cond_complete, + .size = sizeof(struct ec_node_cond), + .free_priv = ec_node_cond_free_priv, + .get_children_count = ec_node_cond_get_children_count, + .get_child = ec_node_cond_get_child, +}; + +EC_NODE_TYPE_REGISTER(ec_node_cond_type); + +struct ec_node *ec_node_cond(const char *id, const char *cmd, + struct ec_node *child) +{ + struct ec_config *config = NULL; + struct ec_node *gen_node = NULL; + int ret; + + if (child == NULL) + return NULL; + + gen_node = ec_node_from_type(&ec_node_cond_type, id); + if (gen_node == NULL) + goto fail; + + config = ec_config_dict(); + if (config == NULL) + goto fail; + + if (ec_config_dict_set(config, "expr", ec_config_string(cmd)) < 0) + goto fail; + + if (ec_config_dict_set(config, "child", ec_config_node(child)) < 0) { + child = NULL; /* freed */ + goto fail; + } + child = NULL; + + ret = ec_node_set_config(gen_node, config); + config = NULL; /* freed */ + if (ret < 0) + goto fail; + + return gen_node; + +fail: + ec_node_free(gen_node); + ec_node_free(child); + ec_config_free(config); + + return NULL; +} + +static void ec_node_cond_exit_func(void) +{ + ec_node_free(ec_node_cond_parser); + ec_node_cond_parser = NULL; + ec_dict_free(ec_node_cond_functions); + ec_node_cond_functions = NULL; +} + +static int add_func(const char *name, cond_func_t *f) +{ + return ec_dict_set(ec_node_cond_functions, name, f, NULL); +} + +static int ec_node_cond_init_func(void) +{ + ec_node_cond_parser = ec_node_cond_build_parser(); + if (ec_node_cond_parser == NULL) + goto fail; + + ec_node_cond_functions = ec_dict(); + if (ec_node_cond_functions == NULL) + goto fail; + + if (add_func("root", eval_root) < 0) + goto fail; + if (add_func("current", eval_current) < 0) + goto fail; + if (add_func("bool", eval_bool) < 0) + goto fail; + if (add_func("or", eval_or) < 0) + goto fail; + if (add_func("and", eval_and) < 0) + goto fail; + if (add_func("first_child", eval_first_child) < 0) + goto fail; + if (add_func("find", eval_find) < 0) + goto fail; + + return 0; + +fail: + EC_LOG(EC_LOG_ERR, "Failed to initialize condition parser\n"); + ec_node_cond_exit_func(); + return -1; +} + +static struct ec_init ec_node_cond_init = { + .init = ec_node_cond_init_func, + .exit = ec_node_cond_exit_func, + .priority = 75, +}; + +EC_INIT_REGISTER(ec_node_cond_init); + +/* LCOV_EXCL_START */ +static int ec_node_cond_testcase(void) +{ + struct ec_node *node; + int testres = 0; + + node = EC_NODE_SEQ(EC_NO_ID, + EC_NODE_SUBSET(EC_NO_ID, + ec_node_str("id_node1", "node1"), + ec_node_str("id_node2", "node2"), + ec_node_str("id_node3", "node3"), + ec_node_str("id_node4", "node4")), + ec_node_cond(EC_NO_ID, + "or(find(root(), id_node1), " + " and(find(root(), id_node2)," + " find(root(), id_node3)))", + ec_node_str(EC_NO_ID, "ok"))); + if (node == NULL) { + EC_LOG(EC_LOG_ERR, "cannot create node\n"); + return -1; + } + testres |= EC_TEST_CHECK_PARSE(node, 2, "node1", "ok"); + testres |= EC_TEST_CHECK_PARSE(node, 3, "node2", "node3", "ok"); + testres |= EC_TEST_CHECK_PARSE(node, 4, "node1", "node2", "node3", "ok"); + testres |= EC_TEST_CHECK_PARSE(node, 3, "node2", "node1", "ok"); + testres |= EC_TEST_CHECK_PARSE(node, -1, "node2", "node4", "ok"); + testres |= EC_TEST_CHECK_PARSE(node, -1, "node2", "ok"); + testres |= EC_TEST_CHECK_PARSE(node, -1, "node3", "ok"); + testres |= EC_TEST_CHECK_PARSE(node, -1, "node4", "ok"); + ec_node_free(node); + + // XXX test completion + + return testres; +} +/* LCOV_EXCL_STOP */ + +static struct ec_test ec_node_cond_test = { + .name = "node_cond", + .test = ec_node_cond_testcase, +}; + +EC_TEST_REGISTER(ec_node_cond_test); diff --git a/src/meson.build b/src/meson.build index 85bd884..7b1fef9 100644 --- a/src/meson.build +++ b/src/meson.build @@ -15,7 +15,7 @@ libecoli_sources = [ 'ecoli_node.c', 'ecoli_node_bypass.c', 'ecoli_node_cmd.c', -# 'ecoli_node_cond.c', + 'ecoli_node_cond.c', 'ecoli_node_dynamic.c', 'ecoli_node_empty.c', 'ecoli_node_expr.c', -- 2.39.5